连长 发表于 2008-5-20 16:02:55

《加密与解密(第三版)》--13.5 DLL文件脱壳(重定位表修复部分)

软件安全系列图书——《加密与解密》(第三版)




第13章 脱壳技术
13.1 基础知识
13.1.1 壳的加载过程
13.1.2 脱壳机
13.1.3 手动脱壳
13.2 寻找OEP
13.2.1 根据跨段指令寻找OEP
13.2.2 用内存访问断点找OEP
13.2.3 根据堆栈平衡原理找OEP
13.2.4 根据编译语言特点找OEP
13.3 抓取内存映像
13.3.1 Dump原理
13.3.2 反DUMP技术
13.4 重建输入表
13.4.1 输入表重建的原理
13.4.2 确定IAT的地址和大小
13.4.3 根据IAT重建输入表
13.4.4 ImportREC重建输入表
13.4.5 输入表加密概括
13.5 DLL文件脱壳
13.5.1 寻找OEP
13.5.2 Dump映像文件
13.5.3 重建DLL的输入表
13.5.4 构造重定位表
13.6 附加数据
13.7 PE文件的优化
13.8 压缩壳
13.8.1 UPX外壳
13.8.2 ASPack外壳
13.9 静态脱壳
13.9.1 外壳Loader的分析
13.9.2 编写静态脱壳器
13.10 加密壳
13.10.1 ASProtect
13.10.2 Thmedia的SDK分析


13.5 DLL文件脱壳


说明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
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所示。


图13.45 抓取DLL内存映像

对于DLL文件来说,Windows系统没有办法保证每一次运行时提供相同的基地址。如果DLL基址所在内存空间被占用或该区域不够大,系统会寻找另一个地址空间的区域来映射DLL,此时外壳将对DLL执行某些重定位操作。从图13.43得知,此时DLL被映射到内存的地址是03D000h,与EdrLib.dll默认的基址400000h不同,被重定位项所指向的地方是已经重定位了的代码数据。
例如这句:
代码:
003D1266   A1 58B43D00      mov   eax, dword ptr
为了得到与加壳前一样的文件,必须找到重定位的代码,跳过它,让其不被重定位。重新加载DLL,对上句重定位的地址3D1267h下内存写断点,中断几下,就可来到重定位的处理代码。

代码:


003DE79Emov   al, byte ptr             ;指向UPX自己加密过的重定位表
003DE7A0inc   edi                                  ;指针移向下一位
003DE7A1or      eax, eax                            ;EAX=0?结束标志
003DE7A3je      short 003DE7C7               
003DE7A5cmp   al, 0EF
003DE7A7ja      short 003DE7BA                  
003DE7A9add   ebx, eax                        ;EBX的初值为(0xFFC+基址)
003DE7ABmov   eax, dword ptr              ;EBX指向需要重定位的数据,取出放到EAX
003DE7ADxchg    ah, al
003DE7AFrol   eax, 10
003DE7B2xchg    ah, al
003DE7B4add   eax, esi                        ; ESI指向UPX0区块的VA,本例=3D1000
003DE7B6mov   dword ptr , eax         ;重定位
003DE7B8jmp   short 003DE79C            
003DE7BAand   al, 0F
003DE7BCshl   eax, 10
003DE7BFmov   ax, word ptr
003DE7C2add   edi, 2
003DE7C5jmp   short 003DE7A9            
003DE7C7mov   ebp, dword ptr       ;改好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 , 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)。


图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 为ItemOffsetIMAGE_RELOCATION ENDS
重定位表以1000h大小为一个段,因为ItemOffset最长为12位,即刚好为1000h。如果还有更多段,将重复上面数据结构,直到VirtualAddress为NULL,表示结束。
ReloREC工具可以根据一组重定位的RVA,重新构造一个新的重定位表。首先要做的工作是将UPX外壳这些要重定位的RVA提取出来。
在处理重定位代码语句中,下面这句就是对代码重定位,其中EBX保存的就是要重定位的地址。

代码:
003DE7B6   mov   dword ptr , eax      ;EBX指向要重定位的RVA
补丁的思路是找块代码空间,跳过去执行补丁代码,将重定位的地址转成RVA,并保存下来。如下语句跳到补丁代码处:

代码:
003DE7B8   jmp   short 003DE80A我们键入的补丁代码:003DE80A   pushad003DE80B   mov   edx, dword ptr     ;从全局变量3E0000h取一地址指针003DE811   sub   ebx, 3D0000                  ;减外壳基址,将ebx中的地址转成RVA003DE817   mov   dword ptr , ebx       ;将获得的RVA保存下来003DE819   add   edx, 4                        ;指向下一个DWRD地址003DE81C   mov   dword ptr , edx;将指针保存到全局变量中003DE822   popad003DE823   jmp   003DE79C                     ;跳回外壳代码

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


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

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

代码:
0000101D000010310000106E0000108D000010A1000010DE000010FB000011090000110F……
ReloREC这款工具,就是根据这些RVA重新生成一份新的重定位表


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







blue002 发表于 2017-8-4 16:26:11

介绍很详细                                                      
页: [1]
查看完整版本: 《加密与解密(第三版)》--13.5 DLL文件脱壳(重定位表修复部分)