• 【转】反调试技巧总结-原理和实现


    总结:

    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工具或者在注册表以下位置创建条目来修改:

    HKLMSoftwareMicrosoftWindowsNtCurrentVersionImage 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;
  • 相关阅读:
    pageX,clientX,screenX,offsetX的区别
    不同的浏览器内核了解学习
    小游戏模仿
    浏览器兼容性
    hack是什么
    DOM对象
    Browser对象
    html状态码与缓存学习
    javascript对象(2)
    javascript对象(1)
  • 原文地址:https://www.cnblogs.com/Lthis/p/4207588.html
Copyright © 2020-2023  润新知