OllyDbg 反调试之 IsDebuggerPresent
使用工具
- OllyDbg 1.10原版,简称
OD
; OD
汉化
和插件
均来自互联网;- CrackMe来自互联网,仅供学习使用;
- 文中特殊数字均是
HEX
,为了书写方便采用DEC
;
分析思路
首先,打开软件到处点点,逆向一个软件起码要会用吧,不会用就没必要说逆向了:
- ID 不可输入,应该是根据电脑硬件的某些特征生成的;
- 输入随机注册码点击 Check 之后,没有任何反馈;
倒入
OD
开始分析:首先按下
Ctrl + A
让OD
分析一下代码;既然程序有输入,有按钮,那可用的 API 可就多了。按下
Ctrl + N
查看使用了哪些API
,用的API
不多,一眼就看到了GetWindowTextA
,在GetWindowTextA
函数上设置断点,然后去断点窗口
双击断点进入反汇编窗口
给断点设置备注,这是一个好的习惯,请保持;然后
F9
运行程序,嘿嘿,程序窗口一闪而过,而且OD
的右下角也显示了程序的状态:已终止
,;如果说程序损坏的话,那么刚开始的时候应该也是打不开的;
如果说
OD
出现了问题,那么程序应该倒入不进来,而且查看不了 API 列表;到底是什么问题呢?不绕弯子,程序有反调试,至于是如何反调试的?它就是今天的主角儿
IsDebuggerPresent
:先了解一下这个 API:
1
2
3
4
5
6
7IsDebuggerPresent
作用
确定调用进程是否由用户模式的调试器调试。
返回值
如果当前进程运行在调试器的上下文,返回值为非零值。
如果当前进程没有运行在调试器的上下文,返回值为零。看着好绕,大白话:检测当前程序是否正在被调试,没有被调试返回 0,只要返回值不是 0,那就表示程序正在被调试;
既然知道它是一个 Windows API,那我们不妨按下
Ctrl + N
去函数列表看看这个程序是否调用了IsDebuggerPresent
:果然,在函数列表中发现了
IsDebuggerPresent
,不过,怎么证明它被调用了,而不是放在函数列表中迷惑我们呢?很简单,给它设置一个断点,然后运行程序,没有意外,程序中断了,那就说明程序调用了这个 API:
我们都知道
API 断点
会中断在函数的行首,也就是说,IsDebuggerPresent
这个函数并没有执行,如果Ctrl + F9
执行到返回,这个函数才算执行完毕了,而一旦它执行完毕,就意味着程序是否被调试它已经检测完成了,但从代码上看,只有区区 4 行,它是如何检测的呢,分析一下;分析一下这 4 行代码:
1
2
3
4MOV 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:[18]
的地址是7FFDF018
,按下F7
,单步执行代码,同时查看EAX 寄存器
,丝毫不差,FS:[18]
存放的正是FS 寄存器
的起始地址7FFDF000
,既然第 1 行代码搞定,那就开始分析第二行;第 2 行代码
MOV EAX,DWORD PTR DS:[EAX+30]
:将 EAX 中的地址加 0x30 之后的地址拷贝到EAX
;与上一步相同,按下
Ctrl + G
跟随7FFDF030
:没有意外,运行结果与我们分析的一致:
第 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
,再运行一下下,结束!