簡介
搜索"匯入表注入", 網上大堆的博客和代碼, 統統都是修改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 =https://www.cnblogs.com/FeJQ/p/= 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 = https://www.cnblogs.com/FeJQ/p/(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 =https://www.cnblogs.com/FeJQ/p/= 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 = https://www.cnblogs.com/FeJQ/p/(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;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/539334.html
標籤:其他
