欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]重载内核之二接管流程

[原创]重载内核之二接管流程

  重载内核的相关文章实在是太多了,鉴于还是有很多初学者研究这一块,本文仅作为一个引导作用,文笔不好,见谅。
  我的博客:http://blog.csdn.net/sidyhe
  开发环境:VS2010 + WinDDK
  测试环境:VirtualDDK + VMware + Win7 sp1 x86
  第一部分http://bbs.pediy.com/showthread.php?t=187863
第二部分:接管流程
  在第一部分完成之后,已经重载了一份新的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。

  测试一段时间后并没有蓝屏现象,说明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的函数索引。

至此所有的SSDT中的函数已全部转移到新模块中。但这还只是一个半成品,仅做测试使用,因为后面还需要改动很多。

上传的附件 001.jpg
002.jpg
2.重载内核之接管流程.doc

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功