您的位置:首页 > 其它

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

2017-04-20 22:47 351 查看
搜集了很多资料和网页,为了枚举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 InMemoryOrderL
fc58
inks;
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: