之前一直没机会整理下这部分知识,这次刚好要做课件了,开始梳理一下。– 2022.7.22
TEB
Introduction Of TEB
TEB(Thread Environment Block)指的是线程环境块:
- 该结构体包含进程中运行线程的各种信息
- 进程中的每个线程都有对应的TEB结构体
进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程
线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
MSDN给出的结构体,然而描述其实太过简单,想了解更多细节得借助WINDBG
(借助WINDBG的符号文件可以查看TEB结构体的所有成员,太多了就不截图了)
Important Members Of The TEB Structure
TEB 结构体的成员多而复杂,在用户模式调试中起着重要作用的成员有2个
ProcessEnvironmentBlock
首先是Offset 30处的 ProcessEnvironmentBlock 成员,它是指向PEB(Process Environment Block,进程环境块),结构体的指针。
PEB 是进程环境块,每个进程对应1个PEB结构体,一会再细说
NitTib
TEB 结构体的第一个成员为 NT_TIB 结构体(TIB 是 Thread Infomation Block 的简称,意味线程信息块)
结构体如下,比较关心的成员有
ExceptionList 指向 _EXCEPTION_REGISTRATION_RECORD 结构体组成的链表,就是Windows OS的SEH
Self 指向 _NT_TIB 结构体自引用指针
- 也是 TEB 结构体指针(因为 TEB 结构体的第一个成员就是 _NT_TEB 结构体)
TEB Access Method
借助 WINDBG 很容易访问 TEB 结构体,那么我们在用户模式下怎么问题?
通过 OS 提供的相关 API 访问
PS: 调试的地址会随计算机环境的不同而不同
Ntdll.NtCurrentTeb()
Ctrl + G 或者 Search for Name in all modules 跳到该API
可以发现 FS:[18] 就是返回TEB地址,这就是就是Self指针
这里我们可以得知 TEB 与 FS 段寄存器有关系!
FS Segment Register
SDT
其实,FS 寄存器用来指示当前线程的 TEB 结构体
然而这种段寄存器只有十六位,如何在 IA-32 系统中32位的指针表示?
实际上,FS段寄存器并非直接指向 TEB 结构体的位置,它持有 SDT 的索引,而该索引持有实际 TEB 地址
SDT 位于内核内存区域,其地址存储在 GDTR (Global Descriptor Table Resiger,全局描述符号表)
由于段寄存器实际存储的是 SDT 的索引,所以它也被称为 “段选择符” (Segment Selector)
SUMMARY
FS[0x18] = TEB 起始地址
FS[0x30] = PEB 起始地址
FS[0] = SEH 起始地址
PEB
Introduction Of PEB
PEB (Process Environment Block,进程环境块),这是存放进程信息的结构体,尺寸非常大。
PEB Access Method
那么访问其实就是 FS:[30] 即可,可以有多种方法
1 | MOV EAX, DWORD PTR FS:[30] ; FS[30] = ADDRESS OF PEB |
Important Members Of The PEB Structure
MSDN 提供的 PEB 结构体如下
(那么经过WINDBG可以详细查看 PEB 结构体成员,太长了就不贴了)
关注几个重点成员
PEB.BeingDebugged
Kenerl32.dll 中有个名为 Kernel32IsDebuggerPresent() 的API,普通程序我们一般不用到
1 | BOOL WINAPI IsDebuggerPresent(void); |
该 API 函数用于判断当前进程是否处于调试状态,并返回判断结果。
该 API 通过检测 PEB.BeingDebuggered 成员确定是否正在调试进程(是,返回1;否,返回0)
该图中
- 获取 FS:[18] 的 TEB 地址
- 然后通过 DS:[TEB + 30] 处的 TEB.Process-EnvironmentBlock 成员访问 PEB 结构体
- 查看 PEB.BeingDebuggered 成员的地址的值
PEB.ImageBaseAddress
PEB.ImageBaseAddress 成员用来表示进程的 ImageBase
GetModuleHandle() API 用来获取 ImageBase
1 | HMODULE WINAPI GetModuleHandle(__in_opt LPCSTR lpModuleName) |
向 lpModuleHandle 参数赋值为 NULL,调用 GetModuleHandle() 函数将返回进程被加载的 ImageBase
PEB.Ldr
PEB.Ldr 成员是指向 _PEB_LDR_DATA 结构体的指针。借助 WinDbg 查看可知
当模块(DLL)加载到进程后,通过 PEB.Ldr 成员可以直接获取该模块的加载地址,所以这是非常重要的成员。
_PEB_LDR_DATA 结构体成员中有3个 _LIST_ENTRY 成员,该又是个结构体
从该结构体看出这是个双向链表,那么该链表又保存着什么信息?
_LDR_DATA_TABLE_ENTRY 结构体
每个加载到进程中的 DLL 模块都有与之相应的 _LDR_DATA_TABLE_ENTRY 结构体
这些结构体互相链接,最终形成 _LIST_ENTRY 双向链表
需要注意的是,_PEB_LDR_DATA 结构体中存在3种链表
- 也就是说,存在多个 _LDR_DATA_TABLE_ENTRY 结构体,并且有三种方法链接起来
PEB.ProcessHeap & PEB.NtGlobalFlag
这两个成员都是应用于反调试,若进程处于调试状态,两个成员都有特定值。
SEH
Introduction Of SEH
SEH 是 Windows 操作系统默认的异常处理机制,在程序源代码中使用 try except finally 关键字来具体实现
SEH 与 Windows 中的 try、catch 具有不同结构!!
When An Exception Occurs
发生异常时调试器运行
会出现警告语句
“Access violation when writing to [00000000]- use Shift+F7/F8/F9 to pass exception to program·在内存0处发生写入异常,若想将异常抛给程序,请使用Shift+F7/F8/F9组合键。
OS Exception Handle
同一程序在正常运行时与调试运行时表现出的行为行动是不同的,这是由于 Windows OS 的异常处理方法不同
正常运行时的异常处理方法
- 进程运行时若发生异常,OS 委托进程进行处理,若进程代码中存在具体的异常处理代码,则能顺利完成,程序继续运行
- 但如果进行内部没有具体的实现 SEH,OS 就会启动默认的异常处理机制,终止程序运行
调试运行时的异常处理方法
若被调试进程内部发生异常,OS 会首先把异常抛给调试进程处理。
调试器几乎拥有被调试者的所有权限
需要特别指出的是,被调试者内部发生的所有异常(错误)都由调试器处理
所以调试过程中发生的所有异常(错误)都要由调试器管理(被调试者的 SEH 依据优先顺序推给调试器)
像这样,被调试者发生异常时,调试器就会暂停运行,必须采取某种措施,完成后继续调试
处理方法如下
1)直接修改异常:代码、寄存器、内存
修改引发异常的位置让其不再异常
2)将异常抛给 被调试者 处理
如果 被调试者 内部存在 SEH(异常处理函数)能够处理异常,那么异常通知会发送给 被调试者,由被调试器自行处理。
如果当前进程在被我们的调试器所调试,我们可以按 SHIFT + F7/F8/F9 命令可以直接将当前异常抛还给 被调试者
3)OS 默认的异常处理机制
若调试器与被调试者都无法处理(或故意不处理)当前异常,则 OS 的默认异常处理机制会触发,终止进程。
Exception
学习异常处理前,有必要了解操作系统中定义的异常
EXCEPTION_ACCESS_VIOLATION (C000 0005)
试图访问不存在或不具有访问权限的内存区域,就会发生 EXCEPTION_ACCESS_VLOLATION(非法访问异常,该异常最常见)。
EXCEPTION_BREAKPOINT (8000 0003)
在运行代码中设置断点后,CPU 尝试执行该地址处的指令时,将会发生 EXCEPTION_BREAKPOINT 异常
INT3
设置该断点的汇编指令为 INT3,对应机器码(IA-32 指令) 0xCC
调试器就是利用该功能实现断点功能
EXCEPTION_ILLGAL_INSTRUCTION (C000 001D)
CPU 遇到无法解析的指令的时候,比如 “0FFF” 指令在 x86 CPU 中未定义,CPU遇到该指令将引发 EXCEPTION_ILLEGAL_INSTRUCTION 异常
EXCETION_INT_DIVIDE_BY_ZERO (C000 0094)
INTEGER(整数)除法运算中,若分母为0(即除0)。
EXCEPTION_SINGLE_STEP (8000 0004)
Single Step(单步)的含义是执行一条指令,然后暂停。
CPU 进入单步模式后,每执行一条指令就会引发 EXCEPTION_SINGLE_STEP 异常,暂停运行。
将 EFLAGS 寄存器的 TF (Trap Flag, 陷阱标志) 位设置为0,CPU就会进入单步工作模式。
SEH 链
SEH 以链的形式存在,第一个异常处理器中若未处理相关异常,就会传递到下个异常处理器,直到得到处理。
从技术层面来看,SEH 是 _EXCEPTION_REGISTRATION_RECORD 结构体组成的链表
Next 成员是指向下一个 _EXCEPTION_REGISTRATION_RECORD 结构体的指针,Handler 成员是异常处理函数(异常处理器)
若 Next 成员的值为 FFFF FFFF,则表示它是链表的最后一个结点
异常发生时,该异常则会按照 (A)->(B)->(C)的顺序依次传递,直到有异常处理器处理
SEH 异常处理函数的定义 _except_handler
函数定义如下
- 异常处理函数接收这4个参数
- 返回值为 EXCEPTION_DISPOSITION 的枚举类型
_except_handler-> pRecord
首先第一个参数的结构体为 EXCEPTION_RECORD
定义如下,关注两个重要成员
1 | ExceptionCode // 异常类型 |
_except_handler-> pContext
其次该结构体中的第三个参数是指向 CONTEXT 结构体的指针
CONTEXT 是用来保存 CPU 寄存器的值,因为多线程环境下需要这样做,当切换去其他线程时,需要保存当前的线程的 CONTEXT 结构体
于是有趣的事情来了,当异常发生时,执行异常代码的线程就会中断运行,转而执行 SEH,OS 就会把线程的 CONTEXT 发送给异常处理函数的相应参数。
该 CONTEXT 结构体的一个成员 EIP(偏移量:B8),注意该成员经常在反调试技术中应用
用法如,在异常处理函数中把 CONTEXT.EIP 设置为其他地址,然后成功返回异常处理函数,这时候就成功执行到了我设置的新 EIP 的位置。
EXCEPTION_DISPOSITION
该枚举类型具体含义
成功处理异常后返回 ExceptionContinueExecution(0)
当前异常无法处理异常返回 ExceptionContinueSearch(1)
TEB.NtTib.ExceptionList
通过 TEB 结构体的 NitTib 成员可以直接访问到 SEH 链
1 | TEB.NtTib.ExceptionList = FS:[0] |
SEH 安装方法
C语言中通过使用 try except finally,而汇编中更加轻松
1 | PUSH @MyHandler ; 要添加的异常处理器 |
这就是把自身的异常处理器添加到已有的 SEH 链表
从技术层面来讲就是将自身的 EXCEPTION_REGISRATION_RECORD 结构体链接到 EXCEPTION_REGISTATION_RECORD 结构体链表
Debugger SEH
首先是异常处理函数的注册,这里相当于直接添加一个自己写的
接下来的代码就是触发异常,现在可以自己处理异常,或者 SHIFT + F9 将异常抛给被调试进程处理
(记得此时要在异常处理函数 40105A 下个断点,因为调试器会一直跑完整个程序)
随后调试异常处理函数
随后删除已注册的 SEH 函数
最后执行的到这就会跳回设置的当时所存放的context
Hello :)
今天被老师一carry刚觉得很轻松的心又有些吊起来了,继续加油8 –2022.8.8
CTF SEH
本来该笔记已经结束,但是 CTF 中的 SEH 好像并不是这个形式,不过意思都是一样的,于是我要补充一个知识点,就是 IDA 中的 SEH 识别与确认执行流
参考文章
https://bbs.pediy.com/thread-252152.htm
https://docs.microsoft.com/en-us/windows/win32/debug/exception-handler-syntax
要注意IDA自从生成的 filter
4016D1 的 filter 返回1,即 EXCEPTION_EXECUTE_HANDLER,意思是捕获该异常,也就是无论发生什么异常,都会被 401687 异常处理捕获
(关于 EXCEPTION_EXECUTE_HANDLER 的更多在微软的文档里说明)
About this Post
This post is written by P.Z, licensed under CC BY-NC 4.0.