James Hoi's Blog

各种CTF逆向题目大杂烩

Word count: 2.7kReading time: 14 min
2021/01/31 Share

某乱七八糟选拔赛 main2101075ff6b9adc84f9.zip

拿到文件发现有upx壳,脱壳。
静态分析发现用随机数与flag.png每一字节异或得到flag.png.enc,而随机数种子用的是生成文件的时间戳
查看flag.png.enc生成时间2020-12-18 21:14:01(北京时间)得到时间戳1608297241,然后异或得到文件flag.png

解题脚本

用linux g++编译后运行

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
#include <stdlib.h>
#include <iostream>
#include <fstream>

using namespace std;

void decrypt(char *a1, int a2){
long long timestamp = 1608297241; //GMT+8 2020-12-18 21:14:01
srand(timestamp);
for (int i = 0; ; ++i)
{
if ( (signed int)i >= a2)
break;
unsigned char v4 = a1[i];
a1[i] = (unsigned long long)rand()^v4;
}
}

int main(){
ifstream infile;
infile.open("flag.png.enc", ios::binary);
infile.seekg(0, ios::end);
int a2 = infile.tellg();// 0x5D9;
infile.seekg(0, ios::beg);

char *a1 = new char[a2];
infile.read(a1, a2);
decrypt(a1,a2);

ofstream outfile;
outfile.open("flag.png");
outfile.write(a1, a2);
}

Flag

flag.png文件

扫一扫得到flag
flag{sRand_ANd_LCg_is_No_Safty_}

NCTF-2020 re4

题目文件 qiandao.exe
脱壳后文件 Dumped.exe
Exeinfo PE打开exe发现有壳,并且打开时提示decode_shell.pdb,得知exe有壳。
题目提示“运行时关闭ida od”,所以先将ida和od关闭后打开exe,再用PE Tools选择exe进程Dump,从而获得脱壳后的exe。
用IDA打开,先复现一次加密函数(代码中的encode),发现(tmp[v1] | v1) & ~(tmp[v1] & v1)为异或运算,反运算后得到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
byte_B731E8 = [
0xA3, 0x4D, 0x44, 0x7F, 0x53, 0xD6, 0xE9, 0x88, 0x4D, 0x95,
0x1A, 0x72, 0x01, 0x3C, 0x71, 0x00, 0xE8, 0xCE, 0xA1, 0xF8,
0x51, 0x48, 0xF5, 0xE9, 0x6A, 0x02, 0x27, 0xD8, 0x96, 0x7F,
0x72, 0xD6, 0xF1, 0xE9, 0x9F, 0xC6, 0x5D, 0x60, 0xE4, 0x10,
0x64, 0x99, 0xA0, 0x00
]

Dst = [
0x70, 0xDA, 0x19, 0xB3, 0x76, 0x8C, 0x66, 0x69, 0x28, 0xC7,
0xD0, 0x1B, 0x11, 0x77, 0x94, 0xA2, 0x92, 0x1C, 0x51, 0xA3,
0x4A, 0xC4, 0xAB, 0xF8, 0xAA, 0x2F, 0xE9, 0x3B, 0x95, 0x84,
0xEE, 0xCD, 0x55, 0xCB, 0xC1, 0x99, 0xE4, 0xA4, 0x7A, 0x4E,
0xE1, 0x9A, 0x89, 0xC3, 0xF9, 0x41, 0x34, 0x13, 0x57, 0xE5,
0xBB, 0x05, 0x64, 0xC5, 0x5C, 0xB7, 0xBF, 0x79, 0x31, 0x26,
0x47, 0x86, 0x1F, 0xD3, 0xA1, 0xF2, 0xE2, 0x3D, 0x9D, 0xDF,
0x43, 0xE7, 0xF4, 0xA6, 0x67, 0xFD, 0xC9, 0x78, 0x00, 0x98,
0x58, 0x8E, 0xA8, 0xD2, 0xFC, 0x71, 0x6A, 0xC2, 0xF0, 0xB2,
0x0A, 0x6E, 0xBC, 0x6D, 0x48, 0xB8, 0xD8, 0x0E, 0x49, 0x1D,
0x6F, 0xAD, 0x74, 0x91, 0xC6, 0x1E, 0x59, 0x82, 0x45, 0x2B,
0x46, 0xAF, 0x0B, 0xD5, 0x5D, 0x17, 0x8D, 0x6B, 0xB4, 0xDB,
0xB9, 0x09, 0x01, 0xBD, 0xEF, 0x7E, 0x6C, 0x23, 0x65, 0x97,
0x96, 0x72, 0x02, 0x33, 0x3C, 0xB6, 0x53, 0x27, 0x32, 0x24,
0x3E, 0xF3, 0x8B, 0xEA, 0x1A, 0xA9, 0x2C, 0xED, 0xD1, 0x56,
0x18, 0x38, 0x75, 0x52, 0xF7, 0x88, 0x0F, 0xF6, 0x20, 0xE0,
0x50, 0x5F, 0xD9, 0x9C, 0x5A, 0x5E, 0xD6, 0x2D, 0x06, 0x63,
0xFE, 0xBA, 0x35, 0xCC, 0xD7, 0x9E, 0xFB, 0x2A, 0x0C, 0xB1,
0x25, 0x44, 0x87, 0xF5, 0xFA, 0x8A, 0x40, 0xCF, 0x7D, 0xB5,
0x04, 0xC8, 0x60, 0xDC, 0x4D, 0xE3, 0xB0, 0xD4, 0x3F, 0x9B,
0xE8, 0x62, 0xC0, 0xA7, 0xA0, 0x21, 0x4B, 0x4C, 0x7F, 0x2E,
0xEC, 0xCE, 0xDD, 0x03, 0xFF, 0xDE, 0xCA, 0x22, 0x5B, 0x29,
0xF1, 0x39, 0x80, 0x9F, 0x73, 0x42, 0xA5, 0x90, 0x10, 0x0D,
0x81, 0x15, 0xEB, 0x8F, 0x4F, 0x61, 0x54, 0x7C, 0x93, 0x36,
0xAC, 0x68, 0x3A, 0x85, 0x16, 0xE6, 0x7B, 0xBE, 0x30, 0x08,
0x83, 0x37, 0x14, 0x12, 0xAE, 0x07, 0xB9, 0x85, 0xEC, 0xC2,
0xC4, 0xFC, 0xA1, 0x02, 0x59, 0x63, 0x75, 0x76, 0x00, 0x00,
0x00, 0x00, 0x40, 0x63, 0x75, 0x76, 0x20, 0xFD, 0xA1, 0x02,
0x44, 0x89, 0x4F, 0x77, 0x00, 0x00, 0x00, 0x00, 0x2D, 0xE3,
0x51, 0xC6
]


def encode(text):
tmp = [data+1 for data in text];v5 = 0;v6 = 0;v7 = 0;
while True:
v5 = (v5 + 1) % 256;
v8 = Dst[v5];
v6 = (v8 + v6) % 256;
Dst[v5] = Dst[v6];
Dst[v6] = v8;
tmp[v7] ^= Dst[(v8 + Dst[v5])&0xFF];
v7 += 1;
if v7 >= 0x2B: break
v1 = 0;
while True:
v2 = (tmp[v1] | v1) & ~(tmp[v1] & v1);
tmp[v1] = v2;
v1 += 1
if v1 >= 43: break
return tmp


def decode(text):
encode_dict = []; v5 = 0;v6 = 0;v7 = 0;
while True:
v5 = (v5 + 1) % 256;
v8 = Dst[v5];
v6 = (v8 + v6) % 256;
Dst[v5] = Dst[v6];
Dst[v6] = v8;
encode_dict.append(Dst[(v8 + Dst[v5])&0xFF]);
v7 += 1;
if v7 >= 0x2B: break

tmp = [text[i]^i for i in range(len(text))]
v7 = 0x2B-1;
while True:
tmp[v7] ^= encode_dict[v7];
v7 -= 1;
if v7 == 0: break
return tmp

print("".join([chr(data) for data in decode(byte_B731E8)]))

得到flag:flag{Process_Hollowing_with_multithreading}

加壳源码

一份专供初学者食用的AES加密壳

NCTF-2019 WxyVM1WxyVM1.rar

虚拟机题目,不过当时做题的时候不觉得是虚拟机…很久之前做的题目了,分析过程早忘了,附上个脚本给有需要的人学习一下
WxyVM.py

NEFUCTF-2020 Test_your_mathTest_your_math.rar

静态分析发现flag是一个方程组的解,用sympy解出

解题脚本

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

for i in range(11):
if i+1 == 4: continue
exec('v{} = Symbol(\'v{}\')'.format(str(i+1),str(i+1)))

answer = solve([
98 * v3 + 73 * v10 + 86 * (v8 + v5) + 79 * (v2 - v1) - 22 * (v11 + 2 * v9) - 23 * v6 - 81 * v7 -20413,
37 * v9 + 86 * v1 + 31 * v7 + 9 * v10 + 7 * (4 * v3 - v11) + 8 * v6 - 89 * v8 - 11 * v5 + 10 * v2 -2513,
48 * v2 + -70 * v10 - 46 * (v5 + v9) - 88 * v7 - 28 * v3 - 95 * v8 - 81 * v1 - (v11 * 2**6) - 52 * v6 +43184,
56 * v6 + 15 * v11 + 79 * v7 - 91 * v10 + 62 * v3 - 73 * v8 - 34 * v5 + 18 * v2 - 12 * v1 - 32 * v9 +5076,
21 * v11 + 80 * v2 + 51 * v3 + 97 * v10 + -83 * v6 - 14 * (v5 + 7 * v7) - 68 * v8 - 82 * v1 - 32 * v9 +9282,
33 * v2 + 51 * v8 + 14 * (v3 - 7 * v6) - 36 * v10 - 92 * v7 - 60 * v5 - 55 * v1 - 90 * v11 - 94 * v9 +30200,
99 * v3 + 67 * v10 + 97 * v6 - 59 * v7 + 24 * v5 - 22 * v2 - 53 * v1 - 37 * v11 - 15 * v9 - v8 -8225,
77 * v1 + 5 * v8 + 96 * v3 + -72 * v7 - (v10 * 2**6) - 47 * v5 - 97 * v2 + 56 * v6 + 49 * v11 + 78 * v9 +4117,
52 * v6 + 35 * v2 + 43 * v3 + 20 * (v11 - 2 * v8) - 75 * v10 - 8 * v7 - 39 * v5 - 38 * v1 + 53 * v9 +2782,
42 * v9 + 44 * v5 + 39 * v10 + 94 * v6 + 12 * (v3 + 2 * v8) + 10 * (v1 - 3 * v11) - 49 * v7 - 32 * v2 -12417,
],
[v1,v2,v3,v5,v6,v7,v8,v9,v10,v11])
# {v1: 55, v2: 110, v3: 57, v5: 77, v6: 85, v7: 95, v8: 121, v9: 70, v10: 104, v11: 64}

flag = []; index = [9,6,3,2,8,7,5,11,1,10]
for i in range(10):
flag.append(eval("chr(answer[v{}])".format(str(index[i]))))
print("flag{"+"".join(flag)+"}")

得到flag:flag{FU9ny_M@7h}

VNCTF-2021 notsudoku notsudoku.zip

这题看了下官方wp前面步骤都不难(wp在这里可以找到),我接手的时候拿到了个用日文混淆的pynotsudoku_obfu.py
我用编辑器替换一下后,如下

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
import time, sys, hashlib

class Class:

def __init__(self):
self.a_dict = {}
self.arr1 = []
self.text = ''
self.arr2 = []
self.num = 65

def wrapper(self, key):

def fun(f):
self.a_dict[key] = f
return f

return fun

def get_data(self, key):
return self.a_dict.get(key)

def checkflag(self):
i = 0
while True:
data1 = self.arr1[i][0]
data2 = self.arr1[i][1]
data3 = self.arr1[i][2]
fun = self.get_data(data1)
fun(data2, data3)
i += 1


obj = Class()

@obj.wrapper('a')
def f(a, b):
if a == 1:
obj.arr2 += b


@obj.wrapper('b')
def f(a, b):
if a == 1:
print(obj.text)
else:
if a == 2:
print(obj.arr2)
else:
if a == 3:
print((obj.flag), end='')
else:
print(a, end='')


@obj.wrapper('c')
def f(a, b):
sys.exit()


@obj.wrapper('d')
def f(a, b):
obj.text = input()


@obj.wrapper('e')
def f(a, b):
time.sleep(a)


@obj.wrapper('f')
def f(a, b):
if len(obj.text) % 2 != 0:
sys.exit()
for i in obj.text:
if ord(i) > 52 or ord(i) < 48:
sys.exit()

x = str(hashlib.new('md5', bytes((obj.text), encoding='utf8')).hexdigest())
if x[:6] != 'e3a912':
pass#sys.exit()
obj.flag = x


@obj.wrapper('g')
def f(a, b):
j = 0
for i in range(0, len(obj.text), 2):
j += 1
a = int(obj.text[i])
b = int(obj.text[(i + 1)])
obj.arr2[a][b] = j


@obj.wrapper('h')
def f(a, b):
if obj.arr2[0][1] != 24 or obj.arr2[4][3] != 2:
sys.exit()
if obj.arr2[0][2] != 1 or obj.arr2[2][3] != 20:
sys.exit()
if obj.arr2[1][0] != 23 or obj.arr2[3][4] != 3:
sys.exit()


@obj.wrapper('k')
def f(a, b):
num = 0
if b == -1:
for i in range(5):
num += obj.arr2[a][i]

if num != obj.num:
sys.exit()
else:
for i in range(5):
num += obj.arr2[i][b]

if num != obj.num:
sys.exit()


obj.arr1 = [
[
'b', 'welcome baby~ ', 0],
[
'b', 'input your flag~:', 0],
[
'd', 0, 0],
[
'b', 'your input is:', 0],
[
'b', 1, 0],
[
'b', "let's check......", 0],
[
'e', 0.5, 0],
[
'a', 1, [[0 for i in range(5)]]],
[
'a', 1, [[0 for i in range(5)]]],
[
'a', 1, [[0 for i in range(5)]]],
[
'a', 1, [[0 for i in range(5)]]],
[
'a', 1, [[0 for i in range(5)]]],
[
'f', 0, 0],
[
'g', 0, 0],
[
'h', 0, 0],
[
'k', 0, -1],
[
'k', 1, -1],
[
'k', 2, -1],
[
'k', 3, -1],
[
'k', 4, -1],
[
'k', 0, 0],
[
'k', 0, 1],
[
'k', 0, 2],
[
'k', 0, 3],
[
'k', 0, 4],
[
'b', 'Goodjob!', 0],
[
'b', 'The flag is vnctf{', 0],
[
'b', 3, 0],
[
'b', '}', 0],
[
'c', 0, 0]]
obj.checkflag()

仔细看看就知道是用wrapper写了个虚拟机,将文字作为指令然后调用函数。程序流程是弄了个5x5的数组然后每行或者每列加起来为65,且数组里面1到25每个数字不能重复,最后弄了个md5验证。

解题脚本

直接用z3约束求解然后再每一个md5一下就找到flag,z3的脚本我参考了解数独的脚本
Python Z3约束求解器解决数独问题

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

# 约束变元数组,变元名为a_行_列
arr=[[Int('a_%d_%d'%(i,j)) for i in range(5)] for j in range(5)]

# 初始条件,数字表示提示数,0表示未填入
problem=((00,24, 1,00,00),
(23,00,00,00,00),
(00,00,00,20,00),
(00,00,00,00, 3),
(00,00,00, 2,00))

# 数独规则约束
Sudoku = []
Sudoku=[And(arr[i][j]>0,arr[i][j]<=25) for i in range(5) for j in range(5)] # 数字为1~25的整数
for i in range(5):
Sudoku+=[arr[i][0]+arr[i][1]+arr[i][2]+arr[i][3]+arr[i][4]==65]
Sudoku+=[arr[0][i]+arr[1][i]+arr[2][i]+arr[3][i]+arr[4][i]==65]
for i in range(25):
for j in range(5):
for k in range(5):
if int(i/5) == j and i%5 == k: continue
Sudoku+=[arr[int(i/5)][i%5]!=arr[j][k]] # 所有元素不能相等

def SolveSudoku(board):
"""使用Z3约束求解器求解数独,若有解,返回表示可行解的5*5列表;若无解返回None"""
global Sudoku,arr
for i in range(5):
for j in range(5):
if board[i][j]!=0:
Sudoku+=[arr[i][j]==board[i][j]] # 将初始数字作为约束条件添加
s=Solver()
n = 0
s.add(Sudoku)

while(s.check()==sat): # 检查是否有解
#fp=open("log.txt","a",encoding="utf-8")
m=s.model() # 获取解的模型
res=[[m[arr[i][j]] for j in range(5)] for i in range(5)]
#for k in res:
# print(k)
text = ["" for i in range(25)]
for i in range(5):
for j in range(5):
text[m[arr[i][j]].as_long()-1] = str(i)+str(j)
t = "".join(text)
n += 1
output = str(n).zfill(4)+": "+t
print(output)
#fp.write(output+"\n")
#fp.close()
x = str(hashlib.new('md5', bytes(t, encoding='utf8')).hexdigest())
if x[:6] == 'e3a912':
print("Found!",n)
print("The flag is vnctf{%s}"%x)
sys.exit()
s.add(Or(arr[0][0]!=res[0][0],arr[0][1]!=res[0][1],arr[0][2]!=res[0][2],arr[0][3]!=res[0][3],arr[0][4]!=res[0][4],
arr[1][0]!=res[1][0],arr[1][1]!=res[1][1],arr[1][2]!=res[1][2],arr[1][3]!=res[1][3],arr[1][4]!=res[1][4],
arr[2][0]!=res[2][0],arr[2][1]!=res[2][1],arr[2][2]!=res[2][2],arr[2][3]!=res[2][3],arr[2][4]!=res[2][4],
arr[3][0]!=res[3][0],arr[3][1]!=res[3][1],arr[3][2]!=res[3][2],arr[3][3]!=res[3][3],arr[3][4]!=res[3][4],
arr[4][0]!=res[4][0],arr[4][1]!=res[4][1],arr[4][2]!=res[4][2],arr[4][3]!=res[4][3],arr[4][4]!=res[4][4]))
else:
return None # 无解返回None

SolveSudoku(problem)
'''
......
4640: 02433420112112034430403122130414004132233324100142
Found! 4640
The flag is vnctf{e3a912c1e911ad82544af0c3d753f44f}
'''

第一次做这种限制md5的题目,最后爆了一段时间(大概半小时)才爆出来

CATALOG
  1. 1. 某乱七八糟选拔赛 main2101075ff6b9adc84f9.zip
    1. 1.1. 解题脚本
    2. 1.2. Flag
  2. 2. NCTF-2020 re4
    1. 2.1. 解题脚本
    2. 2.2. 加壳源码
  3. 3. NCTF-2019 WxyVM1WxyVM1.rar
  4. 4. NEFUCTF-2020 Test_your_mathTest_your_math.rar
    1. 4.1. 解题脚本
  5. 5. VNCTF-2021 notsudoku notsudoku.zip
    1. 5.1. 解题脚本