原文:http://www.pediy.com/kssd/index.html -- 病毒技术 -- 病毒知识 -- Anti Virus专题
第一种方法
通过线程初始化时, 获得esp堆栈指针中的ExitThread函数的地址,然后通过搜索获得kernel32.dll的基地址。
线程在被初始化的时,其堆栈指针指向ExitThread函数的地址,windows这样做是为了通过ret返回时来调用ExitThread地址。所以一般我们可以在我们主线程的起始位置(也就是 程序入口点处)通过获得堆栈指针中ExitThread函数(当然你要想创建一个线程时候获得也可以)
我们直接通过 mov edx, [esp] ;获得堆栈指针中ExitThread地址到edx寄存器。因为ExitThread地址在kernel32.dll空间中,所以我们可以通过它往上搜索来获得基地址。
分析过kernel32.dll的朋友应该都知道kernel32.dll的块对齐值是00001000h, 并且一般DLL以1M为边界,所以我们可以通过10000h (64k) 作为跨度,这样可以增加搜索速度。我们如何确定这个地址是基地址,我们都知道我们判断这个地址的前两个字节是否是"MZ",然后定位到PE头结构,然后判断是否是"PE",如果这两个都符合的话则表示我们的地址则是基地址了.
注意,程序要用汇编编写,至少我不会用VC来内联编写,应为这个方法需要在程序的一开始就获取,而用VC编写的会有启动函数,也就是mainCRTStartup这些
代码如下:
.386 .model flat, stdcall option casemap:none include windows.inc include kernel32.inc include user32.inc includelib user32.lib includelib kernel32.lib .const szFormat db ‘kernel32.dll address is: %x‘, 0dh, 0ah, 0 szFormat1 db ‘LoadLibrary kernel32.dll address is: %x‘, 0dh, 0ah, 0 szKrnl32 db ‘kernel32.dll‘, 0 .data? hStdOut dd ? dwWriteBytes dd ? szBuffer db 1024 (?) .code ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 获取字符串长度 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> GetStrLen proc _lpString mov edi, _lpString or ecx, 0ffffffffh ; ecx初值等于-1 xor eax, eax repnz scasb ; 循环扫描,每循环执行一次,ecx-1,直到出现0为止,0也会减1 ; 一个公式(摘自《C++反汇编与逆向分析技术揭秘》P183): ; ecx(终值) = ecx(初值) - (Len + 1) ; ecx(终值) = -1 - (Len + 1) = -(Len + 2) : 将-1代入公式 ; neg(ecx(终值)) = Len + 2 : neg为求补,ecx(终值) = -(Len + 2),求补后为正Len + 2 ; not(ecx(终值)) + 1 = Len + 2 ; not(ecx(终值)) = Len + 1 ; Len = not(ecx(终值)) - 1 not ecx dec ecx mov eax, ecx ret GetStrLen endp start: ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 获取kernel32的基址 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> mov edx, [esp] Next: cmp word ptr [edx], ‘ZM‘ ; 因为读取后edx的内容是高地址在高位,所以需要倒过来写MZ和PE jz IsPe dec edx xor dx, dx jmp Next IsPe: mov eax, [edx + 3ch] cmp word ptr [edx + eax], ‘EP‘ jnz Next ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 将自己获取的和LoadLibrary获取的地址显示出来用于对比 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> invoke GetStdHandle, STD_OUTPUT_HANDLE mov hStdOut, eax invoke wsprintf, addr szBuffer, addr szFormat, edx invoke GetStrLen, addr szBuffer invoke WriteConsole, hStdOut, addr szBuffer, ecx, addr dwWriteBytes, NULL invoke LoadLibrary, addr szKrnl32 invoke wsprintf, addr szBuffer, addr szFormat1, eax invoke GetStrLen, addr szBuffer invoke WriteConsole, hStdOut, addr szBuffer, eax, addr dwWriteBytes, NULL invoke CloseHandle, hStdOut invoke ExitProcess, NULL end start
第二种方法:
Note:这个方法适用于XP, win7上获得是ntdll.dll的地址。
这个方法是遍历遍历seh异常链,然后获得EXCEPTION_REGISTRATION结构prev为-1的异常处理过程地址,这个异常处理过程地址是位于kernel32.dll,通过它搜索得到kernel32.dll的基地址。 搜索的方法在上面我已经说了,通过减去跨度,然后判断地址前两字节是否是"MZ",是的话,继续定位到PE头结构,然后判断前两个字节是否是"PE",不是的话继续减去跨度搜索。直到是为止。
struct EXCEPTION_REGISTRATION prev dd ? handler dd ? ends
遍历的方法也很简单,我们都知道[fs:0]的ExceptionList 指向EXCEPTION_REGISTRATION结构,所以通过[fs:0]获得EXCEPTION_REGISTRATION结构后,判断prev成员是否是-1,如果是的话则取
异常处理过程地址,然后进行搜索。
#include <stdio.h> #include <tchar.h> #include <windows.h> int _tmain(int argc, _TCHAR* argv[]) { DWORD dwKrnlAddr = 0; __asm { mov edx, fs:[0] // 获得EXCEPTION_REGISTRATION结构地址 Next: inc dword ptr [edx] // 将prev+1,如果是-1为0 jz Krnl dec dword ptr [edx] // 不为-1,还原 mov edx, [edx] // 获得prev指向的地址 jmp Next Krnl: dec dword ptr [edx] // 恢复 mov edx, [edx + 4] // 获得handle指向的地址 Looop: cmp word ptr [edx], ‘ZM‘ jz IsPe dec edx xor dx, dx jmp Looop IsPe: mov eax, dword ptr [edx + 3ch] cmp word ptr [edx + eax], ‘EP‘ jnz Next mov dwKrnlAddr, edx } _tprintf(TEXT("Kernel32.dll address: %x\r\n"), dwKrnlAddr); _tprintf(TEXT("GetModuleHandle Kernel32.dll address: %x\r\n"), GetModuleHandle(TEXT("kernel32.dll"))); return 0; }
第三种方法:
此方法是通过TEB获得PEB结构地址,然后再获得PEB_LDR_DATA结构地址,然后遍历模块列表,查找kernel32.dll模块的基地址。
TEB是线程环境块(Thread Environment Block)结构, 我们的fs段选择子所对应的段指向TEB,也就是fs:[0]指向TEB.那么TEB的ProcessEnvironmentBlock结构成员指 向我们的PEB进程环境块结构(Process Environment Block),然后通过PEB结构来获得PEB_LDR_DATA。 接下来我们通过windbg来查看下相关结构。
我们首先来看下TEB结构,通过windbg的dt命令。
lkd> dt _TEB nt!_TEB +0x000 NtTib : _NT_TIB +0x01c EnvironmentPointer : Ptr32 Void +0x020 ClientId : _CLIENT_ID +0x028 ActiveRpcHandle : Ptr32 Void +0x02c ThreadLocalStoragePointer : Ptr32 Void +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
......省略
我们可以看到TEB结构的0x30偏移处存储的我们的PEB结构的地址。。
然后接下来我们来看PEB结构。
lkd> dt _PEB nt!_PEB +0x000 InheritedAddressSpace : UChar +0x001 ReadImageFileExecOptions : UChar +0x002 BeingDebugged : UChar +0x003 SpareBool : UChar +0x004 Mutant : Ptr32 Void +0x008 ImageBaseAddress : Ptr32 Void +0x00c Ldr : Ptr32 _PEB_LDR_DATA ..........省略
我们可以看到PEB结构的0x0c偏移处存储的我们的_PEB_LDR_DATA结构地址。
然后我们再来查看 _PEB_LDR_DATA结构
lkd> dt _PEB_LDR_DATA nt!_PEB_LDR_DATA +0x000 Length : Uint4B +0x004 Initialized : UChar +0x008 SsHandle : Ptr32 Void +0x00c InLoadOrderModuleList : _LIST_ENTRY +0x014 InMemoryOrderModuleList : _LIST_ENTRY +0x01c InInitializationOrderModuleList : _LIST_ENTRY +0x024 EntryInProgress : Ptr32 Void
我们看到这个结构中的模块立标有3个_LIST_ENTRY结构,它们分别是 InLoadOrderModuleList (加载顺序模块列表) InMemoryOrderModuleList(内存顺序模块排列) InInitializationOrderModuleList(初始化顺序模块列表)
然后我们继续查看这个结构。
nt!_LIST_ENTRY +0x000 Flink : Ptr32 _LIST_ENTRY +0x004 Blink : Ptr32 _LIST_ENTRY 这个结构我们可以看到它是一个双向链表,Flink表示从前往后, Blink表示从后往前。
并且这三个链表的结点是均是指向此结构
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;
我们一般取它的初始化顺序结构(InInitializationOrderModuleList)的Flink成员指向的_LDR_MODULE结构的BaseAddress成员则为我们需要的基地址,当然由于第一个是 ntdll,所以取第二个则为我们的Kernel32.dll。
注意,这里在Win7中为第3个, 第2个是kernelbase.dll,可以通过LDR_MODULE结构的BaseDllName比较dll名称后来获取,参见:
- #include <stdio.h>
- #include <tchar.h>
- #include <windows.h>
- int _tmain(int argc, _TCHAR* argv[])
- {
- DWORD dwKrnlAddr = 0;
- __asm
- {
- mov edx, fs:[30h] // 取得PEB
- mov edx, [edx + 0ch] // PEB_LDR_DATA
- mov edx, [edx + 1ch] // 获得InInitializationOrderModuleList.Flink指向的地址,也即指向第一个LDR_MODULE
- mov edx, [edx] // 因为edx当前为InInitializationOrderModuleList
- // 而它的第一成员Flink又指向下一个LDR_MODULE, 所以直接读取就是下一个
- mov edx, [edx + 8h]
- mov dwKrnlAddr, edx
- }
- _tprintf(TEXT("Kernel32.dll address: %x\r\n"), dwKrnlAddr);
- _tprintf(TEXT("GetModuleHandle Kernel32.dll address: %x\r\n"),
- GetModuleHandle(TEXT("kernel32.dll")));
- return 0;
- }