关于win7下RemoveDPC学习到的一点东西

来源:互联网 发布:io域名广东备案 编辑:程序博客网 时间:2024/06/05 18:41

搜集了很多资料和网页,为了枚举dpc花了很大功夫,为了方便大家学习相关知识与减少花费无谓的时间,将removedpc的小技巧和自己遇到的问题分享给大家


关于枚举DPC

这个是整个removedpc最大的问题。xp下可以导出 KiTimerTableListHead然后遍历,但是win7及以上是没有导出的(无论32位还是64位),所以当前我们的问题是如何获得“KiTimerTableListHead“ 


每一个CPU中都有一个KPRCB结构地址我们发现kpcb下存在一个_KTIMER_TABLE结构的成员叫timetable

下面是结构图

nt!_KTIMER_TABLE
+0x000 TimerExpiry      : [64] Ptr64 _KTIMER
+0x040 TimerEntries     : [256] _KTIMER_TABLE_ENTRY

然后TimerEntries     就是我们想要的KiTimerTableListHead  他是一个数组

关于 _KTIMER_TABLE_ENTRY 下面是它的结构

typedef struct _KTIMER_TABLE_ENTRY
{
UINT32 Lock;
LIST_ENTRY Entry;
UINT32 Unknow;
ULARGE_INTEGER Time;
}KTIMER_TABLE_ENTRY, PKTIMER_TABLE_ENTRY;

关于KPRCB

ntdll!_KPRCB
   +0x000 MinorVersion     : Uint2B
   +0x002 MajorVersion     : Uint2B
   +0x004 CurrentThread    : Ptr32 _KTHREAD
   +0x008 NextThread       : Ptr32 _KTHREAD
   +0x00c IdleThread       : Ptr32 _KTHREAD
   +0x010 LegacyNumber     : UChar
   +0x011 NestingLevel     : UChar
   +0x012 BuildType        : Uint2B
   +0x014 CpuType          : Char
   +0x015 CpuID            : Char
   +0x016 CpuStep          : Uint2B
   +0x016 CpuStepping      : UChar
   +0x017 CpuModel         : UChar
   +0x018 ProcessorState   : _KPROCESSOR_STATE

....................................................................................................

+0x1960 TimerTable       : _KTIMER_TABLE

..........................................................................................

这是个相当大的结构体,具体结构成员不多列举感兴趣的朋友可以通过调试和查阅msdn得到


值得注意的是64位和32位略有不同,在64位下TimerTable的位置在0x2200处,而_KTIMER_TABLE结构的成员timetable则在0x200处

然后kprcb可以通过未导出的变量KdVersionBlock得到,KiProcessorBlock在KdDebuggerData64这个结构中,KdDebuggerData64可以从KdVersionBlock中得到,而KdVersionBlock又在kpcr这个结构中

具体相关结构请浏览

http://blog.csdn.net/hu3167343/article/details/7612595


现在遇到的问题是 32位下KdVersionBlock只在1号处理器中才有值,但是64位下却无论如何也没有值

然后我们先继续32位

我们可以通过KeSetSystemAffinityThread(1)来让当前线程在1号处理器运行而kpcr可以通过读fs寄存器中的0x34位得到

64位则需要读msr的0xC0000101得到kpcr

所以我们可以说已经解决了问题

下来是代码实例

//RemoveDPC32.h

#include <ntifs.h>


typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID      DllBase;
PVOID      EntryPoint;
ULONG32    SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
UINT32   Unknow[17];
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedef struct _KTIMER_TABLE_ENTRY
{
UINT32 Lock;
LIST_ENTRY Entry;
UINT32 Unknow;
ULARGE_INTEGER Time;


}KTIMER_TABLE_ENTRY, PKTIMER_TABLE_ENTRY;


NTSTATUS GetKernelModuleInfoByDriverObject(PDRIVER_OBJECT DriverObject, WCHAR* KernelModuleName, PVOID* KernelModuleBase, UINT32* KernelModuleSize);
BOOLEAN  RemoveDPCInKernelModule(PVOID KernelModuleBase, ULONG32 KernelModuleSize);
BOOLEAN GetDPCTimerInfoByModuleInfo(PVOID KernelModuleBase, ULONG32 KernelModuleSize, ULONG32* Timer);


PUINT32 KeGetCurrentPrcb();


VOID DriverUnload(PDRIVER_OBJECT DriverObject);

//RemoveDPC32.c

#include "RemoveDPC32.h"


PVOID   __KernelModuleBase = NULL;
ULONG32 __KernelModuleSize = 0;
//bp RemoveDPC32!DriverEntry
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
NTSTATUS Status = STATUS_SUCCESS;
WCHAR KernelModuleName[] = L"DPCHookSSDT32.sys";
PDEVICE_OBJECT  DeviceObject = NULL;
DbgPrint("DriverEntry()\r\n");
DriverObject->DriverUnload = DriverUnload;


//获得模块信息  可以扩展成枚举模块信息
Status = GetKernelModuleInfoByDriverObject(DriverObject, KernelModuleName, &__KernelModuleBase, &__KernelModuleSize);
if (Status !=  STATUS_SUCCESS)
{
return Status;
}


if (RemoveDPCInKernelModule(__KernelModuleBase, __KernelModuleSize) == FALSE)
{
return STATUS_UNSUCCESSFUL;
}


return Status;
}




VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
DbgPrint("DriverUnload()\r\n");
}




NTSTATUS GetKernelModuleInfoByDriverObject(PDRIVER_OBJECT DriverObject, WCHAR* KernelModuleName, PVOID* KernelModuleBase, UINT32* KernelModuleSize)
{


PLDR_DATA_TABLE_ENTRY NextEntry = NULL;
PLDR_DATA_TABLE_ENTRY CurrentEntry = NULL;
/*


nt!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase          : Ptr32 Void
+0x01c EntryPoint       : Ptr32 Void
+0x020 SizeOfImage      : Uint4B
+0x024 FullDllName      : _UNICODE_STRING
+0x02c BaseDllName      : _UNICODE_STRING
+0x034 Flags            : Uint4B
+0x038 LoadCount        : Uint2B
+0x03a TlsIndex         : Uint2B
+0x03c HashLinks        : _LIST_ENTRY
+0x03c SectionPointer   : Ptr32 Void
+0x040 CheckSum         : Uint4B
+0x044 TimeDateStamp    : Uint4B
+0x044 LoadedImports    : Ptr32 Void
+0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT
+0x04c PatchInformation : Ptr32 Void
+0x050 ForwarderLinks   : _LIST_ENTRY
+0x058 ServiceTagLinks  : _LIST_ENTRY
+0x060 StaticLinks      : _LIST_ENTRY
+0x068 ContextInformation : Ptr32 Void
+0x06c OriginalBase     : Uint4B
+0x070 LoadTime         : _LARGE_INTEGER


*/
//判断是否合法
if (DriverObject&&MmIsAddressValid(DriverObject))
{
//指向驱动对象的驱动节的首地址也就是第一个成员
CurrentEntry = NextEntry = (PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection;
NextEntry = (PLDR_DATA_TABLE_ENTRY)CurrentEntry->InLoadOrderLinks.Flink;
//循环停止条件   双向链表当前entry指向一个空节点
while ((PLDR_DATA_TABLE_ENTRY)NextEntry != CurrentEntry)
{
//判断nnextentry指向的数据是否合法 并其中的bassdllname与所要得到的内核模块名字相对比(双字
if (NextEntry->BaseDllName.Buffer&&
MmIsAddressValid((PVOID)NextEntry->BaseDllName.Buffer) && !_wcsnicmp(KernelModuleName,
(WCHAR*)NextEntry->BaseDllName.Buffer, wcslen(KernelModuleName)))
{


//为要out的两个参数赋值
//得到基地址和大小
*KernelModuleBase = NextEntry->DllBase;
*KernelModuleSize = NextEntry->SizeOfImage;




return STATUS_SUCCESS;


}
//循环继续
NextEntry = (PLDR_DATA_TABLE_ENTRY)NextEntry->InLoadOrderLinks.Flink;
}
}
else
{
return STATUS_UNSUCCESSFUL;
}


return STATUS_UNSUCCESSFUL;
}
BOOLEAN  RemoveDPCInKernelModule(PVOID KernelModuleBase, ULONG32 KernelModuleSize)
{


ULONG32 Timer = NULL;
//通过刚才获得的模块信息得到dcptimer
if (GetDPCTimerInfoByModuleInfo(KernelModuleBase, KernelModuleSize, &Timer) == FALSE)
{
return FALSE;
}
//如果找到dpc并且得到的ktimer变量合法就取消
if (Timer&&MmIsAddressValid((PVOID)Timer))
{


if (KeCancelTimer((PKTIMER)Timer))
{
return TRUE;
}
}


return FALSE;
}
BOOLEAN GetDPCTimerInfoByModuleInfo(PVOID KernelModuleBase, ULONG32 KernelModuleSize, ULONG32* Timer)
{
ULONG32 v1 = KeNumberProcessors;   //已经被模块导入  就是说这个值是默认存在的可以直接用 表示的是cpu核心数
int i = 0;
int j = 0;
PULONG KPRCB = 0;
PUCHAR  TimerEntries = NULL;
PKTIMER  v3 = NULL;
PULONG32    KiWaitNever = NULL;
PULONG32    KiWaitAlways = NULL;
PLIST_ENTRY CurrentEntry = NULL;
PLIST_ENTRY NextEntry = NULL;
//设立中断级   <= DISPATCH_LEVEL 
KIRQL OldIrql = KeRaiseIrqlToDpcLevel();
//提高中断级到dispatch


//这个应该是获取kprcb结构//dt _KPRCB
KPRCB = KeGetCurrentPrcb();
/*
结构贼大反正不写了
0x2200这个地方是一个_KTIMER_TABLE结构的成员叫timetable
nt!_KTIMER_TABLE
+0x000 TimerExpiry      : [64] Ptr64 _KTIMER
+0x200 TimerEntries     : [256] _KTIMER_TABLE_ENTRY
然后下面那个偏移也就解释的通了
*/


TimerEntries = (PUCHAR)(*(ULONG32*)KPRCB + 0x1960 + 0x40);
for (i = 0; i < 0x100; i++)
{
CurrentEntry = (PLIST_ENTRY)(TimerEntries + sizeof(KTIMER_TABLE_ENTRY) * i + 4);  //这里是个数组  + 过Lock
NextEntry = CurrentEntry->Blink;


if (MmIsAddressValid(CurrentEntry) && MmIsAddressValid(NextEntry))
{
while (NextEntry != CurrentEntry)
{
PKDPC RealDPC;


//获得这个结构的首地址
v3 = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
RealDPC = v3->Dpc;
//如果timer合法 并且dpc函数不为空
if (MmIsAddressValid(v3) && MmIsAddressValid(RealDPC) && MmIsAddressValid(RealDPC->DeferredRoutine))
{
if ((ULONG32)v3 >= (ULONG32)KernelModuleBase && (ULONG32)v3 <= (ULONG32)KernelModuleBase + KernelModuleSize)
{
//如果找到了而且找到的dpc在之前的内核模块之间 就赋值传出
*Timer = (ULONG32)v3;




KeLowerIrql(OldIrql);
return TRUE;
}


}
NextEntry = NextEntry->Blink;
}
}



}
KeLowerIrql(OldIrql);
return FALSE;


}


PUINT32 KeGetCurrentPrcb()
{


PUINT32 KiProcessorBlock = 0;
  
      KeSetSystemAffinityThread(1); //使当前线程运行在第一个处理器上  
  
      _asm
      {
          push eax
          mov  eax, FS:[0x34]; 得到KdVersionBlock的地址
              add  eax, 20h; 得到指向DebuggerDataList的地址
              mov  eax, [eax]; 得到DebuggerDataList的地址
              mov  eax, [eax]; 取出里面的内容,即KdDebuggerData64结构
              mov  eax, [eax + 218h]; 取出KiProcessBlock的地址
              mov  KiProcessorBlock, eax;放到变量里
              pop  eax
      }
  
      KeRevertToUserAffinityThread();
  
      return KiProcessorBlock;
}


32位和64位的不同还体现在得到dpc的解密,因为32位下是未经过加密的而64则需要解密解密则需要解密码在经过一番计算得出

获取解密码可以通过导出KeSetTimer函数的函数地址然后向下查找得到

具体方法如下

BOOLEAN GetKiWaitVariableAddress(PULONG64* KiWaitNever, PULONG64* KiWaitAlways)
{
/*
kd> u kesettimer l 50
nt!KeSetTimer:
fffff800`03ef10a8 4883ec38        sub     rsp,38h
fffff800`03ef10ac 4c89442420      mov     qword ptr [rsp+20h],r8
fffff800`03ef10b1 4533c9          xor     r9d,r9d
fffff800`03ef10b4 4533c0          xor     r8d,r8d
fffff800`03ef10b7 e814000000      call    nt!KiSetTimerEx (fffff800`03ef10d0)
fffff800`03ef10bc 4883c438        add     rsp,38h
fffff800`03ef10c0 c3              ret
fffff800`03ef10c1 90              nop
fffff800`03ef10c2 90              nop
fffff800`03ef10c3 90              nop
fffff800`03ef10c4 90              nop
fffff800`03ef10c5 90              nop
fffff800`03ef10c6 90              nop
fffff800`03ef10c7 90              nop
nt!KxWaitForLockChainValid:
fffff800`03ef10c8 90              nop
fffff800`03ef10c9 90              nop
fffff800`03ef10ca 90              nop
fffff800`03ef10cb 90              nop
fffff800`03ef10cc 90              nop
fffff800`03ef10cd 90              nop
fffff800`03ef10ce 90              nop
fffff800`03ef10cf 90              nop
nt!KiSetTimerEx:
fffff800`03ef10d0 48895c2408      mov     qword ptr [rsp+8],rbx
fffff800`03ef10d5 4889542410      mov     qword ptr [rsp+10h],rdx
fffff800`03ef10da 55              push    rbp
fffff800`03ef10db 56              push    rsi
fffff800`03ef10dc 57              push    rdi
fffff800`03ef10dd 4154            push    r12
fffff800`03ef10df 4155            push    r13
fffff800`03ef10e1 4156            push    r14
fffff800`03ef10e3 4157            push    r15
fffff800`03ef10e5 4883ec50        sub     rsp,50h
fffff800`03ef10e9 488b0518502200  mov     rax,qword ptr [nt!KiWaitNever (fffff800`04116108)]
fffff800`03ef10f0 488b1de9502200  mov     rbx,qword ptr [nt!KiWaitAlways (fffff800`041161e0)]
    */


ULONG64 KeSetTimer = 0;
PUCHAR StartSearchAddress = 0; 
PUCHAR EndSearchAddress   = 0;



INT64   iOffset = 0;    


PUCHAR i = NULL;


//通过函数名字获取函数地址
KeSetTimer = (ULONG64)GetExportVariableFormNtosExportTableByVariableName(L"KeSetTimer");


//查找的范围
StartSearchAddress = (PUCHAR)KeSetTimer; 
EndSearchAddress = StartSearchAddress + 0x500;


for(i=StartSearchAddress; i<EndSearchAddress; i++)
{
if(*i==0x48 && *(i+1)==0x8B && *(i+2)==0x05)
{
memcpy(&iOffset,i+3,4);
//两个密码的值
*KiWaitNever=(PULONG64)(iOffset + (ULONG64)i + 7);
i=i+7;
memcpy(&iOffset,i+3,4);
*KiWaitAlways=(PULONG64)(iOffset + (ULONG64)i + 7);
return TRUE;
}
}


return FALSE;
}

PVOID
GetExportVariableFormNtosExportTableByVariableName(WCHAR *VariableName)
{
UNICODE_STRING v1;
PVOID ExportVariable = NULL;


if (VariableName && wcslen(VariableName) > 0)
{
RtlInitUnicodeString(&v1, VariableName);


//从Ntos模块的导出表中获得一个导出变量的地址
ExportVariable = MmGetSystemRoutineAddress(&v1);
}


return ExportVariable;
}

解密算法如下

KDPC* TransTimerDPCEx(PKTIMER Timer, ULONG64 KiWaitNever, ULONG64 KiWaitAlways)
{
ULONG64 DPC = (ULONG64)Timer->Dpc;     //Time 
DPC ^= KiWaitNever;
DPC = _rotl64(DPC, (UCHAR)(KiWaitNever & 0xFF));
DPC ^= (ULONG64)Timer;
DPC = _byteswap_uint64(DPC);
DPC ^= KiWaitAlways;
return (KDPC*)DPC;
}

具体计算不做过多阐述,因为我也看不明白qwq


补充说明的一点是关于枚举模块基地址方面_LDR_DATA_TABLE_ENTRY结构中在32位下basedllname中的buffer为一个垃圾值(有值)而length则为0,所以当我用

if (NextEntry->BaseDllName.Buffer&&
MmIsAddressValid((PVOID)NextEntry->BaseDllName.Buffer) && !_wcsnicmp(KernelModuleName,
(WCHAR*)NextEntry->BaseDllName.Buffer, NextEntry->BaseDllName.Length)))

进行判断的时候他总是会判断为1

在64位下basedllname的buffer为null而length为0所以判断为0

这个算是容易出错的地方我调了好久才发现qwq

0 0
原创粉丝点击