windows调试与反调试复习笔记

反调试分类

1、检测断点(软件断点,硬件断点,内存断点)
2、各种标记检测(单步标记,内存标记,内核对象,调试端口等)
3、进程名字,窗口及其类名检测
4、文件完整性检测

软件断点

TLS回调函数先于程序入口执行,调试器加载主程序的时候会在OEP处设置软件断点,通过TLS回调得到程序的OEP然后判断入口判断是否为int 3断点
即可判断是否有调试器。当然回调函数也可以有其他的反调试手法。

ypedef struct _IMAGE_TLS_DIRECTORY32 {
    DWORD   StartAddressOfRawData; TLS初始化数据的起始地址
    DWORD   EndAddressOfRawData;  TLS初始化数据的结束地址  两个正好定位一个范围,范围放初始化的值
    DWORD   AddressOfIndex;              TLS 索引的位置
    DWORD   AddressOfCallBacks;          Tls回调函数的数组指针
    DWORD   SizeOfZeroFill;  填充0的个数
    union {
        DWORD Characteristics; 保留
        struct {
            DWORD Reserved0 : 20;
            DWORD Alignment : 4;
            DWORD Reserved1 : 8;
        } DUMMYSTRUCTNAME;
    } DUMMYUNIONNAME;

} IMAGE_TLS_DIRECTORY32;

void    TlsCallBackFunction1(PVOID h, DWORD reason, PVOID pv)
{	//仅在进程初始化创建主线程时执行的代码

	if( reason == DLL_PROCESS_ATTACH )
{
		IMAGE_DOS_HEADER*dos_head=(IMAGE_DOS_HEADER*)GetModuleHandle(NULL);
	PIMAGE_NT_HEADERS32 	nt_head=(PIMAGE_NT_HEADERS32)((DWORD)dos_head+(DWORD)dos_head->e_lfanew);
	BYTE*OEP=(BYTE*)(nt_head->OptionalHeader.AddressOfEntryPoint+(DWORD)dos_head);
		for(unsigned long index=0;index<200;index++){??断点不都在oep前面么
			if(OEP[index]==0xcc){
				ExitProcess(0);
			}
		}
	}
	return;
}

TLS参考资料https://www.cnblogs.com/iBinary/p/7697355.html

IsDebuggerPresent检测调试原理

BOOL WINAPI IsDebuggerPresent(void);
它是个检测调试的api函数。实现更简单,只要调用IsDebuggerPresent就可以了。
在调用它之前,可以加如下代码,以用来检测是否在函数头有普通断点,或是否被钩挂。 
  if(*(BYTE*)Func_addr==0xcc) 
    return true; 
mov eax,dword ptr fs:[18]        // _NT_TIB 
mov eax,dword ptr ds:[eax+30]    // PEB
movzx eax,byte ptr ds:[eax+2]     //BeingDebugged 
Retn
Livekd查看符号表 (放在windbg目录下) 下载地址:
https://technet.microsoft.com/en-us/sysinternals/bb897415.aspx

fs:[0]指向线程环境块TEB;
0: kd> dt _nt_tib
ntdll!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   ………………
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB  ------------------这里指向了自身??既然指向自身直接用不就好了?
0: kd> dt _teb
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void--------------这里指向了TLS
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB----------------这里指向了PEB
0: kd> dt _peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar----------------------------这里有个标记
   其实这个函数在检测peb里面的BeingDebugged的标记。

HWBP_Exception (硬件断点)

就是初始化一个异常handler,手动触发异常。

通过SEH查询context里面的DRX寄存器是否为0

__asm 
  { 
    push   offset exeception_handler; set exception handler 
    push   dword ptr fs:[0h] 
    mov    dword ptr fs:[0h],esp   
    xor    eax,eax;reset EAX invoke int3 
    int    1h  //int1的DPL为0,这表示"cd 01"机器码不能在3环下执行
    pop    dword ptr fs:[0h];restore exception handler 
    add    esp,4 
    ;test if EAX was updated (breakpoint identified)  
    test   eax,eax 
    jnz     rt_label 
    jmp    rf_label 
	
exeception_handler: 
    ;EAX = CONTEXT record 
    mov     eax,dword ptr [esp+0xc] 
    cmp     dword ptr [eax+0x04],0 //dr0到4
    jne     hardware_bp_found 
    cmp     dword ptr [eax+0x08],0 //dr0到4
    jne     hardware_bp_found 
    cmp     dword ptr [eax+0x0c],0 
    jne     hardware_bp_found 
    cmp     dword ptr [eax+0x10],0 
    jne     hardware_bp_found 
    jmp     exception_ret 
hardware_bp_found: 
    ;set Context.EAX to signal breakpoint found 
    mov     dword ptr [eax+0xb0],0xFFFFFFFF 
exception_ret: 
    ;set Context.EIP upon return 
    inc       dword ptr [eax+0xb8];set ContextRecord.EIP 
    inc       dword ptr [eax+0xb8];set ContextRecord.EIP 
    xor     eax,eax 
    retn 

OD_Exception_GuardPages

“保护页异常”是一个简单的反调试技巧。当应用程序尝试执行保护页内的代码时,将会产生一个EXCEPTION_GUARD_PAGE(0x80000001)异常,但如果存在调试器,调试器有可能接收这个异常,并允许该程序继续运行,事实上,在
OD中就是这样处理的,OD使用保护页来实现内存断点。 
 
lptmpB=(BYTE *)lpvBase; 
  *lptmpB=0xc3;//retn 
  VirtualProtect(lpvBase,dwPageSize,PAGE_EXECUTE_READ |PAGE_GUARD,&flOldProtect); 
  __try  { 
    __asm  call dword ptr[lpvBase]; 
    VirtualFree(lpvBase,0,MEM_RELEASE); 
    return true; 
  } 
  __except(1)  { 
    VirtualFree(lpvBase,0,MEM_RELEASE); 
    return false; 
  } 

标记检测

PEB_NtGlobalFlags 
PEB中还有其它FLAG表明了调试器的存在,如NtGlobalFlags。检测NtGlobalFlags的方法如下,这个方法在ExeCryptor中使用过。 
__asm 
  { 
    mov eax, fs:[30h] 
    mov eax, [eax+68h] 
    and eax, 0x70 
    test eax, eax 
    jne rt_label
    jmp rf_label
rt_lable:
    return true;
rf_label:
    return false;
}

Heap_HeapFlags

同样,调试器也会在堆中留下痕迹,你可以使用kernel32_GetProcessHeap()函数,如果你不希望使用api函数(以免暴露),
则可以直接在PEB中寻找。同样的,使用HeapFlags和后面提到的ForceFlags来检测调试器也不是非常可靠,但却很常用。 
这个域由一组标志组成,正常情况下,该值应为2。 
  __asm 
  { 
    mov eax, fs:[30h] 
    mov eax, [eax+18h] ;PEB.ProcessHeap 
    mov eax, [eax+0ch] ;PEB.ProcessHeap.Flags 
    cmp eax, 2 
    jne rt_label 
    jmp rf_label 
  } 

Heap_ForceFlags 

进程堆里另外一个标志,ForceFlags,它也由一组标志组成,正常情况下,该值应为0。 
  __asm 
  { 
    mov eax, fs:[30h] 
    mov eax, [eax+18h] ;PEB.ProcessHeap 
    mov eax, [eax+10h] ;PEB.ProcessHeap.ForceFlags 
    test eax, eax 
    jne rt_label 
    jmp rf_label 
  } 

Heap_Tail 

如果处于调试中,堆尾部也会留下痕迹。标志HEAP_TAIL_CHECKING_ENABLED 将会在分配的堆块尾部生成两个
0xABABABAB。如果需要额外的字节来填充堆尾,HEAP_FREE_CHECKING_ENABLED标志则会生成0xFEEEFEEE。  

    mov eax, buff 
    ;get unused_bytes 
    movzx ecx, byte ptr [eax-2] 
    movzx edx, word ptr [eax-8] ;size 
    sub eax, ecx 
    lea edi, [edx*8+eax] 
    mov al, 0abh 
    mov cl, 8 
    repe sca** 

StartupInfo结构

Windows操作系统中的explorer.exe创建进程的时候会把STARTUPINFO结构中的值设为0,(有的程序也不是explorer启动的)
而非explorer.exe创建进程的时候会忽略这个结构中的值,也就是结构中的值不为0,所以可以利用这个来判断OD是否在调试程序.
    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
   DWORD dwYCountChars;  0024
   DWORD dwFillAttribute;   0028
   DWORD dwFlags;         002D
   ……….
   ………
}

CheckRemoteDebuggerPresent

查询一个在winnt时就有的一个数值,其内部会调用NtQueryInformationProcess(): 
  FARPROC Func_addr ; 
  HMODULE hModule = GetModuleHandle("kernel32.dll"); 
  if (hModule==INVALID_HANDLE_VALUE) 
    return false; 
  (FARPROC&) Func_addr =GetProcAddress(hModule, "CheckRemoteDebuggerPresent"); 
  if (Func_addr != NULL)  
  { 
    __asm  
    { 
      push  eax; 
      push  esp; 
      push  0xffffffff; 
      call  Func_addr; 
      test  eax,eax; 
      je    rf_label; 
      pop    eax; 
      test  eax,eax 
      je    rf_label; 
      jmp    rt_label; 
  }

NtQueryInformationProcess(
    __in HANDLE ProcessHandle,
    __in PROCESSINFOCLASS ProcessInformationClass,
    __out_bcount(ProcessInformationLength) PVOID ProcessInformation,
    __in ULONG ProcessInformationLength,
    __out_opt PULONG ReturnLength
    )
typedef enum _PROCESSINFOCLASS {
    ProcessBasicInformation,   //
……
ProcessDebugPort,        //
  …….
ProcessIoPortHandlers,          // Note: this is kernel mode only
…….
此函数用于返回目标进程的各类信息。
ProcessBasicInformation(0x0)--可检测目标进程的父进程,如果程序被调试,则父进程是调试器。 双击起来的父进程是explorer.exe,被调试的程序的父进程是调试器。
ProcessDebugPort(0x07) -- 如果目标进程正在被调试,系统会为进程分配一个调试端口。通过此参数调用NtQueryInformationProcess则返回调试端口号,
返回0表示当前无调试器附在进程上,反之则被调试。
ProcessDebugFlags(0x1f) -- 此时函数返回EPROCESS->NoDebugInherit域的值。为0表示进程正处于调试状态。
ProcessDebugObjectHandle---ProcessInfo.ObjectHandle查询这个句柄的数值,非零表示有调试器的存在。

关于内部怎么调用的NtQueryInformationProcess,参考博客https://blog.csdn.net/hgy413/article/details/7996652

SeDebugPrivilege()  

  当一个进程获得SeDebugPrivilege,它就获得了对CSRSS.EXE的完全控制,这种特权也会被子进程继承,也就是说一
个被调试的程序如果获得了CSRSS.EXE的进程ID,它就可以使用openprocess操作CSRSS.EXE。获得其进程ID有很多种方法,如Process32Next,或NtQuerySystemInformation,在winxp下可以使用CsrGetProcessId。 
    hTmp=OpenProcess(PROCESS_ALL_ACCESS,false,PID_csrss); 
    if(hTmp!=NULL) 
    { 
      CloseHandle(hProcessSnap ); 
      return true; 
    } 

DebugObject

NTSTATUS  NtQueryObject (
    __in HANDLE Handle,
    __in OBJECT_INFORMATION_CLASS ObjectInformationClass,
    __out_bcount_opt(ObjectInformationLength) PVOID ObjectInformation,
    __in ULONG ObjectInformationLength,
    __out_opt PULONG ReturnLength
    )
typedef struct _OBJECT_TYPE_INFORMATION{
     UNICODE_STRING        TypeName;
     ULONG                       TotalNumberofHandles;
     ULONG                       TotalNumberofObjects;
。。。。。
}
TypeName成员与UNICODE字符串"DebugObject"比较,然后检查TotalNumberofObjects 或 TotalNumberofHandles 是否为非0值。

Closehandle 

如果给CloseHandle()函数一个无效句柄作为输入参数,在无调试器时,将会返回一个错误代码,而有调试器存在时,
将会触发一个EXCEPTION_INVALID_HANDLE (0xc0000008)的异常。 
  __try   
  { 
    CloseHandle(HANDLE(0x00001234)); 
    return false; 
  } 
  __except(1) 
  { 
    return true; 
  } 

Exception_Popf()

我们都知道标志寄存器中的陷阱标志,当该标志被设置时,将产生一个单步异常。在程序中动态设置这给标志,如果处
于调试器中,该异常将会被调试器捕获。 
可通过下面的代码设置标志寄存器。 
    pushf  
    mov dword ptr [esp], 0x100 
    popf 

通过Int3产生异常中断的反调试比较经典。当INT3 被执行到时, 如果程序未被调试, 将会异常处理器程序继续执行。而INT3指令常被调试器用于设置软件断点,int 3会导致调试器误认为这是一个自己的断点,从而不会进入异常处理程序。 

OutputDebugString() 

  在有调试器存在和没有调试器存在时,OutputDebugString函数表现会有所不同。最明显的不同是, 如果有调试器存在,
其后的GetLastError()的返回值为零。 
  OutputDebugString(""); 
  tmpD=GetLastError(); 
  if(tmpD==0) 
    return true; 
  return false; 

INT_2d指令

在windows anti-debug reference中指出,如果程序未被调试这个中断将会生产一个断点异常. 被调试并且未使用跟踪标志
执行这个指令, 将不会有异常产生程序正常执行. 如果被调试并且指令被跟踪, 尾随的字节将被跳过并且执行继续. 因
此, 使用 INT 2Dh 能作为一个强有力的反调试和反跟踪机制。 
  __try 
  { 
    __asm 
    { 
        int 2dh 
        inc eax;any opcode of singlebyte. 
      ;or u can put some junkcode,"0xc8"..."0xc2"..."0xe8"..."0xe9" 
    } 
     return true; 
  } 
  __except(1) 
  { 
    return false; 
  }

ZwYieldExecution

这个函数可以让任何就绪的线程暂停执行,等待下一个线程调度。
当前线程放弃剩余时间,让给其他线程执行。如果没有其他准备好的线程,该函数返回false,否则返回true。
当前线程如果被调试,那么调试器线程若处于单步状态,随时等待继续运行,则被调试线程执行NtYieldExecution时,调试器线程会恢复执行。
此时NtYieldExecution返回true,该线程则认为自身被调试了。

窗口检测

对于FindWindow,EnumWindow等查找调试器窗口获取标题进行反调试,可以用spy++进行查看
EnumProcess等对进程名称进行枚举查找调试器进程进行反调试。
调试工具通常会使用内核驱动,因此如果尝试是否可以打开一些调试器所用到的设备,就可判断是否存在调试器。
常用的设备名称如下: 
\\.\SICE  (SoftICE) 
\\.\SIWVID(SoftICE)     
\\.\NTICE  (SoftICE)     
\\.\REGVXG(RegMON) 
\\.\REGVXD(RegMON) 
\\.\REGSYS(RegMON) 
\\.\REGSYS(RegMON) 
\\.\FILEVXG(FileMON) 
\\.\FILEM(FileMON) 
\\.\TRW(TRW2000) 

NtSetInformationThread

NTSTATUS
NtSetInformationThread(
    __in HANDLE ThreadHandle,
    __in THREADINFOCLASS ThreadInformationClass,
    __in_bcount(ThreadInformationLength) PVOID ThreadInformation,
    __in ULONG ThreadInformationLength
    )
当参数ThreadInformationClass的值为ThreadHideFromDebugger(0x11)时,
此函数可以用来防止调试事件被发往调试器。
 

typedef enum _THREADINFOCLASS {
    ThreadBasicInformation,
……..
ThreadHideFromDebugger,   //0x11
    ThreadBreakOnTermination,
    ThreadSwitchLegacyState,
    ThreadIsTerminated,
    MaxThreadInfoClass
} THREADINFOCLASS

OD_Int3_Pushfd(CC 9D)

int3,pushfd和int3,popfd一样的效果。只要修改int3后面的popfd为其他值,
    OD都能通过。SEH异常机制的运用而已。
    原理:在SEH异常处理中设置了硬件断点DR0=EIP+2,并把EIP的值加2,那么应该在int3,popfd后面的指令执行时会产生单步异常。
但是OD遇到前面是popfd/pushfd时,
OD会自动在popfd后一指令处设置硬件断点,而VMP的seh异常处理会判断是否已经设置硬件断点,如果已经有硬件断点就不产生单步异常,所以不能正常执行。
 

Pushf将会被执行,同时调试器无法设置压进堆栈的陷阱标志,应用程序通过检测陷阱标志就可以判断处是否被跟踪调试。 
PushSS_PopSS 
push ss    //反跟踪指令序列 
pop  ss    //反跟踪指令序列 
pushf    //反跟踪指令序列    这条执行之后看堆栈里的flag值
pop eax    //反跟踪指令序列 

RDTSC 

通过检测某段程序执行的时间间隔,可以判断出程序是否被跟踪调试,被跟踪调试的代码通常都有较大的时间延迟,检
测时间间隔的方法有很多种。比如RDTSC指令,kernel32_GetTickCount函数,winmm_ timeGetTime 函数等等。 
下面为RDTSC的实现代码。 
  int time_low,time_high; 
  __asm 
  { 
    rdtsc 
    mov    time_low,eax 
    mov    time_high,edx 
  } 

参考资料

https://www.freebuf.com/articles/others-articles/181085.html

https://blog.csdn.net/zhuhuibeishadiao/article/details/51229201

 

## Features ### Anti-debugging attacks - IsDebuggerPresent - CheckRemoteDebuggerPresent - Process Environement Block (BeingDebugged) - Process Environement Block (NtGlobalFlag) - ProcessHeap (Flags) - ProcessHeap (ForceFlags) - NtQueryInformationProcess (ProcessDebugPort) - NtQueryInformationProcess (ProcessDebugFlags) - NtQueryInformationProcess (ProcessDebugObject) - NtSetInformationThread (HideThreadFromDebugger) - NtQueryObject (ObjectTypeInformation) - NtQueryObject (ObjectAllTypesInformation) - CloseHanlde (NtClose) Invalide Handle - SetHandleInformation (Protected Handle) - UnhandledExceptionFilter - OutputDebugString (GetLastError()) - Hardware Breakpoints (SEH / GetThreadContext) - Software Breakpoints (INT3 / 0xCC) - Memory Breakpoints (PAGE_GUARD) - Interrupt 0x2d - Interrupt 1 - Parent Process (Explorer.exe) - SeDebugPrivilege (Csrss.exe) - NtYieldExecution / SwitchToThread - TLS callbacks ### Anti-Dumping - Erase PE header from memory - SizeOfImage ### Timing Attacks [Anti-Sandbox] - RDTSC (with CPUID to force a VM Exit) - RDTSC (Locky version with GetProcessHeap & CloseHandle) - Sleep -> SleepEx -> NtDelayExecution - Sleep (in a loop a small delay) - Sleep and check if time was accelerated (GetTickCount) - SetTimer (Standard Windows Timers) - timeSetEvent (Multimedia Timers) - WaitForSingleObject -> WaitForSingleObjectEx -> NtWaitForSingleObject - WaitForMultipleObjects -> WaitForMultipleObjectsEx -> NtWaitForMultipleObjects (todo) - IcmpSendEcho (CCleaner Malware) - CreateWaitableTimer (todo) - CreateTimerQueueTimer (todo) - Big crypto loops (todo) ### Human Interaction / Generic [Anti-Sandbox] - Mouse movement - Total Physical memory (GlobalMemoryStatusEx) - Disk size using DeviceIoControl (IOCTL_DISK_GET_LENGTH_INFO) - Disk size using GetDiskFreeSpaceEx (TotalNumberOfBytes) - Mouse (Single click / Double click) (todo) - DialogBox (todo) - Scrolling (todo) - Execution after reboot (todo) - Count of processors (Win32/Tinba - Win32/Dyre) - Sandbox k
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值