以修改内存的方式实现导入表动态注入dll

简介

搜索"导入表注入", 网上大堆的博客和代码, 统统都是修改PE文件实现的. 这里将介绍exe加载到内存后, 修改主模块映像, 而不必去改变本地的exe文件的注入方法.

原理

注入原理

简单来说, exe被加载时, 会遍历导入表, 依次使用LoadLibrary加载导入表的依赖dll, 而我们要做的是, 在exe被加载前, 就给导入表新增一个项, 使其能够加载我们指定的dll.

PE知识

这一章, 我们要用到PE文件的解析, 导入表的遍历, 导入表项的添加等功能.下面来复习一下:

Dos头固定占0x40大小, 其最后4字节是一个叫AddressOfNewExeHeader的成员, 指示的是Nt头相对于文件的偏移.

image

在Nt头中又分为三部分, 分别是Signature,FileHeader,OptionalHeader, Signature的值固定为0x4550, 翻译成ASCII是PE两个字符, FileHeader文件头中比较重要的成员是SizeOfOptionalHeader, 这决定了我们要如何去解析扩展头. OptionalHeader扩展头中主要用到的成员DataDirArray, 该成员是扩展头的最后一个成员,它是一个数组, 里面记录了各种表, 如导出表,导入表, 资源表, 重定位表等, 这一章主要要用到的是导入表, 即DataDirArray[1], DataDirArray[1]里面有两个成员, 分别是VirtualAddressSize,指定了导入表的RVA和大小, 后面我们建立新的导入表后, 这两个成员也需要更改.

image

导入表的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);
修改导入表

这里有几步操作

  1. 将dll的名字写入到目标进程. 由于系统加载PE文件的导入表项时, 也是使用LoadLibrary函数,而它支持指定绝对路径和省略路径两种模式, 故此处也是一样. 如果将要注入的dll放到exe同目录下, 便可只往目标进程写入dll的名字, 否则要指定路径+名字.

  2. 拷贝原导入表到新的内存. 由于要扩展导入表, 所以需要将原导入表放到新申请的内存中, 并将旧的导入表拷贝过来. 需要注意的是, 导入表最后一定要留一个空的导入表项(即IMAGE_IMPORT_DESCRIPTOR), PE文件是靠IMAGE_IMPORT_DESCRIPTOR的最后一个成员FirstThunk是否为NULL来判断是否为最后一个导入表项的, 而不是扩展头中的optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size.

  3. 填充导出函数. 结构为IMAGE_IMPORT_BY_NAME, 定义如下:

    typedef struct _IMAGE_IMPORT_BY_NAME {
        WORD    Hint;
        CHAR   Name[1];
    } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
    

    前两字节为导出函数的序号, 后面为导出函数的名字

  4. 在新的导入表后面写入新的导入表项(IMAGE_IMPORT_DESCRIPTOR). 需要填充OriginalFirstThunk,Name,FirstThunk三个成员. 其中OriginalFirstThunkFirstThunk分别用于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));
    
  5. 将新的导入表信息应用到扩展头.

    // 修改导入表的大小
    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, 如果申请内存没问题, 那么我们继续检查其他部分. 将填充字符串, 增加新的导入表项等功能删掉, 只保留将原导入表拷贝到新内存的流程. 如果这一步也没问题, 再慢慢加上其他部分的代码, 一步步的缩小问题的规模. 如果猜测可能是导入表填充的有误, 那么, 你可以在调试器中进行检查.

举例: 如图所示, 此处我导入表项填充的有问题, 那么我可以通过调试器的内存搜索功能来进行排错.

image

首先, 我们将这些二进制数据, 与结构体对应起来. 导入表项的结构为

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;
}
posted @ 2022-12-06 12:05  FeJQ  阅读(822)  评论(2)    收藏  举报