GD1
godot引擎开发的游戏
上网上下载一个工具就能反编译资源,就不细说了.
PLUS
首先观察到程序,给了我们一个init.pyd文件和一个经过了混淆的代码.
首先可以化简代码的表达式,让代码变得更加容易读一些(但是这里运行不了...不知道为什么...).
m(exec(3214),955,926)(e)
m(exec(3132),999,691)(e)
m(exec(4020),955,exec(50720),)(e)
m(exec(3635),44,591)(e)
m(exec(4020),1036, i(exec(9578)).encode())(e)
m(exec(3635),39,1036)(e)
m(exec(3635),43,44)(e)
m(exec(3635),40,7)(e)
m(exec(3311), 955, 972)(e)
p(exec(1334)) if (b(m(exec(3637), 1036, 44)(e)).decode() == exec(28274)) else p(exec(3113)) #type:ignore
可以发现频繁调用了一些奇怪的东西,基本上可以猜测是init.pyd中的东西
借用python的模块分析一下inti.pyd,便于我们分析.
init相当于就是一个class,我们调用的就是它的成员函数
import init
print(help(init)) # 显示模块中函数的相关信息
print(dir(init)) # 显示模块中的导出函数
(py3.9) PS D:\Microsoftdownload\9a25567db24384ae94d40bf325c53792\tempdir\REVERSE附件\chal\chal>python .\plus_simplified.py
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
d:\microsoftdownload\9a25567db24384ae94d40bf325c53792\tempdir\reverse附件\chal\chal\init.pyd
None
['__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '__test__', 'a2b_hex', 'b', 'c', 'e', 'exec', 'exit', 'i', 'int', 'm', 'p']
通过file init.pyd可以查看这个文件的属性,说明是在Windows上pyd文件,相当于动态链接库
❯ file init.pyd
init.pyd: PE32+ executable (DLL) (GUI) x86-64, for MS Windows
接下来通过hook方法来查看,把下面这段代码加到代码前
from init import *
import init
originals = {}
for name in ['exec', 'i', 'p', 'b', 'a2b_hex']:
if hasattr(init, name): # 检查init中是否有列表中对应的属性
ori = getattr(init, name) # 如果有,就保存到originals中
originals[name] = ori
# 构建hook函数
def make_hook(fn_name, fn_original):
# 因为我们不知道参数是什么样的,所以我们这样写
def wrapper(*args, **kwargs):
print(f"[{fn_name}] args: {args}")
res = fn_original(*args, **kwargs)
print(f"[{fn_name}] args: {res}")
return res
return wrapper
globals()[name] = make_hook(name, ori)
[exec] args: (30792292888306032,)
[exec] args: mem_map
[exec] args: (30792292888306032,)
[exec] args: mem_map
[exec] args: (2018003706771258569829,)
[exec] args: mem_write
[exec] args: (2154308209104587365050518702243508477825638429417674506632669006169365944097218288620502508770072595029515733547630393909115142517795439449349606840082096284733042186109675198923974401239556369486310477745337218358380860128987662749468317325542233718690074933730651941880380559453,)
[exec] args: 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]'
[exec] args: (2110235738289946063973,)
[exec] args: reg_write
[exec] args: (2018003706771258569829,)
[exec] args: mem_write
[exec] args: (520485229507545392928716380743873332979750615584,)
[exec] args: [+]input your flag:
[i] args: ('[+]input your flag: ',)
[+]input your flag: 11112222
[i] args: 11112222
[exec] args: (2110235738289946063973,)
[exec] args: reg_write
[exec] args: (2110235738289946063973,)
[exec] args: reg_write
[exec] args: (2110235738289946063973,)
[exec] args: reg_write
[exec] args: (1871008466716552426100,)
[exec] args: emu_start
[exec] args: (7882826979490488676,)
[exec] args: mem_read
[b] args: (bytearray(b'\xde\xde\xde\xde\x05\x05\x05\x05\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07'),)
[b] args: b'3t7e3gUFBQUHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwc='
[exec] args: (636496797464929889819018589958474261894226380884858896837050849823120096559828809884712107801783610237788137002972622711849132377866432975817021,)
[exec] args: 425MvHMxtLqZ3ty3RZkw3mwwulNRjkswbpkDMK+3CDCOtbe6kzAqPyrcEAI=
[exec] args: (31084432670685473,)
[exec] args: no way!
[p] args: ('no way!',)
no way!
[p] args: None
发现了关键的两段数据
[exec] args: 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]'
[exec] args: 425MvHMxtLqZ3ty3RZkw3mwwulNRjkswbpkDMK+3CDCOtbe6kzAqPyrcEAI=
反汇编上面的字节码,可以发现就是一段加密
而下面的base64应该就是密文
void __fastcall __noreturn encode(__int64 a, unsigned int range, char key)
{
unsigned int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; i < range; ++i )
a[i] = 8 * a[i] + (key ^ a[i]) + 32 * a[i];
}
接下来就可以编写解密代码了,但是因为key不知道,需要爆破一下
cipher = [
0xe3,0x6e,0x4c,0xbc,0x73,0x31,0xb4,0xba,0x99,0xde,0xdc,0xb7,0x45,0x99,0x30,0xde,
0x6c,0x30,0xba,0x53,0x51,0x8e,0x4b,0x30,0x6e,0x99,0x03,0x30,0xaf,0xb7,0x08,0x30,
0x8e,0xb5,0xb7,0xba,0x93,0x30,0x2a,0x3f,0x2a,0xdc,0x10,0x02
]
def decrypt_with_key(enc_bytes, key):
"""
用给定密钥爆破解密
"""
plain = []
for c in enc_bytes:
bruteforce = [orig for orig in range(256) if ((40 * orig + (key ^ orig)) & 0xff) == c]
if not bruteforce:
return None
printable = [c for c in bruteforce if 32 <= c <= 126]
chosen = printable[0] if printable else bruteforce[0]
plain.append(chosen)
return bytes(plain)
def main():
# 爆破key
for key in range(256):
pt = decrypt_with_key(cipher, key)
if pt is None:
continue
# 已知明文开头是 "DASCTF"
if pt.startswith(b"DASCTF"):
print("Found key:", key)
try:
print("Plaintext:", pt.decode('utf-8'))
except UnicodeDecodeError:
# 若包含非 UTF-8 字符,显示 repr
print("Plaintext (repr):", repr(pt))
break
else:
print("No key found that yields prefix 'DASCTF'.")
if __name__ == "__main__":
main()
flag:DASCTF{un1c0rn_1s_u4fal_And_h0w_ab0ut_exec?}
Key
代码分析
看到附件中给了key.exe和src.py
src.py是被paramor保护了的程序
先用pyinstxtracotr.py解包一下key.exe,再用pycdc反编译一下key.pyc.
但是奇怪的会发现会反编译失败,说是魔术头识别败,看了一下是py3.13,怀疑是pycdc版本太低,所以重新clone编译了一下,这下没问题,但是还是没办法全部编译.
没办法只能用pycdas看字节码了,可以让ai帮我们恢复出大概的函数逻辑(ai太好用了你知道吗).
import ast
import types
import sys
# 目标加密结果(需要匹配的值)
o0o0o0 = [105084753, 3212558540, 351342182, 844102737, 2002504052,
356536456, 2463183122, 615034880, 1156203296]
def changli(o0o0o1, o0o0o2, o0o0o3):
"""TEA加密算法的一轮"""
o0o0o4 = 2269471011 # delta值
o0o0o5 = o0o0o3 & 0xFFFFFFFF
o0o0o6 = ((o0o0o3 >> 8) ^ 305419896) & 0xFFFFFFFF
o0o0o7 = ((o0o0o3 << 4) ^ 2271560481) & 0xFFFFFFFF
o0o0o8 = ((o0o0o3 >> 12) ^ 2882400000) & 0xFFFFFFFF
o0o0o9 = o0o0o1 & 0xFFFFFFFF
o0o0o10 = o0o0o2 & 0xFFFFFFFF
o0o0o11 = 0
# 32轮加密
for _ in range(32):
o0o0o11 = (o0o0o11 + o0o0o4) & 0xFFFFFFFF
o0o0o9 = (o0o0o9 + (((o0o0o10 << 4) + o0o0o5) ^ (o0o0o10 + o0o0o11) ^
((o0o0o10 >> 4) + o0o0o6))) & 0xFFFFFFFF
o0o0o10 = (o0o0o10 + (((o0o0o9 << 4) + o0o0o7) ^ (o0o0o9 + o0o0o11) ^
((o0o0o9 >> 4) + o0o0o8))) & 0xFFFFFFFF
return (o0o0o9, o0o0o10)
def Shorekeeper(o0o0o12):
"""将32位数字拆分为高16位和低16位"""
o0o0o13 = o0o0o12 >> 16
o0o0o14 = o0o0o12 & 65535
return (o0o0o13, o0o0o14)
def Kathysia(o0o0o15, o0o0o16):
"""将两个16位数字合并为32位数字"""
return (o0o0o15 << 16) | (o0o0o16 + 0)
def Phrolova(o0o0o17):
"""动态生成Carlotta函数(包含混淆代码)"""
o0oA = "Carlotta"
o0oB = ['o0oC', 'o0oD', 'o0oE', 'o0oF']
o0oG = []
# 构建函数体(包含一些迷惑性的计算)
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oH", ctx=ast.Store())],
value=ast.Constant(value=305419896)
))
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oI", ctx=ast.Store())],
value=ast.BinOp(
left=ast.Name(id="o0oE", ctx=ast.Load()),
op=ast.BitAnd(),
right=ast.Constant(value=65535)
)
))
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oJ", ctx=ast.Store())],
value=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oE", ctx=ast.Load()),
op=ast.RShift(),
right=ast.Constant(value=16)
),
op=ast.BitAnd(),
right=ast.Constant(value=65535)
)
))
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oK", ctx=ast.Store())],
value=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oE", ctx=ast.Load()),
op=ast.BitXor(),
right=ast.Name(id="o0oF", ctx=ast.Load())
),
op=ast.BitAnd(),
right=ast.Constant(value=65535)
)
))
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oL", ctx=ast.Store())],
value=ast.BinOp(
left=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oE", ctx=ast.Load()),
op=ast.RShift(),
right=ast.Constant(value=8)
),
op=ast.BitXor(),
right=ast.Name(id="o0oF", ctx=ast.Load())
),
op=ast.BitAnd(),
right=ast.Constant(value=65535)
)
))
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oM", ctx=ast.Store())],
value=ast.BinOp(
left=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oH", ctx=ast.Load()),
op=ast.Mult(),
right=ast.BinOp(
left=ast.Name(id="o0oF", ctx=ast.Load()),
op=ast.Add(),
right=ast.Constant(value=1)
)
),
op=ast.BitAnd(),
right=ast.Constant(value=4294967295)
),
op=ast.BitAnd(),
right=ast.Constant(value=4294967295)
)
))
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oN", ctx=ast.Store())],
value=ast.BinOp(
left=ast.BinOp(
left=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oD", ctx=ast.Load()),
op=ast.LShift(),
right=ast.Constant(value=5)
),
op=ast.Add(),
right=ast.Name(id="o0oI", ctx=ast.Load())
),
op=ast.BitXor(),
right=ast.BinOp(
left=ast.Name(id="o0oD", ctx=ast.Load()),
op=ast.Add(),
right=ast.Name(id="o0oM", ctx=ast.Load())
)
),
op=ast.BitXor(),
right=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oD", ctx=ast.Load()),
op=ast.RShift(),
right=ast.Constant(value=5)
),
op=ast.Add(),
right=ast.Name(id="o0oJ", ctx=ast.Load())
)
)
))
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oP", ctx=ast.Store())],
value=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oC", ctx=ast.Load()),
op=ast.Add(),
right=ast.Name(id="o0oN", ctx=ast.Load())
),
op=ast.BitAnd(),
right=ast.Constant(value=65535)
)
))
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oN", ctx=ast.Store())],
value=ast.BinOp(
left=ast.BinOp(
left=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oP", ctx=ast.Load()),
op=ast.LShift(),
right=ast.Constant(value=5)
),
op=ast.Add(),
right=ast.Name(id="o0oK", ctx=ast.Load())
),
op=ast.BitXor(),
right=ast.BinOp(
left=ast.Name(id="o0oP", ctx=ast.Load()),
op=ast.Add(),
right=ast.Name(id="o0oM", ctx=ast.Load())
)
),
op=ast.BitXor(),
right=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oP", ctx=ast.Load()),
op=ast.RShift(),
right=ast.Constant(value=5)
),
op=ast.Add(),
right=ast.Name(id="o0oL", ctx=ast.Load())
)
)
))
o0oG.append(ast.Assign(
targets=[ast.Name(id="o0oQ", ctx=ast.Store())],
value=ast.BinOp(
left=ast.BinOp(
left=ast.Name(id="o0oD", ctx=ast.Load()),
op=ast.Add(),
right=ast.Name(id="o0oN", ctx=ast.Load())
),
op=ast.BitAnd(),
right=ast.Constant(value=65535)
)
))
o0oG.append(ast.Return(
value=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("""
def _tea_helper_func(a, b, c):
magic1 = (a ^ b) & 0xDEADBEEF
magic2 = (c << 3) | (a >> 5)
return (magic1 + magic2 - (b & 0xCAFEBABE)) & 0xFFFFFFFF
def _fake_tea_round(x, y):
return ((x * 0x9E3779B9) ^ (y + 0x12345678)) & 0xFFFFFFFF
_tea_magic_delta = 0x9E3779B9 ^ 0x12345678
_tea_dummy_keys = [0x1111, 0x2222, 0x3333, 0x4444]
""").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]
def shouan(o0o0o32):
"""主处理逻辑"""
if len(o0o0o32) != 9:
raise ValueError("需要输入9个key")
o0o0o35 = []
# 对每个key进行处理
for o0o0o49, o0o0o34 in enumerate(o0o0o32):
o0o0o33 = o0o0o49 * o0o0o49 # i * i
# 拆分为高低16位
o0o0o36, o0o0o37 = Shorekeeper(o0o0o34)
# 使用Carlotta函数处理
o0o0o38, o0o0o39 = Carlotta(o0o0o36, o0o0o37, o0o0o49 + 2025, o0o0o33)
# 合并结果
o0o0o40 = Kathysia(o0o0o38, o0o0o39)
o0o0o35.append(o0o0o40)
o0o0o41 = []
# 对相邻的值进行TEA加密
for i in range(8):
o0o0o35[i], o0o0o35[i + 1] = changli(o0o0o35[i], o0o0o35[i + 1], 2025)
o0o0o41.append(o0o0o35[i])
o0o0o41.append(o0o0o35[8])
return o0o0o41
def jinhsi():
"""主入口函数"""
print("请输入9个数字:")
try:
o0o0o46 = input().strip()
# 解析输入
if "," in o0o0o46:
o0o0o42 = o0o0o46.split(",")
else:
o0o0o42 = o0o0o46.split()
if len(o0o0o42) != 9:
print("错误: 需要输入9个数")
return
o0o0o43 = []
for o0o0o44 in o0o0o42:
try:
o0o0o45 = int(o0o0o44.strip())
o0o0o43.append(o0o0o45)
except ValueError:
print(f"错误: '{o0o0o44}' 不是有效的整数")
return
# 处理输入并验证
o0o0o48 = shouan(o0o0o43)
if o0o0o48 == o0o0o0:
print("正确!这是真正的key")
sys.exit(0)
else:
print("错误!这不是正确的key")
print(f"你的结果: {o0o0o48}")
sys.exit(0)
except Exception as o0o0o47:
print(f"发生错误: {o0o0o47}")
# 执行Phrolova来生成Carlotta函数
Phrolova(globals())
if __name__ == "__main__":
jinhsi()
解密后可以得到原来的key
正确的输入:
[1234, 5678, 9123, 4567, 8912, 3456, 7891, 2345, 6789]
接下来看另一个pyarmor打包的东西
在网上找到了一个好用的项目: https://github.com/Lil-House/Pyarmor-Static-Unpack-1shot
编译后使用得到代码
# File: src.py.1shot.seq (Python 3.13)
# Source generated by Pyarmor-Static-Unpack-1shot (v0.2.1), powered by Decompyle++ (pycdc)
# Note: Decompiled code can be incomplete and incorrect.
# Please also check the correct and complete disassembly file: src.py.1shot.das
'__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__(...)'
这里就很明显了,直接把key和代码丢给ai帮我们写解密脚本就好了
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]
fuck_key = [1234, 5678, 9123, 4567, 8912, 3456, 7891, 2345, 6789]
def ksa(key_bytes, key_len):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key_bytes[i % key_len]) % 256
S[i], S[j] = S[j], S[i]
return S
def modified_prga(box):
i = 0
j = 0
keystream = []
for k in range(256):
i = (i + 1) % 256
j = (j + box[i]) % 256
box[i], box[j] = box[j], box[i]
idx = (box[i] + box[j] + (k % 23)) % 256
keystream.append(box[idx])
return keystream
# 构造 key bytes(docstring 提示)
key_bytes = [x % 0xff for x in fuck_key]
box = ksa(key_bytes, len(key_bytes))
keystream = modified_prga(box.copy())
flag_bytes = []
for i in range(len(cipher)):
if i % 2 == 0:
underscore = fuck_key[i % 9]
else:
underscore = (fuck_key[i % 9] * 2) % 0xFFF
# 按字节处理:先 (keystream + _) & 0xff,再 XOR,然后取低 8 位
b = (cipher[i] ^ ((keystream[i] + underscore) & 0xff)) & 0xff
flag_bytes.append(b)
flag = bytes(flag_bytes)
print(flag.decode('latin-1')) # -> flag{8561a-852sad-7561b-asd-4896-qwx56}
easy_Tauri
程序分析
运行看到是一个2048小游戏,猜测是达到一定分数或者什么的,网上搜一下相关的材料
文章中说到tauri程序的资源是静态打包在文件中的,所以需要我们dump出来
先搜索一下一些关键词js,html,flag.然后将这里字段dump下来就可以了


[!二编]
复现的时候发现,可以直接打开devtools窗口来观察代码,这样子方便多了在程序的文件夹终端中输入: set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--disable-gpu --auto-open-devtools-for-tabs
再启动程序就可以打开devtools窗口了
还有不知道为什么不能用powershell执行,奇了怪了

分析里面的所有的的函数,可以发现主要的函数存在html_actuator.js中.
第一眼可以看到main.js
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_0x5031b3(_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 _0x9a2cbef() {
greetInputEl = document.querySelector("#greet-input");
greetMsgEl = document.querySelector("#greet-msg");
let getFlag = greetInputEl.value;
const ciphertext = Encrypt_0x5031b3("SAdt0ngY1AIrC4hH", getFlag);
greetMsgEl.textContent = await invoke("greet", {
name: uint8ArrayToBase64(ciphertext)
});
}
window.addEventListener("DOMContentLoaded", () => {
document.getElementById("check-form-7fc1a9").addEventListener("submit", (e) => {
e.preventDefault();
_0x9a2cbef();
}
);
}
);
window.addEventListener("DOMContentLoaded", () => {
document.getElementById("check-form-7fc1a9").addEventListener("submit", (e) => {
e.preventDefault();
_0x9a2cbef();
}
);
}
);
可以看到添加了监听check-form-7fc1a9的函数,但是我在文件中翻找了一下,没看见这个表单的设置,反而看到了在html_actuator.js中存在别的逻辑
window.addEventListener("DOMContentLoaded", () => {
document.getElementById("check-form").addEventListener("submit", (e) => {
e.preventDefault();
_0x9a2c6e7();
});
});
这里又监听了check-form表单,这一次可以搜索到了
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!
}
}
});
};
可以发现游戏成功的时候会建立一个check-form表单,然后触发加密函数后发向后端处理
分析发送前的函数可以发现,是通过rc4加密后base64编码后发送到后端.
密钥是
SadTongYiAiRC4HH
那么接下来应该就要分析后端的验证逻辑了,通过ida的findcrypt插件,可以找到一个base64相关的函数.或者在idc字符串搜索中搜索ipc_command就能找到这个函数.
分析得到这里的逻辑是通过魔改后的tea和base64编码后和目标比较,恢复得到jmyHBntjPmBiE9k5OTl+7WHUjc6aLHY1ZThmMWRhvUAxZIVhZn0=
import base64
DELTA = 2117703607
K0, K1, K2, K3 = 1668048215, 1949527375, 1937076784, 1432441972
def _u32(x): return x & 0xFFFFFFFF
def tea_variant_encrypt_block(block8: bytes) -> bytes:
# 读入:小端 -> v0,v1
v0 = int.from_bytes(block8[0:4], "little")
v1 = int.from_bytes(block8[4:8], "little")
sumv = 0
for _ in range(32):
v0 = _u32(v0 + ((_u32(16*v1 + K0) ^ _u32(v1 + sumv) ^ _u32((v1 >> 5) + K1))))
v1 = _u32(v1 + ((_u32(16*v0 + K2) ^ _u32(sumv + v0) ^ _u32((v0 >> 5) + K3))))
sumv = _u32(sumv + DELTA)
# 写出:大端(byteswap)
return v0.to_bytes(4, "big") + v1.to_bytes(4, "big")
def tea_variant_decrypt_block(block8: bytes) -> bytes:
# 读入:大端(密文按 byteswap 存) -> v0,v1
v0 = int.from_bytes(block8[0:4], "big")
v1 = int.from_bytes(block8[4:8], "big")
sumv = _u32(DELTA * 32)
for _ in range(32):
v1 = _u32(v1 - ((_u32(16*v0 + K2) ^ _u32(sumv + v0) ^ _u32((v0 >> 5) + K3))))
v0 = _u32(v0 - ((_u32(16*v1 + K0) ^ _u32(v1 + sumv) ^ _u32((v1 >> 5) + K1))))
sumv = _u32(sumv - DELTA)
# 写出:小端(还原原始输入字节序)
return v0.to_bytes(4, "little") + v1.to_bytes(4, "little")
def tea_variant_encrypt(data: bytes) -> bytes:
# 零填充到 8 的倍数
out = bytearray()
for i in range(0, len(data), 8):
blk = data[i:i+8]
if len(blk) < 8:
blk = blk + b"\x00"*(8 - len(blk))
out += tea_variant_encrypt_block(blk)
return bytes(out)
def tea_variant_decrypt(data: bytes) -> bytes:
assert len(data) % 8 == 0
out = bytearray()
for i in range(0, len(data), 8):
out += tea_variant_decrypt_block(data[i:i+8])
return bytes(out)
# —— 高层:name(字节) -> 常量(Base64);常量 -> name(字节) ——
def name_to_constant_b64(name_bytes: bytes) -> str:
return base64.b64encode(tea_variant_encrypt(name_bytes)).decode("ascii")
def constant_b64_to_name(const_b64: str) -> bytes:
return tea_variant_decrypt(base64.b64decode(const_b64))
decode_data=constant_b64_to_name("daF/DkQxixGmzn0aPFW2E2PhM8NabRtLjp6pI+c8TtY3WMuPxfnvlAsp9aluf8noZy/T6Sz9DJg=")
print(decode_data)
#b'jmyHBntjPmBiE9k5OTl+7WHUjc6aLHY1ZThmMWRhvUAxZIVhZn0=\x00\x00\x00\x00'
接下来就是解密前端的验证了,我先是拿标准rc4解密了一下,发现结果不对,去混淆分析后发现这个并不是标准的rc4(只能说出题人纯属闲的没事干在这个地方下坑).
解法一
还原修改后的rc4
// 还原后的逻辑:
function rc4Encrypt(key, plaintext) {
// 1. 将密钥和明文转换为字节数组
const keyBytes = new TextEncoder().encode(key);
const plaintextBytes = new TextEncoder().encode(plaintext);
// 2. 初始化状态数组S (256字节)
const S = new Uint8Array(256);
// 3. KSA - 密钥调度算法
let j = 0;
for (let i = 0; i < 256; i++) {
S[i] = i;
j = (j + S[i] + keyBytes[i % keyBytes.length]) % 256;
[S[i], S[j]] = [S[j], S[i]]; // 交换
}
// 4. PRGA - 伪随机生成算法,进行XOR加密
let i = 0, j = 0;
const ciphertext = new Uint8Array(plaintextBytes.length);
for (let k = 0; k < plaintextBytes.length; k++) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
[S[i], S[j]] = [S[j], S[i]];
const keystreamByte = S[(S[i] + S[j]) % 256];
ciphertext[k] = plaintextBytes[k] ^ keystreamByte; // XOR加密
}
return ciphertext;
}
发现在ksa阶段初始化和正常的rc4不一样
import base64
def KSA(key):
""" 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):
""" 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 加密流程 """
S = KSA(key)
keystream = PRGA(S)
res = []
for char in text:
res.append(char ^ next(keystream))
return bytes(res)
def main():
cipher = "jmyHBntjPmBiE9k5OTl+7WHUjc6aLHY1ZThmMWRhvUAxZIVhZn0="
key = "SadTongYiAiRC4HH"
cipher_bytes = base64.b64decode(cipher)
key_bytes = key.encode()
print(key_bytes, end="\n")
plain = RC4(key_bytes, cipher_bytes)
try:
print(plain.decode("utf-8"))
except UnicodeDecodeError:
print(plain.hex())
if __name__ == "__main__":
main()
# output ->
# b'SadTongYiAiRC4HH'
# flag{cf8be09b1c8a415f8b5e8f1dac71d4af}
解法二
由于rc4,或者说类rc4代码是对称的,所以直接调用前端代码就可以直接解密
(() => {
const NAME_B64 = "jmyHBntjPmBiE9k5OTl+7WHUjc6aLHY1ZThmMWRhvUAxZIVhZn0=";
const KEY = "SadTongYiAiRC4HH";
const bin = atob(NAME_B64);
console.log(bin)
const RealTE = window.TextEncoder;
class ByteTE {
encode(s) {
const u = new Uint8Array(s.length);
for (let i = 0; i < s.length; i++) u[i] = s.charCodeAt(i)%255;
return u;
}
}
window.TextEncoder = ByteTE;
const rc4 = window.Encrypt_0xa31304;
const ptBytes = rc4(KEY, bin);
window.TextEncoder = RealTE;
console.log(new TextDecoder().decode(ptBytes));
})()
// flag{cf8be09b1c8a415f8b5e8f1dac71d4af}
参考:
羊城杯WP