在NT环境下隐藏进程,也就是说在用户不知情的条件下,执行自己的代码的方法有很多种,比如说使用注册表插入DLL,使用Windows挂钩等等。其中比较有代表性的是Jeffrey Richer在《Windows核心编程》中介绍的LoadLibrary方法和罗云彬在《windows环境下32位汇编语言程序设计》中介绍的方法。两种方法的共同特点是:都采用远程线程,让自己的代码作为宿主进程的线程在宿主进程的地址空间中执行,从而达到隐藏的目的。相比较而言,Richer的方法由于可以使用c/c++等高级语言完成,理解和实现都比较容易,但他让宿主进程使用LoadLibrary来装入新的DLL,所以难免留下蛛丝马迹,隐藏效果并不十分完美。罗云彬的方法在隐藏效果上绝对一流,不过,由于他使用的是汇编语言,实现起来比较难(起码我写不了汇编程序:))。笔者下面介绍的方法可以说是对上述两种方法的综合:采用c/c++编码,实现完全隐藏。并且,笔者的方法极大的简化了远程线程代码的编写,使其编写难度与普通程序基本一致。
基础知识
让自己的代码作为宿主进程的线程,在宿主进程的地址空间中执行确实是个不错的主意。但是要自己把程序放到其他进程的地址空间中去运行,将面临一个严峻的问题:如何实现代码重定位。关于重定位问题,请看下面的程序:
…
int func()//函数func的定义
…
int a = func();//对func的调用
…
这段程序经过编译链接后,可能会变成下面的 子:
…
0x00401800: push ebp//这是函数func的入口
0x00401801: mov ebp, esp
…
0x00402000: call 00401800//对函数func的调用
0x00402005: mov dword ptr [ebp-08], eax
…
请注意“0x00402000”处的直接寻址指令“call 00401800”。上面的程序在正常执行(由windows装入并执行)时,因为PE文件的文件头中含有足够的信息,所以系统能够将代码装入到合适的位置从而保证地址“00401800”处就是函数func的入口。但是当我们自己把程序装入到其他进程的地址空间中时,我们无法保证这一点,最终的结果可能会象下面这样:
…
0x00801800: push ebp//这是函数func的入口
0x00801801: mov ebp, esp
…
0x00802000: call 00401800//00401800处是什么
0x00802005: mov dword ptr [ebp-08], eax
…
显然,运行上面的代码将产生不可预料的结果(最大的可能就是执行我们费尽千辛万苦才装入的代码的线程连同宿主进程一起被系统杀死)。不知大家注意过系统中动态链接库(dll)的装入没有:一个dll被装入不同进程时,装入的地址可能不同,所以系统在这种情况下也必须解决dll中直接寻址指令的重定位问题。原来,绝大多数dll中都包含一些由编译器插入的用于重定位的数据,这些数据就构成了重定位表。系统根据重定位表中的数据,修改dll的代码,完成重定位操作。Richer使用的LoadLibrary也是借用了这一点。所以我们的重定位方法就是:替系统来完成工作,自己根据重定位表中的数据进行重定位。既然如此,那就让我们来了解一下重定位表吧。
先来分析一下重定位表中需要保存哪些信息。还以上面的代码为例,要让它能正确执行,就必须把指令“call 00401800”改为“call 00801800”。进行这一改动需要两个数据,第一是改哪,也就是哪个内存地址中的数据需要修改,这里是“0x00802001”(不是“0x00802000”);第二是怎么改,也就是应该给该位置的数据加上多少,这里是“0x00400000”。这第二个数据可以从dll的实际装入地址和建议装入地址计算而来,只要让前者减后者就行了。其中实际装入地址装入的时候就会知道,而建议装入地址记录在文件头的ImageBase字段中。所以,综上所述,重定位表中需要保存的信息是:有待修正的数据的地址。
位置 | 数据 | 描述 |
0000h | 00001000h | 页起始地址(RVA) |
0004h | 00000010h | 重定位块长度 |
0008h | 3006h | 第一个重定位项,32位都须修正 |
000ah | 300dh | 第二个重定位项,32位都须修正 |
000ch | 3015h | 第三个重定位项,32位都须修正 |
000eh | 0000h | 第四个重定位项,用于对齐 |
0010h | 00003000h | 页起始地址(RVA) |
0014h | 0000000ch | 重定位块长度 |
0018h | 3008h | 第一个重定位项,32位都须修正 |
001ah | 302ah | 第二个重定位项,32位都须修正 |
… | … | 其他重定位块 |
0100h | 0000h | 重定位表结束标志 |
知道了重定位表要保存哪些信息,我们再来看看PE文件的重定位表是如何保存这些信息的。重定位表的位置和大小可以从PE文件头的数据目录中的第六个IMAGE_DATA_DIRECTORY结构中获取。由于记录一个需要修正的代码地址需要一个双字(32位)的存储空间,而且程序中直接寻址指令也比较多,所以为了节省存储空间,windows把重定位表压缩了一下,以页(4k)为单位分块存储。在一个页面中寻址只需要12位的数据,把这12位数据再加上4位其它数据凑齐16位就构成一个重定位项。在每一页的所有重定位项前面附加一个双字表示页的起始地址,另一个双字表示本重定位块的长度,就可以记录一个页面中所有需要重定位的地址了。所有重定位块依次排列,最后以一个页起始地址为0的重定位块结束重定位表。上表是一个重定位表的例子(表中每种颜色代表一个重定位块)。
上面提到每个重定位项还包括4位其他信息,这4位是重定位项的高4位,虽然有4位,但我们实际上能看到的值只有两个:0和3。0表示此项仅用作对齐,无其他意义&#x