Geek2025出题小记
前言
也是赶上ddl了)第二天要交wp今天才开始写,不过还是会努力写详细一点回忆一下心路历程的
突然发现我怎么有四五题都没上)
我的出题源码会放在:https://github.com/Samsara-lo/Geek2025/tree/main
Reverse
Week1
ez_pyyy
下载下来是pyc文件,本意是学习一下pyc文件内容、反编译
pyc反编译工具:(推荐使用pycdc,可以去了解一下这几个的区别,不过其实都有一定的局限性)
uncompyle6
pycdc
反编译出来的内容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
43cipher=[0x30,0x37,0x39,0x32,0x35,0x37,0x35,0x32,0x34,0x32,0x30,0x37,0x65,0x34,0x35,0x32,0x34,0x32,0x34,0x32,0x30,0x37,0x35,0x37,0x37,0x37,0x32,0x36,0x35,0x37,0x36,0x37,0x37,0x37,0x35,0x36,0x62,0x37,0x61,0x36,0x32,0x35,0x38,0x34,0x32,0x34,0x63,0x36,0x32,0x32,0x34,0x32,0x32,0x36]
def str_to_hex_bytes(s: str) -> bytes:
return s.encode("utf-8")
def enc(data: bytes, key: int) -> bytes:
return bytes([b ^ key for b in data])
def en3(b: int) -> int:
# 循环左移4位,相当于高低半字节交换
return ((b << 4) & 0xF0) | ((b >> 4) & 0x0F)
def en33(data: bytes, n: int) -> bytes:
"""整体 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)
# 2. xor 0x11
data = enc(data, 0x11)
# 3. 循环左移4位(对每字节)
data = bytes([en3(b) for b in data])
# 4. reverse
data = data[::-1]
# 5. 整体循环左移32位
data = en33(data, 32)
# 6. tohex
if data.hex() == cipher:
print("Correct! ")
else:
print("Wrong!!!!!!!!")
每一步反着改回去即可:
exp:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23cipher = [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 enc(data: bytes, key: int) -> bytes:
return bytes([b ^ key for b in data])
def en3(b: int) -> int:
return b << 4 & 240 | b >> 4 & 15
def en33(data: bytes, n: int) -> bytes:
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__':
data = bytes.fromhex(bytes(cipher).decode())
data = en33(data, 32)
data = data[::-1]
data = bytes([en3(b) for b in data])
data = enc(data, 17)
print(data)
# SYC{jtfgdsfda554_a54d8as53}
only_flower
第一周似乎有挺多选手哭这道的?但是其实只有一种花
花指令实则就是垃圾代码,它不会影响程序运行但是会影响IDA反编译
可以了解一下IDA递归下降反编译&花指令原理
因为不影响执行,所以花指令动调一下就直接可以跳过,把跳过的字节码nop调就可以。我们讲静态:
实现原理:1
2
3__asm__ __volatile__ (
".byte 0xEB,0xFF,0xC0,0x48;"
);
0xEB |
JMP rel8(短跳转指令) |
|---|---|
0xFF |
作为 JMP 的 跳转偏移(-1),也会被当作下一条指令的 opcode 导致错位 |
0xC0 |
根据上下文会被解析成各种非法或奇怪指令 |
0x48 |
REX.W 前缀(x86_64),单独出现会让反汇编器误判接下来的结构 |
IDA里反编译的效果:1
2
3
4
5
6.text:0040161F loc_40161F: ; CODE XREF: _main:loc_40161F↑j
.text:0040161F EB FF jmp short near ptr loc_40161F+1
.text:0040161F _main endp
.text:0040161F
.text:0040161F ; ---------------------------------------------------------------------------
.text:00401621 C0 48 A1 db 0C0h, 48h, 0A1h
首先你需要打开字节码:IDA-Options-General-Numbers of Opcode这里改成5或者10
EB FF就是字节码
jmp short near ptr loc_40161F+1的意思是跳到loc_40161F往后偏移 +1 字节的地址,所以是跳过EB执行FF,也就是跳过JMP指令,首先在0x040161F的位置按U取消定义,把db 0EBh nop掉(不懂nop去搜,可以下一个keypatch)。
下面的也这么去就好了。
去完花就可以看到清晰的逻辑写exp了
1 | # decrypt_onlyflower.py |
ezRu3t
source code: https://github.com/Samsara-lo/Geek2025/blob/main/ezRu3t/main.rs
shift+F12找到类似Base85和Base64码表的字符串,本意其实不是rust代码审计要求全学一遍,因为校内的进度Base家族不会的话要挨骂了。
密文为AA;XAM?,_@;T[r@7E779h8;s‘`pt=>3c6ASuHFASOtP<Gkf_A4&gPAl1]S
直接base85+base64解密
SYC{Ohjhhh_y0u_g3t_Ezzzzz3_Ru3t!@}
ezSMC
source code: https://github.com/Samsara-lo/Geek2025/tree/main/ezSMC
ida 打开审计代码,联系题目很容易发现.miao段代码是被SMC加密处理过的
两种解法:1.动调过掉SMC自解密;2.写ida python代码解密
第二种解法可以看这个师傅的,写的很详细:https://fairy-10124.github.io/2025/11/28/Geek%202025%EF%BC%88reverse%E6%96%B9%E5%90%91%EF%BC%89/
程序主函数:
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
被SMC的函数是一个base64,加密顺序为密钥为单字节0x11的RC4→转hex→base64→使用自定义表的base58,直接解密即可
SYC{OHhhhhhhh_y0u_Kn0m_SMCCCC@!}
Week2
Mission Ghost Signal
RE部分有两个思路:
- 程序给了解密函数,直接patch
- 同构AES,发现魔改自实现的S盒
程序主函数: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
58int __cdecl main(int argc, const char **argv, const char **envp)
{
_BYTE v4[32]; // [esp+1Ch] [ebp-20Ch] BYREF
char v5[192]; // [esp+3Ch] [ebp-1ECh] BYREF
char Buffer[256]; // [esp+FCh] [ebp-12Ch] BYREF
_BYTE v7[16]; // [esp+1FCh] [ebp-2Ch] BYREF
int n7; // [esp+20Ch] [ebp-1Ch]
int j; // [esp+210h] [ebp-18h]
char v10; // [esp+217h] [ebp-11h]
int i; // [esp+218h] [ebp-10h]
size_t n25_1; // [esp+21Ch] [ebp-Ch]
sub_403500();
SetConsoleOutputCP(0xFDE9u);
SetConsoleCP(0xFDE9u);
qmemcpy(v7, "1145141145144332", sizeof(v7));
puts("密码验证系统");
printf("请输入压缩包密码: ");
if ( fgets(Buffer, 256, (FILE *)iob[0]._ptr) )
{
n25_1 = strlen(Buffer);
if ( n25_1 && Buffer[n25_1 - 1] == 10 )
Buffer[--n25_1] = 0;
if ( n25_1 == 25 )
{
sub_401C08(v5, p_Syclover2025Geek, v7); // "Syclover2025Geek"
memcpy(v4, Buffer, 0x19u);
n7 = 7;
for ( i = 25; i <= 31; ++i )
v4[i] = n7;
AES_CBC_Modified(v5, v4, 0x20u);
v10 = 1;
for ( j = 0; j <= 31; ++j )
{
if ( byte_406020[j] != v4[j] )
{
v10 = 0;
break;
}
}
if ( v10 )
puts("恭喜!这是正确的压缩包密码。");
else
puts("特工,你失败了!密码不正确。");
return 0;
}
else
{
puts("特工,你失败了!密码长度不正确。");
return 1;
}
}
else
{
puts("输入错误!");
return 1;
}
}
其中AES是一个自实现的AES,但程序中给出了解密函数,patch加密为解密即可
1 | .text:00402F59 call AES_CBC_Modified ; 0x402B57 |
密码为We_ve_Trapped_in_The_Sink,解压后得到一个sstv,读取得到一张含有二维码的图片,扫描后得到一个下载链接https://wwnr.lanzoum.com/iIjAG39n7mlg,下载后是一个含摩斯电码的音频,识别得到55316C44657A646F4D5456664D564E664E463835636A52755246396A4D4534316344467951474E5A4C6E303D,转hex得到U1lDezdoMTVfMVNfNF85cjRuRF9jME41cDFyQGNZLn0=,解base64得到flag
SYC{7h15_1S_4_9r4nD_c0N5p1r@cY.}
Week4
国产の光
其实没什么难度不是week4的题,只要配好鸿蒙反编译环境就行。
密文yaApcJ5GoyGwhARDXZLQUdntqPpmVu2GuTChnsLoj5d8ABinwGSsgpGaiPWYbHTTbbzSXxLXwoLgjR1YgquyEnK,自动识别base58,外层密钥welcometosyc2025,iv为helloimsamsaramiao,内层AESCBC,直接解密即可得到flag
obfuscat3
source code:https://github.com/Samsara-lo/Geek2025/blob/main/obfuscat3/obfuscat3.cpp
忘记把256处理一下了成简单题了,我的小巧思没了TT
可以去学习一下ollvm,这题的主要考点是指令替换+瞪眼法,被瞪的太容易了,很容易发现是一个把最后异或魔改成+的RC4
函数里又调用了init_encode和exchange_encode
具体审核分析代码看起来是有个魔改后又拆分加了混淆的RC4,进行了自定义的S盒流密码变体,其中init_encode函数实现了KSA的功能,exchange_encode函数实现了swap的功能,而obf_encode应该是实现了PRGA的功能,RC4算法的魔改点是把最后char ^ S[(S[i] + S[j]) % 256]中的异或运算改成了 + ,所以解密的时候只要改成 - 就行了
具体的GAMBA表达式可以看源码,是源代码混淆的基础上套了两层标准ollvm的指令替换
然后就可以写解密代码了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
48cipher = [
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!}
obfuscat3_revenge
source code:https://github.com/Samsara-lo/Geek2025/blob/main/obfuscat3_revenge/en_obfuscat3_revenge.cpp
主要考点是指令替换GAMBA+逆向
主函数:
1 | int __fastcall main(int argc, const char **argv, const char **envp) |
查看gen:
1 | __int64 __fastcall gen(unsigned __int8 *p_out1, unsigned __int8 *p_out2, const unsigned __int8 *p_key) |
同构gen:
1 | def gen(sbox1, sbox2, key): |
用d810和hrtng反混淆加密函数:
1 | unsigned __int8 *__fastcall real_en( |
查看en1:
1 | __int64 __fastcall en1(unsigned __int8 *inp, const unsigned __int8 *key, int n32) |
是一个循环异或,之后是S盒1替换、顺序替换、倒排、4轮函数、再次倒排,同构:
1 | for i in range(32): inp[i] ^= key[i%16] |
对于轮函数F:
1 | __int64 __fastcall F(int inp, unsigned int dkey, const unsigned __int8 *sbox) |
是一个异或密钥后S盒替换再循环左移,同构:
1 | def b2nle(b, n): return [int.from_bytes(b[i:i+n], byteorder='little', signed=False) for i in range(0, len(b), n)] |
SYC{Then_you_are_1mpressivse}
Misc
Bite off picture
解压缩包发现被加密了,根据提示在010中发现编码
一个翻过来的base64,反转一下解密就行werwerr,解压得到一张图片,用随波 逐流分析发现被修改了宽高,随波逐流直接自动更正,或者去学一下文件结构010改宽高
hidden
flag1其实是隐藏文字,把隐藏文字可见打开就可以
打开文件时不是正常打开,所以把后缀改成zip再寻找剩余flag,都在doc文件夹 里,其中flag3需要修复文件头,jpg文件缺失文件头FF D8,补上即可
gift
压缩包密码g1ft的base64在末尾
根据文件名判断是盲水印,直接用watermark提取
SYC{IT3_gift-f0r-you}
4ak5ra
随波逐流发现有个压缩包,用binwalk提取出来,解压出来是一张png图片,根据题目提示lsb,我们用Stegsolve或者zsteg提取flag
SYC{Im_waiting_for_Sakura_t0_become_a_top_pwn_master}
后记
嘻嘻有前记那也有后记,主要是后面再来博客完善一下wp吧讲讲知识点





