最近一直在努力学习《加密与解密》(第三版)的第13章 脱壳技术,本文也是我学习《加密与解密》以来的学习笔记,对本阶段自己的学习做个总结。拿出来与大家分享,希望对像我一般初入密界的菜鸟朋友有所帮助。--------------------------分享技术也是分享快乐

最简单的壳是UPX压缩壳,所以先从UPX的外壳代码分析开始。使用UPX加壳的记事本作为目标程序,用OD载入,单步跟踪分析代码并做出主要注释:


0040E9CC     8B07               mov eax,dword ptr ds:[edi]
0040E9CE     09C0               or eax,eax
0040E9D0     74 3C              je short UPX.0040EA0E                  ; 如果输入表没有或者处理完,直接跳过IAT填充
0040E9D2     8B5F 04            mov ebx,dword ptr ds:[edi+4]
0040E9D5     8D8430 14EC0000    lea eax,dword ptr ds:[eax+esi+EC14]    ; 装入KERNEL32.DLL字符串的有效地址
0040E9DC     01F3               add ebx,esi                            ; 
0040E9DE     50                 push eax                               ; eax指向 "KERNEL32.DLL"名,压入堆栈
0040E9DF     83C7 08            add edi,8                                 
0040E9E2    FF96 A0EC0000       call dword ptr ds:[esi+ECA0]           ; kernel32.LoadLibraryA
0040E9E8     95                 xchg eax,ebp
0040E9E9     8A07               mov al,byte ptr ds:[edi]
0040E9EB     47                 inc edi
0040E9EC     08C0               or al,al
0040E9EE   ^ 74 DC              je short UPX.0040E9CC
0040E9F0     89F9               mov ecx,edi
0040E9F2     57                 push edi                               ; edi指向函数名字符串,第一次值为0x0040D009
0040E9F3     48                 dec eax
0040E9F4     F2:AE              repne scas byte ptr es:[edi]
0040E9F6     55                 push ebp                               ; 调用kernel32模块的句柄
0040E9F7     FF96 A4EC0000      call dword ptr ds:[esi+ECA4]           ; GetProcAddress获取指定函数地址
0040E9FD     09C0               or eax,eax                             ; GetProcAddress调用失败eax=0
0040E9FF     74 07              je short UPX.0040EA08                  ; 跳过IAT填充过程
0040EA01     8903               mov dword ptr ds:[ebx],eax             ; ebx指向IAT结构,eax为获取到的指定函数地址,填充到IAT中
0040EA03     83C3 04            add ebx,4                              ; 指向下一个IAT指针
0040EA06   ^ EB E1              jmp short UPX.0040E9E9         
0040EA08     FF96 A8EC0000      call dword ptr ds:[esi+ECA8]
0040EA0E     61                 popad                                  ; 恢复现场环境
0040EA0F   - E9 B826FFFF        jmp UPX.004010CC                       ; 跳到原程序的OEP

以上代码是UPX填充IAT的全过程,可见该壳在解压缩还原原始程序后对IAT填充的是调用函数的系统地址,而没有实现IAT的加密。

用LORDPE查加壳记事本,有三个区段upx0,upx1和rsrc段。UPX加壳后改变原来的区块,其中upx0的RSize是0,但Visual Size和加壳前的镜像大小相等,可见upx将解压后的原始数据映射到该区段空间,而外壳执行代码和压缩后的数据是放在upx1区段中,最后一个为资源区段。
在外壳解压缩原始文件的数据填充IAT地址前,在数据窗口中找不到原始的输入表,而壳仅仅保存了输入函数的字符串,然后采用显示装载DLL的方式获得相应函数的地址,直接填充到IAT中。

本文之所以采用记事本加壳,这样可以比较加壳前后文件区段和输入表的变化,更好地分析upx对PE文件区段、输入表、和IAT的处理。

最后,我想用《加密与解密》13.4.5 输入表加密概括中的一句话总结:

UPX加壳时破坏了原输入表,外壳装载时没对IAT进行加密处理。

加壳技术与输入表的处理关系密切,主要有四种类型。本文对其一进行了验证,感觉对这种类型的加壳理解比单单看书要深刻的多。打算以后逐一分析其他类型的加壳技术。

2010.12.6 
By chinhad