<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    在ShellCode里面使用異常處理(Win64位平臺)

    VSole2023-06-21 11:19:17

    網上關于ShellCode編寫的文章很多,但介紹如何在ShellCode里面使用異常處理的卻很少。筆者前段時間寫了一個ShellCode,其中有一個功能是內存加載多個別人寫的DLL插件,然后調用里面的函數,結果因為某個DLL函數里面發生了異常,導致ShellCode進程直接閃退,所以學習了一下在ShellCode里面如何使用異常處理的方法。

    Windows程序的異常處理,其實是三者結合的:操作系統、編譯器和程序代碼。因為x86下異常處理的文章太多,所以本文只介紹Win64下的。X86的異常(本文僅談論SEH異常)一般的流程為:進入 try...語句之前先注冊到異常鏈表,執行完代碼后,再摘除,不管有沒有異常發生,這個步驟都是必不可少的,所以多多少少會影響程序的性能。

    而Win64的異常處理,是基于表的。也就是說,編譯器在編譯代碼的時候,會同時對每個函數(還分非葉,不深究)生成一個異常表,最后鏈接到PE的異常表里。當程序發生異常的時候,操作系統跟根據當前地址,枚舉所有異常表,如果地址位于某個表的開始和結束地址之間,則使用這個表處理。相對來說,這個比X86更加安全和高效。

    這個異常表,稱為RUNTIME_FUNCTION,MSDN里面定義如下:

    typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {
      DWORD BeginAddress;
      DWORD EndAddress;
      union {
        DWORD UnwindInfoAddress;
        DWORD UnwindData;
      } DUMMYUNIONNAME;
    } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION, _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY;
    

    這些表連續存放在PE的異常目錄中,每個表對應一個函數,所有成員都是相當于ImageBase的開始相對地址。其中BeginAddress表示這個函數的開始地址,EndAddress則對應結束地址,最重要的,是UnwindInfoAddress,它對應另外一個結構UNWIND_INFO

    typedef struct _UNWIND_INFO {
        UBYTE Version       : 3;
        UBYTE Flags         : 5;
        UBYTE SizeOfProlog;
        UBYTE CountOfCodes;
        UBYTE FrameRegister : 4;
        UBYTE FrameOffset   : 4;
        UNWIND_CODE UnwindCode[1];/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
    *   union {
    *       OPTIONAL ULONG ExceptionHandler;
    *       OPTIONAL ULONG FunctionEntry;
    *   };
    *   OPTIONAL ULONG ExceptionData[]; */} UNWIND_INFO, *PUNWIND_INFO;
    

    成員如下:

      Version:版本號,一般為1,最高目前是2。

      Flags:取值如下

          #define UNW_FLAG_EHANDLER 0x01 ---表示有except塊

          #define UNW_FLAG_UHANDLER 0x02 ---表示有finally塊

          #define UNW_FLAG_CHAININFO 0x04 ---表示后面是另外一個Runtime_Function

    SizeOfProlog:函數頭到try塊的位置。

     CountOfCodes:表示后面有多少個UNWIND_CODE 結構。注意:這個數值是偶數對齊的,如果為奇數,說明最后一個為空值的UNWIND_CODE。

     UNWIND_CODE數組:實際上就是回滾表,保存了進入異常代碼前的寄存器狀態,用于異常處理后回滾到原來的狀態。

    ExceptionHandler(可選):如果Flags包含了UNW_FLAG_EHANDLER 或UNW_FLAG_UHANDLER,則ExceptionHandler指向異常處理函數。其中Delphi語言是指向system.pas里面的函數_DelphiExceptionHandler;VS相對來說復雜一些,如果是SEH異常,一般是指向__C_specific_handler函數。

    ExceptionData(可選):這個具體語言是不同的,所以windbg解釋異常表的時候也是只解釋到上一個成員。這個一般是(Delphi語言則肯定也只會)指向一個SCOPE_TABLE結構:

     typedef struct _SCOPE_TABLE 
     {
      ULONG Count;     
      struct     
        {         
         ULONG BeginAddress;         
         ULONG EndAddress;         
         ULONG HandlerAddress;         
         ULONG JumpTarget;     
         } ScopeRecord[1]; 
     } SCOPE_TABLE, *PSCOPE_TABLE;
    

    簡單一點來說,Runtime_function是給操作系統判斷異常發生在哪個函數里面,SCOPE_TABLE則是在異常處理函數里面使用的(觸發異常處理函數的時候,會傳遞過去),主要記錄了更精確的異常發生位置區間和需要跳轉處理的地址。

    如果想在ShellCode里面使用異常,則請進行如下步驟:

    1、提取語言的異常處理函數,讓它稱為ShellCode的一個函數(Delphi語言是system.pas里面的_DelphiExceptionHandler函數,VS則應該提取C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\amd64\chandler.c里面的__C_specific_handler函數)。注意提取后的ShellCode化(修正API調用等)。當然,你也可以完全自己編寫處理函數。

    2、在需要使用異常處理的函數像往常一樣使用try catch...try except...,在保存ShellCode的時候,使用API函數RtlLookupFunctionEntry查找這個函數的Runtime_Function,然后查找成員UNWIND_INFO,修正所有的相對地址后,跟ShellCode放在一起。

    3、ShellCode運行后,調用API函數RtlAddFunctionTable將第二步修正的表添加到系統。

    附件說明:

    1、PE64異常表枚舉工具.exe:一個用于枚舉異常表的小工具。對于Delphi編譯的PE,還會枚舉SCOPE_TABLE。

    2、ShellCode64.bin:一個在ShellCode里面使用異常的DEMO。

    3、LoadTest.exe:加載ShellCode64.bin進行測試的程序,代碼見LoadTest.dpr。

    4、Dll.dll:測試調用Dll里面有異常發生的函數。

    5、Dll.dpr、LoadTest.dpr。Delphi代碼。

    其中ShellCode64.bin的部分代碼如下:

    “程序內置單個異常測試”按鈕:

    procedure OnButton1Click(hForm1: HWND);
    var
      bExcept: Boolean;
      i, j, nRet: Integer;
      szMaker: array[0..3] of AnsiChar;
      szBuffer: array[0..127] of AnsiChar;
    begin
      i := 10;
      j := 0;
      bExcept := False;
      try
        nRet := i div j;
      except
        WindowsAPI^.fnMessageBoxA(hForm1, '進入except節', FixPAnsiChar('MyTest'), MB_ICONWARNING + MB_TOPMOST);
        nRet := 0;
        bExcept := True;
      end;
      if bExcept then
      begin
        WindowsAPI^.fnwsprintfA(szBuffer, FixPAnsiChar('發生異常!nRet=%d'), nRet);
      end
      else
      begin
        WindowsAPI^.fnwsprintfA(szBuffer, FixPAnsiChar('沒有異常!nRet=%d'), nRet);
      end;
      WindowsAPI^.fnMessageBoxA(hForm1, szBuffer, FixPAnsiChar('MyTest'), MB_ICONINFORMATION + MB_TOPMOST);
    end;
    

    “程序內置鑲套異常測試”按鈕:這個實際上是先觸發除0異常,再在異常里面通過給一個空指針賦值產生第二個異常:

    procedure OnButton2Click(hForm1: HWND);
    var
      bExcept: Boolean;
      i, j, nRet: Integer;
      szMaker: array[0..3] of AnsiChar;
      szBuffer: array[0..127] of AnsiChar;
      p: Pointer;
    begin
      i := 10;
      j := 0;
      p := nil;//空指針
      bExcept := False;
      try
        nRet := i div j;
      except
        WindowsAPI^.fnMessageBoxA(hForm1, '進入except節1', FixPAnsiChar('MyTest'), MB_ICONWARNING + MB_TOPMOST);
        try
          PInteger(p)^ := 999;
        except
          bExcept := True;
          WindowsAPI^.fnMessageBoxA(hForm1, '進入except節2', FixPAnsiChar('MyTest'), MB_ICONWARNING + MB_TOPMOST);
        end;
        nRet := 0;
        bExcept := True;
      end;
      if bExcept then
      begin
        WindowsAPI^.fnwsprintfA(szBuffer, FixPAnsiChar('發生異常!nRet=%d'), nRet);
      end
      else
      begin
        WindowsAPI^.fnwsprintfA(szBuffer, FixPAnsiChar('沒有異常!nRet=%d'), nRet);
      end;
      WindowsAPI^.fnMessageBoxA(hForm1, szBuffer, FixPAnsiChar('MyTest'), MB_ICONINFORMATION + MB_TOPMOST);
    end;
    

    “Dll函數1"按鈕和“Dll函數2"按鈕的代碼其實是一樣的,只不過是Dll導出的函數名稱不一樣而已:

    procedure OnButton4Click(hForm1, hEdit: HWND);
    type
      TGetInteger = function: Integer; stdcall;
    var
      szDllFileName: array[0..MAX_PATH - 1] of AnsiChar;
      hDll: HMODULE;
      MyGetIntege: TGetInteger;
    var
      bExcept: Boolean;
      nRet: Integer;
      szMaker: array[0..3] of AnsiChar;
      szBuffer: array[0..127] of AnsiChar;
    begin
      WindowsAPI^.fnGetWindowTextA(hEdit, szDllFileName, MAX_PATH);
      hDll := WindowsAPI^.fnLoadLibraryA(szDllFileName);
      if hDll = 0 then
      begin
        WindowsAPI^.fnMessageBoxA(hForm1, FixPAnsiChar('Dll文件加載失敗!'), FixPAnsiChar('MyTest'), MB_ICONWARNING + MB_TOPMOST);
        Exit;
      end;
      @MyGetIntege := WindowsAPI^.fnGetProcAddress(hDll, FixPAnsiChar('GetInteger1'));
      if @MyGetIntege = nil then
      begin
        WindowsAPI^.fnMessageBoxA(hForm1, FixPAnsiChar('函數GetInteger1獲取失敗!'), FixPAnsiChar('MyTest'), MB_ICONWARNING + MB_TOPMOST);
        WindowsAPI^.fnFreeLibrary(hDll);
        Exit;
      end;
      bExcept := False;
      try
        nRet := MyGetIntege;
      except
        WindowsAPI^.fnMessageBoxA(hForm1, '進入except節', FixPAnsiChar('MyTest'), MB_ICONINFORMATION + MB_TOPMOST);
        nRet := 0;
        bExcept := True;
      end;
      if bExcept then
      begin
        WindowsAPI^.fnwsprintfA(szBuffer, FixPAnsiChar('發生異常!nRet=%d'), nRet);
      end
      else
      begin
        WindowsAPI^.fnwsprintfA(szBuffer, FixPAnsiChar('沒有異常!nRet=%d'), nRet);
      end;
      WindowsAPI^.fnMessageBoxA(hForm1, szBuffer, FixPAnsiChar('MyTest'), MB_ICONINFORMATION + MB_TOPMOST);
    end
    

    Dll主要導出兩個函數,一個有異常,一個沒有異常(GetInteger2實際上也是可能存在異常的---如果隨機數為0),你也可以使用VS之類編寫一個DLL來測試:

    function GetInteger1:Integer;stdcall;
    var
    i,j:Integer;
    begin
      i:=0;
      j:=Random(100);
      Result:=j div i;
    end;
    function GetInteger2:Integer;stdcall;
    var
    i,j:Integer;
    begin
      i:=Random(100);
      j:=Random(10000);
      Result:=j div i;
    end;
    

    ShellCode的入口函數:

    procedure _Start;
    var
      p: PAnsiChar;
      nCount: Integer;
      SDKForm: TSDKForm; //SDK窗口類
    begin
      if not InitWindowsAPI then Exit; //初始化全局API函數表
      p := FixPAnsiChar(fnA); //獲取異常表信息位置
      nCount := PInteger(p)^;
      Inc(p, sizeof(Integer));
      if not WindowsAPI^.fnRtlAddFunctionTable(p, nCount, DWORD64(@_Start)) then
      begin
        WindowsAPI^.fnMessageBoxA(0, FixPAnsiChar('fnRtlAddFunctionTable Error'), FixPAnsiChar('Caption'), MB_ICONWARNING + MB_TOPMOST);
        Exit;
      end;
      SDKForm.CreateWindow;
      SDKForm.MessageLoop;
      DoneWindowsAPI;//釋放全局API函數表
    end;
    

    里面的函數FixPAnsiChar其實是在X86下使用的,Win64下Delphi使用的都是相對地址了,可以直接跟平時一樣使用字符串的。

    這是我寫的第五個shellcode程序,如有錯漏之處,敬請指正!

    異常處理delphi
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    網上關于ShellCode編寫的文章很多,但介紹如何在ShellCode里面使用異常處理的卻很少。Windows程序的異常處理,其實是三者結合的:操作系統、編譯器和程序代碼。因為x86下異常處理的文章太多,所以本文只介紹Win64下的。而Win64的異常處理,是基于表的。也就是說,編譯器在編譯代碼的時候,會同時對每個函數生成一個異常表,最后鏈接到PE的異常表里。相對來說,這個比X86更加安全和高效。
    近日,奇安信威脅情報中心紅雨滴團隊在日常的威脅狩獵中捕獲了該組織多個Crimson RAT攻擊樣本。值得注意的是Transparent Tribe組織為了降低攻擊樣本的查殺率,對相關攻擊樣本進行了加殼處理。在本次攻擊活動中,MuddyWater還利用金絲雀令牌來跟蹤目標的成功感染,這是該組織新利用的TTP。
    六方云工業衛士:OT與IT融合下的工控主機“守護神”
    Zeppelin勒索軟件的開發者在經歷了一段相對沉寂的時期后,已經恢復了活動,并開始宣傳該惡意軟件的新版本。上個月末,該惡意軟件的一種最新變種出現在一個黑客論壇上。
    0x00 日常查殼無殼64位0x01 CFGGETC在講這題ollvm與異常處理之前,有必要先搞懂我們到底是怎么輸入的。一共有三處getc處理我們第一段輸入的地方。程序最先開始運行的是 407629,這里我們可以輸入上下左右箭頭與特定的數字。隨后到 40553A 讀取為5B。
    內核學習-異常處理
    2021-12-31 16:22:12
    異常產生后,首先是要記錄異常信息,然后要尋找異常的處理函數,稱為異常的分發,最后找到異常處理函數并調用,稱為異常處理異常處理異常分發,異常處理 展開。
    Windows中主要的異常處理機制:VEH、SEH、C++EH。 SEH中文全稱:結構化異常處理。就是平時用的__try __finally __try __except,是對c的擴展。 VEH中文全稱:向量異常處理。一般來說用AddVectoredExceptionHandler去添加一個異常處理函數,可以通過第一個參數決定是否將VEH函數插入到VEH鏈表頭,插入到鏈表頭的函數先執行,如果為
    但是,這樣一來,就必須在每一個Controller類都定義一套這樣的異常處理方法,因為異常可以是各種各樣。所以注解@ControllerAdvice出現了,簡單的說,該注解可以把異常處理器應用到所有控制器,而不是單個控制器。這就是統一異常處理的原理。統一異常處理實戰在定義統一異常處理類之前,先來介紹一下如何優雅的判定異常情況并拋異常
    Kafka消息積壓的典型場景:1.實時/消費任務掛掉比如,我們寫的實時應用因為某種原因掛掉了,并且這個任務沒有被監控程序監控發現通知相關負責人,負責人又沒有寫自動拉起任務的腳本進行重啟。此外,Kafka分區數是Kafka并行度調優的最小單元,如果Kafka分區數設置的太少,會影響Kafka consumer消費的吞吐量。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类