Kharn
.aware eZine Alpha - Overground Hacking
./kharn
The ultimate aim of every VXer is to write a program which is difficult, or even impossible to remove from the host after it's been attached. This code is then truly viral - it can't be removed without somehow harming the host, or the host's environment. Many methods have been used to acheive this, but at the heart of them all lies various methods of encryption - and RDA is one of them.
RDA is not some new cipher - it stands for Random Decryption Algorithm, and can be used with any encryption algorithm, whether symmetric or assymetric. It was first implemented in the RDA.Fighter virus, a virus which tried different decryption keys against itself until the "decrypted" virus matched a certain checksum - and this was assumed to be correct. This is the simplest implementation of RDA.
Your standard appending virus with encryption, encrypts the body and stores the decryption algorithm, key, and ciphertext within the last section of the host executable. When the executable is loaded, AddressOfEntryPoint has already been hijacked, and points to your decryption routine first. The ciphercode is decrypted, executed, and control is hopefully returned to the host executable without any major hitches.
The weakness of this is that an AV engine, upon recognizing your decryption algorithm, will also recognize your decryption key. Simply layering encryption on doesn't help either - the AV engine will simply peel away the encryption layers, leading to your decrypted code.
RDA solves this problem by throwing away the decryption key, but leaving the algorithm in the open - if the encryption is immune to known-plaintext attacks[1], then the only way an Anti-Virus can look at the plaintext code is by brute force. However, the only way YOU can reach your own decrypted code is also by brute force. Thus, the advantage lies with the virus - the virus can "tolerate" one or two executables on a system being corrupted with incorrect decryption, an anti-virus cannot.
The standard RDA implementation (weak XOR, for example):
However, this has major flaws which impede it's usability. For example, if an anti-virus engine can see this code in the clear and recognizes it, it can emulate the virus decryption engine, and call CheckSum for itself - revealing the decrypted executable. Also, since the XOR algorithm is weak, if a single byte is known in the plaintext, the entire ciphertext becomes decrypted.
With the introduction of structured exception handling in Windows OSes, programmers have a good way of catching unexpected errors and handling them gracefully, instead of leading the user to a BSoD. This is also good for when implementing RDA-based techniques: instead of matching the decrypted code to a checksum, we simply run the decrypted code - and use a different decryption key if we get an exception.
Probability is most certainly on our side here - the chance that we'll get an incorrect decryption key leading to code which executes, but does not lead to an exception, is negligible. Also, the circumstances tilt the playing field towards the virus end - as a virus, it can "tolerate" a few corrupted executables on a system due to incorrect decryption. An anti-virus, on the other hand, cannot. Exception handling is set up with the help of SetUnhandledExceptionFilter API, which is called as thus:
where 'f' is a function (the exception handler) defined as thus:
f is called every time an exception occurs, and is passed the state of the machine at the point of exception, in the Context member of e. Basically, we just try different decryption keys - and if the end result (the plaintext code) is incorrect, it'll most likely throw an exception, and we can try a different key.
Here's the Context member of e, which we're interested in. This may differ from machine to machine, and this was taken from an IA32 box.
Basically, Context stores the state of the registers at the moment of exception. When we exit from our exception handler, we have the option of asking the CPU to return to execution from where it halted. This "return point" is taken from regEIP, in the Context structure. So we modify regEIP to point to our decryption algorithm again. We also need to reset EBP and ESP, to make sure we can still access local variables and whatnot when we return to the decryption algorithm.
NOTE: ebpSafe and espSafe are drawn from values we know (assume) to be correct - since there is an initial run of the decryption algorithm (what if the key happens to be the first one guessed?) - ebp and esp are saved at every iteration of the decryption algorithm. That way, they remain static:
There are problems with this method, however. Firstly, we must make sure to recalculate EBP, especially if we work within the confines of a virus. This must be done within the exception handler itself - if "corrupted" or incorrectly decrypted code modifies EBP before throwing an exception and you keep on using the tainted EBP, you'll throw more exceptions, leading to an infinite loop and Windows terminating your process.
Also, the encryption algorithm must be simple, and efficient - as a virus, you must preserve a reasonable loading time for the host executable. something which will take several minuites to brute force is unacceptable - thus the XOR cipher. However, other fast ciphers exist - for example, the XTEA cipher[2].