最近在跟进一个APT组织的一次攻击,其中有一个样本使用了Arm加壳,所以花了差不多10多天的时间看看这方面的东西。并总结一下。
这篇文章主要参考了FLY和刹那恍惚两位大佬的文章。和录制的视频。以及jcyhlh大侠在2008年写下的总结帖。那时候我估计还在玩泥巴呢。这是我写这篇文章的主要参考来源。前人栽树后人乘凉。此外还看了看雪的知识库。基本看了3.x和4.x所有师傅的文章。
这篇文章的架构,文章架构主要参照了网上下载的视频教程的架构。并对此作出小小修改和注释以及归纳总结。更加方便我等小白学习成长。
由于文章主要脱去的是3.x和4.x的Arm,可能有一些伪大佬又要说都发了几百遍了还在发。。对此我的处理意见是,把其直接挂在文章起始部分。
这篇文章适合我等小白,所以伪大佬勿扰。真大佬可以daidaiwo
最后,加油吧,小伙伴们。
Armadillo,中文名穿山甲,本意为犰狳,就是下面那个有点可爱的家伙。
Armadillo主要采用了Debug-Blocker,CopyMem-II, Enable Import Table Elimination,Enable Nanomites Processing,Enable Memory-Patching Protections保护手段。同时也有单双进程之分,造成了保护手段的多样性。
Debug-Blocker,称为阻止调试器,所谓反调试,基本只要开插件都可以过,所以这也是为什么大家脱穿山甲的时候打开IsProcessDebug去反调试选项和忽略异常的原因。
CopyMem-II:双进程保护,最常使用的是bp OpenMutexA
,然后转到401000 patch代码。另外一种是修改相反跳转的方法。(脚本方法就是不说了)
Enable Import Table Elimination:IAT保护,修改Magic_Jmp。
Enable Nanomites Processing就是CC保护,也是Armadillo最强大的保护机制。原理就是就是将程序中的部分代码改写为int3或者向其中插入int3代码。
知己知彼百战不殆,在脱壳最重要的就是侦壳。这里需要使用到的工具主要有:PEID(不推荐),exepeinfo,ArmaFP,任务管理器。
其中,exepeinfo是用于查壳的,任务管理器是用于判断是单进程还是双进程,如果是双进程就需要双转单。ArmaFP是用于判断其保护模式,是标准模式,还是全保护模式(专业模式)。
不过关于壳的版本,exepeinfo容易误报,所以可以使用这个方法:OD载入程序,下HE OutputDebugStringA
断点。shift+F9中断后,看堆栈如果出现如下的,就是4.0以上的壳。这是由于Arm在4.0利用Od在调式保护格式串的消息时会奔溃而新增的反调试技术。
这是最简单的加密方法,只需要修改Magic_Jmp就可以了,因为这个版本单进程防护只是加密了IAT,(1)只需要绕过加密,(2)并让其解压压缩区段即可。
绕过IAT加密的方法就是修改Magic_Jmp,这是脱穿山甲壳必须使用的方法。步骤如下:
首先需要判断加壳版本是否是4.xxx。关于这点如何判断呢,主要下硬件断点 HE OutputDebugStringA
。在堆栈窗口出现%s%s%s%s的标志,说明这是4.X的壳。
关于Armadillo v4.x单进程脱壳把握两点,第一,使用Magic_Jmp避过IAT加密保护,对GetCurrentThreadId下断点找到OEP
关于第一条,就是上面2.1讲的原则,下面解释第二条。首先对GetCurrentThreadId下断。HE GetCurrentThreadId
。查看堆栈窗口,会出现如下结果.中间省略多个,查看关于GetCurrentThreadId都是来自其他模块的调用,但是最后一个是来自程序的调用。这就是程序返回的时机,所以,F8步过,根据之前说的规则,OEP在该程序段最后一个call ecx中。
这是就比2.2多了一个密码验证,我们直接绕过密码验证就好。首先Shift+F9运行,通过查看导入表,在GetDlgItem处下断bpx GetDlgItem
。然后在输入伪码按OK,程序中断在35359D0处.注意:先运行,在下断!在输入
接着修改魔法跳,可以使用bp GetModueHandle
或者HE GetModuleHandle
。这里发现了kernel32.dll就可以执行到返回查看了。
然后就是找OEP,这里还是可以使用2.2中对GetProcessId下断。这里介绍个新方法。**可以在内存窗口.text段按F2下断点。因为壳执行完肯定会执行代码段的内容。也就是说代码段是由外壳到源程序的一扇门。所以在此处下断必然成立。
总结一下,现在有两个方法可以找OEP。第一是对GetProcessId下断,第二个就是在.text下断。
Armadillo使用Code Splicing和Import Table Elimination两项技术使得程序修复变得更加困难。幸好有大佬开发了ArmInline工具可以使得修复变得简单一些。注意:本节只将修复,不讲程序优化。
当我们寻找到OEP之后,就可以着手修复Code Splicing和Import Table Elimination了。
首先祭上大杀器ArmInline,欲要善其事,必先利其器。需要我们填写的就是上述三个区域,不过我这个版本可以自动填写修复的数据,只需要知道我们需要修复的进程,如图,目标进程ID为FC4,选中后依次删除拼接代码和巡回IAT基址。
然后按照常规的方法dump和修复IAT就可以了。注意的是使用PELord一定要勾选从磁盘粘贴文件头(一般默认勾选上了)
如果你的ArmInline不能自己修复(反正牛逼的师傅都是自己修复的,我不牛逼所以都是软件自动修复的),关于Code Splicing的修复可以这样,Alt+M到内存窗口,在fraps模块之后有一段内存没有被其他模块映射(不知道这样说对不对,反正对于Kernel32这样的dll来说肯定是对的。大家理解就好)。在最后一块内存处,就是拼接代码起点,这个值不是一个定值。(这个只是经验之谈,需要大佬解释一波的)
接着修正IAT乱序,首先随便找个函数调用,在信息窗口点数据窗口跟随地址
,然后向上拖动窗口(你最好改成显示地址)。找到IAT起始地址,然后找到结束地址,两者相减。计算大小即可。关于填充地址。可以考虑在一块没有读写的空白区域就好。不过大佬给的建议是在程序加壳前原来IAT的相近地方。可以这样寻找。Alt+M到内存窗口,因为IAT早rdata区域,又因为IAT肯定保存了一些IID成员,其中有个Name成员,也就是DllName。我们通过全局搜索确定
这一章节主要讲穿山甲的双进程保护手段。所以双进程保护,简单的来说就是创建两个进程,一个进程是另外一个进程的调试进程,又由于在R3下面一个进程只能被一个调试器附加。这样可以有效避免程序被调试。
接下来简单讲解一下关于双进程保护的原理,主要可以利用互斥体来判断进程列表是否存在相同的进程(即多开)。首先是利用CreateMutex创建一个互斥体。然后在利用OpenMutex打开那个互斥体,如果OpenMutex成功返回互斥体句柄,说明已经存在一个进程。如果不存在则在CreateProcess一个进程。而对于穿山甲壳双转单也是如此。如果提前创建了一个即将被打开的互斥体。那么程序就不会去创建新的进程。如下的脱壳方法就是基于这点考虑。
首先对openMutex下断点(HE,bp皆可)。HE openMutexA
,然后shift+F9。观察堆栈
接着需要创建互斥体。转到401000处编写汇编代码,为什么需要401000,因为这是.text段,但是理论上在哪里修改都可以。然后将EIP修改到401000处,就可以在这里执行了,然后shitf+F9.
接着就是处理加密IAT和跳转OEP,DUMP的问题了。最后到达OEP如下:
去除CopyMem-ll 保护通常有两个方法。
方法1:首先寻找OEP,然后对WaitForDebugEvent下断点bp WaitForDebugEvent
,接着运行程序,看堆栈,当出现pDebugEvent
字符的时候,选择在数据窗口跟随,然后对WriteProcessMemory下断bp WriteProcessMemory
,中断后,在数据窗口发现OEP。
这里重点讲一下第二个方法:
bp WaitForDebugEvent,shift+F9运行起来,删除断点,然后执行到程序领空,大概停在0060F8BA
然后Ctrl+F搜索命令:or eax,0FFFFFFF8
,想上看有两个比较,一个是cmp dword ptr ss:[ebp-0xA34],另外一个是cmp ecx,dword ptr ds:[0x64AF48],然后对第一个cmp下断点,F9运行。这一步你需要记住以下内容,等下patch的时候需要用到内容,第一:第一个cmp的地址0060FE43
,第二:第一个cmp【】内的值ebp-0xA34
,第三:第二个cmp【】的值:0x64AF48
.除此以外,需要将次一个cmp栈里面的数据清0
接下来patch数据,我们向下看,找到add eax,0xff
语句,在这里就可以patch了
接着shift+F9,中断在006100F8处,就可以dump处子进程了
接下来就是还原IAT
首先对DebugActiveProcess下断点BP DebugActiveProcess
这样是为了寻找子进程,在堆栈窗口发现子进程ID为D84(不定)。接着重新打开一个OD,附加子进程,然后F9+F12,中断在入口点
将死跳转字节EB FE
正常指令字节55 8B
,然后就可以执行我们上节讲的双变单了。在401000修改完双转单代码后,shift+F9跑起来,再次中断在OpenMutexA处。
带KEY的Armadillo相当于给软件多了一层保护,我们此时还不能通过爆破的方式解决这个KEY,原因有2,第一,OD对于这类情况不提供修改的选项,第二,就算爆破成功了,后期软件中还存在暗桩。所以可以逆向算法的方式得到一组合适的KEY。
首先shitf+F9运行起来,不要管出现的对话框,首先随便输入个Key,然后下HE GetDlgItem
断点即可。取消断点,ALT+F9执行返回。大概停在此处。
然后向上找,找到这个函数开始地方,也就是上一个ret的下个指令.然后下硬件执行断点。然后重新载入,shitf+F9
此时中断在之前下的执行断点处。单步走到021B4478处的第一个大跳转je 021B45F2
右键跟随。
跟随到021B45F2处,F2下断点,执行到此处,继续单步跟。
一直到021B4689处
在021982C2步入,执行到021A59FA处可以发现EAX就是硬件号3C6663B2
。接着在一个可以执行的代码段打补丁
ctrl+g,输入00401000然后输入如下内容
打好补丁后,在401000处F2下下断点,然后返回,剩下的使用上面讲的方法就可以脱去。
得鸽一下。
修改Magic_Jmp绕过IAT加密。
接下来就是和exe脱壳不一样的地方,处理重定位表。大佬这边的操作有点不明白,哪位师傅如果知道告知一下。首先对GetTickCount下硬件断点HE GetTickCount
,然后shitf+f9,观察堆栈是这个结果的话,删除断点,并返回.程序停在029AC3C8处。
然后ctrl+s,搜索,之后在找到的地址下断运行。就会出现如下黄色字体,记住标记的重定位RVA=6000和size=3B0.并将029ACFB8处跳转改为绝对跳转。
此处提供另外一种方法,首先在内存窗口,在PE文件头下访问断点,然后shift+F9中断到029A9BC7,这里是DLL文件的文件头区域
接着ctrl+s搜索如下指令
我们先来跟一下。这里是为了获取pdata,reloc
此时,当我们遍历到了reloc的时候,也就是eac为reloc的时候,在信息窗口显示的数据就是reloc的RVA=6000。size=3B0
绕过重定位表处理之后,直接在 EdrLib .text段上下F2断点,然后shitf+f9直达OEP,注意并不是在LoadDll.exe的text段,而是需要脱壳的DLL的text段下断点。
接下来就可以dump程序了,在LordPE目录中修改重定位信息。因为没有处理重定位表,所以只需要修复DLL原来的重定位表的RVA和大小就行了。
因为没有修改IAT表,所以IAT表的数据是正确的。所以直接Ctrl+M,选中.rdata处,双击,为了方便查看选择地址显示.可以判断起始地址为A24000,结束地址为A240C8,大小为C8
接下有有一个很秀的操作,将我们获得从A24000-A240C8的IAT数据,复制到新打开的notepad中的404000-4040C8处.这叫借鸡生蛋。然后在ImportRCE中IAT的RVA填写404000,大小填写C8就好了
这里使用了非常简单的Arm作为示范,只含有IAT加密,不涉及其他,第二常规方法下建议使用增强版OD。
通常规避IAT加密的方法就是Magic_Jmp,然后.text下断到OEP。这里前辈给出新方法。介绍一下。
首先须要了解到Arm并不是对所有的API函数进行加密,前辈这里的思路是先直接到达OEP,在根据里面一直的函数地址寻找IAT地址,然后寻找出IAT中被加密的地方,下硬件断点。重新运行之后到达被修改的的地址,然后分析加密IAT的过程,使用jmp或者nop规避即可。
在.text下断,然后F9运行,程序到达OEP,只是IAT被加密了。
大家应该都知道IID中所有函数应该是连续的,但是这些是不连续的,应该是被加密的。但是也说过其只是对IAT部分函数地址(有歧义自行理解)进行加密。但是IAT的RVA应该是一致的。所以将IAT的起始RVA=62E4,结束RVA=6524,大小应该为240,oep为4010cc记录下来。
我们在0040645C处下硬件断点,然后重新载入,接着按shitf+9,即可到达0218CF28处
接着往下跟,在0218CD6B处发现比较一个,可以发现第一轮他是和RegCreateKeyA比较
经过如下分析,我们可以知道0218CF1A处就是我们加密IAT的操作。同时也知道Arm只是针对部分IAT进行加密的。所以只需要修改之前在0218CD6B处的jnz short 0218CD88
,或者位于0218CF18处的jnb short 0218CF37
即可!
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)