技术交流,DH讲解.
前几天一个朋友在弄游戏外挂想带NP调试,就像自己来捕获游戏的异常.
好像就要用到SEH这方面的知识.
一起研究了一下,这里看下研究 和 在网上找的资料吧.
SEH就是Structure Exception Handling.结构化异常处理,具体可以看下MSDN.
MSDN在手,走遍天下无敌手.哈哈.
当时先自己看下Delphi 怎么实现try..except..end的吧.我们写段程序然后调试就知道了.
1
2
3
4
5
6
7
8
9
10
11
|
Procedure
TForm1
.
Button1Click( Sender: TObject );
Var
a: TForm1;
Begin
a :=
Nil
;
Try
a
.
Show;
Except
ShowMessage(
'1111'
);
End
;
End
;
|
这样是会发生异常的吧,我们下断点,然后调试的时候ctrl + alt + c进入CPU窗口.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
Unit1
.
pas
.38
: a :=
Nil
;
00452702
33C0
xor
eax,eax
Unit1
.
pas
.39
:
Try
00452704
33D2
xor
edx,edx
00452706
55
push ebp
00452707
6821274500
push
$00452721
0045270C 64FF32 push dword ptr fs:[edx]
0045270F
648922
mov fs:[edx],esp
Unit1
.
pas
.40
: a
.
Show;
00452712
E811B3FFFF call TCustomForm
.
Show
00452717
33C0
xor
eax,eax
00452719
5A pop edx
0045271A
59
pop ecx
0045271B
59
pop ecx
0045271C
648910
mov fs:[eax],edx
0045271F EB14 jmp +
$14
00452721
E91E12FBFF jmp @HandleAnyException
Unit1
.
pas
.42
: ShowMessage(
'1111'
);
00452726
B844274500 mov eax,
$00452744
0045272B E85C8DFDFF call ShowMessage
00452730
E87715FBFF call @DoneExcept
Unit1
.
pas
.44
:
End
;
|
我们看见了try模块的代码了吧.好的自己来写个函数.
1
2
3
4
5
6
7
8
|
Procedure
SetExceptionProc( Proc:
Pointer
);
Asm
//将回调函数指针压入堆栈
push eax
//保护 原来的处理函数
push fs:[
0
]
mov fs:[
0
],esp
End
;
|
你看和Delphi代码里面Try反编译出来的一样吧.
至于为什么要这样写?我也不知道,所以我们现在需要去找资料了.
发生异常时系统的处理顺序(by Jeremy Gordon):
1.系统首先判断异常是否应发送给目标程序的异常处理例程,如果决定应该发送,并且目标程序正在被调试,则系统
挂起程序并向调试器发送EXCEPTION_DEBUG_EVENT消息.呵呵,这不是正好可以用来探测调试器的存在吗?
2.如果你的程序没有被调试或者调试器未能处理异常,系统就会继续查找你是否安装了线程相关的异常处理例程,如果
你安装了线程相关的异常处理例程,系统就把异常发送给你的程序seh处理例程,交由其处理.
3.每个线程相关的异常处理例程可以处理或者不处理这个异常,如果他不处理并且安装了多个线程相关的异常处理例程,
可交由链起来的其他例程处理.
4.如果这些例程均选择不处理异常,如果程序处于被调试状态,操作系统仍会再次挂起程序通知debugger.
5.如果程序未处于被调试状态或者debugger没有能够处理,并且你调用SetUnhandledExceptionFilter安装了最后异
常处理例程的话,系统转向对它的调用.
6.如果你没有安装最后异常处理例程或者他没有处理这个异常,系统会调用默认的系统处理程序,通常显示一个对话框,
你可以选择关闭或者最后将其附加到调试器上的调试按钮.如果没有调试器能被附加于其上或者调试器也处理不了,系统
就调用ExitProcess终结程序.
7.不过在终结之前,系统仍然对发生异常的线程异常处理句柄来一次展开,这是线程异常处理例程最后清理的机会.
事实上,当异常发生时,系统给了我们一个处理异常的机会,他首先会调用我们自定义的seh处理例程,当然也包括
了相关信息,在调用之前,系统把包含这些信息结构的指针压入stack,供我们的异常处理例程调用,
传递给例程的参数通常是四个,其中只有三个有明确意义,另一个到现在为止还没有发现有什么作用,
这些参数是:pExcept:DWORD,pErr:DWORD,pContext:DWORD,pDispatch意义如下:
pExcept: --- EXCEPTION_RECORD结构的指针
pErr: --- 前面ERR结构的指针
pContext: --- CONTEXT结构的指针 ,里面都是我们寄存器的值.
pDispatch:---没有发现有啥意义
Delphi里面已经定义好了这些结构体指针了,我就不多说了.
我们来把回调函数写出来吧.
1
2
3
4
5
|
Function
ExceptionProc( pExcept: PExceptionRecord;pError:
Pointer
;pContxt: PContext;pDispatch:
Pointer
):
Integer
; Stdcall;
Begin
ShowMessage(
'1111'
);
Result:=
0
;
End
;
|
注意是stdcall调用方式,貌似有些资料上面是cdecl.这里我们先不管这么多了.
现在我们看看系统是怎么调用我们回调函数的.
关键的 Win32 数据结构——线程信息块(即 TEB 和 TIB)。
该数据结构的某些域在 Windows NT、Windows 95、Win32s 和 OS/2 平台上是一样的。
TIB 中的第一个 DWORD 是指向线程 EXCEPTION_REGISTRATION 结构的指针。
在 Intel Win32 平台上,FS 寄存器总是指向当前的 TIB。
因此,在 FS:[0]位置,你能找到 EXCEPTION_REGISTRATION 结构的指针。
这里也就解释了 我们SetExceptionProc函数了.
好的我们自己写段代码来测试一下了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Procedure
TestException( );
Begin
//try
SetExceptionProc( @ExceptionProc );
//make a exception
Asm
xor
edx,edx
mov [edx],
0
End
;
//恢复异常
Asm
pop eax
mov fs:[
0
],eax
add esp,
8
End
;
End
;
Procedure
TForm1
.
Button2Click( Sender: TObject );
Begin
TestException;
End
;
|
测试中恢复异常还是有问题.
终于在CSDN的书呆子的博客上面找到了答案.
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
Program
Project2;
{$APPTYPE CONSOLE}
Uses
SysUtils,
Windows;
Type
PExecption_Handler= ^Exception_Handler;
PException_Registration= ^Exception_Registration;
_ExceptionHandler=
Record
ExceptionRecord: PExceptionRecord;
SEH: PException_Registration;
Context: PContext;
DispatcherContext:
Pointer
;
End
;
Exception_Handler= _ExceptionHandler;
_ExceptionRegistration=
Record
Prev: PException_Registration;
Handler: PExecption_Handler;
End
;
Exception_Registration= _ExceptionRegistration;
Const
EXCEPTION_CONTINUE_EXECUTION=
0
;
///恢复CONTEXT里的寄存器环境,继续执行
EXCEPTION_CONTINUE_SEARCH=
1
;
///拒绝处理这个异常,请调用下个异常处理函数
EXCEPTION_NESTED_EXCEPTION=
2
;
///函数中出发了新的异常
EXCEPTION_COLLIDED_UNWIND=
3
;
///发生了嵌套展开操作
EH_NONE=
0
;
EH_NONCONTINUABLE=
1
;
EH_UNWINDING=
2
;
EH_EXIT_UNWIND=
4
;
EH_STACK_INVALID=
8
;
EH_NESTED_CALL=
16
;
STATUS_ACCESS_VIOLATION=
$C0000005
;
///访问非法地址
STATUS_ARRAY_BOUNDS_EXCEEDED=
$C000008C
;
STATUS_FLOAT_DENORMAL_OPERAND=
$C000008D
;
STATUS_FLOAT_DIVIDE_BY_ZERO=
$C000008E
;
STATUS_FLOAT_INEXACT_RESULT=
$C000008F
;
STATUS_FLOAT_INVALID_OPERATION=
$C0000090
;
STATUS_FLOAT_OVERFLOW=
$C0000091
;
STATUS_FLOAT_STACK_CHECK=
$C0000092
;
STATUS_FLOAT_UNDERFLOW=
$C0000093
;
STATUS_INTEGER_DIVIDE_BY_ZERO=
$C0000094
;
///除0错误
STATUS_INTEGER_OVERFLOW=
$C0000095
;
STATUS_PRIVILEGED_INSTRUCTION=
$C0000096
;
STATUS_STACK_OVERFLOW=
$C00000FD
;
STATUS_CONTROL_C_EXIT=
$C000013A
;
Var
G_TEST: DWORD;
Procedure
Log( LogMsg:
String
);
Begin
Writeln
( LogMsg );
End
;
//看这个回调函数,和我们那个有点儿区别,第二个参数的作用原来是ExceptionRegistration,原来秘密在它身上
Function
ExceptionHandler( ExceptionHandler: EXCEPTION_HANDLER ):
LongInt
; Cdecl;
Begin
Result := EXCEPTION_CONTINUE_SEARCH;
If
ExceptionHandler
.
ExceptionRecord
.
ExceptionFlags= EH_NONE
Then
Begin
Case
ExceptionHandler
.
ExceptionRecord
.
ExceptionCode
Of
STATUS_ACCESS_VIOLATION:
Begin
Log(
'发现异常为非法内存访问,尝试修复EBX,继续执行'
);
ExceptionHandler
.
Context
.
Ebx := DWORD( @G_TEST );
Result := EXCEPTION_CONTINUE_EXECUTION;
End
;
Else
Log(
'这个异常我无法处理,请让别人处理吧'
);
End
;
End
Else
If
ExceptionHandler
.
ExceptionRecord
.
ExceptionFlags= EH_UNWINDING
Then
Log(
'异常展开操作'
);
End
;
Begin
Asm
///设置SEH
XOR
EAX, EAX
PUSH OFFSET ExceptionHandler
PUSH FS:[EAX]
MOV FS:[EAX], ESP
///产生内存访问错误
XOR
EBX, EBX
MOV [EBX],
0
///取消SEH
XOR
EAX, EAX
//这里用的这个 而不是我们用的那个pop eax呀..哈哈.一切正常了
MOV ECX, [ESP]
MOV FS:[EAX], ECX
ADD ESP,
8
End
;
Readln;
End
.
|
牛人拜读了.大家可以去CSDN上面看下.
好了我是DH.大家想了解更多可以看看雪的加密解密 以及 那个 什么软件漏洞分析 书上面都有SEH的章节.
http://www.cnblogs.com/huangjacky/archive/2009/12/09/1620644.html