3

我的最终目标是通过在 kernel32.dll 中挂钩文件 api 跟踪 explorer.exe 完成的文件操作,但是我还没有让它工作(explorer.exe 没有调用 API,或者我的结果有问题)。为了弄清楚发生了什么,我设定了一个目标,即每当 notepad.exe 创建文件时进行跟踪,但是由于某种原因这也失败了!

我有 3 个 Visual Studio 2012 C++ 项目:我的 DLL,一个 DLL 注入器,它将强制任何可执行文件加载我的 dll(尽管如果 Unicode/多字节和 32/64 位设置不匹配,它可能会失败),以及一个测试程序调用 API。我有一个批处理文件,它将启动我的测试程序,然后使用注入程序将我的 DLL 加载到测试程序中。奇怪的部分是输出确实显示 API 正在测试程序上被跟踪(生成控制台并打印所有内容!)但 notepad.exe 没有任何反应(没有控制台 = 没有捕获操作)。

这是挂钩 API 的 DLL(使用 mhook 库)。格式和概念取自指南。需要注意的重要事项是我挂钩 CreateFile(A/W),并在第一次操作发生时生成一个控制台来打印文件 I/O 日志。

#include "stdafx.h"
#include "mhook/mhook-lib/mhook.h"

//////////////////////////////////////////////////////////////////////////
// Defines and typedefs
typedef HANDLE (WINAPI *CreateFileWFP)(
    _In_ LPCWSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    );
typedef HANDLE (WINAPI *CreateFileAFP)(
    _In_ LPCSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    );

//////////////////////////////////////////////////////////////////////////
// Original function
CreateFileWFP OriginalCreateFileW = (CreateFileWFP)::GetProcAddress(::GetModuleHandle(TEXT("kernel32")), "CreateFileW");
CreateFileAFP OriginalCreateFileA = (CreateFileAFP)::GetProcAddress(::GetModuleHandle(TEXT("kernel32")), "CreateFileA");

//////////////////////////////////////////////////////////////////////////
// Some Helper Stuff

struct Console{
    HANDLE handle;

    Console(){ handle = INVALID_HANDLE_VALUE; }

    void write(LPCWSTR text){
        if(handle==INVALID_HANDLE_VALUE){
            AllocConsole();
            handle = GetStdHandle(STD_OUTPUT_HANDLE);
        }
        DWORD numCharsWritten = 0;
        WriteConsoleW(handle, text, (DWORD)wcslen(text), &numCharsWritten,NULL);
    }
    void write(LPCSTR text){
        if(handle==INVALID_HANDLE_VALUE){
            AllocConsole();
            handle = GetStdHandle(STD_OUTPUT_HANDLE);
        }
        DWORD numCharsWritten = 0;
        WriteConsoleA(handle, text, (DWORD)strlen(text), &numCharsWritten,NULL);
    }
} console;

void operationPrint(LPCWSTR left, LPCWSTR middle, LPCWSTR right){
    console.write(left);
    console.write(middle);
    console.write(right);
    console.write(L"\n");
}
void operationPrint(LPCSTR left, LPCSTR middle, LPCSTR right){
    console.write(left);
    console.write(middle);
    console.write(right);
    console.write(L"\n");
}

//////////////////////////////////////////////////////////////////////////
// Hooked function

HANDLE HookedCreateFileW(
    _In_ LPCWSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    ){
        HANDLE out = OriginalCreateFileW(
                        lpFileName,
                        dwDesiredAccess,
                        dwShareMode,
                        lpSecurityAttributes,
                        dwCreationDisposition,
                        dwFlagsAndAttributes,
                        hTemplateFile);
        if(out == INVALID_HANDLE_VALUE) return out; //ignore failiures
        operationPrint(L"CreatedW file",L" at ",lpFileName);
        return out;
}
HANDLE HookedCreateFileA(
    _In_ LPCSTR lpFileName,
    _In_ DWORD dwDesiredAccess,
    _In_ DWORD dwShareMode,
    _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    _In_ DWORD dwCreationDisposition,
    _In_ DWORD dwFlagsAndAttributes,
    _In_opt_ HANDLE hTemplateFile
    ){
        HANDLE out = OriginalCreateFileA(
                        lpFileName,
                        dwDesiredAccess,
                        dwShareMode,
                        lpSecurityAttributes,
                        dwCreationDisposition,
                        dwFlagsAndAttributes,
                        hTemplateFile);
        if(out == INVALID_HANDLE_VALUE) return out; //ignore failiures
        operationPrint("CreatedA file"," at ",lpFileName);
        return out;
}

//////////////////////////////////////////////////////////////////////////
// Entry point

BOOL WINAPI DllMain(
    __in HINSTANCE  hInstance,
    __in DWORD      Reason,
    __in LPVOID     Reserved
    )
{        
    switch (Reason)
    {
    case DLL_PROCESS_ATTACH:
        Mhook_SetHook((PVOID*)&OriginalCreateFileW, HookedCreateFileW);
        Mhook_SetHook((PVOID*)&OriginalCreateFileA, HookedCreateFileA);
        break;

    case DLL_PROCESS_DETACH:
        FreeConsole();
        Mhook_Unhook((PVOID*)&OriginalCreateFileW);
        Mhook_Unhook((PVOID*)&OriginalCreateFileA);
        break;
    }

    return TRUE;
}

我无法找到找到注入程序的确切位置,但它与指南几乎相同。有些评论甚至是一样的,所以我很确定一个是从另一个那里拿来的(虽然不确定哪个来自谁)。无论如何,我只是做了一些小的改动,以便在 Unicode 或多字节下编译时它可以工作。除非有要求,否则我不会在这里发布整个代码,因为我认为这很浪费空间,但这是重要的部分。

#include "Injector.h"
#include <windows.h>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <conio.h>
#include <stdio.h> 
#include "DebugPrint.h"
#include <atlbase.h>

using namespace std;

Injector::Injector(void)
{
}


Injector::~Injector(void)
{
} 

#define CREATE_THREAD_ACCESS (PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ) 

bool Injector::Inject(string procName, string dllName){
   DWORD pID = GetTargetThreadIDFromProcName(procName.c_str()); 
   return Inject(pID,dllName);
}

bool Injector::Inject(DWORD pID, string dllName){
   const char* DLL_NAME = dllName.c_str();

   HANDLE Proc = 0; 
   HMODULE hLib = 0; 
   LPVOID RemoteString, LoadLibAddy; 

   if(!pID) 
      return false; 

   Proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pID); 
   if(!Proc) 
   { 
      DEBUG_PRINT("OpenProcess() failed: %d", GetLastError()); 
      return false; 
   } 

   LoadLibAddy = (LPVOID)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "LoadLibraryA"); 

   // Allocate space in the process for our <strong class="highlight">DLL</strong> 
   RemoteString = (LPVOID)VirtualAllocEx(Proc, NULL, strlen(DLL_NAME), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 

   // Write the string name of our <strong class="highlight">DLL</strong> in the memory allocated 
   WriteProcessMemory(Proc, (LPVOID)RemoteString, DLL_NAME, strlen(DLL_NAME), NULL); 

   // Load our <strong class="highlight">DLL</strong> 
   CreateRemoteThread(Proc, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibAddy, (LPVOID)RemoteString, NULL, NULL); 

   CloseHandle(Proc); 
   return true; 
}

DWORD Injector::GetTargetThreadIDFromProcName(const char* ProcName)
{
    PROCESSENTRY32 pe;
    HANDLE thSnapShot;
    BOOL retval, ProcFound = false;

    thSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if(thSnapShot == INVALID_HANDLE_VALUE)
    {
        //MessageBox(NULL, "Error: Unable to create toolhelp snapshot!", "2MLoader", MB_OK);
        DEBUG_PRINT("Error: Unable to create toolhelp snapshot!");
        return false;
    }

    pe.dwSize = sizeof(PROCESSENTRY32);

    retval = Process32First(thSnapShot, &pe);
    while(retval)
    {
#ifdef _UNICODE
        char peSzExeFile[MAX_PATH];
        wcstombs_s(NULL,peSzExeFile,MAX_PATH,pe.szExeFile,MAX_PATH);
#else
        const char* peSzExeFile = pe.szExeFile;
#endif
        DEBUG_PRINT("\nSearching for process: %s ",peSzExeFile);
        if(!strcmp(peSzExeFile, ProcName))
        {
            DEBUG_PRINT(" Found!\n\n");
            return pe.th32ProcessID;
        }
        retval = Process32Next(thSnapShot, &pe);
    }
    return 0;
}

最后,我的测试程序只是调用了一些文件 API 来查看注入的 DLL 是否可以捕获它们。如前所述,它确实非常成功地捕获了呼叫。

#include <windows.h>
#include <tchar.h>

using namespace std;

#ifdef _UNICODE
#define printf(X,...) wprintf(TEXT(X),__VA_ARGS__); //I don't want to have to keep replacing printf whenever I switch to Unicode or Multibyte
#endif

#ifdef _DEBUG
int _tmain(int argc, TCHAR* argv[]){ //makes a console.  printf() will have a place to go in this case
#else
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ //no console
#endif
    Sleep(2000); //let DLL finish loading

    LPCTSTR fileSrc = TEXT("C:\\Users\\jajoach\\Desktop\\hi.txt");
    LPCTSTR fileDst = TEXT("C:\\Users\\jajoach\\Desktop\\hi\\hi.txt");

    printf("Moving file from %s to %s\n",fileSrc,fileDst);
    MoveFile(fileSrc,fileDst);
    Sleep(1000);

    printf("Moving file from %s to %s\n",fileSrc,fileDst);
    MoveFile(fileDst,fileSrc);
    Sleep(1000);

    printf("Copying file from %s to %s\n",fileSrc,fileDst);
    CopyFile(fileSrc,fileDst,true);
    Sleep(1000);

    printf("Deleting file %s\n",fileDst);
    DeleteFile(fileDst);
    Sleep(1000);

    printf("Creating file %s\n",fileDst);
    HANDLE h=CreateFile(fileDst,0,0,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
    Sleep(1000);

    printf("Deleting file %s\n",fileDst);
    CloseHandle(h);
    DeleteFile(fileDst);

    Sleep(5000);

    return 0;
}

这是 Unicode(如 'W's 所指出的)和发布模式的确认输出,以及我对 notepad.exe 和 explorer.exe 的期望(但没有得到)。作为记录,该测试也适用于多字节,但按预期给出了“A”。 在此处输入图像描述

我原以为 explorer.exe 和 notepad.exe 可能不会将这些 API 用于他们的文件 I/O,但我的研究表明并非如此。 这篇文章使用 Detours 在 notepad.exe 中挂钩 CreateFile,并报告该应用程序成功。此外,ProgramMonitor 清楚地显示了在操作期间调用 CreateFile 的 notepad.exe Saveas(在使用不同参数的许多失败请求之后......): 在此处输入图像描述

暂时不用管 explorer.exe;为什么当我执行 Saveas 时,我的钩子不适用于 notepad.exe?

编辑:我忘了提到我还将 MoveFile(A/W) 和 CopyFile(A/W) 与测试程序挂钩,但为了简洁起见,我从这篇文章的 DLL 中删除了该代码。

4

2 回答 2

5

正如 OP 的评论中所指出的,它似乎notepad.exe使用了ASLR,而您的测试程序没有。这样,每个进程中 LoadLibraryA 的地址就会不同,并且您的注入代码会失败。

情况是您LoadLibraryA在注入器地址空间中获取地址,并假设它与目标进程中的地址相同。这通常是正确的,但 ASLR 是专门为使该假设失败而设计的。所以它确实......你创建的线程得到了一个 - 最有可能 - 无效的地址,并且失败了。

于 2013-07-09T21:30:41.213 回答
3

有几个原因:

  1. 你的钩子代码的位数必须与目标匹配。
  2. CreateFileA/W 非常低,但还不足以捕获所有内容!

要解决 2. 您必须从 Ntdll.dll 中挂钩 Zw/NtCreateFile,这就是您在 procmon 中看到的。在用户空间中没有比这些 API 更低的了。

于 2013-07-09T21:38:06.153 回答