Search code examples
c#winapishellcodeprocess-injection

WinAPI CreateThread killing process


I am attempting to write a C# function that executes arbitrary shellcode. It seems to be working, except that when the created thread exits, the entire process terminates. I did not come up with this code myself, but instead got it primarily from this site: https://webstersprodigy.net/2012/08/31/av-evading-meterpreter-shell-from-a-net-service/

Here is the function that executes the shellcode:

public void ExecuteShellCode(String code)
{
    //pipe msfvenom raw to xxd -p -c 999999 (for example)
    byte[] shellcode = StringToByteArray(code);
    UInt32 funcAddr = VirtualAlloc(0, (UInt32)shellcode.Length, 0x1000, 0x40);
    Marshal.Copy(shellcode, 0, (IntPtr)(funcAddr), shellcode.Length);
    IntPtr hThread = IntPtr.Zero;
    UInt32 threadId = 0;
    hThread = CreateThread(0, 0, funcAddr, IntPtr.Zero, 0, ref threadId);
    WaitForSingleObject(hThread, 0xFFFFFFFF);
}

I call it using the following example:

(note - you probably shouldn't run random shellcode from the internet, this example is innocuous, but you shouldn't take my word for it)

I generated the shellcode with msfvenom - it just pops a messagebox.

rsh.ExecuteShellCode(@"d9eb9bd97424f431d2b27731c9648b71308b760c8b761c8b46088b7e208b36384f1875f35901d1ffe1608b6c24248b453c8b54287801ea8b4a188b5a2001ebe334498b348b01ee31ff31c0fcac84c07407c1cf0d01c7ebf43b7c242875e18b5a2401eb668b0c4b8b5a1c01eb8b048b01e88944241c61c3b20829d489e589c2688e4e0eec52e89fffffff894504bb7ed8e273871c2452e88effffff894508686c6c20416833322e64687573657230db885c240a89e656ff550489c250bba8a24dbc871c2452e85fffffff686f6b582031db885c240289e368732158206869656e64687920467268486f776431c9884c240e89e131d252535152ffd031c050ff5508");

while (true)
{
    Thread.Sleep(42);
}

If you need the code to convert the string to bytes, it is here:

private static byte[] StringToByteArray(String opcodes)
{
    int NumberChars = opcodes.Length;
    byte[] bytes = new byte[NumberChars / 2];
    for (int i = 0; i < NumberChars; i += 2)
        bytes[i / 2] = Convert.ToByte(opcodes.Substring(i, 2), 16);
    return bytes;
}

My thoughts:

I feel like there's some issue where the return address of my program needs to be specified somehow in the shellcode, as the shellcode is killing the whole process. I tried all of the "EXITFUNC" parameters with msfvenom, including SEH, Process, and Thread... but no luck. Is the problem with my example shellcode? Is there


Solution

  • CreateThread is not killing your process. This is your shellcode (x86) that calls ExitProcess at the end, eventually leading to process exiting. Also, first bytes of your shellcode is trash - you need fix it. If you don't want to exit process - you need remove ExitProcess call at the end and correct return.

    Also I claim that how this shellcode looks for kernel32.dll is incorrect.

    All your shellcode does is (except first wrong bytes):

    MessageBoxA(0, "Howdy Friends!", "ok", 0);ExitProcess(0);
    

    If we modify it (remove ExitProcess, restore registers, stack and return) - we get the following code (in C or C++)

    static const char sc[] = 
        "60e80000000031d2b27031c9648b71308b760c8b761c8b46088b7e208b36384f1875f35901d1ffe1"
        "608b6c24248b453c8b54287801ea8b4a188b5a2001ebe334498b348b01ee31ff31c0fcac84c07407"
        "c1cf0d01c7ebf43b7c242875e18b5a2401eb668b0c4b8b5a1c01eb8b048b01e88944241c61c3b208"
        "29d489e589c2688e4e0eec52e89fffffff894504bb7ed8e273871c2452e88effffff894508686c6c"
        "20416833322e64687573657230db885c240a89e656ff550489c250bba8a24dbc871c2452e85fffff"
        "ff686f6b582031db885c240289e368732158206869656e64687920467268486f776431c9884c240e"
        "89e131d252535152ffd083c43c61c3";
    
    if (PVOID pv = VirtualAlloc(0, (sizeof(sc) - 1) >> 1, MEM_COMMIT, PAGE_EXECUTE_READWRITE))
    {
    
        ULONG cb = (sizeof(sc) - 1) >> 1;
    
        if (CryptStringToBinaryA(sc, sizeof(sc) - 1, CRYPT_STRING_HEX, (PBYTE)pv, &cb, 0, 0))
        {
            if (FlushInstructionCache(NtCurrentProcess(), pv, cb))
            {
                (FARPROC(pv))();
            }
        }
    
        VirtualFree(pv, 0, MEM_RELEASE);
    }
    

    The way shellcode looks for KERNEL32.DLL is obviously incorrect:

    PLIST_ENTRY InInitializationOrderModuleList = &RtlGetCurrentPeb()->Ldr->InInitializationOrderModuleList, entry = InInitializationOrderModuleList;
    
    _LDR_DATA_TABLE_ENTRY* ldte;
    do 
    {
        entry = entry->Flink;
        ldte = CONTAINING_RECORD(entry, _LDR_DATA_TABLE_ENTRY, InInitializationOrderLinks);
        
    } while (*RtlOffsetToPointer(ldte->BaseDllName.Buffer, 24)); // Assuming that this is `KERNEL32.DLL`