Detours Hook 工具源码阅读三

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[]);
再挖个坑,有时间我们改一个好读一点的版本。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值