从内核角度认识反调试基本原理
概述
系统版本
同上篇文章。(文章末尾提供PDF版本下载地址)
检测进程是否被调试
基础
EPROCESS(执行体进程块)
复制代码 隐藏代码 4: kd> ?? sizeof(_eprocess) //查看结构大小
unsigned int64 0x850
4: kd> dt _eprocess //查看结构
nt!_EPROCESS
+0x000 Pcb : _KPROCESS //进程控制块
+0x2d8 ProcessLock : _EX_PUSH_LOCK
+0x2e0 UniqueProcessId : Ptr64 Void
+0x2e8 ActiveProcessLinks : _LIST_ENTRY
+0x2f8 RundownProtect : _EX_RUNDOWN_REF
+0x300 Flags2 : Uint4B
+0x304 Flags : Uint4B
+0x308 CreateTime : _LARGE_INTEGER
....//省略(减少篇幅)
+0x3e0 InheritedFromUniqueProcessId : Ptr64 Void //父进程PID
....//省略(减少篇幅)
+0x3f8 Peb : Ptr64 _PEB //进程环境块指针(Ring 3)
....//省略(减少篇幅)
+0x420 DebugPort : Ptr64 Void //调试端口
....//省略(减少篇幅)
+0x838 ParentSecurityDomain : Uint8B
+0x840 CoverageSamplerContext : Ptr64 Void
+0x848 MmHotPatchContext : Ptr64 Void
KPROCESS(进程控制块)(选读)
复制代码 隐藏代码 4: kd> dt _kprocess
nt!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x018 ProfileListHead : _LIST_ENTRY
+0x028 DirectoryTableBase : Uint8B
+0x030 ThreadListHead : _LIST_ENTRY
....//省略(减少篇幅)
+0x26c KernelTime : Uint4B
+0x270 UserTime : Uint4B
+0x274 ReadyTime : Uint4B
+0x278 UserDirectoryTableBase : Uint8B
+0x280 AddressPolicy : UChar
+0x281 Spare2 : [71] UChar
+0x2c8 InstrumentationCallback : Ptr64 Void
+0x2d0 SecureState : <unnamed-tag>
PEB(进程环境块)
复制代码 隐藏代码 4: kd> ?? sizeof(_peb) //查看peb大小
unsigned int64 0x7c8
4: kd> dt _peb
nt!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar //是否开始了调试
+0x003 BitField : UChar
+0x004 Padding0 : [4] UChar
+0x008 Mutant : Ptr64 Void
+0x010 ImageBaseAddress : Ptr64 Void
....//省略(减少篇幅)
+0x0bc NtGlobalFlag : Uint4B
....//省略(减少篇幅)
+0x7b8 LeapSecondData : Ptr64 _LEAP_SECOND_DATA
+0x7c0 LeapSecondFlags : Uint4B
+0x7c0 SixtySecondEnabled : Pos 0, 1 Bit
+0x7c0 Reserved : Pos 1, 31 Bits
+0x7c4 NtGlobalFlag2 : Uint4B
分析方法
写出正在调试的进程的EPROCESS(包含KPROCESS)和PEB
复制代码 隐藏代码 2: kd> !process 0 0 Debugging.exe //正在调试的进程
PROCESS ffff9d0e59bc3080
SessionId: 1 Cid: 16a4 Peb: 002e4000 ParentCid: 2048
DirBase: 117c00000 ObjectTable: ffffc40fb7ce6cc0 HandleCount: 48.
Image: Debugging.exe
2: kd> .writemem E:lsDebugging_EPROCESS.a ffff9d0e59bc3080 l0x850 //写出EPROCESS
Writing 850 bytes..
2: kd> .process /p ffff9d0e59bc3080;.writemem E:lsDebugging_PEB.a 002e4000 l0x7c8 //写出PEB
Implicit process is now ffff9d0e`59bc3080
.cache forcedecodeuser done
Writing 7c8 bytes.
写出非调试状态的进程的EPROCESS(包含KPROCESS)和PEB
复制代码 隐藏代码 2: kd> !process 0 0 Non_Debugging.exe //非调试状态下的进程
PROCESS ffff9d0e5b8f50c0
SessionId: 1 Cid: 06c0 Peb: 00323000 ParentCid: 14f8
DirBase: 1bda00000 ObjectTable: ffffc40fb5026dc0 HandleCount: 52.
Image: Non_Debugging.exe
2: kd> .writemem E:lsNon_Debugging_EPROCESS.a ffff9d0e5b8f50c0 l0x850 //写出EPROCESS
Writing 850 bytes..
2: kd> .process /p ffff9d0e5b8f50c0;.writemem E:lsNon_Debugging_PEB.a 00323000 l0x7c8 //写出PEB
Implicit process is now ffff9d0e`5b8f50c0
.cache forcedecodeuser done
Writing 7c8 bytes.
通过WinHex进行对比
图中标黑的就是对比出来不同的地方。再结合EPROCESS结构(包含KPROCESS)和PEB结构,可以得出哪些变量是不同的。
结论
EPROCESS:
复制代码 隐藏代码 +0x304 Flags : Uint4B
+0x304 CreateReported : Pos 0, 1 Bit
+0x304 NoDebugInherit : Pos 1, 1 Bit //未调试时置0,调试时置1
+0x304 ProcessExiting : Pos 2, 1 Bit
+0x304 ProcessDelete : Pos 3, 1 Bit
+0x304 ManageExecutableMemoryWrites : Pos 4, 1 Bit
+0x304 VmDeleted : Pos 5, 1 Bit
+0x304 OutswapEnabled : Pos 6, 1 Bit
+0x304 Outswapped : Pos 7, 1 Bit
+0x304 FailFastOnCommitFail : Pos 8, 1 Bit
+0x304 Wow64VaSpace4Gb : Pos 9, 1 Bit
+0x304 AddressSpaceInitialized : Pos 10, 2 Bits
+0x304 SetTimerResolution : Pos 12, 1 Bit
+0x304 BreakOnTermination : Pos 13, 1 Bit
+0x304 DeprioritizeViews : Pos 14, 1 Bit
+0x304 WriteWatch : Pos 15, 1 Bit
+0x304 ProcessInSession : Pos 16, 1 Bit
+0x304 OverrideAddressSpace : Pos 17, 1 Bit
+0x304 HasAddressSpace : Pos 18, 1 Bit
+0x304 LaunchPrefetched : Pos 19, 1 Bit
+0x304 Background : Pos 20, 1 Bit
+0x304 VmTopDown : Pos 21, 1 Bit
+0x304 ImageNotifyDone : Pos 22, 1 Bit
+0x304 PdeUpdateNeeded : Pos 23, 1 Bit
+0x304 VdmAllowed : Pos 24, 1 Bit
+0x304 ProcessRundown : Pos 25, 1 Bit
+0x304 ProcessInserted : Pos 26, 1 Bit
+0x304 DefaultIoPriority : Pos 27, 3 Bits
+0x304 ProcessSelfDelete : Pos 30, 1 Bit
+0x304 SetTimerResolutionLink : Pos 31, 1 Bit
复制代码 隐藏代码 +0x3e0 InheritedFromUniqueProcessId : Ptr64 Void //父进程
复制代码 隐藏代码 +0x420 DebugPort : Ptr64 Void //调试端口 未调试时为0
PEB:
复制代码 隐藏代码 +0x0bc NtGlobalFlag : Uint4B
//未开启调试 00 00 00 00
//已开启调试 70 00 00 00
复制代码 隐藏代码 +0x002 BeingDebugged : UChar
//未开启调试 0
//已开启调试 1
检测是否存在内核调试器
基本思路
开启内核调试的情况
复制代码 隐藏代码 5: kd> dq KdComponentTable l1
fffff804`2b953a10 fffff804`2badd714
5: kd> db KDskEvt_Flush l1
fffff804`2b974048 0e .
5: kd> db KDskEvt_Write l1
fffff804`2b974058 0b .
5: kd> db KDskEvt_Read l1
fffff804`2b974068 0a .
5: kd> db KdDebuggerDataBlock l4
fffff804`2b9ff5e0 80 19 a3 2b ...+
5: kd> dq KdPrintCircularBuffer l1
fffff804`2ba022a8 fffff804`2ba23340
5: kd> dq KdPrintWritePointer l1
fffff804`2ba022b0 fffff804`2ba23382
5: kd> db KdPitchDebugger l1
fffff804`2ba02db8 00 .
........ //省略很多
未开启内核调试的情况
复制代码 隐藏代码 ic>dq KdComponentTable l1
FFFFF8055C96CA10 FFFFF8055CAF6714
ic>db KDskEvt_Flush l1
FFFFF8055C98D048 0e
ic>db KDskEvt_Write l1
FFFFF8055C98D058 0b
ic>db KDskEvt_Read l1
FFFFF8055C98D068 0a
ic>db KdDebuggerDataBlock l4
FFFFF8055CA185E0 f7 64 8f 7d
ic>dq KdPrintCircularBuffer l1
FFFFF8055CA1B2A8 FFFFF8055CA3C340
ic>dq KdPrintWritePointer l1
FFFFF8055CA1B2B0 FFFFF8055CA3C340
ic>db KdPitchDebugger l1
FFFFF8055CA1BDB8 01
........ //省略很多
结果
复制代码 隐藏代码 db KdPitchDebugger l1
已开启内核调试:0 未开启内核调试:1
dd KdpTimeSlipPending l1
已开启内核调试:1 未开启内核调试:0
db KdpBootedNodebug l1
已开启内核调试:0 未开启内核调试:1
dd KdDebuggerLockMaxWaitTime l1
已开启内核调试:有值 未开启内核调试:0
dd KdDebuggerEnteredCount l1
已开启内核调试:有值 未开启内核调试:0
db KdpContextSent l1
已开启内核调试:1 未开启内核调试:0
db KdpBreakpointTable l4
下内核断点:有值 未下内核断点:0
dq KdTimerStop l1
已开启内核调试:有值 未开启内核调试:0
db KdpPathBuffer l4
已开启内核调试:有值 未开启内核调试:0
dq KdTimerDifference l1
已开启内核调试:有值 未开启内核调试:0
db KdpControlCPressed l1
挂起:1 恢复:0
dd KdUmBreakMarker l1
已开启内核调试:有值 未开启内核调试:0
dd KdEnteredDebugger l1
挂起:1 恢复:0
dq KdDebugDevice l1
已开启内核调试:有值 未开启内核调试:0
db KdPageDebuggerSection l1
已开启内核调试:0 未开启内核调试:1
db KdpDebuggerStructuresInitialized l1
已开启内核调试:1 未开启内核调试:0
dq KdTimerStart l1
已开启内核调试:有值 未开启内核调试:0
db KdLogBuffer l4
已开启内核调试:有值 未开启内核调试:0
db KdDebuggerEnabled l1
已开启内核调试:1 未开启内核调试:0
dq KdDebuggerNotPresent l1
已开启内核调试:0 未开启内核调试:1
db KdpContext l4
已开启内核调试:有值 未开启内核调试:0
db KdBreakAfterSymbolLoad l1
已开启内核调试:1 未开启内核调试:0
db KdpDataBlockEncoded l1
已开启内核调试:0 未开启内核调试:1
dd KdpDebugRoutineSelect l1
已开启内核调试:1 未开启内核调试:0
dq KdpTimeSlipTimer l1
已开启内核调试:有值 未开启内核调试:0
db KdPortLocked l1
已开启内核调试:1 未开启内核调试:0
db KdpMessageBuffer l4
已开启内核调试:有值 未开启内核调试:0
dq KdDebuggerLock l1
挂起:1 恢复:0
探究反调试实例
demo(x64):
复制代码 隐藏代码 #include<Windows.h>
int main() {
while (1) {
system("pause");
MessageBoxA(0, "hello!", 0, 0);
}
}
目的:
第一步:
这里的地址我们要记录一下,除了.text、rdata、data、reloc、rsrc区段以外的都有可能调用检测。
Kernel-mode:
测试模式下直接双击运行
检测到开启测试模式,无法运行。
分析
复制代码 隐藏代码 #include<Windows.h>
typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION {
BOOLEAN KdDebuggerEnabled;
BOOLEAN KdDebuggerNotPresent;
} SYSTEM_KERNEL_DEBUGGER_INFORMATION, * PSYSTEM_KERNEL_DEBUGGER_INFORMATION;
typedef NTSTATUS(WINAPI* pNtQuerySystemInformation)(IN UINT SystemInformationClass, OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength);
//存在内核调试器:返回1 否则:返回0
bool check0()
{
// 取 NtQuerySystemInformation 地址
pNtQuerySystemInformation NtQuerySystemInformation
= (pNtQuerySystemInformation)GetProcAddress(LoadLibrary(L"ntdll.dll"), "ZwQuerySystemInformation");
// 获取系统信息
SYSTEM_KERNEL_DEBUGGER_INFORMATION KdDebuggerInfo;
NtQuerySystemInformation(0x23, &KdDebuggerInfo, sizeof(SYSTEM_KERNEL_DEBUGGER_INFORMATION), NULL);
// 判断调试器
if (KdDebuggerInfo.KdDebuggerEnabled == 0 && KdDebuggerInfo.KdDebuggerNotPresent == 1)
return FALSE;
else
return TRUE;
}
KdDebuggerEnabled:
KdDebuggerNotPresent
对应地址:
可以在内核调试器先在ExpQuerySystemInformation下条件断点,断下来后再在KdDebuggerEnabled与KdDebuggerNotPresent下以线程为条件设置访问断点。(如果有用户调试器,需要先将这之前的反调试过掉)
复制代码 隐藏代码 0: kd> !process 0 0 vmp3.6.exe //获取EPROCESS
PROCESS ffff9d0e5ac83080
SessionId: 1 Cid: 0e84 Peb: efce1d3000 ParentCid: 1e80
FreezeCount 1
DirBase: 166100000 ObjectTable: ffffc40fb6f48080 HandleCount: 34.
Image: zy1.exe
0: kd> bp /p ffff9d0e5ac83080 nt!NtQuerySystemInformation //在ExpQuerySystemInformation下条件断点
0: kd> g
Breakpoint 0 hit //命中断点
nt!NtQuerySystemInformation:
fffff804`2bc9ec50 4883ec38 sub rsp,38h
2: kd> .thread //获取当前线程
Implicit thread is now ffff9d0e`59f8f080
2: kd> ba r1 KdDebuggerEnabled ".if(@$thread == ffff9d0e`59f8f080){} .else{gc}" //以线程为条件设置访问断点
2: kd> g
nt!ExpQuerySystemInformation+0xc7a:
fffff804`2bc9fa1a 8803 mov byte ptr [rbx],al //看下图的位置
复制代码 隐藏代码 bp /p <EPROCESS> <Address> //EPROCESS输入demo的EPROCESS,Address输入ExpQuerySystemInformation对应位置
复制代码 隐藏代码 bp <Address> ".if(dwo(@$proc+0x450)==0x33706d76){} .else{gc}"
//Address输入ExpQuerySystemInformation对应位置,dwo(@$proc+0x450)是取进程名的前四字节。
//这里的条件就是,运行到address这一行时,此时的进程必须满足进程名为XXXX,0x33706d76 就是 “vmp3” 的ascii码
vmp3_6 + 0x39d6cc 明显属于壳的区段了(是壳调用了这个函数进行检测),说明我们的方向是正确的。
复制代码 隐藏代码 1: kd> eb KdDebuggerEnabled 0
1: kd> eb KdDebuggerNotPresent 1
此时触发了一个异常,vmp_3.6 + 0x3819ab 也属于壳的区段,说明这也是一个检测。
复制代码 隐藏代码 //存在调试器:返回1 否则:返回0
bool check1() {
_try{
__asm rdtsc;//举例:触发一个异常
return TRUE;//被解决了,有调试器
}
_except(EXCEPTION_EXECUTE_HANDLER) {
return FALSE;//没被解决,无调试器
}
}
User-mode:
注意:
PEB部分解决:
复制代码 隐藏代码 0: kd> !process 0 0 vmp3.6.exe //获取demo的信息
PROCESS ffff9d0e5aad6080
SessionId: 1 Cid: 17f4 Peb: 25a749000 ParentCid: 0e68
FreezeCount 1
DirBase: 192d00000 ObjectTable: ffffc40fadb1cdc0 HandleCount: 43.
Image: vmp3.6.exe
0: kd> .process /p ffff9d0e5aad6080;eb 25a749000+0x2 0;ed 25a749000+0xbc 0
// PEB.BeingDebugged 和 PEB.NtGlobalFlag 置 0
EPROCESS部分解决:
复制代码 隐藏代码 typedef NTSTATUS(NTAPI * TNtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN DWORD ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength
);
flags.NoDebugInherit(选读):
复制代码 隐藏代码 //存在调试器:返回1 否则:返回0
bool check2() {
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (hNtdll)
{
auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress(
hNtdll, "NtQueryInformationProcess");
if (pfnNtQueryInformationProcess)
{
DWORD ProcessDebugFlags, Returned;
const DWORD CProcessDebugFlags = 0x1f;
NTSTATUS status = pfnNtQueryInformationProcess( //获取NoDebugInherit取反
GetCurrentProcess(),
CProcessDebugFlags,
&ProcessDebugFlags,
sizeof(DWORD),
&Returned);
if (0 == ProcessDebugFlags)
return TRUE;
}
}
return FALSE;
}
复制代码 隐藏代码 2: kd> !process 0 0 zy3.exe //为获取检测示例的EPROCESS
PROCESS ffff9d0e59bc3080
SessionId: 1 Cid: 06b0 Peb: a5abcf5000 ParentCid: 058c
FreezeCount 1
DirBase: 106400000 ObjectTable: ffffc40fb395c680 HandleCount: 34.
Image: zy3.exe
2: kd> bp /p ffff9d0e59bc3080 nt!NtQueryInformationProcess //检测示例运行到nt!NtQueryInformationProcess中断
2: kd> g //运行
Breakpoint 0 hit //符合断点,中断
nt!NtQueryInformationProcess:
fffff804`2bbdbdf0 4053 push rbx
2: kd> dt _eprocess ffff9d0e59bc3080 -y flags //为获取flags的偏移
nt!_EPROCESS
+0x300 Flags2 : 0xd014
+0x304 Flags : 0x144d0c03 //这个
+0x6cc Flags3 : 0x40c008
2: kd> r @$thread //获取当前线程,通过线程设置条件断点
$thread=ffff9d0e58cf1080
2: kd> ba r4 ffff9d0e59bc3080+0x304 ".if(@$thread == ffff9d0e58cf1080) {} .else{gc}" //这个线程访问flags时中断
2: kd> g
nt!NtQueryInformationProcess+0x1a6260: //符合条件中断,是断在符合条件的代码的下一行
fffff804`2bd82050 d1e8 shr eax,1
汇编注释:
通过NtQueryInformationProcess可以获取EPROCESS.flags.NoDebugInherit的相反值。
复制代码 隐藏代码 2: kd> !process 0 0 zy3.exe //为获取检测示例的EPROCESS
PROCESS ffff9d0e59bc3080
SessionId: 1 Cid: 06b0 Peb: a5abcf5000 ParentCid: 058c
FreezeCount 1
DirBase: 106400000 ObjectTable: ffffc40fb395c680 HandleCount: 34.
Image: zy3.exe
2: kd> dt _eprocess ffff9d0e59bc3080 -y flags //为获取flags的偏移
nt!_EPROCESS
+0x300 Flags2 : 0xd014
+0x304 Flags : 0x144d0c03 //这个
+0x6cc Flags3 : 0x40c008
2: kd> ed ffff9d0e59bc3080+0x304 0x144d0c01 //将第二位(NoDebugInherit)修改成0
InheritedFromUniqueProcessId(选读):
复制代码 隐藏代码 0: kd> !process 0 0 vmp3.6.exe //获取demo EPROCESS
PROCESS ffff9d0e5aad6080
SessionId: 1 Cid: 17f4 Peb: 25a749000 ParentCid: 0e68
FreezeCount 1
DirBase: 192d00000 ObjectTable: ffffc40fadb1cdc0 HandleCount: 43.
Image: vmp3.6.exe
2: kd> !process 0 0 explorer.exe //有些反调试会检测父进程是否为explorer.exe
PROCESS ffff9d0e59daf080
SessionId: 1 Cid: 14f8 Peb: 00c26000 ParentCid: 14e0
DirBase: 2220b0000 ObjectTable: ffffc40fab968dc0 HandleCount: 2193.
Image: explorer.exe
2: kd> dt _eprocess ffff9d0e59daf080 -y UniqueProcessId //获取explorer.exe的PID
ntdll!_EPROCESS
+0x2e0 UniqueProcessId : 0x00000000`000014f8 Void
2: kd> dt _eprocess ffff9d0e5aad6080 -y InheritedFromUniqueProcessId //此时demo的父进程为X64dbg
ntdll!_EPROCESS
+0x3e0 InheritedFromUniqueProcessId : 0x00000000`00000e68 Void
2: kd> eq ffff9d0e5aad6080+3e0 0x00000000`000014f8 //将父进程修改为explorer.exe
DebugPort:
初始工作(下断点):
复制代码 隐藏代码 1: kd> !process 0 0 vmp3.6.exe //获取 EPROCESS
PROCESS ffff9d0e5aad6080
SessionId: 1 Cid: 2148 Peb: 3a0a56f000 ParentCid: 211c
FreezeCount 1
DirBase: 1c9b00000 ObjectTable: ffffc40fb0640b40 HandleCount: 43.
Image: vmp3.6.exe
1: kd> .process /p ffff9d0e5aad6080 //切换上下文,方便修改PEB
Implicit process is now ffff9d0e`5aad6080
.cache forcedecodeuser done
1: kd> dt _peb 3a0a56f000 -y BeingDebugged //获取BeingDebugged偏移
ntdll!_PEB
+0x002 BeingDebugged : 0x1 ''
1: kd> eb 3a0a56f000+0x2 0 //修改BeingDebugged为0,排除干扰
1: kd> dt _peb 3a0a56f000 -y NtGlobalFlag //获取NtGlobalFlag偏移
ntdll!_PEB
+0x0bc NtGlobalFlag : 0x70 //这个
+0x7c4 NtGlobalFlag2 : 0
1: kd> ed 3a0a56f000+0xbc 0 //修改NtGlobalFlag为0,排除干扰
1: kd> eb KdDebuggerEnabled 0 //排除内核调试器检测干扰
1: kd> eb KdDebuggerNotPresent 1 //排除内核调试器检测干扰
1: kd> dt _eprocess ffff9d0e5aad6080 -y debugport //获取debugport偏移
ntdll!_EPROCESS
+0x420 DebugPort : 0xffff9d0e`5b8867b0 Void
1: kd> ba r8 ffff9d0e5aad6080+0x420 ".if(@$proc == ffff9d0e5aad6080){} .else{gc}" //下条件访问断点
1: kd> g //取消系统挂起
断点第一次中断:
从壳区段发出的 syscall,调用了NtQueryInformationProcess,来检测是否处于调试状态。
复制代码 隐藏代码 //存在调试器:返回1 否则:返回0
bool check3() {
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (hNtdll)
{
auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress(
hNtdll, "NtQueryInformationProcess");
if (pfnNtQueryInformationProcess)
{
DWORD ProcessDebugPort = 0x7, dwReturned; //检测Debugport
DWORD64 dwProcessDebugPort;
NTSTATUS status = pfnNtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugPort,
&dwProcessDebugPort, //Debugport不为空则返回-1
sizeof(DWORD64),
&dwReturned);
if (-1 == dwProcessDebugPort)
return TRUE;
}
}
return FALSE;
}
断点第二次中断:
复制代码 隐藏代码 //存在调试器:返回1 否则:返回0
bool check4() {
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (hNtdll)
{
auto pfnNtQueryInformationProcess = (TNtQueryInformationProcess)GetProcAddress(
hNtdll, "NtQueryInformationProcess");
if (pfnNtQueryInformationProcess)
{
DWORD dwReturned;
HANDLE hProcessDebugObject = 0;
const DWORD ProcessDebugObjectHandle = 0x1e; //查询ProcessDebugObjectHandle
NTSTATUS status = pfnNtQueryInformationProcess(
GetCurrentProcess(),
ProcessDebugObjectHandle,
&hProcessDebugObject,
sizeof(HANDLE),
&dwReturned);
if (0 != hProcessDebugObject) //DebugObject不为空说明有调试器
return TRUE;
return FALSE;
}
}
}
断点第三次中断:
同第一次中断。
断点第四次中断:
复制代码 隐藏代码 bool check5()
{
__try
{
CloseHandle((HANDLE)0x999999); //如果debugport值不为空,则CloseHandle会触发调试器可解决的异常
return false; //调试器处理了,有调试器
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return true; //没有调试器处理,无调试器
}
}
断点第五次中断(异常):
看过我之前的“WinDows10 x64 异常处理”的帖子就知道,这里为啥会断在(nt!KiDispatchException)这里了
断点第六次中断以及其他中断处:
是从 vmp3.6 + 0x101d 处调用的,不属于壳的区段,可以忽略。继续运行
成功在调试器中运行:
用户调试器无法通过断点中断:
复制代码 隐藏代码 0: kd> bp /p ffff9d0e5aad6080 nt!DbgkForwardException //下条件断点
0: kd> g //恢复系统后到,用户调试器继续运行
Breakpoint 1 hit //符合条件,中断
nt!DbgkForwardException:
fffff804`2bcbc20c 48895c2410 mov qword ptr [rsp+10h],rbx
3: kd> gu //跳出此函数
nt!KiDispatchException+0x2bb: //到达这里,看下图
fffff804`2b66bfeb 84c0 test al,al
看调用栈!断点是发挥了作用了,但是DbgkForwardException返回0。
根本原因就是 ETHREAD.CrossThreadFlags.HideFromDebugger 为 1,所以用户调试无法接收这个异常。
复制代码 隐藏代码 typedef NTSTATUS(WINAPI* NtSetInformationThreadPtr)(
HANDLE threadHandle,
int threadInformationNum,
PVOID threadInformation,
ULONG threadInformationLength
);
void HideFromDebugger() {
HMODULE hNtDll = LoadLibrary(TEXT("ntdll.dll"));
NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hNtDll, "NtSetInformationThread");
NTSTATUS status = NtSetInformationThread(GetCurrentThread(), 17, NULL, 0);//设置隐藏调试
}
复制代码 隐藏代码 3: kd> dt _ethread @$thread -y CrossThreadFlags
ntdll!_ETHREAD
+0x6d0 CrossThreadFlags : 0x5406
3: kd> .formats 0x5406 //查看0x5406的二进制
Evaluate expression:
Hex: 00000000`00005406
Decimal: 21510
Octal: 0000000000000000052006
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 01010100 00000110 //第三位为1
Chars: ......T.
Time: Thu Jan 1 13:58:30 1970
Float: low 3.01419e-041 high 0
Double: 1.06274e-319
3: kd> .formats 0y0000000000000000000000000000000000000000000000000101010000000010
//修改第三位为0后,查看16进制
Evaluate expression:
Hex: 00000000`00005402 //这个
Decimal: 21506
Octal: 0000000000000000052002
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 01010100 00000010
Chars: ......T.
Time: Thu Jan 1 13:58:26 1970
Float: low 3.01363e-041 high 0
Double: 1.06254e-319
3: kd> ed @$thread+0x6d0 0x5402 //修改CrossThreadFlags为0x5402,再取消DbgkForwardException断点
3: kd> g
总结
提取码:ICEY
原文始发于微信公众号(吾爱破解论坛):【系统底层】从内核角度认识反调试基本原理
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论