SEH概述
SEH是异常处理结构体,是异常处理机制的重要数据结构。每个SEH包含两个DWORD指针:SEH链表指针和异常处理函句柄。
SEH结构体存储在系统栈中,一般会同时存在多个SEH,他们串成单向列表。
当异常发生时,首先从TEB的0字节偏移处取出最近的SEH。
实例
使用环境 windows 2000
在test的函数栈里安装一个SEH结构,如果发生异常,则会调用这个异常处理。异常处理机制和堆的分配机制一样会检查进程是否处于调试状态,所以加一个断点来attach附加调试。
也可以对堆进行异常处理的应用,将堆的目标地址改为SEH中句柄的地址,将其替换为shellcode地址。
深入理解Windows异常处理
不同级别的异常处理
异常处理的最小作用域是线程,每个线程的都有自己的SEH链表。
首先执行的是线程中离栈顶最近的SEH异常处理函数,依次执行SEH链表中的函数。若解决不了,进程的SEH来处理。若仍然失败,系统默认的SEH被调用,弹出一个程序崩溃的对话框。
线程的异常处理
线程中用于处理异常的回调函数有四个参数
pExcept:包含了若干与异常相关的信息如异常的类型,异常发生的地址
pFrame:指向战阵的SEH结构体
pContext:指向Context结构体,包含了所有寄存器的状态
pDispatch:未知
回调函数返回值0,异常被成功处理。返回值为1,异常处理失败,顺着SEH链表搜索其他用于异常处理的函数。
还有unwind操作,实际上是对SEH链调用了两遍,第二遍是通知前边异常处理失败的SEH释放资源。
进程的异常处理
API函数SetUnhandledExceptionFilter来注册。
返回值:1,正确处理,程序退出
0,无法处理,转交给系统默认处理
-1,正确处理,继续执行下去
系统默认的异常处理
UnhandledExceptionFilter(),所有无法处理的异常都将被它捕获处理。称为U.E.F.
会检查注册表HKLM\SOFTWARE\Microsoft\WindowsNT\CurrentVersion\AeDebug,这个路径下的Auto值为1不弹出错误对话框,其他值会弹出一个错误的对话框。
VEH
多个VEH结构体之间串成双向链表,次于调试器处理,优于SEH处理。保存在堆中。
关于异常的攻击
VEH
利用堆溢出的DWORD SHOOT修改指向V.E.H头节点指针,在异常处理开始后,能引导程序去执行shellcode。
SEH
修改TEB的指针(SEH头节点),在异常发生时就能将程序引导到shellcode中去执行。
但是一个进程中会有多个线程,也就意味着有多个TEB线程环境块。很难判断一个线程对应的TEB位置。
UEF
利用堆溢出DWORD SHOOT把这个调用句柄改为shellode地址,再制造一个其他异常都无法解决的异常,那么就会调用UEF作为最后一根救命稻草来解决异常。
SetUnhandledExceptionFilter在kernel32中,利用时结合跳板技术,因为异常发生时,EDI往往仍然指向堆中离shellcode不远的地方。
例如:call dword ptr[EDI+0x78]
call dword ptr[ESI + 0x4C]
call dword ptr[EBP + 0x74]
PEB
P.E.B 中线程同步函数的入口地址,每一个进程的PEB都存放着一根同步函数指针。PEB位置不变。这比SEH链头节点的方法稳定可靠。
C++虚表
Heap Spray
用于浏览器的攻击中,攻击者构造一个特殊的HTML文件来触发这个漏洞。不管是堆溢出还是栈溢出,漏洞触发后获得EIP。页面中JavaScript可以申请堆内存。
- 将EIP指向0x0C0C0C0C位置,然后用JavaScript来申请大量堆内存。
- 0x0C0C0C0C被含有shellcode的内存片所覆盖
var nop=unescape("%u9090%u9090");
while (nop.length<= 0x100000/2)
{
nop+=nop;
}//生成一个 1MB 大小充满0x90 的数据块
nop = nop.substring(0, 0x100000/2 - 32/2 - 4/2 - shellcode.length - 2/2 );
//substring() 方法用于提取字符串中介于两个指定下标之间的字符。
var slide = new Arrary();
for (var i=0; i<200; i++)
{
slide[i] = nop + shellcode
}
32bytes—堆块信息
4bytes----字符串长度
2bytes----字符串结束符