中断表进入0环
通过中断门进入0环,首先了解一下中断门的构成
构造一个中断号
Base:函数地址
DPL:3 //因为三环使用调用门的条件就是CPL(即cs段选择子的RPL)<=DPL
P:1 //P为1时,中断表才有效
Segment Selector: 0x0008 //中断成功后切换自己的CPL
在ce中直接写入
或者在windbg中写入
eq 8003f500 0040ee00`00081040
使用Xuetr
工具查看
成功构造好的中断号
通过int
指令即可调用
关闭内核写保护
pushad
pushfdmov eax,cr0 //关闭页保护
and eax,not 0x10000
mov cr0,eaxmov eax,cr0 //开启页保护
or eax,0x10000
mov cr0,eaxpopad
popfd
关于页保护机制
使用windbg查看0x8054252D
的pte
正常地址有-KWEV
权限
因此使用上述指令关闭页写入保护,防止有的地方无法写入jmp指令,导致无法hook
解决进入0环无法任务调度的问题
首先,浅聊一下fs的问题
逆向内核文件 ntkrnlpa.exe
原因是 单核处理器运行该程序
操作系统的内核模块根据处理器的个数和是否支持PAE(Physical Address Extension物理地址扩展)分为以下四种
ntoskrnl.exe ---Uniprocessor单处理器,不支持PAE
ntkrnlpa.exe ---Uniprocessor单处理器,支持PAE
ntkrnlmp.exe ---Multiprocessor多处理器,不支持PAE
ntkrpamp.exe ---Mulitiprocessor多处理器,支持PAE
ntkrnlpa.exe 查看windows内核的0环调用
push 0x30
pop fs
fs
与CPU
的任务切换有一定关系
通过修改fs 切换线程 完成CPU
的任务调度
Ring 3
和Ring 0
的FS没有切换,所以CPU
并没有进行任务调度
使用以下指令修改FS寄存器,使CPU
进行任务调度
push 0x30 //写入fs
pop fspush 0x3b //恢复FS
pop fs
尝试调用0环的API
尝试使用Windows的0环API ExAllocatePool
(目前已弃用) 但是依然可以直接call该地址获取
从汇编指令看 传入了2个参数
使用函数指针的方式调用该函数
首先在Xuetr
工具中寻找ntkrnlpa.exe
的基地址
使用ida的rebase
功能
将查找到的基地址放入 重新初始化地址
寻找ExAllocPool函数
编写如下代码
#include <windows.h>
typedef (__stdll *Ex_ALLOCPOOL)(DWORD PoolType,DWORD NumberOfBytes)
Ex_ALLOCPOOL ExALLOCPOOL = (Ex_ALLOCPOOL)0x80537FF8
得到了被分配的内存
Jmp/Call硬编码问题
接下来进行hook操作,使用jmp指令跳转到刚才的内存里面
jmp的硬编码是e9
那么后面的四位硬编码是什么呢?
可以看到在3环程序中,jmp之后的地址与真实跳转的地址是不一样的,以call举例子
可以看到真正call的地址不是0x000213CF
而是0xFFFFFA27(小端序问题)
那这步地址怎么算?
[Address] =真正要跳转的地址-[call指令的地址]+5(call指令的下一条指令的地址)
也就是说真正要call的地址是0x000213CF - 0x000219A8 = 0xFFFFFA27(溢出保留)
使用__emit
来实现一下
程序正常执行了Call_func函数
Hook _kifastCall函数
首先看看_kifastCall
函数原来的汇编
我们知道jmp [address]
的长度是5个字节 一个jmp指令(1字节)+一个地址(4字节)
刚好此处第一行的mov ecx,23h
也是5个字节,因此劫持该函数
使此处jmp到gdt表中(因为gdtr有相当一部分空间没用用到)[也可以用api来创建内存,不过比较麻烦],然后在gdt表中写入要hook的指令即可
要hook的指令
mov eax,ds:[0x8003f7f0] //写好hook代码
inc eax
mov ds:[0x8003f7f0],eax
就是系统每调用一次api都会经过_kifastCall
因此在此处加一,即可得知,程序被调用了多少次
然后再执行接下来程序本来该执行的指令
mov ecx,23h //执行程序接下来的动作 因为这几步需要用cx寄存器,因此先把用到cx寄存器的代码执行掉
push 30h
pop fs
mov ds,cx
mov es,cx
接下来再返回mov es,cx
的下一步指令
也就是0x8054252D
完整代码如下
#include<stdio.h>
#include<stdlib.h>
void Hooked();
char* p;
int i;
void __declspec(naked) CpyMemory() {p = (char*)0x8003f120;for ( i = 0; i < 64; i++){*p = ((char*)Hooked)[i];p++;}__asm {iretd}
}void __declspec(naked) Hooked() {__asm {pushad //保存当前堆栈pushfd mov eax,ds:[0x8003f7f0] //写好hook代码inc eaxmov ds:[0x8003f7f0],eaxpopfd //恢复堆栈popadmov ecx,23h //执行程序接下来的动作 因为这几步需要用cx寄存器,因此先把用到cx寄存器的代码执行掉push 30hpop fsmov ds,cxmov es,cxmov ecx,0x8054252D //返回一个固定地址 否则需要计算地址(不固定)jmp ecx}
}void __declspec(naked) Hookinline() {__asm {mov eax,cr0 //关闭页保护and eax,not 10000hmov cr0,eax mov ax, 0xe9 //写入jmp指令mov ds:[0x80542520],almov eax,0xFFAFCBFBmov ds:[0x80542521],eax mov eax,cr0 //打开页保护or eax,10000hmov cr0,eaxiretd}
} void intEntry() {__asm {int 0x21 //先写入gdtr表 调用CpyMemory函数int 0x20 //Hook __kifastCall 函数 调用Hookinline}
}int main() {printf("CpyMemory Address:%p\n", CpyMemory);printf("Hookinline Address %p\n", Hookinline);intEntry();system("pause");return 0;
}
成功之后,使用ce观察0x8003f7f0
发现该地址不断在加一
即为hook成功
流程如下