XTrap保护中的IDTHOOK恢复

最近比较懒什么东西都没学。研究了两天传说中的三大游戏保护中的一个,叫XTrap.据说是三个保护

中最弱的一个。但是能力有限,还是没有过掉保护。哎,失败。

不过从中学到了点知识。留个记录。

XTrap保护在驱动里用SSTD HOOK了NtDeviceIoControlFile,NtOpenProcess,NtOpenSection

NtProtectVirtualMemory 等几个API. Shadow SSDT也HOOK了 NtUserFindWindowex

NtUserSetWindowsHookEx等几个API。他还在应用层也HOOK了一些仿调试函数。这些防护可以通过简单

的恢复来绕过。网上也有很多这方面的介绍。本以为绕过这些防护就完事了。可惜太天真了。

里面还有一个IDT HOOK 只要一调试就蓝屏。网上搜索了一下没有找到恢复IDT的资料。

于是自己想了一个办法。

先来说说IDT,IDT = Interrupt Descriptor Table 中断描述表。

这个中断描述表有什么用呢?我说的白话一点。如果你看过有关汇编的教程一定有所了解。

比如我们常用的DEBUG的T命令。他就是通过int 1中断来执行的。当我们执行这个中断的时候

CPU会在执行完一条指令之后,如果检测到TF为1则产生单步中断。

那么系统怎么知道那些中断对应什么操作呢。在系统里有一个中断描述符。它里面就记录了这些对应信

息。

如果我们把IDT里的地址改成我们自己的函数地址。呵呵那么可想而知,在发生相关中断操作的时候就

会执行我们的函数。

XTrap就HOOK了IDT里的int1来阻止调试。幻影壳也用到了这种技术。

有了上面的介绍,下面我们来说说怎么恢复。

首先我们要先把原始的IDT地址保存下来。代码如下

//这条汇编操作是获取IDT
__asm  sidt  idt_info

  idt_entries = (IDTENTRY*) MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase);

//这里是保存我们需要恢复的中断操作指令我这里的NT_INT_TIMER为#define NT_INT_TIMER   0x01
  old_ISR_pointer = MAKELONG(idt_entries[NT_INT_TIMER].LowOffset,idt_entries

[NT_INT_TIMER].HiOffset);

保存好了原始地址 那么当IDT被改变时我们就可以根据这个原始地址来恢复了

恢复代码如下

//获取现在的中断地址
 __asm  sidt  idt_info
  idt_entries = (IDTENTRY*) MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase);

  //表示将处理器标志寄存器的中断标志为清0,不允许中断
  __asm cli
  //用我们保存的地址来替换掉
  idt_entries[NT_INT_TIMER].LowOffset = (unsigned short) old_ISR_pointer;
  idt_entries[NT_INT_TIMER].HiOffset = (unsigned short)((unsigned long)old_ISR_pointer >> 

16);
  //表示将处理器标志寄存器的中断标志置1,允许中断
  __asm sti

到这里恢复就完成了。

呵呵,可是XTrap没有我想像的那么单纯。当我恢复的时候立马蓝屏。估计是有线程在不停的检测这个

地址是否被恢复。如果被恢复那就给你一个蓝屏的钙。于是我把除主线程以外的其它线程都挂起。然后

再恢复。

终于不蓝了。上调试器可以进行附加搜索能操作。但是只要执行中断操作程序立马挂掉。不知道是什么
原因。估计是还HOOK了其它地方。还是多学点调试吧以后直接逆向它的代码来看看。

SSDT HOOK

运行效果

先简单说一下SSDT HOOK。应用程序的API在使用的时候要调用系统的低层的API

那么系统如何找到对应的API呢?在系统里有一张表。

System Services Descriptor Table,系统服务描述符表。

在这个表里就有各种系统API的地址,我们只要把这些地址改成我们自己函数的地址。

那么结果大家可想而知了,系统就会调用我们的API了。道理就是这么简单。当然实现

起来也很简单。

说一下步骤

首先我们要定义一个SSDT的结构代码如下

typedef struct ServiceDescriptorEntry
{
	unsigned int *ServiceTableBase;
	unsigned int *ServiceCounterTableBase; //Used only in checked build
	unsigned int NumberOfServices;
	unsigned char *ParamTableBase;
} SSDTEntry;
__declspec(dllimport) SSDTEntry KeServiceDescriptorTable;

然后我们要定义一个自己的API函数,这个函数根据你要HOOK的API来

NTKERNELAPI NTSTATUS ZwTerminateProcess(IN HANDLE ProcessHandle OPTIONAL, IN NTSTATUS 

ExitStatus);

当然还要一个结构来保存要HOOK的API。为什么要保存,当然是为了恢复。:)他的结构如下

typedef NTSTATUS(*_ZwTerminateProcess)(IN HANDLE ProcessHandle OPTIONAL,IN NTSTATUS 

ExitStatus);
_ZwTerminateProcess Old_ZwTerminateProcess;

好了,下面我们要开始把SSDT里的API替换成我们的了。替换之前先把原先的API保存下。代码如下

	//找出旧函数地址并保存
	Old_ZwTerminateProcess =(_ZwTerminateProcess)(GetSystemFunc(ZwTerminateProcess));

#define GetSystemFunc(FuncName) KeServiceDescriptorTable.ServiceTableBase[*(PULONG)

((PUCHAR)FuncName+1)]

然后我们要改写了。当然系统不会那么随便就让我们来改写这个地址。它是受保护的只能读不能写。

那么我们怎么办呢?创建一个MDL来解决。代码如下

	//设置内存为可写
	MDSystemCall = MmCreateMdl(NULL, KeServiceDescriptorTable.ServiceTableBase, 

KeServiceDescriptorTable.NumberOfServices*4);
	if(!MDSystemCall)
		return STATUS_UNSUCCESSFUL;
	MmBuildMdlForNonPagedPool(MDSystemCall);
	MDSystemCall->MdlFlags = MDSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
	MappedSCT = MmMapLockedPages(MDSystemCall, KernelMode);

已经可以修改了那么我们要修改了。代码如下

HookOn( ZwTerminateProcess, NewZwTerminateProcess);

#define HookOn(_Old, _New) \
	(PVOID) InterlockedExchange( (PLONG) &MappedSCT[GetIndex(_Old)], (LONG) _New)

这样就把地址修改成我们的了,下面怎么处理自己看着办好了。我这里是仿止结束。

//通过ProcessHandle来获得当前要结束的进程的EPROCESS
	if (ObReferenceObjectByHandle

(ProcessHandle,GENERIC_READ,NULL,KernelMode,&SJMPROCESS,0) == STATUS_SUCCESS)
	{
		//如果要结束的是我们需要保护的进程,这里分两种情况
		if (ZHUPROCESS== SJMPROCESS)
		{
			if (ZHUPROCESS != PsGetCurrentProcess())
			{//情况一:当前进程不是我们所保护的进程
				//换句话说也就是其他进程试图结束我们所保护的进程,当然不能

让他结束
				nStatus = STATUS_ACCESS_DENIED;
			}

		}
	}

用完了当然要把它恢复,恢复代码如下

//卸载Hook
	UnHook( ZwTerminateProcess, Old_ZwTerminateProcess);

#define UnHook(_Old, _New) \
	InterlockedExchange( (PLONG) &MappedSCT[GetIndex(_Old)], (LONG) _New)

差点忘了 MDL也要恢复 呵呵 恢复代码如下

	//解锁、释放MDL
	if(MDSystemCall)
	{
		MmUnmapLockedPages(MappedSCT, MDSystemCall);
		IoFreeMdl(MDSystemCall);
	}

至此大致过程已经完了。

CNXCT小组的博客 is Stephen Fry proof thanks to caching by WP Super Cache