1

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

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
Gray
  • 7,050
  • 2
  • 29
  • 52
  • 2
    `UInt32 funcAddr = VirtualAlloc(...);` will truncate the address on 64bit. Use `IntPtr` instead of `UInt32` to receive the allocated address. You are also not closing the `hThread` handle after `WaitForSingleObject()` exits. – Remy Lebeau Mar 15 '17 at 00:27
  • 1
    Also, you are not using `VirtualProtect()`, like the [documentation](https://msdn.microsoft.com/en-us/library/windows/desktop/aa366887.aspx) says: "*To execute dynamically generated code, use `VirtualAlloc` to allocate memory **and the `VirtualProtect` function to grant `PAGE_EXECUTE` access**.*" You are allocating the memory with `PAGE_EXECUTE_READWRITE` access, you should remove the `READWRITE` after copying the shell code bytes into the memory. Or allocate it as `PAGE_READWRITE` and then flip it to `PAGE_EXECUTE` afterwards. – Remy Lebeau Mar 15 '17 at 00:27
  • @RemyLebeau - we need just call `VirtualAlloc(...PAGE_EXECUTE_READWRITE)` - senseless allocate first with `PAGE_READWRITE` and then change it to `PAGE_EXECUTE_READWRITE` - so documentation bad in this case ). remove than `WRITE` access to memory also not need (can be done but optional) – RbMm Mar 15 '17 at 00:31
  • @RbMm: I didn't say to change it from `PAGE_READWRITE` to `PAGE_EXECUTE_READWRITE`, I said to change it to `PAGE_EXECUTE`, ie remove the `READWRITE` portion. It is not senseless to do this, it protects the memory block from unexpected modifications, like buffer overflows in other threads, or malicious code. – Remy Lebeau Mar 15 '17 at 00:35
  • 1
    The code is also missing this step: "*When creating a region that will be executable, the calling program bears responsibility for ensuring cache coherency via an appropriate call to `FlushInstructionCache` once the code has been set in place. Otherwise attempts to execute code out of the newly executable region may produce unpredictable results.*" – Remy Lebeau Mar 15 '17 at 00:36
  • @RemyLebeau - my comment faster related to msdn - `To execute dynamically generated code, use VirtualAlloc to allocate memory and the VirtualProtect function to grant PAGE_EXECUTE access.` - yes we can do this, but *better* just call `VirtualAlloc(...PAGE_EXECUTE_READWRITE)` about change protection to `PAGE_EXECUTE_READ` *after* we write code to memory - this have sense but optional – RbMm Mar 15 '17 at 00:40
  • @RbMm Use `PAGE_EXECUTE`, not `PAGE_EXECUTE_READ`, there is no point in letting the memory block be readable after the code is copied into it, only executable. I don't consider it optional, either. I consider it safe coding practice. – Remy Lebeau Mar 15 '17 at 00:50
  • In any case, when I run this code (with and without making relevant changes to the memory usage) and step through the executed `shellcode` in a debugger, I get an Access Violation part-way through the code when it executes a `LODSB` statement after executing a few jump statements and loops. I don't think the `shellcode` is safe. – Remy Lebeau Mar 15 '17 at 00:57
  • @RemyLebeau - `Use PAGE_EXECUTE, not PAGE_EXECUTE_READ` may be this concrete sell-code not need read data from own memory. but very frequently sell-code need *read* own data - so we need exactly `PAGE_EXECUTE_READ`. for example `call @@1 ; @@1: pop rbx ; access data by [rbx+xx]` – RbMm Mar 15 '17 at 00:59
  • @RbMm: Well, like I said, the shellcode still crashes even when I don't change the access protection at all. The original code uses `PAGE_EXECUTE_READWRITE`, which is good enough for testing purposes. – Remy Lebeau Mar 15 '17 at 01:01
  • @RemyLebeau - this is another question, i not test this specific shell code, but general note - common practice use in shell code some data (strings, function pointers) which than shell code can access. as result we need read access to memory too – RbMm Mar 15 '17 at 01:04
  • look like first ~11 bytes `d9eb9bd..` is trash and only then begin x86 code – RbMm Mar 15 '17 at 01:21
  • you need change first bytes to `e80000000031d2b270` than use as is `31c9648b71308b7...` with this modification shellcode call messagebox `Howdy Friends!` (however how it search for kernel32.dll it funny and incorrect) – RbMm Mar 15 '17 at 01:48
  • @RbMm This is a ton of stuff to work through/look up (thanks for everyone's input). But I tried modifying the shellcode as you suggested with my original code, but it produced the same results - messagebox then crash. I did not write that shellcode, and instead used msfvenom to generate it. You can see what I used here: [metasploit](https://www.offensive-security.com/metasploit-unleashed/msfvenom/). – Gray Mar 15 '17 at 02:00
  • @Gray - no any crash after messagebox - shell code simply call `ExitProcess` - in what problem remove `ExitProcess` call and return for caller ? – RbMm Mar 15 '17 at 02:03
  • and for what you use `CreateThread` ? you can call shellcode direct from current thread (only remove ExitProcess call in end) or if use separate thread - call `ExitThread` in the end of shellcode. and what is your real problem/question ? – RbMm Mar 15 '17 at 02:06
  • I thought I was generating a stable/useful shellcode example for my program. I see now that my source was not as stable/well-made as I had hoped. The call to ExitProcess was one of the choices that resulted in a clean exit of the program. The other options crashed my program after execution completed. The code created by @RbMm executed with no problems. – Gray Mar 15 '17 at 13:00

1 Answers1

6

CreateThread of course not killing process. this is your shellcode (x86) call ExitProcess at the end. so process is exit. also first bytes of your shellcode is trash - you need fix it. if you want not exit process - you need remove ExitProcess call at the end and correct return.

also i claim that how this shellcode search for kernel32.dll is incorrect.

all what your shellcode doing (except first wrong bytes):

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

if some modify it (remove ExitProcess, restore registers and stack and return) - we can got next code (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);
}

how sellcode search for KERNEL32.DLL of course 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)); // assume that this is `KERNEL32.DLL`
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Thanks for your answer. So, you are right that I included a call to ExitProcess. That was a mistake, but I had also tried different `EXITFUNC`s, like nothing, proccess, thread, and seh. They all either resulted in the process crashing or the process terminating. In any case, it sounds like the problem was with generating my shellcode, rather than the method I use to allocate/execute it (though, it sounds like that could be improved as well). Here's how I made it: `msfvenom -a x86 --platform windows -p windows/messagebox TEXT="Test" TITLE="ok" -f raw EXITFUNC=none | xxd -p -c 999999` – Gray Mar 15 '17 at 12:53
  • result: `d9eb9bd97424f431d2b27731c9648b71308b760c8b761c8b46088b7e208b36384f1875f35901d1ffe1608b6c24248b453c8b54287801ea8b4a188b5a2001ebe334498b348b01ee31ff31c0fcac84c07407c1cf0d01c7ebf43b7c242875e18b5a2401eb668b0c4b8b5a1c01eb8b048b01e88944241c61c3b20429d489e589c2688e4e0eec52e89fffffff894504686c6c20416833322e64687573657230db885c240a89e656ff550489c250bba8a24dbc871c2452e870ffffff686f6b582031db885c240289e36858202020685465737431c9884c240489e131d252535152ffd090`. This was with no exit function specified. I don't think there's any more for you to do, but I included this in case you were interested. – Gray Mar 15 '17 at 12:54
  • 1
    @Gray - i not understand first bytes of your shell - `d9eb9bd9742..` - this is asm code must be ? i change begin and end of your shell to `pushad` at begin and `add esp,3c;popad;ret` - in this case shell correct exit to caller – RbMm Mar 15 '17 at 13:09
  • Could it be some obfuscation technique used by msfvenom? msfvenom is a tool that is often used for malicious purposes or evading antivirus. (In my case, I am a penetration tester). Perhaps it randomizes the shellcode as a standard way of not leaving an AV signature? I am really only a novice in this area, and that is just my guess. I have a lot to read up on before I can make modifications like you are talking about. Thank you for taking the time to help me. – Gray Mar 15 '17 at 13:13
  • 2
    @Gray - i first listen about msfvenom, but i good know assembly and os internals - so i view that first bytes - is error code – RbMm Mar 15 '17 at 13:17