春节解题红包2026题解
前言
- 首先感谢吾爱提供天大的机缘,感谢出题老师我们醍醐灌顶
- 祝各位坛友们,岁岁平,岁岁安,岁岁平安
- 本次参加的目的主要是学习大佬们的出题的思路,进而提高自己的知识水平
- 本着能逆则逆,不能逆也要硬逆的原则,有些题解可能多余,但这即是我的道心
工具与运行环境
- IDA:二进制程序静态分析
- 逍遥模拟器:主要主于安卓运行与动态调试
- JEB: APK程序静态分析
- JADX:APK程序静态分析
- Python3.14: 第四题会用到,其余题解只要是pytho3就可以运行
- DeepSeek: 有些事情让Ai帮忙辅助解决和实现会更快一些
第二题(Windows简单)
// 计算flag的核心函数
_BYTE *__cdecl calc_flag(int a1)
{
_BYTE *result; // eax
*(_DWORD *)a1 = 0x2D327077;
*(_DWORD *)(a1 + 4) = 0x63272B28;
*(_DWORD *)(a1 + 8) = 0x701D6363;
*(_DWORD *)(a1 + 12) = 0x1D747072;
*(_DWORD *)(a1 + 16) = 0x3232230A;
*(_DWORD *)(a1 + 20) = 0x272C1D3B;
*(_DWORD *)(a1 + 24) = 0x273B1D35;
*(_BYTE *)(a1 + 30) = 0x63;
*(_WORD *)(a1 + 28) = 0x3023;
result = (_BYTE *)a1;
do
*result++ ^= 0x42u;
while ( result != (_BYTE *)(a1 + 31) );
*(_BYTE *)(a1 + 31) = 0;
return result; // 在此处下断,就可以看到flag
}
enc_bytes = [
0x77,0x70,0x32,0x2D,
0x28,0x2B,0x27,0x63,
0x63,0x63,0x1D,0x70,
0x72,0x70,0x74,0x1D,
0x0A,0x23,0x32,0x32,
0x3B,0x1D,0x2C,0x27,
0x35,0x1D,0x3B,0x27,
0x23,0x30,
0x63,
]
list_str = "".join([chr(enc_bytes[i] ^ 0x42) for i in range(len(enc_bytes))])
print(list_str)
第三题(Android简单)
package r1;
public abstract class DataMatrix {
public static final int[][] matrix;
// 此处是关键字串
static {
DataMatrix.matrix = new int[][]{new int[]{80, 109, 0x77, 0x7B, 77}, new int[]{97, 0x74, 34, 45, 105}, new int[]{102, 49, 0x7C, 45, 5, 94}, new int[]{4, 49, 36, 42, 105}, new int[]{101, 0x71, 100, 45, 88, 102, 73}, new int[]{0x70, 50, 101, 104, 7, 0x77, 34, 0x70, 75}};
}
}
int[][] arr2_v = DataMatrix.matrix;
C c1 = new C(25, new byte[]{54, 1, 22, 28});
StringBuilder stringBuilder1 = new StringBuilder();
stringBuilder1.append("");
int v19 = 0;
for(int v20 = 0; v20 < 6; ++v20) {
int[] arr_v = arr2_v[v20];
++v19;
if(v19 > 1) {
stringBuilder1.append("");
}
// 此处会调用C.q0方法
U.c.g(stringBuilder1, arr_v, c1);
}
stringBuilder1.append("");
int v = 0;
k.e(((int[])object0), "part");
StringBuilder stringBuilder0 = new StringBuilder();
// 块是最核心的解密算法
// 将加密字串分别与前面的【54,1,22,28】进行异或,然后求出结果
while(v < ((int[])object0).length) {
stringBuilder0.append(((char)(((int[])object0)[v] ^ ((byte[])this.k)[v % ((byte[])this.k).length] & 0xFF)));
++v;
}
String s = stringBuilder0.toString();
k.d(s, "toString(...)");
return s;
- 最简单的方法可以用JEB直接全盘分析得到Flag,但仍然胜之不武
if((v17 ^ 305419896L) == 0xE30FE54D0L) {
int[][] arr2_v = DataMatrix.matrix;
C c1 = new C(25, new byte[]{54, 1, 22, 28});
StringBuilder stringBuilder1 = new StringBuilder();
stringBuilder1.append("");
int v19 = 0;
for(int v20 = 0; v20 < 6; ++v20) {
int[] arr_v = arr2_v[v20];
++v19;
if(v19 > 1) {
stringBuilder1.append("");
}
U.c.g(stringBuilder1, arr_v, c1);
}
stringBuilder1.append("");
k.d("flag{Wu41_P0j13_2026_Spr1ng_F3st1v4l}", "joinTo(StringBuilder(), …ed, transform).toString()");
s1 = "flag{Wu41_P0j13_2026_Spr1ng_F3st1v4l}";
}
matrix = [
[80, 109, 0x77, 0x7B, 77],
[97, 0x74, 34, 45, 105],
[102, 49, 0x7C, 45, 5, 94],
[4, 49, 36, 42, 105],
[101, 0x71, 100, 45, 88, 102, 73],
[0x70, 50, 101, 104, 7, 0x77, 34, 0x70, 75],
]
key = [54, 1, 22, 28]
list_str = ""
for i in range(len(matrix)):
for j in range(len(matrix[i])):
list_str += chr((matrix[i][j] ^ key[j % len(key)]) & 0xFF)
print(list_str)
第四题(Windows简单)
python pyinstxtractor.py app.exe
[+] Processing app.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.14
[+] Length of package: 8875932 bytes
[+] Found 64 files in CArchive
[+] Beginning extraction...please standby
[+] Possible entry point: pyiboot01_bootstrap.pyc
[+] Possible entry point: pyi_rth_inspect.pyc
[+] Possible entry point: crackme_easy.pyc
[+] Found 130 files in PYZ archive
[+] Successfully extracted pyinstaller archive: app.exe
You can now use a python decompiler on the pyc files within the extracted directory
import dis, marshal
with open("crackme_easy.pyc", "rb") as f:
f.read(16) # 跳过 pyc 文件头
code_obj = marshal.load(f)
dis.dis(code_obj)
python py2dis.py > dis.txt
- 基本上代码已经可以看懂个七七八八,直接丢给AI去翻译吧,不费那脑子,AI给译出的结果如下
import hashlib
import base64
import sys
def xor_decrypt(data, key):
result = bytearray()
for i, byte in enumerate(data):
result.append(byte ^ key ^ (i & 255))
return result.decode('utf-8', errors='ignore')
def get_encrypted_flag():
enc_data = 'e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O'
return base64.b64decode(enc_data)
def generate_flag():
encrypted = get_encrypted_flag()
key = 78
result = bytearray()
for i, byte in enumerate(encrypted):
result.append(byte ^ key)
return result.decode('utf-8')
def calculate_checksum(s):
total = 0
for i, c in enumerate(s):
total += ord(c) * (i + 1)
return total
def hash_string(s):
return hashlib.sha256(s.encode()).hexdigest()
def verify_flag(user_input):
correct_flag = generate_flag()
if len(user_input) != len(correct_flag):
return False
for i in range(len(correct_flag)):
if user_input[i] != correct_flag[i]:
return False
return True
def fake_check_1(user_input):
fake_hash = 'a1b2c3d4e5f67890abcdef1234567890abcdef1234567890abcdef1234567890'
return hash_string(user_input) == fake_hash
def fake_check_2(user_input):
fake_hash = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
return hash_string(user_input) == fake_hash
def main():
print('==================================================')
print(' CrackMe Challenge - Python Edition')
print('==================================================')
print('Keywords: 52pojie, 2026, Happy New Year')
print('Hint: Decompile me if you can!')
print('--------------------------------------------------')
user_input = input('\n[?] Enter the password: ').strip()
if fake_check_1(user_input):
print('\n[!] Nice try, but not quite right...')
input('\nPress Enter to exit...')
return
if fake_check_2(user_input):
print("\n[!] You're getting closer...")
input('\nPress Enter to exit...')
return
if verify_flag(user_input):
checksum = calculate_checksum(user_input)
expected_checksum = calculate_checksum(generate_flag())
if checksum == expected_checksum:
print('\n==================================================')
print(' *** SUCCESS! ***')
print('==================================================')
print('[+] Congratulations! You cracked it!')
print('[+] Correct flag:', user_input)
else:
print('\n[!] Checksum failed!')
else:
print('\n[X] Access Denied!')
print('[X] Wrong password. Keep trying!')
input('\nPress Enter to exit...')
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('\n\n[!] Interrupted by user')
sys.exit(0)
import base64
buf = "e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O"
data = base64.decodebytes(buf.encode())
list_str = "".join(chr(data[i] ^ 78) for i in range(len(data)))
print(list_str)
第五题(Window中级)
Packer: Nuitka [OneFile]
- 论坛上发现了一个大佬的贴子,帮了我很大的忙,整个解题过程也是完全参照这个来进行的
https://www.52pojie.cn/thread-2063208-1-1.html
https://j1nxem-o.github.io/2026/02/01/python%E9%80%86%E5%90%91%E4%B9%8BNuitka/
git clone https://github.com/extremecoders-re/nuitka-extractor
# 将nuitka-extractor.exe放到与app.exe相同目录下
./nuitka-extractor.exe app.exe
-
在解包的文件找到crackme_hard.dll文件
-
放入IDA分析那部分,跟大佬的也是相同的,此处就略掉了
-
然用restuner提取资源文件RC数据,里面只有一个文件,提取出来重命名为main.bin
-
下载Nuitka开源代码,以便修复解析环境
git clone https://github.com/Nuitka/Nuitka.git
# 需要用参考的代码位置
# nuitka/build/static_src/HelperConstantsBlob.c
* 字节码的提取参照大佬的代码,在此基础上参考Nuitka源码进行解析的修复,缺啥补啥就行
```python
import io
import struct
def read_u8(b): return struct.unpack("<B", b.read(1))[0]
def read_u32(b): return struct.unpack("<I", b.read(4))[0]
def read_varint(b):
shift = 0
r = 0
while True:
c = read_u8(b)
r |= (c & 0x7F) << shift
if c < 0x80:
return r
shift += 7
def read_cstring(b):
s = b""
while True:
c = b.read(1)
if not c or c == b"\x00":
break
s += c
return s
def decode(b):
t = chr(read_u8(b))
# --- ignore filler ---
if t == ".":
return None
# --- strings ---
if t in ("a", "u"):
s = read_cstring(b)
return s.decode("utf-8", "ignore")
if t == "w":
return b.read(1).decode("utf-8", "ignore")
if t == "s":
return read_cstring(b).decode("utf-8", "ignore")
if t == "v":
n = read_varint(b)
return b.read(n).decode("utf-8", "ignore")
# --- bytes ---
if t == "b":
n = read_varint(b)
return b.read(n)
if t == "c":
return read_cstring(b)
if t == "d":
return b.read(1)
# --- numbers ---
if t in ("l", "q"):
v = read_varint(b)
return v if t == "l" else -v
if t in ("t", "F", "n"):
return {
"t": True,
"F": False,
"n": None
}[t]
if t in ("g", "G"):
len = read_varint(b)
data = b.read(len)
return int.from_bytes(data, byteorder='little', signed=True)
# --- tuple ---
if t == "T":
n = read_varint(b)
return tuple(decode(b) for _ in range(n))
# --- list ---
if t == "L":
n = read_varint(b)
return [decode(b) for _ in range(n)]
# --- dict ---
if t == "D":
n = read_varint(b)
d = {}
for _ in range(n):
k = decode(b)
v = decode(b)
d[k] = v
return d
# --- set / frozenset ---
if t in ("P", "S"):
n = read_varint(b)
items = [decode(b) for _ in range(n)]
return set(items) if t == "P" else frozenset(items)
# Slice object
if t == ":":
start = read_varint(b)
stop = read_varint(b)
step = read_varint(b)
return slice(start,stop,step)
raise ValueError(f"Unhandled type {t!r}")
def main():
with open("main.bin", "rb") as f:
data = f.read()
b = io.BytesIO(data)
magic = read_u32(b)
size = read_u32(b)
print(f"magic={hex(magic)} size={size}")
while b.tell() < size:
name = read_cstring(b).decode()
blob_size = read_u32(b)
count = struct.unpack("<H", b.read(2))[0]
print(f"[+] blob {name} count={count}")
if name == "__main__":
for i in range(count):
obj = decode(b)
print(f"{i}: {obj}")
break
else:
b.seek(b.tell() + blob_size - 2)
if __name__ == "__main__":
main()
magic=0xdcaa9d4e size=5635498
[+] blob .bytecode count=323
[+] blob count=107
[+] blob __main__ count=83
0: [b'dc!a;`b', b'\x11', b'cacg', b'/', b'\x19e!!(', b'\x0e', b'\x1fb&', b'\x0e', b'\x08be#', b'ppp']
1: _parts
2: 81
3: _key
4: 30
5: _total_len
6: 解密单个字符
7: current
8: _decrypt_char
9: 获取指定位置的字符
10: self
11: _get_char_at_position
12: 验证用户输入
13: total
14: 计算校验和
15: aflag
16: checksum
17: 获取目标校验和
18: hashlib
19: sha256
20: encode
21: hexdigest
22: slice(110, 108, 8)
23: None
24: 16
25: 哈希函数
26: 305419896
27: -32511
parts = [b'dc!a;`b', b'\x11', b'cacg', b'/', b'\x19e!!(', b'\x0e', b'\x1fb&', b'\x0e', b'\x08be#', b'ppp']
key = 81
full_bytes = b''.join(parts)
list_str = "".join([chr(full_bytes[i]^key) for i in range(len(full_bytes))])
print(list_str)
第六题(翻外篇)
Language: Lua
Archive: Zip (2.0) [1 file]
[drwxrwxrwx] .
├── [drwxrwxrwx] assets
│ ├── [drwxrwxrwx] cat
│ │ ├── [drwxrwxrwx] bottom_left
│ │ │ ├── [-rwxrwxrwx] 1.png
│ │ │ ├── [-rwxrwxrwx] 2.png
│ │ │ ├── [-rwxrwxrwx] 3.png
│ │ │ ├── [-rwxrwxrwx] 4.png
│ │ │ └── [-rwxrwxrwx] 5.png
│ │ ├── [drwxrwxrwx] left
│ │ │ ├── [-rwxrwxrwx] 1.png
│ │ │ ├── [-rwxrwxrwx] 2.png
│ │ │ ├── [-rwxrwxrwx] 3.png
│ │ │ ├── [-rwxrwxrwx] 4.png
│ │ │ └── [-rwxrwxrwx] 5.png
│ │ └── [drwxrwxrwx] top_left
│ │ ├── [-rwxrwxrwx] 1.png
│ │ ├── [-rwxrwxrwx] 2.png
│ │ ├── [-rwxrwxrwx] 3.png
│ │ ├── [-rwxrwxrwx] 4.png
│ │ └── [-rwxrwxrwx] 5.png
│ └── [-rwxrwxrwx] flag.dat
├── [-rwxrwxrwx] conf.lua
└── [-rwxrwxrwx] main.lua
local function getWinMessage()
local content = nil
if love.filesystem.getInfo("assets/flag.dat") then
content = love.filesystem.read("assets/flag.dat")
end
if not content or currentDifficulty ~= "hard" then
return "You WIN!"
end
local key = "52pojie"
local keyLen = #key
local result = {}
local bit = require("bit")
for i = 1, #content do
local b = string.byte(content, i)
local k = string.byte(key, ((i - 1) % keyLen) + 1)
table.insert(result, string.char(bit.bxor(b, k)))
end
return table.concat(result)
end
with open("flag.dat", "rb") as f:
encdata = f.read()
key = b"52pojie"
flag = "".join(
[chr(encdata[i] ^ key[i % len(key)]) for i in range(len(encdata))]
)
print(flag)
第七题(Windows中级)
- 先看一下壳,是UPX,支持自动脱壳,直接用现成的工具进行脱壳即可
upx -d CM1.exe
INT_PTR __fastcall DialogFunc(HWND hwnd, int a2, __int16 a3)
{
INT_PTR result; // rax
FILE *fp_in; // rsi
FILE *fp_out; // rdi
int isFalse; // r12d
HMODULE ModuleHandleA; // rax
HICON IconA; // rsi
HDC DC; // rsi
int DeviceCaps; // ebp
HFONT FontW; // rsi
HWND DlgItem; // rax
HWND v14; // rax
HWND v15; // rax
HWND v16; // rax
CHAR input_flag[80]; // [rsp+70h] [rbp-328h] BYREF
WCHAR Text[80]; // [rsp+C0h] [rbp-2D8h] BYREF
WCHAR String[284]; // [rsp+160h] [rbp-238h] BYREF
if ( a2 == 272 )
{
ModuleHandleA = GetModuleHandleA(nullptr);
IconA = LoadIconA(ModuleHandleA, (LPCSTR)0x14);
SendMessageA(hwnd, 0x80u, 1u, (LPARAM)IconA);
SendMessageA(hwnd, 0x80u, 0, (LPARAM)IconA);
DC = GetDC(hwnd);
DeviceCaps = GetDeviceCaps(DC, 88);
ReleaseDC(hwnd, DC);
FontW = CreateFontW((int)((double)DeviceCaps / 96.0 * 20.0), 0, 0, 0, 400, 0, 0, 0, 0, 7u, 0, 5u, 0x31u, "C");
DlgItem = GetDlgItem(hwnd, 2);
SendMessageW(DlgItem, 0x30u, (WPARAM)FontW, 1);
v14 = GetDlgItem(hwnd, 3);
SendMessageW(v14, 0x30u, (WPARAM)FontW, 1);
v15 = GetDlgItem(hwnd, 4);
SendMessageW(v15, 0x30u, (WPARAM)FontW, 1);
v16 = GetDlgItem(hwnd, 12);
sub_7FF6DAA37F40(v16, (DeviceCaps > 191) + 30);
SetDlgItemTextA(hwnd, 2, "flag.png.encrypted");
SetDlgItemTextA(hwnd, 3, "flag.png");
SetDlgItemTextA(hwnd, 4, "flag{HEX_ME}");
}
else if ( a2 == 273 )
{
if ( a3 == 10 )
{
MessageBoxW(
hwnd,
L"吾爱破解 2026 春节解题领红包之 HEX_ME by 爱飞的猫\n"
"\n"
"本故事纯属虚构。文中及程序中出现的公司、人物、事件和情节均为艺术创作,与任何真实的公司、人物、事件或情节无关。如有雷同,纯属巧合。",
L"关于",
0x40u);
}
else if ( a3 == 11 )
{
memset(input_flag, 0, sizeof(input_flag));
memset(String, 0, 0x200u);
GetDlgItemTextW(hwnd, 2, String, 255);
fp_in = _wfopen(String, "r");
if ( fp_in )
{
GetDlgItemTextW(hwnd, 3, String, 255);
fp_out = _wfopen(String, L"wb");
if ( fp_out )
{
GetDlgItemTextA(hwnd, 4, input_flag, 0x4F);
// 此处是关键函数,只有返回0时才能显示成功
isFalse = check_flag(input_flag, fp_in, fp_out);
fclose(fp_in);
fclose(fp_out);
memset(Text, 0, sizeof(Text));
sub_7FF6DAA38D70((__int64)Text);
if ( isFalse )
MessageBoxW(hwnd, Text, L"错误", 0x10u);
else
MessageBoxW(hwnd, Text, L"成功", 0x40u);
}
else
{
fclose(fp_in);
MessageBoxW(hwnd, &word_7FF6DAA3A0B0, L"错误", 0x10u);
}
}
else
{
MessageBoxW(hwnd, &word_7FF6DAA3A080, L"错误", 0x10u);
}
}
}
else
{
result = 0;
if ( a2 != 16 )
return result;
EndDialog(hwnd, 0);
}
return 1;
}
// 返回0时,提示正确的flag
__int64 __fastcall check_flag(char *input_flag, FILE *in_file, FILE *out_file)
{
size_t flag_len; // rax
__int64 check_num; // rdi
char v8; // dl
__int64 result; // rax
__int64 v10; // rdi
unsigned __int64 file_len; // r13
DWORD *v12; // rax
DWORD *file_stream; // r12
__int64 v14; // rdx
unsigned int v15; // r8d
unsigned __int64 v16; // rax
DWORD file_head[4]; // [rsp+20h] [rbp-858h] BYREF
_DWORD buf[530]; // [rsp+30h] [rbp-848h] BYREF
init(buf);// 初始化crc64的参数
sub_7FF6DAA38500(buf, "52pojie_2026_", 14);// 先计算一次crc64
flag_len = strlen(input_flag); // 最大长度是80
sub_7FF6DAA38500(buf, input_flag, flag_len); // 再用输入的flag计算一次
check_num = crc64(buf); // 获取最终的crc64
fread(file_head, 16u, 1u, in_file);
v8 = sub_7FF6DAA38310(buf, check_num, file_head);// v8=1时才是正确的key
// 函数的功能是将文件头写到buf中
result = 1;
if ( v8 )
{
fseek(in_file, 0, 2);
v10 = ftell(in_file);
result = 2;
file_len = v10 - 16;
if ( (v10 & 7) == 0 )
{
fseek(in_file, 16, 0);
v12 = (DWORD *)malloc(v10 - 16);
file_stream = v12;
if ( v12 )
{
fread(v12, 1u, v10 - 16, in_file);
crc32_update(buf, file_stream, v10 - 16);// 将解密后的文件进行crc32校验
// 最后的crc32值写入了buf[72]中
if ( (unsigned __int8)check_buf(buf, v14, v15) )// 需要返回值为1
// buf[73]=0x8D8445A2
// 条件是~buf[72]==buf[73]
// buf[72]需要= 0x727bba5d 才是正确的逻辑
{
v16 = *((unsigned __int8 *)file_stream + v10 - 17);
if ( file_len < v16 )
{
free(file_stream);
return 5;
}
else
{
fwrite(file_stream, 1u, file_len - v16, out_file);
free(file_stream);
return 0; // 此处是正确的出口
}
}
else
{
free(file_stream);
return 4;
}
}
else
{
return 3;
}
}
}
return result;
}
-
init函数是初始化初值
// 与CRC64的计算比较接近
void __fastcall sub_7FF6DAA38500(_DWORD *buf, _DWORD *input, __int64 len)
{
__int64 v3; // rax
_DWORD *v4; // r11
char *v6; // r8
_DWORD *v7; // r8
int v8; // r10d
int v9; // ecx
v3 = *(_QWORD *)buf;
v4 = buf - 12672;
v6 = (char *)input + len - 46851;
if ( (char *)input - 46851 < v6 )
{
v7 = v6 + 46851; // v7 = input+len
v8 = buf[514] + 1 - (_DWORD)input;
do
{
v3 = *(_QWORD *)&v4[2 * (unsigned __int8)(*(_BYTE *)input ^ v3) + 12674] ^ ((unsigned __int64)v3 >> 8);
v9 = v8 + (_DWORD)input;
input = (_DWORD *)((char *)input + 1);
buf[514] = v9;
}
while ( input != v7 );
}
*(_QWORD *)buf = v3;
}
// 与前面的函数功能差不太多
__int64 __fastcall crc64(_DWORD *buf)
{
__int64 v1; // rax
unsigned __int64 v2; // rbx
int v3; // r8d
int v4; // edx
v1 = 0;
v2 = *(_QWORD *)buf;
v3 = buf[514] + 1; // buf[514]记录的是下一个地址
do
{
v2 = *(_QWORD *)&buf[2 * (unsigned __int8)(*((_BYTE *)buf + v1 + 2056) ^ v2) + 2] ^ (v2 >> 8);
v4 = v3 + v1++;
buf[514] = v4;
}
while ( v1 != 4 );
memset(buf, 0, 0x810u);
return ~v2;
}
-
下面的函数是解密文件前,初始化状态信息和加密key
// 复制文件头中的关键信息
void *__fastcall sub_7FF7C15D8360(_DWORD *buf, __int64 num, _DWORD *head_2)
{
__int64 v3; // rax
v3 = 0;
*(_QWORD *)buf = num;
do
{
*((_BYTE *)buf + v3 + 16) = *((_BYTE *)head_2 + v3);
++v3;
}
while ( v3 != 16 );
// 此处将S-BOX表,复制到buf的第32字节偏移处
return memcpy(buf + 8, byte_7FF6DAA3A270, 256u);
}
// 初始化buf表中的数据
__int64 __fastcall sub_7FF6DAA38310(_DWORD *buf, __int64 check_num, DWORD *file_head)
{
__int64 result; // rax
DWORD v6; // eax
result = 0;
if ( file_head )
{
if ( *file_head == '62MC' )
{
sub_7FF6DAA38360(buf, check_num, file_head + 2);
v6 = file_head[1];
buf[72] = -1;
buf[73] = v6;
return 1;
}
}
return result;
}
-
下面的函数是解密文件,并计算crc32的值
// 非线性数据混合与解密
char __fastcall mix_dword(char *ctx, DWORD *filebuf)
{
int v2; // r8d
__int64 v3; // r11
__int64 v5; // rax
char *pTable; // rcx
unsigned __int64 v7; // rax
char *pFbuf; // rdx
char v9; // al
char result; // al
v2 = 8; // 总计转8次
v3 = *(_QWORD *)filebuf; // 保存上一次的内容
v5 = *(_QWORD *)ctx;
pTable = ctx - 21569;
v7 = __ROL8__(v5, 3); // 操作的是buf的前8字节
do
{
v7 = (v7 << 8) | (unsigned __int8)ctx[(HIBYTE(v7) | 2233088) - 2233056];// 地址偏移
// 2233088-2233056=32
// 与s_box混合,实现非线性随机
--v2;
}
while ( v2 );
*(_QWORD *)ctx = v7; // 写回第0个字节
// buf=v7
pFbuf = (char *)(filebuf + 9422); // 地址偏移
// 9422*4 = 37688
do
{
v9 = *(pFbuf - 37688); // v9=filebuf + index
++pTable;
++pFbuf; // index++
result = pTable[21568] ^ pTable[21584] ^ v9;// 地址偏移
// 21584-21569+1 = 16
// buf[index-1]^buf[index-1+16]^filebuf
*(pFbuf - 37689) = result; // filebuf[index-1] = result
}
while ( pTable != ctx - 21561 ); // 8字节
*((_QWORD *)ctx + 2) = v3; // buf第16字节需要在此处更新
// 下一轮中将会用到这个数值
return result;
}
// 更新加crc32数值
__int64 __fastcall crc32_update(_DWORD *buf, DWORD *filebuf, __int64 len)
{
char *v3; // rbx
char *buf_len; // rdi
_DWORD *key; // r12
__int64 result; // rax
v3 = (char *)filebuf - 24627;
buf_len = (char *)filebuf + (len & 0xFFFFFFFFFFFFFFF8uLL) - 24627;// len是文件长度,定值
if ( (char *)filebuf - 24627 < buf_len )
{
key = buf + 72;
do
{
v3 += 8;
mix_qword(buf, (DWORD *)(v3 + 24619)); // 每8个字节做一次mix
result = crc32(key, (unsigned __int64)(v3 + 24619), 8);// 8字节filebuf校验
}
while ( v3 < buf_len );
}
return result;
}
-
主逻辑函数我注释的比较清晰,总结一下就是,先经过一个类似CRC64的操作将输入的flag转成一个(check_num)8字节数值
-
再用这个check_num值参与解密图片文件,计算解密后数据的crc32,如果输入的flag正确,则校验成功
-
分析到这的时候,我遇到一个问是,别说现在不知道check_num, 就算知道了check_num,也无法逆推flag
-
难道需要暴力破解?这可是8字节,跑到正月十五也不可能找到flag,但是有人分分钟就找到了答案,这是啥脑子呢
-
后面的CRC32也不可逆,两头都被毒死了,真的让人无路可走啊
-
于是乎换了个思路,我何不把图片也给解密了,万一里面有料呢,万一呢
-
仔细分析了一下上面的那个mix_qword函数,发现这个函数是可逆的,我们只要知道原图片中的前8字节就可以逆推出,check_num
-
然后就可以用这个check_num把图片也搞出来吧
-
百度了一下png图片的标准头,前8个字节就是一个固定的值
PNG_HEADER = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
- 顺着这个思路,我们可以写一个反推check_num的函数
def reverse_key(last_key):
invS_Box = [0] * len(S_BOX)
for i in range(len(S_BOX)):
invS_Box[S_BOX[i]] = i
key = last_key
for _ in range(8):
low = key & 0xFF
hi = invS_Box[low]
key = (key >> 8) | ((hi << 56) & 0xFFFFFFFFFFFFFFFF)
return ror64(key, 3)
def inverse():
pre_mix = PNG_HEADER[0:8]
cur_mix = FILE_DATA[8:16]
nex_mix = FILE_DATA[16:24]
cur_key = [pre_mix[i] ^ cur_mix[i] ^ nex_mix[i] for i in range(8)]
pre_key = bytes_to_uint64_le(cur_key)
return reverse_key(pre_key)
- 然后通过这个check_num理论上就应该把图片恢复
- 然后以文本的方式打开图片,flag果然在图片里,啊哈
- 以下是题解
import re
FILE_DATA = [
0x43, 0x4D, 0x32, 0x36, 0xA2, 0x45, 0x84, 0x8D, 0xF5, 0x69, 0x73, 0x60,
0x01, 0xCB, 0x35, 0xBC, 0xFB, 0xDD, 0x1B, 0x92, 0x2B, 0xEF, 0xE3, 0x23,
0x10, 0xEB, 0x81, 0x4C, 0x45, 0x04, 0x48, 0x95, 0xDF, 0x42, 0xBF, 0x52,
0xF7, 0xD0, 0xB7, 0x79, 0x2D, 0x6E, 0x59, 0xF3, 0x67, 0xA8, 0x5A, 0x19,
0x35, 0xBC, 0x9E, 0x85, 0xD4, 0xC4, 0x7E, 0x99, 0x03, 0x7F, 0x43, 0xAD,
0x4A, 0xAF, 0x2F, 0xC5, 0xE4, 0x6C, 0x31, 0x59, 0x32, 0x52, 0x8E, 0x07,
0xCF, 0xC6, 0xDB, 0x6F, 0x90, 0xDB, 0xA6, 0x10, 0xBC, 0x6F, 0x2B, 0x5F,
0x84, 0x3E, 0xF4, 0x3B, 0xF8, 0x49, 0x20, 0x74, 0xDA, 0x1F, 0x2B, 0xEA,
0x0E, 0xC8, 0xB3, 0x6B, 0x06, 0x56, 0x9B, 0x6B, 0xF4, 0x8A, 0x02, 0xB6,
0x8B, 0xEC, 0x26, 0x95, 0x66, 0x24, 0xE1, 0x81, 0x94, 0xD7, 0x5D, 0x56,
0x23, 0xF8, 0x01, 0xC4, 0x59, 0xDF, 0x53, 0xB2, 0xE5, 0x08, 0xAD, 0x21,
0xD8, 0x82, 0x9E, 0xFC, 0x27, 0xCD, 0x91, 0xDB, 0x42, 0x32, 0x13, 0x09,
0xBE, 0xD3, 0xB7, 0xFD, 0x02, 0xB8, 0x98, 0x07, 0x96, 0xDE, 0x86, 0x0E,
0x5B, 0x00, 0xB7, 0xE6, 0xD5, 0xB3, 0xE5, 0x47, 0x13, 0x91, 0x83, 0xC7,
0xA2, 0x4C, 0xDA, 0xCE, 0x8C, 0x92, 0x58, 0x73, 0x6F, 0x21, 0x7D, 0x92,
0x1E, 0x1D, 0x3A, 0x75, 0x70, 0x3C, 0x82, 0xFE, 0xB1, 0x79, 0xF7, 0x18,
0x4C, 0x33, 0x1A, 0x50, 0x93, 0x22, 0xFF, 0x74, 0xC7, 0x67, 0xA4, 0x84,
0x35, 0xB9, 0x93, 0x94, 0xD1, 0x36, 0xFA, 0xB5, 0x57, 0xE5, 0x0F, 0x4B,
0x70, 0xF2, 0x20, 0x61, 0x0A, 0x41, 0xB9, 0xBC, 0xC3, 0x70, 0xF7, 0x10,
0x47, 0xA3, 0x70, 0x5E, 0x70, 0x57, 0xB3, 0x4E, 0x48, 0x26, 0xA2, 0xCE,
0xC0, 0x13, 0xB2, 0x22, 0x58, 0x95, 0x32, 0x8A, 0xD2, 0xA6, 0xB8, 0xEF,
0x7D, 0x2D, 0xE5, 0x5A, 0xE4, 0xF0, 0x0C, 0x4D, 0xF0, 0x57, 0xD6, 0x93,
0x37, 0xB3, 0x16, 0xDB, 0x71, 0x57, 0x29, 0xFD, 0xEB, 0xF7, 0x7E, 0x05,
0x3A, 0xD7, 0x6B, 0x13, 0xCB, 0xC1, 0x14, 0xFC, 0xB1, 0x53, 0x64, 0x25,
0x0A, 0xBF, 0xA5, 0x32, 0xCD, 0xBB, 0x53, 0x92, 0x38, 0x10, 0xD2, 0x8B,
0xE4, 0x54, 0xFA, 0x56, 0x37, 0xF1, 0xDB, 0x1A, 0xF2, 0x31, 0x40, 0x74,
0x91, 0xBF, 0x8F, 0xA1, 0xC4, 0x93, 0xBE, 0x7D, 0x00, 0x49, 0x30, 0xF2,
0x91, 0x62, 0xEE, 0x78, 0xD3, 0xAB, 0x95, 0x56, 0xA6, 0xAD, 0x65, 0x32,
0x17, 0x4A, 0x66, 0x5E, 0xBF, 0xCD, 0x9F, 0x3D, 0xFE, 0xE1, 0xFB, 0xBE,
0xEE, 0x3D, 0xFC, 0xEE, 0x0D, 0xA3, 0x2E, 0xB9, 0x63, 0xD1, 0x24, 0x9B,
0x97, 0x7B, 0x64, 0x49, 0x46, 0x10, 0x55, 0xC6
]
# AES S-Box
S_BOX = [
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B,
0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0,
0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26,
0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2,
0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0,
0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED,
0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F,
0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,
0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC,
0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14,
0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C,
0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D,
0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F,
0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E,
0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11,
0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F,
0xB0, 0x54, 0xBB, 0x16
]
# 标准PNG文件头
PNG_HEADER = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
def bytes_to_uint64_le(array):
return int.from_bytes(array, byteorder="little")
def uint64_to_bytes_le(value):
value &= 0xFFFFFFFFFFFFFFFF
array = value.to_bytes(8, byteorder="little", signed=False)
return array
def rol64(value, shift):
return ((value << shift) & 0xFFFFFFFFFFFFFFFF) | (value >> (64 - shift))
def ror64(value, shift):
return (value >> shift) | ((value << (64 - shift)) & 0xFFFFFFFFFFFFFFFF)
# 反推key
def reverse_key(last_key):
invS_Box = [0] * len(S_BOX)
for i in range(len(S_BOX)):
invS_Box[S_BOX[i]] = i
key = last_key
for _ in range(8):
low = key & 0xFF
hi = invS_Box[low]
key = (key >> 8) | ((hi << 56) & 0xFFFFFFFFFFFFFFFF)
return ror64(key, 3)
# 数据非线性混合
def mix_qword(buf, mixdata, offset):
v5 = bytes_to_uint64_le(buf[0:8])
v7 = rol64(v5, 3)
for _ in range(8):
hi = (v7 >> 56) & 0xFF
low = S_BOX[hi]
v7 = ((v7 << 8) & 0xFFFFFFFFFFFFFFFF) | low
buf[0:8] = uint64_to_bytes_le(v7)
tmp = mixdata[offset : offset + 8]
for i in range(8):
mixdata[offset + i] = buf[i] ^ buf[i + 16] ^ mixdata[offset + i]
buf[16:24] = tmp
# 逆向求解
def inverse():
pre_mix = PNG_HEADER[0:8]
cur_mix = FILE_DATA[8:16]
nex_mix = FILE_DATA[16:24]
cur_key = [pre_mix[i] ^ cur_mix[i] ^ nex_mix[i] for i in range(8)]
pre_key = bytes_to_uint64_le(cur_key)
return reverse_key(pre_key)
# 解密整个文件,并提取flag
def decrypt_file(key):
buf = [0] * 32
buf[0:8] = list(uint64_to_bytes_le(key))
buf[16:24] = FILE_DATA[8:16]
filelen = len(FILE_DATA) - 16
filelen &= 0xFFF8
filedata = FILE_DATA[16 : 16 + filelen]
for i in range(0, filelen, 8):
mix_qword(buf, filedata, i)
with open("flag_decrypted.png", "wb") as f:
f.write(bytes(filedata))
str = bytes(filedata).decode("utf-8", errors="ignore")
result = re.findall(r"flag{\S+?}", str)
print(result)
def main():
check_num = inverse()
print("checknum=" + hex(check_num))
decrypt_file(check_num)
if __name__ == "__main__":
main()
- 最后展示一下王铁锤的logo,奇怪好像在哪里见过呢

第九题(Web中级)
- “如呼吸一般轻松,是产品的设计目标。”,这才是本题的精华
// 如此之校验,让我不能呼吸,心疼我电脑
async function checkCode(code, expectedHash) {
const enc = new TextEncoder()
let current = enc.encode(code)
for (let i = 0; i < 0x2026; i++) {
current = await crypto.subtle.digest('SHA-256', current)
}
const hashArray = Array.from(new Uint8Array(current))
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
return hashHex === expectedHash
}
checkboxText.addEventListener('click', async () => {
const uidInput = document.getElementById('uid')
if (!uidInput.value) {
uidInput.focus()
return
}
const uid = parseInt(uidInput.value) || 0
const voice = document.getElementById('voice').value
try {
const challenge = wasm_bindgen.gen(uid, voice) // 此函数是产生语音的地方
currentHash = challenge.h
audio.src = URL.createObjectURL(new Blob([challenge.a], { type: 'audio/wav' }))
challengeView.style.display = 'block'
checkboxText.classList.remove('btn-important')
document.getElementById('verifyBtn').classList.add('btn-important')
while (n = w.nextNode()) n.data.includes`)` && (n.remove(), i = 0x2026)
audio.play().catch(e => console.warn("Auto-play blocked:", e))
document.getElementById('verifyInput').focus()
checkboxText.innerText = "重新生成语音验证码"
} catch (e) {
console.error(e)
}
})
- 继续跟进,会跟到wasm中的gen函数,函数非常的长,这里就不贴了
- 核心的流程大概是把uid与一些特定的随机数,然后进行HMAC计算hash,最后再进行base64产生字符
- base64码表在内存中的地址是固定的1295903,可以在内存中直接查看a-zA-Z0-9?!
- 最后算出来的这个base64值就是语音播报的内容,后面应该是合成语言的过程,没有分析
- 所以,只需要将这个base64的值给dump出来就可以了
i32.load8_u offset=1295903 // 这个值是base64码表的位置
local.set $var8
local.get $var3
i32.load offset=416
local.get $var0
i32.eq
if
local.get $var3
i32.const 416
i32.add
call $func39
end
local.get $var3 //var3中保存了内存的基址,抓包发现是定值1047920
i32.load offset=420 //这个是地址偏移量
local.get $var6 // var6是索引后的偏移,地址的计算法方是[[var3+420] + var6]
i32.add
local.get $var8 //此处是查完了base64表的值
i32.store // 此处保存回内存
local.get $var3
local.get $var0
i32.const 1
i32.add
local.tee $var0
i32.store offset=424
local.get $var2
i32.const 6
local.get $var0
i32.const 1
i32.add
local.tee $var0
i32.store offset=424
br $label15
end
i32.const 4
i32.const 200
call $func65
unreachable
end
i32.const 1
i32.const 37
call $func65
unreachable
end $label1
local.get $var3 // 在此处下断比较合适,这地方刚好计算完50个字符
i32.load offset=420
local.set $var6
local.get $var0
if (result i32)
- 计算内存偏移的核心部分,用AI给翻译了一下,这块注意一下,不要给AI太多的数据,要不AI会直接崩溃
// 这是一个代码块,用于错误处理或提前退出
{
// 分配内存并检查分配结果
ptr = allocate(37, 1);
if (ptr != NULL) {
// 第一部分:数据准备和初始化
// 对var0进行字节拆分和异或操作
// 从var3+83到var3+80读取4个字节,分别与var0的4个字节异或
byte0 = var0 ^ memory[var3+80];
byte1 = (var0 >> 8) ^ memory[var3+81];
byte2 = (var0 >> 16) ^ memory[var3+82];
byte3 = (var0 >> 24) ^ memory[var3+83];
// 将结果存入分配的内存
ptr[0] = byte0;
ptr[1] = byte1;
ptr[2] = byte2;
ptr[3] = byte3;
// 复制var3+80处的数据到ptr+4
memcpy(ptr+4, var3+80, 8); // 复制8字节
memcpy(ptr+12, var3+88, 8); // 复制8字节
ptr[20] = memory[var3+96]; // 复制1字节
// 将异或结果存回var3+100到var3+103
memory[var3+100] = byte3;
memory[var3+101] = byte2;
memory[var3+102] = byte1;
memory[var3+103] = byte0;
// 分配栈空间
stack = malloc(352);
// 初始化栈空间为0
memset(stack, 0, 352);
// 从内存地址1295967复制14字节到栈空间
memcpy(stack, 1295967, 14);
// 将栈空间的数据复制到var3+416处
memcpy(var3+416, stack, 64);
// 释放栈空间
free(stack);
// 对var3+416开始的64字节进行异或操作(常数54)
for(i=0; i<64; i+=4) {
memory[var3+416+i] ^= 54;
memory[var3+416+i+1] ^= 54;
memory[var3+416+i+2] ^= 54;
memory[var3+416+i+3] ^= 54;
}
// 初始化哈希状态和调用哈希函数
memcpy(var3+480, 1295984, 32); // 复制32字节
var3+512 = 1; // 设置计数器
hash_function(var3+480, var3+416, 1); // 调用哈希函数
// 再次对var3+416开始的64字节进行异或操作(常数106)
for(i=0; i<64; i+=4) {
memory[var3+416+i] ^= 106;
memory[var3+416+i+1] ^= 106;
memory[var3+416+i+2] ^= 106;
memory[var3+416+i+3] ^= 106;
}
// 初始化另一个哈希状态并调用哈希函数
memcpy(var3+520, 1295984, 32); // 复制32字节
var3+552 = 1; // 设置计数器
hash_function(var3+520, var3+416, 1); // 调用哈希函数
// 复制哈希结果
memcpy(var3+560, var3+480, 80); // 复制80字节
memcpy(var3+600, var3+520, 80); // 复制80字节
// 从var3+560复制152字节到var3+256
memcpy(var3+256, var3+560, 152);
// 初始化var3+336处的65字节为0
memset(var3+336, 0, 65);
// 从var3+256复制152字节到var3+104
memcpy(var3+104, var3+256, 152);
// 处理数据块
offset = memory[var3+248]; // 获取当前偏移
if(offset >= 43) {
// 如果偏移>=43,处理剩余数据
remaining = 64 - offset;
if(remaining != 0) {
memcpy(var3+184+offset, ptr, remaining);
}
var3+136 += 1; // 增加计数器
hash_function(var3+104, var3+184, 1); // 调用哈希函数
if(offset != 43) {
memcpy(var3+184, ptr+remaining, offset-43);
}
new_offset = 0;
} else {
// 如果偏移<43,直接复制数据
memcpy(var3+184+offset, ptr, 21);
new_offset = offset + 21;
}
// 更新偏移
memory[var3+248] = new_offset;
// 从var3+104复制152字节到var3+256
memcpy(var3+256, var3+104, 152);
// 在缓冲区的适当位置添加0x80
buffer_offset = memory[var3+400];
buffer[var3+336+buffer_offset] = 0x80;
// 计算消息长度并进行字节序转换
length = buffer_offset * 8; // 转换为比特数
// 进行复杂的字节序转换操作
length_bytes = convert_to_big_endian_64(length);
// 处理填充
if(buffer_offset != 63) {
// 如果缓冲区不满,填充0
fill_count = 63 - buffer_offset;
if(fill_count != 0) {
memset(var3+336+buffer_offset+1, 0, fill_count);
}
if(buffer_offset <= 56) {
// 如果长度可以放在当前块
hash_function(var3+256, var3+336, 1);
// 准备最终块
memset(var3+560, 0, 80);
var3+616 = length_bytes; // 存储长度
hash_function(var3+256, var3+560, 1);
} else {
// 如果长度需要下一个块
var3+392 = length_bytes; // 存储长度
hash_function(var3+256, var3+336, 1);
}
} else {
// 缓冲区已满的情况
var3+392 = length_bytes; // 存储长度
hash_function(var3+256, var3+336, 1);
}
// 重置缓冲区偏移
memory[var3+400] = 32;
// 对哈希状态进行字节序转换
for(i=0; i<8; i++) {
word = *(int*)(var3+256+i*4);
// 转换为大端序
big_endian_word = ((word << 24) & 0xFF000000) |
((word << 8) & 0x00FF0000) |
((word >> 8) & 0x0000FF00) |
((word >> 24) & 0x000000FF);
*(int*)(var3+336+i*4) = big_endian_word;
}
// 处理最终的哈希块
length = *(long long*)(var3+328) * 8; // 转换为比特数
length_bytes = convert_to_big_endian_64(length | 0x100);
// 准备填充
memset(var3+368, 0, 17);
memory[var3+368] = 0x80;
var3+392 = length_bytes;
// 调用最终的哈希函数
hash_function(var3+296, var3+336, 1);
// 获取哈希结果并转换为大端序
hash0 = *(int*)(var3+296);
hash1 = *(int*)(var3+300);
hash2 = *(int*)(var3+304);
hash3 = *(int*)(var3+308);
// 将结果存回分配的内存
*(int*)(ptr+21) = convert_to_big_endian_32(hash0);
*(int*)(ptr+25) = convert_to_big_endian_32(hash1);
*(int*)(ptr+29) = convert_to_big_endian_32(hash2);
*(int*)(ptr+33) = convert_to_big_endian_32(hash3);
// 第二部分:类似Base64编码
// 分配输出缓冲区
output = allocate(200, 4);
if(output != NULL) {
var3+416 = 50; // 设置块大小
var3+420 = output; // 设置输出指针
var3+424 = 0; // 设置输出索引
input_index = 0;
bit_buffer = 0;
bit_count = 0;
input_ptr = ptr;
// 处理输入数据
for(byte_index=0; byte_index<37; byte_index++) {
// 读取一个字节
byte = input_ptr[byte_index];
// 添加到位缓冲区
bit_buffer = (bit_buffer << 8) | byte;
bit_count += 8;
// 处理完整的6位组
while(bit_count >= 6) {
// 提取6位
index = (bit_buffer >> (bit_count - 6)) & 0x3F;
// 检查是否需要调用函数
if(var3+416 == input_index) {
call_function(var3+416);
}
// 从表中查找并存储
table_value = lookup_table[1295903 + index];
output[input_index] = table_value;
input_index++;
var3+424 = input_index;
bit_count -= 6;
}
}
// 处理剩余的位
if(bit_count > 0) {
// 提取剩余的位
index = (bit_buffer << (6 - bit_count)) & 0x3F;
// 检查是否需要调用函数
if(var3+416 == input_index) {
call_function(var3+416);
}
// 从表中查找并存储
table_value = lookup_table[1295903 + index];
output[input_index] = table_value;
input_index++;
var3+424 = input_index;
}
// 块结束
} else {
// 内存分配失败
error(4, 200);
}
} else {
// 内存分配失败
error(1, 37);
}
}
- 直接查内存就可以定位到flag了,顺便写了个dump的函数
__exports.Dump = function(){
ptr = 1048340;
len = 4;
buffer = getArrayU8FromWasm0(ptr,len);
ptr = 0;
for (let i = 0; i < buffer.length; i++) {
console.log("buffer["+i+"]="+buffer[i]);
ptr = (ptr << 8) | buffer[buffer.length - i - 1];
}
console.log("Address:"+ptr);
strbuf = getStringFromWasm0(ptr,200);
const codestr = strbuf.replace(/[^a-zA-Z0-9?!]/g, '');
console.log("CodeLen:"+codestr.length);
console.log("CheckCode:"+codestr);
}
- 断点断下来后,就可以直接调试窗口中运行此函数,即可得到50字节的flag
- 总之做这道题有点费劲,不太懂JS,完全现学现卖
- 也不知道用啥工具来进行分析,此题全作仍赖AI的翻译,解的题的方法也比较笨,欢迎指点
总结
- 今年中级题比较多,而且题出的也比较有意思,作者们都是有故事的人啊
- 今年开始学习了python,但是不是很熟,题解也尽量都用python开写的,写的不好,多多指点
- 个人来讲中级题免强能做,但是做出来还是比较费劲,由其代码中加入一些混淆以后,对我的干扰还是很大的,不知道有没有大佬讲讲如何在IDA中去混淆,让代码可读性更好一些
- 解第七题的时候,一开始一直想着是不是需要暴力破解,写题解的时候,我才发现,作者有提醒,暴力不可取,哎,作者用心良苦啊,耐何我才看到,呵呵
- 还有那个Android代码的混淆,对于我来说直的是无从下手,目前也搜不到好的方法可以反混淆,不知道有没有大佬能提点一二
- 今年中级题遇到的最大的挑战就是,Android中的反调试,一点都不会破,IDA一附加上去程序就崩,我也泪崩
- 总之,通过跟着论坛的贴子一点一点的积累,从啥也不懂的小白,到今年,自我感觉成绩还不错,明年若有机会,我会继续加油努力
- 最后,希望吾爱论坛越做越好,吾爱百年,百年吾爱!