its a MIPS compiled binary with standard security, note that the binary is also statically linked.
looking at the decompilation in ghidra, the binary just takes an overflowed input
note that even though checksec result shows the binary has canary, it seems that it doesn't applied to every function.
so this challenge is just basically a ROP challenge in a unfamiliar architecture, that eventually has a goal of calling execve('/bin/sh', 0, 0), since the binary is statically compiled I just assumed system() is not an option.
Familiarizing with MIPS
so first thing first is to figure out what is the syscall instruction equivalent in MIPS, surprise surpise:
next is what register controls the syscall number, this can be figured out by a quick google search and the answer is v0
as for the arguments we can read the documentation and it reveals to be a0 , a1 and a2 for the first 3 arguments.
for the instructions, I used this cheatsheet for reference for a quick understanding without diving too deep to every single corner of MIPS
Exploitation
for ROP-ing in MIPS its more similar to ARM rather than x64, this is because the saved return address is stored in the ra register instead of in stack (in some occasion the return address does gets stored in the stack)
as for the gadgets, I will summarize their equivalent in x64 terms, I will refer most what I will expalained below from this writeup:
the convention for calling a function is usually in the form of jr/jalr $t9 where jr/jalr is the equivalent of the call instruction in x64 and $t9 is where the function address is usually stored. you will see this a lot in the gadgets.
with that in mind a return will be simply jr/jalr $ra
pop
there's no directly a pop instruction in MIPS, however there is a lw <register> <offset>($sp) with lw stands for load word to register at a certain offset from the stack pointer. the parentheses signifies to dereference $sp as an address, kind of similar to [rsp-<offset>] that you might usually see in x64.
move
in MIPS theres a lot of equivalent that is able to do this, move is a an obvious one, but also addiu, or 0 and other forms as well.
so, a gadget in MIPS ROP typically would involve a load instruction to either t9 or ra and end with jr/jalr to either t9 or ra, something like this:
now some of you might notice, if jalr or jr is the ret and call equivalent instruction, that means its the end of the gadget and why there's additional one instruction also specified after it?
turns out MIPS has this thing called delay slot which is best explain by the aforemention writeup:
the instruction directly after the branch gets executed before the branch/jump gets taken, and also if it's not taken
which means if the next instruction has to be taken into account as it could affect the register you have carefully setup.
now for the actual exploitation, lets first see what registers we have control after the overflow
first thing I take into care of is the syscall number register, v0
I ran ROPGadgets to extract the available gadgets and search for pop $v0 but found nothing, that can be chained, so I opted to control it with other registers, as we already controled much of the t[1-7] registers, I search for move $v0, $t[1-7] gadget but found nothing. what I found however is this:
since $s0 is once again in our control from the 2nd gadget chain, we can control what goes to $a2. next as I run the binary up to this chain, I notice that the $a1 is also got nullified by the result of said delay slot of move $a1, $fp, conviniently $fp was 0.
and so next is just to call syscall and shell should be spawned, here is the exploit being ran againts the remote server:
I was learning how to compile a kernel and my friend tampered with a file. Its just 3 lines, right? No way its a backdoor... right?
nc 152.42.183.87 10022
Author: Zafirr
Analysis
we're given these files
└──╼ [★]$ tree ..├──backdoor.patch├──bzImage├──docker-compose.yml├──Dockerfile├──Kconfig├──rootfs.cpio.gz├──run.sh└──xinetd
as the description says, the author compiled the code from source with 3 lines changed specified in backdoor.patch
--- a/linux-6.11.5/drivers/char/mem.c+++ b/linux-6.11.5/drivers/char/mem.c@@ -606,9 +606,6 @@ { int rc;- if (!capable(CAP_SYS_RAWIO))- return -EPERM;- rc = security_locked_down(LOCKDOWN_DEV_MEM); if (rc) return rc;
it removes the security check for permission for the character device of mem at /dev/mem
quoting from google, character device is:
Character device files in Linux are a type of special file that allows direct communication between user programs and hardware devices. They are part of the Linux filesystem and play a important role in Linux system administration.
we can pinpoint exactly what function the permission check is removed from by visiting the linux repository
static int open_port(struct inode *inode, struct file *filp)
{
int rc;
if (!capable(CAP_SYS_RAWIO))
return -EPERM;
rc = security_locked_down(LOCKDOWN_DEV_MEM);
if (rc)
return rc;
if (iminor(inode) != DEVMEM_MINOR)
return 0;
/*
* Use a unified address space to have a single point to manage
* revocations when drivers want to take over a /dev/mem mapped
* range.
*/
filp->f_mapping = iomem_get_mapping();
return 0;
}
further search, finds that the function is referenced twice