L3HCTF2025
TemporalParadox
拿到这道题目,我运行程序后发现报错
检查了一下我没缺少动态库就没管了,直接静态分析
主函数里面有两个分支,当程序在特定的时间点v58 > 1751990400 && v58 <= 1752052051运行时会输出query:这类东西,由t,r,cipher组成
另一个分支时,当不在时间段内就会要求用户输入然后进行md5加密和一个md5值进行比较(这里我对函数进行了重命名)你可以点进MD5那个函数看到
这是md5的特征
我们主要分析sub_140001963函数
这里有个if条件,当条件成立时输出的东西就是我们需要的明文
也就是&t=t&r=r&cipher=cipher的值
具体分析可以结合我写的注释进行代码理解
这里的salt值应该是可以直接调试出来的,但是我无法调试,就直接进行静态分析
这是salt生成的主要逻辑,以下是我编写的获取salt的脚本
1 |
|
这里的结果是tlkyeueq7fej8vtzitt26yl24kswrgm5,正好32字节
R和t以及cipher我是想着直接爆破得到
R和t这里的话没啥加密,主要是cipher生成这里有个ciphersub_14000184D函数,为了实现这一部分函数我花费了些时间,以下是函数逻辑
涉及到了两个换表
下面是我写的c语言代码整体实现sub_140001963这个关键函数
1 |
|
这边是可以正常输出在时间段内全部的数据,剩下就是进行一个比较了,这边我是因为不会写c语言的md5加密代码,我一直在找md5的头文件,一开始查到用的是openssl里面的头文件,然后我还专门下了一个openssl,但是下的最新版的运用后说vs2022已经不用了(安全问题),然后强制过掉警告又有新的报错,实在搞不下去了,我用AI直接帮我转化为python代码后进行一个md5值的比较
这边调教AI也花费了很多时间
1 | import math |
L3HCTF{5cbbe37231ca99bd009f7eb67f49a98caae2bb0f}
终焉之门
分析主函数代码
1 | __int64 sub_7FF6A04C1CF0() |
可以根据进行的函数重命名和注释来理解,这里的大概意思就是将用户输入的32字节字符串加密拼接成16进制数据存入co_consts前16元素
我们看到
这里是著加密代码点进co_consts数组可以看到前16个元素都是留空的,给了后16个元素,这里我们依旧没有找到check函数,因为这道题目是把验证逻辑放进了OpenGL Compute Shader里,也就是aVersion430Core里面,点进去可以看到
很明显,我们需要找的check函数就是void main()
1 | .data:00007FF6A04C3C2B db 'void main()',0Ah |
这是一个小型VM的实现,操作码在前面的主函数中给了就是opcode数组,我们直接编写代码将其模拟运行,进行输出就可以得到加密逻辑
以下是我写的VM运行脚本:
1 |
|
得到运行逻辑:
1 | [IP=0] stack_data[0] = co_consts[0] = 0xb0 |
这个VM只进行了异或和加减法
将用户输入加密后的前16个元素和固定的后16个元素进行混合加密后和密文cipher进行比较。现在我们有了cipher和co_consts的后十六字节,我们可以进行解密
解密分析
这里我们选择从 IP = 6进行分析,因为从IP = 0进行分析会很懵 别问我咋知道的
IP = 0这里进行的加密和IP = 6的加密一样,都是case 8
1 | [IP=6] stack_data[2] = co_consts[2] = 0xfa |
最后的结果就是cipher[2]
[IP=10] stack_data[2] = a - b = stack_data[3] - stack_data[2] = 0xffffffce = cipher[2]
这里的[IP=7] stack_data[3] = co_consts[1]就是相当于
[IP=7] stack_data[3] = cipher[1]
也就是说这里先将用户输入的第二位与密文cipher[1]进行异或后与co_consts的固定值co_consts[17]向减
好那么上面的[IP=2] stack_data[2] = co_consts[0]就相当于[IP=2] stack_data[2] = co_consts[0] = cipher[0]
后面的加法也是一样的分析,也就是未对co_consts[0]进行操作
解密代码
1 |
|
L3HCTF{df9d4ba41258574ccb7155b9d01f5c58}
easyvm(复现)
找到主函数
如果反编译是一大串函数的话就 ida 打开先设置Options->Compiler… [Compiler: Visual C++]
这边我进行一个重命名
cmp函数存放着32位的数组密文
很清晰了,我们进入VM实现函数发现有很多switch-case函数
这就是VM的handle步骤
我们找到主要操作码分类
算术运算
- 加法 (0x10)
- 减法 (0x11)
- 乘法 (0x12)
- 除法 (0x13)
- 取模 (0x14)
- 位移 (0x16左移, 0x17右移)
- 异或 (0x18)
这边的话我们可以通过条件断点的方式进行查看该vm的具体加密流程,我们可以在add,sub,xor,shl,shr进行条件断点(乘除和取余的话一般是无法逆向恢复的)
给出idapython代码(参考SU的WP)
1 | import idc, idaapi |
得到加密逻辑,取一段进行分析
1 | shl 0x31313131, 0x3 = 0x89898988 |
这边就可以通过代码一行一行进行复原,能知道是一个魔改的xtea加密,有个点要注意一下
1 | add 0x11223344, 0x376a9dbc = 0x488cd100 // 上一组sum值 |
下一组加密8字节用的sum值是上一组结束后的sum值
给出解密脚本
1 |
|
L3HCTF{9c50d10ba864bedfb37d7efa4e110bf2}