专栏名称: 看雪学苑
致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号。
目录
相关文章推荐
中国汽车动力电池产业创新联盟  ·  解读丨关于《中国禁止出口限制出口技术目录》中 ... ·  昨天  
中国汽车动力电池产业创新联盟  ·  解读丨关于《中国禁止出口限制出口技术目录》中 ... ·  昨天  
江苏新闻广播  ·  江苏大学:已报案! ·  2 天前  
江苏新闻广播  ·  江苏大学:已报案! ·  2 天前  
51好读  ›  专栏  ›  看雪学苑

Armadillo_9.64加壳脱壳分析

看雪学苑  · 公众号  · 互联网安全  · 2025-01-03 17:58

主要观点总结

文章详细分析了Armadillo壳的脱壳过程,包括脱壳步骤、处理IAT表和子程序运行的流程。文章强调了逆向或破解他人软件的行为是不道德的,并呼吁尊重他人的劳动成果和权益。同时,提供了作者舒默哦的ID信息和一些相关推荐文章。

关键观点总结

关键观点1: Armadillo壳的脱壳过程

文章详细分析了Armadillo壳的脱壳过程,包括最低保护、仅标准保护、标准保护+检测调试器、双进程+检测调试器(最高保护)等不同情况下的处理。并介绍了IAT表的处理方法和子程序运行的流程。

关键观点2: 道德和权益的呼吁

文章强调逆向或破解他人软件的行为是不道德的,呼吁尊重他人的劳动成果和权益,并强调软件开发人员的艰辛与挑战。盗版软件的泛滥会严重打击软件开发和维护人员的信心,可能导致产品的流产。

关键观点3: 作者舒默哦的ID信息和推荐文章

文章最后提供了作者舒默哦的ID信息和一些相关推荐文章,包括往期推荐的精华文章。


正文




缘由

写此帖的原因在于,许多论坛关于 Armadillo 脱壳的讨论往往只讲解脱壳步骤,而不涉及原理。即使有些帖子提到原理,内容也常常遵循安全人员的脱壳经验,这对初学者并不友好。Armadillo 壳属于强壳,但它是一款较老的壳,似乎已经很久没有更新了。之前只找到 9.64 的汉化版,而没有找到更高版本,因此我将分析这个版本。对于初学者,建议准备一个没有任何插件的 x32dbg,先尝试自己进行分析,然后再参考下面的分析过程。

       





分析 Armadillo

Armadillo_9.64版本的汉化版界面如下:




01 最低保护(最兼容)

① IAT表加密

最低保护,只对IAT表进行了加密。可以与源文件对比,找到IAT表的首地址,下硬件写入断点。壳程序会在断点位置写入两次,第二次才是真正填写IAT表的地址。


    

接下来收集IAT表的信息,需要三个要素:

1.函数名称。

2.函数所在的dll地址(也可以称dll句柄)。

3.最后需要回填的地址。

    

经过多次调试,可以在add edx,4这条指令处下断,以收集IAT的信息。当断点停在这里时,eax寄存器中储存的就是IAT地址,函数名称位于堆栈的[esp-8]位置,而DLL句柄则储存在局部变量[ebp-0x2948]中。


    

其中,定位dll的地址,需要往前面看,dll地址的关键信息,在以下位置:


    

② 确定OEP入口

通过和源文件对比,来确定OEP的入口,然后在堆栈窗口往回溯,找到转跳到OEP的CALL(如下图),记录下图所选择的特征码(8B 55 F4 2B 55 DC FF D2 89 45 FC EB 48)。


    

搜索的时机是在VirtualAlloc函数下断,当遇到申请大小为0x200000时,返回到用户代码并运行到0x43F756这个位置。在0x3EA0000地址(即刚刚申请的0x200000大小的区域)中搜索 OEP 入口的特征码,就能够找到。


③ 修复IAT表

IAT表信息的收集,可以写插件来自动完成,通过插件来跟踪和保存收集到的IAT信息,然后运行到OEP后,再回填到IAT表,以完成修复。


    

因为是断点事件,所以插件的回调函数,其类型为断点回调(CB_BREAKPOINT)。开启跟踪后,收集的IAT信息会被存放到申请的缓存中,程序最终会停在OEP处。此时,查看IAT表,可以看到些地址加密了。


    

再点击回填IAT,以完成修复:


    

④ 脱壳

此时,可以使用x32dbg自带的插件Scylla来完成脱壳。需要注意的是,有些导入表地址是无效的,通过和源文件对比,这些地址是多余的,直接cut掉即可。




02 仅标准保护

勾选仅标准保护,调试的时候,会依次产生如下异常:

    

1.0xC0000005异常


    push ebp
mov ebp,esp
push ecx
mov dword ptr ss:[ebp-4],0
mov eax,dword ptr ss:[ebp-4]
mov byte ptr ds:[eax],0 ; eax值为0,执行到这里会造成0xC0000005异常
mov esp,ebp
pop ebp
ret

    

2.0xC0000096异常


    push ebp
mov ebp,esp
push ecx
push ebx
mov byte ptr ss:[ebp-1],0
mov eax,564D5868
mov ebx,0
mov ecx,A
mov edx,5658
in eax,dx ;in指令是特权指令,只能在0环使用。执行到这里会造成0xC0000096异常
cmp ebx,564D5868
jne 58BD6AA
mov byte ptr ss:[ebp-1],1
mov al,byte ptr ss:[ebp-1]
pop ebx
mov esp,ebp
pop ebp
ret

    

3.0xc000001d异常


004667F0 | 55                    | push ebp                                            |
004667F1 | 8BEC | mov ebp,esp |
004667F3 | 53 | push ebx |
004667F4 | B8 2D684600 | mov eax,huffmancoding.46682D |
004667F9 | 68 2D684600 | push huffmancoding.46682D |
004667FE | 64:FF35 00000000 | push dword ptr fs:[0] |
00466805 | 64:8925 00000000 | mov dword ptr fs:[0],esp |
0046680C | BB 00000000 | mov ebx,0 |
00466811 | B8 01000000 | mov eax,1 |
00466816 | 0F | ??? |;未知指令,造成了0xc000001d异常
00466817 | 3F | aas |
00466818 | 07 | pop es |
00466819 | 0B36 | or esi,dword ptr ds:[esi] |
0046681B | 8B0424 | mov eax,dword ptr ss:[esp] |
0046681E | 64:A3 00000000 | mov dword ptr fs:[0],eax |
00466824 | 83C4 08 | add esp,8 |
00466827 | 85DB | test ebx,ebx |
00466829 | 74 1A | je huffmancoding.466845 |
0046682B | EB 1C | jmp huffmancoding.466849 |
0046682D | 8B4C24 0C | mov ecx,dword ptr ss:[esp+C] |
00466831 | C781 A4000000 FFFFFFF | mov dword ptr ds:[ecx+A4],FFFFFFFF |
0046683B | 8381 B8000000 04 | add dword ptr ds:[ecx+B8],4 |
00466842 | 33C0 | xor eax,eax |
00466844 | C3 | ret |

    

直接nop掉产生第一个异常的指令(如下图),然后脱壳步骤和最低保护一样。




03 标准保护+检测调试器

标准保护加检测调试器,实际上是开启了双进程保护的。可以把仅标准保护的文件和此文件对比着一起调试,很容易发现关键指令je huffmancoding.44056F,跳过去就不会执行双进程策略。因此,在跳过去后,其脱壳步骤和仅标准保护一样。


      

关于检测调试器,有两个地方进行了相关检查:一个是直接调用IsDebuggerPresent,另一个是通过FS寄存器来判断是否存在调试器。然而,调试器的检测也包含在双进程保护策略中。如果前面的检查已经跳过,则无需再进行处理。


      


04 双进程+检测调试器(最高保护)

① 分析方法

根据上一节的方法,直接跳过je huffmancoding.44056F,无法有效阻止双进程保护的策略,后面会造成异常,导致程序崩溃。通过条件判断的来源,可以确认0x004F4838是一个全局变量,似乎是一个用于启用各种保护策略的结构体地址。值得注意的是,这些保护策略的启用或禁用并非简单的true或false,而是经过特定算法处理的开关。


      

正确的做法是不要跳过,而是直接分析创建子进程后的调试流程。因为被保护程序的代码是在子进程中运行的。可以逐步调试并跟踪,或在WaitForDebugEvent函数处下断,以找到关键函数sub_426A50。该函数可以通过 IDA 进行查看,如下所示:



sub_426A50函数执行的主要流程:

while(...)
{
if(WaitForDebugEvent(...)) // 等待调试事件
{
EnterCriticalSection(...); //进入临界区

switch(...)
{
case 1: // 处理异常事件
{
if(异常码 == 0x80000001)
{
...
sub_428370(...) // 代码解密的关键函数
...
}
else if(异常码 == 0xC0000005)
{
...
}
else if(异常码 == 0x80000003)
{
...
}else{
...
}

break;
}

case 2: // 处理创建线程调试事件
{
...
break;
}

case 4: // 处理退出线程调试事件
{
...
break;
}

case 5: // 处理退出进程调试事件
{
...
break;
}

case 8: // 输出调试字符串信息
{
...
break;
}

default:
{
// 这里面处理了调试事件类型的代码为3和6的事件,
// 3是创建进程调试事件,6是加载dll调试事件。
}
}

ContinueDebugEvent(...); // 继续调试事件
LeaveCriticalSection(...); // 离开临界区
}
}

      

在x32dbg中,可以通过脚本来记录所有调试事件,获取的信息如下:


1.捕获创建进程信息(3) -> 恢复线程
2.捕获load-dynamic-link-library(DLL)调试事件(6) -> ReadProcessMemory,读取进程内存
3.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄0x110
4.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄0xFC
5.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄0x2D8

---
多次捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
---

6.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄0x188
7.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄0x308
8.捕获异常信息(1)->0x80000003,断点异常,填写一个未知数组的值
9.捕获退出线程事件(4)
10.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄

---
多次捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
---

---
多次捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄
---

11.捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
12.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄
13.捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
14.捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
15.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄
16.捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
17.捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
18.捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
19.捕获退出线程事件(4)
20.捕获output-debugging-string调试事件(8) -> ????
21.捕获output-debugging-string调试事件(8) -> ????

---
多次捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
---

22.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄
23.捕获退出线程事件(4)
24.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄
25.捕获退出线程事件(4)

26.捕获异常信息(1)->0xc0000005,访问了未被允许的内存区域,造成异常的具体位置如下:

push ebp
mov ebp,esp
push ecx
mov dword ptr ss:[ebp-4],0
mov eax,dword ptr ss:[ebp-4]
mov byte ptr ds:[eax],0 ; eax值为0,执行到这里会造成0xC0000005异常
mov esp,ebp
pop ebp
ret


27.捕获异常信息(1)->0xc00000096,使用了非法指令,具体位置如下:

push ebp
mov ebp,esp
push ecx
push ebx
mov byte ptr ss:[ebp-1],0
mov eax,564D5868
mov ebx,0
mov ecx,A
mov edx,5658
in eax,dx ;in指令是特权指令,只能在0环使用
cmp ebx,564D5868
jne 58BD6AA
mov byte ptr ss:[ebp-1],1
mov al,byte ptr ss:[ebp-1]
pop ebx
mov esp,ebp
pop ebp
ret


28.捕获异常信息(1)->0xc000001d,使用了非法指令,具体位置如下:


004667F0 | 55 | push ebp |
004667F1 | 8BEC | mov ebp,esp |
004667F3 | 53 | push ebx |
004667F4 | B8 2D684600 | mov eax,huffmancoding.46682D |
004667F9 | 68 2D684600 | push huffmancoding.46682D |
004667FE | 64:FF35 00000000 | push dword ptr fs:[0] |
00466805 | 64:8925 00000000 | mov dword ptr fs:[0],esp |
0046680C | BB 00000000 | mov ebx,0 |
00466811 | B8 01000000 | mov eax,1 |
00466816 | 0F | ??? |;未知指令,造成了0xc000001d异常
00466817 | 3F | aas |
00466818 | 07 | pop es |
00466819 | 0B36 | or esi,dword ptr ds:[esi] |
0046681B | 8B0424 | mov eax,dword ptr ss:[esp] |
0046681E | 64:A3 00000000 | mov dword ptr fs:[0],eax |
00466824 | 83C4 08 | add esp,8 |
00466827 | 85DB | test ebx,ebx |
00466829 | 74 1A | je huffmancoding.466845 |
0046682B | EB 1C | jmp huffmancoding.466849 |
0046682D | 8B4C24 0C | mov ecx,dword ptr ss:[esp+C] |
00466831 | C781 A4000000 FFFFFFF | mov dword ptr ds:[ecx+A4],FFFFFFFF |
0046683B | 8381 B8000000 04 | add dword ptr ds:[ecx+B8],4 |
00466842 | 33C0 | xor eax,eax |
00466844 | C3 | ret |


---
多次捕获0xC0000005异常
---

---
多次捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
---

---
多次捕获0xC0000005异常
---

---
多次捕获load-dynamic-link-library(DLL)事件(6) ->加载系统dll
---


29. 捕获unload-DLL调试事件(7)

30.捕获创建线程调试事件(不包括进程的main线程)(2) -> 获取线程句柄


31.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x411023
32.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x413190
33.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x412DE0
34.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x414740
35.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x415C80
36.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x416039

---
捕获其他事件
---


每次调试记录这些调试事件时,可能会有所不同,但这并不影响后续的分析。

     

可以先尝试在第一次捕获0xC0000005异常时脱离进程,然后附加子进程。这样做的原因是,这个异常是在填写 IAT 表之前产生的,因此可以记录 IAT 信息。附加子进程后,可以看到程序停在了0x5A38A7E处。


     

直接将指令mov byte ptr ds:[eax], 0替换为 NOP,然后恢复主线程。在经过0xC000001D0xC00000096两个异常后,程序在0x411023处再次触发0x80000001页保护异常(如下图所示)。可以看到,这一页全是乱码,并不是正常的代码。这表明在0x411023触发页保护异常后,调试的主程序经过一些操作后将源代码拷贝到此处,并去掉了页保护,随后继续运行。


     

从上述脚本记录的异常事件中,可以看到捕获到的0x80000001异常包括以下内容:


31.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x411023
32.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x413190
33.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x412DE0
34.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x414740
35.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x415C80
36.捕获异常信息(1)->0x80000001, 访问标记了页保护的内存区域时,会触发此异常。异常位置:0x416039


通过观察可以看到,第一次触发该异常时,就是OEP位置所在。

     

接下来,我们将分析代码解密的部分,重点关注关键函数 sub_428370。该函数调用了 sub_428620,而关键逻辑则位于 sub_428620 中。首先,该函数会拷贝子程序中触发异常位置的整个代码页。经过两次解密后,它最终调用 WriteProcessMemory 函数将解密后的代码写回去,去掉页保护,然后继续执行。



同理,后面产生的0x80000001页保护异常,也以同样方式处理。

      

② 脱壳步骤

通过以上分析,我们已经了解了子程序运行的整个流程,因此脱壳的过程并不复杂。

可以编写插件来实现脱壳,程序需要运行两遍。第一遍用于收集解密后的代码,并将数据存储到申请的缓存中;第二遍则在0xC0000005异常处断下,然后附加子程序以完成脱壳。具体步骤如下:


1.首先要过掉检测调试器。


2.其次,定位到在sub_426A50函数。在0x80000001异常处下断,断下后,在WriteProcessMemory处再次下断,收集解密后的代码数据,然后结束调试。


3.重新开始调试,在0xC0000005异常处下断,断下后,将子进程的所有线程挂起,然后脱离调试,附加子进程到当前调试器。


4.程序会停在mov byte ptr ds:[eax], 0这条指令处,使用 NOP 指令替换这条指令,并在 IAT 表的首地址设置硬件写入断点,然后恢复主线程。


5.停在 IAT 表第二次断下的地方(如下图所示),将解密后的代码拷贝到相应位置。如果停在 OEP 入口时再进行拷贝,可能会导致 OEP 入口的第一个字节无法拷贝,那么就需要手动修复了。


6.然后收集 IAT 表的信息,运行到 OEP,修复 IAT 表数据。


7.最后,使用 Scylla 插件进行 Dump 和 Fix Dump,完成脱壳过程。




05 高级保护

高级保护的所有选项,开启或者关闭,都没有作用,选项是失效的!因此,不做分析。




06 总结

此壳的最大难点在于双进程保护,只要妥善处理这一部分,其余的就相对简单了。其次,对于 IAT 表的处理,采用的是暴力跟踪的方法。在未找到关键点或加解密算法较为复杂的情况下,使用暴力跟踪来处理 IAT 表是一个有效的策略。






声明

逆向或破解他人软件的行为是极其不道德的,这不仅缺乏对他人劳动成果的尊重,也损害了开发者的权益。作为一名软件开发人员,我深知软件开发过程中的艰辛与挑战。盗版软件的泛滥会严重打击软件开发和维护人员的信心,最终可能导致产品的流产。如果有任何侵权行为,请立即通知我,我将删除此帖。





看雪ID:舒默哦

https://bbs.kanxue.com/user-home-877885.htm

*本文为看雪论坛精华文章,由 舒默哦 原创,转载请注明来自看雪社区


# 往期推荐

1、Hyper-V拒绝服务漏洞CVE-2024-43633分析

2、PWN入门:三打竞态条件漏洞-DirtyCOW

3、细说软件保护

4、强网杯S8决赛PWN-赛题解析

5、强网杯2024 ez_vm 手撕VM + DFA Attack Whitebox AES





球分享

球点赞

球在看



点击阅读原文查看更多

推荐文章
江苏新闻广播  ·  江苏大学:已报案!
2 天前
江苏新闻广播  ·  江苏大学:已报案!
2 天前
最搞笑笑话王  ·  这个小品,忍不住看了3遍,笑喷我了!
7 年前
全球局势战略纵横  ·  道教、佛教与基督教、伊斯兰教的区别!
7 年前