热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

0.前言很久前写过一些应用层的反调试的文章,这类反调试方法的好处是易于实现,但缺点是很容易被绕过----保护因此失效。我们的危机公关手段是:将反调试功能放到内核中用驱动程序实现,以增

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 
#include

#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
    DriverEntry函数仅仅创建DetectDbgThd线程后就退出。GetPsActiveProcessHeadAddr用以获得系统中进程链表地址。DetectDbgThd每间隔1s遍历系统中的所有进程,并获得进程对应的_EPROCESS结构。进程通过_EPROCESS!ActiveProcessLinks字段加入PsActiveProcessHead形成链表。由于DDK并没有导出这个结构,所以要通过windbg获取字段偏移,并在代码中硬编码,以下是Xp sp3该域的偏移:
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
    让我们加载驱动,以调试calc.exe为例,看下反调试效果(上面代码编译后生成的驱动名为DetectDbg.sys):
图1.加载驱动,并分别启动windbg.exe和calc.exe,准备附加到进程。

图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无能为力~

代码链接:


推荐阅读
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
author-avatar
丶敷衍怎么演彡_175
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有