某国赛CTF逆向题目Writeup:re2

目录

脱壳

010打开

UPX改了特征码,版本5.1.0

直接拖到ida分析不出来

这里我们选择动态调试脱壳,先找OEP,f9运行到入口

触发TLS回调函数断点,停在了ret上

64位UPX壳中不会用一个明显的跨段jmp指令跳向OEP,而是把OEP的地址压入堆栈,然后执行一条ret指令,这样CPU就会把堆栈顶部的OEP地址当作返回地址弹出来,并跳转过去

我们直接f8过去

此时进入了系统DLL地址,不用管,执行到用户代码

标准的UPX壳保存环境的方式,步过4次

此时当前的栈顶已经被修改了,内存中转到RSP,在栈顶位置下硬件断点,然后一直f9到触发硬件断点

此时正在恢复保存的寄存器状态,最后的jmp即是尾部跳转,直接f4过去,然后步过一下

可以看到已经解密完成,0x4014E0就是OEP

打开Scylla插件搜索IAT

找到两个IAT大小,因为这个exe只有几十kb,528字节的输入表正好,选择否,然后获取输入表

dump之后修复得到最终脱壳后的程序

第一层EXE

通过ida逆向

可以看到这里是获取了用户的输入并和Str2进行比较,Str 为 NGeQwv8eCRpINEcO

标准的windows可执行文件头

如果正确就会通过sub_401660函数进行base64解码并向缓存路径内写入一个随机名称的exe,直接执行原文件输入密码就能得到

第二层EXE

运行发现需要输入key

继续通过ida逆向这个文件,通过main函数找到处理逻辑(sub_401550函数)

分析可知,这里的逻辑也是接收用户输入的key,然后通过nullsub_3函数判断key是否正确

中间的do-while步骤是优化后的strlen函数实现,输入长度不超过240

现在我们的思路就是分析nullsub_3函数,不过nullsub在ida中代表为只有一个ret的空函数,说明针对反编译器做了修改,我们需要看看到底是不是真的空函数

跟进后发现是一个奇怪的hello节,应该就是题目提示了

这里我们选择通过x64dbg进行动态调试,首先加载后找到主逻辑函数地址

查找Enter the key关键字引用

ida中找到地址

输出Faild和Success中间的call就是,直接跳转然后下断点0x401623

然后一直f9运行到等待用户输入,返回窗口随便输入一串key:12345678

跟进

这里可以看到明显是有内容的,分析逻辑如下:

1、逐字节读取刚刚输入的key

2、计算离16字节的填充并补齐,经典的PKCS#7填充

3、为404CB0函数准备参数(rcx、r9、r8)

基本可以判断是AES或者SM4

运行到404F86(调用404CB0函数之前)拿到三个参数

复制代码
Key: C2 30 12 AB 39 10 18 33 F8 ED 4E 46 8D A1 5D 8D 8C FB F0 72 68 99 DC 7C 84 6E 7E CF 32 BB DA F8

IV: AE BA 0D BB CA 26 7F 99 06 ED 7C 70 E3 8D 8B 11

CipherText: 9B 5E 1E 8F D7 C3 43 62 A2 37 86 C0 CE 3D 3C F4 C3 B6 88 FF 3C 9C 13 D2 BB 6F 49 CE FF 59 A2 5C 36 E4 61 9E 60 61 C3 BB 3F 63 AF 00 3B 3D 8D A7

然而AES和SM4解密都失败了,说明题目对标准加密流程进行了魔改

跟进404CB0函数内部:

这里往栈上压入了一个参数E(十进制14),而加密轮数14的分组密码只有AES,我们上一步解密失败了,最大的可能就是魔改了标准的S盒

往下看,压栈后调用了404B60函数,直接跟进

这里就是AES的核心算法,先把内存地址 4071E0 装载到了 r14 寄存器里,然后把明文字节当做索引 rdx,去 r14 也就是 4071E0 这个地址里查表替换,这就是 AES 加密中最核心的字节替换操作,那么4071E0地址就是魔改S盒的地址

然而进去后发现就是标准的S盒

接下来就该排查行移位ShiftRows和列混淆MixColumns步骤有没有问题

这里调用了2个函数,分别跟进看一下

404070:

0, 5, 10, 15、13, 4, 9, 14、8, 13, 2, 7

正确对应行移位后的矩阵排列

404190:

0x1B 是 AES 有限域乘法的标准多项式常数,2 和 3 也是标准的乘法矩阵

到这里我们总结一下:

1、标准S盒

2、标准行移位

3、标准列混淆

还剩最后一个密钥扩展算法,这里我们想要重新分析算法就比较复杂了,但是最终是要生成好放进内存,直接在密钥生成器之后在内存中获取即可

重新运行跟进到404B60内部:

在字节替换操作之前,调用了一个404940函数,多半就是轮密钥的生成逻辑,运行到此处步过一次,而生成的轮密钥随后就被存放在了rbx寄存器指向的地址,内存窗口中找到

正好240字节

复制代码
C2 30 12 AB 39 10 18 33 F8 ED 4E 46 8D A1 5D 8D 8C FB F0 72 68 99 DC 7C 84 6E 7E CF 32 BB DA F8 B4 67 53 88 8D 77 4B BB 75 9A 05 FD F8 3B 58 70 CD 19 9A 23 A5 80 46 5F 21 EE 38 90 13 55 E2 68 58 FF 16 F5 D5 88 5D 4E A0 12 58 B3 58 29 00 C3 A7 BC F9 0D 02 3C BF 52 23 D2 87 C2 30 87 65 AA 5C B2 BA F1 89 3A E7 BF 29 28 BF 0C 71 01 BF CF 04 C0 F1 87 06 FC 4E D5 25 2E C9 17 15 A9 AC BD 9A 23 C0 A8 13 19 27 17 3A 31 98 1B 4B 30 27 D4 B7 C4 3D CF B1 38 73 1A 94 16 BA 0D 81 BF 16 B0 8B 64 27 A4 98 7D 00 B3 A2 4C 98 A8 E9 7C BF 7C A9 D4 35 DF 18 EC 46 C5 8C FA FC C8 0D 45 EA 78 E4 E3 9B 73 7C 9E 9B C0 DE D2 03 68 37 AE BC 14 33 30 50 25 2B DC 16 E0 A7 26 EA 28 AA 63 00 50 2E 80 C8 DF 52 1E 53 1F 8C CC 50 77 BB 62 EC 63

编写自定义轮密钥的解密脚本:

python 复制代码
# 自定义的轮密钥
expanded_key_hex = "C2 30 12 AB 39 10 18 33 F8 ED 4E 46 8D A1 5D 8D 8C FB F0 72 68 99 DC 7C 84 6E 7E CF 32 BB DA F8 B4 67 53 88 8D 77 4B BB 75 9A 05 FD F8 3B 58 70 CD 19 9A 23 A5 80 46 5F 21 EE 38 90 13 55 E2 68 58 FF 16 F5 D5 88 5D 4E A0 12 58 B3 58 29 00 C3 A7 BC F9 0D 02 3C BF 52 23 D2 87 C2 30 87 65 AA 5C B2 BA F1 89 3A E7 BF 29 28 BF 0C 71 01 BF CF 04 C0 F1 87 06 FC 4E D5 25 2E C9 17 15 A9 AC BD 9A 23 C0 A8 13 19 27 17 3A 31 98 1B 4B 30 27 D4 B7 C4 3D CF B1 38 73 1A 94 16 BA 0D 81 BF 16 B0 8B 64 27 A4 98 7D 00 B3 A2 4C 98 A8 E9 7C BF 7C A9 D4 35 DF 18 EC 46 C5 8C FA FC C8 0D 45 EA 78 E4 E3 9B 73 7C 9E 9B C0 DE D2 03 68 37 AE BC 14 33 30 50 25 2B DC 16 E0 A7 26 EA 28 AA 63 00 50 2E 80 C8 DF 52 1E 53 1F 8C CC 50 77 BB 62 EC 63".replace(" ", "")
iv = bytes.fromhex("AEBA0DBBCA267F9906ED7C70E38D8B11")
ct = bytes.fromhex("9B5E1E8FD7C34362A23786C0CE3D3CF4C3B688FF3C9C13D2BB6F49CEFF59A25C36E4619E6061C3BB3F63AF003B3D8DA7")

expanded_key = bytes.fromhex(expanded_key_hex)
round_keys = [list(expanded_key[i:i+16]) for i in range(0, 240, 16)]

# 标准S盒、逆S盒
SBOX = [
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
]
INV_SBOX = [0] * 256
for i in range(256): INV_SBOX[SBOX[i]] = i

# 逆向移位
def inv_shift_rows(s):
    return [
        s[0],  s[13], s[10], s[7],
        s[4],  s[1],  s[14], s[11],
        s[8],  s[5],  s[2],  s[15],
        s[12], s[9],  s[6],  s[3]
    ]

def gf_mult(a, b):
    p = 0
    for _ in range(8):
        if b & 1: p ^= a
        hi_bit_set = a & 0x80
        a = (a << 1) & 0xFF
        if hi_bit_set: a ^= 0x1b
        b >>= 1
    return p

def inv_mix_columns(s):
    out = [0] * 16
    for i in range(0, 16, 4):
        c = s[i:i+4]
        out[i]   = gf_mult(0x0e, c[0]) ^ gf_mult(0x0b, c[1]) ^ gf_mult(0x0d, c[2]) ^ gf_mult(0x09, c[3])
        out[i+1] = gf_mult(0x09, c[0]) ^ gf_mult(0x0e, c[1]) ^ gf_mult(0x0b, c[2]) ^ gf_mult(0x0d, c[3])
        out[i+2] = gf_mult(0x0d, c[0]) ^ gf_mult(0x09, c[1]) ^ gf_mult(0x0e, c[2]) ^ gf_mult(0x0b, c[3])
        out[i+3] = gf_mult(0x0b, c[0]) ^ gf_mult(0x0d, c[1]) ^ gf_mult(0x09, c[2]) ^ gf_mult(0x0e, c[3])
    return out

# AES解密
def decrypt_block(block, round_keys):
    state = list(block)
    # 第一步:异或最后一轮密钥 (Round 14)
    state = [a ^ b for a, b in zip(state, round_keys[14])]

    # 中间 13 轮逆向推导
    for round_idx in range(13, 0, -1):
        state = inv_shift_rows(state)
        state = [INV_SBOX[b] for b in state]
        state = [a ^ b for a, b in zip(state, round_keys[round_idx])]
        state = inv_mix_columns(state)

    # 最后一轮 (Round 0)
    state = inv_shift_rows(state)
    state = [INV_SBOX[b] for b in state]
    state = [a ^ b for a, b in zip(state, round_keys[0])]
    return state

# CBC 解密链
pt = bytearray()
prev = list(iv)
for i in range(0, len(ct), 16):
    block = ct[i:i+16]
    dec_block = decrypt_block(block, round_keys)
    pt_block = [a ^ b for a, b in zip(dec_block, prev)]
    pt.extend(pt_block)
    prev = block

# 去除PKCS#7填充
pad_len = pt[-1]
flag = pt[:-pad_len].decode('utf-8', errors='ignore')

print(flag)

FLAG

得到flag:dart{c3d4f5cc-8aab-46ce-a188-2fc453f3b288}

相关推荐
努力的lpp1 天前
墨者学院登录密码重置漏洞分析溯源wp
网络·网络安全·ctf
浏览器API调用工程师_Taylor2 天前
web逆向之小红书无水印图片提取工具
前端·javascript·逆向
昊色居士3 天前
从 APK 到完整 API:逆向工程 JavDB 移动端全过程
人工智能·逆向
见青..3 天前
[BUUCTF]Misc篇wp
网络安全·ctf·misc·buuctf
xin^_^3 天前
PolarD&N[web困难]部分题解
ctf
小猪弟4 天前
【app逆向】某壳逆向的wll-kgsa参数,signature参数
python·逆向·wll-kgsa·signature·
ShoreKiten4 天前
SSTI专题(持续更新)
web·ctf·ssti·模板注入