羊城杯2025_Reverse_Writeup

本文最后更新于 2025年10月13日 上午

羊城杯2025_Reverse_Writeup

今年的ycb如期而至,个人感觉re2和re4的质量比较高,比较喜欢re2的设题方式,浅浅写个wp分享一下。

GD1

文件很大,是Godot引擎的游戏,搜索能解包的工具找到https://github.com/GDRETools/gdsdecomp

使用这个工具解包得到许多资源文件

image-20251012205333786

main.gdc里面有关键逻辑

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
extends Node

@export var mob_scene: PackedScene
var score
var a = "000001101000000001100101000010000011000001100111000010000100000001110000000100100011000100100000000001100111000100010111000001100110000100000101000001110000000010001001000100010100000001000101000100010111000001010011000010010111000010000000000001010000000001000101000010000001000100000110000100010101000100010010000001110101000100000111000001000101000100010100000100000100000001001000000001110110000001111001000001000101000100011001000001010111000010000111000010010000000001010110000001101000000100000001000010000011000100100101"

func _ready():

pass



func _process(delta: float) -> void :
pass


func game_over():
$ScoreTimer.stop()
$MobTimer.stop()
$HUD.show_game_over()

func new_game():
score = 0
$Player.start($StartPosition.position)
$StartTimer.start()
$HUD.update_score(score)
$HUD.show_message("Get Ready")
get_tree().call_group("mobs", "queue_free")

func _on_mob_timer_timeout():

var mob = mob_scene.instantiate()


var mob_spawn_location = $MobPath / MobSpawnLocation
mob_spawn_location.progress_ratio = randf()


var direction = mob_spawn_location.rotation + PI / 2


mob.position = mob_spawn_location.position


direction += randf_range( - PI / 4, PI / 4)
mob.rotation = direction


var velocity = Vector2(randf_range(150.0, 250.0), 0.0)
mob.linear_velocity = velocity.rotated(direction)


add_child(mob)


func _on_score_timer_timeout():
score += 1
$HUD.update_score(score)
if score == 7906:
var result = ""
for i in range(0, a.length(), 12):
var bin_chunk = a.substr(i, 12)
var hundreds = bin_chunk.substr(0, 4).bin_to_int()
var tens = bin_chunk.substr(4, 4).bin_to_int()
var units = bin_chunk.substr(8, 4).bin_to_int()
var ascii_value = hundreds * 100 + tens * 10 + units
result += String.chr(ascii_value)
$HUD.show_message(result)

func _on_start_timer_timeout():
$MobTimer.start()
$ScoreTimer.start()

可以看到达到7906分的时候执行输出flag的逻辑,直接模拟一下这个逻辑即可得到flag,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
a = "000001101000000001100101000010000011000001100111000010000100000001110000000100100011000100100000000001100111000100010111000001100110000100000101000001110000000010001001000100010100000001000101000100010111000001010011000010010111000010000000000001010000000001000101000010000001000100000110000100010101000100010010000001110101000100000111000001000101000100010100000100000100000001001000000001110110000001111001000001000101000100011001000001010111000010000111000010010000000001010110000001101000000100000001000010000011000100100101"

result = ""
for i in range(0, len(a), 12):
chunk = a[i:i+12]
hundreds = int(chunk[:4], 2)
tens = int(chunk[4:8], 2)
units = int(chunk[8:12], 2)
ascii_code = hundreds * 100 + tens * 10 + units
result += chr(ascii_code)

print(result)

得到flag:DASCTF{xCuBiFYr-u5aP2-QjspKk-rh0LO-w9WZ8DeS}

PLUS

给了plus.py和init.pyd

plus.py里面是拆分的int exit和exec结构,猜测需要合并

import一下init并help寻找到有效信息

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
>>> help(init)
Help on module init:

NAME
init

FUNCTIONS
a2b_hex(hexstr, /)
Binary data of hexadecimal representation.

hexstr must contain an even number of hex digits (upper or lower case).
This function is also available as "unhexlify()".

exec(x)

exit = eval(source, globals=None, locals=None, /)
Evaluate the given source in the context of globals and locals.

The source may be a string representing a Python expression
or a code object as returned by compile().
The globals must be a dictionary and locals can be any mapping,
defaulting to the current globals and locals.
If only globals is given, locals defaults to it.

i = input(prompt=None, /)
Read a string from standard input. The trailing newline is stripped.

The prompt string, if given, is printed to standard output without a
trailing newline before reading input.

If the user hits EOF (*nix: Ctrl-D, Windows: Ctrl-Z+Return), raise EOFError.
On *nix systems, readline is used if available.

p = print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)

Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.

DATA
__test__ = {}
e = <unicorn.unicorn_py3.arch.intel.UcIntel object>

FILE
c:\users\p3cd0wn\desktop\ycb2025\plus\tempdir\reverse附件\chal\chal\init.pyd

发现exit其实是eval,然后导入了unicorn,应该要模拟执行某些shellcode

选择正则匹配int exit exec去简化代码逻辑,exp如下:

简化int的:

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


def evaluate_and_replace(match):
"""
替换函数:接收一个 re.Match 对象,
计算 exit() 括号内的表达式并返回结果。
"""
# group(1) 捕获的是第一个括号内的内容
expression = match.group(1).strip()

print(f"找到表达式: int({expression})")

try:
# 使用 eval() 计算表达式的值
# 为了安全,eval的上下文可以被限制,但这里为了简单直接使用
# 在真实生产环境中,要非常小心 eval() 的安全风险
evaluated_result = eval(expression)
print(f"计算结果: {evaluated_result}")

# 返回替换后的整个字符串,即计算出的结果
return '"'+str(evaluated_result)+'"'

except Exception as e:
print(f"计算表达式 '{expression}' 时出错: {e}", file=sys.stderr)
# 如果计算失败,可以选择返回原内容,避免破坏文件
return match.group(0)


def process_file(file_path):
"""
读取文件,使用正则和替换函数处理内容,并返回新内容。
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()

# 正则表达式解释:
# exit\s*\( : 匹配 "exit" 后面跟着0或多个空格,然后是左括号 '('
# ( : 开始捕获组 1
# .*? : 非贪婪匹配任意字符,直到遇到下一个模式
# ) : 结束捕获组 1
# \s*\) : 匹配0或多个空格,然后是右括号 ')'
# re.S (dotall): 让 '.' 可以匹配包括换行符在内的任意字符
pattern = re.compile(r'int\s*\((.*?)\s*\)', re.S)

# 使用 re.sub 和我们的替换函数来处理内容
new_content = pattern.sub(evaluate_and_replace, content)

return new_content

except FileNotFoundError:
print(f"错误: 文件 '{file_path}' 未找到。", file=sys.stderr)
return None

简化exit(eval)的:

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


def evaluate_and_replace(match):
"""
替换函数:接收一个 re.Match 对象,
计算 exit() 括号内的表达式并返回结果。
"""
# group(1) 捕获的是第一个括号内的内容
expression = match.group(1).strip()

print(f"找到表达式: exit({expression})")

try:
# 使用 eval() 计算表达式的值
# 为了安全,eval的上下文可以被限制,但这里为了简单直接使用
# 在真实生产环境中,要非常小心 eval() 的安全风险
evaluated_result = eval(expression)
print(f"计算结果: {evaluated_result}")

# 返回替换后的整个字符串,即计算出的结果
return str(evaluated_result)

except Exception as e:
print(f"计算表达式 '{expression}' 时出错: {e}", file=sys.stderr)
# 如果计算失败,可以选择返回原内容,避免破坏文件
return match.group(0)


def process_file(file_path):
"""
读取文件,使用正则和替换函数处理内容,并返回新内容。
"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()

# 正则表达式解释:
# exit\s*\( : 匹配 "exit" 后面跟着0或多个空格,然后是左括号 '('
# ( : 开始捕获组 1
# .*? : 非贪婪匹配任意字符,直到遇到下一个模式
# ) : 结束捕获组 1
# \s*\) : 匹配0或多个空格,然后是右括号 ')'
# re.S (dotall): 让 '.' 可以匹配包括换行符在内的任意字符
pattern = re.compile(r'exit\s*\((.*?)\s*\)', re.S)

# 使用 re.sub 和我们的替换函数来处理内容
new_content = pattern.sub(evaluate_and_replace, content)

return new_content

except FileNotFoundError:
print(f"错误: 文件 '{file_path}' 未找到。", file=sys.stderr)
return None


# --- 主程序 ---
if __name__ == "__main__":
target_file = "plus.py"

print(f"--- 正在处理文件: {target_file} ---\n")

# 获取处理后的内容
processed_content = process_file(target_file)

if processed_content is not None:
print("\n--- 处理前的内容 ---")
with open(target_file, 'r') as f:
print(f.read())

print("\n--- 处理后的内容 ---")
print(processed_content)

得到

1
2
from init import *
m(exec(30792292888306032),16777216,2097152)(e);m(exec(30792292888306032),18874368,65536)(e);m(exec(2018003706771258569829),16777216,exec(2154308209104587365050518702243508477825638429417674506632669006169365944097218288620502508770072595029515733547630393909115142517795439449349606840082096284733042186109675198923974401239556369486310477745337218358380860128987662749468317325542233718690074933730651941880380559453),)(e);m(exec(2110235738289946063973),44,18939903)(e);m(exec(2018003706771258569829),18878464, i(exec(520485229507545392928716380743873332979750615584)).encode())(e);m(exec(2110235738289946063973),39,18878464)(e);m(exec(2110235738289946063973),43,44)(e);m(exec(2110235738289946063973),40,7)(e);m(exec(18710084667165524261000), 16777216, 16777332)(e);p(exec(17353562600)) if (b(m(exec(7882826979490488676), 18878464, 44)(e)).decode()== exec(636496797464929889819018589958474261894226380884858896837050849823120096559828809884712107801783610237788137002972622711849132377866432975817021)) else p(exec(31084432670685473)) #type:ignore

然后处理掉exec,得到

1
2
from init import *
m('mem_map',16777216,2097152)(e);m('mem_map',18874368,65536)(e);m('mem_write',16777216,b'\xf3\x0f\x1e\xfaUH\x89\xe5H\x89}\xe8\x89u\xe4\x89\xd0\x88E\xe0\xc7E\xfc\x00\x00\x00\x00\xebL\x8bU\xfcH\x8bE\xe8H\x01\xd0\x0f\xb6\x00\x8d\x0c\xc5\x00\x00\x00\x00\x8bU\xfcH\x8bE\xe8H\x01\xd0\x0f\xb6\x002E\xe0\x8d4\x01\x8bU\xfcH\x8bE\xe8H\x01\xd0\x0f\xb6\x00\xc1\xe0\x05\x89\xc1\x8bU\xfcH\x8bE\xe8H\x01\xd0\x8d\x14\x0e\x88\x10\x83E\xfc\x01\x8bE\xfc;E\xe4r\xac\x90\x90]',)(e);m('reg_write',44,18939903)(e);m('mem_write',18878464, i('[+]input your flag: ').encode())(e);m('reg_write',39,18878464)(e);m('reg_write',43,44)(e);m('reg_write',40,7)(e);m('emu_start', 16777216, 16777332)(e);p('good') if (b(m('mem_read', 18878464, 44)(e)).decode()== '425MvHMxtLqZ3ty3RZkw3mwwulNRjkswbpkDMK+3CDCOtbe6kzAqPyrcEAI=') else p('no way!') #type:ignore

通过unicorn创建emu执行shellcode,然后进行base64比较,提取shellcode到shellcode.bin

ida反编译得到如下逻辑:

1
2
3
4
5
6
7
8
void __fastcall __noreturn sub_0(char *a1, unsigned int i_1, char a3)
{
unsigned int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i < i_1; ++i )
a1[i] = 8 * a1[i] + (a3 ^ a1[i]) + 32 * a1[i];
JUMPOUT(0x74);
}

所以加密逻辑是a1[i] = 8 * a1[i] + (7^ a1[i]) + 32 * a1[i]; 然后进行base64

并不直接可逆,选择爆破,写出exp如下:

1
2
3
4
5
6
7
8
from base64 import b64decode

cmp = b64decode(b"425MvHMxtLqZ3ty3RZkw3mwwulNRjkswbpkDMK+3CDCOtbe6kzAqPyrcEAI=")

for i in range(len(cmp)):
for j in range(32, 127):
if ((8 * j + (7 ^ j) + 32 * j) & 0xff) == cmp[i]:
print(chr(j), end="")

得到flag:DASCTF{un1c0rn_1s_u4fal_And_h0w_ab0ut_exec?}

ez_py

题目给了pyinstaller打包的key.exe,和pyarmor处理的src.py

解包得到key.pyc,使用pylingual反编译得到如下代码:

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
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: key.py
# Bytecode version: 3.13.0rc3 (3571)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import ast
import types
import sys
o0o0o0 = [105084753, 3212558540, 351342182, 844102737, 2002504052, 356536456, 2463183122, 615034880, 1156203296]

def changli(o0o0o1, o0o0o2, o0o0o3):
o0o0o4 = 2269471011
o0o0o5 = o0o0o3 & 4294967295
o0o0o6 = (o0o0o3 >> 8 ^ 305419896) & 4294967295
o0o0o7 = (o0o0o3 << 4 ^ 2271560481) & 4294967295
o0o0o8 = (o0o0o3 >> 12 ^ 2882400000) & 4294967295
o0o0o9 = o0o0o1 & 4294967295
o0o0o10 = o0o0o2 & 4294967295
o0o0o11 = 0
for _ in range(32):
o0o0o11 = o0o0o11 + o0o0o4 & 4294967295
o0o0o9 = o0o0o9 + ((o0o0o10 << 4) + o0o0o5 ^ o0o0o10 + o0o0o11 ^ (o0o0o10 >> 4) + o0o0o6) & 4294967295
o0o0o10 = o0o0o10 + ((o0o0o9 << 4) + o0o0o7 ^ o0o0o9 + o0o0o11 ^ (o0o0o9 >> 4) + o0o0o8) & 4294967295
return (o0o0o9, o0o0o10)

def Shorekeeper(o0o0o12):
o0o0o13 = o0o0o12 >> 16
o0o0o14 = o0o0o12 & 65535
return (o0o0o13, o0o0o14)

def Kathysia(o0o0o15, o0o0o16):
return o0o0o15 << 16 | o0o0o16 + 0

def Phrolova(o0o0o17):
o0oA = 'Carlotta'
o0oB = ['o0oC', 'o0oD', 'o0oE', 'o0oF']
o0oG = []
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oH', ctx=ast.Store())], value=ast.Constant(305419896)))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oI', ctx=ast.Store())], value=ast.BinOp(ast.Name(id='o0oE', ctx=ast.Load()), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oJ', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oE', ctx=ast.Load()), ast.RShift(), ast.Constant(16)), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oK', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oE', ctx=ast.Load()), ast.BitXor(), ast.Name(id='o0oF', ctx=ast.Load())), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oL', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oE', ctx=ast.Load()), ast.RShift(), ast.Constant(8)), ast.BitXor(), ast.Name(id='o0oF', ctx=ast.Load())), ast.BitAnd(), ast.Constant(65535)))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oM', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oH', ctx=ast.Load()), ast.Mult(), ast.BinOp(ast.Name(id='o0oF', ctx=ast.Load()), ast.Add(), ast.Constant(1))), ast.BitAnd(), ast.Constant(4294967295))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oN', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.BinOp(ast.Name(id='o0oD', ctx=ast.Load()), ast.LShift(), ast.Constant(5)), ast.Add(), ast.Add(), ast.Constant(5)), ast.Add(), ast.Name(id='o0oJ', ctx=ast.Load()))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oP', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oC', ctx=ast.Load()), ast.Add(), ast.Name(id='o0oN', ctx=ast.Load())), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oN', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.BinOp(ast.Name(id='o0oP', ctx=ast.Load()), ast.LShift(), ast.Constant(5)), ast.Add(), ast.Add(), ast.Constant(5)), ast.Add(), ast.Name(id='o0oL', ctx=ast.Load()))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oQ', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oD', ctx=ast.Load()), ast.Add(), ast.Name(id='o0oN', ctx=ast.Load())), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Return(ast.Tuple(elts=[ast.Name(id='o0oP', ctx=ast.Load()), ast.Name(id='o0oQ', ctx=ast.Load())], ctx=ast.Load())))
o0oU = ast.FunctionDef(name=o0oA, args=ast.arguments(posonlyargs=[], args=[ast.arg(arg=a) for a in o0oB], kwonlyargs=[], kw_defaults=[], defaults=[]), body=o0oG, decorator_list=[])
o0oV = ast.parse('\ndef _tea_helper_func(a, b, c):\n magic1 = (a ^ b) & 0xDEADBEEF\n magic2 = (c << 3) | (a >> 5)\n return (magic1 + magic2 - (b & 0xCAFEBABE)) & 0xFFFFFFFF\n\ndef _fake_tea_round(x, y):\n return ((x * 0x9E3779B9) ^ (y + 0x12345678)) & 0xFFFFFFFF\n\n_tea_magic_delta = 0x9E3779B9 ^ 0x12345678\n_tea_dummy_keys = [0x1111, 0x2222, 0x3333, 0x4444]\n').body
o0oW = ast.Module(body=[o0oU] + o0oV, type_ignores=[])
ast.fix_missing_locations(o0oW)
o0oX = compile(o0oW, filename='<tea_obf_ast>', mode='exec')
o0oY = {}
exec(o0oX, o0oY)
if o0oA in o0oY:
o0o0o17[o0oA] = o0oY[o0oA]
return None
Phrolova(globals())

def shouan(o0o0o32):
raise ValueError('需要输入9个key') if len(o0o0o32)!= 9 else None

def jinhsi():
print('请输入9个数字:')
try:
o0o0o46 = input().strip()
if ',' in o0o0o46:
o0o0o42 = o0o0o46.split(',')
if len(o0o0o42)!= 9:
print('错误: 需要输入9个数')
return None
except Exception as o0o0o47:
print(f'发生错误: {o0o0o47}')
if __name__ == '__main__':
jinhsi()

同构发现反编译的有问题,猜测是python版本为3.13过高的原因,考虑直接导入key相关模块进行爆破,写出如下爆破脚本

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
# bruteforce_key.py
# 用法示例见本文末

from multiprocessing import Process, Manager, Value, Lock, Event, cpu_count
import time
from key import Shorekeeper, Carlotta, Kathysia # 你已有的模块

def worker_search(target_val, idx, max_i, counter, lock, chunk_size, found_dict, stop_event, proc_id, progress_interval=5_000_00):
"""
每个 worker 循环取 chunk(通过 counter+lock 分配),并在 chunk 内线性扫描 i。
一旦找到 target_val 就写入 found_dict 并设置 stop_event。
"""
last_report = time.time()
while not stop_event.is_set():
# 获取下一个 chunk
with lock:
base = counter.value
if base >= max_i:
return # 没有更多任务
counter.value = min(max_i, base + chunk_size)
end = min(base + chunk_size, max_i)
# 扫描当前 chunk
for i in range(base, end):
# 提前退出检查
if stop_event.is_set():
return
# 计算产物
hi, lo = Shorekeeper(i)
# Carlotta 接口:Carlo tta(hi, lo, p1, p2) --> (c16, d16) 或 两个值(保持与你模块一致)
p1 = idx + 2025
p2 = idx * idx
c16, d16 = Carlotta(hi, lo, p1, p2)
val = Kathysia(c16, d16)
if val == target_val:
found_dict['result'] = i
stop_event.set()
print(f"[proc {proc_id}] FOUND idx={idx} val=0x{val:08x} -> i={i}")
return
# 进度报告(可选)
now = time.time()
if now - last_report > 5.0:
processed = min(end, max_i)
print(f"[proc {proc_id}] idx={idx} scanned up to {processed}/{max_i} (chunk base {base})")
last_report = now
# 结束
return


def parallel_find_for_index(target_val, idx, max_i=0x1000000, nprocs=None, chunk_size=50_000):
"""
在 [0, max_i) 的范围内并行搜索 i 使得 Kathysia(Carlotta(Shorekeeper(i), ...)) == target_val
返回找到的 i 或 None(未找到)。
chunk_size: 每个进程每次取的区间大小,需权衡进度与锁争用。
"""
if nprocs is None:
nprocs = max(1, cpu_count() - 1)
manager = Manager()
found_dict = manager.dict()
stop_event = manager.Event()
counter = Value('Q', 0) # unsigned long long 作为下一个 chunk base
lock = Lock()

procs = []
start_time = time.time()
for pid in range(nprocs):
p = Process(target=worker_search, args=(target_val, idx, max_i, counter, lock, chunk_size, found_dict, stop_event, pid))
p.start()
procs.append(p)

# 等待子进程
try:
while True:
if stop_event.is_set():
break
alive = any(p.is_alive() for p in procs)
if not alive:
break
time.sleep(0.5)
except KeyboardInterrupt:
print("KeyboardInterrupt: terminating workers...")
stop_event.set()

# 确保所有进程结束
for p in procs:
p.join(timeout=1.0)

elapsed = time.time() - start_time
if 'result' in found_dict:
return found_dict['result'], elapsed
else:
return None, elapsed


if __name__ == "__main__":
# 你要找的 cmp 列表(来自你的互动)
cmp = [4182080951, 235243574, 2930617237, 818266683, 2150848318, 1647473914, 2089977017, 3250628062, 1454944770]

# 请在此调整 max_i(默认示例 2^24 = 16,777,216)
# 若你需要更大范围(例如 0xFFFFFFFF),强烈建议把任务拆成若干子任务运行在多台机器上。
MAX_I = 0x01000000 # 初始测试范围:16,777,216
NPROCS = None # None 表示自动使用 cpu_count()-1
CHUNK = 100_000 # 每个进程每次拿 100k 间隔(可调)

found_indices = [None] * len(cmp)

# 建议:先逐个 idx 搜索(可更简单地并行所有 idx,但会增加内存/进程管理复杂度)
for idx in range(len(cmp)):
target = cmp[idx]
print(f"\n=== Searching for idx={idx} target=0x{target:08x} in range [0, {hex(MAX_I)}) ===")
found_i, elapsed = parallel_find_for_index(target, idx, max_i=MAX_I, nprocs=NPROCS, chunk_size=CHUNK)
if found_i is not None:
print(f"--> Found idx={idx} -> i = {found_i} (elapsed {elapsed:.1f}s)")
found_indices[idx] = found_i
else:
print(f"--> Not found in [0, {hex(MAX_I)}). elapsed {elapsed:.1f}s")
# 若未找到,你可把 MAX_I 扩大并重试,或把搜索任务拆分并分发到别的机器
print("\nAll results (None 表示未找到):")
print(found_indices)

爆破得到key:

1
[1234, 5678, 9123, 4567, 8912, 3456, 7891, 2345, 6789]

src内的src.py直接拿https://github.com/Lil-House/Pyarmor-Static-Unpack-1shot 处理失败,发现删除了开头的PY000000,补上之后使用工具得到src.py.1shot.cdc.py,逻辑如下:

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
# Source Generated with Decompyle++
# File: src.py.1shot.seq (Python 3.13)

'__pyarmor_enter_54743__(...)'
cipher = [
1473,
3419,
9156,
1267,
9185,
2823,
7945,
618,
7036,
2479,
5791,
1945,
4639,
1548,
3634,
3502,
2433,
1407,
1263,
3354,
9274,
1085,
8851,
3022,
8031,
734,
6869,
2644,
5798,
1862,
4745,
1554,
3523,
3631,
2512,
1499,
1221,
3226,
9237]

def init(key, key_len):
'__pyarmor_enter_54746__(...)'
_var_var_0 = 0
_var_var_1 = None(list, None(range, 256))
for _var_var_2 in None(range, 256):
_var_var_0 = (_var_var_0 + _var_var_1[_var_var_2] + key[_var_var_2 % key_len]) % 256
_var_var_1[_var_var_2], _var_var_1[_var_var_0] = _var_var_1[_var_var_0], _var_var_1[_var_var_2]
'__pyarmor_exit_54747__(...)'
return _var_var_1


def make(box):
'__pyarmor_enter_54749__(...)'
_var_var_2 = 0
_var_var_0 = 0
_var_var_3 = []
for _var_var_4 in None(range, 256):
_var_var_2 = (_var_var_2 + 1) % 256
_var_var_0 = (_var_var_0 + box[_var_var_2]) % 256
box[_var_var_2], box[_var_var_0] = box[_var_var_0], box[_var_var_2]
_var_var_5 = (box[_var_var_2] + box[_var_var_0] + _var_var_4 % 23) % 256
None(_var_var_3.append, box[_var_var_5])
'__pyarmor_exit_54750__(...)'
return _var_var_3

if __name__ == '__main__':
init.__doc__ = '欢迎来到羊城!\nThe key len is:9'
make.__doc__ = ' flag = list(b"flag{???}")\n fuck_key = [1,2,3,4,5,6,7,8,9]\n __ = [i % 0xff for i in fuck_key]\n key = make(init(bytes(__), len(__)))\n for i in range(len(cipher)):\n _ = fuck_key[i % 9] if i % 2 == 0 else (fuck_key[i % 9] * 2) % 0xFFF\n flag[i] ^= key[i] + _\n '
print(init.__doc__)
exit(0)
exit(0)
'__pyarmor_exit_54744__(...)'

rc4进行了一个小魔改,然后可以写出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
ef KSA(key):
"""Key-Scheduling Algorithm (KSA)"""
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]
return S


def PRGA(S):
"""Pseudo-Random Generation Algorithm (PRGA)"""
i, j = 0, 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]+ (i-1) % 23) % 256]
yield K


def init(key, key_len):
"""初始化 S 盒"""
return KSA(key)


def make(S):
"""生成伪随机密钥流"""
keystream = PRGA(S)
stream = []
for _ in range(256):
stream.append(next(keystream))
return stream


if __name__ == "__main__":
key = [1234, 5678, 9123, 4567, 8912, 3456, 7891, 2345, 6789]
cipher = [
1473, 3419, 9156, 1267, 9185, 2823, 7945, 618, 7036, 2479, 5791, 1945,
4639, 1548, 3634, 3502, 2433, 1407, 1263, 3354, 9274, 1085, 8851, 3022,
8031, 734, 6869, 2644, 5798, 1862, 4745, 1554, 3523, 3631, 2512, 1499,
1221, 3226, 9237
]

key_bytes = bytes([k % 0xFF for k in key])

box = init(list(key_bytes), len(key_bytes))
keystream = make(box)

plain_nums = []
for i, c in enumerate(cipher):
add = key[i % 9] if (i % 2 == 0) else (key[i % 9] * 2) % 0xFFF
val = c ^ (keystream[i % len(keystream)] + add)
plain_nums.append(val & 0xFF)

plain_bytes = bytes(plain_nums)

try:
decoded = plain_bytes.decode('utf-8')
except Exception:
decoded = plain_bytes.decode('latin-1')

print(decoded)

得到flag:flag{8561a-852sad-7561b-asd-4896-qwx56}

easyTauri

rust基于Tauri框架写的程序,实现了一个要到2024的2048小游戏,然后首先通过一个可疑的base64看到native层的逻辑

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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
char __fastcall sub_1400356D0(__int64 a1, __int64 p_Dst)
{
v100 = -2;
if ( *(_QWORD *)(p_Dst + 472) != 11
|| **(_QWORD **)(p_Dst + 464) ^ 'mmoc_cpi' | *(_QWORD *)(*(_QWORD *)(p_Dst + 464) + 3LL) ^ 'dnammoc_' )
{
sub_14000E200((_QWORD *)p_Dst);
return 0;
}
v3 = (const void *)(p_Dst + 520);
memcpy(Dst, (const void *)p_Dst, sizeof(Dst));
memcpy(v84, v3, sizeof(v84));
Buf1_7 = *(void **)(p_Dst + 936);
v93 = *(_OWORD *)(p_Dst + 920);
v89[6] = 0;
v89[0] = aIpcCommand;
v89[1] = 11;
v89[2] = aName_0;
v89[3] = 4;
v89[4] = Dst;
v89[5] = &v93;
v99 = 1;
sub_1402E0C00(Src, v89);
if ( LOBYTE(Src[0]) != 6 )
{
v92 = *(_OWORD *)Size;
v91 = *(_OWORD *)Src;
memcpy(v89, v3, 0x168u);
v85 = *(_OWORD *)(p_Dst + 880);
v86 = *(_QWORD *)(p_Dst + 896);
v90 = 1;
v99 = 0;
sub_140277AC0((unsigned int)v89, v84[48], (unsigned int)&v90, (unsigned int)&v85, v84[49], HIDWORD(v84[49]));
goto LABEL_83;
}
v5 = (char *)Src[1];
n8_1 = Size[0];
v89[0] = 0;
v89[1] = 1;
v89[2] = 0;
Buf1_4 = 1;
if ( Size[0] )
{
Buf1_8 = 1;
v8 = 0;
do
{
n8 = 8;
if ( n8_1 < 8 )
n8 = n8_1;
v90 = 0;
memcpy(&v90, v5, n8);
v10 = v90;
v11 = HIDWORD(v90);
n32 = 32;
n0x7E3997B7 = 0x7E3997B7;
do
{
v10 += (16 * v11 + 0x636C6557) ^ (v11 + n0x7E3997B7) ^ ((v11 >> 5) + 0x74336D4F);
v11 += (16 * v10 + 0x73757230) ^ (n0x7E3997B7 + v10) ^ ((v10 >> 5) + 0x55615474);
n0x7E3997B7 += 0x7E3997B7;
--n32;
}
while ( n32 );
if ( v89[0] - v8 <= 3 )
{
sub_1405C98F0((unsigned int)v89, v8, 4, 1, 1);
Buf1_8 = v89[1];
v8 = v89[2];
}
*(_DWORD *)(Buf1_8 + v8) = _byteswap_ulong(v10);
v14 = v8 + 4;
v89[2] = v14;
if ( v89[0] - v14 <= 3 )
{
sub_1405C98F0((unsigned int)v89, v14, 4, 1, 1);
v14 = v89[2];
}
v5 += n8;
n8_1 -= n8;
Buf1_8 = v89[1];
*(_DWORD *)(v89[1] + v14) = _byteswap_ulong(v11);
v8 = v14 + 4;
v89[2] = v8;
}
while ( n8_1 );
v97 = v89[0];
Buf1_4 = 1;
}
else
{
v8 = 0;
Buf1_8 = 1;
v97 = 0;
}
v89[0] = 0;
v89[1] = 1;
v89[2] = 0;
Buf1_1 = 0;
v16 = v89;
for ( i = 0; i + 2 < v8; i += 3LL )
{
if ( i >= v8 )
sub_1405DC1E8(i, v8, &off_1405E0BE8); // "src\\lib.rs"
if ( i + 1 >= v8 )
sub_1405DC1E8(i + 1, v8, &off_1405E0C00); // "src\\lib.rs"
v18 = *(_BYTE *)(Buf1_8 + i);
v19 = *(unsigned __int8 *)(Buf1_8 + i + 1);
v20 = *(unsigned __int8 *)(Buf1_8 + i + 2);
v21 = byte_1405E0B90[v18 >> 2];
v22 = (v21 < 0) + 1LL;
Buf1_9 = Buf1_1;
if ( v22 <= v89[0] - (_QWORD)Buf1_1 )
{
if ( v21 >= 0 )
goto LABEL_27;
LABEL_26:
Buf1_9[Buf1_4 + 1] = v21 & 0xBF;
v21 = ((unsigned __int8)v21 >> 6) | 0xC0;
goto LABEL_27;
}
Buf1 = Buf1_1;
sub_1405C98F0((_DWORD)v16, (_DWORD)Buf1_1, (v21 < 0) + 1, 1, 1);
Buf1_4 = v89[1];
Buf1_9 = (char *)v89[2];
Buf1_1 = (char *)Buf1;
v16 = v89;
if ( v21 < 0 )
goto LABEL_26;
LABEL_27:
Buf1_9[Buf1_4] = v21;
v24 = &Buf1_1[v22];
v89[2] = v24;
v25 = byte_1405E0B90[(16 * v18) & 0x30 | (v19 >> 4)];
v26 = (v25 < 0) + 1LL;
v27 = v24;
if ( v26 <= v89[0] - (_QWORD)v24 )
{
Buf1_4 = v89[1];
if ( v25 >= 0 )
goto LABEL_30;
LABEL_29:
v27[Buf1_4 + 1] = v25 & 0xBF;
v25 = ((unsigned __int8)v25 >> 6) | 0xC0;
goto LABEL_30;
}
sub_1405C98F0((_DWORD)v16, (_DWORD)v24, (v25 < 0) + 1, 1, 1);
v27 = (char *)v89[2];
v16 = v89;
Buf1_4 = v89[1];
if ( v25 < 0 )
goto LABEL_29;
LABEL_30:
v27[Buf1_4] = v25;
v28 = &v24[v26];
v89[2] = v28;
v29 = byte_1405E0B90[4 * (v19 & 0xF) + (v20 >> 6)];
v30 = (v29 < 0) + 1LL;
v31 = v28;
if ( v30 <= v89[0] - (_QWORD)v28 )
{
if ( v29 >= 0 )
goto LABEL_33;
LABEL_32:
v31[Buf1_4 + 1] = v29 & 0xBF;
v29 = ((unsigned __int8)v29 >> 6) | 0xC0;
goto LABEL_33;
}
v36 = v16;
sub_1405C98F0((_DWORD)v16, (_DWORD)v28, (v29 < 0) + 1, 1, 1);
Buf1_4 = v89[1];
v31 = (char *)v89[2];
v16 = v36;
if ( v29 < 0 )
goto LABEL_32;
LABEL_33:
v31[Buf1_4] = v29;
v32 = &v28[v30];
v89[2] = v32;
v33 = byte_1405E0B90[v20 & 0x3F];
v34 = (v33 < 0) + 1LL;
v35 = v32;
if ( v34 <= v89[0] - (_QWORD)v32 )
{
if ( v33 >= 0 )
goto LABEL_20;
LABEL_35:
v35[Buf1_4 + 1] = v33 & 0xBF;
v33 = ((unsigned __int8)v33 >> 6) | 0xC0;
goto LABEL_20;
}
v37 = v16;
sub_1405C98F0((_DWORD)v16, (_DWORD)v32, (v33 < 0) + 1, 1, 1);
Buf1_4 = v89[1];
v35 = (char *)v89[2];
v34 = (v33 < 0) + 1LL;
v16 = v37;
if ( v33 < 0 )
goto LABEL_35;
LABEL_20:
v35[Buf1_4] = v33;
Buf1_1 = &v32[v34];
v89[2] = Buf1_1;
}
v38 = i + 1;
if ( i + 1 != v8 )
{
if ( i + 2 != v8 )
goto LABEL_75;
if ( i >= v8 )
sub_1405DC1E8(i, v8, &off_1405E0B60); // "src\\lib.rs"
if ( v38 >= v8 )
sub_1405DC1E8(v38, v8, &off_1405E0B78); // "src\\lib.rs"
v55 = *(_BYTE *)(Buf1_8 + i);
v56 = *(unsigned __int8 *)(Buf1_8 + i + 1);
v57 = byte_1405E0B90[v55 >> 2];
v58 = (v57 < 0) + 1LL;
Buf1_2 = Buf1_1;
if ( v58 > v89[0] - (_QWORD)Buf1_1 )
{
Buf1_3 = Buf1_1;
sub_1405C98F0((unsigned int)v89, (_DWORD)Buf1_1, (v57 < 0) + 1, 1, 1);
Buf1_4 = v89[1];
Buf1_2 = (char *)v89[2];
Buf1_1 = Buf1_3;
}
v60 = (16 * v55) & 0x30;
if ( v57 < 0 )
{
Buf1_2[Buf1_4 + 1] = v57 & 0xBF;
v57 = ((unsigned __int8)v57 >> 6) | 0xC0;
}
Buf1_2[Buf1_4] = v57;
v61 = &Buf1_1[v58];
v89[2] = v61;
v62 = byte_1405E0B90[v60 | (v56 >> 4)];
v63 = (v62 < 0) + 1LL;
v64 = v61;
if ( v63 > v89[0] - (_QWORD)v61 )
{
sub_1405C98F0((unsigned int)v89, (_DWORD)v61, (v62 < 0) + 1, 1, 1);
v64 = (char *)v89[2];
}
v65 = (4 * (_BYTE)v56) & 0x3C;
v66 = v89[1];
if ( v62 < 0 )
{
v64[v89[1] + 1] = v62 & 0xBF;
v62 = ((unsigned __int8)v62 >> 6) | 0xC0;
}
v64[v66] = v62;
v67 = &v61[v63];
v89[2] = v67;
v68 = byte_1405E0B90[v65];
v69 = v67;
v70 = (v68 < 0) + 1LL;
if ( v70 > v89[0] - (_QWORD)v67 )
{
sub_1405C98F0((unsigned int)v89, (_DWORD)v67, (v68 < 0) + 1, 1, 1);
v66 = v89[1];
v69 = (char *)v89[2];
v70 = (v68 < 0) + 1LL;
if ( v68 >= 0 )
goto LABEL_72;
}
else if ( v68 >= 0 )
{
LABEL_72:
v69[v66] = v68;
v71 = &v67[v70];
v89[2] = v71;
v72 = v71;
if ( (char *)v89[0] == v71 )
{
v82 = v71;
sub_1405C98F0((unsigned int)v89, (_DWORD)v71, 1, 1, 1);
v66 = v89[1];
v72 = (char *)v89[2];
v71 = v82;
}
v72[v66] = 61;
Buf1_1 = v71 + 1;
Buf1_4 = v89[1];
LABEL_75:
v53 = v97;
Buf1 = (void *)Buf1_4;
v54 = v89[0];
if ( Buf1_1 != (char *)76 )
goto LABEL_77;
goto LABEL_76;
}
v69[v66 + 1] = v68 & 0xBF;
v68 = ((unsigned __int8)v68 >> 6) | 0xC0;
goto LABEL_72;
}
if ( i >= v8 )
sub_1405DC1E8(i, v8, &off_1405E0BD0); // "src\\lib.rs"
v39 = *(unsigned __int8 *)(Buf1_8 + i);
v40 = byte_1405E0B90[v39 >> 2];
v41 = (v40 < 0) + 1LL;
Buf1_5 = Buf1_1;
if ( v41 > v89[0] - (_QWORD)Buf1_1 )
{
Buf1_6 = Buf1_1;
sub_1405C98F0((unsigned int)v89, (_DWORD)Buf1_1, (v40 < 0) + 1, 1, 1);
Buf1_4 = v89[1];
Buf1_5 = (char *)v89[2];
Buf1_1 = Buf1_6;
v43 = (16 * (_BYTE)v39) & 0x30;
if ( v40 < 0 )
{
LABEL_48:
Buf1_5[Buf1_4 + 1] = v40 & 0xBF;
v40 = ((unsigned __int8)v40 >> 6) | 0xC0;
}
}
else
{
v43 = (16 * (_BYTE)v39) & 0x30;
if ( v40 < 0 )
goto LABEL_48;
}
Buf1_5[Buf1_4] = v40;
v44 = &Buf1_1[v41];
v89[2] = v44;
v45 = byte_1405E0B90[v43];
v46 = (v45 < 0) + 1LL;
v47 = v44;
if ( v46 > v89[0] - (_QWORD)v44 )
{
sub_1405C98F0((unsigned int)v89, (_DWORD)v44, (v45 < 0) + 1, 1, 1);
v47 = (char *)v89[2];
v46 = (v45 < 0) + 1LL;
v48 = v89[1];
if ( v45 < 0 )
{
LABEL_51:
v47[v48 + 1] = v45 & 0xBF;
v45 = ((unsigned __int8)v45 >> 6) | 0xC0;
}
}
else
{
v48 = v89[1];
if ( v45 < 0 )
goto LABEL_51;
}
v47[v48] = v45;
n74 = &v44[v46];
v89[2] = n74;
v50 = (char *)v89[0];
n74_1 = n74;
if ( (char *)v89[0] == n74 )
{
n74_2 = n74;
sub_1405C98F0((unsigned int)v89, (_DWORD)n74, 1, 1, 1);
v50 = (char *)v89[0];
v48 = v89[1];
n74_1 = (char *)v89[2];
n74 = n74_2;
}
n74_1[v48] = 61;
v52 = n74 + 1;
v89[2] = n74 + 1;
v53 = v97;
if ( v50 == n74 + 1 )
{
n74_3 = n74;
sub_1405C98F0((unsigned int)v89, (_DWORD)v50, 1, 1, 1);
v48 = v89[1];
v52 = (char *)v89[2];
v53 = v97;
n74 = n74_3;
}
v52[v48] = 61;
Buf1 = (void *)v89[1];
v54 = v89[0];
if ( n74 != (char *)74 )
{
LABEL_77:
nullsub_1();
v73 = sub_140001080(3, 1);
if ( !v73 )
{
v95 = v54;
sub_1405DBDA3(1, 3, &off_1405E0B38); // "C:\\Users\\Xierluo\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\\lib/rustlib/src/rust\\library\\alloc\\src\\slice.rs"
}
v74 = v73;
*(_BYTE *)(v73 + 2) = 100;
*(_WORD *)v73 = 25922;
if ( v54 )
goto LABEL_79;
goto LABEL_80;
}
LABEL_76:
if ( memcmp(Buf1, aDafDkqxixgmzn0, 0x4Cu) )
goto LABEL_77;
nullsub_1();
v77 = sub_140001080(3, 1);
if ( !v77 )
{
v95 = v54;
sub_1405DBDA3(1, 3, &off_1405E0B38); // "C:\\Users\\Xierluo\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\\lib/rustlib/src/rust\\library\\alloc\\src\\slice.rs"
}
v74 = v77;
*(_BYTE *)(v77 + 2) = 110;
*(_WORD *)v77 = 26967;
if ( v54 )
LABEL_79:
sub_140001090(Buf1, v54, 1);
LABEL_80:
if ( v53 )
sub_140001090(Buf1_8, v53, 1);
memcpy(v89, v84, sizeof(v89));
*(_QWORD *)&v91 = 3;
*((_QWORD *)&v91 + 1) = v74;
*(_QWORD *)&v92 = 3;
LOBYTE(v90) = 6;
v99 = 0;
sub_140294520(v89);
LABEL_83:
if ( !__OFSUB__(0, (_QWORD)v93) )
{
v75 = *((_QWORD *)&v93 + 1);
Buf1 = Buf1_7;
Buf1_8 = 0;
v97 = *((_QWORD *)&v93 + 1);
while ( Buf1 != (void *)Buf1_8 )
{
++Buf1_8;
v76 = v75 + 96;
sub_140255E40(v75);
v75 = v76;
}
if ( (_QWORD)v93 )
sub_140001090(*((_QWORD *)&v93 + 1), 96 * v93, 8);
}
sub_14000F550(Dst);
return 1;
}

先校验了头部是否是ipc_command,然后进行了一个魔改的tea算法,改变端序存储后进行了base64,码表是标准的base64码表。

考虑到这是一个Tauri的程序,需要解包出前端的对应的js代码

搜索到文章:https://blog.yllhwa.com/2023/05/09/Tauri%20%E6%A1%86%E6%9E%B6%E7%9A%84%E9%9D%99%E6%80%81%E8%B5%84%E6%BA%90%E6%8F%90%E5%8F%96%E6%96%B9%E6%B3%95%E6%8E%A2%E7%A9%B6/

从index.html入手,ida python提取出数据之后进行 brotli 解压缩

image-20251012213342453

解压缩脚本如下:

1
2
3
4
5
6
7
8
9
import brotli

with open("animframe_polyfill_js.bin", "rb") as f:
content = f.read()

print(len(content))

decompressed = brotli.decompress(content)
open("animframe_polyfill.js", "wb").write(decompressed)

将所有的html和js都统一解压缩,看到index_flag.html

image-20251012213545799

然后从html_actuator.js找到前端的加密逻辑,如下:

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
function HTMLActuator() {
this.tileContainer = document.querySelector(".tile-container");
this.scoreContainer = document.querySelector(".score-container");
this.bestContainer = document.querySelector(".best-container");
this.messageContainer = document.querySelector(".game-message");

this.score = 0;
}

HTMLActuator.prototype.actuate = function (grid, metadata) {
var self = this;
if(metadata.game){
checkBox = document.querySelector(".check-form");
checkBoxHtml = `
<form class="above-game" id="greet-form">
<input id="greet-input" placeholder="Enter a Flag..." />
<button type="submit">Check</button>
</form>
`;
checkBox.innerHTML = checkBoxHtml;
}


window.requestAnimationFrame(function () {
self.clearContainer(self.tileContainer);

grid.cells.forEach(function (column) {
column.forEach(function (cell) {
if (cell) {
self.addTile(cell);
}
});
});

self.updateScore(metadata.score);
self.updateBestScore(metadata.bestScore);

if (metadata.terminated) {
if (metadata.over) {
self.message(false); // You lose
} else if (metadata.won) {
self.message(true); // You win!
}
}

});
};

// Continues the game (both restart and keep playing)
HTMLActuator.prototype.continueGame = function () {
this.clearMessage();
};

HTMLActuator.prototype.clearContainer = function (container) {
while (container.firstChild) {
container.removeChild(container.firstChild);
}
};

HTMLActuator.prototype.addTile = function (tile) {
var self = this;

var wrapper = document.createElement("div");
var inner = document.createElement("div");
var position = tile.previousPosition || { x: tile.x, y: tile.y };
var positionClass = this.positionClass(position);

// We can't use classlist because it somehow glitches when replacing classes
var classes = ["tile", "tile-" + tile.value, positionClass];

if (tile.value > 2048) classes.push("tile-super");

this.applyClasses(wrapper, classes);

inner.classList.add("tile-inner");
inner.textContent = tile.value;

if (tile.previousPosition) {
// Make sure that the tile gets rendered in the previous position first
window.requestAnimationFrame(function () {
classes[2] = self.positionClass({ x: tile.x, y: tile.y });
self.applyClasses(wrapper, classes); // Update the position
});
} else if (tile.mergedFrom) {
classes.push("tile-merged");
this.applyClasses(wrapper, classes);

// Render the tiles that merged
tile.mergedFrom.forEach(function (merged) {
self.addTile(merged);
});
} else {
classes.push("tile-new");
this.applyClasses(wrapper, classes);
}

// Add the inner part of the tile to the wrapper
wrapper.appendChild(inner);

// Put the tile on the board
this.tileContainer.appendChild(wrapper);
};

HTMLActuator.prototype.applyClasses = function (element, classes) {
element.setAttribute("class", classes.join(" "));
};

HTMLActuator.prototype.normalizePosition = function (position) {
return { x: position.x + 1, y: position.y + 1 };
};

HTMLActuator.prototype.positionClass = function (position) {
position = this.normalizePosition(position);
return "tile-position-" + position.x + "-" + position.y;
};

HTMLActuator.prototype.updateScore = function (score) {
this.clearContainer(this.scoreContainer);

var difference = score - this.score;
this.score = score;

this.scoreContainer.textContent = this.score;

if (difference > 0) {
var addition = document.createElement("div");
addition.classList.add("score-addition");
addition.textContent = "+" + difference;

this.scoreContainer.appendChild(addition);
}
};

HTMLActuator.prototype.updateBestScore = function (bestScore) {
this.bestContainer.textContent = bestScore;
};

HTMLActuator.prototype.message = function (won) {
var type = won ? "game-won" : "game-over";
var message = won ? "You win!" : "Game over!";

this.messageContainer.classList.add(type);
this.messageContainer.getElementsByTagName("p")[0].textContent = message;
};

HTMLActuator.prototype.clearMessage = function () {
// IE only takes one value to remove at a time.
this.messageContainer.classList.remove("game-won");
this.messageContainer.classList.remove("game-over");
};


const { invoke } = window.__TAURI__.core;

let greetInputEl;
let greetMsgEl;

(function(_0x97aee2,_0x14d3d9){const _0x151017=_0x363b,_0x2b0390=_0x97aee2();while(!![]){try{const _0x3b9dd4=parseInt(_0x151017(0xb0))/0x1+parseInt(_0x151017(0xac))/0x2+parseInt(_0x151017(0xaa))/0x3+-parseInt(_0x151017(0xab))/0x4+-parseInt(_0x151017(0xa7))/0x5*(parseInt(_0x151017(0xa8))/0x6)+-parseInt(_0x151017(0xae))/0x7*(-parseInt(_0x151017(0xa6))/0x8)+-parseInt(_0x151017(0xad))/0x9;if(_0x3b9dd4===_0x14d3d9)break;else _0x2b0390['push'](_0x2b0390['shift']());}catch(_0x34886e){_0x2b0390['push'](_0x2b0390['shift']());}}}(_0x3a0b,0x6e7b4));
function Encrypt_0xa31304(_0x5031b3, _0xa31304){const _0x22bac7=_0x363b,_0x5d7b84=new TextEncoder()[_0x22bac7(0xa9)](_0x5031b3),_0x2db5b9=new TextEncoder()[_0x22bac7(0xa9)](_0xa31304),_0x1f7f86=new Uint8Array(0x100);let _0x562e52=0x0;for(let _0x24ca0d=0x0; _0x24ca0d<0x100; _0x24ca0d++){_0x1f7f86[_0x24ca0d]=_0x24ca0d,_0x562e52=(_0x562e52+_0x1f7f86[_0x24ca0d]+_0x5d7b84[_0x24ca0d%_0x5d7b84[_0x22bac7(0xaf)]])%0x100,[_0x1f7f86[_0x24ca0d],_0x1f7f86[_0x562e52]]=[_0x1f7f86[_0x562e52],_0x1f7f86[_0x24ca0d]];}let _0x5b36c3=0x0,_0x205ec1=0x0;const _0x444cf9=new Uint8Array(_0x2db5b9[_0x22bac7(0xaf)]);for(let _0x527286=0x0; _0x527286<_0x2db5b9[_0x22bac7(0xaf)]; _0x527286++){_0x5b36c3=(_0x5b36c3+0x1)%0x100,_0x205ec1=(_0x205ec1+_0x1f7f86[_0x5b36c3])%0x100,[_0x1f7f86[_0x5b36c3],_0x1f7f86[_0x205ec1]]=[_0x1f7f86[_0x205ec1],_0x1f7f86[_0x5b36c3]];const _0x326832=(_0x1f7f86[_0x5b36c3]+_0x1f7f86[_0x205ec1])%0x100;_0x444cf9[_0x527286]=_0x2db5b9[_0x527286]^_0x1f7f86[_0x326832];}return _0x444cf9;}function _0x363b(_0x3e7d70, _0x4a2c88){const _0x3a0bb6=_0x3a0b();return _0x363b=function(_0x363b1f, _0x4025c1){_0x363b1f=_0x363b1f-0xa6;let _0x387f5b=_0x3a0bb6[_0x363b1f];return _0x387f5b;},_0x363b(_0x3e7d70,_0x4a2c88);}function _0x3a0b(){const _0x37fb1e=['3283052tzDAvB','542866JdmzNj','4112658rTyTXQ','16954tUYpad','length','457163LwGIuU','2696pusaTH','233035azfeoA','66oGYEyB','encode','2094372kZRrIa'];_0x3a0b=function(){return _0x37fb1e;};return _0x3a0b();}

function uint8ArrayToBase64(array) {
const binary = Array.from(array).map(byte => String.fromCharCode(byte)).join('');
return btoa(binary);
}


async function _0x9a2c6e7() {
greetInputEl = document.querySelector("#greet-input");
greetMsgEl = document.querySelector("#greet-msg");
let getFlag = greetInputEl.value;
const ciphertext = Encrypt_0xa31304("SadTongYiAiRC4HH", getFlag);
greetMsgEl.textContent = await invoke("ipc_command", { name: uint8ArrayToBase64(ciphertext) });
}

window.addEventListener("DOMContentLoaded", () => {
document.getElementById("check-form").addEventListener("submit", (e) => {
e.preventDefault();
_0x9a2c6e7();
});
});

进行了魔改的rc4加密后base64并发送ipc_command给rust后端,

那么解密逻辑就是base64->魔改tea->base64->魔改rc4, rc4让S盒一开始全为0,写出解密脚本如下:

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
from base64 import b64decode
from ctypes import c_uint32

def tea_decrypt(r, v, key, delta):
v0, v1 = c_uint32(v[0]), c_uint32(v[1])
# delta = 0x9e3779b9
total = c_uint32(0x7E3997B7+delta * r)
for i in range(r):
total.value -= delta
v1.value -= ((v0.value << 4) + key[2]) ^ (v0.value + total.value) ^ ((v0.value >> 5) + key[3])
v0.value -= ((v1.value << 4) + key[0]) ^ (v1.value + total.value) ^ ((v1.value >> 5) + key[1])
return v0.value, v1.value

def KSA(key):
""" Key-Scheduling Algorithm (KSA) 密钥调度算法"""
S = list([0]*256)
j = 0
for i in range(256):
S[i] = i
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
return S


def PRGA(S):
""" Pseudo-Random Generation Algorithm (PRGA) 伪随机数生成算法"""
i, j = 0, 0
while True:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
yield K


def RC4(key, text):
""" RC4 encryption/decryption """
S = KSA(key)
keystream = PRGA(S)
res = []
for char in text:
res.append(char ^ next(keystream))
return bytes(res)

v = b64decode(b"daF/DkQxixGmzn0aPFW2E2PhM8NabRtLjp6pI+c8TtY3WMuPxfnvlAsp9aluf8noZy/T6Sz9DJg=")
v = [int.from_bytes(v[i:i+4], byteorder="big") for i in range(0, len(v), 4)]
k = b"WelcOm3t0rustTaU"
k = [int.from_bytes(k[i:i+4], byteorder="little") for i in range(0, len(k), 4)]
for i in range(0, len(v), 2):
v[i:i+2] = tea_decrypt(32, v[i:i+2], k, 0x7E3997B7)
v = b"".join([int.to_bytes(v[i], byteorder='little', length=4) for i in range(len(v))])
#print(v)
v = b64decode(v)
v = RC4(b"SadTongYiAiRC4HH", v)
print(v)

得到flag:flag{cf8be09b1c8a415f8b5e8f1dac71d4af}


羊城杯2025_Reverse_Writeup
http://example.com/2025/10/13/羊城杯2025-Reverse-Writeup/
作者
p3cd0wn
发布于
2025年10月13日
许可协议