首页 > 其他分享 >2种内核级反用户态调试方法

2种内核级反用户态调试方法

时间:2022-11-07 21:35:04浏览次数:36  
标签:eProc DebugPort PsActiveProcessHeadAddr 用户 内核 ENTRY EPROCESS 调试 调试器


0.前言

    很久前写过一些应用层的反调试的文章,这类反调试方法的好处是易于实现,但缺点是很容易被绕过----保护因此失效。我们的危机公关手段是:将反调试功能放到内核中用驱动程序实现,以增强程序的反调试能力。由于vista后patch guard的影响,因此本文以Xp系统为例,其他系统需要对代码中的硬编码进行调整。

1.调试对象(DEBUG_OBJECT)介绍


    当调试器创建进程时,会创建一系列调试消息,如:进程创建消息,模块加载消息。对于调试器附加到运行中进程,亦会依次产生这些消息(确切的说是杜撰调试消息)。内核将这些消息通过Debug Port对象发送给调试器。另外当被调试进程触发异常时,内核收到异常消息后,亦会通过Debug Port把消息传给调试器。可见Debug Port是联系调试器和被调试器的纽带。内核会为调试器进程创建一个DEBUG_OBJECT对象,并将对象地址保存在进程_EPROCESS!DebugPort中;当调试器创建/附加被调试进程时,内核会把同一个DEBUG_OBJECT对象的地址保存到被调试进程的_EPROCESS!DebugPort字段。


kd> dt _EPROCESS -y DebugPort
nt!_EPROCESS
+0x0bc DebugPort : Ptr32 Void

2.方法1:DebugPort清零    


    基于上面的介绍,我们可以这样实现反调试功能:检测所有进程的_EPROCESS!DebugPort字段,如果发现该字段非空,可以当前系统中至少存在着调试器。出于演示目的,我简单的把_EPROCESS!DebugPort字段清0,这就把调试器或者被调试进程进行消息传递的纽带的一端给切断了。下面来看下代码:


#include <Ntifs.h>
#include <wdm.h>

#ifdef __cplusplus
extern "C" {
#endif

KSTART_ROUTINE DetectDbgThd;
HANDLE detectThdHnd = 0UL;

VOID DriverUnload(PDRIVER_OBJECT);

NTSTATUS DriverEntry(PDRIVER_OBJECT drvObj, PUNICODE_STRING regPath)
{
NTSTATUS ldStatus = STATUS_SUCCESS;
OBJECT_ATTRIBUTES thdAttr;
CLIENT_ID cid;

_asm int 3;

UNREFERENCED_PARAMETER(regPath);

drvObj->DriverUnload = DriverUnload;

memset(&thdAttr, 0, sizeof(OBJECT_ATTRIBUTES));
thdAttr.Length = sizeof(OBJECT_ATTRIBUTES);
ldStatus = PsCreateSystemThread(&detectThdHnd, 0,
&thdAttr, 0,
&cid,
DetectDbgThd, NULL);
if (!NT_SUCCESS(ldStatus))
return ldStatus;

return STATUS_SUCCESS;
}

VOID DriverUnload(IN PDRIVER_OBJECT drvObj)
{
UNREFERENCED_PARAMETER(drvObj);
ZwClose(detectThdHnd);
return;
}

PLIST_ENTRY GetPsActiveProcessHeadAddr()
{
PLIST_ENTRY PsActiveProcessHeadAddr = NULL;
NTSTATUS status;
PEPROCESS eProc = NULL;

status = PsLookupProcessByProcessId((HANDLE)4, &eProc);
if (!NT_SUCCESS(status))
return NULL;
#ifdef WIN32_XP
PsActiveProcessHeadAddr = ((PLIST_ENTRY)(((char*)eProc) + 0x88))->Blink;
#elif WIN32_7
PsActiveProcessHeadAddr = ((PLIST_ENTRY)(((char*)eProc) + 0xb8))->Blink;
#endif
//PsActiveProcessHeadAddr = eProc->ActiveProcessLinks->Blink;
ObDereferenceObject(eProc);

return PsActiveProcessHeadAddr;
}

VOID DetectDbgThd(void* thdCtx)
{
PEPROCESS eProc = NULL;
PLIST_ENTRY PsActiveProcessHeadAddr = NULL;
LIST_ENTRY* pos = NULL;
LARGE_INTEGER period = RtlConvertLongToLargeInteger(-10*1000);
DWORD32* procDebugPortAddr = NULL;
UNREFERENCED_PARAMETER(thdCtx);

PsActiveProcessHeadAddr = GetPsActiveProcessHeadAddr();
if (!PsActiveProcessHeadAddr)
return;

while(1)
{
pos = PsActiveProcessHeadAddr->Blink;
while (pos != PsActiveProcessHeadAddr)
{
//eProc = (PEPROCESS)CONTAINER_OF(pos, EPROCESS, ActiveProcessLinks);
#ifdef WIN32_XP
eProc = (PEPROCESS)((char*)pos - 0x88);
#elif WIN32_7
eProc = (PEPROCESS)((char*)pos - 0xb8);
#endif
//0xBC==EPROCESS!DebugPort
#ifdef WIN32_XP
procDebugPortAddr = (DWORD32*)(((char*)eProc) + 0xBC);
#elif WIN32_7
procDebugPortAddr = (DWORD32*)(((char*)eProc) + 0xeC);
#endif
if(*procDebugPortAddr != 0x00UL)
{
//KillProcess(eProc);
*procDebugPortAddr = 0x00UL;
}

pos = pos->Blink;
}

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

#ifdef __cplusplus
}
#endif


kd> vertarget
Windows XP Kernel Version 2600 (Service Pack 3) UP Free x86 compatible
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 2600.xpsp.080413-2111


kd> dt _EPROCESS -y ActiveProcessLinks
nt!_EPROCESS
+0x088 ActiveProcessLinks : _LIST_ENTRY

获得ActiveProcessLinks域的地址后可以马上获得_EPROCESS对象地址和_EPROCESS!DebugPort地址,之后直接把_EPROCESS!DebugPort指针值改为0即可实现DebugPort清零。


kd> dt _EPROCESS -y DebugPort
nt!_EPROCESS
+0x0bc DebugPort : Ptr32 Void



2种内核级反用户态调试方法_#ifdef


图1.加载驱动,并分别启动windbg.exe和calc.exe,准备附加到进程。

2种内核级反用户态调试方法_windows_02


图2.windbg附加到calc后,每次中断调试目标,都会显示红框中的内容,意思是 调试器进入了挂起中断状态(准调试状态,对这个状态的解释可以参考张银奎<软件调试> 第10.6.7节)。此时calc虽然处于被挂起状态,可以查看内存值,但不能被跟踪(如单步)和下断点。再次运行(F5)调试目标,calc恢复到运行状态。


附注:调试进程被挂起和恢复是因为调试子系统调用了DbgkpSuspendProcess/DbgkpResumeProcess将calc.exe挂起/恢复,这不需要经过DebugPort即可实现。至于可以查看内存值,是因为windbg只需通过ReadProcessMemory就能获得calc的内存值,也不需要DebugPort参与。


3.方法2:Teb!DbgSsReserve句柄清零

   这个方法其实是上面DebugPort清零的衍生版,网上暂时没有找到同样的实现,应该独我一家

(没仔细找是否有雷同)~不过这个标志位和DebugPort有点区别,首先只有调试器进程才有这个标志位;其次,它位于用户空间,由TEB保存。更进一步讲,位于调试器工作线程内部,调试器的UI线程也没有这个标志位;最后,它是一个句柄,指向内核为调试器进程创建的DEBUG_OBJECT对象。


    要实现Teb!DbgSsReserve句柄清零,首先要搜索具有调试特征的进程;搜索到目标进程后,要从当前线程空间Attach到目标进程空间,这样才能从目标进程空间读到有效的内存(虚拟内存);最后,用ZwClose关闭句柄。下面,我们来看下代码:


VOID DetectDbgThd(void* thdCtx)
{
PEPROCESS eProc = NULL;
PETHREAD eThd = NULL;
PLIST_ENTRY PsActiveProcessHeadAddr = NULL;
LIST_ENTRY* procPos = NULL;
LIST_ENTRY* thdPos = NULL;
LIST_ENTRY thdListHead;
KAPC_STATE apcState;
LARGE_INTEGER period = RtlConvertLongToLargeInteger(-10*1000);
DWORD32* thdTebAddr = NULL;
DWORD32* dbgSsReserved = NULL;

UNREFERENCED_PARAMETER(thdCtx);

PsActiveProcessHeadAddr = GetPsActiveProcessHeadAddr();
if (!PsActiveProcessHeadAddr)
return;

while(1)
{
procPos = PsActiveProcessHeadAddr->Blink;
while (procPos != PsActiveProcessHeadAddr)
{
//eProc = (PEPROCESS)CONTAINER_OF(pos, EPROCESS, ActiveProcessLinks);
#ifdef WIN32_XP
//+0x088 EPROCESS!ActiveProcessLinks : _LIST_ENTRY
eProc = (PEPROCESS)((char*)procPos - 0x88);
#endif

#ifdef WIN32_XP
//+0x190 EPROCESS!ThreadListHead : _LIST_ENTRY
//thdListHead = (LIST_ENTRY*)((char*)eProc+0x190);
//memcpy(&thdListHead,((char*)eProc+0x190),sizeof(LIST_ENTRY));
#endif
//pos = ListHead->Blink;
thdPos = (LIST_ENTRY*)(*(DWORD32*)((char*)eProc+0x190));
//while(pos != &ListHead)
while(thdPos != (LIST_ENTRY*)((char*)eProc+0x190))
{
#ifdef WIN32_XP
//+0x22c ETHREAD!ThreadListEntry : _LIST_ENTRY
eThd = (PETHREAD)((char*)thdPos - 0x22c);
//+0x020 ETHREAD!KTHREAD!Teb : Ptr32 Void
thdTebAddr = (*(DWORD32*)((char*)eThd+0x20));
if(!thdTebAddr)
goto Next;
#endif
KeStackAttachProcess(eProc, &apcState);

#ifdef WIN32_XP
//+0xf20 Teb!DbgSsReserved : [2] Ptr32 Void
dbgSsReserved = ((char*)thdTebAddr+0xf20);
#endif
/*
DbgSsReserved[0]:is reserved
DbgSsReserved[1]:for debuggee process, DbgSsReserved[0] stand for handler of debug-object
*/
if(dbgSsReserved[1])
{
ZwClose(dbgSsReserved[1]);
dbgSsReserved[1] = 0x00UL;
}

KeUnstackDetachProcess(&apcState);
Next:
thdPos = thdPos->Blink;
}

procPos = procPos->Blink;
}

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

加载上面的代码后,调试器就无法打开和附加到目标进程。不过有个限制windbg.exe的实现有点特殊,DbgSsReserve字段一直为空,所以上面的代码对windbg无能为力~



标签:eProc,DebugPort,PsActiveProcessHeadAddr,用户,内核,ENTRY,EPROCESS,调试,调试器
From: https://blog.51cto.com/u_13927568/5831397

相关文章

  • windbg调试服务程序
       相比通过输出日志来跟踪程序运行状态,我更倾向使用调试器。虽然我早知调试服务很麻烦,总不会比调试驱动还麻烦吧?基于这个想法,我尝试了在win7上使用windbg调试服务并记......
  • 调试遗漏IoStartNextPacket引起的阻塞
       前面​​driververifier检测驱动死锁 ​​一文中本想检测一下驱动中潜在的死锁来解决驱动无响应的bug,然而并没有实质性的进展。后来通过一系列的调试终于找到了......
  • 制作最小linux内核(1)
       ​​深入理解Linux2.6的initramfs機制(上)​​ 一文提到了制作简易initramfs的过程;而另一篇文章​​使用udevadm(modinfo)查找linux下设备对应的驱动​​ ......
  • windbg调试窗口过程WindowProc(winxp 32bit)
      ollydbg在调试窗口程序方面做得很便捷,虽然windbg在这方面不如od,但通过命令的组合也能达到类似的效果。我借winxp的calc.exe为例来谈谈如何用windbg调试窗口过程。 ......
  • 做用户权限的时候 刷新后页面 404
    ps:404页面一定要放在最后,{path:"*",redirect:"/404",hidden:true}  ,在创建路由实例的时候,加载路由规则routes的时候默认把404页面放在最后,但是使用addR......
  • Springboot 用session监听器统计在线用户数量
    今天给大家分享这个吧。利用Springboot中的session监听器去实现统计在线用户数量的需求(当然其实用shiro或者security是框架自己带有会话管理的,用起来更加方便)。但是,接下来......
  • 编写windbg调试器扩展 入门篇1
      我博客的左侧专栏曾经转过windows下编写调试器的一系列文章,这类文章是从零打造调试器,而这篇文章是介绍如何为windbg编写调试器扩展命令。0.前言  windbg的命令......
  • 浏览器内核的解析和对比
    要搞清楚浏览器内核是什么,首先应该先搞清楚浏览器的构成。简单来说浏览器可以分为两部分,shell+内核。其中shell的种类相对比较多,内核则比较少。Shell是指浏览器的外壳:例如......
  • Linux性能调试——stress压测工具详解
    目录一.stress简介1.stress简介2.stress安装二.stress使用1.stress命令2.使用三.stress测试场景四.stress-ng简介1.stress-ng简介2.stress-ng安装3.stress-ng命令一.stres......
  • 移动端调试工具
    移动端调试工具一般都用的VConsole现在分享一个其他调试工具 eruda个人感觉挺好用的//调试工具https://cdn.jsdelivr.net/npm/eruda除VConsole外另一个移动端调试......