天天看點

利用SEH進行代碼混淆

    這幾天在重看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];

    這幾個域包含了重要資訊。

    來看個常見而又蛋痛的對話框:

利用SEH進行代碼混淆

    當你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;
}      

繼續閱讀