1. What is a Shellcode ?

A shellcode is a string that represents executable binary code. Originally intended to launch a shell ('/bin/sh' under Unix or 'cmd.exe' under DOS and Microsoft Windows for example), the word has evolved to mean any malicious code that hijacks a program from its normal execution. A shellcode can be used by a hacker wanting access to the command line.

2. How it works ?

Generally, shellcodes are injected into the computer's memory by exploiting a buffer overflow. In this case, the execution of the shellcode can be triggered by the replacement in the stack of the normal return address by the address of the injected shellcode. Thus, when the routine is complete, the microprocessor, which should normally execute the instructions located at the return address, executes the shellcode.
Indeed, the shellcode is executed directly by the CPU, which means that no compilation / linkage is required.

It is important to remember the following points for the rest of this tutorial:

  • The size of the shellcode is important, the smaller it is, the better it is.

The reason for this is that when using a buffer overflow (for example), we do not necessarily have a lot of allocated memory, which means that if our shellcode is too big, we will not inject everything into memory and therefore, the operation will fail.

  • Be careful of bad characters like:
    • 0x00, 0x0A, 0x0D

Indeed, we must take into account the function vulnerable to our buffer overflow and understand how it works. In C, the string character is '\0', which is a NULL Byte. Which means that if we run the strcpy function, this function will stop copying our payload as soon as it finds an end-of-string character.

For the function "gets", it is the character of line break which indicates the end (\n), that is why we must not have 0x0A in our shellcode.

3. Where can I find shellcodes ?

It is possible to find shellcodes everywhere on the internet. However, there are specialized sites like:

  • http://shell-storm.org/shellcode/
  • https://www.exploit-db.com/shellcode/

However, I want to draw your attention to an important point. It is not recommended to run a shellcode find on the internet without analyzing it before.
Indeed, we are never safe that the shellcode executes a malicious action (ex: rm -rf /).

4. How to run and / or analyze a shellcode?

To analyze a shellcode, just write the following code:

# gcc -z execstack -fno-stack-protector shellcode.c

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

unsigned char shellcode[] = \
"SHELLCODE";

int main(int ac, char **av)
{
    printf("Shellcode Length:  %d\n", strlen(shellcode));

    int (*ret)() = (int(*)())shellcode;

    ret();
}

Once the code compiled, it is possible to analyze it with a debugger.

gdb-peda$ pdisass main
Dump of assembler code for function main:
   0x0000000000400527 <+0>: push   rbp
   0x0000000000400528 <+1>: mov    rbp,rsp
   0x000000000040052b <+4>: sub    rsp,0x10
   0x000000000040052f <+8>: lea    rdi,[rip+0x200b0a]        # 0x601040 <code>
   0x0000000000400536 <+15>:    call   0x400430 <strlen@plt>
   0x000000000040053b <+20>:    mov    esi,eax
   0x000000000040053d <+22>:    lea    rdi,[rip+0xb0]        # 0x4005f4
   0x0000000000400544 <+29>:    mov    eax,0x0
   0x0000000000400549 <+34>:    call   0x400440 <printf@plt>
   0x000000000040054e <+39>:    lea    rax,[rip+0x200aeb]        # 0x601040 <code>
   0x0000000000400555 <+46>:    mov    QWORD PTR [rbp-0x8],rax
   0x0000000000400559 <+50>:    mov    rdx,QWORD PTR [rbp-0x8]
   0x000000000040055d <+54>:    mov    eax,0x0
   0x0000000000400562 <+59>:    call   rdx  <------------- PUT YOUR BREAKPOINT HERE
   0x0000000000400564 <+61>:    mov    eax,0x0
   0x0000000000400569 <+66>:    leave  
   0x000000000040056a <+67>:    ret    
End of assembler dump.

I advise you to put a breakpoint on the instruction "call rdx", advance once "step" and perform static analysis on the code that will result, to avoid any problem.

There is another technique for running a shellcode directly from python code:

#/usr/bin/env python

from ctypes import *

libc = CDLL('libc.so.6')
mprotect = libc.mprotect
mprotect.restype = c_int
mprotect.argtypes = [c_void_p, c_size_t, c_int]

shellcode_data = (b"SHELLCODE")
shellcode = create_string_buffer(shellcode_data)
function = cast(shellcode, CFUNCTYPE(c_void_p))

addr = cast(function, c_void_p).value
pagesize = libc.getpagesize()
addr_page = (addr // pagesize) * pagesize
for page_start in range(addr_page, addr + len(shellcode_data), pagesize):
    assert mprotect(page_start, pagesize, 0x7) == 0

function()

That's all for today, in the next class, we'll see how to create a shellcode :).