detours 通过修改输入表注入DLL

前段时间有一个需求,就是在进程启动的第一时间实现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


你可能感兴趣的:(软件编程技术)