实战DLL注入
點擊上方關注 “終端研發部”
設為“星標”,和你一起掌握更多數據庫知識來源:https://bbs.pediy.com/user-home-919002.htm
一摘要使用vs2019編寫注入器程序, 在生成的注入器可用前, 踩了不少坑, 因此記錄一下。
本文涉及三種惡意代碼注入方法: 直接dll注入, 反射式dll注入, 鏤空注入。之所以選這三種注入方法, 是因最近在做一個檢測進程內存空間以期發現代碼注入的程序, 而實驗發現這三種方法對目標進程的改變各有特點:
直接dll注入: 還有APC注入, 本質都是在目標進程中執行LoadLibrary函數, 因而在枚舉目標進程的模塊列表時可看到注入的dll。
反射式dll注入: 這種方法也會在目標進程中開辟新的內存空間并寫入代碼。但因為沒有調用LoadLibrary, 所以枚舉目標進程的模塊列表并不能看到注入的dll。(意味著目標進程的PEB沒有變化)。
鏤空注入: 直接改進程中某一模塊的內存空間, 或者先注入一個合法模塊, 再鏤空該模塊。因為該方法沒有注入惡意dll, 所以直接枚舉目標進程的模塊列表也無法發現惡意代碼, 需掃描進程的內存空間。
標了注:的地方基本都是踩的坑。
示例: LibSpy項目的OnMouseMove和InjectDll
流程:
1、OpenProcess打開目標進程(一參為PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE| PROCESS_VM_READ, 后面CreateRemoteThread才能執行)。
需要給進程提權, 得到SeDebug權限。
(1)注:?準確地說不是提權, 而是將訪問令牌中禁用的權限啟用。成功調用下面幾個函數的前提是進程具備該權限, 只是訪問令牌中沒有啟用該權限。而如果進程沒有該權限, 則使用下面的函數后再調用GetLastError會返回ERROR_NOT_ALL_ASSIGN。
(2)先用LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid)得到用戶的debug權限。
(3)然后用OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,&hToken)獲取進程的令牌句柄。
(4)最后用AdjustTokenPrivileges啟用特權。
若要打開關鍵進程(csrss.exe等), 需在驅動中打開, 去掉關鍵進程的EPROCESS中的PsIsProtectProcess標志位, 并關閉dll簽名策略。(參考開源項目blackbone)
2、獲取待注入的dll路徑, 在目標進程內分配一塊內存(VirtualAllocateEx), 將路徑拷貝到該內存中(WriteProcessMemory)。
3、獲取kernel32中的LoadLibraryA地址(如果dll路徑是寬字符串, 則用LoadLibraryW)。
4、調用CreateRemoteThread, 在目標進程中執行LoadLibrary, 進而執行DllMain函數中的目標代碼。
注意, 因為VirtualAllocEx返回的是虛擬地址, 默認情況下CreateRemoteThread函數的lpStartAddress參數使用該地址是沒問題的。但是若注入器是32位而被注入程序是64位, 則可能導致CreateRemoteThread失敗而返回NULL。參考: createremotethread-returns-null-while-trying-to-use-it-to-inject-dll(https://stackoverflow.com/questions/60687458/createremotethread-returns-null-while-trying-to-use-it-to-inject-dll)
概要
沒有使用LoadLibrary函數注入dll。
需由注入器自行解析PE文件:
將dll頭部(包括DOS頭, PE頭, 區塊表)逐字節寫入新開辟的內存。
按重定位表的信息手動重定位。
修復導入函數表: 使用LdrLoadDll得到shellcode需要的庫的內存地址, 用LdrGetProcedureAddress得到要導入的函數的內存地址, 然后將這些地址填入導入表。
流程
1、將自己實現的LoadLibrary功能函數保存為shellcode。
注: 在shellcode中使用的系統api都要事先通過GetProcAddress獲取(使用GetModuleHandleA獲取模塊句柄, 傳入的模塊名不用后綴), 并作為參數傳給shellcode。
注: 需要審查注入器保存的shellcode是否是真實的函數體。調試發現在vs2019中, 按默認選項編譯得到的函數地址處是一條跳轉到實際函數體的jmp指令。因此需要使用jmp指令的操作數計算實際函數地址。如下為獲取一個shellcode函數的代碼:
// shellcode函數 void shellcodeFunc(PMY_PARAMS pParams) {// pParams保存LdrLoadDll等系統api的內存地址// 用NtAllocateVirtualMemory在目標進程中開辟一塊內存(需指定PAGE_EXECUTE_READWRITE權限)// 將dll的文件內容寫入開辟的內存// 修復導入表; 重定位// 執行dll的入口函數DLLMain }DWORD size = 0, ssss=0;// 獲取jmp指令后的雙字操作數(即jmp的目的地址偏移) DWORD* jmpAddr = (DWORD*) ((BYTE*) shellcodeFunc + 1);// 加5得到jmp指令的下一條指令地址, 然后加上jmp的目的地址偏移, 得到函數體的實際起始地址 WORD* Memx0 = (WORD*) ((BYTE*) shellcodeFunc + 5 + *jmpAddr); LONG_PTR* Memx = (LONG_PTR*) Memx0;// 用0xCCCCCCCCCCCCCCCC作為函數體結束識別標識. while (*Memx != 0xCCCCCCCCCCCCCCCC) {Memx++;size += 8; }// 將shellcode寫入文件 HANDLE hFile = CreateFile(LOADECODE, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, NULL, NULL); if (hFile) {WriteFile(hFile, Memx0, size, &ssss, NULL);CloseHandle(hFile); }注: 用windbg調試遠程線程, 發現遠程線程中出現地址訪問沖突:
原因1: 出問題的地方試圖讀取__security_cookie。
解決: 編譯時關閉/GS選項, 禁用棧保護。
原因2: 遠程線程中試圖調用一些不可用的函數, 包括__CheckForDebuggerJustMyCode, _RTC_CheckStackVars。
解決: 禁用/JMC選項, /RTC選項, 其他的如果是必要使用的動態庫函數, 則需要用LoadLibrary和GetProcAddress獲取, 且要確保目標進程已載入相應dll。
注: shellcode函數中不要用字符串常量, 因為在vs2019中調試發現這些字符串總是存在注入器進程的數據區而非棧上, 這樣一來shellcode在運行時無法獲取字符串(畢竟注入并運行的shellcode在目標進程而非注入器進程)。在shellcode中最好還是通過傳參的方式獲取需要用到的字符串常量。
2、在目標進程中開辟新內存, 依次寫入:
(1)要注入的dll的文件內容。
(2)shellcode。
(3)傳給shellcode的參數, 主要是shellcode需要的如下系統api的函數地址:
LdrLoadDll: 獲取注入的dll依賴的dll的內存地址。
LdrGetProcedureAddress: 獲取注入的dll需導入的函數的內存地址。
RtlInitAnsiString RtlAnsiStringToUnicodeString RtlFreeUnicodeString: 用以配合上述兩個函數, 得到導入函數的內存地址。
NtAllocateVirtualMemory: 分配內存空間, 以寫入要注入的dll的文件內容。
3、創建遠程線程, 執行shellcode, 由shellcode載入dll。
優點
僅枚舉進程模塊列表并不能發現注入的dll。
四鏤空(hollowing):概要:
兩種方式:
(1)鏤空已有進程模塊: 直接修改進程中已有模塊的代碼節, 注入惡意代碼.
(2)先注入后鏤空: 注入一個合法dll(擁有合法簽名), 然后修改dll入口點處代碼為自己想執行的代碼。
下面只講先注入dll后鏤空的方法。
流程
1、首先, 如普通的dll注入, CreateRemoteThread創建遠程線程, 執行LoadLibrary注入一個dll, 不同的是注入到進程的是一個合法dll(比如system32目錄下的dll)。
2、EnumProcessModules枚舉進程模塊, GetModuleBaseNameA得到每個模塊的名稱, 從而找到注入的dll。
3、注入器進程中分配0x1000的內存空間(可用malloc或HeapAlloc), 然后將找到的dll的PE頭部內容讀進來。
4、由PE頭中的optional頭得到目標dll的入口地址, 加上枚舉模塊時得到的dll的基址, 得到實際dll入口地址, 并用WriteProcessMemory向該地址寫入shellcode。
注: 通過windbg調試發現, 注入入口地址不一定能成功執行shellcode, 因為DllMain函數可能多次執行。如果只想執行一次shellcode, 可把shellcode及其寫在dll的末尾對齊空間。
如同反射式dll注入, 生成shellcode的方法也是在注入器中定義shellcode函數并獲取其機器碼。
5、創建遠程線程, 并以寫入shellcode的地址為線程執行地址。
優點
無需將惡意dll保存在磁盤中, 可躲避靜態查殺。
回復?【idea激活】即可獲得idea的激活方式 回復?【Java】獲取java相關的視頻教程和資料 回復?【SpringCloud】獲取SpringCloud相關多的學習資料 回復?【python】獲取全套0基礎Python知識手冊 回復?【2020】獲取2020java相關面試題教程 回復?【加群】即可加入終端研發部相關的技術交流群用 Spring 的 BeanUtils 前,建議你先了解這幾個坑!lazy-mock ,一個生成后端模擬數據的懶人工具在華為鴻蒙 OS 上嘗鮮,我的第一個“hello world”,起飛!字節跳動一面:i++ 是線程安全的嗎?一條 SQL 引發的事故,同事直接被開除!!太扎心!排查阿里云 ECS 的 CPU 居然達100%一款vue編寫的功能強大的swagger-ui,有點秀(附開源地址)相信自己,沒有做不到的,只有想不到的在這里獲得的不僅僅是技術!喜歡就給個“在看”總結
- 上一篇: kt条件例题运筹学_运筹学讲解习题
- 下一篇: C语言中 字符串和数字的相互转换