Windows内核实验003 再次回到中断


之前的实验我们已经实现了从三环到零环的提权,但是提权不代表能正常调用内核函数。接下来我们要实现的一个事情就是在我们的代码里正常调用内核的函数。

还是接着用上次的代码,这一次我们先来做两个实验。

两个实验

死循环

首先用windbg连上虚拟机

在这里插入图片描述

在我们的内核提权函数IdtEntry中写上一个死循环,接着用下面这条命令将当前函数地址写入到IDT表中

eq 80b95500 0040ee00`00081040

然后运行程序

在这里插入图片描述

会发现整个虚拟机直接卡死,连windbg也无法接管控制权。这是因为单核的CPU禁止中断时,系统无法中断内核代码的任何操作,导致函数中的死循环一直在运行,进而导致虚拟机卡死现象。

开启中断后的死循环

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGPIOwjt-1573888072291)(assets/1573879516464.png)]

接着我们在代码中加入一条sti指令来开启中断,再次运行程序

在这里插入图片描述

我们发现此时虚拟机直接蓝屏。原因在于开启中断以后产生了时钟的硬件中断,受线程切换的影响产生蓝屏。

KiFastCallEntry

在XP系统中,系统服务在内核的入口是KiFastCallEntry。我们来大概分析一下这个函数和我们缩写的内核代码有什么区别。

首先在XP虚拟机中打开PC Hunter
在这里插入图片描述

接着打开驱动模块,找到第一个ntkrnlpa.exe,右键定位到驱动模块,找到exe文件,并用IDA分析,记得IDA分析的时候下载符号文件。

在这里插入图片描述

在IDA中搜索KiFastCallEntry这个函数

在这里插入图片描述

接着我们观察到这个函数中间也有一条sti指令,也做了一个开中断的操作。开启中断以后后面的代码也会受到线程调度的影响。

那么问题来了,为什么KiFastCallEntry开启中断以后没有蓝屏,我们自己写的代码反而蓝屏了呢?

在这里插入图片描述

这就说明这个函数在一开始到开启中断之间建立了一个安全的线程调度环境,那如果在我们的代码里建立一个安全的线程调度环境,那么我们开中断也是安全的。开启中断以后我们就可以在我们的代码里通过系统调用号来调用零环的API函数了。

调用零环API的两个条件

  1. 有内核权限
  2. 有安全的线程调度环境

分析KiFastCallEntry

第一个条件我们已经完成了,接下来我们通过分析KiFastCallEntry这个函数来达到第二个条件。

在这里插入图片描述

在KiFastCallEntry中,比较重要的代码是上面两句。这两句含义是将0x30赋值给了fs段选择子。

接下来拆解一下0x30这个段选择子所代表的含义

  • 0x30=0000000000110 0 00‬
  • 0000000000110代表查GDT表下标为6的项
  • 0代表查GDT表
  • 00代表0环权限

接下来在windbg中查看GDT表

在这里插入图片描述

第六项也就是80b95030的这个位置,这个位置是一个叫KPCR的结构

什么是KPCR

KPCR即Kernel Processor Control Region,处理器控制区域。因为Windows是需要支持多个CPU的,所以为每一个CPU在内核空间安排一个KPCR的结构。用来保存与线程切换相关的全局信息,里面有我们之前说过的TSS。

ring3下,fs寄存器的指向TEB,ring0下,fs寄存器指向KPCR

也就是说push 0x30;pop fs这两条指令的目的是为了让FS寄存器指向KPCR,使线程切换相关的操作能够正常执行。

在这里插入图片描述

完善代码

我们之前写的代码开启了中断,接着蓝屏的原因是因为没有对fs进行正确的设置。我们在开中断之前只需要做一个和KiFastCall一样的操作就能建立了安全的线程调度环境。

代码修改如下:

void __declspec(naked) IdtEntry()
{
	__asm
	{
		push 0x30;
		pop fs;
		sti;
L:      
		jmp L;
		iretd;
	}
} 

修改完成以后编译运行,
在这里插入图片描述

可以看到现在的情况是程序可以正常运行,虚拟机没有卡死也没有蓝屏,和之前的结果完全不一样。在开启任务管理器,观察我们这个进程

在这里插入图片描述

现在这个程序只是一个占用了几乎百分之百的CPU的一个进程,其他的程序还有机会得到执行,因为已经设置好了线程调度环境,允许其他线程访问计算机资源。

而且现在这个进程不管怎么样都结束不掉。原因在于操作系统结束进程的时机是在0环返回3环的瞬间,而我们的程序现在在0环死循环了,没有机会返回到3环,自然也就无法结束了。

完整代码

最后附上完整代码

#include "pch.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

DWORD g_tmp;

void __declspec(naked) IdtEntry()
{
	__asm
	{
		push 0x30;
		pop fs;
		sti;
L:      
		jmp L;
		iretd;
	}
} 

void go()
{
	__asm int 0x20;
}

//eq 80b95500 0040ee00`00081040
int main()
{
	if ((DWORD)IdtEntry != 0x401040)
	{
		printf("wrong addr:%p", IdtEntry);
		exit(-1);
	}
	go();
	printf("%p", g_tmp);
	system("pause");
}
发布了99 篇原创文章 · 获赞 89 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/qq_38474570/article/details/103098837