2025第八届“强网”拟态防御国际精英挑战赛Final复现

本文最后更新于 2025年12月3日 晚上

Re

easyre

tea 算法魔改

这里重点是有一个 signal 函数,它会捕获事件,第一个参数是标识符,第二个参数是指向信号处理函数的指针,这里的就是 Function

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
__int64 sub_7FF6F92596F0()
{
__int64 (__fastcall *v0)(_QWORD); // rbx
FILE *v1; // rax
FILE *v2; // rax
__m128i Buffer; // [rsp+20h] [rbp-50h] BYREF
__int64 v5; // [rsp+30h] [rbp-40h]
__int64 v6; // [rsp+38h] [rbp-38h]
__int64 v7; // [rsp+40h] [rbp-30h]
__int64 v8; // [rsp+48h] [rbp-28h]
__int64 v9; // [rsp+50h] [rbp-20h]
__int64 v10; // [rsp+58h] [rbp-18h]

sub_7FF6F92519B7();
v0 = off_7FF6F925A170;
Buffer = 0ui64;
v5 = 0i64;
v6 = 0i64;
v7 = 0i64;
v8 = 0i64;
v9 = 0i64;
v10 = 0i64;
v1 = off_7FF6F925A170(1i64);
setvbuf(v1, 0i64, 4, 0i64);
sub_7FF6F9252FD0("Input flag: ");
v2 = v0(0i64);
if ( !fgets(Buffer.m128i_i8, 64, v2) )
return 1i64;
Buffer.m128i_i8[strcspn(Buffer.m128i_i8, "\n")] = 0;
if ( strlen(Buffer.m128i_i8) == 32 )
{
signal(11, Function);
if ( !setjmp(Buf) )
{
MEMORY[0] = 0;
BUG();
}
sub_7FF6F9251850(&Buffer);
}
else
{
puts("Wrong length! Hint: 32 chars.");
}
return 0i64;
}

而 Function 函数对 xmmword_7FF6F925A040 变量进行了异或和加减处理

1
2
3
4
5
6
7
8
9
10
11
void __fastcall __noreturn Function()
{
xmmword_7FF6F925A040 = _mm_unpacklo_epi64(
_mm_unpacklo_epi32(
_mm_cvtsi32_si128(xmmword_7FF6F925A040 ^ 0xDEADBEEF),
_mm_cvtsi32_si128(DWORD1(xmmword_7FF6F925A040) - 2023406815)),
_mm_unpacklo_epi32(
_mm_cvtsi32_si128(DWORD2(xmmword_7FF6F925A040) - 287454020),
_mm_cvtsi32_si128(HIDWORD(xmmword_7FF6F925A040) ^ 0xCCDDEEFF)));
longjmp(Buf, 1);
}

跟进 sub_7FF6F9251850 这边可以发现是 tea 魔改加密,delta 为 1640531527,密钥是 xmmword_7FF6F925A040

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
int __fastcall sub_7FF6F9251850(const __m128i *a1)
{
__m128i v1; // xmm1
char *v2; // rax
_DWORD *i; // rdx
__int128 v5[2]; // [rsp+20h] [rbp-28h] BYREF
char v6; // [rsp+40h] [rbp-8h] BYREF

v1 = _mm_loadu_si128(a1 + 1);
v5[0] = _mm_loadu_si128(a1);
v5[1] = v1;
sub_7FF6F9251790(v5, 8, &xmmword_7FF6F925A040);
v2 = v5;
for ( i = &unk_7FF6F925A020; *v2 == *i; ++i )
{
v2 += 4;
if ( v2 == &v6 )
return puts("Success! You got the flag.");
}
return puts("Wrong flag! Try again.");
}


__int64 __fastcall sub_7FF6F9251790(unsigned int *a1, int a2, _DWORD *a3)
{
unsigned int *v3; // rsi
unsigned int *v4; // rbp
unsigned int v5; // ecx
unsigned int v6; // edx
int v7; // r9d
__int64 result; // rax

if ( a2 > 0 )
{
v3 = a1;
v4 = &a1[2 * ((a2 - 1) >> 1) + 2];
do
{
v5 = *v3;
v6 = v3[1];
v7 = 0;
do
{
v7 -= 1640531527;
v5 += (v7 + v6) ^ (a3[1] + (v6 >> 5)) ^ (*a3 + 16 * v6);
result = (v7 + v5) ^ (a3[3] + (v5 >> 5)) ^ (a3[2] + 16 * v5);
v6 += result;
}
while ( v7 != -957401312 );
*v3 = v5;
v3 += 2;
*(v3 - 1) = v6;
}
while ( v3 != v4 );
}
return result;
}

而现在可以看到密钥的值为,可以直接进行处理,或者动调直接拿处理过的值

1
xmmword_7FF6F925A040 xmmword 76543210FEDCBA989ABCDEF012345678h

动调之后会拿到处理后的密钥

解密脚本如下:

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

int main(void) {

uint8_t cipher_bytes[32] = {
0x86,0x29,0xC2,0xE1,0xC5,0xDD,0x9E,0xD3,0x4D,0x48,0xA1,0xDF,0x3C,0xE5,0xD4,0x10,
0xE4,0x3B,0x9A,0xC4,0x8A,0xF4,0xDB,0x77,0x29,0xAE,0xEB,0xE5,0x5C,0xEC,0x9F,0xE9
};

uint32_t a3[4] = {
0xCC99E897,
0x22222211,
0xEDBA8754,
0xBA89DCEF
};
uint32_t v[8];
for (int i = 0; i < 8; ++i) {
v[i] = cipher_bytes[i*4] | (cipher_bytes[i*4+1] << 8) | (cipher_bytes[i*4+2] << 16) | (cipher_bytes[i*4+3] << 24);
}

const uint32_t delta = 1640531527U;
uint32_t v7 = (uint32_t)((int32_t)-957401312);
const int pairs = 4;

int steps = 0;

while (1) {
for (int p = pairs - 1; p >= 0; --p) {
int i = p * 2;

uint32_t v5 = v[i];
uint32_t v6 = v[i+1];

uint32_t result =((v7 + v5) ^ (a3[3] + (v5 >> 5)) ^ (a3[2] + (v5 << 4)));

v6 -= result;

uint32_t tmp = ((v7 + v6) ^ (a3[1] + (v6 >> 5)) ^ (a3[0] + (v6 << 4)));

v5 -= tmp;

v[i] = v5;
v[i+1] = v6;
}

v7 += delta;
steps++;
if (v7 == 0) break;
}

uint8_t out[32];
for (int i = 0; i < 8; ++i) {
out[i*4] = v[i] & 0xFF;
out[i*4 + 1] = (v[i] >> 8) & 0xFF;
out[i*4 + 2] = (v[i] >> 16) & 0xFF;
out[i*4 + 3] = (v[i] >> 24) & 0xFF;
}

for (int i=0;i<32;i++) putchar(out[i]);
printf("\n");

return 0;
}

得到 flag

1
flag{s1gn4l_h4ndl3r_1s_tr1cky!!}

weakjump

flag 长为 32 字节,一系列加密后和 unk_47A120 比对

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
__int64 sub_401620()
{
__int64 v0; // rax
__int64 v1; // rdx
__int64 v2; // r15
unsigned __int64 v3; // rbx
int v4; // r12d
unsigned __int64 v5; // rsi
unsigned int v6; // ebx
int v7; // edx
__int64 v8; // rsi
__int64 v9; // r15
int v10; // eax
unsigned int v11; // r12d
int v12; // edx
unsigned int v13; // r15d
unsigned int v14; // r13d
int v15; // eax
int v16; // eax
__int64 v17; // rax
__int64 v19; // [rsp+10h] [rbp-E0h]
int v20; // [rsp+18h] [rbp-D8h]
int v21; // [rsp+1Ch] [rbp-D4h]
char *v22; // [rsp+20h] [rbp-D0h]
unsigned int v23; // [rsp+38h] [rbp-B8h]
int v24; // [rsp+3Ch] [rbp-B4h]
_QWORD v25[22]; // [rsp+40h] [rbp-B0h] BYREF

sub_404810();
sub_408E90("Provide the flag for WeakJump:");
if ( sub_4089D0(v25, 128LL, off_4A86D8) )
{
v0 = sub_4010B8(v25);
if ( v0 )
{
v1 = v0 - 1;
if ( *((_BYTE *)&v24 + v0 + 3) != 10 )
v1 = v0;
if ( v1 == 32 )
{
v22 = (char *)&unk_47A120;
v2 = 0LL;
LABEL_7:
v3 = v25[v2];
sub_404810();
v4 = v3 ^ dword_4A9B10;
v5 = HIBYTE(v3);
v6 = 0;
v7 = v5;
v8 = v2;
v9 = v2 + 1;
v10 = (v7 << 24) | BYTE4(v3) | (BYTE6(v3) << 16) | (BYTE5(v3) << 8);
v21 = v9;
v19 = v9;
v20 = -1640531535 * v9;
v11 = __ROL4__(v10, v9) ^ (-1640531535 * v9) ^ v4;
v12 = 2064213139 * v9;
v13 = v10 ^ (79225 * v9 - 79225) ^ (2088004713 * v9 + 23791574);
v14 = dword_4A9B10 ^ (v12 + 47583148);
while ( 1 )
{
v15 = sub_404D10(v13, v8, v14, v6++);
v16 = v11 ^ v15;
v11 = v13;
if ( v6 == 8 )
break;
v13 = v16;
}
v2 = v19;
v23 = v11 ^ v14 ^ (v20 - 1640531535);
v24 = dword_4A9B10 ^ (2135587861 * v21 + 2088004713) ^ v14 ^ v16;
v17 = 0LL;
while ( *((_BYTE *)&v23 + v17) == v22[v17] )
{
if ( ++v17 == 8 )
{
v22 += 8;
if ( v19 != 4 )
goto LABEL_7;
sub_408E90("WeakJump clear, congratulations!");
return 0LL;
}
}
}
}
sub_408E90("Nope, WeakJump resists you.");
}
else
{
sub_408E90("Input error");
}
return 1LL;
}

sub_404D10 这里是加密函数,sub_404A70 和 sub_404BC0 定义了 S 盒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall sub_404D10(unsigned int a1, __int64 a2, int a3, int a4)
{
unsigned int v5; // r14d
__int64 v6; // rax
int v8; // [rsp+Ch] [rbp-54h]
unsigned __int64 v9; // [rsp+18h] [rbp-48h] BYREF
unsigned int v10; // [rsp+20h] [rbp-40h] BYREF
int v11; // [rsp+24h] [rbp-3Ch]
int v12; // [rsp+28h] [rbp-38h]
int v13; // [rsp+2Ch] [rbp-34h]

v5 = a3 ^ (-1640531535 * (a4 + a2 + 1));
v6 = (unsigned int)(5 * a4);
v10 = a1 ^ v5 ^ (2135587861 * (a2 + 3 * a4 + 3));
v11 = __ROL4__(a1 - 1640531535 * (v6 + 8), (a1 & 7) + 1);
v12 = a4 ^ dword_4A9B10 ^ (2135587861 * (3 * (a2 + 1) + 7 * a4));
v13 = __ROL4__(v10 ^ v12 ^ v11 ^ (a4 + 17 * a2), (a1 >> 3) & 0x1E | 1);
v9 = (v6 + (unsigned __int64)(unsigned __int8)a1 + 13 * a2) % 0xE0;
v8 = sub_404A70(&v10, &v9, v5, (unsigned int)a2);
sub_404BC0(&v10, v5 ^ (170 * a4), (unsigned int)a2);
return v8 ^ __ROL4__(a1, a4 + a2 + 1) ^ v13 ^ v12 ^ v11 ^ v10;
}

sub_404810 是一个反调试,直接 nop 掉 call 他的片段,调试之后拿到 dword_4A9B10 的值

加密轮函数调用的东西太多了,直接逆向不方便,用 Unicorn 模拟执行

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
import struct
import sys
from unicorn import *
from unicorn.x86_const import *
from elftools.elf.elffile import ELFFile

BINARY_FILE = "weakjump"

FUNC_F_ADDR = 0x404D10
ADDR_MAGIC = 0x4A9B10

REAL_MAGIC = 0xC3F1E2D4

CIPHERTEXT = bytes([
0x60, 0x91, 0xF3, 0x93, 0x32, 0xCD, 0xDF, 0xB8,
0x23, 0x43, 0x55, 0x2F, 0x9F, 0xD4, 0xFE, 0xE8,
0x8E, 0x7B, 0x5A, 0x36, 0xDE, 0xB6, 0x7F, 0xD7,
0x97, 0x38, 0xEE, 0x43, 0xB6, 0x8D, 0xB0, 0xB2
])

def rol4(value, count):
count &= 31
return ((value << count) | (value >> (32 - count))) & 0xFFFFFFFF

def align_page_down(addr):
return addr & ~0xFFF

def align_page_up(addr):
return (addr + 0xFFF) & ~0xFFF

class VMEngine:
def __init__(self, binary_path):
print("[*] 初始化 Unicorn 引擎...")
self.uc = Uc(UC_ARCH_X86, UC_MODE_64)
try:
with open(binary_path, 'rb') as f:
elf = ELFFile(f)
for seg in elf.iter_segments():
if seg.header.p_type == 'PT_LOAD':
vaddr = seg.header.p_vaddr
memsz = seg.header.p_memsz
data = seg.data()

map_base = align_page_down(vaddr)
map_end = align_page_up(vaddr + memsz)
map_size = map_end - map_base

print(f"[*] Mapping Segment: {hex(vaddr)} (size: {hex(memsz)}) -> Map {hex(map_base)} size {hex(map_size)}")

try:
self.uc.mem_map(map_base, map_size, UC_PROT_ALL)
except UcError:
pass

self.uc.mem_write(vaddr, data)

except ImportError:
print("[!] 请先安装 pyelftools: pip install pyelftools")
sys.exit(1)
except Exception as e:
print(f"[!] 加载 ELF 失败: {e}")
sys.exit(1)

stack_base = 0x70000000
stack_size = 0x100000
self.uc.mem_map(stack_base, stack_size, UC_PROT_READ | UC_PROT_WRITE)
self.uc.reg_write(UC_X86_REG_RSP, stack_base + stack_size - 0x1000)

print(f"[*] Patching Magic at {hex(ADDR_MAGIC)} to {hex(REAL_MAGIC)}")
self.uc.mem_write(ADDR_MAGIC, struct.pack('<I', REAL_MAGIC))

def call_F(self, a1, a2, a3, a4):
# Linux x64 ABI: RDI, RSI, RDX, RCX
self.uc.reg_write(UC_X86_REG_RDI, a1)
self.uc.reg_write(UC_X86_REG_RSI, a2)
self.uc.reg_write(UC_X86_REG_RDX, a3)
self.uc.reg_write(UC_X86_REG_RCX, a4)

rsp = self.uc.reg_read(UC_X86_REG_RSP)
self.uc.mem_write(rsp, struct.pack('<Q', 0))

try:
self.uc.emu_start(FUNC_F_ADDR, 0, count=0, timeout=0)
except UcError:
pass
return self.uc.reg_read(UC_X86_REG_RAX) & 0xFFFFFFFF

def decrypt_block(vm, block_idx, final_L, final_R):
v9 = block_idx + 1

v20 = (-1640531535 * v9) & 0xFFFFFFFF
v21 = v9
v12 = (2064213139 * v9) & 0xFFFFFFFF
v14 = REAL_MAGIC ^ ((v12 + 47583148) & 0xFFFFFFFF)

term_L = (v20 - 1640531535) & 0xFFFFFFFF
feistel_L = final_L ^ v14 ^ term_L

term_R = (2135587861 * v21 + 2088004713) & 0xFFFFFFFF
feistel_R = final_R ^ REAL_MAGIC ^ term_R ^ v14

cur_L, cur_R = feistel_L, feistel_R
arg_a2 = block_idx

for i in range(7, -1, -1):
prev_R = cur_L
f_val = vm.call_F(prev_R, arg_a2, v14, i)
prev_L = cur_R ^ f_val

cur_L, cur_R = prev_L, prev_R

init_v11, init_v13 = cur_L, cur_R
term_13 = ((79225 * v9 - 79225) ^ (2088004713 * v9 + 23791574)) & 0xFFFFFFFF
v10 = init_v13 ^ term_13

term_11 = (-1640531535 * v9) & 0xFFFFFFFF
v4 = init_v11 ^ term_11 ^ rol4(v10, v9)

v3_low = v4 ^ REAL_MAGIC
v3_high = v10

return struct.pack('<II', v3_low, v3_high)

def main():
print("=== WeakJump Solver (Replication Version) ===")
vm = VMEngine(BINARY_FILE)

full_flag = b""
for i in range(4):
block_bytes = CIPHERTEXT[i*8 : (i+1)*8]
final_L, final_R = struct.unpack('<II', block_bytes)

print(f"[*] Decrypting Block {i}...")
decrypted = decrypt_block(vm, i, final_L, final_R)
print(f" Hex: {decrypted.hex()}")
full_flag += decrypted

print("\n" + "="*40)
print(f"FLAG: {full_flag.decode('utf-8', errors='replace')}")
print("="*40)

if __name__ == "__main__":
main()

拿到 flag

1
flag{b10ck_vm_plu5_3xtr4_1337!!}

ezvm

vm 虚拟机,核心在 sub_7FF6AFCF1A00 函数,通过 v4 和 v9 指针进行异或,移动

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
__int64 __fastcall sub_7FF6AFCF1A00(__int64 a1, int a2)
{
int v2; // r13d
int v3; // r15d
char *v4; // r8
int v5; // edi
int v6; // ebp
char *v7; // rsi
int v8; // eax
int *v9; // r10
__int16 v10; // r9
char v11; // r14
__int16 v12; // dx
char v13; // cl
__int16 v14; // bx
__int16 v15; // dx
char v16; // cl
int v17; // ecx
__int64 v18; // rbx
char v19; // dl
char v20; // dl
int v22; // ecx
char v23; // dl

v2 = 0;
v3 = 0;
v4 = &unk_7FF6AFCFA020;
v5 = 0;
v6 = 85;
v7 = &unk_7FF6AFCFA020 + 578;
v8 = 0;
v9 = &dword_7FF6AFCFA270 + 4;
while ( 1 )
{
v10 = *v4;
v11 = *(v9 + ((v4 - &unk_7FF6AFCFA020 + 1) & 3));
LOBYTE(v12) = v8 ^ *(v9 + ((v4 - &unk_7FF6AFCFA020) & 3));
v13 = v12;
HIBYTE(v12) = v11;
v14 = *v4;
v15 = *v4 ^ v12;
*v4 = v15;
if ( v13 == v10 )
break;
v16 = HIBYTE(v15);
v4 += 2;
if ( v15 == 0x80 )
{
v8 *= HIBYTE(v15);
goto LABEL_7;
}
if ( v15 <= 0x80u )
{
if ( v15 == 48 )
{
v8 = (HIBYTE(v15) ^ v8) + 2 * (HIBYTE(v15) & v8);
goto LABEL_7;
}
if ( v15 <= 0x30u )
{
if ( v15 == 16 )
{
v22 = HIBYTE(v15);
if ( v11 == HIBYTE(v14) )
v22 = v5;
if ( v22 < a2 )
v8 = *(a1 + v22);
goto LABEL_7;
}
if ( v15 != 32 )
goto LABEL_40;
v8 ^= HIBYTE(v15);
goto LABEL_7;
}
if ( v15 == 64 )
{
LOBYTE(v8) = __ROL1__(v8, SHIBYTE(v15));
goto LABEL_7;
}
if ( v15 != 80 )
goto LABEL_40;
*(v4 - 1) = v10;
if ( v11 == HIBYTE(v14) )
v16 = v5;
LOBYTE(v8) = *(&dword_7FF6AFCFA270 + (v16 & 0xF)) ^ v8;
if ( v4 == v7 )
return (v3 == 0) & (v2 == 32);
}
else
{
if ( v15 == 0xC0 )
{
++v5;
}
else
{
if ( v15 > 0xC0u )
{
if ( v15 == 0xE0 )
v6 = v8;
else
v3 = 255;
goto LABEL_7;
}
if ( v15 != 0x90 )
{
if ( v15 == 0xA0 )
{
v17 = HIBYTE(v15);
if ( v11 == HIBYTE(v14) )
v17 = v5;
if ( v17 <= 31 )
{
if ( v17 )
{
v18 = v17;
v19 = *(a1 + v17 - 1) ^ v17 & 3;
if ( v17 == 1 || (v19 ^= *(a1 + v17 - 2), v17 == 2) )
v20 = v8 ^ v19;
else
v20 = v8 ^ *(a1 + v17 - 3) ^ v19;
}
else
{
v20 = v8;
v18 = 0i64;
}
++v2;
v23 = *(&dword_7FF6AFCFA270 + (v17 & 0xF)) ^ byte_7FF6AFCFA2A0[v18] ^ v20 ^ 0xA5;
v3 = (v3 + ((2 * v23) | v23));
}
goto LABEL_7;
}
LABEL_40:
v3 = 255;
goto LABEL_7;
}
v8 ^= v6;
}
LABEL_7:
*(v4 - 1) = v10;
if ( v4 == v7 )
return (v3 == 0) & (v2 == 32);
}
}
*v4 = v10;
return (v3 == 0) & (v2 == 32);
}

unk_7FF6AFCFA020 这里是虚拟机的加密指令流也就是 VM 要执行的代码

dword_7FF6AFCFA270 及其后面的数据就是 key 表

byte_7FF6AFCFA2A0 是密文

交叉引用追踪到 sub_7FF6AFCF1720 这里进行了反调试,最后倒数第 4 个结果计算出来应该是 CC

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
int *sub_7FF6AFCF1720()
{
struct _PEB *v0; // rsi
int v1; // ebx
int SystemMetrics; // eax
int v3; // ebx
HMODULE ModuleHandleA; // rax
FARPROC ProcAddress; // rsi
HANDLE CurrentProcess; // rax
unsigned __int64 v7; // r8
char v8; // dl
int *result; // rax
int i; // [rsp+28h] [rbp-40h]
int v11; // [rsp+2Ch] [rbp-3Ch] BYREF
CHAR ProcName[16]; // [rsp+30h] [rbp-38h] BYREF
char v13[16]; // [rsp+40h] [rbp-28h] BYREF

dword_7FF6AFD000C0 = 1;
dword_7FF6AFD00080 = 1;
dword_7FF6AFD00084 = 21930;
for ( i = 0; i <= 99; ++i )
*(&dword_7FF6AFD00080 + 2) = 2 * i;
dword_7FF6AFCFA270 = -272716322;
dword_7FF6AFD0008C = (dword_7FF6AFD00084 >> 12) | (16 * dword_7FF6AFD00080);
qword_7FF6AFCFA274 = 0x78563412BEBAFECAi64;
if ( !dword_7FF6AFD0008C )
dword_7FF6AFD00090 = 1;
dword_7FF6AFCFA27C = -271733872;
v0 = NtCurrentPeb();
v1 = 31 * v0->BeingDebugged;
SystemMetrics = GetSystemMetrics(0);
dword_7FF6AFD00094 = SystemMetrics;
v3 = ((v0->NtGlobalFlag >> 4) & 7) + (v1 ^ 0x37);
LOBYTE(SystemMetrics) = ~SystemMetrics;
dword_7FF6AFD00098 = SystemMetrics;
ModuleHandleA = GetModuleHandleA("KERNEL32.DLL");
if ( ModuleHandleA )
{
strcpy(v13, "gerPresent");
*ProcName = _mm_load_si128(&xmmword_7FF6AFCFB0A0);
dword_7FF6AFD0009C += 67;
ProcAddress = GetProcAddress(ModuleHandleA, ProcName);
if ( ProcAddress )
{
v11 = 0;
CurrentProcess = GetCurrentProcess();
(ProcAddress)(CurrentProcess, &v11);
v3 ^= -v11;
}
}
*ProcName = 0;
v7 = __rdtsc();
do
*(&dword_7FF6AFD00080 + 9) ^= (*ProcName)++;
while ( *ProcName <= 99 );
v8 = v3 ^ (__rdtsc() - v7 >= 0x5001 ? 0x55 : 0);
result = &dword_7FF6AFCFA270 + 4;
*(&dword_7FF6AFCFA270 + 17) = -86;
*(&dword_7FF6AFCFA270 + 19) = -18;
*(&dword_7FF6AFCFA270 + 16) = v8 ^ (v8 & 0xF | 0x20) ^ 0xDC;
*(&dword_7FF6AFCFA270 + 18) = (v8 + 112) ^ v8 ^ 0x2B;
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
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
import sys

unk_7FF6AFCFA020 = bytes([
0xDC,0xAA,0x4D,0xEE,0x7F,0xA9,0x12,0xEB,0x02,0xAA,0xBB,0xED,0x6E,0xAA,0x59,0xEE,
0x0E,0xAA,0xA9,0xEE,0x30,0xAA,0x55,0xED,0xB6,0xAF,0xA4,0xEE,0x6E,0xA9,0x0C,0xEE,
0x3B,0xAA,0x6C,0xEE,0xCB,0xAA,0x4A,0xEE,0x3A,0xA9,0xE9,0xEB,0xFB,0xAA,0x22,0xED,
0xA2,0xAA,0x95,0xEE,0xC2,0xAA,0x65,0xEE,0x3B,0xAA,0x92,0xED,0x07,0xAF,0xEB,0xEE,
0x63,0xA9,0x64,0xEE,0x53,0xAA,0x04,0xEE,0xA3,0xAA,0x50,0xEE,0x48,0xA9,0x87,0xEB,
0x8D,0xAA,0x20,0xED,0xB2,0xAA,0x85,0xEE,0xD2,0xAA,0x75,0xEE,0x11,0xAA,0xA8,0xED,
0x45,0xAF,0x55,0xEE,0xCC,0xA9,0x19,0xEE,0x2E,0xAA,0x79,0xEE,0xDE,0xAA,0x1A,0xEE,
0x7F,0xA9,0x12,0xEB,0x02,0xAA,0xDF,0xED,0x4D,0xAA,0x7A,0xEE,0x2D,0xAA,0x8A,0xEE,
0x31,0xAA,0x77,0xED,0x18,0xAF,0x02,0xEE,0xDB,0xA9,0xA1,0xEE,0x96,0xAA,0xC1,0xEE,
0x66,0xAA,0x1A,0xEE,0xC7,0xA9,0x2A,0xEB,0x3A,0xAA,0x4F,0xED,0xC9,0xAA,0xFE,0xEE,
0xA9,0xAA,0x0E,0xEE,0x3F,0xAA,0xFD,0xED,0xAE,0xAF,0xBC,0xEE,0xEF,0xA9,0x00,0xEE,
0x37,0xAA,0x60,0xEE,0xC7,0xAA,0x74,0xEE,0x08,0xA9,0x47,0xEB,0x4D,0xAA,0x7C,0xED,
0x50,0xAA,0x67,0xEE,0x30,0xAA,0x97,0xEE,0x18,0xAA,0x43,0xED,0x94,0xAF,0x86,0xEE,
0x99,0xA9,0xB3,0xEE,0x84,0xAA,0xD3,0xEE,0x74,0xAA,0x18,0xEE,0xD7,0xA9,0x5A,0xEB,
0x4A,0xAA,0xBD,0xED,0x5E,0xAA,0x69,0xEE,0x3E,0xAA,0x99,0xEE,0x3A,0xAA,0x6F,0xED,
0x00,0xAF,0xEA,0xEE,0x26,0xA9,0x4E,0xEE,0x79,0xAA,0x2E,0xEE,0x89,0xAA,0x18,0xEE,
0x2A,0xA9,0xB9,0xEB,0xAB,0xAA,0x01,0xED,0xBB,0xAA,0x8C,0xEE,0xDB,0xAA,0x7C,0xEE,
0x32,0xAA,0x82,0xED,0xD7,0xAF,0xDB,0xEE,0x53,0xA9,0xE5,0xEE,0xD2,0xAA,0x85,0xEE,
0x22,0xAA,0x58,0xEE,0xC1,0xA9,0x2C,0xEB,0x30,0xAA,0x89,0xED,0xFF,0xAA,0xC8,0xEE,
0x9F,0xAA,0x38,0xEE,0x6F,0xAA,0x9B,0xED,0x1C,0xAF,0x0E,0xEE,0xC4,0xA9,0x59,0xEE,
0x6E,0xAA,0x39,0xEE,0x9E,0xAA,0x74,0xEE,0x51,0xA9,0xDC,0xEB,0xC0,0xAA,0x19,0xED,
0x7B,0xAA,0x4C,0xEE,0x1B,0xAA,0xBC,0xEE,0x6D,0xAA,0x1D,0xED,0x8E,0xAF,0x9C,0xEE,
0x14,0xA9,0xDF,0xEE,0xE8,0xAA,0xBF,0xEE,0x18,0xAA,0x58,0xEE,0xFB,0xA9,0xAE,0xEB,
0xB6,0xAA,0x1B,0xED,0x6B,0xAA,0x5C,0xEE,0x0B,0xAA,0xAC,0xEE,0x03,0xAA,0x63,0xED,
0xF4,0xAF,0xE6,0xEE,0x7F,0xA9,0x84,0xEE,0xB3,0xAA,0xE4,0xEE,0x43,0xAA,0x7F,0xEE,
0x87,0xA9,0xEA,0xEB,0xFA,0xAA,0x27,0xED,0x8A,0xAA,0xBD,0xEE,0xEA,0xAA,0x4D,0xEE,
0x34,0xAA,0xB5,0xED,0x56,0xAF,0x44,0xEE,0x9D,0xA9,0x93,0xEE,0xA4,0xAA,0xF3,0xEE,
0x54,0xAA,0x18,0xEE,0xF7,0xA9,0xBA,0xEB,0xAA,0xAA,0xDF,0xED,0x4D,0xAA,0x7A,0xEE,
0x2D,0xAA,0x8A,0xEE,0x03,0xAA,0x45,0xED,0x86,0xAF,0x94,0xEE,0xC7,0xA9,0x41,0xEE,
0x76,0xAA,0x21,0xEE,0x86,0xAA,0x69,0xEE,0x54,0xA9,0xC3,0xEB,0xD1,0xAA,0xE0,0xED,
0xB4,0xAA,0x83,0xEE,0xD4,0xAA,0x73,0xEE,0x6F,0xAA,0xD0,0xED,0x3D,0xAF,0x2D,0xEE,
0x32,0xA9,0xEE,0xEE,0xD9,0xAA,0x8E,0xEE,0x29,0xAA,0x58,0xEE,0xCA,0xA9,0x19,0xEB,
0x0B,0xAA,0xFC,0xED,0x54,0xAA,0x63,0xEE,0x34,0xAA,0x93,0xEE,0x6B,0xAA,0x34,0xED,
0xD1,0xAF,0xD9,0xEE,0x15,0xA9,0xD7,0xEE,0xE0,0xAA,0xB7,0xEE,0x10,0xAA,0x0A,0xEE,
0xA1,0xA9,0x4C,0xEB,0x50,0xAA,0xFA,0xED,0x64,0xAA,0x53,0xEE,0x04,0xAA,0xA3,0xEE,
0x21,0xAA,0x4E,0xED,0xA3,0xAF,0x8F,0xEE,0x07,0xA9,0x47,0xEE,0x70,0xAA,0x27,0xEE,
0x90,0xAA
])

dword_7FF6AFCFA270 = bytes([
0xDE,0xAD,0xBE,0xEF,
0xCA,0xFE,0xBA,0xBE,
0x12,0x34,0x56,0x78,
0x90,0xAB,0xCD,0xEF,
0xCC,0xAA,0xBB,0xEE,
])

byte_7FF6AFCFA2A0 = bytes([
0x79,0x78,0xDD,0x5D,0xDB,0x25,0x6D,0xA5,0x03,0xE6,0xF2,0x7B,0x7F,0x72,0xAC,0xD1,
0xD3,0x65,0x20,0x92,0x35,0xD8,0xE6,0xE8,0xF5,0xC5,0x2D,0x05,0x23,0xC1,0x15,0x70,
0x96,0xCB,0x2B,0x28,0x0D,0xC2,0x60,0x16,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00
])

def solve():
print("[*] 开始爆破 Flag...")

table16 = dword_7FF6AFCFA270[:16]
key_bytes = dword_7FF6AFCFA270[16:20]

known_flag = []

for char_index in range(32):
found_char = False

for candidate in range(32, 127):
current_input = known_flag + [candidate]


v2 = 0
v3 = 0
v5 = 0
v6 = 85
v8 = 0
ip = 0

valid_candidate = True

while ip < len(unk_7FF6AFCFA020):
if ip + 1 >= len(unk_7FF6AFCFA020):
break

enc_instr = unk_7FF6AFCFA020[ip] | (unk_7FF6AFCFA020[ip+1] << 8)
k_idx_high = (ip + 1) & 3
v11 = key_bytes[k_idx_high]
k_idx_low = ip & 3
v12_low = (v8 ^ key_bytes[k_idx_low]) & 0xFF
v12 = (v11 << 8) | v12_low
dec_instr = enc_instr ^ v12

if v12_low == (enc_instr & 0xFF):
break

opcode = dec_instr & 0xFF
arg = (dec_instr >> 8) & 0xFF
ip += 2

if opcode == 0x80:
v8 = (v8 * arg) & 0xFF
elif opcode <= 0x80:
if opcode == 0x30:
v8 = (v8 + arg) & 0xFF
elif opcode <= 0x30:
if opcode == 0x10:
# Load Input
idx = arg
if v11 == ((enc_instr >> 8) & 0xFF):
idx = v5
if idx < len(current_input):
v8 = current_input[idx]
else:
v8 = 0
elif opcode == 0x20:
v8 ^= arg
else:
v3 = 255
elif opcode == 0x40:
shift = arg & 7
v8 = ((v8 << shift) | (v8 >> (8 - shift))) & 0xFF
elif opcode == 0x50:
idx = arg
if v11 == ((enc_instr >> 8) & 0xFF):
idx = v5
v8 ^= table16[idx & 0xF]
else:
v3 = 255
else:
if opcode == 0xC0:
v5 += 1
elif opcode > 0xC0:
if opcode == 0xE0:
v6 = v8
else:
v3 = 255
else:
if opcode == 0x90:
v8 ^= v6
elif opcode == 0xA0:
target_idx = arg
if v11 == ((enc_instr >> 8) & 0xFF):
target_idx = v5

if target_idx <= 31:
if target_idx > 0:
val_prev1 = current_input[target_idx-1] if target_idx-1 < len(current_input) else 0
v19 = (val_prev1 ^ (target_idx & 3)) & 0xFF
if target_idx == 1:
v20 = (v8 ^ v19) & 0xFF
else:
val_prev2 = current_input[target_idx-2] if target_idx-2 < len(current_input) else 0
v19 ^= val_prev2
if target_idx == 2:
v20 = (v8 ^ v19) & 0xFF
else:
val_prev3 = current_input[target_idx-3] if target_idx-3 < len(current_input) else 0
v20 = (v8 ^ val_prev3 ^ v19) & 0xFF
else:
v20 = v8

v2 += 1

t_val = table16[target_idx & 0xF]
sbox_idx = target_idx if target_idx < len(byte_7FF6AFCFA2A0) else 0
s_val = byte_7FF6AFCFA2A0[sbox_idx]
v23 = (t_val ^ s_val ^ v20 ^ 0xA5) & 0xFF
v3 = (v3 + ((2 * v23) | v23)) & 0xFF
else:
v3 = 255

if v3 != 0:
valid_candidate = False
break

if v2 > char_index:
break

if valid_candidate and v3 == 0 and v2 > char_index:
print(f"[+] Found char {char_index}: '{chr(candidate)}'")
known_flag.append(candidate)
found_char = True
break

if not found_char:
print(f"[-] Failed at index {char_index}")
break

final_flag = "".join([chr(c) for c in known_flag])
print(f"\nFlag: {final_flag}")

if __name__ == "__main__":
solve()

拿到 flag

1
flag{M1m1c_D3f3ns3_1s_Th3_B3s7!}

Misc

标准的绝密压缩

neta 可以检索到 tcp 流里有 png,但是不准确,有一些把好几张合并到一张里面了

idat 隐写,把 IDAT 的内容提取出来 cyber 解

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
Connection established. Hey, you online? It’s been a while since we last talked.
Yeah, I’m here. Busy as always. Feels like the days are getting shorter.
Tell me about it. I barely have time to sleep lately. Between maintenance logs and incident reports, I’m drowning.
Sounds rough. I’ve been buried in audits myself. Every time I finish one, another pops up.
Classic. Sometimes I wonder if the machines are easier to deal with than the people.
No kidding. At least machines don’t ask pointless questions.
True. Anyway, before I forget—how’s that side project you were working on? The one you wouldn’t shut up about months ago.
Still alive… barely. Progress is slow, but steady. You know me—I don’t give up easily.
Good. I hope it pays off one day.
Thanks. Alright… I’m guessing you didn’t ping me just to chat?
Well, half of it was. It’s been a while. But yes—I do have something for you today. Before sending the core cipher, I’ll transmit an encrypted archive first. It contains a sample text and the decryption rules.
Okay. What’s special about this sample text?
And… inside the sample text, I used my favorite Herobrine legend—you know the one I always bring up.
Of course I know. The hidden original text from that weird old site, right?
What can I say—old habits die hard. Anyway, the important part: the sample packet and the core cipher are encrypted with the same password.
Got it. So if I can decrypt the sample, the real one should be straightforward.
Exactly. Send the sample when ready.
I’m ready. Go ahead.
UEsDBBQAAQAIABtFeFu1Ii0dcwAAAHwAAAAJAAAAcnVsZXMudHh07XuRBFDbojGKhAz59VaKEpwD6/rKaZnqUxf+NMH0rybWrAMPewZ/yGyLrMKQjNIcEbPAxjmP5oTh8fP77Vi1wnFwzN37BmrQ9SCkC27FC/xeqbgw/HWcDpgzsEoiNpqT9ZThrbAScyg5syfJmNactjelNVBLAwQUAAEACACGOXhbpdvG1ysBAAAVAgAACgAAAHNhbXBsZS50eHTA1fy4cMLZwZkTI1mEk88yOXy9rmbTbCNBQOo9hqKQPK6vjZVo9aCtTVflmkKYGV99+51qXbinmG7WGik5UvLJk9MKRosThBCDMHrmjibOCzjzNELwEgEyX8DjqJkSc8pIFwj+oRM3bb4i0GtRxbwqgsxCtgwiKdCVoXVdetN7RKLIQ7DD+Huv/ZptNdd0yRNHis9LEA3loB+IHZ+dK7IknqPh4lYF8JwAjx5/wwp0YAM6Bcec7uAvk6B5t1pEztm1rLl8TjniVz5/bBUTo1LjUXnar/pnm1NvE9EAuxz/s6b+O8/ew7/A4ItdNJGzDudh6YULfiV3pCTXFIbR4GCe4LwkohWZIlAjysA+zLRrgkTDoB10vWdNGdfoBAlLRoUdZ95mS7X5/bXV41BLAQI/ABQAAQAIABtFeFu1Ii0dcwAAAHwAAAAJACQAAAAAAAAAIAAAAAAAAABydWxlcy50eHQKACAAAAAAAAEAGABIv3f82lzcAQAAAAAAAAAAAAAAAAAAAABQSwECPwAUAAEACACGOXhbpdvG1ysBAAAVAgAACgAkAAAAAAAAACAAAACaAAAAc2FtcGxlLnR4dAoAIAAAAAAAAQAYAFP0sZjOXNwBAAAAAAAAAAAAAAAAAAAAAFBLBQYAAAAAAgACALcAAADtAQAAAAA=
got it. Decrypting… yeah, it works.
Good. That means the channel is stable.
Alright. Whenever you’re ready, send the real thing.
The core cipher will be transmitted through our secret channel. You remember how to decrypt it, right?
Of course. I’ve got the procedure ready. Start when you’re ready.
Done. Core cipher fully received. Integrity verified—no corruption.
Same to you. And hey… nice talking again.
Agreed. Take care.
Good. Keep things quiet for the next few days.
Yeah. Let’s not wait so long next time.
You too.

对话中间传了个压缩包,里面有两个文件,但是压缩包加密了,可以进行明文攻击

这段话可以知道密码和这个 Herobrine legend 有关

1
2
3
4
5
Okay. What’s special about this sample text?
And… inside the sample text, I used my favorite Herobrine legend—you know the one I always bring up.
Of course I know. The hidden original text from that weird old site, right?
What can I say—old habits die hard. Anyway, the important part: the sample packet and the core cipher are encrypted with the same password.
Got it. So if I can decrypt the sample, the real one should be straightforward.

根据这个传说搜到了下面这个报道 Him.Html

1
It has been reported that some victims of torture, during the act, would retreat into a fantasy world from which they could not WAKE UP. In this catatonic state, the victim lived in a world just like their normal one, except they weren't being tortured. The only way that they realized they needed to WAKE UP was a note they found in their fantasy world. It would tell them about their condition, and tell them to WAKE UP. Even then, it would often take months until they were ready to discard their fantasy world and PLEASE WAKE UP.

CRC 和 sample.txt 的一致,作为明文进行攻击,拿到密钥,解出压缩包

1
[ b47e923c 5aeb49a7 a3cd7af0 ]

rules.txt 内容如下

1
2
1.you need to calc the md5 of port to decrypt the core data.
2.The cipher I put in the zip, in segments, has been deflated.

找端口的 md5,密文在压缩包中分片传输了,端口 30012 到 30091 在进行 tcp 传输数据,将端口 md5 值作为 key 进行 AES 解密发现有分片传输的文件

提取出所有文件

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
import hashlib
from collections import OrderedDict
from pathlib import Path
from scapy.all import rdpcap, IP, TCP, Raw
from Crypto.Cipher import AES

SRC_IP = "192.168.0.234"
PORT_MIN = 30012
PORT_MAX = 30091

ZIP_MAGIC = b"PK\x03\x04"


def load_flows(pcap_path):
packets = rdpcap(pcap_path)
flows = OrderedDict()

for pkt in packets:
if not (IP in pkt and TCP in pkt and Raw in pkt):
continue

ip = pkt[IP]
tcp = pkt[TCP]

if ip.src != SRC_IP:
continue
if not (PORT_MIN <= tcp.sport <= PORT_MAX):
continue

key = (ip.src, tcp.sport, ip.dst, tcp.dport)
flows.setdefault(key, bytearray()).extend(bytes(pkt[Raw].load))

print(f"[+] 找到 {len(flows)} 条 TCP 流")
return flows


def aes_ecb_decrypt(data: bytes, port: int) -> bytes:
# AES Key = MD5 Hex(UTF-8), 32 字节,用作 AES-256
hexkey = hashlib.md5(str(port).encode()).hexdigest() # 32 字节字符串
key = hexkey.encode('utf-8') # 32 字节 key → AES-256

if len(data) % 16 != 0:
data += b"\x00" * (16 - (len(data) % 16))

cipher = AES.new(key, AES.MODE_ECB)
return cipher.decrypt(data)


def extract_zip(data: bytes) -> bytes:
idx = data.find(ZIP_MAGIC)
if idx == -1:
return b""
return data[idx:]


def process_pcap(pcap_path: str):
flows = load_flows(pcap_path)

output_dir = Path("out")
output_dir.mkdir(exist_ok=True)

port_count = {}

for (src, sport, dst, dport), payload in flows.items():
print(f"[+] 解密 {sport} 流,长度 {len(payload)}")

decrypted = aes_ecb_decrypt(bytes(payload), sport)

zip_data = extract_zip(decrypted)
if not zip_data.startswith(ZIP_MAGIC):
print(f"[!] 端口 {sport} 未找到 ZIP Header,跳过")
continue

cnt = port_count.get(sport, 0)
port_count[sport] = cnt + 1

suffix = f"_{cnt}" if cnt > 0 else ""
out_path = Path("out") / f"{sport}{suffix}.zip"

with open(out_path, "wb") as f:
f.write(zip_data)

print(f"[+] 已写入 {out_path}")

print("[+] 处理完毕")


def main():
process_pcap("capture.pcapng")


if __name__ == "__main__":
main()

提出 80 个 zip,每个压缩包内容都是 4 字节,进行不可见字符的 CRC 爆破

参考 MnZn 师傅的脚本爆破

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
import os
import zipfile
import binascii

# ---------------- CRC32 REVERSE CLASS (your original code) -------------------
class CRC32Reverse:
def __init__(self, crc32, length, tbl=bytes(range(256)), poly=0xEDB88320, accum=0):
self.char_set = set(tbl)
self.crc32 = crc32
self.length = length
self.poly = poly
self.accum = accum
self.table = []
self.table_reverse = []

def init_tables(self, poly, reverse=True):
for i in range(256):
for j in range(8):
if i & 1:
i >>= 1
i ^= poly
else:
i >>= 1
self.table.append(i)
if reverse:
for i in range(256):
found = [j for j in range(256) if self.table[j] >> 24 == i]
self.table_reverse.append(tuple(found))

def calc(self, data, accum=0):
accum = ~accum
for b in data:
accum = self.table[(accum ^ b) & 0xFF] ^ ((accum >> 8) & 0x00FFFFFF)
accum = ~accum
return accum & 0xFFFFFFFF

def find_reverse(self, desired, accum):
solutions = set()
accum = ~accum
stack = [(~desired,)]
while stack:
node = stack.pop()
for j in self.table_reverse[(node[0] >> 24) & 0xFF]:
if len(node) == 4:
a = accum
data = []
node = node[1:] + (j,)
for i in range(3, -1, -1):
data.append((a ^ node[i]) & 0xFF)
a >>= 8
a ^= self.table[node[i]]
solutions.add(tuple(data))
else:
stack.append(((node[0] ^ self.table[j]) << 8,) + node[1:] + (j,))
return solutions

def dfs(self, length, outlist=[b'']):
if length == 0:
return outlist
tmp_list = [item + bytes([x]) for item in outlist for x in self.char_set]
return self.dfs(length - 1, tmp_list)

def run_reverse(self):
self.init_tables(self.poly)
desired = self.crc32
accum = self.accum
result_list = []

if self.length >= 4:
patches = self.find_reverse(desired, accum)
for item in self.dfs(self.length - 4):
patch = list(item)
patches = self.find_reverse(desired, self.calc(patch, accum))
for last4 in patches:
patch.extend(last4)
if self.calc(patch, accum) == desired:
result_list.append(bytes(patch))
else:
for item in self.dfs(self.length):
if self.calc(item) == desired:
result_list.append(bytes(item))
return result_list


def crc32_reverse(crc32, length, char_set=bytes(range(256)), poly=0xEDB88320, accum=0):
return CRC32Reverse(crc32, length, char_set, poly, accum).run_reverse()


# ---------------------- MAIN ZIP SCAN ------------------------

def scan_zip_crc(dirname, output_bin="output.bin", brute_length=4):
results = []

for root, dirs, files in os.walk(dirname):
for fname in files:
if fname.lower().endswith(".zip"):
zip_path = os.path.abspath(os.path.join(root, fname))
zip_path = zip_path.replace("\\", "/")

print(f"[+] 发现压缩包:{zip_path}")

try:
with zipfile.ZipFile(zip_path) as z:
for info in z.infolist():
crc = info.CRC
print(f" → 文件头:{info.filename} CRC32={crc:08X}")

res = crc32_reverse(crc, brute_length)
print(f" 逆推得到 {len(res)} 条结果")

results.extend(res)
except Exception as e:
print(f"[-] 无法读取 ZIP:{zip_path}, 错误:{e}")

# 写入 bin
with open(output_bin, "wb") as f:
for r in results:
f.write(r)

print(f"[+] 已写入 {len(results)} 条结果 → {output_bin}")


if __name__ == "__main__":
scan_zip_crc("./out", output_bin="final_output.bin", brute_length=4)

4 字节区分,进行一次 Raw Inflate

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

hex_blob = """
53 29 00 00 CB AE 02 00 CB 2C 00 00 53 31 04 00
D3 32 04 00 D3 32 02 00 D3 32 00 00 D3 32 06 00
33 D5 02 00 33 B2 04 00 D3 32 01 00 33 34 06 00
33 4D 04 00 33 4F 03 00 D3 32 00 00 D3 32 02 00
33 D3 02 00 33 D0 02 00 33 36 05 00 D3 32 00 00
33 31 04 00 33 D6 02 00 4B B6 00 00 33 36 05 00
B3 48 06 00 4B B5 04 00 4B 35 03 00 B3 30 05 00
B3 48 03 00 33 34 03 00 33 33 07 00 33 35 06 00
33 33 06 00 33 4F 01 00 4B 35 04 00 33 31 05 00
4B 31 00 00 4B B6 00 00 33 31 04 00 4B 4E 05 00
4B B5 04 00 4B 4D 03 00 4B 31 07 00 4B 4E 03 00
33 32 00 00 33 B0 00 00 4B 31 04 00 33 4E 05 00
33 35 05 00 33 4C 01 00 4B 31 05 00 B3 30 01 00
4B 32 03 00 B3 4C 06 00 4B 4C 05 00 33 B5 00 00
B3 34 05 00 4B 36 07 00 4B 49 03 00 33 31 05 00
4B 33 06 00 33 4A 03 00 4B 49 03 00 4B 32 05 00
33 4C 01 00 33 48 06 00 33 48 01 00 33 32 07 00
33 B6 00 00 33 32 00 00 33 32 06 00 B3 B4 00 00
B3 34 03 00 4B 31 06 00 4B 35 03 00 D3 52 01 00
D3 2F 00 00 CB AE 02 00 CB 2C 00 00 26 23 6B 0F
"""

bytes_all = bytes(int(x, 16) for x in hex_blob.split())

out_parts = []
for i in range(0, len(bytes_all), 4):
chunk = bytes_all[i:i+4]
try:
out = zlib.decompress(chunk, wbits=-15)
out_parts.append(out)
except:
out_parts.append(b'')

result = b''.join(out_parts)

print(result.decode('latin1'))

拿到数据,直接从 hash 恢复文件

1
$pkzip$1*1*2*0*35*29*4135a7f*0*26*0*35*0413*c8358ce9e6858f166753637de145d0c841cee9efd7cf2008d13e551dd584b69cae5895c7df45f32fdfb51d0c0d273820239896d3e6*$/pkzip

脚本

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
import binascii, zlib, string

class ZipCrypto:
def __init__(self, keys):
self.keys = keys[:]
self.crc_table = [0]*256
for i in range(256):
c = i
for _ in range(8):
c = (0xEDB88320 ^ (c >> 1)) if c & 1 else (c >> 1)
self.crc_table[i] = c & 0xFFFFFFFF

def crc32(self, old, char):
return (self.crc_table[(old ^ char) & 0xFF] ^ (old >> 8)) & 0xFFFFFFFF

def update_keys(self, c):
self.keys[0] = self.crc32(self.keys[0], c)
self.keys[1] = (self.keys[1] + (self.keys[0] & 0xFF)) & 0xFFFFFFFF
self.keys[1] = (self.keys[1] * 134775813 + 1) & 0xFFFFFFFF
self.keys[2] = self.crc32(self.keys[2], (self.keys[1] >> 24) & 0xFF)

def decrypt_byte(self):
t = (self.keys[2] | 3) & 0xFFFFFFFF
return ((t*(t^1)) >> 8) & 0xFF

def decrypt(self, data):
out = bytearray()
for b in data:
p = b ^ self.decrypt_byte()
self.update_keys(p)
out.append(p)
return out

def to_visible(b):
return ''.join(chr(c) if chr(c) in string.printable else '.' for c in b)

my_hash = '$pkzip$1*1*2*0*35*29*4135a7f*0*26*0*35*0413*c8358ce9e6858f166753637de145d0c841cee9efd7cf2008d13e551dd584b69cae5895c7df45f32fdfb51d0c0d273820239896d3e6*$/pkzip$'
parts = my_hash.split('*')
cipher_hex = max([p for p in parts if all(c in "0123456789abcdefABCDEF" for c in p)], key=len)
cipher_bytes = binascii.unhexlify(cipher_hex)

k0, k1, k2 = int("b47e923c",16), int("5aeb49a7",16), int("a3cd7af0",16)
zc = ZipCrypto([k0,k1,k2])
decrypted = zc.decrypt(cipher_bytes)
decrypted = decrypted[12:]
print(to_visible(decrypted))

拿到 flag

1
flag{W0ww_th3_C@ske7|s_Tre4sur3_unl0cke9}

泄露的时间和电码

根据给的 chal.py 加密脚本和 timing.log 进行侧信道攻击解出来的为下面这个

1
2
3
4
5
6
h i j k l m n
8 9 0 / - _ =
a b c d e f g
v w x y z { }
o p q r s t u
1 2 3 4 5 6 7

解码脚本如下

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
def step_lfsr(lfsr):
bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 4)) & 1
return (lfsr >> 1) | (bit << 7)

def unscramble(s):
return ((s - 85) * 223) % 256

with open('timing.log', 'r') as f:
timings = [float(line.strip()) for line in f]

lfsr = 0x92
k_list = []
for _ in timings:
lfsr = step_lfsr(lfsr)
k_list.append(lfsr)

current_ops_list = [round(t / 0.005) for t in timings]

base_ops_list = []
for ops in current_ops_list:
if ops % 2 == 0:
base_ops = ops - 10
else:
base_ops = ops - 40
base_ops = base_ops % 256
base_ops_list.append(base_ops)

flag = ''
for s, k in zip(base_ops_list, k_list):
val = unscramble(s)
c = val ^ k
flag += chr(c)

print(flag)

ModR/M 原理的隐写,steg86 解密 chal 文件得到下面的数据

1
326a31306c206b6b6868203a332024206a686820346820326b32682024336a203468336b206a323068206a6a366c206b6b6c6c206c6c6b205e6a206b6b24686820306a6a202f7a203a3620356b24206a6a206a

hex 解码为

1
2j10l kkhh :3 $ jhh 4h 2k2h $3j 4h3k j20h jj6l kkll llk ^j kk$hh 0jj /z :6 5k$ jj j

每一段就是数字+方向,hjlk 对应 vim 中的方向键 H← J↑ K↓ L→,空格也算字符,初始为(0,0),根据规则得到 flag

1
flag{y0u-are_amaz1ng}

返璞归真

压缩包最后有一个 hashisk3y,用 foremost 进行分离得到 bmp

paperbak http://www.ollydbg.de/Paperbak/ 提出 wow.txt

1
jNX+xu2QKBm23AUlwClt+3xDkQcJGjM=

提示的 hashisk3y 然后用分离出的 jpg 的 md5 作为进行 rc4 解密

1
flag{examp13_f0r_r3a11}

2025第八届“强网”拟态防御国际精英挑战赛Final复现
http://example.com/2025/12/03/2025第八届“强网”拟态防御国际精英挑战赛Final复现/
作者
butt3rf1y
发布于
2025年12月3日
许可协议