首页
社区
课程
招聘
[原创]重载内核之一重载镜像
发表于: 2014-5-16 01:37 24396

[原创]重载内核之一重载镜像

2014-5-16 01:37
24396
系列文章

 重载内核的相关文章实在是太多了,鉴于还是有很多初学者研究这一块,本文仅作为一个引导作用,文笔不好,见谅。
  我的博客:http://blog.csdn.net/sidyhe
  开发环境:VS2010 + WinDDK
  测试环境:VirtualDDK + VMware + Win7 sp1 x86
  第一部分链接:http://bbs.pediy.com/showthread.php?t=187863
  第二部分链接:http://bbs.pediy.com/showthread.php?t=187919
  第三部分链接:http://bbs.pediy.com/showthread.php?t=187982
  第四部分链接:http://bbs.pediy.com/showthread.php?t=188012
  第五部分链接:http://bbs.pediy.com/showthread.php?t=188050
  第六部分链接:http://bbs.pediy.com/showthread.php?t=188092


  重载内核的相关文章实在是太多了,鉴于还是有很多初学者研究这一块,本文仅作为一个引导作用,文笔不好,见谅。
  我的博客:http://blog.csdn.net/sidyhe
  开发环境:VS2010 + WinDDK
  测试环境:VirtualDDK + VMware + Windows 7 sp1 x86
第一部分:重载镜像
  大家可以通过ARK工具来查看系统的内核模块,排在首位的一定是ntXXX.exe这个模块,这个模块就是自系统启动后加载的第一个模块,根据CPU及其不同特性的不同会加载不同的NT内核,如NTOSKRNL.EXE、NTKRNLMP.EXE、NTKRNLPA.EXE、NTKRPAMP.EXE。不同的NT模块代表不同的意义,如单CPU,多CPU,单CPU多核,是否支持PAE等都会影响所加载的NT内核,所以如果大家见到和别人不同的NT内核不要奇怪,那是因为你的CPU和别人的不一样。
  本次技术研究仅仅是针对于x86系统的Windows 7以及Windows XP,Windows x64系统由于强制数字签名以及PatchGuard技术无法实现,故不做讨论,当然如果你有办法解决这两个问题就另当别论了。至于是否兼容Windows 8 x86就有待各位验证了。
  既然要重载内核,肯定是NT内核(废话),上面说到了系统可能会加载不同名字的NT内核,那么就需要一些方法来确定当前系统所使用的内核,其中一个方法是使用ZwQuerySystemInformation传递SystemModuleInformation参数,不过在这里我不打算使用这个方法,因为太麻烦。我使用PsLoadedModuleList来确定NT内核,那问题来了,PsLoadedModuleList是一个未导出变量,这个变量记录了当前系统内核模块的信息,ZwQuerySystemInformation就是访问了PsLoadedModuleList来生成结果,如何定位这个东西呢?我不喜欢硬编码,所以我需要一种在不同系统上通用的方式来获取这个变量,经过收集资料发现在DriverEntry被调用时,第一个参数PDRIVER_OBJECT的PDRIVER_EXTENSION成员其实就是一个LDR_DATA_TABLE_ENTRY指针(参考WRK),这个与PsLoadedModuleList的类型是一致的,也就是说lpDriverObject->DriverSection是PsLoadedModuleList这个双向链表的其中一个节点,而PsLoadedModuleList是这个链表的头节点,根据大量的实践证明,lpDriverObject->DriverSection节点的下一个节点一定是PsLoadedModuleList,因为是双向循环链表嘛,那么定位这个东西就非常简单了,代码如下。
PLDR_DATA_TABLE_ENTRY PsLoadedModuleList = NULL;

VOID InitializePsLoadedModuleList(PDRIVER_OBJECT lpDriverObject)
{
  PLDR_DATA_TABLE_ENTRY ldr = (PLDR_DATA_TABLE_ENTRY)lpDriverObject->DriverSection;

  PsLoadedModuleList = (PLDR_DATA_TABLE_ENTRY)ldr->InLoadOrderLinks.Flink;
  return;
}

  找到了PsLoadedModuleList,那么链表的第一个节点就是NT内核了,可以取得文件路径,解决了重载内核的第一个问题。
  接下来就是读文件数据,并把数据部署为镜像。部署的过程与RING3的镜像一致,不熟悉的朋友可以去恶补一下PE知识。读到文件数据后,把数据部署为镜像的核心代码如下:
PVOID ReloadNtModule(PLDR_DATA_TABLE_ENTRY PsLoadedModuleList)
{
  PVOID lpImageAddress = NULL;
  PLDR_DATA_TABLE_ENTRY NtLdr = (PLDR_DATA_TABLE_ENTRY)PsLoadedModuleList->InLoadOrderLinks.Flink;
  PVOID lpFileBuffer;

  DbgPrint("Nt Module File is %wZ\n", &NtLdr->FullDllName);
  if (lpFileBuffer = KeGetFileBuffer(&NtLdr->FullDllName))
  {
    PIMAGE_DOS_HEADER lpDosHeader = (PIMAGE_DOS_HEADER)lpFileBuffer;
    PIMAGE_NT_HEADERS lpNtHeader = (PIMAGE_NT_HEADERS)((PCHAR)lpDosHeader + lpDosHeader->e_lfanew);

    if (lpImageAddress = ExAllocatePool(NonPagedPool, lpNtHeader->OptionalHeader.SizeOfImage))
    {
      PUCHAR lpImageBytes = (PUCHAR)lpImageAddress;
      IMAGE_SECTION_HEADER *lpSection = IMAGE_FIRST_SECTION(lpNtHeader);
      ULONG i;

      RtlZeroMemory(lpImageAddress, lpNtHeader->OptionalHeader.SizeOfImage);
      RtlCopyMemory(lpImageBytes, lpFileBuffer, lpNtHeader->OptionalHeader.SizeOfHeaders);
      for (i = 0; i < lpNtHeader->FileHeader.NumberOfSections; i++)
      {
        RtlCopyMemory(lpImageBytes + lpSection[i].VirtualAddress, (PCHAR)lpFileBuffer + lpSection[i].PointerToRawData, lpSection[i].SizeOfRawData);
      }
      //代码不完整,后续补充
    }
    ExFreePool(lpFileBuffer);
  }
  if (lpImageAddress) DbgPrint("ImageAddress:0x%p\n", lpImageAddress);
  return lpImageAddress;
}

  至此解决了第二个问题,镜像已具基本雏形,了解的朋友一定知道下一步就是修复镜像了,即处理重定位以及输入表(导入表)。修复输入表没什么,难就难在重定位,重定位中包含了代码重定位和变量重定位,既然我们做的是重载内核,那么肯定是需要让原本走NT模块的流程转移到我们的新模块上,那么可以肯定的是代码重定位一定要在新模块上,至于变量,我个人的做法是指向原模块,因为即使是重载内核,也不能保证所有执行单元都会走新模块,这样保险一些,也简单一些,不过需要注意的是,变量重定位也包含IAT,所以我这里把IAT也指向新模块,否则修复输入表就没意义了,也可以防范IAT HOOK。还有,如果重定位的地方属于“可废弃”的区段(节),可以不用处理,因为原模块已经废弃了。还有还有,内核模块的导入表不存在序号导入,所以处理起来更加简单。
BOOLEAN KeFixIAT(PLDR_DATA_TABLE_ENTRY PsLoadedModuleList, PVOID lpImageAddress)
{
  IMAGE_DOS_HEADER *lpDosHeader = (IMAGE_DOS_HEADER*)lpImageAddress;
  IMAGE_NT_HEADERS *lpNtHeader = (IMAGE_NT_HEADERS *)((PCHAR)lpDosHeader + lpDosHeader->e_lfanew);
  PIMAGE_IMPORT_DESCRIPTOR lpImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress + (ULONG)lpImageAddress);
  PVOID lpModuleAddress;

  while (lpImportDescriptor->Characteristics)
  {
    if (lpModuleAddress = KeGetModuleHandle(PsLoadedModuleList, (PCHAR)lpImageAddress + lpImportDescriptor->Name))
    {
      PIMAGE_THUNK_DATA lpThunk = (PIMAGE_THUNK_DATA)((ULONG)lpImageAddress + lpImportDescriptor->OriginalFirstThunk);
      PVOID *lpFuncTable = (PVOID*)((ULONG)lpImageAddress + lpImportDescriptor->FirstThunk);
      ULONG i;

      for (i = 0; lpThunk->u1.Ordinal; i++)
      {
        if ((lpThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) == 0)
        {
          PIMAGE_IMPORT_BY_NAME lpName = (PIMAGE_IMPORT_BY_NAME)((PCHAR)lpImageAddress + lpThunk->u1.AddressOfData);
          PVOID lpFunc;

          if (lpFunc = KeGetProcAddress(lpModuleAddress, lpName->Name))
          {
            lpFuncTable[i] = lpFunc;
          }
          else
          {
            DbgPrint("KeFixImageImportTable:Cannot found function : %s\n", lpName->Name);
            return FALSE;
          }
        }
        else
        {
          //impossible
        }
        lpThunk++;
      }
    }
    else
    {
      DbgPrint("KeFixImageImportTable:Cannot found Module : %s\n", (PCHAR)lpImageAddress + lpImportDescriptor->Name);
      return FALSE;
    }
    lpImportDescriptor++;
  }
  return TRUE;
}

  下面是处理重定位的代码,相对比较复杂了,这里只贴出来核心代码,即如何处理具体重定位地址的部分。
VOID KeFixRelocEx(PVOID New, PVOID Old, PVOID *lpFixAddress)
{
  IMAGE_DOS_HEADER *lpDosHeader = (IMAGE_DOS_HEADER*)New;
  IMAGE_NT_HEADERS *lpNtHeader = (IMAGE_NT_HEADERS *)((PCHAR)lpDosHeader + lpDosHeader->e_lfanew);
  ULONG_PTR RelocValue = (ULONG_PTR)*lpFixAddress - lpNtHeader->OptionalHeader.ImageBase;

  if (KeFixRelocOfCheckIAT(New, (PCHAR)New + RelocValue))
  {
    *lpFixAddress = (PCHAR)New + RelocValue;
    return;
  }
  else
  {
    IMAGE_SECTION_HEADER *lpSecHdr = IMAGE_FIRST_SECTION(lpNtHeader);
    USHORT i;

    for (i = 0; i < lpNtHeader->FileHeader.NumberOfSections; i++)
    {
      if (RelocValue >= lpSecHdr[i].VirtualAddress && RelocValue < lpSecHdr[i].VirtualAddress + lpSecHdr[i].SizeOfRawData)
      {
        if (lpSecHdr[i].Characteristics & IMAGE_SCN_MEM_WRITE)
        {
          *lpFixAddress = (PCHAR)Old + RelocValue;
        }
        else
        {
          *lpFixAddress = (PCHAR)New + RelocValue;
        }
        return;
      }
    }
  }
  *lpFixAddress = (PCHAR)Old + RelocValue;
  return;
}

  至此,重新加载一份新的NT内核已经完成了绝大部分,还有一些细节没有处理,等到后面遇到时再告诉各位看客老爷,先卖个关子吧,涉及到的未公开结构可以从WRK中寻找,我所使用的结构在目前的x86系统中都没有改动,所以通用。具体工程代码先不打算放出来,如果大部分朋友需要的话,我会在后续文章中放出,不过我还是希望大家能够自己动手,这样收获会比纯粹的复制粘贴更多。
  下面的内容应该是HOOK了,用来接管正常的执行流程,我会抽时间写后续内容的。

[课程]FART 脱壳王!加量不加价!FART作者讲授!

上传的附件:
收藏
免费 6
支持
分享
最新回复 (22)
雪    币: 114
活跃值: (180)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
谢谢楼主分享。睡觉~~~
2014-5-16 01:58
0
雪    币: 459
活跃值: (344)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
3
收藏,百分百用得到。楼主好活
2014-5-16 09:13
0
雪    币: 341
活跃值: (138)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
4
标记一下
2014-5-16 09:48
0
雪    币: 149
活跃值: (2618)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
mark一下啊  楼主好人
2014-5-16 10:24
0
雪    币: 7752
活跃值: (2144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
看源码,推敲学习。
2014-5-17 12:25
0
雪    币: 8865
活跃值: (2379)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
7
good job~
2014-5-17 12:41
0
雪    币: 2664
活跃值: (3401)
能力值: ( LV13,RANK:1760 )
在线值:
发帖
回帖
粉丝
8
总结的不错...
2014-5-17 12:51
0
雪    币: 77
活跃值: (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
mark
2014-5-19 11:35
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jmp
10
mark
2014-5-19 22:54
0
雪    币: 219
活跃值: (738)
能力值: (RANK:290 )
在线值:
发帖
回帖
粉丝
11
mark  job
2014-5-19 22:55
0
雪    币: 44229
活跃值: (19955)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
12
将你所有系列当成一个“酷帖”来设置。

感谢你的分享!
2014-5-31 22:07
0
雪    币: 104
活跃值: (27)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
写得简洁易懂,赞一个!
2015-4-7 09:27
0
雪    币: 112
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
第一个参数PDRIVER_OBJECT的PDRIVER_EXTENSION成员其实就是一个LDR_DATA_TABLE_ENTRY指针(参考WRK),这个与PsLoadedModuleList的类型是一致的

楼主笔误了,DriverSection
2016-3-29 19:56
0
雪    币: 3700
活跃值: (3817)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
15
感谢分享!
2017-7-9 19:37
0
雪    币: 302
活跃值: (246)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
16
请问楼主  KeGetModuleHandle这个函数是你自己定义的还是ntddk的啊    为什么我这边显示找不到这个函数呢
2017-12-13 00:55
0
雪    币: 2143
活跃值: (720)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
17
一二三六 请问楼主 KeGetModuleHandle这个函数是你自己定义的还是ntddk的啊 为什么我这边显示找不到这个函数呢
自己定义的,  就是遍历PsLoadedModuleList来确定模块的加载地址.
2017-12-14 01:20
0
雪    币: 310
活跃值: (2227)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
mark
2017-12-14 08:05
0
雪    币: 302
活跃值: (246)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
19
sidyhe 自己定义的, 就是遍历PsLoadedModuleList来确定模块的加载地址.
你的代码是复制的原始内核的内存如果原内核有SSDT  HOOK  你不就一并复制过来了吗
2017-12-30 18:43
0
雪    币: 350
活跃值: (87)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
20
一二三六 你的代码是复制的原始内核的内存如果原内核有SSDT HOOK 你不就一并复制过来了吗
不止SSDT  HOOK,还有设备堆栈呢?还有IDT呢?还有错误处理函数呢?内核中驱动程序注册的东西,你全部都得复制过来!重载内核不只需要重载重定位表那么简单的!
2017-12-30 19:47
0
雪    币: 350
活跃值: (87)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
21
czcqq 不止SSDT HOOK,还有设备堆栈呢?还有IDT呢?还有错误处理函数呢?内核中驱动程序注册的东西,你全部都得复制过来!重载内核不只需要重载重定位表那么简单的!
我能记得的重要结构有:SSDT  ,IDT,设备堆栈,各种回掉堆栈,还有线程分配的三个链表(我忘了名字了),还有内核申请的各种内存的指针,分页内存池,非分页内存池,DPC回掉链表,APC回掉链表,线程堆栈,进程句柄表,内存映射表等,你都要复制到你的新内核,你把复制内核想太简单了,内核的模块的重写加载,我时做过的!我举一个例子:比如ZwCreateFile函数,这个函数的调用流程是ZwCreateFile->NtCreateFile->IoCreateFile->ObCreateObject->取得设备堆栈顶部的设备->创建IRP,初始化IRP调用堆栈->调用IoCallDriver来查询设备堆栈,查找下一层设备然后调用->调用完成之后通知完成函数->完成函数中设置一个标志,然后正式返回->按调用流程依次返回,你可以看到这个过程调用了多少的指针(几乎全是动态申请的指针),你的新内核,得全部复制这些东西!你的这个实现有点简单了,仅仅修复IAT,和重定位而已!内核数据结构,你一个也没有复制啊
2017-12-30 20:17
1
雪    币: 965
活跃值: (89)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
标记一下
2018-1-23 12:31
0
雪    币: 3700
活跃值: (3817)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
23
感谢分享!
2019-7-23 10:38
0
游客
登录 | 注册 方可回帖
返回
//