搜索"导入表注入", 网上大堆的博客和代码, 统统都是修改PE文件实现的. 这里将介绍exe加载到内存后, 修改主模块映像, 而不必去改变本地的exe文件的注入方法.
简单来说, exe被加载时, 会遍历导入表, 依次使用LoadLibrary加载导入表的依赖dll, 而我们要做的是, 在exe被加载前, 就给导入表新增一个项, 使其能够加载我们指定的dll.
这一章, 我们要用到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);
}
// 获取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;
}
必须要导出至少一个函数
// 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;
}
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序
question的一些答案关于redirect_to让我想到了其他一些问题。基本上,我正在使用Rails2.1编写博客应用程序。我一直在尝试自己完成大部分工作(因为我对Rails有所了解),但在需要时会引用Internet上的教程和引用资料。我设法让一个简单的博客正常运行,然后我尝试添加评论。靠我自己,我设法让它进入了可以从script/console添加评论的阶段,但我无法让表单正常工作。我遵循的其中一个教程建议在帖子Controller中创建一个“评论”操作,以添加评论。我的问题是:这是“标准”方式吗?我的另一个问题的答案之一似乎暗示应该有一个CommentsController参
如何在ruby中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL
如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否
华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt