DASCTF July X CBCTF 4th

逆向题解~

shellcode

下载下来一共四个文件,

image-20210801165128724

其中看到main.bat脚本是接受我们的输入后,以最小化方式启动part1.exe

image-20210801165237488

这里去简单了解了一下bat脚本,开始的@echo off表示关闭执行本指令及其它的指令的回显;set /p表示后面用用户的输入来赋值;start /min表示以最小化窗口的方式启动一个程序,相应的也就有start /max以最大化窗口的方式启动;taskkill /im表示所要关闭的进程的以程序名给出,另外具体详细的用法,直接在命令行窗口使用taskkill /?就清楚了。

但是从bat脚本来看,他是怎么传递我们的输入的呢。

直接启动part1.exe来看,没有什么输出。然后ida中发现是go语言写的程序,但是用以往的恢复符号的脚本用不了,这和国赛初赛那个go语言程序一样,可以使用免费版的ida7.6,自带恢复符号表,效果很好。

从函数名及汇编代码可以看出,这个程序应该是启动一个服务:

image-20210801172227127

再在010editor中看了part2.bin文件,从机器码很明显能看出,就是一个函数的开头:

image-20210801174256827

最后在看part3.exe,一切就很清晰了,它加载了part.bin中shellcode然后执行。

image-20210801172534546

直接动态跟踪进入shellcode,找到很多和网络编程相关的api,及发现它在本地的8080端口请求读文件(正是part1.exe启动的服务):

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
int sub_3A0005()
{
char v1[8]; // [esp+0h] [ebp-BCh] BYREF
int (__stdcall *v2)(char *, int, _DWORD, _DWORD, _DWORD); // [esp+8h] [ebp-B4h]
void (__stdcall *v3)(int); // [esp+Ch] [ebp-B0h]
int (__stdcall *v4)(int, int, int, int *); // [esp+10h] [ebp-ACh]
int (__stdcall *v5)(_DWORD, unsigned __int8 *, int, int); // [esp+14h] [ebp-A8h]
void (__stdcall *v6)(int, _DWORD, _DWORD, _DWORD, _DWORD); // [esp+18h] [ebp-A4h]
int (__stdcall *v7)(int, char *, int, _DWORD, _DWORD, int, _DWORD, _DWORD); // [esp+20h] [ebp-9Ch]
int (__stdcall *v8)(int, char *, char *, char *, _DWORD, _DWORD, int, _DWORD); // [esp+24h] [ebp-98h]
int v9; // [esp+28h] [ebp-94h]
int (*v10)(void); // [esp+2Ch] [ebp-90h]
int v11; // [esp+30h] [ebp-8Ch]
int v12; // [esp+34h] [ebp-88h]
int v13; // [esp+38h] [ebp-84h]
int v14; // [esp+3Ch] [ebp-80h]
char v15[16]; // [esp+40h] [ebp-7Ch] BYREF
char v16[8]; // [esp+50h] [ebp-6Ch] BYREF
int v17; // [esp+58h] [ebp-64h]
int v18; // [esp+5Ch] [ebp-60h] BYREF
char v19[4]; // [esp+60h] [ebp-5Ch] BYREF
char v20[40]; // [esp+64h] [ebp-58h] BYREF
char v21[16]; // [esp+8Ch] [ebp-30h] BYREF
char v22[12]; // [esp+9Ch] [ebp-20h] BYREF
char v23[12]; // [esp+A8h] [ebp-14h] BYREF
int v24; // [esp+B4h] [ebp-8h]
char v25[4]; // [esp+B8h] [ebp-4h] BYREF

sub_3A0480((void (__stdcall **)(char *))v1);
strcpy(v15, "Hello GuiShou");
strcpy(v19, "Tip");
v11 = v5(0, &unk_400000, 4096, 64);
v10 = (int (*)(void))v5(0, &unk_400000, 4096, 64);
v9 = 0x4000000;
strcpy(v22, "127.0.0.1");
strcpy(v16, "8080");
strcpy(v25, "GET");
v21[0] = 47;
v21[1] = 115;
v21[2] = 104;
v21[3] = 101;
v21[4] = 108;
v21[5] = 108;
v21[6] = 47;
v21[7] = 118;
v21[8] = 111;
v21[9] = 105;
v21[10] = 100;
v21[11] = 13;
v21[12] = 10;
v21[13] = 0;
strcpy(v20, "Mozilla/5.0 (Windows NT 6.1; rv:11.0)");
strcpy(v23, "HTTP/1.0");
v14 = 1;
v18 = -1;
v24 = 0;
v12 = v2(v20, 1, 0, 0, 0);
v13 = v7(v12, v22, 8080, 0, 0, 3, 0, 0);
v17 = v8(v13, v25, v21, v23, 0, 0, v9, 0);
v6(v17, 0, 0, 0, 0);
while ( v14 && v18 )
{
v14 = v4(v17, v24 + v11, 4096, &v18);
v24 += v18;
}
v3(v17);
v3(v13);
v3(v12);
((void (__cdecl *)(int, int, int (*)(void)))sub_3A059F)(v11, v24, v10);
return v10();
}

浏览器访问一下本地8080端口看看:确实。

image-20210801173140351

剩下继续跟踪调试就好,请求读了一串字符串,然后用它解密出一个关键加密函数,从其中也知道程序是如何获取我们在bat脚本中输入的FLAG了:通过获取在启动当前进程下的FLAG变量获取。

image-20210801173515697

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GetEnvironmentVariable是一个从调用该函数的进程的环境变量中返回指定的变量名值的函数,主要参数有lpName、lpBuffer等。

GetEnvironmentVariable函数从调用该函数的进程的环境变量中,返回指定的变量名的值,该值是一个以零结尾的字符串指针.
DWORD GetEnvironmentVariable(
LPCTSTR lpName, // address of environment variable name
LPTSTR lpBuffer, // address of buffer for variable value
DWORD nSize // size of buffer, in characters
);
参数:
lpName:要获取值的变量名字符串指针.
lpBuffer:接收变量值的字符串指针
nSize:接收变量值的内存大小.
返回值:
如果函数成功执行,则返回值是写入字符缓冲区的字符数量,但不包含'\0'字符.
如果没有找到所指的变量,则返回零.
如果字符缓冲区的大小小于变量值的长度,返回值为缓冲区的大小.

最后就是rc4流密码加密,得到密钥序列异或回去即可。

1
2
3
4
5
6
7
>>> s = [0x64, 0x2E, 0x90, 0x34, 0x41, 0xD8, 0x24, 0xCB, 0x52, 0x2E, 0xFB, 0x39, 0x3E, 0x91, 0x07, 0x0E, 0x96, 0xF6, 0x3C, 0x09, 0x9C, 0x21, 0x92, 0x21, 0xB2, 0xCC, 0x9F, 0x51, 0x48, 0x63, 0x4C, 0x8F, 0x72, 0x5D, 0xBF, 0x6C, 0x51, 0x76]
>>> t = [0x02, 0x42, 0xF1, 0x53, 0x3A, 0xBB, 0x42, 0xAA, 0x66, 0x1E, 0x9A, 0x58, 0x0E, 0xA8, 0x35, 0x68, 0xA4, 0xCE, 0x09, 0x3C, 0xAC, 0x40, 0xA5, 0x13, 0x8A, 0xFC, 0xAD, 0x30, 0x7B, 0x53, 0x75, 0xBD, 0x41, 0x6A, 0x8B, 0x0A, 0x67, 0x0B]
>>> flag = [s[i]^t[i] for i in range(38)]
>>> flag
[102, 108, 97, 103, 123, 99, 102, 97, 52, 48, 97, 97, 48, 57, 50, 102, 50, 56, 53, 53, 48, 97, 55, 50, 56, 48, 50, 97, 51, 48, 57, 50, 51, 55, 52, 102, 54, 125]
>>> bytes(flag)
b'flag{cfa40aa092f28550a72802a3092374f6}'

replace

第一次做程序中调用lua语言代码的题,这个题本身不难,但在总结题目时,一些环境上面遇到很多问题,记录一下。

我发现这个题是调用了lua是从ida打开的提示信息看到的,这也是出题人编译题目是没有注意这一点,其次从题目中字符串区域上下文找多翻翻也是可以发现的,Luas表示lua版本是5.3

image-20210805200714779

还找到了base64码表,从引用找到一个对码表逆序和base64加密函数,在base64加密函数下断后发现程序不是断下来,猜测是输入字符串长度不对,试了几次长度32,36都不是。

image-20210805201317430

然后既然我上面已经找到了程序加载的lua字节码,直接从这个入手,idapython提取出数据,找工具反编译。

开始找到luadec这个工具,编译环境问题真是花了主要的时间,,这里我去折腾了。工具地址

首先编译这个lua源码,缺少各种依赖吧,印象最深得的缺少readline.h这个库(因为后面我要编译32位的lua找这个库的32位找半天),我是在ubuntu下,直接安装就是:

1
sudo apt-get install libreadline-dev

然后到luadec目录下去编译luadec:make LUAVER=5.3,当时又是很多问题,但找到问题,搜索安装上缺少项就能轻松解决。

编译好了,开始反编译:./luadec ans.luac > ans.lua,问题来了:

image-20210805203310921

从提示信息看应该是位数问题,目标lua是32位的,而我的luadec是64位的。

linux环境是64位的,默认编译的就是64位的,那怎么编译32位的呢。这是编译C代码,用的gcc,记得gcc是有个-m32选项,强制编译32位的程序(前提是我们要安装好32位程序所要依赖的各种库文件)。

1
2
sudo apt-get install build-essential module-assistant
sudo apt-get install gcc-multilib g++-multilib

安装后,随便写了一个C文件,来编译测试了一下,gcc -m32 1.c -o 1,可行。

然后去找Makefile文件,增加编译选项。因为对Makefile不熟悉,这里开始又疑惑了,,为什么没有gcc命令,那程序是怎么编译的!

后面才发现,其实在是第一个Makefile中指定了到src文件下进行make,所以找到src文件下的Makefile,增加-m32

image-20210805204517841

然后编译,出现问题:

1
2
当搜索用于 /usr/lib/x86_64-linux-gnu/libreadline.a 时跳过不兼容的 -lreadline
/usr/bin/ld: 找不到 -lreadline

可以看到,编译时跑去找64位的readline库了,而因为不兼容所以跳过,就提示找不到。

问题是知道了,但找这个32位的库真是要命,搜索根本没有。。最后是在一篇其它问题里发现有这样一个名字:lib32readline6-dev

https://blog.csdn.net/zhbpd/article/details/41805737

安装发现果然是这个,高兴了一小会:

1
sudo apt-get install lib32readline6-dev

再到luadec下去编译luadec,同样要修改Makefile,增加gcc的选项-m32(这里我第一次编译忘了修改,又出现位数不兼容,Undefined reference to ‘__divdi3’的问题,好在及时发现,真是粗心大意,,)

终于成功,再次反编译,问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cannot find blockend > 170 , pc = 169, f->sizecode = 170
cannot find blockend > 43 , pc = 42, f->sizecode = 43
cannot find blockend > 25 , pc = 24, f->sizecode = 25
cannot find blockend > 17 , pc = 16, f->sizecode = 17
cannot find blockend > 32 , pc = 31, f->sizecode = 32
cannot find blockend > 9 , pc = 8, f->sizecode = 9
cannot find blockend > 9 , pc = 8, f->sizecode = 9
cannot find blockend > 9 , pc = 8, f->sizecode = 9
cannot find blockend > 33 , pc = 32, f->sizecode = 33
cannot find blockend > 7 , pc = 6, f->sizecode = 7
cannot find blockend > 7 , pc = 6, f->sizecode = 7
cannot find blockend > 7 , pc = 6, f->sizecode = 7
-- Decompiled using luadec 2.2 rev: 895d923 for Lua 5.3 from https://github.com/viruscamp/luadec
-- Command line: ans2.luac

段错误 (核心已转储)

然后尝试其它选项,如打印出函数调用结构:./luadec -pn ans2.luac

image-20210805210418021

尝试只打印0序号函数,因为其它函数都是它的子结构:./luadec -f 0 ans2.luac > ans2.lua

image-20210805210816751

还行,整体上大概都反编译出了,但是感觉比较难看,我又去找了unluac来看看效果,它就是一个jar,下载和使用起来很方便:

unluac下载链接

usage:如果解码中出现 \ddd的形式,说明源码中有中文,这时注意加上:–rawstring

1
java -jar unluac_2021_06_10.jar --rawstring ans.luac > ans.lua

然后得到的结果,确实要比luadec得到的好一些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
local L0_1, L1_1, L2_1, L3_1, L4_1, L5_1, L6_1, L7_1, L8_1, L9_1, L10_1, L11_1, L12_1, L13_1, L14_1, L15_1, L16_1, L17_1, L18_1, L19_1, L20_1, L21_1, L22_1, L23_1, L24_1, L25_1, L26_1, L27_1, L28_1, L29_1, L30_1, L31_1, L32_1, L33_1, L34_1, L35_1, L36_1, L37_1, L38_1, L39_1, L40_1, L41_1, L42_1, L43_1, L44_1, L45_1, L46_1, L47_1, L48_1, L49_1, L50_1, L51_1, L52_1, L53_1
L0_1 = require
L1_1 = "enclib"
L0_1 = L0_1(L1_1)
enclib = L0_1
function L0_1(A0_2)
local L1_2, L2_2, L3_2, L4_2, L5_2, L6_2, L7_2, L8_2, L9_2, L10_2, L11_2, L12_2
L1_2 = string
L1_2 = L1_2.len
L2_2 = A0_2
L1_2 = L1_2(L2_2)
L2_2 = {}
L3_2 = {}
L4_2 = 0
L5_2 = 255
L6_2 = 1
for L7_2 = L4_2, L5_2, L6_2 do
L2_2[L7_2] = L7_2
end
L4_2 = 1
L5_2 = L1_2
L6_2 = 1
for L7_2 = L4_2, L5_2, L6_2 do
L8_2 = L7_2 - 1
L9_2 = string
L9_2 = L9_2.byte
L10_2 = A0_2
L11_2 = L7_2
L12_2 = L7_2
L9_2 = L9_2(L10_2, L11_2, L12_2)
L3_2[L8_2] = L9_2
end
L4_2 = 0
L5_2 = 0
L6_2 = 255
L7_2 = 1
for L8_2 = L5_2, L6_2, L7_2 do
L9_2 = L2_2[L8_2]
L9_2 = L4_2 + L9_2
L10_2 = L8_2 % L1_2
L10_2 = L3_2[L10_2]
L9_2 = L9_2 + L10_2
L4_2 = L9_2 % 256
L9_2 = L2_2[L4_2]
L10_2 = L2_2[L8_2]
L2_2[L4_2] = L10_2
L2_2[L8_2] = L9_2
end
return L2_2
end
KSA = L0_1
function L0_1(A0_2, A1_2)
local L2_2, L3_2, L4_2, L5_2, L6_2, L7_2, L8_2, L9_2, L10_2
L2_2 = 0
L3_2 = 0
L4_2 = {}
L5_2 = 1
L6_2 = A1_2
L7_2 = 1
for L8_2 = L5_2, L6_2, L7_2 do
L9_2 = L2_2 + 1
L2_2 = L9_2 % 256
L9_2 = A0_2[L2_2]
L9_2 = L3_2 + L9_2
L3_2 = L9_2 % 256
L9_2 = A0_2[L3_2]
L10_2 = A0_2[L2_2]
A0_2[L3_2] = L10_2
A0_2[L2_2] = L9_2
L9_2 = A0_2[L2_2]
L10_2 = A0_2[L3_2]
L9_2 = L9_2 + L10_2
L9_2 = L9_2 % 256
L9_2 = A0_2[L9_2]
L4_2[L8_2] = L9_2
end
return L4_2
end
PRGA = L0_1
function L0_1(A0_2, A1_2)
local L2_2, L3_2, L4_2, L5_2, L6_2, L7_2
L2_2 = string
L2_2 = L2_2.len
L3_2 = A1_2
L2_2 = L2_2(L3_2)
L3_2 = KSA
L4_2 = A0_2
L3_2 = L3_2(L4_2)
L4_2 = PRGA
L5_2 = L3_2
L6_2 = L2_2
L4_2 = L4_2(L5_2, L6_2)
L5_2 = output
L6_2 = L4_2
L7_2 = A1_2
return L5_2(L6_2, L7_2)
end
RC4 = L0_1
function L0_1(A0_2, A1_2)
local L2_2, L3_2, L4_2, L5_2, L6_2, L7_2, L8_2, L9_2, L10_2, L11_2, L12_2
L2_2 = string
L2_2 = L2_2.len
L3_2 = A1_2
L2_2 = L2_2(L3_2)
L3_2 = nil
L4_2 = {}
L5_2 = 1
L6_2 = L2_2
L7_2 = 1
for L8_2 = L5_2, L6_2, L7_2 do
L9_2 = string
L9_2 = L9_2.byte
L10_2 = A1_2
L11_2 = L8_2
L12_2 = L8_2
L9_2 = L9_2(L10_2, L11_2, L12_2)
L3_2 = L9_2
L9_2 = string
L9_2 = L9_2.char
L10_2 = bxor
L11_2 = A0_2[L8_2]
L12_2 = L3_2
L10_2, L11_2, L12_2 = L10_2(L11_2, L12_2)
L9_2 = L9_2(L10_2, L11_2, L12_2)
L4_2[L8_2] = L9_2
end
L5_2 = table
L5_2 = L5_2.concat
L6_2 = L4_2
return L5_2(L6_2)
end
output = L0_1
L0_1 = {}
function L1_1(A0_2, A1_2)
local L2_2
L2_2 = A0_2 + A1_2
if L2_2 == 2 then
L2_2 = 1
if L2_2 then
goto lbl_8
end
end
L2_2 = 0
::lbl_8::
return L2_2
end
L0_1.cond_and = L1_1
function L1_1(A0_2, A1_2)
local L2_2
L2_2 = A0_2 + A1_2
if L2_2 == 1 then
L2_2 = 1
if L2_2 then
goto lbl_8
end
end
L2_2 = 0
::lbl_8::
return L2_2
end
L0_1.cond_xor = L1_1
function L1_1(A0_2, A1_2)
local L2_2
L2_2 = A0_2 + A1_2
if 0 < L2_2 then
L2_2 = 1
if L2_2 then
goto lbl_8
end
end
L2_2 = 0
::lbl_8::
return L2_2
end
L0_1.cond_or = L1_1
function L1_1(A0_2, A1_2, A2_2)
local L3_2, L4_2, L5_2, L6_2, L7_2
if A1_2 < A2_2 then
L3_2 = A2_2
A2_2 = A1_2
A1_2 = L3_2
end
L3_2 = 0
L4_2 = 1
while A1_2 ~= 0 do
L5_2 = A1_2 % 2
r_a = L5_2
L5_2 = A2_2 % 2
r_b = L5_2
L5_2 = L0_1[A0_2]
L6_2 = r_a
L7_2 = r_b
L5_2 = L5_2(L6_2, L7_2)
L5_2 = L4_2 * L5_2
L3_2 = L5_2 + L3_2
L4_2 = L4_2 * 2
L5_2 = math
L5_2 = L5_2.modf
L6_2 = A1_2 / 2
L5_2 = L5_2(L6_2)
A1_2 = L5_2
L5_2 = math
L5_2 = L5_2.modf
L6_2 = A2_2 / 2
L5_2 = L5_2(L6_2)
A2_2 = L5_2
end
return L3_2
end
L0_1.base = L1_1
function L1_1(A0_2, A1_2)
local L2_2, L3_2, L4_2, L5_2
L2_2 = L0_1.base
L3_2 = "cond_xor"
L4_2 = A0_2
L5_2 = A1_2
return L2_2(L3_2, L4_2, L5_2)
end
bxor = L1_1
function L1_1(A0_2, A1_2)
local L2_2, L3_2, L4_2, L5_2
L2_2 = L0_1.base
L3_2 = "cond_and"
L4_2 = A0_2
L5_2 = A1_2
return L2_2(L3_2, L4_2, L5_2)
end
band = L1_1
function L1_1(A0_2, A1_2)
local L2_2, L3_2, L4_2, L5_2
L2_2 = L0_1.base
L3_2 = "cond_or"
L4_2 = A0_2
L5_2 = A1_2
return L2_2(L3_2, L4_2, L5_2)
end
bor = L1_1
L1_1 = print
L2_1 = "Welcome to the world of reverse\n"
L1_1(L2_1)
L1_1 = print
L2_1 = "Now please give me the key : "
L1_1(L2_1)
L1_1 = "RC4KEY"
L2_1 = io
L2_1 = L2_1.read
L3_1 = "*l"
L2_1 = L2_1(L3_1)
L3_1 = string
L3_1 = L3_1.len
L4_1 = L2_1
L3_1 = L3_1(L4_1)
if L3_1 ~= 38 then
L3_1 = print
L4_1 = "wrong length"
L3_1(L4_1)
L3_1 = os
L3_1 = L3_1.exit
L3_1()
end
L3_1 = enclib
L3_1 = L3_1.prepare
L3_1()
L3_1 = enclib
L3_1 = L3_1.encrypt
L4_1 = L2_1
L5_1 = string
L5_1 = L5_1.len
L6_1 = L2_1
L5_1, L6_1, L7_1, L8_1, L9_1, L10_1, L11_1, L12_1, L13_1, L14_1, L15_1, L16_1, L17_1, L18_1, L19_1, L20_1, L21_1, L22_1, L23_1, L24_1, L25_1, L26_1, L27_1, L28_1, L29_1, L30_1, L31_1, L32_1, L33_1, L34_1, L35_1, L36_1, L37_1, L38_1, L39_1, L40_1, L41_1, L42_1, L43_1, L44_1, L45_1, L46_1, L47_1, L48_1, L49_1, L50_1, L51_1, L52_1, L53_1 = L5_1(L6_1)
L3_1 = L3_1(L4_1, L5_1, L6_1, L7_1, L8_1, L9_1, L10_1, L11_1, L12_1, L13_1, L14_1, L15_1, L16_1, L17_1, L18_1, L19_1, L20_1, L21_1, L22_1, L23_1, L24_1, L25_1, L26_1, L27_1, L28_1, L29_1, L30_1, L31_1, L32_1, L33_1, L34_1, L35_1, L36_1, L37_1, L38_1, L39_1, L40_1, L41_1, L42_1, L43_1, L44_1, L45_1, L46_1, L47_1, L48_1, L49_1, L50_1, L51_1, L52_1, L53_1)
res = L3_1
L3_1 = RC4
L4_1 = L1_1
L5_1 = res
L3_1 = L3_1(L4_1, L5_1)
K = L3_1
L3_1 = {}
t = L3_1
L3_1 = {}
L4_1 = 43
L5_1 = 50
L6_1 = 118
L7_1 = 51
L8_1 = 186
L9_1 = 167
L10_1 = 106
L11_1 = 55
L12_1 = 228
L13_1 = 145
L14_1 = 160
L15_1 = 171
L16_1 = 23
L17_1 = 227
L18_1 = 82
L19_1 = 56
L20_1 = 191
L21_1 = 166
L22_1 = 65
L23_1 = 254
L24_1 = 189
L25_1 = 167
L26_1 = 236
L27_1 = 92
L28_1 = 154
L29_1 = 70
L30_1 = 19
L31_1 = 169
L32_1 = 10
L33_1 = 70
L34_1 = 222
L35_1 = 237
L36_1 = 237
L37_1 = 19
L38_1 = 249
L39_1 = 70
L40_1 = 121
L41_1 = 127
L42_1 = 189
L43_1 = 104
L44_1 = 169
L45_1 = 107
L46_1 = 43
L47_1 = 1
L48_1 = 50
L49_1 = 165
L50_1 = 234
L51_1 = 90
L52_1 = 76
L53_1 = 190
L3_1[1] = L4_1
L3_1[2] = L5_1
L3_1[3] = L6_1
L3_1[4] = L7_1
L3_1[5] = L8_1
L3_1[6] = L9_1
L3_1[7] = L10_1
L3_1[8] = L11_1
L3_1[9] = L12_1
L3_1[10] = L13_1
L3_1[11] = L14_1
L3_1[12] = L15_1
L3_1[13] = L16_1
L3_1[14] = L17_1
L3_1[15] = L18_1
L3_1[16] = L19_1
L3_1[17] = L20_1
L3_1[18] = L21_1
L3_1[19] = L22_1
L3_1[20] = L23_1
L3_1[21] = L24_1
L3_1[22] = L25_1
L3_1[23] = L26_1
L3_1[24] = L27_1
L3_1[25] = L28_1
L3_1[26] = L29_1
L3_1[27] = L30_1
L3_1[28] = L31_1
L3_1[29] = L32_1
L3_1[30] = L33_1
L3_1[31] = L34_1
L3_1[32] = L35_1
L3_1[33] = L36_1
L3_1[34] = L37_1
L3_1[35] = L38_1
L3_1[36] = L39_1
L3_1[37] = L40_1
L3_1[38] = L41_1
L3_1[39] = L42_1
L3_1[40] = L43_1
L3_1[41] = L44_1
L3_1[42] = L45_1
L3_1[43] = L46_1
L3_1[44] = L47_1
L3_1[45] = L48_1
L3_1[46] = L49_1
L3_1[47] = L50_1
L3_1[48] = L51_1
L3_1[49] = L52_1
L3_1[50] = L53_1
L4_1 = 239
L5_1 = 227
L3_1[51] = L4_1
L3_1[52] = L5_1
flag = L3_1
L3_1 = 1
L4_1 = string
L4_1 = L4_1.len
L5_1 = K
L4_1 = L4_1(L5_1)
L5_1 = 1
for L6_1 = L3_1, L4_1, L5_1 do
L7_1 = table
L7_1 = L7_1.insert
L8_1 = t
L9_1 = string
L9_1 = L9_1.byte
L10_1 = string
L10_1 = L10_1.sub
L11_1 = K
L12_1 = L6_1
L13_1 = L6_1
L10_1, L11_1, L12_1, L13_1, L14_1, L15_1, L16_1, L17_1, L18_1, L19_1, L20_1, L21_1, L22_1, L23_1, L24_1, L25_1, L26_1, L27_1, L28_1, L29_1, L30_1, L31_1, L32_1, L33_1, L34_1, L35_1, L36_1, L37_1, L38_1, L39_1, L40_1, L41_1, L42_1, L43_1, L44_1, L45_1, L46_1, L47_1, L48_1, L49_1, L50_1, L51_1, L52_1, L53_1 = L10_1(L11_1, L12_1, L13_1)
L9_1, L10_1, L11_1, L12_1, L13_1, L14_1, L15_1, L16_1, L17_1, L18_1, L19_1, L20_1, L21_1, L22_1, L23_1, L24_1, L25_1, L26_1, L27_1, L28_1, L29_1, L30_1, L31_1, L32_1, L33_1, L34_1, L35_1, L36_1, L37_1, L38_1, L39_1, L40_1, L41_1, L42_1, L43_1, L44_1, L45_1, L46_1, L47_1, L48_1, L49_1, L50_1, L51_1, L52_1, L53_1 = L9_1(L10_1, L11_1, L12_1, L13_1, L14_1, L15_1, L16_1, L17_1, L18_1, L19_1, L20_1, L21_1, L22_1, L23_1, L24_1, L25_1, L26_1, L27_1, L28_1, L29_1, L30_1, L31_1, L32_1, L33_1, L34_1, L35_1, L36_1, L37_1, L38_1, L39_1, L40_1, L41_1, L42_1, L43_1, L44_1, L45_1, L46_1, L47_1, L48_1, L49_1, L50_1, L51_1, L52_1, L53_1)
L7_1(L8_1, L9_1, L10_1, L11_1, L12_1, L13_1, L14_1, L15_1, L16_1, L17_1, L18_1, L19_1, L20_1, L21_1, L22_1, L23_1, L24_1, L25_1, L26_1, L27_1, L28_1, L29_1, L30_1, L31_1, L32_1, L33_1, L34_1, L35_1, L36_1, L37_1, L38_1, L39_1, L40_1, L41_1, L42_1, L43_1, L44_1, L45_1, L46_1, L47_1, L48_1, L49_1, L50_1, L51_1, L52_1, L53_1)
end
L3_1 = 1
L4_1 = string
L4_1 = L4_1.len
L5_1 = K
L4_1 = L4_1(L5_1)
L5_1 = 1
for L6_1 = L3_1, L4_1, L5_1 do
L7_1 = t
L7_1 = L7_1[L6_1]
L8_1 = flag
L8_1 = L8_1[L6_1]
if L7_1 ~= L8_1 then
L7_1 = print
L8_1 = "wrong"
L7_1(L8_1)
L7_1 = os
L7_1 = L7_1.exit
L7_1()
end
end
L3_1 = print
L4_1 = "correct!"
L3_1(L4_1)

代码其实可读性还是比较高的,就变量名不好看和调用一个方法时时分开写的。

开始判断输入长度,要为38位,再调用了enlib库中的2个函数,其实就是之前我ida中找到的关于base64那2个函数,最后进行一个rc4加密,密钥RC4KEY,这里怕rc4是魔改过的,我直接把代码改了一下,找一个在线运行lua脚本的脚本,运行输出52长度的异或序列。

再在notepad++中使用正则(L….=)替换密文赋值的变量为逗号,方便提取出密文。

最后异或回去,发现不是可打印字符,那显然是错了,因为是base64加密结果。

哪里错了呢,回到程序分析,因为没有符号表,这里记录使用bindiff恢复符号的方法。

首先下载bindiff,https://www.zynamics.com/software.html .msi文件是Windows Installer的数据包

image-20210805214305565

安装后,在\BinDiff\Plugins\IDA Pro路径下的dll文件复制到ida安装路径的plugins目录下即可。

打开ida,从输出窗口看是否成功加载:

image-20210805214538294

然后下载一个lua源码包,编译出lua解释器。这个就是在上面编译luadec中,编译的第一步(64位和32位都有说),在lua-5.3目录下:make linux后,可以增加一步:

1
make local

这样后,我们的lua-5.3目录下会多一个include文件:里面的bin文件是lua解释器于luac编译器;include文件是我们在C代码中要调用lua代码所需要的头文件。

image-20210805215350341

根据我们的要分析的文件的位数来选择编译对应位数的lua文件,将选择的lua文件放入ida中分析,然后退出保存idb文件。

再把我们要分析的文件拖入ida,按快捷键:ctrl+6

image-20210805215910881

选择我们之前保存的idb文件,可以得到一些对比结果

image-20210805220004779

然后再次快捷键:ctrl+6

image-20210805220050549

两个最小要求一般选择0.5上下:

image-20210805220508926

回到ida函数窗口,可以发现已经识别出了很多函数:

image-20210805220622915

但是对于本题分析要用到的函数,却没有,我也编译了几个版本的lua来看,都没有发现恢复很好的,就直接分析了。

在字符串区域找到了lua脚本中出现的RC4KEY,且下面的Good!!是很可疑的。

image-20210805220827503

从RC4KEY引用定位:用下断点调试辅助分析。

image-20210805221211488

可以发现上面的代码就是比较从lua脚本中获取的字符串是否等于RC4KEY,是的话就用Good!!来覆盖。

所以说我们的rc4加密的key最后是Good!!了。

本地用这个密钥跑一下异或序列,然后再换表base64解码即可。

到这里,题目完了。

最后说一下,上面我是在ubuntu上弄了lua的编译64位与32位环境,弄完我又跑去在centos弄了下,因为开始是在这里弄得,没有成功,现在回去填坑。

因为centos是RedHat系列,它的包管理工具是yum,所以和在ubuntu上有一定的区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
一般来说著名的linux系统基本上分两大类:

1.RedHat系列:Redhat、Centos、Fedora等

2.Debian系列:Debian、Ubuntu等

RedHat系列

1、常见的安装包格式rpm包,安装rpm包的命令是 rpm -参数

2、包管理工具yum

3、支持tar包

Debian系列

1、常见的安装包格式deb包,安装deb包的命令是 dpkg -参数

2、包管理工具apt-get

3、支持tar包

开始还是32位的程序要用的依赖:https://blog.csdn.net/kongshuai19900505/article/details/82775688

1
sudo yum install libgcc*.i686*

然后还是来到了ubuntu遇到的问题,readline库的安装,64位的还好,直接用下面的命令:

1
sudo yum install readline-devel

这样可以编译64位的了。

还是同样的问题,32位的readline的包名字是什么呢,因为这个是yum,所以不是lib32readline6-dev了。

找了半天,无果,最后是猜测是通过后缀来区别的:给它加上.i686试试,

1
sudo yum install readline-devel.i686

真的!!激动小一会。😂。

这其实也是看到64位已经安装的提示有了这样的猜测:

image-20210805222911664

而为什么要加.686呢?

i386对应的是32位系统、而i686是i386的一个子集,i686仅对应P6及以上级别的CPU,i386则广泛适用于80386以上的各种CPU;x86_64主要是64位系统。

经过上面的折腾也熟悉了下apt-get自动清理无用包的命令:

ubuntu下:

1
2
sudo apt-get automove #但有一定风险
sudo apt-get autoclean

centos下:

1
yum clean all #清空yum缓存
-------------本文结束感谢您的阅读-------------