Mysoft 发表于 2006-11-13 09:14:17

ASPack 2.12 外壳流程分析 + DLB原理


标 题: 【原创】ASPack 2.12 外壳流程分析 + DLB原理
作 者: SigCell
时 间: 2006-11-11,14:20
链 接: http://bbs.pediy.com/showthread.php?threadid=34754
整理了一篇,马上发上来,我算是怕了。发上估计是最安全了。
http://bbs.pediy.com/images/smilies/cool.gif

写的时候是HTML形式,拷贝到这里后,格式和色彩标记都会去掉。
着色的还是看附件好了。
=========================================================================
【文章标题】: ASPack 2.12 外壳流程分析 + DLB原理
【作者声明】: 初学脱壳,没什么好东西。个人的一些经验,希望与大家分享,错误之处,欢迎指正!
【测试软件】:Notepad + ASPack
--------------------------------------------------------------------------------
【详细过程】
从脱壳的角度来说ASPack 1.08.03, 2.11 和 2.12 这3个版本都非常相似。
在流程分析中,我只对2.12作了详细的分析,其他两个也就没必要了。
————————————————————脱壳流程(ASPack 2.12)————————————————————
01013000    90            nop
01013001 >60            pushad
01013002    E8 03000000   call    0101300A                      ; 进
01013007- E9 EB045D45   jmp   465E34F7
0101300C    55            push    ebp
0101300D    C3            retn
0101300E    E8 01000000   call    01013014                      ; 进
01013013    90            nop                                 ; 花指令
01013014    5D            pop   ebp                           ; Notepad_.01013013
01013015    BB EDFFFFFF   mov   ebx, -13
0101301A    03DD            add   ebx, ebp
0101301C    81EB 00300100   sub   ebx, 13000
01013022    83BD 22040000 0>cmp   dword ptr , 0
01013029    899D 22040000   mov   , ebx
0101302F    0F85 65030000   jnz   0101339A                      ; 长跳转跟随
01013035    8D85 2E040000   lea   eax,
0101303B    50            push    eax
0101303C    FF95 4D0F0000   call   
01013042    8985 26040000   mov   , eax
01013048    8BF8            mov   edi, eax
0101304A    8D5D 5E         lea   ebx,
跟随后来到:
0101339A    B8 9D730000   mov   eax, 739D                     ; 鼠标点击这一行,F4
0101339F    50            push    eax
010133A0    0385 22040000   add   eax,
010133A6    59            pop   ecx
010133A7    0BC9            or      ecx, ecx
010133A9    8985 A8030000   mov   , eax                ;执行这一行之后,OEP值出现
010133AF    61            popad
010133B0    75 08         jnz   short 010133BA               
010133B2    B8 01000000   mov   eax, 1
010133B7    C2 0C00         retn    0C
010133BA    68 00000000   push    0                           ; OEP
010133BF    C3            retn
用LordPE 来DUMP,直接ImportRec修复即可
(1)与1.08.03相比较没有多大变化,只是在壳入口处增加了花指令及近跳转变形CALL。
(2)长跳转依然存在,脱起来还是这么方便。
步骤总结:直接在壳入口处找到jnz的一个长跳转,跟随,OEP就在下面几行了。
脱这个壳没有多大意义,
---------------------------------ASPack 2.12 完全分析 --------------------------
首先观察区块表:
http://bbs.pediy.com/upload/2006/8/image/9.jpg

3个经过压缩的原始区块,".text",".data",".rsrc".
".aspck"块为壳的代码块
".adata"块为壳的数据块
OD载入:
01013001 >60             pushad
01013002    E8 03000000    call    0101300A            ; 进
01013007- E9 EB045D45    jmp   465E34F7
0101300C    55             push    ebp
0101300D    C3             retn
0101300E    E8 01000000    call    01013014            ; 进
01013013    EB 5D          jmp   short 01013072      ; 花指令
01013015    BB EDFFFFFF    mov   ebx, -13
0101301A    03DD         add   ebx, ebp
0101301C    81EB 00300100sub   ebx, 13000
01013022    83BD 22040000 >cmp   dword ptr , >
01013029    899D 22040000mov   , ebx
0101302F    0F85 65030000jnz   0101339A            ; 跟随
01013035    8D85 2E040000lea   eax,
0101303B    50             push    eax                   ; 字符串"kernel32.dll"
0101303C    FF95 4D0F0000call                 ; CALL kernel32.GetModuleHandleA
01013042    8985 26040000mov   , eax
01013048    8BF8         mov   edi, eax
0101304A    8D5D 5E      lea   ebx,
0101304D    53             push    ebx                   ; 字符串“VirtualAlloc”
0101304E    50             push    eax
0101304F    FF95 490F0000call                 ; CALL kernel32.GetProcAddress
01013055    8985 4D050000mov   , eax
0101305B    8D5D 6B      lea   ebx,
0101305E    53             push    ebx                   ; 字符串“VirtualFree”
0101305F    57             push    edi
01013060    FF95 490F0000call                 ; CALL kernel32.GetProcAddress
01013066    8985 51050000mov   , eax
0101306C    8D45 77      lea   eax,
0101306F    FFE0         jmp   eax                   ; Notepad_.0101308A
取得VirtualAlloc和VirtualFree地址,分别保存在和位置
0101308A    8B9D 31050000mov   ebx,       ; 跳转到这里(有花指令)
01013090    0BDB         or      ebx, ebx
01013092    74 0A          je      short 0101309E      ; 跳
01013094    8B03         mov   eax,
01013096    8785 35050000xchg    , eax
0101309C    8903         mov   , eax
0101309E    8DB5 69050000lea   esi,       ; esi = 0X101357C,指向原始区块信息(数据窗口跟随esi,如下图所示)
010130A4    833E 00      cmp   dword ptr , 0    ;
010130A7    0F84 21010000je      010131CE            ;
010130AD    6A 04          push    4
010130AF    68 00100000    push    1000
010130B4    68 00180000    push    1800                  ; size = 0x1800,(*".data"段长度?*)
010130B9    6A 00          push    0
010130BB    FF95 4D050000call                 ; CALL kernel32.VirtualAlloc
010130C1    8985 56010000mov   , eax      ; EBP = 0x930000:缓冲区起始地址(块一)
http://bbs.pediy.com/upload/2006/8/image/8.jpg

图中蓝色部分数据,显然为3个区块的起始地址和长度(看到这些数据,比较敏感)。经过进一步分析,如0x0000B568,虽然不是".rsrc"块的起始地址,但在".rsrc"内。且0XB568 + 0X7A98 = 0X8000,刚好与".rsrc"块的长度相同,这也就说明ASPack在对资源段进行压缩时并不是从起始位置开始的,而是压缩距离资源段偏移位置(0X568--0X8000)部分。

下面就进入了区块解压部分(在本例中下面这段代码会执行3次,每一次执行意味着解压一个区块):
010130C7    8B46 04      mov   eax,           ; 取得当前要解压块长度,保存在eax
010130CA    05 0E010000    add   eax, 10E            ; eax += 0x10E
010130CF    6A 04          push    4
010130D1    68 00100000    push    1000
010130D6    50             push    eax                   ; 缓冲区长度
010130D7    6A 00          push    0
010130D9    FF95 4D050000call                 ; CALL kernel32.VirtualAlloc,分配解压缓冲区
010130DF    8985 52010000mov   , eax      ; EBP = 0x940000(解压缓冲区起始地址)
010130E5    56             push    esi
010130E6    8B1E         mov   ebx,
010130E8    039D 22040000add   ebx,
010130EE    FFB5 56010000push    dword ptr    ; 参数4:缓冲区一起始地址
010130F4    FF76 04      push    dword ptr    ; 参数3:解压区块数据长度
010130F7    50             push    eax                   ; 参数2:解压缓冲区起始地址
010130F8    53             push    ebx                   ; 参数1:解压区块起始地址
010130F9    E8 6E050000    call    0101366C            ; 取得解压数据保存在解压缓冲区中
010130FE    B3 01          mov   bl, 0               ; 这条指令的第二操作数会变化(如果是代码段为1,其他为0)
01013100    80FB 00      cmp   bl, 0
01013103    75 5E          jnz   short 01013163      ; 事实上,当前不是代码段时,会跳过下面这段蓝色代码
01013105    FE85 EC000000inc   byte ptr
0101310B    8B3E         mov   edi,
0101310D    03BD 22040000add   edi,
01013113    FF37         push    dword ptr        ;干扰指令(这里开始的4个指令)
01013115    C607 C3      mov   byte ptr , 0C3   ; 把0x1001000处的第一字节改为0XC3,即ret指令
01013118    FFD7         call    edi                   ; 变形CALL,相当于无效指令(edx = 0X1001000)
0101311A    8F07         pop   dword ptr
0101311C    50             push    eax
0101311D    51             push    ecx
0101311E    56             push    esi
0101311F    53             push    ebx
01013120    8BC8         mov   ecx, eax
01013122    83E9 06      sub   ecx, 6
01013125    8BB5 52010000mov   esi,
0101312B    33DB         xor   ebx, ebx
0101312D    0BC9         or      ecx, ecx
0101312F    74 2E          je      short 0101315F
01013131    78 2C          js      short 0101315F
01013133    AC             lods    byte ptr
01013134    3C E8          cmp   al, 0E8
01013136    74 0A          je      short 01013142
01013138    EB 00          jmp   short 0101313A
0101313A    3C E9          cmp   al, 0E9
0101313C    74 04          je      short 01013142
0101313E    43             inc   ebx
0101313F    49             dec   ecx
01013140^ EB EB          jmp   short 0101312D
01013142    8B06         mov   eax,
01013144    EB 00          jmp   short 01013146      ; 无效指令,相当于NOP
01013146    803E 06      cmp   byte ptr , 6
01013149^ 75 F3          jnz   short 0101313E
0101314B    24 00          and   al, 0
0101314D    C1C0 18      rol   eax, 18
01013150    2BC3         sub   eax, ebx
01013152    8906         mov   , eax
01013154    83C3 05      add   ebx, 5
01013157    83C6 04      add   esi, 4
0101315A    83E9 05      sub   ecx, 5
0101315D^ EB CE          jmp   short 0101312D
0101315F    5B             pop   ebx
01013160    5E             pop   esi
01013161    59             pop   ecx
01013162    58             pop   eax
01013163    EB 08       jmp    short 0101316D      ;不是代码段时跳到这里
01013165    0000         add   , al
01013167    94             xchg    eax, esp
01013168    0000         add   , al
0101316A    90             nop
0101316B    90             nop
0101316C    90             nop
0101316D    8BC8         mov   ecx, eax
0101316F    8B3E         mov   edi,
01013171    03BD 22040000add   edi,       ; edi 指向区块起始地址
01013177    8BB5 52010000mov   esi,       ; esi 指向解压缓冲区
0101317D    C1F9 02      sar   ecx, 2                ; ecx 保存该区块解压数据的长度
01013180    F3:A5          rep   movs dword ptr es:, dword ptr    ; 区段数据覆盖(对0x1001000设置内存写入断点,第3次中断来到这里,因为前面的一个干扰指令会在这个地址写入ret和擦除。中断后要立即撤消该断点)
01013182    8BC8         mov   ecx, eax
01013184    83E1 03      and   ecx, 3                ; 为了保证4字节对齐
01013187    F3:A4          rep   movs byte ptr es:, byte ptr
01013189    5E             pop   esi
0101318A    68 00800000    push    8000
0101318F    6A 00          push    0
01013191    FFB5 52010000push    dword ptr    ; push 0x940000,释放解压缓冲区
01013197    FF95 51050000call                 ; CALL kernel32.VirtualFree
0101319D    83C6 08      add   esi, 8                ;取下一个区块信息
010131A0    833E 00      cmp   dword ptr , 0    ; 检测下一区块信息是否为空
010131A3^ 0F85 1EFFFFFFjnz   010130C7            ; 不为空则跳转(在本例中跳转3次)
010131A9    68 00800000    push    8000                  ; 一旦到了这里,就表示解压完成
010131AE    6A 00          push    0
010131B0    FFB5 56010000push    dword ptr    ; push 0x930000,释放第一个缓冲区
010131B6    FF95 51050000call                 ; CALL kernel32.VirtualFree
010131BC    8B9D 31050000mov   ebx,
010131C2    0BDB         or      ebx, ebx
010131C4    74 08          je      short 010131CE      ; 跳
010131C6    8B03         mov   eax,
010131C8    8785 35050000xchg    , eax
010131CE    8B95 22040000mov   edx,       ; edx = 0x1000000,基地址
010131D4    8B85 2D050000mov   eax,       ; eax = 0x1000000,基地址
010131DA    2BD0         sub   edx, eax
010131DC    74 79          je      short 01013257      ; 跳
到这里外壳部分已经对软件的3个区块进行了解压
下面开始进入的IAT的还原部分
01013257    8B95 22040000mov   edx,          ; edx = 0x1000000
0101325D    8BB5 41050000mov   esi,          ; esi = 0
01013263    0BF6         or      esi, esi
01013265    74 11          je      short 01013278         ; 跳
01013278    BE 04760000    mov   esi, 7604                ; 7604为输入表的偏移地址(壳备份)
0101327D    8B95 22040000mov   edx,          ; edx = 0x1000000
01013283    03F2         add   esi, edx               ; esi = 0x1007604指向备份输入表
010132858B46 0C       mov    eax,          ; ImportTable->dwDllNameOffset(外循环起始)
01013288    85C0         test    eax, eax               ; 测试esi所指向的输入表项是否为空(即DLL是否处理完了)
0101328A   0F84 0A010000 je    0101339A            ; 若为空,就跳出(跳出外循环)
01013290    03C2         add   eax, edx               ; DLL名字的偏移地址+基地址=虚拟内存地址
01013292    8BD8         mov   ebx, eax
01013294    50             push    eax                      ; eax指向DLL名字的字符串
01013295    FF95 4D0F0000call                    ; CALL kernel32.GetModuleHandleA
0101329B    85C0         test    eax, eax
0101329D    75 07          jnz   short 010132A6         ; 取得DLL模块句柄成功,则跳转
0101329F    53             push    ebx
010132A0    FF95 510F0000call   
010132A6    8985 45050000mov   , eax         ; 把dll模块地址保存在
010132AC    C785 49050000 >mov   dword ptr , 0   ; 保存着地址表的偏移地址
010132B68B95 22040000 mov    edx,       ; edx 为基地址(内循环起始)
010132BC    8B06         mov   eax,                ; eax 为IT->OriginalFirstThunk表的偏移地址
010132BE    85C0         test    eax, eax               ; 测试DLL的IT->OriginalFirstThunk项是否为空
010132C0    75 03          jnz   short 010132C5         ; 若不为空则跳转
010132C2    8B46 10      mov   eax,             ; IT->OriginalThunk项为空,eax为IT->FirstThunk
010132C5    03C2         add   eax, edx               ; eax = eax + 基地址
010132C7    0385 49050000add   eax,          ; eax = eax + 地址表偏移地址
010132CD    8B18         mov   ebx,                ; ebx为OriginalFirstThunk表中的一个值(表示当前dll中的某个函数的地址)
010132CF    8B7E 10      mov   edi,             ; edi为IT->FirstThunk表的偏移地址,即IAT表
010132D2    03FA         add   edi, edx               ; edi = edi + 基地址
010132D4    03BD 49050000add   edi,          ; eax = eax + 地址表偏移地址
010132DA    85DB         test    ebx, ebx               ; 如果ebx=0,意味当前dll的所有函数入口地址都已经处理完了
010132DC    0F84 A2000000je      01013384
010132E2    F7C3 00000080test    ebx, 80000000
010132E8    75 04          jnz   short 010132EE
010132EA    03DA         add   ebx, edx               ; ebx = ebx + 基地址
010132EC    43             inc   ebx
010132ED    43             inc   ebx
010132EE    53             push    ebx
010132EF    81E3 FFFFFF7Fand   ebx, 7FFFFFFF
010132F5    53             push    ebx                      ; ebx指向函数名
010132F6    FFB5 45050000push    dword ptr       ; 为dll模块句柄
010132FC    FF95 490F0000call                    ; CALL kernel32.GetProcAddress
01013302    85C0         test    eax, eax
01013304    5B             pop   ebx
01013305    75 6F          jnz   short 01013376         ; 取函数地址成功,就跳转
……(忽略一段除错处理)
01013376    8907         mov   , eax               ; 跳到这里,把所取得的函数地址存在IAT中
01013378    8385 49050000 >add   dword ptr , 4   ; 下一个函数,增加偏移量
0101337F^ E9 32FFFFFF jmp   010132B6             ; 继续找函数地址(内循环尾部)
01013384    8906         mov   , eax
01013386    8946 0C      mov   , eax
01013389    8946 10      mov   , eax
0101338C    83C6 14      add   esi, 14                  ; esi指向输入表的下一个表项
0101338F    8B95 22040000mov   edx,
01013395   E9 EBFEFFFF jmp   01013285          ; 继续枚举dll输入表项(外循环尾部)
上面的双重循环是用来恢复IAT的,工作流程如下所示:
for ( 每一个输入表项 )
{
for ( 每一个函数 )

取函数地址
写入IAT

}
跳出上面的双重循环后,来到这里:
0101339A    B8 9D730000    mov   eax, 739D                ; 0x739D为OEP偏移
0101339F    50             push    eax
010133A0    0385 22040000add   eax,          ; eax = eax + 基地址
010133A6    59             pop   ecx                     
010133A7    0BC9         or      ecx, ecx
010133A9    8985 A8030000mov   , eax         ;改写OEP存储地址
010133AF    61             popad
010133B0    75 08          jnz   short 010133BA
010133B2    B8 01000000    mov   eax, 1
010133B7    C2 0C00      retn    0C
010133BA   68 9D730001push   0100739D          ; OEP = 0X0100739D,动态生成
010133BF    C3             retn
——————————————————————————————————————
总结ASPack2.12外壳执行流程:
1、取得VirtualAlloc和VirtualFree函数地址
2、分配缓冲区,解压区块(此例中为3个区块),释放缓冲区
3、双重循环,填充IAT表
4、转到OEP
=========================================================================================
DLB(Double Loop Body)原理:
对于一般的压缩壳,外壳需要先解压缩,然后再填充IAT。|
而在填充IAT的时候,通常都会调用GetModuleHandleA,就利用这一点,返回到用户空间之后,
要做的第一件事就找内层循环体,然后再找外层循环体。而当找到“双重循环体(DLB)”之后,OEP就不远了。
在这里提出DLB原理,因为当我把ASPack 2.12分析完之后,我把ASPack的DLB当作标准DLB,
因为在后面的实践中,我发现DLB的形式并不都是这样,有一定的变形,但本质都是一样。
再来回顾一下DLB的特征:
for ( 每一个输入表项 )
{
取得模块地址(GetModuleHandleA),当若干次中断,然后返回到用户空间的时候,在这里的下一行
for ( 每一个函数 )

取函数地址
写入IAT

}
当使用外壳是调用系统的GetModuleHandleA,可以直接设断,中段若干次后返回到用户空间,
返回的地址必定在DLB中,然后找DLB的两个边界。
如果壳中自己实现了这个函数,那就不能直接对这个函数设置断点,需要通过分析来找DLB了。
要熟练利用DLB原理的前提:非常熟悉PE结构,对一些数据很敏感,例如IAT项大小为0X14等。
需要对IAT的填充形式非常清楚,最好能写个程序测试下,仔细观察DLB实现中的每个细节。
关于DLB原理的实践应用,将整理到下一篇。
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

来自:看雪学院

Mysoft 发表于 2006-11-13 09:17:36

最初由 fly 发布
壳(非虚拟机类)运行基本都可以认为有以下某些步骤:
壳代码解压/Anti/程序代码解压/输入表处理/重定位表处理/代码变形处理/SDK处理 等
页: [1]
查看完整版本: ASPack 2.12 外壳流程分析 + DLB原理