L3HCTF Reverse WP

放假生物钟已经倒过来了,每天早上10点都起不来,都是下午1-2点起床做题,全程就拿了snake的2血,其他的都因为生物钟没争取到😢,这次的XCTF强度不高,都是可以做的。

TemporalParadox

通过字符串交叉调用找到main函数,IDA无法F5反编译,包含了许多异常。直接Delete函数,然后忽略开头这些汇编,从下面的开始按P还原函数就可以F5反编译。

alt text

一些参数不确定、由指针调用表示的函数,都可以直接跳转到对应函数按F5,再转回main,再按F5让IDA重新识别。

这边开头是判断了时间戳是否在某个范围内,然后执行里面的代码。

alt text

底下要求输入query,然后MD5后判断是否与目标值相等,那这边主要就研究上面时间戳范围内执行的代码。

alt text

时间戳范围内执行代码的第一个函数进来发现就是构造query的地方。

alt text

将获取的时间戳传入函数,赋值给一个全局变量。

alt text

然后循环调用一个函数进行计算,这个函数就是利用刚刚那个全局变量进行的计算,最后给v45、v48、v49、v50、v51赋值。

alt text

alt text

通过两个全局静态值以及刚刚上面得到的值进行计算,最后比较两个计算值是否相等,然后走不同的query构造代码。

alt text

由于全程计算仅仅由时间戳控制,并且已经给出时间戳范围,就可以采用时间戳爆破方法来爆破符号目标MD5的query字串。

红框处是参数值对应的变量,salt是固定的可以直接调试取到,tlkyeueq7fej8vtzitt26yl24kswrgm5

alt text

解密代码

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
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <string>
#include <math.h>
#include "md5.h"

DWORD dword_7FF72F33B040 = 0;

__int64 sub_7FF72F3314D5()
{
unsigned int v1; // [rsp+Ch] [rbp-4h]

v1 = (((dword_7FF72F33B040 << 13) ^ (unsigned int)dword_7FF72F33B040) >> 17)
^ (dword_7FF72F33B040 << 13)
^ dword_7FF72F33B040;
dword_7FF72F33B040 = (32 * v1) ^ v1;
return dword_7FF72F33B040 & 0x7FFFFFFF;
}

int main()
{
for (DWORD c = 1751990400; c <= 1752052051; c++)
{
dword_7FF72F33B040 = c;
DWORD v45, v48, v49, v50, v51;
for (int i = 0; i < (int)sub_7FF72F3314D5(); ++i)
{
v51 = sub_7FF72F3314D5();
v50 = sub_7FF72F3314D5();
v49 = sub_7FF72F3314D5();
v48 = sub_7FF72F3314D5();
}
v45 = sub_7FF72F3314D5();

char Buf[1024]{};

sprintf(Buf, "salt=tlkyeueq7fej8vtzitt26yl24kswrgm5&t=%d&r=%d&a=%d&b=%d&x=%d&y=%d", c, v45, v51, v50, v49, v48);

auto md5Txt = MD5(Buf).toStr();
if (md5Txt == "8a2fc1e9e2830c37f8a7f51572a640aa")
{
printf("time:%X\n", c);
printf("%s\n", Buf);
break;
}
}

return 0;
}

/*
time:686D4FA5
salt=tlkyeueq7fej8vtzitt26yl24kswrgm5&t=1751994277&r=101356418&a=1388848462&b=441975230&x=1469980073&y=290308156
*/

SHA1(query) = 5cbbe37231ca99bd009f7eb67f49a98caae2bb0f

L3HCTF{5cbbe37231ca99bd009f7eb67f49a98caae2bb0f}

终焉之门

OpenGL程序,核心代码在着色器代码里面,断点此处就可以得到解密后的着色器代码,

alt text

着色器代码:

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
#version 430 core

layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(std430, binding = 0) buffer OpCodes { int opcodes[]; };
layout(std430, binding = 2) buffer CoConsts { int co_consts[]; };
layout(std430, binding = 3) buffer Cipher { int cipher[16]; };
layout(std430, binding = 4) buffer Stack { int stack_data[256]; };
layout(std430, binding = 5) buffer Out { int verdict; };

const int MaxInstructionCount = 1000;

void main()
{
if (gl_GlobalInvocationID.x > 0) return;

uint ip = 0u;
int sp = 0;
verdict = -233;

while (ip < uint(MaxInstructionCount))
{
int opcode = opcodes[int(ip)];
int arg = opcodes[int(ip)+1];

switch (opcode)
{
case 2:
stack_data[sp++] = co_consts[arg];
break;
case 7:
{
int b = stack_data[--sp];
int a = stack_data[--sp];
stack_data[sp++] = a + b;
break;
}
case 8:
{
int a = stack_data[--sp];
int b = stack_data[--sp];
stack_data[sp++] = a - b;
break;
}
case 14:
{
int b = stack_data[--sp];
int a = stack_data[--sp];
stack_data[sp++] = a ^ b;
break;
}

case 15:
{
int b = stack_data[--sp];
int a = stack_data[--sp];
stack_data[sp++] = int(a == b);
break;
}

case 16:
{
bool ok = true;
for (int i = 0; i < 16; i++)
{
if (stack_data[i] != (cipher[i] - 20))
{
ok = false;
break;
}
}
verdict = ok ? 1 : -1;
return;
}

case 18:
{
int c = stack_data[--sp];
if (c == 0) ip = uint(arg);
break;
}

default:
verdict = 500;
return;
}
ip+=2;
}
verdict = 501;
}

可以发现是一个简易VM,通过开头几个参数的binding值,02345可以在IDA找到对应的变量,然后在上面代码就可以找到对应的资源数据。

alt text

alt text

alt text

这边就是判断输入flag长度、格式,然后将括号内每两个字节进行unhex存入co_consts前十六个位置。

如:”1f3d22” -> {0x1f,0x3d,0x22}

alt text

这边采用同构VM代码,通过输出流程来研究加密。

同构代码:

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
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <string>
#include <math.h>

uint32_t opcodes[168] = {
0x00000002, 0x00000000, 0x00000002, 0x00000001, 0x00000002, 0x00000000, 0x0000000E, 0x00000000,
0x00000002, 0x00000010, 0x00000008, 0x00000000, 0x00000002, 0x00000002, 0x00000002, 0x00000001,
0x0000000E, 0x00000000, 0x00000002, 0x00000011, 0x00000008, 0x00000000, 0x00000002, 0x00000003,
0x00000002, 0x00000002, 0x0000000E, 0x00000000, 0x00000002, 0x00000012, 0x00000007, 0x00000000,
0x00000002, 0x00000004, 0x00000002, 0x00000003, 0x0000000E, 0x00000000, 0x00000002, 0x00000013,
0x00000007, 0x00000000, 0x00000002, 0x00000005, 0x00000002, 0x00000004, 0x0000000E, 0x00000000,
0x00000002, 0x00000014, 0x00000008, 0x00000000, 0x00000002, 0x00000006, 0x00000002, 0x00000005,
0x0000000E, 0x00000000, 0x00000002, 0x00000015, 0x00000007, 0x00000000, 0x00000002, 0x00000007,
0x00000002, 0x00000006, 0x0000000E, 0x00000000, 0x00000002, 0x00000016, 0x00000007, 0x00000000,
0x00000002, 0x00000008, 0x00000002, 0x00000007, 0x0000000E, 0x00000000, 0x00000002, 0x00000017,
0x00000007, 0x00000000, 0x00000002, 0x00000009, 0x00000002, 0x00000008, 0x0000000E, 0x00000000,
0x00000002, 0x00000018, 0x00000007, 0x00000000, 0x00000002, 0x0000000A, 0x00000002, 0x00000009,
0x0000000E, 0x00000000, 0x00000002, 0x00000019, 0x00000007, 0x00000000, 0x00000002, 0x0000000B,
0x00000002, 0x0000000A, 0x0000000E, 0x00000000, 0x00000002, 0x0000001A, 0x00000007, 0x00000000,
0x00000002, 0x0000000C, 0x00000002, 0x0000000B, 0x0000000E, 0x00000000, 0x00000002, 0x0000001B,
0x00000008, 0x00000000, 0x00000002, 0x0000000D, 0x00000002, 0x0000000C, 0x0000000E, 0x00000000,
0x00000002, 0x0000001C, 0x00000008, 0x00000000, 0x00000002, 0x0000000E, 0x00000002, 0x0000000D,
0x0000000E, 0x00000000, 0x00000002, 0x0000001D, 0x00000007, 0x00000000, 0x00000002, 0x0000000F,
0x00000002, 0x0000000E, 0x0000000E, 0x00000000, 0x00000002, 0x0000001E, 0x00000008, 0x00000000,
0x00000010, 0x00000000, 0x00000002, 0x00000010, 0x00000002, 0x00000011, 0x0000000F, 0x00000000,
0x00000012, 0x00000054, 0x00000002, 0x0000001F, 0x00000001, 0x00000000, 0x00000003, 0x00000001
};

uint32_t cipher[16] = {
0x000000F3, 0x00000082, 0x00000006, 0x000001FD, 0x00000150, 0x00000038, 0x000000B2, 0x000000DE,
0x0000015A, 0x00000197, 0x0000009C, 0x000001D7, 0x0000006E, 0x00000028, 0x00000146, 0x00000097
};

uint32_t co_consts[32] = {
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x000000B0, 0x000000C8, 0x000000FA, 0x00000086, 0x0000006E, 0x0000008F, 0x000000AF, 0x000000BF,
0x000000C9, 0x00000064, 0x000000D7, 0x000000C3, 0x000000E3, 0x000000EF, 0x00000087, 0x00000000
};

int stack_data[256]{};

const int MaxInstructionCount = 1000;

int main()
{
uint32_t ip = 0u;
int sp = 0;
int verdict = -233;

while (ip < MaxInstructionCount)
{
int opcode = opcodes[int(ip)];
int arg = opcodes[int(ip) + 1];

switch (opcode)
{
case 2:
printf("stack_data[%d] = co_consts[%d];\n", sp, arg);
stack_data[sp++] = co_consts[arg];
break;
case 7:
{

int b = stack_data[--sp];
int a = stack_data[--sp];
printf("stack_data[%d] = stack_data[%d]+stack_data[%d];\n", sp, sp, sp + 1);
stack_data[sp++] = a + b;
break;
}
case 8:
{
int a = stack_data[--sp];
int b = stack_data[--sp];
printf("stack_data[%d] = stack_data[%d]-stack_data[%d];\n", sp, sp + 1, sp);
stack_data[sp++] = a - b;
break;
}
case 14:
{
int b = stack_data[--sp];
int a = stack_data[--sp];
printf("stack_data[%d] = stack_data[%d]^stack_data[%d];\n", sp, sp, sp+1,a^b);
stack_data[sp++] = a ^ b;
break;
}

case 15:
{
int b = stack_data[--sp];
int a = stack_data[--sp];
printf("stack_data[%d] = stack_data[%d]==stack_data[%d];\n", sp, sp, sp+1);
stack_data[sp++] = int(a == b);
break;
}

case 16:
{
printf("Compare...\n");
bool ok = true;
for (int i = 0; i < 16; i++)
{
if (stack_data[i] != (cipher[i] - 20))
{
ok = false;
break;
}
}
verdict = ok ? 1 : -1;
return 0;
}

case 18:
{
int c = stack_data[--sp];
if (c == 0) ip = arg;
break;
}

default:
verdict = 500;
return 0;
}
ip += 2;
}
verdict = 501;
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
stack_data[0] = co_consts[0];
stack_data[1] = co_consts[1];
stack_data[2] = co_consts[0];
stack_data[1] = stack_data[1]^stack_data[2];
stack_data[2] = co_consts[16];
stack_data[1] = stack_data[2]-stack_data[1];
stack_data[2] = co_consts[2];
stack_data[3] = co_consts[1];
stack_data[2] = stack_data[2]^stack_data[3];
stack_data[3] = co_consts[17];
stack_data[2] = stack_data[3]-stack_data[2];
stack_data[3] = co_consts[3];
stack_data[4] = co_consts[2];
stack_data[3] = stack_data[3]^stack_data[4];
stack_data[4] = co_consts[18];
stack_data[3] = stack_data[3]+stack_data[4];
stack_data[4] = co_consts[4];
stack_data[5] = co_consts[3];
stack_data[4] = stack_data[4]^stack_data[5];
stack_data[5] = co_consts[19];
stack_data[4] = stack_data[4]+stack_data[5];
stack_data[5] = co_consts[5];
stack_data[6] = co_consts[4];
stack_data[5] = stack_data[5]^stack_data[6];
stack_data[6] = co_consts[20];
stack_data[5] = stack_data[6]-stack_data[5];
stack_data[6] = co_consts[6];
stack_data[7] = co_consts[5];
stack_data[6] = stack_data[6]^stack_data[7];
stack_data[7] = co_consts[21];
stack_data[6] = stack_data[6]+stack_data[7];
stack_data[7] = co_consts[7];
stack_data[8] = co_consts[6];
stack_data[7] = stack_data[7]^stack_data[8];
stack_data[8] = co_consts[22];
stack_data[7] = stack_data[7]+stack_data[8];
stack_data[8] = co_consts[8];
stack_data[9] = co_consts[7];
stack_data[8] = stack_data[8]^stack_data[9];
stack_data[9] = co_consts[23];
stack_data[8] = stack_data[8]+stack_data[9];
stack_data[9] = co_consts[9];
stack_data[10] = co_consts[8];
stack_data[9] = stack_data[9]^stack_data[10];
stack_data[10] = co_consts[24];
stack_data[9] = stack_data[9]+stack_data[10];
stack_data[10] = co_consts[10];
stack_data[11] = co_consts[9];
stack_data[10] = stack_data[10]^stack_data[11];
stack_data[11] = co_consts[25];
stack_data[10] = stack_data[10]+stack_data[11];
stack_data[11] = co_consts[11];
stack_data[12] = co_consts[10];
stack_data[11] = stack_data[11]^stack_data[12];
stack_data[12] = co_consts[26];
stack_data[11] = stack_data[11]+stack_data[12];
stack_data[12] = co_consts[12];
stack_data[13] = co_consts[11];
stack_data[12] = stack_data[12]^stack_data[13];
stack_data[13] = co_consts[27];
stack_data[12] = stack_data[13]-stack_data[12];
stack_data[13] = co_consts[13];
stack_data[14] = co_consts[12];
stack_data[13] = stack_data[13]^stack_data[14];
stack_data[14] = co_consts[28];
stack_data[13] = stack_data[14]-stack_data[13];
stack_data[14] = co_consts[14];
stack_data[15] = co_consts[13];
stack_data[14] = stack_data[14]^stack_data[15];
stack_data[15] = co_consts[29];
stack_data[14] = stack_data[14]+stack_data[15];
stack_data[15] = co_consts[15];
stack_data[16] = co_consts[14];
stack_data[15] = stack_data[15]^stack_data[16];
stack_data[16] = co_consts[30];
stack_data[15] = stack_data[16]-stack_data[15];
Compare...

通过研究上面流程输出,可以发现co_consts里面存在的那十五个整数是作为一个key的作用,然后分为两种加密。

  1. 密文=密钥[i-1]-(明文[i]^明文[i-1])
  2. 密文=(明文[i]^明文[i-1])+密钥[i-1]

1,2,5,12,13,15下标是第一种,其余是第二种加密。

解密代码

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
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <string>
#include <math.h>

uint32_t cipher[16] = {
0x000000F3, 0x00000082, 0x00000006, 0x000001FD, 0x00000150, 0x00000038, 0x000000B2, 0x000000DE,
0x0000015A, 0x00000197, 0x0000009C, 0x000001D7, 0x0000006E, 0x00000028, 0x00000146, 0x00000097
};

uint32_t key[]{ 0x000000B0, 0x000000C8, 0x000000FA, 0x00000086, 0x0000006E, 0x0000008F, 0x000000AF, 0x000000BF,
0x000000C9, 0x00000064, 0x000000D7, 0x000000C3, 0x000000E3, 0x000000EF, 0x00000087, };

int main()
{
// 比较处比较的是密文和cipher-20,所以该处全部-20为目标密文。
for (int i = 0; i < 16; i++)
{
cipher[i] -= 20;
}

uint32_t* Cipher = cipher;
uint32_t m[16]{};
m[0] = Cipher[0];

int SubIndexList[]{ 1,2,5,12,13,15 };

for (int i = 1; i < 16; i++)
{
bool IsSub = false;
for (int j = 0; j < 6; j++)
{
if (i == SubIndexList[j])
IsSub = true;
}
if (IsSub)
{
m[i] = key[i - 1] - cipher[i];
m[i] ^= m[i - 1];
}
else
{
m[i] = cipher[i] - key[i - 1];
m[i] ^= m[i - 1];
}
}

for (int i = 0; i < 16; i++)
{
printf("%x", m[i]);
}
return 0;
}
// df9d4ba41258574ccb7155b9d01f5c58

L3HCTF{df9d4ba41258574ccb7155b9d01f5c58}

snake

IDA调试发现存在反调试直接退出,直接断点runtime_main_func1开头,用ScyllaHide就可以过反调试。

发现运行后过一会报time error错误退出,可能是存在其他反调试,这边直接断点runtime_exit,然后运行游戏马上按ESC手动退出,通过栈回溯就可以找到调用方。

alt text

发现是这个函数这边调用的,那么就重点调试这边上面部分的代码。调试发现sub_50D220执行游戏就开始,那么游戏核心函数就在这里面。

alt text

之后会遇到很多函数无法反编译,解决方法是直接delete函数,跳过开头这两行汇编,对下面的剩余代码按P还原成函数即可。

alt text

动调sub_50D220内部,最后发现sub_509EA0这边调用了游戏Call,步入进去,走到call rcx再步入,就可以跳到游戏部分,sub_50BAC0。

alt text

在游戏Call开头会发现一个时间检测,检测时间差是否大于80ms,直接在v4判断处断点赋值rcx为0,就可以跳过if里面的代码,也就绕过了时间检测。

alt text

alt text

可以看到该函数里面有一些类RC4的代码。

alt text

通过动调、patch,发现该处是加分的地方,只要满足外面两个if进来就是score+1,然后下面进行一次类RC4的加解密。

alt text

直接将这两处的判断都去掉,就可以让他进入加分代码。

alt text

在此处断点,随便修改个分数,0xAA,然后运行代码。

alt text

发现游戏Call调用方函数下面这个函数被调用了。

alt text

步入函数,发现这边判断了分数是否>=100,然后执行里面的一些相关操作,会输出一些数据,但是发现执行后并没有输出什么在控制台。

猜测可能是要手动一次一次执行加分代码到100,因为加分代码里面存在类RC4加解密代码,可能是需要执行100次才能让这边成功输出数据。

alt text

这边采用patch加分条件代码来让程序跑的时候一直自动加分,还需要patch时间检测相关代码,不然程序正常运行的时候跑一半就会报time error错误。

一下截图为需要nop的两处地方。

alt text

alt text

保存patch到程序,运行让他跑到100,控制台就会自动输出Flag。

alt text

L3HCTF{ad4d5916-9697-4219-af06-014959c2f4c9}

ez_android

java层太多东西找不到核心,直接看lib,在greet函数可以找到加密代码以及密文比较,直接编写解密即可。

alt text

解密代码

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

void Decrypt(uint8_t* Input, uint8_t* Key)
{
int v10{};
for (int i = 0; i < 27; i++)
{
v10 = i - 14;
if (i < 14)
v10 = i;
Input[i] ^= Key[(i + 4) % 14];
Input[i] = ((Input[i] >> (Key[(i + 3) % 0xEu] & 7)) | (Input[i] << (-Key[(i + 3) % 0xEu] & 7)));
Input[i] = (Input[i] - Key[(uint8_t)(((2 * i) | 1) - 14 * ((147 * ((uint8_t)(2 * i) | 1u)) >> 11))]) ^ Key[v10];
}
}

int main()
{

uint8_t key[] = "dGhpc2lzYWtleQ";
uint8_t Cipher[] = { 0x0C,0x15,0x25,0xA0,0x63,0x96,0x40,0x0A,0x5C,0x16,0x65,0x40,0x29,0x06,0xE1,0x1F,0x90,0x72,0x2C,0x0E,0x4C,0x0A,0x02,0xFC,0x4f,0x32,0x2A};
Decrypt(Cipher, key);
printf("%.27s\n", Cipher);
return 0;
}

// L3HCTF{ez_rust_reverse_lol}

L3HCTF{ez_rust_reverse_lol}

easyvm

主函数可以看到先调用了VM,然后再调用了一个密文比较,密文这边就可以取到,共32字节。

alt text

alt text

这里就是核心VM代码。

alt text

可以看到VM里面存在共8种运算,我的做法是只关注运算。直接右键同步到汇编界面,对8个运算的汇编都断点,写条件代码,将运算的两个值以及结果都输出出来。

alt text

alt text

alt text

alt text

alt text

alt text

alt text

alt text

alt text

以加法为例子,汇编界面这边两个加数是从rbp里面取的,然后计算结果存在eax里面,所以就设置断点在add下一行,然后写如下代码,获取两个加数以及计算结果,按”%X = %X + %X;”格式化输出,其余7个运算同样这样做就可以。

alt text

alt text

运行程序输入”11112222333344445555666677778888”,方便观察加密过程。可以得到约5000行输出,但是我们只需要观察其中十几二十行就可以。

alt text

这是第一轮运算,我已经注释上了对应的代码,可以通过运算清楚看出是一个魔改TEA代码,而且Key就在运算中可以得到。

Key:{ 0x0, 0xA56BABCD, 0xFFFFFFFF, 0xABCDEF01 }

Delta:0x11223344

轮数:64

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
// Key{ 0x0, 0xA56BABCD, 0xFFFFFFFF, 0xABCDEF01 }
// Delta 0x11223344
// RoundsNum 0x40
// B -= (((A << 2) + key[2]) ^ (A + Sum + key[3]) ^ ((A >> 5) + key[1]));
91919190 = 32323232 << 3;
36FD3D5D = 91919190 + A56BABCD;
32323232 = 32323232 + 0;
32323232 = 32323232 + 0;
4CF0F6F = 32323232 ^ 36FD3D5D;
3232323 = 32323232 >> 4;
3232322 = 3232323 + FFFFFFFF;
7EC2C4D = 4CF0F6F ^ 3232322;
391D5D7E = 7EC2C4D + 31313131;

// Sum += Delta;
11223344 = 0 + 11223344;

// A -= (((B << 3) + key[1]) ^ (B + Sum + key[0]) ^ ((B >> 4) + key[2]));
E47575F8 = 391D5D7E << 2;
E47575F7 = E47575F8 + FFFFFFFF;
4A3F90C2 = 391D5D7E + 11223344;
F60D7FC3 = 4A3F90C2 + ABCDEF01;
12780A34 = F60D7FC3 ^ E47575F7;
1C8EAEB = 391D5D7E >> 5;
A73496B8 = 1C8EAEB + A56BABCD;
B54C9C8C = 12780A34 ^ A73496B8;
E77ECEBE = B54C9C8C + 32323232;

// Rounds--;
3F = 40 - 1;

还原加密代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
uint32_t key[]{ 0x0, 0xA56BABCD, 0xFFFFFFFF, 0xABCDEF01 };

uint32_t Delta = 0x11223344;

DWORD Sum = 0;

DWORD A = Input[0];
DWORD B = Input[1];

for (int i = 0; i < 0x40; i++)
{
A += (((B << 3) + key[1]) ^ (B + Sum + key[0]) ^ ((B >> 4) + key[2]));

Sum += Delta;

B += (((A << 2) + key[2]) ^ (A + Sum + key[3]) ^ ((A >> 5) + key[1]));
}

Input[0] = A;
Input[1] = B;

需要注意一点,观察输出数据可以发现不同组加密之间共用一个Sum值,也就是第一组数据加密后的Sum值会作为下一组数据加密的初始Sum值,所以解密的时候要注意Sum值。

解密代码

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

void Decrypt(uint32_t* Input, int i)
{
uint32_t key[]{ 0x0, 0xA56BABCD, 0xFFFFFFFF, 0xABCDEF01 };

uint32_t Delta = 0x11223344;

DWORD Sum = Delta * 0x40 * i;

DWORD A = Input[0];
DWORD B = Input[1];

for (int i = 0; i < 0x40; i++)
{
B -= (((A << 2) + key[2]) ^ (A + Sum + key[3]) ^ ((A >> 5) + key[1]));

Sum -= Delta;

A -= (((B << 3) + key[1]) ^ (B + Sum + key[0]) ^ ((B >> 4) + key[2]));
}

Input[0] = A;
Input[1] = B;
}

int main()
{
DWORD v8[8]{};

v8[0] = 0x877A62A6;
v8[1] = 0x6A55F1F3;
v8[2] = 0xAE194847;
v8[3] = 0xB1E643E7;
v8[4] = 0xA94FE881;
v8[5] = 0x9BC8A28A;
v8[6] = 0xC4CFAA9F;
v8[7] = 0xF1A00CA1;

for (int i = 0; i < 32; i+=8)
{
Decrypt((uint32_t*)((uint8_t*)v8 + i), (i + 8) / 8);
}

printf("L3HCTF{%.32s}\n", v8);

return 0;
}
// L3HCTF{9c50d10ba864bedfb37d7efa4e110bf2}

L3HCTF{9c50d10ba864bedfb37d7efa4e110bf2}

obfuscate

调试程序发现会退出,应该有反调试,找到其中一个exit函数,交叉调用发现多处调用,直接把这边的jmp改成retn就可以过反调试。

alt text

alt text

单步调,发现函数里面翻到这两个相邻函数有大量的计算代码。

250函数是传入一个字符串”WelcometoL3HCTF!”,然后生成26个DWORD密钥,然后再将明文和生成的密钥传入E80函数进行加密。

alt text

alt text

生成的密钥可以再250函数结尾取得。

alt text

调试E80加密函数,第一个这边循环是将输入的两个DWORD值分别加上Key[0]和Key[1]。

alt text

下面是从1开始到0xC的循环加密,这边是核心加密,去除混淆无用计算,动调就可以还原代码。

alt text

加密代码还原:

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
void Encrypt_(uint32_t* Input)
{
unsigned int Key[26] = {
0x122F2C9C, 0xE3BCCAE7, 0xD0FFC0F2, 0xD9A12544, 0x8A27992F, 0x55B1B935, 0x9110B161, 0x92811564,
0x5CE9B359, 0x77C79A51, 0x4265527A, 0x8AB57C4B, 0x11529FA4, 0x9D9F63FF, 0xA970B936, 0xC8EABA0D,
0x9A0EB4AA, 0xB0BC6E7F, 0x9784B100, 0x70DCD3AE, 0x6057A44E, 0x89187658, 0xE00098A8, 0x45773540,
0xF9374F1A, 0x913FA548
};

uint32_t A = Input[0] + Key[0];
uint32_t B = Input[1] + Key[1];

for (int i = 1; i <= 0xC; i++)
{
A = Key[2 * i] + (((B ^ A) >> (32 - B)) | ((B ^ A) << B));

B = Key[2 * i + 1] + (((B ^ A) >> (32 - A)) | ((B ^ A) << A));

A ^= B;
}

Input[0] = A;
Input[1] = B;
}

密文的话,查printf的交叉调用就可以找到,这边有进行密文比较,动调取v3数组数据即可。

alt text

解密代码

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

void Decrypt_(uint32_t* Input)
{
unsigned int Key[26] = {
0x122F2C9C, 0xE3BCCAE7, 0xD0FFC0F2, 0xD9A12544, 0x8A27992F, 0x55B1B935, 0x9110B161, 0x92811564,
0x5CE9B359, 0x77C79A51, 0x4265527A, 0x8AB57C4B, 0x11529FA4, 0x9D9F63FF, 0xA970B936, 0xC8EABA0D,
0x9A0EB4AA, 0xB0BC6E7F, 0x9784B100, 0x70DCD3AE, 0x6057A44E, 0x89187658, 0xE00098A8, 0x45773540,
0xF9374F1A, 0x913FA548
};

uint32_t A = Input[0];
uint32_t B = Input[1];

for (int i = 0xC; i >= 1; i--)
{
A ^= B;

B -= Key[2 * i + 1];
B = ((B << (32 - A)) | (B >> A));
B ^= A;

A -= Key[2 * i];
A = ((A << (32 - B)) | (A >> B));
A ^= B;
}

Input[0] = A - Key[0];
Input[1] = B - Key[1];
}

int main()
{
unsigned int Key[26] = {
0x122F2C9C, 0xE3BCCAE7, 0xD0FFC0F2, 0xD9A12544, 0x8A27992F, 0x55B1B935, 0x9110B161, 0x92811564,
0x5CE9B359, 0x77C79A51, 0x4265527A, 0x8AB57C4B, 0x11529FA4, 0x9D9F63FF, 0xA970B936, 0xC8EABA0D,
0x9A0EB4AA, 0xB0BC6E7F, 0x9784B100, 0x70DCD3AE, 0x6057A44E, 0x89187658, 0xE00098A8, 0x45773540,
0xF9374F1A, 0x913FA548
};

unsigned int Cipher[8] = {
0xF2A1BB1B, 0x21877CE9, 0x0AFD378A, 0xBC811A94, 0xAAE31E40, 0x3FD82E73, 0x4271B884, 0x398B35CC
};

for (int i = 0; i < 32; i+=8)
{
Decrypt_((uint32_t*)((uint8_t*)Cipher + i));
}

printf("L3HCTF{%.32s}\n", Cipher);
return 0;
}
// L3HCTF{5fd277be39046905ef6348ba89131922}

L3HCTF{5fd277be39046905ef6348ba89131922}