- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
我们继续延申调试事件的话题,实现进程转存功能,进程转储功能是指通过调试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 许可协议。转载请注明出处! 。
最后此篇关于10.3调试事件转存进程内存的文章就讲到这里了,如果你想了解更多关于10.3调试事件转存进程内存的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我是 Linux 的新手,并且继承了保持我们的单一 Linux 服务器运行的职责。这是我们的SVN服务器,所以比较重要。 原来在我之前维护它的人有一个 cron 任务,当有太多 svnserve 进程
Node 虽然自身存在多个线程,但是运行在 v8 上的 JavaScript 是单线程的。Node 的 child_process 模块用于创建子进程,我们可以通过子进程充分利用 CPU。范例:
Jenkins 有这么多进程处于事件状态是否正常? 我检查了我的设置,我只配置了 2 个“执行者”... htop http://d.pr/i/RZzG+ 最佳答案 您不仅要限制 Master 中的执
我正在尝试在 scala 中运行这样的 bash 命令: cat "example file.txt" | grep abc Scala 有一个特殊的流程管道语法,所以这是我的第一个方法: val f
很难说出这里要问什么。这个问题模棱两可、含糊不清、不完整、过于宽泛或夸夸其谈,无法以目前的形式得到合理的回答。如需帮助澄清此问题以便重新打开,visit the help center . 关闭 1
我需要一些帮助来理解并发编程的基础知识。事实上,我读得越多,就越感到困惑。因此,我理解进程是顺序执行的程序的一个实例,并且它可以由一个或多个线程组成。在单核CPU中,一次只能执行一个线程,而在多核CP
我的问题是在上一次集成测试后服务器进程没有关闭。 在integration.rs中,我有: lazy_static! { static ref SERVER: Arc> = {
我正在使用 Scala scala.sys.process图书馆。 我知道我可以用 ! 捕获退出代码和输出 !!但是如果我想同时捕获两者呢? 我看过这个答案 https://stackoverflow
我正在开发一个C++类(MyClass.cpp),将其编译为动态共享库(MyClass.so)。 同一台Linux计算机上运行的两个不同应用程序将使用此共享库。 它们是两个不同的应用程序。它不是多线程
我在我的 C 程序中使用 recvfrom() 从多个客户端接收 UDP 数据包,这些客户端可以使用自定义用户名登录。一旦他们登录,我希望他们的用户名与唯一的客户端进程配对,这样服务器就可以通过数据包
如何更改程序,以便函数 function_delayed_1 和 function_delayed_2 仅同时执行一次: int main(int argc, char *argv[]) {
考虑这两个程序: //in #define MAX 50 int main(int argc, char* argv[]) { int *count; int fd=shm
请告诉我如何一次打开三个终端,这样我的项目就可以轻松执行,而不必打开三个终端三次然后运行三个exe文件。请问我们如何通过脚本来做到这一点,即打开三个终端并执行三个 exe 文件。 最佳答案 在后台运行
我编写了一个监控服务来跟踪一组进程,并在服务行为异常、内存使用率高、超出 CPU 运行时间等时发出通知。 这在我的本地计算机上运行良好,但我需要它指向远程机器并获取这些机器上的进程信息。 我的方法,在
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 想改进这个问题?将问题更新为 on-topic对于堆栈溢出。 8年前关闭。 Improve this qu
我有一个允许用户上传文件的应用程序。上传完成后,必须在服务器上完成许多处理步骤(解压、存储、验证等...),因此稍后会在一切完成后通过电子邮件通知用户。 我见过很多示例,其中 System.Compo
这个问题对很多人来说可能听起来很愚蠢,但我想对这个话题有一个清晰的理解。例如:当我们在 linux(ubuntu, x86) 上构建一个 C 程序时,它会在成功编译和链接过程后生成 a.out。 a.
ps -eaf | grep java 命令在这里不是识别进程是否是 java 进程的解决方案,因为执行此命令后我的许多 java 进程未在输出中列出。 最佳答案 简答(希望有人写一个更全面的): 获
我有几个与内核态和用户态的 Windows 进程相关的问题。 如果我有一个 hello world 应用程序和一个暴露新系统调用 foo() 的 hello world 驱动程序,我很好奇在内核模式下
我找不到很多关于 Windows 中不受信任的完整性级别的信息,对此有一些疑问: 是否有不受信任的完整性级别进程可以创建命名对象的地方? (互斥锁、事件等) 不受信任的完整性级别进程是否应该能够打开一
我是一名优秀的程序员,十分优秀!