page_address()函数分析--如何通过page取得虚拟地址

本文详细介绍了X86平台上内存管理中page_address()函数的工作原理。针对低端内存和高端内存的不同情况,分别阐述了其查找虚拟地址的具体实现方式。

由于X86平台上面,内存是划分为低端内存和高端内存的,所以在两个区域内的page查找对应的虚拟地址是不一样的。

一. x86上关于page_address()函数的定义

在include/linux/mm.h里面,有对page_address()函数的三种宏定义,主要依赖于不同的平台:

首先来看看几个宏的定义:
CONFIG_HIGHMEM:顾名思义,就是是否支持高端内存,可以查看config文件,一般推荐内存超过896M的时候,才配置为支持高端内存。
WANT_PAGE_VIRTUAL:X86平台是没有定义的。
所以下面的HASHED_PAGE_VIRTUAL在支持高端内存的i386平台上是有定义的
 

#if defined(CONFIG_HIGHMEM) && !defined(WANT_PAGE_VIRTUAL)
#define HASHED_PAGE_VIRTUAL
#endif




1.//所以这里是假的,page_address()在i386上不是在这里定义的


    #if defined(WANT_PAGE_VIRTUAL)

    #define page_address(page) ((page)->virtual)

    #define set_page_address(page, address) //
    do { //
    (page)->virtual = (address); //
    } while(0)
    #define page_address_init() do { } while(0)
    #endif



2.//在没有配置CONFIG_HIGHMEM的i386平台上,page_address是在这里定义的

#if !defined(HASHED_PAGE_VIRTUAL) && !defined(WANT_PAGE_VIRTUAL)

    #define page_address(page) lowmem_page_address(page)

    #define set_page_address(page, address) do { } while(0)

    #define page_address_init() do { } while(0)

    #endif


3.//所以支持高端内存的i386平台上,page_address()是在这里定义的

#if defined(HASHED_PAGE_VIRTUAL)

    void *page_address(struct page *page);

    void set_page_address(struct page *page, void *virtual);

    void page_address_init(void);

    #endif


二. 在低端内存中的page对应的page_address()的实现
在没有配置CONFIG_HIGHMEM的i386平台上,page_address()是等同于lowmem_page_address():

#define page_address(page) lowmem_page_address(page)


static __always_inline void *lowmem_page_address(struct page *page)
{
return __va(page_to_pfn(page) << PAGE_SHIFT);
}

#define page_to_pfn(page) ((unsigned long)((page) - mem_map) + /
                                 ARCH_PFN_OFFSET)


#define __va(x) ((void *)((unsigned long)(x) + PAGE_OFFSET))


我们知道,在小于896M(低端内存)的物理地址空间和3G--3G+896M的线性地址空间是一一对应映射的,所以我们只要知道page所对应的物理地址,就可以知道这个page对应的线性地址空间(pa+PAGE_OFFSET)。
那如何找一个page对应的物理地址呢?我们知道物理内存按照大小为(1<<PAGE_SHIFT)分为很多个页,每个这样的页就对应一个struct page * page结构,这些页描述结构存放在一个称之为mem_map的数组里面,而且是严格按照物理内存的顺序来存放的,也就是物理上的第一个页描述结构,作为mem_map数组的第一个元素,依次类推。所以,每个页描述结构(page)在数组mem_map里的位置在乘以页的大小,就可以得到该页的物理地址了。上面的代码就是依照这个原理来的:
page_to_pfn(page)函数就是得到每个page在mem_map里的位置,左移PAGE_SHIFT就是乘以页的大小,这就得到了该页的物理地址。这个物理地址加上个PAGE_OFFSET(3G)就得到了该page的线性地址了

在低端内存中(小于896M),通过页(struct page * page)取得虚拟地址就是这样转换的。

三. 在高端内存中的page对应的page_address()的实现:

在有配置CONFIG_HIGHMEM的i386平台上,page_address是在mm/highmem.c里面实现的:


/**
* page_address - get the mapped virtual address of a page
* @page: &struct page to get the virtual address of
*
* Returns the page/'s virtual address.
*/

void *page_address(struct page *page)
{

unsigned long flags;
void *ret;
struct page_address_slot *pas;

if (!PageHighMem(page)) //判断是否属于高端内存,如果不是,那么就是属于低
                         端内
存的,通过上面的方法可以直接找到
    return lowmem_page_address(page);

pas = page_slot(page); //见下分析,pas指向page对应的page_address_map结构所在的链表表头

ret = NULL;
spin_lock_irqsave(&pas->lock, flags);
if (!list_empty(&pas->lh)) {
struct page_address_map *pam;

list_for_each_entry(pam, &pas->lh, list) {
if (pam->page == page) {
ret = pam->virtual;
goto done;
}
}
}
done:
spin_unlock_irqrestore(&pas->lock, flags);
return ret;

}


在高端内存中,由于不能通过像在低端内存中一样,直接通过物理地址加PAGE_OFFSET得到线性地址,所以引入了一个结构叫做 page_address_map结构,该结构保存有每个page(仅高端内存中的)和对应的虚拟地址,所有的高端内存中的这种映射都通过链表链接起来,这个结构是在高端内存映射的时候建立,并加入到链表中的。

/*
* Describes one page->virtual association
*/

struct page_address_map {
struct page *page; //page
void *virtual; //虚拟地址
struct list_head list; //指向下一个该结构
};


又因为如果内存远远大于896M,那么高端内存中的page就比较多((内存-896M)/4K个页,假设页大小为4K),如果只用一个链表来表示,那么查找起来就比较耗时了,所以这里引入了HASH算法,采用多个链表,每个page通过一定的hash算法,对应到一个链表上,总够有128个链表:

/*
* Hash table bucket
*/

static struct page_address_slot {
struct list_head lh; // List of page_address_maps 指向一个  

                     //page_address_map结构 链表
spinlock_t lock; /* Protect this bucket/'s list */
}page_address_htable[1<<PA_HASH_ORDER];

PA_HASH_ORDER=7, 所以一共有1<<7(128)个链表,每一个page通过HASH算法后对应一个 page_address_htable链表, 然后再遍历这个链表来找到对应的PAGE和虚拟地址。
page通过HASH算法后对应一个 page_address_htable链表的代码如下:

static struct page_address_slot *page_slot(struct page *page)
{
return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}


hash_ptr(val, bits)函数在32位的机器上是一个很简单的hash算法,就是把val乘一个黄金值 GOLDEN_RATIO_PRIME_32,在把得到的结果(32位)取高 bits位 (这里就是7位)作为哈希表的索引

static inline u32 hash_32(u32 val, unsigned int bits)
{
/* On some cpus multiply is faster, on others gcc will do shifts */
u32 hash = val * GOLDEN_RATIO_PRIME_32;

/* High bits are more random, so use them. */
return hash >> (32 - bits);
}



这样pas = page_slot(page)执行过后,pas就指向该page对应的page_address_map结构所在的链表的表头。
然后再遍历这个链表,就可以找到对应的线性地址(如果存在的话),否则就返回NULL

list_for_each_entry(pam, &pas->lh, list) {
   if (pam->page == page) {
      ret = pam->virtual;
      goto done;
   }
}

详细注释代码 bool hook_function(void* target_function, void* hooked_function,void* trampoline, void** origin_function) { unsigned __int64 physical_address = MmGetPhysicalAddress(target_function).QuadPart; // // Check if function exist in physical memory // if (physical_address == NULL) { LogError("Requested virtual memory doesn't exist in physical one"); return false; } // // Check if page isn't already hooked // PLIST_ENTRY current = &g_vmm_context->ept_state->hooked_page_list; while (&g_vmm_context->ept_state->hooked_page_list != current->Flink) { current = current->Flink; __ept_hooked_page_info* hooked_page_info = CONTAINING_RECORD(current, __ept_hooked_page_info, hooked_page_list); if (hooked_page_info->pfn_of_hooked_page == GET_PFN(physical_address)) { LogInfo("Page already hooked"); __ept_hooked_function_info* hooked_function_info = pool_manager::request_pool<__ept_hooked_function_info*>(pool_manager::INTENTION_TRACK_HOOKED_FUNCTIONS, TRUE, sizeof(__ept_hooked_function_info)); if (hooked_function_info == nullptr) { LogError("There is no pre-allocated pool for hooked function struct"); return false; } // // If we are hooking code cave for second trampoline // then origin function in null and we don't have to get pool for trampoline // if(origin_function != nullptr) { hooked_function_info->first_trampoline_address = pool_manager::request_pool<unsigned __int8*>(pool_manager::INTENTION_EXEC_TRAMPOLINE, TRUE, 100); if (hooked_function_info->first_trampoline_address == nullptr) { pool_manager::release_pool(hooked_function_info); LogError("There is no pre-allocated pool for trampoline"); return false; } } hooked_function_info->virtual_address = target_function; hooked_function_info->second_trampoline_address = trampoline; hooked_function_info->fake_page_contents = hooked_page_info->fake_page_contents; if (hook_instruction_memory(hooked_function_info, target_function, hooked_function, trampoline, origin_function) == false) { if(hooked_function_info->first_trampoline_address != nullptr) pool_manager::release_pool(hooked_function_info->first_trampoline_address); pool_manager::release_pool(hooked_function_info); LogError("Hook failed"); return false; } // Track all hooked functions within page InsertHeadList(&hooked_page_info->hooked_functions_list, &hooked_function_info->hooked_function_list); return true; } } if (is_page_splitted(physical_address) == false) { void* split_buffer = pool_manager::request_pool<void*>(pool_manager::INTENTION_SPLIT_PML2, true, sizeof(__ept_dynamic_split)); if (split_buffer == nullptr) { LogError("There is no preallocated pool for split"); return false; } if (split_pml2(split_buffer, physical_address) == false) { pool_manager::release_pool(split_buffer); LogError("Split failed"); return false; } } __ept_pte* target_page = get_pml1_entry(physical_address); if (target_page == nullptr) { LogError("Failed to get PML1 entry of the target address"); return false; } __ept_hooked_page_info* hooked_page_info = pool_manager::request_pool<__ept_hooked_page_info*>(pool_manager::INTENTION_TRACK_HOOKED_PAGES, true, sizeof(__ept_hooked_page_info)); if (hooked_page_info == nullptr) { LogError("There is no preallocated pool for hooked page info"); return false; } InitializeListHead(&hooked_page_info->hooked_functions_list); __ept_hooked_function_info* hooked_function_info = pool_manager::request_pool<__ept_hooked_function_info*>(pool_manager::INTENTION_TRACK_HOOKED_FUNCTIONS, true, sizeof(__ept_hooked_function_info)); if (hooked_function_info == nullptr) { pool_manager::release_pool(hooked_page_info); LogError("There is no preallocated pool for hooked function info"); return false; } // // If we are hooking code cave for second trampoline // then origin function in null and we don't have to get pool for trampoline // if (origin_function != nullptr) { hooked_function_info->first_trampoline_address = pool_manager::request_pool<unsigned __int8*>(pool_manager::INTENTION_EXEC_TRAMPOLINE, TRUE, 100); if (hooked_function_info->first_trampoline_address == nullptr) { pool_manager::release_pool(hooked_page_info); pool_manager::release_pool(hooked_function_info); LogError("There is no pre-allocated pool for trampoline"); return false; } } hooked_page_info->pfn_of_hooked_page = GET_PFN(physical_address); hooked_page_info->pfn_of_fake_page_contents = GET_PFN(MmGetPhysicalAddress(hooked_page_info->fake_page_contents).QuadPart); hooked_page_info->entry_address = target_page; hooked_page_info->entry_address->execute = 0; hooked_page_info->entry_address->read = 1; hooked_page_info->entry_address->write = 1; hooked_page_info->original_entry = *target_page; hooked_page_info->changed_entry = *target_page; hooked_page_info->changed_entry.read = 0; hooked_page_info->changed_entry.write = 0; hooked_page_info->changed_entry.execute = 1; hooked_page_info->changed_entry.physical_address = hooked_page_info->pfn_of_fake_page_contents; RtlCopyMemory(&hooked_page_info->fake_page_contents, PAGE_ALIGN(target_function), PAGE_SIZE); hooked_function_info->virtual_address = target_function; hooked_function_info->second_trampoline_address = trampoline; hooked_function_info->fake_page_contents = hooked_page_info->fake_page_contents; if(hook_instruction_memory(hooked_function_info, target_function, hooked_function, trampoline, origin_function) == false) { if (hooked_function_info->first_trampoline_address != nullptr) pool_manager::release_pool(hooked_function_info->first_trampoline_address); pool_manager::release_pool(hooked_function_info); pool_manager::release_pool(hooked_page_info); LogError("Hook failed"); return false; } // Track all hooked functions InsertHeadList(&hooked_page_info->hooked_functions_list, &hooked_function_info->hooked_function_list); // Track all hooked pages InsertHeadList(&g_vmm_context->ept_state->hooked_page_list, &hooked_page_info->hooked_page_list); invept_single_context(g_vmm_context->ept_state->ept_pointer->all); return true; }
最新发布
08-07
<think>好的,我现在需要详细注释用户提供的这段代码。首先,我需要理解这段代码的功能。看起来这是一个用于钩子(hook)函数的实现,可能是在内核模式下使用EPT(扩展页表)来拦截对特定内存区域的访问。代码中涉及物理地址获取、内存池管理、EPT表操作以及钩子的安装。 首先,函数参数包括目标函数地址、钩子函数、跳转桩(trampoline)和原始函数指针。函数返回布尔值表示成功与否。 接下来,代码开始获取目标函数的物理地址。如果物理地址无效,记录错误并返回false。这一步是为了确保目标函数所在的内存确实存在于物理内存中,因为后续操作需要修改物理页的映射。 然后,检查该页是否已经被钩住。遍历全局的钩子页列表,查找是否有相同的PFN(物理页帧号)。如果找到,说明该页已经被钩住,此时需要为新的钩子函数分配信息结构,并处理跳转桩的分配。如果origin_function不为空,则需要分配第一个跳转桩的内存。之后调用hook_instruction_memory函数来实际安装钩子,并将新钩子函数信息插入到对应页的钩子函数列表中。如果成功,返回true。 如果该页未被钩住,则需要先检查是否已经分割过PML2页表项。如果没有,进行分割操作,确保可以修改PML1的表项。分割失败则返回错误。 接下来获取目标地址的PML1表项(即页表项),如果获取失败则返回错误。然后分配并初始化钩子页信息结构和钩子函数信息结构。同样,如果origin_function非空,分配第一个跳转桩内存。 之后,设置钩子页的信息,包括PFN、原始和修改后的页表项。修改后的页表项设置为不可读写,但可执行,并指向伪造的页内容。伪造的页内容从目标页复制,可能用于在钩子处理时执行原始代码。 然后处理钩子函数信息,调用hook_instruction_memory,如果失败则释放资源并返回错误。成功的话,将钩子函数和页信息插入全局列表,并刷新EPT以确保更改生效。 现在需要为每个步骤添加详细注释,解释代码的作用和设计意图。同时要注意结构体的定义,比如__ept_hooked_page_info和__ept_hooked_function_info,可能包含钩子页和函数的详细信息。此外,涉及的内存池管理函数如pool_manager::request_pool和release_pool用于分配和释放内核内存,确保资源正确管理。 可能遇到的难点包括理解EPT机制,页表分割的原因,以及如何通过修改页表项来实现钩子。需要解释每个步骤对EPT表的影响,例如将原页的执行权限关闭,使访问触发EPT违例,从而跳转到钩子处理函数。 另外,hook_instruction_memory函数的具体实现可能涉及在伪造的页中插入跳转指令到钩子函数,而trampoline可能用于恢复执行原始函数。需要确保注释中说明这些步骤的作用。 最后,生成相关问题时,要围绕EPT钩子、内存管理、错误处理以及具体函数的作用等方面提出,帮助用户深入理解代码细节。</think>以下是详细注释的代码: ```cpp bool hook_function(void* target_function, void* hooked_function, void* trampoline, void** origin_function) { // 获取目标函数的物理地址 unsigned __int64 physical_address = MmGetPhysicalAddress(target_function).QuadPart; // 检查物理地址有效性 if (physical_address == NULL) { LogError("目标虚拟内存不存在于物理内存中"); return false; } // 检查该物理页是否已被钩住 PLIST_ENTRY current = &g_vmm_context->ept_state->hooked_page_list; while (&g_vmm_context->ept_state->hooked_page_list != current->Flink) { current = current->Flink; __ept_hooked_page_info* hooked_page_info = CONTAINING_RECORD(current, __ept_hooked_page_info, hooked_page_list); // 如果找到相同PFN的页 if (hooked_page_info->pfn_of_hooked_page == GET_PFN(physical_address)) { LogInfo("该页已存在钩子"); // 分配钩子函数信息结构 __ept_hooked_function_info* hooked_function_info = pool_manager::request_pool<__ept_hooked_function_info*>(pool_manager::INTENTION_TRACK_HOOKED_FUNCTIONS, TRUE, sizeof(__ept_hooked_function_info)); if (hooked_function_info == nullptr) { LogError("无法分配钩子函数结构内存"); return false; } // 如果需要保留原始函数 if(origin_function != nullptr) { // 分配第一个跳转桩内存 hooked_function_info->first_trampoline_address = pool_manager::request_pool<unsigned __int8*>(pool_manager::INTENTION_EXEC_TRAMPOLINE, TRUE, 100); if (hooked_function_info->first_trampoline_address == nullptr) { pool_manager::release_pool(hooked_function_info); LogError("无法分配跳转桩内存"); return false; } } // 填充钩子函数信息 hooked_function_info->virtual_address = target_function; hooked_function_info->second_trampoline_address = trampoline; hooked_function_info->fake_page_contents = hooked_page_info->fake_page_contents; // 安装钩子到内存 if (hook_instruction_memory(hooked_function_info, target_function, hooked_function, trampoline, origin_function) == false) { if(hooked_function_info->first_trampoline_address != nullptr) pool_manager::release_pool(hooked_function_info->first_trampoline_address); pool_manager::release_pool(hooked_function_info); LogError("钩子安装失败"); return false; } // 将钩子函数添加到页的钩子列表 InsertHeadList(&hooked_page_info->hooked_functions_list, &hooked_function_info->hooked_function_list); return true; } } // 检查是否需要分割PML2页表 if (is_page_splitted(physical_address) == false) { // 分配分割缓冲区 void* split_buffer = pool_manager::request_pool<void*>(pool_manager::INTENTION_SPLIT_PML2, true, sizeof(__ept_dynamic_split)); if (split_buffer == nullptr) { LogError("无法分配分割内存"); return false; } // 执行PML2页表分割 if (split_pml2(split_buffer, physical_address) == false) { pool_manager::release_pool(split_buffer); LogError("分割失败"); return false; } } // 获取PML1页表项 __ept_pte* target_page = get_pml1_entry(physical_address); if (target_page == nullptr) { LogError("获取PML1页表项失败"); return false; } // 分配钩子页信息结构 __ept_hooked_page_info* hooked_page_info = pool_manager::request_pool<__ept_hooked_page_info*>(pool_manager::INTENTION_TRACK_HOOKED_PAGES, true, sizeof(__ept_hooked_page_info)); if (hooked_page_info == nullptr) { LogError("无法分配钩子页信息结构"); return false; } InitializeListHead(&hooked_page_info->hooked_functions_list); // 分配钩子函数信息结构 __ept_hooked_function_info* hooked_function_info = pool_manager::request_pool<__ept_hooked_function_info*>(pool_manager::INTENTION_TRACK_HOOKED_FUNCTIONS, true, sizeof(__ept_hooked_function_info)); if (hooked_function_info == nullptr) { pool_manager::release_pool(hooked_page_info); LogError("无法分配钩子函数信息"); return false; } // 如果需要保留原始函数 if (origin_function != nullptr) { // 分配第一个跳转桩内存 hooked_function_info->first_trampoline_address = pool_manager::request_pool<unsigned __int8*>(pool_manager::INTENTION_EXEC_TRAMPOLINE, TRUE, 100); if (hooked_function_info->first_trampoline_address == nullptr) { pool_manager::release_pool(hooked_page_info); pool_manager::release_pool(hooked_function_info); LogError("无法分配跳转桩内存"); return false; } } // 填充钩子页信息 hooked_page_info->pfn_of_hooked_page = GET_PFN(physical_address); hooked_page_info->pfn_of_fake_page_contents = GET_PFN(MmGetPhysicalAddress(hooked_page_info->fake_page_contents).QuadPart); hooked_page_info->entry_address = target_page; // 修改原始页表项权限(不可读写,可执行) hooked_page_info->entry_address->execute = 0; hooked_page_info->entry_address->read = 1; hooked_page_info->entry_address->write = 1; // 保存原始和修改后的页表项 hooked_page_info->original_entry = *target_page; hooked_page_info->changed_entry = *target_page; hooked_page_info->changed_entry.read = 0; hooked_page_info->changed_entry.write = 0; hooked_page_info->changed_entry.execute = 1; hooked_page_info->changed_entry.physical_address = hooked_page_info->pfn_of_fake_page_contents; // 复制原始页内容到伪造页 RtlCopyMemory(&hooked_page_info->fake_page_contents, PAGE_ALIGN(target_function), PAGE_SIZE); // 填充钩子函数信息 hooked_function_info->virtual_address = target_function; hooked_function_info->second_trampoline_address = trampoline; hooked_function_info->fake_page_contents = hooked_page_info->fake_page_contents; // 安装钩子到内存 if(hook_instruction_memory(hooked_function_info, target_function, hooked_function, trampoline, origin_function) == false) { if (hooked_function_info->first_trampoline_address != nullptr) pool_manager::release_pool(hooked_function_info->first_trampoline_address); pool_manager::release_pool(hooked_function_info); pool_manager::release_pool(hooked_page_info); LogError("钩子安装失败"); return false; } // 将钩子函数添加到页的钩子列表 InsertHeadList(&hooked_page_info->hooked_functions_list, &hooked_function_info->hooked_function_list); // 将钩子页添加到全局钩子页列表 InsertHeadList(&g_vmm_context->ept_state->hooked_page_list, &hooked_page_info->hooked_page_list); // 刷新EPT上下文 invept_single_context(g_vmm_context->ept_state->ept_pointer->all); return true; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值