您的位置:首页 > 编程语言

(4)恶意代码的亲密接触之文件搜索和API导址

2009-03-19 17:25 274 查看

恶意代码的亲密接触之文件搜索和API导址

  在前一篇文章中介绍了病毒的相关基础知识后,从本文开始我们就要深入病毒内部,开始看一些具体的病毒编码片断,在本文中我们会看到API函数搜索以及文件搜索的技术,为后面更加深入的探讨打下良好的基础,如果你已经准备好了,就让作者带你进入这无所不用其极的病毒技术世界吧。

  获取Kernel32.DLL 基址

  获取Kernel32.DLL基址的方法很多,最常见的一种是搜索法,如果已知Kernel32.DLL加载的大致地址,那么可由该地址向高地址或低地址进行搜索可以找到其基址。另外一种方法是搜索NT PEB 结构中的模块列表获取Kernel32.DLL的准确加载基址。下面看一下具体的实现代码。

  方法1:暴力搜索获取Kernel32.DLL 的基址

  最初的病毒是指定一个大致的加载地址,比如根据实验在9X 下其加载地址是0xBFF70000;在Windows 2000下加载基址是0x77E80000;在XP 和2003 下其加载基址是0x77E60000,因此在NT系统下就可以从0x77e00000开始向高地址搜索,在9X 下可以从0xBFF00000 开始向高地址搜索,如果搜索到Kernel32.DLL的加载地址,其头部一定是“MZ”标志,由模块起始偏移0x3C的双字确定的PE头部标志必然是“PE”标志,因此可根据这两个标志判断是否找到了模块加载地址,也许有人认为该方法不可靠,因为如果恰好有某段数据符合这两个特征,那么找到的基址可能就是错误的,但经实验证明,该判断方法非常可靠,基本不会出现错误。有一点需要注意的是,在所有版本的Windows系统下Kernel32.DLL的加载基址都是按照0x10000对齐的,根据这一特点可以不必逐字节搜索,按照64K 对齐的边界地址搜索即可。

  从大致的一个地址开始搜索Kernel32.DLL基址可能会出现读写到未映射内存区域的情况,因此需要和SEH 配合使用。如果有在各个版本下准确获取Kernel32.DLL中某地址的通用方法,那么就可以更可靠地从该地址开始向低地址搜索,显然会更加通用。事实上,这种方法是存在的。在系统加载PE文件跳转到PE入口点第一条指令的时候,堆栈顶保存的就是Kernel32.DLL 中的某个地址,Elkern中采用的就是这种方法:

_start:
pushfd ;If some flags,especial DF,changed,some APIs can crash down!!!
pushad
_start_@1 equ $
;......
mov ebx,[esp+9*4]
;前面已经由pushfd 和pushad 压入了9 个双字
and ebx,0ffe00000h
;该地址为Kernel32.dll 模块下方的某个地址
;先减去0x100000 确保该地址处于Kernel32.dll 的下方
;向高地址搜索如果将来Windows 的发行版本中Kernel32.dll
;大小和代码结构发生变化,该方法可能无效
  ebx 中现在已经是Kernel32.DLL 基址之前某个地址了,后续代码可以向高地址搜索其基址。该方法有一个缺点,就是必须明确知道程序入口的堆栈指针值,或间接可计算出该值,对于那些在程序入口获取控制权的病毒代码而言,是可以的,但对于采用EPO 技术的病毒而言,该方法则不适用。事实上还有另外一种更加通用的方法,我们知道在Win32 程序执行过程中fs 段寄存器的基址总是指向进程的TEB,TEB 的第一个成员指向SEH 链表,该链表每个节点都是一个EXCEPTION_REGISTRATION 结构,该结构定义如下:

struct EXCEPTION_REGISTRATION{
 struct EXCEPTION_REGISTRATION *prev;
 void* handler;
};
  在Windows 下SEH 链表最后一个成员的handler 指向Kernel32.DLL中函数UnhandledExceptionFilter的起始地址,利用这一特性我们可以写出更通用的代码:

xor esi,esi
lods dword [fs:esi];取得SEH 链表的头指针
@@:
inc eax ;是否是最后一个SEH 节点,检查prev 是
否为0xFFFFFFFF
je @F
dec eax
xchg esi,eax
LODSD ;下一个SEH 节点
jmp near @B
@@:
LODSD ;取得Kernel32.dll中UnhandledExceptionFilter的地址
  在有的病毒直接以0x7FFDE000 作为TEB 的指针值,其原因在于在Windows 2003 SP1、Windows XP SP2以前的NT类系统上,该值是固定的,这样的确可以节省一两个字节。但是在Windows 2003 SP1、Windows XP SP2中,情况已经发生了变化,出于安全性的考虑,Windows系统开
始动态映射TEB 了,也就是说,指向TEB 的指针值不再固定,因此这种硬编码的方法也就走到了尽头。此时可以按照前面的方法向低地址搜索判断直到找到Kernel32.dll的基址为止。Elkern中判断是否找到了Kernel32.dll基址的相关代码如下:

search_api_addr_@1:
add ebx,10000h
jz short search_api_addr_seh_restore
cmp word ptr [ebx],'ZM' ;是否是MZ 标志
jnz short search_api_addr_@1
mov eax,[ebx+3ch]
add eax,ebx
cmp word ptr [eax],'EP' ;是否具有PE 标志
jnz short search_api_addr_@1
;找到了kernel32.dll 的基址
  方法2:搜索PEB 的相关结构获取Kernel32.DLL 的基址

  前述TEB偏移0x30处,亦即FS:[0x30]地址处保存着一个重要的指针,该指针指向PEB(进程环境块),PEB成员很多,这里并不介绍PEB的详细结构。我们只需要知道PEB结构的偏移0xC处保存着另外一个重要指针ldr,该指针指向PEB_LDR_DATA 结构:

typedef struct _PEB_LDR_DATA
{
 ULONG Length; // +0x00
 BOOLEAN Initialized; // +0x04
 PVOID SsHandle; // +0x08
 LIST_ENTRY InLoadOrderModuleList; // +0x0c
 LIST_ENTRY InMemoryOrderModuleList; // +0x14
 LIST_ENTRY InInitializationOrderModuleList;// +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
  该结构的后三个成员是指向LDR_MODULE链表结构中相应三条双向链表头的指针,分别是按照加载顺序、在内存中的地址顺序和初始化顺序排列的模块信息结构的指针。

  LDR_MODULE结构如下所示:

typedef struct _LDR_MODULE
{
 LIST_ENTRY InLoadOrderModuleList; // +0x00
 LIST_ENTRY InMemoryOrderModuleList; // +0x08
 LIST_ENTRY InInitializationOrderModuleList;// +0x10
 PVOID BaseAddress; // +0x18
 PVOID EntryPoint; // +0x1c
 ULONG SizeOfImage; // +0x20
 UNICODE_STRING FullDllName; // +0x24
 UNICODE_STRING BaseDllName; // +0x2c
 ULONG Flags; // +0x34
 SHORT LoadCount; // +0x38
 SHORT TlsIndex; // +0x3a
 LIST_ENTRY HashTableEntry; // +0x3c
 ULONG TimeDateStamp; // +0x44
 // +0x48
} LDR_MODULE, *PLDR_MODULE;
  Peb->Ldr->InInitializationOrderModuleList指向按照初始化顺序排序的第一个L D R _ M O D U L E 节点的InInitializationOrderModuleList成员的指针,在WinNT平台(不包含Win9X)下,该链表头节点的LDR_MODULE结构包含的是NTDLL.DLL的相关信息,而链表的下一个节点所包含的就是Kernel32.dll 相关的信息了,该节点LDR_MODULE结构中的BaseAddress 不正是我们所苦苦寻找的吗。注意InInitializationOrderModuleList 是LDR_MODULE的第3个成员,因此要获取BaseAddress 的地址,只需将其指针加8 再derefrence即可。因此下面的汇编代码即可获取Kernel32.DLL的基址:

mov eax, dword ptr fs:[30h]
;获取PEB 基址
mov eax, dword ptr [eax+0ch]
;获取PEB_LDR_DATA 结构指针
mov esi, dword ptr [eax+1ch]
;获取InInitializationOrderModuleList 链表头第一个LDR_MODULE 节点
InInitializationOrderModuleList 成员的指针
93 2005.08 程序员
lodsd
;获取双向链表当前节点后继的指针
mov ebx, dword ptr [eax+08h]
;取其基地址,该结构当前包含的是
;kernel32.dll 相关的信息
  该方法在所有的Windows NT(包括Windows 2003 SP1和Windows XP SP2)操作系统上都是有效的,唯一的缺憾是由于PEB结构不同,该方
法在Win9X系统上无效。听起来可能比较费解,还是用一张图更加清晰一些(见图6)。



图6:利用PEB 搜索kernel32.dll 基地址的过程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: