手戳shellcode编写入门第一课(动态函数地址调用函数)
一、介绍
在程序免杀中,我们通常不会直接通过函数名称方式来调用函数,一般都是通过执行shellcode来执行我们需要的代码。如果直接将exe文件解析出来的shellcode会发现这些shellcode不能直接运行,因为函数地址调用的问题。因此我们需要动态获取函数地址来调用shellcode。为了了解shellcode的基础,我们先使用c++源码方式来学习,如果动态的获取函数地址,然后再使用汇编编写以下功能实现shellcode的完成编写。
https://arv000.blog.youkuaiyun.com/article/details/141233276
二、PE文件讲解
在编写思路之前我们需要大致了解一下PE文件格式,PE文件被映射到内存中。我们如何通过映射关系拿到对应的函数在内存中的地址。然后通过这个地址调用对应的函数。
2.1 PE文件类型
PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)
2.2 PE文件格式
下图是PE文件格式,因为只需要找到函数地址,我们只需要关注PE文件头中的(DOS头部、IMAGE_NT_HEADERS)就可以了。这个部分很重要,需要读者自己去查看相关资料。
每一个exe和dll都是按照下面的内容映射到内存中的,一个exe的运行,你可以理解为多个pe文件映射到内存中,有多个相同的一下结构。
2.3 相对虚拟地址直接调用函数
在dll中保存了函数的相对虚拟地址(RVA)的信息。
我们可以通过dumpbin方法拿到相对虚拟地址(RVA)的信息。
kernel32.dll文件存储在C:\Windows\System32中
打开Native Tools Command Prompt for VS 2022控制台程序
如下图:
执行命令
cd C:\Windows\System32
dumpbin /exports kernel32.dll | findstr LoadLibraryA
0001F520 为函数地址的偏移量。
获取函数LoadLibraryA的地址偏移量。
RVA = 基地址 + 地址偏移量
#include <iostream>
#include <Windows.h>
int main(){
HANDLE baseAddress = LoadLibraryA("kernel32.dll");
std::cout << "baseAddress:" << baseAddress << std::endl;
std::cout << "LoadLibraryA:" << &LoadLibraryA << std::endl;
long offset = (long)&LoadLibraryA - (long)baseAddress;
std::cout << std::hex << "offset:" << offset << std::endl;
return 0;
}
运行结果可以看到程序中的offset的值和dumpbin查询出来的值是一样的。
三、开发思路
思路如下:
我们应该先拿到LoadLibarayA的函数地址才能加载其他的DLL动态库(我们需要的动态库不一定在程序加载的时候就已经加载进来,因此需要使用LoadLibarayA加载我们需要的dll,例如例子中的MessageBoxA这个函数所在的动态库位user32.dll),那么LoadLibarayA又在那个动态库中呢?LoadLibarayA在KERNEL32.DLL动态库中,KERNEL32.DLL是程序运行一定加载的一个库。所以我们需要先找到KERNEL32.DLL基地址,然后再找LoadLibarayA的函数地址。
步骤如下:
- 先获取PEB(Process Environment Block,进程环境块)的信息。
- 通过PEB获取InLoadOrderModuleList(InLoadOrderModuleList列表中包含了所有加载的模块,包括exe文件以及各个需要加载的dll)
- 循环InLoadOrderModuleList内容,对比dllName名称找到KERNEL32.DLL基地址。
- 通过KERNEL32.DLL基地址拿到KERNEL32.DLL基地址导出表的地址。
- 通过名称对比拿到LoadLibarayA的函数地址。
- 通过LoadLibarayA地址调用LoadLibarayA函数,获取到user32.dll的模块基地址
- 同理通过user32.dll基地址以及名称获取MessageBoxA的函数地址。
- 通过函数地址调用MessageBoxA函数。
四、开发源码
4.1 环境说明
操作系统:window10
架构:x64
开发工具:visual studio 2022
4.2 获取本程序的所有加载模块
获取本程序的所有加载模块的名称,以及每个模块的基地址。
CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.0)
project(printAllDLLModule)
file(GLOB SRC_LIST *.cpp phnt/*.h)
include_directories(phnt)
add_executable(${PROJECT_NAME} ${SRC_LIST})
#include "phnt/phnt_windows.h"
#include "phnt/phnt.h"
#include <iostream>
// 打印所有的dll模块内容。当然其中也包括exe本身。
void printAllDLLModule(){
auto peb = (PEB*)NtCurrentTeb()->ProcessEnvironmentBlock;
// 获取Ldr并遍历InLoadOrderModuleList来查找模块
if (peb && peb->Ldr) {
PLIST_ENTRY moduleList = &peb->Ldr->InLoadOrderModuleList;
PLIST_ENTRY entry = moduleList->Flink;
while (entry != moduleList) {
// 获取当前模块的LDR_DATA_TABLE_ENTRY结构
PLDR_DATA_TABLE_ENTRY currentModule = CONTAINING_RECORD(entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
// 打印模块名称和基址
std::wcout << "Module Name: " << currentModule->BaseDllName.Buffer << std::endl;
std::wcout << "Base Address: " << currentModule->DllBase << std::endl;
// 继续下一个模块
entry = entry->Flink;
}
} else {
std::cerr << "Failed to retrieve PEB or PEB->Ldr is NULL." << std::endl;
}
return;
}
int main(){
printAllDLLModule();
return 0;
}
- 运行结果如下:
将所有程序需要运行时加载依赖的DLL地址已经名字都打印了出来。
打印exe运行时加载的所有dll模块地址以及模块名称
https://download.youkuaiyun.com/download/arv002/89655994
4.2 获取某一个模块的所有函数名称,以及函数地址。
获取某一个模块中的所有函数的函数名称以及虚拟函数地址。
CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.0)
project(printAllFunctionName)
file(GLOB SRC_LIST *.cpp phnt/*.h)
include_directories(phnt)
add_executable(${PROJECT_NAME} ${SRC_LIST})
#include "phnt/phnt_windows.h"
#include "phnt/phnt.h"
#include <iostream>
void printAllFunctionName(HANDLE hModule){
if (hModule == NULL) {
std::cerr << "Failed to load DLL." << std::endl;
return ;
}
// 获取PE头指针
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDOSHeader->e_lfanew);
// 获取导出表的地址
DWORD exportDirRVA = pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + exportDirRVA);
// 获取导出表的信息
DWORD* pAddressOfFunctions = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfFunctions);
DWORD* pAddressOfNames = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfNames);
WORD* pAddressOfNameOrdinals = (WORD*)((BYTE*)hModule + pExportDir->AddressOfNameOrdinals);
// 遍历所有导出函数
for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
char* functionName = (char*)((BYTE*)hModule + pAddressOfNames[i]);
DWORD functionRVA = pAddressOfFunctions[pAddressOfNameOrdinals[i]];
void* functionAddress = (BYTE*)hModule + functionRVA;
std::cout << "Function Name: " << functionName << std::endl;
std::cout << "Function Address: " << functionAddress << std::endl;
}
return;
}
int main(){
HANDLE hKernel32 = LoadLibraryA("kernel32.dll");
printAllFunctionName(hKernel32);
CloseHandle(hKernel32);
return 0;
}
打印exe运行时模块中所有 的函数名称以及地址
https://download.youkuaiyun.com/download/arv002/89655996
- 运行结果如下:
4.4 C++完整源码
CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.0)
project(demo)
file(GLOB SRC_LIST *.cpp phnt/*.h)
include_directories(phnt)
add_executable(${PROJECT_NAME} ${SRC_LIST})
main.cpp文件内容如下:
#include "phnt/phnt_windows.h"
#include "phnt/phnt.h"
#include <iostream>
HANDLE SreachDLL(WCHAR* dllName){
auto peb = (PEB*)NtCurrentTeb()->ProcessEnvironmentBlock;
// 获取Ldr并遍历InLoadOrderModuleList来查找模块
if (peb && peb->Ldr) {
PLIST_ENTRY moduleList = &peb->Ldr->InLoadOrderModuleList;
PLIST_ENTRY entry = moduleList->Flink;
while (entry != moduleList) {
// 获取当前模块的LDR_DATA_TABLE_ENTRY结构
PLDR_DATA_TABLE_ENTRY currentModule = CONTAINING_RECORD(entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
// 打印模块名称和基址
std::wcout << "Module Name: " << currentModule->BaseDllName.Buffer << std::endl;
std::wcout << "Base Address: " << currentModule->DllBase << std::endl;
if(!wcscmp(currentModule->BaseDllName.Buffer,dllName)){
return currentModule->DllBase;
}
// 继续下一个模块
entry = entry->Flink;
}
} else {
std::cerr << "Failed to retrieve PEB or PEB->Ldr is NULL." << std::endl;
}
return nullptr;
}
void * GetProcNameE(HANDLE hModule,const char *funName){
// 假设DLL基地址已经获取到,例如通过加载DLL的句柄
if (hModule == NULL) {
std::cerr << "Failed to load DLL." << std::endl;
return nullptr;
}
// 获取PE头指针
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDOSHeader->e_lfanew);
// 获取导出表的地址
DWORD exportDirRVA = pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + exportDirRVA);
// 获取导出表的信息
DWORD* pAddressOfFunctions = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfFunctions);
DWORD* pAddressOfNames = (DWORD*)((BYTE*)hModule + pExportDir->AddressOfNames);
WORD* pAddressOfNameOrdinals = (WORD*)((BYTE*)hModule + pExportDir->AddressOfNameOrdinals);
// 遍历所有导出函数
for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
char* functionName = (char*)((BYTE*)hModule + pAddressOfNames[i]);
DWORD functionRVA = pAddressOfFunctions[pAddressOfNameOrdinals[i]];
void* functionAddress = (BYTE*)hModule + functionRVA;
if(!strcmp(functionName, funName)){
std::cout << "Function Name: " << functionName << std::endl;
std::cout << "Function Address: " << functionAddress << std::endl;
return functionAddress;
}
}
return nullptr;
}
#define CALL_API(hModule,func) ((decltype(func)*)GetProcNameE(hModule,#func))
int main(){
HANDLE hModule = SreachDLL(L"KERNEL32.DLL");
typedef HMODULE(WINAPI *LoadLibraryFunc)(LPCSTR lpLibFileName);
typedef int(WINAPI *MessageBoxAFunc)(HWND hWnd,LPCSTR lpText, LPCSTR lpCaption,UINT uType);
// 函数执行方法一;
// void * pLoadLibraryA = GetProcNameE(hModule,"LoadLibraryA");
// HANDLE hUser32 = ((LoadLibraryFunc)pLoadLibraryA)("user32.dll");
// void * pMessageBoxA = GetProcNameE(hUser32,"MessageBoxA");
// ((MessageBoxAFunc)pMessageBoxA)(NULL,"hello","hello",0);
// 函数执行方法二:
// HANDLE hUser32 = ((decltype(LoadLibraryA)*)GetProcNameE(hModule,"LoadLibraryA"))("user32.dll");
// ((decltype(MessageBoxA)*)GetProcNameE(hUser32,"MessageBoxA"))(NULL,"hello","hello",0);
// 函数执行方法三:
HANDLE hUser32 = CALL_API(hModule,LoadLibraryA)("user32.dll");
CALL_API(hUser32,MessageBoxA)(NULL,"hello","hello",0);
return 0;
}
4.5 运行结果
五、源码地址
PPE解析+函数地址调用函数
https://download.youkuaiyun.com/download/arv002/89655989
打印exe运行时加载的所有dll模块地址以及模块名称
https://download.youkuaiyun.com/download/arv002/89655994
打印exe运行时模块中所有 的函数名称以及地址
https://download.youkuaiyun.com/download/arv002/89655996
文章地址:https://arv000.blog.youkuaiyun.com/article/details/141233276