前段时间有一个需求,就是在进程启动的第一时间实现dll的注入,当时一想以为很简单嘛,比如拦截CreateProcessInternalW等不就行了吗?后来发现如果你在CreateProcessInternalW前处理进程还没有启动,而之后处理的话,进程的主线程已经开始工作了,再后来通过修改创建标志把创建的进程的主线程挂起,等进程创建后我来注入,此时才发现创建远线程搞不定, 此时进程只有NTDLL,其他模块还没有加载呢.再最后终于发现了detours 2.1为我们提供了一个函数DetourCreateProcessWithDllW,它可以实现在进程创建初期就将dll加载进去,下面我就来分析一下detours怎么做到在进程初期就将dll加载进程的.
http://blog.csdn.net/galihoo/archive/2008/04/18/2305898.aspx
先来看DetourCreateProcessWithDllW函数,可以分为四个步骤:
BOOL
WINAPI
DetourCreateProcessWithDllW(LPCWSTR lpApplicationName,
__in_z LPWSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCWSTR lpCurrentDirectory,
LPSTARTUPINFOW lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation,
LPCSTR lpDetouredDllFullName,
LPCSTR lpDllName,
PDETOUR_CREATE_PROCESS_ROUTINEW pfCreateProcessW)
...{
// 第一步:修改创建标志位
DWORD dwMyCreationFlags = (dwCreationFlags | CREATE_SUSPENDED);
PROCESS_INFORMATION pi;
if (pfCreateProcessW == NULL) ...{
pfCreateProcessW = CreateProcessW;
}
// 第二步:调用原始CreateProcessW
if (!pfCreateProcessW(lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwMyCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
&pi)) ...{
return FALSE;
}
LPCSTR rlpDlls[2];
DWORD nDlls = 0;
if (lpDetouredDllFullName != NULL) ...{
rlpDlls[nDlls++] = lpDetouredDllFullName;
}
if (lpDllName != NULL) ...{
rlpDlls[nDlls++] = lpDllName;
}
// 第三步: 修改目标进程的输入表
if (!UpdateImports(pi.hProcess, rlpDlls, nDlls)) ...{
return FALSE;
}
if (lpProcessInformation) ...{
CopyMemory(lpProcessInformation, &pi, sizeof(pi));
}
// 第四步: 让目标进程的主线程继续运行
if (!(dwCreationFlags & CREATE_SUSPENDED)) ...{
ResumeThread(pi.hThread);
}
return TRUE;
}
呵呵,看出来了,原来detours使用的是修改输入表来实现dll的注入的,在来看其中最关键的函数UpdateImports,
BOOL WINAPI UpdateImports(HANDLE hProcess, LPCSTR *plpDlls, DWORD nDlls)
...{
BOOL fSucceeded = FALSE;
BYTE * pbNew = NULL;
DETOUR_EXE_RESTORE der;
DWORD i;
ZeroMemory(&der, sizeof(der));
der.cb = sizeof(der);
// 从 0x10000 开始寻找MEM_IMGAE标志的内存,然后验证其是否是exe
// 如果是那么返回其首地址
PBYTE pbModule = (PBYTE)FindExe(hProcess);
IMAGE_DOS_HEADER idh;
ZeroMemory(&idh, sizeof(idh));
// 读取 IMAGE_DOS_HEADER
if (!ReadProcessMemory(hProcess, pbModule, &idh, sizeof(idh), NULL)) ...{
DETOUR_TRACE(("ReadProcessMemory(idh) failed: %d ", GetLastError()));
finish:
if (pbNew != NULL) ...{
delete[] pbNew;
pbNew = NULL;
}
return fSucceeded;
}
CopyMemory(&der.idh, &idh, sizeof(idh));
der.pidh = (PIMAGE_DOS_HEADER)pbModule;
// 验证是否是 MZ
if (idh.e_magic != IMAGE_DOS_SIGNATURE) ...{
goto finish;
}
IMAGE_NT_HEADERS inh;
ZeroMemory(&inh, sizeof(inh));
// 读取 IMAGE_NT_HEADERS
if (!ReadProcessMemory(hProcess, pbModule + idh.e_lfanew, &inh, sizeof(inh), NULL)) ...{
DETOUR_TRACE(("ReadProcessMemory(inh) failed: %d ", GetLastError()));
goto finish;
}
CopyMemory(&der.inh, &inh, sizeof(inh));
der.pinh = (PIMAGE_NT_HEADERS)(pbModule + idh.e_lfanew);
// 验证是否是PE, 和验证MZ一样,没有多大必要,其实在FindExe里面都验证过了
if (inh.Signature != IMAGE_NT_SIGNATURE) ...{
goto finish;
}
// 如果没有导入表,直接返回
if (inh.IMPORT_DIRECTORY.VirtualAddress == 0) ...{
DETOUR_TRACE(("No IMAGE_DIRECTORY_ENTRY_IMPORT "));
goto finish;
}
// Zero out the bound table so loader doesn''t use it instead of our new table.
// 不懂为什么要这样
inh.BOUND_DIRECTORY.VirtualAddress = 0;
inh.BOUND_DIRECTORY.Size = 0;
// Find the size of the mapped file.
DWORD dwFileSize = 0;
// FIELD_OFFSET 这个宏很精妙
// 得到 section 的偏移
DWORD dwSec = idh.e_lfanew +
FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) +
inh.FileHeader.SizeOfOptionalHeader;
// 循环读取 节,主要作用就是计算文件的大小,然而文件大小本身又用不到,所以这个东西就是鸡肋
for (i = 0; i < inh.FileHeader.NumberOfSections; i++) ...{
IMAGE_SECTION_HEADER ish;
ZeroMemory(&ish, sizeof(ish));
if (!ReadProcessMemory(hProcess, pbModule + dwSec + sizeof(ish) * i, &ish,
sizeof(ish), NULL)) ...{
DETOUR_TRACE(("ReadProcessMemory(inh) failed: %d ", GetLastError()));
goto finish;
}
DETOUR_TRACE(("ish[%d] : va=%p sr=%d ", i, ish.VirtualAddress, ish.SizeOfRawData));
// If the file didn''t have an IAT_DIRECTORY, we create one...
// 不能理解其深意
if (inh.IAT_DIRECTORY.VirtualAddress == 0 &&
inh.IMPORT_DIRECTORY.VirtualAddress >= ish.VirtualAddress &&
inh.IMPORT_DIRECTORY.VirtualAddress < ish.VirtualAddress + ish.SizeOfRawData) ...{
inh.IAT_DIRECTORY.VirtualAddress = ish.VirtualAddress;
inh.IAT_DIRECTORY.Size = ish.SizeOfRawData;
}
// Find the end of the file...
if (dwFileSize < ish.PointerToRawData + ish.SizeOfRawData) ...{
dwFileSize = ish.PointerToRawData + ish.SizeOfRawData;
}
}
DETOUR_TRACE(("dwFileSize = %08x ", dwFileSize));
// Find the current checksum.
WORD wBefore = ComputeChkSum(hProcess, pbModule, &inh);
DETOUR_TRACE(("ChkSum: %04x + %08x => %08x ", wBefore, dwFileSize, wBefore + dwFileSize));
DETOUR_TRACE((" Imports: %8p..%8p ",
(DWORD_PTR)pbModule + inh.IMPORT_DIRECTORY.VirtualAddress,
(DWORD_PTR)pbModule + inh.IMPORT_DIRECTORY.VirtualAddress +
inh.IMPORT_DIRECTORY.Size));
DWORD obRem = sizeof(IMAGE_IMPORT_DESCRIPTOR) * nDlls;
// 园整
DWORD obTab = PadToDwordPtr(obRem + inh.IMPORT_DIRECTORY.Size);
// 用来存储 OriginalFirstThunk 和 FirstThunk指向的数组
// 每个数组包括一个有用的和一个结尾的,加起来就是4个DWORD_PTR
// 当然还要乘以 nDlls
DWORD obDll = obTab + sizeof(DWORD_PTR) * 4 * nDlls;
DWORD obStr = obDll;
DWORD cbNew = obStr;
DWORD n;
for (n = 0; n < nDlls; n++) ...{
cbNew += PadToDword((DWORD)strlen(plpDlls[n]) + 1);
}
// 现在本地分配一个缓冲区,填充好数据后再写到远程进程去
pbNew = new BYTE [cbNew];
if (pbNew == NULL) ...{
DETOUR_TRACE(("new BYTE [cbNew] failed. "));
goto finish;
}
ZeroMemory(pbNew, cbNew);
PBYTE pbBase = pbModule;
PBYTE pbNext = pbBase
+ inh.OptionalHeader.BaseOfCode
+ inh.OptionalHeader.SizeOfCode
+ inh.OptionalHeader.SizeOfInitializedData
+ inh.OptionalHeader.SizeOfUninitializedData;
if (pbBase < pbNext) ...{
pbBase = pbNext;
}
DETOUR_TRACE(("pbBase = %p ", pbBase));
// 在远进程中分配一个和本地一样的大的 IMPORT 表,比便以后写进去
PBYTE pbNewIid = FindAndAllocateNearBase(hProcess, pbBase, cbNew);
if (pbNewIid == NULL) ...{
DETOUR_TRACE(("FindAndAllocateNearBase failed. "));
goto finish;
}
DWORD dwProtect = 0;
der.impDirProt = 0;
// 修改远程进程的输入表的属性,修改为PAGE_EXECUTE_READWRITE,其实没有必要,不要执行就可以了
// 反正以后要改回来的
if (!VirtualProtectEx(hProcess,
pbModule + inh.IMPORT_DIRECTORY.VirtualAddress,
inh.IMPORT_DIRECTORY.Size, PAGE_EXECUTE_READWRITE, &dwProtect)) ...{
DETOUR_TRACE(("VirtualProtextEx(import) write failed: %d ", GetLastError()));
goto finish;
}
DETOUR_TRACE(("IMPORT_DIRECTORY perms=%x ", dwProtect));
der.impDirProt = dwProtect;
// pbNewIid 是我们在远程线程分配的新的输入表的首地址,和pbModule想减后就得到它的RVA了
DWORD obBase = (DWORD)(pbNewIid - pbModule);
// 将旧的输入表读入到本地,注意有一个偏移obRem
if (!ReadProcessMemory(hProcess,
pbModule + inh.IMPORT_DIRECTORY.VirtualAddress,
pbNew + obRem,
inh.IMPORT_DIRECTORY.Size, NULL)) ...{
DETOUR_TRACE(("ReadProcessMemory(imports) failed: %d ", GetLastError()));
goto finish;
}
PIMAGE_IMPORT_DESCRIPTOR piid = (PIMAGE_IMPORT_DESCRIPTOR)pbNew;
DWORD_PTR *pt;
// 这里是最关键的地方了,修改出一个新的输入表,当下面这个循环执行完成之后就在本地形成了
// 一个新的输入表了,下面这个图就是循环完成之后的内存布局图,
// 我们以添加两个dll为列
/**//*
________
________ IID 1 如:A.dll
________ IID 2 如:B.dll
________ IID 3 这后面的IID就是exe自身的, 看看上面一个ReadProcessMemory就了解了
________ IID 4
________ IID N
________ NULL IID结尾符号
________ IID 1 的 OriginalFirstThunk指向的数组 ,占用两个DWORD,第一个是序号输入值,第二个为NULL
________ IID 1 的FirstThunk指向的数组 ,占用两个DWORD,第一个是讯号输入值,第二个为NULL
________ IID 2 的OriginalFirstThunk指向的数组 ,占用两个DWORD,第一个是序号输入值,第二个为NULL
________ IID 2 的FirstThunk指向的数组 ,占用两个DWORD,第一个是讯号输入值,第二个为NULL
________ A.dll 的名称字符串 "全路径+A.dll",IID 1的Name字段指向这里
________ B.dll 的名称字符串 "全路径+A.dll",IID 2的Name字段指向这里
*/
for (n = 0; n < nDlls; n++) ...{
// 填充路径字符串
HRESULT hrRet = StringCchCopyA((char*)pbNew + obStr, cbNew - obStr, plpDlls[n]);
if (FAILED(hrRet))
...{
DETOUR_TRACE(("StringCchCopyA failed: %d ", GetLastError()));
goto finish;
}
// 得到OriginalFirstThunk指向的数组的偏移
DWORD nOffset = obTab + (sizeof(DWORD_PTR) * (4 * n));
// 根据偏移+首地址,就得到RVA,然后填充到IID中
piid[n].OriginalFirstThunk = obBase + nOffset;
// 得到本地实际的地址
pt = ((DWORD_PTR*)(pbNew + nOffset));
// 使用序号来导入, 因此注入的DLL必须至少有一个导出函数
pt[0] = IMAGE_ORDINAL_FLAG + 1;
pt[1] = 0; // 数组末尾填充NULL
// 偏移两个DWORD,配置FirstThunk,和OriginalFirstThunk类似
nOffset = obTab + (sizeof(DWORD_PTR) * ((4 * n) + 2));
piid[n].FirstThunk = obBase + nOffset;
pt = ((DWORD_PTR*)(pbNew + nOffset));
pt[0] = IMAGE_ORDINAL_FLAG + 1;
pt[1] = 0;
piid[n].TimeDateStamp = 0;
piid[n].ForwarderChain = 0;
piid[n].Name = obBase + obStr;
obStr += PadToDword((DWORD)strlen(plpDlls[n]) + 1);
}
// 只是打印出结果
for (i = 0; i < nDlls + (inh.IMPORT_DIRECTORY.Size / sizeof(*piid)); i++) ...{
DETOUR_TRACE(("%8d. Look=%08x Time=%08x Fore=%08x Name=%08x Addr=%08x ",
i,
piid[i].OriginalFirstThunk,
piid[i].TimeDateStamp,
piid[i].ForwarderChain,
piid[i].Name,
piid[i].FirstThunk));
if (piid[i].OriginalFirstThunk == 0 && piid[i].FirstThunk == 0) ...{
break;
}
}
// 本地配置好了之后写入到远程进程中
if (!WriteProcessMemory(hProcess, pbNewIid, pbNew, obStr, NULL)) ...{
DETOUR_TRACE(("WriteProcessMemory(iid) failed: %d ", GetLastError()));
goto finish;
}
DETOUR_TRACE(("obBase = %p..%p ",
inh.IMPORT_DIRECTORY.VirtualAddress,
inh.IMPORT_DIRECTORY.VirtualAddress + inh.IMPORT_DIRECTORY.Size));
DETOUR_TRACE(("obBase = %p..%p ", obBase, obBase + obStr));
// 在本地修改输入表的RVA,之后会写入到远程进程的
inh.IMPORT_DIRECTORY.VirtualAddress = obBase;
inh.IMPORT_DIRECTORY.Size = cbNew;
/**//////////////////////////////////////////////////// Update the CLR header.
// 搞不懂为什么要清除 IL_ONLY 标志
if (inh.CLR_DIRECTORY.VirtualAddress != 0 &&
inh.CLR_DIRECTORY.Size != 0) ...{
DETOUR_CLR_HEADER clr;
PBYTE pbClr = pbModule + inh.CLR_DIRECTORY.VirtualAddress;
if (!ReadProcessMemory(hProcess, pbClr, &clr, sizeof(clr), NULL)) ...{
DETOUR_TRACE(("ReadProcessMemory(clr) failed: %d ", GetLastError()));
goto finish;
}
der.pclrFlags = (PULONG)(pbClr + offsetof(DETOUR_CLR_HEADER, Flags));
der.clrFlags = clr.Flags;
clr.Flags &= 0xfffffffe; // Clear the IL_ONLY flag.
if (!VirtualProtectEx(hProcess, pbClr, sizeof(clr), PAGE_READWRITE, &dwProtect)) ...{
DETOUR_TRACE(("VirtualProtextEx(clr) write failed: %d ", GetLastError()));
goto finish;
}
if (!WriteProcessMemory(hProcess, pbClr, &clr, sizeof(clr), NULL)) ...{
DETOUR_TRACE(("WriteProcessMemory(clr) failed: %d ", GetLastError()));
goto finish;
}
if (!VirtualProtectEx(hProcess, pbClr, sizeof(clr), dwProtect, &dwProtect)) ...{
DETOUR_TRACE(("VirtualProtextEx(clr) restore failed: %d ", GetLastError()));
goto finish;
}
}
/**//////////////////////// Update the NT header for the import new directory.
/////////////////////////////// Update the DOS header to fix the checksum.
// 修改属性
if (!VirtualProtectEx(hProcess, pbModule, inh.OptionalHeader.SizeOfHeaders,
PAGE_EXECUTE_READWRITE, &dwProtect)) ...{
DETOUR_TRACE(("VirtualProtextEx(inh) write failed: %d ", GetLastError()));
goto finish;
}
idh.e_res[0] = 0;
// 写入 DOS 头
if (!WriteProcessMemory(hProcess, pbModule, &idh, sizeof(idh), NULL)) ...{
DETOUR_TRACE(("WriteProcessMemory(idh) failed: %d ", GetLastError()));
goto finish;
}
// 写入PE 文件头,IMAGE_NT_HEADER
if (!WriteProcessMemory(hProcess, pbModule + idh.e_lfanew, &inh, sizeof(inh), NULL)) ...{
DETOUR_TRACE(("WriteProcessMemory(inh) failed: %d ", GetLastError()));
goto finish;
}
// 计算效验和 . 搞不懂了,高人来指点
WORD wDuring = ComputeChkSum(hProcess, pbModule, &inh);
DETOUR_TRACE(("ChkSum: %04x + %08x => %08x ", wDuring, dwFileSize, wDuring + dwFileSize));
idh.e_res[0] = detour_sum_minus(idh.e_res[0], detour_sum_minus(wDuring, wBefore));
if (!WriteProcessMemory(hProcess, pbModule, &idh, sizeof(idh), NULL)) ...{
DETOUR_TRACE(("WriteProcessMemory(idh) failed: %d ", GetLastError()));
goto finish;
}
// 修改会原来的属性
if (!VirtualProtectEx(hProcess, pbModule, inh.OptionalHeader.SizeOfHeaders,
dwProtect, &dwProtect)) ...{
DETOUR_TRACE(("VirtualProtextEx(idh) restore failed: %d ", GetLastError()));
goto finish;
}
// 最后再次计算效验和,和之前的相比较
WORD wAfter = ComputeChkSum(hProcess, pbModule, &inh);
DETOUR_TRACE(("ChkSum: %04x + %08x => %08x ", wAfter, dwFileSize, wAfter + dwFileSize));
DETOUR_TRACE(("Before: %08x, After: %08x ", wBefore + dwFileSize, wAfter + dwFileSize));
if (wBefore != wAfter) ...{
DETOUR_TRACE(("Restore of checksum failed %04x != %04x. ", wBefore, wAfter));
goto finish;
}
// 没有实际作用的
if (!DetourCopyPayloadToProcess(hProcess, DETOUR_EXE_RESTORE_GUID, &der, sizeof(der))) ...{
DETOUR_TRACE(("DetourCopyPayloadToProcess failed: %d ", GetLastError()));
goto finish;
}
fSucceeded = TRUE;
goto finish;
}
一切都尽在注释中了,归纳一下,不外乎这几个步骤:
1. 读出PE头(IMAGE_NT_HEADERS)
2.根据OptionalHeader的DataDirectory找到输入表
3.构造一个新的输入表(这一步是关键)
4.把PE头,以及输入表都写到远程进程去
其他效验和和最后的DetourCopyPayloadToProcess都是后续工作了
文章出处:http://www.diybl.com/course/4_webprogram/asp.net/asp_netshl/2008422/111042.html