這幾天在重看SEH機制,收獲頗豐。随手寫了一個用SEH進行跳轉的代碼貼于此處以作紀念。
當發生異常,并捕捉了異常,在OS的異常處理機制下,會進入異常過濾函數。過濾函數可以傳回EXCEPTION_EXECUTE_HANDLER/EXCEPTION_CONTINUE_SEARCH/EXCEPTION_CONTINUE_EXECUTION三者之一,以此決定OS的後續操作。如果傳回EXCEPTION_CONTINUE_EXECUTION,OS認為異常已解決,可以從發生異常的指令處繼續運作。一般這是用于解決通路記憶體出錯的情況,比如:
int* ptr=NULL;
int ex(EXCEPTION_POINTERS* ExceptionInfo)
{
ExceptionInfo->ContextRecord->Eax=(DWORD)malloc(sizeof(int));
return EXCEPTION_CONTINUE_EXECUTION;
}
int main()
{
__try
{
*ptr = 0x00;
//004010AC mov eax,[ptr (00422780)]
//004010B1 mov dword ptr [eax],0
MessageBox(NULL,"","",MB_OK);
}
__except(ex(GetExceptionInformation()))
{
}
}
當執行*ptr=0x00時,由于ptr指向空,是以mov dword ptr [eax],0這句會引起異常。為了讓代碼繼續往下執行彈出對話框,隻要讓eax指向有效記憶體即可。那如何讓eax指向有效位址?隻要給eax建立堆記憶體即可,是以可以在異常過濾函數中開辟空間。按windows的設計,異常發生時OS會把壓入在堆棧中寄存器值儲存到線程環境中(通過KiTrapFrameToContext);當從異常傳回時,用儲存的線程環境恢複寄存器繼續執行(通過KiContextToTrapFrame)。那麼所謂的線程環境在哪?代碼中ExceptionInfo->ContextRecord即為所求。是以,我在過濾函數中修改Context->Eax,使之指向有效記憶體,實作了修複異常繼續指令流的目的。其實,這整個流程類似于SetThreadContext/GetThreadContext。
另外,值得一提的是,當異常發生時,如果異常類型是訪存失敗,錯誤碼是0xC0000005的情況下,
ExceptionInfo->ExceptionRecord->NumberParameters;
ExceptionInfo->ExceptionRecord->ExceptionAddress;
ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
這幾個域包含了重要資訊。
來看個常見而又蛋痛的對話框:
當你happy的玩着遊戲時,跳出這個,是不是都毀了?如果仔細看出錯資訊上面給出了如下資訊:出錯的指令的位址-0x6F001080,出錯的原因-0xC0000005(通路無效記憶體),無效記憶體的位址0xE2AF524C。這些資訊,進入核心态以後可以輕松獲得,但是使用者态有沒有辦法獲得?比如想做一個使用者态的調試器?當然有,上面提到的3個域依次對應:異常時ExceptionInformation數組的元素數量、出錯的指令的位址、通路無效記憶體的位址。
有了這些輔助資訊後,開始讨論正題。其實,這裡已經呼之欲出了:代碼混淆一般就是通過各種手段混亂代碼執行流程,放在這篇文章中,隻要修改EXCEPTION_CONTINUE_EXECUTION傳回時的位址即可(注意我的用詞,是EXCEPTION_CONTINUE_EXECUTION的傳回位址,不是異常的傳回位址,如果沒記錯對于vc++6.0異常的傳回位址應該是_except_handle3),進一步說,就是觸發異常并儲存線程環境時Eip的值。
#include <windows.h>
#include <stdio.h>
int* ptr;
//4)Msg被調用
void Msg(int a,int b)
{
int c = a+b;
printf("%d\n",c);
MessageBox(NULL,"","",MB_OK);
__asm
{
mov eax,c
}
}
int ex(EXCEPTION_POINTERS* ExceptionInfo)
{
//3)準備跳轉到Msg中
//Msg是帶參數函數,參數儲存在異常發生前的堆棧上,即Esp指向
//此時ExceptionInfo->ContextRecord->Eip[0]指向Lab1代表的位址
//ExceptionInfo->ContextRecord->Eip[4]=0x01 ExceptionInfo->ContextRecord->Eip[8]=0x02
ExceptionInfo->ContextRecord->Eip = (DWORD)Msg;
return EXCEPTION_CONTINUE_EXECUTION;
}
int main()
{
int i=0;
ptr = (int*)&main;
__try
{
/*
原本向jmp Lab2,結果提示"illegal jump into __try scope",就這麼代替一下
*/
if(1)
goto Lab2;
Lab1:
//5)從Msg傳回後 指令流進入Lab1
i++;
printf("%d\n",i);
ExitProcess(0);
Lab2:
__asm
{
//1)制造Msg函數的參數及傳回位址.模拟c調用函數的過程.異常觸發後要跳入Msg執行
//對于Msg函數,他根本不知道是誰調用他的,他隻關心參數是否正确
push 0x02;
push 0x01;
lea eax,Lab1;
push eax;
}
//2)觸發異常,給與Lab1中真正需要執行的代碼于機會
(*ptr) = 0x01;
}
__except(ex(GetExceptionInformation()))
{}
return 0;
}