OllyDbg 反调试之 ProcessHeap & NTGlobalFlag
使用工具
- OllyDbg 1.10原版,简称
OD
; OD
插件HideDebugger;OD
汉化
和插件
均来自互联网;- CrackMe来自互联网,仅供学习使用;
- 文中特殊数字均是
HEX
,为了书写方便采用DEC
;
分析思路
使用搜索引擎搜寻良久并未找到通俗易懂的说明,在CTF Wiki上发现了描述,但无奈自己功力尚浅,无法透彻理解,遂将链接奉上,感兴趣的同学可以去学习一下NtGlobalFlag,Heap Flags;
文中使用的
CrackMe
无特定要求,可以使用任意一款;了解一下两个标志,以下描述引用自CTF Wiki:
1
2
3
4
5
6
7NtGlobalFlag
描述
1. 在 32 位机器上, NtGlobalFlag字段位于PEB(进程环境块)0x68的偏移处, 64 位机器则是在偏移0xBC位置.
2. 该字段的默认值为 0.
3. 当调试器正在运行时, 该字段会被设置为一个特定的值.
4. 尽管该值并不能十分可信地表明某个调试器真的有在运行, 但该字段常出于该目的而被使用呃,至于
ProcessHeap
,实在是总结不出具体的描述性语言,感兴趣的同学去CTF Wiki看吧;接下来就用实例来说明一下这两个标志的用法:
首先选择一个
CrackMe
倒入OD
:之前学习
IsDebuggerPresent
的时候学习到:如果程序倒入OD
之后没有运行,那么EBX
寄存器指向的位置就是IsDebuggerPresent
检测的位置,用代码表示就是: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 ; 返回在这里简单计算一下,就当作复习之前的知识了,以这个
CrackMe
为例:手动计算一下
IsDebuggerPresent
要检测的位置:MOV EAX,DWORD PTR FS:[18]
:FS 寄存器
从第 18 位开始,存放的是该寄存器从起始位置依次向后的地址,即第 18 位存放的是起始位置的地址,也就是说,在这里,FS:[18] == 7FFDF000
;MOV EAX,DWORD PTR DS:[EAX+30]
:这个很好计算嘛,[EAX+30] == 7FFDF030
,使用Ctrl + G
跟随一下这个地址:7FFDF030
中存储的地址不正是EBX
中的地址嘛;
回顾完之前的内容,开始学习本次内容的第一个标志
NtGlobalFlag
:根据对
NtGlobalFlag
描述可知:在 32 位机器上, NtGlobalFlag 字段位于 PEB(进程环境块)0x68 的偏移处;首先,什么是 PEB?查询了许久之后又引出了 TEB,不太理解,但找到一些感觉描述很通俗的话:
TEB:即 Thread Environment Block ,它记录相关线程的信息,每一个线程都有自己的 TEB,每个 TEB 都有自己的 TIB(Thread Information Block),即线程信息块,每当创建一个线程,系统均会为每个线程分配 TEB,而且 TEB 永远放在 FS 寄存器指定的数据段的 0 偏移处;
PEB:即(Process Envirorment Block Structure),英文翻译过来就是进程环境信息块,在你进行打开这个程序或以调试方式打开这个程序,那么操作系统会对你这个进程的进程环境块的一些标志设置一系列的属性,如果你使用系统的调试方法,那么系统就会把相对应的标志位给设置上;
TEB 结构体位于 FS 段选择符所指的段内存的起始地址处,也就是 0 偏移处;
PEB 成员位于距 TEB 结构体 Offset 30 的位置,也就是 0x30 偏移处;
那么就有两种获取 PEB 的方法:
- 既然 TEB 位于
FS 寄存器
0 偏移处,在本文中就是7FFDF000
,而 PEB 位于距 TEB 结构体 0x30 偏移处,那么是不是可以直接理解为:PEB 位于FS 寄存器
0x30 偏移处,即FS:[30]
就是 PEB,在本文中就是7FFDF030
; - TEB 位于
FS 寄存器
0 偏移处,在本文中就是7FFDF000
,如何获取FS 寄存器
0 偏移处即文中的7FFDF000
呢?当然是FS:[18]
了,既然拿到 TEB 本身,如何获取 PEB 呢?方法就和上面复习的一样了;
- 既然 TEB 位于
既然找到了 PEB 的位置,而根据对
NtGlobalFlag
的定义得知,它位于 PEB 0x68 偏移处;本文中 PEB 为
7FFDF030
地址中的数据7FFD8000
,也就是初始EBX
指向的地址,再偏移 0x68 计算为7FFD8068
,也就是说,如果7FFD8068
地址的数据不为 0,则表示检测到正在调试程序:整理一下代码的实现:
1
2
3
4
5MOV EAX,DWORD PTR FS:[18] ; 将 FS:[18] 中的地址拷贝到 EAX
MOV EAX,DWORD PTR DS:[EAX+30] ; 将 EAX 中的地址加 0x30 之后的地址拷贝到 EAX
MOV EAX,DWORD PTR DS:[EAX+68] ; 将 EAX 中的地址加 0x68 之后的数据拷贝到 EAX
OR EAX,EAX ; 是否为 0
RETN ; 返回验证一下:
再来说说第二个标志
ProcessHeap
:对这个标志的了解少之又少,暂时没有查到直接关联的资料,只能大概总结一下:
位于 PEB 偏移 0x18 处,本文中 PEB 是
7FFD8000
,偏移 0x18 之后是7FFD8018
;把偏移后的地址中的 DWORD 长度的数据作为地址,偏移 0x10,即对本文的
7FFD8018
中的数据00140000
偏移 0x10;如果这个位置的数据不为 0,即本文的
00140010
不为 0,则表示检测到正在调试程序:整理一下代码的实现:
1
2
3
4
5
6MOV EAX,DWORD PTR FS:[18] ; 将 FS:[18] 中的地址拷贝到 EAX
MOV EAX,DWORD PTR DS:[EAX+30] ; 将 EAX 中的地址加 0x30 之后的地址拷贝到 EAX
MOV EAX,DWORD PTR DS:[EAX+18] ; 将 EAX 中的地址加 0x18 之后的数据拷贝到 EAX
MOV EAX,DWORD PTR DS:[EAX+10] ; 将 EAX 中的地址加 0x10 之后的数据拷贝到 EAX
OR EAX,EAX ; 是否为 0
RETN ; 返回验证一下:
两个标志分析完毕,接下来就要说说如何绕过了,不能每次都手动修改吧;
这里需要借助两个
OD
插件HideDebugger
和HideOD
;设置如下图:
细心的同学会发现,这里的设置和上节课一模一样,是的,设置相同,但是重点不同;
虽然之前设置了某些选项,但只是照猫画虎,不知其意,相信学完这节内容,就会理解这样设置的意图;
验证一下插件是否生效:
程序倒入
OD
不要运行,在数据窗口
中跟随EBX
:可以在
数据窗口
直观的看到,IsDebuggerPresent
和NtGlobalFlag
检测的位置已经成功置 0,绕过检测,至于ProcessHeap
,Ctrl + G
跟随地址查看:
聊完两个标志,再来聊聊另外一个知识点:一个崩溃:
一个崩溃不是说 Windows 的 API 又有特殊的用途或参数,或者说是开发人员思路清奇把 API 用出了花儿,而是
OD
的 bug:当被调试的程序通过 OutputDebugString 输出超长的一串调试字符串的时候,OllyDbg 无法处理导致崩溃;