第三届京麒CTF挑战赛·初赛 WriteUp (Reverse方向)

2.8k 词

在这里仅给出我个人解出的题目的WriteUp.

Customize Virtual Machine:

查看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
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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
unsigned int v3; // ebp
unsigned __int64 i; // r14
void *addr; // rbp
char *len; // rbx
__int64 v7; // rax
int v8; // ecx
unsigned __int64 v9; // rcx
unsigned __int64 v10; // rcx
unsigned int v11; // eax
unsigned int j; // ebp
char v13; // bl
int n2; // ecx
__int64 i_1; // rcx
__int64 v16; // rcx
const char *p_s1; // rdi
bool v19; // [rsp+3h] [rbp-B5h]
unsigned int v20; // [rsp+4h] [rbp-B4h]
__int64 v21; // [rsp+8h] [rbp-B0h]
_OWORD s1[3]; // [rsp+10h] [rbp-A8h] BYREF
__int16 v23; // [rsp+40h] [rbp-78h]
char input[104]; // [rsp+50h] [rbp-68h] BYREF

v3 = 0;
printf("input:");
__isoc99_scanf("%50s", input);
if ( strlen(input) != 50 )
{
puts("Wrong. Try Again.");
return v3;
}
v21 = -sysconf(30);
i = 0;
v19 = 1;
v20 = 0;
while ( 1 )
{
addr = (void *)(v21 & (unsigned __int64)*(&funcs + i));
len = (char *)(lens[i] + *(&funcs + i) - (_UNKNOWN *)addr);
if ( mprotect(addr, (size_t)len, 7) == -1 )
break;
v7 = (__int64)*(&funcs + i);
v8 = lens[i];
if ( i >= 0xF )
{
if ( v8 != 1 )
{
v10 = 0;
do
*(_BYTE *)(v7 + v10++) ^= input[i];
while ( v10 < lens[i] - 1LL );
}
}
else if ( v8 )
{
v9 = 0;
do
*(_BYTE *)(v7 + v9++) ^= input[i];
while ( v9 < lens[i] );
}
mprotect(addr, (size_t)len, 5);
v19 = i++ < 0x31;
if ( i == 50 )
goto LABEL_15;
}
perror("mprotect failed");
v20 = 1;
LABEL_15:
if ( v19 )
return v20;
memset(s1, 0, sizeof(s1));
v23 = 0;
v11 = 0;
j = 0;
v13 = 0;
do
{
i_1 = idx[j];
if ( i_1 == 51 )
{
v16 = v13++;
*((_BYTE *)s1 + v16) = v11;
goto LABEL_18;
}
if ( (_DWORD)i_1 != 50 )
{
v11 = ((__int64 (__fastcall *)(_QWORD))*(&funcs + i_1))(v11);
LABEL_18:
n2 = 1;
goto LABEL_19;
}
v11 = idx[j + 1];
n2 = 2;
LABEL_19:
j += n2;
}
while ( j < 426 );
if ( bcmp(s1, "Congratulations! Your flag is flag{your_input}!^_^", 0x32u) )
p_s1 = "Wrong. Try Again.";
else
p_s1 = (const char *)s1;
puts(p_s1);
return v20;
}

注意到是输入的字节SMC解密代码,之后有一个426字节的vm加载密文并进行解密,由于flag是我们的输入所以我们其实不需要关注后面的vm部分,仅仅需要注意50个funcs的解密,而funcs的每一块都由输入的一个字节进行异或,观察这些funcs:

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
.text:00005555555551F0 unk_5555555551F0 db 0EEh                ; DATA XREF: .data:funcs↓o
.text:00005555555551F1 db 24h ; $
.text:00005555555551F2 db 0B5h
.text:00005555555551F3 db 0E0h
.text:00005555555551F4 db 93h
.text:00005555555551F5 db 48h ; H
.text:00005555555551F6 db 0A4h
.text:00005555555551F7 db 27h ; '
.text:00005555555551F8 db 47h ; G
.text:00005555555551F9 db 9Fh
.text:00005555555551FA db 0EDh
.text:00005555555551FB db 63h ; c
.text:00005555555551FC db 63h ; c
.text:00005555555551FD db 63h ; c
.text:00005555555551FE db 0E2h
.text:00005555555551FF db 27h ; '
.text:0000555555555200 db 47h ; G
.text:0000555555555259 db 47h ; G

.text:000055555555525A db 9Bh
.text:000055555555525B db 5
.text:000055555555525C db 0A0h
.text:000055555555525C ; } // starts at 5555555551F0
.text:000055555555525D db 0Fh
.text:000055555555525E db 1Fh
.text:000055555555525F db 0

发现其中绝大多数都有多个连续的三个相同字节,且值恰好在给定的flag字符集范围内,猜测是同一个dword的三个连续高位0字节,带入输入测试发现确实如此,50个块中有45块均可被这样解密,剩余的5块发现其中两个的值大于0x7F,说明其在被异或之前应该是负数(0xFFFFFF??),还有两个为b+1,b,b,推测是大于1个字节,第二位的b被覆盖,而倒数第四个函数块没有找到类似的结构,经过推测猜测首字节应该为异或(0x83),经过测试发现确实如此,提取得到输入c9z2cn9jmvkh30aqjwrb3urxtkp10q8b0vr_9dbfrocalkn1v5
flag{c9z2cn9jmvkh30aqjwrb3urxtkp10q8b0vr_9dbfrocalkn1v5}.

drillbeam:

查看java层:

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
package com.primite.drillbeam;
import android.os.Bundle;
import android.text.method.DigitsKeyListener;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
/* compiled from: MainActivity.kt */
@Metadata(m146d1 = {"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0003\u001a\u00020\u00042\b\u0010\u0005\u001a\u0004\u0018\u00010\u0006H\u0014¨\u0006\u0007"}, m147d2 = {"Lcom/primite/drillbeam/MainActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "app_release"}, m148k = 1, m149mv = {1, 9, 0}, m151xi = ConstraintLayout.LayoutParams.Table.LAYOUT_CONSTRAINT_VERTICAL_CHAINSTYLE)
/* loaded from: classes.dex */
public final class MainActivity extends AppCompatActivity {
/* JADX INFO: Access modifiers changed from: protected */
@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable$default(this, null, null, 3, null);
setContentView(C0792R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(C0792R.id.main), new OnApplyWindowInsetsListener() { // from class: com.primite.drillbeam.MainActivity$$ExternalSyntheticLambda0
@Override // androidx.core.view.OnApplyWindowInsetsListener
public final WindowInsetsCompat onApplyWindowInsets(View view, WindowInsetsCompat windowInsetsCompat) {
WindowInsetsCompat onCreate$lambda$0;
onCreate$lambda$0 = MainActivity.onCreate$lambda$0(view, windowInsetsCompat);
return onCreate$lambda$0;
}
});
final EditText editText = (EditText) findViewById(C0792R.id.editText);
editText.setKeyListener(DigitsKeyListener.getInstance("0123456789abcdef"));
((Button) findViewById(C0792R.id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.primite.drillbeam.MainActivity$$ExternalSyntheticLambda1
@Override // android.view.View.OnClickListener
public final void onClick(View view) {
MainActivity.onCreate$lambda$1(editText, this, view);
}
});
}
/* JADX INFO: Access modifiers changed from: private */
public static final WindowInsetsCompat onCreate$lambda$0(View v, WindowInsetsCompat insets) {
Intrinsics.checkNotNullParameter(v, "v");
Intrinsics.checkNotNullParameter(insets, "insets");
Insets insets2 = insets.getInsets(WindowInsetsCompat.Type.systemBars());
Intrinsics.checkNotNullExpressionValue(insets2, "getInsets(...)");
v.setPadding(insets2.left, insets2.top, insets2.right, insets2.bottom);
return insets;
}
/* JADX INFO: Access modifiers changed from: private */
public static final void onCreate$lambda$1(EditText editText, MainActivity this$0, View view) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
if (new Check().calc(editText.getText().toString()).equals("8c1ee8d42e211d6b649e0b9c33bdc836fc912709")) {
((TextView) this$0.findViewById(C0792R.id.textView)).setText("Congrats, flag为flag{你的输入}");
}
}
}

输入值只能是0~f,在java层拿到密文8c1ee8d42e211d6b649e0b9c33bdc836fc912709,进入so层分析,查看所有函数,找到一个最关键的:

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
void *__fastcall XXTEA(const void *src_1, size_t n, __int128 *a3, _QWORD *a4)
{
enum { = 0x0, = 0x1,} *res; // x8
void *dest_1; // x23
size_t v7; // x8
unsigned int after; // w12
__int64 v12; // [xsp+20h] [xbp-F0h]
void *dest_2; // [xsp+28h] [xbp-E8h]
_DWORD *dest_3; // [xsp+30h] [xbp-E0h]
unsigned __int32 before; // [xsp+3Ch] [xbp-D4h]
__int64 i; // [xsp+40h] [xbp-D0h]
unsigned int m_1; // [xsp+48h] [xbp-C8h]
__int32 sum; // [xsp+4Ch] [xbp-C4h]
unsigned int round; // [xsp+5Ch] [xbp-B4h]
__int64 t; // [xsp+60h] [xbp-B0h]
size_t v21; // [xsp+80h] [xbp-90h]
size_t length; // [xsp+88h] [xbp-88h]
int *m; // [xsp+90h] [xbp-80h]
_DWORD *key; // [xsp+A0h] [xbp-70h]
int *final; // [xsp+C0h] [xbp-50h]
int delta; // [xsp+C8h] [xbp-48h]
int ttl; // [xsp+E0h] [xbp-30h]
unsigned int e; // [xsp+E4h] [xbp-2Ch]
enum { = 0x0, = 0x1,} *res_1; // [xsp+E8h] [xbp-28h]
__int128 a3_1; // [xsp+F0h] [xbp-20h] BYREF
__int64 v31; // [xsp+100h] [xbp-10h]

v31 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
a3_1 = *a3;
t = 0;
if ( !(_BYTE)a3_1 )
goto LABEL_3;
if ( !BYTE1(a3_1) )
{
t = 1;
goto LABEL_3;
}

if ( !BYTE14(a3_1) )
{
t = 14;
LABEL_3:
memset((char *)&a3_1 + t + 1, 0, t ^ 0xF);
}
v12 = 0;
if ( n )
{
v7 = n >> 2;
if ( (n & 3) != 0 )
++v7;
v21 = v7;
length = v7 + 1;
m = (int *)calloc(v7 + 1, 4u);
v12 = 0;
if ( m )
{
m[v21] = n;
memcpy(m, src_1, n);
key = calloc(4u, 4u);
if ( key )
{
*(_OWORD *)key = a3_1;
final = &m[(unsigned int)(length - 1)];
if ( (_DWORD)length != 1 )
{
delta = ::delta;
*(_QWORD *)&sum = (unsigned int)*final;
for ( round = 52 / (unsigned int)length + 5; ; --round )
{
ttl = *(&sum + 1) + delta;
e = ((unsigned int)(*(&sum + 1) + delta) >> 2) & 3;
i = 0;
before = sum;
m_1 = *m;
while ( 1 )
{
after = m[i + 1];
LODWORD(res_1) = ((((4 * after) ^ (before >> 5)) + ((after >> 3) ^ (16 * before)))
^ ((key[i & 3 ^ e] ^ before) + (after ^ ttl)))
+ m_1;
m[i] = (int)res_1;
if ( i + 1 == (_DWORD)length - 1 )
break;
m_1 = after;
++i;
before = (unsigned int)res_1;
}
LODWORD(res) = ((((4 * *m) ^ ((unsigned int)res_1 >> 5)) + (((unsigned int)*m >> 3) ^ (16 * (_DWORD)res_1)))
^ ((key[e ^ ((_BYTE)length - 1) & 3] ^ (unsigned int)res_1) + (*m ^ ttl)))
+ *final;
*final = (int)res;
if ( !round )
break;
sum = (int)res;
*(&sum + 1) += delta;
}
}
dest_1 = malloc((4 * length) | 1);
memcpy(dest_1, m, 4 * length);
*((_BYTE *)dest_1 + 4 * length) = 0;
*a4 = 4 * length;
free(m);
dest_2 = dest_1;
dest_3 = key;
}
else
{
dest_2 = 0;
dest_3 = m;
}
free(dest_3);
return dest_2;
}
}
return (void *)v12;
}

这是一个典型的有长度填充的XXTEA加密,查看密钥和delta值,发现delta值在这个函数中被初始化:

1
2
3
4
void sub_217C()
{
delta = 0x7C1CA67A;
}

Key从外界传入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall brf3(__int64 a1, __int64 a2, __int64 a3)
{
int n_1; // w0
size_t n; // x21
const void *src; // [xsp+40h] [xbp-30h]
_QWORD v9[2]; // [xsp+60h] [xbp-10h] BYREF

v9[1] = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
n_1 = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1344LL))(a1, a3);
n = n_1;
v9[0] = n_1;
src = (const void *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1352LL))(a1, a3, 0);
xorf1(&unk_6510, gen_114514); // 114514
XXTEA(src, n, (__int128 *)&unk_6510, v9);
malloc((2LL * v9[0]) | 1);
__asm { BR X9 }
}

其中xorf1将其第二个参数解密得到密钥114514,在后续的XXTEA中填充为16位,为了确认题目采取的加密形式,编写frida脚本hook Java层:

1
2
3
4
5
6
7
8
9
Java.perform(function () {
var CheckClass = Java.use('com.primite.drillbeam.Check');
CheckClass.calc.overload('java.lang.String').implementation = function (input) {
console.log('[*] 输入: ' + input + " " + input.length);
var result = this.calc(input);
console.log('[*] 加密结果: ' + result + " " + result.length);
return result;
};
});

经过观察比对发现,虽然要求输入的是0~f的值,猜测按两个字符为一个字节进行解析,但是实际测试发现密文的长度与字节数量并不成4的比例,反而与字符数量成4的比例,结合so层的scanf %02x没有任何xref猜测实际上传入的还是字符串,在加密时还会按ASCII值进行解析,编写同构加解密测试脚本发现结果并不相同,又因为密钥是从函数外界传入的参数,而delta值是全局dword,猜测可能暗改了delta值,编写脚本进行爆破:

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
def b2dle(byte_array):
return [int.from_bytes(byte_array[i:i+4], byteorder='little', signed=False) for i in range(0, len(byte_array), 4)]

def d2ble(dword_array):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='little'))
return byte_list

def XXTEA(m, leng, k, delta):
rounds = 6 + 52 // leng
ttl = 0
for j in range(rounds - 1, -1, -1):
ttl = (ttl + delta) & 0xFFFFFFFF
for i in range(leng):
after = m[(i + 1) % len(m)]
before = m[(i - 1) % len(m)]
m[i] = (m[i] + ((((before >> 5) ^ (after << 2)) + ((after >> 3) ^ (before << 4))) ^ ((ttl ^ after) + (k[(i & 3) ^ ((ttl >> 2) & 3)] ^ before)))) & 0xFFFFFFFF
return m

def de_XXTEA(m, leng, k, delta):
rounds = 6 + 52 // leng
ttl = ((rounds + 1) * delta) & 0xFFFFFFFF
for j in range(rounds):
ttl = (ttl - delta) & 0xFFFFFFFF
for i in range(leng - 1, -1, -1):
after = m[(i + 1) % len(m)]
before = m[(i - 1) % len(m)]
m[i] = (m[i] - ((((before >> 5) ^ (after << 2)) + ((after >> 3) ^ (before << 4))) ^ ((ttl ^ after) + (k[(i & 3) ^ ((ttl >> 2) & 3)] ^ before)))) & 0xFFFFFFFF
return m

key = bytearray(b"114514\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
test = bytearray(b"1111111111111111\x10")
dwordkey = b2dle(key)
dwordtest = b2dle(test)

for delta in range(0x100000000):
dwordresult = XXTEA(dwordtest.copy(), len(dwordtest), dwordkey, delta)
result = bytes(d2ble(dwordresult)).hex()
print(result, len(result))
target = "949e2a0a286af84e1d42cb9fffc31deb4af77f1d"
if result == target:
print(hex(delta))
break

发现delta值被改为了0x7c1ca759,测试加密结果与程序相同,但是在解密时发现解出来的东西一团乱麻,不符合之前输入的格式,仔细观察整个程序的所有函数,发现还有sysconf获取系统状态,popen管道创建与pclose管道关闭,mmap内存映射等,猜测还含有检测系统状态的反hook与反调试等,由于实在是不易分析,尝试从密文直接爆破flag,由于有填充,最后一个dword一定小于256,所以解密结果的最后6字符一定为000000,编写脚本爆破全部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
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
#include <iostream>
#include <vector>
#include <cstdint>
#include <thread>
#include <mutex>
#include <iomanip>
#include <sstream>
#include <atomic>
#include <cmath>

struct Result {
uint32_t delta;
std::string hex_result;
};

std::mutex result_mutex;
std::vector<Result> all_results;
std::atomic<uint64_t> total_processed(0);
uint64_t total_range = 0xFFFFFFFF - 0x00000000;

std::vector<uint32_t> b2dle(const std::vector<uint8_t>& bytes) {
std::vector<uint32_t> result;
for (size_t i = 0; i < bytes.size(); i += 4) {
uint32_t val = 0;
for (int j = 0; j < 4 && i + j < bytes.size(); ++j) {
val |= static_cast<uint32_t>(bytes[i + j]) << (j * 8);
}
result.push_back(val);
}
return result;
}

std::vector<uint8_t> d2ble(const std::vector<uint32_t>& dwords) {
std::vector<uint8_t> result;
for (uint32_t d : dwords) {
result.push_back(static_cast<uint8_t>(d));
result.push_back(static_cast<uint8_t>(d >> 8));
result.push_back(static_cast<uint8_t>(d >> 16));
result.push_back(static_cast<uint8_t>(d >> 24));
}
return result;
}

void de_XXTEA(std::vector<uint32_t>& v, int leng, const std::vector<uint32_t>& k, uint32_t delta) {
int rounds = 6 + 52 / leng;
uint32_t ttl = ((rounds + 1ULL) * delta) & 0xFFFFFFFF;
for (int j = 0; j < rounds; ++j) {
ttl = (ttl - delta) & 0xFFFFFFFF;
for (int i = leng - 1; i >= 0; --i) {
uint32_t after = v[(i + 1) % leng];
uint32_t before = v[(i - 1 + leng) % leng];
v[i] = (v[i] - (
(((before >> 5) ^ (after << 2)) + ((after >> 3) ^ (before << 4)))
^
((ttl ^ after) + (k[(i & 3) ^ ((ttl >> 2) & 3)] ^ before))
)) & 0xFFFFFFFF;
}
}
}

std::vector<uint8_t> hex_to_bytes(const std::string& hex) {
std::vector<uint8_t> bytes;
for (size_t i = 0; i < hex.length(); i += 2) {
std::string byte_str = hex.substr(i, 2);
uint8_t byte = static_cast<uint8_t>(strtol(byte_str.c_str(), nullptr, 16));
bytes.push_back(byte);
}
return bytes;
}

void brute_thread(uint32_t start_delta, uint32_t end_delta,
const std::vector<uint32_t>& enc_data,
const std::vector<uint32_t>& key,
int length) {
std::vector<uint32_t> m(length);
uint64_t chunk_size = end_delta - start_delta + 1;
uint64_t report_interval = chunk_size / 100 + 1;

for (uint32_t delta = start_delta; delta <= end_delta; ++delta) {
m = enc_data;
de_XXTEA(m, length, key, delta);
auto decrypted_bytes = d2ble(m);
std::ostringstream oss;
for (auto b : decrypted_bytes) {
oss << std::setw(2) << std::setfill('0') << std::hex << static_cast<int>(b);
}
std::string hex_result = oss.str();
if (hex_result.size() >= 6 && hex_result.substr(hex_result.size() - 6) == "000000") {
std::lock_guard<std::mutex> lock(result_mutex);
all_results.emplace_back(Result{delta, hex_result});
}
uint64_t count = total_processed.fetch_add(1, std::memory_order_relaxed);
if (count % report_interval == 0 || count == total_range - 1) {
double progress = static_cast<double>(count) / total_range * 100.0;
std::lock_guard<std::mutex> lock(result_mutex);
std::cout << "\rProgress: [" << std::fixed << std::setprecision(2) << progress << "%]";
std::cout.flush();
}
}
}

int main() {
std::vector<uint32_t> key = {0x35343131, 0x3431, 0, 0};
std::string enc_hex = "8c1ee8d42e211d6b649e0b9c33bdc836fc912709";
auto enc_bytes = hex_to_bytes(enc_hex);
auto enc_dwords = b2dle(enc_bytes);
int length = static_cast<int>(enc_dwords.size());
const uint32_t thread_count = 32;
const uint32_t per_thread = static_cast<uint32_t>(total_range / thread_count);
std::vector<std::thread> threads;
for (uint32_t t = 0; t < thread_count; ++t) {
uint32_t start = 0x00000000 + t * per_thread;
uint32_t end = (t == thread_count - 1) ? 0xFFFFFFFF : start + per_thread - 1;
threads.emplace_back(brute_thread, start, end, enc_dwords, key, length);
}
for (auto& th : threads) {
if (th.joinable()) th.join();
}
std::cout << "\nSearch completed." << std::endl;
if (!all_results.empty()) {
std::cout << "Found " << all_results.size() << " matching results:" << std::endl;
for (const auto& res : all_results) {
std::cout << "Delta: 0x" << std::hex << res.delta
<< " | Hex: " << res.hex_result << std::endl;
}
} else {
std::cout << "No matching result found." << std::endl;
}
return 0;
}

筛选输出,发现其中有一个解:Delta: 0x7c1ca806 | Hex: 6165313466623332396265353138626310000000符合结果,flag{ae14fb329be518bc}.