上节课讲了许多有关壳的知识,下面我们就开始讲如何脱壳,以及脱壳时要用的的一些工具,首先是查壳,当前流行的查壳工具主要以peid和fileinfo这两个软件为代表。
PEiD的原理是利用查特征串搜索来完成识别工作的。各种开发语言都有固定的启动代码部分,利用这点就可识别出何种语言编译的。同样,不同的壳也有其特征码,利用这点就可以识别是被何种壳所加密。PEiD提供了一个扩展接口文件userdb.txt ,用户可以自定义一些特征码,这样就可识别出新的文件类型。
一般的压缩壳,如Aspack等都有专用的脱壳机。而加密壳(如ASProtect,Armadillo) 一般很少有脱壳机,必须手工脱壳。手工脱壳一般情况是分三步:一是查找程序的真正入口点(OEP);二是抓取内存映像文件;三是输入表重建。(当然现在的加密壳复杂些,要考虑更多的东西)OEP是Original Entry Point缩写,即程序加壳前的真正的入口点。
如何寻找oep
外壳初始化的现场环境(各寄存器值)与原程序的现场环境是相同的。加壳程序初始化时保存各寄存器的值,外壳执行完毕,会恢复各寄存器内容。其代码形式一般如下:
PUSHFD ; 将标志寄存器入栈保存
PUSHAD ; push eax, ecx, edx, ebx, esp, ebp, esi, edi
…… ; 外壳代码部分
POPAD ; pop edi, esi, ebp, esp, ebx, edx, ecx, eax
POPFD ; 恢复标志寄存器
JMP OEP ;
OEP: …… ; 解压后的程序原代码
方法一:利用UPX 通用脱壳机自动脱壳
脱壳后的程序,利用PEid进行分析,PEid给出如下的信息:
Borland Delphi 6.0-7.0
方法二:跨段指令寻找OEP
用Ollydbg来调试脱壳 ,运行Ollydbg,点击菜单“Options/Debugging options”,选择Events项,将第一次暂停设在WinMain函数上。 Ollydbg打开实例Bandook v1.35.exe.
相关代码:
006E0440 $ 60 pushad //一开始Ollydbg就会中断这行,这个就是外壳的入口点,注意这个pushad指令 .
如下图所示
绝大多数加壳程序在被加密的程序中加上一个或多个段,所以依据跨段的转移指令(JMP)就可找到真正的入口点,此时就会有POPAD/POPFD 指令出现。UPX 用了一次跨段的转移指令(JMP),在跳到OEP处会看到虚拟地址的值有一个突变,此时就能确定OEP了。
UPX壳比较简单,中断WinMain后,只需要在Ollydbg里往下翻屏,就会发现这个跨段转移指令:
上图相关代码如下:
006E05A2 > 61 popad //注意这里的popad指令,和开始的pushad对应
006E05A3 - E9 444EE7FF jmp 005553EC //这里跳到OEP,将光标移到这,按F4执行到这行
这句006E05A3 jmp 005553EC就是跳到OEP的指令,执行到这,UPX外壳己将程序解压完毕,并模拟Windows加载器将原始程序加载到内存, 005553EC 就是映射到内存目标程序的入口点,此时就可抓取内存映像文件了。
相关代码:
005553EC 55 push ebp
005553ED 8BEC mov ebp,esp
选择Plugins->OllyDump->Dump debugged process项,Dump保存成新的文件。
方法三:编译语言特点找OEP
各类语言编译的文件入口点都有一些规律,可以这利用这点来寻找入口点。
1)Delphi程序
执行程序,用LordPE(或Prodump)选dump(full)脱壳,存为dump.exe。接着用Hex Workshop打开 dump.exe,搜索文本“runtime”,搜到后,向前查找离“runtime”最近的十六进制数字“55 8B EC”,数字所在的地址就是程序的OEP。
2)Visual C程序
可以利用Visual C启动部分几个函数GetCommandLineA(W)、GetVersion、GetModuleHandleA(W)、GetStartupInfoA(W) 等来定位程序的OEP。
常见的各类编译语言的入口汇编代码都要熟悉,因为一些加密强壳会偷OEP处的代码到壳里,一般情况各编译语言入口代码都相同,到时只需要直接引用相关程序的入口代码,这给我们恢复代码带来方便。
其它技术:
通过堆栈平衡寻找OEP,通过内存断点寻找OEP
补充讲解一下windowsapi函数,这可是脱壳的时候离不开的,如果熟悉了它可以更快的找到程序的入口及程序领空
Win32 API是基于C语言的接口,但是Win32 API中的函数可以由用不同语言编写的程序调用。
Windows的主要部分有三个主要子系统,分别是Kernel/User/GDI。
Kernel:操作系统核心功能服务,包括进行与线程控制、内存管理、文件访问等;
User:负责处理用户接口,包括键盘和鼠标输入、窗口和菜单管理等;
GDI:图形设备接口,允许程序在屏幕和打印机上显示文本和图形。
常用Win32 API函数
GetWindowText函数,作用是取得一个窗体的标题文字,或文本控件的内容。
GetDlgItemText函数,作用是获取对话框文本。
GetModuleFileName函数
InternetOpen函数
InternetOpenURL函数
SetWindowsHookEx函数
AdjustTokenPrivileges函数
LookupPrivilegeValue函数
OpenProcessToken函数
GetCurrentProcessId函数
GetCurrentProcess函数
GetWindowThreadProcessId函数
GetComputerName函数