首页
社区
课程
招聘
[原创]Armadillo_9.64加壳脱壳分析
发表于: 1天前 1527

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

1天前
1527

a 缘由

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

       

b 分析Armadillo

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


01 最低保护(最兼容)

    

① IAT表加密

        最低保护,只对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的入口,然后在堆栈窗口往回溯,找到转跳到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表信息的收集,可以写插件来自动完成,通过插件来跟踪和保存收集到的IAT信息,然后运行到OEP后,再回填到IAT表,以完成修复。

03_4_IAT

    

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

03_5_IAT

    

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

03_6_IAT

    

④ 脱壳

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

03_7_IAT


02 仅标准保护

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

    

1.0xC0000005异常

1
2
3
4
5
6
7
8
9
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异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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掉产生第一个异常的指令(如下图),然后脱壳步骤和最低保护一样。

04_C0000005


03 标准保护+检测调试器

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

05_1_je

      

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

05_2_dbg

      


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

① 分析方法

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

06_1_je

      

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

06_2_IDA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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(...); // 离开临界区
    }
}

      

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
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处。

06_sub_c0000005

     

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

06_sub_OEP

     

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

1
2
3
4
5
6
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 函数将解密后的代码写回去,去掉页保护,然后继续执行。

06_5_decode

同理,后面产生的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,完成脱壳过程。

03_1_IAT


05 高级保护

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

07_1_advanced


06 总结

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


c 声明

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


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

最后于 17小时前 被舒默哦编辑 ,原因: 更正确定OEP入口的错误。
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//