【系列:CCG Crypto CrackMe 逆向全解析 · 第 5 篇】
导读上一篇里BF2000 的解压缩器把花指令、SEH 劫持和 ICEBP 叠在一起继续在 Unicorn 里补异常机制已经不是最划算的路。这一篇换个角度既然样本能在真实 Windows 中自己完成解密和解压就让它自己跑完再用ReadProcessMemory把内存里已经还原的代码读出来。重点不是“读到一段字节”而是用代码形态、算法指纹和数据资源三层证据确认这份 dump 值得继续分析。这篇文章讲的是一次逆向路线的转向不再和壳的执行细节死磕而是把注意力放到它最终留下的内存结果上。文中的地址、命中次数和字符串位置都来自本地独立验证脚本仅用于隔离、授权样本的只读观察。逆向时最容易陷入的一种执念是觉得“没把壳的每条指令走完就不算脱壳”。但上一章分析 BF2000 时问题已经很清楚花指令让静态反汇编错位SEH 劫持把关键控制流塞进异常链ICEBP 又会让调试环境与正常环境出现分叉。Windows 的 SEH 本身就处理硬件与软件异常并与调试器协作在模拟器里补这部分行为成本很快会失控。Microsoft LearnStructured Exception Handling当脚本一次次卡在同一段异常路径里值得换个问题我们真正需要的是复现壳的过程还是拿到壳完成后的结果如果样本能在隔离环境中正常运行它终究要把后续代码放入自己的虚拟地址空间。我们不必替它完成解密、解压和异常分发让真实系统处理这些事情分析脚本只负责观察结果。这就是本文说的“不脱壳的脱壳法”。把分析目标从“执行过程”改成“可观察结果”传统路线没有错。对普通壳而言跟踪入口、识别 OEP、转储内存是一条成熟而直接的工作流。问题在于BF2000 的设计目标之一就是放大这种跟踪的成本。它把正常执行依赖的异常处理、调试状态和栈布局混在一起。模拟器即使能执行大部分 x86 指令也不会天然拥有完整的 Windows 线程环境、SEH 分发规则和调试异常处理顺序。因此本文重新划分责任真实 Windows负责运行样本自身的解密、解压与异常处理逻辑 分析脚本负责只读获取内存并验证读取结果是否可信壳最终必须产出能继续执行的代码。那份代码一旦在内存中稳定出现就比混淆的解压流程更接近我们真正要分析的对象。当然这个方法只适用于自己拥有、明确获授权分析或隔离研究环境中的样本。本文只讨论读取不向目标进程写入内容、不注入代码也不修改它的控制流。第一步先定位目标进程不要手填 PIDReadProcessMemory的前提是一个可读取的进程句柄。这个样本启动后会创建标题为CCG Crypto Crackme v1.0 --- by blowfish的窗口因此可以从窗口句柄取得 PID再申请读取与查询权限。importctypesfromctypesimportwintypes kernel32ctypes.WinDLL(kernel32,use_last_errorTrue)user32ctypes.WinDLL(user32,use_last_errorTrue)PROCESS_VM_READ0x0010PROCESS_QUERY_INFORMATION0x0400hwnduser32.FindWindowW(None,CCG Crypto Crackme v1.0 --- by blowfish)ifnothwnd:raiseRuntimeError(样本尚未启动或窗口标题不匹配)pidwintypes.DWORD()user32.GetWindowThreadProcessId(hwnd,ctypes.byref(pid))processkernel32.OpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION,False,pid.value,)ifnotprocess:raisectypes.WinError(ctypes.get_last_error())这里要守住权限边界PROCESS_VM_READ用于读取目标虚拟内存PROCESS_QUERY_INFORMATION用于查询进程相关信息。这里没有WriteProcessMemory没有远程线程也没有改变目标状态的步骤。但句柄打开成功并不等于已经到了正确的读取时机。窗口刚出现时壳可能还在初始化过晚读取时目标也可能已经释放或覆盖某些页面。本例能够读取固定范围是因为此前已经完成 PE 布局分析并在样本稳定启动后取得快照。面对陌生样本必须重新确认模块基址、内存页状态或执行时机不能照抄地址。第二步读取内存时检查返回长度不少脚本只判断ReadProcessMemory是否返回成功。做演示或许可行接下来要反汇编时就不够严谨了。官方文档说明调用方需要带PROCESS_VM_READ权限的句柄读取区间不可访问时调用会失败lpNumberOfBytesRead则用于接收实际传输的字节数。Microsoft LearnReadProcessMemory如果请求跨越不可访问页面或者内存保护发生变化读取不完整会让后续字节整体错位。少几个字节反汇编器就可能从错误位置开始解码地址、基本块和函数边界都会被带偏。defread_exact(process,address,size):bufferctypes.create_string_buffer(size)bytes_readctypes.c_size_t()okkernel32.ReadProcessMemory(process,ctypes.c_void_p(address),buffer,size,ctypes.byref(bytes_read),)ifnotok:raisectypes.WinError(ctypes.get_last_error())ifbytes_read.value!size:raiseRuntimeError(f读取不完整请求{size:#x}实际{bytes_read.value:#x})returnbuffer.raw本样本是 32 位 PEImageBase 为0x400000。用于后续静态分析的代码快照从0x401000开始读取0x12000字节字符串数据快照则从0x41E000读取。text_coderead_exact(process,0x401000,0x12000)data_stringsread_exact(process,0x41E000,0x2000)这里故意把结果分为text_code.bin和data_strings.bin。这不只是便于管理文件更是在提醒自己代码、立即数、字符串和查找表并不遵循同一种分布规律。把它们混在一片数据里搜索很容易得出“没找到就是不存在”的错误结论。第一层验证这片内存是否呈现正常代码形态读出一段字节后不能因为反汇编器能把它显示成指令就立刻认定“这是真代码”。随机字节同样能被解释成合法指令。我们需要先找结构性信号。在 32 位 x86 程序中常见的函数序言是push ebp ; 55 mov ebp, esp ; 8B EC扫描这段模式可以快速判断内存区域是否具有正常编译代码的特征prologueb\x55\x8B\xECcountsum(text_code[i:ilen(prologue)]prologueforiinrange(len(text_code)-len(prologue)1))print(count)# 96这次快照中55 8B EC命中96处。这个数字绝不能写成“样本有 96 个函数”优化、内联和不同的栈帧建立方式都会影响计数。它真正说明的是这片区域中存在大量符合普通函数布局的结构信号和未解密的高熵数据、或只承担引导职责的壳代码相比呈现出明显不同的形态。第一层验收通过后我们才有理由继续寻找算法实现。第二层验证MD5 常数要按指令编码来找MD5 初始化会用到四个典型的 32 位常数0x67452301 0xEFCDAB89 0x98BADCFE 0x10325476很多人在这里会犯一个很自然的错误把四个数按小端序拼成连续 16 字节然后做一次find()。在这个样本里这种方法会失败。原因是常数不是作为连续数据表出现的而是分别嵌入四条mov dword ptr [mem], imm32指令。每个立即数之间隔着指令自身的编码字节。正确方式是把四个 32 位值逐个按小端序搜索importstruct ivs[0x67452301,0xEFCDAB89,0x98BADCFE,0x10325476]forvalueinivs:offsettext_code.find(struct.pack(I,value))print(hex(0x401000offset))四个命中地址依次为0x40831E 0x408325 0x40832C 0x408333相邻地址间隔正好是7 字节符合“3 字节指令字段 4 字节立即数”的布局。再看周边反汇编就能把它们放回MD5_Init的初始化写入逻辑。这里真正值得记住的不是四个 MD5 IV 本身而是先分清它们是“数据表里的值”还是“指令立即数里的值”。存储形式不同搜索方法就不同。第三层验证字符串和字母表要去数据区找算法指纹存在于代码区不代表所有证据都在那里。Base64 字母表、成功提示、失败提示和 RSA 参数字符串通常属于数据资源应当在数据区验证。alphabetbABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/print(text_code.find(alphabet))# -1print(data_strings.find(alphabet))# 0结果很明确Base64 字母表在data_strings.bin的偏移0x0也就是 VA0x41E000它在text_code.bin中是NOT FOUND。同一数据区还命中了Great、Successfully registered!、65537与B80A90BF53C6C979。这些文本既与程序的 UI 行为相符也与后续 RSA 分析所需的参数相符。这一层验收纠正的不是一个小细节而是一种分析惯性不要因为自己正在研究“代码”就只在代码区找一切。数据在哪里取决于它在程序里扮演什么角色而不是取决于我们当前正在分析哪一部分。三层证据同时成立dump 才值得交给后续分析把三项结果放在一起证据链才完整验证层本次观察排除的误判代码形态55 8B EC命中 96 处把壳残留或随机字节误判为原始代码算法指纹四个 MD5 IV 按 7 字节间隔嵌入指令流用连续 16 字节搜索失败后误判“没有 MD5”数据资源Base64 表与关键字符串位于0x41E000数据区在错误段搜索后得出“目标不存在”任何一项单独命中都不足以证明“这就是完整原始程序”。但三项分别从代码结构、指令级算法特征与数据资源分布出发又指向同一结论我们获得的是已经可供静态分析的代码和数据快照。这也解释了为什么后续章节可以建立在text_code.bin与data_strings.bin上而不必反复启动样本、更不必再次穿过 BF2000 的反调试层。方法有边界别把快照当万能答案ReadProcessMemory 解决的是“观察已经运行的样本”不是自动完成所有脱壳工作。首先时机依然需要证据。太早读取可能拿到尚未还原的页面太晚读取目标可能已二次加密、释放或覆盖内容。其次地址不能照抄。本文的0x401000和0x41E000来自已解析、已验证的映像布局。遇到 ASLR、手工映射或多阶段加载时必须重新定位模块基址和内存页。最后内存快照能帮助静态分析不等于已经得到可直接运行的重建 PE。导入表、重定位、节权限和运行时修补仍然属于另一组问题。小结面对反调试壳完整模拟并不总是最优解。只要目标是获得可分析的后续代码让样本在真实环境中自行完成初始化再以只读方式观察内存往往更直接、更可验证。如果下一次又被壳的异常路径拖住可以先停一下读到内存不等于拿到可信代码常数也不一定连续躺在字节流里。把验证做在前面后面的反汇编会少走很多弯路。下一篇我们就从这两份可信快照出发沿着 MD5、RC4 和 Base64 的痕迹建立算法地图。你在逆向时遇到过“继续模拟不如直接观察”的转折点吗参考文献与引用Microsoft Learn. ReadProcessMemory function (memoryapi.h)函数参数、PROCESS_VM_READ权限与读取失败条件。Microsoft Learn. Structured Exception HandlingWindows 对硬件与软件异常的结构化处理机制。Intel. Intel® 64 and IA-32 Architectures Software Developer Manualsx86 指令编码、异常与调试相关的体系结构参考。觉得有用点个关注持续获取优质内容。

相关新闻

手机里的“数字禁区”:当加密设备成为法律争议的焦点

手机里的“数字禁区”:当加密设备成为法律争议的焦点

🌊 大家好,我是 在水芬芳」。专注 AI 大模型与前沿科技深度解析,习惯从工程师视角拆解技术热点。> 📚 欢迎 点赞、收藏、关注,一起在技术浪潮中保持清醒与好奇 🚀手机里的“数字禁区”:当加密…

2026/10/7 15:12:23 阅读更多 →
USB转串口模块UartSBee V4:嵌入式开发调试与逻辑分析实战指南

USB转串口模块UartSBee V4:嵌入式开发调试与逻辑分析实战指南

1. 项目概述:从串口调试到硬件交互的桥梁如果你玩过单片机、树莓派或者任何需要和电脑“对话”的嵌入式硬件,那你一定绕不开一个东西:串口。这玩意儿就像硬件世界的“普通话”,是调试、烧录、数据交换的基础。但电脑主板上的USB口…

2026/10/1 23:23:11 阅读更多 →
终极英雄联盟工具箱:如何用League Akari快速提升游戏胜率

终极英雄联盟工具箱:如何用League Akari快速提升游戏胜率

终极英雄联盟工具箱:如何用League Akari快速提升游戏胜率 【免费下载链接】League-Toolkit An all-in-one toolkit for LeagueClient. Gathering power 🚀. 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit League Akari是一款基于英…

2026/10/2 2:50:27 阅读更多 →

最新新闻

U-Boot移植实战指南:从启动链解剖到DDR初始化与BSP适配

U-Boot移植实战指南:从启动链解剖到DDR初始化与BSP适配

拿到一块新板子,系统起不来,第一个要解决的不是内核,而是U-Boot。U-Boot移植本质上是把处理器上电之后的前几百毫秒从头到尾搞清楚:ROM里跑完是谁接管、DDR什么时候初始化、存储介质能不能读写、环境变量放哪、最后怎么把控制权交…

2026/10/7 15:13:16 阅读更多 →
驾校学员管理系统源码 Java+SpringBoot+Vue3 前后分离

驾校学员管理系统源码 Java+SpringBoot+Vue3 前后分离

一、关键词驾校学员管理系统,驾校学员信息管理系统,驾校学员培训管理系统二、作品包含源码数据库全套环境和工具资源本地部署教程三、项目技术前端技术:Html、Css、Js、Vue3、Element-plus后端技术:Java、SpringBoot2、MyBatis四、…

2026/10/7 15:13:16 阅读更多 →
【计算机毕设选题】基于Hadoop+Spark的化妆品销售数据分析与可视化系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习

【计算机毕设选题】基于Hadoop+Spark的化妆品销售数据分析与可视化系统源码 毕业设计 选题推荐 毕设选题 数据分析 机器学习

计算机毕设指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流!也可以在主页上或文末下与…

2026/10/7 15:13:16 阅读更多 →
把职位匹配变成AI Agent的超能力:JustHireMe MCP服务器与Agent Skill接入教程

把职位匹配变成AI Agent的超能力:JustHireMe MCP服务器与Agent Skill接入教程

把职位匹配变成AI Agent的超能力:JustHireMe MCP服务器与Agent Skill接入教程 【免费下载链接】JustHireMe Local-first AI job intelligence workbench for scraping roles, ranking fit, and generating tailored application materials. 项目地址: https://git…

2026/10/7 15:13:16 阅读更多 →
商用热水工程IoT监控实战:Modbus+MQTT+InfluxDB+Grafana全链路

商用热水工程IoT监控实战:Modbus+MQTT+InfluxDB+Grafana全链路

1. 商用热水工程为什么必须上IoT监控做过商用热水工程的人都知道,最头疼的不是安装那几天,而是交付之后长达数年的运维期。一栋酒店、一个医院住院部、一个学校宿舍楼,热水系统一旦出问题,投诉电话直接打到工程部,但等…

2026/10/7 15:13:16 阅读更多 →
从零编写 AMD-V Hook 驱动:SVM 最小闭环、拦截与嵌套虚拟化实战

从零编写 AMD-V Hook 驱动:SVM 最小闭环、拦截与嵌套虚拟化实战

简介:这是一份面向Windows内核驱动开发者与虚拟化技术研究者的实战项目源码,聚焦AMD-V硬件虚拟化,从零构建基于SVM指令集的内核驱动,实现硬件级Hook并支持嵌套虚拟化。项目适合具备操作系统架构、内核编程接口与硬件指令集基础的中…

2026/10/7 15:12:15 阅读更多 →

日新闻

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

ROS2机械臂仿真与运动控制:从URDF建模到Gazebo实战全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:01:58 阅读更多 →
用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

用浏览器直接改ESP32的WiFi密码:NVS键值配置工具设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →
芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

芯片封装缺陷检测:扫描声学显微镜(SAT)原理与实操指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 1:02:00 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 14:34:12 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 14:34:13 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 9:29:10 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 14:34:12 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 11:43:46 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 13:34:55 阅读更多 →