Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Offensive Shellcode from Scratch

You're reading from   Offensive Shellcode from Scratch Get to grips with shellcode countermeasures and discover how to bypass them

Arrow left icon
Product type Paperback
Published in Apr 2022
Publisher Packt
ISBN-13 9781803247427
Length 208 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Rishalin Pillay Rishalin Pillay
Author Profile Icon Rishalin Pillay
Rishalin Pillay
Arrow right icon
View More author details
Toc

What is shellcode?

The term shellcode was originally derived based on its purpose to spawn or create a reverse shell via the execution of code. It has nothing to do with shell scripting, which essentially entails writing scripts of bash commands to complete a task.

Shellcode interacts with the registers and functions of a program by directly manipulating the program in order to perform an outcome. Due to this interaction, it is written in an assembler and then translated into hexadecimal opcodes. We will cover assemblers and opcodes later in this chapter.

When a vulnerability is discovered, shellcode can be used to exploit that vulnerability. Depending on the complexity of the vulnerability, you may make use of a few lines of code to exploit it. In some cases, the size of your shellcode can be quite substantial. The bottom line is that sometimes, obtaining a reverse shell or a specific outcome when using shellcode can be very lightweight. This results in a very efficient attack that can be used if you provide the right input to the program.

Examples of shellcode

Let's take a look at a few samples. We will begin by looking at a simple piece of code that is written in C. The purpose of this code is to return a shell. The privilege level of the returned shell will depend on the privilege level of the target program at the time this shellcode is run. In simple terms, the newly spawned shell will inherit the same permissions as the target program:

#include <stdio.h>
int main()
{
    char *args[2];
    args[0] = "/bin/sh";
    args[1] = NULL;
    execve("/bin/sh", args, NULL);
    return 0;
}

When this compiled and modified further with an editor, it's possible to turn it into input strings that can then be used against a vulnerable program to obtain a shell.

There are additional steps required to make this piece of code useable.

Shellcode is often used with buffer overflow attacks. In its simplest terms, a buffer overflow happens when a program writes data into memory that is larger than what has been have reserved. The end result is that the program may crash, overwrite data, or execute other code.

In the following piece of code, you will notice that the code is expecting an input of a certain number of characters. This is defined by the char input [12] command:

#include <stdio.h>
int main()
{
    char input[12];
    printf("Please enter your password: ");
    // If the password is longer than 12 characters, a buffer overflow will happen;
    scanf("%s", input);
    printf("Your password is %s", input);
    return(0);
}

Because there is no input validation and the program has reserved 12 bytes of memory for the input, if a string of data longer than 12 bytes is entered, then the application will crash. This specific action may not be useful if you are looking at obtaining a reverse shell, but it is useful if your intent is to cause an application to crash.

Using the logic of a buffer overflow, a carefully crafted piece of shellcode can exploit this vulnerability. The end result could be a specific attack such as a stack-based buffer overflow attack, or a heap-based buffer overflow attack. We will cover these later in the book.

Now on to a more complex example of shellcode. In January 2021, a malware sample was shared with a research team at Check Point. This malware sample resembled a loader that belongs to a well-known APT group called Lazarus. This malware made use of a phishing attack that included a document loaded with a macro that was used as a job application on LinkedIn, a popular platform for professionals.

The macro in the document made use of Visual Basic for Applications (VBA) shellcode that did not contain suspicious APIs such as VirtualAlloc, WriteProcessMemory, or CreateThread. These types of APIs are usually detected by endpoint protection products since these relate to memory allocation, writing to memory, and starting a new CPU thread.

Now, when this VBA macro was executed, it made use of a number of interesting techniques. Firstly, it created aliases of the various API calls so that its intent was less obvious. It then made use of various calls such as HeapCreate and HeapAlloc to create an executable memory location. Later, it made use of functions such as FindImage that employed a UuidFromStringA API function that had a list of hardcoded UUID values. This UuidFromStringA ultimately provides a pointer to a memory heap address allowing it to be used to decode data and write it to memory without making use of the more common functions such as memcpy or WriteProcessMemory. The following is a snippet of the shellcode; however, here it's executing the code to start up the Windows calculator application, which is referenced by its executable name calc, but you can see the complexity of the shellcode:

#include <Windows.h>
#include <Rpc.h>
#include <iostream>
#pragma comment(lib, "Rpcrt4.lib")
const char* uuids[] =
 
{ 
    "6850c031-6163-636c-5459-504092741551",
    "2f728b64-768b-8b0c-760c-ad8b308b7e18",
    ..snip..
};
int main() 
{
 
    HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
    void* ha = HeapAlloc(hc, 0, 0x100000);
    DWORD_PTR hptr = (DWORD_PTR)ha;
    int elems = sizeof(uuids) / sizeof(uuids[0]);
    
    for (int i = 0; i < elems; i++) {
          RPC_STATUS status = UuidFromStringA((RPC_CSTR)uuids[i], (UUID*)hptr);
          if (status != RPC_S_OK) {
               printf("UuidFromStringA() != S_OK\n");
               CloseHandle(ha);
                return -1;
        }
         hptr += 16;
    }
    printf("[*] Hexdump: ");
    for (int i = 0; i < elems*16; i++) {
        printf("%02X ", ((unsigned char*)ha)[i]);
    }
    EnumSystemLocalesA((LOCALE_ENUMPROCA)ha, 0);
    CloseHandle(ha);
    return 0;
}

We will not go further into the analysis of this shellcode, the VBA, or the attack since it's out of scope for this book. The aim of this example is to show you the complexity of what shellcode looks like and how it can make use of multiple elements.

Shellcode versus a payload

As we start to dig into the components of shellcode, let's make a clear differentiation between shellcode and payloads. Often these are referred to as the same thing; however, they are actually different.

Payloads

A payload is a piece of custom code that an attacker wants the system to run. This custom code can be delivered by various means, such as a script or even within shellcode. An example of a payload is a reverse shell that generates a Windows Command Prompt connection. It can also be a bind shell, which is a payload that binds a shell to a listening port on the target machine to which the attacker can connect. A payload might potentially be as basic as a set of commands to run on the target operating system.

Think of the payload as the code that you want to run. It serves the purpose of doing something useful that you want it to do. Payloads can be included within shellcode so that they are executed by a program.

The following is an example of shellcode that has a payload included. The payload is highlighted for reference:

#include <windows.h>
void main() {
  void* exec;
  ...snip...
  unsigned char payload[] =
    "\x38\x45\xff\x48\xf7\xe7\x65\x48\x8b\x58\x60\x48\x8b\x5b\x18\x41\x6b\x5b\x20\x48\x8b\x1b\x48\x8b\x1b\x48\x8b\x5b\x20\x49\x45\xd8\x8b"
    ...snip...
  unsigned int payload_len = 205;
  exec = VirtualAlloc(0, payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  RtlMoveMemory(exec, payload, payload_len);
  rv = VirtualProtect(exec, payload_len, PAGE_EXECUTE_READ, &oldprotect);
  th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)exec, 0, 0, 0);
  WaitForSingleObject(th, -1);
}

In the example, you will notice that we have a payload incorporated into the shellcode. As the shellcode runs, memory is allocated using exec = VirtualAlloc(…), then references the payload using …exec, payload…, and ultimately runs the payload.

Shellcode

Shellcode is frequently used as part of the payload when a software vulnerability is exploited to gain control of or exploit a compromised computer. Think of shellcode as a set of precisely designed commands that may be executed once injected into a running application. In relation to a vulnerability, it's a set of instructions used as a payload. In most cases, the shellcode is written in assembly language. In most situations, a command shell or a Meterpreter shell will be supplied after the target computer has completed the set of instructions. This brings us back to its original purpose, as discussed in the introduction of this chapter, which is to establish a shell.

You have been reading a chapter from
Offensive Shellcode from Scratch
Published in: Apr 2022
Publisher: Packt
ISBN-13: 9781803247427
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime