首页
社区
课程
招聘
[原创]Armadillo_9.64加壳脱壳分析
发表于: 2024-11-23 00:01 4286

[原创]Armadillo_9.64加壳脱壳分析

2024-11-23 00:01
4286

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

       

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

    

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

03_1_IAT

    

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

     1.函数名称。

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

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

    

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

03_2_IAT

​     

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

03_3_IAT

    

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

    

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

02_2_findOEP

02_3_findOEP

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

03_4_IAT

    

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

03_5_IAT

    

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

03_6_IAT

    

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

03_7_IAT

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

    

1.0xC0000005异常

    

2.0xC0000096异常

    

3.0xc000001d异常

    

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

04_C0000005

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

05_1_je

      

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

05_2_dbg

      

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

06_1_je

      

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

06_2_IDA

      

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

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

     

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

06_sub_c0000005

     

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

06_sub_OEP

     

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

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

     

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

06_5_decode

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

      

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

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

03_1_IAT

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

07_1_advanced

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

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

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
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
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
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
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                                                 |
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                                                 |
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:
            {
                // 这里面处理了调试事件类型的代码为36的事件,
                // 3是创建进程调试事件,6是加载dll调试事件。
            }
        }
         
        ContinueDebugEvent(...); // 继续调试事件
        LeaveCriticalSection(...); // 离开临界区
    }
}
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: // 处理退出进程调试事件
            {
                ...

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-11-23 13:27 被舒默哦编辑 ,原因: 更正确定OEP入口的错误。
收藏
免费 2
支持
分享
最新回复 (5)
雪    币: 175
活跃值: (2566)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
这个壳现在用的貌似比较少了。
2024-11-25 16:56
0
雪    币: 50161
活跃值: (20550)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
3

这个版本是不是破解不完全?高级保护几个选项将保护提高到一个新的难度,比如Nanomites,外壳将一些字节换成INT 3,运行时触发异常再解码处理。

最后于 2024-11-27 17:23 被kanxue编辑 ,原因:
2024-11-27 17:21
0
雪    币: 3990
活跃值: (5121)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
4
kanxue 这个版本是不是破解不完全?高级保护几个选项将保护提高到一个新的难度,比如Nanomites,外壳将一些字节换成INT 3,运行时触发异常再解码处理。
应该是,我也不太熟悉,拿着这个壳子就想分析
3天前
0
雪    币: 3990
活跃值: (5121)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
5
xingbing 这个壳现在用的貌似比较少了。
嗯,老壳子了,很经典。
3天前
0
雪    币: 4978
活跃值: (4753)
能力值: ( LV10,RANK:171 )
在线值:
发帖
回帖
粉丝
6
感谢分享!
3天前
0
游客
登录 | 注册 方可回帖
返回
//