4.4 滥用结构化异常处理
结构化异常处理(SEH)提供提供一种控制流的方法,该方法不能被反汇编器采用,但可以用来欺骗反汇编器。
SEH是一个函数列表,目的是处理线程中的异常。列表中的每个函数,要么处理异常,要么将异常传递给下一个函数。如果一个异常总是被传递到最后一个异常处理函数处,就会被认为是一个不能处理的异常。这种情况下,最后一个异常处理函数会负责弹出一个消息提示框“an unhadled execption has occurred”。在大多数进程中,异常是有规律产生的,但在到达最后状态之前,异常都会被静悄悄处理掉 。
为了查询SEH链,操作系统会检查FS段寄存器。这个寄存器会包含一个段选择子,使用段选择子可以得到线程环境块(TEB),TEB中的第一个数据结构是线程信息块(TIB),TIB中的第一个元素即第一个字节就是SEH链指针。
当线程运行在 用户空间 下时, FS 指向的段是 GDT 中的 0x3B 段。该段的长度为 4K ,基地址为当前线程的线程环境块( TEB ),所以该段也被称为“ TEB 段”。 因为 Windows 中线程是不停切换的,所以该段的基地址值将随线程切换而改变的。 寄存器FS和GS是段寄存器。 它们没有处理器定义的目的,而是由操作系统运行它们来实现目的。 FS用于指向Windows进程上的线程信息块(TIB)。一个典型示例是(SEH),它在FS:[0x00]中存储指向回调函数的指针。GS通常用作指向线程本地存储(TLS)的指针。 FS寄存器指向当前活动线程的TEB结构(线程结构) 偏移 说明 000 指向SEH链指针 004 线程堆栈顶部 008 线程堆栈底部 00C SubSystemTib 010 FiberData 014 ArbitraryUserPointer 018 FS段寄存器在内存中的镜像地址 020 进程PID 024 线程ID 02C 指向线程局部存储指针 030 PEB结构地址(进程结构) 034 上个错误号 得到KERNEL32.DLL基址的方法 assume fs:nothing ;打开FS寄存器 mov eax,fs:[30h] ;得到PEB结构地址 mov eax,[eax + 0ch] ;得到PEB_LDR_DATA结构地址 mov esi,[eax + 1ch] ;InInitializationOrderModuleList lodsd ;得到KERNEL32.DLL所在LDR_MODULE结构的InInitializationOrderModuleList地址 mov edx,[eax + 8h] ;得到BaseAddress,既Kernel32.dll基址
SEH链是一个简单的8字节数据结构链表,叫做EXCEPTION_TEGISTRATION记录。
struct _EXCETION_REGISTRATION{
DWORD prev; 指向前一个记录的指针
DWORD hadler; 指向异常处理函数的指针
}
这个链表以栈的方式进行操作,第一个调用的是最后一个加入链表的记录。由于子进程的调用与嵌套的异常处理块的原因,SEH链的增长和缩小都等同于程序中异常处理层的改变,所以SEH记录总是建在栈上。
实现变相控制程序流,需要了解怎么将自己的异常处理添加到链表头部。
为了将一条记录添加到这个链中,我们需要在栈上构造一条新的记录。因为记录结构有两个DWORD变量组成,所有需要使用两个PUSH来完成。第一个push进栈的是异常处理函数指针,第二个push进栈的是下一条记录的指针。当添加一条记录到链表头部是,下一条记录需要完成的异常处理是当前栈顶,由fs[0]指针给出。
push ExceptionHandler
push fs:[0]
mov fs:[0],esp
异常发生时,首先调用函数ExceptionHandler。这个动作受到微软的软件数据执行保护机制(软件DEP, 也叫SafeSEH)的限制。
软件DEP是一项安全功能,目的是阻止程序运行过程中添加第三方异常处理。对于硬编码的代码,有几种方法能够绕过这种技术。
例如使用支持SafeSEH指令的汇编器。另外使用微软的C编译器也能达到此目的,添加/SAFESEH:NO到链接器命令行,就可使这种限制无效。
当调用ExceptionHandler函数时,栈将被大幅改变。
要达到目的,不必检查添加到栈中的所有数据。
我们必须知道怎么返回异常发生前的栈位置。当异常被调用时,操作系统添加了其他的SEH处理。
为了让程序恢复正常操作,不仅要将我们的异常处理从异常处理链中断开,还要将系统添加的异常处理冲异常处理链中断开。因此。需要从esp+8处而不是esp处取出原始指针。
这是为了让程序恢复正常。
mov esp, [esp+8]
mov eax, fs:[0] fs:[0]代表上一条记录指针prev,存储prev的地址
mov eax, [eax] 赋值后eax为prev的值,值是上一条记录prev的地址
mov eax, [eax] 赋值后eax为上一条记录prev的值
mov fs:[0], eax
add esp, 8
②处eax被设置为40106C,通过这次操作秘密地安装一个异常处理,然后将EAX加14h,构造404080函数指针。通过xor ecx,ecx将ECX寄存器设置为0,随后div ecx③,触发除0异常。
在401080处按C键