前面几次实验我们已经完成了一个三环的程序调用零环API的必要条件。
- 提升到零环权限
- 使fs指向KPCR
完善代码
这次我们去掉之前的死循环代码,并且将函数地址写入到IDT表项,在虚拟机中运行一下程序,看看会有什么结果。
这里他抛出了一个内存访问异常。原因在于我们修改了fs寄存器之后,在iretd指令返回三环的时候,系统不会自动帮我们将FS寄存器还原。
所以我们在返回三环之前还需要将fs还原回去。代码如下:
__asm
{
push 0x30;
pop fs;
sti;
push 0x3B;
pop fs;
iretd;
}
再次运行我们的程序,
此时程序完全正常运行。
内核API调用
接下来我们在自己的代码中调用一个内核的API函数ExAllocatePool来分配一块内存。
首先在PC Hunter中将ntkrnlpa.exe拷贝出来,然后用IDA分析,接着设置基址和驱动模块的基地址一致。
在IDA中找到ExAllocatePool这个函数,并且记录下函数地址。
然后按Y键可以复制出函数原型,然后定义函数指针,并且将函数地址赋值给函数指针变量
typedef DWORD (__stdcall *EX_ALLOCATE)(DWORD PoolType, DWORD NumberOfBytes);
EX_ALLOCATE ExAllocatePool= (EX_ALLOCATE)0x83E51976;
接着编写调用代码如下:
void __declspec(naked) IdtEntry()
{
__asm
{
push 0x30;
pop fs;
sti;
}
g_pool=ExAllocatePool(0, 4096);
__asm
{
push 0x3B;
pop fs;
iretd;
}
}
运行程序
看到这里将我们申请的内存首地址打印出来了,而且是一个内核的地址,符合我们的预期
然后我们可以尝试调用一下DbgPrint,同样的方法找到函数地址和原型进行调用
typedef DWORD ( __cdecl *DBGPRINT)(char* Format, ...);
DBGPRINT MyDbgPrint=(DBGPRINT)0x83E5541F;
char str[] = "Hello GuiShou!";
void __declspec(naked) IdtEntry()
{
__asm
{
push 0x30;
pop fs;
sti;
}
//g_pool=ExAllocatePool(0, 4096);
MyDbgPrint(str);
__asm
{
push 0x3B;
pop fs;
iretd;
}
}
运行程序以后
在windbg窗口打印出了我们设置好的字符串,说明API调用成功
修复一个潜在问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bD6wu6Cl-1573908064431)(assets/1573905826474.png)]
仔细观察我们写的这几句还原FS段选择子返回三环的代码,实际上是有问题的。
在pop fs这句代码执行完成之后,iretd执行之前;依然是开启中断的状态,那么意味着这两行汇编指令之间CPU有可能收到时钟中断,造成线程切换。
而线程切换需要用的FS寄存器的值,然而FS这个时候是一个三环的段选择子,而我们现在却处在一个零环的环境下,FS需要指向KPCR,但是却没有指向那个位置。
就是说如果在指向这两句代码之间发生了线程切换,就会发生蓝屏。尽管产生这个问题的几率很小,但是依然不能忽视。
复现问题
接下来我们稍微修改一下代码,就能复现这个问题:
void go()
{
while(1)
__asm int 0x20;
}
我们在int 0x20指令加上一条死循环。当CPU产生int 20异常时,会进入到我们设置好的IdtEntry函数提权到零环,然后IdtEntry函数会重新返回到三环。接着由于int 0x20这条指令是在死循环里面。所以这个程序就会一直在三环和零环之前往返。这就会大大增加在push 0x2B;pop fs这两句代码之间发生线程切换的几率。
运行程序,在不连接调试器的情况下会直接蓝屏,如果连接上调试器则虚拟机会出现卡死现象。
解决的方法很简单,就是让CPU在执行这两句代码时不接收硬件中断
在恢复FS寄存器之前关闭中断,即可解决问题,事实上KiFastCallEntry也是这么做的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CHN5izUD-1573908064445)(assets/1573907354519.png)]
再次运行程序,此时依然是死循环,但是因为已经关闭了中断,所以循环往返三环和零环都是安全的,不会发生蓝屏和卡死的现象。
完整代码
最后附上完整的实验代码
#include "pch.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
typedef DWORD (__stdcall *EX_ALLOCATE)(DWORD PoolType, DWORD NumberOfBytes);
EX_ALLOCATE ExAllocatePool= (EX_ALLOCATE)0x83E51976;
DWORD g_pool;
typedef DWORD ( __cdecl *DBGPRINT)(char* Format, ...);
DBGPRINT MyDbgPrint=(DBGPRINT)0x83E5541F;
char str[] = "Hello GuiShou!";
void __declspec(naked) IdtEntry()
{
__asm
{
push 0x30;
pop fs;
sti;
}
//g_pool=ExAllocatePool(0, 4096);
//MyDbgPrint(str);
__asm cli;
__asm
{
push 0x3B;
pop fs;
iretd;
}
}
void go()
{
while(1)
__asm int 0x20;
}
//eq 80b95500 0040ee00`00081040
int main()
{
if ((DWORD)IdtEntry != 0x401040)
{
printf("wrong addr:%p", IdtEntry);
exit(-1);
}
go();
//printf("%p\n", g_pool);
system("pause");
}