本篇文章基于周壑老师的讲解,感谢周壑老师。
实验环境
- windbg双机调试环境
- VS开发环境
- 32位 WIN7 虚拟机
内核提权
内核态的程序拥有一切权限,在Windows操作系统上,没有任何其他软件可以限制内核态的程序。如果让一个用户层的小程序,提升至内核权限,那么就可以操作系统上的一切资源,做任何想做的事。今天我们借助一个内核实验来完成用户层到内核层的提权。
IDT的基本知识
什么是中断
当出现需要时,CPU暂时停止当前程序的执行转而执行处理新情况的程序和执行过程。即在程序运行过程中,系统出现了一个必须由CPU立即处理的情况。此时,CPU暂时终止程序的运行转而去处理这个情况的过程就叫中断
什么是IDT表
常见的中断有 除零(0号中断)、断点(3号中断)、系统调用(2e号中断), 每一种中断对应一个中断号。每一个中断号都有对应的中断处理函数。
这样操作系统就会用数据结构来维护这些中断处理函数,这个数据结构就是IDT 表。
IDT表全称:Interrupt Descriptor Table,中断描述符表。
当CPU产生中断时,就会去查IDT表,然后执行IDT表中对应的中断处理函数
在PC Hunter中查看IDT表
打开PC Hunter,点击内核钩子->系统中断表
我们可以看到当前系统的IDT表,其中序号代表的是中断号 函数名称指的是产生该序号中断时会执行的函数,后面则是对应的函数地址。
例如当CPU遇到除零异常时,就会抛出0号中断,此时查IDT表,找到对应的处理函数Divide error,然后跳转到函数地址处理除零异常。
中断提权的基本原理
IDT表是系统用于处理中断的,自然就有最高的内核权限。中断提权的基本原理实际上就是将我们自己的函数地址,去替换掉IDT表中的函数地址。进而抛出一个中断,让系统跳转到我们自己的函数。
那么替换掉哪个函数呢?还是回到PC Hunter,IDT表的位置往下拉
从序号0x20开始有一部分未被使用的IDT表,我们就将自己的函数地址替换到0x20的位置即可
写一个三环的小程序
基本代码如下
#include "pch.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
void __declspec(naked) IdtEntry()
{
__asm
{
iretd;
}
}
int main()
{
//0x401040为IdtEntry的函数地址
if ((DWORD)IdtEntry!=0x401040)
{
printf("wrong addr:%p", IdtEntry);
exit(-1);
}
printf("%p", IdtEntry);
system("pause");
}
我们自己写一个裸函数,然后将地址打印出来。为了方便后面的调试
需要将属性设置为release,并且去除随机基址,改用固定基址。
修改IDT表
在配置好双机调试环境以后,用windbg查看IDT表的基址,IDT表的基址保存在idtr寄存器中
接着用dq命令查看IDT表,每8位对应一个IDT表的处理函数
对比PC Hunter中的函数地址可以发现,IDT表中的函数地址实际上是由IDT地址表中的前四位和后四位拼接而成。
接着我们找到第一个值为00000000的位置,也就是序号为0x20的位置写入我们自己的函数
kd> eq 80b95500 00408e00`00081040
接着将8e修改为ee(8e对应的是DPL描述符特权级 修改以后就能让用户层的程序访问了)
kd> eq 80b95500 0040ee00`00081040
提权测试
现在我们还需要让程序产生一个中断序号为0x20的中断,从而跳转到我们的函数中。那么这个怎么实现呢。只需要加上这样一条代码
void go()
{
__asm int 0x20;
}
任何一个中断都能用int [index]的方式进入。
为了观察是否提权成功,我们在函数中读取一个内核的地址,我选择的是IDT表的基址
mov eax,dword ptr ds: [0x80b95400];
mov g_tmp, eax;
最后在主函数中将这个地址打印出来。最后编译exe,在虚拟机中运行
可以看到程序已经打印出了IDT表的基址后半部分0x8CFC0
此时刷新一下PC Hunter,在0x20的位置 函数地址也显示为我们自己设置的0x401040。
到此,本次实验结束。最后附上完整代码
#include "pch.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
DWORD g_tmp;
void __declspec(naked) IdtEntry()
{
__asm
{
mov eax,dword ptr ds: [0x80b95400];
mov g_tmp, eax;
iretd;
}
}
void go()
{
__asm int 0x20;
}
int main()
{
if ((DWORD)IdtEntry != 0x401040)
{
printf("wrong addr:%p", IdtEntry);
exit(-1);
}
go();
printf("%p", g_tmp);
system("pause");
}