文章目录
异常
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;
}
总结:
- 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