3環下要想隐藏dll,僅僅靠斷鍊和抹去PE頭資訊是不夠的;這樣做能騙過同樣在3環運作的調試器,但是騙不過在0環通過驅動做檢測的PChunter、Process Hacker等工具;要想徹底隐藏,需要更進一步搞定驅動層的各種檢測,下面會詳細介紹隐藏的細節原理和操作方法!
1、VAD 虛拟記憶體管理
記憶體分兩種:實體記憶體和虛拟記憶體;作業系統和程序共享實體記憶體,程序獨享虛拟記憶體;實體記憶體可以通過CR3在程序之間互相隔離,確定程序之間互不侵犯;那麼程序内部的虛拟位址該怎麼管理了? 32位下,每個程序獨享4GB記憶體,怎麼知道哪些記憶體已經使用過,哪些沒用過? 已經使用的記憶體,是可讀可寫可執行的麼? 還是隻讀的了? 該怎麼記錄這些關鍵資訊了?windwos采用一種叫做virtual address descripot的自平衡二叉樹來管理虛拟記憶體,低端的記憶體位址放在根節點左子樹,高端記憶體位址放根節點右子樹,大緻的結構如下:每當程序調用virtualAlloc配置設定虛拟記憶體時,作業系統會先周遊這個樹,看看還有哪些地方的虛拟記憶體還未使用,然後傳回給開發人員:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5SN0gzM3cTYihzN3EWZiRDZyYzX3ADMwATM4EzLcdDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
windbg能查到每個程序VAD的root節點:
VAD裡面也記錄了該程序dll的使用情況。
當記憶體使用完畢,建議立即調用VirtualFree,将這段虛拟記憶體從VAD從抹去,後續再次周遊時才能繼續使用!正常情況下,如果要解除安裝dll,可以調用windwos提供的freeLibrary接口,裡面有關鍵的函數:ZwUnmapViewOfSection,可以直接把dll對應的記憶體從VAD中删除(這裡多說兩句:ZwUnmapViewOfSection 功能很強大,可以替換程序的代碼,讓其稱為傀儡執行惡意的代碼)。
2、之前分享過一個驅動隐藏的思路: 讓driver entry傳回false,作業系統會認為驅動加載失敗,不會記錄。但在driverentry裡面把自己想要執行的代碼拷貝到堆上,然後将代碼入口點作為imageLoad回調函數的入口點。雖然驅動加載“失敗”,但代碼已經拷貝到堆,并且注冊成為了回調函數,dll隐藏也可以借鑒類似的思路:
- 先重新申請一個新空間,把需要隐藏的dll拷貝到新空間備份
- 用freelibrary釋放需要隐藏的dll,VAD中會删除這個dll的。此時如果eip跳轉到dll執行,肯定報錯
- 重新用virtualAlloc申請原dll位址,再把第一步備份的原dll代碼拷貝到這次申請的位址(其實就是dll原來加載的位址)
- 此時如果eip跳轉到這個位址執行代碼是ok的
這麼做的本質是:把dll從vad的記錄中抹去,重新申請記憶體來存放dll的代碼。雖說在vad還是有記憶體的使用記錄,但因為并未使用loadlibrary,是以也不會在vad中留下dll的記錄(這是本質是把dll變相當成shellcode在用,至于全局變量、導入函數、重定位這些,由編譯器和作業系統都做好了,不需要開發人員操心);核心代碼如下:
/************************************************************************/
/* 把目前程序的所有DLL(除開需要隐藏的那個)都使用LoadLibrary再次加載一邊,增加引用計數, */
/* 使得Free時對應的DLL資源不釋放 */
/************************************************************************/
void LockAllModules()
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, GetCurrentProcessId());
if (hSnapshot != INVALID_HANDLE_VALUE)
{
MODULEENTRY32 me = { sizeof(me) };
BOOL fOk = Module32First(hSnapshot, &me);
for (fOk = Module32Next(hSnapshot, &me); fOk; fOk = Module32Next(hSnapshot, &me))
{
//跳過第一個(自身)
CString wInfo;
wInfo.Format(_T("%s"), me.szModule);
wInfo.MakeLower();
if (wInfo != _T("dlls.dll"))LoadLibrary(me.szModule);//加載除了dlls.dll以外的所有記憶體
}
}
}
BOOL CopycatAndHide(HMODULE hDll)
{
// 整體思路:先把DLL加載到目前程序,然後将該加載的DLL再備份到目前程序空間;
// 接下來該DLL再Free了,此時程序再通路該DLL的話會出錯;
// Free後,再把預先備份的DLL資料還原,而且還原的資料位址是原先DLL加載的位址
// 如此,程序内再調用該DLL的話,由于資料完整,一切OK
DWORD g_dwImageSize = 0;
VOID* g_lpNewImage = NULL;
IMAGE_DOS_HEADER* pDosHeader;
IMAGE_NT_HEADERS* pNtHeader;
IMAGE_OPTIONAL_HEADER* pOptionalHeader;
LPVOID lpBackMem = 0;
DWORD dwOldProtect;
DWORD dwCount = 30;
pDosHeader = (IMAGE_DOS_HEADER*)hDll;
pNtHeader = (IMAGE_NT_HEADERS*)(pDosHeader->e_lfanew + (DWORD)hDll);
pOptionalHeader = (IMAGE_OPTIONAL_HEADER*)&pNtHeader->OptionalHeader;
LockAllModules();
// 找一塊記憶體把需要隐藏而且已經加載到記憶體的DLL備份
// SizeOfImage,4個位元組,表示程式調入後占用記憶體大小(位元組),等于所有段的長度之和。
lpBackMem = VirtualAlloc(0, pOptionalHeader->SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!lpBackMem)
return FALSE;
if (!VirtualProtect((LPVOID)hDll, pOptionalHeader->SizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect))
return FALSE;
g_dwImageSize = pOptionalHeader->SizeOfImage;
memcpy(lpBackMem, (LPVOID)hDll, g_dwImageSize);
// 抹掉PE頭
//memset(lpBackMem, 0, 0x200);
*((PBYTE)hDll + pOptionalHeader->AddressOfEntryPoint) = (BYTE)0xc3;
// DWORD dwRet =0;
// Free掉DLL
do
{
dwCount--;
} while (FreeLibrary(hDll) && dwCount);
// 把備份的DLL資料還原回來,使得預先引用該DLL的程式能夠繼續正常運作
g_lpNewImage = VirtualAlloc((LPVOID)hDll, g_dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (g_lpNewImage != (LPVOID)hDll)
return FALSE;
memcpy(g_lpNewImage, lpBackMem, g_dwImageSize);
VirtualFree(lpBackMem, 0, MEM_RELEASE);
return TRUE;
}
參考:
1、https://wenku.baidu.com/view/439526b369dc5022aaea0077 記憶體管理
2、https://bbs.pediy.com/thread-257179.htm VC黑防日記(二):DLL隐藏和逆向