Late Writeups – cheer_msg, jmper (seccon)

Recently, I progressed through the “cheermsg” and “jmper” challenges from the SECCON CTF event last year. I was not able to solve these during the time allocated for the event. I will document my solves below.

Note that both challenges rely on libc – for ease of testing, I have done these with my CTF VM’s libc, instead of the provided ones.

cheer_msg

This challenge was presented as a binary with libc, you can download this here.

Disassembling the challenge, we can determine the vulnerable function is a “write-what-where” in the “message”, stemming from an extremely oddly constructed pointer based on the message length first entered by the user:

cheer_msg_x

By specifying a negative message length, we are able to write to arbitrary locations on the stack. Specify -152, and we’re able to write to the return address of the current function:

cheer_msg_huzzah

From here, this is a reasonably straightforward exercise in multi-stage ROP chains. Our ultimate goal is to make a call to system(“/bin/sh”). To do this, we construct multiple ROP chains which return to main:

  • ROP Chain 1: Infoleak GOT (to derive system in libc)
    • printf
    • main (return address)
    • 0x0804A010 (printf in the GOT)
  • ROP Chain 2: Seed /bin/sh into the data section
    • getnline
    • main (return address)
    • 0x0804A030 (data section)
    • 0x41414141 (length. doesn’t really matter)
  • ROP Chain 3: Call system
    • system (derived from initial infoleak in ROP chain 1)
    • main (return address – may as well be exit())
    • 0x0804A030 (data section)

A quick Python script later, and we have a shell:

cheer_msg_shell

Note the initial failure: this doesn’t always work cleanly, if an address in the GOT contains a zero (and terminates the infoleak too early), we simply exit and try again. It is possible to continue ROP chaining to leak more bytes of the GOT, but given the possibility of a GOT address containing a zero versus the possibility of it not, it’s faster to just try again.

You can download the Python script here.

jmper

This challenge was presented as a binary with libc, you can download this here.

This challenge is presented as an executable which allows operations on a number of “students”. Each “student” is represented in memory like this:

struct student{
  char memo[0x20]
  char *name;
}

The application allows the user to create students (which malloc’s the structure above as well as the ‘name’ and stores it in a big array), and view or edit the memo or name of any existing student record.

The vulnerability comes in the form of an off-by-one in the “write memo” function: this allows a user to write a single byte past the end of a memo or a name:

jmper_vulnfunction

It’s possible to abuse this to create a limited read/write-what-where condition, by overflowing the “memo” field for student zero, one can write within a limited space surrounding student zero’s “name” field:

jmper_overwriteptr

 

As illustrated above, we are able to exert limited control over the name pointer of a student object. With this limited level of control, we can read and write to an arbitrary location. We can relax this restriction, by creating a second student object, and overflowing student[0].memo to make student[0].name point to the address of student[1].name.

We don’t need to know where it is exactly, but if we know where it is relative to student[0].name, and that’s within a single-byte-overflow range, we’re in business – and this relative distance isn’t changing between execution’s so ASLR isn’t a problem.

We then set student[0].name (pointing to student[1].name’s address) to an arbitrary address (e.g. the GOT), and then read it via reading student[1].name (which is now set to the GOT).

This becomes an arbitrary read-write-where primitive. From here, we can use the setjmp/longjmp functionality to gain code execution:

setjmp_longjmp

We can abuse this functionality, as we know that setjmp “saves” the state of execution to a buffer, the address of which is stored locally: between the read-write primitive, the GOT and the setjmp buffer, we should be able to return execution to system pretty easily – unfortunately, it isn’t that simple. Viewing the buffer in gdb shows the following:

jmper_showbuf

Oddly enough, what appears to be RIP and RSP in the jump buffer isn’t, it’s actually what’s stored in the red box. A bit of documentation surfing and traipsing through the disassembly of setjmp, and we can determine that the pointer is mangled through a rather simple function:

PTR_MANGLE = (PTR ^ FS30_STACK_COOKIE) ROL 0x11

Using this, we can de-obfuscate the pointers cleanly and write our own new RIP and RSP. Simply setting RSP to system() won’t work – if RDI isn’t clear, system() on a 64-bit system will attempt to read the first argument out of RDI.

At this point, I looked to an existing writeup for guidance. Credit to Inndy@Github for this idea: https://github.com/Inndy/ctf-writeup/blob/master/2016-seccon/jmper/jmper.py.

From here, we can use a simple static pop rdi; ret ROP gadget to set RDI, as well as prepare the stack for a return to system(). Therefore, our order of operations is as follows, abusing the arbitrary write primitive above:

  • Leak a function from the GOT to locate system()
  • Leak RSP from jmpbuf
  • Write jmpbuf->RSP[0] = a pointer to /bin/sh (an unmodified student[1].name works nicely, see above – we’ll have the address of this anyway as we set up the read-write-anywhere primitive).
  • Write jmpbuf->RSP[1] = system()
  • Write jmpbuf->RIP = POP RDI, RET rop gadget

We then trigger longjmp by adding 29 more students. When longjmp gets triggered, RIP and RSP are set according to the values in jmpbuf, as follows:

  • RIP is POP RDI; RET
  • RDI is now a pointer to /bin/sh
  • RIP is RET
  • RIP is system(), and we get shell:

jmper_shell

You can download the Python script for the full exploit here.

Tools – Binary Ninja

During this time, I did the reverse engineering for jmper exclusively in Binary Ninja. This isn’t a tool I use often, and “feels different” for someone used to working in IDA. To me, it was clear that this was designed with a fresh pair of eyes, as opposed to IDA being layer upon layer of useful functionality (the “evolved” feel).

Two things of note, for reference:

  • The option to display opcodes and addresses is hidden in the bottom right, in the “Options” menu next to “ELF” and “Linear”:
    binja_showoptions
  • There appears to be no function to show segment permissions in the tool itself, I had to rely on objdump –headers to see which sections were writeable in the executable.

Despite it’s relative youth, this appears to be a solid tool. I look forward to experimenting more with it’s capabilities.

As always, I’d like to thank the SECCON team for putting together a fantastic and challenging CTF last year. I was (fortunately!) able to save a great many challenges from this CTF for future reference, and look forward to solving them at my own pace, if you’d like to play along, come find me and I’d be happy to share what I have with you.

I’ll see you all in the Insomni’hack teaser CTF in a week’s time.

About Norman

Sometimes, I write code. Occasionally, it even works.
This entry was posted in Bards, Computers, Jesting and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s