OllyDbg实战:逆向破解Pdf2rtf.exe与中文乱码解决方案

OllyDbg实战:逆向破解Pdf2rtf.exe与中文乱码解决方案 1. 项目概述与逆向工程入门逆向工程听起来像是电影里黑客的专属技能其实它离我们并不遥远。很多软件开发者、安全研究员甚至是对某个小工具功能不满的普通用户都可能需要用到它。今天我们就以一个非常具体的实战案例——逆向破解一个名为“Pdf2rtf.exe”的小工具——来手把手带你入门。这个工具的功能如其名就是将PDF文件转换为RTF格式但很可能它存在功能限制、试用期或者恼人的注册弹窗。我们的目标就是使用经典的动态调试工具OllyDbg简称OD结合“吾爱破解”论坛上流传的实战思路去解除这些限制并解决过程中可能遇到的中文乱码问题。为什么选这个案例首先目标明确就是一个单一的Windows可执行文件没有复杂的网络验证或壳保护非常适合新手练手。其次过程中会涉及逆向工程中最核心的几个环节定位关键代码、分析注册算法、修改程序逻辑以及处理字符编码这类实际开发中常见的问题。最后中文乱码的解决方案是很多教程里一笔带过但实际操作中几乎必踩的坑我们会详细拆解。无论你是对软件安全感兴趣想了解程序是如何运行的还是单纯想解锁某个小工具的完整功能这篇内容都能给你提供一条清晰的路径和可复现的操作步骤。2. 逆向环境准备与目标分析工欲善其事必先利其器。在开始动刀之前我们需要搭建一个安全、隔离的调试环境并对我们的目标程序有一个初步的了解。2.1 工具链配置与实验环境搭建首先绝对不要在物理主机或日常使用的工作环境中进行逆向分析。任何调试操作都可能引起程序崩溃或系统不稳定更别说分析对象可能潜在的风险。最稳妥的方案是使用虚拟机。我习惯用VMware或VirtualBox创建一个干净的Windows 7或Windows 10虚拟机安装必要的系统补丁后做一个快照。这样无论我们怎么“折腾”都能一键恢复到干净状态。核心工具是OllyDbgOD。这里我们使用流传较广、插件丰富的“吾爱破解”版OD。它集成了很多实用插件比如强大的反汇编引擎、内存断点管理、字符串搜索增强等能极大提升我们的效率。将OD解压到虚拟机的一个目录即可无需安装。此外我们还需要一个辅助工具PEiD或Exeinfo PE。用于快速检测目标程序是否加壳。加壳的程序会压缩或加密原始代码我们需要先脱壳才能正常分析。幸运的是我们这个“Pdf2rtf.exe”大概率是一个简单的、未加壳的Delphi或VC程序用PEiD一看便知。另一个重要的准备是目标程序本身。你需要先正常运行一次这个Pdf2rtf.exe观察它的行为。是直接弹出注册窗口还是功能受限比如只能转换前3页或者转换后的RTF文件带有水印把这些现象记下来这是我们后续寻找关键代码的“路标”。同时准备一个小的、用于测试的PDF文件最好包含中文文本这样我们才能验证乱码问题是否解决。2.2 目标程序初步行为分析运行Pdf2rtf.exe。假设我们发现未注册版本在转换完成的RTF文件末尾会添加一行“Unregistered Version”的水印并且软件界面上有一个“Register”按钮点击后弹出注册框要求输入用户名和序列号。这个行为告诉我们几个关键信息验证逻辑存在程序内部肯定有一段代码在判断注册状态。验证时机很可能在转换功能的核心函数执行完毕后会调用一个“添加水印”的函数。同时注册按钮的点击事件处理函数里包含了校验用户名和序列号的算法。突破口我们可以从两个方向入手。一是拦截“添加水印”的调用让它失效二是分析注册算法找到一个有效的用户名和序列号或者直接让验证函数永远返回“成功”。对于新手我建议先从让水印失效这个更直观的目标开始。因为它不涉及复杂的算法逆向更容易获得成就感。而注册算法的逆向可以作为进阶练习。注意在进行任何修改前请务必备份原始程序。右键点击程序文件“复制”然后“粘贴”生成一个“Pdf2rtf - 备份.exe”。所有的调试和修改都在副本上进行。3. 使用OD动态调试定位关键代码动态调试是逆向工程的灵魂。我们不是静态地看代码而是让程序跑起来像外科手术一样在它执行的过程中下断点、观察内存和寄存器的变化。3.1 启动调试与字符串检索法将我们备份的“Pdf2rtf.exe”拖入OD窗口OD会加载程序并暂停在入口点Entry Point。这时先不要按F9运行。我们的第一个利器是字符串检索。大部分程序都会把界面文字、提示信息、错误消息等以明文形式存储在内存里。点击OD插件栏的“超级字串参考”或使用快捷键搜索所有可读字符串。在搜索结果中我们寻找与之前观察到的行为相关的字符串。比如“Unregistered Version”、“Register”、“Invalid serial”、“Thank you for registering”等等。很快我们就能找到“Unregistered Version”这个字符串。双击它OD会跳转到反汇编窗口中该字符串被引用的代码行附近。; 假设找到的代码片段类似这样 004015A0 |. 68 84304000 PUSH Pdf2rtf.00403084 ; ASCII Unregistered Version 004015A5 |. E8 10400000 CALL JMP.kernel32.lstrlenA 004015AA |. 83C4 04 ADD ESP,4 004015AD |. 8945 F8 MOV [LOCAL.2],EAX ... ; 后续可能是将字符串写入输出文件的代码这段代码的上面或下面很可能就是一个判断分支。我们需要向上滚动寻找关键跳转JMP, JE, JNE, JZ, JNZ等。3.2 关键跳转分析与断点设置向上看我们可能会发现这样的结构00401570 |. /74 2E JE SHORT Pdf2rtf.004015A0 ; 如果条件满足就跳转到显示水印的代码块 00401572 |. |... ; 这里是注册版本的正常流程不添加水印 ... 004015A0 | \68 84304000 PUSH Pdf2rtf.00403084 ; ASCII Unregistered Version (水印代码开始)JE是“等于则跳转”。它的跳转条件ZF1通常由前面的CMP比较或TEST指令设置。所以我们需要再向上找0040156A |. 83F8 00 CMP EAX,0 ; 比较EAX和0 0040156D |. 74 2E JE SHORT Pdf2rtf.004015A0 ; 如果EAX0则跳转到水印代码逻辑很清晰了程序将某个值存在EAX寄存器里与0比较。如果相等EAX0就跳去添加水印如果不相等就继续执行正常流程。那么这个EAX的值很可能就代表了“是否已注册”的状态标志。0代表未注册1或其他非零值代表已注册。我们的破解思路立刻出现了让这个判断永远不成立即永远不跳转到水印代码。有两种方法修改标志位在CMP EAX,0之后JE跳转之前通过修改ZF标志位来影响跳转。但这种方法每次运行都需要手动干预不实用。修改跳转指令直接修改JE指令让它变为无条件跳转JMP或者相反条件的跳转JNE。更彻底的方法是将JE74改为JMPEB这样无论EAX是什么都会强制跳走但跳去哪我们需要计算偏移。更简单粗暴且常用的方法是NOP填充。NOP是“空操作”指令机器码0x90CPU执行它时什么都不做只是走到下一条指令。我们可以把JE这条指令它占2个字节74 2E用两个NOP90 90替换掉。这样无论比较结果如何程序都会顺序执行跳过添加水印的代码块。操作步骤在OD反汇编窗口中选中74 2E这行代码右键 - “二进制” - “编辑”。在弹出的编辑框中将机器码改为“90 90”然后点击“确定”。你会看到指令变成了两个NOP。至此针对水印的破解就完成了。实操心得在修改代码前最好先在这个JE指令所在行按F2下断点然后F9运行程序并执行一次转换功能。当程序断下时观察右侧寄存器窗口EAX的值确认它确实是0未注册状态。这能验证我们的分析是否正确。4. 注册算法分析与关键Call追踪解决了水印我们来挑战更有趣的部分破解注册机制。这需要我们追踪程序对用户输入的用户名和序列号的校验过程。4.1 定位注册验证函数回到OD的字符串搜索窗口这次我们搜索“Invalid serial”或“注册失败”之类的错误提示。双击找到的字符串来到引用它的代码附近。通常这里会有一个条件跳转在验证失败时跳转到显示错误信息的代码块。00402010 |. E8 8BFAFFFF CALL Pdf2rtf.00401AA0 ; 关键CALL可能是验证函数 00402015 |. 83C4 10 ADD ESP,10 ; 平衡堆栈 00402018 |. 85C0 TEST EAX,EAX ; 测试返回值 0040201A |. 75 1C JNZ SHORT Pdf2rtf.00402038 ; 如果EAX!0验证成功跳转到成功流程 0040201C |. 68 9C304000 PUSH Pdf2rtf.0040309C ; 失败流程显示Invalid serial ...看这里有一个CALL Pdf2rtf.00401AA0。调用CALL之后程序测试返回值EAX。如果非零JNZ就跳向成功否则显示错误信息。这个位于00401AA0的函数极大概率就是核心的注册验证函数。4.2 深入验证函数与算法分析我们在CALL Pdf2rtf.00401AA0这一行按F2下断点然后在程序界面点击注册按钮输入任意用户名和序列号比如“test”和“123456”点击确定。程序会被OD断下。按F7单步步入进入这个CALL。现在我们进入了验证函数的内部。这里的代码会复杂很多可能包含循环、计算、内存比较等。我们的目标是理解它的逻辑。一个典型的简单注册算法可能是获取用户输入的用户名字符串。根据一个固定的算法比如将每个字符的ASCII码相加、相乘或者进行异或操作计算出一个“正确序列号”。将计算出的序列号与用户输入的序列号进行比较。返回比较结果相同返回1不同返回0。在OD中我们需要关注字符串参数在CALL之前通常会用PUSH指令将用户名和序列号的地址压入堆栈。在函数内部会通过[EBP8]、[EBPC]等方式来访问这些参数。循环与计算寻找LOOP指令或者用ECX做计数器的循环结构。观察对输入字符串每个字符的处理。关键比较函数最后往往有一个CMP或TEST指令比较计算出的序列号和输入的序列号然后设置EAX返回值。一个实用的技巧内存断点。如果你发现函数内部调用了lstrcmp或strcmp之类的字符串比较函数可以在比较函数的参数即两个待比较的字符串地址上设置内存访问断点。当程序读取或写入这些内存时OD会中断这样你就能看到是谁准备比较这两个字符串。分析算法的过程可能需要耐心不断F8单步步过跟踪观察寄存器和内存的变化。对于新手如果算法复杂一个更快捷的“爆破”方法是直接修改验证函数的返回值。观察函数结尾00401B2A |. /75 0A JNZ SHORT Pdf2rtf.00401B36 00401B2C |. |B8 01000000 MOV EAX,1 ; 返回1 (成功) 00401B31 |. |E9 05000000 JMP Pdf2rtf.00401B3B 00401B36 | \33C0 XOR EAX,EAX ; 返回0 (失败) 00401B38 |. 40 INC EAX ; 这行可能不存在假设EAX被清0 00401B39 |. 90 NOP 00401B3A |. 90 NOP 00401B3B | 5D POP EBP ; 函数返回 00401B3C \. C3 RETN如果函数通过比较后决定跳转到XOR EAX,EAX将EAX置0表示失败我们可以修改代码让它直接执行MOV EAX,1。或者更简单在函数开头就写入MOV EAX,1和RETN让函数无论输入什么都直接返回成功。注意事项直接修改返回值爆破虽然简单但可能会被程序的其它校验机制检测到例如程序其他地方可能会再次读取注册信息文件。更优雅的方法是分析出算法写出注册机。但对于这个练习我们的首要目标是让程序“认为”自己已注册。5. 中文乱码问题的根源与解决方案在逆向或修改程序时尤其是涉及字符串显示、文件读写的场景中文乱码是一个高频问题。其根源几乎可以归结为字符编码不一致。5.1 乱码产生的常见场景在我们的Pdf2rtf案例中乱码可能出现在两个地方OD界面本身显示乱码当我们在OD中搜索或查看字符串时中文字符显示为问号“?”或乱码方块。这是因为OD默认使用的字体或编码不支持中文。程序运行结果乱码我们破解后程序转换出的RTF文件中的中文内容变成了乱码。这可能是因为程序在读取PDF中的中文文本或向RTF写入文本时编码转换逻辑出了问题。我们的修改比如NOP掉某些代码可能意外影响了一个负责字符集转换的函数。5.2 OD界面中文显示修复对于OD本身显示乱码解决方法很简单。在OD的菜单栏选择“选项” - “界面设置”。在“字体”选项卡中将“反汇编窗口”、“数据窗口”、“信息窗口”等处的字体修改为一种支持中文的等宽字体例如“新宋体”、“宋体”或“SimSun”。修改后OD中引用的中文字符串就能正常显示了这对于我们搜索和分析至关重要。5.3 程序运行时中文乱码分析与处理程序运行结果乱码更复杂一些。我们需要判断乱码是在我们修改之前就存在还是修改之后才出现的。修改前就存在这说明是原程序自身的Bug或者它依赖的某个库如PDF解析库在处理特定编码的PDF时有问题。这种情况下我们的逆向目标可以增加一项修复这个乱码Bug。修改后才出现这极有可能是我们误伤了“友军”。回想一下我们NOP掉的那条JE指令是否在一个更大的函数里这个函数除了判断注册状态是否还初始化了某些全局变量比如一个指向“代码页”或“字符集”的指针排查步骤恢复代码首先将我们之前修改的90 90恢复为原来的74 2E编辑二进制改回去。然后运行程序测试转换功能看中文是否正常。如果正常说明乱码确实是我们修改引起的。上下文分析仔细查看JE指令所在函数的所有代码。关注函数开头是否有类似Call JMP.kernel32.GetACP获取系统活动代码页或Call JMP.kernel32.MultiByteToWideChar多字节转宽字符的调用。这些调用负责字符编码处理。定位编码相关代码在OD中搜索字符串“CP936”、“GBK”、“UTF-8”或“MultiByteToWideChar”、“WideCharToMultiByte”等API函数名。找到调用这些函数的地方下断点分析。对比调试如果怀疑某个函数Call XXXX影响了编码可以尝试在JE跳转的两个分支注册版路径和未注册版路径都设置断点然后分别运行。观察在走不同分支时那个可疑的Call XXXX的参数和返回值是否有差异。也许未注册版本我们NOP后强制走的路径会错误地调用一个默认的、不支持中文的编码设置。解决方案如果找到了导致乱码的关键CALL或赋值语句并且确认是我们修改导致程序走了错误的逻辑分支那么我们需要更精细地修改。不是简单地NOP掉JE而是修改条件或者直接修改那个导致乱码的赋值语句。例如如果发现未注册分支会将一个全局变量设为0代表ASCII而注册分支设为936代表GBK那么我们可以直接找到给这个变量赋值为0的指令将其改为936。实操心得处理乱码问题最需要的是耐心和对比。创建一个“修改前”和“修改后”的程序行为对比表记录下寄存器、关键内存地址值的变化。善用OD的“运行跟踪”功能记录下程序执行的所有指令然后对比两次运行的跟踪日志差异点往往就是问题的根源。6. 补丁制作与程序保存当我们完成了代码修改去水印、破解注册验证、修复乱码最后一步就是将修改永久地应用到程序文件上。6.1 修改有效性验证在OD中不要关闭程序。直接在OD里点击目标程序的界面进行功能测试尝试转换一个带中文的PDF查看生成的RTF文件是否无水印、无乱码。点击注册按钮输入任意信息查看是否提示注册成功或不再提示失败。如果一切正常说明我们的修改在内存中是成功的。6.2 生成补丁或保存文件OD提供了两种方式将内存中的修改保存到磁盘文件上直接复制到可执行文件在反汇编窗口右键 - “复制到可执行文件” - “所有修改”。在弹出的新窗口中会列出所有我们更改过的代码。再次右键 - “保存文件”给文件起一个新名字如“Pdf2rtf_patched.exe”并保存。生成补丁如果你只想分享你的修改部分或者想制作一个一键破解补丁可以使用OD的“补丁”功能。右键 - “复制到可执行文件” - “所有修改”在新窗口右键 - “补丁” - “保存补丁文件”。这会生成一个.rar或.exe格式的补丁程序运行它可以将修改应用到原始程序上。至关重要的一步校验。保存新文件后务必关闭OD然后直接运行新生成的“Pdf2rtf_patched.exe”进行完整的、独立的测试。因为OD运行时有些代码或数据可能还驻留在内存中关闭OD后运行才是程序真实的状态。6.3 常见问题与排查技巧实录即使按照步骤操作你也可能会遇到一些“坑”。这里记录几个常见问题及解决思路问题1OD附加进程失败或一运行程序就崩溃。可能原因程序有反调试技术。简单的反调试会检测IsDebuggerPresentOD的插件或设置可能没有绕过。解决使用“吾爱破解”版OD自带的反反调试插件如HideOD、PhantOm在OD插件菜单中启用它们。也可以尝试换用x64dbg等更现代的工具。问题2修改代码后保存文件时提示“访问被拒绝”或文件大小不变。可能原因原文件可能被系统或杀毒软件锁定或者OD没有以管理员权限运行。解决关闭所有可能占用该程序的进程包括资源管理器预览以管理员身份运行OD再执行保存操作。也可以先将文件复制到桌面等有完全控制权的目录进行操作。问题3破解后程序主界面仍然显示“未注册”字样。可能原因程序可能存在多处验证。我们只破解了水印添加和注册验证函数但主界面标题或状态栏的文字是在程序启动时从配置文件或注册表读取另一个标志位来显示的。解决搜索字符串“未注册”找到所有引用它的地方分析其显示条件进行相应修改。或者直接搜索“已注册”字符串找到设置这个状态的代码让程序启动时就执行那段代码。问题4跟踪注册算法时计算过程非常复杂涉及大量API和循环看不懂。策略调整如果目标是“能用就行”可以放弃分析算法转而寻找一个更上层的验证结果判断点进行“爆破”。例如找到调用验证函数的那个地方直接修改调用后的跳转JNZ改为JMP或者把验证函数开头改成MOV EAX,1; RETN。借助工具可以尝试使用专门的注册机编写工具如Keymake来辅助分析。或者如果算法是标准的密码学算法如MD5、SHA1、RSA可以通过其特有的常数如初始化向量来识别。逆向工程是一门实践性极强的技能。这个Pdf2rtf的案例涵盖了一个简单软件破解的完整流程环境准备、行为分析、字符串定位、关键跳转修改、注册算法追踪、乱码问题排查以及最终补丁制作。每一个步骤都需要细心观察和大胆假设、小心验证。真正的熟练来自于反复练习和解决一个又一个具体的问题。当你成功绕过第一个软件的验证时那种透过机器码窥见程序逻辑的成就感会驱动你继续探索下去。记住所有的练习都应在合法合规的前提下用于学习与研究目的。