#include <ntifs.h>
#include <ntddk.h>
#include <intrin.h>
#include "ptehook.h"
#define CR0_WP (1 << 16)
#include <ntifs.h>
NTSTATUS ProtectMemory(PVOID address, SIZE_T size, ULONG newProtection) {
HANDLE process = NtCurrentProcess();
PVOID baseAddress = address;
SIZE_T protectSize = size;
ULONG oldProtection;
NTSTATUS status = ZwProtectVirtualMemory(
process,
&baseAddress,
&protectSize,
newProtection,
&oldProtection
);
if (!NT_SUCCESS(status)) {
DbgPrint("ZwProtectVirtualMemory failed with status: 0x%X\n", status);
}
else {
DbgPrint("Memory protection changed from %X to %X\n", oldProtection, newProtection);
}
return status;
}
typedef INT(*LDE_DISASM)(PVOID address, INT bits);
typedef unsigned long DWORD;
typedef unsigned __int64 ULONG64;
// 使用WDK标准类型
typedef unsigned char BYTE;
typedef LONG NTSTATUS;
// 修正后的跳转指令结构
#pragma pack(push, 1)
typedef struct _JMP_ABS {
BYTE opcode[6]; // FF25 00000000 (jmp [rip+0])
ULONG64 address; // 目标地址
} JMP_ABS, *PJMP_ABS;
#pragma pack(pop)
LDE_DISASM lde_disasm;
// 初始化引擎
VOID lde_init()
{
lde_disasm = (LDE_DISASM)ExAllocatePool(NonPagedPool, 12800);
memcpy(lde_disasm, szShellCode, 12800);
}
// 得到完整指令长度,避免截断
ULONG GetFullPatchSize(PUCHAR Address)
{
ULONG LenCount = 0, Len = 0;
// 至少需要14字节
while (LenCount <= 14)
{
Len = lde_disasm(Address, 64);
Address = Address + Len;
LenCount = LenCount + Len;
}
return LenCount;
}
#define PROCESS_NAME_LENGTH 16
#define DRIVER_TAG 'HKOB'
EXTERN_C char* PsGetProcessImageFileName(PEPROCESS process);
char target_process_name[] = "oxygen.exe";
typedef NTSTATUS(*fn_ObReferenceObjectByHandleWithTag)(
HANDLE Handle,
ACCESS_MASK DesiredAccess,
POBJECT_TYPE ObjectType,
KPROCESSOR_MODE AccessMode,
ULONG Tag,
PVOID* Object,
POBJECT_HANDLE_INFORMATION HandleInformation
);
fn_ObReferenceObjectByHandleWithTag g_OriginalObReferenceObjectByHandleWithTag = NULL;
// PTE Hook Framework
#define MAX_G_BIT_RECORDS 128
#define MAX_HOOK_COUNT 64
#define PAGE_ALIGN(va) ((PVOID)((ULONG_PTR)(va) & ~0xFFF))
#define PDPTE_PS_BIT (1 << 7)
#define PDE_PS_BIT (1 << 7)
#define PTE_NX_BIT (1ULL << 63)
#define CACHE_WB (6ULL << 3)
// 页表结构定义
typedef struct _PAGE_TABLE {
UINT64 LineAddress;
union {
struct {
UINT64 present : 1;
UINT64 write : 1;
UINT64 user : 1;
UINT64 write_through : 1;
UINT64 cache_disable : 1;
UINT64 accessed : 1;
UINT64 dirty : 1;
UINT64 pat : 1;
UINT64 global : 1;
UINT64 ignored_1 : 3;
UINT64 page_frame_number : 36;
UINT64 reserved_1 : 4;
UINT64 ignored_2 : 7;
UINT64 protection_key : 4;
UINT64 execute_disable : 1;
} flags;
UINT64 value;
}*PteAddress;
union {
struct {
UINT64 present : 1;
UINT64 write : 1;
UINT64 user : 1;
UINT64 write_through : 1;
UINT64 cache_disable : 1;
UINT64 accessed : 1;
UINT64 dirty : 1;
UINT64 large_page : 1;
UINT64 global : 1;
UINT64 ignored_2 : 3;
UINT64 page_frame_number : 36;
UINT64 reserved_1 : 4;
UINT64 ignored_3 : 7;
UINT64 protection_key : 4;
UINT64 execute_disable : 1;
} flags;
UINT64 value;
}*PdeAddress;
union {
struct {
UINT64 present : 1;
UINT64 write : 1;
UINT64 user : 1;
UINT64 write_through : 1;
UINT64 cache_disable : 1;
UINT64 accessed : 1;
UINT64 ignored_1 : 1;
UINT64 page_size : 1;
UINT64 ignored_2 : 4;
UINT64 page_frame_number : 36;
UINT64 reserved_1 : 4;
UINT64 ignored_3 : 7;
UINT64 protection_key : 4;
UINT64 execute_disable : 1;
} flags;
UINT64 value;
}*PdpteAddress;
UINT64* Pml4Address;
BOOLEAN IsLargePage;
BOOLEAN Is1GBPage;
UINT64 OriginalPte;
UINT64 OriginalPde;
UINT64 OriginalPdpte;
UINT64 OriginalPml4e;
HANDLE ProcessId;
} PAGE_TABLE, * PPAGE_TABLE;
// G位信息记录结构体
typedef struct _G_BIT_INFO {
void* AlignAddress;
union {
struct {
UINT64 present : 1;
UINT64 write : 1;
UINT64 user : 1;
UINT64 write_through : 1;
UINT64 cache_disable : 1;
UINT64 accessed : 1;
UINT64 dirty : 1;
UINT64 large_page : 1;
UINT64 global : 1;
UINT64 ignored_2 : 3;
UINT64 page_frame_number : 36;
UINT64 reserved_1 : 4;
UINT64 ignored_3 : 7;
UINT64 protection_key : 4;
UINT64 execute_disable : 1;
} flags;
UINT64 value;
}*PdeAddress;
union {
struct {
UINT64 present : 1;
UINT64 write : 1;
UINT64 user : 1;
UINT64 write_through : 1;
UINT64 cache_disable : 1;
UINT64 accessed : 1;
UINT64 dirty : 1;
UINT64 pat : 1;
UINT64 global : 1;
UINT64 ignored_1 : 3;
UINT64 page_frame_number : 36;
UINT64 reserved_1 : 4;
UINT64 ignored_2 : 7;
UINT64 protection_key : 4;
UINT64 execute_disable : 1;
} flags;
UINT64 value;
}*PteAddress;
BOOLEAN IsLargePage;
} G_BIT_INFO, * PG_BIT_INFO;
typedef struct _HOOK_INFO {
void* OriginalAddress;
void* HookAddress;
UINT8 OriginalBytes[20];
UINT8 HookBytes[20];
UINT32 HookLength;
BOOLEAN IsHooked;
HANDLE ProcessId;
union {
struct {
UINT64 present : 1;
UINT64 write : 1;
UINT64 user : 1;
UINT64 write_through : 1;
UINT64 cache_disable : 1;
UINT64 accessed : 1;
UINT64 dirty : 1;
UINT64 pat : 1;
UINT64 global : 1;
UINT64 ignored_1 : 3;
UINT64 page_frame_number : 36;
UINT64 reserved_1 : 4;
UINT64 ignored_2 : 7;
UINT64 protection_key : 4;
UINT64 execute_disable : 1;
} flags;
UINT64 value;
}*HookedPte;
union {
struct {
UINT64 present : 1;
UINT64 write : 1;
UINT64 user : 1;
UINT64 write_through : 1;
UINT64 cache_disable : 1;
UINT64 accessed : 1;
UINT64 dirty : 1;
UINT64 large_page : 1;
UINT64 global : 1;
UINT64 ignored_2 : 3;
UINT64 page_frame_number : 36;
UINT64 reserved_1 : 4;
UINT64 ignored_3 : 7;
UINT64 protection_key : 4;
UINT64 execute_disable : 1;
} flags;
UINT64 value;
}*HookedPde;
} HOOK_INFO;
class PteHookManager {
public:
bool fn_pte_inline_hook_bp_pg(HANDLE process_id, _Inout_ void** ori_addr, void* hk_addr);
bool fn_remove_hook(HANDLE process_id, void* hook_addr);
static PteHookManager* GetInstance();
HOOK_INFO* GetHookInfo() { return m_HookInfo; }
char* GetTrampLinePool() { return m_TrampLinePool; }
UINT32 GetHookCount() { return m_HookCount; }
bool fn_resume_global_bits(void* align_addr);
~PteHookManager(); // 添加析构函数声明
private:
bool WriteTrampolineInstruction(void* trampoline, const JMP_ABS& jmpCmd);
void fn_add_g_bit_info(void* align_addr, void* pde_address, void* pte_address);
bool fn_isolation_pagetable(UINT64 cr3_val, void* replace_align_addr, void* split_pde);
bool fn_isolation_pages(HANDLE process_id, void* ori_addr);
bool fn_split_large_pages(void* in_pde, void* out_pde);
NTSTATUS get_page_table(UINT64 cr3, PAGE_TABLE& table);
void* fn_pa_to_va(UINT64 pa);
UINT64 fn_va_to_pa(void* va);
__forceinline KIRQL DisableWriteProtection();
__forceinline void EnableWriteProtection(KIRQL oldIrql);
void logger(const char* info, bool is_err, LONG err_code = 0);
void PrintPageTableInfo(const PAGE_TABLE& table);
void PrintHookInfo(const HOOK_INFO& hookInfo);
void PrintGBitInfo(const G_BIT_INFO& gbitInfo);
static constexpr SIZE_T MAX_HOOKS = 256; // 根据需求调整
G_BIT_INFO m_GbitRecords[MAX_G_BIT_RECORDS];
UINT32 m_GbitCount = 0;
void* m_PteBase = 0;
HOOK_INFO m_HookInfo[MAX_HOOK_COUNT] = { 0 };
DWORD m_HookCount = 0;
char* m_TrampLinePool = nullptr; // 合并为一个声明
UINT32 m_PoolUsed = 0;
static PteHookManager* m_Instance;
};
PteHookManager* PteHookManager::m_Instance = nullptr;
// 实现部分
__forceinline KIRQL PteHookManager::DisableWriteProtection() {
KIRQL oldIrql = KeRaiseIrqlToDpcLevel();
UINT64 cr0 = __readcr0();
__writecr0(cr0 & ~0x10000); // 清除CR0.WP位
_mm_mfence();
return oldIrql;
}
__forceinline void PteHookManager::EnableWriteProtection(KIRQL oldIrql) {
_mm_mfence();
UINT64 cr0 = __readcr0();
__writecr0(cr0 | 0x10000); // 设置CR0.WP位
KeLowerIrql(oldIrql);
}
void PteHookManager::logger(const char* info, bool is_err, LONG err_code) {
if (is_err) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[PTE_HOOK] ERROR: %s (0x%X)\n", info, err_code);
}
else {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] INFO: %s\n", info);
}
}
void PteHookManager::PrintPageTableInfo(const PAGE_TABLE& table) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] Page Table Info for VA: 0x%p\n", (void*)table.LineAddress);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" PML4E: 0x%llx (Address: 0x%p)\n", table.OriginalPml4e, table.Pml4Address);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" PDPTE: 0x%llx (Address: 0x%p), Is1GBPage: %d\n",
table.OriginalPdpte, table.PdpteAddress, table.Is1GBPage);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" PDE: 0x%llx (Address: 0x%p), IsLargePage: %d\n",
table.OriginalPde, table.PdeAddress, table.IsLargePage);
if (!table.IsLargePage && !table.Is1GBPage) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" PTE: 0x%llx (Address: 0x%p)\n", table.OriginalPte, table.PteAddress);
}
}
void PteHookManager::PrintHookInfo(const HOOK_INFO& hookInfo) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] Hook Info:\n");
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" Original Address: 0x%p\n", hookInfo.OriginalAddress);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" Hook Address: 0x%p\n", hookInfo.HookAddress);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" Hook Length: %d\n", hookInfo.HookLength);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" Is Hooked: %d\n", hookInfo.IsHooked);
// 打印原始字节
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " Original Bytes: ");
for (UINT32 i = 0; i < sizeof(hookInfo.OriginalBytes); i++) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "%02X ", hookInfo.OriginalBytes[i]);
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "\n");
// 打印Hook字节
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, " Hook Bytes: ");
for (UINT32 i = 0; i < sizeof(hookInfo.HookBytes); i++) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "%02X ", hookInfo.HookBytes[i]);
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "\n");
}
void PteHookManager::PrintGBitInfo(const G_BIT_INFO& gbitInfo) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] G-Bit Info:\n");
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" Align Address: 0x%p\n", gbitInfo.AlignAddress);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" IsLargePage: %d\n", gbitInfo.IsLargePage);
if (gbitInfo.PdeAddress) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" PDE: 0x%llx (Address: 0x%p)\n", gbitInfo.PdeAddress->value, gbitInfo.PdeAddress);
}
if (gbitInfo.PteAddress) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
" PTE: 0x%llx (Address: 0x%p)\n", gbitInfo.PteAddress->value, gbitInfo.PteAddress);
}
}
void* PteHookManager::fn_pa_to_va(UINT64 pa) {
PHYSICAL_ADDRESS physAddr;
physAddr.QuadPart = pa;
return MmGetVirtualForPhysical(physAddr);
}
UINT64 PteHookManager::fn_va_to_pa(void* va) {
PHYSICAL_ADDRESS physAddr = MmGetPhysicalAddress(va);
return physAddr.QuadPart;
}
NTSTATUS PteHookManager::get_page_table(UINT64 cr3_val, PAGE_TABLE& table) {
UINT64 va = table.LineAddress;
UINT64 pml4e_index = (va >> 39) & 0x1FF;
UINT64 pdpte_index = (va >> 30) & 0x1FF;
UINT64 pde_index = (va >> 21) & 0x1FF;
UINT64 pte_index = (va >> 12) & 0x1FF;
// PML4
UINT64 pml4_pa = cr3_val & ~0xFFF;
UINT64* pml4_va = (UINT64*)fn_pa_to_va(pml4_pa);
if (!pml4_va) return STATUS_INVALID_ADDRESS;
table.Pml4Address = &pml4_va[pml4e_index];
table.OriginalPml4e = *table.Pml4Address;
if (!(table.OriginalPml4e & 1)) return STATUS_ACCESS_VIOLATION;
// PDPTE
UINT64 pdpte_pa = table.OriginalPml4e & ~0xFFF;
UINT64* pdpte_va = (UINT64*)fn_pa_to_va(pdpte_pa);
if (!pdpte_va) return STATUS_INVALID_ADDRESS;
table.PdpteAddress = (decltype(table.PdpteAddress))&pdpte_va[pdpte_index];
table.OriginalPdpte = table.PdpteAddress->value;
table.Is1GBPage = (table.PdpteAddress->flags.page_size) ? TRUE : FALSE;
if (!(table.OriginalPdpte & 1)) return STATUS_ACCESS_VIOLATION;
if (table.Is1GBPage) return STATUS_SUCCESS;
// PDE
UINT64 pde_pa = table.OriginalPdpte & ~0xFFF;
UINT64* pde_va = (UINT64*)fn_pa_to_va(pde_pa);
if (!pde_va) return STATUS_INVALID_ADDRESS;
table.PdeAddress = (decltype(table.PdeAddress))&pde_va[pde_index];
table.OriginalPde = table.PdeAddress->value;
table.IsLargePage = (table.PdeAddress->flags.large_page) ? TRUE : FALSE;
if (!(table.OriginalPde & 1)) return STATUS_ACCESS_VIOLATION;
if (table.IsLargePage) return STATUS_SUCCESS;
// PTE
UINT64 pte_pa = table.OriginalPde & ~0xFFF;
UINT64* pte_va = (UINT64*)fn_pa_to_va(pte_pa);
if (!pte_va) return STATUS_INVALID_ADDRESS;
table.PteAddress = (decltype(table.PteAddress))&pte_va[pte_index];
table.OriginalPte = table.PteAddress->value;
if (!(table.OriginalPte & 1)) return STATUS_ACCESS_VIOLATION;
// 打印页表信息
PrintPageTableInfo(table);
return STATUS_SUCCESS;
}
bool PteHookManager::fn_split_large_pages(void* in_pde_ptr, void* out_pde_ptr) {
auto in_pde = (decltype(PAGE_TABLE::PdeAddress))in_pde_ptr;
auto out_pde = (decltype(PAGE_TABLE::PdeAddress))out_pde_ptr;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 正在拆分大页: 输入PDE=0x%llx, 输出PDE=0x%p\n",
in_pde->value, out_pde);
PHYSICAL_ADDRESS LowAddr = { 0 }, HighAddr = { 0 };
HighAddr.QuadPart = MAXULONG64;
auto pt = (decltype(PAGE_TABLE::PteAddress))MmAllocateContiguousMemorySpecifyCache(
PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached);
if (!pt) {
logger("分配连续内存失败 (用于拆分大页)", true);
return false;
}
UINT64 start_pfn = in_pde->flags.page_frame_number;
for (int i = 0; i < 512; i++) {
pt[i].value = 0;
pt[i].flags.present = 1;
pt[i].flags.write = in_pde->flags.write;
pt[i].flags.user = in_pde->flags.user;
pt[i].flags.write_through = in_pde->flags.write_through;
pt[i].flags.cache_disable = in_pde->flags.cache_disable;
pt[i].flags.accessed = in_pde->flags.accessed;
pt[i].flags.dirty = in_pde->flags.dirty;
pt[i].flags.global = 0;
pt[i].flags.page_frame_number = start_pfn + i;
}
out_pde->value = in_pde->value;
out_pde->flags.large_page = 0;
out_pde->flags.page_frame_number = fn_va_to_pa(pt) >> 12;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 大页拆分完成: 新PTE表物理地址=0x%llx\n", fn_va_to_pa(pt));
return true;
}
bool PteHookManager::fn_isolation_pagetable(UINT64 cr3_val, void* replace_align_addr, void* split_pde_ptr) {
PHYSICAL_ADDRESS LowAddr = { 0 }, HighAddr = { 0 };
HighAddr.QuadPart = MAXULONG64;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 开始隔离页表: CR3=0x%llx, 地址=0x%p\n",
cr3_val, replace_align_addr);
auto Va4kb = (UINT64*)MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached);
auto VaPt = (UINT64*)MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached);
auto VaPdt = (UINT64*)MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached);
auto VaPdpt = (UINT64*)MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE, LowAddr, HighAddr, LowAddr, MmNonCached);
if (!VaPt || !Va4kb || !VaPdt || !VaPdpt) {
if (VaPt) MmFreeContiguousMemory(VaPt);
if (Va4kb) MmFreeContiguousMemory(Va4kb);
if (VaPdt) MmFreeContiguousMemory(VaPdt);
if (VaPdpt) MmFreeContiguousMemory(VaPdpt);
logger("分配连续内存失败 (用于隔离页表)", true);
return false;
}
PAGE_TABLE Table = { 0 };
Table.LineAddress = (UINT64)replace_align_addr;
NTSTATUS status = get_page_table(cr3_val, Table);
if (!NT_SUCCESS(status)) {
MmFreeContiguousMemory(VaPt);
MmFreeContiguousMemory(Va4kb);
MmFreeContiguousMemory(VaPdt);
MmFreeContiguousMemory(VaPdpt);
logger("获取页表信息失败", true, status);
return false;
}
UINT64 pte_index = (Table.LineAddress >> 12) & 0x1FF;
UINT64 pde_index = (Table.LineAddress >> 21) & 0x1FF;
UINT64 pdpte_index = (Table.LineAddress >> 30) & 0x1FF;
UINT64 pml4e_index = (Table.LineAddress >> 39) & 0x1FF;
memcpy(Va4kb, replace_align_addr, PAGE_SIZE);
if (Table.IsLargePage && split_pde_ptr) {
auto split_pde = (decltype(PAGE_TABLE::PdeAddress))split_pde_ptr;
memcpy(VaPt, (void*)(split_pde->flags.page_frame_number << 12), PAGE_SIZE);
}
else {
memcpy(VaPt, (void*)(Table.PdeAddress->flags.page_frame_number << 12), PAGE_SIZE);
}
memcpy(VaPdt, (void*)(Table.PdpteAddress->flags.page_frame_number << 12), PAGE_SIZE);
memcpy(VaPdpt, (void*)(Table.Pml4Address[pml4e_index] & ~0xFFF), PAGE_SIZE);
auto new_pte = (decltype(PAGE_TABLE::PteAddress))VaPt;
new_pte[pte_index].flags.page_frame_number = fn_va_to_pa(Va4kb) >> 12;
auto new_pde = (decltype(PAGE_TABLE::PdeAddress))VaPdt;
new_pde[pde_index].value = Table.OriginalPde;
new_pde[pde_index].flags.large_page = 0;
new_pde[pde_index].flags.page_frame_number = fn_va_to_pa(VaPt) >> 12;
auto new_pdpte = (decltype(PAGE_TABLE::PdpteAddress))VaPdpt;
new_pdpte[pdpte_index].flags.page_frame_number = fn_va_to_pa(VaPdt) >> 12;
auto new_pml4 = (UINT64*)fn_pa_to_va(cr3_val & ~0xFFF);
new_pml4[pml4e_index] = (new_pml4[pml4e_index] & 0xFFF) | (fn_va_to_pa(VaPdpt) & ~0xFFF);
__invlpg(replace_align_addr);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 页表隔离完成: 新PFN=0x%llx\n", fn_va_to_pa(Va4kb) >> 12);
return true;
}
bool PteHookManager::fn_isolation_pages(HANDLE process_id, void* ori_addr) {
PEPROCESS Process;
if (!NT_SUCCESS(PsLookupProcessByProcessId(process_id, &Process))) {
logger("查找进程失败", true);
return false;
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 开始隔离页面: PID=%d, 地址=0x%p\n",
(ULONG)(ULONG_PTR)process_id, ori_addr);
KAPC_STATE ApcState;
KeStackAttachProcess(Process, &ApcState);
void* AlignAddr = PAGE_ALIGN(ori_addr);
PAGE_TABLE Table = { 0 };
Table.LineAddress = (UINT64)AlignAddr;
UINT64 target_cr3 = *(UINT64*)((UCHAR*)Process + 0x28);
if (!NT_SUCCESS(get_page_table(target_cr3, Table))) {
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(Process);
logger("获取目标进程页表失败", true);
return false;
}
bool success = false;
decltype(PAGE_TABLE::PdeAddress) split_pde = nullptr;
if (Table.IsLargePage) {
split_pde = (decltype(PAGE_TABLE::PdeAddress))ExAllocatePoolWithTag(NonPagedPool, sizeof(*split_pde), 'pdeS');
if (!split_pde || !fn_split_large_pages(Table.PdeAddress, split_pde)) {
if (split_pde) ExFreePoolWithTag(split_pde, 'pdeS');
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(Process);
logger("拆分大页失败", true);
return false;
}
if (Table.PdeAddress->flags.global) {
Table.PdeAddress->flags.global = 0;
fn_add_g_bit_info(AlignAddr, Table.PdeAddress, nullptr);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 清除大页G位: PDE=0x%llx\n", Table.PdeAddress->value);
}
}
else if (Table.PteAddress && Table.PteAddress->flags.global) {
Table.PteAddress->flags.global = 0;
fn_add_g_bit_info(AlignAddr, nullptr, Table.PteAddress);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 清除PTE G位: PTE=0x%llx\n", Table.PteAddress->value);
success = fn_isolation_pagetable(__readcr3(), AlignAddr, split_pde);
if (split_pde) ExFreePoolWithTag(split_pde, 'pdeS');
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 页表状态: IsLargePage=%d, Is1GBPage=%d\n",
Table.IsLargePage, Table.Is1GBPage);
if (Table.PteAddress) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] PTE 值: 0x%llx (G位=%d)\n",
Table.PteAddress->value, Table.PteAddress->flags.global);
}
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(Process);
if (success) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 页面隔离成功\n");
}
else {
logger("页面隔离失败", true);
}
return success;
}
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(Process);
return true;
}
bool PteHookManager::WriteTrampolineInstruction(void* trampoline, const JMP_ABS& jmpCmd)
{
// 确保地址对齐 (16字节边界)
if (reinterpret_cast<ULONG_PTR>(trampoline) & 0xF) {
DbgPrint("Trampoline address not aligned: 0x%p\n", trampoline);
return false;
}
KIRQL oldIrql = DisableWriteProtection();
// 使用更可靠的MDL映射方式
PMDL pMdl = IoAllocateMdl(trampoline, sizeof(JMP_ABS), FALSE, FALSE, NULL);
if (!pMdl) {
DbgPrint("IoAllocateMdl failed\n");
EnableWriteProtection(oldIrql);
return false;
}
__try {
MmBuildMdlForNonPagedPool(pMdl);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("MmBuildMdlForNonPagedPool exception\n");
IoFreeMdl(pMdl);
EnableWriteProtection(oldIrql);
return false;
}
// 映射内存并提升权限
PVOID mappedAddr = MmMapLockedPagesSpecifyCache(pMdl, KernelMode, MmNonCached, NULL, FALSE, NormalPagePriority);
if (!mappedAddr) {
DbgPrint("MmMapLockedPagesSpecifyCache failed\n");
IoFreeMdl(pMdl);
EnableWriteProtection(oldIrql);
return false;
}
SIZE_T size = sizeof(JMP_ABS);
ULONG oldProtect;
NTSTATUS status = ZwProtectVirtualMemory(NtCurrentProcess(), &mappedAddr, &size, PAGE_EXECUTE_READWRITE, &oldProtect);
if (!NT_SUCCESS(status)) {
DbgPrint("ZwProtectVirtualMemory failed: 0x%X\n", status);
MmUnmapLockedPages(mappedAddr, pMdl);
IoFreeMdl(pMdl);
EnableWriteProtection(oldIrql);
return false;
}
// 写入前打印原始内存内容(调试用)
DbgPrint("Before write at 0x%p: ", trampoline);
for (int i = 0; i < sizeof(JMP_ABS); i++) {
DbgPrint("%02X ", *((BYTE*)trampoline + i));
}
DbgPrint("\n");
// 使用volatile确保内存写入不被优化
volatile JMP_ABS* pDest = static_cast<volatile JMP_ABS*>(mappedAddr);
pDest->opcode[0] = jmpCmd.opcode[0];
pDest->opcode[1] = jmpCmd.opcode[1];
pDest->opcode[2] = jmpCmd.opcode[2];
pDest->opcode[3] = jmpCmd.opcode[3];
pDest->opcode[4] = jmpCmd.opcode[4];
pDest->opcode[5] = jmpCmd.opcode[5];
pDest->address = jmpCmd.address;
// 更强的缓存刷新
KeInvalidateAllCaches();
__invlpg(trampoline);
_mm_mfence();
// 写入后打印内存内容(调试用)
DbgPrint("After write at 0x%p: ", trampoline);
for (int i = 0; i < sizeof(JMP_ABS); i++) {
DbgPrint("%02X ", *((BYTE*)trampoline + i));
}
DbgPrint("\n");
// 清理
MmUnmapLockedPages(mappedAddr, pMdl);
IoFreeMdl(pMdl);
EnableWriteProtection(oldIrql);
// 验证写入
const JMP_ABS* pCheck = static_cast<const JMP_ABS*>(trampoline);
if (pCheck->opcode[0] != 0xFF || pCheck->opcode[1] != 0x25 ||
pCheck->address != jmpCmd.address)
{
DbgPrint("Validation failed: Opcode=%02X%02X, Address=0x%llX (expected 0x%llX)\n",
pCheck->opcode[0], pCheck->opcode[1], pCheck->address, jmpCmd.address);
return false;
}
return true;
}
bool PteHookManager::fn_pte_inline_hook_bp_pg(HANDLE process_id, _Inout_ void** ori_addr, void* hk_addr)
{
if (!ori_addr || !hk_addr || !*ori_addr) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[PTE_HOOK] 错误: 无效参数 (ori_addr=%p, hk_addr=%p)\n",
ori_addr, hk_addr);
return false;
}
if (!m_TrampLinePool) {
PHYSICAL_ADDRESS LowAddr = { LowAddr.QuadPart = 0x100000 };
PHYSICAL_ADDRESS HighAddr = { HighAddr.QuadPart = ~0ull };
m_TrampLinePool = (char*)MmAllocateContiguousMemorySpecifyCache(
PAGE_SIZE * 8, LowAddr, HighAddr, LowAddr, MmNonCached);
if (!m_TrampLinePool) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[PTE_HOOK] 错误: 无法分配跳板池内存\n");
return false;
}
PMDL pMdl = IoAllocateMdl(m_TrampLinePool, PAGE_SIZE * 8, FALSE, FALSE, NULL);
if (pMdl) {
MmBuildMdlForNonPagedPool(pMdl);
MmProtectMdlSystemAddress(pMdl, PAGE_EXECUTE_READWRITE);
IoFreeMdl(pMdl);
}
}
// 构造正确的跳转结构
JMP_ABS jmpCmd = {};
memcpy(jmpCmd.opcode, "\xFF\x25\x00\x00\x00\x00", 6); // FF25 00000000
jmpCmd.address = reinterpret_cast<ULONG64>(hk_addr); // 确保是完整的 64 位地址
void* trampoline = (void*)((ULONG_PTR)(m_TrampLinePool + m_PoolUsed + 15) & ~15);
SIZE_T requiredSize = ((char*)trampoline - m_TrampLinePool) + sizeof(JMP_ABS);
if (requiredSize > PAGE_SIZE * 8) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[PTE_HOOK] 错误: 跳板池空间不足\n");
return false;
}
if (!WriteTrampolineInstruction(trampoline, jmpCmd)) {
return false;
}
for (UINT32 i = 0; i < MAX_HOOK_COUNT; i++) {
if (!m_HookInfo[i].IsHooked) {
m_HookInfo[i].OriginalAddress = *ori_addr;
m_HookInfo[i].HookAddress = trampoline;
m_HookInfo[i].ProcessId = process_id;
m_HookInfo[i].IsHooked = TRUE;
RtlCopyMemory(m_HookInfo[i].HookBytes, &jmpCmd, sizeof(jmpCmd));
m_HookCount++;
break;
}
}
*ori_addr = trampoline;
m_PoolUsed = (UINT32)requiredSize;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] Hook安装成功!跳板指令:\n"
" jmp [0x%p] -> 0x%p\n",
(char*)trampoline + 6, hk_addr);
return true;
}
// 析构函数清理资源
PteHookManager::~PteHookManager()
{
if (m_TrampLinePool) {
MmFreeContiguousMemory(m_TrampLinePool);
m_TrampLinePool = nullptr;
}
}
bool PteHookManager::fn_remove_hook(HANDLE process_id, void* hook_addr) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 尝试移除Hook: Hook地址=0x%p\n", hook_addr);
for (UINT32 i = 0; i < m_HookCount; i++) {
if (m_HookInfo[i].HookAddress == hook_addr && m_HookInfo[i].IsHooked) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 找到匹配的Hook: 原始地址=0x%p\n", m_HookInfo[i].OriginalAddress);
KIRQL oldIrql = DisableWriteProtection();
memcpy(m_HookInfo[i].OriginalAddress, m_HookInfo[i].OriginalBytes,
sizeof(m_HookInfo[i].OriginalBytes));
EnableWriteProtection(oldIrql);
m_HookInfo[i].IsHooked = FALSE;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] Hook已成功移除\n");
return true;
}
}
logger("未找到匹配的Hook", true);
return false;
}
void PteHookManager::fn_add_g_bit_info(void* align_addr, void* pde_address, void* pte_address) {
if (m_GbitCount >= MAX_G_BIT_RECORDS) {
logger("达到最大G位记录数量限制", true);
return;
}
PG_BIT_INFO record = &m_GbitRecords[m_GbitCount++];
record->AlignAddress = align_addr;
record->PdeAddress = (decltype(G_BIT_INFO::PdeAddress))pde_address;
record->PteAddress = (decltype(G_BIT_INFO::PteAddress))pte_address;
record->IsLargePage = (pde_address && ((decltype(PAGE_TABLE::PdeAddress))pde_address)->flags.large_page);
// 打印G位信息
PrintGBitInfo(*record);
}
bool PteHookManager::fn_resume_global_bits(void* align_addr) {
KIRQL oldIrql = DisableWriteProtection();
bool found = false;
DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] 开始恢复G位: 对齐地址=0x%p\n", align_addr);
for (UINT32 i = 0; i < m_GbitCount; i++) {
PG_BIT_INFO record = &m_GbitRecords[i];
if (align_addr && record->AlignAddress != align_addr) continue;
if (record->PteAddress) {
record->PteAddress->flags.global = 1;
__invlpg(record->AlignAddress);
DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL,
" 恢复PTE G位: PTE=0x%llx, 地址=0x%p\n",
record->PteAddress->value, record->AlignAddress);
}
if (record->PdeAddress) {
record->PdeAddress->flags.global = 1;
if (record->IsLargePage) {
__invlpg(record->AlignAddress);
}
DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL,
" 恢复PDE G位: PDE=0x%llx, 地址=0x%p, 大页=%d\n",
record->PdeAddress->value, record->AlignAddress, record->IsLargePage);
}
found = true;
if (align_addr) break;
}
EnableWriteProtection(oldIrql);
if (found) {
DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] G位恢复完成\n");
}
else {
logger("未找到匹配的G位记录", true);
}
return found;
}
PteHookManager* PteHookManager::GetInstance() {
if (!m_Instance) {
m_Instance = static_cast<PteHookManager*>(
ExAllocatePoolWithTag(NonPagedPool, sizeof(PteHookManager), 'tpHk'));
if (m_Instance) {
RtlZeroMemory(m_Instance, sizeof(PteHookManager));
DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_INFO_LEVEL,
"[PTE_HOOK] PTE Hook管理器实例已创建: 地址=0x%p\n", m_Instance);
}
else {
DbgPrintEx(DPFLTR_ERROR_LEVEL, DPFLTR_ERROR_LEVEL,
"[PTE_HOOK] 创建PTE Hook管理器实例失败\n");
}
}
return m_Instance;
}
// 全局PTE Hook管理器实例
PteHookManager* g_PteHookManager = nullptr;
// 辅助函数:检查是否为目标进程
BOOLEAN IsTargetProcess(CHAR* imageName) {
CHAR currentName[16];
// 复制到本地缓冲区并确保 NULL 终止
RtlCopyMemory(currentName, imageName, 16);
currentName[15] = '\0'; // 确保终止
// 修剪尾部空格
for (int i = 15; i >= 0; i--) {
if (currentName[i] == ' ') currentName[i] = '\0';
else if (currentName[i] != '\0') break;
}
return (strcmp(currentName, target_process_name) == 0);
}
// Hook 函数
NTSTATUS MyObReferenceObjectByHandleWithTag(
HANDLE Handle,
ACCESS_MASK DesiredAccess,
POBJECT_TYPE ObjectType,
KPROCESSOR_MODE AccessMode,
ULONG Tag,
PVOID* Object,
POBJECT_HANDLE_INFORMATION HandleInformation
) {
PEPROCESS currentProcess = PsGetCurrentProcess();
CHAR* imageName = PsGetProcessImageFileName(currentProcess);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[!] [HookFunction] 进入 Hook 函数! 当前进程: %s\n", imageName);
__debugbreak(); // 强制中断,确认是否执行到这里
if (IsTargetProcess(imageName)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[!] [HookFunction] 拒绝访问目标进程 PID=%d\n", HandleToULong(PsGetCurrentProcessId()));
return STATUS_ACCESS_DENIED;
}
return g_OriginalObReferenceObjectByHandleWithTag(
Handle,
DesiredAccess,
ObjectType,
AccessMode,
Tag,
Object,
HandleInformation
);
}
NTSTATUS InstallHook() {
UNICODE_STRING funcName;
RtlInitUnicodeString(&funcName, L"ObReferenceObjectByHandleWithTag");
g_OriginalObReferenceObjectByHandleWithTag = (fn_ObReferenceObjectByHandleWithTag)MmGetSystemRoutineAddress(&funcName);
if (!g_OriginalObReferenceObjectByHandleWithTag) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[-] [InstallHook] 找不到 ObReferenceObjectByHandleWithTag\n");
return STATUS_NOT_FOUND;
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[+] [InstallHook] 找到目标函数地址: %p\n", g_OriginalObReferenceObjectByHandleWithTag);
void* targetFunc = (void*)g_OriginalObReferenceObjectByHandleWithTag;
void* hookFunc = (void*)MyObReferenceObjectByHandleWithTag;
HANDLE currentProcessId = PsGetCurrentProcessId();
if (!g_PteHookManager->fn_pte_inline_hook_bp_pg(currentProcessId, &targetFunc, hookFunc)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[-] [InstallHook] PTE Hook 安装失败\n");
return STATUS_UNSUCCESSFUL;
}
g_OriginalObReferenceObjectByHandleWithTag = (fn_ObReferenceObjectByHandleWithTag)targetFunc;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[+] [InstallHook] Hook 成功安装. 跳板地址: %p\n", targetFunc);
__debugbreak(); // 强制中断,验证是否执行到这里
return STATUS_SUCCESS;
}
// 移除 Hook
VOID RemoveHook() {
if (g_OriginalObReferenceObjectByHandleWithTag && g_PteHookManager) {
g_PteHookManager->fn_remove_hook(PsGetCurrentProcessId(), (void*)MyObReferenceObjectByHandleWithTag);
}
}
// 工作线程函数
VOID InstallHookWorker(PVOID Context) {
UNREFERENCED_PARAMETER(Context);
DbgPrint("[+] Worker thread started for hook installation\n");
InstallHook();
PsTerminateSystemThread(STATUS_SUCCESS);
}
// 进程创建回调
VOID ProcessNotifyCallback(
_In_ HANDLE ParentId,
_In_ HANDLE ProcessId,
_In_ BOOLEAN Create
) {
UNREFERENCED_PARAMETER(ParentId);
if (Create) {
PEPROCESS process = NULL;
if (NT_SUCCESS(PsLookupProcessByProcessId(ProcessId, &process))) {
CHAR* imageName = PsGetProcessImageFileName(process);
CHAR currentName[16];
RtlCopyMemory(currentName, imageName, 16);
currentName[15] = '\0';
for (int i = 15; i >= 0; i--) {
if (currentName[i] == ' ') currentName[i] = '\0';
else if (currentName[i] != '\0') break;
}
if (strcmp(currentName, target_process_name) == 0) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[+] [ProcessNotifyCallback] 目标进程 %s 创建 (PID: %d)\n", currentName, HandleToULong(ProcessId));
HANDLE threadHandle;
NTSTATUS status = PsCreateSystemThread(
&threadHandle,
THREAD_ALL_ACCESS,
NULL,
NULL,
NULL,
InstallHookWorker,
NULL
);
if (NT_SUCCESS(status)) {
ZwClose(threadHandle);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[+] [ProcessNotifyCallback] 工作线程已创建\n");
}
else {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[-] [ProcessNotifyCallback] 创建线程失败: 0x%X\n", status);
}
}
ObDereferenceObject(process);
}
}
}
// 驱动卸载函数
VOID DriverUnload(PDRIVER_OBJECT DriverObject) {
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("[+] Driver unloading...\n");
// 移除进程通知回调
PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)ProcessNotifyCallback, TRUE);
// 移除Hook
RemoveHook();
// 清理PTE Hook资源
if (g_PteHookManager) {
DbgPrint("[PTE_HOOK] Cleaning up PTE...\n");
// 恢复所有被修改的G位
g_PteHookManager->fn_resume_global_bits(nullptr);
// 移除所有活动的Hook
HOOK_INFO* hookInfo = g_PteHookManager->GetHookInfo();
UINT32 hookCount = g_PteHookManager->GetHookCount();
for (UINT32 i = 0; i < hookCount; i++) {
if (hookInfo[i].IsHooked) {
g_PteHookManager->fn_remove_hook(PsGetCurrentProcessId(), hookInfo[i].HookAddress);
}
}
// 释放跳板池内存
char* trampLinePool = g_PteHookManager->GetTrampLinePool();
if (trampLinePool) {
ExFreePoolWithTag(trampLinePool, 'JmpP');
}
// 释放管理器实例
ExFreePoolWithTag(g_PteHookManager, 'tpHk');
g_PteHookManager = nullptr;
}
DbgPrint("[+] Driver unloaded successfully\n");
}
extern "C"
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[+] [DriverEntry] 驱动加载开始\n");
DriverObject->DriverUnload = DriverUnload;
g_PteHookManager = PteHookManager::GetInstance();
if (!g_PteHookManager) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "[-] [DriverEntry] 初始化 PteHookManager 失败\n");
return STATUS_INSUFFICIENT_RESOURCES;
}
NTSTATUS status = PsSetCreateProcessNotifyRoutineEx((PCREATE_PROCESS_NOTIFY_ROUTINE_EX)ProcessNotifyCallback, FALSE);
if (!NT_SUCCESS(status)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[-] [DriverEntry] 注册进程通知失败 (0x%X)\n", status);
return status;
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[+] [DriverEntry] 驱动加载成功\n");
return STATUS_SUCCESS;
}严重性 代码 说明 项目 文件 行 禁止显示状态 详细信息
错误(活动) E0020 未定义标识符 "ZwProtectVirtualMemory" obpcallback C:\Users\17116\source\repos\obpcallback\obpcallback\源.cpp 705
严重性 代码 说明 项目 文件 行 禁止显示状态 详细信息
错误 C3861 “ZwProtectVirtualMemory”: 找不到标识符 obpcallback C:\Users\17116\source\repos\obpcallback\obpcallback\源.cpp 14