标 题: 【原创】重载内核全程分析笔记
作 者: Speeday
时 间: 2013-08-20,20:19:46
链 接: http://bbs.pediy.com/showthread.php?t=177555

还记得七夕的那几天,老V率先把AGP的源码发布出来,然后是EasyDebugger的源码出土,后面陆续有很多大牛把珍藏已久的代码拿出来晒太阳,那段疯狂的日子,让看雪论坛都面临崩溃的边缘。折腾了大半天,终于把代码都下载下来了,可是下载下来做什么呢,自己连看都看不懂。
于是,带着激动而又郁闷的心情继续学习内核编程。
由于是初学者,很多时候也有不清楚的地方,若下文中有什么地方理解有误,还请大牛多多指正,
文中所写代码参考自       
看雪论坛大牛  和   梦织未来论坛大牛  
好了,进入正题吧,继续看我的废话。

重载内核内容:
1、  将内核文件加载到内存
2、  进行基址重定位
3、  重定位ssdt结构
4、  Hook KiFastCallEntry,让RING3进程调用走新内核

下面一步一步的进行分析
1、  加载内核文件
我们要加载哪一个文件呢?答案是:ntkrnlpa.exe,我测试的系统的XP sp3,该文件所在目录为C:\WINDOWS\system32\ntkrnlpa.exe。加载文件要一部分一部分的加载,因为硬盘上的文件对齐方式与在内存中文件的对齐方式不同,所以如果你直接申请一块内存,然后把整个磁盘文件加载进去,那肯定是不对的,我们要按照PE结构一块块的进行加载。具体加载顺序是:IMAGE_DOS_HEADER,IMAGE_NT_HEADER,IMAGE_SECTION_HEADER,最后是把各区段的内容加载进去。加载过程中用到的内核函数有:打开文件:ZwCreateFile,初始化ZwCreateFile中的一个参数:IninializeObjectAttributes,读取文件内容:ZwReadFile,分配内存空间:ExAllocatePool,释放内存空间:ExFreePool,关闭句柄:ZwClose。
由于这段代码比较简单,又有很多地方是做重复性的工作,这里就不贴上来讲解了,具体请大家下载源码进行分析吧。如有不懂的请回帖交流。
这段代码写完了,有什么用呢?加载PE文件这事,不像写helloworld,写Helloworld,写完了一运行,至少能看到一句话吧,可是LoadPe这事,在哪去看效果呢?如果代码中有错误,我们怎么知道有没有正确运行,光凭代码中的Kdprint打印信息是不够的。既然是内核程序,我们就可以通过windbg来查看,方法是:
随便找一个不是ssdt的函数(ssdt后面再说),如:IoCreateFile,打开windbg,输入lkd>u IoCreateFile l a,会看到一段汇编代码,获取第一句汇编代码的地址,如我这里是0x8056cc7e,再用xuetr看到的模块加载地址是0x804D8000,偏移=0x8056cc7e-0x804D8000=0x94C7E;再将debugview上打印出来的我们新加载的内核的首地址找到,加上这个偏移,我得到的是0x85BB0000,用windbg查看 lkd>u 85C44C7E l a
效果如图: 

如果看到与图中相似的画面,也就是说和原来内核的代码几乎一样,那就说明加载内核文件成功了。
为什么说几乎一样呢?请看图中用红色线框标记的部分,原来的内核直接能够识别出一个全局变量名,而我们新载入的内核却只能看到一个数字。这是什么原因呢?要解决这个问题,就需要进行重载内核第二步,基址重定位。
2、  基址重定位
基址重定位的目的我们已经知道了,那具体该怎么做呢?微软在PE结构中已经有一个表定义了需要重定位的一些数据,就是重定位表,我们只需要修改里面的数据就可以了。现在的问题是如何修改。我们修改的目的是让新内核使用到正确的数据,来看看重定位表的数据结构:
typedef struct _IMAGE_BASE_RELOCATION {
    ULONG   VirtualAddress;
    ULONG   SizeOfBlock;
  USHORT  TypeOffset[1];
} IMAGE_BASE_RELOCATION;
第一个数据是相对地址,第二个数据是整个数据结构的大小,第三个数据就是要修改的地方了。
第三个数据是一个USHORT结构,包含两字节,高四位用于表示该数据的属性,我们这里要判断是否等于3,因为只有等于3的数据才是需要重定位的地址。我们将后12位取出来,再与新地址的首地址相加,得到一个实际的地址。得到这个实际地址后,这个地址的数据还并不是想要得到的数据,我们还要加上一个偏移,这个偏移就得用原内核的模块首地址减去imagebase,这样就可以正确定位了。
参考代码如下:

代码:
void FixBaseRelocTable(PVOID pNewImage)
{//将新内核地址作为一个PE文件头,依次向下,目的是寻找重定位表结构pImageDosHeader=(PIMAGE_DOS_HEADER)pNewImage;//定位到IMAGE_NT_HEADERpImageNtHeader=(PIMAGE_NT_HEADERS)((ULONG)pNewImage+pImageDosHeader->e_lfanew);//获取内核文件的imagebase,以便后面做偏移修改。OriginalImageBase=pImageNtHeader->OptionalHeader.ImageBase;//定位到数据目录ImageDataDirectory = pImageNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];//定位到重定位表结构pImageBaseRelocation = (PIMAGE_BASE_RELOCATION)(ImageDataDirectory.VirtualAddress + (ULONG)pNewImage);if (pImageBaseRelocation==NULL){return;  }while (pImageBaseRelocation->SizeOfBlock){   //计算需要修改的地址的个数uRelocTableSize=(pImageBaseRelocation->SizeOfBlock-8)/2;//循环遍历for (uIndex=0;uIndex<uRelocTableSize;uIndex++){//判断高4位是否等于3Type=pImageBaseRelocation->TypeOffset[uIndex]>>12;if (Type==3){//修改地址,相对地址加上一个新内核地址,使其成为一个实际地址uRelocAddress=(ULONG *)((ULONG)(pImageBaseRelocation->TypeOffset[uIndex]&0x0fff)+pImageBaseRelocation->VirtualAddress+(ULONG)pNewImage);//再加上内核首地址到imagebase的偏移*uRelocAddress=*uRelocAddress+(OrigImage-OriginalImageBase);}}//进行下一个重定位表的修改pImageBaseRelocation=(IMAGE_BASE_RELOCATION *)((ULONG)pImageBaseRelocation+pImageBaseRelocation->SizeOfBlock);}
}

这一步完成过后,我们又可以去内核里面看看了。按照上面所说的方法,如果正确修正基址后,结果如下图所示:
 
从图中可以看到,虽然是不同的地址,但是汇编代码却完全一样。这样,基址重定位就完成了。接下来就该进行ssdt重定位了。
3、  ssdt重定位
这个重定位相对于基址重定位来说就要简单得多了,因为PE结构的东西有点复杂(对于像我这样的新手来说)。
因为SSDT结构有多层,所以要分别进行运算,设置NewSSDT方法如下:
1、首先确定新SSDT在哪 个位置,因为用导出KeServiceDescriptorTable的方法是导出的老内核的SSDT结构,所以我们无法再用这个方法 。正确的方法是用原来的SSDT地址+相对加载地址。
2、如何得到KeServiceDescriptorTable.ServiceTableBase的地址?方法是用原来的(ServiceTableBase地址-老内核加载地址)+ 新内核加载地址。
3、修正SSDT函数中的地址。方法是在原来的函数地址上+ 相对加载地址。
相对加载地址=新内核加载地址 - 老内核加载地址

参考代码如下 :

代码:
VOID SetNewSSDT(PVOID pNewImage)
{ULONG              uIndex;ULONG              uNewKernelInc,uOffset;//新内核地址-老内核地址,得到相对偏移uNewKernelInc = (ULONG)pNewImage -OrigImage;//老内核的ssdt指针加上相对偏移,得到新内核的ssdt指针pNewSSDT = (ServiceDescriptorTableEntry_t *)((ULONG)&KeServiceDescriptorTable + uNewKernelInc);if (!MmIsAddressValid(pNewSSDT)){KdPrint(("pNewSSDT is unaviable!"));return;}//由于数量是一个数值,因此不必作相对偏移pNewSSDT->NumberOfServices = KeServiceDescriptorTable.NumberOfServices;//计算相对函数地址uOffset = (ULONG)KeServiceDescriptorTable.ServiceTableBase -OrigImage;//得到新的ssdt函数表地址pNewSSDT->ServiceTableBase = (unsigned int*)((ULONG)pNewImage + uOffset);if (!MmIsAddressValid(pNewSSDT->ServiceTableBase)){KdPrint(("pNewSSDT->ServiceTableBase: %X",pNewSSDT->ServiceTableBase));return;}//依次遍历for (uIndex = 0;uIndex<pNewSSDT->NumberOfServices;uIndex++){//新的函数地址再加上相对加载地址,得到现在的ssdt函数地址pNewSSDT->ServiceTableBase[uIndex] += uNewKernelInc;}
}

好了,这一步完成后,重载内核差不多就完成了,但是只是这样做,我们就什么事都没干,重载内核就这么无聊么?以前不是听说很多过游戏保护的方式都是重载内核么?是啊,做到这一步如果不继续往下学习,那么重载内核是一件多么无聊的事啊。所以我们继续进行下一步吧。
4、  HOOK KiFastCallEntry,让RING3进程调用走新内核
谈到Hook KiFastCallEntry是不是兴奋劲又来了,国内的360安全卫士就是hook 了这个函数,起到了很强大的作用。这里找hook点的方法我用的是堆栈回溯法,因为我现在也只会用这个方法。
Hook KiFastCallEntry要用到两个技术,一个是ssdt hook ,一个是inline hook。
我们先用ssdt hook 随便 hook 一个ssdt函数,然后当RING3下调用这个函数时,就会进入到我们定义的函数内,而在我们的函数中,就可以通过mov eax,[ebp+4],得到的eax值就在KiFastCallEntry函数的代码范围内了。具体原理如下:
这是一段取自KiFastCallEntry的汇编代码:

代码:
8053e71c 8b3f            mov     edi,dword ptr [edi]
8053e71e 8b1c87          mov     ebx,dword ptr [edi+eax*4]
8053e721 2be1            sub     esp,ecx
8053e723 c1e902          shr     ecx,2
8053e726 8bfc            mov     edi,esp
8053e728 3b35549b5580    cmp     esi,dword ptr [nt!MmUserProbeAddress (80559b54)]
8053e72e 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (8053e8dc)
8053e734 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
8053e736 ffd3            call    ebx
8053e738 8be5            mov     esp,ebp

其中:8053e736 ffd3            call    ebx这一句就是调用ssdt函数的call,而ebx就是ssdt中对应函数的地址。所以这里call进去,就会到达我们自己定义的函数内。而调用这个call ebx之前,会把下一条指令mov esp,ebp的地址放入堆栈中,便于调用完子程序后还能返回到程序的下一条指令,继续执行后面的代码。所以我们刚才说的mov eax,[ebp+4]就可以得到 这个栈中的地址。
下一步,我们就在KiFastCallEntry的代码里找一个合适的地方,用Inline hook 的方法,hook KiFastCallEntry吧。这里我们就用360软件hook 的地方吧,因为这个地方好处多多。
那么360到底hook 了哪 个点呢?答案就是:
8053e721 2be1            sub     esp,ecx
8053e723 c1e902          shr     ecx,2
这里就啰嗦几句吧。
我的电脑里没有装360,因此我hook 这个地方是没有问题的。昨天我也看了下,360目前仍然是hook的这个地方,因此如果你电脑里安装了360安全卫士,那么你再hook这个点,是不会成功的。
  关于ssdt hook 我之前发表过一篇文章,如果有不懂的可以去看看,为了论坛的文章质量,我这里就不重复叙述了。
新手学ssdt_hook
  讲讲Inline hook 吧。
  Inline hook 就是将想要hook 的地方用jmp 或者call指令修改,让它跳到自己的代码处,然后在自己的代码里面,执行完自己想要的功能后,再把这5个字节码还原。这里面涉及到的重点问题就是jmp_code的机器码获取。5个字节,第一个固定为指令jmp 是E9,call 是E8。。其它的自己查查看。后面四个字节该怎样获得呢,方法是:后四字节内容=新函数的地址-旧函数的地址-5。为什么要这样做呢?因为机器码跳转的位置不是通过硬编码得到的,而是通过计算两个位置间的距离,那为什么还要减5呢,因为计算两条指令之前的距离是在这条指令之后到下一条指令开始的距离,而jmp这类指令占5个字节,所以就减5,如果是je这种小跳转,那就是减2。
Inline hook 做好后,现在就该 在我们自己的函数里面做文章了。通过分析KiFastCallEntry 的汇编代码发现,360hook的那个地方简直是风水宝地,因为在那里即能得到ssdt的地址,还能得到ssdt地址总表,更能得到ssdt索引号。所以这个地方集天地之灵气,360现在都还舍不得离开,可见发现这个地方的人是怎样一位大牛。
好了,回到正题 上来吧。既然这个地方能够得到这么多有用信息,那我们怎样利用呢?
首先,我们通过PsGetCurrentProcess函数得到一个EPROCESS,再通过偏移得到当前进程的进程名,然后我们判断,如果这个进程名(假设我们这里要让CE走新内核)是cheatengine-i38,这里为什么我没把进程名写全呢?因为EPROCESS这个结构的这一项只能放这么多个字符,所以只有这样写才能够正确判断,如果想写全的话,可以把CE进程名改短点。得到这个进程名后,我们就进行判断,如果是CE的进程,我们就让它走新内核,怎样给它指路呢?只需要返回新内核对应的ssdt函数地址就行了。这里得到了函数索引号,所以这一步就完成了。
下一步就是在我们和程序返回之前,修改堆栈中的ebx的值,用什么值修改呢?当然是用上面返回的新内核的ssdt地址修改了,修改方法是mov [esp+0x14],eax。
这里大家估计有疑问了:为什么修改ebx?我们看看KiFastCallEntry的代码就知道了。
8053e734 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
8053e736 ffd3            call    ebx
8053e738 8be5            mov     esp,ebp
看到了吧,因为下面call 的寄存器就是ebx,但是这并不是一层不变的,呵呵,别担心,一层不变不是说每一次开机后这个就会变,变是地方是xp和win7系统的变化,在xp中,是ebx,在win7中就是edx了,所以如果想把本代码放在win7上用,这里是必须要改的。
好了,废话也说了这么多了,再来看看代码吧。

代码:
ULONG display(ULONG ServiceTableBase,ULONG FuncIndex,ULONG OrigFuncAddress)
{if (ServiceTableBase==(ULONG)KeServiceDescriptorTable.ServiceTableBase){//比较当前调用的进程是不是ceif (!strcmp((char*)PsGetCurrentProcess()+0x174,"cheatengine-i38")){return pNewSSDT->ServiceTableBase[FuncIndex];}}return OrigFuncAddress;
}
__declspec(naked)void MyKiFastCallEntry()
{__asm{pushadpushfdpush  ebxpush  eaxpush  edicall  display//再返回前修改堆栈里的数据mov    [esp+0x14],eaxpopfdpopad//恢复以前的代码,以便内核正常运行sub esp,ecxshr ecx,2jmp jmp_ret}
}void hook_KiFastCallEntry()
{UCHAR jmp_code[5];jmp_code[0]=0xe9;//计算jmp_code*(ULONG *)&jmp_code[1]=(ULONG)MyKiFastCallEntry-5-addr_hookaddr;//计算返回jmpjmp_ret = addr_hookaddr + 5;PageProtectOff();//inline hookRtlCopyMemory((PVOID)addr_hookaddr,jmp_code,5);PageProtectOn();
}

到这里,重载内核的内容和目的我们都达成了,来个图看看效果。
 
上面的图片我加载了两个驱动程序,一个是ssdt hook,一个是ReloadKernel,
通过上图可以发现OD走的老内核,由于NtOpenProcess函数被hook了,所以进程列表中连计算器的进程都找不到,而CE走的新内核,不仅能找到,还能正常打开进程,并进行数据搜索功能。
结束了,送一句结束语吧。
写驱动,犹如走夜路,步步惊心,或许下一秒,电脑就蓝了!

重载内核全程分析笔记相关推荐

  1. Windows内核情景分析 笔记

    803页:WDK文档强调IoRegisterDriverReinitialization 主要用于同时支持Non-PNP和PNP下层的驱动.大概原因是:只依赖Legacy下层的驱动可以通过LoadOr ...

  2. Linux内核源代码情景分析笔记

    Linux内核源代码情景分析笔记 好吧,首先我承认我要是读者的话,这篇文章我看着也头疼,因为写的太长太泛(其主要部分集中在内存管理,进程管理,文件系统)!原本是想按自己理解的精简精简的,按照操作系统中 ...

  3. 一份内核重载代码的学习笔记

    0x00 前言 参考文章: https://blog.csdn.net/whatday/article/details/14160875 https://blog.csdn.net/pjz969/ar ...

  4. linux内核源码分析笔记

    一.内核源码目录结构 1.Linux 内核源代码包括三个主要部分 1)内核核心代码:包括linux内核整体架构分析笔记描述的各子系统和子模块,以及其他支撑子系统,如:电源管理.linux初始化等. 2 ...

  5. 《Linux内核情景分析》阅读笔记

    <Linux内核情景分析>这本书读过了一遍,不想继续读第二遍了. <Linux Kernel Development>这本书前后读了3遍,写得实在是好,正所谓"布衣暖 ...

  6. windows内核开发学习笔记十七:IRP 和 IO_STACK_LOCATION 的交互

    windows内核开发学习笔记十七:IRP 和 IO_STACK_LOCATION 的交互 前面两篇学习笔记分别介绍了IRP和IO_STACK_LOCATION,整个设备栈来处理这个IRP,但是每个设 ...

  7. 深入MTK平台bootloader启动之【 lk -> kernel】分析笔记

    接上一篇分析: <深入MTK平台bootloader启动之[ Pre-loader -> Lk]分析笔记> Pre-loader 运行在ISRAM,待完成 DRAM 的初始化后,再将 ...

  8. linux input系统的分析笔记(一)

    linux input系统的分析笔记(一) 我的学习的思路是:知其然,然后再 知其所以然. 我要得是看得到的结果和现象,然后再想办法改变和理解它的原理. 在android的shell下有个好用的工具: ...

  9. 报文分析笔记---常见wireshark报文标记

    文章目录 报文分析笔记---常见wireshark报文标记 Fragmented IP protocol Packet size limited during capture TCP Previous ...

最新文章

  1. 使用GitHub免费搭建属于自己的网站
  2. php中public放什么,PHP中常用关键字public, private, protected, static...
  3. 【PAT乙级】1064 朋友数 (20 分)
  4. 如何使用ABAP把数字转换成单词
  5. python value函数_python 函数基础
  6. vos限制客户呼出时间
  7. Windows 2012 - Dynamic Access Control 浅析
  8. 华为交换机配置syslog发送_华为/H3C Syslog配置
  9. python 知乎 合并 pdf_32.使用selenium爬取知乎,并实现多页保存为一个PDF文件
  10. Nginx 场景应用
  11. FusionChartsFree在JSP中的用法
  12. 计算机网络专业名称解释
  13. 谷歌浏览器弹出Chrome版本太旧解决方式
  14. w ndows摄像头驱动怎么安,如何安装摄像头驱动?求安装步骤和方法!!!
  15. unity 5.x android发布注意事项
  16. Elastic:关于索引生命周期ILM的一些试验;warm,cold,delete节点中min_age设置的是增量还是累计值
  17. Java之美[从菜鸟到高手演变]之Java学习方法
  18. android平板2018,2018 Android平板电脑推荐三星或华为更好
  19. 智安网络丨浅谈信息系统终止时如何确保信息资产的安全
  20. 2018-2019第一学期Java助教心得

热门文章

  1. Java关键字(四)——final
  2. 线下生意再次“受宠”:大数据给你添点料
  3. Struts2如何实现MVC,与Spring MVC有什么不同?
  4. 世界最成功的僵尸网络使用Fast Flux技术躲避检测
  5. Oracle笔记(中) 多表查询
  6. C++下的DLL编程入门
  7. C#类方法中使用数组参数params关键字的作用
  8. 说说如何搭建 Nginx 反向代理 Tomcat
  9. 阿里centos7 yum源
  10. MySQL字符集的一个坑