OllyDbg 反调试之 IsDebuggerPresent

使用工具

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

分析思路

  1. 首先,打开软件到处点点,逆向一个软件起码要会用吧,不会用就没必要说逆向了:

    打开

    • ID 不可输入,应该是根据电脑硬件的某些特征生成的;
    • 输入随机注册码点击 Check 之后,没有任何反馈;
  2. 倒入OD开始分析:

    • 首先按下Ctrl + AOD分析一下代码;

    • 既然程序有输入,有按钮,那可用的 API 可就多了。按下Ctrl + N查看使用了哪些API,用的API不多,一眼就看到了GetWindowTextA,在GetWindowTextA函数上设置断点,然后去断点窗口双击断点进入反汇编窗口给断点设置备注,这是一个好的习惯,请保持;

    • 然后F9运行程序,嘿嘿,程序窗口一闪而过,而且OD的右下角也显示了程序的状态:已终止,;

    • 如果说程序损坏的话,那么刚开始的时候应该也是打不开的;

    • 如果说OD出现了问题,那么程序应该倒入不进来,而且查看不了 API 列表;

    • 到底是什么问题呢?不绕弯子,程序有反调试,至于是如何反调试的?它就是今天的主角儿IsDebuggerPresent:

      • 先了解一下这个 API:

        1
        2
        3
        4
        5
        6
        7
        IsDebuggerPresent

        作用
        确定调用进程是否由用户模式的调试器调试。
        返回值
        如果当前进程运行在调试器的上下文,返回值为非零值。
        如果当前进程没有运行在调试器的上下文,返回值为零。

        看着好绕,大白话:检测当前程序是否正在被调试,没有被调试返回 0,只要返回值不是 0,那就表示程序正在被调试;

    • 既然知道它是一个 Windows API,那我们不妨按下Ctrl + N去函数列表看看这个程序是否调用了IsDebuggerPresent

      API

      果然,在函数列表中发现了IsDebuggerPresent,不过,怎么证明它被调用了,而不是放在函数列表中迷惑我们呢?

    • 很简单,给它设置一个断点,然后运行程序,没有意外,程序中断了,那就说明程序调用了这个 API:

      中断

      我们都知道API 断点会中断在函数的行首,也就是说,IsDebuggerPresent这个函数并没有执行,如果Ctrl + F9执行到返回,这个函数才算执行完毕了,而一旦它执行完毕,就意味着程序是否被调试它已经检测完成了,但从代码上看,只有区区 4 行,它是如何检测的呢,分析一下;

    • 分析一下这 4 行代码:

      1
      2
      3
      4
      MOV EAX,DWORD PTR FS:[18]       ; 将 FS:[18] 中的地址拷贝到 EAX
      MOV EAX,DWORD PTR DS:[EAX+30] ; 将 EAX 中的地址加 0x30 之后的地址拷贝到 EAX
      MOVZX EAX,BYTE PTR DS:[EAX+2] ; 将 EAX 中的地址,存储的第 2 个字节的数据拷贝到 EAX
      RETN ; 返回

      嘛意思?往下分析;

      • FS:[18]中的FS 寄存器的地址是什么呢?

        SF 标志位指向FS 寄存器的地址;

      • 开始分析代码,先是第 1 行代码MOV EAX,DWORD PTR FS:[18]:将FS:[18]中的地址拷贝到 EAX

        FS 寄存器从第 18 位开始,存放的是该寄存器从起始位置依次向后的地址,即第 18 位存放的是起始位置的地址;

        对这句话很困惑?那我们按下Ctrl + G跟随一下这个地址,在这里,我的机器上,FS的地址是7FFDF000,那FS:[18]的地址就显而易见了,是7FFDF018

        FS

        FS:[18]的地址是7FFDF018,按下F7,单步执行代码,同时查看EAX 寄存器,丝毫不差,FS:[18]存放的正是FS 寄存器的起始地址7FFDF000,既然第 1 行代码搞定,那就开始分析第二行;

      • 第 2 行代码MOV EAX,DWORD PTR DS:[EAX+30]:将 EAX 中的地址加 0x30 之后的地址拷贝到EAX

        与上一步相同,按下Ctrl + G跟随7FFDF030

        加30

        没有意外,运行结果与我们分析的一致:

        运行后

      • 第 3 行代码MOVZX EAX,BYTE PTR DS:[EAX+2]:将 EAX 中的地址,存储的第 2 个字节的数据拷贝到 EAX;

        跟随一下这个地址,看看它存储的是什么内容:

        数据

        它存储的数据很简单,第二个字节是01,而根据没有被调试返回 0,只要返回值不是 0,那就表示程序正在被调试这句话来看,很明显,它检测到我们正在调试程序:

        返回

        至此,恍然大明白,原来这 4 行代码就可以检测程序有没有被调试,至于为什么这里存放的是01这个数据,不得而知;

      • 顺带提一嘴,如果程序倒入OD没有运行,那么EBX 寄存器指向的地址就是以上三行代码执行后需要取出数据的地址,也就是存放 01 的地址;

    • 既然已经明白了它是如何检测的以及知道了它的返回值,那如何绕过呢?

      当然是修改返回值,也就是EAX 寄存器的值喽,没有调试返回 0,那返回 0 就是没有调试;

    • 原理也明白了,也能绕过了,那么,它检测到程序被调试后,是如何退出程序的呢?意犹未尽;

      不修改返回值,跟随一下RETN,看看它接下来会做什么?

      退出消息

      哦豁,它用PostQuitMessage提交了退出消息,接着跟;

      退出进程

      然后,调用退出进程函数ExitProcess,再运行一下下,结束!