锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

备用

时间:2023-03-09 09:30:00 75a过流继电器zw97继电器



风叶林-资源最多的免费辅助教程论坛->驱动保护->收集反调试和反调试的内容 方便大家学习[打印本页]

啊冲 2016-02-03 10:42

收集反调试和反调试的内容 方便大家学习

反调试技术在调试一些病毒程序时,你可能会遇到一些反调试技术,也就是说,调试程序可以检测到你是否被调试器附加。如果你知道你正在被调试,一定有人试图通过反汇编来解决自己。为了了解如何解决反调试技术,让我们先看看反调试技术。

一、Windows API方法
Win32提供了两个API, IsDebuggerPresent和CheckRemoteDebuggerPresent可用于检测当前过程是否正在调试IsDebuggerPresent例如函数如下:


BOOL ret = IsDebuggerPresent();
printf("ret = %d\n", ret);


解决方法很简单,就是在系统中使用这两个函数hook让这两个函数一直返回false可以,网上有很多事情要做。hook API也有很多工具源代码是开放的,所以这里就不细说了。


二、查询过程PEB的BeingDebugged标志位

当进程被调试器所附加的时候,操作系统会自动设置这个标志位,因此在程序里定期查询这个标志位就可以了,例子如下:


bool PebIsDebuggedApproach()
{
char result = 0;
__asm
{
// 进程的PEB地址放在fs寄存器位置
mov eax, fs:[30h]
 // 查询BeingDebugged标志位
              mov al, BYTE PTR [eax + 2] 
              mov result, al
       }

       return result != 0;
}


三、查询进程PEB的NtGlobal标志位 

跟第二个方法一样,当进程被调试的时候,操作系统除了修改BeingDebugged这个标志位以外,还会修改其他几个地方,其中NtDll中一些控制堆(Heap)操作的函数的标志位就会被修改,因此也可以查询这个标志位,例子如下:

bool PebNtGlobalFlagsApproach()
{
       int result = 0;

       __asm
       {
                      // 进程的PEB
              mov eax, fs:[30h]
                          // 控制堆操作函数的工作方式的标志位
              mov eax, [eax + 68h]
                          // 操作系统会加上这些标志位FLG_HEAP_ENABLE_TAIL_CHECK, 
                          // FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,
                          // 它们的并集就是x70
                          //
                          // 下面的代码相当于C/C++的
                          //     eax = eax & 0x70
              and eax, 0x70
              mov result, eax
       }

       return result != 0;
}


四、查询进程堆的一些标志位

这个方法是第三个方法的变种,只要进程被调试,进程在堆上分配的内存,在分配的堆的头信息里,ForceFlags这个标志位会被修改,因此可以通过判断这个标志位的方式来反调试。因为进程可以有很多的堆,因此只要检查任意一个堆的头信息就可以了,所以这个方法貌似很强大,例子如下:


bool HeapFlagsApproach()
{
       int result = 0;

       __asm
       {
                      // 进程的PEB
              mov eax, fs:[30h]
                      // 进程的堆,我们随便访问了一个堆,下面是默认的堆
              mov eax, [eax + 18h]
                          // 检查ForceFlag标志位,在没有被调试的情况下应该是
              mov eax, [eax + 10h]
              mov result, eax
       }

       return result != 0;
}
反调试技术二
五、使用NtQueryInformationProcess函数
NtQueryInformationProcess函数是一个未公开的API,它的第二个参数可以用来查询进程的调试端口。如果进程被调试,那么返回的端口值会是-1,否则就是其他的值。由于这个函数是一个未公开的函数,因此需要使用LoadLibrary和GetProceAddress的方法获取调用地址,示例代码如下:

// 声明一个函数指针。
typedef NTSTATUS (WINAPI *NtQueryInformationProcessPtr)(
       HANDLE processHandle,
       PROCESSINFOCLASS processInformationClass,
       PVOID processInformation,
       ULONG processInformationLength,
       PULONG returnLength);

bool NtQueryInformationProcessApproach()
{
       int debugPort = 0;
       HMODULE hModule = LoadLibrary(TEXT("Ntdll.dll "));
       NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule, "NtQueryInformationProcess");
       if ( NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)7, &debugPort, sizeof(debugPort), NULL) )
              printf("[ERROR NtQueryInformationProcessApproach] NtQueryInformationProcess failed\n");
       else
              return debugPort == -1;

       return false;
}

六、NtSetInformationThread方法
这个也是使用Windows的一个未公开函数的方法,你可以在当前线程里调用NtSetInformationThread,调用这个函数时,如果在第二个参数里指定0x11这个值(意思是ThreadHideFromDebugger),等于告诉操作系统,将所有附加的调试器统统取消掉。示例代码:

// 声明一个函数指针。
typedef NTSTATUS (*NtSetInformationThreadPtr)(HANDLE threadHandle,
       THREADINFOCLASS threadInformationClass,
       PVOID threadInformation,
       ULONG threadInformationLength);

void NtSetInformationThreadApproach()
{
       HMODULE hModule = LoadLibrary(TEXT("ntdll.dll"));
      NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hModule, "NtSetInformationThread");
  
       NtSetInformationThread(GetCurrentThread(), (THREADINFOCLASS)0x11, 0, 0);
}

七、触发异常的方法
这个技术的原理是,首先,进程使用SetUnhandledExceptionFilter函数注册一个未处理异常处理函数A,如果进程没有被调试的话,那么触发一个未处理异常,会导致操作系统将控制权交给先前注册的函数A;而如果进程被调试的话,那么这个未处理异常会被调试器捕捉,这样我们的函数A就没有机会运行了。
这里有一个技巧,就是触发未处理异常的时候,如果跳转回原来代码继续执行,而不是让操作系统关闭进程。方案是在函数A里修改eip的值,因为在函数A的参数_EXCEPTION_POINTERS里,会保存当时触发异常的指令地址,所以在函数A里根据这个指令地址修改寄存器eip的值就可以了,示例代码如下:
// 进程要注册的未处理异常处理程序A
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pei)
{
       SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)
              pei->ContextRecord->Eax);
       // 修改寄存器eip的值
       pei->ContextRecord->Eip += 2;
       // 告诉操作系统,继续执行进程剩余的指令(指令保存在eip里),而不是关闭进程
       return EXCEPTION_CONTINUE_EXECUTION;
}

bool UnhandledExceptionFilterApproach()
{
       SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
       __asm
       {
              // 将eax清零
              xor eax, eax
              // 触发一个除零异常
              div eax
       }

       return false;
}

八、调用DeleteFiber函数
如果给DeleteFiber函数传递一个无效的参数的话,DeleteFiber函数除了会抛出一个异常以外,还是将进程的LastError值设置为具体出错原因的代号。然而,如果进程正在被调试的话,这个LastError值会被修改,因此如果调试器绕过了第七步里讲的反调试技术的话,我们还可以通过验证LastError值是不是被修改过来检测调试器的存在,示例代码:
bool DeleteFiberApproach()
{
       char fib[1024] = {0};
       // 会抛出一个异常并被调试器捕获
       DeleteFiber(fib);

       // 0x57的意思是ERROR_INVALID_PARAMETER
       return (GetLastError() != 0x57);
}

啊冲 2016-02-03 10:42

从零开始的反反调试日志
=============    编程环境    =====================
VS2008+DDK+VA+DDKWIZARD
1、安装VS2008,MSDN
2、安装DDK
3、安装ddkwizard_setup
4、安装Visual Assist X
5、-> 错误1 => 找到ddkbuild.bat、ddkbuild.cmd(下载)放入/windows/system32 目录下
6、-> 错误2 => 计算机/.../环境变量 ,添加两个系统变量
    1、W7BASE = D:\WinDDK\7600.16385.1
    2、WXPBASE = D:\WinDDK\7600.16385.1
7、-> 错误3 => VS2008/Tools/Options/Projects and Solutions/VC++ Directories
    Win32/Include files =>
    添加D:\WinDDK\7600.16385.1\inc\api 到末尾,否则编译普通win32应用程序会提示错误
    添加D:\WinDDK\7600.16385.1\inc\ddk 到末尾
Other:如果安装顺序有错,导致VA无法支持DDK,则将api、ddk 添加到VA 的/Options/Projects/C/C++ Directories
    => custom/Stable include files ,一样,添加到末尾
= done =

1 : error PRJ0019: A tool returned an error code from "Performing Makefile project actions"
=>'ddkbuild.cmd' 不是内部或外部命令,也不是可运行的程序
2 :1>DDKBLD: ERROR #3: To build using type W7 you need to set the %W7BASE% environment variable to point to the Windows 7/Windows 2008 Server R2 DDK base directory!
3 :VS2008 中UNICODE_STRING 按F12 无法追踪


=============    双机调试  =======================
Windbg+VMware
1、安装VMware
2、安装Windbg(DDK里面有这个东西)
3、安装IDA
4、VMware 设置(装有xp 和win7 两个系统)
xp版:
在c:\boot.ini 文件中添加debug 的启动项
[operating systems]
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional - debug" /fastdetect /debug /debugport=com1 /baudrate=115200

win7版:
在msconfig 中添加debug 启动项
msconfig -> 高级选项-> 调试,调试端口COM1,波特率115200

在VMware 上修改两个系统的串口设置:
打开电源时连接
此终端是服务器
另一终端是一个应用程序
i/o 模式 轮询时主动放弃CPU占用
xp:使用命名管道\\.\pipe\com_1
win7:使用命名管道\\.\pipe\com_2

5、Windbg 设置
符号路径,xp 与win7 的符号都可以放在同一个目录下,没有的话,windbg 会自动将文件下载到E:\sysbols
E:\symbols;SRV*E:\symbols*http://msdl.microsoft.com/download/symbols

建立两个windbg 快捷方式的设置,修改其中参数,分别连接两个虚拟机
xp:
D:\tools\windbg\windbg.exe -b -k com:port=\\.\pipe\com_1,baud=115200,pipe
win7版:
D:\tools\windbg\windbg.exe -b -k com:port=\\.\pipe\com_2,baud=115200,pipe

6、在windbg 下dump 两个系统
  将整个win7系统内存dump下来( Full kernel dump),耗费了我40多个小时...其中一次机器休眠了...
  (Creating a full kernel dump over the COM port is a VERY VERY slow operation.)有除COM外其他的连接方式,但我不会。。
  .dump /f e:\win7_dump.dmp

=============   驱动加载工具====================
      后面附一个源码,喜欢的可以下

=============   准备工作完成====================
      至此,我们有了一个能随时能增加功能的驱动加载工具,一份win7 dump 文件,双机调试,编程环境。

=============   从零开始分析驱动层的反调试=========
      在这以前,只有应用层的逆向经验。没接触过驱动,也不知道反调试。
      下面是我过某个游戏驱动保护的过程,这个过程从11月14号左右开始,到12月1号结束。
         游戏一开始给人的表象有:
1、游戏进程、守护进程、驱动
2、OllyICE 附加列表中无法看到目标进程,任务管理栏则可正常显示目标进程名称。
3、Windows7:去除两个内核钩子Hook后,OD可看到目标进程,但是附加时提示附加失败!(用xuetr 看的到内核钩子)
4、Windows XP:去掉两个内核钩子,游戏直接退出。
5、采用虚拟机VMware+Windbg 调试,游戏进程 启动时报错!
6、VMware+Windows 7 [Debug]:游戏启动后Windows 7系统无响应,只能重新启动系统。
7、VMware+Windows 7:游戏可正常启动。
8、OD加载游戏主程,OD崩溃,模块时发生错误,错误代码:0xc0000005(最后发现是PE结构中一个模块名字超长导致,我的OD很老了)
9、OD正常加载游戏主程之后,有被检测到的信息,多次尝试找信息出处,无果


      以上是11月17日之前的各种尝试,也是最痛苦的时候——完全找不到任何方向。之后调整了思考方向,把重心放到第5、6、7条线索上。以下是当时调试日志的主要部分,有点小修改。


2010/11/17对**的调试终于有点突破^_^
      之前一直不清楚**是如何区分系统处于Debug还是正常状态。经过对Windows的异常分发机制,了解了Debug与正常状态的流程不同,主要是KdpTrap与KdpStub两个函数对应于不同的系统。
      至此,与双机调试有关的地方有4处:KdpDebugRoutine(函数指针)、KdpBootedNodbug(bool)、KdPitchDebugger(bool)、DebuggerEnabled(bool)。
      通过修改KdpDebugRoutine 指向KdpStub ,以及另外3个标志位,可将系统从Debug修改为正常状态,Windbg将处于等待状态。**可正常执行,待**加载完毕后,将上述4个值修改回来,Windbg可重新获取话语权!
      ******
      因此,我将要做另外一个任务,一个驱动程序,可以让系统在Debug与正常状态相互切换!这样,我就可以在游戏运行期间,随时进行调试。如果有可能,最好让驱动随时与OD进行通讯。

2010/11/18  完成驱动加载工具
      完成一个通用的驱动加载工具,测试,可将Debug系统在Debug 与 正常状态间随意切换。但是对于正常系统,却无法切换成Debug。下一步要做的,就是将正常系统也能随意切换!
(这个到现在也没开始做...)

2010/11/19
1、经过测试,被转换后的系统可以进行双机调试,下断ws2_32!send 失败。
2、使用XueTr恢复两个内核钩子后,OD能够看到** 进程,附加失败
3、针对附加失败,使用双机调试查看原因!关键函数kernel32!DebugActiveProcess。

         流程kernel32!DebugActiveProcess -> ntdll!ZwDebugActiveProcess -> 功能号0x60 -> KeServiceDescriptorTable[0][0x60*4] -> nt!NtDebugActiveProcess
         上述步骤能够成功运行
         失败存在于ntdll!NtCreateThreadEx -> nt!NtCreateThreadEx:
         经过跟踪发现,最终问题在上述线路中的nt_RtlImageNtHeaderEx+0x45处,由于对象** 进程的PE头被抹去,导致此函数判断时,返回了一个失败值!
         进一步的,在不恢复内核钩子的情况下,** 的Pe头不被改写,一旦恢复之后,**的某个线程会将此PE头抹去,导致OD无法附加
(有win7 dump ,结合ida 感觉真是好)

2010/11/??
         ** 在对比黑白名单后,判断是否放行目标进程。
         通过修改黑白名单的内容,OD 可以顺利附加,但是无法读出** 的模块信息!
(不知道具体日期了,主要是从xuetr 上看到的2个内核钩子入手nt!NtReadVirtualMemory,nt!NtWriteVirtualMemory,这期间,通过这条线索搞定了它的白名单)

2010/11/22  
         制作完相关工具后,经测试,OD 能够看见目标进程,附加,但附加之后便发生错误,无法看到对象的模块信息。应该是目标进程在不断的对debugport 进行清零操作,目前发现有

         多个线程有此动作,其中有一个是在不断新建线程,新的线程就是不断对debugport 做检查。如果绕过debugport 检查?
         (这里可能会有些不准确,但确定是的某个线程在对debugport 清零,查看了不少帖子,最后线索来自看雪)

2010/11/23   ** 对debugport 清零的动作
         Windbg 对debugport 下写断点
kd> u **+0x41764
**+0x41764:
9b2fb764 8702            xchg    eax,dword ptr [edx]    //清零操作
9b2fb766 6685e9         test     cx,bp
9b2fb769 660fbae501   bt        bp,1
9b2fb76e 8b36            mov     esi,dword ptr [esi]
9b2fb770 83ecdc         sub      esp,0FFFFFFDCh
9b2fb773 0f886545ffff  js         **+0x35cde (9b2efcde)
9b2fb779 f5               cmc
9b2fb77a 3bf1            cmp     esi,ecx


         手动修改edx 值,发现od 附加后可正常存活。但是如果暂停该线程,则会导致od 附加后,很快游戏自动退出!

         使用工具对**驱动代码部分做修改(debugport清零),在多次测试中,很少的情况可以一直附加,但实体机状态下,OD很快就被检测到。在程序自退出时,有弹出守护进程被异常终止的对话框。程序自退出时,会有一个单独线程,冻结此线程,OD 会存活的比较久。
(到现在为止,还不能对游戏下断点)

2010/11/25
         OD 对游戏下断,游戏会异常退出,0x80000003

2010/11/29
         了解线程的HidePort后,制作工具可以下断点,但是OD 还会被检测到。主要的问题在于线程0x00cc0654中调用了RtlExitUserProcess 函数(该函数又调用了ZwTerminateProcess)。
         该线程会不停的创建,但未经过CreateThread API(功能号为0x58)。
现在的问题是,创建该线程是否传递了参数进来?如果未有参数传递,是否该线程检测到OD运行?!
         补充:由于游戏主线程的HidePort被设置为1,导致内核将该线程上的异常屏蔽,不分发给用户层。因此OD修改的代码int3 会引发一个异常,导致主线程退出。

2010/11/30
         在nt!NtCreatethreadEx 下断,没有相关创建0x00cc0654 线程的调用!因此,还是无法知道程序中哪里创建了线程0x00cc0654 。比较奇怪的是,该线程应该是不断的被创建的、且线程ID 总是相同,但是retn 之后,该线程便不再被创建。。(之所以这么说,是因为在该线程的入口点,总是能断下)

2010/12/01

         基本实现OD 的附加调试,但是0x00cc0654 线程是从哪里来的,如何被创建,如何检查OD? (一直未解决,太多的代码变异)


总结:
大部分的反调试还是在驱动层面,并且是已知的几个技术点
1、  反Debug系统                          debug 系统与 正常版本切换
2、  DebugPort 清零                       nop 掉相关代码段
3、  主线程HidePort 置1                          重置HidePort
4、  内核函数钩子,采用白名单方式放行。     找到白名单,手动添加
5、  0x00cc0654 线程检测                  直接将线程入口修改为retn



我想很多在内核之外的人,跟我一样在门外徘徊,其实,只要做,并没有那么难。  

啊冲 2016-02-03 10:42

反反调试器跟踪”,对,没写错。

什么是“反调试器跟踪”?举个例子,用WinDbg尝试打开“极品飞车9“的speed.exe,运行,会被提示”Unload the debugger and try again".....
这就是“反调试器跟踪”,一般有两种方法:①调用kernel32!IsDebuggerPresent②把代码放到SetUnhandledExceptionFilter设定的函数里面。通过人为触发一个unhandled exception来执行。

HOWTO:“反反调试器跟踪”?
对第一种情况,很简单,kernel32!IsDebuggerPresent的实现是这样的:
0:000> uf kernel32!IsDebuggerPresent
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\WINDOWS\system32\KERNEL32.dll -
KERNEL32!IsDebuggerPresent:
7c813093 64a118000000    mov     eax,dword ptr fs:[00000018h]
7c813099 8b4030          mov     eax,dword ptr [eax+30h]
7c81309c 0fb64002        movzx   eax,byte ptr [eax+2]
7c8130a0 c3              ret
这就简单啦,把[[FS:[18]]:30]:2的值改成0,IsDebuggerPresent就返回false,这种方法就挂了:
首先,bp kernel32!IsDebuggerPresent
0:004> g
Breakpoint 0 hit
eax=00a75950 ebx=00180dd0 ecx=013b2b48 edx=013ce3d8 esi=0012f018 edi=0012f0d4
eip=7c813093 esp=0012efec ebp=0012f000 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
KERNEL32!IsDebuggerPresent:
7c813093 64a118000000    mov     eax,dword ptr fs:[00000018h] fs:003b:00000018=7ffdf000
0:000> p
eax=7ffdf000 ebx=00180dd0 ecx=013b2b48 edx=013ce3d8 esi=0012f018 edi=0012f0d4
eip=7c813099 esp=0012efec ebp=0012f000 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
KERNEL32!IsDebuggerPresent+0x6:
7c813099 8b4030          mov     eax,dword ptr [eax+30h] ds:0023:7ffdf030=7ffd7000
0:000> p
eax=7ffd7000 ebx=00180dd0 ecx=013b2b48 edx=013ce3d8 esi=0012f018 edi=0012f0d4
eip=7c81309c esp=0012efec ebp=0012f000 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
KERNEL32!IsDebuggerPresent+0x9:
7c81309c 0fb64002        movzx   eax,byte ptr [eax+2]       ds:0023:7ffd7002=01
0:000> eb 0023:7ffd7002 00  <--------------------------------
0:000> g
所以稍微好一点的应用程序不会使用这种方法:)
第二种情况会麻烦一些,看如下代码:
LONG WINAPI UnhandledExceptionFilter1( struct _EXCEPTION_POINTERS* ExceptionInfo )
{
MessageBox(0,"UnhandledExceptionFilter1",0,0);
return 0;
}

int main(int, char*)
{
SetUnhandledExceptionFilter(UnhandledExceptionFilter1);
char *p=0;
*p=0;
return 0;
}
当应用程序被一个调试器attach之后,UnhandledExceptionFilter1不会被调用,从而程序可以通过这种逻辑来进行”反调试器跟踪“。
针对这种情况,可以通过下列办法让UnhandledExceptionFilter1执行,从而cheat应用程序:
0:000> bp kernel32!UnhandledExceptionFilter
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\WINDOWS\system32\kernel32.dll -
0:000> g
ModLoad: 76300000 7631d000   C:\WINDOWS\system32\IMM32.DLL
ModLoad: 77da0000 77e49000   C:\WINDOWS\system32\ADVAPI32.dll
ModLoad: 77e50000 77ee1000   C:\WINDOWS\system32\RPCRT4.dll
ModLoad: 62c20000 62c29000   C:\WINDOWS\system32\LPK.DLL
ModLoad: 73fa0000 7400b000   C:\WINDOWS\system32\USP10.dll
(4f4.314): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=7ffdf000 ecx=00007d06 edx=7c92eb94 esi=0012fe04 edi=0012ff0c
eip=0041155c esp=0012fe04 ebp=0012ff0c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010246
*** WARNING: Unable to verify checksum for tet.exe
tet!WinMain+0x3c:
0041155c c60000          mov     byte ptr [eax],0           ds:0023:00000000=??
0:000> bp Ntdll!NtQueryInformationProcess
0:000> g
Breakpoint 0 hit
eax=0012fa28 ebx=00000000 ecx=c0000005 edx=00000000 esi=00000000 edi=00000000
eip=7c862e62 esp=0012fa04 ebp=0012fff0 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
kernel32!UnhandledExceptionFilter:
7c862e62 6874060000      push    674h
0:000> g
Breakpoint 1 hit
eax=ffffffff ebx=00000004 ecx=7c862c02 edx=7c92eb94 esi=0012fa28 edi=c0000005
eip=7c92e01b esp=0012f358 ebp=0012fa00 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
ntdll!NtQueryInformationProcess:
7c92e01b b89a000000      mov     eax,9Ah
0:000> p
eax=0000009a ebx=00000004 ecx=7c862c02 edx=7c92eb94 esi=0012fa28 edi=c0000005
eip=7c92e020 esp=0012f358 ebp=0012fa00 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
ntdll!NtQueryInformationProcess+0x5:
7c92e020 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
0:000> p
eax=0000009a ebx=00000004 ecx=7c862c02 edx=7ffe0300 esi=0012fa28 edi=c0000005
eip=7c92e025 esp=0012f358 ebp=0012fa00 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
ntdll!NtQueryInformationProcess+0xa:
7c92e025 ff12            call    dword ptr [edx]      ds:0023:7ffe0300={ntdll!KiFastSystemCall (7c92eb8b)}
0:000> p
eax=00000000 ebx=00000004 ecx=0012f354 edx=7c92eb94 esi=0012fa28 edi=c0000005
eip=7c92e027 esp=0012f358 ebp=0012fa00 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
ntdll!NtQueryInformationProcess+0xc:
7c92e027 c21400          ret     14h
0:000> p
eax=00000000 ebx=00000004 ecx=0012f354 edx=7c92eb94 esi=0012fa28 edi=c0000005
eip=7c862eef esp=0012f370 ebp=0012fa00 iopl=0         nv up ei ng nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000286
kernel32!UnhandledExceptionFilter+0x8d:
7c862eef 85c0            test    eax,eax
0:000> r eax=80000000 <-----------------------------------
0:000> g
Breakpoint 1 hit
eax=0012f360 ebx=7c883780 ecx=00000000 edx=7c883780 esi=7c885ab4 edi=0012f8e4
eip=7c92e01b esp=0012f348 ebp=0012f364 iopl=0         nv up ei ng nz ac pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000296
ntdll!NtQueryInformationProcess:
7c92e01b b89a000000      mov     eax,9Ah
0:000> bc 1
0:000> g
ModLoad: 5adc0000 5adf7000   C:\WINDOWS\system32\uxtheme.dll
ModLoad: 74680000 746cb000   C:\WINDOWS\system32\MSCTF.dll
ModLoad: 10000000 10023000   C:\WINDOWS\system32\PROCHLP.DLL
ModLoad: 77bd0000 77bd8000   C:\WINDOWS\system32\version.dll
ModLoad: 73640000 7366e000   C:\WINDOWS\system32\msctfime.ime
ModLoad: 76990000 76acd000   C:\WINDOWS\system32\ole32.dll
ModLoad: 69760000 69776000   C:\WINDOWS\system32\faultrep.dll
ModLoad: 77bd0000 77bd8000   C:\WINDOWS\system32\VERSION.dll
ModLoad: 759d0000 75a7e000   C:\WINDOWS\system32\USERENV.dll
ModLoad: 762d0000 762e0000   C:\WINDOWS\system32\WINSTA.dll
ModLoad: 5fdd0000 5fe24000   C:\WINDOWS\system32\NETAPI32.dll
ModLoad: 76f20000 76f28000   C:\WINDOWS\system32\WTSAPI32.dll
ModLoad: 76060000 761b6000   C:\WINDOWS\system32\SETUPAPI.dll
ModLoad: 77f40000 77fb6000   C:\WINDOWS\system32\SHLWAPI.dll
ModLoad: 76d70000 76d92000   C:\WINDOWS\system32\apphelp.dll
ModLoad: 76d70000 76d92000   C:\WINDOWS\system32\Apphelp.dll
(4f4.314): Access violation - code c0000005 (!!! second chance !!!)
eax=00000000 ebx=7ffdf000 ecx=00007d06 edx=7c92eb94 esi=0012fe04 edi=0012ff0c
eip=0041155c esp=0012fe04 ebp=0012ff0c iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
tet!WinMain+0x3c:
0041155c c60000          mov     byte ptr [eax],0           ds:0023:00000000=??


HAVE fun:)

啊冲 2016-02-03 10:42
第二部分:接管流程
  在第一部分完成之后,已经重载了一份新的NT内核,接下来就是接管执行流程。
  自Windows XP以后从RING3进RING0统一调用sysenter汇编指令,但仍保留了INT 2E中断,在sysenter调用之后会执行内核空间中的KiFastCallEntry函数,这个函数负责所有的应用层请求,即使是INT 2E也会走这个函数,而KiFastCallEntry在经过简单的处理后会通过KeServiceDescriptorTable或者KeServiceDescriptorTableShadow来获取内核函数指针并调用,重载内核的目的就是为了在执行内核函数的时候转移到新的模块上,从而不被其他HOOK所影响。那么需要做的就是在调用内核函数的时候转移指令,但并不是所有内核函数都由ntoskrnl实现,内核中GUI函数却是由win32k.sys实现,由于目前只重载了ntoskrnl,所以只能做到转移SSDT中的函数。额外说明一下,即使NT内核有不同的版本,但所有NT内核的导出模块名称都是ntoskrnl.exe(详见输出表),所以我所说的ntoskrnl泛指NT内核,而不是具体的文件名。
  在KiFastCallEntry中调用内核函数的地方大家很容易找到资料,最先是谁提出的我记不清了,反正现在是被某出名软件所使用,下面HOOK的地方与其他软件可能会冲突,所以在测试的时候需要保证没有安装任何安全软件。
  用户态程序调用内核函数的流程简述为:用户态->ntdll.ZwApi->sysenter->内核态->KiFastCallEntry->ServiceTable->ServiceRoutine。ServiceTable分为两种,上面已经提到了,接下来要做的就是在调用SSDT中函数的时候转移到新模块上,在WRK中可以找到这样的代码:
代码:
        mov     esi, edx                ; (esi)->User arguments
        mov     ebx, [edi]+SdNumber     ; get argument table address
        xor     ecx, ecx
        mov     cl, byte ptr [ebx+eax]  ; (ecx) = argument size
        mov     edi, [edi]+SdBase       ; get service table address
        mov     ebx, [edi+eax*4]        ; (ebx)-> service routine
        sub     esp, ecx                ; allocate space for arguments
        shr     ecx, 2                  ; (ecx) = number of argument DWORDs
        mov     edi, esp                ; (edi)->location to receive 1st arg
        //省略部分代码
        call    ebx                     ; call system service
  显然,最后一句的call指令就是调用了内核函数,而需要hook的地方就是sub esp, ecx这一句,此时,edi指向ServiceTable,eax为函数索引,ebx为函数地址,而且这一句连同下面的两句指令共7字节,可以容纳一个JMP/CALL指令,绝佳的HOOK点。至于这个地址怎么定位,就要用字节码搜索了,实在没有什么好办法,但总比直接写地址或定偏移的那种硬编码好。
代码:
PVOID __declspec(naked) _GetKiFastCallEntryAddress()
{
  __asm
  {
    MOV  ECX, 0x00000176;
    RDMSR;
    RETN;
  }
}
PVOID FindHookKiFastCallEntryAddress(PVOID lpKiFastCallEntry)
{
  /*
  sub    esp, ecx
  shr    ecx, 2
  mov    edi, esp
  */
  UCHAR HookBytes[] = {0x2B, 0xE1, 0xC1, 0xE9, 0x02, 0x8B, 0xFC};

  return RtlFindMemory(lpKiFastCallEntry, 0x300, HookBytes, sizeof(HookBytes));
}
  通过MSR寄存器获取KiFastCallEntry的函数地址,有兴趣的朋友可以翻阅WRK中系统初始化部分的代码。HOOK函数这里采用了普通的MDL方式来写入只读内存,可能会有人问了,在写入地址的一瞬间别的CPU执行到了这里怎么办,我个人认为是杞人忧天,现在的CPU都有缓存机制,也就意味着内存和CPU缓存未必同步,在你写入的一瞬间即使别的CPU执行到了这里,那也是缓存中的代码,和内存中的并不一致,有兴趣的朋友可以查找CPU的TLB、TIB的资料。当然这只是降低了写入与执行冲突的几率,并不代表完全没有可能蓝屏,但话说回来,你要修改一个可执行指令,不可避免的有几率冲突,这是完全无法避免的,所以尽可能是降低几率就可以了。
代码:
VOID __declspec(naked) _MyKiFastCallEntryFrame()
{
  __asm
  {
    pop  edx;  //save ret address
    sub  esp, ecx;
    push edx;  //restore ret address
    mov  edx, dword ptr [edi + eax * 0x04];

    shr  ecx, 2;
    mov  edi, esp;
    add  edi, 0x04;
    retn;
  }
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT lpDriverObject, IN PUNICODE_STRING lpRegPath)
{
  PVOID lpHookKiFastCallEntryAddress;
  UCHAR HookCode[7];

  DbgPrint("Driver Load.\n");
  InitializePsLoadedModuleList(lpDriverObject);
  g_lpNewNtoskrnlAddress = ReloadNtModule(PsLoadedModuleList);
  lpHookKiFastCallEntryAddress = FindHookKiFastCallEntryAddress(GetKiFastCallEntryAddress());
  HookCode[0] = 0xE8;
  *(ULONG*)&HookCode[1] = (ULONG_PTR)_MyKiFastCallEntryFrame - (ULONG_PTR)lpHookKiFastCallEntryAddress - 5;
  HookCode[5] = 0x90;
  HookCode[6] = 0x90;
  RtlCopyMemoryEx(lpHookKiFastCallEntryAddress, HookCode, sizeof(HookCode));
  return STATUS_SUCCESS;
}
  编译并测试,通过ARK查看效果。注意!_MyKiFastCallEntryFrame是在Windows 7中测试的,XP下并不通用,因为执行上下文并不一样,XP中是mov ebx, dword ptr [edi + eax * 0x04]而Windows 7是mov edx, dword ptr [edi + eax * 0x04],这里我就不做兼容性写法了,手里也没有XP的虚拟机。XP已经停止服务了,相信没有多少人打算再使用了,如果非要兼容XP,自己做下简单的修改即可,即把edx换成ebx。
名称:  001.jpg
查看次数: 1
文件大小:  38.8 KB
  测试一段时间后并没有蓝屏现象,说明HOOK成功,但是目前并没有写任何过滤内容,下面就来丰富一下过滤函数,即所有的SSDT都走新内核。并继续考虑兼容XP。
代码:
PVOID ServiceCallFilter(PVOID *lppServiceTableBase, ULONG_PTR ServiceIndex)
{
  if (lppServiceTableBase == KeServiceDescriptorTable->ServiceTableBase)
  {
    if (ServiceIndex == 190)
    {
      DbgPrint("Call NtOpenProcess\n");
    }
    return (ULONG_PTR)lppServiceTableBase[ServiceIndex] - (ULONG_PTR)g_lpNtoskrnlAddress + (ULONG_PTR)g_lpNewNtoskrnlAddress;
  }
  return lppServiceTableBase[ServiceIndex];
}

VOID __declspec(naked) _MyKiFastCallEntryFrame()
{
  __asm
  {
    pop  ebx;  //save ret address
    sub  esp, ecx;
    push ebx;  //restore ret address

    push eax;
    push ecx;
    push edx;

    push eax;
    push edi;
    call ServiceCallFilter;
    mov  ebx, eax;

    pop  edx;
    pop  ecx;
    pop  eax;
    mov  edx, ebx;

    shr  ecx, 2;
    mov  edi, esp;
    add  edi, 0x04;
    retn;
  }
}
  由于仅测试效果,我就暂时写190,这是NtOpenProcess的函数索引。
名称:  002.jpg
查看次数: 0
文件大小:  43.1 KB
至此所有的SSDT中的函数已全部转移到新模块中。但这还只是一个半成品,仅做测试使用,因为后面还需要改动很多。

啊冲 2016-02-03 10:42
小结与修正
  目前已经实现了一份简单的重载内核代码,但是如果你也跟着我实现了此部分,会发现此代码根本不能使用,甚至不能拿到本机来测试,是的,新的内核还是有大量的问题,驱动加载后会导致一些程序打不开,
锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章