Discover an AntiDebug feature: a newbie approach
This blogpost shows the way I understod where the anti debug check were implemented with a basic, I would say, tending to 0, knowledge on Windows Internals. I started from a problem: the target process I wanted to attach was protected by a kind of watchdog. Did not know if the watchdog was in kernel space or in user space. During this journey, I learnt something about Windows internals, x64dbg/titan engine internals and improved windbg usage.
Problem: Access DENIED to process
I wanted to debug a service running with elevated privilege, i.e. SYSTEM, but this seems to be impossible even using x32dbg run as SYSTEM user.
Running x32dbg with system privileges and attaching to the process results in an ACCESS DENIED error.
The PID to debug is 4056.
So, to understand where the debugger received the ACCESS DENIED, I started digging into the procedure used by the debugger to attach and debug a process.
First thing, I did, is to put a bp on ntOpenProcess(). I was pretty sure that ntOpenProcess() was the first involved routine.
In the following image is clear that the debugger under debug is opening the target process.
The ntOpenProcess() returns a valid handle, as shown in the image, this suggested that the process is correctly opened.
So, then I cross searched in titan engine and in the x64 debugger source code where this call happens and what there is after.
After some research I found that the failing function was DbgUiDebugActiveProcess_(IN HANDLE Process)), i.e. the systemcall NtDebugActiveProcess() returned ACCESS DENIED
This is a function exported by ntdll.dll and ends directly in kernel space without any intermediate. Moreover, checking the target binary statically seems that did not exists any anti debug trick implemented inside. </br>
` OpenProcess() returns no error + NtDebugActiveProcess() return ACCESS DENIED + No anti debug in target binary -> check most likely in kernel space `
Just to see if some of these functions is called and cause an access denied.
Test case
At this point, I focused my research on kernel space in order to check why that routine return ACCESS DENIED even though the process has been correctly opened.
If exists some anti debug trick in kernel space that would not allow to debug a process then it would deny any injection too.
So, to have a better test case I wrote a small snippet in C that open a process and inject a DLL.
HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, pId);
if (h) {
printf("[+] Process %d Opened handle no. %08x\n", pId, (unsigned int)h);
LPVOID LoadLibAddr = (LPVOID)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
printf("[+] LoadLibraryA address: %p\n", LoadLibAddr);
getchar();
LPVOID dereercomp = VirtualAllocEx(h, NULL, strlen(dllName)+1, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (dereercomp == NULL)
{
fprintf(stderr, "[-] Impossible allocate new memory in the target process %d\n", GetLastError());
return 1;
}
printf("[+] Alloced space on: %p\n", dereercomp);
if (!WriteProcessMemory(h, dereercomp, dllName, strlen(dllName), NULL)) {
fprintf(stderr, "[-] Impossible writing in process memory %d\n", GetLastError());
return 1;
}
else {
printf("[+] Process memory correctly written\n");
}
HANDLE asdc = CreateRemoteThread(h, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibAddr, dereercomp, 0, NULL);
printf("[+] Remote Thread Output: %d %d\n", asdc, GetLastError());
WaitForSingleObject(asdc, INFINITE);
VirtualFreeEx(h, dereercomp, strlen(dllName), MEM_RELEASE);
CloseHandle(asdc);
CloseHandle(h);
return 0;
}
Running the test case against the process pid from an elevated powershell results in ACCESS DENIED too on VirtualAllocEx() routine.
Windows Kernel Debugging: searching for the anti debug
At this point, I started to debug the windows kernel using windbg, so I decided, firstly to inspect a bit the target process.
kd> !process 0xfd8
Searching for Process with Cid == fd8
PROCESS ffffc20239807080
SessionId: 0 Cid: 0fd8 Peb: 00256000 ParentCid: 0270
DirBase: 1ce063000 ObjectTable: ffff89020ea49180 HandleCount: 357.
VadRoot ffffc2023704ee50 Vads 163 Clone 0 Private 1876. Modified 125. Locked 0.
DeviceMap ffff890205035ae0
Token ffff89020e225060
ElapsedTime 17:06:01.856
UserTime 00:00:00.312
KernelTime 00:00:00.187
QuotaPoolUsage[PagedPool] 628552
QuotaPoolUsage[NonPagedPool] 23512
Working Set Sizes (now,min,max) (56976, 50, 345) (227904KB, 200KB, 1380KB)
PeakWorkingSetSize 84807
VirtualSize 322 Mb
PeakVirtualSize 419 Mb
PageFaultCount 126060
MemoryPriority BACKGROUND
BasePriority 8
CommitCharge 11044
According to flags, BeingDebugged and NtGlobalFlag, contained in the process environment block data structure, seems that the process is not currently under debug of another process.
A great article on this topic is provided by CheckPoint Research
kd> .process /p ffffc20239807080; !peb 00256000
Implicit process is now ffffc202`39807080
.cache forcedecodeuser done
PEB at 0000000000256000
InheritedAddressSpace: No
ReadImageFileExecOptions: No
BeingDebugged: No
ImageBaseAddress: 0000000000400000
NtGlobalFlag: 0
NtGlobalFlag2: 0
Ldr 00007fff9f8ba4c0
Ldr.Initialized: Yes
Ldr.InInitializationOrderModuleList: 00000000005627a0 . 0000000000562ff0
Ldr.InLoadOrderModuleList: 0000000000562910 . 0000000000562fd0
Ldr.InMemoryOrderModuleList: 0000000000562920 . 0000000000562fe0
kd> dt _peb 0x0256000
win32k!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0 ''
+0x003 BitField : 0 ''
...
+0x0b8 NumberOfProcessors : 1
+0x0bc NtGlobalFlag : 0
+0x0c0 CriticalSectionTimeout : _LARGE_INTEGER 0xffffe86d`079b8000
+0x0c8 HeapSegmentReserve : 0x100000
+0x0d0 HeapSegmentCommit : 0x2000
+0x0d8 HeapDeCommitTotalFreeThreshold : 0x10000
+0x0e0 HeapDeCommitFreeBlockThreshold : 0x1000
+0x0e8 NumberOfHeaps : 2
+0x0ec MaximumNumberOfHeaps : 0x10
+0x0f0 ProcessHeaps : 0x00007fff`9f8b8d40 -> 0x00000000`00560000 Void
...
+0x7c4 NtGlobalFlag2 : 0
At this point, I checked the eprocess structure.
kd> dt ffffc20239807080 _eprocess
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x438 ProcessLock : _EX_PUSH_LOCK
+0x440 UniqueProcessId : 0x00000000`00000fd8 Void
+0x448 ActiveProcessLinks : _LIST_ENTRY [ 0xffffc202`39bc84c8 - 0xffffc202`370b54c8 ]
+0x458 RundownProtect : _EX_RUNDOWN_REF
+0x460 Flags2 : 0xd000
+0x464 Flags : 0x144d0c01
+0x464 CreateReported : 0y1
+0x464 NoDebugInherit : 0y0
+0x464 ProcessExiting : 0y0
+0x464 ProcessDelete : 0y0
+0x464 ManageExecutableMemoryWrites : 0y0
+0x464 VmDeleted : 0y0
+0x464 OutswapEnabled : 0y0
+0x464 Outswapped : 0y0
+0x464 FailFastOnCommitFail : 0y0
+0x464 Wow64VaSpace4Gb : 0y0
+0x464 AddressSpaceInitialized : 0y11
+0x464 SetTimerResolution : 0y0
+0x464 BreakOnTermination : 0y0
+0x464 DeprioritizeViews : 0y0
+0x464 WriteWatch : 0y0
+0x464 ProcessInSession : 0y1
+0x464 OverrideAddressSpace : 0y0
+0x464 HasAddressSpace : 0y1
+0x464 LaunchPrefetched : 0y1
+0x464 Background : 0y0
+0x464 VmTopDown : 0y0
+0x464 ImageNotifyDone : 0y1
+0x464 PdeUpdateNeeded : 0y0
+0x464 VdmAllowed : 0y0
+0x464 ProcessRundown : 0y0
+0x464 ProcessInserted : 0y1
+0x464 DefaultIoPriority : 0y010
+0x464 ProcessSelfDelete : 0y0
+0x464 SetTimerResolutionLink : 0y0
+0x468 CreateTime : _LARGE_INTEGER 0x01d879aa`94a2629d
...
+0x550 Peb : 0x00000000`00256000 _PEB
+0x558 Session : 0xffffd280`69c5b000 _MM_SESSION_SPACE
+0x560 Spare1 : (null)
+0x568 QuotaBlock : 0xfffff806`12053800 _EPROCESS_QUOTA_BLOCK
+0x570 ObjectTable : 0xffff8902`0ea49180 _HANDLE_TABLE
+0x578 DebugPort : (null)
+0x580 WoW64Process : 0xffffc202`350fed50 _EWOW64PROCESS
The DebugPort is null so seems there is no debug object linked to the target process.
At this point, I set a breakpoint on virtual alloc system call handler in kernel space when the handle is equal to the handle number used by the test case, i.e. 0xc4.
In this way, I could stop the OS when the VirtualAllocEx() called by the test case enter in kernel space.
` bp /w “@ecx == 0xc4” nt!NtAllocateVirtualMemory `
So, I traced every instruction until return of the nt!NtAllocateVirtualMemory() using the command: ta fffff806`11ab6940.
RAX at the end contain the value of the ACCESS DENIED error, i.e. 0x00000000c0000022.
Looking the trace from the end, has been identified where the return value is set.
nt!ObpReferenceObjectByHandleWithTag+0x4e8:
fffff806`119f61b8 8bc7 mov eax,edi
nt!ObpReferenceObjectByHandleWithTag+0x4ea:
fffff806`119f61ba e944fdffff jmp nt!ObpReferenceObjectByHandleWithTag+0x233 (fffff806`119f5f03)
nt!ObpReferenceObjectByHandleWithTag+0x233:
fffff806`119f5f03 4c8b742448 mov r14,qword ptr [rsp+48h]
nt!ObpReferenceObjectByHandleWithTag+0x238:
fffff806`119f5f08 488b6c2450 mov rbp,qword ptr [rsp+50h]
nt!ObpReferenceObjectByHandleWithTag+0x23d:
fffff806`119f5f0d 4883c458 add rsp,58h
nt!ObpReferenceObjectByHandleWithTag+0x241:
fffff806`119f5f11 415f pop r15
nt!ObpReferenceObjectByHandleWithTag+0x243:
fffff806`119f5f13 415d pop r13
nt!ObpReferenceObjectByHandleWithTag+0x245:
fffff806`119f5f15 415c pop r12
nt!ObpReferenceObjectByHandleWithTag+0x247:
fffff806`119f5f17 5f pop rdi
nt!ObpReferenceObjectByHandleWithTag+0x248:
fffff806`119f5f18 5e pop rsi
nt!ObpReferenceObjectByHandleWithTag+0x249:
fffff806`119f5f19 5b pop rbx
nt!ObpReferenceObjectByHandleWithTag+0x24a:
fffff806`119f5f1a c3 ret
nt!MiAllocateVirtualMemoryPrepare+0x4e3:
fffff806`11ab73d3 448bf0 mov r14d,eax
nt!MiAllocateVirtualMemoryPrepare+0x4e6:
fffff806`11ab73d6 85c0 test eax,eax
nt!MiAllocateVirtualMemoryPrepare+0x4e8:
fffff806`11ab73d8 0f880e841500 js nt!MiAllocateVirtualMemoryPrepare+0x1588fc (fffff806`11c0f7ec)
nt!MiAllocateVirtualMemoryPrepare+0x1588fc:
fffff806`11c0f7ec 488b442448 mov rax,qword ptr [rsp+48h]
nt!MiAllocateVirtualMemoryPrepare+0x158901:
fffff806`11c0f7f1 4885c0 test rax,rax
nt!MiAllocateVirtualMemoryPrepare+0x158904:
fffff806`11c0f7f4 740d je nt!MiAllocateVirtualMemoryPrepare+0x158913 (fffff806`11c0f803)
nt!MiAllocateVirtualMemoryPrepare+0x158913:
fffff806`11c0f803 418bc6 mov eax,r14d
nt!MiAllocateVirtualMemoryPrepare+0x158916:
fffff806`11c0f806 e9327aeaff jmp nt!MiAllocateVirtualMemoryPrepare+0x34d (fffff806`11ab723d)
nt!MiAllocateVirtualMemoryPrepare+0x34d:
fffff806`11ab723d 488b9c24b8000000 mov rbx,qword ptr [rsp+0B8h]
nt!MiAllocateVirtualMemoryPrepare+0x355:
fffff806`11ab7245 4883c460 add rsp,60h
nt!MiAllocateVirtualMemoryPrepare+0x359:
fffff806`11ab7249 415f pop r15
nt!MiAllocateVirtualMemoryPrepare+0x35b:
fffff806`11ab724b 415e pop r14
nt!MiAllocateVirtualMemoryPrepare+0x35d:
fffff806`11ab724d 415d pop r13
nt!MiAllocateVirtualMemoryPrepare+0x35f:
fffff806`11ab724f 415c pop r12
nt!MiAllocateVirtualMemoryPrepare+0x361:
fffff806`11ab7251 5f pop rdi
nt!MiAllocateVirtualMemoryPrepare+0x362:
fffff806`11ab7252 5e pop rsi
nt!MiAllocateVirtualMemoryPrepare+0x363:
fffff806`11ab7253 5d pop rbp
nt!MiAllocateVirtualMemoryPrepare+0x364:
fffff806`11ab7254 c3 ret
nt!NtAllocateVirtualMemory+0x16a:
fffff806`11ab688a 8bd8 mov ebx,eax
nt!NtAllocateVirtualMemory+0x16c:
fffff806`11ab688c 89442474 mov dword ptr [rsp+74h],eax
nt!NtAllocateVirtualMemory+0x170:
fffff806`11ab6890 85c0 test eax,eax
nt!NtAllocateVirtualMemory+0x172:
fffff806`11ab6892 7861 js nt!NtAllocateVirtualMemory+0x1d5 (fffff806`11ab68f5)
nt!NtAllocateVirtualMemory+0x1d5:
fffff806`11ab68f5 85db test ebx,ebx
nt!NtAllocateVirtualMemory+0x1d7:
fffff806`11ab68f7 7855 js nt!NtAllocateVirtualMemory+0x22e (fffff806`11ab694e)
nt!NtAllocateVirtualMemory+0x22e:
fffff806`11ab694e 4883bc240001000000 cmp qword ptr [rsp+100h],0
nt!NtAllocateVirtualMemory+0x237:
fffff806`11ab6957 0f84578d1500 je nt!NtAllocateVirtualMemory+0x158f94 (fffff806`11c0f6b4)
nt!NtAllocateVirtualMemory+0x158f94:
fffff806`11c0f6b4 ff05eeee4300 inc dword ptr [nt!MiState+0x1f28 (fffff806`1204e5a8)]
nt!NtAllocateVirtualMemory+0x158f9a:
fffff806`11c0f6ba e93a72eaff jmp nt!NtAllocateVirtualMemory+0x1d9 (fffff806`11ab68f9)
nt!NtAllocateVirtualMemory+0x1d9:
fffff806`11ab68f9 4983fe02 cmp r14,2
nt!NtAllocateVirtualMemory+0x1dd:
fffff806`11ab68fd 0f83bc8d1500 jae nt!NtAllocateVirtualMemory+0x158f9f (fffff806`11c0f6bf)
nt!NtAllocateVirtualMemory+0x1e3:
fffff806`11ab6903 488b8c2498000000 mov rcx,qword ptr [rsp+98h]
nt!NtAllocateVirtualMemory+0x1eb:
fffff806`11ab690b 4885c9 test rcx,rcx
nt!NtAllocateVirtualMemory+0x1ee:
fffff806`11ab690e 7532 jne nt!NtAllocateVirtualMemory+0x222 (fffff806`11ab6942)
nt!NtAllocateVirtualMemory+0x1f0:
fffff806`11ab6910 85db test ebx,ebx
nt!NtAllocateVirtualMemory+0x1f2:
fffff806`11ab6912 780e js nt!NtAllocateVirtualMemory+0x202 (fffff806`11ab6922)
nt!NtAllocateVirtualMemory+0x202:
fffff806`11ab6922 8bc3 mov eax,ebx
nt!NtAllocateVirtualMemory+0x204:
fffff806`11ab6924 4c8d9c2480010000 lea r11,[rsp+180h]
nt!NtAllocateVirtualMemory+0x20c:
fffff806`11ab692c 498b5b38 mov rbx,qword ptr [r11+38h]
nt!NtAllocateVirtualMemory+0x210:
fffff806`11ab6930 498b7348 mov rsi,qword ptr [r11+48h]
nt!NtAllocateVirtualMemory+0x214:
fffff806`11ab6934 498be3 mov rsp,r11
nt!NtAllocateVirtualMemory+0x217:
fffff806`11ab6937 415f pop r15
nt!NtAllocateVirtualMemory+0x219:
fffff806`11ab6939 415e pop r14
nt!NtAllocateVirtualMemory+0x21b:
fffff806`11ab693b 415d pop r13
nt!NtAllocateVirtualMemory+0x21d:
fffff806`11ab693d 415c pop r12
nt!NtAllocateVirtualMemory+0x21f:
fffff806`11ab693f 5f pop rdi
nt!NtAllocateVirtualMemory+0x220:
fffff806`11ab6940 c3 ret
Basically, edi is set into eax at nt!ObpReferenceObjectByHandleWithTag+0x4e8 and even though there are many register movements at the end nt!NtAllocateVirtualMemory+0x220 has in RAX the value contained by edi at nt!ObpReferenceObjectByHandleWithTag+0x4e8.
Looking further back, it’s clear that edi is set to the ACCESS DENIED value.
fffff806`119f5e99 0f85ee020000 jne nt!ObpReferenceObjectByHandleWithTag+0x4bd (fffff806`119f618d)
nt!ObpReferenceObjectByHandleWithTag+0x4bd:
fffff806`119f618d bf220000c0 mov edi,0C0000022h
nt!ObpReferenceObjectByHandleWithTag+0x4c2:
fffff806`119f6192 8b9424b0000000 mov edx,dword ptr [rsp+0B0h]
nt!ObpReferenceObjectByHandleWithTag+0x4c9:
fffff806`119f6199 488d4b30 lea rcx,[rbx+30h]
nt!ObpReferenceObjectByHandleWithTag+0x4cd:
fffff806`119f619d e8ee20c1ff call nt!ObfDereferenceObjectWithTag (fffff806`11608290)
nt!ObpReferenceObjectByHandleWithTag+0x4d2:
fffff806`119f61a2 80bc24b800000000 cmp byte ptr [rsp+0B8h],0
nt!ObpReferenceObjectByHandleWithTag+0x4da:
fffff806`119f61aa 0f858bb61e00 jne nt!ObpReferenceObjectByHandleWithTag+0x1ebb6b (fffff806`11be183b)
nt!ObpReferenceObjectByHandleWithTag+0x4e0:
fffff806`119f61b0 498bcf mov rcx,r15
nt!ObpReferenceObjectByHandleWithTag+0x4e3:
fffff806`119f61b3 e8584ec1ff call nt!KeLeaveCriticalRegionThread (fffff806`1160b010)
nt!ObpReferenceObjectByHandleWithTag+0x4e8:
fffff806`119f61b8 8bc7 mov eax,edi
Microsoft says that RDI register should be preserved by the callee and indeed it is through the calls to call nt!ObfDereferenceObjectWithTag and call nt!KeLeaveCriticalRegionThread.
RDI, is preserved in nt!ObfDereferenceObjectWithTag, even if it is never touched, pushing it on the stack and popping it at the end.
nt!ObfDereferenceObjectWithTag:
fffff806`11608290 48895c2408 mov qword ptr [rsp+8],rbx
nt!ObfDereferenceObjectWithTag+0x5:
fffff806`11608295 4889742410 mov qword ptr [rsp+10h],rsi
nt!ObfDereferenceObjectWithTag+0xa:
fffff806`1160829a 57 push rdi
nt!ObfDereferenceObjectWithTag+0xb:
fffff806`1160829b 4883ec30 sub rsp,30h
nt!ObfDereferenceObjectWithTag+0xf:
fffff806`1160829f 833d6a2daf0000 cmp dword ptr [nt!ObpTraceFlags (fffff806`120fb010)],0
nt!ObfDereferenceObjectWithTag+0x16:
fffff806`116082a6 488bf1 mov rsi,rcx
nt!ObfDereferenceObjectWithTag+0x19:
fffff806`116082a9 0f8525f22000 jne nt!ObfDereferenceObjectWithTag+0x20f244 (fffff806`118174d4)
nt!ObfDereferenceObjectWithTag+0x1f:
fffff806`116082af 48c7c3ffffffff mov rbx,0FFFFFFFFFFFFFFFFh
nt!ObfDereferenceObjectWithTag+0x26:
fffff806`116082b6 f0480fc15ed0 lock xadd qword ptr [rsi-30h],rbx
nt!ObfDereferenceObjectWithTag+0x2c:
fffff806`116082bc 4883eb01 sub rbx,1
nt!ObfDereferenceObjectWithTag+0x30:
fffff806`116082c0 7e14 jle nt!ObfDereferenceObjectWithTag+0x46 (fffff806`116082d6)
nt!ObfDereferenceObjectWithTag+0x32:
fffff806`116082c2 488bc3 mov rax,rbx
nt!ObfDereferenceObjectWithTag+0x35:
fffff806`116082c5 488b5c2440 mov rbx,qword ptr [rsp+40h]
nt!ObfDereferenceObjectWithTag+0x3a:
fffff806`116082ca 488b742448 mov rsi,qword ptr [rsp+48h]
nt!ObfDereferenceObjectWithTag+0x3f:
fffff806`116082cf 4883c430 add rsp,30h
nt!ObfDereferenceObjectWithTag+0x43:
fffff806`116082d3 5f pop rdi
nt!ObfDereferenceObjectWithTag+0x44:
fffff806`116082d4 c3 ret
In the nt!KeLeaveCriticalRegionThread(), RDI is preserved since it is never touched.
nt!KeLeaveCriticalRegionThread:
fffff806`1160b010 4883ec28 sub rsp,28h
nt!KeLeaveCriticalRegionThread+0x4:
fffff806`1160b014 668381e401000001 add word ptr [rcx+1E4h],1
nt!KeLeaveCriticalRegionThread+0xc:
fffff806`1160b01c 750c jne nt!KeLeaveCriticalRegionThread+0x1a (fffff806`1160b02a)
nt!KeLeaveCriticalRegionThread+0xe:
fffff806`1160b01e 488d8198000000 lea rax,[rcx+98h]
nt!KeLeaveCriticalRegionThread+0x15:
fffff806`1160b025 483900 cmp qword ptr [rax],rax
nt!KeLeaveCriticalRegionThread+0x18:
fffff806`1160b028 7506 jne nt!KeLeaveCriticalRegionThread+0x20 (fffff806`1160b030)
nt!KeLeaveCriticalRegionThread+0x1a:
fffff806`1160b02a 4883c428 add rsp,28h
nt!KeLeaveCriticalRegionThread+0x1e:
fffff806`1160b02e c3 ret
At this point, it’s required to understand why nt!ObpReferenceObjectByHandleWithTag+0x4bd is executed!
nt!ExpLookupHandleTableEntry+0x30:
fffff806`119f62f0 c3 ret
nt!ObpReferenceObjectByHandleWithTag+0xef:
fffff806`119f5dbf 488bf8 mov rdi,rax
nt!ObpReferenceObjectByHandleWithTag+0xf2:
fffff806`119f5dc2 4885c0 test rax,rax
nt!ObpReferenceObjectByHandleWithTag+0xf5:
fffff806`119f5dc5 0f840b040000 je nt!ObpReferenceObjectByHandleWithTag+0x506 (fffff806`119f61d6)
nt!ObpReferenceObjectByHandleWithTag+0xfb:
fffff806`119f5dcb 0f0d08 prefetchw [rax]
nt!ObpReferenceObjectByHandleWithTag+0xfe:
fffff806`119f5dce 488b08 mov rcx,qword ptr [rax]
nt!ObpReferenceObjectByHandleWithTag+0x101:
fffff806`119f5dd1 488b6808 mov rbp,qword ptr [rax+8]
nt!ObpReferenceObjectByHandleWithTag+0x105:
fffff806`119f5dd5 48896c2438 mov qword ptr [rsp+38h],rbp
nt!ObpReferenceObjectByHandleWithTag+0x10a:
fffff806`119f5dda 48894c2430 mov qword ptr [rsp+30h],rcx
nt!ObpReferenceObjectByHandleWithTag+0x10f:
fffff806`119f5ddf 4c8b742430 mov r14,qword ptr [rsp+30h]
nt!ObpReferenceObjectByHandleWithTag+0x114:
fffff806`119f5de4 49f7c6feff0100 test r14,1FFFEh
nt!ObpReferenceObjectByHandleWithTag+0x11b:
fffff806`119f5deb 0f84ff010000 je nt!ObpReferenceObjectByHandleWithTag+0x320 (fffff806`119f5ff0)
nt!ObpReferenceObjectByHandleWithTag+0x121:
fffff806`119f5df1 41f6c601 test r14b,1
nt!ObpReferenceObjectByHandleWithTag+0x125:
fffff806`119f5df5 0f8451030000 je nt!ObpReferenceObjectByHandleWithTag+0x47c (fffff806`119f614c)
nt!ObpReferenceObjectByHandleWithTag+0x12b:
fffff806`119f5dfb 498d5efe lea rbx,[r14-2]
nt!ObpReferenceObjectByHandleWithTag+0x12f:
fffff806`119f5dff 488bcd mov rcx,rbp
nt!ObpReferenceObjectByHandleWithTag+0x132:
fffff806`119f5e02 498bc6 mov rax,r14
nt!ObpReferenceObjectByHandleWithTag+0x135:
fffff806`119f5e05 488bd5 mov rdx,rbp
nt!ObpReferenceObjectByHandleWithTag+0x138:
fffff806`119f5e08 f0480fc70f lock cmpxchg16b oword ptr [rdi]
nt!ObpReferenceObjectByHandleWithTag+0x13d:
fffff806`119f5e0d 4c8bf0 mov r14,rax
nt!ObpReferenceObjectByHandleWithTag+0x140:
fffff806`119f5e10 4889442430 mov qword ptr [rsp+30h],rax
nt!ObpReferenceObjectByHandleWithTag+0x145:
fffff806`119f5e15 488bea mov rbp,rdx
nt!ObpReferenceObjectByHandleWithTag+0x148:
fffff806`119f5e18 4889542438 mov qword ptr [rsp+38h],rdx
nt!ObpReferenceObjectByHandleWithTag+0x14d:
fffff806`119f5e1d 0f8558030000 jne nt!ObpReferenceObjectByHandleWithTag+0x4ab (fffff806`119f617b)
nt!ObpReferenceObjectByHandleWithTag+0x153:
fffff806`119f5e23 488bd8 mov rbx,rax
nt!ObpReferenceObjectByHandleWithTag+0x156:
fffff806`119f5e26 48d1eb shr rbx,1
nt!ObpReferenceObjectByHandleWithTag+0x159:
fffff806`119f5e29 6683fb10 cmp bx,10h
nt!ObpReferenceObjectByHandleWithTag+0x15d:
fffff806`119f5e2d 0f84e4030000 je nt!ObpReferenceObjectByHandleWithTag+0x547 (fffff806`119f6217)
nt!ObpReferenceObjectByHandleWithTag+0x163:
fffff806`119f5e33 488bd8 mov rbx,rax
nt!ObpReferenceObjectByHandleWithTag+0x166:
fffff806`119f5e36 48c1fb10 sar rbx,10h
nt!ObpReferenceObjectByHandleWithTag+0x16a:
fffff806`119f5e3a 4883e3f0 and rbx,0FFFFFFFFFFFFFFF0h
nt!ObpReferenceObjectByHandleWithTag+0x16e:
fffff806`119f5e3e 833dcb51700000 cmp dword ptr [nt!ObpTraceFlags (fffff806`120fb010)],0
nt!ObpReferenceObjectByHandleWithTag+0x175:
fffff806`119f5e45 0f852eb91e00 jne nt!ObpReferenceObjectByHandleWithTag+0x1ebaa9 (fffff806`11be1779)
nt!ObpReferenceObjectByHandleWithTag+0x17b:
fffff806`119f5e4b 488b9424a0000000 mov rdx,qword ptr [rsp+0A0h]
nt!ObpReferenceObjectByHandleWithTag+0x183:
fffff806`119f5e53 4c8d0da6a1a0ff lea r9,[nt!VrpRegistryString <PERF> (nt+0x0) (fffff806`11400000)]
nt!ObpReferenceObjectByHandleWithTag+0x18a:
fffff806`119f5e5a 488bc3 mov rax,rbx
nt!ObpReferenceObjectByHandleWithTag+0x18d:
fffff806`119f5e5d 48c1e808 shr rax,8
nt!ObpReferenceObjectByHandleWithTag+0x191:
fffff806`119f5e61 324318 xor al,byte ptr [rbx+18h]
nt!ObpReferenceObjectByHandleWithTag+0x194:
fffff806`119f5e64 3205c2687000 xor al,byte ptr [nt!ObHeaderCookie (fffff806`120fc72c)]
nt!ObpReferenceObjectByHandleWithTag+0x19a:
fffff806`119f5e6a 4885d2 test rdx,rdx
nt!ObpReferenceObjectByHandleWithTag+0x19d:
fffff806`119f5e6d 0f8423010000 je nt!ObpReferenceObjectByHandleWithTag+0x2c6 (fffff806`119f5f96)
nt!ObpReferenceObjectByHandleWithTag+0x1a3:
fffff806`119f5e73 384228 cmp byte ptr [rdx+28h],al
nt!ObpReferenceObjectByHandleWithTag+0x1a6:
fffff806`119f5e76 0f851a010000 jne nt!ObpReferenceObjectByHandleWithTag+0x2c6 (fffff806`119f5f96)
nt!ObpReferenceObjectByHandleWithTag+0x1ac:
fffff806`119f5e7c 8b8c2498000000 mov ecx,dword ptr [rsp+98h]
nt!ObpReferenceObjectByHandleWithTag+0x1b3:
fffff806`119f5e83 81e5ffffff01 and ebp,1FFFFFFh
nt!ObpReferenceObjectByHandleWithTag+0x1b9:
fffff806`119f5e89 80bc24a800000000 cmp byte ptr [rsp+0A8h],0
nt!ObpReferenceObjectByHandleWithTag+0x1c1:
fffff806`119f5e91 7414 je nt!ObpReferenceObjectByHandleWithTag+0x1d7 (fffff806`119f5ea7)
nt!ObpReferenceObjectByHandleWithTag+0x1c3:
fffff806`119f5e93 8bc5 mov eax,ebp
nt!ObpReferenceObjectByHandleWithTag+0x1c5:
fffff806`119f5e95 f7d0 not eax
nt!ObpReferenceObjectByHandleWithTag+0x1c7:
fffff806`119f5e97 85c1 test ecx,eax
nt!ObpReferenceObjectByHandleWithTag+0x1c9:
fffff806`119f5e99 0f85ee020000 jne nt!ObpReferenceObjectByHandleWithTag+0x4bd (fffff806`119f618d)
nt!ObpReferenceObjectByHandleWithTag+0x4bd:
fffff806`119f618d bf220000c0 mov edi,0C0000022h
So the code arrive to nt!ObpReferenceObjectByHandleWithTag+0x4bd because ` ecx != eax ,
eax = not(ebp & 0x1FFFFFF) ` and ` rbp = [rax+8] where rax = nt!ExpLookupHandleTableEntry() `.
ecx is read from the stack at [rsp+98h].
ExpLookupHandleTableEntry() returns the table entry from the handle number, in our case it would be 0xc4, and the handle table of the process that is calling VirtualAlloc().
At this, point let’s put a breakpoint on ExpLookupHandleTableEntry() return.
RAX = 0xffff89020dd77310 and *RAX+8 = 0x1ff7d6. Analyzing the handle entry table and the handle, i.e. index 0xc4, something strange appear.
kd> dt nt!_HANDLE_TABLE_ENTRY 0xffff89020dd77310
+0x000 VolatileLowValue : 0n-4466944656595156999
+0x000 LowValue : 0n-4466944656595156999
+0x000 InfoTable : 0xc2023980`7050fff9 _HANDLE_TABLE_ENTRY_INFO
+0x008 HighValue : 0n2095062
+0x008 NextFreeHandleEntry : 0x00000000`001ff7d6 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x000 RefCountField : 0n-4466944656595156999
+0x000 Unlocked : 0y1
+0x000 RefCnt : 0y0111111111111100 (0x7ffc)
+0x000 Attributes : 0y000
+0x000 ObjectPointerBits : 0y11000010000000100011100110000000011100000101 (0xc2023980705)
+0x008 GrantedAccessBits : 0y0000111111111011111010110 (0x1ff7d6)
+0x008 NoRightsUpgrade : 0y0
+0x008 Spare1 : 0y000000 (0)
+0x00c Spare2 : 0
kd> !handle 0xc4
PROCESS ffffc2023726c080
SessionId: 1 Cid: 142c Peb: 00839000 ParentCid: 113c
DirBase: 1b1d57000 ObjectTable: ffff89020ea4cdc0 HandleCount: 55.
Image: ShellCodeInjector.exe
Handle table at ffff89020ea4cdc0 with 55 entries in use
00c4: Object: ffffc20239807080 GrantedAccess: 001ff7d6 (Protected) (Audit) Entry: ffff89020dd77310
Object: ffffc20239807080 Type: (ffffc202314ac380) Process
ObjectHeader: ffffc20239807050 (new version)
HandleCount: 11 PointerCount: 354527
HighValue == 2095062 == 0x1ff7d6 at ffff89020dd77310+8
that is the granted access mask to the object, i.e. the target process.
The access mask is incoherent with the one used in the test case, i.e. PROCESS_ALL_ACCESS == 0x01fffff.
This could lead to access denied on virtual alloc call.
The access mask means, that PROCESS_TERMINATE | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_SUSPEND_RESUME are not granted so it’s impossible to allocate memory in the process!
We have at least two ways to bypass this:
- Trying to bypass this resetting granted access in the object handle.
- Understand where the access mask is modified and disable that feature.
The fastest way doing this is to intercept where the mask is changed in order to disable this once and not every time I have to attach to the process.
So, I put a breakpoint on the open process kernel implementation, i.e. nt!PsOpenProcess.
bp nt!PsOpenProcess ".if ( poi(@r9) != 0xfd8) {gc}"
Let’s track the code flow until the end.
nt!ObpPreInterceptHandleCreate+0xa5:
fffff806`11a87ea5 e886fdffff call nt!ObpCallPreOperationCallbacks (fffff806`11a87c30)
So likely, a callback has been registered for process handle operations via ObRegisterCallbacks().
nt!ObpCallPreOperationCallbacks+0x103:
fffff806`11a87d33 488b4908 mov rcx,qword ptr [rcx+8]
nt!ObpCallPreOperationCallbacks+0x107:
fffff806`11a87d37 e81480d7ff call nt!guard_dispatch_icall (fffff806`117ffd50)
nt!guard_dispatch_icall:
fffff806`117ffd50 4c8b1d111ba000 mov r11,qword ptr [nt!guard_icall_bitmap (fffff806`12201868)]
nt!guard_dispatch_icall+0x7:
fffff806`117ffd57 4885c0 test rax,rax
nt!guard_dispatch_icall+0xa:
fffff806`117ffd5a 0f8d7a000000 jge nt!guard_dispatch_icall+0x8a (fffff806`117ffdda)
nt!guard_dispatch_icall+0x10:
fffff806`117ffd60 4d85db test r11,r11
nt!guard_dispatch_icall+0x13:
fffff806`117ffd63 741c je nt!guard_dispatch_icall+0x31 (fffff806`117ffd81)
nt!guard_dispatch_icall+0x31:
fffff806`117ffd81 4c8b1dc0ce8f00 mov r11,qword ptr [nt!retpoline_image_bitmap (fffff806`120fcc48)]
nt!guard_dispatch_icall+0x38:
fffff806`117ffd88 4c8bd0 mov r10,rax
nt!guard_dispatch_icall+0x3b:
fffff806`117ffd8b 4d85db test r11,r11
nt!guard_dispatch_icall+0x3e:
fffff806`117ffd8e 742e je nt!guard_dispatch_icall+0x6e (fffff806`117ffdbe)
nt!guard_dispatch_icall+0x6e:
fffff806`117ffdbe 0faee8 lfence
nt!guard_dispatch_icall+0x71:
fffff806`117ffdc1 ffe0 jmp rax
The function called that implements the antidebug is quite simple and basically get the process opened pid against a list of pids to check. Indeed the anti debug module obtains the opened process pid via PsGetProcessID()
nt!PsGetProcessId:
fffff806`1166ab30 mov rax,qword ptr [rcx+440h]
nt!PsGetProcessId+0x7:
fffff806`1166ab37 ret
Check it against a list of pids contained by the anti debug module self.
test rax,rax
je fffff806`17481093
cmp rax,0FFFFFFFFFFFFFFFFh
je fffff806`17481093
lea rbx,[fffff806`174969da0]
cmp rax,qword ptr [rbx]
If the process pid opened is in the list then the access mask is checked to have the PROCESS_SUSPEND_RESUME bit enabled if so it is zeroed.
Disable PROCESS_SUSPEND_RESUME permission
bt dword ptr [rdx+4], 0Bh
jae FFFFF80617481081
btr dword ptr [rdx], 0Bh
More over the access mask is computed doing multiple and operation that disable other things like:
Disable PROCESS_TERMINATE
and dword ptr [rdx], 0FFFFFFFEh
...
Disable PROCESS_VM_OPERATION
dword ptr [rdx], 0FFFFFFF7h
...
Disable PROCESS_VM_WRITE
dword ptr [rdx], 0FFFFFFDFh
At this point, I just changed the list of the pids in order to not enter in the code block that alters the access mask, ed fffff806`174969da0 0. Finally, I bypassed the anti debug trick.
Finally, a consideration is required: I did a mess just to found that the handle’s rights were different by the one I asked for.