作 者: mickeylan
時 間: 2008-06-10,00:45
鏈 接: http://bbs.pediy.com/showthread.php?t=66291
在進入主題之前,先來簡單地看一下結構化異常處理(Structured Exception Handling, SEH),本篇的程序需要這個東東。
結構化異常處理
這裡我並不打算詳細講結構化異常處理,關於SEH,在網上你能找到相關的內容,SHE能用於所有的異常處理,也就是說,SEH既能用於用戶模式又能用於內核模式。但這兩種模式下的異常處理有一個本質上的差別:
在內核模式下,借助於seh,並非所有的異常都能得到處理!比如說,即使使用了seh,用零作除數作除法也會使系統崩潰。最為可怕的是,引用未定義的內核內存也會導致藍屏死機BSOD。而對未定義的用戶模式內存的引用異常,seh卻可以輕松處理。因此避免系統崩潰的唯一辦法就是所編寫的代碼不要導致無法處理的異常。
以下是個使用結構化異常的例子:
代碼:unit seh;
interface
uses
nt_status;
function _DriverEntry(pDriverObject:PDRIVER_OBJECT;
pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;
implementation
uses
ntoskrnl;
const
SEH_SafePlaceCounter = 0;
SEH_INSTALLED = 0;
type
_SEH = record
SafeEip: DWORD; { 線程繼續執行的地方 }
PrevEsp: DWORD; { 以前esp的值 }
PrevEbp: DWORD; { 以前ebp的值 }
end;
var
sseh: _SEH;
function DefaultExceptionHandler(pExcept:PEXCEPTION_RECORD;
pFrame:DWORD;
pContext:PCONTEXT;
pDispatch:DWORD): DWORD; cdecl;
begin
DbgPrint(#13#10SEH: An exception %08X has occured#13#10,
pExcept^.ExceptionCode);
if pExcept^.ExceptionCode = $0C0000005 then
begin
{如果發生了EXCEPTION_ACCESS_VIOLATION類型的異常,}
{則輸出以下信息.}
DbgPrint( Access violation at address: %08X#13#10,
pExcept^.ExceptionAddress);
if pExcept^.ExceptionInformation[0] <> nil then {試圖讀還是寫?}
begin
DbgPrint( The code tried to write to address %08X#13#10#13#10,
DWORD(pExcept^.ExceptionInformation[4]));
end else
begin
DbgPrint( The code tried to read from address %08X#13#10#13#10,
DWORD(pExcept^.ExceptionInformation[4]));
end;
end;
asm
lea eax, sseh
push (_SEH PTR [eax]).SafeEip
push (_SEH PTR [eax]).PrevEsp
push (_SEH PTR [eax]).PrevEbp
mov eax, pContext
pop (CONTEXT PTR [eax]).regEbp
pop (CONTEXT PTR [eax]).regEsp
pop (CONTEXT PTR [eax]).regEip
end;
result := 0;
end;
procedure BuggyReader; assembler;
asm
xor eax, eax
mov eax, [eax] {!!! 沒有SEH的話 - BSOD !!!}
end;
procedure BuggyWriter; assembler;
asm
mov eax, offset MmUserProbeAddress
mov eax, [eax]
mov eax, [eax]
mov byte ptr [eax], 0 {!!!沒有SEH的話 - BSOD !!!}
end;
function _DriverEntry(pDriverObject:PDRIVER_OBJECT;
pusRegistryPath:PUNICODE_STRING): NTSTATUS; stdcall;
label
SafePlace;
begin
DbgPrint(#13#10SEH: Entering DriverEntry#13#10);
{ "手工"安裝SEH }
asm
push offset DefaultExceptionHandler {我們的SEH程序}
push fs:[0]
mov fs:[0], esp
mov sseh.SafeEip, offset SafePlace {SafePlace是處理完異常後繼續執行的地方}
mov sseh.PrevEbp, ebp
mov sseh.PrevEsp, esp
end;
BuggyReader;
BuggyWriter;
SafePlace:
asm
pop fs:[0]
add esp, 4
end;
DbgPrint(#13#10SEH: Leaving DriverEntry#10#13);
result := STATUS_DEVICE_CONFIGURATION_ERROR;
end;
end.
安裝SHE-Frame
由於在內核模式下我們無法直接使用Delphi自身的異常處理機制,因為在驅動程序中我們要自己手工安裝SHE,這裡我們使用Delphi的BASM來做這件事情,了解SHE的朋友都知道,做這件事情是非常簡單的。
代碼:asm
push offset DefaultExceptionHandler {我們的SEH程序}
push fs:[0]
mov fs:[0], esp
mov sseh.SafeEip, offset SafePlace {SafePlace是處理完異常後繼續執行的地方}
mov sseh.PrevEbp, ebp
mov sseh.PrevEsp, esp
end;
為了在異常處理之後我們的處理程序能恢復線程的執行,我們應該保存esp、ebp寄存器的內容以及線程繼續執行的地址。我們將這三項信息保存在seh結構體中並調用函數BuggyReader。BuggyReader函數試圖從地址00000000讀取一個DWORD。
代碼:procedure BuggyReader; assembler;
asm
xor eax, eax
mov eax, [eax] {!!! 沒有SEH的話 - BSOD !!!}
end;
nil指針引用是一個十分常見的錯誤,微軟在00000000-0000FFFFh劃出了64k字節的內存區,並使此區域無法訪問。訪問此區域中的任何一個字節都會導致EXCEPTION_ACCESS_VIOLATION類型的異常。
異常處理
函數BuggyReader從地址00000000讀取引發了異常,我們就進入了我們指定的處理程序。
代碼:function DefaultExceptionHandler(pExcept:PEXCEPTION_RECORD;
pFrame:DWORD;