160 CrackMe abexcm5逆向实战:SMC自解密与反调试绕过

发布时间:2026/10/9 8:21:55
160 CrackMe abexcm5逆向实战:SMC自解密与反调试绕过 最近在啃 160 CrackMe 合集的朋友大概率会在 002 号 abexcm5 上多停留一阵。这个文件名看起来不起眼整个程序也就一两 KB界面上只有一个输入框和一个 Check 按钮但真正动手做逆向分析时你会发现自己撞上了自修改代码、反调试、内存动态解密这几个逆向基本功里最典型的硬茬。这篇文章我就拿 abexcm5 作为案例完整走一遍从运行观察、静态侦察、动态调试、绕过反调试、到最终还原算法并写出注册机的全过程。适合刚接触逆向分析的新手也适合被 SMC 自解密卡住、想对比一下别人处理思路的老朋友把这个 002 号吃透后面再遇到带自解密特征的程序会顺手很多。1. 初见 abexcm5这个经典小样本的玩法与价值1.1 它是什么新 160 CrackMe 是逆向圈流传很广的一套训练样本集从 001 到 160每个样本由不同作者编写侧重点各不相同。002 号 abexcm5 是其中非常有代表性的一枚作者是 abex采用汇编语言编写体积通常只有 1KB 出头。它运行时弹出一个普通窗口包含一个用于输入的编辑框、一个 Check 按钮点击之后会给出成功或失败的弹窗提示。表面上看这就是个小玩具但它的设计含量很高。这个样本把几样在破解实战里经常遇见的技巧压缩在了一起代码段自解密SMCSelf-Modifying Code、基于 GetTickCount 的时间反调试、以及把真正的验证逻辑放在运行时解密出的代码块中。正因为如此它在新人训练列表里的地位一直很高。直接去看别人的 writeup 花不了几分钟但自己完整分析一遍获得的收获完全不是一个量级。1.2 逆向分析的目标分三层我开始动手前先把目标拆成三层这样后面不会迷失方向第一层找到一组能让程序弹窗提示“成功”的字符串。这个目标最直接很多帖子讲到这里就结束了。第二层还原验证算法。搞清楚程序拿我的输入做了什么运算、和什么内存数据比较而不是靠猜。第三层吃透这个样本的自我保护机制理解 SMC 解密流程和反调试原理并总结出一套可复用的分析路径。我的看法是如果只为了找一个密码直接在 MessageBoxA 上下断点回溯调用可能十分钟就能搞定。但那样浪费了 abexcm5 的教学价值。下面我不会把“最终密码”放在开头而是按完整分析流程来每一步都说明为什么这么做这样你读完拿到的不只是答案更是一套可迁移的方法。2. 环境准备与静态侦察先摸清对手底细2.1 分析环境与工具链动手之前先搭好环境。我用的是 Windows 7 32 位虚拟机原因很朴素abexcm5 是 32 位 PEWin7 的兼容性很好而且虚拟机里做调试不用怕样本把系统搞坏。调试器选择 x64dbg 的 32 位版本也就是 x32dbg它比 OllyDbg 更新对现代硬件和断点机制的调教也好一些。静态反汇编用 IDA Free查壳和编译特征用 DIE也就是 Detect It Easy另外备一个 010 Editor 或 HxD后面抠内存数据时会用到。工具不需要多关键是清楚每一样负责什么。DIE 管快速识别壳和编译器IDA 管静态代码地图x32dbg 管动态跟踪和内存操作010 Editor 管精确提取内存快照。对付 abexcm5 这种小样本这套组合已经非常充裕。我见过一些新手上来就装一堆重型插件结果光是插件配置就把自己绕晕了反而是简单工具更容易聚焦问题本身。2.2 静态侦察DIE、字符串与入口点先把样本放到虚拟机里运行一遍确认它确实只做两件事输入、Check、弹窗。然后关掉程序用 DIE 打开 abexcm5.exe。一般会得到这样的信息32 位 PE、无壳、编译器特征指向汇编工具链或者没有明显特征。这个结果本身就很有价值它告诉你不用在脱壳上浪费时间也提醒你体积小是因为代码都是直接用汇编写的。接着用 DIE 的字符串功能快速扫一眼看看能不能找到 “Good”、“Bad” 之类的明文提示。abexcm5 里一般不会留太明显的明文线索不是靠字符串搜索就能过关的。真正值得关注的是导入表。我在导入表里能看到 GetWindowTextA 或 GetDlgItemTextA 这类读取输入的 API同时也能看到 GetTickCount。这里插一句判断逻辑一个只有输入框和按钮的小程序既没有计时需求也没有动画逻辑为什么需要 GetTickCount答案只有一个做时间差反调试。程序在关键代码执行前后调用 GetTickCount 并比较差值如果差值过大就认为有人在使用调试器单步跟踪从而跳进干扰分支。这个信息在后面制定调试策略时非常关键。2.3 入口点代码里的 SMC 信号用 IDA Free 加载样本直接看入口点代码。先别急着逐条读懂而是像看简历一样扫一遍整体结构。正常编译的小程序入口点通常是一堆 push、call、跳转到对话框资源逻辑的流水账。但 abexcm5 的入口点附近会出现类似这样的片段mov ecx, 30h ; 循环次数 lea edi, [00401160] ; 目标地址位于代码段内部 mov al, 5Ah ; 解密密钥 XOR byte ptr [edi], al inc edi loop 上一行 jmp 00401160含义非常直白从 00401160 开始用 0x5A 把内存里的字节逐一异或然后跳过去执行。汇编程序通常不会在运行时修改自己的指令字节一旦出现对代码段内存的写操作基本就是在做 SMC。磁盘上原来的字节是加密后的密文程序运行时在内存里现场解密成真正指令再跳入执行。静态分析工具看到的是密文真正逻辑必须等解密后才能看到。这里有个可以沉淀下来的静态识别技巧当 IDA 里看到mov al, 立即数配XOR [edi], al再加loop的连续结构基本可以高概率标为“疑似解密循环”如果后面还跟着一个 jmp 跳进代码段内部那 SMC 基本实锤了。这个套路在后面分析花指令、简单壳时经常能用到。3. 动态调试实战让自解密代码现出原形3.1 SMC 和反调试是怎么构造出“迷宫”的自修改代码的原理并不复杂程序在运行过程中修改自己的指令字节。用生活里的例子说就像你收到一封密信必须先用约定的字典翻译一遍才能看到真正的内容。对逆向者来说SMC 的麻烦在于静态分析只能看到密文反汇编出来的指令无意义而用调试器去跟踪时又容易触发反调试导致程序跑进错误分支。abexcm5 的高明之处是把验证逻辑整体放在解密代码块里。只要不等待解密完成就去读内存看到的永远是些看起来毫无意义的字节如果不小心触发了反调试程序会直接退出、死循环或者误导到假路径。所以它的难点不是算术有多复杂而是你要想办法安全地走到“解密之后、执行之前”那个窗口期。我始终觉得逆向里最难的不是算一个算法而是找到正确的观察位置。SMC 样本教会你的第一件事就是不要一头扎进单步调试里先搞清楚对方的防御机制再决定在哪里停下。3.2 断点策略API 断点优先硬件断点兜底我在实际调试 abexcm5 时没有从入口点一路 F8 单步。原因很简单入口附近的 GetTickCount 时间检测会对单步操作极其敏感。如果两条关键指令之间的时间差异常程序就跳到干扰路径。正确策略是让程序先自然运行然后在读取输入的系统 API 上下断点把程序拉到离验证逻辑更近的位置。具体操作是这样的在 x32dbg 中打开符号窗口找到 GetWindowTextA按 F2 下断。然后在程序的输入框里随便输入一串假码点 Check程序调用这个 API 时就会断下。此时根据函数参数可以定位到输入缓冲区地址在数据窗口里能看到自己输入的字符。如果 GetWindowTextA 没断下来就换 GetDlgItemTextA或者对两个都下断覆盖不同控件的读取方式。还有一个重要的坑要提醒有些样本会扫描自己代码里的 0xCC也就是软件断点如果发现就判定为在被调试。软件断点是通过改写指令字节实现的所以容易被这类校验发现。遇到这种情况就需要改用硬件断点。硬件断点由 CPU 的调试寄存器触发不会修改指令字节抗检测能力强很多。在 x32dbg 里选中目标行右键 - 断点 - 硬件断点 - 硬件执行断点即可。abexcm5 有的版本恰好就吃这一套。另外建议配合 ScyllaHide 插件。ScyllaHide 通过 hook 系统 API 来隐藏调试器特征能屏蔽 PEB 里的 BeingDebugged 标志也能处理 NtQueryInformationProcess 这类常见的反调试查询。默认配置下对付 abexcm5 已经足够不需要追求全选所有隐藏选项隐藏太多反而可能让程序逻辑异常。3.3 找出解密循环并接管内存现场当你成功在 GetWindowTextA 断下之后别急着在栈窗口里查数据然后直接跑掉。这时应该打开调用栈回溯到调用位置因为你很快就会接近核心逻辑。实际操作时我跟着调用链一路走了没多远就看到了入口附近那段解密循环。这时还有一个细节解密循环执行前程序可能会先跳到一个静态解析下完全无意义的区域里面全是垃圾指令。遇到这种情况千万不要慌更不要试图手动去“读懂”那些字节。正确做法是回到解密循环起点记录 edi 指向的起始地址、循环长度、异或密钥。在我分析的这份样本里循环长度是 0x30 字节密钥是 0x5A。不同来源的变体可能在长度和密钥上有差异但结构都是一样的。关于效率这里有个很好的操作习惯不要在XOR [edi], al上下断点然后手动按几十次 F9 去跑完循环那样既慢又有风险。正确做法是把断点下在解密循环后面那个jmp 00401160上然后只按一次 F9。循环会一口气执行完断点命中时目标区域已经被解密成真实指令。然后把目光移到数据窗口找到刚才 edi 指向的区域选中后右键选择“二进制 - 复制到可执行文件”把这一块内存保存成 dump.exe。接着把这个 dump 文件拖进 IDA 重新反汇编你会看到之前乱码的位置全部变成了干净、可读的 x86 指令。这一步就是 SMC 样本分析的标准流程先在内存里抓到明文代码再离线慢慢分析。4. 算法还原与注册机实现4.1 从输入 API 一路追到比较指令拿到 dump 文件之后用 IDA 继续分析。我在代码中看到GetWindowTextA 返回后输入字符串被保存到栈缓冲区随后一个指针被加载到 esi另一个指针指向数据段某地址并加载到 edi。这是一个非常典型的逐字符循环结构esi 指向输入edi 指向内存中预先存放的期望数据。继续往下翻出现类似这样的指令序列movzx eax, byte ptr [esi] add al, 03h cmp al, byte ptr [edi] jnz short Fail inc esi inc edi loop MainLoop这段反汇编的语义是取输入字符加 3然后与内存字节比较不相等就跳到失败分支相等则处理下一个字符。循环次数是 4也就是说程序只会验证前 4 个字符。这里很容易犯一个直觉性错误看到cmp就想把输入改成和比较对象一致。实际上比较的是“变换后的输入”与“内存常量”直接把输入改成那个常量是过不去的必须先把变换逻辑反推回来。这就是“还原算法”比“找到一条比较指令”更有意义的原因。只有完整理解了对输入做过的每一步变换才知道真正的注册码应该长什么样。4.2 内存快照与参数提取现在去数据段找到 edi 指向的内存地址。在我这个版本里数据窗口显示的是四个十六进制字节64 65 66 67对应 ASCII 字符为“defg”。结合前面的算法整个验证体系可以写成这样程序对输入的每个字符执行字符 3的变换变换后的结果必须等于内存里的对应字节也就是输入字符 3 内存字节反过来推导注册码字符 内存字节 - 3。实际算一遍0x64-3 0x61对应字母 a0x65-30x62对应 b0x66-30x63对应 c0x67-30x64对应 d。所以能让程序提示成功的输入是abcd。这个结果虽然简单但它是通过完整还原算法得到的而不是靠试错试出来的。如果你拿到的样本算法被改过比如变换指令不是add 3而是xor 某个常量或者期望数据存放在不同的段分析路径依然不变。核心就两条第一找到对输入逐字节做了什么的指令第二把内存中参与比较的期望值取出来。剩下的只是把运算反过来。4.3 注册机用十几行代码完成验证闭环算法还原完毕之后写注册机只是顺手的事。这里用 Python 做验证代码很短# 从内存数据区提取到的期望值 expected [0x64, 0x65, 0x66, 0x67] # 验证逻辑是 input_char 3 expected # 所以注册码 expected - 3 serial .join(chr(b - 3) for b in expected) print(f注册码: {serial})运行结果输出abcd。把它填回程序点 Check弹窗提示成功整个逆向分析闭环完成。有人可能会问就这么简单的字符串至于绕这么大圈子吗但请注意如果你一开始就看到了最终密码并不会理解为什么是这个密码。写注册机这个动作其实是把你还原出的算法又正向编码了一遍双重验证确保没有理解偏差。如果以后遇到更复杂的样本例如带累加校验、查表替换、滚动码注册机的核心思想仍然不变把所有对输入做的变换按相反顺序实现一次期望值从内存里动态读取即可。注册机用 C 语言写、用汇编写、用 Python 写都不重要重要的是它忠实反映了逆向得到的算法。这一套能力在分析授权验证、序列号保护之类的场景中是通用的基本功。5. 经验总结与常见误区5.1 常见问题与排查速查表这个样本我反复带人做过好多遍也见过很多人在相似的坑里栽跟头。我整理成一张速查表方便你对照排查。现象可能原因解决办法一单步就跳飞或者程序崩溃踩中 GetTickCount 反调试不要入口处一路 F8先运行到 API 断点再分析必要时开 ScyllaHide断点无效或行为异常软件断点被 SMC 覆盖或检测到 0xCC改用硬件执行断点静态反汇编全是乱码还没走到解密环节在解密循环后的 jmp 下断跑完循环后 dump 内存再分析输入框 API 断点没有命中读取函数不是当前猜的那个换 GetDlgItemTextA / GetWindowTextA或使用内存访问断点找到了正确输入却提示失败程序可能缓存了输入长度校验检查循环次数和长度判断条件确认是否存在二次校验虚拟机里运行异常系统兼容性问题换 Windows 7 32 位或 XP 虚拟机关闭图形加速等选项老实说很多人看 writeup 觉得 abexcm5 很简单但自己动手时往往卡在 dump 内存这个环节。一是不知道要在解密完的瞬间保存内存二是不知道如何把 dump 出来的内容导出给 IDA。所以这篇文章里我把这个操作重复强调了几次。它比单纯找到注册码重要得多因为在自动脱壳、分析加密数据、还原被混淆代码的工作里内存 dump 都是绕不开的一步。5.2 从 abexcm5 延伸到通用分析套路把 abexcm5 完整刷完一遍之后可以沉淀出五步方法论先运行观察行为。目标不主动自杀就先让它自然跑起来通过弹窗、输入框等交互点暴露它的接口。再静态摸底。查壳、查编译器、看导入表从 GetTickCount 这类异常导入往反调试方向想。沿输入 API 切入核心区域。通过 GetWindowTextA、GetDlgItemTextA 这类可控交互点把程序拉到验证代码附近。遇 SMC 就 dump。不要试图静态读懂密文先把解密后的真实指令从内存里提取出来再分析。以注册机收尾。把还原出的算法反向编码成程序验证你确实看懂了整个验证流程。这套流程并不只适用于 abexcm5。以后你只要碰到运行时有自解密特征的程序先问自己三件事解密循环在哪、密钥和长度是多少、解密后的真实代码怎么导出来。把这三个问题解决后面往往就是普通的指令阅读而已。另外说一个调试器使用习惯上的建议。遇到带反调试的样本尽量不要在关键路径上持续单步而应该多利用条件断点、日志断点、硬件断点让程序环境尽量自然。人肉 F8 是基本功但知道什么时候不该按 F8才是真正能节省时间的进阶经验。我在分析 abexcm5 时正是因为先在 API 上断开让程序自己跑完解密循环才避开了那一堆反调试陷阱。这个思路放到实战样本里同样成立。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询