TemporalParadox

拿到这道题目,我运行程序后发现报错

0

检查了一下我没缺少动态库就没管了,直接静态分析

0

主函数里面有两个分支,当程序在特定的时间点v58 > 1751990400 && v58 <= 1752052051运行时会输出query:这类东西,由t,r,cipher组成

0

另一个分支时,当不在时间段内就会要求用户输入然后进行md5加密和一个md5值进行比较(这里我对函数进行了重命名)你可以点进MD5那个函数看到

0

这是md5的特征

我们主要分析sub_140001963函数

0

0

0

这里有个if条件,当条件成立时输出的东西就是我们需要的明文

也就是&t=t&r=r&cipher=cipher的值

具体分析可以结合我写的注释进行代码理解

这里的salt值应该是可以直接调试出来的,但是我无法调试,就直接进行静态分析

0

这是salt生成的主要逻辑,以下是我编写的获取salt的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include<stdint.h>
#include<math.h>

int main()
{
//生成salt
uint32_t dword_14000B060[32] = { 0x000000CC, 0x000000B4, 0xFFFFFF94, 0xFFFFFF86, 0xFFFFFF9A, 0xFFFFFF8A,
0xFFFFFF9A, 0xFFFFFF8E, 0xFFE7AC2D, 0x000000A2, 0xFFFFFF9A,0x000000AE, 0xFFB70487, 0x000000D2, 0x000000CC,
0x000000DE, 0xFFFFFF96, 0x000000CC,0x000000CC, 0xFFFFE65F, 0xFFF7E40F, 0xFFFFFF86, 0x000000B4, 0xFFFFE65F,
0xFFFF1957, 0xFFFFFF94, 0xFFFFFF8C, 0xFFFFFF88, 0x000000C6, 0xFFFFFF98, 0xFFFFFF92, 0xFFFD4C05 };
char salt[33]{};
int v10 = 0;
for (int i = 0; i <= 31; ++i)
{
int32_t v9 = (int32_t)dword_14000B060[i];
if (v9 >= 0)
{
v10 = v9 / 3 + 48;
}
else
{
if (v9 >= -728)
v10 = ~v9;
else
v10 = (log(-v9) / 1.09861228866811 - 6.0 + 48.0);
}
salt[i] = v10;
}
salt[32] = '\0';
printf("%s", salt);
return 0;
}

这里的结果是tlkyeueq7fej8vtzitt26yl24kswrgm5,正好32字节

R和t以及cipher我是想着直接爆破得到

R和t这里的话没啥加密,主要是cipher生成这里有个ciphersub_14000184D函数,为了实现这一部分函数我花费了些时间,以下是函数逻辑

0

0

0

涉及到了两个换表

下面是我写的c语言代码整体实现sub_140001963这个关键函数

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

uint32_t S_box[16] = { 0x0000000E, 0x00000004, 0x0000000D, 0x00000001, 0x00000002, 0x0000000F, 0x0000000B, 0x00000008, 0x00000003, 0x0000000A, 0x00000006, 0x0000000C, 0x00000005, 0x00000009,0x00000000, 0x00000007 };
uint32_t P_box[16] = { 0x00000001, 0x00000005, 0x00000009, 0x0000000D, 0x00000002, 0x00000006, 0x0000000A, 0x0000000E, 0x00000003, 0x00000007, 0x0000000B, 0x0000000F, 0x00000004, 0x00000008,0x0000000C, 0x00000010 };

uint32_t srange(uint32_t *dword_14000B040)
{
uint32_t v1;

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



//cipher生成函数 ~~~~~~
uint32_t sub_1400016A0(uint32_t i, uint32_t r)
{
return (r << (4 * (i - 1)) >> 16);
}

uint32_t sub_1400016C1(uint32_t v12, uint32_t* S_box)
{
uint32_t result;
uint32_t i;

for (i = 0; i <= 3; ++i)
{
result = v12;
v12 = S_box[(v12 >> 12) & 0xF] | (16 *v12);
}
return result;

}

uint32_t sub_140001785(uint32_t v12, uint32_t* P_box)
{
uint32_t result;
uint32_t i;
uint32_t v6;

v6 = 0;
for (i = 0; i <= 15; ++i)
v6 |= ((v12 << (P_box[i] - 1)) & 0x8000) >> i;
result = v12;
v12 = v6;
return result;

}

uint32_t sub_1400017F7(uint32_t v12, uint32_t* S_box, uint32_t* P_box)
{
v12 = sub_1400016C1(v12, S_box);
return sub_140001785(v12,P_box);
}

uint32_t generate_cipher(uint32_t t, uint32_t r, uint32_t* S_box, uint32_t* P_box)
{
uint32_t v4;
uint32_t v5;
uint32_t v6;
uint32_t v7;
uint32_t v8;
uint32_t v9;
int v10;
uint32_t v12 = t;
for (int i = 1; i <= 3; ++i)
{
v4 = sub_1400016A0(i, r);
v5 = v12;
v12 ^= v4;
v12 = sub_1400017F7(v12, S_box, P_box);
}
v8 = sub_1400016A0(4, r);
v9 = v12;
v12 ^= v8;
for (uint32_t i = 0; i <= 3; ++i)
{
v12 = S_box[(v12 >> 12) & 0xF] | (16 *v12);
}
v10 = sub_1400016A0(5, r);
return v12 ^ v10;

}
// ~~~~~~


int main()
{
uint32_t y;
uint32_t x;
uint32_t b;
uint32_t a;
uint32_t r;
double v10 = 0x61;
double v12 = 0xb;
double value1, value2;


for (uint32_t t = 1751990400;t <= 1752052051;t++)
{
// 伪随机数生成
uint32_t dword = t;
uint32_t cnt = srange(&dword);
uint32_t i = 0;
while(i<cnt)
{
a = srange(&dword);
b = srange(&dword);
x = srange(&dword);
y = srange(&dword);
cnt = srange(&dword);
i++;
}
r = srange(&dword);

value1 = v10 * pow((a | x), 2.0);
value2 = pow((b | y), 2.0) * v12;
double diff = fabs(value1 - value2);
double denom = fmax(fabs(value1), fabs(value2));
int is_close = (denom == 0) ? (diff < 1e-9) : (diff / denom < 1e-9);
if (is_close)
{
int32_t cipher = generate_cipher(t, r, S_box, P_box);
printf("salt=tlkyeueq7fej8vtzitt26yl24kswrgm5");
printf("&t=%d", t);
printf("&r=%d", r);
printf("&cipher=%d", cipher);
printf("\n");
break;
}
else
printf("salt=tlkyeueq7fej8vtzitt26yl24kswrgm5&t=%d&r=%d&a=%d&b=%d&x=%d&y=%d\n", t, r, a, b, x, y);

}
return 0;
}

这边是可以正常输出在时间段内全部的数据,剩下就是进行一个比较了,这边我是因为不会写c语言的md5加密代码,我一直在找md5的头文件,一开始查到用的是openssl里面的头文件,然后我还专门下了一个openssl,但是下的最新版的运用后说vs2022已经不用了(安全问题),然后强制过掉警告又有新的报错,实在搞不下去了,我用AI直接帮我转化为python代码后进行一个md5值的比较

这边调教AI也花费了很多时间

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
import math
from hashlib import md5, sha1

def gen(dword):
v1 = ((((dword << 13)&0xffffffff) ^ dword) >> 17) ^ ((dword << 13)&0xffffffff) ^ dword
dword = (((32 * v1)&0xffffffff) ^ v1) &0xffffffff
return dword, dword & 0x7FFFFFFF


S_BOX = [0x0000000E, 0x00000004, 0x0000000D, 0x00000001, 0x00000002, 0x0000000F, 0x0000000B, 0x00000008, 0x00000003, 0x0000000A, 0x00000006, 0x0000000C, 0x00000005, 0x00000009, 0x00000000, 0x00000007]

P_BOX = [0x00000001, 0x00000005, 0x00000009, 0x0000000D, 0x00000002, 0x00000006, 0x0000000A, 0x0000000E, 0x00000003, 0x00000007, 0x0000000B, 0x0000000F, 0x00000004, 0x00000008, 0x0000000C, 0x00000010]


def to_u32(n):
"""将一个数转换为32位无符号整数"""
return n & 0xFFFFFFFF


def to_s32(n):
"""将一个数转换为32位有符号整数"""
n = n & 0xFFFFFFFF
if n & 0x80000000:
return n - 0x100000000
return n


def s_box_transform(state, s_box_table):
"""
对应 C 函数 sub_7FF65E2B16C1 (S-盒替换)
"""
s = to_u32(state)
for _ in range(4):
# 提取高4位作为索引
index = (s >> 12) & 0xF
sbox_val = s_box_table[index]
# (16 * s) 等价于 (s << 4)
s = sbox_val | (s << 4)
return to_u32(s)


def p_box_transform(state, p_box_table):
"""
对应 C 函数 sub_7FF65E2B1785 (P-盒置换)
"""

s = to_u32(state)
new_state = 0
for i in range(16):
# 获取源比特的位置 (C数组是1-based, Python是0-based)
source_bit_pos = p_box_table[i] - 1
# 检查源比特是否为1
if (s >> source_bit_pos) & 1:
# 如果是1,则在目标位置i设置比特
new_state |= (1 << i)
return new_state


def round_function(state, s_box_table, p_box_table):
"""
对应 C 函数 sub_7FF65E2B17F7 (轮函数)
"""
state = s_box_transform(state, s_box_table)
state = p_box_transform(state, p_box_table)
return state


def generate_round_key(key, round_num):
"""
对应 C 函数 sub_7FF65E2B16A0 (轮密钥生成)
"""
key_u32 = to_u32(key)
shift_amount = 4 * (round_num - 1)
# C++ 代码中 (unsigned int) >> 是逻辑右移
shifted_key = key_u32 << shift_amount
return to_u32(shifted_key) >> 16


def encrypt_token(timestamp, r_key, s_box_table, p_box_table):
"""
对应 C 函数 sub_7FF65E2B184D (加密主函数)
"""
state = to_u32(timestamp)

# 循环 3 轮
for i in range(1, 4):
round_key = generate_round_key(r_key, i)
state ^= round_key
state = round_function(state, s_box_table, p_box_table)

# 循环后的第4步
round_key_4 = generate_round_key(r_key, 4)
state ^= round_key_4
state = s_box_transform(state, s_box_table)

# 最终返回前的第5步
round_key_5 = generate_round_key(r_key, 5)
final_state = state ^ round_key_5

return to_u32(final_state)


a = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476]
for t in range(1751990400, 1752052052):
dword = t
dword, ret = gen(dword)
cnt = ret
i = 0
while i < cnt:
dword, ret = gen(dword)
a = ret
dword, ret = gen(dword)
b = ret
dword, ret = gen(dword)
x = ret
dword, ret = gen(dword)
y = ret
dword, ret = gen(dword)
cnt = ret
i+=1
dword, ret = gen(dword)
r = ret
# pow(a | x, 2)
val1 = math.pow(float(to_s32(a) | to_s32(x)), 2.0)
# pow(b | y, 2)
val2 = math.pow(float(to_s32(b) | to_s32(y)), 2.0)

if math.isclose(0x61 * val1, 0xb * val2):
cipher = encrypt_token(
t, r,
S_BOX,
P_BOX
)
query = f"salt=tlkyeueq7fej8vtzitt26yl24kswrgm5&t={t}&r={r}&cipher={cipher}"
else:
query = f"salt=tlkyeueq7fej8vtzitt26yl24kswrgm5&t={t}&r={r}&a={a}&b={b}&x={x}&y={y}"
print(t, query)
if md5(query.encode()).hexdigest() == "8a2fc1e9e2830c37f8a7f51572a640aa":
print(sha1(query.encode()).hexdigest())
break

0

L3HCTF{5cbbe37231ca99bd009f7eb67f49a98caae2bb0f}

终焉之门

分析主函数代码

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
__int64 sub_7FF6A04C1CF0()
{
int v0; // ebx
__m128i v1; // xmm6
unsigned int v2; // eax
unsigned int v3; // r13d
unsigned int v4; // eax
int v5; // eax
__int64 v6; // rdi
bool v7; // dl
char v8; // al
double v9; // xmm0_8
unsigned int v10; // eax
char *v12; // r15
unsigned int v13; // ebx
int v14; // eax
__int64 v15; // r9
int v16; // edx
int v17; // eax
int v18; // ecx
int v19; // eax
unsigned int v20; // [rsp+38h] [rbp-100h]
unsigned int v21; // [rsp+3Ch] [rbp-FCh]
unsigned int v22; // [rsp+40h] [rbp-F8h]
unsigned int v23; // [rsp+44h] [rbp-F4h]
unsigned int v24; // [rsp+48h] [rbp-F0h]
int v25; // [rsp+4Ch] [rbp-ECh]
__m128i v26; // [rsp+50h] [rbp-E8h] BYREF
int v27; // [rsp+6Ch] [rbp-CCh] BYREF
char Str[8]; // [rsp+70h] [rbp-C8h] BYREF
__int64 v29; // [rsp+78h] [rbp-C0h]
__int64 v30; // [rsp+80h] [rbp-B8h]
__int64 v31; // [rsp+88h] [rbp-B0h]
__int64 v32[7]; // [rsp+90h] [rbp-A8h] BYREF
__int64 v33[3]; // [rsp+C8h] [rbp-70h]

v0 = 0;
sub_7FF6A04BE370();
sub_7FF6A0453480(8256i64);
sub_7FF6A044F730(1280i64, 800i64, "Password Checker");
sub_7FF6A0451100(&v26, 0i64, &aVersion330Defi);
v1 = _mm_loadu_si128(&v26);
v2 = sub_7FF6A043E700(&aVersion430Core, 37305i64);
v20 = sub_7FF6A043EEE0(v2);
v21 = sub_7FF6A043EFF0(672i64, &opcodes, 35050i64);
v3 = sub_7FF6A043EFF0(128i64, &co_consts, 35050i64);
v22 = sub_7FF6A043EFF0(64i64, &cipher, 35050i64);
v23 = sub_7FF6A043EFF0(1024i64, &stack, 35050i64);
v4 = sub_7FF6A043EFF0(4i64, &out, 35050i64);
v33[0] = 0i64;
v24 = v4;
*Str = 0i64;
v29 = 0i64;
v30 = 0i64;
v31 = 0i64;
memset(v32, 0, sizeof(v32));
*(v33 + 5) = 0i64;
sub_7FF6A04531A0(60i64);
while ( !sub_7FF6A044CAC0() ) // 判断窗口是否关闭
{
v5 = get();
if ( v5 > 0 && v0 <= 99 )
{
v6 = v0 + 1;
do
{
Str[v6 - 1] = v5;
v0 = v6;
v5 = get();
v7 = v6++ <= 99;
}
while ( v7 && v5 > 0 );
}
v8 = sub_7FF6A04558E0(259i64); // 259:这是Backspace键的键码
if ( v0 > 0 && v8 )
Str[--v0] = 0;
if ( sub_7FF6A04558E0(257i64) && strlen(Str) == 40 && !strncmp(Str, "L3HCTF{", 7ui64) && HIBYTE(v32[0]) == 125 )
{
v25 = v0;
v12 = &Str[7];
v13 = 0;
do // 这个do-while循环是将输入的字符每两个一组加密拼接成16进制
{
v17 = *v12;
v18 = v12[1];
if ( v17 > 96 )
v14 = v17 - 87;
else
v14 = v17 - 48;
v19 = 16 * v14;
v15 = v13;
v16 = v18 - 48;
if ( v18 >= 97 )
v16 = v18 - 87;
v12 += 2;
v13 += 4;
v27 = v16 + v19;
sub_7FF6A043F0B0(v3, &v27, 4i64, v15); // 将用户加密输入存入co_consts前16元素
}
while ( v32 + 7 != v12 ); // 当v12移动到花括号部分的末尾就退出。
v0 = v25;
sub_7FF6A043C100(v20); // 下面这一块应该是加载计算着色器
sub_7FF6A043F180(v21, 0i64);
sub_7FF6A043F180(v3, 2i64);
sub_7FF6A043F180(v22, 3i64);
sub_7FF6A043F180(v23, 4i64);
sub_7FF6A043F180(v24, 5i64);
sub_7FF6A043EFE0(1i64, 1i64, 1i64);
sub_7FF6A043F140(v24, &out, 4i64, 0i64);
sub_7FF6A043C110();
}
sub_7FF6A044FC90();
v26 = v1;
sub_7FF6A0450650(&v26);
v9 = sub_7FF6A044E170();
v26 = v1;
*&v9 = v9;
v27 = LODWORD(v9);
v10 = sub_7FF6A0451440(&v26, "time");
v26 = v1;
sub_7FF6A0451460(&v26, v10, &v27, 0i64);
sub_7FF6A046B9D0(0, 0, 1280, 800, -1);
sub_7FF6A0450690();
printf(Str, 100, 200, 40, -16777216);
if ( out == 1 )
printf("success", 100, 300, 40, -13863680);
else
printf("wrong password", 100, 300, 40, -13162010);
printf("Type password and press [Enter] to check!", 100, 100, 20, -8224126);
printf("Press [Backspace] to delete characters.", 100, 130, 20, -8224126);
sub_7FF6A0455CE0();
}
sub_7FF6A044FAA0();
return 0i64;
}

可以根据进行的函数重命名和注释来理解,这里的大概意思就是将用户输入的32字节字符串加密拼接成16进制数据存入co_consts前16元素

我们看到

0

这里是著加密代码点进co_consts数组可以看到前16个元素都是留空的,给了后16个元素,这里我们依旧没有找到check函数,因为这道题目是把验证逻辑放进了OpenGL Compute Shader里,也就是aVersion430Core里面,点进去可以看到

0

0

很明显,我们需要找的check函数就是void 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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
.data:00007FF6A04C3C2B db 'void main()',0Ah
.data:00007FF6A04C3C37 db '{',0Ah
.data:00007FF6A04C3C39 db ' if (gl_GlobalInvocationID.x > 0) return;',0Ah
.data:00007FF6A04C3C66 db 0Ah
.data:00007FF6A04C3C67 db ' uint ip = 0u;',0Ah
.data:00007FF6A04C3C79 db ' int sp = 0;',0Ah
.data:00007FF6A04C3C89 db ' verdict = -233;',0Ah
.data:00007FF6A04C3C9D db 0Ah
.data:00007FF6A04C3C9E db ' while (ip < uint(MaxInstructionCount))',0Ah
.data:00007FF6A04C3CC9 db ' {',0Ah
.data:00007FF6A04C3CCF db ' int opcode = opcodes[int(ip)];',0Ah
.data:00007FF6A04C3CF6 db ' int arg = opcodes[int(ip)+1];',0Ah
.data:00007FF6A04C3D1F db 0Ah
.data:00007FF6A04C3D20 db ' switch (opcode)',0Ah
.data:00007FF6A04C3D38 db ' {',0Ah
.data:00007FF6A04C3D42 db ' case 2:',0Ah
.data:00007FF6A04C3D56 db ' stack_data[sp++] = co_consts[arg];',0Ah
.data:00007FF6A04C3D89 db ' break;',0Ah
.data:00007FF6A04C3DA0 db ' case 7:',0Ah
.data:00007FF6A04C3DB4 db ' {',0Ah
.data:00007FF6A04C3DC2 db ' int b = stack_data[--sp];',0Ah
.data:00007FF6A04C3DEC db ' int a = stack_data[--sp];',0Ah
.data:00007FF6A04C3E16 db ' stack_data[sp++] = a + b;',0Ah
.data:00007FF6A04C3E40 db ' break;',0Ah
.data:00007FF6A04C3E57 db ' }',0Ah
.data:00007FF6A04C3E65 db ' case 8:',0Ah
.data:00007FF6A04C3E79 db ' {',0Ah
.data:00007FF6A04C3E87 db ' int a = stack_data[--sp];',0Ah
.data:00007FF6A04C3EB1 db ' int b = stack_data[--sp];',0Ah
.data:00007FF6A04C3EDB db ' stack_data[sp++] = a - b;',0Ah
.data:00007FF6A04C3F05 db ' break;',0Ah
.data:00007FF6A04C3F1C db ' }',0Ah
.data:00007FF6A04C3F2A db ' case 14:',0Ah
.data:00007FF6A04C3F3F db ' {',0Ah
.data:00007FF6A04C3F4D db ' int b = stack_data[--sp];',0Ah
.data:00007FF6A04C3F77 db ' int a = stack_data[--sp];',0Ah
.data:00007FF6A04C3FA1 db ' stack_data[sp++] = a ^ b;',0Ah
.data:00007FF6A04C3FCB db ' break;',0Ah
.data:00007FF6A04C3FE2 db ' }',0Ah
.data:00007FF6A04C3FF0 db 0Ah
.data:00007FF6A04C3FF1 db ' case 15:',0Ah
.data:00007FF6A04C4006 db ' {',0Ah
.data:00007FF6A04C4014 db ' int b = stack_data[--sp];',0Ah
.data:00007FF6A04C403E db ' int a = stack_data[--sp];',0Ah
.data:00007FF6A04C4068 db ' stack_data[sp++] = int(a == b);',0Ah
.data:00007FF6A04C4098 db ' break;',0Ah
.data:00007FF6A04C40AF db ' }',0Ah
.data:00007FF6A04C40BD db 0Ah
.data:00007FF6A04C40BE db ' case 16:',0Ah
.data:00007FF6A04C40D3 db ' {',0Ah
.data:00007FF6A04C40E1 db ' bool ok = true;',0Ah
.data:00007FF6A04C4101 db ' for (int i = 0; i < 16; i++)',0Ah
.data:00007FF6A04C412E db ' {',0Ah
.data:00007FF6A04C4140 db ' if (stack_data[i] != (cipher[i] - 20))',0Ah
.data:00007FF6A04C417B db ' { ',0Ah
.data:00007FF6A04C4192 db ' ok = false; ',0Ah
.data:00007FF6A04C41B7 db ' break; ',0Ah
.data:00007FF6A04C41D7 db ' }',0Ah
.data:00007FF6A04C41ED db ' }',0Ah
.data:00007FF6A04C41FF db ' verdict = ok ? 1 : -1;',0Ah
.data:00007FF6A04C4226 db ' return;',0Ah
.data:00007FF6A04C423E db ' }',0Ah
.data:00007FF6A04C424C db 0Ah
.data:00007FF6A04C424D db ' case 18:',0Ah
.data:00007FF6A04C4262 db ' {',0Ah
.data:00007FF6A04C4270 db ' int c = stack_data[--sp];',0Ah
.data:00007FF6A04C429A db ' if (c == 0) ip = uint(arg);',0Ah
.data:00007FF6A04C42C6 db ' break;',0Ah
.data:00007FF6A04C42DD db ' }',0Ah
.data:00007FF6A04C42EB db 0Ah
.data:00007FF6A04C42EC db ' default:',0Ah
.data:00007FF6A04C4301 db ' verdict = 500;',0Ah
.data:00007FF6A04C4320 db ' return;',0Ah
.data:00007FF6A04C4338 db ' }',0Ah
.data:00007FF6A04C4342 db 0Ah
.data:00007FF6A04C4343 db ' ip+=2;',0Ah
.data:00007FF6A04C4352 db ' }',0Ah
.data:00007FF6A04C4358 db ' verdict = 501;',0Ah
.data:00007FF6A04C436B db '}',0Ah,0

这是一个小型VM的实现,操作码在前面的主函数中给了就是opcode数组,我们直接编写代码将其模拟运行,进行输出就可以得到加密逻辑

以下是我写的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
#include <stdio.h>
#include <stdint.h>

int main()
{
int opcodes[] = {
2,0,2,1,2,0,14,0,2,16,8,0,2,2,2,1,14,0,2,17,8,0,2,3,2,2,14,0,2,18,7,0,
2,4,2,3,14,0,2,19,7,0,2,5,2,4,14,0,2,20,8,0,2,6,2,5,14,0,2,21,7,0,
2,7,2,6,14,0,2,22,7,0,2,8,2,7,14,0,2,23,7,0,2,9,2,8,14,0,2,24,7,0,
2,10,2,9,14,0,2,25,7,0,2,11,2,10,14,0,2,26,7,0,2,12,2,11,14,0,2,27,8,0,
2,13,2,12,14,0,2,28,8,0,2,14,2,13,14,0,2,29,7,0,2,15,2,14,14,0,2,30,8,0,
16,0,2,16,2,17,15,0,18,84,2,31,1,0,3,1
};

int co_consts[] = {
0xB0, 0xC8, 0xFA, 0x86, 0x6E, 0x8F, 0xAF, 0xBF,
0xC9, 0x64, 0xD7, 0xC3, 0xE3, 0xEF, 0x87, 0x00
};

int cipher[] = {
0xF3, 0x82, 0x06, 0x1FD, 0x150, 0x38, 0xB2, 0xDE,
0x15A, 0x197, 0x9C, 0x1D7, 0x6E, 0x28, 0x146, 0x97
};

int stack_data[256] = {};
int sp = 0;

for (int i = 0; i < 168; i += 2)
{
int opcode = opcodes[i];
int arg = opcodes[i + 1];
int sp0 = sp;

switch (opcode)
{
case 2:
{
int v = (arg < 16) ? co_consts[arg] : 0;
stack_data[sp++] = v;
printf("[IP=%d]\tstack_data[%d] = co_consts[%d] = %#x\n",
i / 2, sp - 1, arg, v);
break;
}
case 7:
{
int b = stack_data[--sp];
int a = stack_data[--sp];
stack_data[sp++] = a + b;
printf("[IP=%d]\tstack_data[%d] = a + b = stack_data[%d] + stack_data[%d] = %#x\n",
i / 2, sp - 1, sp0 - 2, sp0 - 1, a + b);
break;
}
case 8:
{
int a = stack_data[--sp];
int b = stack_data[--sp];
stack_data[sp++] = a - b;
printf("[IP=%d]\tstack_data[%d] = a - b = stack_data[%d] - stack_data[%d] = %#x\n",
i / 2, sp - 1, sp0 - 1, sp0 - 2, a - b);
break;
}
case 14:
{
int b = stack_data[--sp];
int a = stack_data[--sp];
stack_data[sp++] = a ^ b;
printf("[IP=%d]\tstack_data[%d] = a ^ b = stack_data[%d] ^ stack_data[%d] = %#x\n",
i / 2, sp - 1, sp0 - 2, sp0 - 1, a ^ b);
break;
}
case 15:
{
int b = stack_data[--sp];
int a = stack_data[--sp];
stack_data[sp++] = int(a == b);
printf("[IP=%d]\tstack_data[%d] = (a == b) = stack_data[%d] == stack_data[%d] = %#x\n",
i / 2, sp - 1, sp0 - 2, sp0 - 1, (a == b));
break;
}
case 16:
{
printf("[IP=%d]\t=== VERIFY cipher check ===\n", i / 2);
for (int j = 0; j < 16; j++)
{
printf(" stack[%2d]=0x%X vs cipher[%2d]-20=0x%X\n",
j, stack_data[j], j, cipher[j] - 20);
}
break;
}
case 18:
{
int c = stack_data[--sp];
printf("[IP=%d]\tJZ if top==0 jump to %d (top=%d)\n",
i / 2, arg, c);
if (c == 0)
{
i = arg * 2 - 2; // 跳转
}
break;
}
default:
{
printf("[IP=%d]\tUNKNOWN OPCODE %d, abort\n", i / 2, opcode);
return -1;
}
}
}

return 0;
}

得到运行逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
[IP=0]  stack_data[0] = co_consts[0] = 0xb0
[IP=1] stack_data[1] = co_consts[1] = 0xc8
[IP=2] stack_data[2] = co_consts[0] = 0xb0
[IP=3] stack_data[1] = a ^ b = stack_data[1] ^ stack_data[2] = 0x78
[IP=4] stack_data[2] = co_consts[16] = 0
[IP=5] stack_data[1] = a - b = stack_data[2] - stack_data[1] = 0xffffff88
[IP=6] stack_data[2] = co_consts[2] = 0xfa
[IP=7] stack_data[3] = co_consts[1] = 0xc8
[IP=8] stack_data[2] = a ^ b = stack_data[2] ^ stack_data[3] = 0x32
[IP=9] stack_data[3] = co_consts[17] = 0
[IP=10] stack_data[2] = a - b = stack_data[3] - stack_data[2] = 0xffffffce
[IP=11] stack_data[3] = co_consts[3] = 0x86
[IP=12] stack_data[4] = co_consts[2] = 0xfa
[IP=13] stack_data[3] = a ^ b = stack_data[3] ^ stack_data[4] = 0x7c
[IP=14] stack_data[4] = co_consts[18] = 0
[IP=15] stack_data[3] = a + b = stack_data[3] + stack_data[4] = 0x7c
[IP=16] stack_data[4] = co_consts[4] = 0x6e
[IP=17] stack_data[5] = co_consts[3] = 0x86
[IP=18] stack_data[4] = a ^ b = stack_data[4] ^ stack_data[5] = 0xe8
[IP=19] stack_data[5] = co_consts[19] = 0
[IP=20] stack_data[4] = a + b = stack_data[4] + stack_data[5] = 0xe8
[IP=21] stack_data[5] = co_consts[5] = 0x8f
[IP=22] stack_data[6] = co_consts[4] = 0x6e
[IP=23] stack_data[5] = a ^ b = stack_data[5] ^ stack_data[6] = 0xe1
[IP=24] stack_data[6] = co_consts[20] = 0
[IP=25] stack_data[5] = a - b = stack_data[6] - stack_data[5] = 0xffffff1f
[IP=26] stack_data[6] = co_consts[6] = 0xaf
[IP=27] stack_data[7] = co_consts[5] = 0x8f
[IP=28] stack_data[6] = a ^ b = stack_data[6] ^ stack_data[7] = 0x20
[IP=29] stack_data[7] = co_consts[21] = 0
[IP=30] stack_data[6] = a + b = stack_data[6] + stack_data[7] = 0x20
[IP=31] stack_data[7] = co_consts[7] = 0xbf
[IP=32] stack_data[8] = co_consts[6] = 0xaf
[IP=33] stack_data[7] = a ^ b = stack_data[7] ^ stack_data[8] = 0x10
[IP=34] stack_data[8] = co_consts[22] = 0
[IP=35] stack_data[7] = a + b = stack_data[7] + stack_data[8] = 0x10
[IP=36] stack_data[8] = co_consts[8] = 0xc9
[IP=37] stack_data[9] = co_consts[7] = 0xbf
[IP=38] stack_data[8] = a ^ b = stack_data[8] ^ stack_data[9] = 0x76
[IP=39] stack_data[9] = co_consts[23] = 0
[IP=40] stack_data[8] = a + b = stack_data[8] + stack_data[9] = 0x76
[IP=41] stack_data[9] = co_consts[9] = 0x64
[IP=42] stack_data[10] = co_consts[8] = 0xc9
[IP=43] stack_data[9] = a ^ b = stack_data[9] ^ stack_data[10] = 0xad
[IP=44] stack_data[10] = co_consts[24] = 0
[IP=45] stack_data[9] = a + b = stack_data[9] + stack_data[10] = 0xad
[IP=46] stack_data[10] = co_consts[10] = 0xd7
[IP=47] stack_data[11] = co_consts[9] = 0x64
[IP=48] stack_data[10] = a ^ b = stack_data[10] ^ stack_data[11] = 0xb3
[IP=49] stack_data[11] = co_consts[25] = 0
[IP=50] stack_data[10] = a + b = stack_data[10] + stack_data[11] = 0xb3
[IP=51] stack_data[11] = co_consts[11] = 0xc3
[IP=52] stack_data[12] = co_consts[10] = 0xd7
[IP=53] stack_data[11] = a ^ b = stack_data[11] ^ stack_data[12] = 0x14
[IP=54] stack_data[12] = co_consts[26] = 0
[IP=55] stack_data[11] = a + b = stack_data[11] + stack_data[12] = 0x14
[IP=56] stack_data[12] = co_consts[12] = 0xe3
[IP=57] stack_data[13] = co_consts[11] = 0xc3
[IP=58] stack_data[12] = a ^ b = stack_data[12] ^ stack_data[13] = 0x20
[IP=59] stack_data[13] = co_consts[27] = 0
[IP=60] stack_data[12] = a - b = stack_data[13] - stack_data[12] = 0xffffffe0
[IP=61] stack_data[13] = co_consts[13] = 0xef
[IP=62] stack_data[14] = co_consts[12] = 0xe3
[IP=63] stack_data[13] = a ^ b = stack_data[13] ^ stack_data[14] = 0xc
[IP=64] stack_data[14] = co_consts[28] = 0
[IP=65] stack_data[13] = a - b = stack_data[14] - stack_data[13] = 0xfffffff4
[IP=66] stack_data[14] = co_consts[14] = 0x87
[IP=67] stack_data[15] = co_consts[13] = 0xef
[IP=68] stack_data[14] = a ^ b = stack_data[14] ^ stack_data[15] = 0x68
[IP=69] stack_data[15] = co_consts[29] = 0
[IP=70] stack_data[14] = a + b = stack_data[14] + stack_data[15] = 0x68
[IP=71] stack_data[15] = co_consts[15] = 0
[IP=72] stack_data[16] = co_consts[14] = 0x87
[IP=73] stack_data[15] = a ^ b = stack_data[15] ^ stack_data[16] = 0x87
[IP=74] stack_data[16] = co_consts[30] = 0
[IP=75] stack_data[15] = a - b = stack_data[16] - stack_data[15] = 0xffffff79
[IP=76] === VERIFY cipher check ===
stack[ 0]=0xB0 vs cipher[ 0]-20=0xDF
stack[ 1]=0xFFFFFF88 vs cipher[ 1]-20=0x6E
stack[ 2]=0xFFFFFFCE vs cipher[ 2]-20=0xFFFFFFF2
stack[ 3]=0x7C vs cipher[ 3]-20=0x1E9
stack[ 4]=0xE8 vs cipher[ 4]-20=0x13C
stack[ 5]=0xFFFFFF1F vs cipher[ 5]-20=0x24
stack[ 6]=0x20 vs cipher[ 6]-20=0x9E
stack[ 7]=0x10 vs cipher[ 7]-20=0xCA
stack[ 8]=0x76 vs cipher[ 8]-20=0x146
stack[ 9]=0xAD vs cipher[ 9]-20=0x183
stack[10]=0xB3 vs cipher[10]-20=0x88
stack[11]=0x14 vs cipher[11]-20=0x1C3
stack[12]=0xFFFFFFE0 vs cipher[12]-20=0x5A
stack[13]=0xFFFFFFF4 vs cipher[13]-20=0x14
stack[14]=0x68 vs cipher[14]-20=0x132
stack[15]=0xFFFFFF79 vs cipher[15]-20=0x83
[IP=77] stack_data[16] = co_consts[16] = 0 //这下面一块没用
[IP=78] stack_data[17] = co_consts[17] = 0
[IP=79] stack_data[16] = (a == b) = stack_data[16] == stack_data[17] = 0x1
[IP=80] JZ if top==0 jump to 84 (top=1)
[IP=81] stack_data[16] = co_consts[31] = 0
[IP=82] UNKNOWN OPCODE 1, abort

这个VM只进行了异或和加减法

将用户输入加密后的前16个元素和固定的后16个元素进行混合加密后和密文cipher进行比较。现在我们有了cipher和co_consts的后十六字节,我们可以进行解密

解密分析

这里我们选择从 IP = 6进行分析,因为从IP = 0进行分析会很懵 别问我咋知道的

IP = 0这里进行的加密和IP = 6的加密一样,都是case 8

1
2
3
4
5
[IP=6]  stack_data[2] = co_consts[2] = 0xfa
[IP=7] stack_data[3] = co_consts[1] = 0xc8
[IP=8] stack_data[2] = a ^ b = stack_data[2] ^ stack_data[3] = 0x32
[IP=9] stack_data[3] = co_consts[17] = 0
[IP=10] stack_data[2] = a - b = stack_data[3] - stack_data[2] = 0xffffffce

最后的结果就是cipher[2]

[IP=10] stack_data[2] = a - b = stack_data[3] - stack_data[2] = 0xffffffce = cipher[2]

这里的[IP=7] stack_data[3] = co_consts[1]就是相当于

[IP=7] stack_data[3] = cipher[1]

也就是说这里先将用户输入的第二位与密文cipher[1]进行异或后与co_consts的固定值co_consts[17]向减

好那么上面的[IP=2] stack_data[2] = co_consts[0]就相当于[IP=2] stack_data[2] = co_consts[0] = cipher[0]

后面的加法也是一样的分析,也就是未对co_consts[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
#include <stdio.h>
#include <stdint.h>

int main() {
int CIPHER[16] = {
0xF3, 0x82, 0x06, 0x1FD, 0x150, 0x38, 0xB2, 0xDE,
0x15A, 0x197, 0x9C, 0x1D7, 0x6E, 0x28, 0x146, 0x97
};

uint8_t CONSTS[16] = {
0xB0, 0xC8, 0xFA, 0x86, 0x6E, 0x8F, 0xAF, 0xBF,
0xC9, 0x64, 0xD7, 0xC3, 0xE3, 0xEF, 0x87, 0x00
};

uint8_t op_pattern[15] = {
1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1
};

int targets[16];
for (int i = 0; i < 16; i++) {
targets[i] = CIPHER[i] - 20;
}

uint8_t inputs[16] = { 0 };

inputs[0] = (uint8_t)(targets[0]);

for (int i = 1; i < 16; i++) {
uint8_t prev_input = inputs[i - 1];
int target = targets[i];
uint8_t constant = CONSTS[i - 1];
uint8_t op_type = op_pattern[i - 1];

uint8_t flag;

if (op_type == 0) {
// ADD
flag = (uint8_t)((target - constant) & 0xff);
}
else if (op_type == 1) {
// SUB
flag = (uint8_t)((constant - target) & 0xff);
}

inputs[i] = flag ^ prev_input;
}

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

return 0;
}////df9d4ba41258574ccb7155b9d01f5c58

L3HCTF{df9d4ba41258574ccb7155b9d01f5c58}

easyvm(复现)

找到主函数

如果反编译是一大串函数的话就 ida 打开先设置Options->Compiler… [Compiler: Visual C++]

这边我进行一个重命名

cmp函数存放着32位的数组密文

很清晰了,我们进入VM实现函数发现有很多switch-case函数

这就是VM的handle步骤

我们找到主要操作码分类

算术运算

  • 加法 (0x10)
  • 减法 (0x11)
  • 乘法 (0x12)
  • 除法 (0x13)
  • 取模 (0x14)
  • 位移 (0x16左移, 0x17右移)
  • 异或 (0x18)

这边的话我们可以通过条件断点的方式进行查看该vm的具体加密流程,我们可以在add,sub,xor,shl,shr进行条件断点(乘除和取余的话一般是无法逆向恢复的)

给出idapython代码(参考SU的WP

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
import idc, idaapi
op1_val = idc.get_reg_value("EDX")
op2_val = idc.get_reg_value("ECX") & 0xFF
print(f"shl {hex(op1_val)}, {hex(op2_val)} = {hex((op1_val<<op2_val)&0xffffffff)}")

import idc, idaapi
op1_val = idc.get_reg_value("EDX")
op2_val = idc.get_reg_value("ECX") & 0xFF
print(f"shr {hex(op1_val)}, {hex(op2_val)} = {hex((op1_val>>op2_val)&0xffffffff)}")

import idc, idaapi
op1_val = idc.get_reg_value("EAX")
rbp_val = idc.get_reg_value("RBP")
mem_addr = rbp_val + 0x4C
op2_val = idc.get_wide_dword(mem_addr)
print(f"xor {hex(op1_val)}, {hex(op2_val)} = {hex((op1_val^op2_val)&0xffffffff)}")

import idc, idaapi
op1_val = idc.get_reg_value("EAX")
rbp_val = idc.get_reg_value("RBP")
mem_addr = rbp_val + 0x1C
op2_val = idc.get_wide_dword(mem_addr)
print(f"sub {hex(op1_val)}, {hex(op2_val)} = {hex((op1_val-op2_val)&0xffffffff)}")

import idc, idaapi
op1_val = idc.get_reg_value("EAX")
op2_val = idc.get_reg_value("EDX")
print(f"add {hex(op1_val)}, {hex(op2_val)} = {hex((op1_val+op2_val)&0xffffffff)}")

得到加密逻辑,取一段进行分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
shl 0x31313131, 0x3 = 0x89898988
add 0xa56babcd, 0x89898988 = 0x2ef53555
add 0x0, 0x31313131 = 0x31313131
add 0x0, 0x31313131 = 0x31313131
xor 0x31313131, 0x2ef53555 = 0x1fc40464
shr 0x31313131, 0x4 = 0x3131313
add 0xffffffff, 0x3131313 = 0x3131312
xor 0x1fc40464, 0x3131312 = 0x1cd71776
add 0x31313131, 0x1cd71776 = 0x4e0848a7
add 0x11223344, 0x0 = 0x11223344
shl 0x4e0848a7, 0x2 = 0x3821229c
add 0xffffffff, 0x3821229c = 0x3821229b
add 0x11223344, 0x4e0848a7 = 0x5f2a7beb
add 0xabcdef01, 0x5f2a7beb = 0xaf86aec
xor 0xaf86aec, 0x3821229b = 0x32d94877
shr 0x4e0848a7, 0x5 = 0x2704245
add 0xa56babcd, 0x2704245 = 0xa7dbee12
xor 0x32d94877, 0xa7dbee12 = 0x9502a665
add 0x31313131, 0x9502a665 = 0xc633d796
sub 0x40, 0x1 = 0x3f

这边就可以通过代码一行一行进行复原,能知道是一个魔改的xtea加密,有个点要注意一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
add 0x11223344, 0x376a9dbc = 0x488cd100   // 上一组sum值
shl 0x574756d1, 0x2 = 0x5d1d5b44
add 0xffffffff, 0x5d1d5b44 = 0x5d1d5b43
add 0x488cd100, 0x574756d1 = 0x9fd427d1
add 0xabcdef01, 0x9fd427d1 = 0x4ba216d2
xor 0x4ba216d2, 0x5d1d5b43 = 0x16bf4d91
shr 0x574756d1, 0x5 = 0x2ba3ab6
add 0xa56babcd, 0x2ba3ab6 = 0xa825e683
xor 0x16bf4d91, 0xa825e683 = 0xbe9aab12
add 0x1ca93e77, 0xbe9aab12 = 0xdb43e989
sub 0x1, 0x1 = 0x0
shl 0x31313131, 0x3 = 0x89898988
add 0xa56babcd, 0x89898988 = 0x2ef53555
add 0x488cd100, 0x31313131 = 0x79be0231
add 0x0, 0x79be0231 = 0x79be0231
xor 0x79be0231, 0x2ef53555 = 0x574b3764
shr 0x31313131, 0x4 = 0x3131313
add 0xffffffff, 0x3131313 = 0x3131312
xor 0x574b3764, 0x3131312 = 0x54582476
add 0x31313131, 0x54582476 = 0x858955a7
add 0x11223344, 0x488cd100 = 0x59af0444 //// 下一组sum值

下一组加密8字节用的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
52
#include<stdio.h>
#include<stdint.h>
#define dalte 0x11223344



void encypt(uint32_t *v, uint32_t key[3])
{

uint32_t v0 = v[0], v1 = v[1];
uint32_t sum = 0;
for(int i = 0;i<64;i++)
{
uint32_t key[4] = { 0xa56babcd ,0xffffffff,0xabcdef01 };

v0 += ((v1 << 3) + key[0]) ^ (sum + v1+key[1]) ^ ((v1 >> 4) + key[2]);
sum += dalte;
v1 += ((v0 << 2) + key[2]) ^ (sum + v0 + key[3]) ^ ((v0 >> 5) + key[0]);
}
v[0] = v0;
v[1] = v1;
}

void decypt(uint32_t v[2], uint32_t* key, uint32_t i)
{
uint32_t v0 = v[0], v1 = v[1];
i += 1;
uint32_t sum = dalte * 64 * i;
for (int i = 0;i < 64;i++)
{
v1 -= ((v0 << 2) + key[2]) ^ (sum + v0 + key[3]) ^ ((v0 >> 5) + key[0]);
sum -= dalte;
v0 -= ((v1 << 3) + key[0]) ^ (sum + v1+key[1]) ^ ((v1 >> 4) + key[2]);
}
v[0] = v0;
v[1] = v1;

}

int main()
{
uint32_t enc[] = { 0x877A62A6,0x6A55F1F3,0xAE194847,0xB1E643E7,0xA94FE881,0x9BC8A28A,0xC4CFAA9F,0xF1A00CA1 };
uint32_t key[4] = { 0xa56babcd ,0x00,0xffffffff,0xabcdef01 };
uint32_t i = 0;
for(int i = 0;i<4;i++)
{
decypt(&enc[i * 2], key, i);
}
printf("%s", enc);

return 0;
}//9c50d10ba864bedfb37d7efa4e110bf2

L3HCTF{9c50d10ba864bedfb37d7efa4e110bf2}

obfuscate(复现)

打开主函数进行反编译时发现大量指令混淆,能力不够去不掉,但是可以看别的函数进行分析

发现反调试,还不只一个,我们可以一个一个nop掉或者直接通过修改掉_exit(1)这个退出函数,因为这个函数被多处调用

检查一下发现都是反调调用的,直接将

jmp改为ret后patch

之后就可以进行调试了

这里先分析其他函数,发现sub_1250和sub_1E80有加密逻辑,但是混淆的有点严重,我们可以通过D810或者IDA自带的goomba插件解除部分混淆

(右键-De-obfuscate)即可,但是得到的函数依然存在很多逻辑混淆,大部分是永真永假和一些指令替换

1
2
3
4
if ( unk_B1C8 < 10 && unk_B1C8 >= 10 )    // 恒假
goto LABEL_26;
if ( unk_B218 >= 10 || unk_B218 < 10 ) // 恒真
break;
1
*v37 - 1067854539 + 1067854538    // 等价于*v37 - 1

我们可以将永真永假去除后得到较为干净的代码

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
__int64 __fastcall sub_1E80(_DWORD *a1, __int64 *a2, _DWORD *a3)
{
__int64 *v3; // rax
__int64 *v4; // rdi
__int64 *v5; // rsi
__int64 *v6; // rcx
int v7; // r8d
__int64 *v8; // rcx
__int64 *v9; // rax
__int64 *v10; // rcx
__int64 result; // rax
__int64 *v12; // [rsp+0h] [rbp-40h] BYREF
__int64 *v13; // [rsp+8h] [rbp-38h]
__int64 *v14; // [rsp+10h] [rbp-30h]
__int64 *v15; // [rsp+18h] [rbp-28h]
__int64 *v16; // [rsp+20h] [rbp-20h]
_DWORD *v17; // [rsp+28h] [rbp-18h]
__int64 *v18; // [rsp+30h] [rbp-10h]
_DWORD *v19; // [rsp+38h] [rbp-8h]

v17 = a1;
v18 = a2;
v19 = a3;
v12 = (&v12 - 2);
v13 = (&v12 - 2);
v14 = (&v12 - 2);
v15 = (&v12 - 2);
v16 = (&v12 - 2);
*v12 = a1;
*(&v12 - 2) = a2;
*(&v12 - 2) = a3;
*(&v12 - 4) = 2 * **(&v12 - 2);
*(&v12 - 4) = 2 * *(*(&v12 - 2) + 1);
*(&v12 - 4) = 1;
while ( *v16 <= 0xCu )
{
v3 = v14;
v4 = v16;
v5 = v12;
v6 = v15;
*v14 = *(*v12 + 4LL * (2 * *v16)) + __ROL4__(*v14 ^ *v15, *v15);
v7 = (*v6 ^ *v3) << *v3;
v8 = v15;
*v15 = (((*v15 ^ *v3) >> (32 - *v3)) | v7) + *(*v5 + 4LL * (2 * *v4 + 1));
*v3 ^= *v8;
++*v16;
}
v9 = v13;
v10 = v15;
**v13 = *v14;
result = *v9;
*(result + 4) = *v10;
return result;
}
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
_BOOL8 __fastcall sub_1250(__int64 a1, __int64 a2)
{
_DWORD *v2; // rax
_DWORD *v3; // rax
_DWORD *v4; // rax
_DWORD *v5; // rcx
_DWORD *v6; // rdx
_DWORD *v7; // rsi
_DWORD *v8; // rdi
unsigned int *v9; // rdi
unsigned int *v10; // rax
_BYTE *v11; // rsi
unsigned int *v12; // rcx
unsigned int v13; // edx
int v14; // r8d
unsigned int *v15; // rcx
unsigned int v16; // edx
int *v17; // rax
_BYTE v19[12]; // [rsp+0h] [rbp-70h] BYREF
_DWORD **v24; // [rsp+10h] [rbp-60h]
_QWORD *v25; // [rsp+18h] [rbp-58h]
_BYTE *v26; // [rsp+20h] [rbp-50h]
_DWORD *v27; // [rsp+28h] [rbp-48h]
_DWORD *v28; // [rsp+30h] [rbp-40h]
_DWORD *v29; // [rsp+38h] [rbp-38h]
_DWORD *v30; // [rsp+40h] [rbp-30h]
_DWORD *v31; // [rsp+48h] [rbp-28h]
_DWORD *v32; // [rsp+50h] [rbp-20h]
__int64 v33; // [rsp+58h] [rbp-18h]
__int64 v34; // [rsp+60h] [rbp-10h]

v33 = a1;
v34 = a2;
v24 = &v19[-16];
v25 = &v19[-16];
v26 = &v19[-16];
v27 = &v19[-16];
v28 = &v19[-16];
v29 = &v19[-16];
v30 = &v19[-16];
v31 = &v19[-16];
v32 = &v19[-16];
*&v19[-16] = a1;
*&v19[-16] = a2;
*&v19[-16] = 0;
while ( *v29 < 4u )
{
v2 = v30;
*&v26[4 * *v29] = 0;
*v2 = 0;
while ( *v30 < 4u )
{
*&v26[4 * *v29] = *(*v25 + (*v30 + 4 * *v29)) + (*&v26[4 * *v29] << 8);
++*v30;
}
++*v29;
}
v3 = v29;
**v24 = -1209970333;
*v3 = 1;
while ( *v29 < 0x1Au )
{
(*v24)[*v29] = (*v24)[*v29 - 1] - 1640531527;
++*v29;
}
v4 = v31;
v5 = v32;
v6 = v27;
v7 = v28;
v8 = v29;
*v30 = 0;
*v8 = 0;
*v7 = 0;
*v6 = 0;
*v5 = 78;
*v4 = 0;
while ( *v31 < *v32 )
{
v9 = v30;
v10 = v28;
v11 = v26;
v12 = v27;
v13 = ((*v27 + (*v24)[*v29] + *v28) >> 29) | (8 * (*v27 + (*v24)[*v29] + *v28));
(*v24)[*v29] = v13;
*v12 = v13;
v14 = (*v12 + *&v11[4 * *v9] + *v10) << (*v12 + *v10);
v15 = v29;
v16 = ((*v10 + *v27 + *&v11[4 * *v9]) >> (32 - (*v10 + *v27))) | v14;
*&v11[4 * *v9] = v16;
*v10 = v16;
v17 = v30;
*v15 = (*v15 + 1) % 0x1A;
*v17 = (*v17 + 1) & 3;
++*v31;
}
return unk_B144 < 10;
}

将这些代码丢给gpt让其去除掉剩余的指令替换得到更加直白的代码

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
_BOOL8 sub_1250(__int64 key_ptr, __int64 input_ptr)
{
uint32_t L[4] = {0}; // 从 key_ptr 构造的 4 个 32bit 的值
uint32_t S[26]; // S 表,初始化 + 扩展 + 混合
uint32_t i, j, A = 0, B = 0;

// 1. 从 key_ptr 加载 4 × 4 字节,组成 L[0..3]
for (i = 0; i < 4; ++i) {
L[i] = 0;
for (j = 0; j < 4; ++j)
L[i] |= ((uint8_t*)key_ptr)[i * 4 + j] << (8 * j);
}

// 2. 初始化 S 表
S[0] = 0xB7E15163; // -1209970333 as unsigned
for (i = 1; i < 26; ++i)
S[i] = S[i - 1] - 0x61C88647; // -1640531527

// 3. 混合 S 和 L,使用 78 次的 schedule
i = j = 0;
A = B = 0;
for (uint32_t k = 0; k < 78; ++k) {
A = S[i] = ROTL32(S[i] + A + B, 3);
B = L[j] = ROTL32(L[j] + A + B, (A + B));
i = (i + 1) % 26;
j = (j + 1) % 4;
}

// 4. 返回全局变量 unk_B144 的比较结果
return unk_B144 < 10;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 sub_1E80(uint32_t* output, uint32_t* S, uint32_t* input)
{
uint32_t A = input[0];
uint32_t B = input[1];

A += S[0];
B += S[1];

for (int i = 1; i <= 12; ++i) {
A = ROTL32(A ^ B, B) + S[2 * i];
B = ROTL32(B ^ A, A) + S[2 * i + 1];
}

output[0] = A;
output[1] = B;
return (__int64)output;
}

RC5加密算法,魔改点就是主加密哪里多了个异或和密钥扩展S[i] = S[i - 1] - 0x61C88647这部分将加改为了减

Q的数据也改了一下,密钥可以动调出来(rc5的密钥扩展哪里),动调的话随便输入一个32位长度的数据就行(一开始是进行输入数据长度判断)

密文在sub_6180函数

可以动调也可以手动异或出来

解密代码

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

// RC5 parameters
#define w 32 // 字长
#define r 12 // 加密轮数
#define b 16 // 密钥长度
#define t (2 * r + 2) // 子密钥数
#define c (b * 8 / w) // 主密钥分租数

// 循环位移
#define ROTL(x, y) (((x) << ((y) & (w - 1))) | ((x) >> (w - ((y) & (w - 1)))))
#define ROTR(x, y) (((x) >> ((y) & (w - 1))) | ((x) << (w - ((y) & (w - 1)))))

// 主密钥初始化,这里是随机生成密钥用作测试,实际操作可根据需要删除
//void InitialKey(uint8_t* K)
//{
// K[0] = 3;
// for (int j = 1; j < b; j++) {
// K[j] = (uint8_t)((int)pow(3, j) % (255 - j));
// }
//}

// 密钥扩展
void generateSubkeys(uint8_t* K, uint32_t* S)
{
uint32_t L[c] = { 0 };

// 将密钥转换为小端序的32位字
for (int i = 0; i < b; i++) {
L[i / 4] |= ((uint32_t)K[i]) << (8 * (3 - (i % 4)));
}

// 子密钥生成
S[0] = 0xB7E15163;//常量Q
for (int i = 1; i < t; i++) {
S[i] = S[i - 1] - 0x61C88647;
}

// 主子密钥混合
uint32_t A = 0, B = 0;
int i = 0, j = 0;
for (int k = 0; k < 3 * t; k++) {
A = S[i] = ROTL(S[i] + A + B, 3);
B = L[j] = ROTL(L[j] + A + B, A + B);
i = (i + 1) % t;
j = (j + 1) % c;
}
}

// 加密函数
void Encrypt(uint32_t* in, uint32_t* out, uint32_t* S)
{
uint32_t A = in[0] + S[0];
uint32_t B = in[1] + S[1];
for (int i = 1; i <= r; i++) {
A = ROTL(A ^ B, B) + S[2 * i];
B = ROTL(B ^ A, A) + S[2 * i + 1];
}
out[0] = A;
out[1] = B;
}

// 解密函数
void Decrypt(uint32_t* in, uint32_t* out, uint32_t* S)
{
uint32_t A = in[0];
uint32_t B = in[1];
for (int i = r; i >= 1; i--) {
A ^= B;
B = ROTR(B - S[2 * i + 1], A) ^ A;
A = ROTR(A - S[2 * i], B) ^ B;
}
out[0] = A - S[0];
out[1] = B - S[1];
}

int main()
{
// define arrays
uint8_t K[b] = { 'W','e','l','c','o','m','e','t','o','L','3','H','C','T','F','!' };
uint32_t S[t];
uint32_t plaintext[2];
uint8_t cipher_bytes[32] = {
0x1B, 0xBB, 0xA1, 0xF2, 0xE9, 0x7C, 0x87, 0x21,
0x8A, 0x37, 0xFD, 0x0A, 0x94, 0x1A, 0x81, 0xBC,
0x40, 0x1E, 0xE3, 0xAA, 0x73, 0x2E, 0xD8, 0x3F,
0x84, 0xB8, 0x71, 0x42, 0xCC, 0x35, 0x8B, 0x39
};
uint32_t decrypted[8];
uint32_t ciphertext[8];
// 将字节序列转换为小端序的32位字
for (int i = 0; i < 8; i++) {
ciphertext[i] =
(cipher_bytes[4 * i + 3] << 24) |
(cipher_bytes[4 * i + 2] << 16) |
(cipher_bytes[4 * i + 1] << 8) |
cipher_bytes[4 * i];
}
// 密钥扩展
generateSubkeys(K, S);
for(int i =0;i<4;i++)
Decrypt(&ciphertext[i*2], &decrypted[i*2], S);
for(int j = 0;j<8;j+=2)
printf("Decrypted: %08X %08X\n", decrypted[j], decrypted[j+1]);
printf("\n\n");
printf("%s", decrypted);

return 0;
}

L3HCTF{5fd277be39046905ef6348ba89131922}

snake(复现)

这一题主要是找到加分函数在哪

sub_17BAC0里面有加分的逻辑,上面的那个if的判断是:这里会判断是否和食物坐标一样,一样的话就是吃到了豆豆,也就会加一分,那么我们可以将其改为

当坐标不一样时,视为吃,然后就可以不吃就加分,活一会就有了

patch后直接打开会随机触发不稳定的反调试,这是因为在游戏Call开头会发现一个时间检测,检测时间差是否大于80ms

我们可以在cmd中调用patch后的文件,这样的话不会崩

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

ez_android(复现)

用jadx反编译找到MainActivity类发现没东西

运行APK发现输入数据错误时提示Wrong answer

解压APk分析so文件根据Wrong answer提示,找到加密函数

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
__int64 __usercall ez_android_lib::greet@<X0>(void *a1@<X0>, size_t n27@<X1>, __int64 *a3@<X8>)
{
__int64 v6; // x23
char *flag_1; // x0
char *flag; // x20
unsigned __int64 i; // x8
unsigned __int64 i_1; // x15
unsigned __int8 v11; // w13
__int64 n16; // x22
__int64 v16; // x0
__int64 n16_1; // x8
__int64 result; // x0
_BYTE v19[11]; // [xsp+10h] [xbp-50h] BYREF

qmemcpy(&v19[8], "O2*", 3);
*v19 = 0xFC020A4C0E2C7290LL;
if ( (n27 & 0x8000000000000000LL) != 0 )
{
v6 = 0LL;
goto LABEL_30;
}
if ( !n27 )
{
flag = (&dword_0 + 1);
memcpy(&dword_0 + 1, a1, 0LL);
goto LABEL_22;
}
v6 = 1LL;
flag_1 = _rust_alloc(n27, 1LL);
if ( !flag_1 )
LABEL_30:
alloc::raw_vec::handle_error(v6, n27, &anon_87fc6f247fa1f8b0fae38f081a8df7a4_43_llvm_1227004194129537882);// "C:\\Users\\yll20\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\\lib/rustlib/src/rust\\library/alloc/src/slice.rs"
flag = flag_1;
memcpy(flag_1, a1, n27);
if ( n27 == 27 )
{
for ( i = 0LL; i != 27; ++i )
{
i_1 = i - 14;
if ( i < 14 )
i_1 = i;
v11 = H[(((2 * i) | 1) - 14 * ((147 * ((2 * i) | 1u)) >> 11))] + (flag[i] ^ H[i_1]);
flag[i] = H[(i + 4) % 14u] ^ ((v11 << (H[(i + 3) % 14u] & 7)) | (v11 >> (-H[(i + 3) % 14u] & 7)));
}
if ( *flag == 0xA409663A025150CLL
&& *(flag + 1) == 0x1FE106294065165CLL
&& *(flag + 2) == 0xFC020A4C0E2C7290LL
&& *(flag + 19) == *&v19[3] )
{
n16 = 16LL;
v16 = _rust_alloc(16LL, 1LL);
if ( v16 )
{
n16_1 = 16LL;
*v16 = Congratulations_; // Congratulations!
goto LABEL_27;
}
}
else
{
n16 = 12LL;
v16 = _rust_alloc(12LL, 1LL);
if ( v16 )
{
*(v16 + 8) = 1919252339; // Wrong answer
n16_1 = 12LL;
*v16 = *&H[14];
LABEL_27:
*a3 = n16_1;
a3[1] = v16;
a3[2] = n16_1;
return _rust_dealloc(flag, n27, 1LL);
}
}
LABEL_31:
alloc::raw_vec::handle_error(1LL, n16, &anon_87fc6f247fa1f8b0fae38f081a8df7a4_43_llvm_1227004194129537882);// "C:\\Users\\yll20\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\\lib/rustlib/src/rust\\library/alloc/src/slice.rs"
}
LABEL_22:
n16 = 12LL;
result = _rust_alloc(12LL, 1LL);
if ( !result )
goto LABEL_31;
*(result + 8) = 1919252339;
*result = *&H[14];
*a3 = 12LL;
a3[1] = result;
a3[2] = 12LL;
if ( n27 )
return _rust_dealloc(flag, n27, 1LL);
return result;
}

很明显的加密,直接进行爆破可以得到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
#include<stdio.h>
#include<stdint.h>

char H[] = "dGhpc2lzYWtleQ";
char 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 };

void enc(char* fake, int i)
{
int i_1 = i;
i_1 = i - 14;
if (i < 14)
i_1 = i;
//v11 = H[(((2 * i) | 1) - 14 * ((147 * ((2 * i) | 1)) >> 11))] + (*fake^ H[i_1]);
//*fake = H[(i + 4) % 14] ^ ((v11 << (H[(i + 3) % 14] & 7)) | (v11 >> (-H[(i + 3) % 14] & 7)));
unsigned int index = (2 * i | 1);
index = index - 14 * ((147 * index) >> 11);
unsigned char v11 = H[index] + (*fake ^ H[i_1]);
unsigned char shift = H[(i + 3) % 14] & 7;
unsigned char rotated = (v11 << shift) | (v11 >> (8 - shift));
*fake = H[(i + 4) % 14] ^ rotated;
}

int main()
{

char Input = 0;
char flag[27] = { 0 };
for (int i = 0;i < 27;i++)
{
for (int j = 32;j < 127;j++)
{
Input = j;
enc(&Input, i);
if (Input == cipher[i])
{
flag[i] = j;
break;
}
}
}
for (int i = 0;i < 27;i++)
printf("%c", flag[i]);
return 0;
}//L3HCTF{ez_rust_reverse_lol}

不能直接复制使用ida反编译的加密代码,因为char 类型在 C 语言中默认是有符号的,当值大于 127 时会变为负数,导致移位操作不正确,导致部分爆破失败flag缺失

L3HCTF{ez_rust_reverse_lol}