VB 调试之分析 N-Code(三)
再探 NAG 窗口和硬编码 CM,对思路进行总结;
使用工具
- OllyDbg 1.10原版,简称
OD
; OD
汉化
和插件
均来自互联网;- CrackMe来自互联网,仅供学习使用;
- 文中特殊数字均是
HEX
,为了书写方便采用DEC
;
分析思路
将 CM 导入 OD,查看 EP 位置的代码,很明显,这是一个 VB 程序:
然后 F9 运行程序;
首先弹出 NAG 窗口,点击确定之后程序主窗体才会出现,当然,如果点击取消的话,程序就退出了;
同时,仔细查看 NAG 窗口的提示文本就会知道,这个 CM 要求移除所有 NAG 窗口,然后找到正确的序列号;
把程序上所有按钮点击一遍,熟悉程序的逻辑;
看来,算上运行程序时的 NAG 窗口,一共有两个 NAG 窗口,然后就是寻找序列号了,至于 Exit 按钮就不用点了,会退出的,亲测;
既然有弹窗,肯定会用到
rtcMsgBox
,右键选择查找 >> 所有模块间的调用
,找到rtcMsgBox
,设置断点:这里需要给所有调用
rtcMsgBox
的 CALL 设置断点,因为谁也不知道哪个 CALL 才是 NAG 窗口调用的那一个;设置好断点,重载程序,然后 F9 运行程序,程序会中断下来:
中断位置是 CALL,至于如何确定它调用的是
rtcMsgBox
,很简单,选中中断行,按下 Enter 键或右键选择跟随即可;首先按照正常思路,如果把 CALL 用 NOP 填充,那么弹窗肯定就没有了,值得一试:
不过,最好的方法不是 NOP,而是根据函数参数的大小,保证函数调用前后,栈能保持不变;
如何确定函数调用使用的栈大小呢,很简单,给调用行和返回行设置断点,然后运行程序:
在此处,函数调用前,栈顶地址是 0012FA34,而函数返回后,栈顶地址是 0012FA48,相减之后得到,函数调用中使用的栈大小为 14(HEX);
重载并运行程序,然后,将 CALL 所在行:
1
200402CFE E8 1DE4FFFF CALL <JMP.&MSVBVM50.#595>
替换为:
1
2
300402CFE 83C4 14 ADD ESP,14
00402D01 90 NOP
00402D02 90 MOP这里由于之前的 CALL 指令大小为 5 个字节(E8 1D E4 FF FF,16 进制两位为 1 字节,这里是 5 个字节),而 ADD 指令只占了 3 个字节,为了保证代码不乱,其余 2 个字节用 NOP 填充;
修改完成后,F9 继续运行程序,没有预期的主窗体弹出,而是程序已终止:
这是为什么呢,重载程序,探讨一下;
重载程序,会提示断点被禁用,是因为上面的修改导致当前指令与断点设置的指令不匹配,删除断点重新设置或者直接激活断点即可:
让程序重新中断在之前的位置,然后分析后面的代码:
可以看到,这里存储了 EAX,也就是 CALL 的返回值,后面肯定是进行了判断,如果是 1 (表示确定按钮)就显示主程序,否则就退出程序,
至于这个 1 是怎么来的,执行一下 CALL,看看 EAX 不就知道了;既然这样,如果手动把 EAX 置 1,不就可以了:
1
200402CFE 83C4 14 ADD ESP,14
00402D01 B8 01000000 MOV EAX,1但是,这两条指令一共占用了 8 个字节,而原来的 CALL 只有 5 个字节,势必会覆盖后面的指令,产生其它错误,所以这样做肯定也是不行的,不过,这也是种思路,在其它程序中,空间够用的情况下可以一试;
还有一种思路就是间接寻址,整理栈并修改 EAX,然后返回这里,动手能力强的小伙伴可以自己尝试一下;
换个思路:当前位置是调用
rtcMsgBox
用来弹出 NAG 窗口的,在其后修改已经没有意义了,只能在前面修改,在前面哪里修改呢,向上滚动代码:这里发现了生成栈帧的指令,也就是说,调用
rtcMsgBox
的指令嵌套在另一函数中,如果,让上层的这个函数不调用,也就不会调用rtcMsgBox
了;给
00402C17
设置断点,重载并运行程序;程序中断后,在
栈顶地址
按下 Enter 键或右键选择反汇编窗口中跟随:来到返回位置,上面的
CALL EAX
肯定就是调用位置,不过这里不能修改,因为指令地址以 74 开头,是系统 DLL 的领域,牵一发而动全身,而且 OD 标题栏写的很清楚了,当前是MSVBVM50.DLL
的领空,它可是 VB 的解释器,更不能改了;到了这里,我能想到的有两种修改方案,分别来说说:
方案 1:
此处的
CALL EAX
会调用包含rtcMsgBox
指令的函数,且是系统领空,不能修改;但是,调用
rtcMsgBox
指令的函数却不是系统领空,可以修改,如果让这个函数的首行就返回,后面的指令不执行,那么就不会调用rtcMsgBox
了;现在需要确定的就是如何返回,我们都知道,函数调用完成后会清理占用的栈空间,查看
CALL EAX
下面一行的指令,没有整理栈,也就是说,这里使用的是 stdcall(被调用者清理栈)所以,只需要确定CALL EAX
在执行中占用了多少栈空间就可以了;这很简单啊,给
CALL EAX
和它的下一行(也就是返回位置,函数执行完毕,会返回到调用指令的下一行)设置断点,重载并运行程序;这里计算可知:0012FB0C - 0012FB08 = 4,
CALL EAX
在执行过程中会占用 4 个字节的栈空间;取消这里的两个断点,重载并运行程序,程序会中断在调用
rtcMsgBox
指令的函数的行首,也就是生成栈帧的位置:将这里的
PUSH EBP
修改为RET 4
,细心的童鞋可能会发现,RET 4
占用了 3 个字节,而PUSH EBP
只有 1 个字节的空间,所以,后面的MOV EBP,ESP
被覆盖了,这就是空间不够修改指令会产生的问题,不过,在这里,它不是问题,因为首行已经返回了,即使把这个函数后面所有的指令都覆盖,也不会产生问题;修改完成,接下来就是验证成果的时候了,F9 运行程序:
嗯,完美,没有 NAG 弹窗,直接出现程序主窗体,而且,在主程序中点击
Nag?
按钮也不会出现 NAG 窗口,看来,它们调用的是同一个函数;
方案二:
还是得回到
CALL EAX
这一行来说,这里的 EAX 到底是什么:坐和放宽
,选中CALL EAX
按下 Enter 键或右键选择跟随:哟豁,这里的指令很眼熟,
交通枢纽
嘛,给所有的 JMP 设置断点,用来确定程序的走向;禁用
CALL EAX
处的断点,重载并运行程序,开始分析程序的走向:经过分析后,大部分 JMP 的逻辑已经分析出来,而且可以清楚的看到,两次 NAG 窗口调用的是同一处代码,剩余的两个 JMP 恕我无能,分析不出来;
至此,可能有的童鞋已经猜测出来第 2 个方案是什么了?是的,就是修改这里的跳转地址,不过遗憾的是,这里并没有创建主程序的 JMP,所以,退而求其次,随便找个
RET 4
语句的地址,把跳向 NAG 窗口的 JMP 后面的地址修改即可;这段话是嘛意思?解释一下;
在有创建主程序窗口的 JMP 时,可以让创建 NAG 窗口的跳转直接跳向创建主程序窗口,这样就达到了去除 NAG 窗口的目的;
然而,在没有创建主程序窗口的 JMP 时,它该跳向哪里呢?
还记得方案 1 的做法吗:让
CALL EAX
最终执行RET 4
;而如果选中
CALL EAX
并 F8 单步执行后,你会发现,它首先跳向了这里,然后通过 JMP 跳向对应的函数,最后执行我们修改的RET 4
;说到这里,是不是有思路了?
对啦,既然
CALL EAX
是通过 JMP 最终跳向我们修改的RET 4
,何必这么麻烦呢?找个已有的RET 4
不就好了?何况修改RET 4
时,由于空间不足还会覆盖其它指令,而修改跳转指令后面的地址,肯定不会产生这样的问题,何乐而不为呢?下面是让
CALL EAX
最终执行RET 4
时的情形:仔细观察会发现,要修改的
PUSH EBP
的上一行指令就是RET 4
;修改两个 NAG 窗口的 JMP 地址:
修改两个 JMP 地址后,和方案 1 的做法就完全一致了:让
CALL EAX
最终执行RET 4
,不过这种方法最初的用途是:让跳向创建 NAG 窗口的 JMP 跳向创建程序主窗体;
修改完成,测试一下,没有任何问题,去除 NAG 窗口到此结束;
不过,这种做法需要注意:不是所有的程序都是找个
RET 4
的地址就可以,还是要先计算出调用指令(如这里的CALL EAX
)执行中所占栈空间的大小,然后才可以去匹配对应的语句,实在找不到对应的语句,要么用方案 1,要么自己写一个RET x
,然后 JMP 就完事儿了;
CM 还有另一个要求,找到真实的注册码,这个太简单了;
首先在 CM 中搜索所有 UNICODE 字符串(因为 VB 用的就是 UNICODE),然后找到错误弹窗中提示的字符串,双击定位到对应指令,然后向上查找字符串比较 API,这里使用的是 vbaStrCmp,接着就找到真实注册码了;
测试一下:
注意 ⚠️:不要直接使用注释中的注册码,仔细看,注册码区分大小写;