一、基础知识
壳的定义:在一些计算机软件里也有一段专门负责保护软件不被非法修改或反编译的程序。它们一般都是先于程序运行,拿到控制权,然后完成它们保护软件的任务。由于这段程序和自然界的壳在功能上有很多相同的地方,基于命名的规则,大家就把这样的程序称为“壳”了,无非是保护、隐蔽壳内的东西。而从技术的角度出发,壳是一段执行于原始程序前的代码。原始程序的代码在加壳的过程中可能被压缩、加密……。当加壳后的文件执行时,壳-这段代码先于原始程序运行,他把压缩、加密后的代码还原成原始程序代码,然后再把执行权交还给原始代码。 软件的壳分为加密壳、压缩壳、伪装壳、多层壳等类,目的都是为了隐藏程序真正的OEP(入口点,防止被破解)。
1.1.1 壳的加载过程
1.保存入口函数
加壳的程序首先会初始化所有寄存器的值,在外壳执行完毕后,会恢复各个寄存器的内容,再跳到OEP去执行程序!通常加壳程序会用pushad / pushfd进行保存现场,用popad / popfd来恢复现场,例如:我用delphi7.0程序加了一个UPX的壳!他的入口就是利用Pushad来保存的现场!
004629D0 > 60 pushad //利用pushad来进行保存现场
004629D1 BE 00F04300 mov esi, 0043F000
004629D6 8DBE 0020FCFF lea edi, dword ptr [esi+FFFC2000]
004629DC C787 9CC00400 7> mov dword ptr [edi+4C09C], 46CD167B
004629E6 57 push edi
004629E7 83CD FF or ebp, FFFFFFFF
004629EA EB 0E jmp short 004629FA
在UPX的外壳结尾:
00462B5F 8D87 1F020000 lea eax, dword ptr [edi+21F]
00462B65 8020 7F and byte ptr [eax], 7F
00462B68 8060 28 7F and byte ptr [eax+28], 7F
00462B6C 58 pop eax
00462B6D 50 push eax
00462B6E 54 push esp
00462B6F 50 push eax
00462B70 53 push ebx
00462B71 57 push edi
00462B72 FFD5 call ebp
00462B74 58 pop eax
00462B75 61 popad //利用popad把所有的寄存器都恢复!
2.获取壳自己所需要的函数
一般外壳的输入表都只有GetMoudleHandleA、GetProcAddress和LoadLibrary这几个API函数!有的甚至只有Kernel32.dll以及GetProcAddress,如果壳程序需要加载其他的函数,就可以调用LoadLibrary将DLL映射到调用进程的地址空间中,函数返回的hinstance值用于标识文件映像映射到虚拟内存地址:我们查看MSDN,找到LoadLibrary函数的原型:
HINSTANCE LoadLibrary( LPCTSTR lpLibFileName // address of filename of executable module);
当DLL文件已经被映射到调用进程的地址空间中,就可以掉用GetMoudleHandleA了,调用此函数可以活的DLL模块的句柄,GetMoudleHandleA的函数原型是:
HMODULE GetModuleHandle (
LPCTSTR lpModuleName);
当Dll模块被加载后,就可以调用GetProcAddress函数来获取输入函数的地址,GetProcAddress函数的原型是:
FARPROC GetProcAddress( HMODULE hModule, // handle to DLL module LPCSTR lpProcName // name of function );
经过这三个函数的调用,就可以获得想要的函数API,外壳中所用到的其他的API基本都是利用这三个函数的搭配来进行调用的!但是有的高强度的加密壳,作者可能连最基本的GetProcAddress都不用,而是自己编写一个类似与GetProcAddress函数的函数来进行替换,这样可以使函数使用的隐蔽性大大提高!
举个例子,我用Delphi7.0加的UPX的壳,我们来看看他的外壳是不是用的我们上文提到的几个函数:
UPX的外壳用到了LoadLibrary、GetProcAddresss等函数,和我们上文提到的函数差不多!
3.解密各个加壳软件的区段的数据
壳会保护被加壳程序的区块的数据和代码,会加密被加壳程序的各个区块,在程序执行时,外壳会对这些被加密的数据进行解密,从而能够让被加密的程序可以正常运行!壳会按照被加密的区块的顺序进行解密,把解密的区块的数据按照区块的定义放在合适的内存位置!
4.初始化IAT
程序在加壳的时候,加壳程序自己构造了一个输入表,并把PE头中的输入表的指针指向了自己构造了的输入表,所以,PE装载器会对自己建立的输入表进行了填写,那么原来的IAT是通过PE装载器来实现的,可是现在就只能依靠外壳来填写PE输入表,外壳只需要对新输入表结构从头到尾扫描一遍,对每个DLL的引入的所有函数重新获取地址,并填写IAT!
5.跳到OEP
当外壳的执行完毕后,会跳到原来的程序的入口点,即Entry Point,也可以称作OEP!当一般加密强度不是很大的壳,会在壳的末尾有一个大的跨段,跳向OEP,类似一个壳与程序入口点的“分界线”!例如:
00462B74 58 pop eax
00462B75 61 popad
00462B76 8D4424 80 lea eax, dword ptr [esp-80]
00462B7A 6A 00 push 0
00462B7C 39C4 cmp esp, eax
00462B7E ^ 75 FA jnz short 00462B7A
00462B80 83EC 80 sub esp, -80
00462B83 ^ E9 109FFEFF jmp 0044CA98 //大的跨段,此时的EIP是00462B83,而将要跳到的是0044CA98,说明这里是一个大的跨段,是个分界点!
有的加密壳会把程序的入口点挪到壳段,并把这段代码给清除掉,就是我们称作的“Stolen Code”,这样壳与程序就部分彼此,加大了对脱壳的难度。(当然,我们可以把清除掉的OEP补回来!然后再进行脱壳!有的壳可能会利用虚拟机将入口出给VM掉,都是需要我们手动修复的!)
总结:
壳的加载过程:首先是保存现场 ==》获取壳程序自己所需要的函数(请记住这几个函数) ==》解密数据 ==》初始化IAT ==》跳到OEP ==》进行脱壳、修复。