【轉】反調試技巧總結-原理和實現


總結:

 

1.  FindWindow。比如 FindWindowA("OLLYDBG", NULL);

2.  EnumWindow函數調用后,系統枚舉所有頂級窗口,為每個窗口調用一次回調函數。在回調函數中用 GetWindowText得到窗口標題,進行檢測。

3.  GetForeGroundWindow返回前台窗口(用戶當前工作的窗口)。當程序被調試時,調用這個函數將獲得Ollydbg的窗口句柄,再用GetWindowTextA檢測。

4.  枚舉進程列表,看是否有調試器進程(OLLYDBG.EXE,windbg.exe等)

5.  父進程是否是Explorer。通常進程的父進程是explorer.exe(雙擊執行的情況下),否則可能程序被調試。

6.  RDTSC/ GetTickCount時間敏感程序段當進程被調試時,調試器事件處理代碼、步過指令等將占用CPU循環當進程被調試時,調試器事件處理代碼、步過指令等將占用CPU循環。

7.  StartupInfo結構檢測Windows操作系統中的explorer.exe創建進程的時候會把STARTUPINFO結構中的值設為0,而非explorer.exe創建進程的時候會忽略這個結構中的值,也就是結構中的值不為0,所以可以利用這個來判斷OD是否在調試程序。

8.  BeingDebuggedkernel32!IsDebuggerPresent() API檢測進程環境塊(PEB)中的BeingDebugged標志檢查這個標志以確定進程是否正在被用戶模式的調試器調試。

9.  PEB.NtGlobalFlag,Heap.HeapFlags, Heap.ForceFlags。通常程序沒有被調試時,PEB另一個成員NtGlobalFlag(偏移0x68)值為0,如果進程被調試通常值為0x70(代表下述標志被設置)由於NtGlobalFlag標志的設置,堆也會打開幾個標志,這個變化可以在ntdll!RtlCreateHeap()里觀測到。正常情況下系統為進程創建第一個堆時會將Flags和ForceFlags分別設為2(HEAP_GROWABLE)和0  。當進程被調試時,這兩個標志通常被設為50000062(取決於NtGlobalFlag)和0x40000060(等於Flags AND 0x6001007D)。

10.EPROCESS的DebugPort成員: CheckRemoteDebuggerPresent() /NtQueryInformationProcess()。Kernel32!CheckRemoteDebuggerPresent() 是用於確定是否有調試器被附加到進程,內部調用了ntdll!NtQueryInformationProcess()檢索內核結構EPROCESS的DebugPort成員。

11.SetUnhandledExceptionFilter/Debugger Interrupts調試器中步過INT3和INT1指令的時候,由於調試器通常會處理這些調試中斷,所以設置的異常處理例程默認情況下不會被調用,這樣我們可以在異常處理例程中設置標志,通過INT指令后如果這些標志沒有被設置則意味着進程正在被調試。

12.Trap Flag單步標志異常TF=1的時候,會觸發單步異常,在異常中設定檢測,正常程序可進入,未修改OD調試程序不能進入此異常,從而檢測調試。

13.SeDebugPrivilege進程權限.默認情況下進程沒有SeDebugPrivilege權限,調試時,會從調試器繼承這個權限。

14.DebugObject:NtQueryObject()內核對象檢測。

15.OllyDbg:Guard Pages.OllyDbg允許設置一個內存訪問/寫入斷點,這種類型的斷點是通過頁面保護來實現的,  頁面保護是通過PAGE_GUARD頁面保護修改符來設置的,如果訪問的內存地址是受保護頁面的一部分,將會產生一個 STATUS_GUARD_PAGE_VIOLATION(0x80000001)異常。如果進程被OllyDbg調試並且受保護的頁面被訪問,將不會拋 出異常,訪問將會被當作內存斷點來處理,從而檢測到。

16.Software Breakpoint.通過修改目標地址代碼為0xCC(INT3/BreakpointInterrupt)來設置的斷點。通過在受保護的代碼段和(或)API函數中掃描字節0xCC來識別軟件斷點。

17.HardwareBreakpoints.通過傳遞給異常處理例程的ContextRecord參數來訪問,  含有調試寄存器值的CONTEXT結構,判斷Dr0-Dr3是否設置了值,來判斷調試。

18.PatchingDetectionCodeChecksumCalculation補丁檢測,代碼檢驗和.能識別殼的代碼是否被修改,或軟件斷點。

19.block input封鎖鍵盤、鼠標輸入。

20.EnableWindow禁用窗口。

21.ThreadHideFromDebugger. ntdll!NtSetInformationThread()用來設置一個線程的相關信息。把ThreadInformationClass參數設為ThreadHideFromDebugger(11H)  可以禁止線可以禁止線程產生調試事件。

22.DisablingBreakpoints禁用硬件斷點。利用CONTEXT結構,該結構利用異常處理獲得,異常處理完后會自動寫回。

23.OllyDbg:OutputDebugString()Format String Bug。OutputDebugString函數用於向調試器發送一個格式化的串,Ollydbg會在底端顯示相應的信息。OllyDbg存在格式化字符串 溢出漏洞,非常嚴重,輕則崩潰,重則執行任意代碼。這個漏洞是由於Ollydbg對傳遞給kernel32!OutputDebugString()的字符串參數過濾不嚴導致的,它只對參數進行那個長度檢查,只接受255個字節,但沒對參數進行檢查,所以導致緩沖區溢出溢出。

24.TLS Callbacks使用Thread Local Storage (TLS)回調函數可以實現在實際的入口點之前執行反調試的代碼,這也是OD載入程序就退出的原因所在。

25.CreateFile檢測。win32程序對vxd程序通信時,是通過調用DeviceIoControl函數進入vxd,此函數的一個參數就是由createfile獲得的設備句柄。這樣,同樣生成或打開文件的調用也能夠打開一個到vxd的通道。要用createfile打開一個vxd,而不是一個通常的文件,在文件名的地方必須使用特殊形式。

1.FindWindow

比如 FindWindowA("OLLYDBG", NULL);

szClassName     db     'ollydbg',0

        invoke FindWindow,addr szClassName,NULL ;通過類名進行檢測

              .if       eax     ;找到

                    jmp   debugger_found    

              .endif               

 

2.EnumWindow

系統枚舉所有頂級窗口,為每個窗口調用一次回調函數。在回調函數中用 GetWindowText得到窗口標題,進行檢測。

.386

.modelflat,stdcall

optioncasemap:none

includewindows.inc

includeuser32.inc

includelibuser32.lib

includekernel32.inc

includelibkernel32.lib

include  Shlwapi.inc

includelib Shlwapi.lib   ;strstr

 

            .const

szTitle     db       'ollydbg',0       

szCaption   db       '結果',0

szFindOD    db       '發現目標窗口',0

szText      db       '枚舉已結束,沒提示發現目標,則沒有找到目標窗口',0

            .code

;定義回調函數

_CloseWnd procuses ebx edi esi,_hWnd,_lParam

         LOCAL  @szBuffer[1024]:BYTE   ;接收窗口標題

         invoke IsWindowVisible,_hWnd

         .if eax ;是否是可見的窗口

             invoke GetWindowText,_hWnd,addr@szBuffer,sizeof @szBuffer

             invoke StrStrI,addr@szBuffer,offset szTitle  ;查找標題中有無字符串,不帶I的大小寫敏感

             .if eax

                 invoke   MessageBox,NULL,addr szFindOD,addrszCaption,MB_OK

                 invoke   PostMessage,_hWnd,WM_CLOSE,0,0  ;關閉目標

             .endif

         .endif

         mov eax,TRUE ;返回true 時,EnumWindows繼續枚舉下一個窗口,false退出枚舉.

         ret

_CloseWnd endp

 

start:

           invoke   EnumWindows,addr _CloseWnd,NULL

;EnumWindows調用,系統枚舉所有頂級窗口,為每個窗口調用一次回調函數

           invoke   MessageBox,NULL,addr szText,addrszCaption,MB_OK

           invoke   ExitProcess,NULL

           end start

 

3.GetForeGroundWindow返回前台窗口

GetForeGroundWindow返回前台窗口(用戶當前工作的窗口)。當程序被調試時,調用這個函數將獲得Ollydbg的窗口句柄,這樣就可以向其發送WM_CLOSE消息將其關閉了。

 

invoke IsDebuggerPresent

              .if     eax

                      invoke GetForegroundWindow   ;獲得的是OD的窗口句柄

                      invoke SendMessage,eax,WM_CLOSE,NULL,NULL

              .endif

獲取OD窗口句柄后的處理

 

(1)向窗口發送WM_CLOSE消息

              invoke  FindWindow,addr szClassName,NULL  ;通過類名進行檢測

              .if    eax     ;找到

                      mov     hWinOD,eax

invoke     MessageBox,NULL,offset szFound,offset szCaption,MB_OK                     invoke   SendMessage,hWinOD,WM_CLOSE,NULL,NULL

              .endif

(2)終止相關進程,根據窗口句柄獲取進程ID,根據進程ID獲取進程句柄,

_GetODProcID    proc

        LOCAL  @hWinOD              ;窗口句柄

        LOCAL  @hProcessOD           ;進程句柄

        LOCAL  @idProcessOD          ;進程ID

       invoke FindWindow,addr szClassName,NULL ;通過類名進行檢測

       .if    eax     ;找到

             mov       @hWinOD,eax        ;窗口句柄  

             invoke   GetWindowThreadProcessId,@hWinOD,addr @idProcessOD  

;獲取進程ID在@idProcessOD里

             invoke   OpenProcess,PROCESS_TERMINATE,TRUE,@idProcessOD     

;獲取進程句柄在返回值里

             .if    eax                     ;獲取句柄成功

                     mov      @hProcessOD,eax

               invoke   TerminateProcess,@hProcessOD,200    ;利用句柄終止進程

                     invoke     CloseHandle,@hProcessOD            ;關閉進程句柄

                     invoke   MessageBox,NULL,addr szClose,addr szMerry,MB_OK

             .else                          ;獲取句柄失敗,多因權限問題

                     invoke    MessageBox,NULL,addr szFail,addr szCaption,MB_OK

             .endif                         .

       .endif               

       ret

_GetODProcIDendp

4. 枚舉進程列表,看是否有調試器進程

枚舉進程列表,看是否有調試器進程(OLLYDBG.EXE,windbg.exe等)。

利用kernel32!ReadProcessMemory()讀取進程內存,然后尋找調試器相關的字符串(如”OLLYDBG”)以防止逆向分析人員修改調試器的可執行文件名。

              .386

              .model flat, stdcall

              option casemap :none

include           windows.inc

include           user32.inc

includelib      user32.lib

include           kernel32.inc

includelib      kernel32.lib

               .const

stSysProc       db     'OLLYDBG.EXE',0

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

              .code

_GetProcList    proc

       LOCAL  @stProcessEntry:PROCESSENTRY32

       LOCAL  @hSnapShot

       invoke  CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,NULL

       mov    @hSnapShot,eax                                    

       mov    @stProcessEntry.dwSize,sizeof @stProcessEntry

       invoke Process32First,@hSnapShot,addr @stProcessEntry

       .while eax

              invokelstrcmp,addr @stProcessEntry.szExeFile,addr stSysProc

              .if    eax == 0       ;為0,說明進程名相同

                  push 20

                 invoke  MessageBox,NULL,addrszFound,addr szCaption,MB_OK

              .endif             

              invokeProcess32Next,@hSnapShot,addr @stProcessEntry                     

       .endw

       pop    eax

       .if    eax != 20

                invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

       .endif

       ret

_GetProcListendp

 

start:

              invoke  _GetProcList

              invoke     ExitProcess,NULL

              end  start

 

5. 父進程是否是Explorer

原理:通常進程的父進程是explorer.exe(雙擊執行的情況下),否則可能程序被調試。

下面是實現這種檢查的一種方法:

1.通過TEB(TEB.ClientId)或者使用GetCurrentProcessId()來檢索當前進程的PID

2.用Process32First/Next()得到所有進程的列表,注意explorer.exe的PID(通過PROCESSENTRY32.szExeFile)和通過PROCESSENTRY32.th32ParentProcessID獲得的當前進程的父進程PID。Explorer進程ID也可以通過桌面窗口類和名稱獲得。

3.如果父進程的PID不是explorer.exe,cmd.exe,Services.exe的PID,則目標進程很可能被調試

對策:OllyAdvanced提供的方法是讓Process32Next()總是返回fail,使進程枚舉失效,PID檢查將會被跳過。這些是通過補丁kernel32!Process32NextW()的入口代碼(將EAX值設為0然后直接返回)實現的。

(1)通過桌面類和名稱獲得Explorer的PID 源碼見附件

                .data?    

szDesktopClass       db    'Progman',0                ;桌面的窗口類

szDesktopWindow  db    'ProgramManager',0         ;桌面的窗口名稱

dwProcessID     dd  ?                        ;保存進程ID

dwThreadID      dd  ?                       ;保存線程ID

                .code

invoke     FindWindow,addr szDesktopClass,addrszDesktopWindow  ;獲取桌面窗口句柄

invoke     GetWindowThreadProcessId,eax,offsetdwProcessID      ;獲取EXPLORER進程ID

mov     dwThreadID,eax                     ;線程ID

(2)通過進程列表快照獲得Explorer的PID 源碼見附件

szExplorer      db     'EXPLORER.EXE',0

dwParentID     dd     ?

dwExplorerID   dd     ?

_ProcTest  proc

        local @stProcess:PROCESSENTRY32         ;每一個進程的信息

              local  @hSnapShot                    ;快照句柄     

          pushad                          

          

        invoke GetCurrentProcessId

        mov    ebx,eax                ;當前進程ID

              invoke     RtlZeroMemory,addr @stProcess,sizeof @stProcess ; 0初始化進程信息結構

              mov      @stProcess.dwSize,sizeof@stProcess             ;手工填寫結構大小

              invoke     CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS,0;獲取進程列表快照

              mov       @hSnapShot,eax                                  ;快照句柄

              invoke     Process32First,@hSnapShot,addr @stProcess       ;第一個進程

              .while     eax

                .if  ebx ==@stProcess.th32ProcessID               ;是當前進程嗎?

                      mov  eax,@stProcess.th32ParentProcessID      ;是,則保存父進程ID

                      mov  dwParentID,eax                  

                .endif

                invoke   lstrcmp,addr @stProcess.szExeFile,addrszExplorer ;Explorer進程ID

              .if   eax == 0       ;為0,說明進程名相同                       

                       mov eax,@stProcess.th32ProcessID

                       mov dwExplorerID,eax

               .endif                   

                   invoke  Process32Next,@hSnapShot,addr @stProcess ;下一個進程

              .endw

              invoke     CloseHandle,@hSnapShot  ;關閉快照

             

              mov  ebx,dwParentID

        .if ebx == dwExplorerID    ;父進程ID與EXPLORER進程ID比較                                  invoke MessageBox,NULL,offset szNotFound,offset szCaption,MB_OK

              .else

                     invoke  MessageBox,NULL,offset szFound,offsetszCaption,MB_OK

              .endif

        popad

              ret

_ProcTest endp

6.RDTSC/ GetTickCount時間敏感程序段

當進程被調試時,調試器事件處理代碼、步過指令等將占用CPU循環。如果相鄰指令之間所花費的時間如果大大超出常規,就意味着進程很可能是在被調試。

(1)RDTSC

將計算機啟動以來的CPU運行周期數放到EDXEAX里面,EDX是高位,EAX是低位。

如果CR4的TSD(timestamp disabled)置位,則rdtsc在ring3下運行會導致異常(特權指令),所以進入ring0,把這個標記置上,然后Hook OD的WaitForDebugEvent,攔截異常事件,當異常代碼為特權指令時,把異常處的opcode讀出檢查,如果是rdtsc,把eip加2,SetThreadContext,edx:eax的返回由你了。

(2)GetTickCount 源碼見附件

invoke GetTickCount           ;第一次調用

              mov     ebx,eax                ;結果保存在ebx里

              mov     ecx,10                 ;延時開始

              mov     edx,6                  ;單步走,放慢速度     

            mov     ecx,10                 ;延時結束

              invoke  GetTickCount           ;第二次調用

              sub     eax,ebx                ;計算差值

              .if     eax > 1000          ;假定大於1000ms,就說明有調試器  

                jmp   debugger_found

              .endif                  

 

7.StartupInfo結構檢測

原理:Windows操作系統中的explorer.exe創建進程的時候會把STARTUPINFO結構中的值設為0,而非explorer.exe創建進程的時候會忽略這個結構中的值,也就是結構中的值不為0,所以可以利用這個來判斷OD是否在調試程序.

if (Info.dwX<>0) or(Info.dwY<>0) or (Info.dwXCountChars<>0) or(Info.dwYCountChars<>0) or   (Info.dwFillAttribute<>0) or (Info.dwXSize<>0) or(Info.dwYSize<>0) then  “有調試器

*******************************************************************************

結構體

typedef struct _STARTUPINFO

{

   DWORD cb;            0000

   PSTR lpReserved;        0004

   PSTR lpDesktop;         0008

   PSTR lpTitle;            000D

  DWORD dwX;          0010

   DWORD dwY;           0014

  DWORD dwXSize;        0018

   DWORD dwYSize;        001D

   DWORD dwXCountChars;  0020

   DWORDdwYCountChars;  0024

   DWORDdwFillAttribute;   0028

   DWORD dwFlags;         002D

   WORD wShowWindow;    0030

   WORD cbReserved2;       0034

   PBYTE lpReserved2;       0038

   HANDLE hStdInput;       003D

   HANDLE hStdOutput;      0040

   HANDLE hStdError;       0044

} STARTUPINFO, *LPSTARTUPINFO;

_ProcTest proc

               LOCAL  @stStartupInfo:STARTUPINFO       

               pushad           

                 invoke  GetStartupInfo,addr @stStartupInfo

                 cmp     @stStartupInfo.dwX,0

                 jnz      foundDebugger

                 cmp     @stStartupInfo.dwY,0

               jnz      foundDebugger

                 cmp     @stStartupInfo.dwXCountChars,0

               jnz      foundDebugger

                 cmp     @stStartupInfo.dwYCountChars,0

                 jnz      foundDebugger

                 cmp     @stStartupInfo.dwFillAttribute,0

                 jnz      foundDebugger

                 cmp     @stStartupInfo.dwXSize,0

                 jnz      foundDebugger

                 cmp     @stStartupInfo.dwYSize,0

                 jnz      foundDebugger                

     noDebugger: “無調試器

               jmp     TestOver

  foundDebugger: “有調試器

       TestOver:       

               popad

               ret

_ProcTest endp

 

8. BeingDebugged

kernel32!IsDebuggerPresent() API檢測進程環境塊(PEB)中的BeingDebugged標志檢查這個標志以確定進程是否正在被用戶模式的調試器調試。

每個進程都有PEB結構,一般通過TEB間接得到PEB地址

Fs:[0]指向當前線程的TEB結構,偏移為0處是線程信息塊結構TIB

TIB偏移18H處是self字段,是TIB的反身指針,指向TIB(也是PEB)首地址

TEB偏移30H處是指向PEB結構的指針

PEB偏移2H處,就是BeingDebugged字段,Uchar類型

(1)      調用IsDebuggerPresent函數,間接讀BeingDebugged字段

(2)      利用地址直接讀BeingDebugged字段

對策:

(1)      數據窗口中Ctrl+G fs:[30] 查看PEB數據,將PEB.BeingDebugged標志置0

(2)      Ollyscript命令"dbh"可以補丁這個標志

.386

.modelflat,stdcall

optioncasemap:none

include    windows.inc

include    user32.inc

include    kernel32.inc

includelibuser32.lib

includelibkernel32.lib

                .const    

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

              .code

start:

               ;調用函數IsDebuggerPresent

              invoke IsDebuggerPresent

              .if     eax

                     invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK

              .else

                     invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

              .endif                               

                ;直接去讀字段

                assume fs:nothing

               mov     eax,fs:[30h]

               movzx   eax,byte ptr [eax+2]

              .if     eax

                     invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK

              .else

                     invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

              .endif                 

              invoke     ExitProcess,NULL

              end  start

 

9. PEB.NtGlobalFlag, Heap.HeapFlags, Heap.ForceFlags

(1)通常程序沒有被調試時,PEB另一個成員NtGlobalFlag(偏移0x68)值為0,如果進程被調試通常值為0x70(代表下述標志被設置):

FLG_HEAP_ENABLE_TAIL_CHECK(0X10)

FLG_HEAP_ENABLE_FREE_CHECK(0X20)

FLG_HEAP_VALIDATE_PARAMETERS(0X40)

這些標志是在ntdll!LdrpInitializeExecutionOptions()里設置的。請注意PEB.NtGlobalFlag的默認值可以通過gflags.exe工具或者在注冊表以下位置創建條目來修改:

HKLM\Software\Microsoft\WindowsNt\CurrentVersion\Image File Execution Options

assume fs:nothing

                mov     eax,fs:[30h]

                mov     eax,[eax+68h]

                and     eax,70h

(2)由於NtGlobalFlag標志的設置,堆也會打開幾個標志,這個變化可以在ntdll!RtlCreateHeap()里觀測到。正常情況下系統為進程創建第一個堆時會將Flags和ForceFlags分別設為2(HEAP_GROWABLE)和0 。當進程被調試時,這兩個標志通常被設為50000062(取決於NtGlobalFlag)和0x40000060(等於Flags AND 0x6001007D)。

assume fs:nothing

              mov     ebx,fs:[30h]     ;ebx指向PEB

            mov     eax,[ebx+18h]   ;PEB.ProcessHeap

            cmp      dword ptr [eax+0ch],2    ;PEB.ProcessHeap.Flags

            jne        debugger_found

                 cmp dword ptr [eax+10h],0         ;PEB.ProcessHeap.ForceFlags

                 jne   debugger_found

這些標志位都是因為BeingDebugged引起的。系統創建進程的時候設置BeingDebugged=TRUE,后來NtGlobalFlag根據這個標記設置FLG_VALIDATE_PARAMETERS等標記。在為進程創建堆時,又由於NtGlobalFlag的作用,堆的Flags被設置了一些標記,這個Flags隨即被填充到ProcessHeap的Flags和ForceFlags中,同時堆中被填充了很多BAADF00D之類的東西(HeapMagic,也可用來檢測調試)。

一次性解決這些狀態見加密解密P413

.386

.model flat,stdcall

optioncasemap:none

include    windows.inc

include    user32.inc

include    kernel32.inc

includelibuser32.lib

includelibkernel32.lib

                .const    

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

              .code

start:

              assume  fs:nothing

              mov     ebx,fs:[30h]  ;ebx指向PEB

              

              ;PEB.NtGlobalFlag

            mov    eax,[ebx+68h]

           cmp     eax,70h

                 je     debugger_found                   

 

                ;PEB.ProcessHeap

             mov    eax,[ebx+18h]

 

             ;PEB.ProcessHeap.Flags

             cmp      dwordptr [eax+0ch],2       

            jne debugger_found

              

              ;PEB.ProcessHeap.ForceFlags

                  cmp      dword ptr [eax+10h],0

                  jne debugger_found

             

             invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

             jmp     exit

debugger_found:invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK     

                                  

          exit:   invoke     ExitProcess,NULL

                 end  start

 

10.EPROCESS的DebugPort成員

Kernel32!CheckRemoteDebuggerPresent()是用於確定是否有調試器被附加到進程。

BOOL CheckRemoteDebuggerPresent(

 HANDLE   hProcess,

 PBOOL     pbDebuggerPresent

)

Kernel32!CheckRemoteDebuggerPresent()接受2個參數,第1個參數是進程句柄,第2個參數是一個指向boolean變量的指針,如果進程被調試,該變量將包含TRUE返回值。

這個API內部調用了ntdll!NtQueryInformationProcess(),由它完成檢測工作。

.386

.modelflat,stdcall

optioncasemap:none

include    windows.inc

include    user32.inc

include    kernel32.inc

includelibuser32.lib

includelibkernel32.lib

                .data?

dwResult       dd     ?

                .const    

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

              .code

start:

               invoke  GetCurrentProcessId

               invoke  OpenProcess,PROCESS_ALL_ACCESS,NULL,eax             

               invoke  CheckRemoteDebuggerPresent,eax,addr dwResult

               cmp     dword ptr dwResult,0

               jne     debugger_found   

                         

                invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

                jmp     exit

debugger_found:invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK                    exit:    invoke       ExitProcess,NULL

                      end  start

ntdll!NtQueryInformationProcess()有5個參數。

為了檢測調試器的存在,需要將ProcessInformationclass參數設為ProcessDebugPort(7)。

NtQueryInformationProcess()檢索內核結構EPROCESS5的DebugPort成員,這個成員是系統用來與調試器通信的端口句柄。非0的DebugPort成員意味着進程正在被用戶模式的調試器調試。如果是這樣的話,ProcessInformation將被置為0xFFFFFFFF,否則ProcessInformation將被置為0。

ZwQueryInformationProcess(

IN HANDLEProcessHandle,

INPROCESSINFOCLASS ProcessInformationClass,

OUT PVOIDProcessInformation,

IN ULONGProcessInformationLength,

OUT PULONGReturnLength OPTIONAL

);

.386

.modelflat,stdcall

optioncasemap:none

 

include    windows.inc

include    user32.inc

includelibuser32.lib

include    kernel32.inc

includelibkernel32.lib

include   ntdll.inc        ;這兩個

includelib ntdll.lib

               .data?

dwResult        dd     ?

                .const    

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

 

              .code

start:

                invoke  GetCurrentProcessId

               invoke  OpenProcess,PROCESS_ALL_ACCESS,NULL,eax 

               invoke  ZwQueryInformationProcess,eax,7,offsetdwResult,4,NULL   

               cmp     dwResult,0               

               jne     debugger_found   

      

                invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

                jmp     exit

debugger_found:invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK     

                                  

       exit: invoke     ExitProcess,NULL

              end  start

 

11.SetUnhandledExceptionFilter/ Debugger Interrupts

調試器中步過INT3和INT1指令的時候,由於調試器通常會處理這些調試中斷,所以設置的異常處理例程默認情況下不會被調用,Debugger Interrupts就利用了這個事實。這樣我們可以在異常處理例程中設置標志,通過INT指令后如果這些標志沒有被設置則意味着進程正在被調試。另外,kernel32!DebugBreak()內部是調用了INT3來實現的,有些殼也會使用這個API。注意測試時,在異常處理里取消選中INT3 breaks Singal-stepbreak

              .386

              .model flat,stdcall

              option casemap:none

include           windows.inc

include           user32.inc

includelib       user32.lib

include           kernel32.inc

includelib       kernel32.lib

              .data

lpOldHandler  dd    ?

              .const

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

              .code

; ExceptionHandler 異常處理程序

_Handler proc _lpExceptionPoint        

              pushad

              mov esi,_lpExceptionPoint

              assume    esi:ptr EXCEPTION_POINTERS

              mov edi,[esi].ContextRecord

              assume    edi:ptr CONTEXT

              mov    [edi].regEax,0FFFFFFFFH  ;設置EAX

              mov     [edi].regEip,offset SafePlace

              assume    esi:nothing,edi:nothing

              popad

              mov eax,EXCEPTION_CONTINUE_EXECUTION

              ret

_Handler endp

 

start:

              invoke   SetUnhandledExceptionFilter,addr_Handler

             mov lpOldHandler,eax

             

       xor eax,eax       ;清零eax

       int    3             ;產生異常,然后_Handler被調用

SafePlace:

             test       eax,eax

             je   debugger_found

 

             invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

       jmp    exit

debugger_found: invoke MessageBox,NULL,addr szFound,addr szCaption,MB_OK                         exit:       invoke    SetUnhandledExceptionFilter,lpOldHandler   ;取消異常處理函數

              invoke     ExitProcess,NULL

              end  start

由於調試中斷而導致執行停止時,在OllyDbg中識別出異常處理例程(通過視圖->SEH鏈)並下斷點,然后Shift+F9將調試中斷/異常傳遞給異常處理例程,最終異常處理例程中的斷點會斷下來,這時就可以跟蹤了。

另一個方法是允許調試中斷自動地傳遞給異常處理例程。在OllyDbg中可以通過 選項-> 調試選項 -> 異常 -> 忽略下列異常 選項卡中鈎選"INT3中斷"和"單步中斷"復選框來完成設置。

 

12.單步標志異常

TF=1的時候,會觸發單步異常。該方法屬於異常處理,不過比較特殊:未修改的OD無論是F9還是F8都不能處理異常,有插件的OD在F9時能正確處理,F8時不能正確處理。

              .386

              .model flat,stdcall

              option casemap:none

include           windows.inc

include           user32.inc

includelib      user32.lib

include           kernel32.inc

includelib      kernel32.lib

              .data

lpOldHandler  dd    ?

szCaption       db     '檢測結果',0

szFound         db     '程序未收到異常,說明有調試器',0

szNotFound      db     '程序處理了異常而到達安全位置,沒有調試器',0

             .code

_Handler proc _lpExceptionPoint        

              pushad

              mov esi,_lpExceptionPoint

              assume    esi:ptr EXCEPTION_POINTERS

              mov edi,[esi].ContextRecord

              assume    edi:ptr CONTEXT

              mov [edi].regEip,offset SafePlace

              assume    esi:nothing,edi:nothing

              popad

              mov eax,EXCEPTION_CONTINUE_EXECUTION

              ret         

_Handler endp

 

start:

              invoke     SetUnhandledExceptionFilter,addr _Handler

              mov       lpOldHandler,eax

              pushfd ;push    eflags

             or      dword ptr [esp],100h   ;TF=1

             popfd

             nop

             jmp     die            

  SafePlace:      

                invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

                jmp     exit

       die:  invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK

       exit:  invoke       SetUnhandledExceptionFilter,lpOldHandler   ;取消異常處理函數

              invoke     ExitProcess,NULL

              end  start

13.SeDebugPrivilege 進程權限

默認情況下進程沒有SeDebugPrivilege權限,調試時,會從調試器繼承這個權限,可以通過打開CSRSS.EXE進程間接地使用SeDebugPrivilege確定進程是否被調試。注意默認情況下這一權限僅僅授予了Administrators組的成員。可以使用ntdll!CsrGetProcessId() API獲取CSRSS.EXE的PID,也可以通過枚舉進程來得到CSRSS.EXE的PID。

實例測試中,OD載入后,第一次不能正確檢測,第二次可以,不知為何。

              .386

              .model flat,stdcall

              option casemap:none

include           windows.inc

include           user32.inc

include           kernel32.inc

include      ntdll.inc

includelib      user32.lib

includelib      kernel32.lib

includelib    ntdll.lib

              .const

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

              .code

start:

              invoke CsrGetProcessId ;ntdll!CsrGetProcessId獲取CSRSS.EXEPID

              invoke OpenProcess,PROCESS_QUERY_INFORMATION,NULL,eax

              test    eax,eax

              jnz   debugger_found

              invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

                jmp     exit

debugger_found:invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK                         exit: invoke       ExitProcess,NULL

              end  start

 

14.DebugObject:NtQueryObject()

除了識別進程是否被調試之外,其他的調試器檢測技術牽涉到檢查系統當中是否有調試器正在運行。逆向論壇中討論的一個有趣的方法就是檢查DebugObject類型內核對象的數量。這種方法之所以有效是因為每當一個應用程序被調試的時候,將會為調試對話在內核中創建一

個DebugObject類型的對象。

DebugObject的數量可以通過ntdll!NtQueryObject()檢索所有對象類型的信息而獲得。NtQueryObject接受5個參數,為了查詢所有的對象類型,ObjectHandle參數被設為NULL,ObjectInformationClass參數設為ObjectAllTypeInformation(3):

NTSTATUS NTAPI NtQueryObject(

IN    HANDLE                                         ObjectHandle,

IN    OBJECT_INFORMATION_CLASS   ObjectInformationClass,

OUT   PVOID                                           ObjectInformation,

IN    ULONG                                           Length,

OUT   PULONG                                        ResultLength

)

這個API返回一個OBJECT_ALL_INFORMATION結構,其中NumberOfObjectsTypes成員為所有的對象類型在ObjectTypeInformation數組中的計數:

typedef struct _OBJECT_ALL_INFORMATION{

ULONG                                          NumberOfObjectsTypes;

OBJECT_TYPE_INFORMATION         ObjectTypeInformation[1];

}

檢測例程將遍歷擁有如下結構的ObjectTypeInformation數組:

typedef struct _OBJECT_TYPE_INFORMATION{

[00] UNICODE_STRING        TypeName;

[08] ULONG                          TotalNumberofHandles;

[0C] ULONG                  TotalNumberofObjects;

...more fields...

}

TypeName成員與UNICODE字符串"DebugObject"比較,然后檢查TotalNumberofObjects 或 TotalNumberofHandles 是否為非0值。

 

15.Guard Pages

這個檢查是針對OllyDbg的,因為它和OllyDbg的內存訪問/寫入斷點特性相關。

除了硬件斷點和軟件斷點外,OllyDbg允許設置一個內存訪問/寫入斷點,這種類型的斷點是通過頁面保護來實現的。簡單地說,頁面保護提供了當應用程序的某塊內存被訪問時獲得通知這樣一個途徑。

頁面保護是通過PAGE_GUARD頁面保護修改符來設置的,如果訪問的內存地址是受保護頁面的一部分,將會產生一個STATUS_GUARD_PAGE_VIOLATION(0x80000001)異常。如果進程被OllyDbg調試並且受保護的頁面被訪問,將不會拋出異常,訪問將會被當作內存斷點來處理,而殼正好利用了這一點。

示例

下面的示例代碼中,將會分配一段內存,並將待執行的代碼保存在分配的內存中,然后啟用頁面的PAGE_GUARD屬性。接着初始化標設符EAX為0,然后通過執行內存中的代碼來引發STATUS_GUARD_PAGE_VIOLATION異常。如果代碼在OllyDbg中被調試,因為異常處理例程不會被調用所以標設符將不會改變。

對策

由於頁面保護引發一個異常,逆向分析人員可以故意引發一個異常,這樣異常處理例程將會

被調用。在示例中,逆向分析人員可以用INT3指令替換掉RETN指令,一旦INT3指令被執行,Shift+F9強制調試器執行異常處理代碼。這樣當異常處理例程調用后,EAX將被設為正確的值,然后RETN指令將會被執行。

如果異常處理例程里檢查異常是否真地是STATUS_GUARD_PAGE_VIOLATION,逆向分析人員可以在異常處理例程中下斷點然后修改傳入的ExceptionRecord參數,具體來說就是ExceptionCode, 手工將ExceptionCode設為STATUS_GUARD_PAGE_VIOLATION即可。

實例:

                   .386

                   .modelflat,stdcall

                   optioncasemap:none

include                 windows.inc

include                 user32.inc

includelib        user32.lib

include                 kernel32.inc

includelib          kernel32.lib

                   .data

lpOldHandler      dd     ?

dwOldType   dd      ?

                   .const

szCaption      db      '檢測結果',0

szFound        db      '檢測到調試器',0

szNotFound     db      '沒有調試器',0

                   .code

_Handler    proc  _lpExceptionPoint                 

                   pushad

                   mov  esi,_lpExceptionPoint

                   assume       esi:ptr EXCEPTION_POINTERS

                   mov  edi,[esi].ContextRecord

                   assume       edi:ptr CONTEXT

                   mov    [edi].regEax,0FFFFFFFFH   ;檢測標志

                   mov     [edi].regEip,offset SafePlace

                   assume       esi:nothing,edi:nothing

                   popad

                   mov  eax,EXCEPTION_CONTINUE_EXECUTION

                   ret

_Handler    endp

 

start:

                   invoke        SetUnhandledExceptionFilter,addr_Handler

                   mov  lpOldHandler,eax

                                  

         invoke VirtualAlloc,NULL,1000H,MEM_COMMIT,PAGE_READWRITE ;分配內存

        push    eax                             

        mov    byte ptr [eax],0C3H ;寫一個 RETN到保留內存,以便下面的調用

       invoke VirtualProtect,eax,1000h,PAGE_EXECUTE_READ or PAGE_GUARD,addr dwOldType

        xor     eax,eax         ;檢測標志

        pop     ecx

        call     ecx         ;執行保留內存代碼,觸發異常  

SafePlace:

                   test  eax,eax      ;檢測標志

                   je     debugger_found                

                   invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

         jmp     exit

debugger_found: invoke MessageBox,NULL,addr szFound,addr szCaption,MB_OK

  exit:        invoke VirtualFree,ecx,1000H,MEM_DECOMMIT

              invoke       SetUnhandledExceptionFilter,lpOldHandler   ;取消異常處理函數

                   invoke        ExitProcess,NULL

                   end    start

 

16.Software Breakpoint.

軟件斷點是通過修改目標地址代碼為0xCC(INT3/BreakpointInterrupt)來設置的斷點。通過在受保護的代碼段和(或)API函數中掃描字節0xCC來識別軟件斷點。這里以普通斷點和函數斷點分別舉例。

(1)      實例一   普通斷點

注意:在被保護的代碼區域下INT3斷點進行測試

                .386

              .model flat,stdcall

              option casemap:none

include           windows.inc

include           user32.inc

includelib       user32.lib

include           kernel32.inc

includelib     kernel32.lib

              .const

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

              .code

start:          jmp     CodeEnd    

   CodeStart:  mov     eax,ecx  ;被保護的程序段

                nop

                push    eax

                push    ecx

                pop     ecx

                pop     eax

   CodeEnd:    

                cld              ;檢測代碼開始

               mov     edi,offset CodeStart

               mov     ecx,offset CodeEnd -offset CodeStart

               mov     al,0CCH

               repne   scasb               

               jz      debugger_found      

                         

              invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

          jmp     exit

debugger_found:invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK

          exit:    invoke     ExitProcess,NULL

              end  start

(1)      實例二 函數斷點bp

利用GetProcAddress函數獲取API的地址

注意:檢測時,BPMessageBoxA

.386

.modelflat,stdcall

optioncasemap:none

includewindows.inc

includeuser32.inc

includelibuser32.lib

includekernel32.inc

includelibkernel32.lib

 

            .const

szKernelDll  db       'user32.dll',0

szAPIMessboxdb        'MessageBoxA',0

szCaption    db       '結果',0

szFound       db       '發現API斷點',0

szNotFound   db       '未發現斷點',0

            .code

start:

           invoke  GetModuleHandle,addr szKernelDll

          invoke   GetProcAddress,eax,addrszAPIMessbox  ;API地址

          cld               ;檢測代碼開始

          mov     edi,eax  ;API開始位置

          mov     ecx,100H ;檢測100字節

          mov     al,0CCH  ;CC

          repne   scasb                

          jz      debugger_found      

                         

         invoke  MessageBox,NULL,addrszNotFound,addr szCaption,MB_OK

           jmp     exit

debugger_found:invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK

          exit:    invoke     ExitProcess,NULL

                end start

 

17.Hardware Breakpoints.

硬件斷點是通過設置名為Dr0到Dr7的調試寄存器來實現的。Dr0-Dr3包含至多4個斷點的地址,Dr6是個標志,它指示哪個斷點被觸發了,Dr7包含了控制4個硬件斷點諸如啟用/禁用或者中斷於讀/寫的標志。

由於調試寄存器無法在Ring3下訪問,硬件斷點的檢測需要執行一小段代碼。可以利用含有調試寄存器值的CONTEXT結構,該結構可以通過傳遞給異常處理例程的ContextRecord參數來訪問。

              .386

              .model flat,stdcall

              option casemap:none

include           windows.inc

include           user32.inc

includelib      user32.lib

include           kernel32.inc

includelib    kernel32.lib

              .data

lpOldHandler  dd    ?

              .const

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

              .code

_Handler proc _lpExceptionPoint        

              pushad

              mov esi,_lpExceptionPoint

              assume    esi:ptr EXCEPTION_POINTERS

              mov edi,[esi].ContextRecord

              assume    edi:ptr CONTEXT

              mov [edi].regEip,offset SafePlace

              cmp     [edi].iDr0,0     ;檢測硬件斷點

             jne     debugger_found           

             cmp     [edi].iDr1,0

             jne     debugger_found    

             cmp     [edi].iDr2,0

             jne     debugger_found    

             cmp     [edi].iDr3,0

             jne     debugger_found                  

              invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

                jmp     TestOver

 debugger_found:invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK 

       TestOver:assume      esi:nothing,edi:nothing

              popad

              mov eax,EXCEPTION_CONTINUE_EXECUTION

              ret         

_Handler endp

 

start:

              invoke     SetUnhandledExceptionFilter,addr _Handler

              mov lpOldHandler,eax         

                xor eax,eax       ;清零eax

                mov     dword ptr [eax],0    ;產生異常,然后_Handler被調用               

SafePlace:          invoke     SetUnhandledExceptionFilter,lpOldHandler   ;取消異常處理函數

              invoke     ExitProcess,NULL

              end  start

 

18.補丁檢測,代碼檢驗和

補丁檢測技術能識別殼的代碼是否被修改,也能識別是否設置了軟件斷點。補丁檢測是通過代碼校驗來實現的,校驗計算包括從簡單到復雜的校驗和/哈希算法。

實例:改動被保護代碼的話,CHECKSUM需要修改,通過OD等找出該值

注意:在被保護代碼段下F2斷點或修改字節來測試

                .386

              .model flat,stdcall

              option casemap:none

include           windows.inc

include           user32.inc

includelib      user32.lib

include           kernel32.inc

includelib     kernel32.lib

CHECKSUM        EQU    915Ch       ;改動被保護代碼的話,需要修改

              .const

szCaption       db     '檢測結果',0

szFound         db     '檢測到調試器',0

szNotFound      db     '沒有調試器',0

              .code

start:          jmp     CodeEnd    

   CodeStart:  mov     eax,ecx  ;被保護的程序段

                nop

                push    eax

                push    ecx

                pop    ecx

                pop     eax

   CodeEnd:                   

                mov       esi,CodeStart

               mov       ecx,CodeEnd - CodeStart

               xor eax,eax

 checksum_loop:

               movzx    ebx,byte ptr [esi]

               add        eax,ebx

               rol eax,1

               inc esi

               loop       checksum_loop

               

               cmp       eax,CHECKSUM

                jne debugger_found            

                         

              invoke  MessageBox,NULL,addr szNotFound,addrszCaption,MB_OK

          jmp     exit

debugger_found:invoke  MessageBox,NULL,addr szFound,addrszCaption,MB_OK

          exit:    invoke     ExitProcess,NULL

              end  start

19.封鎖鍵盤、鼠標輸入

user32!BlockInput() API 阻斷鍵盤和鼠標的輸入。

典型的場景可能是逆向分析人員在GetProcAddress()內下斷,然后運行脫殼代碼直到被斷下。但是跳過一段垃圾代碼之后殼調用BlockInput()。當GetProcAddress()斷點斷下來后,逆向分析人員會突然困惑地發現無法控制調試器了,不知究竟發生了什么。

示例:源碼看附件

BlockInput()參數fBlockIt,true,鍵盤和鼠標事件被阻斷;false,鍵盤和鼠標事件解除阻斷:

; Block input

push                     TRUE

call               [BlockInput]

 

;...Unpackingcode...

 

;Unblock input

push                     FALSE

call               [BlockInput]

對策

(1)最簡單的方法就是補丁 BlockInput()使它直接返回。

(2)同時按CTRL+ALT+DELETE鍵手工解除阻斷。

 

20.禁用窗口

在資源管理器里直接雙擊運行的話,會使當前的資源管理器窗口被禁用。

在OD里面的話,就會使OD窗口被禁用。

                .386

              .model flat,stdcall

              option casemap:none

include           windows.inc

include           user32.inc

includelib          user32.lib

include           kernel32.inc

includelib     kernel32.lib

              .const

szCaption       db     '結果',0

szEnableFalse   db     '窗口已經禁用',0

szEnableTrue    db     '窗口已經恢復',0

              .code

start:         

                invoke  GetForegroundWindow

                mov     ebx,eax

                invoke  EnableWindow,eax,FALSE

                invoke  MessageBox,NULL,addr szEnableFalse,addrszCaption,MB_OK

                nop

                invoke  EnableWindow,ebx,TRUE

                invoke  MessageBox,NULL,addr szEnableTrue,addrszCaption,MB_OK

                nop

                  invoke     ExitProcess,NULL

              end  start

 

21.ThreadHideFromDebugger

ntdll!NtSetInformationThread()用來設置一個線程的相關信息。把ThreadInformationClass參數設為ThreadHideFromDebugger(11H)可以禁止線程產生調試事件。

ntdll!NtSetInformationThread的參數列表如下。ThreadHandle通常設為當前線程的句柄(0xFFFFFFFE):

NTSTATUS NTAPI NtSetInformationThread(

IN  HANDLE                                           ThreadHandle,

IN  THREAD_INFORMATION_CLASS      ThreadInformaitonClass,

IN  PVOID                                              ThreadInformation,

IN  ULONG                                             ThreadInformationLength

);

ThreadHideFromDebugger內部設置內核結構ETHREAD的HideThreadFromDebugger成員。一旦這個成員設置以后,主要用來向調試器發送事件的內核函數_DbgkpSendApiMessage()將不再被調用。

invoke GetCurrentThread

invoke NtSetInformationThread,eax,11H,NULL,NULL

對策:

(1)在ntdll!NtSetInformationThread()里下斷,斷下來后,操縱EIP防止API調用到達內核

(2)Olly Advanced插件也有補這個API的選項。補過之后一旦ThreadInformaitonClass參數為HideThreadFromDebugger,API將不再深入內核僅僅執行一個簡單的返回。

.386

.modelflat,stdcall

optioncasemap:none

include    windows.inc

include    user32.inc

include    kernel32.inc

includelib  user32.lib

includelib  kernel32.lib

include    ntdll.inc

includelib  ntdll.lib

               .const    

szCaption       db     '確定以后看看效果',0

szNotice        db     '匯編代碼會消失哦',0

szResult        db     '看到效果了嗎?沒有則稍等',0

              .code

start:

                invoke  MessageBox,NULL,addr szNotice,addrszCaption,MB_OK

                invoke  GetCurrentThread

               invoke  NtSetInformationThread,eax,11H,NULL,NULL  

                invoke  MessageBox,NULL,addr szResult,addrszCaption,MB_OK   

                mov     eax,ebx ;其它指令                         

              invoke     ExitProcess,NULL

              end  start            

 

22.禁用硬件斷點

;執行過后,OD查看硬件斷點還存在,但實際已經不起作用了

;利用CONTEXT結構,該結構利用異常處理獲得,異常處理完后會自動寫回

              .386

              .model flat,stdcall

              option casemap:none

include           windows.inc

include           user32.inc

includelib       user32.lib

include           kernel32.inc

includelib       kernel32.lib

              .data

lpOldHandler  dd    ?

              .code

_Handler proc _lpExceptionPoint        

              pushad

              mov esi,_lpExceptionPoint

              assume    esi:ptr EXCEPTION_POINTERS

              mov edi,[esi].ContextRecord

              assume    edi:ptr CONTEXT

              mov [edi].regEip,offset SafePlace

              xor     eax,eax

             mov     [edi].iDr0,eax

             mov     [edi].iDr1,eax

             mov     [edi].iDr2,eax

             mov     [edi].iDr3,eax             

mov     [edi].iDr6,eax

             mov     [edi].iDr7,eax

              assume    esi:nothing,edi:nothing

              popad

              mov eax,EXCEPTION_CONTINUE_EXECUTION

              ret         

_Handler endp

 

start:

              invoke     SetUnhandledExceptionFilter,addr _Handler

              mov lpOldHandler,eax

             

                xor eax,eax       ;清零eax

                mov     dword ptr [eax],0    ;產生異常,然后_Handler被調用               

SafePlace:          invoke     SetUnhandledExceptionFilter,lpOldHandler   ;取消異常處理函數

              invoke     ExitProcess,NULL

              end  start

 

23.OllyDbg:OutputDebugString() Format String Bug

OutputDebugString函數用於向調試器發送一個格式化的串,Ollydbg會在底端顯示相應的信息。OllyDbg存在格式化字符串溢出漏洞,非常嚴重,輕則崩潰,重則執行任意代碼。這個漏洞是由於Ollydbg對傳遞給kernel32!OutputDebugString()的字符串參數過濾不嚴導致的,它只對參數進行那個長度檢查,只接受255個字節,但沒對參數進行檢查,所以導致緩沖區溢出。

例如:printf函數:%d,當所有參數壓棧完畢后調用printf函數的時候,printf並不能檢測參數的正確性,只是機械地從棧中取值作為參數,這樣堆棧就被破壞了,棧中信息泄漏。。

示例:下面這個簡單的示例將導致OllyDbg拋出違規訪問異常或不可預期的終止。

szFormatStr     db    '%s%s',0

push   offset szFormatStr

call   OutputDebugString

對策:補丁 kernel32!OutputDebugStringA()入口使之直接返回

              .386

              .model flat,stdcall

              option casemap:none

include           windows.inc

include           user32.inc

includelib       user32.lib

include           kernel32.inc

includelib       kernel32.lib

              .data

szFormatStr     db    '%s%s',0

szCaption       db     '呵呵',0

szNotice        db     '執行已結束,看到效果了嗎?',0

 

              .code

start:

              push    offset szFormatStr

             call    OutputDebugString

              invoke  MessageBox,NULL,addr szNotice,addrszCaption,MB_OK         

              invoke     ExitProcess,NULL

              end  start

 

24.TLS Callbacks

使用Thread Local Storage (TLS)回調函數可以實現在實際的入口點之前執行反調試的代碼,這也是OD載入程序就退出的原因所在。(Anti-OD)

線程本地存儲器可以將數據與執行的特定線程聯系起來,一個進程中的每個線程在訪問同一個線程局部存儲時,訪問到的都是獨立的綁定於該線程的數據塊。動態綁定(運行時)線程特定數據是通過 TLS API(TlsAlloc、TlsGetValue、TlsSetValue 和 TlsFree)的方式支持的。除了現有的 API 實現,Win32 和 Visual C++ 編譯器現在還支持靜態綁定(加載時間)基於線程的數據。當使用_declspec(thread)聲明的TLS變量

時,編譯器把它們放入一個叫.tls的區塊里。當應用程序加載到內存時,系統尋找可執行文件中的.tls區塊,並動態的分配一個足夠大的內存塊,以便存放TLS變量。系統也將一個指向已分配內存的指針放到TLS數組里,這個數組由FS:[2CH]指向。

數據目錄表中第9索引的IMAGE_DIRECTORY_ENTRY_TLS條目的VirtualAddress指向TLS數據,如果非零,這里是一個IMAGE_TLS_DIRECTORY結構,如下:

IMAGE_TLS_DIRECTORY32   STRUC

  StartAddressOfRawData  DWORD  ?   ; 內存起始地址,用於初始化新線程的TLS

  EndAddressOfRawData   DWORD  ?   ; 內存終止地址

AddressOfIndex         DWORD ?   ; 運行庫使用該索引來定位線程局部數據

AddressOfCallBacks     DWORD ?   ; PIMAGE_TLS_CALLBACK函數指針數組的地址

SizeOfZeroFill          DWORD ?   ; 用0填充TLS變量區域的大小

Characteristics           DWORD ?   ; 保留,目前為0

IMAGE_TLS_DIRECTORY32   ENDS

AddressOfCallBacks 是線程建立和退出時的回調函數,包括主線程和其它線程。當一個線程創建或銷毀時,在列表中的每一個函數被調用。一般程序沒有回調函數,這個列表是空的。TLS數據初始化和TLS回調函數調用都在入口點之前執行,也就是說TLS是程序最開始運行的地方。程序退出時,TLS回調函數再被執行一次。回調函數:

TLS_CALLBACK proto Dllhandle : LPVOID, Reason : DWORD,Reserved : LPVOID

參數如下:

Dllhandle : 為模塊的句柄

Reason可取以下值:

DLL_PROCESS_ATTACH 1 : 啟動一個新進程被加載

DLL_THREAD_ATTACH 2 : 啟動一個新線程被加載

DLL_THREAD_DETACH 3 : 終止一個新線程被加載

DLL_PROCESS_DETACH 0 : 終止一個新進程被加載

Reserverd:用於保留,設置為0

IMAGE_TLS_DIRECTORY結構中的地址是虛擬地址,而不是RVA。這樣,如果可執行文件不是從基地址裝入,則這些地址會通過基址重定位修正。而且IMAGE_TLS_DIRECTORY本身不在.TLS區塊中,而在.rdata里。

TLS回調可以使用諸如pedump之類的PE文件分析工具來識別。如果可執行文件中存在TLS條目,數據條目將會顯示出來。

Data directory

EXPORT                                rva:00000000      size:00000000

IMPORT                                 rva:00061000      size:000000E0

:::

TLS                                         rva:000610E0      size:00000018

:::

IAT                                          rva:00000000      size:00000000

DELAY_IMPORT                  rva:00000000      size:00000000

COM_DESCRPTR                rva:00000000      size:00000000

unused                                    rva:00000000      size:00000000

接着顯示TLS條目的實際內容。AddressOfCallBacks成員指向一個以null結尾的回調函數數組。

TLS directory:

StartAddressOfRawData:                          00000000

EndAddressOfRawData:                           00000000

AddressOfIndex:                              004610F8

AddressOfCallBacks:                       004610FC

SizeOfZeroFill:                                          00000000

Characteristics:                                          00000000

在這個例子中,RVA 0x4610fc指向回調函數指針(0x490f43和0x44654e):

默認情況下OllyDbg載入程序將會暫停在入口點,應該配置一下OllyDbg使其在TLS回調被調用之前中斷在實際的loader。

通過“選項->調試選項->事件->第一次中斷於->系統斷點”來設置中斷於ntdll.dll內的實際loader代碼。這樣設置以后,OllyDbg將會中斷在位於執行TLS回調的ntdll!LdrpRunInitializeRoutines()之前的ntdll!_LdrpInitializeProcess(),這時就可以在回調例程中下斷並跟蹤了。例如,在內存映像的.text代碼段上設置內存訪問斷點,可以斷在TLS回調函數。

.386

.model   flat,stdcall

option   casemap:none

includewindows.inc

includeuser32.inc

includekernel32.inc

includelibuser32.lib

includelibkernel32.lib

 

.data?

dwTLS_Indexdd  ?

 

OPTION    DOTNAME

;; 定義一個TLS節         

.tls  SEGMENT                       

TLS_StartLABEL  DWORD

 dd   0100h    dup ("slt.")

TLS_End   LABEL DWORD

.tls   ENDS

OPTION    NODOTNAME

 

.data

TLS_CallBackStart  dd TlsCallBack0

TLS_CallBackEnd    dd  0

szTitle            db "Hello TLS",0

szInTls            db "我在TLS里",0

szInNormal         db "我在正常代碼內",0

szClassName        db "ollydbg"        ; OD 類名

;這里需要注意的是,必須要將此結構聲明為PUBLIC,用於讓連接器連接到指定的位置,

;其次結構名必須為_tls_uesd這是微軟的一個規定。編譯器引入的位置名稱也如此。

PUBLIC_tls_used

_tls_usedIMAGE_TLS_DIRECTORY <TLS_Start, TLS_End, dwTLS_Index, TLS_CallBackStart, 0,?>

 

.code

;***************************************************************

;; TLS的回調函數

TlsCallBack0proc Dllhandle:LPVOID,dwReason:DWORD,lpvReserved:LPVOID 

     mov    eax,dwReason ;判斷dwReason發生的條件

     cmp    eax,DLL_PROCESS_ATTACH  ; 在進行加載時被調用

     jnz    ExitTlsCallBack0

     invoke FindWindow,addr szClassName,NULL ;通過類名進行檢測

     .if    eax     ;找到

             invoke    SendMessage,eax,WM_CLOSE,NULL,NULL

     .endif

     invoke MessageBox,NULL,addr szInTls,addr szTitle,MB_OK

     mov    dword ptr[TLS_Start],0 

     xor    eax,eax

     inc    eax

ExitTlsCallBack0:

     ret

TlsCallBack0   ENDP

;****************************************************************

Start:

    invoke  MessageBox,NULL,addr szInNormal,addr szTitle,MB_OK

    invoke  ExitProcess, 1

    end Start

 

25.CreateFile檢測

win32程序對vxd程序通信時,是通過調用DeviceIoControl函數進入vxd,此函數的一個參數就是由createfile獲得的設備句柄。這樣,同樣生成或打開文件的調用也能夠打開一個到vxd的通道。要用createfile打開一個vxd,而不是一個通常的文件,在文件名的地方必須使用特殊形式。

 

 

function SoftIce9x32: Boolean; //探測Win9x下的SoftIce, 發現為True,否則為False
begin {Detect Softice Win9x 32bit}
    if CreateFile('\\.\SICE', GENERIC_READ or GENERIC_WRITE,
                            LE_SHARE_READ or FILE_SHARE_WRITE, nil,
                            EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then
    Result := True
  else Result := False;
end;

function SoftIce9x16: Boolean; //探測Dos下的SoftIce, 發現為True,否則為False
begin {Detect Softice Win9x 16bit}
    if _lopen(PChar('\\.\SICE'), OF_READWRITE) <> HFILE_ERROR then
      Result := True
  else Result := False;
end;

function SoftIceNT32: Boolean; //探測WinNt下的SoftIce, 發現為True,否則為False
begin {Detect Softice WinNt 32bit}
    if CreateFile('\\.\NTICE', GENERIC_READ or GENERIC_WRITE,
                             LE_SHARE_READ or FILE_SHARE_WRITE, nil,
                             OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then
    Result := True
  else Result := False;
end;

function SoftIceNt16: Boolean; //探測WinNt下的16位的SoftIce, 發現為True,否則為False
begin {Detect Softice WinNt 16bit}
    if _lopen(PChar('\\.\NTICE'), OF_READWRITE) <> HFILE_ERROR then
      Result := True
  else Result := False;
end;

function SIWDEBUG: Boolean; //探測Win9x/Nt下的SoftIce, 發現為True,否則為False
begin {Detect Softice SIWDEBUG}
    if CreateFile('\\.\SIWDEBUG', GENERIC_READ or GENERIC_WRITE,
                             LE_SHARE_READ or FILE_SHARE_WRITE, nil,
                             EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then
    Result := True
  else Result := False;
end;

function SIWVID: Boolean; ////探測Win9x/Nt下的SoftIce, 發現為True,否則為False
begin {Detect Softice SIWVID}
    if CreateFile('\\.\SIWVID', GENERIC_READ or GENERIC_WRITE,
                             LE_SHARE_READ or FILE_SHARE_WRITE, nil,
                             EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then
    Result := True
  else Result := False;
end;

function FileMon: Boolean; //探測File Monitor, 發現為True,否則為False
begin {Detect File Monitor}
    if CreateFile('\\.\FILEMON', GENERIC_READ or GENERIC_WRITE,
                             LE_SHARE_READ or FILE_SHARE_WRITE, nil,
                             EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then
    Result := True
  else Result := False;
end;

function RegMon: Boolean; //探測Reg Monitor 發現為True,否則為False
begin {Detect File Monitor}
    if CreateFile('\\.\REGMON', GENERIC_READ or GENERIC_WRITE,
                             LE_SHARE_READ or FILE_SHARE_WRITE, nil,
                             EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then
    Result := True
  else Result := False;
end;

function Trw: Boolean; //探測Win9x下的TRW, 發現為True,否則為False
var
  Trw, TrwDebug: Boolean;
begin {Detect Trw}
    if CreateFile('\\.\Trw', GENERIC_READ or GENERIC_WRITE,
                           LE_SHARE_READ or FILE_SHARE_WRITE, nil,
                           EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then
    Trw := True
  else Trw := False;
    if CreateFile('\\.\TRWDEBUG', GENERIC_READ or GENERIC_WRITE,
                                LE_SHARE_READ or FILE_SHARE_WRITE, nil,
                                EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then
    TrwDebug := True
  else TrwDebug := False;
  Result := Trw or TrwDebug;
end;

function IceDump: Boolean; //探測Win9x下的IceDump, 發現為True,否則為False
begin {Detect IceDump}
    if CreateFile('\\.\ICEDUMP', GENERIC_READ or GENERIC_WRITE,
                             LE_SHARE_READ or FILE_SHARE_WRITE, nil,
                             EN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) <> INVALID_HANDLE_VALUE then
    Result := True
  else Result := False;
end;

procedure FindSoftIce; //探測Win9x/WinNt 下的 SoftIce
begin 
  try //容錯代碼
    asm
      mov     ebp, 04243484Bh //Bounds Checker為SoftICE預留的后門 
      mov     ax, 04h
      int     3
      cmp     al,4
      jnz     GotSoftIce
    end;
  except
  end;
end;

procedure FindSoftIce9x; //探測Win9x 下的 SoftIce
begin 
  try
    asm
      mov ah, 43h
      int 68h
      cmp ax, 0f386h //檢測此處是否被調試器設置0f386h
      jz  GotSoftIce
    end;
  except
  end;
end;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM