L3H_Sec招新2024-RE出题人wp

EasyReverse

题目附件为exe文件,使用exeinfo查看文件信息,64位、无壳、msvc编译器

使用IDA Pro加载exe文件,自动定位到main函数,使用F5反编译查看伪c代码

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rdx
__int64 v4; // r8
unsigned __int64 v6; // [rsp+20h] [rbp-98h]
unsigned __int64 v7; // [rsp+20h] [rbp-98h]
int i; // [rsp+28h] [rbp-90h]
char v9; // [rsp+2Fh] [rbp-89h]
__int64 v10[17]; // [rsp+30h] [rbp-88h] BYREF

v6 = 0i64;
for ( i = 0; i < 16; ++i )
v10[i] = 0i64;
sub_140001060(aInputYourFlag, argv, envp);
do
*((_BYTE *)v10 + v6++) = getchar();
while ( v6 < 0x78 && *(&v9 + v6) != 10 );
*((_BYTE *)v10 + v6) = 0;
v7 = v6 - 1;
sub_1400010C0(v10, v7);
if ( (unsigned int)sub_1400013C0(v10, v7) )
sub_140001060(aNoNoNo, v3, v4);
else
sub_140001060(aRight, v3, v4);
return 0;
}

使用N键对部分函数及变量进行重命名(Y键可修改变量类型)

不难看出sub_1400010C0是加密函数,sub_1400013C0是校验函数

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)
{
unsigned __int64 idx; // [rsp+20h] [rbp-98h]
unsigned __int64 len; // [rsp+20h] [rbp-98h]
int i; // [rsp+28h] [rbp-90h]
char flag[128]; // [rsp+30h] [rbp-88h] BYREF

idx = 0i64;
for ( i = 0; i < 16; ++i )
*(_QWORD *)&flag[8 * i] = 0i64;
printf("Input your flag: ");
do
flag[idx++] = getchar();
while ( idx < 0x78 && flag[idx - 1] != 10 );
flag[idx] = 0;
len = idx - 1;
encrypt((__int64)flag, len);
if ( (unsigned int)check((__int64)flag, len) )
printf("no no no!\n");
else
printf("right!\n");
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
__int64 __fastcall encrypt(char *flag, unsigned __int64 len)
{
__int64 result; // rax
char tmp[3]; // [rsp+0h] [rbp-58h]
int j; // [rsp+4h] [rbp-54h]
int i; // [rsp+8h] [rbp-50h]
char key[72]; // [rsp+10h] [rbp-48h] BYREF

strcpy(key, "REVERSE{sagashite_honmono_no_flag_ganbatte!}");
tmp[0] = 'l';
tmp[1] = '3';
tmp[2] = 'h';
for ( i = 0; i < len; ++i )
flag[i] ^= key[i];
for ( j = 0; ; j += 3 )
{
result = j;
if ( j >= len + 3 )
break;
flag[j] += tmp[0];
flag[j] ^= tmp[0];
tmp[0] ^= flag[j];
flag[j] ^= tmp[0];
flag[j + 1] += tmp[1];
flag[j + 1] ^= tmp[1];
tmp[1] ^= flag[j + 1];
flag[j + 1] ^= tmp[1];
flag[j + 2] += tmp[2];
flag[j + 2] ^= tmp[2];
tmp[2] ^= flag[j + 2];
flag[j + 2] ^= tmp[2];
}
return result;
}

加密过程首先将输入flag与密钥REVERSE{sagashite_honmono_no_flag_ganbatte!}进行异或,接下来每次3字节,与tmp相加,异或后tmp与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
__int64 __fastcall check(char *flag, __int64 len)
{
int i; // [rsp+0h] [rbp-48h]
char ans[64]; // [rsp+8h] [rbp-40h] BYREF

qmemcpy(ans, "l3h", 3);
ans[3] = -118;
ans[4] = -87;
ans[5] = -122;
ans[6] = -96;
ans[7] = -64;
ans[8] = -106;
ans[9] = -34;
ans[10] = -20;
ans[11] = -84;
ans[12] = 46;
ans[13] = -16;
ans[14] = -3;
ans[15] = 76;
ans[16] = -3;
ans[17] = 51;
ans[18] = -113;
ans[19] = 7;
ans[20] = 51;
ans[21] = -18;
ans[22] = 14;
ans[23] = 62;
ans[24] = 32;
ans[25] = 70;
ans[26] = -100;
ans[27] = 61;
ans[28] = -76;
ans[29] = -90;
ans[30] = 109;
ans[31] = 35;
ans[32] = -90;
ans[33] = -96;
ans[34] = 86;
ans[35] = -88;
ans[36] = -55;
ans[37] = 88;
ans[38] = -69;
ans[39] = 36;
ans[40] = 95;
ans[41] = -41;
for ( i = 0; i < (unsigned __int64)(len + 3); ++i )
{
if ( flag[i] != ans[i] )
return 0xFFFFFFFFi64;
}
return 0i64;
}

校验时将加密后的flag与ans进行比较,完全相同返回0

check中获得加密后的flag,根据encrypt的加密逻辑逆向求解得到解密算法(解密时flag后3字节作为tmp初始值)

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
void crack()
{
uint8_t flag[BUF_SIZE];
size_t len = 39;
uint8_t ans[] = { 0x6c, 0x33, 0x68, 0x8a, 0xa9, 0x86, 0xa0, 0xc0, 0x96, 0xde, 0xec, 0xac, 0x2e, 0xf0, 0xfd, 0x4c, 0xfd, 0x33, 0x8f, 0x07, 0x33, 0xee, 0x0e, 0x3e, 0x20, 0x46, 0x9c, 0x3d, 0xb4, 0xa6, 0x6d, 0x23, 0xa6, 0xa0, 0x56, 0xa8, 0xc9, 0x58, 0xbb, 0x24, 0x5f, 0xd7 };
uint8_t xor_key[] = { "REVERSE{sagashite_honmono_no_flag_ganbatte!}" };
uint8_t magic_nums[3] = { 0x24, 0x5f, 0xd7 };

for (int i = len-3; i >= 0; i -= 3)
{
flag[i + 0] = magic_nums[0] - ans[i + 0];
magic_nums[0] = magic_nums[0] - flag[i + 0];
flag[i + 1] = magic_nums[1] - ans[i + 1];
magic_nums[1] = magic_nums[1] - flag[i + 1];
flag[i + 2] = magic_nums[2] - ans[i + 2];
magic_nums[2] = magic_nums[2] - flag[i + 2];
}

for (int i = 0; i < len; i++)
{
flag[i] ^= xor_key[i];
}

flag[len] = '\0';
printf("%s\n", flag);
}

正确flag:L3HSEC{We1c0me_7o_7he_W0r1d_0f_Rever5e}

空字符串也能使程序输出right,应该不会有人把空字符串当作flag吧

JunkCode

花指令(junk code)是一种专门用来迷惑反编译器的指令片段,这些指令片段不会影响程序的原有功能,但会使得反汇编器的结果出现偏差,从而使破解者分析失败。花指令 - CTF Wiki

题目附件为ELF文件,可在linux平台运行

使用file命令查看附件信息,x64可执行ELF文件、动态链接

使用IDA Pro加载附件,自动识别到main,无法使用F5进行反编译,提示positive sp value has been found,分析其汇编代码

发现如下异常指令,jmp loc_174A+1指令长度2字节,跳转的目标地址为0x174B,目标地址位于该jmp指令内部,对jmp指令使用U键取消定义,对0x174B处使用C键转换为汇编指令

分析发现该部分指令无实际意义,只是对IDA反汇编功能的干扰,将这部分代码nop掉(推荐使用patching插件或keypatch插件)

除了上面的jmp花指令外,还有call花指令,处理方法类型

对于其它花指令,使用相似的方法进行处理

patch后仍然无法直接使用F5对main函数反编译,因为IDA没有正确识别main函数的范围,右键main函数,选择修改函数,更改main范围,随后即可正常反编译

main函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int i; // [rsp+84h] [rbp-9Ch]
size_t v5; // [rsp+88h] [rbp-98h]
char s[136]; // [rsp+90h] [rbp-90h] BYREF
unsigned __int64 v7; // [rsp+118h] [rbp-8h]

v7 = __readfsqword(0x28u);
for ( i = 0; i <= 127; ++i )
s[i] = 0;
printf("Give me your secret:");
__isoc99_scanf("%127s", s);
v5 = strlen(s);
((void (__fastcall *)(void *, char *, __int64))loc_11C9)(&unk_4040, off_4018, qword_4010);
sub_14A0(&unk_4040, s, v5);
if ( (unsigned int)sub_164B(s, v5) )
puts(&::s);
else
puts(&byte_2070);
return 0LL;
}

关注到loc_11C9 sub_14A0 sub_164B三个函数,猜测前两个与加密相关,第三个与校验相关

这三个函数中同样存在花指令,patch后设置函数范围并反编译

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
unsigned __int64 __fastcall sub_11C9(__int64 a1, __int64 a2, unsigned __int64 a3)
{
__int64 v3; // kr00_8
int i; // [rsp+A8h] [rbp-98h]
int j; // [rsp+A8h] [rbp-98h]
int v7; // [rsp+ACh] [rbp-94h]
__int64 v8[17]; // [rsp+B0h] [rbp-90h] BYREF
unsigned __int64 v9; // [rsp+138h] [rbp-8h]

v9 = __readfsqword(0x28u);
v7 = 0;
memset(v8, 0, 128);
for ( i = 0; i <= 127; ++i )
{
*(_BYTE *)(i + a1) = i;
*((_BYTE *)v8 + i) = *(_BYTE *)(a3 - i % a3 - 1 + a2);
}
for ( j = 0; j <= 127; ++j )
{
v3 = *(unsigned __int8 *)(j + a1) + *((char *)v8 + j);
v7 ^= (((HIDWORD(v3) >> 25) + *(_BYTE *)(j + a1) + *((_BYTE *)v8 + j)) & 0x7F) - (HIDWORD(v3) >> 25);
*(_BYTE *)(j + a1) ^= *(_BYTE *)(v7 + a1);
*(_BYTE *)(v7 + a1) ^= *(_BYTE *)(j + a1);
*(_BYTE *)(j + a1) ^= *(_BYTE *)(v7 + a1);
}
return v9 - __readfsqword(0x28u);
}
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
unsigned __int64 __fastcall sub_14A0(__int64 a1, __int64 a2, unsigned __int64 a3)
{
__int64 v3; // kr00_8
unsigned __int64 result; // rax
int v5; // [rsp+6Ch] [rbp-14h]
int v6; // [rsp+70h] [rbp-10h]
unsigned __int64 i; // [rsp+78h] [rbp-8h]

v5 = 0;
v6 = 0;
for ( i = 0LL; ; ++i )
{
result = i;
if ( i >= a3 )
break;
v5 = (v5 + 1) % 128;
v3 = *(unsigned __int8 *)(v5 + a1) + v6;
v6 = (((HIDWORD(v3) >> 25) + *(_BYTE *)(v5 + a1) + (_BYTE)v6) & 0x7F) - (HIDWORD(v3) >> 25);
*(_BYTE *)(v5 + a1) ^= *(_BYTE *)(v6 + a1);
*(_BYTE *)(v6 + a1) ^= *(_BYTE *)(v5 + a1);
*(_BYTE *)(v5 + a1) ^= *(_BYTE *)(v6 + a1);
*(_BYTE *)(a2 + i) += *(_BYTE *)(((*(_BYTE *)(v5 + a1) ^ *(_BYTE *)(v6 + a1)) & 0x7F) + a1);
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall sub_164B(__int64 a1)
{
int i; // [rsp+1Ch] [rbp-34h]
__int64 v3[6]; // [rsp+20h] [rbp-30h]

v3[5] = __readfsqword(0x28u);
v3[0] = 0x6EB178AFAD8440A9LL;
v3[1] = 0x9F77728FD99EBF68LL;
v3[2] = 0x62B6B0D9ADA991EDLL;
v3[3] = 0x8397AED465C995B2LL;
v3[4] = 4086009499LL;
for ( i = 0; i <= 4; ++i )
{
if ( v3[i] != *(_QWORD *)(8LL * i + a1) )
return 0xFFFFFFFFLL;
}
return 0LL;
}

熟悉常见加密算法的应该不难看出该加密算法是对RC4的魔改,修改S盒大小为128,并且将加密中的异或替换为加法

完成加密后,将加密flag与正确的值进行对比

根据上面的流程写出对应的解密代码(不必要逆向S盒初始化的代码,动态调试dump内存效率更高)

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
void rc4_init(uint8_t *s, uint8_t *key, uint64_t Len) // 初始化函数
{
int i = 0, j = 0;
char k[128] = {0};
for (i = 0; i < 128; i++)
{
s[i] = i;
k[i] = key[Len - 1 - (i % Len)];
}
for (i = 0; i < 128; i++)
{
j ^= (s[i] + k[i]) % 128;
s[i] ^= s[j];
s[j] ^= s[i]; // 交换s[i]和s[j]
s[i] ^= s[j];
}
}

void rc4_decrypt(uint8_t *s, uint8_t *Data, uint64_t Len) // 加解密
{
int i = 0, j = 0, t = 0;
uint64_t k = 0;
for (k = 0; k < Len; k++)
{
i = (i + 1) % 128;
j = (j + s[i]) % 128;
s[i] ^= s[j]; // 交换s[x]和s[y]
s[j] ^= s[i];
s[i] ^= s[j];
t = (s[i] ^ s[j]) % 128;
Data[k] -= s[t];
}
}

解密得到正确的flag:L3HSEC{Patch_15_vsefu1_1n_rev_w0r1d}

MoreJunkCode

题如其名,更多的花指令

main函数能够正常反编译

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
size_t v3; // rax
size_t v5; // [rsp+0h] [rbp-E0h]
char s[64]; // [rsp+10h] [rbp-D0h] BYREF
char v7[136]; // [rsp+50h] [rbp-90h] BYREF
unsigned __int64 v8; // [rsp+D8h] [rbp-8h]

v8 = __readfsqword(0x28u);
memset(s, 0, sizeof(s));
memset(v7, 0, 0x80uLL);
printf("Give me your secret:");
__isoc99_scanf("%63s", s);
v5 = strlen(s);
s[v5] = 0;
if ( (v5 & 0xF) != 0 )
v5 = 16 * ((v5 >> 4) + 1);
((void (__fastcall *)(char *, size_t, char *, __int64))loc_4B587)(s, v5, v7, 128LL);
v3 = strlen(::s);
((void (__fastcall *)(char *, size_t, char *, __int64))loc_7CBD5)(::s, v3, v7, 128LL);
if ( (unsigned int)check(v7, 128LL) )
puts(&byte_86228);
else
puts(&byte_86250);
return 0LL;
}

加密之后的校验函数也能正常反编译

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
__int64 __fastcall check(__int64 a1)
{
int i; // [rsp+1Ch] [rbp-94h]
__int64 v3[18]; // [rsp+20h] [rbp-90h]

v3[17] = __readfsqword(0x28u);
v3[0] = 0x9741FD0DE69A44F8LL;
v3[1] = 0x8B7EAD8A7653DCB7LL;
v3[2] = 0xEC2BFD3F4B6C8358LL;
v3[3] = 0xA8E3399B1230FD4CLL;
v3[4] = 0x22940D53958BC645LL;
v3[5] = 0xB39CE56DC272C3C4LL;
v3[6] = 0xBDA544631E4AF3E4LL;
v3[7] = 0x1DC049365EF29271LL;
v3[8] = 0x702E0ABC7BC99541LL;
v3[9] = 0xC23569AE9AEEF96LL;
v3[10] = 0xEF3A16A05CD5ED13LL;
v3[11] = 0x42F70CA508156AA2LL;
v3[12] = 0xA961263C1F455E4DLL;
v3[13] = 0x152355723BB4F6C1LL;
v3[14] = 0x36D8C5C2A4A15D16LL;
v3[15] = 0x75CB5242177C870ALL;
for ( i = 0; i <= 15; ++i )
{
if ( v3[i] != *(_QWORD *)(8LL * i + a1) )
return 0xFFFFFFFFLL;
}
return 0LL;
}

loc_4B587loc_7CBD5可能是加密相关函数,被大量插花,无法直接反编译

分析发现0x14D1~`0x85C44`的部分都被大量插花

手动去除花指令显然不可行,因此接下来使用IDA Python脚本进行批量化操作

程序中的花指令可大致分为下面两种类型

  • 影响反汇编的跳转指令

  • 不影响反汇编的混淆指令

不影响反汇编的混淆指令可以先不考虑,先将影响反汇编的跳转指令patch掉使IDA能将所有代码正确反汇编

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

start = 0x14D1
end = 0x85C44

# 删除所有的反汇编指令
ea = start
while (ea < end):
idc.del_items(ea)
ea += 1

# 修复跳转指令
ea = start
while (ea < end):
# 获取指令长度
ins_len = idc.create_insn(ea)
if (ins_len == 0):
break
# 将ea处数据解析为汇编指令
ins = idc.generate_disasm_line(ea, 0)

# 识别jxx指令,强制跳转到目标位置
if (ins.startswith("j")):
dst = idc.get_operand_value(ea, 0)
# 跳转目标地址在分析片段内,外部跳转忽略
# 目标位置需要能识别为汇编指令
if (dst >= start and dst < end and idc.create_insn(dst) == 0):
# jmp指令
if (ins.startswith("jmp")):
idc.del_items(ea)
# nop掉jmp指令与目标地址间的部分
for a in range(ea, dst):
idc.patch_byte(a, 0x90)
idc.create_insn(a)
# 跳转到目标地址处
ea = dst
continue
# jb指令
if (ins.startswith("jb") and dst > ea and dst < ea + ins_len):
# jb花指令前一条指令为popfq `0x9D`
if (idc.get_bytes(ea-1, 1) == b'\x9d'):
idc.del_items(ea)
# nop掉jb指令与目标地址间的部分
for a in range(ea, dst):
idc.patch_byte(a, 0x90)
idc.create_insn(a)
# 跳转到目标地址处
ea = dst
continue

# 识别call指令,强制跳转到到目标位置
if (ins.startswith("call")):
dst = idc.get_operand_value(ea, 0)
# 跳转目标地址在当前call指令内
if (dst >= ea and dst < ea + ins_len):
# call的花指令只有1种,长度为25字节,全部nop掉就行
if (idc.get_bytes(ea, 25) == b'\xe8\xff\xff\xff\xff\xf2\x9cA_H\x83\xc4\x08AXI\x83\xc0\x14APAW\x9d\xc3'):
for a in range(ea, ea+25):
idc.patch_byte(a, 0x90)
idc.create_insn(a)
ea += 25
continue

# ea跳转到下一条指令
ea += ins_len

去除掉影响反汇编的跳转指令后,发现已经可以通过指令特征确定每一个函数的区域

函数入口

函数出口

根据函数入口和出口创建每一个函数

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
# 识别函数
func_ls = []

ea = start
func_start = -1
while (ea < end):
# 获取指令长度
ins_len = idc.create_insn(ea)
if (ins_len == 0):
break
# 将ea处数据解析为汇编指令
ins = idc.generate_disasm_line(ea, 0)

if (ins == 'sub rsp, 200000h'):
# 匹配函数入口
if (idc.get_bytes(ea-4, 11) == b'UH\x89\xe5H\x81\xec\x00\x00 \x00'):
func_start = ea - 4
if (ins == 'leave'):
# 匹配函数出口
if (idc.get_bytes(ea, 2) == b'\xc9\xc3'):
func_end = ea + 2
if (func_start != -1):
idc.add_func(func_start)
idc.set_func_end(func_start, func_end)
func_ls.append((func_start, func_end))
func_start = -1

# ea跳转到下一条指令
ea += ins_len

完成上面的patch后,保存patch,查看程序是否能正常运行

通过动态调试dump加密前后的数据进行对比可以确定patch是否影响到程序正常逻辑

能够正常运行,说明patch并未影响程序逻辑,并且IDA已经可以生成正确的CFG

但是无法进行反编译,因为代码中还有大量影响堆栈的花指令造成程序堆栈过大,需要去除这些花指令并调整栈平衡

程序中的花指令需要满足如下几个要求才能确保不影响原程序:

  1. 花指令自身栈平衡

  2. 花指令不影响程序使用的寄存器

根据程序中未加花函数,可以获得如下程序指令特征:

  1. 函数内不操作RSP寄存器(不是使用push pop等指令)

  2. 使用RAX~RSI寄存器、RBP寄存器

  3. 不使用R8~R15寄存器

  4. 不使用pushf popf操作FLAG寄存器

提取出程序中所有的指令块,根据指令块重复的次数及上面的规则,判断指令块属于花指令或原程序,对属于花指令的指令块进行patch

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

start = 0x14D1
end = 0x85C44
ea = start
while (ea < end):
idc.del_items(ea)
ea += 1

func_ls = [(5329, 21619), (21619, 34554), (34554, 225380), (225380, 308615), (308615, 352674), (352674, 510933), (510933, 547908)]

for func in func_ls:
idc.add_func(func[0])
idc.set_func_end(func[0], func[1])

# 记录所有指令块
all_ins = {}
blocks = []

# 对每一个函数进行栈平衡匹配
for func in func_ls:
code_start = func[0] + 11
code_end = func[1] - 2

ea = code_start

block_start = ea
block_ins = ""

while (ea < code_end):
# 获取指令长度
ins_len = idc.create_insn(ea)
# 将ea处数据解析为汇编指令
ins = idc.generate_disasm_line(ea, 0)

if (ins == "nop"):
# 以nop指令
if (block_start != ea):
block_end = ea
blocks.append((block_start, block_end, block_ins))
block_ins = ""
block_start = ea + ins_len

# ea跳转到下一条指令
ea += ins_len
continue

if (ins.startswith("push") or (ins.startswith("sub") and "rsp" in ins)):
# push指令重新开始一个代码块
if (block_start != ea):
block_end = ea
blocks.append((block_start, block_end, block_ins))
block_ins = "\n" + ins
block_start = ea

# ea跳转到下一条指令
ea += ins_len
continue

if (ins.startswith("pop") or (ins.startswith("add") and "rsp" in ins)):
# pop指令结束一个代码块
block_end = ea + ins_len
block_ins += "\n"
block_ins += ins
blocks.append((block_start, block_end, block_ins))
block_ins = ""
block_start = ea + ins_len

# ea跳转到下一条指令
ea += ins_len
continue

block_ins += "\n"
block_ins += ins
# ea跳转到下一条指令
ea += ins_len

# 最后一个基本块
if (block_start != ea):
block_end = ea
blocks.append((block_start, block_end, block_ins))
for start, end, ins in blocks:
if (ins in all_ins):
all_ins[ins] += 1
else:
all_ins[ins] = 1

print(all_ins)

提取到的指令块不到500个,可以很轻松得分辨出哪些属于花指令(下面的情况可能会有不同,以脚本运行得到的结果为主)

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
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
is_junk_ins = {
'\nmov [rbp-4], edi': 1,
'\npushfq\nmov r11, [rsp]': True,
'\nsub rsp, 8\nor r11, 40h\nmov [rsp], r11\npopfq': True,
'\njnz _exit\npopfq': True,
'\nmov [rbp-10h], rsi': 1,
'\npush rbp': True,
'\npush rdi': True,
'\npop r11': True,
'\npushfq': True,
'\npush rbp\npop r13': True,
'\nmov r11, [rsp]': True,
'\nsub rsp, 8\nor r11, 40h\nmov [rsp], r11': True,
'\npush rax\npop r10': True,
'\nmov r15, [rsp]\nor r15, 1': True,
'\npush r15': True,
'\npush rdi\npop r11': True,
'\npopfq': True,
'\npush rcx': True,
'\npop r9': True,
'\nmov r14, [rsp]\nand r14, 0FFEh': True,
'\npush r14\npopfq': True,
'\njb _exit\npopfq': True,
'\npop r13': True,
'\npush rbx\npop r8': True,
'\npush rax': True,
'\npush rsp': True,
'\npop r8': True,
'\npush rbx': True,
'\npush rsi\npop r8': True,
'\npop r10': True,
'\npush rsp\npop r8': True,
'\nmov eax, [rbp-4]': 7,
'\nmov edx, eax': 8,
'\npushfq\nmov r15, [rsp]\nor r15, 1': True,
'\npush r15\npopfq': True,
'\npush rcx\npop r9': True,
'\nmov rax, [rbp-10h]': 5,
'\nmov [rax], dl': 4,
'\nshr eax, 8': 1,
'\npushfq\nmov r14, [rsp]\nand r14, 0FFEh': True,
'\npush r14': True,
'\npush rsi': True,
'\njb _exit': True,
'\njnz _exit': True,
'\nmov rax, [rbp-10h]\nadd rax, 1': 1,
'\nshr eax, 10h': 1,
'\nadd rax, 2': 2,
'\nshr eax, 18h': 1,
'\nadd rax, 3': 2,
'\nmov [rbp-8], rdi': 1,
'\nmov rax, [rbp-8]': 3,
'\nmovzx eax, byte ptr [rax]': 6,
'\nmovzx eax, al': 3,
'\nmov rdx, [rbp-8]': 1,
'\nadd rdx, 1': 1,
'\nmovzx edx, byte ptr [rdx]': 1,
'\nmovzx edx, dl': 1,
'\nshl edx, 8': 1,
'\nor edx, eax': 2,
'\nshl eax, 10h': 1,
'\nshl eax, 18h': 1,
'\nor eax, edx': 5,
'\nmov [rbp-298h], rdi': 1,
'\nmov [rbp-2A0h], rsi': 1,
'\nmov [rbp-2A8h], rdx\nmov dword ptr [rbp-150h], 0D76AA478h': 1,
'\nmov dword ptr [rbp-14Ch], 0E8C7B756h': 1,
'\nmov dword ptr [rbp-148h], 242070DBh': 1,
'\nmov dword ptr [rbp-144h], 0C1BDCEEEh': 1,
'\nmov dword ptr [rbp-140h], 0F57C0FAFh': 1,
'\nmov dword ptr [rbp-13Ch], 4787C62Ah': 1,
'\nmov dword ptr [rbp-138h], 0A8304613h': 1,
'\nmov dword ptr [rbp-134h], 0FD469501h': 1,
'\nmov dword ptr [rbp-130h], 698098D8h': 1,
'\nmov dword ptr [rbp-12Ch], 8B44F7AFh': 1,
'\nmov dword ptr [rbp-128h], 0FFFF5BB1h\nmov dword ptr [rbp-124h], 895CD7BEh': 1,
'\nmov dword ptr [rbp-120h], 6B901122h': 1,
'\nmov dword ptr [rbp-11Ch], 0FD987193h': 1,
'\nmov dword ptr [rbp-118h], 0A679438Eh': 1,
'\nmov dword ptr [rbp-114h], 49B40821h\nmov dword ptr [rbp-110h], 0F61E2562h': 1,
'\nmov dword ptr [rbp-10Ch], 0C040B340h\nmov dword ptr [rbp-108h], 265E5A51h': 1,
'\nmov dword ptr [rbp-104h], 0E9B6C7AAh': 1,
'\nmov dword ptr [rbp-100h], 0D62F105Dh': 1,
'\nmov dword ptr [rbp-0FCh], 2441453h\nmov dword ptr [rbp-0F8h], 0D8A1E681h': 1,
'\nmov dword ptr [rbp-0F4h], 0E7D3FBC8h': 1,
'\nmov dword ptr [rbp-0F0h], 21E1CDE6h': 1,
'\nmov dword ptr [rbp-0ECh], 0C33707D6h': 1,
'\nmov dword ptr [rbp-0E8h], 0F4D50D87h\nmov dword ptr [rbp-0E4h], 455A14EDh': 1,
'\nmov dword ptr [rbp-0E0h], 0A9E3E905h': 1,
'\nmov dword ptr [rbp-0DCh], 0FCEFA3F8h': 1,
'\nmov dword ptr [rbp-0D8h], 676F02D9h': 1,
'\nmov dword ptr [rbp-0D4h], 8D2A4C8Ah': 1,
'\nmov dword ptr [rbp-0D0h], 0FFFA3942h': 1,
'\nmov dword ptr [rbp-0CCh], 8771F681h': 1,
'\nmov dword ptr [rbp-0C8h], 6D9D6122h': 1,
'\nmov dword ptr [rbp-0C4h], 0FDE5380Ch': 1,
'\nmov dword ptr [rbp-0C0h], 0A4BEEA44h': 1,
'\nmov dword ptr [rbp-0BCh], 4BDECFA9h': 1,
'\nmov dword ptr [rbp-0B8h], 0F6BB4B60h': 1,
'\nmov dword ptr [rbp-0B4h], 0BEBFBC70h\nmov dword ptr [rbp-0B0h], 289B7EC6h': 1,
'\nmov dword ptr [rbp-0ACh], 0EAA127FAh': 1,
'\nmov dword ptr [rbp-0A8h], 0D4EF3085h': 1,
'\nmov dword ptr [rbp-0A4h], 4881D05h': 1,
'\nmov dword ptr [rbp-0A0h], 0D9D4D039h': 1,
'\nmov dword ptr [rbp-9Ch], 0E6DB99E5h': 1,
'\nmov dword ptr [rbp-98h], 1FA27CF8h': 1,
'\nmov dword ptr [rbp-94h], 0C4AC5665h': 1,
'\nmov dword ptr [rbp-90h], 0F4292244h': 1,
'\nmov dword ptr [rbp-8Ch], 432AFF97h': 1,
'\nmov dword ptr [rbp-88h], 0AB9423A7h\nmov dword ptr [rbp-84h], 0FC93A039h': 1,
'\nmov dword ptr [rbp-80h], 655B59C3h': 1,
'\nmov dword ptr [rbp-7Ch], 8F0CCC92h': 1,
'\nmov dword ptr [rbp-78h], 0FFEFF47Dh': 1,
'\nmov dword ptr [rbp-74h], 85845DD1h': 1,
'\nmov dword ptr [rbp-70h], 6FA87E4Fh': 1,
'\nmov dword ptr [rbp-6Ch], 0FE2CE6E0h': 1,
'\nmov dword ptr [rbp-68h], 0A3014314h': 1,
'\nmov dword ptr [rbp-64h], 4E0811A1h': 1,
'\nmov dword ptr [rbp-60h], 0F7537E82h': 1,
'\nmov dword ptr [rbp-5Ch], 0BD3AF235h': 1,
'\nmov dword ptr [rbp-58h], 2AD7D2BBh': 1,
'\nmov dword ptr [rbp-54h], 0EB86D391h': 1,
'\nmov dword ptr [rbp-250h], 7\nmov dword ptr [rbp-24Ch], 0Ch': 1,
'\nmov dword ptr [rbp-248h], 11h': 1,
'\nmov dword ptr [rbp-244h], 16h': 1,
'\nmov dword ptr [rbp-240h], 7': 1,
'\nmov dword ptr [rbp-23Ch], 0Ch': 1,
'\nmov dword ptr [rbp-238h], 11h': 1,
'\nmov dword ptr [rbp-234h], 16h': 1,
'\nmov dword ptr [rbp-230h], 7': 1,
'\nmov dword ptr [rbp-22Ch], 0Ch': 1,
'\nmov dword ptr [rbp-228h], 11h': 1,
'\nmov dword ptr [rbp-224h], 16h': 1,
'\nmov dword ptr [rbp-220h], 7': 1,
'\nmov dword ptr [rbp-21Ch], 0Ch': 1,
'\nmov dword ptr [rbp-218h], 11h': 1,
'\nmov dword ptr [rbp-214h], 16h': 1,
'\nmov dword ptr [rbp-210h], 5': 1,
'\nmov dword ptr [rbp-20Ch], 9': 1,
'\nmov dword ptr [rbp-208h], 0Eh': 1,
'\nmov dword ptr [rbp-204h], 14h': 1,
'\nmov dword ptr [rbp-200h], 5': 1,
'\nmov dword ptr [rbp-1FCh], 9': 1,
'\nmov dword ptr [rbp-1F8h], 0Eh': 1,
'\nmov dword ptr [rbp-1F4h], 14h': 1,
'\nmov dword ptr [rbp-1F0h], 5': 1,
'\nmov dword ptr [rbp-1ECh], 9': 1,
'\nmov dword ptr [rbp-1E8h], 0Eh': 1,
'\nmov dword ptr [rbp-1E4h], 14h': 1,
'\nmov dword ptr [rbp-1E0h], 5': 1,
'\nmov dword ptr [rbp-1DCh], 9\nmov dword ptr [rbp-1D8h], 0Eh': 1,
'\nmov dword ptr [rbp-1D4h], 14h': 1,
'\nmov dword ptr [rbp-1D0h], 4': 1,
'\nmov dword ptr [rbp-1CCh], 0Bh': 1,
'\nmov dword ptr [rbp-1C8h], 10h\nmov dword ptr [rbp-1C4h], 17h': 1,
'\nmov dword ptr [rbp-1C0h], 4': 1,
'\nmov dword ptr [rbp-1BCh], 0Bh': 1,
'\nmov dword ptr [rbp-1B8h], 10h': 1,
'\nmov dword ptr [rbp-1B4h], 17h': 1,
'\nmov dword ptr [rbp-1B0h], 4\nmov dword ptr [rbp-1ACh], 0Bh': 1,
'\nmov dword ptr [rbp-1A8h], 10h': 1,
'\nmov dword ptr [rbp-1A4h], 17h': 1,
'\nmov dword ptr [rbp-1A0h], 4': 1,
'\nmov dword ptr [rbp-19Ch], 0Bh': 1,
'\nmov dword ptr [rbp-198h], 10h': 1,
'\nmov dword ptr [rbp-194h], 17h': 1,
'\nmov dword ptr [rbp-190h], 6': 1,
'\nmov dword ptr [rbp-18Ch], 0Ah': 1,
'\nmov dword ptr [rbp-188h], 0Fh': 1,
'\nmov dword ptr [rbp-184h], 15h': 1,
'\nmov dword ptr [rbp-180h], 6': 1,
'\nmov dword ptr [rbp-17Ch], 0Ah': 1,
'\nmov dword ptr [rbp-178h], 0Fh': 1,
'\nmov dword ptr [rbp-174h], 15h': 1,
'\nmov dword ptr [rbp-170h], 6': 1,
'\nmov dword ptr [rbp-16Ch], 0Ah': 1,
'\nmov dword ptr [rbp-168h], 0Fh': 1,
'\nmov dword ptr [rbp-164h], 15h': 1,
'\nmov dword ptr [rbp-160h], 6\nmov dword ptr [rbp-15Ch], 0Ah': 1,
'\nmov dword ptr [rbp-158h], 0Fh': 1,
'\nmov dword ptr [rbp-154h], 15h': 1,
'\nmov qword ptr [rbp-48h], 0': 1,
'\nmov dword ptr [rbp-4], 67452301h': 1,
'\nmov dword ptr [rbp-8], 0EFCDAB89h': 1,
'\nmov dword ptr [rbp-0Ch], 98BADCFEh': 1,
'\nmov dword ptr [rbp-10h], 10325476h': 1,
'\nmov rax, [rbp-2A0h]': 2,
'\nadd rax, 1': 2,
'\nmov [rbp-18h], rax': 2,
'\njmp loc_1CB13': 1,
'\nadd qword ptr [rbp-18h], 1': 1,
'\nmov rax, [rbp-18h]': 11,
'\nand eax, 3Fh': 1,
"\ncmp rax, 38h ; '8'": 1,
'\njnz loc_1C3D6': 1,
'\nadd rax, 8': 2,
'\nmov rdi, rax': 8,
'\ncall _malloc': 2,
'\nmov [rbp-48h], rax': 1,
'\nmov rdx, [rbp-2A0h]': 2,
'\nmov rcx, [rbp-298h]': 1,
'\nmov rax, [rbp-48h]': 5,
'\nmov rsi, rcx': 2,
'\ncall _memcpy': 1,
'\nmov rdx, [rbp-48h]': 3,
'\nadd rax, rdx': 7,
'\nmov byte ptr [rax], 80h\nmov rax, [rbp-2A0h]': 1,
'\nmov [rbp-20h], rax': 1,
'\njmp near ptr unk_21415': 1,
'\nmov rax, [rbp-20h]': 31,
'\nmov byte ptr [rax], 0': 1,
'\nadd qword ptr [rbp-20h], 1': 1,
'\ncmp rax, [rbp-18h]': 2,
'\njb loc_20C96': 1,
'\nadd rdx, rax\nmov rax, [rbp-2A0h]': 1,
'\nshl eax, 3': 8,
'\nmov rsi, rdx': 5,
'\nmov edi, eax\ncall sub_14D1': 1,
'\nmov rax, [rbp-18h]\nlea rdx, [rax+4]': 1,
'\nshr rdx, 1Dh': 1,
'\nmov rsi, rax': 1,
'\nmov edi, edx': 1,
'\ncall sub_14D1': 5,
'\nmov qword ptr [rbp-20h], 0': 1,
'\njmp near ptr unk_341AA': 1,
'\nmov dword ptr [rbp-34h], 0': 2,
'\njmp near ptr unk_274DA': 1,
'\nmov eax, [rbp-34h]': 4,
'\nshl eax, 2': 2,
'\nmov edx, eax\nmov rax, [rbp-20h]': 1,
'\nadd rdx, rax\nmov rax, [rbp-48h]':3319,
'\nmov edx, [rbp-34h]': 4,
'\nmov [rbp+rdx*4-290h], eax': 1,
'\nadd dword ptr [rbp-34h], 1': 2,
'\ncmp dword ptr [rbp-34h], 0Fh': 2,
'\njbe loc_25451': 1,
'\nmov eax, [rbp-4]\nmov [rbp-24h], eax': 1,
'\nmov eax, [rbp-8]': 15,
'\nmov [rbp-28h], eax': 1,
'\nmov eax, [rbp-0Ch]': 7,
'\nmov [rbp-2Ch], eax': 2,
'\nmov eax, [rbp-10h]': 9,
'\nmov [rbp-30h], eax': 2,
'\njmp near ptr unk_321CC': 1,
'\nja near ptr unk_29D35': 1,
'\nmov eax, [rbp-28h]': 3,
'\nand eax, [rbp-2Ch]': 2,
'\nmov edx, eax\nmov eax, [rbp-28h]': 1,
'\nnot eax': 3,
'\nand eax, [rbp-30h]': 1,
'\nmov [rbp-38h], eax': 3,
'\nmov [rbp-3Ch], eax': 3,
'\njmp near ptr unk_2EA8D': 2,
'\ncmp dword ptr [rbp-34h], 1Fh': 1,
'\nja near ptr unk_2B894': 1,
'\nmov eax, [rbp-30h]\nand eax, [rbp-28h]': 1,
'\nmov eax, [rbp-30h]': 4,
'\nor eax, edx\nmov [rbp-38h], eax': 1,
'\nmov eax, edx': 3,
'\nadd eax, edx': 2,
'\nadd eax, 1': 1,
'\nand eax, 0Fh': 3,
'\nmov [rbp-3Ch], eax\njmp near ptr unk_2EA8D': 1,
"\ncmp dword ptr [rbp-34h], 2Fh ; '/'\nja near ptr unk_2DDB9": 1,
'\nmov eax, [rbp-28h]\nxor eax, [rbp-2Ch]': 1,
'\nxor eax, [rbp-30h]': 1,
'\nmov eax, edx\nadd eax, eax': 1,
'\nadd eax, 5': 1,
'\nor eax, [rbp-28h]': 1,
'\nxor eax, [rbp-2Ch]': 1,
'\nsub eax, edx': 1,
'\nmov [rbp-4Ch], eax': 1,
'\nmov eax, [rbp-2Ch]': 2,
'\nmov edx, [rbp-24h]': 1,
'\nmov eax, [rbp-38h]': 1,
'\nadd edx, eax': 2,
'\nmov eax, [rbp+rax*4-150h]': 1,
'\nadd edx, eax\nmov eax, [rbp-3Ch]': 1,
'\nmov eax, [rbp+rax*4-290h]\nadd edx, eax': 1,
'\nmov eax, [rbp+rax*4-250h]': 1,
'\nmov ecx, eax': 13,
'\nrol edx, cl': 1,
'\nadd [rbp-28h], eax': 1,
'\nmov eax, [rbp-4Ch]': 1,
'\nmov [rbp-24h], eax': 1,
"\ncmp dword ptr [rbp-34h], 3Fh ; '?'": 1,
'\njbe loc_2835A': 1,
'\nmov eax, [rbp-24h]': 1,
'\nadd [rbp-4], eax': 1,
'\nadd [rbp-8], eax': 1,
'\nadd [rbp-0Ch], eax': 1,
'\nadd [rbp-10h], eax': 1,
"\nadd qword ptr [rbp-20h], 40h ; '@'": 1,
'\njb loc_25366': 1,
'\ncall _free': 2,
'\nmov rdx, [rbp-2A8h]\nmov eax, [rbp-4]': 1,
'\nmov edi, eax': 4,
'\nmov rax, [rbp-2A8h]': 3,
'\nlea rdx, [rax+4]': 1,
'\nmov eax, [rbp-8]\nmov rsi, rdx': 1,
'\nlea rdx, [rax+8]': 1,
'\nlea rdx, [rax+0Ch]': 1,
'\nmov [rbp-28h], rdi': 2,
'\nmov [rbp-30h], rsi\nmov [rbp-38h], rdx': 1,
'\nmov [rbp-40h], rcx': 2,
'\nmov rax, [rbp-30h]': 3,
'\nshr rax, 1': 1,
'\nmov [rbp-10h], rax': 3,
'\nand eax, 1': 1,
'\ntest rax, rax': 2,
'\njnz loc_391C9': 1,
'\ncmp [rbp-40h], rax': 2,
'\njnb loc_394AA': 1,
'\nmov eax, 0FFFFFFFFh\njmp near ptr unk_4B216': 1,
'\nmov dword ptr [rbp-8], 0': 2,
'\njmp near ptr unk_4A233': 1,
'\nmov byte ptr [rbp-1], 0': 1,
'\nadd eax, eax': 3,
'\nmovsxd rdx, eax': 6,
'\nmov rax, [rbp-28h]': 3,
'\nmov [rbp-11h], al': 2,
'\ncdqe': 3,
'\nlea rdx, [rax+1]': 1,
'\nmov [rbp-12h], al': 1,
"\ncmp byte ptr [rbp-11h], 2Fh ; '/'": 1,
'\njle near ptr unk_3FFA3': 1,
"\ncmp byte ptr [rbp-11h], 39h ; '9'": 1,
'\njg near ptr unk_3FFA3': 1,
'\nmovzx eax, byte ptr [rbp-11h]': 3,
"\nsub eax, 30h ; '0'": 2,
'\nshl eax, 4': 3,
'\nmov [rbp-1], al': 7,
'\njmp near ptr unk_4269A': 2,
"\ncmp byte ptr [rbp-11h], 60h ; '`'": 1,
'\njle near ptr unk_40E2B': 1,
"\ncmp byte ptr [rbp-11h], 66h ; 'f'\njg near ptr unk_40E2B": 1,
"\nsub eax, 57h ; 'W'": 2,
'\nmov [rbp-1], al\njmp near ptr unk_4269A': 1,
"\ncmp byte ptr [rbp-11h], 40h ; '@'": 1,
'\njle near ptr unk_4185B': 1,
"\ncmp byte ptr [rbp-11h], 46h ; 'F'": 1,
'\njg near ptr unk_4185B': 1,
"\nmovzx eax, byte ptr [rbp-11h]\nsub eax, 37h ; '7'": 1,
'\nmov eax, 0FFFFFFFFh': 3,
'\njmp near ptr unk_4B216': 2,
"\ncmp byte ptr [rbp-12h], 2Fh ; '/'": 1,
'\njle near ptr unk_43C58': 1,
"\ncmp byte ptr [rbp-12h], 39h ; '9'\njg near ptr unk_43C58": 1,
'\nmovzx eax, byte ptr [rbp-12h]': 3,
'\nmovzx eax, byte ptr [rbp-1]': 7,
'\njmp near ptr unk_474D8': 3,
"\ncmp byte ptr [rbp-12h], 60h ; '`'": 1,
'\njle near ptr unk_45C9D': 1,
"\ncmp byte ptr [rbp-12h], 66h ; 'f'": 1,
'\njg near ptr unk_45C9D': 1,
"\ncmp byte ptr [rbp-12h], 40h ; '@'": 1,
'\njle near ptr unk_46EC3': 1,
"\ncmp byte ptr [rbp-12h], 46h ; 'F'": 1,
'\njg near ptr unk_46EC3': 1,
"\nsub eax, 37h ; '7'": 1,
'\nmov rax, [rbp-38h]': 2,
'\nadd rdx, rax': 4,
'\nmov [rdx], al': 2,
'\nadd dword ptr [rbp-8], 1': 2,
'\ncmp rax, [rbp-10h]': 1,
'\njb loc_3A3F8': 1,
'\nmov eax, 0': 2,
'\nmov [rbp-30h], rsi': 1,
'\nmov [rbp-38h], rdx': 1,
'\nmov rax, [rbp-30h]\nadd rax, rax': 1,
'\njnb loc_4D2E4': 1,
'\njmp near ptr unk_561A0': 1,
'\njmp near ptr unk_558F8': 1,
'\nshr al, 4': 1,
'\nmovzx eax, byte ptr [rbp-11h]\nand eax, 0Fh': 1,
'\nmov [rbp-2], al': 3,
'\ncmp byte ptr [rbp-1], 0': 1,
'\njs near ptr unk_4F8FB': 1,
'\ncmp byte ptr [rbp-1], 9': 2,
'\njg near ptr unk_4F8FB': 1,
"\nadd eax, 30h ; '0'": 2,
'\njmp near ptr unk_50964': 1,
'\njle near ptr unk_50964': 1,
'\ncmp byte ptr [rbp-1], 0Fh': 1,
'\njg near ptr unk_50964': 1,
"\nadd eax, 57h ; 'W'\nmov [rbp-1], al": 1,
'\ncmp byte ptr [rbp-2], 0': 1,
'\njs near ptr unk_53228': 1,
'\ncmp byte ptr [rbp-2], 9': 2,
'\njg near ptr unk_53228': 1,
'\nmovzx eax, byte ptr [rbp-2]': 3,
'\njmp near ptr unk_53D32': 1,
'\njle near ptr unk_53D32': 1,
'\ncmp byte ptr [rbp-2], 0Fh': 1,
'\njg near ptr unk_53D32': 1,
"\nadd eax, 57h ; 'W'": 1,
'\nmov [rdx], al\nmov eax, [rbp-8]\nadd eax, eax\ncdqe': 1,
'\nlea rdx, [rax+1]\nmov rax, [rbp-38h]': 1,
'\ncmp rax, [rbp-30h]': 1,
'\njb loc_4D4C8': 1,
'\nmov [rbp-18h], rdi': 1,
'\nmov [rbp-20h], rsi': 1,
"\nmov dword ptr [rbp-4], 20h ; ' '\nmov dword ptr [rbp-8], 0\njmp near ptr unk_7BE51": 1,
'\nsub dword ptr [rbp-8], 61C88647h': 1,
'\nmov eax, [rax+1Ch]': 2,
'\nmov [rbp-0Ch], eax': 5,
'\nmov eax, [rax+4]': 1,
'\nmov [rbp-10h], eax': 6,
'\nmov edx, [rax]': 8,
'\nshr eax, 5': 6,
'\nmov ecx, eax\nmov eax, [rbp-0Ch]': 1,
'\nmov esi, ecx': 8,
'\nxor esi, eax': 12,
'\nxor eax, [rbp-10h]': 6,
'\nmov eax, [rax]': 9,
'\nadd eax, ecx': 8,
'\nxor eax, esi': 1,
'\nmov [rax], edx': 6,
'\nmov eax, [rax+8]': 1,
'\nadd rax, 4': 2,
'\nmov ecx, esi\nmov rax, [rbp-20h]': 1,
'\nadd rax, 4\nadd edx, ecx': 1,
'\nmov eax, [rax+4]\nmov [rbp-0Ch], eax': 1,
'\nmov eax, [rax+0Ch]': 2,
'\nmov [rbp-10h], eax\nmov rax, [rbp-20h]\nadd rax, 8': 1,
'\nmov ecx, esi': 6,
'\nmov rax, [rbp-20h]\nadd rax, 8\nadd edx, ecx': 1,
'\nmov eax, [rax+8]\nmov [rbp-0Ch], eax': 1,
'\nmov eax, [rax+10h]\nmov [rbp-10h], eax': 1,
'\nadd rax, 0Ch': 3,
'\nshr eax, 5\nmov ecx, eax': 1,
'\nmov eax, [rbp-8]\nxor eax, [rbp-10h]': 1,
'\nadd edx, ecx': 2,
'\nmov eax, [rax+14h]': 2,
'\nadd rax, 10h': 4,
'\nmov eax, [rbp-0Ch]\nshl eax, 3': 2,
'\nxor eax, [rbp-10h]\nmov ecx, eax': 1,
'\nmov eax, [rax+10h]': 1,
'\nmov eax, [rax+18h]': 2,
'\nadd rax, 14h': 2,
'\nadd rax, 14h\nmov eax, [rax]': 1,
'\nxor ecx, eax': 3,
'\nadd edx, ecx\nmov [rax], edx': 2,
'\nmov [rbp-0Ch], eax\nmov rax, [rbp-20h]': 1,
'\nadd rax, 18h': 2,
'\nmov ecx, eax\nmov rax, [rbp-18h]': 1,
'\nadd rax, 18h\nadd edx, ecx': 1,
'\nadd rax, 1Ch': 3,
'\nmov eax, [rbp-10h]\nshr eax, 5': 1,
'\nlea edx, [rax-1]': 1,
'\nmov [rbp-4], edx': 1,
'\ntest eax, eax': 1,
'\njnz loc_568A8': 1,
'\nmov [rbp-48h], rdi': 1,
'\nmov [rbp-50h], rsi': 1,
'\nmov [rbp-58h], rdx': 1,
'\nmov [rbp-60h], rcx': 1,
'\nmov rax, [rbp-50h]\nshr rax, 1': 1,
'\nlea rdx, [rbp-40h]': 1,
'\nmov rcx, [rbp-50h]': 1,
'\ncall sub_86FA': 2,
'\nmov rcx, [rbp-10h]': 1,
'\nmov rdx, [rbp-18h]': 1,
'\nmov rsi, [rbp-50h]': 1,
'\ncall sub_37064': 1,
'\nlea rax, [rbp-40h]': 2,
'\nmov rsi, [rbp-10h]': 1,
'\nmov rcx, [rbp-18h]': 1,
'\nmov rdx, rax': 1,
'\nmov rdi, rcx': 1,
'\nmov rax, [rbp-60h]': 2,
'\nand eax, 1Fh': 1,
'\njnz near ptr unk_858C5': 1,
'\nmov dword ptr [rbp-4], 0': 1,
'\njmp loc_843A5': 1,
'\nshl eax, 5': 1,
'\nmov rax, [rbp-58h]': 1,
'\nmov rdi, rax\ncall sub_561A2': 1,
'\nadd dword ptr [rbp-4], 1': 1,
'\nshr rax, 5': 1,
'\ncmp rdx, rax': 1,
'\njb loc_83115': 1,
'\njmp loc_85B13': 1
}

将花指令块nop掉

1
2
3
4
5
6
for start, end, ins in blocks:
if (ins in is_junk_ins):
if (type(is_junk_ins[ins]) is bool and is_junk_ins[ins] == True):
for ea in range(start, end):
idc.patch_byte(ea, 0x90)
idc.create_insn(ea)

保存patch检查程序能否正常运行(只要不段错误基本就没问题)

尝试反编译,提示栈帧过大

把2M的栈帧手动减小到0x1000,按U删掉函数,按P重新创建函数

到这一步,成功去花,接下来分析加密算法即可,回到main函数

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
size_t v3; // rax
unsigned __int64 v5; // [rsp+0h] [rbp-E0h]
char s[64]; // [rsp+10h] [rbp-D0h] BYREF
char v7[136]; // [rsp+50h] [rbp-90h] BYREF
unsigned __int64 v8; // [rsp+D8h] [rbp-8h]

v8 = __readfsqword(0x28u);
memset(s, 0, sizeof(s));
memset(v7, 0, 0x80uLL);
printf("Give me your secret:");
__isoc99_scanf("%63s", s);
v5 = strlen(s);
s[v5] = 0;
if ( (v5 & 0xF) != 0 )
v5 = 16 * ((v5 >> 4) + 1);
sub_4B587((__int64)s, v5, (__int64)v7, 0x80uLL);
v3 = strlen(::s);
sub_7CBD5(::s, v3, (__int64)v7, 0x80uLL);
if ( (unsigned int)sub_11C9(v7, 128LL) )
puts(&byte_86228);
else
puts(&byte_86250);
return 0LL;
}

计算输入字符串长度,16字节补齐(0填充)

1
2
3
4
v5 = strlen(s);
s[v5] = 0;
if ( (v5 & 0xF) != 0 )
v5 = 16 * ((v5 >> 4) + 1);

sub_4B587将输入的flag转换为16进制字符串(动调看函数运行后的结果也很容易看出来)

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
__int64 __fastcall sub_4B587(__int64 a1, unsigned __int64 a2, __int64 a3, unsigned __int64 a4)
{
unsigned __int8 v5; // [rsp+FEFh] [rbp-11h]
int i; // [rsp+FF8h] [rbp-8h]
char v7; // [rsp+FFEh] [rbp-2h]
char v8; // [rsp+FFEh] [rbp-2h]
char v9; // [rsp+FFFh] [rbp-1h]
char v10; // [rsp+FFFh] [rbp-1h]

if ( a4 < a2 )
return 0xFFFFFFFFLL;
for ( i = 0; i < a2; ++i )
{
v5 = *(_BYTE *)(i + a1);
v9 = v5 >> 4;
v7 = v5 & 0xF;
if ( (unsigned __int8)(v5 >> 4) > 9u )
v10 = v9 + 'W';
else
v10 = v9 + '0';
if ( (v5 & 0xFu) > 9 )
v8 = v7 + 87;
else
v8 = v7 + 48;
*(_BYTE *)(a3 + 2 * i) = v10;
*(_BYTE *)(a3 + 2 * i + 1LL) = v8;
}
return 0LL;
}

sub_11C9已经确定过是校验函数,那么sub_7CBD5自然就是加密函数

第一个参数和第二个参数猜测是密钥及密钥长度

1
2
v3 = strlen(::s);
sub_7CBD5(::s, v3, (__int64)v7, 0x80uLL);

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
unsigned __int64 __fastcall encrypt(const void *key, size_t key_len, unsigned __int8 *data, unsigned __int64 data_len)
{
unsigned __int64 result; // rax
int v7[4]; // [rsp+FC0h] [rbp-40h] BYREF
int v8[4]; // [rsp+FD0h] [rbp-30h] BYREF
void *ptr; // [rsp+FE8h] [rbp-18h]
size_t size; // [rsp+FF0h] [rbp-10h]
int i; // [rsp+FFCh] [rbp-4h]

size = key_len >> 1;
ptr = malloc(key_len >> 1);
sub_86FA(key, key_len, (__int64)v7);
sub_37064((__int64)key, key_len, (__int64)ptr, size);
sub_86FA(ptr, size, (__int64)v8);
free(ptr);
result = data_len & 0x1F;
if ( (data_len & 0x1F) == 0 )
{
for ( i = 0; ; ++i )
{
result = data_len >> 5;
if ( i >= data_len >> 5 )
break;
sub_561A2(v7, &data[32 * i]);
}
}
return result;
}

sub_86FAsub_37064都是对密钥进行操作,与输入的字符串无关,因此没有必要逆向这两个函数的算法,直接动调dump值

sub_86FA

其实就是md5

sub_37064

显然是把十六进制字符串转换为字节数据

然后再算一遍md5,现在就得到了密钥处理后的数据

1
2
3
4
{0x9a, 0xca, 0xb5, 0xa3, 0x1d, 0xb6, 0x6a, 0x2a,
0xc6, 0x98, 0xaf, 0x42, 0x99, 0xc1, 0xb2, 0x06,
0xe4, 0x9a, 0x6d, 0xe6, 0x88, 0xe9, 0x3c, 0xee,
0xcb, 0xb7, 0x53, 0x5f, 0x56, 0x2d, 0x3b, 0x1f}

接下来分组进行加密操作,每次32字节

1
2
3
4
5
6
7
8
9
10
if ( (data_len & 0x1F) == 0 )
{
for ( i = 0; ; ++i )
{
result = data_len >> 5;
if ( i >= data_len >> 5 )
break;
sub_561A2(v7, &data[32 * i]);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall sub_561A2(_DWORD *a1, _DWORD *a2)
{
__int64 result; // rax
int i; // [rsp+FF8h] [rbp-8h]
unsigned int v4; // [rsp+FFCh] [rbp-4h]

v4 = 32;
for ( i = 0; ; a2[7] += ((*a2 ^ i) + a1[7]) ^ (8 * a2[6]) ^ (*a2 >> 5) )
{
result = v4--;
if ( !(_DWORD)result )
break;
i -= 0x61C88647;
*a2 += (8 * a2[7]) ^ (a2[1] >> 5) ^ ((a2[1] ^ i) + *a1);
a2[1] += ((a2[2] ^ i) + a1[1]) ^ (8 * *a2) ^ (a2[2] >> 5);
a2[2] += ((a2[3] ^ i) + a1[2]) ^ (8 * a2[1]) ^ (a2[3] >> 5);
a2[3] += ((a2[4] ^ i) + a1[3]) ^ (8 * a2[2]) ^ (a2[4] >> 5);
a2[4] += ((a2[5] ^ i) + a1[4]) ^ (8 * a2[3]) ^ (a2[5] >> 5);
a2[5] += ((a2[6] ^ i) + a1[5]) ^ (8 * a2[4]) ^ (a2[6] >> 5);
a2[6] += ((a2[7] ^ i) + a1[6]) ^ (8 * a2[5]) ^ (a2[7] >> 5);
}
return result;
}

变种xxtea

解密代码如下

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

#define DELTA 0x9e3779b9

void xxtea_decrypt(uint32_t *key, uint32_t *data)
{
uint32_t x, y = data[0], q = 32, sum = q * DELTA;
uint8_t p;
while (sum != 0)
{

for (p = 7; p > 0; p--)
{
x = data[p - 1];
y = data[p] -= ((y >> 5) ^ (x << 3) ^ ((sum ^ y) + (key[p])));
}

x = data[7];
y = data[0] -= ((y >> 5) ^ (x << 3) ^ ((sum ^ y) + (key[p])));
sum -= DELTA;
}
}

int from_hex(uint8_t *src, size_t src_size, uint8_t *dst, size_t dst_size)
{
size_t len = src_size / 2;
uint8_t value;
char c1, c2;
if ((src_size % 2 != 0) || (len > dst_size))
{
return -1;
}
for (int i = 0; i < len; i++)
{
value = 0;
c1 = src[i * 2];
c2 = src[i * 2 + 1];
if (c1 >= '0' && c1 <= '9')
{
value = (c1 - '0') << 4;
}
else if (c1 >= 'a' && c1 <= 'f')
{
value = (c1 - 'a' + 10) << 4;
}
else if (c1 >= 'A' && c1 <= 'F')
{
value = (c1 - 'A' + 10) << 4;
}
else
{
return -1;
}
if (c2 >= '0' && c2 <= '9')
{
value |= c2 - '0';
}
else if (c2 >= 'a' && c2 <= 'f')
{
value |= c2 - 'a' + 10;
}
else if (c2 >= 'A' && c2 <= 'F')
{
value |= c2 - 'A' + 10;
}
else
{
return -1;
}
dst[i] = value;
}
return 0;
}

int main()
{
uint8_t key[32] = {
0x9a, 0xca, 0xb5, 0xa3, 0x1d, 0xb6, 0x6a, 0x2a,
0xc6, 0x98, 0xaf, 0x42, 0x99, 0xc1, 0xb2, 0x06,
0xe4, 0x9a, 0x6d, 0xe6, 0x88, 0xe9, 0x3c, 0xee,
0xcb, 0xb7, 0x53, 0x5f, 0x56, 0x2d, 0x3b, 0x1f};
uint64_t ans[] = {
0x9741fd0de69a44f8, 0x8b7ead8a7653dcb7, 0xec2bfd3f4b6c8358, 0xa8e3399b1230fd4c,
0x22940d53958bc645, 0xb39ce56dc272c3c4, 0xbda544631e4af3e4, 0x1dc049365ef29271,
0x702e0abc7bc99541, 0x0c23569ae9aeef96, 0xef3a16a05cd5ed13, 0x42f70ca508156aa2,
0xa961263c1f455e4d, 0x152355723bb4f6c1, 0x36d8c5c2a4a15d16, 0x75cb5242177c870a, 0};
char buf[128];

for (int i = 0; i < 128; i += 32)
{
xxtea_decrypt((uint32_t *)key, (uint32_t *)((uint8_t *)ans+i));
}

from_hex((uint8_t *)ans, strlen((uint8_t *)ans), buf, sizeof(buf));
puts(buf);

return 0;
}

正确flag:L3HSEC{Boku_to_keiyaku_shite_ctfer_ni_natte_yo!}

AWayOut

预期解法

IDA能够正确识别并反编译main函数

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
int v3; // eax
int v4; // eax
unsigned int v6; // [rsp+4h] [rbp-11Ch]
unsigned int v7; // [rsp+8h] [rbp-118h]
int v8; // [rsp+Ch] [rbp-114h]
char s[264]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v10; // [rsp+118h] [rbp-8h]

v10 = __readfsqword(0x28u);
v6 = 1;
v7 = 1;
v8 = 0;
puts("find a way out!");
_isoc99_scanf("%255s", s);
while ( v8 <= 255 && (v6 != 30 || v7 != 30) )
{
v3 = v8++;
v4 = s[v3];
if ( v4 == 'l' )
{
++v7;
}
else
{
if ( v4 > 'l' )
goto LABEL_19;
switch ( v4 )
{
case 'k':
--v6;
break;
case 'h':
--v7;
break;
case 'j':
++v6;
break;
default:
goto LABEL_19;
}
}
if ( (unsigned int)sub_17DB(v6, v7, v7) )
goto LABEL_19;
}
if ( strlen(s) == v8 && v8 == 84 )
{
puts("you found the best way!");
puts("L3HSEC{md5(your way)}");
return 0LL;
}
LABEL_19:
puts("plz try again!");
return 0LL;
}

很明显的黑盒迷宫,hjkl控制方向,sub_17DB检查坐标是否可走,坐标可以走返回0,继续检查下一步,不可走die掉

sub_17DB被vmp虚拟化,不太可能直接逆向,只能通过该函数的输入和输出判断某坐标是否可以走

因此有如下两种解法:

  • 通过注入、patch的方式手动调用sub_17DB函数,通过修改参数遍历迷宫所有坐标

  • patch主函数,记录sub_17DB返回不为0时所走的步数,如果die掉时走了n步,则输入的前n步所经过的坐标都是可走的

先在gdb中动调调用sub_17DB,检查解法一是否可行

随意对一个函数断点,例如write(为什么选write不选puts呢?因为程序中有导入puts,可能存在保护)

程序跑起来之后在write断下,修改rdi、rsi寄存器为坐标,修改rip寄存器为sub_17DB

删掉write断点继续运行程序,正常情况下应该能在write调用的返回位置断下,查看rax获取返回值,但是运行后程序提示FILE CORRUPTED!!!并直接退出,说明函数内应该存在保护,无法手动调用sub_17DB(在上面的操作中,并没有对程序进行修改,那么程序是如何检测到上面的操作的呢?)

接下来继续使用gdb动调测试解法二

断点后运行发现也被程序检测到了,猜测是有软件断点检查,换用硬件断点

使用硬件断点后,程序正常运行,并且能够获取函数的返回值

经过上面的测试,发现程序有软件断点检测(软件断点的原理?patch是否会被检测?),只能用硬件断点来监测函数的输入输出

监测上面函数的输出比较复杂,可以在程序退出返回时下硬件断点,读取走的步数

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
#!/usr/bin/env python

from pwn import *
import os

context.terminal = ["/usr/bin/bash", "-c"]


def maze_walk(maze_path:str):
maze_len = 0
maze_win = False
p = process("./AWayOut")

base = p.libs()[os.path.abspath(p.executable)]

gdbscript = f"""
hb *({base}+0x13BF)
c
call printf(\"[GDB] %d\\n\", *(int *)($rbp-0x114))
c
"""

_, pgdb = gdb.attach(p, gdbscript=gdbscript, api=True)

p.sendline(maze_path)
for line in p.recvall().decode().split("\n"):
if ("[GDB]" in line):
maze_len = int(line.split(" ")[1])
if ("you found the best way!" in line):
maze_win = True
if ("plz try again!" in line):
maze_win = False

return (maze_len, maze_win)

print(maze_walk("lllllllllll"))

现在已经可以走迷宫了,直接上dfs(由于程序比较慢,可以加上多线程)

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
#!/usr/bin/env python

from pwn import *
import os
from threading import Thread

def maze_walk(maze_path:str):
context.terminal = ["/usr/bin/bash", "-c"]
maze_len = 0
maze_win = False
p = process("./AWayOut")

base = p.libs()[os.path.abspath(p.executable)]

gdbscript = f"""
hb *({base}+0x13BF)
c
call printf(\"[GDB] %d\\n\", *(int *)($rbp-0x114))
c
"""

_, pgdb = gdb.attach(p, gdbscript=gdbscript, api=True)

p.sendline(maze_path)
for line in p.recvall().decode().split("\n"):
if ("[GDB]" in line):
maze_len = int(line.split(" ")[1])
if ("you found the best way!" in line):
maze_win = True
if ("plz try again!" in line):
maze_win = False

return (maze_len, maze_win)

def maze_dfs(x, y, maze_len, maze_path:str, maze_walked:set, dfs_ret:list):
print(f"{maze_len} {maze_path}")
maze_walked.add((x, y))

if (maze_len > 90):
return

if (len(maze_path) != 0):
new_len, win = maze_walk(maze_path)
if (win == True):
dfs_ret.append(maze_path)
return
if (new_len != maze_len):
return

threads = [None for _ in range(4)]

if ((x-1, y) not in maze_walked):
new_path = maze_path + "h"
new_walked = set()
for tmp_x, tmp_y in maze_walked:
new_walked.add((tmp_x, tmp_y))
threads[0] = Thread(target=maze_dfs, args=[x-1, y, maze_len+1, new_path, new_walked, dfs_ret])
threads[0].start()

if ((x, y+1) not in maze_walked):
new_path = maze_path + "j"
new_walked = set()
for tmp_x, tmp_y in maze_walked:
new_walked.add((tmp_x, tmp_y))
threads[1] = Thread(target=maze_dfs, args=[x, y+1, maze_len+1, new_path, new_walked, dfs_ret])
threads[1].start()

if ((x, y-1) not in maze_walked):
new_path = maze_path + "k"
new_walked = set()
for tmp_x, tmp_y in maze_walked:
new_walked.add((tmp_x, tmp_y))
threads[2] = Thread(target=maze_dfs, args=[x, y-1, maze_len+1, new_path, new_walked, dfs_ret])
threads[2].start()

if ((x+1, y) not in maze_walked):
new_path = maze_path + "l"
new_walked = set()
for tmp_x, tmp_y in maze_walked:
new_walked.add((tmp_x, tmp_y))
threads[3] = Thread(target=maze_dfs, args=[x+1, y, maze_len+1, new_path, new_walked, dfs_ret])
threads[3].start()

for thread in threads:
if (thread != None):
thread.join()

return

maze_ret = []
maze_dfs(1, 1, 1, "", set(), maze_ret)
print(f"[right path] {maze_ret}")

正确路径: lljjljjhhjjjjjllkkklllkklllljllljjjjjjjlllljjjlllllklllllljjjhhhjjjljjllljjhhjjljjlj

正确flag: L3HSEC{b078e5b6cc04f827fe807a39631b240f}

非预期解法

迷宫每走一步调用一次sub_17DB,如果是不可走的坐标就直接die掉,如果是可走的坐标才会检查下一步

由于sub_17DB函数被虚拟化指令量极大,运行的时间消耗非常大(50ms+),因此可以通过记录程序运行的时间来dfs遍历迷宫

AntiDebuggerAllInOne

题目附件分为linux服务端与windows客户端两个部分,使用网络通信

client.exe

32位exe文件

点击按钮后获取输入框内容,调用sub_401000进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
LRESULT __stdcall sub_401300(HWND hWndParent, UINT Msg, WPARAM wParam, LPARAM lParam)
{
WCHAR String[256]; // [esp+4h] [ebp-804h] BYREF
WCHAR v6[256]; // [esp+204h] [ebp-604h] BYREF
WCHAR v7[256]; // [esp+404h] [ebp-404h] BYREF
WCHAR v8[256]; // [esp+604h] [ebp-204h] BYREF

if ( Msg > 0x10 )
{
if ( Msg != 273 )
return DefWindowProcW(hWndParent, Msg, wParam, lParam);
if ( (unsigned __int16)wParam == 104 )
{
GetWindowTextW(hWnd, String, 256);
GetWindowTextW(dword_4056C8, v6, 256);
GetWindowTextW(dword_4056D8, v7, 256);
GetWindowTextW(dword_4056D4, v8, 256);
sub_401000(hWndParent, String, v6, v7, v8);
}
}

sub_401000将输入框的宽字符转换位ASCII字符,将输入字符串格式化拷贝到buf中,sub_401780(buf)对buf进行操作,随后创建socket使用UDP将buf发送到服务端,从服务端接收数据并使用MessageBoxA显示

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
int __cdecl sub_401000(HWND hWnd, LPCWCH lpWideCharStr, wchar_t *String, LPCWCH a4, LPCWCH a5)
{
__int16 v6; // [esp+0h] [ebp-B14h]
SOCKET s; // [esp+4h] [ebp-B10h]
struct WSAData WSAData; // [esp+8h] [ebp-B0Ch] BYREF
char buf[96]; // [esp+198h] [ebp-97Ch] BYREF
struct sockaddr to; // [esp+1F8h] [ebp-91Ch] BYREF
int fromlen; // [esp+208h] [ebp-90Ch] BYREF
char optval[4]; // [esp+20Ch] [ebp-908h] BYREF
CHAR v13[256]; // [esp+210h] [ebp-904h] BYREF
CHAR v14[256]; // [esp+310h] [ebp-804h] BYREF
CHAR v15[256]; // [esp+410h] [ebp-704h] BYREF
CHAR Buffer[256]; // [esp+510h] [ebp-604h] BYREF
CHAR OutputString[256]; // [esp+610h] [ebp-504h] BYREF
CHAR MultiByteStr[256]; // [esp+710h] [ebp-404h] BYREF
CHAR v19[256]; // [esp+810h] [ebp-304h] BYREF
CHAR v20[256]; // [esp+910h] [ebp-204h] BYREF
CHAR Text[256]; // [esp+A10h] [ebp-104h] BYREF

v6 = wtoi(String);
WideCharToMultiByte(0, 0, lpWideCharStr, 256, MultiByteStr, 256, 0, 0);
WideCharToMultiByte(0, 0, a4, 256, v20, 256, 0, 0);
WideCharToMultiByte(0, 0, a5, 256, v19, 256, 0, 0);
sub_4025A0(OutputString, Format, (char)MultiByteStr);
OutputDebugStringA(OutputString);
sub_4025A0(Buffer, aPortD, v6);
OutputDebugStringA(Buffer);
sub_4025A0(v15, aUsernameS, (char)v20);
OutputDebugStringA(v15);
sub_4025A0(v14, aPasswordS, (char)v19);
OutputDebugStringA(v14);
memset(buf, 0, sizeof(buf));
sub_402560(buf, 0x10u, aS, (char)v20);
sub_402560(&buf[16], 0x40u, aS_0, (char)v19);
sub_401780(buf);
fromlen = 16;
WSAStartup(0x202u, &WSAData);
s = socket(2, 2, 17);
*(_DWORD *)optval = 5000;
setsockopt(s, 0xFFFF, 4102, optval, 4);
to.sa_family = 2;
*(_DWORD *)&to.sa_data[2] = inet_addr(MultiByteStr);
*(_WORD *)to.sa_data = htons(v6);
sendto(s, buf, 96, 0, &to, 16);
memset(Text, 0, sizeof(Text));
if ( recvfrom(s, Text, 256, 0, &to, &fromlen) == -1 )
sub_4025A0(Text, aAuthServerErro, v6);
closesocket(s);
WSACleanup();
sub_4025A0(v13, aResultS, (char)Text);
OutputDebugStringA(v13);
return MessageBoxA(hWnd, Text, Caption, 0);
}

代码中有很多的OutputDebugStringA,该API用于输出调试信息,使用DebugView监听调试信息

调试信息中打印了输入框的内容,并且存在一条提示密码需要为UUID格式

动调观察以下buf的内容,前16个字节为用户名,接下来64字节为密码,buf总长度96字节,剩余16字节目前为空

接着分析sub_181780对buf的操作

1
2
3
4
5
6
7
8
int __cdecl sub_181780(int a1)
{
((void (__cdecl *)(int))sub_1817D0)(a1);
sub_181B40(a1);
((void (__cdecl *)(int))sub_181CD0)(a1);
((void (__cdecl *)(int))sub_181DE0)(a1);
return ((int (__cdecl *)(int))sub_1824D0)(a1);
}

其中调用了5个函数依次对buf进行操作,这5个函数全部都被vmp虚拟化了,不能直接进行分析,尝试通过动调调试观察每个函数执行前后缓冲区的变化

下面的分析中有很多的反调试,使用ScyllaHide和SharpOD插件可以很轻松的绕过,但就如题目描述所说,不建议这样做,建议做题和复现时先手动绕过一遍反调试,绕过成功后再开启工具相应的反调试功能以节约时间

  • func1

    func1断下后单步跳过,程序直接退出,查看DebugVier发现存在提示IsDebuggerPresent,根据该提示可猜测是调试器被IsDebuggerPresent API检测到了

    IsDebuggerPresent用于检测调试器,调试器存在时,返回值为1,调试器不存在是返回0

    对IsDebuggerPresent下断点,断下后将返回值eax修改为0

    成功绕过检查

    观察buf内容,发现func1所做的操作是将uuid格式的密码解码为16进制保存在buf后16字节中

    0x82, 0x84, 0xC9, 0xC4, 0xE7, 0x78, 0x43, 0x0F, 0xA7, 0x77, 0xEB, 0x7E, 0xF2, 0x2D, 0xF1, 0x49

  • func2

    func2断下后单步跳过,和func1一样,程序退出,DebugView存在提示

    NtGlobalFlag是PEB中的一个字段,从ctfwiki中可以知道检测方式如下

    1
    2
    3
    4
    5
    mov eax, fs:[30h] ;Process Environment Block
    mov al, [eax+68h] ;NtGlobalFlag
    and al, 70h
    cmp al, 70h
    je being_debugged

    该检测方法和IsDebuggerPresent很相似,都是从PEB中获取调试信息,直接修改PEB的内容就能绕过

    将上面两处内容修改为0(第一处是IsDebuggerPresent的判定,改了之后就不用再修改返回值)

    成功绕过检查

    0xF1, 0xA7, 0x84, 0xE7, 0x78, 0x2D, 0x49, 0x82, 0xC9, 0xEB, 0x43, 0x0F, 0x77, 0xC4, 0x7E, 0xF2

    分析处理前后两组数据,可以观察到第一组中的所有数据都在第二组中存在,只是位置发生了交换

    1
    2
    {0x82, 0x84, 0xC9, 0xC4, 0xE7, 0x78, 0x43, 0x0F, 0xA7, 0x77, 0xEB, 0x7E, 0xF2, 0x2D, 0xF1, 0x49}
    {0xF1, 0xA7, 0x84, 0xE7, 0x78, 0x2D, 0x49, 0x82, 0xC9, 0xEB, 0x43, 0x0F, 0x77, 0xC4, 0x7E, 0xF2}

    为了更好的观察数据变化情况,更换一组更有辨识度的密码00010203-0405-0607-0809-0a0b0c0d0e0f

    1
    2
    {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
    {0x0E, 0x08, 0x01, 0x04, 0x05, 0x0D, 0x0F, 0x00, 0x02, 0x0A, 0x06, 0x07, 0x09, 0x03, 0x0B, 0x0C}

    分析获得置换盒{0x0E, 0x08, 0x01, 0x04, 0x05, 0x0D, 0x0F, 0x00, 0x02, 0x0A, 0x06, 0x07, 0x09, 0x03, 0x0B, 0x0C}

  • func3

    DebugView提示func3存在CheckRemoteDebuggerPresent保护,同样的,直接断点该函数,改返回值为0(注意该返回值是通过参数返回,在堆栈中,不是eax)

    func3运行后

    两组数据对比

    1
    2
    {0x0E, 0x08, 0x01, 0x04, 0x05, 0x0D, 0x0F, 0x00, 0x02, 0x0A, 0x06, 0x07, 0x09, 0x03, 0x0B, 0x0C}
    {0x81, 0xC1, 0x00, 0x20, 0x80, 0xA1, 0xA1, 0xE0, 0x00, 0x41, 0x40, 0xC0, 0xE1, 0x20, 0x61, 0x61}
    1
    2
    {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
    {0xE0, 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE1, 0x01, 0x21, 0x41, 0x61, 0x81, 0xA1, 0xC1}

    分析发现func3是将数据整体循环右移3位

  • func4

    DebugView提示func4存在NtQueryInformationProcess保护,根据ctfwiki可以知道,该API可以查询3个与调试相关的内容0x07 DebugPort0x1e DebugObjectHandle0x1f DebugFlags

    同样的,断点改返回值(注意该API的返回指令并非反汇编识别出来的ret)

    0x07 DebugPort

    把返回值-1修改为0

    0x1e DebugObjectHandle

    把返回值修改为NULL

    0x1f DebugFlags

    把返回值修改为TRUE

    成功绕过检查

    1
    2
    {0x81, 0xC1, 0x00, 0x20, 0x80, 0xA1, 0xA1, 0xE0, 0x00, 0x41, 0x40, 0xC0, 0xE1, 0x20, 0x61, 0x61}
    {0xB5, 0x7E, 0x54, 0x3D, 0x6D, 0x8A, 0x8A, 0xEF, 0x54, 0x48, 0xAF, 0xCA, 0xF6, 0x3D, 0x45, 0x45}

    从上面的两组数据可以发现0x00 -> 0x540x20 -> 0x3d0xa1 -> 0x8a0x61 -> 0x45,相同的值变换后的值也相同,猜测可能是代换?

    多跑几组数据(不用返回重启程序,直接改EIP)

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

    256个字符跑完,得到完整的代换表

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

    func5运行之后程序退出,无提示,猜测func5内也存在反调试手段

    在ctfwiki中查看ZwSetInformationThread的相关介绍,发现和当前的情况相似,断点查看是否调用该api

    发现确实是该api进行的反调试,直接设置EIP到ret处,成功绕过

    1
    2
    {0xB5, 0x7E, 0x54, 0x3D, 0x6D, 0x8A, 0x8A, 0xEF, 0x54, 0x48, 0xAF, 0xCA, 0xF6, 0x3D, 0x45, 0x45}
    {0xAD, 0xF3, 0xA2, 0xE9, 0x6B, 0x54, 0x54, 0x7F, 0xA2, 0x42, 0x7D, 0x56, 0xB7, 0xE9, 0x2A, 0x2A}
    1
    2
    {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
    {0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x78}

    观察发现这一次是整体循环左移3位

根据上面5个函数的分析,可以还原处sub_181780函数的加密逻辑,逆向求解破解client程序

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
#!/usr/bin/env python

data = []

# 循环右移3位
tmp_data = b''
for d in data:
tmp = (d >> 3) | (d << 5);
tmp &= 0xFF
tmp_data += tmp.to_bytes(1)
data = tmp_data
print("[5]:", data.hex())

# 代换
sbox= [84, 74, 31, 134, 94, 166, 0, 163, 111, 170, 254, 149, 208, 140, 211, 236, 240, 13, 47, 16, 77, 65, 81, 71, 244, 146, 210, 21, 76, 235, 34, 247, 61, 17, 204, 216, 187, 53, 132, 110, 103, 119, 54, 57, 7, 183, 79, 159, 19, 214, 249, 59, 36, 3, 225, 231, 157, 70, 2, 215, 117, 25, 131, 150, 175, 72, 5, 184, 42, 165, 50, 162, 218, 100, 22, 58, 90, 200, 88, 147, 1, 179, 155, 29, 127, 52, 148, 242, 101, 135, 207, 133, 248, 125, 39, 223, 24, 69, 153, 171, 226, 122, 191, 197, 98, 10, 120, 237, 151, 108, 139, 55, 41, 130, 27, 67, 190, 99, 14, 123, 9, 28, 224, 201, 107, 68, 217, 85, 109, 181, 26, 91, 142, 46, 45, 51, 196, 113, 253, 33, 199, 104, 206, 234, 38, 15, 167, 250, 30, 4, 252, 173, 220, 73, 121, 56, 62, 160, 40, 241, 176, 138, 229, 105, 64, 141, 219, 228, 172, 23, 209, 48, 198, 233, 87, 212, 195, 63, 238, 60, 243, 116, 188, 230, 169, 144, 180, 143, 178, 203, 137, 227, 202, 126, 112, 185, 89, 97, 161, 182, 186, 86, 152, 193, 20, 82, 156, 194, 124, 222, 213, 35, 174, 11, 102, 95, 6, 129, 80, 251, 192, 245, 177, 12, 239, 246, 164, 118, 92, 18, 106, 189, 49, 78, 43, 44, 154, 168, 136, 75, 66, 8, 37, 93, 145, 115, 96, 205, 32, 158, 83, 114, 128, 232, 221, 0]

tmp_data = b''
for d in data:
tmp = sbox.index(d)
tmp_data += tmp.to_bytes(1)
data = tmp_data
print("[6]:", data.hex())

# 整体左移3位
tmp_data = b''
last_bits = data[0] >> 5
for i in range(16):
tmp = (data[15-i] << 3) | last_bits
tmp &= 0xFF
last_bits = data[15-i] >> 5
tmp_data = tmp.to_bytes(1) + tmp_data
data = tmp_data
print("[7]:", data.hex())

# 位置交换
tmp_data = b''
tbox = [14, 8, 1, 4, 5, 13, 15, 0, 2, 10, 6, 7, 9, 3, 11, 12]
for i in range(16):
tmp = data[tbox.index(i)]
tmp_data += tmp.to_bytes(1)
data = tmp_data
print("[8]:", data.hex())

# 转换为uuid
data_hex = data.hex()
uuid = data_hex[0:8] + '-' + data_hex[8:12] + '-' + data_hex[12:16] + '-' + data_hex[16:20] + '-' + data_hex[20:32]
print(uuid)

由于是udp通信,直接抓包收集加密后的数据

server.out

64位elf文件,保留有符号

观察函数表发现有AES加密相关的函数,猜测程序中可能使用了AES进行加解密

main函数就是一个简单的udp通信,接收到buf后由do_login处理,校验成功返回Congratulations! flag is $(Username){$(Password)},校验失败返回wrong password!,从这里可以知道,用户名应该是L3HSEC

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
socklen_t v3; // ebx
size_t v4; // rax
socklen_t addr_len; // [rsp+Ch] [rbp-B4h] BYREF
int fd; // [rsp+10h] [rbp-B0h]
int v7; // [rsp+14h] [rbp-ACh]
char *v8; // [rsp+18h] [rbp-A8h]
struct sockaddr s; // [rsp+20h] [rbp-A0h] BYREF
struct sockaddr addr; // [rsp+30h] [rbp-90h] BYREF
char buf[104]; // [rsp+40h] [rbp-80h] BYREF
unsigned __int64 v12; // [rsp+A8h] [rbp-18h]

v12 = __readfsqword(0x28u);
addr_len = 16;
fd = socket(2, 2, 0);
if ( fd == -1 )
{
perror("socket");
exit(1);
}
memset(&s, 0, sizeof(s));
s.sa_family = 2;
*(_WORD *)s.sa_data = htons(0x2CBBu);
*(_DWORD *)&s.sa_data[2] = 0;
if ( bind(fd, &s, 0x10u) == -1 )
{
perror("bind");
exit(1);
}
printf("Listening on port %d...\n", 11451LL);
while ( 1 )
{
v7 = recvfrom(fd, buf, 0x60uLL, 0, &addr, &addr_len);
if ( v7 == -1 )
break;
v8 = "Congratulations! flag is $(Username){$(Password)}";
if ( do_login((__int64)buf) )
v8 = "wrong password!";
v3 = addr_len;
v4 = strlen(v8);
sendto(fd, v8, v4, 0, &addr, v3);
}
perror("recvfrom");
exit(1);
}

与client相似,do_login中使用4个函数对buf进行处理,完成后将buf后16字节与license校验,校验通过返回0,即登陆成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned char license[] =
{
0xBE, 0xF4, 0xC4, 0x82, 0x0E, 0xA4, 0xA9, 0x4D, 0x04, 0x20,
0xC4, 0xEC, 0x99, 0x34, 0xAE, 0xE8
};

int __fastcall do_login(__int64 a1)
{
do_login_1(a1);
do_login_2(a1);
do_login_3(a1);
do_login_4(a1);
return memcmp(&license, (const void *)(a1 + 80), 0x10uLL);
}

同样的,4个处理函数都被vmp保护了

继续动调dump,对aes的几个函数断点

1
2
3
4
5
6
7
8
9
b aes_key_expansion
b aes_init
b aes_cipher
b aes_inv_cipher
b do_login
b *(do_login+23)
b *(do_login+35)
b *(do_login+47)
b *(do_login+59)
  • do_login1

    处理前,buf中的数据与udp接收到的数据相同

    ni跳过do_login1后,发现gdb提示有fork,gdb自动附加到了子进程(do_login1中的反调试被自动绕过)

    分析加密前后两组数据,没有明显特征,手动修改buf再跑一遍

    1
    2
    91 e6 d0 01 53 c3 02 8b  50 f8 f7 c0 e4 f9 97 74
    6e 19 2f fe ac 3c fd 74 af 07 08 3f 1b 06 68 8b

    1
    2
    00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f
    ff fe fd fc fb fa f9 f8 f7 f6 f5 f4 f3 f2 f1 f0

    发现刚好是bit反转

  • do_login2

    ni之后程序直接退出了,猜测是调试器被检测到

    对exit下断点

    程序退出前在exit断下,在堆栈中检查到TracerPid字段,因此可以猜测程序是通过检查/proc/self/status实现的,在ida中确实也发现了该字符串

    尝试将TracerPidpatch成Seccomp(其它为0的字段也可以,为什么不patch文件名或者直接将TracerPid修改成不存在的字段呢?)

    成功绕过检查

    1
    2
    6e 19 2f fe ac 3c fd 74  af 07 08 3f 1b 06 68 8b
    71 1c 32 01 af 3f 00 77 b2 0a 0b 42 1e 09 6b 8e

    这一步操作比较简单,对每个字节进行+3

  • do_login3

    ni发现程序在函数内断了下来,前面并没有对该函数内设置断点,查看指令发现代码中有int3

    先不管这个int3,直接c

    c之后直接跳到了exit,说明又触发了保护,猜测与这条int3指令有关

    nop掉这个int3试试(还是会exit,这里就不写出了)

    让gdb忽略该信号handle SIGTRAP nostop print(当然也是不行的)

    程序内部触发int3并且能够正常运行,说明程序自身存在处理SIGTRAP信号的能力,而在上面的调试中,gdb并没有处理该信号,仍然将信号交由程序处理,那么程序是如何检测到调试器的呢?

    观察程序的导入函数发现有gettimeofday,该api用于获取时间戳

    对该api断点,查看是否存在调用

    ni之后在gettimeofday断下,说明do_login3应该是使用运行时间检测调试器是否存在

    直接把gettimeofday的plt给patch成memset,timeval结构体为16字节(除了patch,也可以使用LD_PRELOAD链接魔改的gettimeofday)

    成功绕过

    1
    2
    71 1c 32 01 af 3f 00 77  b2 0a 0b 42 1e 09 6b 8e
    71 d6 90 78 42 d0 50 4d ee 00 fc f5 80 4c 38 8e
    1
    2
    00 01 02 03 04 05 06 07  08 09 0a 0b 0c 0d 0e 0f
    f0 70 b0 30 d0 50 90 10 e0 60 a0 20 c0 40 80 00

    可以看出是对16个字节按bit进行了旋转,第一个bit替换到最后一个bit

  • do_login4

    ni之后程序退出,显然还是有反调试,linux反调试当然少不了ptrace

    直接patch掉返回0

    aes_init断下

    aes_key_expansion断下

    密钥长度16,密钥的值刚好是buf中的用户名,0填充长度

    aes_cipher断下,使用aes将buf后16字节加密

    加密后数据

    1
    2
    71 d6 90 78 42 d0 50 4d  ee 00 fc f5 80 4c 38 8e
    b9 ea 8d a1 7d 34 11 2f 50 f3 64 64 55 0e df 8f

    CyberChef跑一下确认是没有魔改的AES

do_login里4个函数分析完,不难写出解密脚本,结合客户端的解密部分

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
#!/usr/bin/env python

from Crypto.Cipher import AES

# 加密后的数据
encrypted_ls = [0xbe, 0xf4, 0xc4, 0x82, 0x0e, 0xa4, 0xa9, 0x4d, 0x04, 0x20, 0xc4, 0xec, 0x99, 0x34, 0xae, 0xe8]

data = b''.join([k.to_bytes(1) for k in encrypted_ls])
print("[0]:", data.hex())

# aes解密
aes_key = b'L3HSEC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
cipher = AES.new(aes_key, AES.MODE_ECB)
data = cipher.decrypt(data)
print("[1]:", data.hex())

# 整体bit反转
tmp_data = b''
for d in data:
tmp = 0
for i in range(8):
tmp = tmp << 1
tmp = tmp | ((d >> i) & 1)
tmp_data = tmp.to_bytes(1) + tmp_data
data = tmp_data
print("[2]:", data.hex())

# 减3
tmp_data = b''
for d in data:
tmp = (d - 3) % 256
tmp_data += tmp.to_bytes(1)
data = tmp_data
print("[3]:", data.hex())

# 位反转
tmp_data = b''
for d in data:
tmp = d ^ 0xFF
tmp_data += tmp.to_bytes(1)
data = tmp_data
print("[4]:", data.hex())

# 循环右移3位
tmp_data = b''
for d in data:
tmp = (d >> 3) | (d << 5);
tmp &= 0xFF
tmp_data += tmp.to_bytes(1)
data = tmp_data
print("[5]:", data.hex())

# 代换
sbox= [84, 74, 31, 134, 94, 166, 0, 163, 111, 170, 254, 149, 208, 140, 211, 236, 240, 13, 47, 16, 77, 65, 81, 71, 244, 146, 210, 21, 76, 235, 34, 247, 61, 17, 204, 216, 187, 53, 132, 110, 103, 119, 54, 57, 7, 183, 79, 159, 19, 214, 249, 59, 36, 3, 225, 231, 157, 70, 2, 215, 117, 25, 131, 150, 175, 72, 5, 184, 42, 165, 50, 162, 218, 100, 22, 58, 90, 200, 88, 147, 1, 179, 155, 29, 127, 52, 148, 242, 101, 135, 207, 133, 248, 125, 39, 223, 24, 69, 153, 171, 226, 122, 191, 197, 98, 10, 120, 237, 151, 108, 139, 55, 41, 130, 27, 67, 190, 99, 14, 123, 9, 28, 224, 201, 107, 68, 217, 85, 109, 181, 26, 91, 142, 46, 45, 51, 196, 113, 253, 33, 199, 104, 206, 234, 38, 15, 167, 250, 30, 4, 252, 173, 220, 73, 121, 56, 62, 160, 40, 241, 176, 138, 229, 105, 64, 141, 219, 228, 172, 23, 209, 48, 198, 233, 87, 212, 195, 63, 238, 60, 243, 116, 188, 230, 169, 144, 180, 143, 178, 203, 137, 227, 202, 126, 112, 185, 89, 97, 161, 182, 186, 86, 152, 193, 20, 82, 156, 194, 124, 222, 213, 35, 174, 11, 102, 95, 6, 129, 80, 251, 192, 245, 177, 12, 239, 246, 164, 118, 92, 18, 106, 189, 49, 78, 43, 44, 154, 168, 136, 75, 66, 8, 37, 93, 145, 115, 96, 205, 32, 158, 83, 114, 128, 232, 221, 0]

tmp_data = b''
for d in data:
tmp = sbox.index(d)
tmp_data += tmp.to_bytes(1)
data = tmp_data
print("[6]:", data.hex())

# 整体左移3位
tmp_data = b''
last_bits = data[0] >> 5
for i in range(16):
tmp = (data[15-i] << 3) | last_bits
tmp &= 0xFF
last_bits = data[15-i] >> 5
tmp_data = tmp.to_bytes(1) + tmp_data
data = tmp_data
print("[7]:", data.hex())

# 位置交换
tmp_data = b''
tbox = [14, 8, 1, 4, 5, 13, 15, 0, 2, 10, 6, 7, 9, 3, 11, 12]
for i in range(16):
tmp = data[tbox.index(i)]
tmp_data += tmp.to_bytes(1)
data = tmp_data
print("[8]:", data.hex())

# 转换为uuid
data_hex = data.hex()
uuid = data_hex[0:8] + '-' + data_hex[8:12] + '-' + data_hex[12:16] + '-' + data_hex[16:20] + '-' + data_hex[20:32]
print(uuid)

L3HSEC{209611a7-aa51-3314-8119-fbef933a1c8f}


L3H_Sec招新2024-RE出题人wp
https://blog.noxke.fun/2024/10/16/ctf_wp/L3H-Sec招新2024-RE出题人wp/
作者
noxke
发布于
2024年10月16日
许可协议