DubheCTF 2024 re 复现
DubheCTF re
VMT
有非常多的反调试,因此直接使用x32dbg+sharpOD过反调,中间发现一个类似Key的东西
输入aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,运行到这key变了,且输出,好像发现我patch过程序了
key肯定是这俩中的一个
这个有点像密文
这个函数是个加密算法
cyberchef试了几种加密算法发现是SM4直接出了
DubheCTF{VMT_H00K_4ND_$3H_15_U53FUL}
1 | from gmssl.sm4 import CryptSM4, SM4_DECRYPT, NoPadding |
从其它佬的wp发现了个floss工具
提取字符串,可以发现是SM4了
也可以不用插件,直接patch,这里就不展示patch了,问GPT就行,注意patch完别用IDA调试还会有问题,还有就是patch的时候注意栈平衡可能也会引发问题
有些反调试,不需要强行去掉,如果调试器支持硬件断点/线程恢复(如 x64dbg),影响不大。
patch完大概长这样:
Destination
main看着很简单,调试看看,单步运行。发现会在程序进入main前的j__initterm退出,应该是个反调试
C++ 程序启动 在进入main()前 会 调用两次 _initterm, 在 main() 结束后 还会调用 两次 _initterm
- 第一次 _initterm 初始化 C 环境(与多线程有关)
- 第二次 _initterm 创建 C++ 全局变量, 如果是自定义类型, 会 调用 atexit() 传入 全局对象的析构函数, 注: atexit 内维护的是栈, 不是队列, 先传入的函数 会 后调用
- main() 执行
- 处理 atexit 中的函数
- 第三次 _initterm do pre terminators
- 第四次 _initterm do terminators
直接patch掉
=运行会发现,最后判断时输入会被加密,并且这里布过 __debugbreak () 函数会触发一个软件断点异常。
跟进看看
存在花指令
1 | .text:0041411C add dword ptr [esp+0], 5 |
下个断点调试看看,然后使用IDA trace,可以把运行过的高亮标起来
接下来总结了几种方法:
调试时,发现了花指令和混淆代码
测试输入:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
只有mov是真正要执行的命令,下面的为花指令
下面的jmp指令只是为了衔接程序其实并没有多大意义,混淆一下
下面那个也是混淆指令:
基本上一大块只有一句是真正要执行的指令
法一:手动trace+自己(GPT)分析
1 | 004140D7 | push ebp |
….
比较费时间
法二:IDA手动摘出每一句patch
一个有效指令,然后提取出来机器码,最终将所有的机器码提取出来,再patch进去。再P+F5即可
也挺麻烦的
…
法三:IDC脚本去花
但是无法F5
官解的wp的去花脚本有点问题,
应该是这样才对
1 | if(Byte(eip) == 0xE8 && Byte(eip+1) == 0x00 && Byte(eip+2) == 0x00 && Byte(eip+3) == 0x00 && Byte(eip+4) == 0x00) |
1 |
|
出现这样的问题,不知道怎么解决
法四:
x32dbgtrace:
单步trace把跟踪的结果导出为txt,写脚本过滤
1 | with open('./1.txt','rb+') as f: |
可以得到:
1 | push ebp |
可以判断这是一个xxtea算法,轮次为11,delta为0x5B4B9F9E
结合其中几个地址:00415B41 | mov esi,dword ptr ds:[esi*4+42309C] |
可以猜到这可能是key
我们动调可以发现push ebp经过两次,说明加密了两次
让GPT还原下代码:
1 | #include <stdint.h> |
直接写解密代码
解出来一堆乱码
1 |
|
猜测密文可能有点问题,交叉引用了下
发现
连GPT都看不懂做了什么,跟我说无法逆向
得知这里运用了天堂之门的的技术,通过修改 CS 寄存器实现 32 位到 64 位代码的切换,用于反调试(无法跟进)
天堂之门 (WoW64 技术) 总结及 CTF 中的分析 - CTF 对抗 - 看雪 - 安全社区 | 安全招聘 | kanxue.com
[原创] 天堂之门 (Heaven’s Gate) C 语言实现 - 软件逆向 - 看雪 - 安全社区 | 安全招聘 | kanxue.com
我们可以用 x32dbg 再打开程序
难怪运行时候总是显示:
调用了这个dll
我们只需要将opcode dump出来然后patch到64位程序中,再去生成函数即可还原
1 | code = [0x55, 0x8B, 0xEC, 0x81, 0xEC, 0xC0, 0x0, 0x0, 0x0, |
Online x86 and x64 Intel Instruction Assembler
再去IDA试试
其实就是
1 | // 每个 32-bit 数据(12 个)执行如下循环: |
像一个CRC,首先判断符号位,如果为1,先左移一位,在异或。如果不为1,则直接左移一位。这里的异或数据末尾为1,所以和左移一位后(最低位为0)的数据异或后末尾为1。直接左移的末尾为0。可以根据此将32位还原
1 |
|
最终代码:
1 |
|
DubheCTF{82e1e3f8-85fe469f-8499dd48-466a9d60}
ezVK
Vulkan 是一种现代、高性能的图形和计算 API,由 Khronos Group 开发。它被设计为跨平台的解决方案,适用于高效利用 GPU 的应用程序,例如游戏、虚拟现实、3D 渲染和科学计算。
1 | int __fastcall main_0(int argc, const char **argv, const char **envp) |
CTF 比赛常用 GPU 做验证计算,增加逆向难度。可以考虑对 Vulkan shader 部分进行提取和分析,或用 vulkan SDK 做运行态 hook/debug。
Shader(着色器)是一种在图形处理单元(GPU)上运行的程序,它负责对图形进行渲染处理,或者执行并行计算任务。简单来说,它是一段专门为 GPU 编写的代码,用来告诉 GPU 如何处理输入数据并输出图像或计算结果。
需要反编译Shader
SPIR-V(Standard Portable Intermediate Representation - Vulkan)是由 Khronos Group 制定的一种中间语言,它是专门为 GPU 编程设计的,用于表示 Shader 和计算内核程序。
就像 CPU 的汇编语言一样,SPIR-V 是 GPU 的“字节码”或“汇编语言”。
找到了GitHub - KhronosGroup/SPIRV-Cross:SPIRV-Cross 是一个实用的工具和库,用于对 SPIR-V 执行反射并将 SPIR-V 反汇编回高级语言。这个工具
resorce hacker导出shader
1 |
|
1 | #include <iostream> |
最后一个字节为0,被遮掩了,需要爆破,注意长度为42也就是我们只需要爆破两字节即可,但是最后一位肯定是}其实我们只需爆破一个字节
l的变化与r有关,因此我们只需要爆破最后与r比较即可
相同的逻辑
1 |
|
其它方法动调,也可以得到shader.bin
1 | import idaapi |
fffffragment
参考了星盟的wp,奈何后半段代码写的有点抽象,我优化了下DubheCTF2024 Writeup - 星盟安全团队
这个调用…
找了下没有so层,只能这样分析了
当前是做了混淆,putString每次都会打印出东西来
跟进去
发现有两个选择,共同点是putString后会到下一个class
整体思路是一路追踪字符串在Bundle中的传递和拼接,还原出最终的明文数据。
我们发现无论分支怎么走都在package p013II111;
搜索Wrong和congraluate,eh0的上一个是q70,也就是说当到了这样一条线肯定是失败了,其实下面的脚本光q70就够了,因为q70的上游是有多个,eh0的上游只有q70
jadx导出工程,这里是要到Congratulations这里,2个按钮分为向左向右走,最终走到Congratulations这里,手搓函数太多了不太可能
写一个脚本追踪一下路径
1 | import os |
找到这样一条路径
1 | found |
接下来我们把putString搞出来
1 | import os |
导入工程,然后手修一下错误就行
1 | for i in range(1,257): |
1 | package out_java; |
1 | 71_FITz728fUFH3VM4mtnQCyf7mnFUkjUcpJER982SfiR_zAhsVTzUNh4mdEvoyqYMyoMt0WXSa2UX2SUNrm4YLz5k_DegEeO7RuK_SOfAwetgoXc4iGvpd9BYix9JpCAuuK7CXDppzvWHvWnw_5AmjRl_O4JXfqJ27Pwg8nHCawmSfna5KxHexQtmvqxsGetjFQ7eYueBCBd_7hsmQXrYolPhI39Q6FRrSHUSKs8AiLxdd3lqFfTl8Qvvpwt0aO7FTiNN0T3olLllHy026HCczgMc8Dt4HEZZ1zUjZ5AoeP5XkIts9H6Tda_cBKNKnaahj_FTs0FKqI0rUaClVgfvKDetQxDT7WOSONEQpqwU8MjCOG58GEv5dJevHnRnqFVAXn_dkXO3nwK7QVI6KuUoxge0csUHy3lor2V5pXWTv3goKrngMNCiUerF9V_NXoTWAWdZr_pHaAOPhQgmeyj1a08HRaaQdhyfiGa8IFV8wCJ_RVL2Bco2Ztu6AbyeQJjPDHbmjLQzJ0lCTI0bFO5f0F6xOaGDqsDqRDmWIBkVCrzRCxQNMxWQdylrwAtjlIo9wcs2_XqdO5_35nfjSuL_mY1lxhkrmWlHYFllXzNJBO8dxmlCQTOubH1F8snqTNvdU36b2aWKYPGRnMlboNEiX8xkht7qR3vSW_INOt_TpxiYvE5xdYUJk9armmk1CtpEYGqUx3sz2k6M3zB6qNNTW4cLsAJAkykhznpehsoBEvyIDP5hMJDSgf6utz |
最后sha1一下
1 | import hashlib |
flag:DubheCTF{20c21afe96f05b02430a017a550bfce5addb6fe2}
还有其他方法:JEB脚本提取
此脚本适用于jeb3,python2,修改麻烦所以没有试
1 | # ?description= |
手撕方案(这个太狠了😂)
1 | ch0 |
moon
详细见:DubheCTF-Destination&Moon | Clovershrub
个人不是很懂密码
sub_140001780是高精度快速幂,sub_140001000是高精度乘法,sub_140001260是高精度除法,xmmword_140007848是一个大质数,Src是做除法时生成的余数。最后可以得到程序在干这么个事:
没看懂这里的数字为啥是反的?
1 | prime=1537131588382913092665966115381275601741897676956736016043055688971156548045659189201332511868437849089 |
交给gemini-pro求解
1 | import itertools |
1 | Found candidate_a: 9282518933443361561931517206943216160800270539664959226902371097049976661617888730966203839253721281 |