During a penetration testing engagement, it’s quite common to have antivirus software applications installed in a client’s computer. This makes it quite challenging for the penetration tester to run common tools while giving the clients a perception that their systems are safe, but that’s not always the case. Antivirus software applications do help in protecting systems but there are still cases where these defenses can be bypassed.
Antivirus evasion is a broad topic and this article only presents very basic methods to bypass detection when the program is resting as a file in a non-volatile storage. Evasion techniques for a run-time state are quite different and challenging because of behavior monitoring done by antivirus programs.
In this article, I will be discussing a few techniques that can be used to bypass antivirus software applications like string manipulation and code substitution. Before anything else however, an understanding of programming is required because I’ll assume that the detected software application has its source code available for modification. I’ll probably work out another separate article for evasion of programs that don’t have their source code available.
There will be two basic steps to do. First will be finding the cause of the detection while the next step goes into how the detection can be bypassed. This is because we won’t be able to fix something if we don’t know what the problem is.
Looking for the Origin of the Detection
For the demonstration, I will be using an object-oriented language, specifically C#, with the help of Visual Studio 2012. I grabbed a snippet from here specifically the functions “startup” and “USBSpread” while creating a new project to put both of these. This is what it looks like after creating a console project in C#:
Please note that I have minimized the region of the code in the screenshot above to make it short. I’ll leave the credits where it is due for both those functions. After compiling the project and scanning it in VirusTotal, the result shows two antiviruses detecting it namely ESET and Sophos.
Please forgive me. If any of you are not familiar, VirusTotal actually distributes copies of a scanned file, especially if a few antiviruses detect it. Chances are that if you are reading this right now, the scan results might have changed already when you visit the link. This endangers your tool to become detected very fast and should not be used for scanning when you are developing a penetration testing tool to be used for legal assessments.
Now here comes the fun part. How can we find out what’s causing the detection? Since we have a copy of the source code, what we can do is remove parts of the code line by line and rescan it. To start off, I have commented out the whole “USBSpread” function as seen below:
Compiling this and scanning in VirusTotal will give us a result of:
Notice that only ESET is now detecting it and the detection previously shown by Sophos disappeared. Uncomment the function “USBSpread” and then comment out the function “startup” as seen below:
The result after rescanning in VirusTotal will be:
As you may have guessed, the detection found by ESET now disappeared and Sophos has reappeared. From what we did, we can conclude that having the “startup” function actually triggers detection from ESET while having the “USBSpread” function actually triggers detection from Sophos. Sounds easy to identify the detection right?
Bypassing the Detection
After being able to identify where the detection came from, we can now try to work out how it can be bypassed. Note that the identification process shown above is in general terms. To successfully bypass the antivirus detecting it, we need to continuously do the previous step while working on a fix line by line. There are a few methods like string manipulation and code substitution that usually work but sometimes also trigger more detection so these methods are quite “experimental”.
This method simply points to how a normal string can be converted into another form while being evaluated with the same meaning. To understand this better, suppose we have a registry path:
In C#, if we declare this, it should look something like:
There are numerous ways on how we can change the form of that string while maintaining the original meaning when the code executes. Here are some very basic ways to do it:
Using an encoder tool, enter the string “SOFTWAREMicrosoftWindowsCurrentVersionRun” and click “encode”. You should get something like this as a result:
Now we copy that string back to the source code with the following evaluator and notice that when the program runs, the string still gets evaluated to the original form:
This is a pretty simple solution and it actually works sometimes too! Converting the string to its numeric ASCII form looks like this:
In the image above, I have converted the “” character into its numeric ASCII form. If we check out the ASCII table, the character “” is equivalent to 92 in decimal. Doing some other simple calculations basically differentiate the original code from the current one.
Encryption can also take part in this like having your string encrypted and written down to a binary file. The program can then load it by browsing and decrypting the contents during execution or so. There are lots of possible methods for string manipulation and this is basically where your creativity comes into play.
This method requires more in-depth knowledge of programming because it requires understanding of what a specific line of code or what a specific function does. For example, suppose we have a code that downloads the contents of a web page:
With code substitution, an understanding of what the code does is essential because we will need to find a replacement of the code by commands while achieving the same logic and goal at the end. In this case, the goal of the code above tries to get the client IP address in the network where the program is running. This can also be achieved with the use of this code:
Notice that the output is the same, which means the code logic does the same functionality and goal but the way it is done is different. If ever the first code is detected by a few antiviruses, it can be substituted with the next one or vice-versa. There are other more ways to grab the IP address but I’ll leave that part as a research for the readers.
Going back to the example program, let’s start with Sophos. At this point, the function “startup” is commented out to stop ESET from detecting it while we fix the first one. This antivirus was previously detecting the method “USBSpread” and after some trial and error, the detection was still popping up even after commenting out the whole function contents:
This simply means that Sophos notices when the function name is “USBSpread” so we change it to “ThisIsATest” and by scanning it again, we get:
By doing the process again, we uncomment out the function contents as seen here:
Once uploaded for scanning in VirusTotal, the result was 0/65!
Sophos in this case was basically finding that function name and flagged it as malicious. To proceed, we now do the same steps for the “startup” function and the specific line that started the detection by ESET was:
Since there are parameters in the line and one of the parameters is a string, we will need to separate it to another line so we can confirm what is being detected by ESET. A variable “temp” was assigned to hold the string representation of the registry path for this:
Scanning on the other hand still leaves us with an angry ESET here:
This confirms that the string is the one being detected by ESET. Below is a short test case that I have tried so far:
- Reversing the string making it “nuR\noisreVtnerruC\swodniW\tfosorciM\ERAWTFOS” - Another detection popped up
- Moving the variable outside the function making it a global variable:
static string temp = "SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
- ESET still detecting
- Converting each characters to their decimal number equivalent in the ASCII table:
static string temp = ((char)83).ToString() + ((char)79).ToString() ... ;
- ESET still detecting
- Removing the string contents after “SOFTWARE” leaving: string temp = “SOFTWARE”; - ESET still detecting
At this point, ESET was still detecting the program even if the registry path doesn’t really make sense, so this might not be the “real” thing being flagged by ESET or there is another line of code in which if combined with the current string, gets detected. Once this happens, we need to carefully go back each step and see what could probably be the issue. While leaving the code uncommented:
string temp = "SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
Plus having this code commented out:
destination = System.IO.Path.Combine(destination, "nvdisp.exe");
We actually get 0/65 from VirusTotal!
At this point, new test cases were applied because the string “nvdisp.exe” appears to be the real reason behind the trigger:
Changing the string “nvdisp.exe” to “ThisIsAnotherTest.exe” - ESET still detecting
- Moving variable outside the function making it a global variable: static string dest = “nvdisp.exe” - ESET still detecting
- Encoding the string to base64 deriving to the code:
destination = System.IO.Path.Combine(destination, System.Text.Encoding.Default.GetString(Convert.FromBase64String("Im52ZGlzcC5leGUi")));
- Another detection popped up
Now, there should be a lot of test cases here but to cut the testing short, since some basic string and variable manipulation don’t work, we can try to do some code substitution. Most programmers can understand what the “startup” function does. It simply adds the program to the Windows start up so it can execute once Windows boots. There are numerous ways to add a program in the Windows start up. This could be through copying the executable in “C:Users<USER>AppDataRoamingMicrosoftWindowsStart MenuProgramsStartup” or maybe by using a scheduled task.
The “startup” function in this case was replaced by this simple code:
This basically does a similar job by logic but different in terms of instructions. The final program looks similar to this:
Once compiled and scanned in VirusTotal, the result gives us a rate of 0/65.
While being able to achieve a fully undetected program which helps in penetration testing engagements, it is essential to understand that running this program will probably catch the attention of antiviruses that monitor its actions during run time. This is why antivirus evasion is challenging to do with automated tools because the scope is very wide.
P.S. The code presented above might have different scan results as time goes on because as mentioned, VirusTotal distributes copies of applications being scanned and other antiviruses might decide to put a flag on them.