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}