網(wǎng)站建站所用的技術(shù)自動優(yōu)化句子的軟件
Win32匯編學習筆記09.SEH和反調(diào)試-C/C++基礎(chǔ)-斷點社區(qū)-專業(yè)的老牌游戲安全技術(shù)交流社區(qū) - BpSend.net
SEH - structed exception handler 結(jié)構(gòu)化異常處理
跟篩選一樣都是用來處理異常的,但不同的是 篩選器是整個進程最終處理異常的函數(shù),但無法做到比較精細的去處理異常(例如處理某個函數(shù)的異常), 跟 C++ 的 try { } catch { } 的思路一脈相承, SHE 實現(xiàn)的 就是 函數(shù)自己 來處理自己的異常,實現(xiàn)方式就是通過回調(diào)函數(shù)實現(xiàn)的,把回調(diào)函數(shù)注冊給操作系統(tǒng),當你函數(shù)內(nèi)部出現(xiàn)異常時,系統(tǒng)就會調(diào)用你的回調(diào)函數(shù),此時就可以處理了,處理完之后可以繼續(xù)執(zhí)行代碼或者把異常交給篩選器
因此要使用SHE只需要做2件事,1是自己實現(xiàn)異常回調(diào)函數(shù),2是吧異?;卣{(diào)函數(shù)注冊給系統(tǒng)
把函數(shù)注冊給系統(tǒng)的方式就是 把函數(shù)地址 存到 fs:[0] 就可以了
可以看到 偏移為0 的位置 是一個異常鏈 表, , 記錄的是一個結(jié)構(gòu)體指針
因此我們需要構(gòu)造一個結(jié)構(gòu)體 , 把 函數(shù)地址 放到 Handler
回調(diào)函數(shù)聲明在 msdn 是沒有定義,這是微軟沒有文檔化的函數(shù),但是在微軟 C 庫的 實現(xiàn)用了,可以直接到里面去搜
參數(shù): 第一個 異常記錄 (異常信息) 第二個 不用管 第3個環(huán)境記錄 (寄存器環(huán)境) 第4個也可以不用管
第2個和第4個是給嵌套異常和展開異常用的
聲明
第3個和第四個也是給 嵌套異常 和展開異常用的
.586
.model flat,stdcall
option casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.lib;構(gòu)造結(jié)構(gòu)體 異?;卣{(diào)函數(shù)結(jié)構(gòu)體
EXCEPTION_REGISTRATION_RECORD strucNext dd 0 ;調(diào)用這異常回調(diào)函數(shù)函數(shù)結(jié)構(gòu)體指針Handler dd 0 ;當前異?;卣{(diào)函數(shù)地址
EXCEPTION_REGISTRATION_RECORD ends.datag_szF0 db "F0",0g_szF1 db "F1",0.codeassume fs:nothing ;對fs的類型進行強轉(zhuǎn);處理 F1 異常的回調(diào)函數(shù)
F1Handler proc uses esi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordinvoke MessageBox, NULL, offset g_szF1, NULL, MB_OK;ExceptionContinueExecution, - 程序繼續(xù)執(zhí)行;ExceptionContinueSearch, - 此異常我不處理,交給其它處理;異常交給F0 的 異?;卣{(diào)函數(shù)處理mov eax, ExceptionContinueSearch ;返回異常處理方式 不然會直接退出ret
F1Handler endp ;產(chǎn)生異常函數(shù) F1
F1 procLOCAL @err:EXCEPTION_REGISTRATION_RECORDLOCAL @dwOldSeh:dword ;原先的過程函數(shù)地址;保存調(diào)用者的異常回調(diào)函數(shù)mov eax, fs:[0]mov @dwOldSeh, eax ;保存調(diào)用者回調(diào)函數(shù)地址 到 next,不然無法找到調(diào)用者異常函數(shù)處理的地址mov eax, fs:[0]mov @err.Next, eax ;注冊異?;卣{(diào)mov @err.Handler, offset F1Handlerlea eax, @errmov fs:[0], eax;產(chǎn)生異常xor esi, esidiv esi;卸載SEH 還原過程函數(shù)(不然 F0 產(chǎn)生的異常會又回來)mov eax, @dwOldSehmov fs:[0], eaxret
F1 endp ;處理 F0 異常的回調(diào)函數(shù)
F0Handler proc pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordinvoke MessageBox, NULL, offset g_szF0, NULL, MB_OK;處理 F1 產(chǎn)生的除0異常 assume esi:ptr EXCEPTION_RECORD ;類型強轉(zhuǎn)mov esi, pER ;將 異常信息 pER 給 esi .if [esi].ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO ;如果是除0異常;處理,跳過產(chǎn)生異常的代碼mov esi, pContextassume esi:ptr CONTEXTadd [esi].regEip, 2 ;除0指令是2個字節(jié) regEip 是返回的地址assume esi:nothingmov eax, ExceptionContinueExecution ;返回異常處理方式 不然會直接退出ret.endifassume esi:nothingret
F0Handler endp;產(chǎn)生異常函數(shù) F0
F0 procLOCAL @err:EXCEPTION_REGISTRATION_RECORDLOCAL @dwOldSeh:dword;保存調(diào)用者的異?;卣{(diào)函數(shù)mov eax, fs:[0]mov @dwOldSeh, eax;保存調(diào)用者回調(diào)函數(shù)地址 到 next,不然無法找到調(diào)用者異常函數(shù)處理的地址mov eax, fs:[0]mov @err.Next, eax;注冊異常mov @err.Handler, offset F0Handler ;存入異?;卣{(diào)函數(shù)地址lea eax, @errmov fs:[0], eaxinvoke F1;產(chǎn)生異常mov eax, 1211hmov [eax], eax;卸載SEHmov eax, @dwOldSehmov fs:[0], eaxret
F0 endpstart:invoke F0xor eax, eaxinvoke ExitProcess,eax
end start
異常鏈 : SEH鏈 尾結(jié)點是系統(tǒng)默認的異常函數(shù)處理地址
但是我們一般不會像上面寫
因為結(jié)構(gòu)體是2成員, 一個是調(diào)用者的異?;卣{(diào)函數(shù)信息結(jié)構(gòu)體地址 , 一個是自己的異?;卣{(diào)函數(shù)地址,都是 4字節(jié)
那么我們只需要在棧上 push 2個 dword(2個地址指針) ,就可以了
.586
.model flat,stdcall
option casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.lib.datag_szF0 db "F0",0g_szF1 db "F1",0.codeassume fs:nothingF1Handler proc uses esi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordinvoke MessageBox, NULL, offset g_szF1, NULL, MB_OK;ExceptionContinueExecution, - 程序繼續(xù)執(zhí)行;ExceptionContinueSearch, - 此異常我不處理,交給其它處理mov eax, ExceptionContinueSearchret
F1Handler endp F1 proc;注冊SEHpush offset F1Handler ; handler push 自己異?;卣{(diào)函數(shù)的地址push fs:[0] ;next ;push 調(diào)用者異常處理結(jié)構(gòu)體信息地址mov fs:[0], esp ;注冊回調(diào)函數(shù),移位此時esp 存的就是結(jié)構(gòu)體首地址xor esi, esidiv esi;卸載SEHpop fs:[0] ;把 next 彈回 fs:[0] add esp, 4 ;平棧,因為自己的異?;卣{(diào)函數(shù)地址不需要彈棧,直接丟棄ret
F1 endp F0Handler proc pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordinvoke MessageBox, NULL, offset g_szF0, NULL, MB_OKassume esi:ptr EXCEPTION_RECORDmov esi, pER.if [esi].ExceptionCode == EXCEPTION_INT_DIVIDE_BY_ZERO;處理,跳過產(chǎn)生異常的代碼mov esi, pContextassume esi:ptr CONTEXTadd [esi].regEip, 2assume esi:nothingmov eax, ExceptionContinueExecutionret.endifassume esi:nothingret
F0Handler endpF0 proc;注冊異常; | next | <--esp; | Fohandler | push offset F0Handler ;handler ;push 自己異?;卣{(diào)函數(shù)的地址push fs:[0] ;next ;push 調(diào)用者異常處理結(jié)構(gòu)體信息地址mov fs:[0], esp ;注冊回調(diào)函數(shù),移位此時esp 存的就是結(jié)構(gòu)體首地址invoke F1;產(chǎn)生異常mov eax, 1211hmov [eax], eax;卸載SEH pop fs:[0] ;把 next 彈回 fs:[0] add esp, 4 ;平棧,因為自己的異?;卣{(diào)函數(shù)地址不需要彈棧,直接丟棄ret
F0 endpstart:invoke F0xor eax, eaxinvoke ExitProcess,eax
end start
反調(diào)試
一切阻止調(diào)試的方法都被稱為反調(diào)試
在 OD 或者 x32Dbg 中下斷點時,他會插入一行代碼,但在調(diào)試器中看不出來的 那就是把這一行指令在內(nèi)存的第一個字節(jié)改成了 CC (int 3)
當 TF 被置位 為 1 時 ,執(zhí)行一行代碼 就會 拋出異常,拋出異常之后就會恢復(fù)為 0,因此可以不斷通過 改變 TF 位,來判斷每一行代碼,判斷是否被下斷點
.586
.model flat,stdcall
option casemap:noneinclude windows.incinclude user32.incinclude kernel32.incincludelib user32.libincludelib kernel32.lib.datag_szCaption db "友情提示",0g_szText db "你干嘛調(diào)試我?",0g_szText2 db "結(jié)束了", 0g_ddEnd dd 0 ;函數(shù)結(jié)束地址.codeassume fs:nothingFuncTest proc;存儲mov g_ddEnd, offset ENDTF ;保存函數(shù)結(jié)束位置;設(shè)置TF標志位 (將值置為1 就會拋異常)pushfdor dword ptr [esp], 100hpopfdxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxxor eax, eaxENDTF:retFuncTest endp;異常回調(diào)函數(shù),將 TF 置位
F0Handler proc uses esi edi pER:ptr EXCEPTION_RECORD, pFrame:dword, pContext:ptr CONTEXT, pDC:dwordassume esi:ptr EXCEPTION_RECORDmov esi, pERmov edi, pContextassume edi:ptr CONTEXT;判斷mov eax, [edi].regEip.if byte ptr [eax] == 0cch ;指令的第一個字節(jié)是CC 說明被調(diào)試;被設(shè)置斷點了invoke MessageBox, NULL, offset g_szText, offset g_szCaption, MB_OKinvoke ExitProcess, 0 ;退出進程.endif;結(jié)束mov eax, g_ddEnd.if [edi].regEip == eax ;程序結(jié)束, TF就不需要置位了mov eax, ExceptionContinueExecutionret.endif;繼續(xù)設(shè)置TF標志位or [edi].regFlag, 100hmov eax, ExceptionContinueExecutionassume edi:nothingassume esi:nothingret
F0Handler endpF0 proc;注冊異常; | next | <--esp; | Fohandler | push offset F0Handler ;handlerpush fs:[0] ;nextmov fs:[0], espinvoke FuncTest;卸載SEHpop fs:[0]add esp, 4ret
F0 endpstart:invoke F0invoke MessageBox, NULL , offset g_szText2, NULL, MB_OKxor eax, eaxinvoke ExitProcess,eax
end start
對于部分調(diào)試器,他會接收所有異常,這種處理方式就是 把 主要代碼放在異常中實現(xiàn)
異常很多時候都被用作反調(diào)試
對抗反調(diào)試的方法:
把代碼分成多塊,每塊做加密,執(zhí)行每塊代碼之前 先進異常還原,還原之后再進異常變成加密狀態(tài),因為不解密前面的代碼,無法知道后面的代碼去哪
處理方法:代碼追蹤,把執(zhí)行的每一行代碼記錄下來
把代碼放到堆里面,在堆里面執(zhí)行完再回到代碼區(qū),這樣代碼追蹤就失效了,因為重啟地址就變了