热门标签 | 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无能为力~

代码链接:


推荐阅读
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
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社区 版权所有