TitanHide/TitanHideTest/main.cpp
Duncan Ogilvie 1731ac0ae9
Some checks failed
Visual Studio / build (push) Has been cancelled
Bound ObjectTypes traversal to native output
2026-07-18 15:36:03 +02:00

982 lines
37 KiB
C++

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <Subauth.h>
#include "..\TitanHide\TitanHide.h"
//Thanks to:
//http://www.codeproject.com/Articles/30815/An-Anti-Reverse-Engineering-Guide
//http://pferrie.host22.com/papers/antidebug.pdf
//http://resources.infosecinstitute.com/anti-debugging-detecting-system-debugger/
bool CheckProcessDebugFlags()
{
// Much easier in ASM but C/C++ looks so much better
typedef int (WINAPI * pNtQueryInformationProcess)
(HANDLE, UINT, PVOID, ULONG, PULONG);
DWORD NoDebugInherit = 0;
int Status;
// Get NtQueryInformationProcess
pNtQueryInformationProcess NtQIP = (pNtQueryInformationProcess)
GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")),
"NtQueryInformationProcess");
Status = NtQIP(GetCurrentProcess(),
0x1f, // ProcessDebugFlags
&NoDebugInherit, sizeof(NoDebugInherit), NULL);
if(Status != 0x00000000)
{
printf("NtQueryInformationProcess failed with %X\n", Status);
return false;
}
if(NoDebugInherit == FALSE)
return true;
else
return false;
}
bool CheckProcessDebugPort()
{
// Much easier in ASM but C/C++ looks so much better
typedef int (WINAPI * pNtQueryInformationProcess)
(HANDLE, UINT, PVOID, ULONG, PULONG);
DWORD_PTR DebugPort = 0;
ULONG ReturnSize = 0;
int Status;
// Get NtQueryInformationProcess
pNtQueryInformationProcess NtQIP = (pNtQueryInformationProcess)
GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")),
"NtQueryInformationProcess");
Status = NtQIP(GetCurrentProcess(),
0x7, // ProcessDebugPort
&DebugPort, sizeof(DebugPort), &ReturnSize);
if(Status != 0x00000000)
{
printf("NtQueryInformationProcess failed with %X, %u\n", Status, ReturnSize);
return false;
}
if(DebugPort)
return true;
else
return false;
}
bool CheckProcessDebugObjectHandle()
{
typedef NTSTATUS(NTAPI * pNtQueryInformationProcess)
(HANDLE, UINT, PVOID, ULONG, PULONG);
const NTSTATUS StatusInfoLengthMismatch = (NTSTATUS)0xC0000004L;
const NTSTATUS StatusAccessViolation = (NTSTATUS)0xC0000005L;
const NTSTATUS StatusAccessDenied = (NTSTATUS)0xC0000022L;
const NTSTATUS StatusPortNotSet = (NTSTATUS)0xC0000353L;
const NTSTATUS StatusDatatypeMisalignment = (NTSTATUS)0x80000002L;
const ULONG SentinelReturnLength = 0xB6B6B6B6;
#ifdef _WIN64
const ULONG UnwrittenReturnLength = SentinelReturnLength;
#else
// The WOW64 thunk leaves this value when the native call does not write
// ReturnLength.
const ULONG UnwrittenReturnLength = (ULONG)-(LONG)sizeof(ULONG);
#endif
const ULONG_PTR SentinelHandle = (ULONG_PTR)-1;
pNtQueryInformationProcess NtQIP = (pNtQueryInformationProcess)
GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")),
"NtQueryInformationProcess");
if(NtQIP == nullptr)
return false;
bool Detected = false;
ULONG_PTR DebugHandle = SentinelHandle;
ULONG ReturnSize = SentinelReturnLength;
// Length validation happens before all handle and buffer validation. It does
// not write either output, including ReturnLength.
const ULONG InvalidLengths[] = { sizeof(DebugHandle) - 1, sizeof(DebugHandle) + 1 };
for(ULONG Length : InvalidLengths)
{
DebugHandle = SentinelHandle;
ReturnSize = SentinelReturnLength;
NTSTATUS Status = NtQIP(GetCurrentProcess(),
30, // ProcessDebugObjectHandle
&DebugHandle,
Length,
&ReturnSize);
if(Status != StatusInfoLengthMismatch ||
DebugHandle != SentinelHandle ||
ReturnSize != UnwrittenReturnLength)
{
printf("ProcessDebugObjectHandle length contract mismatch: %08X, %p, %08X, %u\n",
Status, (PVOID)DebugHandle, ReturnSize, Length);
Detected = true;
}
}
// A null output with the exact length is probed after process-handle access.
ReturnSize = SentinelReturnLength;
NTSTATUS Status = NtQIP(GetCurrentProcess(),
30,
nullptr,
sizeof(DebugHandle),
&ReturnSize);
if(Status != StatusAccessViolation || ReturnSize != UnwrittenReturnLength)
{
printf("ProcessDebugObjectHandle null-buffer contract mismatch: %08X, %08X\n", Status, ReturnSize);
Detected = true;
}
#ifdef _WIN64
__declspec(align(8)) unsigned char Output[sizeof(HANDLE) + 8];
HANDLE LimitedProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
FALSE,
GetCurrentProcessId());
if(LimitedProcess != nullptr)
{
memset(Output, 0xA5, sizeof(Output));
ReturnSize = SentinelReturnLength;
Status = NtQIP(LimitedProcess,
30,
Output + 1,
sizeof(HANDLE),
&ReturnSize);
if(Status != StatusDatatypeMisalignment || ReturnSize != SentinelReturnLength)
{
printf("ProcessDebugObjectHandle validation-order mismatch: %08X\n", Status);
Detected = true;
}
memset(Output, 0xA5, sizeof(Output));
ReturnSize = SentinelReturnLength;
Status = NtQIP(LimitedProcess,
30,
Output,
sizeof(HANDLE),
&ReturnSize);
if(Status != StatusAccessDenied || ReturnSize != SentinelReturnLength)
{
printf("ProcessDebugObjectHandle access contract mismatch: %08X\n", Status);
Detected = true;
}
CloseHandle(LimitedProcess);
}
// Native x64 requires four-byte rather than pointer-size alignment.
memset(Output, 0xA5, sizeof(Output));
ReturnSize = SentinelReturnLength;
Status = NtQIP(GetCurrentProcess(),
30,
Output + 4,
sizeof(HANDLE),
&ReturnSize);
ULONG_PTR UnalignedHandle = SentinelHandle;
memcpy(&UnalignedHandle, Output + 4, sizeof(UnalignedHandle));
if(Status == STATUS_SUCCESS && UnalignedHandle != 0 && UnalignedHandle != SentinelHandle)
CloseHandle((HANDLE)UnalignedHandle);
if(Status != StatusPortNotSet || UnalignedHandle != 0 || ReturnSize != sizeof(HANDLE))
{
printf("ProcessDebugObjectHandle alignment contract mismatch: %08X\n", Status);
Detected = true;
}
#endif
DebugHandle = SentinelHandle;
ReturnSize = SentinelReturnLength;
Status = NtQIP(GetCurrentProcess(),
30,
&DebugHandle,
sizeof(DebugHandle),
&ReturnSize);
if(Status == STATUS_SUCCESS)
{
if(DebugHandle != 0 && DebugHandle != SentinelHandle)
CloseHandle((HANDLE)DebugHandle);
return true;
}
if(Status != StatusPortNotSet || ReturnSize != sizeof(HANDLE)
#ifdef _WIN64
|| DebugHandle != 0
#endif
)
{
printf("ProcessDebugObjectHandle result contract mismatch: %08X, %u\n", Status, ReturnSize);
Detected = true;
}
// The output is written before ReturnLength. This order is observable when
// both pointers overlap.
DebugHandle = SentinelHandle;
Status = NtQIP(GetCurrentProcess(),
30,
&DebugHandle,
sizeof(DebugHandle),
(PULONG)&DebugHandle);
if(Status != StatusPortNotSet || DebugHandle != sizeof(HANDLE))
{
printf("ProcessDebugObjectHandle overlap contract mismatch: %08X\n", Status);
Detected = true;
}
return Detected;
}
bool HideFromDebugger()
{
typedef NTSTATUS(NTAPI * NT_SET_INFORMATION_THREAD)(
IN HANDLE ThreadHandle,
IN ULONG ThreadInformationClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength
);
NT_SET_INFORMATION_THREAD NtSIT = (NT_SET_INFORMATION_THREAD)
GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")),
"NtSetInformationThread");
return NT_SUCCESS(NtSIT(GetCurrentThread(),
0x11, //ThreadHideFromDebugger
0,
0));
}
static DWORD WINAPI ContractThreadProc(PVOID)
{
return 0;
}
bool CheckThreadHideFromDebuggerContract()
{
typedef NTSTATUS(NTAPI * NT_QUERY_INFORMATION_THREAD)(
HANDLE, ULONG, PVOID, ULONG, PULONG);
typedef NTSTATUS(NTAPI * NT_SET_INFORMATION_THREAD)(
HANDLE, ULONG, PVOID, ULONG);
typedef NTSTATUS(NTAPI * NT_CREATE_THREAD_EX)(
PHANDLE, ACCESS_MASK, PVOID, HANDLE, PVOID, PVOID, ULONG,
SIZE_T, SIZE_T, SIZE_T, PVOID);
NT_QUERY_INFORMATION_THREAD NtQIT = (NT_QUERY_INFORMATION_THREAD)
GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtQueryInformationThread");
NT_SET_INFORMATION_THREAD NtSIT = (NT_SET_INFORMATION_THREAD)
GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtSetInformationThread");
NT_CREATE_THREAD_EX NtCTE = (NT_CREATE_THREAD_EX)
GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtCreateThreadEx");
if(NtQIT == nullptr || NtSIT == nullptr || NtCTE == nullptr)
return false;
HANDLE Thread = CreateThread(nullptr, 0, ContractThreadProc, nullptr, CREATE_SUSPENDED, nullptr);
if(Thread == nullptr)
return false;
bool Detected = false;
BOOLEAN Hidden = TRUE;
ULONG ReturnLength = 0;
NTSTATUS Status = NtQIT(Thread, 0x11, &Hidden, sizeof(Hidden), &ReturnLength);
if(!NT_SUCCESS(Status) || Hidden != FALSE || ReturnLength != sizeof(Hidden))
{
printf("ThreadHideFromDebugger initial-state mismatch: %08X, %u, %u\n",
Status, Hidden, ReturnLength);
Detected = true;
}
Status = NtSIT(Thread, 0x11, nullptr, 0);
if(!NT_SUCCESS(Status))
{
printf("ThreadHideFromDebugger set failed: %08X\n", Status);
Detected = true;
}
Hidden = FALSE;
ReturnLength = 0;
Status = NtQIT(Thread, 0x11, &Hidden, sizeof(Hidden), &ReturnLength);
if(!NT_SUCCESS(Status) || Hidden != TRUE || ReturnLength != sizeof(Hidden))
{
printf("ThreadHideFromDebugger virtual-state mismatch: %08X, %u, %u\n",
Status, Hidden, ReturnLength);
Detected = true;
}
TerminateThread(Thread, 0);
WaitForSingleObject(Thread, INFINITE);
Hidden = FALSE;
ReturnLength = 0;
Status = NtQIT(Thread, 0x11, &Hidden, sizeof(Hidden), &ReturnLength);
if(!NT_SUCCESS(Status) || Hidden != TRUE || ReturnLength != sizeof(Hidden))
{
printf("Exited ThreadHideFromDebugger state mismatch: %08X, %u, %u\n",
Status, Hidden, ReturnLength);
Detected = true;
}
CloseHandle(Thread);
// A thread created with the hide flag has the same observable state as one
// hidden later through NtSetInformationThread.
const ULONG ThreadCreateFlagsCreateSuspended = 0x1;
const ULONG ThreadCreateFlagsHideFromDebugger = 0x4;
Thread = nullptr;
Status = NtCTE(&Thread,
THREAD_ALL_ACCESS,
nullptr,
GetCurrentProcess(),
(PVOID)ContractThreadProc,
nullptr,
ThreadCreateFlagsCreateSuspended | ThreadCreateFlagsHideFromDebugger,
0,
0,
0,
nullptr);
if(!NT_SUCCESS(Status) || Thread == nullptr)
{
printf("NtCreateThreadEx hide-state setup failed: %08X\n", Status);
return true;
}
Hidden = FALSE;
ReturnLength = 0;
Status = NtQIT(Thread, 0x11, &Hidden, sizeof(Hidden), &ReturnLength);
if(!NT_SUCCESS(Status) || Hidden != TRUE || ReturnLength != sizeof(Hidden))
{
printf("NtCreateThreadEx hide-state mismatch: %08X, %u, %u\n",
Status, Hidden, ReturnLength);
Detected = true;
}
TerminateThread(Thread, 0);
CloseHandle(Thread);
return Detected;
}
bool CheckGetContextFailureContract()
{
typedef NTSTATUS(NTAPI * NT_GET_CONTEXT_THREAD)(HANDLE, PCONTEXT);
NT_GET_CONTEXT_THREAD NtGCT = (NT_GET_CONTEXT_THREAD)
GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtGetContextThread");
if(NtGCT == nullptr)
return false;
HANDLE LimitedThread = OpenThread(THREAD_QUERY_LIMITED_INFORMATION,
FALSE,
GetCurrentThreadId());
if(LimitedThread == nullptr)
return false;
__declspec(align(16)) CONTEXT Context;
memset(&Context, 0xA5, sizeof(Context));
Context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
CONTEXT OriginalContext;
memcpy(&OriginalContext, &Context, sizeof(Context));
NTSTATUS Status = NtGCT(LimitedThread, &Context);
CloseHandle(LimitedThread);
const NTSTATUS StatusAccessDenied = (NTSTATUS)0xC0000022L;
if(Status != StatusAccessDenied || memcmp(&Context, &OriginalContext, sizeof(Context)) != 0)
{
printf("NtGetContextThread failure contract mismatch: %08X\n", Status);
return true;
}
return false;
}
typedef struct _OBJECT_TYPE_INFORMATION
{
UNICODE_STRING TypeName;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccessMask;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
UCHAR TypeIndex;
CHAR ReservedByte;
ULONG PoolType;
ULONG DefaultPagedPoolCharge;
ULONG DefaultNonPagedPoolCharge;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_ALL_INFORMATION
{
ULONG NumberOfObjects;
OBJECT_TYPE_INFORMATION ObjectTypeInformation[1];
} OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;
typedef enum _OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectTypesInformation,
ObjectDataInformation
} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;
typedef struct _TEST_OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} TEST_OBJECT_ATTRIBUTES, *PTEST_OBJECT_ATTRIBUTES;
bool CheckObjectTypeInformation()
{
typedef NTSTATUS(NTAPI * pNtCreateDebugObject)(PHANDLE, ULONG, PTEST_OBJECT_ATTRIBUTES, ULONG);
typedef NTSTATUS(NTAPI * pNtQueryObject)(HANDLE, OBJECT_INFORMATION_CLASS, PVOID, ULONG, PULONG);
pNtCreateDebugObject NtCDO = (pNtCreateDebugObject)GetProcAddress(
GetModuleHandle(TEXT("ntdll.dll")),
"NtCreateDebugObject");
pNtQueryObject NtQO = (pNtQueryObject)GetProcAddress(
GetModuleHandle(TEXT("ntdll.dll")),
"NtQueryObject");
if(NtCDO == NULL || NtQO == NULL)
return false;
TEST_OBJECT_ATTRIBUTES ObjectAttributes = { sizeof(TEST_OBJECT_ATTRIBUTES) };
HANDLE DebugObjectHandle = NULL;
NTSTATUS Status = NtCDO(&DebugObjectHandle, 0x0008, &ObjectAttributes, 0);
if(!NT_SUCCESS(Status))
return false;
// Preserve every handle owned by the protected process, not merely one.
// The active debugger may independently hold multiple handles to its own
// debug object, all of which should be removed from the reported total.
HANDLE DuplicateHandles[2] = {};
ULONG OwnedHandleCount = 1;
for(HANDLE& DuplicatedHandle : DuplicateHandles)
{
if(DuplicateHandle(
GetCurrentProcess(),
DebugObjectHandle,
GetCurrentProcess(),
&DuplicatedHandle,
0,
FALSE,
DUPLICATE_SAME_ACCESS))
{
OwnedHandleCount++;
}
}
bool Detected = false;
ULONG RequiredLength = 0;
Status = NtQO(DebugObjectHandle, ObjectTypeInformation, NULL, 0, &RequiredLength);
if(Status != (NTSTATUS)0xC0000004L || RequiredLength < sizeof(OBJECT_TYPE_INFORMATION)) // STATUS_INFO_LENGTH_MISMATCH
{
Detected = true;
}
else
{
OBJECT_TYPE_INFORMATION* TypeInformation = (OBJECT_TYPE_INFORMATION*)HeapAlloc(
GetProcessHeap(), HEAP_ZERO_MEMORY, RequiredLength);
if(TypeInformation == NULL)
{
Detected = true;
}
else
{
Status = NtQO(DebugObjectHandle, ObjectTypeInformation, TypeInformation, RequiredLength, NULL);
if(!NT_SUCCESS(Status) ||
TypeInformation->TotalNumberOfObjects == 0 ||
TypeInformation->TotalNumberOfHandles < OwnedHandleCount)
{
Detected = true;
}
// VMProtect 3.9.5+ overlaps ReturnLength with TypeName.Buffer.
Status = NtQO(DebugObjectHandle,
ObjectTypeInformation,
TypeInformation,
RequiredLength,
(PULONG)&TypeInformation->TypeName.Buffer);
if(!NT_SUCCESS(Status) ||
*(PULONG)&TypeInformation->TypeName.Buffer != RequiredLength ||
TypeInformation->TotalNumberOfObjects == 0 ||
TypeInformation->TotalNumberOfHandles < OwnedHandleCount)
{
Detected = true;
}
HeapFree(GetProcessHeap(), 0, TypeInformation);
}
}
for(HANDLE DuplicatedHandle : DuplicateHandles)
{
if(DuplicatedHandle != NULL)
CloseHandle(DuplicatedHandle);
}
CloseHandle(DebugObjectHandle);
return Detected;
}
// ObjectListCheck uses NtQueryObject to check the environments
// list of objects and more specifically for the number of
// debug objects. This function can cause an exception (although rarely)
// so either surround it in a try catch or __try __except block
// but that shouldn't happen unless one tinkers with the function
bool CheckObjectList()
{
__try
{
typedef NTSTATUS(NTAPI * pNtQueryObject)(HANDLE, OBJECT_INFORMATION_CLASS, PVOID, ULONG, PULONG);
POBJECT_ALL_INFORMATION pObjectAllInfo = NULL;
void* pMemory = NULL;
NTSTATUS Status;
ULONG Size = 0;
// Get NtQueryObject
pNtQueryObject NtQO = (pNtQueryObject)GetProcAddress(
GetModuleHandle(TEXT("ntdll.dll")),
"NtQueryObject");
// Get the size of the list
Status = NtQO(NULL, ObjectTypesInformation, //ObjectAllTypesInformation
&Size, sizeof(ULONG), &Size);
// Allocate room for the list
pMemory = VirtualAlloc(NULL, SIZE_T(Size), MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE);
if(pMemory == NULL)
return false;
// Now we can actually retrieve the list
Status = NtQO(GetCurrentProcess(), ObjectTypesInformation, pMemory, Size, NULL);
// Status != STATUS_SUCCESS
if(Status != STATUS_SUCCESS)
{
VirtualFree(pMemory, 0, MEM_RELEASE);
return false;
}
// We have the information we need
pObjectAllInfo = (POBJECT_ALL_INFORMATION)pMemory;
unsigned char* pObjInfoLocation = (unsigned char*)pObjectAllInfo->ObjectTypeInformation;
ULONG NumObjects = pObjectAllInfo->NumberOfObjects;
for(UINT i = 0; i < NumObjects; i++)
{
POBJECT_TYPE_INFORMATION pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)pObjInfoLocation;
// The debug object will always be present
wchar_t DebugObject[] = L"DebugObject";
auto DebugObjectLength = wcslen(DebugObject) * sizeof(wchar_t);
if(pObjectTypeInfo->TypeName.Length == DebugObjectLength && !memcmp(pObjectTypeInfo->TypeName.Buffer, DebugObject, DebugObjectLength)) //UNICODE_STRING is not NULL-terminated (pointed to by deepzero!)
{
// Are there any objects?
if(pObjectTypeInfo->TotalNumberOfObjects || pObjectTypeInfo->TotalNumberOfHandles)
{
VirtualFree(pMemory, 0, MEM_RELEASE);
return true;
}
else
{
VirtualFree(pMemory, 0, MEM_RELEASE);
return false;
}
}
// Get the address of the current entries
// string so we can find the end
pObjInfoLocation = (unsigned char*)pObjectTypeInfo->TypeName.Buffer;
// Add the size
pObjInfoLocation += pObjectTypeInfo->TypeName.MaximumLength;
// Skip the trailing null and alignment bytes
ULONG_PTR tmp = ((ULONG_PTR)pObjInfoLocation) & -(int)sizeof(void*);
// Not pretty but it works
if((ULONG_PTR)tmp != (ULONG_PTR)pObjInfoLocation)
tmp += sizeof(void*);
pObjInfoLocation = ((unsigned char*)tmp);
}
VirtualFree(pMemory, 0, MEM_RELEASE);
return false;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
puts("exception!");
return false;
}
}
bool CheckObjectTypesInformationOverlapContract()
{
typedef NTSTATUS(NTAPI * pNtQueryObject)(
HANDLE, OBJECT_INFORMATION_CLASS, PVOID, ULONG, PULONG);
pNtQueryObject NtQO = (pNtQueryObject)GetProcAddress(
GetModuleHandle(TEXT("ntdll.dll")),
"NtQueryObject");
if(NtQO == nullptr)
return false;
ULONG Size = 0;
NTSTATUS Status = NtQO(nullptr, ObjectTypesInformation, nullptr, 0, &Size);
if(Status != (NTSTATUS)0xC0000004L || Size == 0)
return false;
const ULONG BufferSize = Size + 0x10000;
unsigned char* Buffer = (unsigned char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, BufferSize);
if(Buffer == nullptr)
return false;
ULONG ActualLength = 0;
Status = NtQO(nullptr, ObjectTypesInformation, Buffer, BufferSize, &ActualLength);
if(!NT_SUCCESS(Status))
{
printf("ObjectTypesInformation baseline query failed: %08X\n", Status);
HeapFree(GetProcessHeap(), 0, Buffer);
return true;
}
OBJECT_ALL_INFORMATION* All = (OBJECT_ALL_INFORMATION*)Buffer;
unsigned char* Location = (unsigned char*)All->ObjectTypeInformation;
SIZE_T DebugObjectOffset = (SIZE_T)-1;
SIZE_T PreviousObjectOffset = (SIZE_T)-1;
SIZE_T PreviousOffset = (SIZE_T)-1;
const wchar_t DebugObject[] = L"DebugObject";
const USHORT DebugObjectLength = sizeof(DebugObject) - sizeof(wchar_t);
for(ULONG i = 0; i < All->NumberOfObjects; i++)
{
OBJECT_TYPE_INFORMATION* Type = (OBJECT_TYPE_INFORMATION*)Location;
if(Type->TypeName.Length == DebugObjectLength &&
memcmp(Type->TypeName.Buffer, DebugObject, DebugObjectLength) == 0)
{
DebugObjectOffset = (SIZE_T)(Location - Buffer);
PreviousObjectOffset = PreviousOffset;
break;
}
PreviousOffset = (SIZE_T)(Location - Buffer);
Location = (unsigned char*)Type->TypeName.Buffer + Type->TypeName.MaximumLength;
Location = (unsigned char*)(((ULONG_PTR)Location + sizeof(void*) - 1) &
-(LONG_PTR)sizeof(void*));
}
bool Detected = DebugObjectOffset == (SIZE_T)-1 ||
PreviousObjectOffset == (SIZE_T)-1;
if(Detected)
puts("ObjectTypesInformation did not contain a preceding DebugObject entry");
if(!Detected)
{
const SIZE_T OverlapOffsets[] =
{
DebugObjectOffset + FIELD_OFFSET(OBJECT_TYPE_INFORMATION, TypeName.Buffer),
PreviousObjectOffset + FIELD_OFFSET(OBJECT_TYPE_INFORMATION, TypeName.MaximumLength),
DebugObjectOffset + sizeof(OBJECT_TYPE_INFORMATION),
DebugObjectOffset + FIELD_OFFSET(OBJECT_TYPE_INFORMATION, TypeIndex)
};
for(ULONG i = 0; i < ARRAYSIZE(OverlapOffsets); i++)
{
ULONG NewActualLength = 0;
Status = NtQO(nullptr,
ObjectTypesInformation,
Buffer,
BufferSize,
&NewActualLength);
if(!NT_SUCCESS(Status))
{
printf("ObjectTypesInformation overlap reset failed: %08X\n", Status);
Detected = true;
break;
}
PULONG Overlap = (PULONG)(Buffer + OverlapOffsets[i]);
Status = NtQO(nullptr, ObjectTypesInformation, Buffer, BufferSize, Overlap);
if(!NT_SUCCESS(Status) || *Overlap != NewActualLength)
{
printf("ObjectTypesInformation overlap %u mismatch: %08X\n", i, Status);
Detected = true;
}
}
}
// Bytes after the native object-type list are caller-owned even when the
// supplied allocation is larger than ReturnLength. A filter must not scan
// or modify a crafted entry in that tail.
ULONG TailActualLength = 0;
Status = NtQO(nullptr,
ObjectTypesInformation,
Buffer,
BufferSize,
&TailActualLength);
if(NT_SUCCESS(Status))
{
All = (OBJECT_ALL_INFORMATION*)Buffer;
Location = (unsigned char*)All->ObjectTypeInformation;
for(ULONG i = 0; i < All->NumberOfObjects; i++)
{
OBJECT_TYPE_INFORMATION* Type = (OBJECT_TYPE_INFORMATION*)Location;
Location = (unsigned char*)Type->TypeName.Buffer +
Type->TypeName.MaximumLength;
Location = (unsigned char*)(((ULONG_PTR)Location + sizeof(void*) - 1) &
-(LONG_PTR)sizeof(void*));
}
const SIZE_T FakeSize = sizeof(OBJECT_TYPE_INFORMATION) + sizeof(DebugObject);
if(Location >= Buffer + TailActualLength &&
Location + FakeSize <= Buffer + BufferSize)
{
OBJECT_TYPE_INFORMATION* Fake =
(OBJECT_TYPE_INFORMATION*)Location;
memset(Fake, 0, FakeSize);
Fake->TypeName.Buffer = (PWSTR)(Fake + 1);
Fake->TypeName.Length = DebugObjectLength;
Fake->TypeName.MaximumLength = sizeof(DebugObject);
Fake->TotalNumberOfObjects = 0xA1B2C3D4;
Fake->TotalNumberOfHandles = 0xB1C2D3E4;
memcpy(Fake->TypeName.Buffer, DebugObject, sizeof(DebugObject));
unsigned char Expected[sizeof(OBJECT_TYPE_INFORMATION) + sizeof(DebugObject)];
memcpy(Expected, Fake, sizeof(Expected));
Status = NtQO(nullptr,
ObjectTypesInformation,
Buffer,
BufferSize,
&TailActualLength);
if(!NT_SUCCESS(Status) ||
memcmp(Fake, Expected, sizeof(Expected)) != 0)
{
printf("ObjectTypesInformation tail contract mismatch: %08X\n", Status);
Detected = true;
}
}
}
else
{
printf("ObjectTypesInformation tail setup failed: %08X\n", Status);
Detected = true;
}
HeapFree(GetProcessHeap(), 0, Buffer);
return Detected;
}
enum PROCESSINFOCLASS
{
ProcessBasicInformation = 0, // 0, q: PROCESS_BASIC_INFORMATION, PROCESS_EXTENDED_BASIC_INFORMATION
ProcessQuotaLimits, // qs: QUOTA_LIMITS, QUOTA_LIMITS_EX
ProcessIoCounters, // q: IO_COUNTERS
ProcessVmCounters, // q: VM_COUNTERS, VM_COUNTERS_EX
ProcessTimes, // q: KERNEL_USER_TIMES
ProcessBasePriority, // s: KPRIORITY
ProcessRaisePriority, // s: ULONG
ProcessDebugPort, // q: HANDLE
ProcessExceptionPort, // s: HANDLE
ProcessAccessToken, // s: PROCESS_ACCESS_TOKEN
ProcessLdtInformation, // 10
ProcessLdtSize,
ProcessDefaultHardErrorMode, // qs: ULONG
ProcessIoPortHandlers, // (kernel-mode only)
ProcessPooledUsageAndLimits, // q: POOLED_USAGE_AND_LIMITS
ProcessWorkingSetWatch, // q: PROCESS_WS_WATCH_INFORMATION[]; s: void
ProcessUserModeIOPL,
ProcessEnableAlignmentFaultFixup, // s: BOOLEAN
ProcessPriorityClass, // qs: PROCESS_PRIORITY_CLASS
ProcessWx86Information,
ProcessHandleCount, // 20, q: ULONG, PROCESS_HANDLE_INFORMATION
ProcessAffinityMask, // s: KAFFINITY
ProcessPriorityBoost, // qs: ULONG
ProcessDeviceMap, // qs: PROCESS_DEVICEMAP_INFORMATION, PROCESS_DEVICEMAP_INFORMATION_EX
ProcessSessionInformation, // q: PROCESS_SESSION_INFORMATION
ProcessForegroundInformation, // s: PROCESS_FOREGROUND_BACKGROUND
ProcessWow64Information, // q: ULONG_PTR
ProcessImageFileName, // q: UNICODE_STRING
ProcessLUIDDeviceMapsEnabled, // q: ULONG
ProcessBreakOnTermination, // qs: ULONG
ProcessDebugObjectHandle, // 30, q: HANDLE
ProcessDebugFlags, // qs: ULONG
ProcessHandleTracing, // q: PROCESS_HANDLE_TRACING_QUERY; s: size 0 disables, otherwise enables
ProcessIoPriority, // qs: ULONG
ProcessExecuteFlags, // qs: ULONG
ProcessResourceManagement,
ProcessCookie, // q: ULONG
ProcessImageInformation, // q: SECTION_IMAGE_INFORMATION
ProcessCycleTime, // q: PROCESS_CYCLE_TIME_INFORMATION
ProcessPagePriority, // q: ULONG
ProcessInstrumentationCallback, // 40
ProcessThreadStackAllocation, // s: PROCESS_STACK_ALLOCATION_INFORMATION, PROCESS_STACK_ALLOCATION_INFORMATION_EX
ProcessWorkingSetWatchEx, // q: PROCESS_WS_WATCH_INFORMATION_EX[]
ProcessImageFileNameWin32, // q: UNICODE_STRING
ProcessImageFileMapping, // q: HANDLE (input)
ProcessAffinityUpdateMode, // qs: PROCESS_AFFINITY_UPDATE_MODE
ProcessMemoryAllocationMode, // qs: PROCESS_MEMORY_ALLOCATION_MODE
ProcessGroupInformation, // q: USHORT[]
ProcessTokenVirtualizationEnabled, // s: ULONG
ProcessConsoleHostProcess, // q: ULONG_PTR
ProcessWindowInformation, // 50, q: PROCESS_WINDOW_INFORMATION
ProcessHandleInformation, // q: PROCESS_HANDLE_SNAPSHOT_INFORMATION // since WIN8
ProcessMitigationPolicy, // s: PROCESS_MITIGATION_POLICY_INFORMATION
ProcessDynamicFunctionTableInformation,
ProcessHandleCheckingMode,
ProcessKeepAliveCount, // q: PROCESS_KEEPALIVE_COUNT_INFORMATION
ProcessRevokeFileHandles, // s: PROCESS_REVOKE_FILE_HANDLES_INFORMATION
MaxProcessInfoClass
};
bool NTAPI NtSetInformationProcess(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
IN PVOID ProcessInformation,
IN ULONG ProcessInformationLength
)
{
typedef NTSTATUS(NTAPI * NTSETINFORMATIONPROCESS)
(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
IN PVOID ProcessInformation,
IN ULONG ProcessInformationLength
);
static NTSETINFORMATIONPROCESS NtSIP = 0;
if(!NtSIP)
{
NtSIP = (NTSETINFORMATIONPROCESS)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtSetInformationProcess");
if(!NtSIP)
return false;
}
return NT_SUCCESS(NtSIP(ProcessHandle, ProcessInformationClass, ProcessInformation, ProcessInformationLength));
}
bool CheckSystemDebugger()
{
typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION
{
BOOLEAN DebuggerEnabled;
BOOLEAN DebuggerNotPresent;
} SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION;
enum SYSTEM_INFORMATION_CLASS { SystemKernelDebuggerInformation = 35 };
typedef NTSTATUS(__stdcall * ZW_QUERY_SYSTEM_INFORMATION)(IN SYSTEM_INFORMATION_CLASS SystemInformationClass, IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength);
ZW_QUERY_SYSTEM_INFORMATION ZwQuerySystemInformation;
SYSTEM_KERNEL_DEBUGGER_INFORMATION Info;
ZwQuerySystemInformation = (ZW_QUERY_SYSTEM_INFORMATION)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwQuerySystemInformation");
if(ZwQuerySystemInformation && NT_SUCCESS(ZwQuerySystemInformation(SystemKernelDebuggerInformation, &Info, sizeof(Info), NULL)))
{
if(Info.DebuggerEnabled || !Info.DebuggerNotPresent)
{
return true;
}
}
return false;
}
bool CheckSystemDebugControl()
{
enum SYSDBG_COMMAND { SysDbgQueryModuleInformation = 0 };
typedef NTSTATUS(__stdcall * ZW_SYSTEM_DEBUG_CONTROL)(IN SYSDBG_COMMAND Command, IN PVOID InputBuffer OPTIONAL, IN ULONG InputBufferLength, OUT PVOID OutputBuffer OPTIONAL, IN ULONG OutputBufferLength, OUT PULONG ReturnLength OPTIONAL);
static const NTSTATUS STATUS_DEBUGGER_INACTIVE = (NTSTATUS)0xC0000354L;
ZW_SYSTEM_DEBUG_CONTROL ZwSystemDebugControl = (ZW_SYSTEM_DEBUG_CONTROL)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwSystemDebugControl");
if(ZwSystemDebugControl == NULL)
{
return false;
}
return ZwSystemDebugControl(SysDbgQueryModuleInformation, NULL, 0, NULL, 0, NULL) != STATUS_DEBUGGER_INACTIVE;
}
bool CheckNtClose()
{
__try
{
CloseHandle((HANDLE)0x1234);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return true;
}
return false;
}
int main(int argc, char* argv[])
{
if(argc == 2 && strcmp(argv[1], "--native-contracts") == 0)
{
const bool ProcessDebugObject = CheckProcessDebugObjectHandle();
const bool ThreadHide = CheckThreadHideFromDebuggerContract();
const bool GetContextFailure = CheckGetContextFailureContract();
const bool ObjectTypesOverlap = CheckObjectTypesInformationOverlapContract();
printf("Native contracts: ProcessDebugObject=%d ThreadHide=%d GetContextFailure=%d ObjectTypesOverlap=%d\n",
ProcessDebugObject, ThreadHide, GetContextFailure, ObjectTypesOverlap);
return ProcessDebugObject || ThreadHide || GetContextFailure || ObjectTypesOverlap ? 1 : 0;
}
char title[256] = "";
sprintf_s(title, "pid: %d", (int)GetCurrentProcessId());
SetConsoleTitleA(title);
BOOL IsWow64 = FALSE;
#ifndef _WIN64
IsWow64Process(GetCurrentProcess(), &IsWow64);
#endif
while(1)
{
printf("ProcessDebugFlags: %d\n", CheckProcessDebugFlags());
printf("ProcessDebugPort: %d\n", CheckProcessDebugPort());
printf("ProcessDebugObjectHandle: %d\n", CheckProcessDebugObjectHandle());
printf("NtQueryObject: %d\n", CheckObjectList());
printf("NtQueryObjectType: %d\n", CheckObjectTypeInformation());
printf("CheckSystemDebugger: %d\n", CheckSystemDebugger());
if(!IsWow64) // This syscall is not implemented in wow64.dll
printf("SystemDebugControl: %d\n", CheckSystemDebugControl());
printf("CheckNtClose: %d\n", CheckNtClose());
//printf("ThreadHideFromDebugger: %d\n", HideFromDebugger());
puts("");
if(argc > 1)
break;
Sleep(1000);
}
/*int pid=0;
printf("pid: ");
scanf("%d", &pid);
if(OpenProcess(PROCESS_ALL_ACCESS, false, pid))
puts("OpenProcess OK!");
else
puts("OpenProcess FAILED...");
printf("%u\n", GetCurrentProcessId());*/
/*HANDLE hDevice=CreateFileA("\\\\.\\TitanHide", GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
if(hDevice==INVALID_HANDLE_VALUE)
puts("invalid handle...");
else
{
puts("handle ok!");
printf("pid: ");
ULONG pid=0;
scanf("%d", &pid);
DWORD written=0;
HIDE_INFO HideInfo;
HideInfo.Pid=pid;
HideInfo.Arg=0;
HideInfo.Command=HidePid;
HideInfo.Type=HideProcessDebugFlags|HideProcessDebugPort|HideProcessDebugObjectHandle|HideDebugObject;
WriteFile(hDevice, &HideInfo, sizeof(HIDE_INFO), &written, 0);
CloseHandle(hDevice);
}
system("pause");*/
return 0;
}