10.3 調試事件轉存進程記憶體

来源:https://www.cnblogs.com/LyShark/archive/2023/10/05/17743137.html
-Advertisement-
Play Games

我們繼續延申調試事件的話題,實現進程轉存功能,進程轉儲功能是指通過調試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 許可協議。轉載請註明出處!

文章作者:lyshark (王瑞)
文章出處:https://www.cnblogs.com/LyShark/p/17743137.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 軟體設計原則 GRASP 通用職責分配軟體模式 來自 Craig Larman 的軟體設計書《UML 和模式應用》,Larman 在書中提出軟體設計的關鍵任務是職責分配,並提煉總結出 9 種 (5 種核心 +4 種擴展) 軟體職責分配模式,這些模式是比 GoF 設計模式更抽象的元模式。 信息專家 ( ...
  • 本文記錄了穩定性摸排過程中的一些思考和沉澱。 前言 在之前寫了篇文章《上線十年,81萬行Java代碼的老系統如何重構》,在文章後有同學留言問“這麼複雜的改動,質量是如何應對的”,是一個特別好的問題,當時只是從現有的一些監控、測試、卡口手段上進行了回答。但在回答過程當中就在思考一個問題,交接過來的老代 ...
  • 前言 在日常的開發中,我們經常使用key-value鍵值對的HashMap,其使用哈希表實現,用空間換取時間,提升查詢性能 但在多線程的併發場景中,HashMap並不是線程安全的 如果想使用線程安全的,可以使用ConcurrentHashMap、HashTable、Collections.synch ...
  • Pattern模式是Dart 3.0發佈的3個高級特性之一,在第09天我們學習了模式的概覽和用法,對模式的強大之處有了基本的認識,今天我們來看看Dart中的全部模式類型,總共有15種,它們包括邏輯或、邏輯與、關係、值轉換、空檢測、空斷言、常量、變數、標識符、括弧、List列表、Map映射、Recor... ...
  • 簡介 SSE 的全稱是 Server Sent Events,即伺服器推送事件。它是一種基於 HTTP 的伺服器到客戶端的單向(半雙工)通信機制,使伺服器能夠主動將實時數據推送給客戶端,而不需要客戶端多次發起請求。 官方文檔:https://developer.mozilla.org/en-US/d ...
  • 跨域問題是指在瀏覽器上運行的Web應用程式試圖通過XMLHttpRequest或Fetch API等方式向不同源(功能變數名稱、協議或埠)的伺服器發送請求時,瀏覽器會根據同源策略(Same-Origin Policy)阻止這種行為。同源策略是一種安全機制,用於限制來自不同源的頁面對當前頁面的訪問。它可以防... ...
  • 信息學 學習/複習 抽簽器(附源碼) 效果圖 以下是源代碼,可自行修改 [C++] //By DijkstraPhoenix #include<bits/stdc++.h> #include<windows.h> using namespace std; vector<string>item; in ...
  • 萬里歸來年愈少 PB編程新思維10.5:外傳2(PowerPlume下一代解決方案) 前言 今天我們就來盤點一下,PB下一代開發的所有技術可能性。所謂下一代開發技術,就是指脫離或半脫離PBVM的應用開發技術,主要指後端。 後端技術彙總 前端PB+JSON 前端PB+BLOB WEB 後端PBVM P ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...