[=========================================================================]
[----------------------------[ Exploring RDA ]----------------------------]
[=========================================================================]
_==####==_
.############.
.################.
.##################.__ __ __ __ _ _ __ __
##############/¯_`|#\ V V // _` | '_/ -_)
##############\__,|# \_/\_/ \__,_|_| \___|
###########*¯*######
*#########{ }####*
##########._.##### "Kill! Maim! Kill! Maim! Burn!"
################ ./kharn
*############*
¯==####==¯
--[ 0 ]-----------------------------------------------[ 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.
--[ 1 ]-----------------------------------------------[ 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.
--[ 2 ]-----------------------------------[ 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].
2) 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.
----------------------------------[ EOF ]----------------------------------
|