2025羊城杯

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下来就可以了

image.png

image.png

[!二编]
复现的时候发现,可以直接打开devtools窗口来观察代码,这样子方便多了

在程序的文件夹终端中输入: set WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--disable-gpu --auto-open-devtools-for-tabs

再启动程序就可以打开devtools窗口了

还有不知道为什么不能用powershell执行,奇了怪了

image.png

分析里面的所有的的函数,可以发现主要的函数存在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

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇