10.3 调试事件转存进程内存

本文涉及的产品
公网NAT网关,每月750个小时 15CU
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 我们继续延申调试事件的话题,实现进程转存功能,进程转储功能是指通过调试API使获得了目标进程控制权的进程,将目标进程的内存中的数据完整地转存到本地磁盘上,对于加壳软件,通常会通过加密、压缩等手段来保护其代码和数据,使其不易被分析。在这种情况下,通过进程转储功能,可以将加壳程序的内存镜像完整地保存到本地,以便进行后续的分析。在实现进程转储功能时,主要使用调试API和内存读写函数。具体实现方法包括:以调试方式启动目标进程,将其暂停在运行前的位置;让目标进程进入运行状态;使用ReadProcessMemory函数读取目标进程内存,并将结果保存到缓冲区;将缓冲区中的数据写入文件;关闭目标进程的调试状态

我们继续延申调试事件的话题,实现进程转存功能,进程转储功能是指通过调试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 许可协议。转载请注明出处!

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
高可用应用架构
欢迎来到“高可用应用架构”课程,本课程是“弹性计算Clouder系列认证“中的阶段四课程。本课程重点向您阐述了云服务器ECS的高可用部署方案,包含了弹性公网IP和负载均衡的概念及操作,通过本课程的学习您将了解在平时工作中,如何利用负载均衡和多台云服务器组建高可用应用架构,并通过弹性公网IP的方式对外提供稳定的互联网接入,使得您的网站更加稳定的同时可以接受更多人访问,掌握在阿里云上构建企业级大流量网站场景的方法。 学习完本课程后,您将能够: 理解高可用架构的含义并掌握基本实现方法 理解弹性公网IP的概念、功能以及应用场景 理解负载均衡的概念、功能以及应用场景 掌握网站高并发时如何处理的基本思路 完成多台Web服务器的负载均衡,从而实现高可用、高并发流量架构
相关文章
|
10天前
麒麟系统mate-indicators进程占用内存过高问题解决
【10月更文挑战第7天】麒麟系统mate-indicators进程占用内存过高问题解决
65 2
|
1月前
|
存储 Linux 调度
深入理解操作系统:从进程管理到内存分配
【8月更文挑战第44天】本文将带你深入操作系统的核心,探索其背后的原理和机制。我们将从进程管理开始,理解如何创建、调度和管理进程。然后,我们将探讨内存分配,了解操作系统如何管理计算机的内存资源。最后,我们将通过一些代码示例,展示这些概念是如何在实际操作系统中实现的。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和深入的理解。
|
3月前
|
存储 算法 调度
深入理解操作系统:从进程管理到内存分配
本文将探讨操作系统的核心概念,包括进程管理、内存分配以及文件系统等。我们将通过具体的案例和数据来分析这些概念的工作原理,以及它们如何影响计算机的性能和稳定性。文章将提供对操作系统内部机制的深入理解,帮助读者更好地理解和使用计算机。
78 0
|
16天前
|
缓存 算法 调度
深入浅出操作系统:从进程管理到内存优化
本文旨在为读者提供一次深入浅出的操作系统之旅。我们将从进程管理的基本概念出发,逐步深入到内存管理的复杂世界,最终探索如何通过实践技巧来优化系统性能。文章将结合理论与实践,通过代码示例,帮助读者更好地理解操作系统的核心机制及其在日常技术工作中的重要性。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往操作系统深层次理解的大门。
|
12天前
麒麟系统mate-indicators进程占用内存过高问题解决
【10月更文挑战第5天】麒麟系统mate-indicators进程占用内存过高问题解决
58 0
|
3月前
|
存储 算法 定位技术
深入理解操作系统:从进程管理到内存分配
【7月更文挑战第27天】本文旨在为读者提供一个全面而深入的视角,以理解操作系统的核心概念和机制。我们将通过探讨进程管理、内存分配等关键主题,揭示这些复杂系统如何协同工作以确保计算环境的稳定和高效。文章将采用比喻和实例来阐释抽象的概念,使技术内容更加贴近生活,易于理解。
|
1月前
|
监控 Ubuntu API
Python脚本监控Ubuntu系统进程内存的实现方式
通过这种方法,我们可以很容易地监控Ubuntu系统中进程的内存使用情况,对于性能分析和资源管理具有很大的帮助。这只是 `psutil`库功能的冰山一角,`psutil`还能够提供更多关于系统和进程的详细信息,强烈推荐进一步探索这个强大的库。
38 1
|
1月前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
1月前
|
Linux Windows
检测进程内存的活跃程度
检测进程内存的活跃程度
|
1月前
|
Linux
查看进程的内存使用信息
查看进程的内存使用信息

相关实验场景

更多