-
-
[原创]根据al-khaser项目简要的对反调试技术进行整理
-
发表于:
2022-11-25 15:18
16929
-
[原创]根据al-khaser项目简要的对反调试技术进行整理
前言
本整理基于 LordNoteworthy/al-khaser: Public malware techniques used in the wild: Virtual Machine, Emulation, Debuggers, Sandbox detection. (github.com)
作者已经于第贰期 REVERSE 分享会中详细介绍了本文提到的种种技术。现在编辑一遍在看雪上共享给大家。
由于技术复杂,并且Windows存在大量未公开的内核参数、函数等,这些技术可能会过时。
反调试技术整理
PEB表、IsDebuggerPresent
检查FS:[0x30]
(32位)GS:[0x60]
(64位),等同于IsDebuggerPresent()
1 | BOOL IsDebuggerPresent();
|
参见IsDebuggerPresent - CTF Wiki (ctf-wiki.org)
CheckRemoteDebuggerPresent()/NtQueryInformationProcess
CheckRemoteDebuggerPresent()
调用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, optional] PULONG ReturnLength
);
|
调用此API,传入参数ProcessInformationClass = 7
将返回一个句柄指向调试器
参见CheckRemoteDebuggerPresent - CTF Wiki (ctf-wiki.org)、NtQueryInformationProcess - CTF Wiki (ctf-wiki.org)
异常捕获和处理
Windows异常处理流程简述
硬件异常:
CPU转储当前现场
CPU根据IDT查找异常处理例程(KiTrapXX
)
参见CPU和软件模拟异常的执行流程_鬼手56的博客-CSDN博客
使用IDA打开ntkrnlpa.idb
文件,在其中查找_IDT
可以看到对应的处理表
异常处理例程处理异常,完成异常信息封装
调用CommonDispatchException
,完善EXCEPTION_RECORD
结构
1 2 3 4 5 6 7 8 | typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD * ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;
|
将结构传递给KiDispatchException
1 2 3 4 5 6 7 | VOID KiDispatchException(
[ in ] PEXCEPTION_RECORD ExceptionRecord,
[ in ] PKEXCEPTION_FRAME ExceptionFrame,
[ in ] PKTRAP_FRAME TrapFrame,
[ in ] KPROCESSOR_MODE PreviousMode,
[ in ] BOOLEAN FirstChance
);
|
软件异常:
- 发生
throw
关键字
- 转入
_CxxThrowException
1 2 3 4 | extern "C" void __stdcall _CxxThrowException(
void * pExceptionObject
_ThrowInfo * pThrowInfo
);
|
- 通过
KERNEL32.DLL!RaiseException
填充EXCEPTION_RECORD
结构体1 2 3 4 5 6 | void RaiseException(
[ in ] DWORD dwExceptionCode,
[ in ] DWORD dwExceptionFlags,
[ in ] DWORD nNumberOfArguments,
[ in ] const ULONG_PTR * lpArguments
);
|
- 转入
NTDLL.DLL!RtlRaiseException
- 转入内核
NtRaiseException
- 转入内核
KiRaiseException
,Exception Code
最高位置零
- 传递到分发函数
KiDispatchException
内核异常
- 尝试传递给内核调试器
- 失败,利用
RtlDispatchException
传递至SEH1 2 3 4 | BOOLEAN RtlDispatchException(
[ in ] PEXCEPTION_RECORD ExceptionRecord,
[ in ] PCONTEXT ContextRecord
);
|
- 传递给内核调试器
- 终止Windows运行(BSoD)
用户异常
- 传递给内核调试器
- 失败或者不存在内核调试器:填充上下文
CONTEXT
,从KiExceptionDispatch
转入KeUserExceptionDispather
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 | typedef struct _CONTEXT {
DWORD64 P1Home;
DWORD64 P2Home;
DWORD64 P3Home;
DWORD64 P4Home;
DWORD64 P5Home;
DWORD64 P6Home;
DWORD ContextFlags;
DWORD MxCsr;
WORD SegCs;
WORD SegDs;
WORD SegEs;
WORD SegFs;
WORD SegGs;
WORD SegSs;
DWORD EFlags;
DWORD64 Dr0;
DWORD64 Dr1;
DWORD64 Dr2;
DWORD64 Dr3;
DWORD64 Dr6;
DWORD64 Dr7;
DWORD64 Rax;
DWORD64 Rcx;
DWORD64 Rdx;
DWORD64 Rbx;
DWORD64 Rsp;
DWORD64 Rbp;
DWORD64 Rsi;
DWORD64 Rdi;
DWORD64 R8;
DWORD64 R9;
DWORD64 R10;
DWORD64 R11;
DWORD64 R12;
DWORD64 R13;
DWORD64 R14;
DWORD64 R15;
DWORD64 Rip;
union {
XMM_SAVE_AREA32 FltSave;
NEON128 Q[ 16 ];
ULONGLONG D[ 32 ];
struct {
M128A Header[ 2 ];
M128A Legacy[ 8 ];
M128A Xmm0;
M128A Xmm1;
M128A Xmm2;
M128A Xmm3;
M128A Xmm4;
M128A Xmm5;
M128A Xmm6;
M128A Xmm7;
M128A Xmm8;
M128A Xmm9;
M128A Xmm10;
M128A Xmm11;
M128A Xmm12;
M128A Xmm13;
M128A Xmm14;
M128A Xmm15;
} DUMMYSTRUCTNAME;
DWORD S[ 32 ];
} DUMMYUNIONNAME;
M128A VectorRegister[ 26 ];
DWORD64 VectorControl;
DWORD64 DebugControl;
DWORD64 LastBranchToRip;
DWORD64 LastBranchFromRip;
DWORD64 LastExceptionToRip;
DWORD64 LastExceptionFromRip;
} CONTEXT, * PCONTEXT;
|
内核从KiUserExceptionDispatcher
获得控制,调用RtlDispatchException
查找并调用异常处理函数:VEH→SEH,从fs:[0]
开始。
1 2 3 4 5 | VOID KiUserExceptionDispatcher
(
[ in ] PEXCEPTION_RECORD ExceptionRecord,
[ in ] PCONTEXT ContextRecord
);
|
参考:VEH和SEH_鬼手56的博客-CSDN博客_veh和seh
- 失败或者未处理:进入内核再次处理
- 如果传递给内核调试器失败或者未处理,终止进程
参见异常处理流程 - ciyze0101 - 博客园 (cnblogs.com)
CloseHandle()
如果一个进程在调试器下运行,并且一个无效的句柄被传递给ntdll!NtClose()
或kernel32!CloseHandle()
函数,那么将引发EXCEPTION_INVALID_HANDLE:0xC0000008
异常。这个异常可以被一个异常处理程序缓存起来。如果控制被传递给异常处理程序,表明有一个调试器存在。
植入中断(包括int 3
和int 2dh
)
int 2dh
可以用于检测包括RING0,RING3
在内的所有调试器。通过提前植入一个断点,并附带植入一个VEH异常解决例程,可以用于判断调试器的存在与否。
另外,附加调试器的程序在运行完int 2dh
后,会跳过此指令之后的一个字节.
参见Interrupt 3 - CTF Wiki (ctf-wiki.org)
利用STATUS_GUARD_PAGE_VIOLATION
异常
分配被保护的内存区,利用属性PAGE_GUARD
标记分配的内存区。向其中填入ret
指令。当存在OD调试器解释时将返回到上一个入栈地址,而直接执行将产生STATUS_GUARD_PAGE_VIOLATION
错误(数据执行保护)
(Windows XP/2000)使用宏OutputDebugString
1 2 3 | void OutputDebugStringA(
[ in , optional] LPCSTR lpOutputString
);
|
此宏在没有调试器附加时,执行将产生一个LastError
。检查此错误的值在执行前后是否发生改变,可以知道是否有调试器寄生。
引发EXCEPTION_EXECUTE_HANDLER
错误
触发方法:关闭一个不存在的句柄
1 | CloseHandle( 0x99999999ULL );
|
使用EFLAGS
写入异常标志,然后监视异常
直接将EFLAGS
与0x100
或运算,利用VEH检查调试器的存在
利用UnhandledExceptionFilter()
在除去try…catch块的同时接管错误
1 2 3 4 5 6 7 8 | / * Global * / BOOL bIsBeinDbg = TRUE;
/ / ...
/ / 示例
LPTOP_LEVEL_EXCEPTION_FILTER Top = SetUnhandledExceptionFilter(myUnhandledExcepFilter);
/ / 在myUnhandledExcepFilter里更改全局变量bIsBeinDbg的值为FALSE
RaiseException(EXCEPTION_FLT_DIVIDE_BY_ZERO, 0 , 0 , NULL); / / 在此处产生任意错误
SetUnhandledExceptionFilter(Top);
return bIsBeinDbg;
|
内存扫描与监视
硬件断点寄存器检查
- 提示:对于API
ZeroMemory
,部分编译器会将此宏直接优化掉。因此建议使用SecureZeroMemory
- 利用
GetThreadContext
获取程序运行上下文,检查DrN
寄存器的值是否为0利用MEM_WRITE_WATCH
监察申请的内存块的访问、写入情况。尤其适合对反调试部分的字节码进行动态写入之后检查是否被修改。1 2 3 4 | BOOL GetThreadContext(
[ in ] HANDLE hThread,
[ in , out] LPCONTEXT lpContext
);
|
利用MEM_WRITE_WATCH
监察申请的内存块的访问、写入情况。尤其适合对反调试部分的字节码进行动态写入之后检查是否被修改。
在分配内存时指定
1 2 3 4 5 6 | LPVOID VirtualAlloc(
[ in , optional] LPVOID lpAddress,
[ in ] SIZE_T dwSize,
[ in ] DWORD flAllocationType,
[ in ] DWORD flProtect
);
|
使用VirtualAlloc
时指定参数[in] flAllocationType
为MEM_WRITE_WATCH = 0x00200000
需要检查时,使用GetWriteWatch
函数,其中参数[in] lpBaseAddress
填入上文申请的内存地址
参见GetWriteWatch function (memoryapi.h) - Win32 apps | Microsoft Docs
1 2 3 4 5 6 7 8 | UINT GetWriteWatch(
[ in ] DWORD dwFlags,
[ in ] PVOID lpBaseAddress,
[ in ] SIZE_T dwRegionSize,
[out] PVOID * lpAddresses,
[ in , out] ULONG_PTR * lpdwCount,
[out] LPDWORD lpdwGranularity
);
|
LFH:低碎片堆
LFH 不是单独的堆。 而是应用程序可以为其堆启用的策略。 启用 LFH 后,系统会在某些预先确定的大小中分配内存。 当应用程序从启用了 LFH 的堆请求内存分配时,系统会分配足够大以包含所请求大小的最小内存块。 无论是否启用 LFH,系统都不会将 LFH 用于大于 16 KB 的分配。
低碎片堆 | Microsoft Docs
- 当多次申请、释放堆内存之后,可能出现堆碎片。堆碎片的产生可能导致无法一次性分配大量连续的内存,尽管这个大小的内存在数值上是空闲的。
参见/windows 堆分析 - 合天网安实验室/《软件调试》
在调试模式下,不存在此堆。因此可以通过探测此堆的地址,间接判断调试器存在与否。
探测方法很简单,只需要查看nt!_HEAP.FrontEndHeap
对于从Windows10开始的操作系统,此值的偏移经常变动,因此不便于通过硬编码的方式查找
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 | BOOL LowFragmentationHeap(VOID)
{
PINT_PTR FrontEndHeap = NULL;
HANDLE hHeap = GetProcessHeap();
if (IsWindowsVista() || IsWindows7()) {
FrontEndHeap = (PINT_PTR)((CHAR * )hHeap + 0x178 );
FrontEndHeap = (PINT_PTR)((CHAR * )hHeap + 0xd4 );
}
if (IsWindows8or8PointOne()) {
FrontEndHeap = (PINT_PTR)((CHAR * )hHeap + 0x170 );
FrontEndHeap = (PINT_PTR)((CHAR * )hHeap + 0xd0 );
}
/ / In Windows 10. the offset changes very often.
/ / Ignoring it from now.
if (FrontEndHeap && * FrontEndHeap = = NULL) {
return TRUE;
}
return FALSE;
}
|
反HOOK
- 检查某DLL内部的函数的地址(利用
GetProcAddress
),并于DLL的地址比较。如果被HOOK,那么函数的地址就将位于对应DLL的地址空间之外。
判断地址空间利用lpBaseOfDll
PE属性和SizeOfImage
属性
- 要检查函数是否被HOOK,可以传入错误的参数,例如传入错误句柄或者错误大小,观察函数对错误参数的处理
NtGlobalFlag
检查FLG_HEAP_ENABLE_TAIL_CHECK/FLG_HEAP_ENABLE_FREE_CHECK/FLG_HEAP_VALIDATE_PARAMETERS
的值。
在 32 位机器上, NtGlobalFlag
字段位于PEB
(进程环境块)0x68
的偏移处, 64 位机器则是在偏移0xBC
位置. 该字段的默认值为 0. 当调试器正在运行时, 该字段会被设置为一个特定的值. 尽管该值并不能十分可信地表明某个调试器真的有在运行, 但该字段常出于该目的而被使用.
简单示例:
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 | __declspec(naked) BOOL DetectDebuggerUsingNtGlobalFlag32()
{
__asm
{
push ebp;
mov ebp, esp;
pushad;
mov eax, fs:[ 30h ]; / / 从此处获取PEB表
mov al, [eax + 68h ];
and al, 70h ;
cmp al, 70h ;
je being_debugged;
popad;
mov eax, 0 ;
jmp being_debugged + 6
being_debugged:
popad;
mov eax, 1 ;
leave;
retn;
}
}
|
参见NtGlobalFlag - CTF Wiki (ctf-wiki.org)
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, optional] PULONG ReturnLength
);
|
NtQueryInformationProcess function (winternl.h) - Win32 apps | Microsoft Docs
函数ntdll!NtQueryInformationProcess()
可以从一个进程中检索不同种类的信息。它接受一个ProcessInformationClass
参数,该参数指定了ProcessInformation
参数的输出类型。
1 2 3 4 5 6 7 8 9 | typedef enum _PROCESSINFOCLASS {
ProcessBasicInformation = 0 ,
ProcessDebugPort = 7 ,
ProcessWow64Information = 26 ,
ProcessImageFileName = 27 ,
ProcessBreakOnTermination = 29 ,
ProcessDebugObjectHandle = 30 , / / undocumented, 0x1e
ProcessDebugFlags = 31 / / undocumented, 0x1f
} PROCESSINFOCLASS;
|
传递ProcessDebugPort
时,如果进程正在被调试,API会检索到一个等于0xFFFFFFFF(十进制-1)的DWORD值。
传递ProcessDebugFlags
,将返回一个EPROCESS
内核结构
Windows 内核不透明结构 - Windows drivers | Microsoft Docs
参见EPROCESS 结构体属性介绍_hambaga的博客-CSDN博客
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 | / / 这是一个简略的`EPROCESS`结构,请访问参见来查看完整的结构
/ / undocumented
typedef struct _EPROCESS {
/ / ...
/ / 调试端口
PVOID DebugPort;
/ / 异常端口
PVOID ExceptionPort;
/ / ...
/ / 指向 3 环PEB(进程环境块),包含了进程地址空间中的堆和系统模块等信息
PPEB Peb;
/ / ...
union {
/ / 包含了进程的标志位,反映了进程当前状态和配置,上面那一大堆宏就是了
ULONG Flags;
/ / 字段只能由 PS_SET_BITS 和其他互锁宏设置。 最好通过位定义来读取字段,因此很容易找到引用
struct {
ULONG CreateReported : 1 ;
ULONG NoDebugInherit : 1 ; / / 调试器
/ / ...
}
}
} EPROCESS, * PEPROCESS;
|
传递ProcessDebugObjectHandle
,获取调试对象句柄
通过NtQuerySystemInformation
尝试获取调试器句柄
1 2 3 4 5 6 | __kernel_entry NTSTATUS NtQuerySystemInformation(
[ in ] SYSTEM_INFORMATION_CLASS SystemInformationClass,
[ in , out] PVOID SystemInformation,
[ in ] ULONG SystemInformationLength,
[out, optional] PULONG ReturnLength
);
|
参数SYSTEM_INFORMATION_CLASS
结构如下
1 2 3 4 5 6 7 | typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0 ,
/ / ...
SystemKernelDebuggerInformation = 35 , / / undocumented, 0x23
/ / ...
SystemPolicyInformation = 134 ,
} SYSTEM_INFORMATION_CLASS;
|
ntdll!NtQuerySystemInformation()
函数接受要查询的信息类别作为参数,然而该参数大多数类都没有被记录下来,包括SystemKernelDebuggerInformation(0x23)
类,它从Windows NT开始就存在了。
SystemKernelDebuggerInformation
返回两个标志寄存器的值:al
中的KdDebuggerEnabled
,和ah
中的KdDebuggerNotPresent
。因此,如果内核调试器存在,ah
中的返回值为零。
参见反调试:调试标志寄存器-|bbs.pediy.com
查询调试对象
参见调试篇——调试对象与调试事件
DbgUiConnectToDbg
函数会创建调试对象,并把它放到TEB
的DbgSsReserved[1]
成员,也就是该偏移0xF24
位置。利用NtQueryObject
检查所有调试对象,可以广泛的禁止系统调试,但是容易误伤。
1 2 3 4 5 6 7 | __kernel_entry NTSYSCALLAPI NTSTATUS NtQueryObject(
[ in , optional] HANDLE Handle,
[ in ] OBJECT_INFORMATION_CLASS ObjectInformationClass,
[out, optional] PVOID ObjectInformation,
[ in ] ULONG ObjectInformationLength,
[out, optional] PULONG ReturnLength
);
|
利用NtSetInformationThread
将线程从调试器中隐藏。
1 2 3 4 5 6 | __kernel_entry NTSYSCALLAPI NTSTATUS NtSetInformationThread(
[ in ] HANDLE ThreadHandle,
[ in ] THREADINFOCLASS ThreadInformationClass,
[ in ] PVOID ThreadInformation,
[ in ] ULONG ThreadInformationLength
);
|
关于ThreadInformationClass
的枚举量,请查阅THREADINFOCLASS (geoffchappell.com)
1 2 3 4 5 6 7 | typedef enum _THREADINFOCLASS {
ThreadBasicInformation = 0 ,
/ / ...
ThreadHideFromDebugger = 17 ,
/ / ...
MaxThreadInfoClass = 51 ,
} THREADINFOCLASS;
|
1 2 | / / 示例
NtSetInformationThread(handle.get(), ThreadHideFromDebugger, nullptr, 0 );
|
参见ZwSetInformationThread - CTF Wiki (ctf-wiki.org)
反制[原创]调试陷阱ThreadHideFromDebugger的另一种对抗方法-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com
NtYieldExecution
这个函数可以让任何就绪的线程暂停执行,等待下一个线程调度。线程放弃剩余时间,让给其他线程执行。如果没有其他准备好的线程,该函数返回false,否则返回true。当前线程如果被调试,那么调试器线程若处于单步状态,随时等待继续运行,则被调试线程执行NtYieldExecution
时,调试器线程会恢复执行。此时NtYieldExecution返回true,该线程则认为自身被调试了。
此方法并不准确,因为检测到的行为可能是由上层应用程序触发。因此会设置一个计数器。
父进程检查
如果启动进程不是cmd.exe
、explorer.exe
,则返回警告
HeapFlags
和HeapForceFlags
检查几个位,注意这些位的值的大小。如果HeapFlags
的值大于 2 说明程序处于调试状态;如果 HeapForceFlags
的值大于 0 则说明处于调试状态。
- Flags 字段:
- 在 32 位 Windows NT, Windows 2000 和 Windows XP 中,
Flags
位于堆的0x0C
偏移处. 在 32 位 Windows Vista 及更新的系统中, 它位于0x40
偏移处.
- 在 64 位 Windows XP 中,
Flags
字段位于堆的0x14
偏移处, 而在 64 位 Windows Vista 及更新的系统中, 它则是位于0x70
偏移处.
- ForceFlags 字段:
- 在 32 位 Windows NT, Windows 2000 和 Windows XP 中,
ForceFlags
位于堆的0x10
偏移处. 在 32 位 Windows Vista 及更新的系统中, 它位于0x44
偏移处.
- 在 64 位 Windows XP 中,
ForceFlags
字段位于堆的0x18
偏移处, 而在 64 位 Windows Vista 及更新的系统中, 它则是位于0x74
偏移处.
参见Heap Flags - CTF Wiki (ctf-wiki.org)
检查作业容器Job Object
Windows 提供一个作业对象,它允许我们将进程组合在一起并创建一个"沙箱"来限制进程能做什么.可以将作业想象成一个进程容器.但是,只包含一个进程的作业同样有用,因为这样可以对进程施加平时不能施加的限制.
检查同在一个作业对象中的其他进程。
SeDebugPrivileges
默认情况下进程是没有SeDebugPrivilege权限的,但是当进程通过调试器启动时,由于调试器本身启动了SeDebugPrivilege权限,所以我们可以检测进程的SeDebugPrivilege权限来间接判断是否存在调试器,而对SeDebugPrivilege权限的判断可以用能否打开csrss.exe进程来判断。
KUSER_SHARED_DATA
参见KUSER_SHARED_DATA (ntddk.h) - Windows drivers | Microsoft Docs
1 2 3 4 5 6 7 8 | / / 这是一个简略的`EPROCESS`结构,请访问参见来查看完整的结构
typedef struct _KUSER_SHARED_DATA {
ULONG TickCountLowDeprecated;
/ / ...
BOOLEAN KdDebuggerEnabled; / / 位置: 0x2D4
/ / ...
ULONG64 UserPointerAuthMask;
} KUSER_SHARED_DATA, * PKUSER_SHARED_DATA;
|
用户空间和内核空间其实有一块共享区域KUSER_SHARED_DATA
,大小为 4 KB。它们的内存地址虽然不一样,但是它们都是有同一块物理内存映射出来的,其中存在内核调试检查位KdDebuggerEnabled
可以获取内核调试状态。
此内存块的地址为0xFFDF0000
(x86)、0xFFFFF78000000000
(x64),对应的要检查的位在0x2d4
处
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | BOOL SharedUserData_KernelDebugger()
{
const ULONG_PTR UserSharedData = 0x7FFE0000 ;
const UCHAR KdDebuggerEnabledByte = * (UCHAR * )(UserSharedData + 0x2D4 ); / / 0x2D4 = the offset of the field
/ / Extract the flags.
/ / The meaning of these is the same as in NtQuerySystemInformation(SystemKernelDebuggerInformation).
/ / Normally if a debugger is attached, KdDebuggerEnabled is true, KdDebuggerNotPresent is false and the byte is 0x3 .
const BOOLEAN KdDebuggerEnabled = (KdDebuggerEnabledByte & 0x1 ) = = 0x1 ;
const BOOLEAN KdDebuggerNotPresent = (KdDebuggerEnabledByte & 0x2 ) = = 0 ;
if (KdDebuggerEnabled || !KdDebuggerNotPresent)
return TRUE;
return FALSE;
}
|
扫描关键位置字节码,排除0xCC
利用TLS进行反调试
TLS在执行主函数前执行
WUDF驱动框架动态链接库
从"C:\Windows\System32\WUDFPlatform.dll"
中导出几个函数,其中存在一个与IsDebuggerPresent
相类似的函数WudfIsAnyDebuggerPresent
等
参考资料
al-khaser学习笔记(一)Anti Debug - CrisCzy - 博客园 (cnblogs.com)
我的博客原文
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2022-11-25 15:38
被Hedione编辑
,原因: 小修小补