背景
逛世界上最大的交友网站的时候发现了一个开源的vt调试器,抱着学习的心态,下载下来学习学习。但是呢,实际编译出来的程序和release发布的有很大的区别,而且源码很乱,release版本的驱动至少能调试程序,但是源码编译之后的驱动还有各种bug,你还不如只放一个bin上去。
就很生气,于是呢!想着自己重写一份吧,但是我又很菜,又很懒,调试体系需要实现的函数也很多。于是在经过不断左右摇摆和尝试之后替换几个api之后完成对debugport的隐藏。==>点我前往<==
调试体系
各位都是大佬,基本都是倒背调试体系的能人,弟弟就不在这里卖弄了,但是呢!还是有必要提一下。
调试主要还是两种,附加和创建调试。
创建:处理是在创建成功之后清零debugport和PS_PROCESS_FLAGS_NO_DEBUG_INHERIT标志位,并且记录调试的目标进程到链表中
if (NT_SUCCESS(status) && ProcessHandle != NULL)
{
PDebugInfomation TmpDebuginfo = NULL;
BOOLEAN isDebug = FALSE;
KIRQL OldIrql = { 0 };
KeAcquireSpinLock(&g_DebugLock, &OldIrql);
for (PLIST_ENTRY pListEntry = g_Debuginfo.List.Flink; pListEntry != &g_Debuginfo.List; pListEntry = pListEntry->Flink)
{
PDebugInfomation pDebuginfo = CONTAINING_RECORD(pListEntry, DebugInfomation, List);
if (pDebuginfo->SourceProcessId == PsGetCurrentProcessId())
{
TmpDebuginfo = pDebuginfo;
isDebug = TRUE;
break;
}
}
KeReleaseSpinLock(&g_DebugLock, OldIrql);
if (isDebug)
{
PEPROCESS temp_process = NULL;
status = ObReferenceObjectByHandle(*ProcessHandle, 0x0400, *PsProcessType, ExGetPreviousMode(), (void**)& temp_process, NULL);
if (!NT_SUCCESS(status))
return status;
HANDLE target_pid = PsGetProcessId(temp_process);
TmpDebuginfo->TargetProcessId = target_pid;
PVOID DebugPort__ = GetProcess_DebugPort(temp_process);
*(ULONG64 *)(DebugPort__) = 0;
DbgkpMarkProcessPeb(temp_process);
PVOID Flags = GetProcess_ProcessFlags(temp_process);
*(PULONG64)Flags &= ~PS_PROCESS_FLAGS_NO_DEBUG_INHERIT;
return status;
}
}
附加:没什么特别需要处理的,记录调记录调试的目标进程到链表中
CurrentProcess = (PEPROCESS)PsGetCurrentProcess();
status = ObReferenceObjectByHandle(
DebugObjectHandle,
0x2,
*g_DbgkDebugObjectType,
PreviousMode,
(PVOID*)& DebugObject,
NULL);
KIRQL OldIrql = { 0 };
KeAcquireSpinLock(&g_DebugLock, &OldIrql);
for (PLIST_ENTRY pListEntry = g_Debuginfo.List.Flink; pListEntry != &g_Debuginfo.List; pListEntry = pListEntry->Flink)
{
PDebugInfomation pDebuginfo = CONTAINING_RECORD(pListEntry, DebugInfomation, List);
if (pDebuginfo->SourceProcessId == PsGetCurrentProcessId())
{
DebugObject = pDebuginfo->DebugObject;
pDebuginfo->TargetProcessId = PsGetProcessId(Process);
break;
}
}
KeReleaseSpinLock(&g_DebugLock, OldIrql);
代码的结构
代码总体分未两部分,应用层负责加载dbg api的符号,内核层负责替换调试类型的值和hook函数,主要支持了win7(sp1)和win10(20h2),因为这里部分Eprocess和Ethread的结构体数据是需要自己去定位的。
#ifdef WIN7
#define Thread_CrossThreadFlags 0x448
#define Thread_RundownProtect 0x430
#define Process_DebugPort 0x1f0
#define Process_RundownProtect 0x178
#define ProcessFlagS 0x440
#define ProcessSectionObject 0x268
#define ProcessSectionBaseAddress 0x270
#define ThreadStartAddress 0x388
#else
#define Thread_CrossThreadFlags 0x510
#define Thread_RundownProtect 0x4f8
#define Process_DebugPort 0x578
#define Process_RundownProtect 0x458
#define ProcessFlagS 0x464
#define ProcessSectionObject 0x518
#define ProcessSectionBaseAddress 0x520
#define ThreadStartAddress 0x450
#endif
应用层
利用sym api从http://msdl.microsoft.com/download/symbols下载符号到本地,然后将对应的api函数地址传输到驱动中
BOOLEAN isSuccess = FALSE;
do
{
HMODULE hNtdll = GetModuleHandle("ntdll.dll");
if (!hNtdll)
break;
g_ZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)GetProcAddress(hNtdll, "ZwQuerySystemInformation");
if (!g_ZwQuerySystemInformation)
break;
Module_INFO Module = { 0 };
GetKernelModuleInfo(&Module);
if (!InitSymHandler())
break;
HMODULE hDll = LoadLibraryEx("ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES);
char SymFile1[MAX_PATH] = { "" };
char szFile[MAX_PATH], SymFile[MAX_PATH] = { "" };
GetModuleFileNameA(hDll, szFile, sizeof(szFile) / sizeof(szFile[0]));
if (!SymGetSymbolFile((HANDLE)-1, NULL, szFile, sfPdb, SymFile, MAX_PATH, SymFile1, MAX_PATH))
break;
char FileName[MAX_PATH];
GetSystemDirectoryA(FileName, sizeof(FileName));
strcat_s(FileName, "\\");
strcat_s(FileName, Module.KernelName);
HANDLE hFile = CreateFileA(FileName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
break;
DWORD dwfilesize = GetFileSize(hFile, NULL);
DWORD64 BaseOfDll = SymLoadModule64((HANDLE)-1, hFile, FileName, NULL, (DWORD64)Module.KernelBass, dwfilesize);
CloseHandle(hFile);
if (!BaseOfDll)
break;
if (!SymEnumSymbols((HANDLE)-1, BaseOfDll, 0, (PSYM_ENUMERATESYMBOLS_CALLBACK)& EnumAllSymbolsCallBack, CallBack))
break;
isSuccess = TRUE;
} while (FALSE);
return isSuccess;
会用到的api
typedef struct _SYMBOLS_DATA{
PVOID NtCreateDebugObject;
PVOID PsGetNextProcessThread;
PVOID DbgkpPostFakeThreadMessages;
PVOID DbgkpWakeTarget;
PVOID DbgkpSetProcessDebugObject;
PVOID DbgkCreateThread;
PVOID DbgkpQueueMessage;
PVOID PsCaptureExceptionPort;
PVOID DbgkpSendApiMessage;
PVOID DbgkpSendApiMessageLpc;
PVOID DbgkpSendErrorMessage;
PVOID DbgkForwardException;
PVOID DbgkpSuppressDbgMsg;
PVOID DbgkpSectionToFileHandle;
PVOID DbgkUnMapViewOfSection;
PVOID DbgkpPostFakeProcessCreateMessages;
PVOID NtDebugActiveProcess;
PVOID DbgkpMarkProcessPeb;
PVOID KiDispatchException;
PVOID NtCreateUserProcess;
PVOID DbgkDebugObjectType;
PVOID ObTypeIndexTable;
PVOID NtTerminateProcess;
PVOID DbgkMapViewOfSection;
PVOID DbgkSendSystemDllMessages;
}SYMBOLS_DATA, * PSYMBOLS_DATA;
内核
初始化记录debugport对象的链表,替换部分使用到debugport的函数和DbgkDebugObjectType,其中这里的hook使用了airhv版本和ShotHv版本的vt,ShotHv版本的是放到源代码中的,并且ShotHv也比较适合学习,只handle必要的vmexit和ept
//初始化调试对象的链表和锁
InitializeListHead(&g_Debuginfo.List);
KeInitializeSpinLock(&g_DebugLock);
//这里开始hook函数
#ifdef WINVM
PHHook(g_SymbolsData.DbgkCreateThread, DbgkCreateThread, (PVOID*)&OriginalDbgkCreateThread);
PHHook(g_SymbolsData.DbgkpQueueMessage, DbgkpQueueMessage, (PVOID*)&OriginalDbgkpQueueMessage);
PHHook(g_SymbolsData.NtTerminateProcess, NtTerminateProcess, (PVOID*)&OrignalNtTerminateProcess);
PHHook(g_SymbolsData.NtCreateUserProcess, NtCreateUserProcess, (PVOID*)&OrignalNtCreateUserProcess);
PHHook(g_SymbolsData.KiDispatchException, KiDispatchException, (PVOID*)&OrignalKiDispatchException);
PHHook(g_SymbolsData.NtCreateDebugObject, NtCreateDebugObject, (PVOID*)&OriginalNtCreateDebugObject);
PHHook(g_SymbolsData.NtDebugActiveProcess, NtDebugActiveProcess, (PVOID*)&OriginalNtDebugActiveProcess);
PHHook(g_SymbolsData.DbgkForwardException, DbgkForwardException, (PVOID*)&OriginalDbgkForwardException);
PHHook(g_SymbolsData.DbgkMapViewOfSection, DbgkMapViewOfSection, (PVOID*)&OriginalDbgkMapViewOfSection);
PHHook(g_SymbolsData.DbgkUnMapViewOfSection, DbgkUnMapViewOfSection, (PVOID*)&OriginalDbgkUnMapViewOfSection);
PHHook(g_SymbolsData.DbgkpSetProcessDebugObject, DbgkpSetProcessDebugObject, (PVOID*)&OriginalDbgkpSetProcessDebugObject);
PHActivateHooks();
#else
hook_function(g_SymbolsData.DbgkCreateThread, DbgkCreateThread, (PVOID*)&OriginalDbgkCreateThread);
hook_function(g_SymbolsData.DbgkpQueueMessage, DbgkpQueueMessage, (PVOID*)&OriginalDbgkpQueueMessage);
hook_function(g_SymbolsData.NtTerminateProcess, NtTerminateProcess, (PVOID*)&OrignalNtTerminateProcess);
hook_function(g_SymbolsData.NtCreateUserProcess, NtCreateUserProcess, (PVOID*)&OrignalNtCreateUserProcess);
hook_function(g_SymbolsData.KiDispatchException, KiDispatchException, (PVOID*)&OrignalKiDispatchException);
hook_function(g_SymbolsData.NtCreateDebugObject, NtCreateDebugObject, (PVOID*)&OriginalNtCreateDebugObject);
hook_function(g_SymbolsData.NtDebugActiveProcess, NtDebugActiveProcess, (PVOID*)&OriginalNtDebugActiveProcess);
hook_function(g_SymbolsData.DbgkForwardException, DbgkForwardException, (PVOID*)&OriginalDbgkForwardException);
hook_function(g_SymbolsData.DbgkMapViewOfSection, DbgkMapViewOfSection, (PVOID*)&OriginalDbgkMapViewOfSection);
hook_function(g_SymbolsData.DbgkUnMapViewOfSection, DbgkUnMapViewOfSection, (PVOID*)&OriginalDbgkUnMapViewOfSection);
hook_function(g_SymbolsData.DbgkpSetProcessDebugObject, DbgkpSetProcessDebugObject, (PVOID*)&OriginalDbgkpSetProcessDebugObject);
#endif
if (!HookDbgkDebugObjectType())
return FALSE;
hook函数的说明
需要处理的函数大概就是下面所罗列的,但是由于有懒人所以并没有都处理。
/*
win7-win10下
会用到debugport的函数
PsGetProcessDebugPort //获取debugport的值,不处理
DbgkpSetProcessDebugObject //这里不将debugport的值写到eprocess的debugport字段,也不调用DbgkpMarkProcessPeb
DbgkpMarkProcessPeb //DbgkClearProcessDebugObject、DbgkpCloseObject(objectType的CloseProcedure,这里直接不实现)和DbgkpSetProcessDebugObject中会调用
DbgkCreateThread //简单实现内部有点长,不实现线程回调
PspExitThread //不实现,不要线程退出消息
DbgkExitThread //PspExitThread会调用DbgkExitThread,上面都不实现
DbgkpQueueMessage //实现比较简单
KiDispatchException //可以不实现,但内核调试器会先捕获到异常需要gn,不方便内核调试,而且不处理过不了int 2d
DbgkForwardException //调用了三个原函数
NtQueryInformationProcess //不处理
DbgkClearProcessDebugObject //不实现
DbgkpCloseObject //不实现
DbgkMapViewOfSection //调用了两个原函数
DbgkUnMapViewOfSection //调用了两个原函数
DbgkExitProcess //不实现
*/
KiDispatchException
可以不处理,但是不处理就过不了int 2d
if (PreviousMode != KernelMode)
{
BOOLEAN isDebug = FALSE;
KIRQL OldIrql = { 0 };
KeAcquireSpinLock(&g_DebugLock, &OldIrql);
for (PLIST_ENTRY pListEntry = g_Debuginfo.List.Flink; pListEntry != &g_Debuginfo.List; pListEntry = pListEntry->Flink)
{
PDebugInfomation pDebuginfo = CONTAINING_RECORD(pListEntry, DebugInfomation, List);
if (pDebuginfo->TargetProcessId == PsGetCurrentProcessId())
{
isDebug = TRUE;
break;
}
}
KeReleaseSpinLock(&g_DebugLock, OldIrql);
if (isDebug)
{
if ((TrapFrame->SegCs & 0xfff8) == KGDT64_R3_CMCODE)
{
switch (ExceptionRecord->ExceptionCode)
{
case STATUS_BREAKPOINT:
ExceptionRecord->ExceptionCode = STATUS_WX86_BREAKPOINT;
break;
case STATUS_SINGLE_STEP:
ExceptionRecord->ExceptionCode = STATUS_WX86_SINGLE_STEP;
break;
}
}
if (DbgkForwardException(ExceptionRecord, TRUE, FALSE))
{
//int 2d 不返回,直接下发异常到异常处理
if (*(PUSHORT)((ULONG64)(TrapFrame->Rip) - 3) != 0x2DCD)//int 2d
return;
}
if ((TrapFrame->SegCs & 0xfff8) == KGDT64_R3_CMCODE)
{
switch (ExceptionRecord->ExceptionCode)
{
case STATUS_WX86_BREAKPOINT:
ExceptionRecord->ExceptionCode = STATUS_BREAKPOINT;
break;
case STATUS_WX86_SINGLE_STEP:
ExceptionRecord->ExceptionCode = STATUS_SINGLE_STEP;
break;
}
}
}
}
OrignalKiDispatchException(ExceptionRecord, ExceptionFrame, TrapFrame, PreviousMode, FirstChance);
return;
NtTerminateProcess
主要是进程结束之后删除记录,避免出现调试器不重启不能调试的情况
NTSTATUS st;
PEPROCESS Process = NULL;
if (ProcessHandle)
{
st = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_TERMINATE,
*PsProcessType,
ExGetPreviousMode(),
(PVOID*)& Process,
NULL);
}
else
{
Process = PsGetCurrentProcess();
}
if (Process)
{
KIRQL OldIrql = { 0 };
KeAcquireSpinLock(&g_DebugLock, &OldIrql);
for (PLIST_ENTRY pListEntry = g_Debuginfo.List.Flink; pListEntry != &g_Debuginfo.List; pListEntry = pListEntry->Flink)
{
PDebugInfomation pDebuginfo = CONTAINING_RECORD(pListEntry, DebugInfomation, List);
if (pDebuginfo->TargetProcessId == PsGetProcessId(Process))
{
RemoveEntryList(&pDebuginfo->List);
ExFreePool(pDebuginfo);
break;
}
}
KeReleaseSpinLock(&g_DebugLock, OldIrql);
if (ProcessHandle)
ObDereferenceObject(Process);
}
return OrignalNtTerminateProcess(ProcessHandle,ExitStatus);
最后的结果
能正常调试vmp和se,并且没有debugport的痕迹(如果你想调试某游戏,我的评价是我不知道,因为我不玩)。
al-khaser_x86下除了父进程和watch memory,debug的痕迹都没有了(当然检测的方式还有窗口名和进程名的方式,这里并没有处理,你可以参考hyperhiden把这些也加上),NtYieldExecution在当前开了vt的虚拟机下就是bad(不管)。