首页
社区
课程
招聘
[原创]2024年KCTF水泊梁山-WriteUp-反混淆
发表于: 2024-8-30 23:48 4961

[原创]2024年KCTF水泊梁山-WriteUp-反混淆

2024-8-30 23:48
4961

签名:水泊梁山.zZhouQing

日期:2024年8月30日

 今天正式成年,开心。想起之前答应坛友 mb_mgodlfyn 说在赛期结束前会提供一份反混淆代码,其实我这俩天一直没关注这个问题,怕他被气到,于是有了本篇随笔,希望各位看的愉快。O(∩_∩)O

 首先,这道题内容一般,所以我会结合《加密与解密(第四版)》的内容讲一些基础的东西。帮助没有基础的朋友过渡一下。幸运地是,我也正好忘记掉这道题是如何编写的了。(相信你会学明白的,因为我也是个小菜)

 可以看到,程序是添加混淆了的,有着俩个区段,分别为 obfobf。(其中一个用于存储数据,一个用于执行代码)

 可以看到,程序的混淆与栈相勾连。但是,我们的经验可以告诉我们,这个混淆似乎仅仅只是做了指令变形,而无代码乱序的功能。这对于反混淆来说,是一个非常重要的消息。

 我们已经知道了混淆并无代码乱序的功能(代码乱序会使线性扫描算法出现错误),于是,我们对应的反汇编算法既可以是 线性扫描算法,也可以是 递归行进算法。出于方便,这里使用线性扫描算法。

 这一技术,应该常见于早期的 花指令 对抗,混淆通过添加基于固定模式生成的花指令达到抵御静态分析的效果,而反混淆(机器码层的特征码匹配)则将混淆中的固定模式转化为字节形式的模板,通过特征匹配,将对应的代码删除,达到反混淆的目的。

 在这里,由于技术过于古老,反混淆效果不太好,我仅作一个示例。发现程序具有如下特征,提取特征码,编写脚本进行反混淆。

 由于使用的是 memset 命令来去除花指令,而该命令作用后,并不会将修改结果保存到 补丁 当中。所以,这里使用 scylla(x64dbg 配套插件)来保存修改后的程序。

 Dbg 将程序停在 EP (EntryPoint)的位置,scylla 依次点击 Dump -> PE Rebuild

 可以看到,经过修改后的程序能够正常运行,同时程序的硬盘占用大大减小。

 由于这里仅出于演示目的,我并未考虑将那样特征的汇编删去有何后果,很有可能是不会正常运行的。但,反混淆,就应该多尝试,甚至不需要程序能够正常运行,毕竟时间有限,能将关键代码显现出来即可。

 这一技术,常见于早期 VM 对抗的 handle 匹配或是常见密码学的特征匹配。我看到坛友 tacesrever 就是通过这一方式来反混淆的,但是效果不佳。

 这里举个例子,我们将这样的特征匹配为 VADD_Q。

 当然,这一技术不仅仅是化简程序的流程,还可以用来还原本题的 CF(控制流:control flow)。IDA 打开经过第一次修改的程序 kctf_crackme_sbls_dump.exe

 可以看到,有个立即数 sub_A41F0D,IDA 转过去发现,正是下一个基本块。而 dword_4AA6100Ah 进行异或,值是 0042A9E0 ,IDA 转过去发现,是 _alloca_probe 函数。

 看样子我们可以知道了,混淆对 Call Imm 的指令做了变形,导致 IDA 的控制流分析失败了。利用 python 编写个简单的脚本还原即可。还记得上文提到的 线性扫描算法 吗?就是从一个 base 开始死循环反汇编,脚本自己会异常打印日志的。

 由于生成的去混淆脚本内容较长,这里不贴出了。

 这一个脚本还不足以还原完整的 CF ,因为 Call 指令分为四种类型。(这里并未使用 intel 指令语法)

 提取三次汇编特征,编写三个脚本就能还原,有了第一次的经验,复制粘贴一下就行了。经过三次修复,IDA 已经能够正常显示 CFG 了。

 本想写到这里便停下的,不过,看到 tacesrever 通过汇编匹配来还原原始的汇编指令,我也写个脚本来做个简单的化简好了。IDA 将程序 main 函数反编译后,可以发现这样形式的全局变量。这个全局变量有俩次读,一次写的操作。

 而读操作却是这样的,这样的汇编指令可以直接化简为 push eax。看样子。这样的全局变量可以直接删除了。

 又浏览了几个全局变量,发现还有这样形式的,这样的全局变量似乎是参与运算了,保留即可。

 编写脚本,做第六次反混淆操作,依旧是套用第二次的模板。(第一次为机器码匹配)

 这次生成的反混淆脚本巨大,X64Dbg 的补丁窗口会卡死,所以通过 scylla 这一插件进行保存修改。

 测试下修改结果,发现程序正常执行,继续玩下去。

IDAPython 提供了操作 AST 的接口,这里的 AST 对应结构为 ctree,可以在 idapython_docs 当中查看相关信息。

ctree 中的节点称为 citem_t,而 citem_t 这一抽象结构可以具体分化为 cinsn_tcexpr_t。(cinsn_t 的结构中包含 cexpr 结构)

 同时,IDAPython 提供了 ctree_visitor_t 类,通过继承该类,重写特定函数(visit_insnvisit_expr)达到对 ctreecurd 操作。

 接下来做几个基本的演示。

 通过观察反编译结果,发现如下特征代码。这些代码对于我们分析程序来说,是没有用处的,毕竟谁会去在意 eflags 呢?

 我们使用 HRDevHelper 提取对应表达式的结构,这个结构用于判断是否是垃圾代码。

 编写如下代码,即可去除 ctree 中特定类型的指令。

 IDA 执行脚本后,发现如下报错,这是没有及时更新函数内容导致的。

 解决方案:修改下函数名,让 IDA 更新函数内容。

 可以看到,IDA 中的反汇编结果已经没有了那串垃圾代码。

 在上图当中,我们发现,有太多全局变量的操作了,影响我们分析,干脆将 表达式中第一个操作数为全局变量 的指令全部删掉。它们的值无需关心,因为在这样情况下的静态分析,你已无法清晰地知道程序的动态结果。(若要用,跑个 Trace 补上就好)

 在 isjunk 函数中添加如下逻辑即可:

 运行脚本,并手动更新下函数。怎样,程序逻辑是不是一下子就明晰了?

 我印象里我的程序里并未定义啥全局变量,所以没有判断全局变量的地址是否在混淆范围内。(应该是有定义的,我想说些闲话)

 至此,我们可以开始愉快地做题啦,考虑到实际做题时间的问题,懒得继续优化下去了。(从反混淆的第一步到现在的第八步,不会花多长时间的,预计不超过三小时)

 Dbg 创建程序运行起来,输入如下内容(不要着急按下回车开始验证身份)。

 Dbg 转到内存窗口,给最后一个段下 内存执行 断点。

 程序断在这个位置,IDA 转过去。

 发现程序最后调用了 sub_A6322D 函数,这个函数内容有点大,如出现 function is too big 的问题,请查阅附录中的 IDA问题解决->function is too big

 在我观察 main 函数的时候,发现程序调用了 std::cout,扫下它的引用看看。

 发下这段代码很可疑,X64Dbg 转过去看看,注意下 &unk_42C4DE 是什么。

 哈,看样子我们很幸运,这下子我们知道程序是如何判断 flag 的了。

 由于 IDA 并未将 *(_DWORD *)(a1 + 116)*(_DWORD *)(*(_DWORD *)(a1 + 68) + 8) 识别为变量,故采取复制内容到 VsCode 进行分析的方案。Ctrl+F 搜索 *(_DWORD *)(a1 + 116) 发现其有俩个引用,好消息,这意味着 *(_DWORD *)(*(_DWORD *)(a1 + 68) + 8) 很有可能为 const_flag。(经过搜索,发现其的确只引用了一次)

 寻找将这条语句包裹起来的代码块,即寻找 if 语句。

 途中发现这样的垃圾代码,编写脚本删掉这个 citem_t 即可。

 发现其被包裹在这样的语句中,在这些代码中寻找我们的 特殊性,这是我们思考问题的方法。这 PAIR64 函数未免太特殊了,得多注意。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2024-9-8 15:41 被kanxue编辑 ,原因:
上传的附件:
收藏
免费 4
支持
分享
最新回复 (6)
雪    币: 256
活跃值: (674)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2

-----------------------------

(感谢学长给我从实验室拿了一台玩~)

最后于 2024-9-17 10:34 被zZhouQing编辑 ,原因:
2024-8-31 01:09
0
雪    币: 11
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
新手表示看不懂
2024-9-28 18:27
0
雪    币: 256
活跃值: (674)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
5
mb_vgbsclvi 新手表示看不懂
那可以去试试做《加密与解密(第四版)》中的随书附件,这个简单。
2024-10-10 12:43
0
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6

capston和keystone库怎么导入呢,我直接导入都报错,博主可以写一篇文章分享一下吗?目前导入keystone显示的是"attempted relative import with no known parent package"

最后于 2024-10-26 11:13 被王氏国信编辑 ,原因:
2024-10-26 11:12
0
雪    币: 256
活跃值: (674)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
7
王氏国信 capston和keystone库怎么导入呢,我直接导入都报错,博主可以写一篇文章分享一下吗?目前导入keystone显示的是"attempted relative ...

不好意思,才看到消息,按照正常的情况,我应该会发你一份我翻译好的 《IDAPython 实用文档》,不过资料存在旧电脑里,今年是拿不到了。你的报错,我似乎复现不了,你可以按照我的流程来安装,我用的是新电脑,在我写这篇文档之前,都是没有 IDA 的。(如果还不能解决,只好加下我的好友,我发你一份 docker 镜像了。还有,你是否安装成 keystone 而非 keystone-engine)

IDAPython install keystone-engine

 先通过 idapyswitch.exe 检查下当前的环境(爱盘下载的 IDA8.3):

1
2
3
4
5
D:\HackyTOOL\IDA8.3>idapyswitch.exe
IDA previously used: "D:\HackyTOOL\IDA8.3\python311\python311.dll" (guessed version: 3.11.7 ('3.11.7150.1013')). Making this the preferred version.
The following Python installations were found:
    #0: 3.11.7 ('3.11.7150.1013') (D:\HackyTOOL\IDA8.3\python311\python3.dll)
Please pick a number between 0 and 0 (default: 0)

 到 python311 目录下,命令行输入以下命令(更新 pip 包管理器):

1
python.exe -m pip install --upgrade pip

 到 python311/Scripts 目录下,命令行输入以下命令(安装 keystone-engine 包):

1
2
# 注意!不是 pip install keystone
pip install keystone-engine

 到 IDAPython 界面执行一段 keystone docs 提供的代码(IDA 反汇编窗口按下 Shift+F2 打开 IDAScript 窗口):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 注意将 Script Language 设置成 Python 而非 IDC
from keystone import *
 
print('this is keystone example')
 
# separate assembly instructions by ; or \n
CODE = b"INC ecx; DEC edx"
  
try:
   # Initialize engine in X86-32bit mode
   ks = Ks(KS_ARCH_X86, KS_MODE_32)
   encoding, count = ks.asm(CODE)
   print("%s = %s (number of statements: %u)" %(CODE, encoding, count))
except KsError as e:
   print("ERROR: %s" %e)

 命令行输出效果:

1
2
this is keystone example
b'INC ecx; DEC edx' = [65, 74] (number of statements: 2)

IDAPython install capstone

 按照上述的操作,到 python311/Scripts 目录下,命令行安装 capstone:

1
2
# 我用 pip install capstone 安装失败了,所以添加个代理源
pip install capstone -ihttps://mirrors.aliyun.com/pypi/simple

IDAPython 运行一段 Python tutorial for Capstone 提供的代码:

1
2
3
4
5
6
7
8
# test1.py
from capstone import *
 
CODE = b"\x55\x48\x8b\x05\xb8\x13\x00\x00"
 
md = Cs(CS_ARCH_X86, CS_MODE_64)
for i in md.disasm(CODE, 0x1000):
    print("0x%x:\t%s\t%s" %(i.address, i.mnemonic, i.op_str))

 命令行输出结果:

1
2
0x1000: push    rbp
0x1001: mov rax, qword ptr [rip + 0x13b8]

附录

keystone-docs
Python tutorial for Capstone

最后于 2024-10-28 01:11 被zZhouQing编辑 ,原因:
2024-10-28 00:57
0
游客
登录 | 注册 方可回帖
返回
//