Install our userspace function in place of the syscall 223
Our function should create and install credentials with root id for our process
Explanation
The kernel has a new syscall on syscall number 223 which will convert lower case bytes to upper case.
The system call defined in syscall.c has a bug! The kernel does not check whether the supplied paramaters are coming from the user address space. (user attribute is missing from the param declaration.)
Since this kernel remapped the syscall table to be writable we can use this sycall to actually overwrite its syscall entry with a function we supply! The linux kernel does not have a separate user and kernel linear virtual address space for performance reasons.
When a syscall is being executed the linux kernel is running on behalf of a process. It also has access to the address space of the process! This makes it possible to reference userspace memory.
What this means is that we can install our own syscall by overwriting the syscall entry 223 then calling the syscall 223 again.
At this point we also have access to kernel space obviously so we can use kernel functions (we can look them up from /proc/kallsyms as they are readable without sudo on that machine).
We simply clone the current credentials then we overwrite the new cred structure with the id 0 (root id) then we install the new credentials. After this we can return from the syscall and we have root privileges!
Plan
Lookup the addresses of the following kernel symbols (kernel address space layout randomization is turned off on the host):
You can use:
cat /proc/kallsyms | grep <symbol>
sys_call_table
prepare_creds
commit_creds
sys_call_table is where the function pointers to the syscall handlers are stored.
struct cred* prepare_creds() is a kernel function (linux/kernel/cred.c) to clone the current cred struct.
int commit_creds(struct cred*) will install the new credentials to the process.
Remap our text section to the heap and make it executable. (Actually it is enough to remap the code that will be executed when in kernel mode)
Install our userspace callback function into the 223 syscall entry. The entry 223 is unused - this is where the lower_to_upper syscall is stored originally.
This is done by supplying to the buggy syscall:
destination address:
sys_call_table + 223 * sizeof(void*)
source address:
our remapped callback’s address (NOTICE: remap to an address which will result in an address of the callback so that none of the bytes of the address falls into the lowercase value range as the buggy syscall will try to make it uppercase value.)
Trigger the syscall 223 again - now are injected callback will be executed.
Now we should have root priviliges and now just simply open and read the /root/flag file.
The new userspace ‘syscall’
On x86 linux kernel uses registers to pass parameters to function if it can so we need to repsect that when interfacing with internal kernel functions.
https://kernelnewbies.org/ABI
Once the syscall returns the second time (the first was to exploit the bug and overwrite the syscall entry) we have root priviliges.
Now we can use open() and read() syscalls on the /root/flag file simply in userspace.
Compilation
gcc -no-pie -o exploit exploit.c
no-pie means not position independent code. We need fixed userspace addresses.
Note: On x86 it is very important to add the attribute mregparm= to function interfacing kernel functions in order to pass up to arguments through registers!
The Linux kernel uses ABI that expects that.