关于seh的那些事一

      seh是英文structured exception handing的简写,叫做结构化异常,我觉得更精确的描述应该是线程异常处理回调,因为seh是在每个线程之间独立的异常处理方法,基本上,每个高级语言都会有seh的应用,比如delphi的异常处理就是try..except,很遗憾的事,易语言并没有,但是毛太祖说过,自己动手,丰衣足食.那么,我们今天就通过易语言内联汇编建立一个易语言的try..except模型

    建立seh的模型我们就先要知道seh的一些特点,1.seh是基于线程的,所以我们可以为每个线程设置不同的的异常处理程序 2.seh可以在一个线程多次安装,那么,这个线程碰到异常后,按照最后安装的seh最先执行的顺序.

   怎么安装seh呢?W32系统为每个线程定义了一个线程信息结构,这个结构叫NT_TIB结构

NT_TIB struct     

000  指向SEH链指针
004  线程堆栈顶部
008  线程堆栈底部
00C  SubSystemTib
010  FiberData
014  ArbitraryUserPointer
018  FS段寄存器在内存中的镜像地址,线程TEB结构地址
020  进程PID
024  线程ID
02C  指向线程局部存储指针
030  PEB结构地址(进程结构)
034  上个错误号

NT_TIB end 

 

     这里最重要是第一个字段,这个字段就是SEH链指针,这个指针指向一个exception_registration结构,这个结构可以自由定义,但是前2字段是固定的

  exception_registration struct

    prev   上一个SEH链指针

    handler 异常处理函数地址

     ...........后面字段可以根据自己需要自己定义

  exception_registration end

 

     这里我们看到了 只要在一个线程里填充exception_registration这个结构,就可以注册一个seh异常处理了,那么exception_registration这个结构的地址在哪里,也就是TIB的第一个字段[向SEH链指针]是多少呢?

    答案是fs:[0].

    FS段选择器指定数据段的0偏移处为TIB的第一个字段,同理,fs:[20]为进程PID,fs:[24]为线程序PID,为fs:[18]等于线程环境块,teb结构里有一些很有趣的东西,我们先不说,我们写个验证程序试验下

  

  代码如下:

.版本 2

.子程序 测试
.局部变量 局_变量一
.局部变量 局_进程PID
.局部变量 局_线程PID

局_变量一 = 0
局_进程PID = 0
局_线程PID = 0
' _asm
' {
' mov eax, [fs:0]
' mov [ebp-04h],eax
' mov eax, [fs:20h]
' mov [ebp-08h],eax
' mov eax, [fs:24h]
' mov [ebp-0ch],eax
' }
信息框 (局_变量一, 0, )
信息框 (局_进程PID, 0, “我是进程PID”)
信息框 (局_线程PID, 0, “我是线程PID”)

 

说明这个结构是对.

现在安装一个seh就很简单了,就是申请一块内存,填充一个exception_registration结构,然后把地址写入FS[0]处.现在基本都用堆栈构造exception_registration结构,好处是遍于封装,那么我们也用堆栈来构造

push 异常处理地址

push fs[0] 上一个异常处理指针

mov fs:[0],esp

把这2个推入堆栈后,然后把esp放入FS段选择器指定数据段的0偏移处

这样就注册了一个seh

 

用汇编注册seh是非常简单的事情,易语言的难点在于重定位,我们下面来分析下易语言的写法.

我先把易语言代码发出来,再逐一解释

.版本 2

.子程序 子程序_模拟try

' _asm{
' pushad
' call my

' my:
' pop eax ;自定位
' lea ecx, dword [eax+safe-6]
' push ecx
' lea ecx,dword [eax+handle-6]
' push ecx
' push dword  [fs:0]
' mov  dword  [fs:0], esp

' xor eax,eax
' mov [eax],eax
' jmp safe

' handle:
' pushad

' mov edi,[ebp+10h]
' mov eax,[ebp+0ch]

' push dword [eax+8h]
' pop dword [edi+0B8h]

' popad
' mov eax,0
' ret 10h

' safe:
' mov esp, dword  [fs:0]
' pop dword  [fs:0]
' add esp, 8h
' popad
' }

 这一段就是易语言内联汇编写的模拟try..except的模型,我们下面来分析下

首先.第一个难点,要解决重定位的问题,我们首先

pushad
call my

my:
pop eax ;自定位

利用CALL的特性,把pop eax 处的地址弹入eax,那么

asm{处的地址就是eax-6,因为pushad call my包含6字节

下面

lea ecx, dword [eax-6+safe]
push ecx       '推入安全地址safe

lea ecx,dword [eax-6+handle]
push ecx     '推入seh处理地址handle  
push dword  [fs:0]  '推入原seh结构地址
mov  dword  [fs:0], esp 把构造seh结构的堆栈放入fs加0偏移处,注册seh

易语言注册seh的办法,这里我们扩充了exception_registration结构,除开必须要的2个字段外,我们增加了一个1个安全地址,safe,也就是,现在exception_registration结构是这样的

exception_registration struct

    prev   上一个SEH链指针

    handler 异常处理函数地址

     safe 安全的地址

  exception_registration end

这里说明下,易语言内联汇编关于标号,是相对便移,也就是相对asm{开始处的便移,所以我们先进行了一个自定位,然后eax-6+safe计算寻找到了真实地址

 

xor eax,eax
mov [eax],eax


jmp safe

 

这里构造了一个读写异常,也就是如果我们要写一些要被保护的代码的话,在这里写,写完后跳到safe安全地址去执行.

 

如果发生了异常.进入hanld处理函数.

 

handle:
pushad  '保护积存器

mov edi,[ebp+10h]  '第三个参数,寄存器context地址 
mov eax,[ebp+0ch]  '第二个参数,exception_registration地址

push dword [eax+08h]  'exception_registration地址+8,这里就是我们自定义的safe地址了
pop dword [edi+0B8h] 'safe地址等于eip,[edi+0B8h]等于eip

popad '保护积存器
mov eax,0
ret 10h       '恢复堆栈平衡

 

seh的异常回调,有4个参数.

类似这样:

context这里是个结构,寄存器结构,好东西.我们说下lpseh,这个参数指向注册回调函数时候使用的exception_registration结构的地址

那么,我们知道,我们注册的exception_registration结构里,有个安全地址,也就是,我们把安全地址等于context结构的eip,然后返回0,就可以走到安全位置去了.

 safe:
mov esp, dword  [fs:0]
pop dword  [fs:0]
add esp, 8h
popad

撤消注册的seh,恢复堆栈平衡,我们在注册的时候用了3个push,由于撤消seh要pop一个堆栈,那么还有2个push,我们要平衡,所以add,esp,8h,然后恢复被保存的寄存器.注(pop eax ;自定位,这里跟call myl平衡了堆栈,call=push返回地址 jmp目标地址)

 

整个易语言的try..except的模型就说完了,一个是重定位问题,堆栈还要注意平衡,再就是exception_registration这个结构我们自己在固定的结构后面增加了一个safe字段,整个模型没有用到一个全局变量,都在堆栈里进行,方便封装成模块或者作为模型.

 

posted on 2013-12-28 19:00  shellcode  阅读(2018)  评论(0编辑  收藏  举报

导航