VB 调试之 P-Code 替换操作码
使用工具
- OllyDbg 1.10原版,简称
OD; OD汉化和插件均来自互联网;- CrackMe 和 opcodes.txt来自互联网,仅供学习使用;
- 文中特殊数字均是
HEX,为了书写方便采用DEC;
分析思路
运行程序,程序会先弹出一个 NAG 窗口,点击确定后弹出主窗体:
![运行程序]()
导入
OD开始分析,首先按照常规思路,既然有弹窗,肯定会使用 API:rtcMsgBox:![查找API]()
查看 API 列表,找到
rtcMsgBox设置断点,然后运行程序;程序中断后,在栈窗口
反汇编窗口跟随到返回位置:![反汇编窗口跟随]()
在调用位置设置断点,然后重载并运行程序;
![在调用位置设置断点]()
调用指令是
CALL EAX,当前 EAX 值为 401000,Ctrl + G跳转到 401000,正是rtcMsgBox:![401000]()
按下
-减号键,回到断点位置,既然是调用函数,CALL 上面的指令就是它的参数,这种传参方式在 P-Code 中很常见,还没有完,继续;![CALL的参数]()
CALL 的参数到底是什么呢?
根据上面的代码逻辑,获取一个操作码后无条件跳转了,所以,
JMP以上的代码与 CALL 无关;因此,CALL 的参数应该是
JMP向下,直到CALl;给参数起始位置设置断点,因为中间包含
JE跳转,为了防止跳转,同时给JE也设置断点,重载并运行程序;这里的戏就很精彩了,程序中断在参数位置:
![程序中断在参数位置]()
然后按下
Ctrl + A分析程序,在信息窗口发现跳转至此处的位置太多,如果挨个儿去跟,那就相当精彩了;同时,EAX 中有一个操作码
OA,为啥它是操作码?因为根据 P-Code 的惯例,接下来既然要获取参数了,且代码没有执行,那 EAX 中存放的就是上一步的结果,而 P-Code 的逻辑就是先获取操作码,紧接着就是获取参数,那就说明,接下来获取的参数既是 CALL 的参数,也是OA操作码的参数,So,OA肯定是一个操作码;数据窗口跟随 ESI,发现
OA位于当前 ESI 之前,地址是:401AD2;![数据窗口跟随 ESI]()
既然接下来要执行 CALL 也就是
rtcMsgBox函数了,那么OA在 P-Code 中是否就是rtcMsgBox呢?继续运行程序,直到将要获取下一个操作码:
![将要获取下一个操作码]()
标志性的获取操作码的语句,接下来要获取的操作码是
36,同时也反向说明,在这行代码之前,OA操作码是一直有效的;而且,
OA的参数是两个:![OA 的参数是两个]()
因为操作了两次 ESI,同时,位于
OA和36之间的数据被分别放入了 ECX 和 EDI;大胆的猜测一下,如果
OA就是rtcMsgBox,那么替换它是否就能跳过弹窗呢?重载程序,在数据窗口 goto 到
OA,也就是 401AD2:![goto 到 OA]()
打开
opcode.txt,发现07与OA参数数量和参数大小都相同,且07没有副作用:![opcode.txt]()
如上图所示:
OA表示调用指针(ptr)指向的函数,且没有返回值,4 字节大小,有 2 个参数,参数一 2 字节,参数二 2 字节(4 2 2 2);07表示 PUSH 值到堆栈,4 字节大小,有 2 个参数,参数一 2 字节,参数二 2 字节(4 2 2 2);
这里不用
0B替换OA的原因是:CALL 的副作用要比 PUSH 大的多;所以,替换
0A为07:![替换 OA]()
需要注意的是:替换操作码时,两个操作码的参数数量和参数大小必须完全相同,且替换的操作码对程序的运行没有副作用;
然后运行程序:
![然后运行程序]()
没有出现 NAG 窗口,直接显示程序主窗体;
保存修改到可执行文件,运行程序:
![保存修改到可执行文件]()
修改依然没有问题,完工!














