OllyDbg 反调试之检测进程名

使用工具

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

分析思路

  1. 本次内容学习 Windows API 才是重点;

  2. 打开CrackMe看看:

    打开软件

    既然是学习反调试,那么注册码当然不是重点,重点是它对OD的影响,打开OD,发现闪了一下然后被关闭了,嗯,反调试了;

    还有一个现象就是:给OD改个名后,就不会被反调试了,即使是OD载入程序并运行,也不会被反调试;

  3. 倒入OD开始分析:

    • 首先,Ctrl + N查看 API 列表,API 很多,搜索GetProcAddress设置 CC 断点;

      • 了解一下 Windows API GetProcAddress

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

        作用
        检索指定的动态链接库中的输出库函数地址;
        (获取函数列表中不显示且被调用的隐藏函数的地址;)
        (捕捉间接加载或调用的其他隐藏函数;)
        参数
        hModule:包含此函数的 DLL 模块的句柄;
        (程序的句柄)
        IpProcName:包含函数名的以 NULL 结尾的字符串;
        (程序的名字)
    • 接着,F9运行程序,程序会在设置断点的 API 上中断多次,每中断一次,都代表获取了一个隐藏的函数;

    • 在大约中断了 143 次后,对接下来获取到的 3 个重要的 API 设置 CC 断点:EnumProcessesEnumProcessesModulesGetModuleBaseNameA

      • 国际惯例,分别了解一下这几个 API :

        1
        2
        3
        4
        5
        6
        EnumProcesses

        作用
        检索进程列表中每一个进程的标识符;
        (枚举进程的 PID;)
        (获取进程列表所有进程的 PID;)
        1
        2
        3
        4
        5
        EnumProcessesModules

        作用
        检索指定进程中每个模块的句柄;
        (获取进程的基址;)
        1
        2
        3
        4
        5
        GetModuleBaseNameA

        作用
        检索指定模块的基本名称;
        (获取进程名;)
      • 设置断点的方法:

        程序中断后,在堆栈窗口确认是需要的 API 后,在函数名称所在行右键菜单选择复制到剪贴板,

        复制

        接着Ctrl + F9执行到返回,

        执行到返回

        然后bp EAX设置断点,

        设置断点

        在断点窗口双击断点进入汇编窗口,

        断点窗口

        在注释栏双击并粘贴复制的函数名,也就是给断点设置备注,

        注释

        断点设置完成;

        注释完成

        至于为什么要设置备注,如果有 10 个没有设置备注的断点,那就傻傻分不清了;

    • 这里不得不提一下 PID,它的全称是Process ID,通俗易懂嘛,进程 ID

      • 至于怎么查看 PID,打开任务管理器,选择进程列,就显示了当前所有程序的诸如程序名、PID、用户名等等信息:

        任务管理器

        而我们使用的OD也赫然在列;

      • 当然,如果你的进程列表中没有 PID 这一列,不要慌,你的电脑没问题,点击菜单栏中的查看按钮并选择选择列选项:

        选择列

      • 然后勾选PID复选框并确定,你的进程列表中就有 PID 这一列了:

        复选框

    • 了解并知道如何查看 PID 之后,就要进入正题了:

      以我的机器为例,OD的 PID 是1380,这是一个 10 进制数,要在OD里使用它,当然要转换为 16 进制:

      进制转换的方法很多,比如使用网页提供的进制转换器,使用系统内置的计算器,使用OD自带的进制转换,这里以OD为例:

      16进制

      随便双击一个寄存器,然后修改它的无符号值,十六进制栏就会显示对应的 16 进制数,这里OD的 PID 对应的 16 进制数就是564;当然,修改寄存器的值只是为了进制转换,一定不要点击确定哦;

    • 设置好 3 个函数的断点后,继续运行程序,程序会再次中断,不过,不再是中断在GetProcAddress,而是中断在新设置的第一个断点,也就是EnumProcesses

      • 我们已经了解了EnumProcesses的功能:获取进程列表所有进程的 PID;

      • 既然EnumProcesses不需要参数,那么堆栈窗口ESP + 4的位置,存储的就是它的返回值,数据窗口中跟随一下:

        数据窗口

      • 接着Ctrl + F9执行到返回,发现以基址为起始位置的部分地址的内容被覆盖了,而OD的 PID 也在其中:

        od16

    • 既然是要了解反调试对OD的影响,当然要跟随OD的数据了,在OD的 PID 上设置内存访问断点:

      内存访问断点

    • 继续运行程序,程序再次中断,一眼就看到了OD的 PID 被当作参数传递给了函数OpenProcess

      参数

      • OpenProcess这个函数的作用是什么呢,了解一下:

        1
        2
        3
        4
        OpenProcess

        作用
        通过已知 PID 获取程序的句柄;
      • OD危险了,因为这个CrackMe拿到了它的句柄,也就是它的生杀大权;

      • F8单步步过执行程序,看看是否如是:

        拿到句柄

        因为函数的返回值一般都在EAX,所以我们大胆猜测,EAX中应该就是OD的句柄,如何证明呢?去句柄窗口看看:

        句柄窗口

        句柄窗口中有一个数值相同的句柄,而这个函数是用来获取句柄的,又把OD的 PID 当作了参数,那么它返回的应该就是OD的句柄,所以可以确定,EAX中就是OD的句柄也就是000000C4;

    • 继续运行程序,程序再次中断,来到了我们设置的三个函数断点中的其二,也就是EnumProcessesModules:获取指定进程的基址

      获取基址

      在数据窗口中跟随存放函数执行结果的地址,然后Ctrl + F9执行到返回后,可以发现OD的基址是00400000:

      基址

    • 继续F9运行程序,程序再次中断,这次是GetModuleBaseNameA

      执行到返回发现,它通过进程和基址获取到了OD的名称:

      名称

    • 接下来就不能直接F9运行程序了,因为再运行下去程序可能就结束了,所以使用F8单步执行,看看它获取了进程名之后要做什么:

      • 咦,又一个把OD句柄当作参数传递的函数,了解一下:

        close

        1
        2
        3
        4
        CloseHandle

        作用
        通过已知对象的句柄关闭句柄;
      • 继续执行后,去句柄窗口查看,已经找不到OD的句柄了,说明它被关闭了;

      • 接着向下运行,发现一个把OD的名称当作参数的函数:

        名称

        使用F7步入跟进后,发现它是将OD的名称转换为 UpperCase,紧跟着下一个函数把转换后的名称和给定字符进行了比较:

        比较

      • 上一步执行完毕后,由于比较的结果相同,跳转失败,程序又重新通过OpenProcess使用OD的 PID 获取了OD的句柄:

        重新获取

      • 接着,把OD的句柄作为参数传递给TerminateProcess

        句柄当做参数

        1
        2
        3
        4
        TerminateProcess

        作用
        通过进程句柄终止指定进程及其所有线程;

        了解了这个 API 的用途后,继续运行程序,结果不言而喻:灵光一闪,程序关闭!