This article continues the series of publications on post-exploitation:
No doubt you had encountered situations when access to the attacked network is gained and just one little step separates you from the much-desired goal – but the antivirus does not allow you to make this step (e.g. prevents you from launching the required program). In many cases, you have only one attempt – and it fails because of the alarm raised by the antivirus. Sometimes antiviruses delete absolutely harmless files, not only executable ones. As a result, it becomes extremely difficult to conceal the real threat.
All information provided in this article is intended for educational purposes only. Neither the author nor the Editorial Board can be held liable for any damages caused by improper usage of this publication.
You may need to bypass antivirus software in two situations:
- during an attack. In such cases, the payload is run either by a vulnerable application or, more often, by the user (social engineering). In most cases, this gives you a foothold and ensures your persistence in the system. The most important thing is to avoid detection; or
- during post-exploitation. In this case, it is you who runs the program on the compromised PC. It may be a sniffer, a privilege escalator, or other hacking software used to advance you through the network. For the post-exploitation purposes, the program has to be run by all means – even if you failed to launch it on the first try and the antivirus has raised several alarms.
In the first situation, you can use a well-known technique called “polymorphism”. Just make a few changes in the code without changing its functionality. A good tactics is to write the code yourself. For instance, you can implement a simple reverse shell using twenty strings of VBS code and successfully bypass any antivirus. The second case is more interesting and difficult; accordingly, it will be the main topic of this article.
Many useful tools allowing to bypass the antivirus can be created using Meterpreter, but first you need to run it on the target system. In each situation discussed below, your ultimate goal is to launch Meterpreter. This tool has all the required capabilities; if necessary, it can be used to run other specialized software directly in RAM. And everything that happens in RAM is virtually beyond the control of protection tools: a detailed and continuous memory analysis requires colossal computing costs that antiviruses cannot afford.
To bypass an antivirus, you have to think like an antivirus and try to predict its logic. Consider it a black box. You don’t know in detail how it works, but based on its behavior in different situations, you can conclude that it carefully monitors the sources from where you download executable files and also monitors whether these files were launched before (and if yes, how many times). Repeated launches of the same file will attract less attention from the antivirus.
Generally speaking, you have to deal with two protection mechanisms:
- signature-based; and
- heuristic (behavioral) ones.
In signature analysis, the antivirus takes into account many factors, including the compiler. I scanned a harmless helloworld app written in C on VirusTotal, and you can see the results below.
- i686-w64-mingw32-gcc – 11/68 detects;
- msvc – 2/64 detects;
- win-gcc – 0 detects.
With regards to the program’s behavior, keep in mind that even if you managed to bypass the signatures, you can still be detected because any migrate PID or
sekurlsa:: can be intercepted due to the use of typical combinations of WinAPI functions closely monitored by antiviruses.
I suggest focusing on the signature engine since, in most situations, it’s sufficient to bypass it. I purposively don’t mention any specific antiviruses in this article to avoid advertising (or anti-advertising) them. The techniques presented below aren’t designed against any specific AV program. The results will be verified using a running antivirus; the goal is to develop a universal method so that you don’t have to invent new bypass tricks every time. In each situation, your task is to covertly smuggle Meterpreter onto the compromised machine; then it will enable you to execute anything in memory and unleash the entire hacking arsenal at your disposal.
The best fight is the one that was avoided. Therefore, hackers and pentesters often use perfectly legitimate means against antiviruses. Yes, they don’t support many ‘advanced’ functions, but the minimum required functionality such tools provide (i.e. reverse shell with persistence, lateral movement, and a built-in proxy server with a flexible traffic redirection system for pivoting). The utilities are commonly used, well-known, and efficient: nc.exe, ncat.exe, socat.exe, and plink.exe. They are addressed in detail in my previous articles. Furthermore, Black Hats sometimes use common cloud-based remote administration tools (like RMS).
If you have to deploy a fully featured foothold in the compromised system (i.e. Metasploit and similar hacking tools), you can hide behind virtualization. A step-by-step guide is provided in my another article.
This well-known technique involves the injection of code into a running process that has already passed the testing. The idea is that the program
shellcode_inject. and the shellcode itself are two different files. It is difficult for an antivirus to detect a threat if it’s split between several files, especially if each of them doesn’t pose any threat by itself.
Your shellcode will look even more harmless if you convert it into printable characters.
Looking at the contents of
meter., I would rather think that this is a Base64 string, not a shellcode.
It is necessary to note that the above example uses the
meterpreter_reverse_tcp shellcode, not
meterpreter/. This autonomous code contains all the Meterpreter functions and won’t download anything over the network, thus, reducing the detection risk. But the combination of shellcode_inject.exe and meter.txt is dangerous. Let’s see whether the antivirus can recognize the threat.
Important: a system process was used for code injection; as a result, it immediately runs in the
System context. And it seems that the tested antivirus was fooled by this trick (even though it noticed something wrong in shellcode_inject.exe).
Meterpreter enables the attacker to execute the payload directly in memory (i.e. bypassing the HDD).
For many years, I’ve been successfully using this simple trick. This time, it worked well, too.
. file (PE format) contains some code. The code represents as a set of functions. During compilation, the functions are placed not directly one after the other, but with some alignment (16 bytes). Even larger empty spaces occur due to the alignment between sections (4096 bytes). Such alignments create a lot of small ‘code caves’ where additional code can be written. A lot depends on the compiler. But for most PE files, taking that you are primarily interested in the Windows OS, it looks like in the screenshot below.
What is such a ‘cave’?
If you determine the location of the caves more accurately (by signatures), you will get approximately the following picture in the executable file.
In this example, only 12-byte caves are shown; so, their actual number is much greater. Although the number of caves is large, they are obviously not sufficient to accommodate a fully featured program. Therefore, this method is suitable only for shellcode injection since the shellcode size rarely exceeds 1KB.
Let’s see if it’s possible to distribute a small multiple-step
windows/ shellcode between these caves. The size of a code cave rarely exceeds 16 bytes; so, the shellcode should be split into pieces smaller than the basic blocks. This means that you have to insert additional
jmp instructions to link them together and adjust the addresses of conditional jumps. Don’t worry, this is a fairly routine operation.
As a result, a shellcode 354 bytes in size was split into 62 pieces and injected into random caves between functions.
In theory, this approach should ensure polymorphism: every time, 2-3 shellcode instructions are placed to random caves (there is a special term for this: permutation). The code will be obfuscated even at the execution trace level due to the large number of
jmp instructions between fragments.
Using this technique, you can bypass many ‘simple’ antiviruses.
However, this trick won’t fool ‘serious’ AV products.
Surprisingly, but the classical xoring of an executable file with a dynamic key still works well against even the most formidable antiviruses. Let’s take, for instance, some very dangerously-looking executable file and encrypt everything that is possible with xor. The encryption sections
. will look something like this.
Next, let’s conceal the version info.
. section contains almost all suspicious strings. But the problem is that the directories of the import and deferred import tables (that cannot be touched!) are projected onto it; otherwise the file won’t run. Therefore, you have to find in this section an area containing mostly such strings and encrypt it.
Since you have encrypted everything in the executable file, when you run it, its offsets in the code won’t be correctly adjusted in accordance with the location address. Therefore, you have to disable ASLR:
PE->NT headers->Optional header->DllCharacteristics |=0x40
Time to check whether everything is concealed and your mimikatz doesn’t raise suspicions anymore.
Great. However, the file is inoperative in its current state since everything in it is encrypted. Prior to proceeding further, I recommend to run the file in a debugger to make sure that the PE structures weren’t damaged and the file is valid.
You have to write a small piece of machine code for decryption. Important: the key must be set at runtime (i.e. it won’t be saved anywhere in the code). In that case, the antivirus won’t detect any threats while running your app in the sandbox.
xor_stub. will maintain the initial state and add write permissions to encrypted sections.
Here and elsewhere, don’t forget to change the addresses of the WinAPI functions to your values. Now you have to adjust the entry point since a
jump to this code will be inserted into it a bit later.
The next step is to request the decryption key from the user and dexor all encrypted areas.
Finally, you have to restore the initial state, fix the stack, and jump to the entry point.
Time to add an empty
r-x section to mimikatz to place your
Now you have to compile this assembler code and insert it into the newly-created section.
In the end, don’t forget to make a
jump to your code from the entry point.
Voila! You run the shell and enter the decryption key (in this particular case, it’s the
w character (
That’s it. Now let’s slightly automate this process and launch Meterpreter.
You execute the code and enter the
The effect is the same.
Vuln inject (spawn)
I remember well one old case. I couldn’t open a Meterpreter session on the victim because of the antivirus and had to reexploit the oldie-goodie MS08-067 every time by running Meterpreter directly in memory. For some reason, the antivirus was unable to prevent this.
Apparently, antiviruses are primarily designed to detect malicious programs and shellcodes with known signatures (on the HDD and over the network, respectively) and prevent exploitation of popular vulnerabilities. But what if the vulnerability isn’t known to the antivirus yet?
In such a case, a pretty unusual technique is used; it’s based on the vulnerability injection principle (buffer overflow) and results in the unevident execution of arbitrary code. In fact, this is yet another variant of reflective code execution: the code bypasses the HDD and is present exclusively in RAM. But in the described-above situation, you also have to hide the malicious code entry point.
The antivirus static analyzer is unlikely to detect that the program contains a vulnerability and that arbitrary code is to be executed. This is a weak spot of modern computers, and I don’t expect the situation to change much in the near future. But can the antivirus detect the process in dynamics and react promptly?
To check this, you have to write and run a simple network service containing your invented 0-day vulnerability (buffer overflow on the stack) and exploit it. To make sure that everything works fine in newer Windows versions, you have to bypass DEP. But this isn’t a problem if the required ROP gadgets can be added to the program.
I am not going to explain here in detail what are buffer overflow and ROP chains: this is beyond the scope of the article. Instead of digging deep into this exciting topic, let’s just take an out-of-the-box solution. Important: you have to compile the service without ASLR support (and without DEP if you want):
cl.exe /c vuln_rop.clink.exe /out:vuln_rop.exe vuln_rop.obj /nxcompat:no /fixed
This network service will operate in the
reverse modes. All data will be transmitted in encrypted form to avoid being detected by the signature analyzer. This is very important: some antiviruses ‘dislike’ Meterpreter so much that even a simple transmission of this software to any open port would inevitably provoke an alert with subsequent ban on the attacker’s IP address.
Technically, the resultant executable file is not malicious: it just contains a memory error. Otherwise, any program should be considered malicious since it may potentially contain bugs.
The vulnerable service has been successfully launched and waits for incoming data. Time to create a payload and run an exploit with it.
The vulnerable service receives your data and ‘accidentally’ runs the payload code because of the memory error.
The payload opens a Meterpreter session.
Important: all this mess takes place despite the running antivirus. In fact, just a few antivirus programs can successfully detect this trick. Let’s find out why. The code was injected in a seemingly unexpected way: through buffer overflow. Concurrently, the code wasn’t transmitted in the clear over the network; so, the signature engines should not detect any threats. But right before the execution, your machine code appears in memory. Apparently, at this point some antiviruses recognize the painfully familiar Meterpreter.
The antivirus does not trust an
. file downloaded from an unknown source and launched for the first time. It emulates its execution and analyzes the code (perhaps each of its instructions). This comes at a price of performance, and antiviruses cannot afford to apply this procedure to all processes. Therefore, processes that are already validated at startup (e.g. system components or programs with known checksums) operate under less supervision. So, let’s inject the vulnerability into one of them!
Vuln inject (attach)
The easiest way to execute code in someone else’s address space (process) is to inject a library. DLL is not much different from EXE, and you can recompile your vulnerable service into a library format by simply changing
cl.exe /c vuln_rop.clink.exe vuln_rop.obj /out:vuln_rop.dll /dll /nxcompat:no /fixed
For maximum portability, it’s preferable to use 32-bit programs; so, the vulnerability has to be injected into a 32-bit process. You can take any running process or run it yourself. On a 64-bit Windows system, you can always find 32-bit system programs in
Now you can add the vulnerability to any 32-bit process by simply injecting your library into it.
Your DLL without ASLR is successfully loaded at the standard address.
Now the target process with the injected buffer overflow vulnerability is ready to receive data over the network.
Since your vulnerable module was loaded at the address
0x10000000 (the default address for non-ASLR libraries), you have to slightly adjust the exploit code.
Time to run the exploit.
An overflow occurs in the context of a legitimate process
And the ‘malicious’ code is executed bypassing the antivirus.
The above-described technique uses the effect of ‘sudden’ code execution in memory through a 0-day vulnerability; in most cases, the antivirus is unable to predict it and block the threat. Loading a DLL into someone else’s process is a well-known trick, and it was used just for convenience: you hardly had to change anything.
In fact, you could use a more sophisticated method: substitute key instructions in the process memory at a point where user input is processed, thus, injecting a vulnerability there (kind of an antipatch). To make it exploitable, all you have to do is inject a couple of suitable ROP gadgets into the code caves. But so far, this is not required.
The technique that conceals code execution through buffer overflow isn’t new, although rather little known. In this article, I described the most trivial example, a buffer overflow on the stack, and it brought success. But there are also much more tricky memory errors resulting in RCE (hidden execution): use after free, double free, overflow in heap, format strings, etc. They offer a virtually unlimited potential for antivirus bypass techniques.