狗儿

热爱的话就坚持吧~

0%

华为xctf第三场

hi, siri~

reverse

puzzle

mips32,ida7.5可反汇编 。

输入首先经过变种base64解码,解码后必须是0~9,然后走check函数。

image-20201228082701679

check函数:

image-20201228082739340

table的数据为4, 0, 3, 7, 2, 6, 8, 1, 5。

base64解码后的字符必须是2468,来控制方向。

就是个滑块,棋盘共9块区域,3*3,然后只有8块拼图,一块空白,拼图可以移动到空白处。

sub_400ffc验证table是否是123456780:

image-20201228083023801

所以就是移动拼图,最终状态为123456780。

QQ图片20201228084841

算法上这叫八数码。网上找个轮子直接跑状态。

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
#include<iostream>
#include<stdio.h>
#include<cmath>
using namespace std;

int open_cnt = 0;
int open_node_cnt;//open表节点个数
int close_cnt = 0;
int noresoult = 0;
struct Node {
int a[3][3];
int x, y;
int f, g, h;
int flag; //上一次移动方向
Node *father;
}start, End;
struct Open_Close {
int f;
Node *np;
}open[10000], close[10000];
bool isable() {//判断是否有解,逆序数之和奇偶性相同,有解
int s[9], e[9];
int tf = 0, ef = 0;
int k = 0;
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
s[k] = start.a[i][j];
e[k] = End.a[i][j];
k++;
}
}
for (int i = 0; i<9; i++) {
for (int j = 0; j<i; j++) {
if (s[i]>s[j] && s[j] != 0) tf += 1;
if (e[i]>e[j] && e[j] != 0) ef += 1;
}
}
if ((tf % 2 == 1 && ef % 2 == 1) || (tf % 2 == 0 && ef % 2 == 0)) return true;
else return false;
}
int a_start_h(Node *node) { //求 h()
int old_x, old_y, End_x, End_y;
int h = 0;
for (int k = 1; k<9; k++) {
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
if (node->a[i][j] == k) { //相应开始点的下表
old_x = i;
old_y = j;
}
if (End.a[i][j] == k) { //相应目标的结点下标
End_x = i;
End_y = j;
}
}
}
h += abs(old_x - End_x) + abs(old_y - End_y); //计算h
}
return h;
}
void input() { //输入
cout << "=====输入起始图====="<<endl;
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
cin >> start.a[i][j];
if (start.a[i][j] == 0) {
start.x = i;
start.y = j;
}
}
}
cout << endl;
cout << "=====输入目标图====="<<endl;
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
cin >> End.a[i][j];
if (End.a[i][j] == 0) {
End.x = i;
End.y = j;
}
}
}
cout << endl;
start.g = 0;
start.h = a_start_h(&start);
start.f = start.g + start.h;
}
int show(Node *node) { //显示
Node *p = node;
if (p == &start) return 1;
else show(p->father);
cout << "==============\n";
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
cout << p->a[i][j] << " ";
}
printf("\n");
}
cout << "==============\n\n";
}


bool isend(Node *node) { //判断是否为目标节点
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
if (node->a[i][j] != End.a[i][j])
return false;
}
}
return true;
}


void sort(Open_Close *open) { //open表排序
int min = 99999, min_flag = 0;
Open_Close temp;
for (int i = 0; i <= open_cnt; i++) {
if (min>open[i].f&&open[i].f>0) {
min = open[i].f;
min_flag = i;
}
}
temp = open[min_flag];
open[min_flag] = open[0];
open[0] = temp;
}



void move(int flag, Node *node) { //向四个方向扩展
int temp;
if (flag == 1 && node->x>0) { //turn left
Node *n = new Node();
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
n->a[i][j] = node->a[i][j];
}
}
n->a[node->x][node->y] = node->a[node->x - 1][node->y];
n->a[node->x - 1][node->y] = 0;
n->x = node->x - 1;
n->y = node->y;
n->flag = 3;
n->father = node;
n->g = node->g + 1; // 求 g()
n->h = a_start_h(n);
n->f = n->g + n->h; // 求 f()
open_cnt++;
open_node_cnt++;
open[open_cnt].np = n; //添加到open表
open[open_cnt].f = n->f;
}

else if (flag == 2 && node->y<2) { //go up
Node *n = new Node();
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
n->a[i][j] = node->a[i][j];
}
}
n->a[node->x][node->y] = node->a[node->x][node->y + 1];
n->a[node->x][node->y + 1] = 0;
n->x = node->x;
n->y = node->y + 1;
n->flag = 4;
n->father = node;
n->g = node->g + 1; // 求 g()
n->h = a_start_h(n);
n->f = n->g + n->h; // 求 f()
open_cnt++;
open_node_cnt++;
open[open_cnt].np = n; //添加到open表
open[open_cnt].f = n->f;
}
else if (flag == 3 && node->x<2) { //turn right
Node *n = new Node();
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
n->a[i][j] = node->a[i][j];
}
}
n->a[node->x][node->y] = node->a[node->x + 1][node->y];
n->a[node->x + 1][node->y] = 0;
n->x = node->x + 1;
n->y = node->y;
n->flag = 1;
n->father = node;
n->g = node->g + 1; // 求 g()
n->h = a_start_h(n);
n->f = n->g + n->h;// 求 f()
open_cnt++;
open_node_cnt++;
open[open_cnt].np = n; //添加到open表
open[open_cnt].f = n->f;
}
else if (flag == 4 && node->y>0) { //go down
Node *n = new Node();
for (int i = 0; i<3; i++) {
for (int j = 0; j<3; j++) {
n->a[i][j] = node->a[i][j];
}
}
n->a[node->x][node->y] = node->a[node->x][node->y - 1];
n->a[node->x][node->y - 1] = 0;
n->x = node->x;
n->y = node->y - 1;
n->flag = 2;
n->father = node;
n->g = node->g + 1; // 求 g()
n->h = a_start_h(n);
n->f = n->g + n->h;// 求 f()
open_cnt++;
open_node_cnt++;
open[open_cnt].np = n; //添加到open表
open[open_cnt].f = n->f;
}
}
void expand(Node *node) { //节点扩展
for (int i = 1; i<5; i++) {
if (i != node->flag) move(i, node);
}
}


int main() {
input();
open[0].np = &start;//start放入open表
open_node_cnt = 1;
if (isable()) {
while (true) {//open表不为空
if (isend(open[0].np)) {
cout << "\n路径:\n";
show(open[0].np);
cout << open[0].np->g << endl;
break;
}
expand(open[0].np);//扩展最优节点的子节点
open[0].np = NULL;
open[0].f = -1;
open_node_cnt--;
sort(open); //open表排序
}
}
else cout << "无解";
system("pause");
return(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
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
=====输入起始图=====
4 0 3 7 2 6 8 1 5

=====输入目标图=====
1 2 3 4 5 6 7 8 0


路径:
==============
4 2 3
7 0 6
8 1 5
==============

==============
4 2 3
7 1 6
8 0 5
==============

==============
4 2 3
7 1 6
8 5 0
==============

==============
4 2 3
7 1 0
8 5 6
==============

==============
4 2 0
7 1 3
8 5 6
==============

==============
4 0 2
7 1 3
8 5 6
==============

==============
4 1 2
7 0 3
8 5 6
==============

==============
4 1 2
7 5 3
8 0 6
==============

==============
4 1 2
7 5 3
0 8 6
==============

==============
4 1 2
0 5 3
7 8 6
==============

==============
0 1 2
4 5 3
7 8 6
==============

==============
1 0 2
4 5 3
7 8 6
==============

==============
1 2 0
4 5 3
7 8 6
==============

==============
1 2 3
4 5 0
7 8 6
==============

==============
1 2 3
4 5 6
7 8 0
==============

15

所以是884226886224488。

然后还得base64变种算法编码回去。

变种的地方在sub_400B58

image-20201228083529955

跟进去

image-20201228083730699

其实就是查找字符的第一次出现的地址,减去字符串起始地址,得到偏移值,在模64加。

所以就是base64换表而已。

解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
import base64
b = b'884226886224488'
print(b)

import base64
string2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
string1 = string2[-18:] + string2[:-18]
print(string1)

tmp = base64.b64encode(b)
print(tmp)
print(tmp.decode().translate(str.maketrans(string2,string1)))

Snipaste_2020-12-27_22-42-58

re123

给出一个没有后缀名的文件。查文件头,是chm文件。

image-20201228084130864

改成chm后,运行,杀软提示调用了cmd和powershell。

7z可以打开chm,提取出文件。

image-20201228084324266

其中doc.htm中有构造的cmd命令,调用了powershell。

image-20201228084415686

1
,cmd.exe,/c START /MIN C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass -NoLogo -NoProfile powershell.exe -WindowStyle hidden -nologo -noprofile -e SQBuAHYAbwBrAGUALQBFAHgAcAByAGUAcwBzAGkAbwBuACAAJAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBTAHQAcgBlAGEAbQBSAGUAYQBkAGUAcgAgACgAJAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBEAGUAZgBsAGEAdABlAFMAdAByAGUAYQBtACAAKAAkACgATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAE0AZQBtAG8AcgB5AFMAdAByAGUAYQBtACAAKAAsACQAKABbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACcAVABZADUAQgBDADQASQB3AEcASQBiAHYAZwB2ADkAaABqAEIAMgBNAGMASgBoAEUAaABOAEMAaABKAE0ARwBUAGsATgAyAHEAZwA3AHEAdgBGAEgAUQBUAC8AYgBMADUANwA1AHYAcABvAFYAMgAvADUAMwBuADIAcwBrAEoASgBCAEkAbgBrAFEARwA1AHgAdwBxAE8AcQBoAGsAYwBRAFgAQwBBAFQAeAA3AHEAKwBnAGsAYQBIAHMAdgBZAGoANwBrAEkAVgB2AEMAZwBiAHUAcgBJAHQAVgBnAG0AOQBNAFQAeABiAFYAQgA1AEwAQQBUAHAANQBPAGwAUQB2AGIANgBJAE0AVgAwAEwAZABRAHYAZABQAHAAdQArADgAeAA2ADYAUwBMADIAZQBPAHIATQBsACsAQwBrADcAbgBhAFUAQQA2ADkAZwBnAE4ARAA1AFUAYwBvAEUATwB6AEkAKwBwAFUAYwA4AHAANgAyAEcAMwBUAFIAWgB1AGIAdgAzADQASwA2AEkAYgBMAGUAcwBwAEEARABvAEcAUgAyADcAdgB2ACsAUgA3AEgAcABxAFgAegB0ADgAUQA5AHkAMABJAEoASQA1AE4AOABSAEwAQwB0AEwAdwA9AD0AJwApACkAKQApACwAIABbAEkATwAuAEMAbwBtAHAAcgBlAHMAcwBpAG8AbgAuAEMAbwBtAHAAcgBlAHMAcwBpAG8AbgBNAG8AZABlAF0AOgA6AEQAZQBjAG8AbQBwAHIAZQBzAHMAKQApACwAIABbAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkAKQAuAFIAZQBhAGQAVABvAEUAbgBkACgAKQA7AA==

解密base64,得到unicode的字符串。

1
2
3
4
5
import base64

b = 'SQBuAHYAbwBrAGUALQBFAHgAcAByAGUAcwBzAGkAbwBuACAAJAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBTAHQAcgBlAGEAbQBSAGUAYQBkAGUAcgAgACgAJAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABJAE8ALgBDAG8AbQBwAHIAZQBzAHMAaQBvAG4ALgBEAGUAZgBsAGEAdABlAFMAdAByAGUAYQBtACAAKAAkACgATgBlAHcALQBPAGIAagBlAGMAdAAgAEkATwAuAE0AZQBtAG8AcgB5AFMAdAByAGUAYQBtACAAKAAsACQAKABbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACcAVABZADUAQgBDADQASQB3AEcASQBiAHYAZwB2ADkAaABqAEIAMgBNAGMASgBoAEUAaABOAEMAaABKAE0ARwBUAGsATgAyAHEAZwA3AHEAdgBGAEgAUQBUAC8AYgBMADUANwA1AHYAcABvAFYAMgAvADUAMwBuADIAcwBrAEoASgBCAEkAbgBrAFEARwA1AHgAdwBxAE8AcQBoAGsAYwBRAFgAQwBBAFQAeAA3AHEAKwBnAGsAYQBIAHMAdgBZAGoANwBrAEkAVgB2AEMAZwBiAHUAcgBJAHQAVgBnAG0AOQBNAFQAeABiAFYAQgA1AEwAQQBUAHAANQBPAGwAUQB2AGIANgBJAE0AVgAwAEwAZABRAHYAZABQAHAAdQArADgAeAA2ADYAUwBMADIAZQBPAHIATQBsACsAQwBrADcAbgBhAFUAQQA2ADkAZwBnAE4ARAA1AFUAYwBvAEUATwB6AEkAKwBwAFUAYwA4AHAANgAyAEcAMwBUAFIAWgB1AGIAdgAzADQASwA2AEkAYgBMAGUAcwBwAEEARABvAEcAUgAyADcAdgB2ACsAUgA3AEgAcABxAFgAegB0ADgAUQA5AHkAMABJAEoASQA1AE4AOABSAEwAQwB0AEwAdwA9AD0AJwApACkAKQApACwAIABbAEkATwAuAEMAbwBtAHAAcgBlAHMAcwBpAG8AbgAuAEMAbwBtAHAAcgBlAHMAcwBpAG8AbgBNAG8AZABlAF0AOgA6AEQAZQBjAG8AbQBwAHIAZQBzAHMAKQApACwAIABbAFQAZQB4AHQALgBFAG4AYwBvAGQAaQBuAGcAXQA6ADoAQQBTAEMASQBJACkAKQAuAFIAZQBhAGQAVABvAEUAbgBkACgAKQA7AA=='
r = base64.b64decode(b).decode().replace('\x00', '')
print(r)

输出:

1
2
Invoke-Expression $(New-Object IO.StreamReader ($(New-Object IO.Compression.DeflateStream ($(New-Object 
IO.MemoryStream (,$([Convert]::FromBase64String('TY5BC4IwGIbvgv9hjB2McJhEhNChJMGTkN2qg7qvFHQT/bL575vpoV2/53n2skJJBInkQG5xwqOqhkcQXCATx7q+gkaHsvYj7kIVvCgburItVgm9MTxbVB5LATp5OlQvb6IMV0LdQvdPpu+8x66SL2eOrMl+Ck7naUA69ggND5UcoEOzI+pUc8p62G3TRZubv34K6IbLespADoGR27vv+R7HpqXzt8Q9y0IJI5N8RLCtLw==')))), [IO.Compression.CompressionMode]::Decompress)), [Text.Encoding]::ASCII)).ReadToEnd();

powershell会调用上面的命令。那个base64解码后不是明文,因为是压缩过的数据。搞web的队友帮忙解码。

image-20201228084955709

如果你把本题的文件改名为doc.chm,并放到$pwd目录下,会在$env:temp目录中生成一个2020.tmp。

image-20201228085351265

本题文件的后半段是base64编码的,解码后发现是个没有文件头的pe程序。这个命令就是把这个文件解码并释放出来。

其实大多数人都是直接发现文件后半段有base64,直接解码吧。

添加pe头之后,发现是dll。里面有aes。

image-20201228090005327

aes128,密钥密文都在上图了。

aes的轮子太长了,就不贴了,只贴主函数吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
AES aes(128, 283);
uint8_t key[16] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C };
uint8_t cipher[128] = { 0xB5, 0xF4, 0x3F, 0x45, 0x43, 0xD6, 0x99, 0xE7, 0x56, 0x1B,
0x2A, 0xAA, 0x84, 0x20, 0xC4, 0x46 };
uint8_t* plain = aes.DecryptECB(cipher, BLOCK_BYTES_LENGTH, key);

printf("\n\n\n\n");
for (int i = 0; i < 16; i++) {
printf("%c, ", plain[i]);
}

system("pause");
return 0;
}

一开始python没解出aes来,我还以为是魔改aes,一直在对比算法。结果没解出来是因为python脚本解的是aes256,而这题是aes128。浪费了大量时间。

既然研究过了,那就顺便写一下吧。

dll怎么调试?我也不太会,因为这题的dll没有导出函数。但好在主逻辑是写在dllEntryPoint里的。如果显示调用(LoadLibrary)dll的话,会自动执行dllEntryPoint,所以我们需要写个c代码显示调用dll。

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <windows.h>
int main(){
long long r = LoadLibrary("re123.dll");
// 成功则返回句柄,失败返回0
printf("0x%p\n", r);
return 0;
}

但是调用完就没了,怎么调试,难不成从LoadLibrary开始一步一步跟进去,一点一点进去?

有个巧妙的思路是把dllEntryPoint的第一个字节patch成0xcc,这样运行到此处时会弹异常,从而断在此处。然后我们手动把这个patch掉的字节改回原来的字节,然后在此处set ip。

爆异常:

image-20201228093409216

来到此处:

image-20201228093506414

把7FFF53CC1B64的0xcc改成原来的0x48。

然后改过0xcc的这个地方,set ip。因为本来运行了0xcc,爆了异常,此时rip是0x7FFF53CC1B65了,我们得把rip改回7FFF53CC1B64。

在7FFF53CC1B64右键,然后点set ip:

image-20201228100909902

(本dll改不改都行是个特例,因为

image-20201228101156048

image-20201228101139623

刚好运行到这里时,rbx高位是0。)

然后继续运行,点NO,这个0xcc异常不要传给程序。

image-20201228093615071

然后往后就是在dllEntryPoint这个函数里了,正常调试就行。

crash

给出的是core转储文件。这题是队友做的。

我看出有个md5算法,然后找到了几个md5的值,都能在cmd5上查到,但是要收费。还走了个异或常数。

等题解吧。听说是md5查表拿到原文后再异或常数,就是flag。但我没舍得花钱去反查cmd5.