Shellcode Injection Attack

To launch a shellcode injection attack against a target program, we need to do:
  1. Inject a shellcode: If the target program contains functions such as scanf, fread etc, we can inject data containing a shellcode.

  2. Have the program execute the shellcode.

    However, the injected shellcode lies in somewhere in the memory. The main challenge is how to change the program control so that the injected shellcode can be executed..

Changing the program control by modifying the return address

We will take advantage of the following aspect of programs to achieve the second task above:
Main Attack Idea
Therefore, we first need to know the detail about what's going on when a function is called and then returned. Specifically, we need to know where the return address is stored in the memory.

Review

rip, rbp, rsp

We show the memory layout in the picture on the right:

callq and ret instructions

Sample Source Code

We will see what's going on in more detail by using the GDB. In particular, let's consider the following program:
(gdb) l
...
9       int main()
10      {
(gdb)
11          char buf_main[] = "in main";
12          printf("%s\n", buf_main);
13          f1(10);
14          return 0;
15      }
16
17      int f1(int n)
18      {
19          char buf_f1[] = "in f1";
20          printf("%s: n= %d\n", buf_f1, n);
21          return 0;
22      }
...
Let's see how the program works
(gdb) r
Starting program: .../callstack
in main
in f1: n= 10
[Inferior 1 (process 30869) exited normally]
Let's set breakpoints to see the detailed memory dumps. In particular, we set the breakpoints right before and after the function call, and also right before returning.
// in main: right before calling f1 in main
(gdb) b 13
Breakpoint 1 at 0x400588: file callstack.c, line 13.
// in f1: right before returing to main
(gdb) b 21
Breakpoint 2 at 0x4005ca: file callstack.c, line 21.
// in main: after f1 is done
(gdb) b 14
Breakpoint 3 at 0x400592: file callstack.c, line 14.

Breakpoints 1 -> 2 -> 3: Stack and Registers

Below we show the hexdump of the stack at the moment of the above three breakpoints. To show how the stack changes, we aligned the dumps so that the same address should be shown in the same line. Notice the different fonts and back-ground colors.
(gdb) p &main
$1 = (int (*)()) 0x400566 <main>
(gdb) p &f1
$2 = (int (*)(int)) 0x400599 <f1>
(gdb) r
Starting program: .../callstack 
in main

Breakpoint 1, main () at callstack.c:13
13          f1(10);
(gdb) p $rip
$3 = (void (*)()) 0x400588 <main+34>
(gdb) p $rsp
$4 = (void *) 0x7fffffffe7c0
(gdb) p $rbp
$5 = (void *) 0x7fffffffe7d0








(gdb) hd $rsp $rbp-$rsp
0x7fffffffe7c0: 69 6E 20 6D       i n . m
0x7fffffffe7c4: 61 69 6E 00       a i n .
0x7fffffffe7c8: 00 00 00 00       . . . .
0x7fffffffe7cc: 00 00 00 00       . . . .
(gdb) c
Continuing.

Breakpoint 2, f1 (n=10) at callstack.c:21
21          return 0;
(gdb) p $rip
$6 = (void (*)()) 0x4005ca <f1+49>
(gdb) p $rsp
$7 = (void *) 0x7fffffffe790
(gdb) p $rbp
$8 = (void *) 0x7fffffffe7b0
(gdb) p &n
$9 = (int *) 0x7fffffffe79c
(gdb) p &buf_f1
$10 = (char (*)[6]) 0x7fffffffe7a0
(gdb) hd $rsp 64
0x7fffffffe790: C0 E7 FF FF       . . . .
0x7fffffffe794: FF 7F 00 00       . . . .
0x7fffffffe798: FA D7 A7 F7       . . . .
0x7fffffffe79c: 0A 00 00 00       . . . .
0x7fffffffe7a0: 69 6E 20 66       i n . f
0x7fffffffe7a4: 31 00 00 00       1 . . .
0x7fffffffe7a8: D0 E7 FF FF       . . . .
0x7fffffffe7ac: FF 7F 00 00       . . . .
0x7fffffffe7b0: D0 E7 FF FF       . . . .
0x7fffffffe7b4: FF 7F 00 00       . . . .
0x7fffffffe7b8: 92 05 40 00       . . @ .
0x7fffffffe7bc: 00 00 00 00       . . . .
0x7fffffffe7c0: 69 6E 20 6D       i n . m
0x7fffffffe7c4: 61 69 6E 00       a i n .
0x7fffffffe7c8: 00 00 00 00       . . . .
0x7fffffffe7cc: 00 00 00 00       . . . .
(gdb) c
Continuing.

Breakpoint 3, main () at callstack.c:14
14          return 0;
(gdb) p $rip
$11 = (void (*)()) 0x400592 <main+44>
(gdb) p $rsp
$12 = (void *) 0x7fffffffe7c0
(gdb) p $rbp
$13 = (void *) 0x7fffffffe7d0
















(gdb) hd $rsp $rbp-$rsp
0x7fffffffe7c0: 69 6E 20 6D       i n . m
0x7fffffffe7c4: 61 69 6E 00       a i n .
0x7fffffffe7c8: 00 00 00 00       . . . .
0x7fffffffe7cc: 00 00 00 00       . . . .
(gdb) c
Continuing.
[Inferior 1 (process 31033) exited normally]

Remarks

  1. [callq instruction] The return address has been written on top of the stack.
    (gdb) p $rip
    $3 = (void (*)()) 0x400588 <main+34>
    
    (gdb) hd $rsp $rbp-$rsp
    0x7fffffffe7c0: 69 6E 20 6D       i n . m
    0x7fffffffe7c4: 61 69 6E 00       a i n .
    0x7fffffffe7c8: 00 00 00 00       . . . .
    0x7fffffffe7cc: 00 00 00 00       . . . .
    
    
    
    0x7fffffffe7b8: 92 05 40 00     . . @ .
    0x7fffffffe7bc: 00 00 00 00      . . . .
    0x7fffffffe7c0: 69 6E 20 6D      i n . m
    0x7fffffffe7c4: 61 69 6E 00      a i n .
    0x7fffffffe7c8: 00 00 00 00      . . . .
    0x7fffffffe7cc: 00 00 00 00      . . . .
    
    When function f1 is finished, the program needs to jump to the main function back.

  2. [Function prolog assembly code of f1]. $rbp of function main is pushed on to the stack. When function f1 is finished and the control returns to the main function, the program now should go back to use the stack frame of function main. In particular,
  3. New stack frame for f1.
    When function f1 is called, a new stack frame for f1 has been set up (data in the red color in breakpoint 2). The new stack frame is on top of the old stack frame (i.e., data in the blue color, which is the stack from for function main)

Going Back To our Goal

Summary of call stacks

Please remember the figure on the right.

What if?

  • What if the following return address is changed somehow to something else?
    0x7fffffffe7b8: 92 05 40 00       . . @ .
    0x7fffffffe7bc: 00 00 00 00       . . . .
    
    For example, what if the above data magically changes to the following?
    0x7fffffffe7b8: 88 05 40 00       . . @ .
    0x7fffffffe7bc: 00 00 00 00       . . . .
    
    Answer:
    Function f1 will return to line 13 (0x400588) instead of line 14 (0x400892)!!

Our goal

Recall we want to achieve the following tasks:
  1. Inject a shellcode: If the target problem contains strcpy or fread routine, we can inject a data containing the shellcode.

  2. Have have the program execute the shellcode.

    However, the shellcode lies in somewhere in the stack (not the text area, where the normal program code lies). The main challenge is how to control the program counter (i.e., \$rip) so that it points to the shellcode. Now, we have a good idea to achieve this task:

    Overflow the buffer and modify the return address to point to the shellcode! Then the funtion will return to the shellcode, and the program will execute the shellcode!

Stack Smashing: General Idea

Here's the general idea of stack smashing.
Overflow the buffer with a payload.



Payload structure

The payload can be constructed as follows (see also the picture above):
  1. Shellcode
  2. Dummy data. This region is almost useless except that it allows us to finally modify the return address. We will just put a bunch of arbitrary bytes, say, 0x90. (You can choose any other value instead of 0x90).
  3. Address of shellcode. By modifying the return address to point to the shellcode, the attack will have the function return to the shellcode.

Things to consider in constructing the payload

  1. Shellcode: We have it. Easy.
  2. Dummies: We need to figure out how many dummy bytes to put in the payload.
  3. Address of shellcode: It's a memory address that's determined in runtime. So, we need to figure this out, too.