相关名词
  
  1)Entry Point (入口点)
  
  PE格式的可执行文件的执行时的入口点,即是PE格式中的Entry Point。
  
  用PEditor或者LordPE之类的PE查看工具看看NotePad.exe,你就会看到Entry Point的值。
  
  也就是说NotePad.exe在执行时的第一行代码的地址应该就是这个值。(当然应该加上基地址)
  
  2)Section (节区)
  
  PE格式文件是按节区进行规划组织的,不同的节区一般保存的数据的作用也不相同。通常使用缺省方式编译的程序文件,有CODE/DATA/TLS/.text/.data/.tls/.rsrc/.rdata/.edata/.reloc等不同的名称,有的用于保存程序代码,如CODE和.text节区,有的用于保存程序中的变量的,如DATA/.data节区,有的保存重定位信息,如.reloc,有的用于保存资源数据,如.rsrc。等等等等,当然这是缺省情况下编译器产生的结构。
  
  而节区名称和节区中的数据其实没有必然的联系,节区中保存的数据也没有什么硬性的限制。所以你可以在编译时用开关参数改变这些情况。
  
  3)ImageBase (基地址)
  
  不仅程序文件按节区规划,而且程序文件在运行时Windows系统也是按节区加载的。那么每一块的节区的顺序如何?起始的地址是什么呢?
  
  这就由基地址决定。在程序的文件头部保存了每个节区的描述信息,比如有前面提到的节区名称,还有节区的大小,以及节区的相对虚拟地址(RVA)。
  
  如果我们把节区的相对虚拟地址(RVA)加上基地址(ImageBase)就可以知道节区在内存中的虚拟地址(VA)了。Windows系统就是按照这个要求来加载各个节区的。这样Windows系统依次把各个节区放到了它相应的虚拟地址空间。
  
  所以如果我们把相对虚拟地址(RVA)看成是坐标的偏移量的话,那么ImageBase就是原点了。有了这个原点,一切都简单了。
  
  好了有了简要的介绍,我们来看看壳的加载过程吧。注意这里说的是一般情况,不特指某个壳,如果那样的话,我想那大概是洋洋洒洒几万字的了,好象我没有写过这么长的。虽然我的五笔练得还不错。
  
  1)获取壳自己所需要使用的API地址
  
  如果你用PE查看工具看看加壳后的程序文件,会发现未加壳的程序文件和加壳后的程序文件的Import Table不太一样,加壳后的Import Table一般所引入的DLL和API很少,甚至只有Kernel32.dll以及GetProcAddress这个API。
  
  我想你不会认为壳只用这个API就可以做所有的事吧。壳还需要很多其他的API来完成它的工作。当然他并不想让你知道他想用哪个API,所以一般他只是在壳的代码中动态加载这些API,而只把一些你嗅不过什么味道的几个API放在Import Table中。当然这其中壳可能会用到一些Anti技术,不过这和本文主旨无关,所以就不说了。
  
  2)解密原程序的各个节区(Section)的数据
  
  壳出于保护原程序代码和数据的目的,一般都会加密原程序文件的各个节区。既然是加密保存,那么在程序执行时你总不能也保持加密状态吧,所以解密是壳必做的工作之一。一般壳按节区加密的,那么在解密时也按节区解密,并且把解密的节区数据按照节区的定义放在合适的内存位置。
  
  如果加壳时用到了压缩技术,那么在解密之前还有一道工序,当然是解压缩。这也是一些壳的特色之一,比如说原来的程序文件未加壳时1-2M大小,加壳后反而只有几百K,这种瘦身技术当然会吸引了不少眼球。
  
  3)重定位
  
  前面我们提到了ImageBase,即程序的基地址,当然这只是程序文件中声明的,程序运行时能够保证系统一定满足你的要求吗?
  
  对于EXE的程序文件来说,Windows系统会尽量满足你的要求。
  
  比如一般EXE文件的基地址为0x400000,而运行时Windows系统提供给程序的基地址也同样是0x400000。在这种情况下就不需要进行地址"重定位"了。
  
  由于不需要对EXE文件进行"重定位",所以很多壳在加壳时把原程序文件中用于保存重定位信息的节区干脆也去掉了,这样使得加壳后的文件更加小巧。有些工具提供Wipe Reloc的功能,其实就是这个作用。
  
  不过对于DLL的动态链接库文件来说,Windows系统没有办法保证每一次DLL运行时提供相同的基地址。这样"重定位"就很重要了。
  
  此时壳中也需要提供进行"重定位"的代码,否则原程序中的代码是无法正常运行起来的。从这点来说,加壳的DLL比加壳的EXE更难修正。
  
  4)HOOK-API
  
  我们知道程序文件中的Import Table的作用是让Windows系统在程序运行时提供API的实际地址给程序使用。在程序的第一行代码执行之前,Windows系统就完成了这个工作。
  
  而壳一般都修改了原程序文件的Import Table,那么原程序文件的Import Table由谁来处理呢?这当然由壳来自己处理了,因此壳不得不模仿Windows系统的工作来填充Import Table中相关的数据。
  
  Import Table结构中与实际运行相关的主要是IAT结构,这个结构中用于保存API的实际地址,因此壳所需要的就是填充这个结构中的数据。
  
  不过壳不是填充这些实际的API地址,而是填充壳中用来HOOK-API的代码的地址。
  
  这样壳中的代码一旦完成了加载工作,在进入原程序的代码之后,仍然能够间接地获得程序的控制权。
  
  因为程序总是需要与系统打交道,与系统交道的途径是API,而API的地址已经替换成了壳的HOOK-API的地址,那么每一次程序与系统打交道,都会让壳的代码获得一次控制权,一来壳可以进行反跟踪继续保护软件,二来可以完成某些特殊的任务。其实这就是所谓HOOK技术。
  
  5)最后当然是跳转到程序原入口点
  
  这个大家比较熟悉,找的就是它。脱壳时大多数也是在这个时候。从这个时候起壳要把控制权交还给原程序了。
  
  以上是一个简单的总结。这代表了大多数壳的加载过程,不过特殊的不包括在内,介绍一下让大家了解一些。当然还有一些壳充分利用了PE结构的特点,比如利用TLS加载的特点也挺有趣。
  
  一点经验
  
  手动脱壳不一定要在程序原入口点。没有人规定你一定要在那里动手。只要你能保证脱壳后程序正常执行就行了。
  
  当然我们必需得知道这个原入口点的值,否则我们无法修复程序文件中的Entry Point的值了。
  
  那么手动脱壳还有比较常用的地方吗?有就是前面所提到的第2步完成后。此时Section数据已经解密,而且壳还没有来得及进行Hook-Api,很多壳图简单没有把原程序的Import Table破坏掉,这个时候正是我们下手的好时机。
  
  要发现这个地方也很容易,一般重定位代码的特征是很明显的。特别是有些壳有压缩功能的时候,更是容易发现。这个时候脱出来的文件组织一下可以达到和原程序几乎相同的大小。而不是通常的内存映象大小。