NetRoc's Blog

N-Tech

 

XTrap驱动分析

 

最近拿到一个使用XTrap的游戏,据说此物乃NPHS之外的第三大反外挂系统,so拿来瞧了瞧。

Ring3层包括几个dll和一个进程。看里面貌似使用了pipe相关的函数,运行时也起了一个进程。所以XTrap的架构应该和NP很类似,但是实现上就要弱很多了。

1、 现在还没发现有ring3全局注入的dll

2、 大量工作放到了作为和游戏接口的dll里面。通过dll方式提供游戏使用这点不同于NPlib库,而更类似HS。这种方式一大弱点就是那个dll容易被模拟,并且比较难发现。

3、 驱动相对来说应该是三个系统里面最弱的了,原因下面会讲到。花了5天时间逆出了整个驱动的源码,好像没有那么多硬编码的东西,呵呵。不过倒是发现了一些编程的BUG什么的。基本的功能点只有三个:HOOK SSDT实现的跨进程访问控制、通过对IoAccessMap的设置关闭对鼠标键盘端口访问权限、通过挂接Int 1中断获得调试信息。

 

大概的流程如下:

1、 DriverEntry:通过PsGetVersion判断系统版本,并根据不同的版本保存要Hook的在SSDT Shadow表中服务的ID。而SSDT表中的则是由后面IoControl里面Ring3传下来的。目前来看已经支持Vista了。通过KeQuerySystemTime拿了一下系统时间并保存下来,不过后面就没有再使用了,估计以后为了反调试会做时间检查什么的东西吧。申请了0x2000长度的内存,这是用于后面设置IoAccessMap的。然后就是例行的IoCreateDeviceIoCreateSymbolicLink,设置Dispatch例程。XTrapIRP_MJ_DEVICE_CONTROLIRP_MJ_CREATEIRP_MJ_CLOSEIRP_MJ_CLEANUP是在同一个例程中处理的。最后有一个莫名其妙的调用KfLowerIrql( KeRaiseIrqlToDpcLevel());偶的水平实在是还难以理解高丽棒子为啥要这样做,嘿嘿。

2、 剩下的就是通过DeviceIoControl来控制的了,我这个版本的XTrap一共有17ControlCodeDispatch例程的代码如下

NTSTATUS

XDvaDispatchAll( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)

{

         PIO_STACK_LOCATION pIrpStack;

         PVOID pSystemBuffer;

         PVOID pOutBuffer;

         ULONG ulMajorFunction;

         NTSTATUS ntStatus;

        

         pIrpStack = IoGetCurrentIrpStackLocation( Irp);

         Irp->IoStatus.Status = STATUS_SUCCESS;

         Irp->IoStatus.Information = 0;

         pSystemBuffer = Irp->AssociatedIrp.SystemBuffer;

         ulMajorFunction = pIrpStack->MajorFunction;

 

         switch( ulMajorFunction)

         {

         case IRP_MJ_DEVICE_CONTROL:

                  if( (pIrpStack->Parameters.DeviceIoControl.IoControlCode & 0x3) == 0x3)

                  {

                           pOutBuffer = Irp->UserBuffer;

                  }

                  else

                  {

                           pOutBuffer = pSystemBuffer;

                  }

                  return DoDeviceIoControl( Irp, pIrpStack->FileObject, 1, pSystemBuffer,

                           pIrpStack->Parameters.DeviceIoControl.InputBufferLength,

                           pOutBuffer, pIrpStack->Parameters.DeviceIoControl.OutputBufferLength,

                           pIrpStack->Parameters.DeviceIoControl.IoControlCode,

                           &Irp->IoStatus, DeviceObject);

         case IRP_MJ_CLOSE:

                  HookSSDT( g_HookInfo.NtOpenProcessInfo.Id, (ULONG)g_pNtOpenProcess, (ULONG)NewNtOpenProcess);

                  HookSSDT( g_HookInfo.NtDeviceIoControlFileInfo.Id, (ULONG)g_pNtDeviceIoControlFile, (ULONG)NewNtDeviceIoControlFile);

                  HookSSDT( g_HookInfo.NtWriteVirtualMemoryInfo.Id, (ULONG)g_pNtWriteVirtualMemory, (ULONG)NewNtWriteVirtualMemory);

                  HookSSDT( g_HookInfo.NtOpenSectionInfo.Id, (ULONG)g_pNtOpenSection, (ULONG)NewNtOpenSection);

                  HookSSDT( g_HookInfo.NtProtectVirtualMemoryInfo.Id, (ULONG)g_pNtProtectVirtualMemory, (ULONG)NewNtProtectVirtualMemory);

                  HookSSDT( g_HookInfo.NtTerminateProcessInfo.Id, (ULONG)g_pNtTerminateProcess, (ULONG)NewNtTerminateProcess);

                  HookSSDT2( g_dwNtGdiGetPixelId, (ULONG)g_pNtGdiGetPixel, (ULONG)NewNtGdiGetPixel);

                  HookSSDT2( g_dwNtUserSendInputId, (ULONG)g_pNtUserSendInput, (ULONG)NewNtUserSendInput);

                  HookSSDT2( g_dwNtUserCallNextHookExId, (ULONG)g_pNtUserCallNextHookEx, (ULONG)NewNtUserCallNextHookEx);

                  HookSSDT2( g_dwNtUserPostMessageId, (ULONG)g_pNtUserPostMessage, (ULONG)NewNtUserPostMessage);

                  HookSSDT2( g_dwNtUserTranslateMessageId, (ULONG)g_pNtUserTranslateMessage, (ULONG)NewNtUserTranslateMessage);

 

                  if( g_byIsSuccess)

                  {

                           g_byIsSuccess = FALSE;

                  }

                  if( g_byIsReboot)

                  {

                           _asm cli;

                           WRITE_PORT_UCHAR( (PUCHAR)0x64, (UCHAR)0xFE);

                           _asm hlt;

                  }

                  else

                  {

                           ntStatus = STATUS_SUCCESS;

                  }

                  break;

         case IRP_MJ_CREATE:

                  if( g_arrSomeCode[0] == 0)

                  {

                           memcpy( g_arrSomeCode, MyInt1, 5*sizeof( ULONG));

                  }

                  ntStatus = STATUS_SUCCESS;

                  break;

         case IRP_MJ_CLEANUP:

                  ntStatus = STATUS_SUCCESS;

                  break;

         default:

                  Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;

                  ntStatus = STATUS_INVALID_DEVICE_REQUEST;

         }

 

         IofCompleteRequest( Irp, IO_NO_INCREMENT);

         return ntStatus;

}

 

IRP_MJ_DEVICE_CONTROL的处理是在另外一个函数里面,由于太复杂,影响blog的美观,就不写出来了,哈哈。这里面可以看到,XTrap采用了和NP一样的办法重起电脑,就是往64端口写0xFE。挺白痴的是居然通过WRITE_PORT_UCHAR,难道以为只有他会Hook~

IRP_MJ_CREATE里面把自己的Int 1 函数的代码复制了一段出来,这个会用于后面再覆盖回去,是用于对付Inline hook的伎俩。

其他就没什么好说的了。

3、 关于SSDTHook

SSDTHook的函数有以下几个:NtOpenProcessNtDeviceIoControlFileNtWriteVirtualMemoryNtOpenSectionNtProtectVirtualMemoryNtTerminateProcess

Shadow TableHook的函数有下面几个:NtGdiGetPixelNtUserSendInputNtUserCallNextHookExNtUserPostMessageNtUserTranslateMessage

所有函数Hook的目的都很清楚,没有什么古怪的地方,呵呵。不过相当部分的钩子都只是简单的pass过去,并没有任何实质性的处理。可以看出来XTrap仍然是一个非常不完善的系统,这些部分应该都是留到以后进行功能扩充的。

关于Shadow Table的处理有一些特别的地方。Shadow Table的地址获取采用了硬编码+验证的方式。这一点偶个人觉得还是在KeAddSystemServiceTable中去取比较好,至少说在出现新的系统的时候很大可能并不用修改代码。另外,取到Shadow Table地址之后,除了将KSERVICE_TABLE_DESCRIPTOR地址保存之外,还将Shadow TableBase保存到了KeServiceDescriptorTable第二项的Base中,以后在Hook或者其他操作的时候就直接到KeAddSystemServiceTable地址+0x10去取了。这一点我也觉得有些奇怪,保存到全局变量什么的不就好了,为什么要去修改系统本身的东西,虽然目前那个位置并没有什么用。大约是为了反调试。

4、 关于Int 1的处理

这里貌似也没什么好说的,记录了一下断点被触发的次数、dr0dr4的内容什么的,然后IoControl里面Ring3会取走这些信息。不过有个很搞笑的BUGHook中断的函数里面的cli没有对应的sti

5、 关于IoAccessMap的处理

这里没什么好说的,是由Ring3触发,Ring0实现。贴一段DeviceIoContrl里面的代码就明白了。

case 0x85000044:

                  ntStatus = STATUS_INVALID_PARAMETER;

                  if( !pSystemBuffer || ulInputBufferLength != 4)

                  {

                           break;

                  }

                  PsLookupProcessByProcessId( *((ULONG*)pSystemBuffer), pSystemBuffer);

                  ((PUCHAR)g_pIoAccessMap)[0x0C] |= 0xFF;

                  ((PUCHAR)g_pIoAccessMap)[0x0D] |= 0xFF;

                  Ke386IoSetAccessProcess( pSystemBuffer, 1);

                  Ke386SetIoAccessMap( 1, g_pIoAccessMap);

                  ntStatus = STATUS_SUCCESS;

                  break;

         现在模拟键盘的所谓硬件模式,大部分人都是使用了网上一些开源工具,例如WinIo,基本原理就是通过IoAccessMap打开ring0的端口读写权限(啰嗦一句,上次看到某人拿来的一个sys,貌似将整个机器的io都打开了,实在是无比暴力……。寒一个)。所以对应办法就是也通过改写IoAccessMap关闭掉权限。

这也是我现在比较推荐使用的方法,对使用WinIo的按键精灵什么的外挂,都有药到病除的疗效。而且,影响范围比较小,只关闭了有限的端口。对于某些特殊情况下的程序,也可以发现之后再单独处理。不过对于自己写驱动读写端口的一类外挂来说,任何办法都没用了。In~~~out~~~~in~~~~out~~~~~in~~~~~~out~~~~~~J

6、 下面选一些函数贴出来吧J

ULONG __stdcall

NewNtGdiGetPixel( PVOID hDC, LONG XPos, LONG YPos)

{

         BOOLEAN blIsBlock = TRUE;

         if ( g_dwCurrentProcessId == (ULONG)PsGetCurrentProcessId())

         {

                  blIsBlock = FALSE;

         }

 

         //这里奇怪,不知道为什么这么搞

         if ( XPos == 0)

         {

                  if( YPos != 0)

                  {

                           if( YPos == 0x5A)

                           {

                                     blIsBlock = FALSE;

                           }

                  }

                  else

                  {

                           blIsBlock = FALSE;

                  }

         }

 

         if( g_byIsSuccess == TRUE && blIsBlock == TRUE)

                  return 0;

         return g_pNtGdiGetPixel( hDC, XPos, YPos);

}

 

 

ULONG

__stdcall NewNtUserSendInput(

                                                                  ULONG nInputs,

                                                                  LPINPUT pInput,

                                                                  ULONG cbSize)

{

         if( (g_byIsSuccess != TRUE) || (g_byAllowUserSendInput == TRUE))

         {

                  return g_pNtUserSendInput( nInputs, pInput, cbSize);

         }

         else

         {

                  return 1;

         }

}

 

NTSTATUS

__stdcall NewNtOpenProcess (

                                                                 PHANDLE ProcessHandle,

                                                                 ACCESS_MASK DesiredAccess,

                                                                 POBJECT_ATTRIBUTES ObjectAttributes,

                                                                 PCLIENT_ID ClientId

                                                                 )

{

         if( g_dwCurrentProcessId != 0)

         {

                  if( (ULONG)ClientId->UniqueProcess == g_dwCurrentProcessId)

                  {

                           if( DesiredAccess != 0x478)

                           {

                                     DesiredAccess &= 0xFFFFFFCF;//清掉PROCESS_VM_READPROCESS_VM_WRITE

                           }

                  }

         }

 

         if( g_dwProtectPid2 != 0)

         {

                  if( g_dwProtectPid2 == (ULONG)ClientId->UniqueProcess)

                  {

                           DesiredAccess &= 0x0FFFFFFFE; //清掉PROCESS_TERMINATE

                  }

         }

 

         if( g_dwCurrentProcessId == (ULONG)ClientId->UniqueProcess)

         {

                  g_dwIsSomeoneOpenMe = 1;

         }

         return g_pNtOpenProcess( ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);

}

 

NTSTATUS

__stdcall NewNtWriteVirtualMemory(

                                                                           HANDLE ProcessHandle,

                                                                           PVOID BaseAddress,

                                                                           CONST VOID *Buffer,

                                                                           SIZE_T BufferSize,

                                                                           PSIZE_T NumberOfBytesWritten

                                                                           )

{

         BOOLEAN blIsNeedSkip = FALSE;

         PROCESS_BASIC_INFORMATION stProcessInfo;

         HANDLE Handle;

         HANDLE hCurrentPid;

         RtlZeroMemory( &stProcessInfo, sizeof(stProcessInfo));

 

         if( STATUS_SUCCESS ==

                  ZwDuplicateObject( (HANDLE)0xFFFFFFFF,

                                                                 ProcessHandle,

                                                                 (HANDLE)0xFFFFFFFF,

                                                                 &Handle,

                                                                 0x400,

                                                                 0,

                                                                 0)

                  )

         {

                  ZwQueryInformationProcess( Handle, 0, &stProcessInfo, 0x18, 0);

                  ZwClose( Handle);

         }

 

         if( g_dwCurrentProcessId == (ULONG)stProcessInfo.UniqueProcessId)

         {

                  blIsNeedSkip = TRUE;

         }

 

         hCurrentPid = PsGetCurrentProcessId();

         if( (ULONG)hCurrentPid == g_dwSafePid1 ||

                  (ULONG)hCurrentPid == g_dwSafePid2 ||

                  (ULONG)hCurrentPid == g_dwSafePid3)

         {

                  blIsNeedSkip = FALSE;

         }

 

         if( (ULONG)hCurrentPid == g_dwCurrentProcessId)

         {

                  if( (g_dwFromUser2 | 0xFFFFF0F) == 0xFFFFF1F)

                  {

                           blIsNeedSkip = FALSE;

                  }

         }

 

         if ( !g_byIsSuccess || !blIsNeedSkip)

         {

                  return g_pNtWriteVirtualMemory( ProcessHandle, BaseAddress, Buffer, BufferSize, NumberOfBytesWritten);

         }

         return 0;

}

 

ULONG __stdcall NewNtUserTranslateMessage(PMSG lpMsg, ULONG dwhkl)

{

         CHAR ucScanCode, ucScanCode2;

         UCHAR blNeedSkip = FALSE;

         if( lpMsg->message == 0x100 || lpMsg->message == 0x101)

         {//WM_KEYDOWN,WM_KEYUP

                  ucScanCode2 = IsNeedSkipKeyMsg( lpMsg->wParam);

                  if( ucScanCode2)

                  {

                           ucScanCode = (lpMsg->lParam & 0x00FF0000) >> 16;

                           if( ucScanCode == ucScanCode2)

                           {

                                     blNeedSkip = TRUE;

                           }

                  }

         }

         if( !g_byIsSuccess || !blNeedSkip)

         {

                  return g_pNtUserTranslateMessage( lpMsg, dwhkl);

         }

         return 1;

}

 

         后记:

不知不觉做反XX工作已经快两年了,再加上做XX工作的时间,突然发现搞这些东西居然这么久了。貌似接触的东西都没什么新意了,万变不离其宗。XX和反XX也就那么几招,倒不如直接找人去砍来得方便快捷。至此三大系统全部告破,呵呵。

众里寻她千百度,蓦然回首,偶还是喜欢.NET

奇怪,我的MSIME打不出来“蓦”。

posted on 2008-05-04 14:57 NetRoc 阅读(2234) 评论(4)  编辑 收藏 引用 所属分类: 内核技术逆向分析

评论

# re: XTrap驱动分析 2010-06-29 16:27 xlmj

不知博主能否教下怎么才能bypass np呢  回复  更多评论   

# re: XTrap驱动分析 2010-06-29 16:28 xlmj

邮箱地址 chkxwkxw@21cn.com  回复  更多评论   

# re: XTrap驱动分析 2010-12-26 19:21 路人

能否教導我們如何過X Trap  回复  更多评论   

# re: XTrap驱动分析 2015-06-17 22:13 纯属好玩

博主大大...您曾经发过一个HideKd的工具,现在没有了,找了两天了找不到,,,不知道能否给小弟发一个v0.3版的呀...谢谢啦.786978691@qq.com  回复  更多评论   

只有注册用户登录后才能发表评论。

导航

统计

常用链接

留言簿(7)

随笔档案(99)

文章分类(35)

文章档案(32)

Friends

Mirror

搜索

最新评论

阅读排行榜

评论排行榜