简介
搜索"导入表注入", 网上大堆的博客和代码, 统统都是修改PE文件实现的. 这里将介绍exe加载到内存后, 修改主模块映像, 而不必去改变本地的exe文件的注入方法.
原理
注入原理
简单来说, exe被加载时, 会遍历导入表, 依次使用LoadLibrary
加载导入表的依赖dll, 而我们要做的是, 在exe被加载前, 就给导入表新增一个项, 使其能够加载我们指定的dll.
PE知识
这一章, 我们要用到PE文件的解析, 导入表的遍历, 导入表项的添加等功能.下面来复习一下:
Dos头固定占0x40大小, 其最后4字节是一个叫AddressOfNewExeHeader
的成员, 指示的是Nt头相对于文件的偏移.
在Nt头中又分为三部分, 分别是Signature
,FileHeader
,OptionalHeader
, Signature
的值固定为0x4550, 翻译成ASCII是PE
两个字符, FileHeader
文件头中比较重要的成员是SizeOfOptionalHeader
, 这决定了我们要如何去解析扩展头. OptionalHeader
扩展头中主要用到的成员DataDirArray
, 该成员是扩展头的最后一个成员,它是一个数组, 里面记录了各种表, 如导出表,导入表, 资源表, 重定位表等, 这一章主要要用到的是导入表, 即DataDirArray[1], DataDirArray[1]里面有两个成员, 分别是VirtualAddress
和Size
,指定了导入表的RVA和大小, 后面我们建立新的导入表后, 这两个成员也需要更改.
导入表的
VirtualAddress
指向的是IMAGE_IMPORT_DESCRIPTOR
数组, 这个数组的最后一项要置空, 因为PE文件是靠IMAGE_IMPORT_DESCRIPTOR
的最后一个成员FirstThunk
是否为NULL
来判断是否为最后一个导入表项的, 而不是DataDirArray[1].Size.
实现细节
启动进程
前面提到, 由于导入表被加载的时机比较早, 所以为实现动态导入表注入, 我们要以挂起的方式来创建目标进程, 代码如下
HANDLE createProcessSuspendly(wstring processFileName)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
if (CreateProcessW(processFileName.c_str(), NULL, NULL, NULL, FALSE, IDLE_PRIORITY_CLASS | CREATE_SUSPENDED, NULL, NULL, &si, &pi))
{
CloseHandle(pi.hProcess);
return pi.hThread;
}
return INVALID_HANDLE_VALUE;
}
获取进程句柄
#include <TlHelp32.h>
DWORD getProcessIdByName(const wchar_t* name)
{
PROCESSENTRY32 entry = { sizeof(PROCESSENTRY32) };
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
DWORD id = 0;
if (INVALID_HANDLE_VALUE == hSnapshot)
{
return 0;
}
if (!Process32FirstW(hSnapshot, &entry))
{
return 0;
}
do
{
if (wcscmp(entry.szExeFile, name) == 0)
{
return entry.th32ProcessID;
}
} while (Process32Next(hSnapshot, &entry));
return 0;
}
......
DWORD pid = getProcessIdByName(processName);
if (pid == 0) return false;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
if (hProcess == INVALID_HANDLE_VALUE) return false;
遍历模块
一般遍历模块的方法是使用EnumProcessModulesEx
来遍历, 但这种方式对部分程序不起作用. 没有深究原因, 我更换了另一种遍历模块的方案, 即通过内存搜索的方式, 来寻找所有的模块, 这种方式会找到很多在模块列表看不到的dll, 但这不妨碍我们找到主模块的基址.
// 这里代码从IDA copy的
unsigned __int64 __fastcall findNextModule(HANDLE hProcess, __int64 base, DWORD *buffer)
{
unsigned __int64 address; // rbx
DWORD v6; // eax
struct _MEMORY_BASIC_INFORMATION mbi; // [rsp+30h] [rbp-C8h] BYREF
HANDLE v9; // [rsp+60h] [rbp-98h]
DWORD *v10; // [rsp+70h] [rbp-88h]
__int16 Buffer[32]; // [rsp+80h] [rbp-78h] BYREF
mbi.BaseAddress = 0;
mbi.AllocationBase = 0;
mbi.AllocationProtect = 0;
mbi.RegionSize = 0;
mbi.State = 0;
mbi.Type = 0;
address = 0x10000;
if (base)
address = base + 0x10000;
while (VirtualQueryEx(hProcess, (LPCVOID)address, &mbi, 0x30ui64)
&& (mbi.RegionSize & 0xFFF) != 0xFFFi64
&& (char *)mbi.BaseAddress + mbi.RegionSize >= (PVOID)address)
{
if (mbi.State == 4096)
{
v6 = mbi.Protect;
// 这里Buffer即Dos头
if (LOBYTE(mbi.Protect) != 1
&& !_bittest((const long *)&v6, 8u)
&& ReadProcessMemory(hProcess, (LPCVOID)address, Buffer, 0x40ui64, 0i64)
&& Buffer[0] == 0x5A4D
&& *(unsigned int *)&Buffer[30] <= mbi.RegionSize
&& *(DWORD *)&Buffer[30] >= 0x40u
&& ReadProcessMemory(hProcess, (LPCVOID)(address + *(int *)&Buffer[30]), buffer, 0xF8ui64, 0i64)
&& *buffer == 0x4550)
{
return address;
}
}
address = (unsigned __int64)mbi.BaseAddress + mbi.RegionSize;
}
return 0i64;
}
HMODULE getModuleByName(string moduleName)
{
// 这里用于存放PE文件的头0x140个字节
char buf[0x140] = { 0 };
// 通过内存检索的方式遍历模块,并通过判断Characteristics的IMAGE_FILE_DLL位是否为1来判断是否为主模块
unsigned long long moduleBase = findNextModule(hProcess, (ULONG_PTR)GetModuleHandle(moduleName.c_str()), (DWORD*)buf);
char * finalModuleBase = NULL;
do
{
if ((*(WORD*)(buf + 0x16) & IMAGE_FILE_DLL) == 0)
{
finalModuleBase = (char *)moduleBase;
break;
}
moduleBase = findNextModule(hProcess, moduleBase, (DWORD*)buf);
} while (moduleBase);
}
获取主模块PE结构信息
// 获取Dos头
IMAGE_DOS_HEADER dosHeader = { 0 };
if (!readMemory(hProcess, iTunesModuleBase, (char*)&dosHeader, sizeof(dosHeader)))
return false;
// 获取Signature
DWORD signature = { 0 };
char* signatureAddress = iTunesModuleBase + dosHeader.e_lfanew;
if (!readMemory(hProcess, signatureAddress, (char*)&signature, sizeof(DWORD)))
return 0;
// 获取文件头
IMAGE_FILE_HEADER fileHeader = { 0 };
char* fileHeaderAddress = signatureAddress + sizeof(signature);
if (!readMemory(hProcess, fileHeaderAddress, (char*)&fileHeader, sizeof(IMAGE_FILE_HEADER)))
return 0;
// 获取扩展头
IMAGE_OPTIONAL_HEADER optHeader = { 0 };
char* optHeaderAddress = fileHeaderAddress + sizeof(IMAGE_FILE_HEADER);
if (!readMemory(hProcess, optHeaderAddress, (char*)&optHeader, fileHeader.SizeOfOptionalHeader))
return 0;
// 读取节区表
IMAGE_SECTION_HEADER* sectionHeader = new IMAGE_SECTION_HEADER[fileHeader.NumberOfSections];
char* sectionHeaderAddress = optHeaderAddress + fileHeader.SizeOfOptionalHeader;
if (!readMemory(hProcess, sectionHeaderAddress, (char*)sectionHeader, fileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)))
return 0;
申请内存
在目标进程中申请内存, 用于存放新的导入表和字符串等信息. 这里我只申请了一次内存, 并将这部分内存分为多段来使用.需要注意的是, 后面会有许多地方要填主模块相对于这块内存的偏移, 即RVA, 且数据结构为4字节, 所以此处申请的内存地址的位置与主模块首地址的差值不能超过4GB, 至于这段内存的地址是否可以比主模块小, 没有尝试过, 感兴趣的可自行尝试.
// 用于对齐内存
unsigned long long align(unsigned long long value)
{
unsigned long long v = value % 0x10;
if (v != 0)
{
value += (0x10-v);
}
return value;
}
// 远程申请内存
void* remoteAllocMemory(HANDLE hProcess, PVOID beginAddress, int size)
{
MEMORY_BASIC_INFORMATION mbi = { 0 };
if (VirtualQueryEx(hProcess, beginAddress, &mbi, 0x30))
{
while (true)
{
if ((mbi.RegionSize & 0xFFF) == 0xFFF)
break;
if (mbi.State == 0x10000)
{
for (char* address = (char*)mbi.BaseAddress; address < (char*)mbi.BaseAddress + mbi.RegionSize; address +=0x10000)
{
if (VirtualAllocEx(hProcess, address, size, MEM_RESERVE, PAGE_READWRITE))
{
PVOID result = VirtualAllocEx(hProcess, address, size, MEM_COMMIT, PAGE_READWRITE);
if (result) return result;
}
}
}
char* baseAddress = (char*)mbi.BaseAddress;
size_t regionSize = mbi.RegionSize;
if (!VirtualQueryEx(hProcess, &baseAddress[regionSize], &mbi, 0x30))
{
int e = GetLastError();
return nullptr;
}
}
}
}
size_t newImportTableSize = align(optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size + sizeof(IMAGE_IMPORT_DESCRIPTOR));
size_t newStringTableSize = align(strlen(dllName));
size_t newImportByNameSize = align(sizeof(WORD)+strlen(exportName));
size_t newThunkDataSize = align(sizeof(IMAGE_THUNK_DATA) * 2);
size_t newMemorySize = newImportTableSize + newStringTableSize + newImportByNameSize+ newThunkDataSize;
char* queryAddress = iTunesModuleBase + optHeader.SizeOfCode + optHeader.SizeOfInitializedData + optHeader.SizeOfUninitializedData + optHeader.BaseOfCode;
IMAGE_IMPORT_DESCRIPTOR* newImportTable = (IMAGE_IMPORT_DESCRIPTOR*)remoteAllocMemory(hProcess, queryAddress, newMemorySize);
writeZeroMemory(hProcess, newImportTable, newMemorySize);
// 用于存放字符串
// dllName\0+Hint+exportName\0
char* newStringTable = (char*)newImportTable + newImportTableSize;
// 存放IMAGE_IMPORT_BY_NAME结构体, IMAGE_THUNK_DATA里面唯一的成员Ordinal就是该结构体的RVA
IMAGE_IMPORT_BY_NAME* newImportByName = (IMAGE_IMPORT_BY_NAME*)(newStringTable + newStringTableSize);
// 用于INT和IAT
IMAGE_THUNK_DATA* newThunkData = (IMAGE_THUNK_DATA*)((char*)newImportByName + newImportByNameSize);
修改导入表
这里有几步操作
-
将dll的名字写入到目标进程. 由于系统加载PE文件的导入表项时, 也是使用
LoadLibrary
函数,而它支持指定绝对路径和省略路径两种模式, 故此处也是一样. 如果将要注入的dll放到exe同目录下, 便可只往目标进程写入dll的名字, 否则要指定路径+名字. -
拷贝原导入表到新的内存. 由于要扩展导入表, 所以需要将原导入表放到新申请的内存中, 并将旧的导入表拷贝过来. 需要注意的是, 导入表最后一定要留一个空的导入表项(即
IMAGE_IMPORT_DESCRIPTOR
), PE文件是靠IMAGE_IMPORT_DESCRIPTOR
的最后一个成员FirstThunk
是否为NULL
来判断是否为最后一个导入表项的, 而不是扩展头中的optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size. -
填充导出函数. 结构为
IMAGE_IMPORT_BY_NAME
, 定义如下:typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; CHAR Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
前两字节为导出函数的序号, 后面为导出函数的名字
-
在新的导入表后面写入新的导入表项(
IMAGE_IMPORT_DESCRIPTOR
). 需要填充OriginalFirstThunk
,Name
,FirstThunk
三个成员. 其中OriginalFirstThunk
和FirstThunk
分别用于INT和IAT, 这两个地方填充IMAGE_THUNK_DATA
结构体的RVA.IMAGE_THUNK_DATA
又是什么呢?这是ms的定义:typedef struct _IMAGE_THUNK_DATA64 { union { ULONGLONG ForwarderString; // PBYTE ULONGLONG Function; // PDWORD ULONGLONG Ordinal; ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA64;
只有一个
DWORD_PTR
类型的成员, 它存放的是一个叫IMAGE_IMPORT_BY_NAME
结构体的RVA, 这便是第3步中所创建的结构体.// 填充导出函数 // 导出函数的结构为IMAGE_IMPORT_BY_NAME WORD Hint = 1; writeMemory(hProcess, (char*)newImportByName, (char*)&Hint, sizeof(WORD)); writeMemory(hProcess, (char*)newImportByName + sizeof(WORD), (char*)exportName, strlen(exportName)); // 填充INT DWORD_PTR Ordinal = (DWORD_PTR)((char*)newImportByName - iTunesModuleBase); writeMemory(hProcess, newThunkData, &Ordinal, sizeof(DWORD_PTR)); DWORD OriginalFirstThunk = 0; DWORD FirstThunk = (DWORD_PTR)newThunkData - (DWORD_PTR)iTunesModuleBase; if (boundImport) { OriginalFirstThunk = 0; } else { OriginalFirstThunk = (DWORD_PTR)newThunkData - (DWORD_PTR)iTunesModuleBase; } // 填充注入dll的improt descriptor, 其后要紧跟一个空的描述符, 用于判定描述符表是否结束, 由于在申请内存后, 将内存置0了,故此处无需额外处理 writeMemory(hProcess, &newImportTable[i].OriginalFirstThunk, &OriginalFirstThunk, sizeof(DWORD)); DWORD Name = (DWORD)(newStringTable-iTunesModuleBase); writeMemory(hProcess, &newImportTable[i].Name, &Name, sizeof(DWORD)); writeMemory(hProcess, &newImportTable[i].FirstThunk, &FirstThunk, sizeof(DWORD));
-
将新的导入表信息应用到扩展头.
// 修改导入表的大小 DWORD importSizeOffset = (DWORD_PTR)&optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size - (DWORD_PTR)&optHeader; // 这里i为原导入表的数量(包括结尾置空的那一项) optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size += (i + 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR); writeMemory(hProcess, optHeaderAddress + importSizeOffset, &optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size, sizeof(DWORD)); // 修改导入表的VA DWORD importVirtualAdressOffset = (DWORD_PTR)&optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - (DWORD_PTR)&optHeader; // 这里的VirtualAddress为4字节类型, 所以VA不能超过4GB optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = (DWORD)newImportTable - (DWORD)iTunesModuleBase; writeMemory(hProcess, optHeaderAddress + importVirtualAdressOffset, &(optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress), sizeof(DWORD));
调试
由于本例中大量操作其他进程的内存, 所以传统的调试方法就不是那么的有用, 需要与x64dbg/od等调试器,010Edit结合起来使用, 并灵活使用二分删除法来缩减问题的规模.
如, 当代码写好后, 如果程序不能按预期运行, 使用调试器附加目标进程, 并检查申请的内存地址与主模块的RVA是否超过了4GB, 如果申请内存没问题, 那么我们继续检查其他部分. 将填充字符串, 增加新的导入表项等功能删掉, 只保留将原导入表拷贝到新内存的流程. 如果这一步也没问题, 再慢慢加上其他部分的代码, 一步步的缩小问题的规模. 如果猜测可能是导入表填充的有误, 那么, 你可以在调试器中进行检查.
举例: 如图所示, 此处我导入表项填充的有问题, 那么我可以通过调试器的内存搜索功能来进行排错.
首先, 我们将这些二进制数据, 与结构体对应起来. 导入表项的结构为
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
5个成员, 共20字节. 上图上红色标记比是我填充的导入表项, 绿色标记笔是原导入表的最后一个有值的项. 我们分别去检查这两个IMAGE_IMPORT_DESCRIPTOR
结构的每一个成员, 其中OriginalFirstThunk
, Name
, FirstThunk
三个成员存放的都是RVA, 使用他们加上主模块基址便可找到对应的结构. 最后通过比较两个导入表项的结构, 发现是FirstThunk
指向的IMAGE_THUNK_DATA
填充有误. 同理, 如果存在其他问题, 也很容易就可以分析出来了.
完整源码
注入器
#include <iostream>
#include <Windows.h>
#include <mutex>
using namespace std;
#include <windows.h>
#include <iostream>
#include <exception>
#include <string>
#include <TlHelp32.h>
#include <psapi.h>
#pragma comment(lib,"psapi.lib")
using namespace std;
struct ModuleInfo
{
TCHAR szExeFile[MAX_PATH]; // 模块文件名
DWORD ImageBase;
DWORD SizeOfImage;
};
struct ProcessInfo
{
ModuleInfo MainModuleInfo; // 主模块信息
DWORD dwPID; // 进程ID
ModuleInfo *modules; // 子模块数组
DWORD dwModules; // 子模块数量
};
// 枚举进程地址空间内的模块句柄,返回数组长度
DWORD EnumModulesHandle(HANDLE hProcess, HMODULE **lpModule)
{
DWORD cbBytesNeeded = 0;
// 备注:EnumProcessModules 函数无法枚举64位进程的模块,除非程序以64位编译
int ret = EnumProcessModulesEx(hProcess, NULL, 0, &cbBytesNeeded, LIST_MODULES_64BIT); // 计算数组大小
int error = GetLastError();
*lpModule = (HMODULE *)malloc(cbBytesNeeded + 0x1000);
ret = EnumProcessModulesEx(hProcess, *lpModule, cbBytesNeeded + 0x1000, &cbBytesNeeded, LIST_MODULES_64BIT); // 枚举模块句柄
return cbBytesNeeded / sizeof(HMODULE);
}
unsigned __int64 __fastcall findNextModule(HANDLE hProcess, __int64 base, DWORD *buffer)
{
unsigned __int64 address; // rbx
DWORD v6; // eax
struct _MEMORY_BASIC_INFORMATION mbi; // [rsp+30h] [rbp-C8h] BYREF
HANDLE v9; // [rsp+60h] [rbp-98h]
DWORD *v10; // [rsp+70h] [rbp-88h]
__int16 Buffer[32]; // [rsp+80h] [rbp-78h] BYREF
mbi.BaseAddress = 0;
mbi.AllocationBase = 0;
mbi.AllocationProtect = 0;
mbi.RegionSize = 0;
mbi.State = 0;
mbi.Type = 0;
address = 0x10000;
if (base)
address = base + 0x10000;
while (VirtualQueryEx(hProcess, (LPCVOID)address, &mbi, 0x30ui64)
&& (mbi.RegionSize & 0xFFF) != 0xFFFi64
&& (char *)mbi.BaseAddress + mbi.RegionSize >= (PVOID)address)
{
if (mbi.State == 4096)
{
v6 = mbi.Protect;
if (LOBYTE(mbi.Protect) != 1
&& !_bittest((const long *)&v6, 8u)
&& ReadProcessMemory(hProcess, (LPCVOID)address, Buffer, 0x40ui64, 0i64)
&& Buffer[0] == 0x5A4D
&& *(unsigned int *)&Buffer[30] <= mbi.RegionSize
&& *(DWORD *)&Buffer[30] >= 0x40u
&& ReadProcessMemory(hProcess, (LPCVOID)(address + *(int *)&Buffer[30]), buffer, 0xF8ui64, 0i64)
&& *buffer == 0x4550)
{
return address;
}
}
address = (unsigned __int64)mbi.BaseAddress + mbi.RegionSize;
}
return 0i64;
}
int readMemory(HANDLE hProcess, LPVOID address, PVOID buffer, int size)
{
size_t read;
BOOL result = ReadProcessMemory(hProcess, address, buffer, size, &read);
if (!result || read == 0)
{
int e = GetLastError();
return 0;
}
return read;
}
int writeMemory(HANDLE hProcess, LPVOID address, PVOID buffer, int size)
{
DWORD oldProtect;
if (!VirtualProtectEx(hProcess, address, size, PAGE_READWRITE, &oldProtect))
return 0;
size_t written;
BOOL result = WriteProcessMemory(hProcess, address, buffer, size, &written);
if (!result || written == 0)
{
int e = GetLastError();
return 0;
}
if (!VirtualProtectEx(hProcess, address, size, oldProtect, &oldProtect))
return 0;
return written;
}
int writeZeroMemory(HANDLE hProcess, LPVOID address, int size)
{
char* zeroMemory = new char[size];
memset(zeroMemory, 0, size);
size_t written;
BOOL result = WriteProcessMemory(hProcess, address, zeroMemory, size, &written);
if (written == 0)
{
int e = GetLastError();
return 0;
}
delete[] zeroMemory;
return written;
}
DWORD getProcessIdByName(const wchar_t* name)
{
PROCESSENTRY32 entry = { sizeof(PROCESSENTRY32) };
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
DWORD id = 0;
if (INVALID_HANDLE_VALUE == hSnapshot)
{
return 0;
}
if (!Process32FirstW(hSnapshot, &entry))
{
return 0;
}
do
{
if (wcscmp(entry.szExeFile, name) == 0)
{
return entry.th32ProcessID;
}
} while (Process32Next(hSnapshot, &entry));
return 0;
}
bool closeProcessByName(const wchar_t* name)
{
DWORD pid = getProcessIdByName(name);
if (pid == 0) return false;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
if (hProcess == INVALID_HANDLE_VALUE) return false;
return TerminateProcess(hProcess, 0);
}
HANDLE createProcessSuspendly(wstring processFileName)
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
if (CreateProcessW(processFileName.c_str(), NULL, NULL, NULL, FALSE, IDLE_PRIORITY_CLASS | CREATE_SUSPENDED, NULL, NULL, &si, &pi))
{
CloseHandle(pi.hProcess);
return pi.hThread;
}
return INVALID_HANDLE_VALUE;
}
void* remoteAllocMemory(HANDLE hProcess, PVOID beginAddress, int size)
{
MEMORY_BASIC_INFORMATION mbi = { 0 };
if (VirtualQueryEx(hProcess, beginAddress, &mbi, 0x30))
{
while (true)
{
if ((mbi.RegionSize & 0xFFF) == 0xFFF)
break;
if (mbi.State == 0x10000)
{
for (char* address = (char*)mbi.BaseAddress; address < (char*)mbi.BaseAddress + mbi.RegionSize; address +=0x10000)
{
if (VirtualAllocEx(hProcess, address, size, MEM_RESERVE, PAGE_READWRITE))
{
PVOID result = VirtualAllocEx(hProcess, address, size, MEM_COMMIT, PAGE_READWRITE);
if (result) return result;
}
}
}
char* baseAddress = (char*)mbi.BaseAddress;
size_t regionSize = mbi.RegionSize;
if (!VirtualQueryEx(hProcess, &baseAddress[regionSize], &mbi, 0x30))
{
int e = GetLastError();
return nullptr;
}
}
}
}
unsigned long long align(unsigned long long value)
{
unsigned long long v = value % 0x10;
if (v != 0)
{
value += (0x10-v);
}
return value;
}
BOOL addNewSectionInMemory(const wchar_t* processName, const char* dllName, const char* exportName)
{
wstring processFileName = LR"(C:\Program Files\iTunes\iTunes.exe)";
closeProcessByName(processName);
HANDLE hThread = createProcessSuspendly(processFileName);
if (hThread == INVALID_HANDLE_VALUE) return false;
DWORD pid = getProcessIdByName(processName);
if (pid == 0) return false;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);
if (hProcess == INVALID_HANDLE_VALUE) return false;
char buf[0x140] = { 0 };
// 通过内存检索的方式遍历模块,并通过判断Characteristics的IMAGE_FILE_DLL位是否为1来判断是否为iTunes主模块
unsigned long long moduleBase = findNextModule(hProcess, (ULONG_PTR)GetModuleHandleA("iTunes.exe"), (DWORD*)buf);
char * iTunesModuleBase = NULL;
do
{
if ((*(WORD*)(buf + 0x16) & IMAGE_FILE_DLL) == 0)
{
iTunesModuleBase = (char *)moduleBase;
break;
}
moduleBase = findNextModule(hProcess, moduleBase, (DWORD*)buf);
} while (moduleBase);
// 获取Dos头
IMAGE_DOS_HEADER dosHeader = { 0 };
if (!readMemory(hProcess, iTunesModuleBase, (char*)&dosHeader, sizeof(dosHeader)))
return false;
// 获取Signature
DWORD signature = { 0 };
char* signatureAddress = iTunesModuleBase + dosHeader.e_lfanew;
if (!readMemory(hProcess, signatureAddress, (char*)&signature, sizeof(DWORD)))
return 0;
// 获取文件头
IMAGE_FILE_HEADER fileHeader = { 0 };
char* fileHeaderAddress = signatureAddress + sizeof(signature);
if (!readMemory(hProcess, fileHeaderAddress, (char*)&fileHeader, sizeof(IMAGE_FILE_HEADER)))
return 0;
// 获取扩展头
IMAGE_OPTIONAL_HEADER optHeader = { 0 };
char* optHeaderAddress = fileHeaderAddress + sizeof(IMAGE_FILE_HEADER);
if (!readMemory(hProcess, optHeaderAddress, (char*)&optHeader, fileHeader.SizeOfOptionalHeader))
return 0;
// 读取节区表
IMAGE_SECTION_HEADER* sectionHeader = new IMAGE_SECTION_HEADER[fileHeader.NumberOfSections];
char* sectionHeaderAddress = optHeaderAddress + fileHeader.SizeOfOptionalHeader;
if (!readMemory(hProcess, sectionHeaderAddress, (char*)sectionHeader, fileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)))
return 0;
size_t newImportTableSize = align(optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size + sizeof(IMAGE_IMPORT_DESCRIPTOR));
size_t newStringTableSize = align(strlen(dllName));
size_t newImportByNameSize = align(sizeof(WORD)+strlen(exportName));
size_t newThunkDataSize = align(sizeof(IMAGE_THUNK_DATA) * 2);
size_t newMemorySize = newImportTableSize + newStringTableSize + newImportByNameSize+ newThunkDataSize;
char* queryAddress = iTunesModuleBase + optHeader.SizeOfCode + optHeader.SizeOfInitializedData + optHeader.SizeOfUninitializedData + optHeader.BaseOfCode;
IMAGE_IMPORT_DESCRIPTOR* newImportTable = (IMAGE_IMPORT_DESCRIPTOR*)remoteAllocMemory(hProcess, queryAddress, newMemorySize);
// 申请一块内存, 用于存放新的导入表
//IMAGE_IMPORT_DESCRIPTOR* newImportTable = (IMAGE_IMPORT_DESCRIPTOR*)VirtualAllocEx(hProcess, NULL, newImportTableSize + newStringTableSize + newThunkDataSize, MEM_COMMIT, PAGE_READWRITE);
writeZeroMemory(hProcess, newImportTable, newMemorySize);
// 用于存放字符串
// dllName\0+Hint+exportName\0
char* newStringTable = (char*)newImportTable + newImportTableSize;
// 存放IMAGE_IMPORT_BY_NAME结构体, IMAGE_THUNK_DATA里面唯一的成员Ordinal就是该结构体的RVA
IMAGE_IMPORT_BY_NAME* newImportByName = (IMAGE_IMPORT_BY_NAME*)(newStringTable + newStringTableSize);
// 用于INT和IAT
IMAGE_THUNK_DATA* newThunkData = (IMAGE_THUNK_DATA*)((char*)newImportByName + newImportByNameSize);
// 将要注入dll名称写入到新内存中
writeMemory(hProcess, newStringTable, (void*)dllName, strlen(dllName));
// 拷贝原导入表内容
IMAGE_IMPORT_DESCRIPTOR* oldImportTable = (IMAGE_IMPORT_DESCRIPTOR*)new char[optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size];
readMemory(hProcess, (IMAGE_IMPORT_DESCRIPTOR*)(iTunesModuleBase + optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress), oldImportTable, optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size);
bool boundImport = false;
// 判断是否使用了绑定导入表
if (oldImportTable->Characteristics == 0 && oldImportTable->FirstThunk != 0)
{
boundImport = true;
optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].Size = 0;
optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress = 0;
}
int i = 0;
while (oldImportTable[i].FirstThunk != 0 || oldImportTable[i].Characteristics != 0)
{
writeMemory(hProcess, &newImportTable[i], &oldImportTable[i], sizeof(IMAGE_IMPORT_DESCRIPTOR));
i++;
}
// 填充导出函数
// 导出函数的结构为IMAGE_IMPORT_BY_NAME
WORD Hint = 1;
writeMemory(hProcess, (char*)newImportByName, (char*)&Hint, sizeof(WORD));
writeMemory(hProcess, (char*)newImportByName + sizeof(WORD), (char*)exportName, strlen(exportName));
// 填充INT
DWORD_PTR Ordinal = (DWORD_PTR)((char*)newImportByName - iTunesModuleBase);
writeMemory(hProcess, newThunkData, &Ordinal, sizeof(DWORD_PTR));
DWORD OriginalFirstThunk = 0;
DWORD FirstThunk = (DWORD_PTR)newThunkData - (DWORD_PTR)iTunesModuleBase;
if (boundImport)
{
OriginalFirstThunk = 0;
}
else
{
OriginalFirstThunk = (DWORD_PTR)newThunkData - (DWORD_PTR)iTunesModuleBase;
}
// 填充注入dll的improt descriptor, 其后要紧跟一个空的描述符, 用于判定描述符表是否结束, 由于在申请内存后, 将内存置0了,故此处无需额外处理
writeMemory(hProcess, &newImportTable[i].OriginalFirstThunk, &OriginalFirstThunk, sizeof(DWORD));
DWORD Name = (DWORD)(newStringTable-iTunesModuleBase);
writeMemory(hProcess, &newImportTable[i].Name, &Name, sizeof(DWORD));
writeMemory(hProcess, &newImportTable[i].FirstThunk, &FirstThunk, sizeof(DWORD));
// 修改导入表的大小
DWORD importSizeOffset = (DWORD_PTR)&optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size - (DWORD_PTR)&optHeader;
optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size += (i + 1) * sizeof(IMAGE_IMPORT_DESCRIPTOR);
writeMemory(hProcess, optHeaderAddress + importSizeOffset, &optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size, sizeof(DWORD));
// 修改导入表的VA
DWORD importVirtualAdressOffset = (DWORD_PTR)&optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - (DWORD_PTR)&optHeader;
optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = (DWORD)newImportTable - (DWORD)iTunesModuleBase;
writeMemory(hProcess, optHeaderAddress + importVirtualAdressOffset, &(optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress), sizeof(DWORD));
ResumeThread(hThread);
CloseHandle(hProcess);
return 0;
}
int main()
{
//getchar();
wstring processName = L"iTunes.exe";
string dllName = "dlltest.dll";
string dllFileName = R"(D:\WorkStation\Project\Test\cpptest\x64\Debug\dlltest.dll)";
// 导出的函数名字
string exportName = "test";
addNewSectionInMemory(processName.c_str(), dllFileName.c_str(), exportName.c_str());
//AddImportTable(R"(C:\Program Files\iTunes\iTunes.exe)", dllFileName, exportName);
system("pause");
return true;
}
被注入的dll
必须要导出至少一个函数
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
extern "C" __declspec(dllexport) void test(){}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBoxA(0,"dll加载成功",0,0);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
标签:return,IMAGE,char,修改,导入,内存,IMPORT,DWORD,hProcess
From: https://www.cnblogs.com/FeJQ/p/16954873.html