隐藏进程的方法是把要在进程中完成的功能放在DLL文件中完成,然后将DLL文件注入到其他进程当中,从而达到隐藏进程的目的。现在要做的是隐藏进程中的DLL文件,当把DLL文件注入到远程进程后,可以将DLL也隐藏掉。操作系统在进程中维护着一个叫做TEB的结构体,这个结构体是线程环境块。下面通过WinDBG这个调试工具来一步一步地学习TEB,并通过TEB来学习如何隐藏DLL文件。
一、启动WinDBG
启动WinDBG工具,如图1所示。
依次单击菜单栏的“File”->“Symbol File Path…”命令,在里面输入符号文件路径,这里直接填入微软为我们提供的符号服务器,“srv*F:\Program Files\symbolcache*http://msdl.microsoft.com/download/symbols”,如图2所示。
设置好符号路径,就可以开始调试了。这里调试的目标就是WinDBG,因为原理是相同的。我们来进行本地调试,依次单击菜单“File”->“Kernel Debug”命令,出现如图3所示的窗口。
选择“Local”选项卡,也就是进行本地调试,单击“确定”按钮。这样,就可以用WinDBG开始调试了,跟着步骤一步一步做就可以了。
二、调试步骤
首先获取TEB,也就是线程环境块。在编程的时候,TEB始终保存在寄存器FS中。获取TEB的命令为“!teb”。在WinDBG的命令提示处输入该命令,WinDBG将输出如下内容。
从上面的输出内容可以看出,TEB地址为7ffde000。
获得TEB以后,通过TEB的地址来解析TEB的数据结构,从而获得PEB,也就是进程环境块,命令为“dt _teb 7ffde000”,WinDBG的输出内容如下:
上面的输出只是部分输出,该结构体非常长,这里只查看其中的一部分内容,只要找到PEB在TEB中的偏移就可以了。从该命令的输出可以看出,PEB结构体的地址位于TEB结构体偏移0x30的位置,该位置保存的地址是7ffd5000。也就是说PEB的地址是7ffd5000,通过该地址来解析PEB,并获得LDR。在命令提示符处输入命令“dt nt!_peb 7ffd5000”,输出内容如下:
从输出结果可以看出,LDR在PEB结构体偏移的0x0C处,该地址保存的地址是001a1e90。通过该地址来解析LDR结构体。在命令提示符输入命令“dt _peb_ldr_data001a1e90”,WinDBG输出如下内容:
在这个结构体中,可以看到3个相同的数据结构,也就是在偏移0x0c、0x14、0x24处的3个结构体_LIST_ENTRY。该结构体是个链表,定义如下:
上面这个结构体在SDK提供的帮助中是找不到的,需要去WDK的帮助中才可以找到。这3条链表分别保存的是_LDR_DATA_TABLE_ENTRY,也就是LDR_DATA表的入口。
现在来手动遍历一下第一条链表,输入如下命令“dd 1a1ec0”。
lkd> dd 1a1ec0 // _list_entry的地址
001a1ec0 001a1f18 001a1e9c 001a1f20 001a1ea4
001a1ed0 00000000 00000000 01000000 010582f7
001a1ee0 00096000 00580056 00020c64 00160014
001a1ef0 00020ca6 00005000 0000ffff 001a2cd4
001a1f00 7c99e310 49a5f6a7 00000000 00000000
001a1f10 000b000b 000801b7 001a1fc0 001a1ec0
001a1f20 001a1fc8 001a1ec8 001a1fd0 001a1eac
001a1f30 7c920000 7c932c60 00096000 0208003a
在这么多的输出中,在链表偏移0x18的位置是模块的映射地址,即ImageBase,在链表偏移0x28的位置是模块的路径及名称的地址,在0x30的位置是模块名称的地址。查看一下,1a1ec0偏移0x28的位置中保存的地址是20c64,接下来输入命令 “du 20c64”。
lkd> du 20c64
00020c64 "F:\WinDDK\7600.16385.0\Debuggers"
00020ca4 "\windbg.exe"
可以看到,输出WinDBG的全部路径,来看一下偏移0x18的地址,该进程的映射基址为01000000。再来看一下偏移0x30处的地址保存着20ca6,查看该地址,输入命令“du 20ca6”。
lkd> du 20ca6
00020ca6 "windbg.exe"
的确是模块的名称。既然是链表,那么看看下一条链表的信息。
lkd> dd 1a1f18 // 1a1f18中保存的是下一个_list_entry的地址
001a1f18 001a1fc0 001a1ec0 001a1fc8 001a1ec8
001a1f28 001a1fd0 001a1eac 7c920000 7c932c60
001a1f38 00096000 0208003a 7c9a0028 00140012
001a1f48 7c942838 80084004 0000ffff 7c99e2c8
001a1f58 7c99e2c8 498ffe8a 00000000 00000000
001a1f68 000b000a 000e01b8 003a0043 0057005c
001a1f78 004e0049 004f0044 00530057 0073005c
001a1f88 00730079 00650074 0033006d 005c0032
lkd> dd 1a1fc0 // _list_entry的地址
001a1fc0 001a2068 001a1f18 001a2070 001a1f20
001a1fd0 001a21b8 001a1f28 7c800000 7c80b5be
001a1fe0 0011d000 00420040 001a1f70 001a0018
001a1ff0 001a1f98 80084004 0000ffff 001a2a44
001a2000 7c99e2b0 49c4f753 00000000 00000000
001a2010 000b000a 000e0157 003a0043 0057005c
001a2020 004e0049 004f0044 00530057 0073005c
001a2030 00730079 00650074 0033006d 005c0032
按照上面介绍的解析方法自己进行解析。
lkd> du 1a1f70
001a1f70 "C:\WINDOWS\system32\kernel32.dll"
001a1fb0 ""
上面介绍的几个结构体在VC6的头文件中是找不到的,不过在网上还是可以查到的。这里给出MSDN上给出的几个结构体的定义,该MSDN的地址为:http://msdn.microsoft.com/zh-cn/library/aa813708(v=VS.85).aspx,方便大家查看。涉及的几个结构体的定义如下:
从这两个结构体中可以看出有非常多的保留字段,这些都是微软不愿意公开的,或不愿意让大家使用的。不过在网上有大量的相关结构体的具体定义,大家可以自行查找进行阅读。
看完上面的各种结构体是不是觉得我们自己都可以实现枚举进程中模块的函数了?是的,我们来写一个吧。
三、编写枚举进程中模块的函数
枚举进程中的模块的方法就是通过上面介绍的几个结构体来完成,其步骤是。
获得TEB地址->获得PEB地址->得到Ldr->获得第二条链表的地址->遍历该链表并输出其地0x18的值和0x28指向的内容。
只要把上面在WinDBG中找到链表的方法学明白,那么就不是太大的问题了。关键的问题是TEB怎么找到。告诉大家,TEB保存在FS中,有了这个提示就很好解决了吧?看代码吧。
该函数的实现没有太多的技巧,主要在于对C语言中指针的掌握,还有就是对以上介绍的几个结构体之间的关系能够掌握,也就是各结构体之间的数据关系。在main()函数中调用一下这个函数,输出结果如图4所示。
四、指定模块的隐藏
模块的隐藏是把指定模块在链表中的节点断掉,也就是做一个数据结构中链表的删除动作,只不过不进行删除,只是将其节点脱链即可,如图5所示。
如果是枚举模块的话,一般情况下,只要枚举第二条链表就可以了,也就是偏移0×14处的那条。如果要做模块隐藏的话,那最好是将3条链表中的指定模块全部都脱链。对于脱链的方法,其实也是对3条链表进行遍历,然后将指定的模块脱链就可以了。和上面枚举的方法差别不大。下面给出代码,如下:
在main()函数中调用这个函数,主函数如下:
接下来隐藏调用“kernel32.dll”这个模块,当然,这里的隐藏只能是这个程序运行时隐藏该进程中的“kernel32.dll”模块,对其余进程中的模块并没有影响。在程序的末尾使用getchar(),其用意是希望该进程可以停留住,否则它如果退出,便没有验证“kernel32.dll”模块是否真的被隐藏的机会了。编译连接并运行,然后用工具来查看一下,如图6所示。
从图6中可以看出,在HideModule.exe进程中看不到Kernel32.dll的模块名。当然,就算用我们自己编写的枚举的模块函数也是没用的,因为模块已经不在链表中了。虽然这个程序把进程中“kernel32.dll”隐藏了,但是并没有多大的实际意义。隐藏模块主要是用在被注入的DLL中,也就是一个DLL文件被注入到远程线程中后,为了不被发现而隐藏。