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
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`