技术交流

好好学习,天天向上。

0%

替换内核常量的方法

[toc]

背景

已知我们可以编译模块,问题是,如何才能通过内核模块来对系统的const变量(常量)进行修改?对计算机系统来说,数据都在内存里。有内存就有地址,有地址就有页表,页表里面是页表项,页表项控制读写权限。内核常量往往会被编译到系统的只读区域,只要能将该区域对应的页表项的控制标志上加上读标志,就可以实现对目标变量的编辑了。

思路

  • 第一步,找,通过地址找对应的pte,不会找就看看page_fault自己是怎么找的,然后抄
  • 第二步,改,看看对应平台怎么改,加上读标志就行了
  • 第三步,写,用指针去修改目的常量所在地址

aarch64

找pte

通过分析aarch64的page_fault流程,发现其处理内核地址的page_fault时,会进入__do_kernel_fault函数,进而有个show_pte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*
page_fault
|
----->__do_kernel_fault
|
-----> show_pte
*/
void show_pte(unsigned long addr)
{
struct mm_struct *mm;
pgd_t *pgdp;
pgd_t pgd;

if (addr < TASK_SIZE) {
/* TTBR0 */
mm = current->active_mm;
if (mm == &init_mm) {
pr_alert("[%016lx] user address but active_mm is swapper\n",
addr);
return;
}
} else if (addr >= VA_START) {
/* TTBR1 */
mm = &init_mm;
} else {
pr_alert("[%016lx] address between user and kernel address ranges\n",
addr);
return;
}

pr_alert("%s pgtable: %luk pages, %u-bit VAs, pgdp = %p\n",
mm == &init_mm ? "swapper" : "user", PAGE_SIZE / SZ_1K,
mm == &init_mm ? VA_BITS : (int) vabits_user, mm->pgd);
pgdp = pgd_offset(mm, addr);
pgd = READ_ONCE(*pgdp);
pr_alert("[%016lx] pgd=%016llx", addr, pgd_val(pgd));

do {
pud_t *pudp, pud;
pmd_t *pmdp, pmd;
pte_t *ptep, pte;

if (pgd_none(pgd) || pgd_bad(pgd))
break;

pudp = pud_offset(pgdp, addr);
pud = READ_ONCE(*pudp);
pr_cont(", pud=%016llx", pud_val(pud));
if (pud_none(pud) || pud_bad(pud))
break;

pmdp = pmd_offset(pudp, addr);
pmd = READ_ONCE(*pmdp);
pr_cont(", pmd=%016llx", pmd_val(pmd));
if (pmd_none(pmd) || pmd_bad(pmd))
break;

ptep = pte_offset_map(pmdp, addr); // 这里,最终找到了ptep
pte = READ_ONCE(*ptep);
pr_cont(", pte=%016llx", pte_val(pte));
pte_unmap(ptep);
} while(0);

pr_cont("\n");
}

修改权限

asm/pgtable.h中有如下定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static inline void set_pte(pte_t *ptep, pte_t pte)
{
WRITE_ONCE(*ptep, pte);

/*
* Only if the new pte is valid and kernel, otherwise TLB maintenance
* or update_mmu_cache() have the necessary barriers.
*/
if (pte_valid_not_user(pte))
dsb(ishst);
}

static inline pte_t pte_mkwrite(pte_t pte)
{
pte = set_pte_bit(pte, __pgprot(PTE_WRITE));
pte = clear_pte_bit(pte, __pgprot(PTE_RDONLY));
return pte;
}

一句话就可以修改

1
set_pte(ptep, pte_mkwrite(*ptep));

x86-64

找pte

从page_fault找过来的话,可以看到vmalloc_fault有走page walk的流程,按照里面的思路可以实现对ptep的查找。实际上,x86已经有一个现成的函数用来找内核虚拟地址对应的ptep了。

1
2
3
4
5
pte_t *lookup_address(unsigned long address, unsigned int *level)
{
return lookup_address_in_pgd(pgd_offset_k(address), address, level);
}
EXPORT_SYMBOL_GPL(lookup_address);

在内核代码中有如下定义,可以看到内核虚拟地址的页表也是从init_mm开始找的

1
#define pgd_offset_k(address) pgd_offset(&init_mm, (address))

再来看lookup_address_in_pgd函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
pte_t *lookup_address_in_pgd(pgd_t *pgd, unsigned long address,
unsigned int *level)
{
p4d_t *p4d;
pud_t *pud;
pmd_t *pmd;

*level = PG_LEVEL_NONE;

if (pgd_none(*pgd))
return NULL;

p4d = p4d_offset(pgd, address);
if (p4d_none(*p4d))
return NULL;

*level = PG_LEVEL_512G;
if (p4d_large(*p4d) || !p4d_present(*p4d))
return (pte_t *)p4d;

pud = pud_offset(p4d, address);
if (pud_none(*pud))
return NULL;

*level = PG_LEVEL_1G;
if (pud_large(*pud) || !pud_present(*pud))
return (pte_t *)pud;

pmd = pmd_offset(pud, address);
if (pmd_none(*pmd))
return NULL;

*level = PG_LEVEL_2M;
if (pmd_large(*pmd) || !pmd_present(*pmd))
return (pte_t *)pmd;

*level = PG_LEVEL_4K;

return pte_offset_kernel(pmd, address); // 这里,最终找到了ptep
}

修改权限

和aarch64保持一致。

其他需要的内容

x86-64的lookup_address直接返回ptep,不需要修改这个函数,可以直接调用。而aarch64需要手动将找ptep的流程剥离出来,写进模块代码里。由于默认init_mm不导出,所以需要通过符号查找的方法找到init_mm的地址

1
2
#include <linux/kallsyms.h>
struct mm_struct init_mm = kallsyms_lookup_name("init_mm");