DebugPort清零实现反调试

Posted by Qfrost on 2021-02-19
Estimated Reading Time 9 Minutes
Words 2k In Total
Viewed Times

在0环对3环的反调试中,最常用的应该就是DebugPort清零。先介绍一下这个DebugPort是什么东西。当调试器附加一个进程时,会调用NtDebugActiveProcess附加进程,这个函数会调用DbgkpPostFakeProcessCreateMessages告诉调试器创建调试线程(DbgkpPostFakeThreadMessages)并加载调试所需模块(DbgkpPostFakeModuleMessages),然后再调用DbgkpSetProcessDebugObject,这个函数会把DebugObject设置到进程_EPROCESS.DebugPort下。 这个调试对象可就很有用了,在调试过程当中,0环会通过这个对象将调试信息发送到三环调试器,那同样意味着我们对这个位置的清零会使三环调试器接收不了任何调试信息,以此实现了反调试。

那实际上我们就将关键问题定位到了怎么那道受保护进程EPROCESS结构体基址和DebugPort标志位对于EPROCESS结构体基址的偏移是多少这两个问题上。注意EPROCESS结构不等同与三环下的进程环境块(PEB),EPROCESS是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
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
0: kd> dt_eprocess ffffb08edc6a4080
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x2e0 ProcessLock : _EX_PUSH_LOCK
+0x2e8 UniqueProcessId : 0x00000000`0000122c Void
+0x2f0 ActiveProcessLinks : _LIST_ENTRY [ 0xffffb08e`df3d15f0 - 0xffffb08e`dfe0b670 ]
+0x300 RundownProtect : _EX_RUNDOWN_REF
+0x308 Flags2 : 0x30c0d094
+0x308 JobNotReallyActive : 0y0
+0x308 AccountingFolded : 0y0
+0x308 NewProcessReported : 0y1
+0x308 ExitProcessReported : 0y0
+0x308 ReportCommitChanges : 0y1
+0x308 LastReportMemory : 0y0
+0x308 ForceWakeCharge : 0y0
+0x308 CrossSessionCreate : 0y1
+0x308 NeedsHandleRundown : 0y0
+0x308 RefTraceEnabled : 0y0
+0x308 PicoCreated : 0y0
+0x308 EmptyJobEvaluated : 0y0
+0x308 DefaultPagePriority : 0y101
+0x308 PrimaryTokenFrozen : 0y1
+0x308 ProcessVerifierTarget : 0y0
+0x308 RestrictSetThreadContext : 0y0
+0x308 AffinityPermanent : 0y0
+0x308 AffinityUpdateEnable : 0y0
+0x308 PropagateNode : 0y0
+0x308 ExplicitAffinity : 0y0
+0x308 ProcessExecutionState : 0y11
+0x308 EnableReadVmLogging : 0y0
+0x308 EnableWriteVmLogging : 0y0
+0x308 FatalAccessTerminationRequested : 0y0
+0x308 DisableSystemAllowedCpuSet : 0y0
+0x308 ProcessStateChangeRequest : 0y11
+0x308 ProcessStateChangeInProgress : 0y0
+0x308 InPrivate : 0y0
+0x30c Flags : 0x144d0c41
+0x30c CreateReported : 0y1
+0x30c NoDebugInherit : 0y0
+0x30c ProcessExiting : 0y0
+0x30c ProcessDelete : 0y0
+0x30c ManageExecutableMemoryWrites : 0y0
+0x30c VmDeleted : 0y0
+0x30c OutswapEnabled : 0y1
+0x30c Outswapped : 0y0
+0x30c FailFastOnCommitFail : 0y0
+0x30c Wow64VaSpace4Gb : 0y0
+0x30c AddressSpaceInitialized : 0y11
+0x30c SetTimerResolution : 0y0
+0x30c BreakOnTermination : 0y0
+0x30c DeprioritizeViews : 0y0
+0x30c WriteWatch : 0y0
+0x30c ProcessInSession : 0y1
+0x30c OverrideAddressSpace : 0y0
+0x30c HasAddressSpace : 0y1
+0x30c LaunchPrefetched : 0y1
+0x30c Background : 0y0
+0x30c VmTopDown : 0y0
+0x30c ImageNotifyDone : 0y1
+0x30c PdeUpdateNeeded : 0y0
+0x30c VdmAllowed : 0y0
+0x30c ProcessRundown : 0y0
+0x30c ProcessInserted : 0y1
+0x30c DefaultIoPriority : 0y010
+0x30c ProcessSelfDelete : 0y0
+0x30c SetTimerResolutionLink : 0y0
+0x310 CreateTime : _LARGE_INTEGER 0x01d7066a`a27dfc74
+0x318 ProcessQuotaUsage : [2] 0x71c0
+0x328 ProcessQuotaPeak : [2] 0x73e0
+0x338 PeakVirtualSize : 0x00000001`1595e000
+0x340 VirtualSize : 0x00000001`1555e000
+0x348 SessionProcessLinks : _LIST_ENTRY [ 0xffffb08e`df3d1648 - 0xffffb08e`dddc8808 ]
+0x358 ExceptionPortData : 0xffffb08e`da03fd30 Void
+0x358 ExceptionPortValue : 0xffffb08e`da03fd30
+0x358 ExceptionPortState : 0y000
+0x360 Token : _EX_FAST_REF
+0x368 MmReserved : 0
+0x370 AddressCreationLock : _EX_PUSH_LOCK
+0x378 PageTableCommitmentLock : _EX_PUSH_LOCK
+0x380 RotateInProgress : (null)
+0x388 ForkInProgress : (null)
+0x390 CommitChargeJob : 0xffffb08e`dc341060 _EJOB
+0x398 CloneRoot : _RTL_AVL_TREE
+0x3a0 NumberOfPrivatePages : 0xc91
+0x3a8 NumberOfLockedPages : 0
+0x3b0 Win32Process : 0xffff8946`82eea4e0 Void
+0x3b8 Job : 0xffffb08e`dc341060 _EJOB
+0x3c0 SectionObject : 0xffff9b01`478b2920 Void
+0x3c8 SectionBaseAddress : 0x00007ff7`78280000 Void
+0x3d0 Cookie : 0x8fc5c269
+0x3d8 WorkingSetWatch : (null)
+0x3e0 Win32WindowStation : 0x00000000`00000170 Void
+0x3e8 InheritedFromUniqueProcessId : 0x00000000`000002e8 Void
+0x3f0 OwnerProcessId : 0x2ea
+0x3f8 Peb : 0x0000001b`c6a7e000 _PEB
+0x400 Session : 0xffffc300`ae1f3000 _MM_SESSION_SPACE
+0x408 Spare1 : (null)
+0x410 QuotaBlock : 0xffffb08e`da170cc0 _EPROCESS_QUOTA_BLOCK
+0x418 ObjectTable : 0xffff9b01`486b6140 _HANDLE_TABLE
+0x420 DebugPort : (null)
+0x428 WoW64Process : (null)
+0x430 DeviceMap : 0xffff9b01`4b574910 Void
+0x438 EtwDataSource : 0xffffb08e`dbfd9350 Void
+0x440 PageDirectoryPte : 0
+0x448 ImageFilePointer : 0xffffb08e`df330e50 _FILE_OBJECT
+0x450 ImageFileName : [15] "Calculator.exe"
+0x45f PriorityClass : 0x2 ''
+0x460 SecurityPort : (null)
+0x468 SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
+0x470 JobLinks : _LIST_ENTRY [ 0xffffb08e`dc341088 - 0xffffb08e`dc341088 ]
+0x480 HighestUserAddress : 0x00007fff`ffff0000 Void
+0x488 ThreadListHead : _LIST_ENTRY [ 0xffffb08e`dfe1e738 - 0xffffb08e`df2f16f8 ]
+0x498 ActiveThreads : 0x15
+0x49c ImagePathHash : 0x16dc128d
+0x4a0 DefaultHardErrorProcessing : 0
+0x4a4 LastThreadExitStatus : 0n0
+0x4a8 PrefetchTrace : _EX_FAST_REF
+0x4b0 LockedPagesList : (null)
+0x4b8 ReadOperationCount : _LARGE_INTEGER 0x0
+0x4c0 WriteOperationCount : _LARGE_INTEGER 0x0
+0x4c8 OtherOperationCount : _LARGE_INTEGER 0xec
+0x4d0 ReadTransferCount : _LARGE_INTEGER 0x0
+0x4d8 WriteTransferCount : _LARGE_INTEGER 0x0
+0x4e0 OtherTransferCount : _LARGE_INTEGER 0x55e
+0x4e8 CommitChargeLimit : 0
+0x4f0 CommitCharge : 0x1042
+0x4f8 CommitChargePeak : 0x1563
+0x500 Vm : _MMSUPPORT_FULL
+0x640 MmProcessLinks : _LIST_ENTRY [ 0xffffb08e`df3d1940 - 0xffffb08e`dfe0b9c0 ]
+0x650 ModifiedPageCount : 0x14fb
+0x654 ExitStatus : 0n259
+0x658 VadRoot : _RTL_AVL_TREE
+0x660 VadHint : 0xffffb08e`dcea7450 Void
+0x668 VadCount : 0xce
+0x670 VadPhysicalPages : 0
+0x678 VadPhysicalPagesLimit : 0
+0x680 AlpcContext : _ALPC_PROCESS_CONTEXT
+0x6a0 TimerResolutionLink : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x6b0 TimerResolutionStackRecord : (null)
+0x6b8 RequestedTimerResolution : 0
+0x6bc SmallestTimerResolution : 0
+0x6c0 ExitTime : _LARGE_INTEGER 0x0
+0x6c8 InvertedFunctionTable : (null)
+0x6d0 InvertedFunctionTableLock : _EX_PUSH_LOCK
+0x6d8 ActiveThreadsHighWatermark : 0x19
+0x6dc LargePrivateVadCount : 0
+0x6e0 ThreadListLock : _EX_PUSH_LOCK
+0x6e8 WnfContext : 0xffff9b01`478b39b0 Void
+0x6f0 ServerSilo : (null)
+0x6f8 SignatureLevel : 0x6 ''
+0x6f9 SectionSignatureLevel : 0x6 ''
+0x6fa Protection : _PS_PROTECTION
+0x6fb HangCount : 0y000
+0x6fb GhostCount : 0y000
+0x6fb PrefilterException : 0y0
+0x6fc Flags3 : 0x40c008
+0x6fc Minimal : 0y0
+0x6fc ReplacingPageRoot : 0y0
+0x6fc Crashed : 0y0
+0x6fc JobVadsAreTracked : 0y1
+0x6fc VadTrackingDisabled : 0y0
+0x6fc AuxiliaryProcess : 0y0
+0x6fc SubsystemProcess : 0y0
+0x6fc IndirectCpuSets : 0y0
+0x6fc RelinquishedCommit : 0y0
+0x6fc HighGraphicsPriority : 0y0
+0x6fc CommitFailLogged : 0y0
+0x6fc ReserveFailLogged : 0y0
+0x6fc SystemProcess : 0y0
+0x6fc HideImageBaseAddresses : 0y0
+0x6fc AddressPolicyFrozen : 0y1
+0x6fc ProcessFirstResume : 0y1
+0x6fc ForegroundExternal : 0y0
+0x6fc ForegroundSystem : 0y0
+0x6fc HighMemoryPriority : 0y0
+0x6fc EnableProcessSuspendResumeLogging : 0y0
+0x6fc EnableThreadSuspendResumeLogging : 0y0
+0x6fc SecurityDomainChanged : 0y0
+0x6fc SecurityFreezeComplete : 0y1
+0x6fc VmProcessorHost : 0y0
+0x700 DeviceAsid : 0n0
+0x708 SvmData : (null)
+0x710 SvmProcessLock : _EX_PUSH_LOCK
+0x718 SvmLock : 0
+0x720 SvmProcessDeviceListHead : _LIST_ENTRY [ 0xffffb08e`dc6a47a0 - 0xffffb08e`dc6a47a0 ]
+0x730 LastFreezeInterruptTime : 0x00000003`57d14ca7
+0x738 DiskCounters : 0xffffb08e`dc6a4900 _PROCESS_DISK_COUNTERS
+0x740 PicoContext : (null)
+0x748 EnclaveTable : (null)
+0x750 EnclaveNumber : 0
+0x758 EnclaveLock : _EX_PUSH_LOCK
+0x760 HighPriorityFaultsAllowed : 0
+0x768 EnergyContext : 0xffffb08e`dc6a4928 _PO_PROCESS_ENERGY_CONTEXT
+0x770 VmContext : (null)
+0x778 SequenceNumber : 0x192
+0x780 CreateInterruptTime : 0x00000003`55b2aa0c
+0x788 CreateUnbiasedInterruptTime : 0x00000003`55b2aa0c
+0x790 TotalUnbiasedFrozenTime : 0
+0x798 LastAppStateUpdateTime : 0x00000003`57d7b40d
+0x7a0 LastAppStateUptime : 0y0000000000000000000000000000000000010000111101010001010011011 (0x21ea29b)
+0x7a0 LastAppState : 0y001
+0x7a8 SharedCommitCharge : 0x902
+0x7b0 SharedCommitLock : _EX_PUSH_LOCK
+0x7b8 SharedCommitLinks : _LIST_ENTRY [ 0xffff9b01`4ab2aeb8 - 0xffff9b01`4b9d4398 ]
+0x7c8 AllowedCpuSets : 0
+0x7d0 DefaultCpuSets : 0
+0x7c8 AllowedCpuSetsIndirect : (null)
+0x7d0 DefaultCpuSetsIndirect : (null)
+0x7d8 DiskIoAttribution : (null)
+0x7e0 DxgProcess : 0xffff9b01`4b169a60 Void
+0x7e8 Win32KFilterSet : 0
+0x7f0 ProcessTimerDelay : _PS_INTERLOCKED_TIMER_DELAY_VALUES
+0x7f8 KTimerSets : 0x140
+0x7fc KTimer2Sets : 0
+0x800 ThreadTimerSets : 0x3d
+0x808 VirtualTimerListLock : 0
+0x810 VirtualTimerListHead : _LIST_ENTRY [ 0xffffb08e`ddffd7f0 - 0xffffb08e`ddffca70 ]
+0x820 WakeChannel : _WNF_STATE_NAME
+0x820 WakeInfo : _PS_PROCESS_WAKE_INFORMATION
+0x850 MitigationFlags : 0x38
+0x850 MitigationFlagsValues : <anonymous-tag>
+0x854 MitigationFlags2 : 0
+0x854 MitigationFlags2Values : <anonymous-tag>
+0x858 PartitionObject : 0xffffb08e`d7c9c340 Void
+0x860 SecurityDomain : 0x00000001`000000c6
+0x868 ParentSecurityDomain : 0
+0x870 CoverageSamplerContext : (null)
+0x878 MmHotPatchContext : (null)

可以看到是一个非常庞大的结构体,其中大部分的成员我们不用关心,只需要记住少数几个即可。

首先说说怎么遍历进程的EPROCESS结构,这里我写驱动用 IoGetCurrentProcess() 函数得到第一个进程的EPROCESS指针,然后遍历 _EPROCESS.ActiveProcessLinks 链表遍历所有进程。_EPROCESS.ActiveProcessLinks链表是 _LIST_ENTRY 结构体

1
2
3
4
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink; // 当前节点的后一个节点
struct _LIST_ENTRY *Blink; // 当前节点的前一个节点
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

_EPROCESS.ActiveProcessLinks是一个带头节点的循环链表,因此我们可以取 _EPROCESS.ActiveProcessLinks.Flink 来达到遍历链表的目的。但是这里有一个注意点,_EPROCESS.ActiveProcessLinks.Flink和*_EPROCESS.ActiveProcessLinks.Blink* 指向的是后一个和前一个进程的*_EPROCESS.ActiveProcessLinks*,我们在用这种方法遍历进程EPROCESS结构的时候要注意每取Next一次要将地址减 ActiveProcessLinks成员相对于EPROCESS基址的偏移才能得到EPROCESS基址,比如我这里就是

1
2
3
4
5
while(TRUE) {
// ...
pCurrentProcess = (PEPROCESS)(*(PULONG64)((ULONG64)pCurrentProcess + 0x2f0)-0x2f0); // ActiveProcessLinks
// ...
}

然后因为是要一直保护,因此我开了一条线程,并把线程设置为低实时模式,循环的遍历EPROCESS,每次遍历到受保护进程就清它的DebugPort标志位,然后因为是多线程,驱动卸载时一定要保证线程退出,比较好的做法是开一个锁,这里我偷懒就用了一个标志变量控制

代码实现

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
#include <ntddk.h>

PETHREAD pThreadObj = NULL;
BOOLEAN bTerminated = FALSE;
UCHAR szProcessName[16] = "Calculator.exe";

VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
VOID AntiDbgThread(PVOID pContext);

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
OBJECT_ATTRIBUTES ObjAddr = { 0 };
HANDLE ThreadHandle = 0;
NTSTATUS NtStatus = STATUS_SUCCESS;

KdPrint(("Driver Entry"));

pDriverObject->DriverUnload = DriverUnload;

InitializeObjectAttributes(&ObjAddr, NULL, OBJ_KERNEL_HANDLE, 0, NULL);

NtStatus = PsCreateSystemThread(&ThreadHandle, THREAD_ALL_ACCESS, &ObjAddr, NULL, NULL, AntiDbgThread, NULL);

if (NT_SUCCESS(NtStatus))
{
KdPrint(("Thread Created"));

NtStatus = ObReferenceObjectByHandle(ThreadHandle, THREAD_ALL_ACCESS, *PsThreadType, KernelMode, &pThreadObj, NULL);

ZwClose(ThreadHandle);

if (!NT_SUCCESS(NtStatus))
{
bTerminated = TRUE;
}
}

return NtStatus;
}

VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
bTerminated = TRUE;
KeWaitForSingleObject(pThreadObj, Executive, KernelMode, FALSE, NULL);

ObDereferenceObject(pThreadObj);
}

VOID AntiDbgThread(PVOID pContext)
{
PEPROCESS pCurrentProcess = NULL;
PEPROCESS pFirstProcess = NULL;
LARGE_INTEGER inteval;

inteval.QuadPart = -20000000;

KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY);


while (TRUE)
{
if (bTerminated)
{
break;
}

pCurrentProcess = IoGetCurrentProcess();
pFirstProcess = pCurrentProcess;


while ( TRUE ) // ImageFileName
{
if (strstr((PCHAR)((ULONG64)pCurrentProcess + 0x450), szProcessName) != NULL) {
// KdBreakPoint();
*(PULONG64)((ULONG64)pCurrentProcess + 0x420) = 0; // DebugPort
DbgPrint("Detach Calc\n");
}
pCurrentProcess = (PEPROCESS)(*(PULONG64)((ULONG64)pCurrentProcess + 0x2f0)-0x2f0); // ActiveProcessLinks.Flink - 0x2f0 得到下一个EPROCESS的基址

if (pCurrentProcess == pFirstProcess)
break;

}


KeDelayExecutionThread(KernelMode, FALSE, &inteval);
}
}

加载驱动后可以在windbg上看到成功清除了计算器的DebugPort成员


清除后发现调试器不响应任何调试操作了,也没有提示程序脱离,F7、F8、F9按去都没有反应了

最关键的问题当然是要放最后说,就是这个EPROCESS结构通用性的问题。最早我用的是网络上的EPROCESS结构题,然后发现不行各种蓝,后来没办法自己上windbg看了一眼,hjh,结构体偏移完全不一样,然后搜了一圈自己也换了些系统试了试发现甚至不同的小版本这个结构体偏移都是不一样的。对于这个问题我问了不少师傅给出的答案都是总结各个版本的EPROCESS结构并动态获取版本号判断偏移。我偷懒直接去 BlackBone库里抄了 BBInitDynamicData 函数,用里面的 EPROCESS.ObjectTable + 8 作为DebugPort的偏移,目前在各Windows10发行版中运行良好。若师傅们有更好的方法也欢迎与我交流。

参考资料

  1. https://blog.csdn.net/haodawei123/article/details/88608742

  2. https://blog.csdn.net/weixin_30455023/article/details/99064102

  3. https://pediy.com/kssd/pediy12/132095.html