您當前的位置:首頁 > 體育

VC黑防日記:DLL隱藏和逆向(續)

作者:由 看雪 發表于 體育時間:2020-02-27

本文為看雪論壇優秀文章

看雪論壇作者ID:小迪xiaodi

【本文主題】:談談上次遺留的“幹掉吾愛破解OD等工具的模組列舉檢測”

【實驗平臺】:Win7 x64

【開發平臺】:Win10 x64 + VS2017

【除錯工具】:Ollydbg

【本文作者】:小迪xiaodi,一個非著名技術段子文章菜鳥寫手

————————————————————————————————————————————————————————————————————————————————————————————————————————

0x00:前言

經過幾天的努力,我的畢業論文終於寫的差不多了,老師說:

VC黑防日記:DLL隱藏和逆向(續)

OK,那咱們繼續回論壇吹水~

前段日子呢,寫了個《VC黑防日記(一):DLL隱藏和逆向(上)》的水文,也得到論壇的帶佬對我“能力”的認可:

VC黑防日記:DLL隱藏和逆向(續)

那我們今天,繼續來混一下字數,談談上次遺留的的問題——幹掉吾愛破解OD這類工具對HOOK FreeLibrary方式隱藏DLL的檢測

怎麼幹呢,很簡單,就是讓它沒了:

注入進去DLL之後,複製DLL映象到一塊記憶體地址,然後解除安裝DLL後,再把記憶體映象還原回原地址

這樣子,只是簡單的記憶體資料複製,而DLL的確被解除安裝了,複製後的DLL記憶體資料,只要計算下匯出函式地址便可以呼叫,相當於遠端調Call。

在此,也要感謝上次各位老哥推薦的靈活的方法思路,本文章並未進行復現,有機會可以一塊兒寫出來,在此表示感謝!

VC黑防日記:DLL隱藏和逆向(續)

————————————————————————————————————————————————————————————————————————————————————————————————————————

0x01:回顧

我們上次是透過分析FreeLibrary函式的流程得知大概分為四個步驟:

1。判斷DLL控制代碼是否有效,有效就說明該DLL存在於程序中

2。 遞減模組的引用計數,且判斷是否為0

3。 呼叫模組的DllMain函式響應 DLL_PROCESS_DETACH訊息

4。從程序空間撤銷對DLL的記憶體對映

然後我們透過對第四個步驟的分析,採用HOOK FreeLibrary 函式中的 ZwUnmapViewOfSection函式,實現了擦除DLL痕跡隱藏DLL的功能,但是出現了一丟丟的問題,那就是被某些“強大”的工具檢測並枚舉了出來,那麼為什麼會這樣呢?今天我們進來分析分析~

VC黑防日記:DLL隱藏和逆向(續)

————————————————————————————————————————————————————————————————————————————————————————————————————————

0x02:分析

既然我們上次HOOK ZwUnmapViewOfSection出現了小問題,能不能對 ZwUnmapViewOfSection做一些小手腳呢,暫時我的知識儲備還不夠,處理不了,但是我選擇了其他的繞過方式,在講這個方式之前,我們先了解一部分逆向的理論知識:

虛擬地址描述符:

大家都知道,在x86的平臺上Windows作業系統為每個程序描述了一個完整的4G的地址空間,這4G空間由低位2G的使用者地址空間和高位2G的系統地址空間構成。每個程序的使用者地址空間是相互隔離的,不可見的。但是系統地址空間是各個程序間共享的,對於程序有不同的檢視。使用者的私有的資料程式碼還有載入的動態連結庫(DLL)都存放在使用者地址空間中。現在有個問題是,總要有個地方記錄著這2G地址空間,到底那些被預留了,那些被提交了,那些被訪問了吧。還有個問題就是,對於程式來講,地址不是連續的,是分段的。程式碼段、資料段、堆、棧等等。可是程序的空間是連續的,從0x0000000到0x7FFFFFF。總要有個資料結構描述程式的各個段對應那段地址空間。這兩個重要任務就交個了VAD,即虛擬地址描述符(Virtual Address Descriptor)。

VAD組織成了一個AVL自平衡二叉樹(參考Mark Russinovich的《深入解析Windows作業系統》),這種組織方式完全是方便快速查詢。樹中的每一個節點代表了一段虛擬地址空間。所以程式的程式碼段,資料段,堆段都會各種佔用一個或多個VAD節點,由一個MMVAD結構完整描述。

核心空間並不受VAD的管理。

https://baike。baidu。com/item/%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80%E6%8F%8F%E8%BF%B0%E7%AC%A6/295257?fr=aladdin

分析一下:

1。有個玩意兒叫做“虛擬地址描述符”,存放了DLL的記憶體空間使用情況資料結構。

2。如果正常呼叫 ZwUnmapViewOfSection,記憶體地址所對應的“VAD”就會從樹中摘除。

3。我們上次hook了 ZwUnmapViewOfSection,剛好使得被注入的DLL的 “記憶體空間資料結構”無法從整體的“二叉樹”結構中消除。

結論:

1。如果不HOOK ZwUnmapViewOfSection ,就假戲真做,DLL就真的被解除安裝了。

2。如果HOOK了 ZwUnmapViewOfSection,就很難受,虛擬地址描述符清除不掉,還是過不了吾愛破解OD的檢測。

3。我們需要找一個方法在假戲真做的同時,儲存DLL的記憶體映象。

VC黑防日記:DLL隱藏和逆向(續)

————————————————————————————————————————————————————————————————————————————————————————————————————————

0x03:解決方案-記憶體複製法

由上一節的分析我們得知,如果我們假戲真做,DLL會被完整的解除安裝掉,那麼我們如果在解除安裝之前先儲存自身DLL記憶體映象到另外的位置,然後真正的解除安裝掉,最後再把記憶體映象複製回去就可以了,複製的時候複製回原地址是最好的方法,因此,我們開始解決問題吧!

1.複製DLL記憶體映象需要得知記憶體映象的大小,根據PE檔案結構操作,解:

char

szExePath[MAX_PATH] = “C:\\Users\\86186\\Desktop\\mydll。dll”;

HANDLE

hFile = CreateFile(szExePath, GENERIC_ALL, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);

//獲得PE檔案控制代碼

HANDLE

hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);

//建立一個新的檔案對映核心物件

PVOID

pbFile = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);

//將一個檔案對映物件對映到記憶體,得到指向對映到記憶體的第一個位元組的指標pbFile

if

(INVALID_HANDLE_VALUE == hFile || NULL == hMapping || NULL == pbFile)

{

printf(“\n\t—————— The File Inexistence! ——————\n”);

if

(NULL != pbFile)

{

UnmapViewOfFile(pbFile);

}

if

(NULL != hMapping)

{

CloseHandle(hMapping);

}

if

(INVALID_HANDLE_VALUE != hFile)

{

CloseHandle(hFile);

}

return

0;

}

//pDosHeader指向DOS頭起始位置

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pbFile;

printf(“PE Header e_lfanew:0x%x\n”, pDosHeader->e_lfanew);

//計算PE頭位置

PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pbFile + pDosHeader->e_lfanew);

//計算DLL模組映象大小

DWORD

dwSizeOfImage = (DWORD)pNTHeader->OptionalHeader。SizeOfImage;

printf(“SizeOfImage: 0x%08X\n”, dwSizeOfImage);

得到DLL模組映象大小 == 我們要複製寫的記憶體的大小。

2.分配記憶體地址方便寫入:

使用VirtualAllocEx函式:

https://

docs。microsoft。com/zh-c

n/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex

1

VirtualAllocEx(hProcess, NULL, dwSizeOfImage, MEM_COMMIT, PAGE_READWRITE);

然後修改記憶體屬性為可讀可寫:

BOOL

VirtualProtectEx(

HANDLE

hProcess,

LPVOID

lpAddress,

SIZE_T

dwSize,

DWORD

flNewProtect,

PDWORD lpflOldProtect

);

3.記憶體複製DLL資料

//儲存陣列

BYTE

code[] = { 0 };

DWORD

lp_copy = (DWORD)lpaddress;

DWORD

lp_start = addr_start;

for

(int

i = 0; i < dwSizeOfImage; i++, lp_start++, lp_copy++) {

ReadProcessMemory(hProcess, (LPCVOID)lp_start, code, 1, NULL);

WriteProcessMemory(hProcess, (LPVOID)lp_copy, code, 1, NULL);

}

printf(“原地址 = 0x%x 複製地址 = 0x%x\n”, addr_start, lpaddress);

MessageBox(NULL, “複製完成!”, “Cap”, MB_OK);

4.解除安裝DLL

解除安裝DLL後就可以真正的無影無蹤了:

1

UnInject(Pid, DLL路徑);

5.這樣,DLL就真的沒了,被複製在別的位置,只要知道起始地址,那麼就可以正常的呼叫DLL的匯出函數了

本次實驗的程式碼如下:

int

main()

{

const

char* a = "C:\\Users\\86186\\Desktop\\mydll.dll";

HANDLE

hToken = NULL;

//開啟當前程序的訪問令牌

int

hRet = OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);

if

(hRet)

{

TOKEN_PRIVILEGES tp;

tp.PrivilegeCount = 1;

//取得描述許可權的LUID

LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);

tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

//調整訪問令牌的許可權

AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);

CloseHandle(hToken);

}

char

szExePath[MAX_PATH] = "C:\\Users\\86186\\Desktop\\mydll.dll";

HANDLE

hFile = CreateFile(szExePath, GENERIC_ALL, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);

//獲得PE檔案控制代碼

HANDLE

hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);

//建立一個新的檔案對映核心物件

PVOID

pbFile = MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);

//將一個檔案對映物件對映到記憶體,得到指向對映到記憶體的第一個位元組的指標pbFile

if

(INVALID_HANDLE_VALUE == hFile || NULL == hMapping || NULL == pbFile)

{

printf("\n\t---------- The File Inexistence! ----------\n");

if

(NULL != pbFile)

{

UnmapViewOfFile(pbFile);

}

if

(NULL != hMapping)

{

CloseHandle(hMapping);

}

if

(INVALID_HANDLE_VALUE != hFile)

{

CloseHandle(hFile);

}

return

0;

}

//pDosHeader指向DOS頭起始位置

PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pbFile;

printf("PE Header e_lfanew:0x%x\n", pDosHeader->e_lfanew);

//計算PE頭位置

PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pbFile + pDosHeader->e_lfanew);

//計算DLL模組映象大小

DWORD

dwSizeOfImage = (DWORD)pNTHeader->OptionalHeader.SizeOfImage;

printf("SizeOfImage: 0x%08X\n", dwSizeOfImage);

UnmapViewOfFile(pbFile);

CloseHandle(hMapping);

CloseHandle(hFile);

Inject(GetProcessIDByName("程式碼注入器.exe"), (char*)a);

MessageBox(NULL, "注入完成!", "Cap", MB_OK);

int

Pid = 0;

while

(1)

{

Pid = GetProcessIDByName("程式碼注入器.exe");

if

(Pid > 0)

{

MessageBox(NULL, "檢測到程序,點選確定開始申請記憶體!", "Cap", MB_OK);

break;

}

}

//申請記憶體

HANDLE

hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Pid);

DWORD

dwOldProtect;

LPVOID

lpaddress = VirtualAllocEx(hProcess, NULL, dwSizeOfImage, MEM_COMMIT, PAGE_READWRITE);

printf("分配地址:0x%x\n",lpaddress);

VirtualProtectEx(hProcess, lpaddress, dwSizeOfImage + 1, PAGE_EXECUTE_READWRITE, &dwOldProtect);

DWORD

addr_start = 0;

while

(1)

{

addr_start = GetProcessModuleHandleByName(Pid, "mydll.dll");

if

(addr_start > 0)

{

MessageBox(NULL, "檢測到DLL被注入了,點選確定開始複製DLL!", "Cap", MB_OK);

break;

}

}

//儲存陣列

BYTE

code[] = { 0 };

DWORD

lp_copy = (DWORD)lpaddress;

DWORD

lp_start = addr_start;

for

(int

i = 0; i < dwSizeOfImage; i++, lp_start++, lp_copy++) {

ReadProcessMemory(hProcess, (LPCVOID)lp_start, code, 1, NULL);

WriteProcessMemory(hProcess, (LPVOID)lp_copy, code, 1, NULL);

}

printf("原地址 = 0x%x 複製地址 = 0x%x\n", addr_start, lpaddress);

MessageBox(NULL, "複製完成!", "Cap", MB_OK);

//真正去解除安裝

UnInject(GetProcessIDByName("程式碼注入器.exe"), (char*)a);

printf("原地址 = 0x%x \n", addr_start);

//還原DLL映象至原地址

DWORD

VirtualAddress = lpaddress;

VirtualFreeEx(hProcess, addr_start, dwSizeOfImage + 1, MEM_RELEASE);

DWORD

returnValue = VirtualAllocEx(hProcess, addr_start, dwSizeOfImage + 1, MEM_COMMIT, PAGE_READWRITE);

int

error1 = GetLastError();

printf("the value = %d error = %d \n", returnValue, error1);

MessageBox(NULL, "原記憶體地址恢復完成!", "Cap", MB_OK);

VirtualProtectEx(hProcess, addr_start, dwSizeOfImage + 1, PAGE_EXECUTE_READWRITE, &dwOldProtect);

for

(int

i = 0; i < dwSizeOfImage; i++, addr_start++, VirtualAddress++) {

ReadProcessMemory(hProcess, (LPCVOID)VirtualAddress, code, 1, NULL);

WriteProcessMemory(hProcess, (LPVOID)addr_start, code, 1, NULL);

}

MessageBox(NULL, "還原完成!DLL隱藏完成!", "Cap", MB_OK);

getchar();

return

0;

}

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

0x04:未解決不完美之處

本來打算把DLL的記憶體映象複製回解除安裝DLL之前DLL在程式中的記憶體地址的,無奈分配地址會出錯,暫時還沒找到解決的方法。

如果能夠複製回原來的地址就比較完美了

DWORD

returnValue = VirtualAllocEx(hProcess, addr_start, dwSizeOfImage + 1, MEM_COMMIT, PAGE_READWRITE);

int

error1 = GetLastError();

printf(“the value = %d error = %d \n”, returnValue, error1);

MessageBox(NULL, “原記憶體地址恢復完成!”, “Cap”, MB_OK);

透過GetLastError得到結果:

VC黑防日記:DLL隱藏和逆向(續)

發現錯誤487來源於:

VC黑防日記:DLL隱藏和逆向(續)

暫時還沒解“初始化的安全區”導致的分配原地址失敗的問題,如果有大佬會的話希望大佬能夠評論一下~

結語:

把一些新奇的想法用程式碼去做實驗實現還是比較有意思的,技術有些粗陋,重要的是享受過程(其實就是我菜)

#FormatImgID_15##FormatImgID_16#

文章開頭說到“遠端調Call”幾個字眼,那麼下節實驗的題目就來了~

下期實驗預告:遊戲外掛攻防——Call呼叫檢測攻防雜談

VC黑防日記:DLL隱藏和逆向(續)

標簽: null  dll  start  記憶體  地址