1.2 2.kernel32基地址获得
kernel32基地址的获得也是病毒中必不可少的技术,因为在win32环境中一般初始化PE文件的时候,我们的ntdll.dll, kernel32.dll是随进程以及线程初始化时候加载的。所以即使程
序中我们不引入任何输入表结构,这两个DLL在我们程序进程以及线程初始化时也必须加载。所以我们今天这篇文章的目的就是在我们的进程内存空间中来获得加载kernel32.dll的基地址。因为我们获得
kernel32.dll的基地址后,就可以获得LoadLibray函数来继续加载其他的动态链接库,然后再通过我们的GetProcAdress函数来获得相应需要的api函数地址,这样也就做到了可移植性的要求,所有的函数
均是自己动态获取并填充。
今天我给大家介绍三种获取kernel32.dll基地址的技术,其实还有部分方法也可以获得,只是这3种比较通用,也是病毒中经常用到的。它们分别是
1. 通过线程初始化时, 获得esp堆栈指针中的ExitThread函数的地址,然后通过搜索获得kernel32.dll的基地址。
2. 遍历seh异常链,然后获得EXCEPTION_REGISTRATION结构prev为-1的异常处理过程地址,这个异常处理过程地址是位于kernel32.dll,通过它搜索得到kernel32.dll的基地址。
3. 通过TEB获得PEB结构地址,然后再获得PEB_LDR_DATA结构地址,然后遍历模块列表,查找kernel32.dll模块的基地址。
1.
第一种方法:
我们上面介绍了线程在被初始化的时,其堆栈指针指向ExitThread函数的地址,windows这样做是为了通过ret返回时来调用ExitThread地址。所以一般我们可以在我们主线程的起始位置(也就是
程序入口点处)通过获得堆栈指针中ExitThread函数(当然你要想创建一个线程时候获得也可以o(∩_∩)o...哈哈)。
我们直接通过
mov edx, [esp] ;获得堆栈指针中ExitThread地址到edx寄存器。因为ExitThread地址在kernel32.dll空间中,所以我们可以通过它往上搜索来获得基地址。
分析过kernel32.dll的朋友应该都知道kernel32.dll的块对齐值是00001000h, 并且一般DLL以1M为边界,所以我们可以通过10000h (64k) 作为跨度,这样可以增加搜索速度。我们如何确定这个
地址是基地址,我们都知道我们判断这个地址的前两个字节是否是"MZ",然后定位到PE头结构,然后判断是否是"PE",如果这两个都符合的话则表示我们的地址则是基地址了。。
有了以上的了解我们就可以来写代码了。
mov edx, [esp]
.Next:
dec edx ;
xor dx, dx ; 减去跨度
cmp word [edx], "MZ"
jz .IsPe
jmp .Next
.IsPe:
mov eax, [edx+3ch]
cmp word [eax+edx], 'PE'
jnz .Next
xchg eax, edx ; eax = kernel32 基地址
这里贴一个封装过的过程。
;++ ; ; int ; GetKrnlBase( ; int KrnlApiAddress ; ) ; ; Routine Description: ; ; 获得kernel32基地址 ; ; Arguments: ; ; (esp) - return address ; ; Data (esp+4) - KrnlApiAddress, ptr ApiAddress ; ; ; Return Value: ; ; eax = krnl32 base ; ;-- GetKrnlBase: pop eax pop edx push eax .Next: cmp word [edx], 'MZ' jz .IsPe dec edx xor dx, dx jmp .Next .IsPe: mov eax, [edx+3ch] cmp word [eax+edx], 'PE' jnz .Next xchg eax, edx ret
2.
第二种方法:
这个方法是遍历遍历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,如果是的话则取
异常处理过程地址,然后进行搜索。
有了思路,我们开始写代码。
mov edx, [fs:0] ;获得EXCEPTION_REGISTRATION结构地址
.Next:
inc dword [edx] ;将prev成员 + 1
jz .Krnl ;如果ZF = 1, 则跳转.Krnl
dec dword [edx]
mov edx, [edx]
jmp .Next
.Krnl:
dec dword [edx] ;恢复 -1
mov edx, [edx+4] ;获得handler, 然后下面进行搜索..
.Loop:
cmp word [edx], 'MZ'
jz .IsPe
dec edx
xor dx, dx
jmp .Loop
.IsPe:
mov eax, [edx+3ch]
cmp word [eax+edx], 'PE'
jnz .Next
xchg eax, edx ; eax = kernel32 基地址
一个封装过的过程:
;++ ; ; int ; GetKrnlBase2( ; void ; ) ; ; Routine Description: ; ; 获得kernel32基地址 ; ; Arguments: ; ; (esp) - return address ; ; ; Return Value: ; ; eax = krnl32 base ; ;-- GetKrnlBase2: mov edx, [fs:0] .Next: inc dword [edx] jz .Krnl dec dword [edx] mov edx, [edx] jmp .Next .Krnl: dec dword [edx] mov edx, [edx+4] .Loop: cmp word [edx], 'MZ' jz .IsPe dec edx xor dx, dx jmp .Loop .IsPe: mov eax, [edx+3ch] cmp word [eax+edx], 'PE' jnz .Next xchg eax, edx ret
第三种方法:
此方法是通过TEB获得PEB结构地址,然后再获得PEB_LDR_DATA结构地址,然后遍历模块列表,查找kernel32.dll模块的基地址。
TEB是线程环境块(Thread Environment Block)结构, 我们的fs段选择子所对应的段指向TEB,也就是fs:0(注意这里可不是“[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结构地址的代码写出来。
mov eax, [fs:30h] ;Get Peb
mov eax, [eax+0ch] ;Get _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。
OK我们开始写代码,上面我们已经写了取得_PEB_LDR_DATA结构地址的代码了。我们把它copy下来。
mov eax, [fs:30h] ;Get Peb
mov eax, [eax+0ch] ;Get _PEB_LDR_DATA
mov eax, [eax+1ch];Get InInitializationOrderModuleList.Flink, 此时eax指向的是ntdll模块的InInitializationOrderModuleList线性地址。所以我们获得它的下一个则是kernel32.dll
mov eax, [eax]
mov eax, [eax+8] ; 8 = sizeof.LIST_ENTRY
ret
同样我们贴一个封装过的过程。
;++ ; ; int ; GetKrnlBase3( ; void ; ) ; ; Routine Description: ; ; 获得kernel32基地址 ; ; Arguments: ; ; (esp) - return address ; ; ; Return Value: ; ; eax = krnl32 base ; ;-- GetKrnlBase3: mov eax, [fs:30h] mov eax, [eax+0ch] mov eax, [eax+1ch] mov eax, [eax] mov eax, [eax+8h] ret