OllyDbg 反调试之检测进程名&窗口类名&窗口标题名

使用工具

  • OllyDbg 1.10原版,简称OD
  • OD 汉化插件均来自互联网;
  • OD补丁程序re-pair
  • 类名检测工具Greatis WinDowse
  • CrackMe来自互联网,仅供学习使用;
  • 文中特殊数字均是HEX,为了书写方便采用DEC

分析思路

  1. 重点依然是学习 Windows API;

  2. 打开CrackMe看看:

    打开软件

    如果没有打开OD,程序可以打开并显示如上,如果打开了OD,程序则无法打开;

  3. 倒入OD开始分析:

    • 首先,Ctrl + N查看一下 API 列表,很意外,出奇的干净,只有区区两个 API:

      API列表

      还好有一个我们认识的ExitProcess

      1
      2
      3
      4
      ExitProcess

      作用
      结束调用的进程及其所有的线程;

      那就只能从它下手了:设置 CC 断点并设置好备注;

    • 接着,运行程序,程序会中断在我们设置的断点ExitProcess函数的行首,然后在堆栈窗口右键反汇编窗口中跟随来到调用这个函数的位置:

      反汇编窗口中跟随

      这里很困惑?困惑的同学没学懂,再复习一下 API 吧;

      常规操作是:Ctrl + F9执行到返回,然后看它获取的数据,再进行下一步操作;

      然鹅,别忘了ExitProcess的功能:结束进程及其所有的线程。

      如果我们Ctrl + F9执行到返回,是不是意味着我们手动执行了结束进程的函数,那么,这个断点的意义是什么?

      所以,我们要跟随函数到调用它的地方,往上看,为什么要调用它?

    • 来到反汇编窗口,发现调用位置的同时,也发现了很多 API,其中大部分都不认识,但是有一根救命稻草GetProcAddress:

      反汇编窗口

      既然 API 列表没有内容的同时发现了GetProcAddress,那是不是说明,大多数函数都被隐藏了,而GetProcAddress就是关键呢?

      再来复习一下GetProcAddress的功能:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      GetProcAddress

      作用
      检索指定的动态链接库中的输出库函数地址;
      (获取函数列表中不显示且被调用的隐藏函数的地址;)
      (捕捉间接加载或调用的其他隐藏函数;)
      参数
      hModule:包含此函数的 DLL 模块的句柄;
      (程序的句柄)
      IpProcName:包含函数名的以 NULL 结尾的字符串;
      (程序的名字)

      毫无疑问,给GetProcAddress设置断点并做好备注,看看都有哪些隐藏的函数:

      GetProcAddress

    • 然后重载并运行程序后程序会中断,接着多次运行直到中断在了ExitProcess后,发现只是调用了以下隐藏函数:CreateToolhelp32Snapshot OpenProcess Process32First Process32Next TerminateProcess lstrcmpA FindWindowA

      • 其中,OpenProcess TerminateProcess lstrcmpA很熟悉:

        1
        2
        3
        4
        OpenProcess

        作用
        通过已知 PID 获取程序的句柄;
        1
        2
        3
        4
        TerminateProcess

        作用
        通过进程句柄终止指定进程及其所有线程;
        1
        2
        3
        4
        5
        6
        7
        8
        9
        lstrcmpA

        作用
        区分大小写的字符串比较;

        lstrcmpi

        作用
        不区分大小写的字符串比较;
      • 至于其他几个,需要学习一下:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        CreateToolhelp32Snapshot

        作用
        给所有进程的详细信息拍摄快照,返回快照句柄供其他 API调用;
        参数
        dwFlags:用来指定“快照”中需要返回的对象;
        th32ProcessID:指定将要快照的进程ID;
        - 该参数只有在dwFlags设置了TH32CS_SNAPHEAPLIST或者TH32CS_SNAPMODULE后才有效;
        - 该参数为 0 则获取当前进程快照;
        - 其他情况下参数会被忽略,所有的进程都会被快照;
        1
        2
        3
        4
        Process32First

        作用
        获取进程快照第一个进程的句柄;
        1
        2
        3
        4
        Process32Next

        作用
        获取进程快照下一个进程的句柄;
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        FindWindowA

        参数
        lpClassName:指向类名的字符串或一个可以确定类名字符串的原子;
        lpWindowName:指向窗口名(即窗口标题)的字符串;
        作用
        检索并处理顶级窗口的类名和窗口标题匹配指定的字符串;
        不搜索子窗口;
        不区分大小写;
        如果有指定的类名或窗口标题则表示成功返回一个窗口的句柄;否则返回零;
    • 了解这些 API 后,重载并运行程序,程序中断后,分别给这些 API 设置断点并做好备注:

      设置断点并做好备注

    • 设置好所有断点后,再次运行程序,发现程序并不是和刚才一样中断在ExitProcess,而是中断在新设置的断点CreateToolhelp32Snapshot

      CreateToolhelp32Snapshot

      • 根据CreateToolhelp32Snapshot的用法,发现它的参数Flags并不是TH32CS_SNAPHEAPLIST 或 TH32CS_SNAPMODULE二者中的任何一个,那么它的参数将被忽略,所以,在这里它会获取所有进程的快照,并返回快照的句柄供其他 API 调用;

      • 接着,Ctrl + F9执行到返回,在EAX中有一个返回值,那么它是不是一个句柄呢?去句柄窗口看看:

        句柄窗口有

        可以看到,句柄窗口有一个相同的句柄,结果不言而喻;

    • 继续运行程序,程序再次中断,这次是中断在Process32First

      • 可以看到,它要获取的第一个进程的句柄是hSnapshot = 00000038,而它的第二参数是一个指向进程详细信息的指针,数据窗口中跟随一下:

        数据窗口中跟随

      • 接着Ctrl + F9执行到返回,可以看到,它获取到了信息并且信息一致:

        信息一致

    • 接着运行程序,程序中断在FindWindowA,而它给定的参数是Class = "OllyDbg":

      FindWindowA

      • 根据FindWindowA的用法如果有指定的类名或窗口标题则表示成功返回一个窗口的句柄;否则返回零;,如果接下来Ctrl + F9执行到返回后,EAX是非零的值,是不是意味着找到了与Class = "OllyDbg"指定的类名匹配的窗口,并且拿到了它的句柄?

      • 先来看看OD的类名与标题是什么,这里要借助一个软件Greatis WinDowse

        Greatis WinDowse需要安装;
        Greatis WinDowse的用法是:将鼠标悬停在需要检测的窗口的标题栏即可;

        类名与标题

        可以看到,检测到的标题与窗口标题完全一致,那类名不言而喻;

      • 接下来Ctrl + F9执行到返回后,发现EAX里的确有一个非零的值,如何证明它就是OD的句柄呢,再次打开Greatis WinDowse

        OD的句柄

        检测到的句柄与EAX的值完全相同,那OD可就危险了;

    • 到了这里,如果继续运行程序,肯定会获得一个程序结束的大礼包,有继续运行程序冲动的同学需要加强学习了,既然拿到了OD的句柄,也就是生杀大权,那么,应该跟着它,看看它拿着句柄要干什么?So,F8单步执行程序:

      来到了这里

      程序来到了这里,先不要执行代码,观察一下,如果没有猜错,下面的ExitProcess就是刚运行程序设置的第一个断点调用的位置;

      • 再来查看一下代码,前三行,两个比较一个跳转,既然找到了与指定类名相同的窗口,那么句柄也就是EAX肯定不为 0,所以第三行的跳转一定会成立;

      • 紧接着看看下面两个跳转,除了起始位置不同,都完美的跳过了ExitProcess

        两个跳转

      • 当然,以上都只是推论,代码并没有执行,如何证实推论?当然是将句柄也就是EAX置 0:

        EAX置0

      • 然后继续F8单步执行程序,果不其然,程序跳过了ExitProcess,来到了lstrcmpA

        字符串比较

        用获取到的第一个进程的标题名和给定字符串做比较;

      • 继续向下执行,逻辑瞬间清晰:

        逻辑清晰

        至此,终于理清了它反调试的套路:

        1. 首先使用CreateToolhelp32Snapshot获取进程快照;
        2. 接着使用Process32First获取进程快照第一个进程的句柄;
        3. 然后使用FindWindowA获取给定类名或标题的进程的句柄,如果获取成功,则使用句柄关闭这个程序的所有进程及线程;
        4. 如果使用FindWindowA获取失败,则通过Process32First获取的第一个进程的标题与给定字符串进行比较,相同则关闭这个程序;
        5. 如果第一个进程的信息比较不相同,则使用Process32Next获取进程快照的下一个进程,并用其标题与给定字符串比较,相同则关闭程序,不同则取下一个,直到进程快照中的所有进程比较完毕;
    • 既然已经跳过了这个程序对OD的检测,是不是意味着现在可以看到最开始的弹窗呢?禁用所有断点并运行程序:
      弹窗

      完工!

    • 不不不,没有完工,既然知道了这个程序的反调试原理,如何绕过呢,不能每次都手动吧?这里需要借助一个OD补丁程序re-pair

      补丁程序

      补丁程序会生成了一个名称随机的OD主程序,试试效果如何:

      完工啦

      再多句嘴,插件HideDebugger虽然也有FindWindow/EnumWindows选项,但只能绕过标题名检测,无法绕过类名检测:

      HideDebugger

      这次是真的完工了!