recall复现
main函数中要求输入24字节的字符串,被分为六个dword(就是六个三十二位整数),再将其分为三组,每组两个整数使用xxtea加密算法。第一组:加密v0和v1(主线程加密前),第二组:加密v3和v2(子线程中间),第三组:加密v4和v5(子线程结束后,回到主线程)。这三组加密完后和dword41E048的值比较。
在tlscallback0里面分析出修改后的密钥值(十六进制,不知道为什么我的ida显示的是十进制)。这里要注意修改密钥值的顺序是按照程序实际运行的过程发生的。
过程 首先程序启动即调用tls,此时触发进程启动,key0被改(第一组加密),然后主线程加密v0和v1,创建子进程时就是触发线程创建进入case2,此时key2被改(第二组加密),密钥随着也改,然后子线程运行,加密了v2和v3.子线程结束即触发线程结束进入case3,此时key3被改当然密钥也改了(第三组加密)。最后加密剩下的v4v5。那么此时就疑问了,tls四个事件还有一个进程退出呢?这个事件所修改的key1是等加密都加密完了才修改的,根本没有用。那么分析完了,现在只差delta值(tea系列算法中的核心常数)在tls中也规定了,我们应该选用无调试器的那个,因为我们没有用调试器。
ok,此时就能写脚本解密了
import struct
# 三组密钥(根据TLS回调顺序推导)
keys = [
[0x386EA53B, 0xD7E2667D, 0xC38166DB, 0x2913A100], # 第一组
[0x386EA53B, 0xD7E2667D, 0x291E3726, 0x2913A100], # 第二组
[0x386EA53B, 0xD7E2667D, 0x291E3726, 0x88A3F735] # 第三组
]
# 目标密文(从 dword_41E048 提取)
cipher = [
0x2D66FD90, 0xF6FB537A, # 第一组
0xE32FCE6D, 0x07248633, # 第二组
0xDF96A0AD, 0x65E18188 # 第三组
]
# delta值(正常运行时)
delta = 0x88A3F735
def xxtea_decrypt_pair(v0, v1, key, delta):
"""
XXTEA解密一对32位整数
:param v0: 第一个整数
:param v1: 第二个整数
:param key: 4个32位整数的密钥列表
:param delta: delta值
:return: 解密后的两个整数
"""
n = 2
rounds = 6 + 52 // n # n=2时,rounds=32
sum_val = (delta * rounds) & 0xFFFFFFFF
y = v0
for _ in range(rounds):
e = (sum_val >> 2) & 3
# 解密第二个整数
z = v0
v1 = (v1 - ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^
((sum_val ^ y) + (key[(1 ^ e) & 3] ^ z)))) & 0xFFFFFFFF
y = v1
# 解密第一个整数
z = v1
v0 = (v0 - ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^
((sum_val ^ y) + (key[(0 ^ e) & 3] ^ z)))) & 0xFFFFFFFF
y = v0
sum_val = (sum_val - delta) & 0xFFFFFFFF
return v0, v1
def main():
print("[*] SCTF2025 逆向题 - 'Tea' 解密脚本")
print("[*] 三组加密使用不同的密钥(TLS回调修改)")
# 解密三组数据
plain = []
for i in range(3):
p0, p1 = xxtea_decrypt_pair(cipher[i*2], cipher[i*2+1], keys[i], delta)
plain.append(p0)
plain.append(p1)
print("\n解密后的6个整数(十六进制):")
for i, val in enumerate(plain):
print(f" [{i}] = {hex(val)}")
# 将整数按小端序转换为字节序列(共24字节)
flag_bytes = b''.join(struct.pack('<I', val) for val in plain)
print(f"\n原始字节(hex): {flag_bytes.hex()}")
# 尝试解码为ASCII字符串
try:
flag_str = flag_bytes.decode('ascii')
if all(32 <= b < 127 for b in flag_bytes):
print(f"\nFlag: {flag_str}")
else:
# 可能有不可见字符,显示可打印部分
printable = ''.join(chr(b) if 32 <= b < 127 else '.' for b in flag_bytes)
print(f"\n可打印字符: {printable}")
except UnicodeDecodeError:
# 尝试UTF-8解码
try:
flag_str = flag_bytes.decode('utf-8', errors='ignore').strip()
if flag_str:
print(f"\nUTF-8解码(忽略错误): {flag_str}")
except:
pass
# 检查常见flag格式
if b'{' in flag_bytes and b'}' in flag_bytes:
start = flag_bytes.find(b'{')
end = flag_bytes.find(b'}')
if start < end:
potential = flag_bytes[start:end+1]
print(f"\n疑似Flag格式: {potential}")
# 验证:用解密后的数据重新加密,确认是否与目标密文匹配
print("\n[*] 验证解密结果...")
def xxtea_encrypt_pair(v0, v1, key, delta):
"""XXTEA加密一对整数(用于验证)"""
n = 2
rounds = 6 + 52 // n
sum_val = 0
y = v0
for _ in range(rounds):
sum_val = (sum_val + delta) & 0xFFFFFFFF
e = (sum_val >> 2) & 3
# 加密第一个整数
z = v1
v0 = (v0 + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^
((sum_val ^ y) + (key[(0 ^ e) & 3] ^ z)))) & 0xFFFFFFFF
y = v0
# 加密第二个整数
z = v0
v1 = (v1 + ((((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^
((sum_val ^ y) + (key[(1 ^ e) & 3] ^ z)))) & 0xFFFFFFFF
y = v1
return v0, v1
for i in range(3):
e0, e1 = xxtea_encrypt_pair(plain[i*2], plain[i*2+1], keys[i], delta)
if e0 == cipher[i*2] and e1 == cipher[i*2+1]:
print(f" 第{i+1}组验证成功")
else:
print(f" 第{i+1}组验证失败")
print(f" 期望: {hex(cipher[i*2])}, {hex(cipher[i*2+1])}")
print(f" 实际: {hex(e0)}, {hex(e1)}")
if __name__ == "__main__":
main()
Flag: ISCTF{Y9r_gO0D@_Tl5_T3A}
TLS学习
TLS(Thread Local Storage,线程局部存储)回调函数(CallBack Function),就是在程序主入口/主函数执行之前和线程创建/销毁时由操作系统调用的函数,
执行时机:加载程序,执行回调函数,进入主函数。并会在1进程启动时(DLL-PROCESS-ATTACH) 2线程创建时(DLL-THREAD-ATTACH) 3线程结束时(DLL-THREAD-DETACH) 4进程退出时*(DLL-PROCESS-DETACH)被调用,类比这道题目就是:第一组加密经历case1(进程启动),第二组加密经历case1、2(线程创建,第三组加密经历case1、2、3(线程结束)
作用1反调试,比如这道题可以在程序开始前检测调试器然后修改delta值(但是od等调试器一般都有反反调试好像) 2混淆代码,在主函数之前执行一些混淆操作,让静态分析的代码和运行时不同。
有tls的pe文件中TLS相关信息存储在IMAGE-TLS-DIRECTORY结构体中,该结构体的位置由IMAGE-OPTION-HEADER中的IMAGE-DATA-DIRECTORY TLSDirectory指定。想要添加tls就是先扩展pe文件最后一个节区的大小,然后增加目录表创建结构体,接着设置 AddressOfCallBacks 指向回调函数地址,最后根据地址编写函数
0 条评论