Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Exploring RDA

Kharn
.aware eZine Alpha - Overground Hacking

1
[Back to index] [Comments]
"Kill! Maim! Kill! Maim! Burn!"

./kharn


Introduction

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.

Standard 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):

   setup:
       xor ebx,ebx

   iterate:
       mov esi,[ebp + hostOffset]
       mov edi,esi
       mov ecx,[ebp + host_size]
       inc ebx
   decrypt:
       lodsb
       xor al,bl
       stosb
       loop decrypt

   check:
       mov esi,[ebp + hostOffset]
       push esi
       mov ecx,[ebp + host_size]
       push ecx
       mov eax,[ebp + __ADDR_CheckSum]  ; whatever this happens to be
       call eax
       test eax,eax
       jnz iterate
       mov esi,[ebp + hostOffset]
       jmp esi
 

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.

  1. known plaintext attack: where a hostile entity knows part of, or the entirety of your plaintext, and uses it to manipulate your encryption. for example, bytewise XOR is weak to known plaintext attack - if I know one charachter of the plaintext, i know the entire plaintext, because the key is the same.

Variation: SEH-based RDA

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:

       SetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER f);
 

where 'f' is a function (the exception handler) defined as thus:

       LONG f(STRUCT_EXCEPTION_POINTERS e);
 

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.

       CONTEXT STRUCT
         ContextFlags  DWORD      ?
         iDr0          DWORD      ?
         ...
         regEbp        DWORD      ?
         regEip        DWORD      ?
         regCs         DWORD      ?
         regFlag       DWORD      ?
         regEsp        DWORD      ?
         regSs         DWORD      ?
         ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
       CONTEXT ENDS
 

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.

        pop eax
        assume eax:ptr EXCEPTION_POINTERS
        mov ebx,[eax].ContextRecord
        mov eax,ebx
        assume eax:ptr CONTEXT

    ; reset EIP, so we return to "decrypt:"
        lea ebx,decrypt
        mov [eax].regEip,ebx

        mov ebx,ebpSafe
        mov [eax].regEbp,ebx
        mov ebx,espSafe
        mov [eax].regEsp,ebx

        mov eax,-1
        ret
 

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:

    decrypt:
        mov [ebp+espSafe],esp
        mov [ebp+ebpSafe],ebp
 

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].

  1. XTEA cipher: although reasonably fast in it's standard form, using a 32-bit key is certainly too long for our brute forcing attempts - we don't have time to cover that much keyspace. We can shorten XTEA to only use 8 bits of randomness in a 32-bit key, to reduce our brute forcing time.
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxheaven.org aka vx.netlux.org
deenesitfrplruua