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 指向回调函数地址,最后根据地址编写函数

分类: study

0 条评论

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注