Geek 2025

  1. 1. Geek 2025(reverse方向)
    1. 1.1. 1.ez_pyyy
    2. 1.2. 2.QYQSの奇妙冒险
    3. 1.3. 3.ezRust
    4. 1.4. 4.only_flower
    5. 1.5. 5.encode
    6. 1.6. 6.ezSMC
    7. 1.7. 7.Gensh1n
    8. 1.8. 8.QYQSの奇妙冒险2
    9. 1.9. 9.stack_bomb
    10. 1.10. 10.ez_vm
    11. 1.11. 11.GeekBinder
    12. 1.12. 12.obfuscat3
    13. 1.13. 13.reReverse
    14. 1.14. 14.国产の光
    15. 1.15. 15.Lastone
    16. 1.16. 16.ez_android

Geek 2025(reverse方向)

1.ez_pyyy

拿到一个pyc文件,直接丢给网页反编译

完整反编译出来的代码为:

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
cipher = [
48,
55,
57,
50,
53,
55,
53,
50,
52,
50,
48,
55,
101,
52,
53,
50,
52,
50,
52,
50,
48,
55,
53,
55,
55,
55,
50,
54,
53,
55,
54,
55,
55,
55,
53,
54,
98,
55,
97,
54,
50,
53,
56,
52,
50,
52,
99,
54,
50,
50,
52,
50,
50,
54]

def str_to_hex_bytes(s = None):
return s.encode('utf-8')


def enc(data = None, key = None):
return None((lambda .0 = None: [ b ^ key for b in .0 ])(data))


def en3(b = None):
return b << 4 & 240 | b >> 4 & 15


def en33(data = None, n = None):
'''整体 bitstream 循环左移 n 位'''
bit_len = len(data) * 8
n = n % bit_len
val = int.from_bytes(data, 'big')
val = (val << n | val >> bit_len - n) & (1 << bit_len) - 1
return val.to_bytes(len(data), 'big')

if __name__ == '__main__':
flag = ''
data = str_to_hex_bytes(flag)
data = enc(data, 17)
data = bytes((lambda .0: [ en3(b) for b in .0 ])(data))
data = data[::-1]
data = en33(data, 32)
if data.hex() == cipher:
print('Correct! ')
else:
print('Wrong!!!!!!!!')

该代码对输入进行了五步处理:

  1. 转化为hex字符串
  2. 异或17
  3. 高低半字节交换
  4. 逆序
  5. 循环左移32位

逆操作顺序:

  1. 把密文拼回hex字符串并转成bytes
  2. 循环右移32位
  3. 逆序
  4. 再次进行高低半字节交换(可自反操作)
  5. 异或17
  6. 最后转回UTF-8

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cipher = [
48,55,57,50,53,55,53,50,52,50,48,55,101,52,53,50,52,50,52,50,48,55,53,55,55,55,50,54,53,55,54,55,55,55,53,54,98,55,97,54,50,53,56,52,50,52,99,54,50,50,52,50,50,54
]
hexs = ''.join(chr(c) for c in cipher)
data = bytes.fromhex(hexs)

def rotate_right_bytes(data, n_bits):
bit_len = len(data) * 8
n = n_bits % bit_len
val = int.from_bytes(data, 'big')
val = (val >> n) | ((val << (bit_len - n)) & ((1 << bit_len) - 1))
return val.to_bytes(len(data), 'big')

def nibble_swap(b):
return ((b << 4) & 0xF0) | ((b >> 4) & 0x0F)

d1 = rotate_right_bytes(data, 32) # 逆 en33
d2 = d1[::-1] # 逆序
d3 = bytes(nibble_swap(b) for b in d2) # 逆 en3
flag = bytes(b ^ 17 for b in d3) # 逆 enc (xor 17)

print(flag.decode())

# SYC{jtfgdsfda554_a54d8as53}

2.QYQSの奇妙冒险

直接ida打开审代码

主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
  strcpy(v6, "QYQS");
v7[0] = 2;
v7[1] = 1;
v7[2] = 16;
v7[3] = 43;
v7[4] = 28;
v7[5] = 3;
v7[6] = 23;
v7[7] = 57;
v7[8] = 6;
v7[9] = 1;
v7[10] = 34;
v7[11] = 41;
v7[12] = 14;
v7[13] = 11;
v7[14] = 45;
v7[15] = 109;
v7[16] = 6;
v7[17] = 32;
v7[18] = 23;
v7[19] = 127;
v7[20] = 56;
sub_140011320();
sub_140011235("%s", Str);
if ( j_strlen(Str) == 21 )
{
for ( j = 0; ; ++j )
{
v10 = j;
if ( j >= j_strlen(Str) )
break;
Str[j] ^= j;
v10 = j;
Str[j] ^= v6[j % 4];
}
for ( k = 0; ; ++k )
{
v10 = k;
if ( k >= j_strlen(Str) )
break;
if ( Str[k] != v7[k] )
goto LABEL_5;
}
sub_140011401("验证成功\n");
sub_1400111C7("\n");
sub_1400111C7("\n按任意键退出...");
getch();
}
else
{
LABEL_5:
sub_140011401("验证失败,请获得QYQS授权\n");
}
sub_14001136B(v3, &unk_14001C2F0);
return 0i64;
}

首先检测输入长度为21,然后对每个字节做两次XOR:

Str[j] ^= j; Str[j] ^= v6[j % 4];

已知v6 = “QYQS”和密文

直接解密就行

EXP:

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
#include <iostream>
#include <cstring>
using namespace std;

int main()
{
char v6[44] = "QYQS";
int v7[21] = {
2, 1, 16, 43, 28, 3, 23, 57, 6, 1,
34, 41, 14, 11, 45, 109, 6, 32, 23, 127, 56
};

int array_size = 21;

for (int j = 0; j < array_size; ++j)
{
v7[j] ^= j;
v7[j] ^= v6[j % 4];
printf("%c", (char)v7[j]);
}
cout << endl;

return 0;
}

//SYC{I_@m_QyqS_r1GhT?}

3.ezRust

Rust语言编写的文件,ida打开审计代码

Shift+F12找到了疑似Base85和Base64码表的字符串

QQ_1763286792603

跟进继续找,感觉下面这串乱码看起来很像密文,而且也符合Base85的加密规律

QQ_1763287117452

提取密文用cyberchef解密

QQ_1763287180831

SYC{Ohjhhh_y0u_g3t_Ezzzzz3_Ru3t!@}

4.only_flower

ida打开,题目都叫flower了,那还说啥了,去花就完了

QQ_1763287330839

去完花就能看到清晰的加密逻辑:

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
__main();
printf("Welcome to Flowerdance. Input your flag: ");
if ( !fgets(Buffer, 256, __iob[0]._ptr) )
return 0;
v7 = strlen(Buffer);
if ( v7 && Buffer[v7 - 1] == 10 )
Buffer[--v7] = 0;
if ( (checkcheck)(Buffer) )
{
Size = strlen(Buffer);
if ( Size == 28 )
{
Buf1 = malloc(0x1Cu);
if ( Buf1 )
{
encrypt(Buffer, Buf1, Size);
if ( !memcmp(Buf1, &CIPHER, Size) )
{
puts("Correct! Flowerdance!");
}
else
{
puts("Incorrect. Keep dancing.");
hint();
}
free(Buf1);
return 0;
}
else
{
return 0;
}
}
else
{
printf("Wrong length (expected %lu bytes including braces).\n", 28);
hint();
return 0;
}
}
else
{
puts("Bad format..");
hint();
return 0;
}
}

跟进encrypt函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int __cdecl encrypt(int a1, int a2, unsigned int a3)
{
unsigned int result; // eax
size_t v4; // [esp+18h] [ebp-10h]
unsigned int i; // [esp+1Ch] [ebp-Ch]

v4 = strlen(KEY);
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(i + a2) = i + rol8((KEY[i % v4] ^ *(a1 + i)), KEY[i % v4] & 7);
}
return result;
}

加密步骤:

  1. KEY[i % key_len] ^ a1[i] - 用密钥异或明文字节
  2. rol8(result, KEY[i % key_len] & 7) - 将结果循环左移 (0-7) 位
  3. i + result - 再加上索引值

逆向操作:

  1. 减去索引
  2. 循环右移
  3. 密钥异或

EXP:

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
def ror8(value, shift):
"""8位循环右移"""
shift = shift % 8
return ((value >> shift) | (value << (8 - shift))) & 0xFF


def decrypt(ciphertext, key):
plaintext = []
key_len = len(key)
key_bytes = key.encode('utf-8')

for i in range(len(ciphertext)):
# 减去索引 i
temp = (ciphertext[i] - i) & 0xFF

# 循环右移 (KEY[i % key_len] & 7) 位
shift_amount = key_bytes[i % key_len] & 7
temp = ror8(temp, shift_amount)

# 密钥异或
plain_byte = temp ^ key_bytes[i % key_len]
plaintext.append(plain_byte)

return bytes(plaintext)


CIPHER = [
0x0A, 0x84, 0xC2, 0x84, 0x51, 0x48, 0x5F, 0xF2, 0x9E, 0x8D,
0xD0, 0x84, 0x75, 0x67, 0x73, 0x8F, 0xCA, 0x57, 0xD7, 0xE6,
0x14, 0x6E, 0x77, 0xE2, 0x29, 0xFE, 0xDF, 0xCC
]

KEY = "GEEK2025"

flag_bytes = decrypt(CIPHER, KEY)
flag = flag_bytes.decode('utf-8')

print(flag)

#SYC{asdjjasdhjk12wk12ijkejk}

5.encode

elf文件,代码很简洁,但是藏了加密逻辑

QQ_1763288659383

main函数里有个异或0x5A

QQ_1763288722683

compare函数里藏了个base64,上面的unk_100003EF1双击进去是密文,跟进也能发现base64是没换表的

QQ_1763288834835

但是在码表上面看到了疑似密钥的字符串,跟进调用密钥的函数

X追踪调用后发现,还有一个enc函数被藏在scanf函数里

QQ_1763288882893

这个加密是一个对密钥进行魔改处理的tea算法

QQ_1763288954487

密文密钥和加密逻辑都到手了,接下来就是写解密代码

先用cyberchef解了base64和xor,然后转换一下数据端序组合成4字节为一组

QQ_1763296256606

EXP:

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
#include <bits/stdc++.h>
using namespace std;

void tea_decrypt(unsigned int *a1, unsigned int *key)
{
unsigned int v5; // [esp+D0h] [ebp-2Ch]
unsigned int v6; // [esp+DCh] [ebp-20h]
uint32_t delta = 0x61C88647;
uint32_t sum = (uint32_t)(-(int32_t)(delta * 32));
v6 = *a1;
v5 = a1[1];
for( int i = 0; i < 32; ++i )
{
v5 -= (((v6 >> 5) ^ (16 * v6)) + v6) ^ (sum + key[(sum >> 11) & 3]);
sum += 0x61C88647;
v6 -= (((v5 >> 5) ^ (16 * v5)) + v5) ^ (sum + key[sum & 3]);

}
*a1 = v6;
a1[1] = v5;
}

void build_v7_from_key(const uint8_t *key_bytes, uint32_t *v7) //构造密钥
{
for (int i = 0; i < 4; i++)
{
int base = 4 * i;
uint32_t b0 = key_bytes[base];
uint32_t b1 = key_bytes[base + 1];
uint32_t b2 = key_bytes[base + 2];
uint32_t b3 = key_bytes[base + 3];
v7[i] = ((b1 << 16) | (b0 << 24) | (b2 << 8) | b3);
}
}

int main()
{
uint8_t key_bytes[16] = {'g','e','e','k','2','0','2','5','r','e','v','e','r','s','e','!'};
uint32_t key[4];
build_v7_from_key(key_bytes, key);

uint8_t ciphertext[] = {
0xe6,0x46,0x8d,0x85,0x18,0xf2,0x9c,0x07,
0x9d,0xfe,0x6c,0xc2,0x32,0x08,0x10,0xdd,
0x7a,0x25,0x8f,0x93,0x74,0x0e,0x73,0x86,
0xb9,0x5c,0x3b,0xf5,0x5f,0x4c,0x22,0x81,
0xc3,0xca,0x56,0x31,0xa3,0x53,0xa3,0x0b,
0xfc,0x41,0xfc,0xa6,0xfc,0xc2,0x70,0xbe
};

size_t len = sizeof(ciphertext);
uint8_t plaintext[len];

for (size_t i = 0; i < len; i += 8) {
uint32_t v_be[2];
// 转换成大端序排列
v_be[0] = ((uint32_t)ciphertext[i] << 24) | ((uint32_t)ciphertext[i + 1] << 16) |
((uint32_t)ciphertext[i + 2] << 8) | ciphertext[i + 3];
v_be[1] = ((uint32_t)ciphertext[i + 4] << 24) | ((uint32_t)ciphertext[i + 5] << 16) |
((uint32_t)ciphertext[i + 6] << 8) | ciphertext[i + 7];

tea_decrypt(v_be, key);

// 大端序转换回字节
plaintext[i] = (v_be[0] >> 24) & 0xFF;
plaintext[i + 1] = (v_be[0] >> 16) & 0xFF;
plaintext[i + 2] = (v_be[0] >> 8) & 0xFF;
plaintext[i + 3] = v_be[0] & 0xFF;
plaintext[i + 4] = (v_be[1] >> 24) & 0xFF;
plaintext[i + 5] = (v_be[1] >> 16) & 0xFF;
plaintext[i + 6] = (v_be[1] >> 8) & 0xFF;
plaintext[i + 7] = v_be[1] & 0xFF;
}

printf("Decrypted hex:\n");
for (size_t i = 0; i < len; i++)
printf("%02X ", plaintext[i]);
printf("\n\nASCII output:\n");
for (size_t i = 0; i < len; i++) {
unsigned char c = plaintext[i];
printf("%c", (c >= 32 && c <= 126) ? c : '.');
}
printf("\n");


return 0;
}

//SYC{St4nd4rd_Funct10n_N0t_4lw4ys_St4nd4rd}

6.ezSMC

ida打开审计代码,联系题目很容易发现.miao段代码是被SMC加密处理过的

QQ_1763289170399

*两种解法:1.动调过掉SMC自解密;2.写ida python代码解密

这里演示第二种解法:

要写代码解密,就得先知道是怎么加密的

跟进miao_encrypt –> miao_xor函数,发现是对这段代码进行了一个异或3的处理

QQ_1763289426253

然后回去确认.miao段代码的起始地址(0x4A2000)和结束地址(0x4A2200)

接下来就是写ida python代码解密:

1
2
3
4
5
start_address = 0x4A2000
end_address = 0x4A2200

for i in range(start_address,end_address):
patch_byte(i, get_wide_byte(i) ^ 0x03)

然后回到.miao段重新按P反编译定义一下,就能看到加密逻辑了

这一块是一个没换表的基础base64

QQ_1763289894338

但是下面还有个enc0de函数,里面是一个换表base58

QQ_1763289995094

QQ_1763290974269

再回去审一下主函数:

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
_main(argc, argv, envp);
v17 = "tHMoSoMX71sm62ARQ8aHF6i88nhkH9Ac2J7CrkQsQgXpiy6efoC8YVkzZu1tMyFxCLbbqvgXZHxtwK5TACVhPi1EE5mK6JG56wPNR4d2GmkELGfJHgtcAEH7";
printf("Plz input your flag miao: ");
v3 = __acrt_iob_func(0);
fgets(v11, 1024, v3);
v11[strcspn(v11, "\r\n")] = 0;
v16 = ascii_to_hexbytes(v11, &v10);
v15 = hexstr_to_bytes(v16, &v9);
v8 = 17;
init(v7, &v8, 1i64);
encode(v7, v15, v9);
v14 = bytes_to_hexstr(v15, v9);
miao_encrypt();
v4 = strlen(v14);
v13 = encodee(v14, v4);
if ( v13 )
{
v6 = strlen(v13);
v12 = enc0de(v13, v6);
if ( !strcmp(v12, v17) )
puts("Correct!");
else
puts("Wrong!");
free(v16);
free(v15);
free(v14);
free(v13);
free(v12);
return 0;
}
else
{
puts("encodee returned NULL");
return 0;
}

跟进分析上面的init和encode函数,代码实现了经典的RC4算法初始化和加解密,但这个RC4的密钥是单字节17,即0x11(v8 = 17; init(v7, &v8, 1i64);

整段主函数的加密流程为:

  1. 输入转hex字符串
  2. 再转成bytes –> data
  3. 单字节0x11初始化RC4
  4. 对data进行RC4加密
  5. 把RC4输出转回hex字符串
  6. smc自解密(把后续要用到的base64算法解密出来)
  7. base64编码
  8. base58编码

接下来就是逆向加密顺序写解密代码了

先用cyberchef把换表base58和base64解了

QQ_1763291574457

EXP:

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
import sys
import binascii

HEX_CIPHER = "dfd24c6c33beee2b49329e61186012b05da1542dd3f09fb0e9aed330c87477cc"

def rc4_decrypt(key_bytes: bytes, data_bytes: bytes) -> bytes:
# KSA
S = list(range(256))
j = 0
keylen = len(key_bytes)
for i in range(256):
j = (j + S[i] + key_bytes[i % keylen]) & 0xFF
S[i], S[j] = S[j], S[i]
# PRGA + XOR
i = 0
j = 0
out = bytearray()
for b in data_bytes:
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) & 0xFF]
out.append(b ^ K)
return bytes(out)

def main():
hexstr = HEX_CIPHER.strip()
if len(hexstr) % 2 != 0:
print("Hex length is odd — check input.")
return

try:
cipher = bytes.fromhex(hexstr)
except Exception as e:
print("Invalid hex:", e)
return

key = bytes([0x11]) # 单字节密钥
plain = rc4_decrypt(key, cipher)

print("Cipher (hex):", cipher.hex())
print("Plain (hex) :", plain.hex())
try:
print("Plain (ascii):", plain.decode('utf-8'))
except:
print("Plain (ascii):", plain.decode('latin1', errors='replace'))

if __name__ == "__main__":
main()

#SYC{OHhhhhhhh_y0u_Kn0m_SMCCCC@!}

7.Gensh1n

ida打开文件,一开始看到的是一个哈希计算函数,但发现后面的判定有问题

1
2
3
4
5
6
7
8
for (j = 0; j <= 7; ++j)
{
if (v7[j] == global_nodes[16 * j])
{
v5 = 1;
break;
}
}

这段判断的意思是,如果存在任意一个位置满足v7[j] == global_nodes[16 * j]的条件就判定成功

介于这个奇怪的判定,我觉得这应该不是真正的加密逻辑,我先试了动调,然后发现这个文件动调一次就开一次原神的网页(;`O´)o

还是回去Shift+F12吧,在字符串列表里找到了另一个判定语句,以及一个疑似密钥的字符串

QQ_1763293300313

跟踪发现调用这个密钥(arr)的函数是前面的那个init初始化函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __fastcall init_node(__int64 a1)
{
__int64 v1; // rax
int i; // [rsp+1Ch] [rbp-4h]

if ( a1 )
{
for ( i = 0; i <= 7; ++i )
{
*(16LL * i + a1) = arr[i];
v1 = 16LL * i + a1;
if ( i > 6 )
*(v1 + 8) = 0LL;
else
*(v1 + 8) = 16 * (i + 1LL) + a1;
}
}
else
{
LODWORD(v1) = puts("error");
}
return v1;
}

结合前面分析,只要输入的8个字符里有任意一个与 "geek2025" 对应位置相同,就算判定成功

而跟进另一个判定语句进入的是一个cleanup函数:

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
unsigned __int64 cleanup()
{
int v1; // [rsp+8h] [rbp-238h]
int i; // [rsp+Ch] [rbp-234h]
int v3; // [rsp+10h] [rbp-230h]
int j; // [rsp+14h] [rbp-22Ch]
int v5; // [rsp+20h] [rbp-220h]
int v6; // [rsp+24h] [rbp-21Ch]
char v7[8]; // [rsp+28h] [rbp-218h] BYREF
char s[256]; // [rsp+30h] [rbp-210h] BYREF
char dest[264]; // [rsp+130h] [rbp-110h] BYREF
unsigned __int64 v10; // [rsp+238h] [rbp-8h]

v10 = __readfsqword(0x28u);
fflush(_bss_start);
if ( !fgets(s, 256, stdin) )
exit(1);
v1 = strlen(s);
if ( v1 > 0 && s[v1 - 1] == 10 )
s[--v1] = 0;
if ( !validate_input_length(v1) )
exit(1);
for ( i = 0; i <= 7; ++i )
v7[i] = global_nodes[16 * i];
if ( !validate_key(v7, 8LL) )
exit(1);
strncpy(dest, s, v1);
dest[v1] = 0;
compute_checksum(dest, v1);
stack_push(dest);
stack_push(v1);
stack_push(v7);
stack_push(8LL);
stack_push(sub_44656);
stack_push(0LL);
stack_push(4LL);
reverse_call();
if ( v1 != 28 )
exit(1);
v3 = 1;
for ( j = 0; j < 28; ++j )
{
if ( dest[j] != result[j] )
{
v3 = 0;
break;
}
}
v5 = calculate_crc32(dest, 28LL);
v6 = calculate_crc32(result, 28LL);
if ( !v3 || v5 != v6 )
{
secure_memset(dest, 0LL, 256LL);
secure_memset(v7, 0LL, 8LL);
exit(1);
}
puts("Great!");
secure_memset(dest, 0LL, 256LL);
secure_memset(v7, 0LL, 8LL);
return v10 - __readfsqword(0x28u);
}

这里才是真正的加密逻辑,要求输入为28字节,然后调用sub_44656函数,最后与result进行比对,还另外匹配了calculate_crc32(dest,28)​ 与 calculate_crc32(result,28)(双重保险),都相等才算判定成功

跟进sub_44656函数,发现就是一个标准的RC4:

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
unsigned __int64 __fastcall sub_44656(__int64 a1, int a2, __int64 a3, int a4)
{
int i; // [rsp+20h] [rbp-220h]
int j; // [rsp+20h] [rbp-220h]
int v7; // [rsp+20h] [rbp-220h]
int v8; // [rsp+24h] [rbp-21Ch]
int v9; // [rsp+24h] [rbp-21Ch]
int k; // [rsp+28h] [rbp-218h]
char v11; // [rsp+2Ch] [rbp-214h]
char v12; // [rsp+2Ch] [rbp-214h]
char v13[520]; // [rsp+30h] [rbp-210h]
unsigned __int64 v14; // [rsp+238h] [rbp-8h]

v14 = __readfsqword(0x28u);
v8 = 0;
for ( i = 0; i <= 255; ++i )
{
v13[i] = i;
v13[i + 256] = *(i % a4 + a3);
}
for ( j = 0; j <= 255; ++j )
{
v8 = (v13[j + 256] + v8 + v13[j]) % 256;
v11 = v13[j];
v13[j] = v13[v8];
v13[v8] = v11;
}
v9 = 0;
v7 = 0;
for ( k = 0; k < a2; ++k )
{
v7 = (v7 + 1) % 256;
v9 = (v9 + v13[v7]) % 256;
v12 = v13[v7];
v13[v7] = v13[v9];
v13[v9] = v12;
*(k + a1) ^= v13[(v13[v7] + v13[v9])];
}
return v14 - __readfsqword(0x28u);
}

密钥就是之前的geek2025,密文是result,可以解密了

EXP:

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
def rc4_decrypt(data: bytes, key: bytes) -> bytes:
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]

i = j = 0
out = bytearray()
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
out.append(byte ^ K)
return bytes(out)

data = bytes([
0x52, 0x59, 0xF3, 0x8A, 0x00, 0x0F, 0xE6, 0x56,
0x36, 0xE5, 0xF0, 0x33, 0x40, 0x6E, 0x56, 0x81,
0x5A, 0xE5, 0x6F, 0x87, 0x6F, 0x9F, 0x21, 0xC9,
0xA6, 0xBB, 0x16, 0x51
])

key = b"geek2025"

flag = rc4_decrypt(data, key)
print(flag)

#SYC{50_y0u_pl@y_Gensh1n_too}

8.QYQSの奇妙冒险2

ida打开文件,发现主函数跟上一题长得差不多,不信邪解了一次,果然是错的

然后开始动调

经过测试,在主函数最后一句return 0;上下个断点,然后直接让代码跑完

这时候再回头去定位跳转到验证成功的代码位置,发现居然有三个不同的判定

QQ_1763297949214

image

QQ_1763298327106

但在我的动调过程中,我没发现它有运行进入过第三层判定的代码,所以仔细检索一下第三层判定代码,发现其中unk_7FF603C0F000对应的数据即为真正的flag

(事后推测应该是藏有一个SMC之类的自解密算法)

QQ_1763298436025

SYC{M@y_bE_y0u_F1nd?}

9.stack_bomb

拿到文件,发现主函数没法反编译,看汇编中间应该是加了段混淆

QQ_1763299321477

但是在混淆前面还有一串调用函数,一个一个跟进查看函数作用

整理得:

1
2
3
4
5
6
7
8
9
push    offset loc_411307    //a2 + a1;
push offset sub_41109B //a1 - a2;
push offset sub_4113F2 //a2 ^ a1;
push offset loc_411186 //a2 & a1;
push offset sub_41115E //a1 << a2;
push offset sub_411091 //a1 >> a2;
push offset sub_4112D0 //a1 % a2;
push offset sub_411163 //a2 * a1;
push offset sub_411005 //a1 / a2;

然后继续往下分析,为了静态分析代码,先把混淆nop掉

主函数:

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
v16[0] = 1;
v16[1] = 2;
v16[2] = 3;
v16[3] = 4;
sub_4110E6("plz input your ans:\n", v4);
_acrt_iob_func(0);
v0 = sub_41127B();
fgets(Buffer, 33, v0);
if ( sub_41127B() )
{
strcspn(Buffer, "\n");
v6 = sub_41127B();
if ( v6 >= 0x21 )
sub_411050();
Buffer[v6] = 0;
if ( j_strlen(Buffer) == 32 )
{
j_memcpy(v10, Buffer, 8u);
j_memcpy(v9, v12, 8u);
j_memcpy(v8, v13, 8u);
j_memcpy(v7, v14, 8u);
(v10[3])();
(sub_411014)(v10, v16);
(sub_411014)(v9, v16);
(sub_411014)(v8, v16);
(sub_411014)(v7, v16);
if ( v10[0] == 0x9A8C0C4B
&& v10[1] == 0xC412FF1C
&& v9[0] == 0xBFC3A488
&& v9[1] == 0xB16C8FD0
&& v8[0] == 0x4136E319
&& v8[1] == 0x8835E4FF
&& v7[0] == 0x118263A7
&& v7[1] == 0x7C85D629 )
{
sub_4110E6("You are right!", v15);
}
else
{
sub_4110E6("No it's wrong", v15);
}
LODWORD(v1) = 0;
}
else
{
sub_4110E6("len wrong\n", v5);
LODWORD(v1) = 1;
}
}
else
{
sub_4110E6(aError, v5);
LODWORD(v1) = 1;
}
v3 = v1;
sub_411217(&savedregs, &dword_41236C);
return v3;

这段主函数看密文像一个tea,跟进sub_411014函数:

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
v71 = *a1;
v70 = *(a1 + 4);
v69 = 0;
v67 = *DWORD1(a1);
v66 = *(DWORD1(a1) + 4);
v65 = *(DWORD1(a1) + 8);
v64 = *(DWORD1(a1) + 12);
v59 = 1;
v68 = STACK[0x294];
while ( v59 )
{
while ( 1 )
{
STACK[0x26C] = a1;
STACK[0x268] = *(&a1 + 1);
STACK[0x264] = v69;
STACK[0x260] = DWORD1(a1);
STACK[0x25C] = *(&a1 + 5);
STACK[0x258] = *(&a1 + 6);
STACK[0x254] = *(&a1 + 7);
STACK[0x250] = dword_41B198;
v69 = (STACK[0x2C0])(STACK[0x250], STACK[0x264]);
v48 = (STACK[0x2B0])(v70, STACK[0x284]);
v60 = (STACK[0x2C0])(v67, v48);
v49 = (STACK[0x2C0])(v69, v70);
v61 = (STACK[0x2B8])(v49, v60);
v50 = (STACK[0x2AC])(v70, STACK[0x280]);
v51 = (STACK[0x2C0])(v66, v50);
v52 = (STACK[0x2B8])(v61, v51);
v71 = (STACK[0x2C0])(v71, v52);
v53 = (STACK[0x2B0])(v71, STACK[0x284]);
v62 = (STACK[0x2C0])(v65, v53);
v54 = (STACK[0x2C0])(v69, v71);
v63 = (STACK[0x2B8])(v54, v62);
v55 = (STACK[0x2AC])(v71, STACK[0x280]);
v56 = (STACK[0x2C0])(v64, v55);
v57 = (STACK[0x2B8])(v63, v56);
v70 = (STACK[0x2C0])(v70, v57);
if ( v68 >= a48 )
break;
++v68;
}
v59 = 0;
}
*a1 = v71;
*(a1 + 4) = v70;
return 0;

代码中出现了一大堆STACK[…]的内容,在动调的辅助下慢慢看每个STACK对应的操作分别是什么,然后把他们整理下来

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
uint32_t part1 = *(uint32_t*)ptr = STACK[0x26C];         
uint32_t part2 = *(uint32_t*)(ptr+4) = STACK[0x268];
uint32_t k0 = *(uint32_t*)(*(uint32_t*)ptr + 0) = 1;
uint32_t k1 = *(uint32_t*)(*(uint32_t*)ptr + 4) = 2;
uint32_t k2 = *(uint32_t*)(*(uint32_t*)ptr + 8) = 3;
uint32_t k3 = *(uint32_t*)(*(uint32_t*)ptr + 12) = 4;

uint32_t sum = STACK[0x264] = 0;
uint32_t rconstA = STACK[0x284] = 4;
uint32_t rconstB = STACK[0x280] = 5;
uint32_t delta = STACK[0x250] = 0x9000000;

for (int round = 0; round <= rounds_limit; ++round) {
sum = sub_0x2C0(delta, sum); +

t0 = sub_0x2B0(part2, rconstA); << 4
t1 = sub_0x2C0(k0, t0); + 1
t2 = sub_0x2C0(sum, part2); +
t3 = sub_0x2B8(t2, t1); ^

t4 = sub_0x2AC(part2, rconstB); >> 5
t5 = sub_0x2C0(k1, t4); + 2
t6 = sub_0x2B8(t3, t5); ^

part1 = sub_0x2C0(part1, t6); +

t7 = sub_0x2B0(part1, rconstA); << 4
t8 = sub_0x2C0(k2, t7); + 3
t9 = sub_0x2C0(sum, part1); +
t10 = sub_0x2B8(t9, t8); ^

t11 = sub_0x2AC(part1, rconstB); >> 5
t12 = sub_0x2C0(k3, t11); + 4
t13 = sub_0x2B8(t10, t12); ^

part2 = sub_0x2C0(part2, t13); +
}


*(uint32_t*)ptr = part1;
*(uint32_t*)(ptr+4) = part2;

再把这些整理成可读的代码:

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
sum += delta;
t0= part2 << 4;
t1= t0 + k0;
t2= sum + part2;
t3= t1 ^ t2;

t4= part2 >> 5;
t5= t4 + k1;
t6= t5 ^ t3;

part1 += t6;

t7= part1 << 4;
t8= t7 + k2;
t9= part1 + sum;
t10= t9 ^ t8;

t11= part1 >> 5;
t12= t11 + k3;
t13= t12 ^ t10;

part2 += t13;

sum += delta;

part1 += (((part2 << 4) + k0) ^ (sum + part2)) ^ ((part2 >> 5) + k1);
part2 += (((part1 << 4) + k2) ^ (part1 + sum)) ^ ((part1 >> 5) + k3);
delta =0x9000000
key [1,2,3,4]

很显然一个标准tea算法的模样就出来了

最后就是提取主函数里的密文写解密代码了

EXP:

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

void tea_decrypt(uint32_t *v, uint32_t *k)
{
uint32_t part1 = v[0];
uint32_t part2 = v[1];
uint32_t delta = 0x9000000;
uint32_t sum = delta * 32;
int i;

for (i = 0; i < 32; i++) {
part2 -= (((part1 << 4) + k[2]) ^ (part1 + sum)) ^ ((part1 >> 5) + k[3]);
part1 -= (((part2 << 4) + k[0]) ^ (part2 + sum)) ^ ((part2 >> 5) + k[1]);
sum -= delta;
}

v[0] = part1;
v[1] = part2;
}

int main()
{
uint32_t key[4] = {1, 2, 3, 4};
uint32_t enc[][2] =
{
{0x9A8C0C4B, 0xC412FF1C},
{0xBFC3A488, 0xB16C8FD0},
{0x4136E319, 0x8835E4FF},
{0x118263A7, 0x7C85D629}
};

char flag[4 * 8 + 1];
char *b = flag;

for(int i = 0; i < 4; i++) {
tea_decrypt(enc[i], key);
for(int j = 0; j < 4; j++) {
*b++ = (enc[i][0] >> (j * 8)) & 0xFF;
}
for(int j = 0; j < 4; j++) {
*b++ = (enc[i][1] >> (j * 8)) & 0xFF;
}
}
*b = '\0';
printf("%s", flag);
return 0;
}

//syc{QYQS_F1nD_Th3_@nswer_H3re~~}

10.ez_vm

ida打开,联系题目就知道是vm虚拟机的题目

主函数里可以看到两个函数名明显有问题的函数

QQ_1763387351692

先找找有没有可疑的字符串,比如密文或者opcode

在sub_5c6d7e的位置找到了疑似密文的字符串

QQ_1763388161314

在sub_1a2b3c函数下面看到了一次vm,其中的string_process_program就是这一轮vm的opcode

image

同理在sub_9e8f7a函数里找到了第二轮vm的opcode——xor_compare_program

QQ_1763388442246

由两个opcode定义的不同名称猜测这个题应该是有两轮不同的加密逻辑,其中一轮是异或

然后再跟进去分析vm的具体实现,发现也存在两段不同的vm实现逻辑

QQ_1763388683059

QQ_1763388703915

介于vm本身的麻烦性和代码的复杂性,我选择边调试边理清代码的运行顺序和加密逻辑,通过几次调试对比opcode

最后整理出两段opcode对应的汇编及反汇编代码(参考了ai的分析):

image

贴上两段整理好的汇编:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0x01,   0x01, 0xFF,   0x00, 0x00, 0x00, 0x00, 0x00,   MOV
0x01, 0x02, 0xFF, 0x00, 0x1D, 0x00, 0x00, 0x00,
0x01, 0x03, 0xFF, 0x00, 0x03, 0x00, 0x00, 0x00,
0x01, 0x05, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x00,

0x06, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, CMP
0x08, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, JZ
0x01, 0x06, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, MOV
0x02, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, ADD
0x0E, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, LDB(从代码区按索引读 1 字节)
0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, ADD 3
0x0F, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, STB(向代码区按索引写 1 字节)
0x02, 0x01, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, ADD
0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, JMP
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 HALT
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
0x01,   0x01, 0xFF,   0x00, 0x00, 0x00, 0x00, 0x00,   MOV
0x01, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, MOV
0x01, 0x03, 0xFF, 0x00, 0x5A, 0x00, 0x00, 0x00, MOV(这里赋值的就是异或常量)
0x01, 0x04, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, MOV
0x01, 0x05, 0xFF, 0x00, 0x00, 0x02, 0x00, 0x00, MOV
0x01, 0x07, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x00, MOV

0x06, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, CMP
0x08, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, JZ(相等跳转)
0x01, 0x06, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, MOV
0x02, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, ADD
0x0E, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, LDB(从代码区按索引读 1 字节)
0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, XOR 0x5A
0x0F, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, STB(向代码区按索引写 1 字节)
0x02, 0x01, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, ADD
0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, JMP
0x01, 0x06, 0xFF, 0x00, 0x1D, 0x00, 0x00, 0x00, MOV
0x06, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, CMP
0x09, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, JNZ(不等跳转)
0x01, 0x07, 0xFF, 0x00, 0x00, 0x01, 0x00, 0x00, MOV
0x01, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, MOV
0x06, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, CMP
0x08, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, JZ
0x01, 0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, MOV
0x02, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, ADD
0x0E, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, LDB(从代码区按索引读 1 字节)
0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, PUSH
0x01, 0x06, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, MOV
0x02, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, ADD
0x0E, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, LDB(从代码区按索引读 1 字节)
0x0B, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, POP
0x06, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, CMP
0x09, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, JNZ
0x02, 0x01, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, ADD
0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, JMP
0x01, 0x04, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, MOV
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, HALT
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, HALT

第二段异或的值也可以动调出来

屏幕截图 2025-11-17 215909

所以总之就是进行了一个加3和一个异或0x5A的操作,用cyberchef就能解了

QQ_1763387917243

SYC{W31c0m3_t0_r3@1_r3verse!}

11.GeekBinder

文件包里有个docker,先看so文件

代码很少,打开就能看见疑似密文的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if ( !a1 || !a2 )
return 0xFFFFFFFFLL;
v3 = malloc(0x5BuLL);
if ( !v3 )
return 4294967294LL;
*v3 = 0x7C725E7310263C34LL;
v3[1] = 0x5D666F5505541F1ELL;
v3[2] = 0x4601535D19153A54LL;
v3[3] = 0x4266037034165614LL;
v3[4] = 0x505E5974340B0002LL;
v3[5] = 0x5B5D536D18543A54LL;
v3[6] = 0x5A666F4B19251713LL;
v3[7] = 0x6A5E705F19550B38LL;
v3[8] = 0x651594608251717LL;
v3[9] = 0x506D5560340B5438LL;
v3[10] = 0x440555705540209LL;
*(v3 + 44) = 521;
*(v3 + 90) = 24;
*a1 = v3;
*a2 = 91LL;
return 0LL;

再检索一下代码还能找到一处异或处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__int64 __fastcall attr_xor_cipher(__int64 a1, size_t a2, _QWORD *a3, size_t *a4)
{
void *v7; // [rsp+28h] [rbp-8h]

if ( !a1 || !a2 || !a3 || !a4 )
return 0xFFFFFFFFLL;
v7 = malloc(a2);
if ( !v7 )
return 4294967294LL;
sub_1119(a1, a2, v7);
*a3 = v7;
*a4 = a2;
return 0LL;
}

跟进一下sub_1119:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned __int64 __fastcall sub_1119(__int64 a1, unsigned __int64 a2, __int64 a3)
{
unsigned __int64 result; // rax
unsigned __int64 i; // [rsp+20h] [rbp-8h]

for ( i = 0LL; ; ++i )
{
result = i;
if ( i >= a2 )
break;
*(a3 + i) = *(a1 + i) ^ aGeek2025[i % 8];
}
return result;
}

其中引用的key是geek2025,然后就可以直接写解密代码了

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
enc = [
0x7C725E7310263C34,
0x5D666F5505541F1E,
0x4601535D19153A54,
0x4266037034165614,
0x505E5974340B0002,
0x5B5D536D18543A54,
0x5A666F4B19251713,
0x6A5E705F19550B38,
0x0651594608251717,
0x506D5560340B5438,
0x0440555705540209,
]
key = b"geek2025"
data = b''.join(x.to_bytes(8, 'little') for x in enc)

dec = bytes(b ^ key[i % len(key)] for i, b in enumerate(data))

print(dec)

#SYC{An@Iyz1ng_Th3_proc3ss3s_B3Tween_File3_1s_contr@ry_To_n0rm@l_pr@ctic3_1n_Re_eng1neer1}

12.obfuscat3

主函数是很简洁明了的代码,但是关键加密函数(obf_encode)里加了混淆(但是这个带混淆的代码直接给ai它也能分析OvO)

QQ_1763644582880

函数里又调用了init_encode和exchange_encode

具体审核分析代码看起来是有个魔改后又拆分加了混淆的RC4,进行了自定义的S盒流密码变体,其中init_encode函数实现了KSA的功能,exchange_encode函数实现了swap的功能,而obf_encode应该是实现了PRGA的功能,RC4算法的魔改点是把最后char ^ S[(S[i] + S[j]) % 256]​中的异或运算改成了 + ,所以解密的时候只要改成 - 就行了

ai的分析是:用了很多复杂的布尔式/按位算术把标准的 j = j + S[i] + key[i%len]​ 和 i++​/idx = S[(S[i] + S[j]) & 0xFF] 等简单操作包裹、隐藏起来

然后就可以写解密代码了

EXP:

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
cipher = [
0xB4, 0xCD, 0x69, 0x54, 0xBD, 0x67, 0x20, 0x9D, 0xF2, 0xC3,
0x24, 0x14, 0xC2, 0x1B, 0xE9, 0x6A, 0x44, 0x14, 0x4E, 0x39,
0xC5, 0xC8, 0x5B, 0x11, 0x75, 0xAD, 0xDE, 0xBB, 0xFE, 0xE4,
0x6E, 0x65, 0x06, 0x9A, 0x91, 0xFE, 0xA0, 0x68, 0xA4, 0x86,
0x17, 0x6C, 0x0A, 0xCF, 0x1E, 0x67, 0xE3, 0x0D, 0x60, 0x47,
0x13, 0x6B, 0xD1, 0x36, 0xF2, 0x77, 0x58, 0x76, 0x1E, 0x98,
0xF5, 0x7F, 0x0A, 0x92, 0xB7, 0x0A, 0xEA, 0xAE, 0x46, 0x7E,
0x6A, 0x18, 0x4A, 0x59, 0x4E, 0x71, 0xB2, 0xE1, 0x41, 0x7A,
0x0B, 0x31, 0xBA, 0xC6, 0xAA, 0xCF, 0xCE, 0x09, 0xBF, 0x2E,
0xF8, 0x4D, 0x75, 0xEF, 0x14, 0xED, 0x5F, 0x66, 0x44, 0x6F,
0xDE, 0xE2, 0x7C, 0x10, 0x8C, 0xB7, 0x4E, 0x6B, 0xB2, 0xD4,
0xF6, 0x91, 0xD7, 0x84, 0x86, 0x1F, 0xF8, 0x65, 0x94, 0x0B,
0x14, 0x28, 0xFB, 0xDD, 0x47, 0xF4, 0xC1, 0x17, 0x42, 0x3F,
0x1E, 0x38, 0x07, 0xBB, 0x37, 0x33, 0x12, 0x0C, 0x16, 0x68,
0xE0, 0x23, 0x12, 0x75, 0x72, 0xD9, 0x71, 0x7A, 0x88, 0xD0,
0x46, 0x28, 0x88, 0xAD, 0x1E, 0x98, 0x8F, 0x92, 0x7E, 0x0E,
0x69, 0x29, 0x37, 0xB1, 0xFF, 0xC5, 0xAF, 0x6F, 0x41, 0x37,
0x65, 0x0E, 0xD2, 0x62, 0x11, 0x8F, 0xA6, 0x3E, 0x95, 0xF5,
0x80, 0x9A, 0xDC
]

key = b"Samsara"

def rc4_keystream(length, key_bytes):
S = list(range(256))
j = 0
# KSA
for i in range(256):
j = (j + S[i] + key_bytes[i % len(key_bytes)]) & 0xFF
S[i], S[j] = S[j], S[i]
# PRGA
i = 0
j = 0
out = []
for _ in range(length):
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) & 0xFF]
out.append(K)
return out

ks = rc4_keystream(len(cipher), list(key))
plain = bytes((c - ks[i]) & 0xFF for i, c in enumerate(cipher))
print(plain.decode('ascii'))

#SYC{Alright_I_sti1l_h0pe_th3t_you_solved_the_chall3nge_by_deobfuscating_them_Geek_is_just_the_first_step_of_your_CTF_journey_Im_glad_I_could_be_part_of_your_growth_Good_luck_for_y0u!}

13.reReverse

代码很简单,但看不出什么东西,后面看汇编看到有没反编译出来的内容,明显有判断对错的字符串

QQ_1763645334307

然后把这一大段汇编丢给gpt分析,分析出是一个xxtea

在如下汇编的位置能看到四个重复出现的字符串,就是xxtea的密钥

QQ_1763646347158

在大段汇编的最开始也出现了经典的delta数值

QQ_1763646560527

而密文也能在这段很长的汇编中间找到

QQ_1763646909695

QQ_1763647029474

而这几个密文具体的加密比对顺序在密钥的位置能看见(也可以调试起来看)

QQ_1763647305144

然后取一段分析加密式子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.text:00000000004009A1                 mov     r12d, r10d
.text:00000000004009A4 add r13d, r12d
.text:00000000004009A7 mov r14d, [rsp+28h]
.text:00000000004009AC xor r12d, r14d
.text:00000000004009AF shr r12d, 3
.text:00000000004009B3 mov r12d, r10d
.text:00000000004009B6 xor r13d, r12d
.text:00000000004009B9 shl r14d, 4
.text:00000000004009BD shr r13d, 5
.text:00000000004009C1 mov r14d, ebp
.text:00000000004009C4 mov r13d, ebp
.text:00000000004009C7 lea r12d, ds:0[r10*4]
.text:00000000004009CF add ebp, r12d
.text:00000000004009D2 xor r12d, r13d
.text:00000000004009D5 add r13d, r14d
.text:00000000004009D8 xor r14d, ebx
.text:00000000004009DB xor r13d, r11d

最后贴上解密代码

EXP:

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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 解密 10×u32(40 字节)XXTEA/CBTEA,得到明文(flag)

DELTA = 0x9E3779B9

def u32(x):
return x & 0xFFFFFFFF

def MX(z, y, s, k, p, e):
# Corrected Block TEA 的核心混合项
return (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ ((s ^ y) + (k[(p & 3) ^ e] ^ z))

def btea(v, k, decode=False):
"""v: list[int] (u32), k: 4×u32 key, decode=True 走解密"""
v = list(v)
n = len(v)
if not decode: # 加密(用于回测)
q = 6 + 52 // n
s = 0
z = v[n-1]
for _ in range(q):
s = u32(s + DELTA)
e = (s >> 2) & 3
for p in range(n - 1):
y = v[p + 1]
v[p] = u32(v[p] + MX(z, y, s, k, p, e)); z = v[p]
y = v[0]
v[n-1] = u32(v[n-1] + MX(z, y, s, k, n-1, e))
z = v[n-1]
return v
else: # 解密
q = 6 + 52 // n
s = u32(q * DELTA)
y = v[0]
for _ in range(q):
e = (s >> 2) & 3
for p in range(n - 1, 0, -1):
z = v[p - 1]
v[p] = u32(v[p] - MX(z, y, s, k, p, e))
y = v[p]
z = v[n-1]
v[0] = u32(v[0] - MX(z, y, s, k, 0, e))
y = v[0]
s = u32(s - DELTA)
return v

def pack_le32(arr):
return b"".join(int(x).to_bytes(4, "little") for x in arr)

def main():
# rodata:401090 -> 4×u32(小端)
k = [0x0000DEAD, 0x0000BEEF, 0x00005A7D, 0x0000C0FF]

# 汇编对比顺序:r9d, edi, esi, ecx, edx, r8d, ebx, ebp, r11d, r10d
target = [
0x2973BD37, 0x1BA99AA3, 0xB3C20088, 0xBFC393AB, 0x352ADCCF,
0x3B98E6E6, 0xAE421991, 0xD7B702CF, 0x0EEF6889, 0x08662435,
]

# 解密得到明文(程序期望的 40 字节输入)
plain_u32 = btea(target, k, decode=True)
plain = pack_le32(plain_u32)

print("plain u32:", [f"0x{v:08x}" for v in plain_u32])
print("plain hex:", plain.hex())
try:
txt = plain.decode("ascii")
print("flag:", txt)
except UnicodeDecodeError:
print("bytes (non-ascii):", plain)

# 反向回测(确保实现无误)
assert btea(plain_u32.copy(), k, decode=False) == target

if __name__ == "__main__":
main()

#SYC{sakurakouji_runasama_wa_seikai_ichi}

14.国产の光

鸿蒙逆向,用魔改jadx反编译abc文件,在Index里找到密文和两个疑似密钥的内容

QQ_1763952784994

QQ_1763952845080

然后ida打开so文件,看见了一个AES和一个base58,AES的密钥和iv就是前面在jadx里找到的那两个

QQ_1763953166186

跟进base58发现码表被换了

QQ_1763953279455

然后cyberchef解密就行

QQ_1763953319456

SYC{HarmonyOS_1s_right?right!}

15.Lastone

ida打开审计代码,在主函数就能看见密文的赋值

QQ_1763979168895

然后跟进对buffer进行处理的sub_5A1032函数,审计代码

QQ_1763979348287

在sub_5A11DB函数里是vm虚拟机的switch循环函数,相应的unk_5AC040就是opcode

QQ_1763979427301

对照分析了一下opcode:

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
0x10,   0x00,  0x12,  0x00, 0x00, 0x00,     r0 = 0x12
0x10, 0x01, 0x34, 0x00, 0x00, 0x00, r1 = 0x34
0x23, 0x00, 0x03, 0x00, 0x00, 0x00, r0 *= 3 (r0 = 0x36)
0x22, 0x01, 0xAB, 0x00, 0x00, 0x00, r1 ^= 0xAB
0x40, 0x00, 0x01, r0 += r1
0x10, 0x02, 0x07, 0x00, 0x00, 0x00, r2 = 0x07
0x10, 0x03, 0x11, 0x00, 0x00, 0x00, r3 = 0x11
0xFF, HALT
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,

0x10, 0x00, 0xAB, 0x00, 0x00, 0x00, r0 = 0xAB
0x10, 0x01, 0xCD, 0x00, 0x00, 0x00, r1 = 0xCD
0x2B, 0x00, 0x04, 0x00, 0x00, 0x00, r0 = rol(r0, 4)
0x2C, 0x01, 0x02, 0x00, 0x00, 0x00, r1 = ror(r1, 2)
0x22, 0x00, 0x55, 0x00, 0x00, 0x00, r0 ^= 0x55
0x10, 0x02, 0x09, 0x00, 0x00, 0x00, r2 = 0x09
0x10, 0x03, 0x22, 0x00, 0x00, 0x00, r3 = 0x22
0xFF, HALT
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,

0x10, 0x00, 0xFF, 0x00, 0x00, 0x00, r0 = 0xFF
0x10, 0x01, 0xAA, 0x00, 0x00, 0x00, r1 = 0xAA
0x26, 0x00, 0x02, 0x00, 0x00, 0x00, r0 << 2
0x25, 0x00, 0x64, 0x00, 0x00, 0x00, r0 %= 0x64
0x20, 0x01, 0x11, 0x00, 0x00, 0x00, r1 += 0x11
0x10, 0x02, 0x0D, 0x00, 0x00, 0x00, r2 = 0x0D
0x10, 0x03, 0x33, 0x00, 0x00, 0x00, r3 = 0x33
0xFF, HALT
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,

0x10, 0x00, 0x11, 0x00, 0x00, 0x00, r0 = 0x11
0x10, 0x01, 0x22, 0x00, 0x00, 0x00, r1 = 0x22
0x28, 0x00, 0x0F, 0x00, 0x00, 0x00, r0 &= 0x0F
0x29, 0x01, 0xF0, 0x00, 0x00, 0x00, r1 |= 0xF0
0x22, 0x00, 0x33, 0x00, 0x00, 0x00, r0 ^= 0x33
0x10, 0x02, 0x0E, 0x00, 0x00, 0x00, r2 = 0x0E
0x10, 0x03, 0x44, 0x00, 0x00, 0x00, r3 = 0x44
0xFF, HALT
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,

0x10, 0x00, 0xDE, 0x00, 0x00, 0x00, r0 = 0xDE
0x10, 0x01, 0xAD, 0x00, 0x00, 0x00, r1 = 0xAD
0x31, 0x00, ++r0
0x32, 0x01, --r1
0x30, 0x00, 0x01, swap(r0, r1)
0x20, 0x00, 0x10, 0x00, 0x00, 0x00, r0 += 0x10
0x10, 0x02, 0x02, 0x00, 0x00, 0x00, r2 = 0x02
0x10, 0x03, 0x55, 0x00, 0x00, 0x00, r3 = 0x55
0xFF, HALT
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,

0x10, 0x00, 0xBE, 0x00, 0x00, 0x00, r0 = 0xBE
0x10, 0x01, 0xEF, 0x00, 0x00, 0x00, r1 = 0xEF
0x23, 0x00, 0x02, 0x00, 0x00, 0x00, r0 *= 0x02
0x24, 0x01, 0x03, 0x00, 0x00, 0x00, r1 /= 0x03
0x40, 0x00, 0x01, r0 += r1
0x10, 0x02, 0x0A, 0x00, 0x00, 0x00, r2 = 0x0A
0x10, 0x03, 0x66, 0x00, 0x00, 0x00, r3 = 0x66
0xFF, HALT
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,

0x10, 0x00, 0xCA, 0x00, 0x00, 0x00, r0 = 0xCA
0x10, 0x01, 0xFE, 0x00, 0x00, 0x00, r1 = 0xFE
0x2A, 0x00, r0 = ~r0
0x28, 0x00, 0xFF, 0x00, 0x00, 0x00, r0 &= 0xFF
0x22, 0x01, 0x7F, 0x00, 0x00, 0x00, r1 ^= 0x7F
0x10, 0x02, 0x0F, 0x00, 0x00, 0x00, r2 = 0x0F
0x10, 0x03, 0x77, 0x00, 0x00, 0x00, r3 = 0x77
0xFF, HALT
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,

0x10, 0x00, 0x99, 0x00, 0x00, 0x00, r0 = 0x99
0x10, 0x01, 0x88, 0x00, 0x00, 0x00, r1 = 0x88
0x20, 0x00, 0x11, 0x00, 0x00, 0x00, r0 += 0x11
0x21, 0x01, 0x08, 0x00, 0x00, 0x00, r1 -= 0x08
0x2B, 0x00, 0x03, 0x00, 0x00, 0x00, r0 = rol(r0, 3)
0x10, 0x02, 0x05, 0x00, 0x00, 0x00, r2 = 0x05
0x10, 0x03, 0x88, 0x00, 0x00, 0x00, r3 = 0x88
0xFF, HALT
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

这里算出来的8组r0, r1, r2, r3的值是用于下面funcs中最后一个参数算式中的,即(v8 + v6 * v7) ^ (40503 * v9)

但是这一部分也可以直接动调跑出来:

1
2
3
4
5
6
7
8
1. (0x9F + 0x11 * 0x07) ^ (0x9E37 * 0xD5)
2. (0x40000033 + 0x22 * 0x09) ^ (0x9E37 * 0xAE5)
3. (0xBB + 0x33 * 0x0D) ^ (0x9E37 * 0x14)
4. (0xF2 + 0x44 * 0x0E) ^ (0x9E37 * 0x32)
5. (0xDF + 0x55 * 0x02) ^ (0x9E37 * 0xBC)
6. (0x4F + 0x66 * 0x0A) ^ (0x9E37 * 0x1CB)
7. (0x81 + 0x77 * 0x0F) ^ (0x9E37 * 0x35)
8. (0x80 + 0x88 * 0x05) ^ (0x9E37 * 0x550)

这八个算式算出来的值就是异或的参数,但是我这里直接去试着异或解不出来

于是选择动态调试,直接把密文patch进去然后运行,但是这样运行得到的结果中存在乱码

QQ_1763979908746

前四位根据经验直接替换成SYC{,后面的几位乱码经过几次手动爆破和动调验证之后得出了真正的值(不知道是不是非预期解)

SYC{1@St_0nE_THanKs_I_lOvE_y0U!}

16.ez_android

安卓题,先安装到雷电模拟器运行看看,发现有三个输入框,应该是有三段flag

QQ_1763994063478

然后jadx打开看看,在源码里找到了三个function函数,在资源文件里找到了三段密文

QQ_1763994114984

先看第一段,是一个标准的DES/ECB/NoPadding算法,密钥为12345678,但是对密文又进行了一次转16进制的操作

QQ_1763994201035

直接cyberchef解密就行

QQ_1763994276847

看第二段,主逻辑在native层

QQ_1763994314455

解压之后反编译so文件,虽然在Java这个函数下面没看到关键,但是在旁边的函数列表可以看到一个decrypt_xor函数

QQ_1763994402266

交叉引用回去也能看到它的调用情况

QQ_1763994504134

分析一下调用的参数,发现异或的是一整段内容(看命名应该是一个so文件),异或的值为0xAA

QQ_1763994647173

然后拿去cyberchef解密一下,解出来是一个elf文件

QQ_1763994956829

反编译查看这个elf文件,是一个标准的RC4,密钥为mysecret

再拿去cyberchef解密就能得到第二段flag

QQ_1763995060416最后看第三段,也是在native层

QQ_1764067987451

ida打开分析代码,是个des算法

把des加密中的1改成0就变成解密了

QQ_1764068075022

然后动调把密文patch进去,直接就能调出第三段flag

QQ_1764067918889

SYC{Th1s_1s_Th3_R3@1ly_F1ag!}