OllyDbg 反调试之检测进程名&窗口类名&窗口标题名
使用工具
- OllyDbg 1.10原版,简称
OD
; OD
汉化
和插件
均来自互联网;OD
补丁程序re-pair;- 类名检测工具Greatis WinDowse;
- CrackMe来自互联网,仅供学习使用;
- 文中特殊数字均是
HEX
,为了书写方便采用DEC
;
分析思路
重点依然是学习 Windows API;
打开
CrackMe
看看:如果没有打开
OD
,程序可以打开并显示如上,如果打开了OD
,程序则无法打开;倒入
OD
开始分析:首先,
Ctrl + N
查看一下 API 列表,很意外,出奇的干净,只有区区两个 API:还好有一个我们认识的
ExitProcess
:1
2
3
4ExitProcess
作用
结束调用的进程及其所有的线程;那就只能从它下手了:设置 CC 断点并设置好备注;
接着,运行程序,程序会中断在我们设置的断点
ExitProcess
函数的行首,然后在堆栈窗口
右键反汇编窗口中跟随
来到调用这个函数的位置:这里很困惑?困惑的同学没学懂,再复习一下 API 吧;
常规操作是:
Ctrl + F9
执行到返回,然后看它获取的数据,再进行下一步操作;然鹅,别忘了
ExitProcess
的功能:结束进程及其所有的线程。如果我们
Ctrl + F9
执行到返回,是不是意味着我们手动执行了结束进程的函数,那么,这个断点的意义是什么?所以,我们要跟随函数到调用它的地方,往上看,为什么要调用它?
来到
反汇编窗口
,发现调用位置的同时,也发现了很多 API,其中大部分都不认识,但是有一根救命稻草GetProcAddress
:既然 API 列表没有内容的同时发现了
GetProcAddress
,那是不是说明,大多数函数都被隐藏了,而GetProcAddress
就是关键呢?再来复习一下
GetProcAddress
的功能:1
2
3
4
5
6
7
8
9
10
11GetProcAddress
作用
检索指定的动态链接库中的输出库函数地址;
(获取函数列表中不显示且被调用的隐藏函数的地址;)
(捕捉间接加载或调用的其他隐藏函数;)
参数
hModule:包含此函数的 DLL 模块的句柄;
(程序的句柄)
IpProcName:包含函数名的以 NULL 结尾的字符串;
(程序的名字)毫无疑问,给
GetProcAddress
设置断点并做好备注,看看都有哪些隐藏的函数:然后重载并运行程序后程序会中断,接着多次运行直到中断在了
ExitProcess
后,发现只是调用了以下隐藏函数:CreateToolhelp32Snapshot
OpenProcess
Process32First
Process32Next
TerminateProcess
lstrcmpA
FindWindowA
;其中,
OpenProcess
TerminateProcess
lstrcmpA
很熟悉:1
2
3
4OpenProcess
作用
通过已知 PID 获取程序的句柄;1
2
3
4TerminateProcess
作用
通过进程句柄终止指定进程及其所有线程;1
2
3
4
5
6
7
8
9lstrcmpA
作用
区分大小写的字符串比较;
lstrcmpi
作用
不区分大小写的字符串比较;至于其他几个,需要学习一下:
1
2
3
4
5
6
7
8
9
10CreateToolhelp32Snapshot
作用
给所有进程的详细信息拍摄快照,返回快照句柄供其他 API调用;
参数
dwFlags:用来指定“快照”中需要返回的对象;
th32ProcessID:指定将要快照的进程ID;
- 该参数只有在dwFlags设置了TH32CS_SNAPHEAPLIST或者TH32CS_SNAPMODULE后才有效;
- 该参数为 0 则获取当前进程快照;
- 其他情况下参数会被忽略,所有的进程都会被快照;1
2
3
4Process32First
作用
获取进程快照第一个进程的句柄;1
2
3
4Process32Next
作用
获取进程快照下一个进程的句柄;1
2
3
4
5
6
7
8
9
10FindWindowA
参数
lpClassName:指向类名的字符串或一个可以确定类名字符串的原子;
lpWindowName:指向窗口名(即窗口标题)的字符串;
作用
检索并处理顶级窗口的类名和窗口标题匹配指定的字符串;
不搜索子窗口;
不区分大小写;
如果有指定的类名或窗口标题则表示成功返回一个窗口的句柄;否则返回零;
了解这些 API 后,重载并运行程序,程序中断后,分别给这些 API 设置断点并做好备注:
设置好所有断点后,再次运行程序,发现程序并不是和刚才一样中断在
ExitProcess
,而是中断在新设置的断点CreateToolhelp32Snapshot
:根据
CreateToolhelp32Snapshot
的用法,发现它的参数Flags
并不是TH32CS_SNAPHEAPLIST 或 TH32CS_SNAPMODULE
二者中的任何一个,那么它的参数将被忽略,所以,在这里它会获取所有进程的快照,并返回快照的句柄供其他 API 调用;接着,
Ctrl + F9
执行到返回,在EAX
中有一个返回值,那么它是不是一个句柄呢?去句柄窗口
看看:可以看到,
句柄窗口
有一个相同的句柄,结果不言而喻;
继续运行程序,程序再次中断,这次是中断在
Process32First
:可以看到,它要获取的第一个进程的句柄是
hSnapshot = 00000038
,而它的第二参数是一个指向进程详细信息的指针,数据窗口
中跟随一下:接着
Ctrl + F9
执行到返回,可以看到,它获取到了信息并且信息一致:
接着运行程序,程序中断在
FindWindowA
,而它给定的参数是Class = "OllyDbg"
:根据
FindWindowA
的用法如果有指定的类名或窗口标题则表示成功返回一个窗口的句柄;否则返回零;
,如果接下来Ctrl + F9
执行到返回后,EAX
是非零的值,是不是意味着找到了与Class = "OllyDbg"
指定的类名匹配的窗口,并且拿到了它的句柄?先来看看
OD
的类名与标题是什么,这里要借助一个软件Greatis WinDowse
:Greatis WinDowse
需要安装;Greatis WinDowse
的用法是:将鼠标悬停在需要检测的窗口的标题栏即可;可以看到,检测到的标题与窗口标题完全一致,那类名不言而喻;
接下来
Ctrl + F9
执行到返回后,发现EAX
里的确有一个非零的值,如何证明它就是OD
的句柄呢,再次打开Greatis WinDowse
:检测到的句柄与
EAX
的值完全相同,那OD
可就危险了;
到了这里,如果继续运行程序,肯定会获得一个程序结束的大礼包,有继续运行程序冲动的同学需要加强学习了,既然拿到了
OD
的句柄,也就是生杀大权,那么,应该跟着它,看看它拿着句柄要干什么?So,F8
单步执行程序:程序来到了这里,先不要执行代码,观察一下,如果没有猜错,下面的
ExitProcess
就是刚运行程序设置的第一个断点调用的位置;再来查看一下代码,前三行,两个比较一个跳转,既然找到了与指定类名相同的窗口,那么句柄也就是
EAX
肯定不为 0,所以第三行的跳转一定会成立;紧接着看看下面两个跳转,除了起始位置不同,都完美的跳过了
ExitProcess
:当然,以上都只是推论,代码并没有执行,如何证实推论?当然是将句柄也就是
EAX
置 0:然后继续
F8
单步执行程序,果不其然,程序跳过了ExitProcess
,来到了lstrcmpA
:用获取到的第一个进程的标题名和给定字符串做比较;
继续向下执行,逻辑瞬间清晰:
至此,终于理清了它反调试的套路:
- 首先使用
CreateToolhelp32Snapshot
获取进程快照; - 接着使用
Process32First
获取进程快照第一个进程的句柄; - 然后使用
FindWindowA
获取给定类名或标题的进程的句柄,如果获取成功,则使用句柄关闭这个程序的所有进程及线程; - 如果使用
FindWindowA
获取失败,则通过Process32First
获取的第一个进程的标题与给定字符串进行比较,相同则关闭这个程序; - 如果第一个进程的信息比较不相同,则使用
Process32Next
获取进程快照的下一个进程,并用其标题与给定字符串比较,相同则关闭程序,不同则取下一个,直到进程快照中的所有进程比较完毕;
- 首先使用
既然已经跳过了这个程序对
OD
的检测,是不是意味着现在可以看到最开始的弹窗呢?禁用所有断点并运行程序:完工!
不不不,没有完工,既然知道了这个程序的反调试原理,如何绕过呢,不能每次都手动吧?这里需要借助一个
OD
补丁程序re-pair
:补丁程序会生成了一个名称随机的
OD
主程序,试试效果如何:再多句嘴,插件
HideDebugger
虽然也有FindWindow/EnumWindows
选项,但只能绕过标题名检测,无法绕过类名检测:这次是真的完工了!