DLL文件脱壳

2009/2/25 来源:www.arpun.com 作者:小白

DLL文件的脱壳与EXE文件步骤差不多, 所不同的是, DLL文件多了个基址重定位表等要考虑。

 

在2003年出版的《加密与解密》(第二版)中以UPX, PECompact为例讲述了DLL重定位重建的方法, 由于本人的思路限制, 当时只是从UPX, PECompact自身特点找思路解决这问题, 即先分析UPX, PECompact对重定位表处理算法, 然后写工具逆算法还原重定位表, 如UPXAngela.exe等工具。 这种思路的通用性不好, 针对不同的壳和版本, 要重写工具, 并且逆算法可能不完美, 从而存在bug。

 

后来, askformore在“重建重定位表脚本”一文中, 提出了一种更通用性的解决办法, 利用外壳重定位相关数据时, 会根据外壳转储的重定位表确定要重定位的RVA, 完成代码重定位工作。 将这些要重定位的RVA提取出来, 再将这些RVA根据重定位表的定义重新生成一份新的重定位表。 shoooo也曾提到过这个思路。 于是, 在第三版重写这部分时, 根据这个思路写了一款工具来完成这个重建功能, 详见附件的ReloREC。 另外, ReloREC重构重定位表的算法代码, 参考了ccfer在看雪论坛.珠海金山2007逆向分析挑战赛 第二阶段第三题 提交的代码。 在此一并表示感谢!

 

 

声明:本文以第三版“13.5 DLL文件脱壳”一文和其他章节临时整理组织, 稍有简化, 可能有部分地方用词和描述不是太连贯。

加壳的DLL处理重定位表有以下几种情况:

1)完整的保留了原重定位表;

2)对原重定位表进行了加密处理;

等等

 

像ASPack,ASProtect等壳属于第1种情况, 没有加密重定位表, 脱壳后, 只需找到重定位的地址和大小即可。

像UPX, PECompact等壳属于第2种情况, 必须重建重定位表, 这也是本文所要讨论的, 本文以UPX为例来讲述一下重定位的重建。

用UPX v3.01将EdrLib.dll文件加壳, 用PE工具查看其PE信息。

EntryPoint:E640h

ImageBase:400000h

 

 

13.5.1 寻找OEP

 

当DLL被初次映射到进程的地址空间中时, 系统将调用DllMain函数, 当卸载DLL时也会再次调用DllMain函数。 也就是说, DLL文件相比EXE文件运行有一些特殊性, EXE的入口点只在开始时执行一次, 而DLL的入口点在整个执行过程中至少要执行两次。 一次是在开始时, 用来对DLL做一些初始化。 至少还有一次是在退出时, 用来清理DLL再退出。 所以DLL找OEP也有两条路可以走, 一是载入时找, 另一方法是在退出时找。 而且一般来说前一种方法外壳代码较复杂, 建议用第二种方法。

UPX壳比较简单, 往下翻翻, 就可看到跳到OEP的代码:

 

代码:

003DE7F5   .  58              pop     eax

003DE7F6   .  61              popad

003DE7F7   .  8D4424 80       lea     eax, dword ptr [esp-80]

003DE7FB   >  6A 00           push    0

003DE7FD   .  39C4            cmp     esp, eax

003DE7FF   .^ 75 FA           jnz     short 003DE7FB

003DE801   .  83EC 80         sub     esp, -80

003DE804   >- E9 372AFFFF     jmp     003D1240  //跳到OEP

13.5.2 Dump映像文件

 

停在OEP后, 运行LordPE, 在进程窗口选择loaddll.exe进程, 在下方窗口中的EdrLib.dll模块上单击右键, 执行“dump full”菜单命令, 将文件抓取并保存到文件里, 如图13.43所示。

 

dll.gif

图13.45 抓取DLL内存映像

 

对于DLL文件来说, Windows系统没有办法保证每一次运行时提供相同的基地址。 如果DLL基址所在内存空间被占用或该区域不够大, 系统会寻找另一个地址空间的区域来映射DLL, 此时外壳将对DLL执行某些重定位操作。 从图13.43得知, 此时DLL被映射到内存的地址是03D000h, 与EdrLib.dll默认的基址400000h不同, 被重定位项所指向的地方是已经重定位了的代码数据。

例如这句:

 

代码:

003D1266     A1 58B43D00        mov     eax, dword ptr [3DB458]

为了得到与加壳前一样的文件, 必须找到重定位的代码, 跳过它, 让其不被重定位。 重新加载DLL, 对上句重定位的地址3D1267h下内存写断点, 中断几下, 就可来到重定位的处理代码。

 

 

代码:

003DE79E  mov     al, byte ptr [edi]              ;指向UPX自己加密过的重定位表

003DE7A0  inc     edi                                  ;指针移向下一位

003DE7A1  or      eax, eax                            ;EAX=0?结束标志

003DE7A3  je      short 003DE7C7              

003DE7A5  cmp     al, 0EF

003DE7A7  ja      short 003DE7BA                  

003DE7A9  add     ebx, eax                          ;EBX的初值为(0xFFC+基址)

003DE7AB  mov     eax, dword ptr [ebx]             ;EBX指向需要重定位的数据, 取出放到EAX

003DE7AD  xchg    ah, al

003DE7AF  rol     eax, 10

003DE7B2  xchg    ah, al

003DE7B4  add     eax, esi                          ; ESI指向UPX0区块的VA, 本例=3D1000

003DE7B6  mov     dword ptr [ebx], eax           ;重定位

003DE7B8  jmp     short 003DE79C             

003DE7BA  and     al, 0F

003DE7BC  shl     eax, 10

003DE7BF  mov     ax, word ptr [edi]

003DE7C2  add     edi, 2

003DE7C5  jmp     short 003DE7A9           

003DE7C7  mov     ebp, dword ptr [esi+E044]        ;改好ESI为401000后, 按F4到这里

UPX壳己将原基址重定位表清零, 重定位操作时, 使用其自己的重定位表。 地址3DE7B4h处ESI指向UPX0区块的VA, 本例为3D1000h, 为了让代码以默认ImageBase的值400000h重定位代码, 可以在这句强制将ESI的值改为401000h。 来到这句后, 双击ESI寄存器, 改成401000h, 然后按F4来到3DE7C7h这时。 此时代码段的数据没被重定位, 可以Dump了。

 

代码:

003D1253     833D 68AD4000 00     cmp     dword ptr [40AD68], 0

运行LordPE将DLL映像抓取, 并保存为upx_dumped.dll。

 

13.5.3 重建DLL的输入表

 

ImportREC能很好地支持DLL的输入表的重建, 首先, 在Options里将“Use PE Header From Disk”默认的选项去除。 这是因为ImportREC需要获得基址计算RVA值, DLL如果重定位了, 从磁盘取默认基址计算会导致结果错误。

 

1)在ImportREC下拉列表框中选择DLL装载器的进程, 此处为loaddll.exe进程。

2)单击“Pick DLL”按钮, 在DLL进程列表中选择EdrLib.dll进程(见图13.47)。

 

dll2.gif

图13.47 选择DLL进程

3)在OEP处, 填上DLL入口的RVA值1240h, 单击IAT AutoSearch按钮获取IAT地址。 如果失败, 必须手工判断DLL的IAT位置和大小, 其RVA为7000h, Size为E8h。

4)单击“Get Import”按钮, 让其分析IAT结构重建输入表。

5)勾选Add new section, 单击“Fix Dump”按钮, 并选择刚抓取的映像文件dumped.dll, 它将创建一个dumped_.dll文件。

 

13.5.4 构造重定位表

 

原理请参考本文开始处的说明。

先来回顾一个重定位表的结构:

 

代码:

IMAGE_BASE_RELOCATION STRUCT

    VirtualAddress    dd    0

    SizeOfBlock        dd    0

    Type1            dw    0; 其中:Bit15—Bit12为类型 type, Bit11--Bit0 为ItemOffset

IMAGE_RELOCATION ENDS

重定位表以1000h大小为一个段, 因为ItemOffset最长为12位, 即刚好为1000h。 如果还有更多段, 将重复上面数据结构, 直到VirtualAddress为NULL, 表示结束。

ReloREC工具可以根据一组重定位的RVA, 重新构造一个新的重定位表。 首先要做的工作是将UPX外壳这些要重定位的RVA提取出来。

 在处理重定位代码语句中, 下面这句就是对代码重定位, 其中EBX保存的就是要重定位的地址。

 

代码:

003DE7B6     mov     dword ptr [ebx], eax      ;EBX指向要重定位的RVA

补丁的思路是找块代码空间, 跳过去执行补丁代码, 将重定位的地址转成RVA, 并保存下来。 如下语句跳到补丁代码处:

 

代码:

003DE7B8   jmp     short 003DE80A

我们键入的补丁代码:

003DE80A   pushad

003DE80B   mov     edx, dword ptr [3E0000]    ;从全局变量3E0000h取一地址指针

003DE811   sub     ebx, 3D0000                  ;减外壳基址, 将ebx中的地址转成RVA

003DE817   mov     dword ptr [edx], ebx       ;将获得的RVA保存下来

003DE819   add     edx, 4                        ;指向下一个DWRD地址

003DE81C   mov     dword ptr [3E0000], edx  ;将指针保存到全局变量中

003DE822   popad

003DE823   jmp     003DE79C                       ;跳回外壳代码

3E0000h这个地址是OllyDbg的插件HideOD临时分配的, 其初始值设为3E0010h, 如图13.71。

dll3.gif

 

 补丁代码键入完成后, 外壳在处理重定位相关代码时, 这段补丁代码将需要重定位的RVA全部提取出来。 执行完补丁代码, 数据窗口将保存需要重定位的RVA,

 将需要重定位的RVA复制出来(选取数据时, 最后一个DWORD数据是0), 操作时单击鼠标右键, 执行菜单Binary/Binary copy(二进制复制)功能, 再运行WinHex, 新建一文档, 将这段二进制数据粘贴进去, 粘贴时, 选择ASCII Hex模式(图13.52), 然后将提取的数据保存为Relo.bin。

Relo.bin中保存的就是需要重定位的地址, 以RVA表示。 部分数据如下:

代码:

0000101D

00001031

0000106E

0000108D

000010A1

000010DE

000010FB

00001109

0000110F

……

ReloREC这款工具, 就是根据这些RVA重新生成一份新的重定位表

 准备工作完成, 运行ReloREC, 将Relo.bin拖放到ReloREC主界面上可打开此文件, 如图13.53。 然后在dumped_.dll里找一块空白代码处保存重定位表(一般在UPX1或UPX2区块里找), 在这选择C000h处。 在Relocation's RVA域里填上原始重定位表的RVA地址, 本例为C000h, 最后单击“Fix Dump”按钮, 打开上节刚修复输入表的dumped_.dll文件, 即可完成重定位表的修复。

网友评论
评论(...
全部评论