VB 调试之分析 N-Code(二)

之前分析过这个 CrackMe(参见:VB 调试之分析 N-Code(一)),需要借助修改版的 OD 才能去除 NAG 窗口,在阅读逆向工程核心原理一书后,学到了一种新的技巧,记录下来;

使用工具

  • OllyDbg 1.10原版,简称OD
  • OD 汉化插件均来自互联网;
  • CrackMe来自互联网,仅供学习使用;
  • 文中特殊数字均是HEX,为了书写方便采用DEC

分析思路

  1. CrackMe倒入OD,查找rtcMsgBox并设置断点:

    查找
    设置断点

  2. 然后F9运行程序,程序会中断在调用位置,与直接给 API 设置的断点不同,这样设置的断点会中断在调用位置,而给 API 设置的断点会中断在rtcMsgBox函数的行首:

    调用位置

  3. 向上滚动代码,找到函数栈帧,然后设置断点:

    至于什么是栈帧?

    ESP 始终指向栈的顶端,程序运行中,ESP 寄存器的值随时变化,访问栈中函数的局部变量、参数时,若以 ESP 的值为基准会十分困难,使 CPU 很难引用到准确的位置;

    所以,调用函数时,先要把作为基准点(函数起始地址)的 ESP 值保存到 EBP,并维持在函数内部,这样,无论 ESP 如何变化,以 EBP 的值为基准总是能够安全的访问到相关函数的局部变量、参数、返回地址,这就是栈帧;

    也就是说,ESP 寄存器承担着栈顶指针的作用,而 EBP 寄存器则负责行使栈帧指针的职能;

    函数栈帧

  4. 取消其它断点,重载并运行程序,程序会在断点处中断:

    断点处中断

    然后在堆栈窗口选择反汇编窗口中跟随到调用位置;

  5. 给调用位置和返回位置设置F2断点并取消其它断点:

    设置两个断点

  6. 重载并运行程序,程序中断在调用位置设置的断点处:

    调用位置栈顶指针

    记录调用位置栈顶指针的地址;

  7. F9运行程序,NAG 窗口弹出,点击确定,程序中断在函数的返回位置:

    返回位置栈顶指针

    记录返回位置栈顶指针的地址;

  8. 用返回位置的栈顶指针也就是 ESP 的值减去调用位置 ESP 的值;

    ESP相减

    以图为例:0012FA10 - 0012FA08 = 8(16 进制),是不是可以这么理解:在调用函数中使用了 8 个字节的栈内存;

  9. Win32 API 使用的函数调用约定是 stdcall 方式,这种方式的栈内存由被调用者清理,在这里就是由被调用的函数清理,如果在函数的起始位置直接清理栈内存,函数就不会被执行,也就不会弹出 NAG 窗口了;

    函数调用约定参见:寄存器小记

  10. 重载并运行程序,程序中断在调用位置,F7单步步入到函数内部:

    再次中断

    然后来到了 VB 程序类似于交通枢纽的位置,继续F7,跳转到创建 NAG 窗口的函数内部;

    交通枢纽

    创建 NAG 窗口的函数:

    创建 NAG 窗口的函数

    咦,好眼熟啊,是的,这里就是设置程序第 1 个断点的函数,调用rtcMsgBox的函数;

  11. 之前的步骤终于派上用场了,根据计算,调用创建 NAG 窗口的函数会使用 8 个字节的栈内存,所以,修改函数的行首,清理栈内存:

    清理栈内存

    然后运行程序,没有 NAG 窗口,直接弹出程序主窗体;

  12. 保存修改到可执行文件,然后运行程序:

    运行程序

  13. 将修改后的 CrackMe 倒入OD,开始处理序列号的问题,然后发现,给rtcMsgBox设置断点后,程序并不会中断,怎么办?

    既然程序在输入错误的序列号后会弹出错误弹窗,那么程序内部肯定验证了序列号,那就查找关于字符串比较的函数:

    vbastrcmp

    找到__vbaStrCmp并设置断点,然后F9运行程序;

  14. 程序中断在调用__vbaStrCmp的位置,然后,在 EAX 中发现了输入的随机序列号:

    发现序列号

    既然 EAX 是第 2 参数,那么 ECX 中存放的就是第 1 参数,可能就是真正的序列号;

  15. 数据窗口中跟随 ECX 后发现,西欧文字系统无法显示,就是一堆乱码,既然真实注册码无法使用,那有没有可能让任意字符成为注册码,继续向下分析:

    返回非0

    首先,向下执行一行代码后,EAX 为非 0 值,这里使用的是__vbaStrCmp,所以非 0 说明不相等;

    操作EDI

    接着,把返回值拷贝到 EDI 中,进行了一系列操作后,来到了一个跳转,这就很可疑了;

    这里对 EDI 进行的一系列操作,如果 EDI 为 0,则没有任何影响,最后的结果是跳转实现,但随机注册码和指定字符串比较,结果肯定不为 0,最终的结果是跳转失败;

    按照这个逻辑来推断,这个跳转应该是关键跳转,如何证明呢?修改JEJMP,然后运行程序:

    正确弹窗

    修改JEJMP并运行程序后,弹出提示序列号正确的弹窗,至于为什么这是正确的弹窗,很简单,因为它和错误弹窗不一样;

  16. 保存修改到可执行程序,然后运行程序:

    成功了

    输入任意字符,点击 OK 后,弹出成功弹窗,结束!