无论是在 32 位系统内存分布,还是在 64 位系统内存分布中,我们知道高地址空间分配给系统内核使用,低地址空间分配给用户进程使用。
事实上,用户空间和内核空间其实有一块共享区域(KUSER_SHARED_DATA),大小为 4 KB。它们的内存地址虽然不一样,但是它们都是有同一块物理内存映射出来的,KdDebuggerEnabled 就在存放这一块内存里。
对于 32 位系统和 64 位系统来说,这块共享区域对应的内核地址范围以及对应用户空间的地址范围如下表所示:
🤡内核起始地址内核结束地址用户起始地址用户结束地址x860xFFDF00000xFFDF0FFF0x7FFE00000x7FFE0FFFx640xFFFFF780000000000xFFFFF78000000FFF0x7FFE00000x7FFE0FFF在 windbg 里查看 KUSER_SHARED_DATA:
dt _KUSER_SHARED_DATA nt!_KUSER_SHARED_DATA +0x000 TickCountLowDeprecated : Uint4B +0x004 TickCountMultiplier : Uint4B +0x008 InterruptTime : _KSYSTEM_TIME +0x014 SystemTime : _KSYSTEM_TIME +0x020 TimeZoneBias : _KSYSTEM_TIME +0x02c ImageNumberLow : Uint2B +0x02e ImageNumberHigh : Uint2B +0x030 NtSystemRoot : [260] Wchar +0x238 MaxStackTraceDepth : Uint4B +0x23c CryptoExponent : Uint4B +0x240 TimeZoneId : Uint4B +0x244 LargePageMinimum : Uint4B +0x248 AitSamplingValue : Uint4B +0x24c AppCompatFlag : Uint4B +0x250 RNGSeedVersion : Uint8B +0x258 GlobalValidationRunlevel : Uint4B +0x25c TimeZoneBiasStamp : Int4B +0x260 NtBuildNumber : Uint4B +0x264 NtProductType : _NT_PRODUCT_TYPE +0x268 ProductTypeIsValid : UChar +0x269 Reserved0 : [1] UChar +0x26a NativeProcessorArchitecture : Uint2B +0x26c NtMajorVersion : Uint4B +0x270 NtMinorVersion : Uint4B +0x274 ProcessorFeatures : [64] UChar +0x2b4 Reserved1 : Uint4B +0x2b8 Reserved3 : Uint4B +0x2bc TimeSlip : Uint4B +0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE +0x2c4 BootId : Uint4B +0x2c8 SystemExpirationDate : _LARGE_INTEGER +0x2d0 SuiteMask : Uint4B +0x2d4 KdDebuggerEnabled : UChar // <<<<<<<<< 划重点 +0x2d5 MitigationPolicies : UChar +0x2d5 NXSupportPolicy : Pos 0, 2 Bits +0x2d5 SEHValidationPolicy : Pos 2, 2 Bits +0x2d5 CurDirDevicesSkippedForDlls : Pos 4, 2 Bits +0x2d5 Reserved : Pos 6, 2 Bits +0x2d6 Reserved6 : [2] UChar +0x2d8 ActiveConsoleId : Uint4B +0x2dc DismountCount : Uint4B +0x2e0 ComPlusPackage : Uint4B +0x2e4 LastSystemRITEventTickCount : Uint4B +0x2e8 NumberOfPhysicalPages : Uint4B +0x2ec SafeBootMode : UChar +0x2ed VirtualizationFlags : UChar +0x2ee Reserved12 : [2] UChar +0x2f0 SharedDataFlags : Uint4B +0x2f0 DbgErrorPortPresent : Pos 0, 1 Bit +0x2f0 DbgElevationEnabled : Pos 1, 1 Bit +0x2f0 DbgVirtEnabled : Pos 2, 1 Bit +0x2f0 DbgInstallerDetectEnabled : Pos 3, 1 Bit +0x2f0 DbgLkgEnabled : Pos 4, 1 Bit +0x2f0 DbgDynProcessorEnabled : Pos 5, 1 Bit +0x2f0 DbgConsoleBrokerEnabled : Pos 6, 1 Bit +0x2f0 DbgSecureBootEnabled : Pos 7, 1 Bit +0x2f0 DbgMultiSessionSku : Pos 8, 1 Bit +0x2f0 DbgMultiUsersInSessionSku : Pos 9, 1 Bit +0x2f0 DbgStateSeparationEnabled : Pos 10, 1 Bit +0x2f0 SpareBits : Pos 11, 21 Bits +0x2f4 DataFlagsPad : [1] Uint4B +0x2f8 TestRetInstruction : Uint8B +0x300 QpcFrequency : Int8B +0x308 SystemCall : Uint4B +0x30c SystemCallPad0 : Uint4B +0x310 SystemCallPad : [2] Uint8B +0x320 TickCount : _KSYSTEM_TIME +0x320 TickCountQuad : Uint8B +0x320 ReservedTickCountOverlay : [3] Uint4B +0x32c TickCountPad : [1] Uint4B +0x330 Cookie : Uint4B +0x334 CookiePad : [1] Uint4B +0x338 ConsoleSessionForegroundProcessId : Int8B +0x340 TimeUpdateLock : Uint8B +0x348 BaselineSystemTimeQpc : Uint8B +0x350 BaselineInterruptTimeQpc : Uint8B +0x358 QpcSystemTimeIncrement : Uint8B +0x360 QpcInterruptTimeIncrement : Uint8B +0x368 QpcSystemTimeIncrementShift : UChar +0x369 QpcInterruptTimeIncrementShift : UChar +0x36a UnparkedProcessorCount : Uint2B +0x36c EnclaveFeatureMask : [4] Uint4B +0x37c TelemetryCoverageRound : Uint4B +0x380 UserModeGlobalLogger : [16] Uint2B +0x3a0 ImageFileExecutionOptions : Uint4B +0x3a4 LangGenerationCount : Uint4B +0x3a8 Reserved4 : Uint8B +0x3b0 InterruptTimeBias : Uint8B +0x3b8 QpcBias : Uint8B +0x3c0 ActiveProcessorCount : Uint4B +0x3c4 ActiveGroupCount : UChar +0x3c5 Reserved9 : UChar +0x3c6 QpcData : Uint2B +0x3c6 QpcBypassEnabled : UChar +0x3c7 QpcShift : UChar +0x3c8 TimeZoneBiasEffectiveStart : _LARGE_INTEGER +0x3d0 TimeZoneBiasEffectiveEnd : _LARGE_INTEGER +0x3d8 XState : _XSTATE_CONFIGURATION众所周知,想要使用 windbg 调试,首先要 cmd 执行以下命令,开启本机的调试模式:
bcdedit /debug on重启之后 KdDebuggerEnabled 的值就从 0 变成了 1。 想要隐藏调试模式很简单,只需要修改这个地址的值就可以。 windows 已经导出了 _KUSER_SHARED_DATA,我们可以直接在驱动里这样使用:
SharedUserData->KdDebuggerEnabled = 0;修改完之后, 不影响 windbg 的本地内核调试的使用
我这里以网络双击调试作为例子,此时 KdDebuggerEnabled 的值已经不是 1 ,而是 3: 如果你尝试去修改它,那么它会变成 2。我试过抱着侥幸心理去创建一个 dpc 定时器去循环的修改这个值,但就算我的延时设置成 1毫秒它仍然可以以更快的速度恢复成 2。
我又想,如果无法恢复这个值,那是不是可以找到是谁访问了这个地址,让检测双机调试的代码去检测其他的地址(或者是直接把这段代码 nop ),岂不也能达到我的目的 ?
最开始想到的办法就是对这个地址下访问断点:
ba r1 fffff780000002d4 ba :硬件断点r : 读1 :1字节但不尽人意的是,这个地址会被某些代码频繁访问:
不得不说这确实让人想吐。。。😱
之后我试了下用条件断点做过滤,但是结果都不太理想。于是我决定修改这些会访问 KdDebuggerEnabled 的系统代码,让它们不再访问这个地址。
首先来到第一处访问这个地址的位置: 可以看到它把 fffff780000002d4 中的值赋值给了 al,使用 r al 命令查看 al 的值 发现 al 的值就是 3,到此整体的思路就已经很明朗了:
先在某个地址中写入一个伪装值:3 这个地址我选择了 共享区域中的最下层(因为用户空间和内核空间的共享区域大小为 4 KB,内核占用其中一小部分,下面的内存不会被系统使用,被污染的可能性小) 让 mov rax,0xfffff780000002d4 这条指令改为 mov rax,伪装值所在地址至此,这段代码已经不再对 0xfffff780000002d4 访问,并且逻辑不会改变(万不得已不要改变内核代码的逻辑,否则很可能会蓝屏 😐)修改完成后: 其他位置同理,修改完成后,windbg 不再频繁断下。并且我自己的驱动代码可以正常断下:
此时再修改 fffff780000002d4 的值,已经不会再被恢复成 2 了,并且windbg 双击调试仍然可用: