动态反调试技术

异常

SEH

Windows操作系统中的一些典型异常

EXCEPTION_DATATYPE_MISALIGNMENT     			(0x80000002)
EXCEPTION_BREAKPOINT							(0x80000003)
EXCEPTION_SINGLE_STEP							(0x80000004)
EXCEPTION_ACCESS_VIOLATION						(0xC0000005)
EXCEPTION_IN_PACE_ERROR							(0xC0000006)
EXCEPTION_ILLEGAL_INSTRUCTION					(0xC000001D)
EXCEPTION_NONCONTINUABLE_EXCEPTION				(0xC0000025)
EXCEPTION_INVALID_DISPOSITION					(0xC0000026)
EXCEPTION_ARRAY_BOUNDS_EXCEPTION				(0xC000008C)
EXCEPTION_FLT_DENORMAL_OPERAND					(0xC000008D)
EXCEPTION_FLT_DIVIDE_BY_ZERO					(0xC000008E)
EXCEPTION_FLT_INEXACT_RESULT					(0xC000008F)
EXCEPTION_FLT_INVALID_OPERATION					(0xC0000090)
EXCEPTION_FLT_OVERFLOW							(0xC0000091)
EXCEPTION_FLT_STACK_CHECK						(0xC0000092)
EXCEPTION_FLT_UNDERFLOW							(0xC0000093)
EXCEPTION_INT_DIVIDE_BY_ZERO					(0xC0000094)
EXCEPTION_INT_OVERFLOW							(0xC0000095)
EXCEPTION_PRIV_INSTRUCTION						(0xC0000096)
EXCEPTION_STACK_OVERFLOW						(0xC00000FD)

EXCEPTION_BREAKPOINT

Windows操作系统最具有代表性的异常是断点异常。BREAKPOINT指令触发异常时,若程序处于正常运行状态,则自动调用已经注册过的SEH;若程序处于调试运行状态,则系统会立刻停止运行程序,并将控制权转给调试器。

一般而言,异常处理器都含有修改EIP的代码。修改调试器选项可以把处在调试中的进程产生的相关异常转给操作系统,自动调用SEH处理。而我们在异常处理器中适当运用静态反调试技术,也能轻松判断进程是否处于调试状态。

1.安装SEH

	PUSH 40102C 
	PUSH DWORD PTR FS:[0]
	MOV DWORD PTR FS:[0],ESP

解释:
PUSH 40102C 就是把函数的实际地址压入栈中,
PUSH DWORD PTR FS:[0]这个也就相当于一个NEXT指针(需要在SEH链顶添加一个节点,所以他指向了原来的第一个节点)
MOV DWORD PTR FS:[0],ESP 这个也就是把栈顶指针( 即这个结构体的地址)放在SEH中,即下面这种形式:

typedef struct _EXCEPTION_REGISTRATION_RECORD
{
  struct _EXCEPTION_REGISTRATION_RECORD *Next;	//指向下一个ERR
  PEXCEPTION_ROUTINE Handler;					//异常处理函数
}EXCEPTION_REGISTRATION_RECORD

2.发生 INT 3异常

INT 3

3.1调试运行-终止进程

MOV EAX -1
JMP EAX

若进程处于调试状态,则需要由调试器(OD)处理异常。INT3指令是CPU中断命令,在用户模式的 调试器中什么也不做,继续执行其下命令。

3.2正常运行(非调试运行)–运行SEH
若进程为非调试运行,那么执行到INT 3指令时就会调用前面已经注册的SEH。

MOV EAX,DWORD PTR SS:[ESP+C]
MOV EBX,0x401040
MOV DWORD PTR DS:[EAX+B8],EBX
XOR EAX,EAX
RETN

SS:[ESP+C]是CONTEXT *pContext结构体的指针,而CONTEXT *pContext结构体正是SEH的第三个参数,它是一个发送异常的线程CONTEXT结构体。DS:[EAX+B8]指向pContext---->Eip成员,所以MOV DWORD PTR DS:[EAX+B8],EBX用来将该结构体的EIP修改为401040然后,异常处理器返回0.接下来,发送异常的线程再次从修改的EIP地址处(401040)开始运行

SEH异常处理器函数定义如下:


EXCEPTION_DISPOSITION ExceptHandler{
		EXCEPTION_RECORD *pRecord,
		EXCEPTION_REGISTRATION_RECORD *pFrame,
		CONTEXT * pContext,
		PVOID pValue
};
typedef enum _EXCEPTION_DISPOSITION
{
         ExceptionContinueExecution = 0,
         ExceptionContinueSearch = 1,
         ExceptionNestedException = 2,
         ExceptionCollidedUnwind = 3
} EXCEPTION_DISPOSITION;

以下是CONTEXT结构体的定义

typedef struct _CONTEXT
{
    DWORD           ContextFlags    // -|               +00h
    DWORD           Dr0             //  |               +04h
    DWORD           Dr1             //  |               +08h
    DWORD           Dr2             //  >调试寄存器     +0Ch
    DWORD           Dr3             //  |               +10h
    DWORD           Dr6             //  |               +14h
    DWORD           Dr7             // -|               +18h

    FLOATING_SAVE_AREA FloatSave;   //浮点寄存器区      +1Ch~~~88h

    DWORD           SegGs           //-|                +8Ch
    DWORD           SegFs           // |\段寄存器       +90h
    DWORD           SegEs           // |/               +94h
    DWORD           SegDs           //-|                +98h

    DWORD           Edi             //________          +9Ch
    DWORD           Esi             // |  通用          +A0h
    DWORD           Ebx             // |   寄           +A4h
    DWORD           Edx             // |   存           +A8h
    DWORD           Ecx             // |   器           +ACh
    DWORD           Eax             //_|___组_          +B0h

    DWORD           Ebp             //++++++            +B4h
    DWORD           Eip             // |控制            +B8h
    DWORD           SegCs           // |寄存            +BCh
    DWORD           EFlag           // |器组            +C0h
    DWORD           Esp             // |                +C4h
    DWORD           SegSs           //++++++            +C8h

    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
    typedef     CONTEXT     *PCONTEXT;
    #define     MAXIMUM_SUPPORTED_EXTENSION     512

4.删除SEH

POP DWORD PTR FS:[0]
ADD	ESP,4

解释:
原来栈顶存放的是NEXT指针(也就是下一个异常处理结构体的地址(即原来SEH链最顶端的结构体)),直接把栈顶值放回去,还原了SEH链最顶端的结构体,然后ESP直接+4就把栈中异常处理器函数地址直接抹杀。

破解之法

选项------>调试设置
在这里插入图片描述

然后点击异常选项:
在这里插入图片描述

最后勾选“INT 3 Break”
在这里插入图片描述
选了“INT 3 Breaks”后,调试器就会忽略调试进程中发生的INT3异常,而由自身的SEH处理。

即设置好后,进程调试过程中遇到INT 3指令时,调试器不会停下来,而会自动调用执行被调试进程的SEH(与正常运行一样)

调试程序代码实现

#include "stdio.h"
#include "windows.h"
#include "tchar.h"

void AD_BreakPoint()
{
    printf("SEH : BreakPoint\n");

    __asm {
        // install SEH
        push handler
        push DWORD ptr fs : [0]
        mov DWORD ptr fs : [0] , esp

        //INT 3 指令是CPU中断命令,在用户模式的调试器啥都不做(经过调试发现不会触发ntdll.dll中的KiUserExceptionDispatcher)。
            // generating exception
            int 3

            // 1) debugging
            //    go to terminating code
            mov eax, 0xFFFFFFFF
            jmp eax                 // process terminating!!!

            // 2) not debugging
            //    go to normal code
        handler:
        mov eax, dword ptr ss : [esp + 0xc]
            mov ebx, normal_code
            mov dword ptr ds : [eax + 0xb8] , ebx
            xor eax, eax
            retn

            normal_code :
        //   remove SEH
        pop dword ptr fs : [0]
            add esp, 4
    }

    printf("  => Not debugging...\n\n");
}

int _tmain(int argc, TCHAR* argv[])
{
    AD_BreakPoint();
    system("pause");
    return 0;
}

注意:OD的异常选项 忽略int 3 中断必须不选,strongOD插件选项最好不用,才能进入指令

SetUnhandledExceptionFilter()

进程中发生异常时,若SEH未处理或注册的SEH根本不存在,会发生什么呢?此时会调用执行系统的kernel32!UnhandledExceptionFilter()API。该函数内部会运行系统的最后一个异常处理器(名为Top Level Exception Filter或Last Exception Filter)。系统最后的异常处理器通常会弹出消息错误消息框,然后终止进程运行

值得注意的是kernel32!UnhandledExceptionFilter()内部调用了kernel32!NtQueryInformationProcess(ProcessDebugPort)API(静态反调试技术 ),以判断是否正在调试进程。若进程正常运行(非调试运行),则运行系统最后的异常处理器;若进程处于调试中,则将异常派送给调试器。通过kernel32!SetUnhandledExceptionFilter()API可以修改系统最后的异常处理器,函数原型如下:

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter (
 __In	LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
) ;

调用该函数修改系统最后异常处理器时,只要将新的 Top Level Exception Filter函数地址传递给函数的lpTopLevelExceptionFilter参数即可(返回值为上一个 Last Exception Filter函数地址)。Top Level Exception Filter函数定义如下:

typedef struct _EXCEPTION_POINTERS{
  PEXCEPTION_RECORD ExceptionRecord;
  PCONTEXT ContextRecord;
} EXCEPTION_POINTERS,*PEXCEPTION_POINTERS

	LONG TopLevelExceptionFilter(
	PEXCEPTION_POINTERS pExcept;
	);

基于异常的反调试技术中,通常都是先触发异常,然后在新注册的 Last Exception Filter 内部判断进程正常运行还是调试运行,并根据判断结果修改EIP值。

在这里插入图片描述

程序实现代码:

#include "stdio.h"
#include "windows.h"
#include "tchar.h"
 
LPVOID g_pOrgFilter = 0;
 
LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS pExcept)
{
    SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)g_pOrgFilter);
 
    // 8900    MOV DWORD PTR DS:[EAX], EAX
    // FFE0    JMP EAX
    pExcept->ContextRecord->Eip += 4;
 
    return EXCEPTION_CONTINUE_EXECUTION;
}
 
void AD_SetUnhandledExceptionFilter()
{
    printf("SEH : SetUnhandledExceptionFilter()\n");
 
	//SetUnhandledExceptionFilter()来注册新的Top Level Exception Filter回调函数。
	//触发异常时,系统在前面没有处理异常的情况下,会调用Kernel32.dll中的
	//UnhandledExceptionFilter()函数。UnhandledExceptionFilter()会利用
	//ntdll.dll中的NtQueryInformationProcess()来判断是否被调试,
	//若判断在被调试,异常给调试器(调试器无法处理异常,进程终止)。
	//若判断未被调试,则调用Top Level Exception Filter回调函数。
	g_pOrgFilter = (LPVOID)SetUnhandledExceptionFilter(
                                (LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter);
 
    __asm {
        xor eax, eax;
        mov dword ptr [eax], eax
        jmp eax                     
    }
    
    printf("  => Not debugging...\n\n");
}
 
int _tmain(int argc, TCHAR* argv[])
{
    AD_SetUnhandledExceptionFilter();
 
    return 0;
}

Timing Check

在调试器中逐行跟踪程序代码比程序正常(非调试运行)耗费的时间要多出很多。Timing Check技术通过计算运行时间的差异来判断进程是否处于被调试状态。

基于Timing Check 的反调试原理相当简单,只要直接操作获取时间信息或比较时间的语句
即可。

提示:
Timing Check 技术也常常用作反模拟技术。程序在模拟器中运行时,运行速度要比程序正常运行(非模拟器运行)慢很多,所以Timing Check 技术也能用来探测程序是否在模拟器中运行。

时间间隔测量法

测量时间间隔的方法有很多种,常用方法如下所示:

1.Counter based method
RDTSC
kernel32!QueryPerformanceCounter()/NtQueryPermanceCounter()
kernel32!GetTickCount()

2.Time based method
timeGetTime()
_ftime()

测量时间间隔的方法大致分为两大类:一类是利用CPU的计算器;另一类是利用系统但实际时间。

提示:
计数器的准确程度由高到低排列如下:
RDSTC>NtQueryPerformanceCounter()>GetTickCount()
NtQueryPerformanceCounter()GetTickCount()使用相同硬件,但二者准备程度不同(NtQueryPerformanceCounter()准确度更高),而RDTSC是CPU内部的计数器,其准确程度最高。基于时间的方法与基于计数器的方法在实现过程上比较类似,原理也差不多。

RDTSC(Read Time Stamp Counter,读取时间戳计算器)

X86 CPU中存在一个名为TSC(Time Stamp Counter,时间戳计数器)的64位寄存器。CPU对每个Clock Cycle(时钟周期)计数,然后保存到TSC。RDTSC是一条汇编指令,用来将TSC值读入到EDX:EAX寄存器(TSC大小为64位,其高32位保存到EDX寄存器,低32位保存至EAX寄存器)

在这里插入图片描述
在这里插入图片描述

破解之法

1.不使用跟踪命令,直接使用RUN命令越过相关代码。
0xCA17CF地址 处设置断点后运行。虽然运行速度略慢于正常运行速度,但与正常跟踪相比要快很多。
2.操作第二个RDTSC的结果值(EDX:EAX
操作第二个RDTSC的结果值,使之与第一个结果值相同,从而顺利通过CMP语句
3.操纵条件分支(CMP/Jcc)
在调试器中强制修改Flags的值,阻止执行跳转至0xCA17EF地址处。大部分的Jcc指令挥手CF或ZF的影响,只要修改这些标志即可控制Jcc指令。
在这里插入图片描述

CF与ZF全为0时,JA指令执行跳转动作,只要将CF与ZF之一的值修改为1,JA指令即失效,继续调试即可。。
4.利用内核模式驱动程序使RDTSC指令失效
利用内核模式驱动程序可以从根本使基于RDTSC的动态反调试技术失效(其实,Olly Advanced PlugIn就采用了该方法)

调试程序代码实现

#include "stdio.h"
#include "windows.h"
#include "tchar.h"
 
void DynAD_RDTSC()
{
//Timing Check 技术通过计算运行时间的差异来反调试,反模拟。
//1.Counter based method
//	RDTSC 汇编指令(RD==READ TSC == Time Stamp Counter)
//		x86 CPU中存在一个名为TSC(Time Stamp Counter,时间戳计数器)的64位寄存器。
//		CPU对每个Clock Cycle(时钟周期)计数,然后保存到TSC.RDTSC是一条汇编指令,
//		用来将TSC值读入EDX:EAX寄存器)。
  //		OD中的插件Olly Advanced->options  反调试2 Anti-RDSTC(基于驱动模式可以绕过),此功能有可能造成死机。
  //	kernel32!QueryPerformanceCounter()/ntdll!NtQueryPerformanceCounter()
  //	kernel32!GetTickCount()
  //2.Time based method
  //	timeGetTime()
  //	_ftime()
  
    DWORD dwDelta = 0;
 
    printf("Timing Check (RDTSC method)");
 
    __asm {
		pushad
		//0F31      rdtsc
		rdtsc
		push edx
		push eax
		//用于消耗时间的循环(实际代码相当复杂)
		xor eax, eax
		mov ecx, 0x3e8
 
_LOOP_START:
		inc eax
		loop _LOOP_START
 
		rdtsc
		pop esi			// eax
		pop edi			// edx
		// check high order bits
		cmp edx, edi
		ja _DEBUGGER_FOUND
		// check low order bits
		sub eax, esi
        mov dwDelta, eax
		cmp eax, 0xffffff
		jb _DEBUGGER_NOT_FOUND
 
        // debugger found -> crash!!!
_DEBUGGER_FOUND:
		xor eax, eax
		mov [eax], eax
 
        // debugger not found
_DEBUGGER_NOT_FOUND:
		popad
	}
    
    printf(" : delta = %X (ticks)\n", dwDelta);
    printf("  => Not debugging...\n\n");
}
 
int _tmain(int argc, TCHAR* argv[])
{
    DynAD_RDTSC();
 
    return 0;
}

陷阱标志

陷阱标志指EFLAGS寄存器的第九个(index 8)比特位
在这里插入图片描述

单步执行

TF值设置为1时,CPU将进入单步执行(Single Step)模式。单步执行模式中,CPU执行一条指令即触发1个EXCEPTION_SINGLE_STEP异常,然后陷阱标志会自动清零(0),该EXCEPTION_SINGLE_STEP异常可以与SEH技法结合,在反调试技术中用于探测调试器。
在这里插入图片描述
在这里插入图片描述

可以注意一下这里如何设置TF位的手法
在这里插入图片描述

破解之法

首先,修改OllyDbg调试器选项(忽略EXCEPTION_SINGLE_STEP异常),让被调试者直接处理EXCEPTION_SINGLE_STEP异常
???
在这里插入图片描述
在这里插入图片描述

然后,在注册SEH的地址0x002D17E0处设置断点。执行0x002D17D8地址处的指令后,调试器就会停在SEH的断点处。在新的EIP地址处再次设置断点,接着运行跟踪可执行正常代码。

调试程序代码实现

#include "stdio.h"
#include "windows.h"
#include "tchar.h"
 
void DynAD_SingleStep()
{
   //陷阱标志指EFLAGS寄存器的第9个(Index8)比特位。
   //TF值设置为1时,CPU将进入单步执行(Single Step)模式。单步直行模式中,CPU执行1条指令后即
  //触发一个EXCEPTION_SINGLE_STEP(0x80000004)异常,异常地址为下一条指令。然后陷阱标志会
  //自动清零。
    printf("Trap Flag (Single Step)\n");
 
    __asm {
        // install SEH
        push handler
        push DWORD ptr fs:[0]
        mov DWORD ptr fs:[0], esp
        
        //因无法修改EFLAGS,故通过栈修改
        pushfd
        or dword ptr ss:[esp], 0x100
        popfd
        
        //执行完nop指令后,才触发EXCEPTION_SINGLE_STEP异常,OD在nop指令使用F7,F8,F9都可以。
        //即KiUserExceptionDispatcher的异常地址指向 mov eax,0xFFFFFFFF
        //1)若为正常运行,则运行前面注册过的SEH
        //2)若为调试运行,则继续执行以下指令,不管使用F7,F8,F9都断在mov eax,0xFFFFFFFF
        nop
 
        // 1) debugging
        //    go to terminating code
        mov eax, 0xFFFFFFFF
        jmp eax                 // process terminating!!!
 
        // 2) not debugging
        //    go to normal code
handler:
        mov eax, dword ptr ss:[esp+0xc]
        mov ebx, normal_code
        mov dword ptr ds:[eax+0xb8], ebx
        xor eax, eax
        retn
 
normal_code:
        //   remove SEH
        pop dword ptr fs:[0]
        add esp, 4
    }
 
    printf("  => Not debugging...\n\n");
}
 
int _tmain(int argc, TCHAR* argv[])
{
    DynAD_SingleStep();
 
    return 0;
}

INT 2D

INT 2D 原为内核模式中用来触发断点异常的指令,也可以在用户模式下触发异常。但程序调试运行时不会触发异常,只是忽略。

1.忽略下条指令的第一个字节
在调试模式中执行完 INT 2D指令后,下条指令的第一个字节将被忽略,后一个字节会被识别为新的指令继续执行

2.一直运行到断点处
INT 2D 指令的另一特征,使用StepInto(F7)或StepOver(F8)命令跟踪INT 2D指令时,程序不会停在其下条指令的地方,而是一直运行,直到遇到断点,就像使用RUN(F9)命令运行程序一样。
在这里插入图片描述
在这里插入图片描述
程序讲解:

程序如果正常运行(非调试运行)时,执行完0x10117D2地址处的INT 2D 指令后,发生异常,运行SEH(0x10117DE);
程序调试运行时,执行INT 2D 指令后不会运行SEH,而是跳过1字节(90)继续执行0x10117D5的MOV指令

破解之法

这里呢,我换了未带插件的OD,吾爱破解那个太强大了。。。不管什么情况都直接NO debugger,改啥都没用。接下来逐步讲解:

第一步:
在这里插入图片描述
这里设置了一个SEH链,首先,我们得在SEH这里下一个断点,如果不下断点,这里面直接秒执行完。。。以至于你看到的情况是直接跳转。。。

第二步:
在调试选项里面勾选一下单步中断,利用单步中断异常在调试时去跳转到异常处理器函数,
在这里插入图片描述

在这里插入图片描述

第三步:
在这里插入图片描述
执行到这里的时候,修改TF位,让它产生EXCEPTION_SINGLE_STEP异常,(前提是第二步和第一步已完成情况下,你才能看到接下来的跳转。)
在这里插入图片描述
GAMEOVER

程序实现代码

#include "stdio.h"
#include "windows.h"
#include "tchar.h"
 
void DynAD_INT2D()
{
	//INT 2D 原为内核模式中用来触发异常的指令,也可以在用户模式下触发异常。但程序
	//调试运行时不会触发异常,只是忽略。
	//INT 2D指令会造成两个有趣的现象
	//1.在调试模式中执行INT2D指令后(F7,F8),下条指令的第一个字节将被忽略,后一个字节会被识别为新的指令继续执行。此特性,可用于代码混淆。
	//2.在调试模式中执行INT2D指令后(F7,F8),程序不会停在其下条指令开始的地方,而是一直运行,直到遇到断点(原有的代码字节顺序被打乱,OD的BUG)。
    BOOL bDebugging = FALSE;
 
    __asm {
        // install SEH
        push handler
        push DWORD ptr fs:[0]
        mov DWORD ptr fs:[0], esp
        
        //可以在执行到此条指令时,修改EFlags寄存器TF=1,然后就能进入SEH处理函数
        int 0x2d
 
        nop
        mov bDebugging, 1
        jmp normal_code
 
handler:
        mov eax, dword ptr ss:[esp+0xc]
        mov dword ptr ds:[eax+0xb8], offset normal_code
        mov bDebugging, 0
        xor eax, eax
        retn
 
normal_code:
        //   remove SEH
        pop dword ptr fs:[0]
        add esp, 4
    }
 
    printf("Trap Flag (INT 2D)\n");
    if( bDebugging )  printf("  => Debugging!!!\n\n");
    else              printf("  => Not debugging...\n\n");
}
 
int _tmain(int argc, TCHAR* argv[])
{
    DynAD_INT2D();
 
    return 0;
}

0xCC探测

API断点

若只调试程序中的某个局部功能,一个比较快的方法是先在程序要调试的API处设置断点,再运行程序。运行暂停在相应断点后,再查看存储在栈中的返回地址。“跟踪返回地址调试相应部分”的方式能够大幅缩小代码调试范围。反调试技术中,探测这些位置在API上的断点就能准确判断当前进程是否处于调试状态。一般而言,断点都是设置在API代码的开始部分,所以,只需要检测API代码的第一个字节是否是0xCC即可判断出当前进程是否处于调试之中

代码逆向人员常用API列表:

进程:

CreatProcess				CreateProcessAsUser     	CreateRemoteThread
CreatThread					GetThreadContext			SetThreadContext
EnumProcesses				EnumProcessModules			OpenProcess
CreateToolhelp32Snapshot	Process32First				Process32Next
ShellExecuteA				WinExec						TerminateProcess

内存:

ReadProcessMemory			WriteProcessMemory			VirtualAlloc
VirtualAllocEx				VirtualProtect				VirtualProtectEx
VirtualQuery				VirtualQueryEx

文件:

CreateFile					ReadFile					WriteFile
CopyFile					CreateDirectory				DeleteFile
MoveFile					MoveFileEx					FindFirstFile
FindNextFile				GetFileSize					GetWindowsDirectory
GetSystemDirectory			GetFileAttributes			SetFileAttributes
SetFilePointer				CreateFileMapping			MapViewOfFile
MapViewOfFileEx				UnmapViewOfFile				_open
_write						_read						_lseek
_tell

寄存器:

RegCreateKeyEx				RegDeleteKey				RegDeleteValue
RegEnumKeyEx				RegQueryValueEx				RegSetValue
RegSetValueEx

网络:

WSAStartup					socket						inet_addr
closesocket					getservbyname				gethostbybname
htons						connect						inet_htoa
recv						send						HttpOpenRequest
HttpSendRequest				HttpQueryInfo				InternetCloseHandle
InternetConnect				InternetGetConnectedState	InternetOpen
InternetOpenUrl				InternetReadFile			URLDownloadToFile

其它:

OpenProcessToken			LookupPrivilegeVaule		AdjustTokenPrivileges
OpenSCManager				CreateService				OpenService
ControlService				DeleteService				RegisterServiceCtrlHandler
SetServiceStatus			QueryServiceStatusEx		CreateMutex
OpenMutex					FindWindow					LoadLibrary
GetProAddress				GetModuleFileNameA			GetCommandLine
OutputDebugString			………………………………………………

破解之法

向系统API设置断点时尽量避开第一个字节,将之设置在代码的中间部分。此外,设置硬件断点也能避开上述所说

比较和校验

检测代码中设置的软件断点的另一个方法是,比较特定代码区域的校验和值。比如,假定程序中0x401000~0x401070地址区域的校验和值为0x12345678,在代码调试时,必然会设置一些断点(0xCC),这样一来,新的校验和值就与原值不一样了。像这样的话,比较校验和即可判断是否处于调试状态
在这里插入图片描述
未设置任何断点时,计算出来的校验值保存在内存单元0xF9A000中,在程序运行利用循环重新计算一遍校验值,然后再和原来对比。这里计算完后,直接修改je跳转即可跳转
在这里插入图片描述
如果不跳转,直接把它赋值为1;所以这里只需要让它跳过即可。

破解之法

从理论上讲,只要不在计算CRC的代码区域中设置断点或修改其中代码,基于校验和的反调试技术就会失效。但是最好的破解办法是直接修改CRC比较语句

程序实现代码

#include "stdio.h"
#include "windows.h"
#include "tchar.h"
 
DWORD g_dwOrgChecksum = 0xF5934986;
 
int _tmain(int argc, TCHAR* argv[]);
 
void DynAD_Checksum()
{
    BOOL bDebugging = FALSE;
    DWORD dwSize = 0;
    printf("Checksum\n");
    
    __asm {
        mov ecx, offset _tmain
        mov esi, offset DynAD_Checksum
        sub ecx, esi            // ecx : loop count (buf size)
        xor eax, eax            // eax : checksum
        xor ebx, ebx
 
_CALC_CHECKSUM:
        movzx ebx, byte ptr ds:[esi]
        add eax, ebx
        rol eax, 1
        inc esi
        loop _CALC_CHECKSUM
 
        cmp eax, g_dwOrgChecksum
        je _NOT_DEBUGGING
        mov bDebugging, 1
 
_NOT_DEBUGGING:
    }
 
    if( bDebugging )  printf("  => Debugging!!!\n\n");
    else              printf("  => Not debugging...\n\n");
}
 
 
int _tmain(int argc, TCHAR* argv[])
{
    DynAD_Checksum();
 
    return 0;
}

总结:

  1. CC int3 指令(注意两者之间没有空格)。

2.CD 2D int 2d 指令

pushfd

or [esp],100 

popfd

使EFlags寄存器TF==1.

此3种方法,都是利用了调试器特有的性质(1.忽略 2.一直运行,直到断点。 3.断在执行指令后的一条指令,都没有触发ntdll!KiUserExceptionDispatcher()

反调试技术系列:

静态反调试技术(1)https://blog.csdn.net/CSNN2019/article/details/113105292
静态反调试技术(2)https://blog.csdn.net/CSNN2019/article/details/113147820
静态反调试技术(3)https://blog.csdn.net/CSNN2019/article/details/113178232
动态反调试技术 https://blog.csdn.net/CSNN2019/article/details/113181558
高级反调试技术 https://blog.csdn.net/CSNN2019/article/details/113263215

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寻梦&之璐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值