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

再探 NAG 窗口和硬编码 CM,对思路进行总结;

使用工具

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

分析思路

  1. 将 CM 导入 OD,查看 EP 位置的代码,很明显,这是一个 VB 程序:

    VB

    然后 F9 运行程序;

    首先弹出 NAG 窗口,点击确定之后程序主窗体才会出现,当然,如果点击取消的话,程序就退出了;

    NAG

    同时,仔细查看 NAG 窗口的提示文本就会知道,这个 CM 要求移除所有 NAG 窗口,然后找到正确的序列号;

    程序主窗体

    把程序上所有按钮点击一遍,熟悉程序的逻辑;

    所有按钮点击一遍

    看来,算上运行程序时的 NAG 窗口,一共有两个 NAG 窗口,然后就是寻找序列号了,至于 Exit 按钮就不用点了,会退出的,亲测;

  2. 既然有弹窗,肯定会用到rtcMsgBox,右键选择查找 >> 所有模块间的调用,找到rtcMsgBox,设置断点:

    rtcMsgBox

    这里需要给所有调用rtcMsgBox的 CALL 设置断点,因为谁也不知道哪个 CALL 才是 NAG 窗口调用的那一个;

  3. 设置好断点,重载程序,然后 F9 运行程序,程序会中断下来:

    第一次中断

    中断位置是 CALL,至于如何确定它调用的是rtcMsgBox,很简单,选中中断行,按下 Enter 键或右键选择跟随即可;

  4. 首先按照正常思路,如果把 CALL 用 NOP 填充,那么弹窗肯定就没有了,值得一试:

    不过,最好的方法不是 NOP,而是根据函数参数的大小,保证函数调用前后,栈能保持不变;

    如何确定函数调用使用的栈大小呢,很简单,给调用行和返回行设置断点,然后运行程序:

    确定函数调用使用的栈大小

    在此处,函数调用前,栈顶地址是 0012FA34,而函数返回后,栈顶地址是 0012FA48,相减之后得到,函数调用中使用的栈大小为 14(HEX);

  5. 重载并运行程序,然后,将 CALL 所在行:

    1
    2
    00402CFE    E8 1DE4FFFF     CALL <JMP.&MSVBVM50.#595>

    替换为:

    1
    2
    3
    00402CFE    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 继续运行程序,没有预期的主窗体弹出,而是程序已终止:

    程序已终止

    这是为什么呢,重载程序,探讨一下;

  6. 重载程序,会提示断点被禁用,是因为上面的修改导致当前指令与断点设置的指令不匹配,删除断点重新设置或者直接激活断点即可:

    断点被禁用

    让程序重新中断在之前的位置,然后分析后面的代码:

    分析后面的代码

    可以看到,这里存储了 EAX,也就是 CALL 的返回值,后面肯定是进行了判断,如果是 1 (表示确定按钮)就显示主程序,否则就退出程序,
    至于这个 1 是怎么来的,执行一下 CALL,看看 EAX 不就知道了;

    既然这样,如果手动把 EAX 置 1,不就可以了:

    1
    2
    00402CFE    83C4 14         ADD ESP,14
    00402D01 B8 01000000 MOV EAX,1

    但是,这两条指令一共占用了 8 个字节,而原来的 CALL 只有 5 个字节,势必会覆盖后面的指令,产生其它错误,所以这样做肯定也是不行的,不过,这也是种思路,在其它程序中,空间够用的情况下可以一试;

    还有一种思路就是间接寻址,整理栈并修改 EAX,然后返回这里,动手能力强的小伙伴可以自己尝试一下;

  7. 换个思路:当前位置是调用rtcMsgBox用来弹出 NAG 窗口的,在其后修改已经没有意义了,只能在前面修改,在前面哪里修改呢,向上滚动代码:

    向上滚动代码

    这里发现了生成栈帧的指令,也就是说,调用rtcMsgBox的指令嵌套在另一函数中,如果,让上层的这个函数不调用,也就不会调用rtcMsgBox了;

    00402C17设置断点,重载并运行程序;

  8. 程序中断后,在栈顶地址按下 Enter 键或右键选择反汇编窗口中跟随:

    右键选择反汇编窗口中跟随

    来到返回位置

    来到返回位置,上面的CALL EAX肯定就是调用位置,不过这里不能修改,因为指令地址以 74 开头,是系统 DLL 的领域,牵一发而动全身,而且 OD 标题栏写的很清楚了,当前是 MSVBVM50.DLL的领空,它可是 VB 的解释器,更不能改了;

  9. 到了这里,我能想到的有两种修改方案,分别来说说:

    • 方案 1:

      1. 此处的CALL EAX会调用包含rtcMsgBox指令的函数,且是系统领空,不能修改;

      2. 但是,调用rtcMsgBox指令的函数却不是系统领空,可以修改,如果让这个函数的首行就返回,后面的指令不执行,那么就不会调用rtcMsgBox了;

      3. 现在需要确定的就是如何返回,我们都知道,函数调用完成后会清理占用的栈空间,查看CALL EAX下面一行的指令,没有整理栈,也就是说,这里使用的是 stdcall(被调用者清理栈)所以,只需要确定CALL EAX在执行中占用了多少栈空间就可以了;

      4. 这很简单啊,给CALL EAX和它的下一行(也就是返回位置,函数执行完毕,会返回到调用指令的下一行)设置断点,重载并运行程序;

        给`CALL EAX`和它的下一行

        这里计算可知:0012FB0C - 0012FB08 = 4,CALL EAX在执行过程中会占用 4 个字节的栈空间;

      5. 取消这里的两个断点,重载并运行程序,程序会中断在调用rtcMsgBox指令的函数的行首,也就是生成栈帧的位置:

        也就是生成栈帧的位置

        将这里的PUSH EBP修改为RET 4,细心的童鞋可能会发现,RET 4占用了 3 个字节,而PUSH EBP只有 1 个字节的空间,所以,后面的MOV EBP,ESP被覆盖了,这就是空间不够修改指令会产生的问题,不过,在这里,它不是问题,因为首行已经返回了,即使把这个函数后面所有的指令都覆盖,也不会产生问题;

      6. 修改完成,接下来就是验证成果的时候了,F9 运行程序:

        修改完成,接下来就是验证成果的时候了

        嗯,完美,没有 NAG 弹窗,直接出现程序主窗体,而且,在主程序中点击Nag?按钮也不会出现 NAG 窗口,看来,它们调用的是同一个函数;

    • 方案二:

      1. 还是得回到CALL EAX这一行来说,这里的 EAX 到底是什么:

        这里的 EAX 到底是什么

      2. 坐和放宽,选中CALL EAX按下 Enter 键或右键选择跟随:

        选中`CALL EAX`按下 Enter

        哟豁,这里的指令很眼熟,交通枢纽嘛,给所有的 JMP 设置断点,用来确定程序的走向;

        用来确定程序的走向

      3. 禁用CALL EAX处的断点,重载并运行程序,开始分析程序的走向:

        开始分析程序走向的逻辑

        经过分析后,大部分 JMP 的逻辑已经分析出来,而且可以清楚的看到,两次 NAG 窗口调用的是同一处代码,剩余的两个 JMP 恕我无能,分析不出来;

      4. 至此,可能有的童鞋已经猜测出来第 2 个方案是什么了?是的,就是修改这里的跳转地址,不过遗憾的是,这里并没有创建主程序的 JMP,所以,退而求其次,随便找个RET 4语句的地址,把跳向 NAG 窗口的 JMP 后面的地址修改即可;

        1. 这段话是嘛意思?解释一下;

        2. 在有创建主程序窗口的 JMP 时,可以让创建 NAG 窗口的跳转直接跳向创建主程序窗口,这样就达到了去除 NAG 窗口的目的;

        3. 然而,在没有创建主程序窗口的 JMP 时,它该跳向哪里呢?

        4. 还记得方案 1 的做法吗:让CALL EAX最终执行RET 4

        5. 而如果选中CALL EAX并 F8 单步执行后,你会发现,它首先跳向了这里,然后通过 JMP 跳向对应的函数,最后执行我们修改的RET 4

        6. 说到这里,是不是有思路了?

        7. 对啦,既然CALL EAX是通过 JMP 最终跳向我们修改的RET 4,何必这么麻烦呢?找个已有的RET 4不就好了?何况修改RET 4时,由于空间不足还会覆盖其它指令,而修改跳转指令后面的地址,肯定不会产生这样的问题,何乐而不为呢?

          下面是让CALL EAX最终执行RET 4时的情形:

          下面是修改`CALL EAX`为`RET 4`时的情形

          仔细观察会发现,要修改的PUSH EBP的上一行指令就是RET 4

        8. 修改两个 NAG 窗口的 JMP 地址:

          修改两个 NAG 窗口的 JMP 地址

          修改两个 JMP 地址后,和方案 1 的做法就完全一致了:让CALL EAX最终执行RET 4,不过这种方法最初的用途是:让跳向创建 NAG 窗口的 JMP 跳向创建程序主窗体;

      5. 修改完成,测试一下,没有任何问题,去除 NAG 窗口到此结束;

      6. 不过,这种做法需要注意:不是所有的程序都是找个RET 4的地址就可以,还是要先计算出调用指令(如这里的CALL EAX)执行中所占栈空间的大小,然后才可以去匹配对应的语句,实在找不到对应的语句,要么用方案 1,要么自己写一个RET x,然后 JMP 就完事儿了;

  10. CM 还有另一个要求,找到真实的注册码,这个太简单了;

    首先在 CM 中搜索所有 UNICODE 字符串(因为 VB 用的就是 UNICODE),然后找到错误弹窗中提示的字符串,双击定位到对应指令,然后向上查找字符串比较 API,这里使用的是 vbaStrCmp,接着就找到真实注册码了;

    接着就找到真实注册码了

    测试一下:

    测试一下

    注意 ⚠️:不要直接使用注释中的注册码,仔细看,注册码区分大小写;