我們繼續延申調試事件的話題,實現進程轉存功能,進程轉儲功能是指通過調試API使獲得了目標進程式控制制權的進程,將目標進程的記憶體中的數據完整地轉存到本地磁碟上,對於加殼軟體,通常會通過加密、壓縮等手段來保護其代碼和數據,使其不易被分析。在這種情況下,通過進程轉儲功能,可以將加殼程式的記憶體鏡像完整地保存到本... ...
我們繼續延申調試事件的話題,實現進程轉存功能,進程轉儲功能是指通過調試API使獲得了目標進程式控制制權的進程,將目標進程的記憶體中的數據完整地轉存到本地磁碟上,對於加殼軟體,通常會通過加密、壓縮等手段來保護其代碼和數據,使其不易被分析。在這種情況下,通過進程轉儲功能,可以將加殼程式的記憶體鏡像完整地保存到本地,以便進行後續的分析。
在實現進程轉儲功能時,主要使用調試API和記憶體讀寫函數。具體實現方法包括:以調試方式啟動目標進程,將其暫停在運行前的位置;讓目標進程進入運行狀態;使用ReadProcessMemory函數讀取目標進程記憶體,並將結果保存到緩衝區;將緩衝區中的數據寫入文件;關閉目標進程的調試狀態。
首先老樣子先來看OnException
回調事件,當進程被斷下時首先通過線程函數恢復該線程的狀態,在進程被正確解碼並運行起來時直接將該進程的EIP入口地址傳遞給MemDump();
記憶體轉存函數,實現轉存功能;
void OnException(DEBUG_EVENT *pDebug, BYTE *bCode)
{
CONTEXT context;
DWORD dwNum;
BYTE bTmp;
// 打開當前進程與線程
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pDebug->dwProcessId);
printf("[+] 當前打開進程句柄: %d 進程PID: %d \n", hProcess, pDebug->dwProcessId);
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pDebug->dwThreadId);
printf("[+] 當前打開線程句柄: %d 線程PPID: %d \n", hThread, pDebug->dwThreadId);
// 暫停當前線程
SuspendThread(hThread);
// 讀取出異常產生的首地址
ReadProcessMemory(hProcess, pDebug->u.Exception.ExceptionRecord.ExceptionAddress, &bTmp, sizeof(BYTE), &dwNum);
printf("[+] 當前異常產生地址為: 0x%08X \n", pDebug->u.Exception.ExceptionRecord.ExceptionAddress);
// 設置當前線程上下文,獲取線程上下文
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(hThread, &context);
printf("[-] 恢復斷點前: EAX = 0x%08X EIP = 0x%08X \n", context.Eax, context.Eip);
// 將剛纔的CC斷點取消,也就是回寫原始的指令集
WriteProcessMemory(hProcess, pDebug->u.Exception.ExceptionRecord.ExceptionAddress, bCode, sizeof(BYTE), &dwNum);
// 當前EIP減一併設置線程上下文
context.Eip--;
SetThreadContext(hThread, &context);
printf("[+] 恢復斷點後: EAX = 0x%08X EIP = 0x%08X \n", context.Eax, context.Eip);
printf("[+] 獲取到動態入口點: 0x%08x \n", pDebug->u.CreateProcessInfo.lpBaseOfImage);
// 轉儲記憶體鏡像
MemDump(pDebug, context.Eip, (char *)"dump.exe");
// 恢複線程
ResumeThread(hThread);
CloseHandle(hThread);
CloseHandle(hProcess);
}
MemDump函數中,首先通過調用CreateFile
函數打開me32.szExePath
路徑也就是轉存之前的文件,通過使用VirtualAlloc
分配記憶體空間,分配大小是PE頭中文件實際大小,接著OpenProcess
打開正在運行的進程,並使用ReadProcessMemory
讀取文件的數據,此處讀取的實在記憶體中的鏡像數據,當讀取後手動修正,文件的入口地址,及文件的對齊方式,接著定位PE節區數據,找到節區首地址,並迴圈將當前節區數據賦值到新文件緩存中,最後當一切準備就緒,通過使用WriteFile
函數將轉存後的文件寫出到磁碟中;
void MemDump(DEBUG_EVENT *pDe, DWORD dwEntryPoint, char *DumpFileName)
{
// 得到當前需要操作的進程PID
DWORD dwPid = pDe->dwProcessId;
MODULEENTRY32 me32;
// 對系統進程拍攝快照
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
me32.dwSize = sizeof(MODULEENTRY32);
// 得到第一個模塊句柄,第一個模塊句柄也就是程式的本體
BOOL bRet = Module32First(hSnap, &me32);
printf("[+] 當前轉儲原程式路徑: %s \n", me32.szExePath);
// 打開源文件,也就是dump之前的文件
HANDLE hFile = CreateFile(me32.szExePath, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
exit(0);
// 判斷PE文件的有效性
IMAGE_DOS_HEADER imgDos = { 0 };
IMAGE_NT_HEADERS imgNt = { 0 };
DWORD dwReadNum = 0;
// 讀入當前記憶體程式的DOS頭結構
ReadFile(hFile, &imgDos, sizeof(IMAGE_DOS_HEADER), &dwReadNum, NULL);
// 判斷是否是一個合格的DOS頭
if (imgDos.e_magic != IMAGE_DOS_SIGNATURE)
return;
// 設置文件指針到NT頭上
SetFilePointer(hFile, imgDos.e_lfanew, 0, FILE_BEGIN);
ReadFile(hFile, &imgNt, sizeof(IMAGE_NT_HEADERS), &dwReadNum, NULL);
// 判斷是否是合格的NT頭
if (imgNt.Signature != IMAGE_NT_SIGNATURE)
return;
// 得到EXE文件的大小
DWORD BaseSize = me32.modBaseSize;
printf("[+] 當前記憶體文件大小: %d --> NT結構原始大小: %d 一致性檢測: True \n", BaseSize, imgNt.OptionalHeader.SizeOfImage);
// 如果PE頭中的大小大於實際記憶體大小,則以PE頭中大小為模板
if (imgNt.OptionalHeader.SizeOfImage > BaseSize)
{
BaseSize = imgNt.OptionalHeader.SizeOfImage;
}
// 分配記憶體空間,分配大小是PE頭中文件實際大小,並打開進程
LPVOID pBase = VirtualAlloc(NULL, BaseSize, MEM_COMMIT, PAGE_READWRITE);
printf("[+] 正在分配轉儲空間 句柄: %d \n", pBase);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
// 讀取文件的數據,此處讀取的實在記憶體中的鏡像數據
bRet = ReadProcessMemory(hProcess, me32.modBaseAddr, pBase, me32.modBaseSize, NULL);
// 判斷PDOS頭的有效性
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBase;
if (pDos->e_magic != IMAGE_DOS_SIGNATURE)
return;
// 計算出NT頭數據
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (PBYTE)pBase);
if (pNt->Signature != IMAGE_NT_SIGNATURE)
return;
// 設置文件的入口地址
pNt->OptionalHeader.AddressOfEntryPoint = dwEntryPoint - pNt->OptionalHeader.ImageBase;
printf("[*] 正在設置Dump文件相對RVA入口地址: 0x%08X \n", pNt->OptionalHeader.AddressOfEntryPoint);
// 設置文件的對齊方式
pNt->OptionalHeader.FileAlignment = 0x1000;
printf("[*] 正在設置Dump文件的對齊值: %d \n", pNt->OptionalHeader.FileAlignment);
// 找到節區首地址,並迴圈將當前節區數據賦值到新文件緩存中
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((PBYTE)&pNt->OptionalHeader + pNt->FileHeader.SizeOfOptionalHeader);
for (int i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
pSec->PointerToRawData = pSec->VirtualAddress;
printf("[+] 正在將虛擬地址: 0x%08X --> 設置到文件地址: 0x%08X \n", pSec->VirtualAddress, pSec->PointerToRawData);
pSec->SizeOfRawData = pSec->Misc.VirtualSize;
printf("[+] 正在將虛擬大小: %d --> 設置到文件大小: %d \n", pSec->Misc.VirtualSize, pSec->SizeOfRawData);
pSec++;
}
CloseHandle(hFile);
// 打開轉儲後的文件.
hFile = CreateFile(DumpFileName, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
exit(0);
printf("[*] 轉儲 %s 文件到本地 \n", DumpFileName);
DWORD dwWriteNum = 0;
// 將讀取的數據寫入到文件
bRet = WriteFile(hFile, pBase, me32.modBaseSize, &dwWriteNum, NULL);
if (dwWriteNum != me32.modBaseSize || FALSE == bRet)
printf("寫入錯誤 !");
// 關閉於釋放資源
CloseHandle(hFile);
VirtualFree(pBase, me32.modBaseSize, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hSnap);
}
讀者可自行運行這段程式,當程式運行後即可將指定的一個文件記憶體數據完整的轉存到磁碟中,輸出效果如下圖所示;
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/5e2f7b11.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
文章出處:https://www.cnblogs.com/LyShark/p/17743137.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!