Detours PE修改相关API源码分析
Setdll sample
先看疗效
D:\code\detour\Detours-main\bin.X64>sleepold.exe
sleepold.exe: Starting (at 00007FF65AF011C0).
SleepEx = 00007FFA841DFB70 [00007FFA85C52020]
00007FFA841DFB70: 89542410
00007FFA841DFB74: 4c8bdc
00007FFA841DFB77: 53
sleepold.exe: Calling Sleep for 1 second.
sleepold.exe: Calling SleepEx for 1 second.
sleepold.exe: Calling Sleep again for 1 second.
sleepold.exe: Done sleeping.
初始状态,执行sleepold.exe 干干净净。使用Dependency 查看依赖,sleepold.exe 只依赖kernel32.dll。
执行 setdll.exe -d:slept64.dll sleepold.exe 尝试把dll注入到exe文件中。
D:\code\detour\Detours-main\bin.X64>setdll.exe -d:slept64.dll sleepold.exe
Adding slept64.dll to binary files.
sleepold.exe:
slept64.dll
KERNEL32.dll -> KERNEL32.dll
D:\code\detour\Detours-main\bin.X64>sleepold.exe
slept64.dll: Starting.
slept64.dll: ExeEntry=00007FF67BFD2D08, DllEntry=00007FFA4E688B54
SleepEx = 00007FFA841DFB70 [00007FFA85C52020]
00007FFA841DFB70: 89542410
00007FFA841DFB74: 4c8bdc
00007FFA841DFB77: 53
slept64.dll: Detoured SleepEx() @ 00007FFA441C0120.
sleepold.exe: Starting (at 00007FF67BFD11C0).
SleepEx = 00007FFA841DFB70 [00007FFA85C52020]
00007FFA841DFB70: e90306fe bf [00007FFA441C0178]
00007FFA841DFB75: cc [FFFFFFFFFFFFFFFF]
00007FFA841DFB76: cc [FFFFFFFFFFFFFFFF]
sleepold.exe: Calling Sleep for 1 second.
sleepold.exe: Calling SleepEx for 1 second.
sleepold.exe: Calling Sleep again for 1 second.
sleepold.exe: Done sleeping.
slept64.dll: Removed SleepEx() detour (0), slept 3015 ticks.
使用setdll 注入slept64.dll后,每次启动sleepold.exe都会加载 slept64.dll。有点厉害!使用dependency看看
果然,exe文件的依赖关系都改了!疗效甚至比withdll还好。来来来,看看源码。
Setdll 源码分析
简单来讲,原理如下图:读出exe文件,修改依赖表,再写回exe文件中。源码理应也这么清晰,理应 哈哈哈。
惯例:为了更好的理解主线流程,删减掉来部分不核心的代码
int CDECL main(int argc, char **argv)
{
// 省略参数解析
// 假设命令行参数是:setdll.exe -d:slept64.dll sleepold.exe
// 这是给全局变量,s_xxx命名还是比较规范的,但这样写确实很难理解
// 这个不合理的实现,至少浪费掉我半天时间
s_szDllPath = argv[1];// s_szDllPath = "slept64.dll";
// 调用SetFile来修改exe
SetFile(argv[2]); // SetFile("sleepold.exe");
return 0;
}
接下来我们分析SetFile
SetFile 源码分析
BOOL SetFile(PCHAR pszPath)
{
// 参数拼装,略
// szOrg = "sleepold.exe";
// szNew = "sleepold.exe#");
// szOld = "sleepold.exe~");
// 打开原始exe文件,sleepold.exe
hOld = CreateFileA(szOrg,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
// 创建新的文件,sleepold.exe
hNew = CreateFileA(szNew,
GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
// 打开二进制文件,老二进制文件
pBinary = DetourBinaryOpen(hOld);
// 重新设置导出表
DetourBinaryResetImports(pBinary);
// 如果不是删除,修改导出表
if (!s_fRemove) {
DetourBinaryEditImports(pBinary,
&bAddedDll,
AddBywayCallback, NULL, NULL, NULL);
}
// 再修改一遍,囧
DetourBinaryEditImports(pBinary, NULL,
ListBywayCallback, ListFileCallback,
NULL, NULL));
// 把修改后的二进制文件写回新的exe中
DetourBinaryWrite(pBinary, hNew);
// 关闭二进制文件
DetourBinaryClose(pBinary);
// 删掉老的
DeleteFileA(szOld);
// 把原始的改成老的
MoveFileA(szOrg, szOld);
// 把新的改成原始的
MoveFileA(szNew, szOrg);
// 删除新的
DeleteFileA(szNew);
}
简单来说就是,打开了exe,重新编辑的导出表,然后写回exe。
DetourBinaryOpen 、DetourBinaryWrite和 DetourBinaryEditImports 源码分析
PDETOUR_BINARY WINAPI DetourBinaryOpen(_In_ HANDLE hFile)
{
Detour::CImage *pImage = new NOTHROW
pImage->Read(hFile);
return (PDETOUR_BINARY)pImage;
}
BOOL WINAPI DetourBinaryWrite(_In_ PDETOUR_BINARY pdi,
_In_ HANDLE hFile)
{
Detour::CImage *pImage = Detour::CImage::IsValid(pdi);
return pImage->Write(hFile);
}
BOOL WINAPI DetourBinaryEditImports(_In_ PDETOUR_BINARY pBinary,
_In_opt_ PVOID pContext,
_In_opt_ PF_DETOUR_BINARY_BYWAY_CALLBACK pfByway,
_In_opt_ PF_DETOUR_BINARY_FILE_CALLBACK pfFile,
_In_opt_ PF_DETOUR_BINARY_SYMBOL_CALLBACK pfSymbol,
_In_opt_ PF_DETOUR_BINARY_COMMIT_CALLBACK pfCommit)
{
Detour::CImage *pImage = Detour::CImage::IsValid(pBinary);
return pImage->EditImports(pContext,
pfByway,
pfFile,
pfSymbol,
pfCommit);
}
看上去,实际上都是转调 Detour::CImage,并且Detour::CImage的命名更规范一些,至少 Read、 Write 看上去更对称一些。可以看出Detours实现二进制文件注入和核心代码是CImage类,在image.cpp中。另外,经分析image.cpp没有额外依赖非常独立。找机会我们拆出来跑跑。挖坑*1
CImage::Read源码分析
image非常值得一看。BOOL CImage::Read(HANDLE hFile) 实际上在解析exe文件。因此,为了更好的理解代码,我们先科普一下pe文件格式吧。
一篇非常全面的参考文档:https://blog.csdn.net/lyshark_lyshark/article/details/125847139
[图片]
抄一张图来。这张图分为左右两个部分,左边是windows pe文件(exe、dll、com)等文件的格式。右边是系统把pe文件加载到内存以后的地址分布。加载过程很像把pe文件拆散,一块块放入内存。
注意,这里的加载和一会看到源码里做的内存映射不是一个事情。
CImage::Read源码分析
BOOL CImage::Read(HANDLE hFile)
{
// 把pe文件先整块放进内存里。上图左边部分的结构,不存在操作系统分块加载的过程
m_nFileSize = GetFileSize(hFile, NULL);
m_hMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
m_pMap = (PBYTE)MapViewOfFileEx(m_hMap, FILE_MAP_READ, 0, 0, 0, NULL);
/ 解析DOS头和PE头
//
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)m_pMap;
m_nPeOffset = pDosHeader->e_lfanew;
m_nPrePE = 0;
m_cbPrePE = QuadAlign(pDosHeader->e_lfanew);
// DOS头存入m_DosHeader
CopyMemory(&m_DosHeader, m_pMap + m_nPrePE, sizeof(m_DosHeader));
// PE头存入m_NtHeader
CopyMemory(&m_NtHeader, m_pMap + m_nPeOffset, sizeof(m_NtHeader));
/ 解析Section Headers.上图的块表
// 计算Section Headers起始位置
m_nSectionsOffset = m_nPeOffset
+ sizeof(m_NtHeader.Signature)
+ sizeof(m_NtHeader.FileHeader)
+ m_NtHeader.FileHeader.SizeOfOptionalHeader;
// Section Headers 存入m_SectionHeaders
CopyMemory(&m_SectionHeaders,
m_pMap + m_nSectionsOffset,
sizeof(m_SectionHeaders[0]) * m_NtHeader.FileHeader.NumberOfSections);
///解析 detour Section.
// 从 m_SectionHeaders 中找到名字是".detour" 的Sectron,略
解析 Import Table.
// Import Table ———— 依赖表 Dependency应该也是读这里
DWORD rvaImageDirectory = m_NtHeader.OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
// 读出iidp PIMAGE_IMPORT_DESCRIPTOR 依赖描述表
PIMAGE_IMPORT_DESCRIPTOR iidp
= (PIMAGE_IMPORT_DESCRIPTOR)RvaToVa(rvaImageDirectory);
// 读出oidp detour修改前的原始的“依赖描述表”,保存在上面解析出的 detour Section.
PIMAGE_IMPORT_DESCRIPTOR oidp
= (PIMAGE_IMPORT_DESCRIPTOR)RvaToVa(rvaOriginalImageDirectory);
CImageImportFile **ppLastFile = &m_pImportFiles;
m_pImportFiles = NULL;
// 读出每一个依赖文件,
for (n = 0; n < nFiles; n++, iidp++) {
CImageImportFile *pImportFile = new NOTHROW CImageImportFile;
// 读出依赖的依赖
for (DWORD f = 0; f < nNames; f++, pImportName++){};
}
oidp++;
}
return TRUE;
}
简言之,从PE文件里读出一堆东西,包括依赖表
CImage::Write源码分析
可以预期就是把Read的过程反着实现一遍。配一张纯纯的水图。
BOOL CImage::Write(HANDLE hFile)
{
// 拷贝PE头
CopyFileData(hFile, 0, m_NtHeader.OptionalHeader.SizeOfHeaders);
// 替换Dos头
m_nPeOffset = sizeof(m_DosHeader) + sizeof(s_rbDosCode);
m_nSectionsOffset = m_nPeOffset
+ sizeof(m_NtHeader.Signature)
+ sizeof(m_NtHeader.FileHeader)
+ m_NtHeader.FileHeader.SizeOfOptionalHeader;
m_DosHeader.e_lfanew = m_nPeOffset;
WriteFile(hFile, &m_DosHeader, sizeof(m_DosHeader), &cbDone);
WriteFile(hFile, &s_rbDosCode, sizeof(s_rbDosCode), &cbDone);
/// 复制 Sections.
for (; n < m_NtHeader.FileHeader.NumberOfSections; n++) {
CopyFileData(hFile,
m_SectionHeaders[n].PointerToRawData,
m_SectionHeaders[n].SizeOfRawData);
}
if (fNeedDetourSection || !m_pImageData->IsEmpty()) {
// 写Detours Section 略
Step Through Imports.
// 写import表
for (CImageImportFile *pImportFile = m_pImportFiles;
pImportFile != NULL; pImportFile = pImportFile->m_pNextFile) {}
// 写 SectionHeaders
WriteFile(hFile, m_pbOutputBuffer, m_SectionHeaders[nSection].SizeOfRawData,
&cbDone);
}
/ 写扩展信息Extra Data.略
return TRUE;
}
CImage::EditImport源码分析
BOOL CImage::EditImports(PVOID pContext,
PF_DETOUR_BINARY_BYWAY_CALLBACK pfBywayCallback,
PF_DETOUR_BINARY_FILE_CALLBACK pfFileCallback,
PF_DETOUR_BINARY_SYMBOL_CALLBACK pfSymbolCallback,
PF_DETOUR_BINARY_COMMIT_CALLBACK pfCommitCallback)
{
// 遍历依赖列表
CImageImportFile *pImportFile = NULL;
CImageImportFile **ppLastFile = &m_pImportFiles;
while ((pImportFile = *ppLastFile) != NULL) {
//旁路处理
// pfBywayCallback 不为空就调一下
if (pfBywayCallback != NULL) {
// 通过pfBywayCallback 获取要旁路的文件
LPCSTR pszFile = NULL;
(*pfBywayCallback)(pContext, NULL, &pszFile)
CImageImportFile *pByway = NewByway(pszFile);
// 依赖链表头部插入旁路文件 CImageImportFile *pByway
pByway->m_pNextFile = pImportFile;
*ppLastFile = pByway;
ppLastFile = &pByway->m_pNextFile;
continue;
}
// 如果是旁路文件
if (pImportFile->m_fByway) {
if (pfBywayCallback != NULL) {
LPCSTR pszFile = NULL;
if (!(*pfBywayCallback)(pContext, pImportFile->m_pszName, &pszFile)) {
goto fail;
}
if (pszFile != NULL) {
// Replace? Byway
if (pszFile != pImportFile->m_pszName) {
LPCSTR pszLast = pImportFile->m_pszName;
pImportFile->m_pszName = DuplicateString(pszFile);
ReleaseString(pszLast);
if (pImportFile->m_pszName == NULL) {
goto fail;
}
}
}
else { // Delete Byway
*ppLastFile = pImportFile->m_pNextFile;
pImportFile->m_pNextFile = NULL;
delete pImportFile;
m_nImportFiles--;
continue; // Retry after delete.
}
}
}
else {
// 如果不是旁路文件
if (pfFileCallback != NULL) {
LPCSTR pszFile = NULL;
// 有pfFileCallback,根据pfFileCallback返回结果改名字
if (!(*pfFileCallback)(pContext,
pImportFile->m_pszOrig,
pImportFile->m_pszName,
&pszFile)) {
goto fail;
}
if (pszFile != NULL) {
if (pszFile != pImportFile->m_pszName) {
LPCSTR pszLast = pImportFile->m_pszName;
pImportFile->m_pszName = DuplicateString(pszFile);
ReleaseString(pszLast);
if (pImportFile->m_pszName == NULL) {
goto fail;
}
}
}
}
// 有pfSymbolCallback ,根据pfSymbolCallback 返回结果改名字
if (pfSymbolCallback != NULL) {
for (DWORD n = 0; n < pImportFile->m_nImportNames; n++) {
CImageImportName *pImportName = &pImportFile->m_pImportNames[n];
LPCSTR pszName = NULL;
ULONG nOrdinal = 0;
if (!(*pfSymbolCallback)(pContext,
pImportName->m_nOrig,
pImportName->m_nOrdinal,
&nOrdinal,
pImportName->m_pszOrig,
pImportName->m_pszName,
&pszName)) {
goto fail;
}
if (pszName != NULL) {
if (pszName != pImportName->m_pszName) {
pImportName->m_nOrdinal = 0;
LPCSTR pszLast = pImportName->m_pszName;
pImportName->m_pszName = DuplicateString(pszName);
ReleaseString(pszLast);
if (pImportName->m_pszName == NULL) {
goto fail;
}
}
}
else if (nOrdinal != 0) {
pImportName->m_nOrdinal = nOrdinal;
if (pImportName->m_pszName != NULL) {
delete[] pImportName->m_pszName;
pImportName->m_pszName = NULL;
}
}
}
}
}
ppLastFile = &pImportFile->m_pNextFile;
pImportFile = pImportFile->m_pNextFile;
}
for (;;) {
if (pfBywayCallback != NULL) {
LPCSTR pszFile = NULL;
if (!(*pfBywayCallback)(pContext, NULL, &pszFile)) {
goto fail;
}
if (pszFile != NULL) {
// Insert a new Byway.
CImageImportFile *pByway = NewByway(pszFile);
if (pByway == NULL) {
return FALSE;
}
pByway->m_pNextFile = pImportFile;
*ppLastFile = pByway;
ppLastFile = &pByway->m_pNextFile;
continue; // Retry after Byway.
}
}
break;
}
// pfCommitCallback 一切搞定以后调用一下
if (pfCommitCallback != NULL) {
if (!(*pfCommitCallback)(pContext)) {
goto fail;
}
}
SetLastError(NO_ERROR);
return TRUE;
fail:
return FALSE;
}
有点别扭的一个函数,功能很明确,实现很绕。删完支线还会这么多,实在是删不动了。功能就是修改依赖表。感觉应该是这样一个形状:
BOOL CImage::EditImports(In LPCSTR pszNames[]);
如果还不够清晰就改成三个函数:
BOOL CImage::AddImports(In LPCSTR pszNames[]);
BOOL CImage::DelImports(In LPCSTR pszNames[]);
BOOL CImage::ModifyImports(In LPCSTR pszNames[]);
再挖个坑,有时间我们改一个好读一点的版本。