目录

反调试技术

静态反调试

查看PEB.BeingDebugged的值

IsDebuggerPresent()的实现方式,如果该值为1,则表示当前进程被调试。
PEB访问方法:

  1. 直接获取PEB地址
1
MOV EAX, DWORD PTR FS:[30] ; FS[30] = address of PEB  
  1. 先获取TEB地址,在通过ProcessEnvironmentBlock成员(+30偏移)获取PEB地址
1
2
MOV EAX, DWORD PTR FS:[18] ; FS[18] = address of TEB  
MOV EAX, DWORD PTR DS:[EAX+30] ; DS[EAX+30] = address of PEB  

PEB.Ldr(PEB+0xC)

(仅限在WindowsXP系统,之后的系统无法使用,且以附加形式将无法在堆内存中出现上述标识)
调试进程时,其堆内存会出现一些奇怪的标志,表示它正处于被调试状态。最醒目的是,未使用的堆内存区域全部填充着0xEEFEEEFE,这证明该进程正在被调试。
PEB.Ldr指向_PEB_LDR_DATA结构体的指针,而_PEB_LDR_DATA结构体结构体又恰好在堆内存中创建,所以扫描该区域是否存在0xEEFEEEFE区域即可判断。

  • 破解方法:将相关0xEEFEEEFE区域全部覆盖为NULL即可。

PEB.Process Heap(PEB+0x18)

(仅限在WindowsXP系统,且以附加形式将无法在堆内存中出现上述特征)

PEB.Process Heap成员既可以直接从PEB结构体获取,也可以从GetProcessHeap()API获取。

HEAP(PEB+0x18)地址(黄色方框)为0x00790000


我们访问0x00790000
进程正常运行(非调试运行)时,Heap.Flags成员(+0xC)的值为0x2,Heap.ForceFlags成员(+0x10)的值为0x0.
进程处于被调试状态时,这些值也会随之改变。比较这些值即可判断。

  • 破解方法:将Heap.Flags与Heap.ForceFlags的值分别重新设置为2与0即可。

NtGlobalFalg(PEB+0x68)

(将运行中的进程附加到调试器的时候,NtGlobalFalg值不变)
调试进程时,PEB.NtGlobalFalg成员(+0x68)的值会被设置为0x70.所以,检测该成员的值即可判断进程是否处于被调试阶段.
NtGlobalFalg 0x70是由下列的flags值进行位或(bit OR)运算的结果

FLG_HEAP_ENABLE_TAIL_CHECK(0X10)
FLG_HEAP_ENABLE_FREE_CHECK(0X20)
FLG_HEAP_VALIDATE_PARAMETERS(0X40)

  • 破解方法:设置PEB.NtGlobalFalg为0即可。

NtQueryInformationProcess()系列

函数原型

1
2
3
4
5
6
7
__kernel_entry NTSTATUS NtQueryInformationProcess(  
IN HANDLE           ProcessHandle,  
IN PROCESSINFOCLASS ProcessInformationClass,  
OUT PVOID           ProcessInformation,  
IN ULONG            ProcessInformationLength,  
OUT PULONG          ReturnLength  
);  

ProcessInformationClass为enum类型。其中,与调试器探测有关的成员为

  • ProcessDebugPort(0x7)
  • ProcessDebugObjectHandle(0x1E)
  • ProcessDebugFlags(0x1F)

ProcessDebugPort(0x7)

进程处于非调试阶段时,dwDebugPort的值为0,处于调试阶段,则为0xFFFFFFFF
例如

1
2
3
4
DWORD dwDebugPort=0;  
pNtQueryInformationProcess(GetCurrentProcess(), ProcessDebugPort, &dwDebugPort, sizeof(dwDebugPort), NULL);  
if(dwDebugPort != 0)  
      printf("being debuged\n");  

即CheckRemoteDebuggerPresent()函数的实现方法,该函数还可以检测其他进程是否处于被调试状态。

ProcessDebugObjectHandle(0x1E)

如果进程处于被调试状态,则调试对象句柄存在,反之,该句柄值为NULL
例如

1
2
3
4
HANDLE hDebugObject = NULL;  
pNtQueryInformationProcess(GetCurrentProcess(),(PROCESSINFOCLASS)30 , &hDebugObject, sizeof(hDebugObject), NULL);  
if(hDebugObject != null)  
      printf("being debuged\n");  

API文档头文件中的ProcessInformationClass成员中找不到此值,故直接写入30作为参数。当然,也可以自己声明好PROCESSINFOCLASS。

ProcessDebugFlags(0x1F)

函数的第二个参数设置为ProcessDebugFlags(0x1F)后,调用函数后,通过第三个参数即可获取调试标志的值。如果为0,则进程处于被调试状态;若为1,则进程处于非调试状态。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
BOOL bDebugFlag = TRUE;  
  pNtQueryInformationProcess(GetCurrentProcess(),  
                             ProcessDebugFlags,  
                             &bDebugFlag,  
                             sizeof(bDebugFlag),  
                             NULL);  
  printf("NtQueryInformationProcess(ProcessDebugFlags) = 0x%X\n", bDebugFlag);  
  if( bDebugFlag == 0x0  )  printf("  => Debugging!!!\n\n");  
  else                      printf("  => Not debugging...\n\n");  
  

注意,此处BOOL为大写,即与int等价(如果用了bool,会导致结果错误)

NtQuerySystemInformation()

API文档
函数原型

1
2
3
4
5
6
7
__kernel_entry NTSTATUS NtQuerySystemInformation(  
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,  
OUT PVOID                   SystemInformation,  
IN ULONG                    SystemInformationLength,  
OUT PULONG                  ReturnLength  
);  
  
  • SystemInformationClass是枚举类型,其成员SystemKernelDebuggerInformation的值为0x23
  • 向函数第一个参数传入0x23,通过获取的第二个参数SystemInformation结构体的DebuggerEnabled和DebuggerInfo的值即可判断。
  • 注:当前(2018.12.26)的API文档中没有了SystemInformation结构体的定义,官方说明NtQuerySystemInformation可能将在未来的Windows版本中发生改变。
  • 破解方法:windows7中在命令行窗口(管理员模式)执行bcdedit /debug off即可。

NtQueryObject()

系统中的调试器调试进程的时候,会创建一个调试对象类型的内核对象。检测该对象是否存在即可判断是否有进程正在被调试。

ZwSetInformationThread()

  • 通过设置当前线程的信息来将自身从调试器中分离出来。
  • 原理:隐藏当前线程,使调试器无法再收到该线程的调试事件。

DebugActiveProcessStop()

用于分离调试器和被调试进程。

动态反调试

异常

  1. SEH
  2. SetUnhandledExceptionFilter()

进程中发生异常,若SEH未处理或者注册的SEH不存在,此时会调用执行系统的kernel32!UnhandledExceptionFilter()API.该函数内部会运行系统的最后一个异常处理器(名为Top Level Exception Filter或Last Exception Filter).系统最后的异常处理器通常会弹出错误消息框,然后终止进程运行。
kernel32!UnhandledExceptionFilter()API内部调用了ntdll!NtQueryInformationProcess(ProcessDebugPort)这个API(静态反调试),来判断是否正在调试进程。若进程正常运行(非调试状态),则运行系统最后的异常处理器,否则将异常派送给调试器。通过kernel32!SetUnhandledExceptionFilter可以修改系统最后的异常处理器。
基于异常的反调试技术中,通常先故意触发异常,然后在新注册的Last Exception Filter内部判断进程是正常运行还是调试运行,并根据判断结果修改EIP。

Timing Check

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

  1. 时间间隔测量

测量时间间隔的方法有很多,例如
基于计数器

1
2
3
RDTSC (汇编指令)  
kernel32!QueryPerformanceCounter() / ntdll!NtQueryPerformanceCounter()  
kernel32!GetTickCount()  

基于时间

1
2
timeGetTime()  
__ftime()  

计数器的精准程度从高到低:
RDTSC>NtQueryPerformanceCounter()>GetTickCount()

  1. RDTSC

x64CPU中存在一个名为TSC(Time Stamp Counter时间戳计数器)的64位寄存器。RDTSC这个汇编指令将TSC值读入EDX:EAX寄存器(高32位被保存到EDX,低32位被保存到EAX)

陷阱标志

陷阱标志指EFLAGS寄存器的第九个(Index 8)比特位, Trap Flag,(在x64dbg中表示为TF)
TF设置为1后,CPU将进入单步执行模式。单步执行模式中,CPU执行1条指令后即触发EXCEPTION_SINGLE_STEP异常,然后TF会自动清零(0)。
INT 2D
在调试模式下执行完INT 2D后,下一条指令的第一个字节将被调试器忽略。(od的bug?)(若设置TF为1后再执行INT 2D,则不会忽略)

0xCC探测

API断点0xCC检测

比较校验和

异常处理

SEH: 结构化异常处理
VEH: 向量化异常处理
TopLevelEH:顶层异常处理

1
2
3
4
5
6
7
EXCEPTION_EXECUTE_HANDLER :该异常被处理。从异常处下一条指令继续执行  
EXCEPTION_CONTINUE_SEARCH:不能处理该异常,让别人处理它吧  
EXCEPTION_CONTINUE_EXECUTION:该异常被忽略。从异常处处继续执行  
  
//调试器返回值:  
DBG_CONTINUE : 等同于EXCEPTION_CONTINUE_EXECUTION  
DBG_EXCEPTION_NOT_HANDLED :等同于EXCEPTION_CONTINUE_SEARCH  

异常处理器处理顺序流程:

  1. 交给调试器(进程必须被调试)
  2. 执行VEH
  3. 执行SEH
  4. TopLevelEH(进程被调试时不会被执行)
  5. 交给调试器(上面的异常处理都说处理不了,就再次交给调试器)
  6. 调用异常端口通知csrss.exe
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// exception.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。  
//  
#include <stdio.h>  
#include <windows.h>  
#include "atlstr.h"   
  
//先介绍一下返回值的意思  
//EXCEPTION_EXECUTE_HANDLER			//该指令异常被处理。从下一条指令继续执行  
//EXCEPTION_CONTINUE_SEARCH			//不能处理该异常,让别人处理它吧  
//EXCEPTION_CONTINUE_EXECUTION		//该指令被忽略。从该指令处继续执行  
  
  
void ShowExceptionInfo(PEXCEPTION_POINTERS pExcepInfo)  
{  
  
}  
LONG ShowSelectMessageBox(TCHAR* pTitle)  
{  
	int nRet = MessageBox(0, _T("--------\n我要“认领”该异常?\n--------\nYES:认领该异常。\n\nNO: 交给别人处理(return EXCEPTION_CONTINUE_SEARCH)"), pTitle, MB_YESNO);  
	if (nRet != IDYES)  
	{//让别人处理  
		_tprintf(_T("[EH.Exe] [SELE] Select EXCEPTION_CONTINUE_SEARCH\n"));  
		return EXCEPTION_CONTINUE_SEARCH;  
	}  
	nRet = MessageBox(0, _T("--------\n是“忽略”还是“处理”该异常?\n--------\nYES:忽略,从该指令处继续执行(return EXCEPTION_CONTINUE_EXECUTION)。\n\nNO: 处理,从下一条指令继续执行(return EXCEPTION_EXECUTE_HANDLER)"), pTitle, MB_YESNO);  
	if (nRet != IDYES)  
	{//处理  
		_tprintf(_T("[EH.Exe] [SELE] Select EXCEPTION_EXECUTE_HANDLER\n"));  
		return EXCEPTION_EXECUTE_HANDLER;  
	}  
  
	//忽略  
	_tprintf(_T("[EH.Exe] [SELE] Select EXCEPTION_CONTINUE_EXECUTION\n"));  
	return EXCEPTION_CONTINUE_EXECUTION;  
}  
LONG NTAPI FirstVectExcepHandler(PEXCEPTION_POINTERS pExcepInfo)  
{  
	TCHAR* pTitle = _T("*首个* VEH异常处理器");  
	_tprintf(_T("[EH.Exe] [VEH][1] in \n"));  
	LONG nRet = ShowSelectMessageBox(pTitle);  
	if (nRet == EXCEPTION_EXECUTE_HANDLER)  
	{  
		_tprintf(_T("[EH.Exe] [VEH][1] EXCEPTION_EXECUTE_HANDLER 标志在VEH中无效!,所以第二个VEH被调用。\n"));  
	}  
	if (nRet == EXCEPTION_CONTINUE_EXECUTION)  
	{  
		if (MessageBox(0, _T("jmp int3?(跳过INT3指令,否则还会被断下)"), _T("是否修正到下一条指令执行"), MB_YESNO) == IDYES)  
		{  
			pExcepInfo->ContextRecord->Eip += 1;//跳过int3  
			_tprintf(_T("[EH.Exe] [VEH][1] 该异常被处理。 且:jmp int3\n"));  
		}  
		else  
		{  
			_tprintf(_T("[EH.Exe] [VEH][1] 该异常被处理\n"));  
		}  
	}  
	_tprintf(_T("[EH.Exe] [VEH][1] out\n"));  
	return nRet;  
}  
  
LONG NTAPI LastVectExcepHandler(PEXCEPTION_POINTERS pExcepInfo)  
{  
	TCHAR* pTitle = _T("*最后* VEH异常处理器");  
	_tprintf(_T("[EH.Exe] [VEH][2] in \n"));  
	LONG nRet = ShowSelectMessageBox(pTitle);  
	if (nRet == EXCEPTION_EXECUTE_HANDLER)  
	{  
		_tprintf(_T("[EH.Exe] [VEH][2] EXCEPTION_EXECUTE_HANDLER 标志在VEH中无效!,所以SEH被调用。\n"));  
	}  
	if (nRet == EXCEPTION_CONTINUE_EXECUTION)  
	{  
		if (MessageBox(0, _T("jmp int3?(跳过INT3指令,否则还会被断下)"), _T("是否修正到下一条指令执行"), MB_YESNO) == IDYES)  
		{  
			pExcepInfo->ContextRecord->Eip += 1;//跳过int3  
			_tprintf(_T("[EH.Exe] [VEH][2] 该异常被处理。 且:jmp int3\n"));  
		}  
		else  
		{  
			_tprintf(_T("[EH.Exe] [VEH][2] 该异常被处理\n"));  
		}  
	}  
	_tprintf(_T("[EH.Exe] [VEH][2] out \n"));  
	return nRet;  
}  
  
LONG NTAPI TopLevelExcepFilter(PEXCEPTION_POINTERS pExcepInfo)  
{  
	TCHAR* pTitle = _T("*顶级* 异常处理器");  
	_tprintf(_T("[EH.Exe] [TOP] in \n"));  
	LONG nRet = ShowSelectMessageBox(pTitle);  
	_tprintf(_T("[EH.Exe] [TOP] out \n"));;  
	return nRet;  
}  
  
LONG FirstSEHer(PEXCEPTION_POINTERS pExcepInfo)  
{  
	TCHAR* pTitle = _T("第一个SEH处理器");  
	_tprintf(_T("[EH.Exe] [SEH][1] in \n"));  
	LONG nRet = ShowSelectMessageBox(pTitle);  
	_tprintf(_T("[EH.Exe] [SEH][1] out \n"));  
	return nRet;  
}  
LONG SecondSEHer(PEXCEPTION_POINTERS pExcepInfo)  
{  
	TCHAR* pTitle = _T("第二个SEH处理器");  
	_tprintf(_T("[EH.Exe] [SEH][2] in \n"));  
	LONG nRet = ShowSelectMessageBox(pTitle);  
	_tprintf(_T("[EH.Exe] [SEH][2] out \n"));;  
	return nRet;  
}  
  
LONG ThirdSEHer(PEXCEPTION_POINTERS pExcepInfo)  
{  
	TCHAR* pTitle = _T("第三个SEH处理器");  
	_tprintf(_T("[EH.Exe] [SEH][3] in \n"));  
	LONG nRet = ShowSelectMessageBox(pTitle);  
	_tprintf(_T("[EH.Exe] [SEH][3] out \n"));;  
	return nRet;  
}  
  
void ExcepFunction()  
{  
	__try  
	{  
		__try  
		{  
			_tprintf(_T("[EH.Exe] *[CALL] int 3\n"));  
			__asm int 3;  
		}  
		__except (FirstSEHer(GetExceptionInformation()))  
		{  
			_tprintf(_T("[EH.Exe] [SEH][1] 被俺处理了~(只有返回EXCEPTION_EXECUTE_HANDLER才会走到这里)\n"));  
		}  
	}  
	__except (SecondSEHer(GetExceptionInformation()))  
	{  
		_tprintf(_T("[EH.Exe] [SEH][2] 被俺处理了(只有返回EXCEPTION_EXECUTE_HANDLER才会走到这里)\n"));  
	}  
}  
  
DWORD __stdcall ExcepThread(LPVOID lpThreadParameter)  
{  
	_tprintf(_T("[EH.Exe] [ExcepThread] in\n"));  
	__try  
	{  
		ExcepFunction();  
	}  
	__except (ThirdSEHer(GetExceptionInformation()))  
	{  
		_tprintf(_T("[EH.Exe] [SEH][3] 被俺处理了(只有返回EXCEPTION_EXECUTE_HANDLER才会走到这里)\n"));  
	}  
	_tprintf(_T("[EH.Exe] [ExcepThread] out\n"));  
	return 0;  
}  
  
int _tmain(int argc, _TCHAR* argv[])  
{  
	_tprintf(_T("[EH.Exe] Add VEH.\n"));  
	AddVectoredExceptionHandler(1, &FirstVectExcepHandler);  
	_tprintf(_T("[EH.Exe] Add VEH.\n"));  
	AddVectoredExceptionHandler(0, &LastVectExcepHandler);  
	_tprintf(_T("[EH.Exe] Add Top LEF.\n"));  
	SetUnhandledExceptionFilter(&TopLevelExcepFilter);  
  
	HANDLE hThread = CreateThread(NULL, 0, &ExcepThread, NULL, 0, NULL);  
	if (hThread)  
	{  
		WaitForSingleObject(hThread, INFINITE);  
	}  
	_tprintf(_T("[EH.Exe] 进程退出\n"));  
	return 0;  
}  

例子

空指针六月re公开赛这道题用了很多反调试的技术。

  1. TLS回调函数中进行反调试检测

TLS回调函数是指,每当创建/终止进程的线程时,会自动调用执行的函数。创建或终止某线程时,TLS回调函数都会自动调用执行,前后共两次。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int __stdcall TlsCallback_0(int a1, int a2, int a3)  
{  
HMODULE v3; // eax  
FARPROC v4; // eax  
int result; // eax  
int v6; // [esp+DCh] [ebp-8h]  
  
v6 = 0;  
v3 = GetModuleHandleW(L"ntdll.dll");  
v4 = GetProcAddress(v3, "NtQueryInformationProcess");  
result = ((int (__stdcall *)(signed int, signed int, int *, signed int, _DWORD))v4)(-1, 7, &v6, 4, 0);  
if ( result )  
  return result;  
if ( v6 )  
{  
  result = TlsIndex;  
  *(_DWORD *)(*(_DWORD *)(__readfsdword(0x2Cu) + 4 * TlsIndex) + 4) = 0xDEADBEEF;  
}  
else  
{  
  result = TlsIndex;  
  *(_DWORD *)(*(_DWORD *)(__readfsdword(0x2Cu) + 4 * TlsIndex) + 4) = 0;  
}  
return result;  
}  

NtQueryInformationProcess,等于7检测ProcessDebugPort。
2. NtGlobalFalg(PEB+0x68)

调试进程时,PEB.NtGlobalFalg成员(+0x68)的值会被设置为0x70

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
__int64 sub_402740()  
{  
int v0; // edx  
__int64 v1; // ST00_8  
signed int i; // [esp+D0h] [ebp-6Ch]  
int v4[24]; // [esp+DCh] [ebp-60h]  
  
v4[22] = 0;  
v4[22] = *(_DWORD *)(__readfsdword(0x30u) + 0x68) & 0x70;  
v4[0] = 31;  
v4[1] = 43;  
v4[2] = 14;  
v4[3] = 87;  
v4[4] = 34;  
v4[5] = 64;  
v4[6] = 15;  
v4[7] = 7;  
v4[8] = 56;  
v4[9] = 32;  
v4[10] = 67;  
v4[11] = 98;  
v4[12] = 23;  
v4[13] = 45;  
v4[14] = 87;  
v4[15] = 63;  
v4[16] = 25;  
v4[17] = 74;  
v4[18] = 68;  
v4[19] = 88;  
if ( v4[22] != 23 )  
{  
  for ( i = 0; i < 20; ++i )  
    byte_452F80[i] = LOBYTE(v4[i]) ^ (23 - LOBYTE(v4[22]));  
}  
AddVectoredExceptionHandler(0, Handler);  
HIDWORD(v1) = v0;  
LODWORD(v1) = 0;  
return v1;  
}  
  1. GetTickCount

获取时间间隔

1
2
3
4
5
6
7
int sub_402AB0()  
{  
__int16 v0; // STE8_2  
  
v0 = GetTickCount();  
return (((unsigned __int16)GetTickCount() - v0) & 0xFF00) >> 8;  
}  
  1. 校验和
    这里不仅是代码不能修改,也不能下断点,因为断点的0xcc也会被检测
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
int __cdecl sub_4029E0(unsigned __int8 *a1, unsigned int a2)  
{  
int v3; // [esp+D0h] [ebp-8h]  
  
v3 = 0;  
while ( (unsigned int)a1 < a2 )  
  v3 += *a1++;  
return v3;  
}  
__int64 sub_402470()  
{  
signed int v0; // edx  
__int64 v1; // ST00_8  
signed int j; // [esp+D0h] [ebp-DCh]  
signed int i; // [esp+DCh] [ebp-D0h]  
int v5[24]; // [esp+E8h] [ebp-C4h]  
int v6; // [esp+150h] [ebp-5Ch]  
int v7; // [esp+15Ch] [ebp-50h]  
int v8; // [esp+168h] [ebp-44h]  
int v9; // [esp+174h] [ebp-38h]  
int v10; // [esp+180h] [ebp-2Ch]  
int v11; // [esp+18Ch] [ebp-20h]  
int v12; // [esp+198h] [ebp-14h]  
int v13; // [esp+1A4h] [ebp-8h]  
  
v13 = sub_402A30();  
v12 = sub_402AB0();  
v10 = sub_4029E0((unsigned __int8 *)sub_402B50 + 80, (unsigned int)sub_402B50 + 188);  
v9 = sub_4029E0((unsigned __int8 *)main + 16, (unsigned int)main + 206);  
v8 = sub_4029E0((unsigned __int8 *)sub_402E90 + 50, (unsigned int)sub_402E90 + 139);  
v7 = sub_4029E0((unsigned __int8 *)TopLevelExceptionFilter, (unsigned int)TopLevelExceptionFilter + 300);  
v6 = sub_4029E0((unsigned __int8 *)sub_402DC0 + 123, (unsigned int)sub_402DC0 + 136);  
v0 = (unsigned __int8)v8;  
v11 = (unsigned __int8)v6  
    + (unsigned __int8)v8  
    + (unsigned __int8)v7  
    + (unsigned __int8)v10  
    + (unsigned __int8)v9  
    + (*(_DWORD *)(*(_DWORD *)(__readfsdword(0x2Cu) + 4 * TlsIndex) + 4) & 0xFF)  
    + v12  
    + v13;  
v5[0] = 31;  
v5[1] = 43;  
v5[2] = 14;  
v5[3] = 87;  
v5[4] = 34;  
v5[5] = 64;  
v5[6] = 15;  
v5[7] = 7;  
v5[8] = 56;  
v5[9] = 32;  
v5[10] = 67;  
v5[11] = 98;  
v5[12] = 23;  
v5[13] = 45;  
v5[14] = 87;  
v5[15] = 63;  
v5[16] = 25;  
v5[17] = 74;  
v5[18] = 68;  
v5[19] = 88;  
v5[20] = 90;  
v5[21] = 113;  
v5[22] = 57;  
v5[23] = 97;  
if ( v11 )  
{  
  for ( i = 0; i < 24; ++i )  
  {  
    v0 = i;  
    byte_452F80[i] = LOBYTE(v5[i]) ^ v11;  
  }  
}  
else  
{  
  for ( j = 0; j < 24; ++j )  
  {  
    LOBYTE(v0) = v5[j];  
    byte_452F80[j] = v0;  
  }  
}  
HIDWORD(v1) = v0;  
LODWORD(v1) = 0;  
return v1;  
}  
  1. VEH
1
AddVectoredExceptionHandler(0, Handler);  
  1. SetUnhandledExceptionFilter
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
unsigned int sub_402DC0()  
{  
DWORD flOldProtect; // [esp+D0h] [ebp-2Ch]  
LPVOID lpAddress; // [esp+DCh] [ebp-20h]  
SIZE_T dwSize; // [esp+E8h] [ebp-14h]  
HMODULE v4; // [esp+F4h] [ebp-8h]  
  
v4 = GetModuleHandleW(0);  
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)TopLevelExceptionFilter);  
dwSize = 188;  
lpAddress = sub_402B50;  
dword_452FA0 = (int)sub_402B50;  
VirtualProtect(sub_402B50, 0xBCu, 0x40u, &flOldProtect);  
return sub_4032A0((int)lpAddress, dwSize, (int)&byte_452F90, 16);  
}  

参考链接

反调试技术总结
白话windows之四 异常处理机制(VEH、SEH、TopLevelEH…)
动态反调试技术
静态反调试技术
2020 空指针 5月RE公开赛