July 22, 2022

关于逆向工程核心原理-From TEB to PEB to SEH

之前一直没机会整理下这部分知识,这次刚好要做课件了,开始梳理一下。– 2022.7.22

TEB

Introduction Of TEB

TEB(Thread Environment Block)指的是线程环境块:

进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程

线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

MSDN给出的结构体,然而描述其实太过简单,想了解更多细节得借助WINDBG

image-20220722155319976

(借助WINDBG的符号文件可以查看TEB结构体的所有成员,太多了就不截图了)

Important Members Of The TEB Structure

TEB 结构体的成员多而复杂,在用户模式调试中起着重要作用的成员有2个

image-20220722160931981

ProcessEnvironmentBlock

首先是Offset 30处的 ProcessEnvironmentBlock 成员,它是指向PEB(Process Environment Block,进程环境块),结构体的指针。

PEB 是进程环境块,每个进程对应1个PEB结构体,一会再细说

NitTib

TEB 结构体的第一个成员为 NT_TIB 结构体(TIB 是 Thread Infomation Block 的简称,意味线程信息块)

结构体如下,比较关心的成员有

image-20220722192748806

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 段寄存器有关系!

image-20220722195435055

FS Segment Register

SDT

其实,FS 寄存器用来指示当前线程的 TEB 结构体

然而这种段寄存器只有十六位,如何在 IA-32 系统中32位的指针表示?

实际上,FS段寄存器并非直接指向 TEB 结构体的位置,它持有 SDT 的索引,而该索引持有实际 TEB 地址

SDT 位于内核内存区域,其地址存储在 GDTR (Global Descriptor Table Resiger,全局描述符号表)

由于段寄存器实际存储的是 SDT 的索引,所以它也被称为 “段选择符” (Segment Selector)

image-20220722202149185

SUMMARY

PEB

Introduction Of PEB

PEB (Process Environment Block,进程环境块),这是存放进程信息的结构体,尺寸非常大。

PEB Access Method

那么访问其实就是 FS:[30] 即可,可以有多种方法

1
2
3
4
MOV EAX, DWORD PTR FS:[30] ; FS[30] = ADDRESS OF PEB

MOV EAX, DWORD PTR FS:[18] ; FS[18] = ADDRESS OF TEB
MOV EAX, DWORD PTR FS:[EAX + 30] ; DS[EAX + 30] = ADDRESS OF PEB

Important Members Of The PEB Structure

MSDN 提供的 PEB 结构体如下

image-20220722204751164

(那么经过WINDBG可以详细查看 PEB 结构体成员,太长了就不贴了)

关注几个重点成员

image-20220722205015799

PEB.BeingDebugged

Kenerl32.dll 中有个名为 Kernel32IsDebuggerPresent() 的API,普通程序我们一般不用到

1
BOOL WINAPI IsDebuggerPresent(void);

该 API 函数用于判断当前进程是否处于调试状态,并返回判断结果。

该 API 通过检测 PEB.BeingDebuggered 成员确定是否正在调试进程(是,返回1;否,返回0)

image-20220722210750425

该图中

image-20220722211000787

PEB.ImageBaseAddress

PEB.ImageBaseAddress 成员用来表示进程的 ImageBase

GetModuleHandle() API 用来获取 ImageBase

1
HMODULE WINAPI GetModuleHandle(__in_opt LPCSTR lpModuleName)

向 lpModuleHandle 参数赋值为 NULL,调用 GetModuleHandle() 函数将返回进程被加载的 ImageBase

image-20220722211954276

PEB.Ldr

PEB.Ldr 成员是指向 _PEB_LDR_DATA 结构体的指针。借助 WinDbg 查看可知

image-20220722212920103

当模块(DLL)加载到进程后,通过 PEB.Ldr 成员可以直接获取该模块的加载地址,所以这是非常重要的成员。

_PEB_LDR_DATA 结构体成员中有3个 _LIST_ENTRY 成员,该又是个结构体

image-20220722213140086

从该结构体看出这是个双向链表,那么该链表又保存着什么信息?

_LDR_DATA_TABLE_ENTRY 结构体

image-20220722213432869

image-20220722213446471

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 会首先把异常抛给调试进程处理。

像这样,被调试者发生异常时,调试器就会暂停运行,必须采取某种措施,完成后继续调试

处理方法如下

1)直接修改异常:代码、寄存器、内存

修改引发异常的位置让其不再异常

2)将异常抛给 被调试者 处理

如果 被调试者 内部存在 SEH(异常处理函数)能够处理异常,那么异常通知会发送给 被调试者,由被调试器自行处理。

如果当前进程在被我们的调试器所调试,我们可以按 SHIFT + F7/F8/F9 命令可以直接将当前异常抛还给 被调试者

3)OS 默认的异常处理机制

若调试器与被调试者都无法处理(或故意不处理)当前异常,则 OS 的默认异常处理机制会触发,终止进程。

Exception

学习异常处理前,有必要了解操作系统中定义的异常

image-20220806131536651

EXCEPTION_ACCESS_VIOLATION (C000 0005)

试图访问不存在或不具有访问权限的内存区域,就会发生 EXCEPTION_ACCESS_VLOLATION(非法访问异常,该异常最常见)。

image-20220806212646438

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 异常

image-20220806213650384

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 结构体组成的链表

image-20220806231301323

Next 成员是指向下一个 _EXCEPTION_REGISTRATION_RECORD 结构体的指针,Handler 成员是异常处理函数(异常处理器)

若 Next 成员的值为 FFFF FFFF,则表示它是链表的最后一个结点

image-20220807180348413

异常发生时,该异常则会按照 (A)->(B)->(C)的顺序依次传递,直到有异常处理器处理

SEH 异常处理函数的定义 _except_handler

函数定义如下

image-20220807190056601

_except_handler-> pRecord

首先第一个参数的结构体为 EXCEPTION_RECORD

定义如下,关注两个重要成员

1
2
ExceptionCode // 异常类型
ExceptionAddress // 发生异常的地址

image-20220807190439883

_except_handler-> pContext

其次该结构体中的第三个参数是指向 CONTEXT 结构体的指针

CONTEXT 是用来保存 CPU 寄存器的值,因为多线程环境下需要这样做,当切换去其他线程时,需要保存当前的线程的 CONTEXT 结构体

image-20220807191220349

image-20220807191233700

于是有趣的事情来了,当异常发生时,执行异常代码的线程就会中断运行,转而执行 SEH,OS 就会把线程的 CONTEXT 发送给异常处理函数的相应参数。

该 CONTEXT 结构体的一个成员 EIP(偏移量:B8),注意该成员经常在反调试技术中应用

用法如,在异常处理函数中把 CONTEXT.EIP 设置为其他地址,然后成功返回异常处理函数,这时候就成功执行到了我设置的新 EIP 的位置。

EXCEPTION_DISPOSITION

该枚举类型具体含义

成功处理异常后返回 ExceptionContinueExecution(0)

当前异常无法处理异常返回 ExceptionContinueSearch(1)

image-20220807192403802

TEB.NtTib.ExceptionList

通过 TEB 结构体的 NitTib 成员可以直接访问到 SEH 链

1
TEB.NtTib.ExceptionList = FS:[0]

SEH 安装方法

C语言中通过使用 try except finally,而汇编中更加轻松

1
2
3
PUSH @MyHandler	; 要添加的异常处理器
PUSH DWORD PTR FS:[0] ; 原先的异常列表
MOV DWORD PTR FS:[0], ESP ; 将添加后的链表设置到链表

这就是把自身的异常处理器添加到已有的 SEH 链表

从技术层面来讲就是将自身的 EXCEPTION_REGISRATION_RECORD 结构体链接到 EXCEPTION_REGISTATION_RECORD 结构体链表

Debugger SEH

首先是异常处理函数的注册,这里相当于直接添加一个自己写的

image-20220807205757155

接下来的代码就是触发异常,现在可以自己处理异常,或者 SHIFT + F9 将异常抛给被调试进程处理

(记得此时要在异常处理函数 40105A 下个断点,因为调试器会一直跑完整个程序)

image-20220808225953086

随后调试异常处理函数

image-20220808230600986

随后删除已注册的 SEH 函数

image-20220808230827124

最后执行的到这就会跳回设置的当时所存放的context

image-20220808230929523

Hello :)

image-20220808231159924

今天被老师一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 异常处理捕获

image-20220810133924996

(关于 EXCEPTION_EXECUTE_HANDLER 的更多在微软的文档里说明)

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.