異常處理第一講(SEH),篩選器異常,以及__asm的擴展
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝
一丶__Asm的擴展知識
①丶使用關鍵字,解決局部變量申請問題
昨天已經介紹了__asm的基本用法,現在對其做個擴展(上一篇是32為匯編第七講)
昨天我們寫的裸函數,那么變量的問題需要解決
請看C的內聯匯編
_declspec(naked) int MySub(int n1,int n2)
{
int nLocal1; //注意變量 int nLocal2; _asm { push ebp mov ebp,esp sub esp,8 //申請了兩個變量,那么棧就要抬高8個字節,一個變量4個字節 push 0 push 0 push 0 push 0 pop ebp ret } }
那么對於上面的程序,你覺着可能沒問題,但是我們想一下,如果我在定義多個變量,那么下面的指令就要多次改動
sub esp,xxx
但是這樣不好,為什么,如果來個數組,來個結構體套結構體,你怎么保證我們要開辟多少個局部變量?
所以VC為我們提供了語法的支持
__LOCAL_SIZE 申請局部變量的時候自動抬棧
,ALT + 8看下反匯編窗口到底做了什么
我們看到了,我們就定義了兩個局部變量,為什么是申請了48個字節,原因是我這里是Debug版本,默認編譯器會幫我們申請40h(也就是64個字節)的局部空間,那么加上我們的兩個局部變量正好48H個字節
如果是發布版(Release)那么則會根據你寫的匯編代碼的不同,申請不同的空間,為什么這樣說.
因為你定義了兩個局部變量,而在__asm里面你只使用了一個
例如:
__asm
{
...
mov nLocal,eax //把eax的值給局部變量
... }
如果另一個沒有使用,編譯器就可能給你優化掉,只給你申請4個局部變量空間,以為不是Debug版本,所以不會在額外給你申請40H個字節了
注意,在裸函數中你定義的局部變量是不能初始化的
也就是說你可以寫成我上面的那樣子,但是不能初始化值,因為這個時候還沒有抬棧,比如抬棧之后初始化,
而初始化就可能在__asm里面去寫
當然更多的擴展的__asm語法可以MSDN查詢
比如上面這個
輸入__asm查詢即可
②丶解決數組求大小,求數組類型大小,以及求數組/類型的問題
我們有的時候會想,我們的Sizeof()還是想使用的,很方便的
那么現在我們不能使用了,但是VC為了支持,還是提供了額外的語法支持
例如MSDN上查詢到的
上面說了詳細的例子
LENGTH 數組 代表了C語言的 Sizeof(數組) / sizeof(數組[0])
SIZE 數組 代表了C語言的求數組大小
TYPE 數組 代表了C語言的求數組第一項類型的大小
具體測試,請自己上機實踐
③丶解決db定義數據的關鍵字
在VC內聯匯編中,db關鍵字 dd dw ....等等都不可以使用了,但是提供了額外的語法
_emit指令
上面說的很清楚,如果想使用db命令則用這個指令代替
例如:
__asm
{
_emit 04ah
}
如果想使用DW 則定義兩個,使用DD 着用三個即可.
注意,使用這個指令我們可以把OUT指令的二進制定義出來,還有操作碼,那么匯編就是對應的OUT指令了
我們都知道,我們32位匯編下都是保護模式了,也就是說,IN OUT不管用了,(不代表不能用)我們一樣可以用,只不過
IN OUT 指令是三環,所以執行這條二進制指令的時候,CPU是拒絕執行的,我們要執行就是在0環下執行,也就是常說的操作系統內部,內核執行.
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝
二丶異常數處理(SEH)篩選器異常
首先我們要明白什么是異常,以及異常的作用(抱着疑問來學習,事半功倍)
什么是異常:
SEH("Structured Exception Handling"),即結構化異常處理.是操作系統提供給程序設計者的強有力的處理程序錯誤或異常的武器.在VISUAL C++中你或許已經熟悉了_try{} _finally{} 和_try{} _except {} 結構,這些並不是 編譯程序本身所固有的,本質上只不過是對windows內在提供的結構化異常處理的包裝
說白了,就是 try cath的異常和這個異常不是同一個
我們這里說的異常,是這個異常怎么產生的,以及怎么處理的,也就是說你寫程序長出現的C00005這種異常,空指針異常
作用:
相信大家可能都遇到過程序崩潰的情況,或者我們有時候使用QQ 通訊工具的時候也會崩潰 :)
那么QQ處理的就是彈出一個框,讓你發送錯誤報告什么的,為什么,因為QQ用戶量很大,都是億萬級的用戶量
而這些用戶就是測試QQ的最好的人,比如有的用戶無聊,好友列表來回點上1個小時,對,就是那么無聊.
比如王者榮耀,有人還打個游戲幾個小時 :)廢話好多
作用就是 讓我們快速定位程序問題,如果你彈出一個崩潰框框,那么用戶可能隨手關了,用戶也不懂這些對吧.
那么今天介紹一下篩選器異常
①丶篩選器異常
1.設置篩選器異常
啥是篩選器異常?
篩選器處理異常是由程序指定一個異常處理回調函數,當發生異常的時候,系統將調用這個回調函數,並根據回調函數的返回值決定如何進行下一步操作。 在進程范圍內,篩選器異常處理回調函數是惟一的,設置了一個新的回調函數后,原來的就失效了。
啥意思,就是你提供一個函數,當程序出錯了系統會調用這個函數,如果這個回調就一個,那么我們可以保存一下,當我們設置新的時候,也可以調用舊的,不過這個一般不使用
看下API 和回調函數
API,和API原型:
SetUnhandledExceptionFilter //設置一個異常處理回調
LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
);
MSDN圖片
很簡單,我們寫個回調,然后傳入函數的指針即可.
如果我們取消異常處理,則用
UnhandledExceptionFilter
回調函數的設置
LONG __stdcall 自己的回調函數名字(__EXCEPTION_POINTERS *ExceptionInfo)
{
MessageBoxA(NULL,NULL,NULL,NULL);
return 0; }
且看我寫一個真正的異常處理C/C++程序
其實很簡單,就是給個回調,設置一下就完了.
看下C++代碼
#include <stdio.h>
#include <windows.h> LONG __stdcall MyException(_EXCEPTION_POINTERS *Ecptpoints) { MessageBox(NULL, NULL, NULL, NULL); return 0; } int main(_In_ int _Argc, _In_reads_(_Argc) _Pre_z_ char ** _Argv, _In_z_ char ** _Env) { SetUnhandledExceptionFilter(MyException); //設置異常的回調函數,如果有異常,操作系統會調用我們給的回調 int *pNum = NULL; *pNum = 1; //這里故意引發異常,空指針異常 return 0; }
我們打開程序,看看會怎么樣的結果
因為空指針異常了,所以操作系統調用了我們的回調函數,而在回調函數里面我們謝了MsgBox,所以彈框了
但是我們點擊確定,又會出現系統崩潰,我們看下
為什么?,對了,我們沒有調用退出函數,也就是沒有調用
ExitProcess 退出進程
調用了就可以了.
但是,猜錯了,固然我們調用退出進程可以解決問題,但是結果不是這樣的,這和會調用的返回值有關,且看下文
詳解回調返回值
2.篩選器異常回調函數的返回值問題
他有三種情況:
這里介紹兩種,
1.EXCEPTION_CONTINUE_SEARCH 宏定義就是 0 意思就是處理完畢之后,不處理了,你接着處理,上文我們的代碼就是這樣
2.EXCEPTION_EXECUTE_HANDLER 宏定義就是1 意思就是我處理完了,不讓下方處理了,也就代表這結束進程
上下一個自己MSDN查詢把 :)
3.篩選器異常的反調試功能
為什么這樣說,上面我們用異常輸出了一個信息框,但是現在我們在里面藏着我們的代碼,如果我們調試,
那么異常就會被OD接受,也就是說我們的異常函數不會到的,也就不會輸出了.比如
這個是我們上面的代碼,首先給eax清空,然后 又把1 給空地址寫入內容,所以產生異常了.
但是注意,這個只是我們的代碼,而我們利用回調函數能成功輸出字符串,所以現在這里OD調試的時候,則會接受異常
我們找下我們參數在哪里
可以看出,我們壓棧的參數則是 回調函數的地址,我們跳轉到那里,則可以看到我們的代碼了
因為我們是Debug版本,所以內部多了一程JMP跳轉
具體怎么寫大家自己調試
4.回調函數的參數問題
現在我們可以看下參數了,我們知道回調函數有一個參數,這個參數主要保存了錯誤信息
看下內容是什么
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord; //保存異常信息的結構體
PCONTEXT ContextRecord; //保存但是寄存器信息的結構體
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
可以看出內部還是嵌套了結構體
第一個結構體的作用: 主要保存異常信息,例如錯誤代碼,發生錯誤的地址等等...
第二個結果提的作用: 主要保存了但是錯誤信息發生的時候的寄存器信息,記錄了EIP,而我們知道EIP保存了當前錯誤地址的信息
那么具體展開看下把,如果沒興趣,則不用看了,直接看下方的目錄即可.
第一個結構體信息:
typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; //保存了錯誤代碼 DWORD ExceptionFlags; //保存了錯誤標志 struct _EXCEPTION_RECORD *ExceptionRecord; //類似於鏈表,繼續保存錯誤信息(異常鏈) PVOID ExceptionAddress; //錯誤發生的時候的地址 DWORD NumberParameters; //個數 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD, *PEXCEPTION_RECORD; //附加數組
現在我們只關心錯誤代碼,以及錯誤的地址即可.
看下第二個結構體信息:
typedef struct _CONTEXT {
//
// The flags values within this flag control the contents of
// a CONTEXT record.
//
// If the context record is used as an input parameter, then
// for each portion of the context record controlled by a flag
// whose value is set, it is assumed that that portion of the
// context record contains valid context. If the context record
// is being used to modify a threads context, then only that
// portion of the threads context will be modified.
//
// If the context record is used as an IN OUT parameter to capture
// the context of a thread, then only those portions of the thread's
// context corresponding to set flags will be returned.
//
// The context record is never used as an OUT only parameter.
//
DWORD ContextFlags; // // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is // set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT // included in CONTEXT_FULL. // DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; //這些寄存器我們沒見過,主要是Dr0 - 3 是我們OD中硬件斷點使用的,而 6 - 7是內存斷點使用,具體設置寄存器,有專門的API DWORD Dr6; DWORD Dr7; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_FLOATING_POINT. // FLOATING_SAVE_AREA FloatSave; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_SEGMENTS. // DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_INTEGER. // DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_CONTROL. // DWORD Ebp; DWORD Eip; DWORD SegCs; // MUST BE SANITIZED DWORD EFlags; // MUST BE SANITIZED DWORD Esp; DWORD SegSs; // // This section is specified/returned if the ContextFlags word // contains the flag CONTEXT_EXTENDED_REGISTERS. // The format and contexts are processor specific // BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT;
可以看出,這個就是保存但是錯誤的時候的寄存器的環境
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝
三丶修改寄存器,和獲取寄存器的值,以及寄存器注入
簡單的一場我們也理解了
這里簡單提一下,我們可以使用API來設置寄存器的信息,也可以獲取
分別是
SetThreadContext //設置寄存器信息
GetThreadContext //獲取寄存器信息
看下SetThreadContext的效果
BOOL SetThreadContext(
HANDLE hThread, // handle to thread
CONST CONTEXT*lpContext // context structure
);
這里他需要一個線程的句柄,還需要一個ConText結構體
上面說了這個結構體中保存的寄存器,所以我們給一個結構體則可以設置
這里主要簡單提一下
注意,我們可以用這個做一個注入
你可以修改EIP的值,讓它變為loadlibray的地址,這樣可以不需要創建遠程現成
這里簡單提下,因為時間關系,沒時間細寫了
/*
1.OpenThread 打開一個線程
2.SuspendThread 暫定這個線程
3.VirtualAllocEX 申請空間
4.WriteProcessMemory 寫入DLL路徑
5.修改Eip
6.ResumeThread 啟動線程
....
*/
具體先看下網上的內容,時間關系,后面寫
今天主要是代碼沒有課堂資料
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝