脱壳成长之路(2)-2代壳进阶(2)
2020-02-10 15:52:46 Author: bbs.pediy.com(查看原文) 阅读量:112 收藏

64位Windows系统挂上内核钩子(不过强制签名)

6天前 676

64位Windows系统挂上内核钩子(不过强制签名)

首先了解代码的实现原理,  在内核中钩子实现与用户层的原理都差不多,利用 InlineHook 修改内核函数的代码,让其修改RIP先跳到我们的函数地址起始,执行完我们的函数再执行原来的函数流程

明白了原理开始实现我们的代码, 再实现代码前我们需要反汇编引擎帮我们计算,所需要的硬编码为多少(关于为什么计算请学习硬编码)
GitHub下载,将其导入我们的头文件中(链接: https://github.com/BeaEngine/lde64)

0环驱动代码实现如下 :

#include <Ntifs.h>
#include <WinDef.h>
#include "LDE64x64.h" // 识别硬编码的长度

// 实现内核钩子(针对PsLookupProcessByProcessId函数)

// 导出函数声明
PCHAR PsGetProcessImageFileName(PEPROCESS Process); // 获取内核对象的进程名称

typedef NTSTATUS
(*pPsLookupProcessByProcessId)(
	_In_ HANDLE ProcessId,
	_Outptr_ PEPROCESS *Process
);
pPsLookupProcessByProcessId OriFun;
ULONG PachSize;

KIRQL irQl;
// 修改Cr0寄存器, 去除写保护(内存保护机制)
KIRQL RemovWP()
{
	DbgPrint("RemovWP\n");
	// (PASSIVE_LEVEL)提升 IRQL 等级为DISPATCH_LEVEL,并返回旧的 IRQL
	// 需要一个高的IRQL才能修改
	irQl = KeRaiseIrqlToDpcLevel();
	ULONG_PTR cr0 = __readcr0(); // 内联函数:读取Cr0寄存器的值, 相当于: mov eax,  cr0;

	// 将第16位(WP位)清0,消除写保护
	cr0 &= ~0x10000; // ~ 按位取反
	_disable(); // 清除中断标记, 相当于 cli 指令,修改 IF标志位
	__writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax
	DbgPrint("退出RemovWP\n");
	return irQl;
}
// 复原Cr0寄存器
KIRQL UndoWP()
{
	DbgPrint("UndoWP\n");
	ULONG_PTR cr0 = __readcr0();
	cr0 |= 0x10000; // WP复原为1
	_disable(); // 清除中断标记, 相当于 cli 指令,清空 IF标志位
	__writecr0(cr0); // 将cr0变量数据重新写入Cr0寄存器中,相当于: mov cr0, eax

	// 恢复IRQL等级
	KeLowerIrql(irQl);
	DbgPrint("退出UndoWP\n");
	return irQl;
}

NTSTATUS
HookPsLookupProcessByProcessId(
	_In_ HANDLE ProcessId,
	_Outptr_ PEPROCESS *Process
)
{
	DbgPrint("进入HookPsLookupProcessByProcessId\n");
	NTSTATUS status = STATUS_SUCCESS;
	status = OriFun(ProcessId, Process); // 执行原来的函数代码
	if (!NT_SUCCESS(status))
	{
		return status;
	}

	if (strcmp(PsGetProcessImageFileName(*Process), "calc.exe") == 0) // 如果目标为计算器
	{
		return STATUS_ACCESS_DENIED; // 返回拒绝访问
	}

	return status;
}

VOID HookKernelRoutine(PVOID ApiAddress/*目标函数*/, PVOID Proxy_Address/*我们的函数*/, 
	PVOID * Ori_Addresss/*存储原来的汇编代码空间*/, ULONG * PatchSize/*存储的汇编指令长度*/)
{
	KdBreakPoint();
	DbgPrint("进入HookKernelRoutine\n");
	// 构建 ShellCode 代码
	char jmp_Code[14] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
	// 跳回源代码的 Code
	char jmp_OriCode[14] = "\xFF\x25\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";

	DWORD AsmLength = 0;
	DWORD CodeLength = 0;
	PCHAR OriCodeStartAddr = (PCHAR)ApiAddress;

	while (CodeLength < 14) // 循环判断指令长度是否能存下 ShellCode 
	{
		// 通过 LDE 函数获取,当前汇编指令长度
		AsmLength = LDE(ApiAddress/*函数地址*/, 64/*系统位数*/);
		OriCodeStartAddr += AsmLength; // 获取返回到原程序的起始执行地址
		CodeLength += AsmLength;
	}

	*PatchSize = CodeLength; // 存储的汇编指令长度
	
	*Ori_Addresss = ExAllocatePool(NonPagedPool, CodeLength + sizeof(jmp_OriCode)); // 开辟空间存储原来的代码 + 返回原代码地址Code
	// 复制原本的硬编码
	RtlMoveMemory(*Ori_Addresss, ApiAddress, CodeLength);

	// 放入跳回地址
	*(PVOID *)&jmp_OriCode[6] = (PCHAR)ApiAddress + *PatchSize;
	// 将其拷贝到 开辟空间存储的原来的代码 的尾部, 这样就组成了原来的函数代码
	RtlCopyMemory((PCHAR)*Ori_Addresss + *PatchSize, jmp_OriCode, sizeof(jmp_OriCode));

	// 将我们的函数地址存入到 ShellCode 中
	*(PVOID *)&jmp_Code[6] = HookPsLookupProcessByProcessId;
	// 关闭写保护
	RemovWP();
	RtlCopyMemory(ApiAddress, jmp_Code, sizeof(jmp_Code)); // 将 ShellCode 写入目标函数中
	// 恢复写保护
	UndoWP();
	DbgPrint("退出HookKernelRoutine\n");
}

VOID Unload(IN PDRIVER_OBJECT DriverObject)
{
	// 恢复源代码
	// 关闭写保护
	RemovWP();
	RtlCopyMemory(PsLookupProcessByProcessId, OriFun, PachSize); // 将 ShellCode 写入目标函数中
	// 恢复写保护
	UndoWP();

	DbgPrint("卸载驱动\n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegeditPath)
{
	DbgPrint("加载驱动\n");
	NTSTATUS status = STATUS_SUCCESS;
	DriverObject->DriverUnload = Unload;
	// 初始化反汇编引擎
	LDE_init();

	HookKernelRoutine(PsLookupProcessByProcessId, HookPsLookupProcessByProcessId, &((PVOID)OriFun), &PachSize);

	return status;
}

实现效果如下(运行前我们关闭强制签名):
PCHunter 识别出了内核钩子, 可以看出只要3或0环的任何程序调用 PsLookupProcessByProcessId 都会经过我们的代码

我们利用各种方式现在都无法打开计算器 

2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!

最后于 6天前 被灵幻空间编辑 ,原因: 图片不显示


文章来源: https://bbs.pediy.com/thread-257490.htm
如有侵权请联系:admin#unsafe.sh