VB 调试之修改系统 DLL

使用工具

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

分析思路

  1. 首先运行软件,了解一下程序:

    运行程序

    运行程序后,首先弹出一个 NAG 窗口,点击 Register 后弹出程序主窗口,随机输入注册码并点击 OK 后,弹出错误弹窗;

  2. 寻找注册码

    了解程序逻辑后,接下来需要解决注册码,因为机器码为自动生成,所以对于本机来说,这是一个硬编码,那就非常简单了;

    • 所见即所得,既然有弹窗,说明程序使用了rtcMsgBox

    • 随机输入后弹出错误弹窗,说明程序进行了字符串比较;

    • 将程序倒入OD后,前两行代码说明了这是一个 VB 程序(这不是废话嘛,看文章标题就知道了)

      前两行

      VB 程序有个特点:入口处都是一个 PUSH 指令,然后一个 CALL 指令;而且这里的 CALL 指令中的 JMP 后面跟着 MSVBVM60,也间接说明了这是 VB 程序;

    • 接着,按下Ctrl + N查看函数列表,确定一下字符串比较使用的 API:

      函数列表

      在其中发现了__vbaStrCmp,程序应该是用这个函数判断真假注册码的;

    • 既然知道了使用的 API,那就给这两个 API 设置断点,并做好备注,然后运行程序:

      断点禁用再启用

      运行程序后发现,由于__vbaStrCmp比较常用,多次中断在无关的位置,所以,暂时先禁用这个断点,等输入注册码后再启用,然后点击 OK;

    • 程序中断两次,根据堆栈窗口的参数判断,第 1 次是机器码比较,第 2 次才是注册码比较,然后反汇编窗口跟随来到调用位置,在调用函数的参数上设置断点:

      反汇编跟随

    • 禁用__vbaStrCmp断点,重载并运行程序后,程序中断在预期位置:

      重载并运行

      但是通过查看参数,发现这里不是理想的位置,应该是用于判断是否有输入;

      既然这里不是需要的位置,那就清除此处的断点,启用__vbaStrCmp断点,继续跟踪,不过,在跟踪多次之后,还是遇到了刚开始时候的问题:由于__vbaStrCmp比较常用,会多次中断在无关的位置,那就放弃__vbaStrCmp吧,不是还有rtcMsgBox吗?

    • 禁用__vbaStrCmp断点,重载并运行程序后,程序中断在rtcMsgBox的行首,反汇编窗口中跟随,来到调用位置,因为接下来就要弹窗了,所以字符串比较肯定在弹窗之前,往上翻阅代码,看看有没有__vbaStrCmp,别说,还真有,那还等什么,给__vbaStrCmp的参数设置断点,重载并运行程序:

      收获

      单步执行程序后,收获颇丰,这里的第 2 参数不再是空,那这里就相当可疑了;

    • 既然拿到了可疑的字符串,当然要试一试:

      满意

      嗯,这个弹窗甚是满意,这串字符就是本机机器码对应的真实注册码;

  3. 4C 法移除 NAG 窗口

    拿到注册码就结束了?当然不是,不觉得 NAG 窗口很烦人吗?那就去除它!

    首先,第 1 想法就是4C法,简单粗暴,do it!

    • 程序倒入OD后,不要运行,goto到入口 PUSH 指令中的地址加上 4C 后的地址:

      偏移后的地址

    • 然后在数据窗口,继续跟踪当前地址中存储的另一个DWORD地址:

      跟随 DWORD

    • 跟随DWORD来到数据窗口:

      结果类似的代码块

      这里有两个结构类似的数据块,每块 50(十六进制)个字节的长度,每块数据的第 24(十六进制)个字节处都有一个标志(第一个是 00,第二个是 04);

      而这个标志指定了代码块(也就是程序启动后要加载的窗体)出现的顺序,先加载 00,再加载 04;

      大胆的猜测一下,由于 00 先加载,那 00 可能就是 NAG 窗口,而 04 则是主窗口,至于缺失的 01, 02, 03,可能是Help WrongOkey,因为它们都是子窗口,所以就不在这里吧;

    • 所以,为了去掉 NAG 窗口,将此处的两个标志的值互换,即:00 改 04,04 改 00,这样, NAG 窗口就永远也没机会出现了,最后将修改保存到文件:

      互换值

    • 最后,运行保存的文件:

      运行

      没有出现 NAG 窗口,而是直接出现程序主窗体,接着,输入注册码,弹出 OK 弹窗;

    • 以为这样就结束了?NO NO NO,4C 法并不是今天的重点,今天的重点是:修改系统 DLL;

  4. 修改系统 DLL 移除 NAG 窗口

    • 首先,要用到修改版的OD,将程序倒入OD

      内存访问断点

      内存窗口代码段设置内存访问断点,然后运行程序;

    • 多次运行程序后,来到程序的总分支,给所有跳转设置断点,了解它们的功能:

      总分支

      这好像是 VB 程序的一个特点,有一个类似交通枢纽的地方控制着程序的走向;

    • 设置好断点后,重载并运行程序,程序会在设置断点的JMP位置依次中断,跟踪分析:

      分析JMP

      前三个已经满足了需求,至于其它几个,who cares?

    • 既然已经知道了创建主窗体的跳转,去除 NAG 窗体还不简单,把创建 NAG 窗体的跳转改为创建主窗体的跳转不就结束战斗了?值得一试:

      修改 NAG

      复制创建主窗体的跳转并覆盖创建 NAG 窗体的跳转,然后保存修改到文件;

    • 运行保存的程序:

      运行程序

      呃呃,并不是预期的结果,NAG 窗口还是出现了,不过,现在不用点击 Register,程序主窗体也会出现,也就是运行程序后,NAG 窗口和程序主窗体同时显示;

    • 既然这样,以上做的都是无用功啦,直接去修改系统 DLL 它不香吗?

      接下来的操作证明,这一步是修改系统 DLL 去除 NAG 窗口很关键的一步,至于重要在哪里,接着向下 👇

    • 接下来使用的是原版汉化OD,有很多插件可以使用,工欲善其事,必先利其器嘛,而且修改版OD特殊的内存访问断点已经用不到了,鸟尽弓藏

    • 然后将保存的程序倒入OD,按下Ctrl + G转到CreateWindowExA函数并双击行首或按下F2设置断点并做好备注:

      goto 到 API

      既然程序有窗体,那它怎么也绕不开一个 APICreateWindow,至于这里为什么使用CreateWindowExA,而不是CreateWindowCreateWindowExW,在goto窗口试试不就知道了;

      认识一下CreateWindow

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      CreateWindow

      作用
      该函数创建一个重叠式窗口、弹出式窗口或子窗口。
      它指定窗口类,窗口标题,窗口风格,以及窗口的初始位置及大小(可选的)。
      该函数也指定该窗口的父窗口或所属窗口(如果存在的话),及窗口的菜单。
      若要使用除 CreateWindow 函数支持的风格外的扩展风格,则使用 CreateWindowEx 函数代替 CreateWindow 函数。
      在 winuser.h 中根据是否已定义 Unicode 被分别定义为 CreateWindowW 和 CreateWindowA,然后两者又被分别定义为对 CreateWindowExW 和 CreateWindowExA 函数的调用。
      参数
      LPCTSTR lpClassName, // 窗口类名称
      LPCTSTR lpWindowName, // 窗口标题
      DWORD dwStyle, // 窗口风格,或称窗口格式

      参数很多非常多,在这里,关注的是第 2 和第 3 参数,也就是WindowNameStyle

    • 设置好CreateWindowExA断点后,运行程序,程序中断后,在堆栈窗口可以看到:

      空标题

      前面的中断都不是需要的位置,而原因很简单,不管是 NAG 窗口还是程序主窗体都是有标题的,说明参数WindowName不可能为空;

    • 那就继续运行程序,直到WindowName不为空:

      标题一样

      这次WindowName的值和 NAG 窗口的标题一模一样,就是它了;

    • 然而,麻烦也来了,因为栈顶显示的很明白,调用来自MSVBVM60.DLL,这是个系统文件,所有程序共用此文件,如果因为这个程序而修改了系统文件,其它程序可能就会出错,怎么办?

      当然有办法啦,和编程中的变量就近原则一样,调用也遵循这一原则,同名文件,谁离得近就用谁,那复制系统文件MSVBVM60.DLL到程序目录不就 OK👌 了?是的;

      私有 DLL

      复制系统 DLL 到程序目录后,就变成了私有 DLL,修改就不会污染全局了;

    • 搞定了 DLL 文件,就要开始修改了,改什么?

      修改 Style

      修改CreateWindowExA的参数Style的值,这里的Style有这么多的值,所以 NAG 窗口是一个有标题、按钮、文本以及层叠属性的窗体,而修改的目的很简单,就是让它成为不能拥有菜单,不能弹出的子窗体,对应的Style属性的值为WS_CHILD;

      到了这里,理解深刻的同学可能已经知道了上个问题的答案:为什么要先修改交通枢纽的JMP跳转让程序运行时 NAG 窗口和主窗体同时显示后,然后才能修改系统 DLL 去除 NAG 窗口了;

      因为这一步修改后,NAG 窗口不能弹出,没有按钮,那么按照之前程序的逻辑,点击 Register 按钮去触发主窗体就无从谈起了,因为修改后 Register 按钮已经不存在了,问题就来了:如果程序运行时 NAG 窗口和主窗体没有同时显示,如何在没有 Register 按钮的情况下,让主窗体显示?多么痛的领悟,卡了一天才恍然大明白!

    • 修改Style属性,也就是窗口样式:

      修改 Style

      可以看到,将Style的值修改为40000000后的属性值,正是想要的;

      然后禁用CreateWindowExA断点,运行程序:

      堆栈修改运行

      程序主窗体直接出现,并没有出现 NAG 窗口,看来,此法可行;

    • 但是,堆栈的修改只对本次运行有效,如何才能保存到文件呢?

      那就要在代码上下功夫了,启用CreateWindowExA断点,重载并运行程序,到达理想的断点位置,跟随调用位置去看看:

      调用位置

      不用看也知道调用是一个 CALL,因为CreateWindowExA是函数,既然要修改函数的Style参数,那肯定要在调用之前参数之后,而且还有一个条件:WindowName的值必须和 NAG 窗口相同,满足了这个条件,才能修改Style让它消失;

      既有判断代码,还有操作代码,但这里并没有多余的空指令行来施展,多出来的这些代码要写在哪里呢?设想,如果修改这个 CALL 为跳转,跳转到某个位置,做一些操作之后再跳回来,是否可行?值得一试;

      值得注意 ⚠️ 的是,修改既不能影响程序的运行,还需要达到目的,在这里 CALL 只有一行,也就是说,接下来的跳转也只能是一行,只能用间接寻址了;

    • 首先复制原本 CALL 的代码,然后修改 CALL 为 JMP,跳转到空代码段:

      修改为 JMP

      因为目的是在 CALL 调用之前添加判断和操作代码,所以调用 CALL 的代码后面还会用到,需要复制到可靠的位置保留下来,至于跳转的空代码段,位置随意,看心情,但地址需要记得住,当然也可以复制地址到可靠的位置保存;

    • 接下来就是到空代码段,写属于自己的逻辑:

      1. 因为是间接寻址,所以,JMP 跳转的其实是当前地址中存储的地址:

        地址为空

      2. 所以要把真正的跳转地址写入当前地址的存储位置:

        写入真实地址

        写入真实跳转地址后,再看 JMP,就发现,左侧的跳转提示有了,信息窗口的跳转地址不再是空了;

      3. 然后写属于自己的代码:

        自己的代码

        首先,判断WindowName是否为 NULL,如果是 NULL,直接调用CreateWindowExA,如果不是,判断WindowName的前 4 字节和给定字符是否相等,不相等就直接调用CreateWindowExA,如果相等,说明是 NAG 窗口,则修改StyleWS_CHILD,然后调用CreateWindowExA,最后,调用结束后,需要用JMP回到程序原来的位置,至于给定的字符,其实就是 NAG 窗口标题的前 4 字节:

        给定字符

        如果想让程序更严谨一些,可以循环比较WindowName的值和 NAG 窗口的标题是否完全一致;

    • 修改完成后,就是保存修改到文件了,由于这里没有保存全部修改选项,只能分为两次保存:

      保存空代码段

      保存增加的代码到文件;

      修改 CALl 为 JMP

      保存修改的 JMP 到文件;

    • 然后运行程序,嗯?双击程序后,没有任何反应,看来修改出错了,程序被改坏了,不要着急删除,倒入OD看看出错在哪里?

      位置不同

      可以看到,程序运行时,窗口标题和样式的堆栈位置前移了 4 个字节,应该就是这里出错了,修改代码并保存,然后重载程序,检查修改:

      参数被修改

      程序运行自定义代码后,NAG 窗口在被创建前,Style属性的值已经被修改了;

    • 运行程序并输入注册码:

      成功

      直接显示程序主窗体,输入注册码,显示 OK 弹窗!