前言:
学习syscall和免杀绕不开PEB这个部分,我们要对相关结构熟悉,既要会手动解析,也能利用代码解析。
PEB结构:
经典大图,永不过时:
注:PEB的结构微软并未完全公开,以上图片可以参考但不一定权威
如果你对偏移比较熟悉的话可以直接用指针+偏移找,不需要特地定义结构体,而且双向链表_LIST_ENTRY
这个是公开的。
1 2 3 4
| typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
|
自定义PEB的结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| typedef struct _PEB { BOOLEAN InheritedAddressSpace; BOOLEAN ReadImageFileExecOptions; BOOLEAN BeingDebugged; BOOLEAN Spare; HANDLE Mutant; PVOID ImageBase; PPEB_LDR_DATA LoaderData; PVOID ProcessParameters; PVOID SubSystemData; PVOID ProcessHeap; PVOID FastPebLock; PVOID FastPebLockRoutine; PVOID FastPebUnlockRoutine; ULONG EnvironmentUpdateCount; PVOID* KernelCallbackTable; PVOID EventLogSection; PVOID EventLog; PVOID FreeList; ULONG TlsExpansionCounter; PVOID TlsBitmap; ULONG TlsBitmapBits[0x2]; PVOID ReadOnlySharedMemoryBase; PVOID ReadOnlySharedMemoryHeap; PVOID* 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; PVOID** 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;
|
我们主要是需要0x0c(64位为0x18)偏移处的PPEB_LDR_DATA
结构
1 2 3 4 5 6 7 8
| typedef struct _PEB_LDR_DATA { ULONG Length; ULONG Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, * PPEB_LDR_DATA;
|
在PEB_LDR_DATA
的0x0c、0x14、0x1c中为三个双向链表LIST_ENTRY
三个链表加载模块分别代表模块加载顺序,模块在内存中的加载顺序以及模块初始化装载的顺序,PEB_LDR_DATA
结构类似于一个火车头,三个双向链表LIST_ENTRY
意思相同,只是顺序不一样,都指向下一个_LDR_DATA_TABLE_ENTRY结构(也有的定义为_LDR_MODULE
结构)毕竟微软没有公开结构,只要我们所需的偏移一致就行。
_LDR_DATA_TABLE_ENTRY 32位版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| struct _LDR_DATA_TABLE_ENTRY { struct _LIST_ENTRY InLoadOrderLinks; struct _LIST_ENTRY InMemoryOrderLinks; struct _LIST_ENTRY InInitializationOrderLinks; VOID* DllBase; VOID* EntryPoint; ULONG SizeOfImage; struct _UNICODE_STRING FullDllName; struct _UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { struct _LIST_ENTRY HashLinks; struct { VOID* SectionPointer; ULONG CheckSum; }; }; union { ULONG TimeDateStamp; VOID* LoadedImports; }; struct _ACTIVATION_CONTEXT* EntryPointActivationContext; VOID* PatchInformation; struct _LIST_ENTRY ForwarderLinks; struct _LIST_ENTRY ServiceTagLinks; struct _LIST_ENTRY StaticLinks; VOID* ContextInformation; ULONG OriginalBase; union _LARGE_INTEGER LoadTime; };
|
_LDR_DATA_TABLE_ENTRY64 64位版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| typedef struct _LDR_DATA_TABLE_ENTRY64 { struct LIST_ENTRY64 InLoadOrderLinks; struct LIST_ENTRY64 InMemoryOrderLinks; struct LIST_ENTRY64 InInitializationOrderLinks; VOID* DllBase; VOID* EntryPoint; ULONG SizeOfImage; struct _UNICODE_STRING FullDllName; struct _UNICODE_STRING BaseDllName; ULONG Flags; USHORT LoadCount; USHORT TlsIndex; union { struct LIST_ENTRY64 HashLinks; struct { VOID* SectionPointer; ULONG CheckSum; }; }; union { ULONG TimeDateStamp; VOID* LoadedImports; }; struct _ACTIVATION_CONTEXT* EntryPointActivationContext; VOID* PatchInformation; struct LIST_ENTRY64 ForwarderLinks; struct LIST_ENTRY64 ServiceTagLinks; struct LIST_ENTRY64 StaticLinks; VOID* ContextInformation; ULONGLONG OriginalBase; union _LARGE_INTEGER LoadTime; }LDR_DATA_TABLE_ENTRY64, * PLDR_DATA_TABLE_ENTRY64;
|
_LDR_MODULE 版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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;
|
PPEB_LDR_DATA
结构是一个入口,类似于火车头,里面_LIST_ENTRY->Flink
指向第一个_LDR_DATA_TABLE_ENTRY,然后第一个指向第二个以此类推,最后一个指向第一个,而_LIST_ENTRY->Blink
指向的是上一个火车,第二个指向火车头,火车头指向最后一个。
这是广为流传的一张图,大多数文章都会拿它做案例,但其实它有一点点错误(吐槽:这么多人都没发现),Blink指向的并不是上一个Blink,而是指向上一个LIST_ENTRY
的地址,下面进行详细分析。
解析PEB:
我们如何找到PEB的地址?这里有三种方法:
- 查看OD等调试器的寄存器窗口,fs段寄存器的后面会接着TEB结构指针。直接在内存窗口跳过去即可
- 通过fs的值拆分成段选择子,通过GDT表查找段描述符,得到一个3环的调用门….#*#%$()#….. 很显然我不会,直接放弃。
- 第三种是最方便的,32位:fs:[0x18]存储着TEB结构指针,fs:[0x30]存储着PEB结构指针。64位:gs:[0x60]存储着PEB结构指针。
为什么不能通过fs直接过去呢?因为fs存储的是段选择子,不是真正的地址所以需要通过偏移。
接下来尝试手动解析:这里用的是64位exe,工具x64dbg
利用gs:[0x60]获取PEB地址,也可以直接用peb()快捷获取,得到地址:000000CA38C50000,ctrl+g跳转,此时就是PEB的结构指针。我们在0x18h(在64位下偏移是18h)处找到PPEB_LDR_DATA
地址00007FF8E7B1C4C0
跳转到PPEB_LDR_DATA
处,0X20处偏移是我们要找的第一个LDR_DATA_TABLE_ENTRY
的InMemoryOrderModuleList
地址:0000021892B02820,此时
- Flink地址:0000021892B02650
- Blink地址:00007FF8E7B1C4E0
- EntryPoint:00007FF7A96916E0
- FullDllName:(0x58-0x60)
- BaseDllName:(0x68-0x70)
根据Flink跳转至下一个LDR_DATA_TABLE_ENTRY
下一个_LDR_DATA_TABLE_ENTRY结构的InMemoryOrderModuleList
处
此时Flink:0000021892B02D70指向下一个InMemoryOrderModuleList
地址,Blink:0000021892B02820刚好指向了上一个InMemoryOrderModuleList
地址
- FullDllName:c:\windows\system32\ntdll.dll
- BaseDllName:ntdll.dll
继续下一跳,Flink:0000021892B02D70,Blink:0000021892B02650刚好指向ntdll.dll的InMemoryOrderModuleList
起始地址
我们可以依次遍历下去,找到我们所需要的基址
代码实现:
微软很贴心的提供了两个获取PEB的api
我们也可以不用Windows的api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| .CODE GetPeb64 PROC mov rax,gs:[60h] ret GetPeb64 ENDP END
.CODE GetPeb32 PROC mov rax, fs:[30h]; ret; GetPeb32 ENDP END
.CODE GetPebLdr PROC mov rax, gs:[60h]; add rax, 18h; mov rax, [rax]; ret; GetPebLdr ENDP END
|
asm.h
1 2 3
| #define __ASMCODE_H
extern "C" PVOID64 _cdecl GetPeb();
|
定义结构体查找导出函数地址:
注:结构体定义太长了没贴上来,对着上面抄就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include <windows.h> #include <winternl.h> #include "global.h" #include <stdio.h> int main() { PIMAGE_DOS_HEADER pDos = NULL; PIMAGE_FILE_HEADER pFile = NULL; PIMAGE_OPTIONAL_HEADER pOptional = NULL; PIMAGE_EXPORT_DIRECTORY pExport = NULL; PMPEB peb = (PMPEB)__readgsqword(0x60); PLDR_DATA_TABLE_ENTRY64 pLdr = NULL; PBYTE imageBase = NULL;
pLdr = (PLDR_DATA_TABLE_ENTRY64)((PBYTE)peb->LoaderData->InMemoryOrderModuleList.Flink - 0x10); wprintf(L"[*]name:%s\n", pLdr->BaseDllName.Buffer); printf("[*]DllBase:%p\n", (PBYTE)pLdr->DllBase); while ((_wcsicmp(pLdr->BaseDllName.Buffer, L"ntdll.dll")) != 0) { pLdr = (PLDR_DATA_TABLE_ENTRY64)((PBYTE)pLdr->InMemoryOrderLinks.Flink - 0x10); imageBase = (PBYTE)pLdr->DllBase; wprintf(L"[*]name: %s\n",pLdr->BaseDllName.Buffer); printf("[*]DllBase: %p\n", imageBase); } printf("[*]find ntdll.dll address.....\n");
pDos = (PIMAGE_DOS_HEADER)imageBase; if (*(PWORD)pDos != 0x5A4D) { printf("[*]pe parsing failed.."); } printf("\n"); pOptional = (PIMAGE_OPTIONAL_HEADER)((PBYTE)pDos + pDos->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER)); pExport = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)imageBase + pOptional->DataDirectory[0].VirtualAddress);
PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)(imageBase + pExport->AddressOfFunctions)); PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)imageBase + pExport->AddressOfNames); PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)imageBase + pExport->AddressOfNameOrdinals); for (WORD cx = 0; cx < pExport->NumberOfNames; cx++) { PCHAR pczFunctionName = (PCHAR)((PBYTE)imageBase + pdwAddressOfNames[cx]); PVOID pFunctionAddress = (PBYTE)imageBase + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]]; printf("[*]Function Name:%s\tFunction Address:%p\n", pczFunctionName, pFunctionAddress); } return 0; }
|
直接利用偏移:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| #include <windows.h> #include <winternl.h> #include "getPeb.h"; #include <stdio.h>
typedef BOOL(WINAPI* CustomVirtualProtect)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); CustomVirtualProtect myVirtualProtect;
PVOID GetFunctionAddress(PVOID pBaseAddress, PCHAR pFunctionName) { PIMAGE_DOS_HEADER pDos = NULL; PIMAGE_FILE_HEADER pFile = NULL; PIMAGE_OPTIONAL_HEADER pOptional = NULL; PIMAGE_EXPORT_DIRECTORY pExport = NULL; PVOID getFunctionAddress = NULL;
pDos = (PIMAGE_DOS_HEADER)pBaseAddress; if (*(PWORD)pDos != 0x5A4D) { printf("[*]pe parsing failed.."); } printf("\n"); pOptional = (PIMAGE_OPTIONAL_HEADER)((PBYTE)pDos + pDos->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER)); pExport = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)pBaseAddress + pOptional->DataDirectory[0].VirtualAddress);
PDWORD pdwAddressOfFunctions = (PDWORD)((PBYTE)pBaseAddress + pExport->AddressOfFunctions); PDWORD pdwAddressOfNames = (PDWORD)((PBYTE)pBaseAddress + pExport->AddressOfNames); PWORD pwAddressOfNameOrdinales = (PWORD)((PBYTE)pBaseAddress + pExport->AddressOfNameOrdinals); for (WORD cx = 0; cx < pExport->NumberOfNames; cx++) { PCHAR pczFunctionName = (PCHAR)((PBYTE)pBaseAddress + pdwAddressOfNames[cx]); PVOID pFunctionAddress = (PBYTE)pBaseAddress + pdwAddressOfFunctions[pwAddressOfNameOrdinales[cx]]; if (0 == _strnicmp(pczFunctionName, pFunctionName, strlen(pczFunctionName))) { printf("[*]Function Name:%s\tFunction Address:%p\n", pczFunctionName, pFunctionAddress); getFunctionAddress = pFunctionAddress; break; } } return getFunctionAddress; }
int main() { PVOID64 peb = GetPeb64(); PVOID64 PEB_LDR_DATA = *(PVOID64**)((PBYTE)peb + 0x18); PVOID64 PEB_LDR_TABLE_ENTRY = (PVOID64)((PBYTE)PEB_LDR_DATA + 0x20); UNICODE_STRING* fullName = NULL; LIST_ENTRY* pListEntry = NULL; HMODULE hKernel32 = NULL; PVOID apiID = NULL; pListEntry = (LIST_ENTRY*)PEB_LDR_TABLE_ENTRY; do { pListEntry = pListEntry->Flink; fullName = (UNICODE_STRING*)((PBYTE)pListEntry + 0x48); hKernel32 = (HMODULE)(*((PULONG64)((PBYTE)pListEntry + 0x20))); wprintf(L"[*]moduleNaame: %s\n", fullName->Buffer); wprintf(L"[*]moduleAddress: %p\n", hKernel32);
} while ((_wcsicmp(fullName->Buffer, L"kernel32.dll")) != 0); printf("find address....\n");
apiID = GetFunctionAddress((PVOID)hKernel32, (PCHAR)"VirtualProtect"); myVirtualProtect = (CustomVirtualProtect)(DWORD)apiID; return 0; }
|
注意事项:取值时要注意是指针还是二重指针,数据宽度要注意
参考文章: