8 - DLL注入和解除安裝

有毒發表於2020-03-27

DLL注入和解除安裝

一、DLL注入

DLL注入:向執行中的其他程式強制插入特定的DLL檔案,主要是命令其他程式自行呼叫LoadLibrary() API,載入使用者指定的DLL檔案。

 

DLL注入與一般DLL載入的主要區別是載入的目標程式是其自身或其他程式。

1. DLL

DLL(Dynamic Linked Library,動態連結庫),DLL被載入到程式後會自動執行DllMain函式,使用者可以把想要執行的額程式碼放到DllMain函式,每當載入DLL時,新增的程式碼就會自動得到執行。利用該特性可以修復程式BUG,或向程式新增新功能。

// DllMain()函式 
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvRserved)
{
  switch(dwReason)
  {
    case DLL_PROCESS_ATTACH:
      // 新增想要執行的程式碼
      break;

    case DLL_THREAD_ATTACH:
      break;

    case DLL_THREAD_DETACH:
      break;

    case DLL_PROCESS_DETACH:
      break;
  }

  return TRUE;
}

2. DLL注入例項

使用LoadLibrary()載入某個DLL時,該DLL中的DllMain()函式會被呼叫執行。DLL注入的原理就是從外部促使目標程式呼叫LoadLibrary() API。

  1. 改善功能與修復BUG
  2. 訊息鉤取--Windows 預設提供的訊息鉤取功能本質上應用的就是一種DLL注入技術
  3. API鉤取--先建立DLL形態的鉤取函式,然後注入要鉤取的目標程式,主要是應用了被注入的DLL擁有目標程式記憶體訪問許可權這一特性
  4. 其他應用程式--監視、管理PC使用者的應用程式
  5. 惡意程式碼--非法注入,進行程式碼隱藏

3. DLL注入的實現方法

1. 建立遠端執行緒(CreateRemoteThread() API)

此處主要記錄一下書上的原始碼分析,操作部分請自行實踐。

// myhack.cpp
#include "windows.h"
#include "tchar.h"

#pragma comment(lib, "urlmon.lib")

#define DEF_URL         (L"http://www.naver.com/index.html")
#define DEF_FILE_NAME   (L"index.html")

HMODULE g_hMod = NULL;

DWORD WINAPI ThreadProc(LPVOID lParam)
{
    TCHAR szPath[_MAX_PATH] = {0,};

    if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
        return FALSE;

    TCHAR *p = _tcsrchr( szPath, '\\' );
    if( !p )
        return FALSE;

    _tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME); //引數準備

    URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL); //呼叫函式進行URL下載

    return 0;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    HANDLE hThread = NULL;

    g_hMod = (HMODULE)hinstDLL;

    switch( fdwReason )
    {
    case DLL_PROCESS_ATTACH : 
        OutputDebugString(L"<myhack.dll> Injection!!!");

        //建立遠端執行緒進行download
        hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);

        // 需要注意,切記隨手關閉控制程式碼,保持好習慣
        CloseHandle(hThread);
        break;
    }

    return TRUE;
}
// InjectDll.cpp
#include "windows.h"
#include "tchar.h"

BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
    HANDLE hProcess = NULL, hThread = NULL;
    HMODULE hMod = NULL;
    LPVOID pRemoteBuf = NULL;

      //確定路徑需要佔用的緩衝區大小
    DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR); 
    LPTHREAD_START_ROUTINE pThreadProc;

    // #1. 使用OpenProcess函式獲取目標程式控制程式碼(PROCESS_ALL_ACCESS許可權)
    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    {
        _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
        return FALSE;
    }

    // #2. 使用VirtualAllocEx函式在目標程式中分配記憶體,大小為szDllName
      // VirtualAllocEx函式返回的是hProcess指向的目標程式的分配所得緩衝區的記憶體地址
    pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

    // #3.  將myhack.dll路徑 ("c:\\myhack.dll")寫入目標程式中分配到的記憶體
    WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

    // #4. 獲取LoadLibraryA() API的地址
      // 這裡主要利用來了kernel32.dll檔案在每個程式中的載入地址都相同這一特點,所以不管是獲取載入到    
      // InjectDll.exe還是notepad.exe程式的kernel32.dll中的LoadLibraryW函式的地址都是一樣的。這裡的載入地
      // 址相同指的是在同一次系統執行中,如果再次啟動系統kernel32.dll的載入地址會變,但是每個程式的
      // kernerl32.dll的載入地址還是一樣的。
      hMod = GetModuleHandle(L"kernel32.dll");
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

    // #5. 在目標程式notepad.exe中執行遠端執行緒
      // pThreadProc = notepad.exe程式記憶體中的LoadLibraryW()地址
      // pRemoteBuf = notepad.exe程式記憶體中待載入注入dll的路徑字串的地址
    hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
    WaitForSingleObject(hThread, INFINITE);    

      //同樣,記得關閉控制程式碼
    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
}

int _tmain(int argc, TCHAR *argv[])
{
    if( argc != 3)
    {
        _tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]);
        return 1;
    }

    // change privilege
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;

    // inject dll
    if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) )
        _tprintf(L"InjectDll(\"%s\") success!!!\n", argv[2]);
    else
        _tprintf(L"InjectDll(\"%s\") failed!!!\n", argv[2]);

    return 0;
}

main()函式主要檢查輸入程式的引數,然後呼叫InjectDll函式。InjectDll函式是實施DLL注入的核心函式,功能是命令目標程式自行呼叫LoadLibrary API。

 

重點介紹一下CreateRemoteThread()函式,該函式在進行DLL注入時會經常用到,其函式原型如下:

CreateRemoteThread()
HANDLE WINAPI CreateRemoteThread(
  __in HANDLE    hProcess,    //目標程式控制程式碼
  __in LPSECURITY_ATTRIBUTES   lpThreadAttributes,
  __in SIZE_T dwStackSize,
  __in LPTHREAD_START_ROUTNE    lpStartAddress,    //執行緒函式地址
  __in LPVOID dwCreationFlags,    //執行緒引數地址
  __out LPDOWRD lpThreadId
);

2. AppInit_DLLs

第二種方法是操作登錄檔,Windows的登錄檔中預設提供了AppInit_DLLs與LoadAppInit_DLLs兩個登錄檔項,只要將要注入DLL的路徑字串寫入AppInit_DLLs專案,並在LoadAppInit_DLLs中設定值為1,重啟時,系統就會將指定的DLL注入到所有執行程式中。主要原理是User32.dll被載入到程式時,會讀取AppInit_DLLs登錄檔項,若值為1,就呼叫LoadLibrary()函式載入使用者DLL。所以嚴格來說,是將注入DLL載入到使用user32.dll的程式中。

// myhack2.cpp
// 主要作用是以隱藏模式執行IE,連線到指定網站

#include "windows.h"
#include "tchar.h"

#define DEF_CMD  L"c:\\Program Files\\Internet Explorer\\iexplore.exe" 
#define DEF_ADDR L"http://www.naver.com"
#define DEF_DST_PROC L"notepad.exe"

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    TCHAR szCmd[MAX_PATH]  = {0,};
    TCHAR szPath[MAX_PATH] = {0,};
    TCHAR *p = NULL;
    STARTUPINFO si = {0,};
    PROCESS_INFORMATION pi = {0,};

    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_HIDE;

    switch( fdwReason )
    {
    case DLL_PROCESS_ATTACH : 
        if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
            break;

        if( !(p = _tcsrchr(szPath, '\\')) )
            break;

        if( _tcsicmp(p+1, DEF_DST_PROC) )
            break;

        wsprintf(szCmd, L"%s %s", DEF_CMD, DEF_ADDR);
        if( !CreateProcess(NULL, (LPTSTR)(LPCTSTR)szCmd, 
                            NULL, NULL, FALSE, 
                            NORMAL_PRIORITY_CLASS, 
                            NULL, NULL, &si, &pi) )
            break;

        if( pi.hProcess != NULL )
            CloseHandle(pi.hProcess);

        break;
    }

    return TRUE;
}

將上述dll檔案複製到某個位置,修改登錄檔項HKEY_LOCAL_MACHINE\SOFTWARE\Microdoft\Windows NT\CurrentVersion\Windows,將AppInit_DLLs項的值修改為待注入DLL的絕對路徑,然後修改LoadAppInit_DLLs登錄檔項的值為1,重啟,執行notepad.exe,就會看到DLL已經被注入。

3. 使用SetWindowsHookEx()函式

第三個方法就是訊息鉤取,使用SetWindowsHookEx安裝鉤子,將指定DLL強制注入程式。

二、DLL解除安裝

DLL解除安裝原理:驅使目標程式呼叫FreeLibrary()函式,即將FreeLibrary()函式的地址傳遞給CreateRemoteThread()函式的lpStartAddress引數,並把待解除安裝的DLL控制程式碼傳遞給lpParameter引數。

 

需要注意的一點是:引用計數問題。呼叫一次FreeLibrary()函式,引用計數就會-1。引用計數表示的是核心物件被使用的次數。

// EjectDll.exe

#include "windows.h"
#include "tlhelp32.h"
#include "tchar.h"

#define DEF_PROC_NAME    (L"notepad.exe")
#define DEF_DLL_NAME    (L"myhack.dll")

DWORD FindProcessID(LPCTSTR szProcessName)
{
    DWORD dwPID = 0xFFFFFFFF;
    HANDLE hSnapShot = INVALID_HANDLE_VALUE;
    PROCESSENTRY32 pe;

    // 獲取系統快照
    pe.dwSize = sizeof( PROCESSENTRY32 );
    hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

    // 查詢程式
    Process32First(hSnapShot, &pe);
    do
    {
        if(!_tcsicmp(szProcessName, (LPCTSTR)pe.szExeFile))
        {
            dwPID = pe.th32ProcessID;
            break;
        }
    }
    while(Process32Next(hSnapShot, &pe));

    CloseHandle(hSnapShot);

    return dwPID;
}

BOOL SetPrivilege(LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) 
{
    TOKEN_PRIVILEGES tp;
    HANDLE hToken;
    LUID luid;

    if( !OpenProcessToken(GetCurrentProcess(),
                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 
                          &hToken) )
    {
        _tprintf(L"OpenProcessToken error: %u\n", GetLastError());
        return FALSE;
    }

    if( !LookupPrivilegeValue(NULL,           // lookup privilege on local system
                              lpszPrivilege,  // privilege to lookup 
                              &luid) )        // receives LUID of privilege
    {
        _tprintf(L"LookupPrivilegeValue error: %u\n", GetLastError() ); 
        return FALSE; 
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if( bEnablePrivilege )
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.
    if( !AdjustTokenPrivileges(hToken, 
                               FALSE, 
                               &tp, 
                               sizeof(TOKEN_PRIVILEGES), 
                               (PTOKEN_PRIVILEGES) NULL, 
                               (PDWORD) NULL) )
    { 
        _tprintf(L"AdjustTokenPrivileges error: %u\n", GetLastError() ); 
        return FALSE; 
    } 

    if( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
    {
        _tprintf(L"The token does not have the specified privilege. \n");
        return FALSE;
    } 

    return TRUE;
}

BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)
{
    BOOL bMore = FALSE, bFound = FALSE;
    HANDLE hSnapshot, hProcess, hThread;
    HMODULE hModule = NULL;
    MODULEENTRY32 me = { sizeof(me) };
    LPTHREAD_START_ROUTINE pThreadProc;

    // dwPID = notepad 程式ID
    // 使用TH32CS_SNAPMODULE引數,獲取載入到notepad程式的DLL名稱
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

    bMore = Module32First(hSnapshot, &me);
    for( ; bMore ; bMore = Module32Next(hSnapshot, &me) )
    {
        if( !_tcsicmp((LPCTSTR)me.szModule, szDllName) || 
            !_tcsicmp((LPCTSTR)me.szExePath, szDllName) )
        {
            bFound = TRUE;
            break;
        }
    }

    if( !bFound )
    {
        CloseHandle(hSnapshot);
        return FALSE;
    }

    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    {
        _tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
        return FALSE;
    }

    hModule = GetModuleHandle(L"kernel32.dll");
      // 獲取FreeLibrary函式載入地址,並使用CreateRemoteThread進行呼叫
    pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
    hThread = CreateRemoteThread(hProcess, NULL, 0, 
                                 pThreadProc, me.modBaseAddr, 
                                 0, NULL);
    WaitForSingleObject(hThread, INFINITE);    

    CloseHandle(hThread);
    CloseHandle(hProcess);
    CloseHandle(hSnapshot);

    return TRUE;
}

int _tmain(int argc, TCHAR* argv[])
{
    DWORD dwPID = 0xFFFFFFFF;

    // 查詢process
    dwPID = FindProcessID(DEF_PROC_NAME);
    if( dwPID == 0xFFFFFFFF )
    {
        _tprintf(L"There is no <%s> process!\n", DEF_PROC_NAME);
        return 1;
    }

    _tprintf(L"PID of \"%s\" is %d\n", DEF_PROC_NAME, dwPID);

    // 更改 privilege
    if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
        return 1;

    // 注入 dll
    if( EjectDll(dwPID, DEF_DLL_NAME) )
        _tprintf(L"EjectDll(%d, \"%s\") success!!!\n", dwPID, DEF_DLL_NAME);
    else
        _tprintf(L"EjectDll(%d, \"%s\") failed!!!\n", dwPID, DEF_DLL_NAME);

    return 0;
}

CreateToolhelp32Snapshot()函式主要用來獲取載入到程式的模組資訊,將獲取的hSnapshot控制程式碼傳遞給Module32First()/Module32Next()函式後,即可設定與MODULEENTRY32結構相關的模組資訊,以下為該結構的詳細定義:

typedef sturc tagMODULEENTRY32
{
    DWORD dwSize;
  DWORD th32ModuleID;        // 該模組
  DWORD th32ProcessID;    // 模組擁有的程式
  DWORD GlbcntUsage;        //模組中的global usage計數
  DWORD ProcessUsage;        
  BYTE * modBaseAddr;        //在程式的上下文中的模組的基地址
  DWORD modBaseSize;        // 在modBaseAddr開始位置的模組的大小(位元組為單位)
  HMODULE hModule;
  char szModule[MAX_MODULE_NAME32+1];    //DLL名稱
  char szExePath[MAX_PATH];
}MODULEENTRY32;

參考

《逆向工程核心原理》

相關文章