VB 调试之修改系统 DLL
使用工具
- OllyDbg 1.10原版,简称
OD
; - OllyDbg修改版,简称
OD
; OD
汉化
和插件
均来自互联网;- CrackMe来自互联网,仅供学习使用;
- 文中特殊数字均是
HEX
,为了书写方便采用DEC
;
分析思路
首先运行软件,了解一下程序:
运行程序后,首先弹出一个 NAG 窗口,点击 Register 后弹出程序主窗口,随机输入注册码并点击 OK 后,弹出错误弹窗;
寻找注册码
了解程序逻辑后,接下来需要解决注册码,因为机器码为自动生成,所以对于本机来说,这是一个硬编码,那就非常简单了;
所见即所得,既然有弹窗,说明程序使用了
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 参数不再是空,那这里就相当可疑了;
既然拿到了可疑的字符串,当然要试一试:
嗯,这个弹窗甚是满意,这串字符就是本机机器码对应的真实注册码;
4C 法移除 NAG 窗口
拿到注册码就结束了?当然不是,不觉得 NAG 窗口很烦人吗?那就去除它!
首先,第 1 想法就是
4C法
,简单粗暴,do it!程序倒入
OD
后,不要运行,goto
到入口 PUSH 指令中的地址加上 4C 后的地址:然后在数据窗口,继续跟踪当前地址中存储的另一个
DWORD
地址:跟随
DWORD
来到数据窗口:这里有两个结构类似的数据块,每块 50(十六进制)个字节的长度,每块数据的第 24(十六进制)个字节处都有一个标志(第一个是 00,第二个是 04);
而这个标志指定了代码块(也就是程序启动后要加载的窗体)出现的顺序,先加载 00,再加载 04;
大胆的猜测一下,由于 00 先加载,那 00 可能就是 NAG 窗口,而 04 则是主窗口,至于缺失的 01, 02, 03,可能是
Help
Wrong
和Okey
,因为它们都是子窗口,所以就不在这里吧;所以,为了去掉 NAG 窗口,将此处的两个标志的值互换,即:00 改 04,04 改 00,这样, NAG 窗口就永远也没机会出现了,最后将修改保存到文件:
最后,运行保存的文件:
没有出现 NAG 窗口,而是直接出现程序主窗体,接着,输入注册码,弹出 OK 弹窗;
以为这样就结束了?NO NO NO,4C 法并不是今天的重点,今天的重点是:修改系统 DLL;
修改系统 DLL 移除 NAG 窗口
首先,要用到修改版的
OD
,将程序倒入OD
:在
内存窗口
给代码段
设置内存访问断点,然后运行程序;多次运行程序后,来到程序的总分支,给所有跳转设置断点,了解它们的功能:
这好像是 VB 程序的一个特点,有一个类似交通枢纽的地方控制着程序的走向;
设置好断点后,重载并运行程序,程序会在设置断点的
JMP
位置依次中断,跟踪分析:前三个已经满足了需求,至于其它几个,who cares?
既然已经知道了创建主窗体的跳转,去除 NAG 窗体还不简单,把创建 NAG 窗体的跳转改为创建主窗体的跳转不就结束战斗了?值得一试:
复制创建主窗体的跳转并覆盖创建 NAG 窗体的跳转,然后保存修改到文件;
运行保存的程序:
呃呃,并不是预期的结果,NAG 窗口还是出现了,不过,现在不用点击 Register,程序主窗体也会出现,也就是运行程序后,NAG 窗口和程序主窗体同时显示;
既然这样,以上做的都是无用功啦,直接去修改系统 DLL 它不香吗?
接下来的操作证明,这一步是修改系统 DLL 去除 NAG 窗口很关键的一步,至于重要在哪里,接着向下 👇
接下来使用的是原版汉化
OD
,有很多插件可以使用,工欲善其事,必先利其器
嘛,而且修改版OD
特殊的内存访问断点
已经用不到了,鸟尽弓藏
;然后将保存的程序倒入
OD
,按下Ctrl + G
转到CreateWindowExA
函数并双击行首或按下F2
设置断点并做好备注:既然程序有窗体,那它怎么也绕不开一个 API
CreateWindow
,至于这里为什么使用CreateWindowExA
,而不是CreateWindow
或CreateWindowExW
,在goto
窗口试试不就知道了;认识一下
CreateWindow
:1
2
3
4
5
6
7
8
9
10
11
12CreateWindow
作用
该函数创建一个重叠式窗口、弹出式窗口或子窗口。
它指定窗口类,窗口标题,窗口风格,以及窗口的初始位置及大小(可选的)。
该函数也指定该窗口的父窗口或所属窗口(如果存在的话),及窗口的菜单。
若要使用除 CreateWindow 函数支持的风格外的扩展风格,则使用 CreateWindowEx 函数代替 CreateWindow 函数。
在 winuser.h 中根据是否已定义 Unicode 被分别定义为 CreateWindowW 和 CreateWindowA,然后两者又被分别定义为对 CreateWindowExW 和 CreateWindowExA 函数的调用。
参数
LPCTSTR lpClassName, // 窗口类名称
LPCTSTR lpWindowName, // 窗口标题
DWORD dwStyle, // 窗口风格,或称窗口格式参数很多非常多,在这里,关注的是第 2 和第 3 参数,也就是
WindowName
和Style
;设置好
CreateWindowExA
断点后,运行程序,程序中断后,在堆栈窗口可以看到:前面的中断都不是需要的位置,而原因很简单,不管是 NAG 窗口还是程序主窗体都是有标题的,说明参数
WindowName
不可能为空;那就继续运行程序,直到
WindowName
不为空:这次
WindowName
的值和 NAG 窗口的标题一模一样,就是它了;然而,麻烦也来了,因为栈顶显示的很明白,调用来自
MSVBVM60.DLL
,这是个系统文件,所有程序共用此文件,如果因为这个程序而修改了系统文件,其它程序可能就会出错,怎么办?当然有办法啦,和编程中的变量就近原则一样,调用也遵循这一原则,同名文件,谁离得近就用谁,那复制系统文件
MSVBVM60.DLL
到程序目录不就 OK👌 了?是的;复制系统 DLL 到程序目录后,就变成了私有 DLL,修改就不会污染全局了;
搞定了 DLL 文件,就要开始修改了,改什么?
修改
CreateWindowExA
的参数Style
的值,这里的Style
有这么多的值,所以 NAG 窗口是一个有标题、按钮、文本以及层叠属性的窗体,而修改的目的很简单,就是让它成为不能拥有菜单,不能弹出的子窗体,对应的Style
属性的值为WS_CHILD
;到了这里,理解深刻的同学可能已经知道了上个问题的答案:为什么要先修改交通枢纽的
JMP
跳转让程序运行时 NAG 窗口和主窗体同时显示后,然后才能修改系统 DLL 去除 NAG 窗口了;因为这一步修改后,NAG 窗口不能弹出,没有按钮,那么按照之前程序的逻辑,点击 Register 按钮去触发主窗体就无从谈起了,因为修改后 Register 按钮已经不存在了,问题就来了:如果程序运行时 NAG 窗口和主窗体没有同时显示,如何在没有 Register 按钮的情况下,让主窗体显示?多么痛的领悟,卡了一天才恍然大明白!
修改
Style
属性,也就是窗口样式:可以看到,将
Style
的值修改为40000000
后的属性值,正是想要的;然后禁用
CreateWindowExA
断点,运行程序:程序主窗体直接出现,并没有出现 NAG 窗口,看来,此法可行;
但是,堆栈的修改只对本次运行有效,如何才能保存到文件呢?
那就要在代码上下功夫了,启用
CreateWindowExA
断点,重载并运行程序,到达理想的断点位置,跟随调用位置去看看:不用看也知道调用是一个 CALL,因为
CreateWindowExA
是函数,既然要修改函数的Style
参数,那肯定要在调用之前参数之后,而且还有一个条件:WindowName
的值必须和 NAG 窗口相同,满足了这个条件,才能修改Style
让它消失;既有判断代码,还有操作代码,但这里并没有多余的空指令行来施展,多出来的这些代码要写在哪里呢?设想,如果修改这个 CALL 为跳转,跳转到某个位置,做一些操作之后再跳回来,是否可行?值得一试;
值得注意 ⚠️ 的是,修改既不能影响程序的运行,还需要达到目的,在这里 CALL 只有一行,也就是说,接下来的跳转也只能是一行,只能用间接寻址了;
首先复制原本 CALL 的代码,然后修改 CALL 为 JMP,跳转到空代码段:
因为目的是在 CALL 调用之前添加判断和操作代码,所以调用 CALL 的代码后面还会用到,需要复制到可靠的位置保留下来,至于跳转的空代码段,位置随意,看心情,但地址需要记得住,当然也可以复制地址到可靠的位置保存;
接下来就是到空代码段,写属于自己的逻辑:
因为是间接寻址,所以,JMP 跳转的其实是当前地址中存储的地址:
所以要把真正的跳转地址写入当前地址的存储位置:
写入真实跳转地址后,再看 JMP,就发现,左侧的跳转提示有了,信息窗口的跳转地址不再是空了;
然后写属于自己的代码:
首先,判断
WindowName
是否为 NULL,如果是 NULL,直接调用CreateWindowExA
,如果不是,判断WindowName
的前 4 字节和给定字符是否相等,不相等就直接调用CreateWindowExA
,如果相等,说明是 NAG 窗口,则修改Style
为WS_CHILD
,然后调用CreateWindowExA
,最后,调用结束后,需要用JMP
回到程序原来的位置,至于给定的字符,其实就是 NAG 窗口标题的前 4 字节:如果想让程序更严谨一些,可以循环比较
WindowName
的值和 NAG 窗口的标题是否完全一致;
修改完成后,就是保存修改到文件了,由于这里没有保存全部修改选项,只能分为两次保存:
保存增加的代码到文件;
保存修改的 JMP 到文件;
然后运行程序,嗯?双击程序后,没有任何反应,看来修改出错了,程序被改坏了,不要着急删除,倒入
OD
看看出错在哪里?可以看到,程序运行时,窗口标题和样式的堆栈位置前移了 4 个字节,应该就是这里出错了,修改代码并保存,然后重载程序,检查修改:
程序运行自定义代码后,NAG 窗口在被创建前,
Style
属性的值已经被修改了;运行程序并输入注册码:
直接显示程序主窗体,输入注册码,显示 OK 弹窗!