从零开始Inline Hook

中断表进入0环

通过中断门进入0环,首先了解一下中断门的构成
从零开始Inline Hook_第1张图片
构造一个中断号

Base:函数地址
DPL:3	//因为三环使用调用门的条件就是CPL(即cs段选择子的RPL)<=DPL
P:1		//P为1时,中断表才有效
Segment Selector: 0x0008 //中断成功后切换自己的CPL 

在ce中直接写入
从零开始Inline Hook_第2张图片
或者在windbg中写入

eq 8003f500 0040ee00`00081040

使用Xuetr工具查看
从零开始Inline Hook_第3张图片
成功构造好的中断号
通过int指令即可调用

关闭内核写保护

pushad
pushfd

mov eax,cr0			//关闭页保护
and eax,not 0x10000
mov cr0,eax


mov eax,cr0			//开启页保护
or eax,0x10000
mov cr0,eax


popad
popfd 

关于页保护机制
使用windbg查看0x8054252D的pte
从零开始Inline Hook_第4张图片

正常地址有-KWEV权限
从零开始Inline Hook_第5张图片

因此使用上述指令关闭页写入保护,防止有的地方无法写入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环调用
从零开始Inline Hook_第6张图片

push 0x30
pop fs

fsCPU的任务切换有一定关系
通过修改fs 切换线程 完成CPU的任务调度
从零开始Inline Hook_第7张图片
Ring 3Ring 0的FS没有切换,所以CPU并没有进行任务调度
使用以下指令修改FS寄存器,使CPU进行任务调度

push 0x30 //写入fs
pop fs

push 0x3b	//恢复FS
pop fs  

从零开始Inline Hook_第8张图片

尝试调用0环的API

尝试使用Windows的0环API ExAllocatePool(目前已弃用) 但是依然可以直接call该地址获取
从汇编指令看 传入了2个参数
使用函数指针的方式调用该函数
从零开始Inline Hook_第9张图片

首先在Xuetr工具中寻找ntkrnlpa.exe的基地址
使用ida的rebase功能
从零开始Inline Hook_第10张图片

将查找到的基地址放入 重新初始化地址

从零开始Inline Hook_第11张图片

寻找ExAllocPool函数
从零开始Inline Hook_第12张图片

编写如下代码

#include 
typedef (__stdll *Ex_ALLOCPOOL)(DWORD PoolType,DWORD NumberOfBytes)
Ex_ALLOCPOOL ExALLOCPOOL = (Ex_ALLOCPOOL)0x80537FF8

从零开始Inline Hook_第13张图片

得到了被分配的内存

Jmp/Call硬编码问题

接下来进行hook操作,使用jmp指令跳转到刚才的内存里面
jmp的硬编码是e9
那么后面的四位硬编码是什么呢?
可以看到在3环程序中,jmp之后的地址与真实跳转的地址是不一样的,以call举例子

从零开始Inline Hook_第14张图片

可以看到真正call的地址不是0x000213CF
而是0xFFFFFA27(小端序问题)
那这步地址怎么算?

[Address] =真正要跳转的地址-[call指令的地址]+5(call指令的下一条指令的地址)

也就是说真正要call的地址是0x000213CF - 0x000219A8 = 0xFFFFFA27(溢出保留)

从零开始Inline Hook_第15张图片

使用__emit来实现一下

从零开始Inline Hook_第16张图片

程序正常执行了Call_func函数

Hook _kifastCall函数

首先看看_kifastCall函数原来的汇编
从零开始Inline Hook_第17张图片

我们知道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的下一步指令
从零开始Inline Hook_第18张图片

也就是0x8054252D
完整代码如下

#include
#include
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 eax
		mov ds:[0x8003f7f0],eax

		popfd		//恢复堆栈
		popad

		mov ecx,23h	//执行程序接下来的动作 因为这几步需要用cx寄存器,因此先把用到cx寄存器的代码执行掉
		push 30h
		pop fs
		mov ds,cx
		mov es,cx

		mov ecx,0x8054252D	//返回一个固定地址 否则需要计算地址(不固定)
		jmp ecx
	}
}

void __declspec(naked) Hookinline() {
	__asm {

		mov eax,cr0	//关闭页保护
		and eax,not 10000h
		mov cr0,eax 

		mov ax, 0xe9		//写入jmp指令
		mov ds:[0x80542520],al
		mov eax,0xFFAFCBFB
		mov ds:[0x80542521],eax	

		mov eax,cr0	//打开页保护
		or eax,10000h
		mov cr0,eax

		iretd
	}
}	

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发现该地址不断在加一
从零开始Inline Hook_第19张图片

即为hook成功
流程如下
从零开始Inline Hook_第20张图片

Call指令Hook

你可能感兴趣的:(网络安全,网络安全,安全,网络)