There is no "polymorphic decryptor" and "encrypted data" as different objects, it is all decryptor, whose task is just to push some data to the stack in reversed order and then pass control to that data using instructions equivalent to JMP ESP.
|Source data: (virus)||Encrypted data:|
... nop db 22 db 33 db 44 db 55 db 66 db 77 db 88 ...
... XOR EAX, EBX ADD EAX, (88776655h - curr_eax) ROR EBX, 4 XOR EBX, (44332290h xor curr_ebx) ... PUSH EAX ... PUSH EBX ...
Instead of standard polymorphic engines which produces data of length DECRYPTOR_LENGTH + SOURCE_DATA_LENGTH, KME produces data of length SOURCE_DATA_LENGTH * k, where k is some data expansion coefficient, which depends upon many parameters.
When all features are enabled, produced data is 2-3 times more than source data. But there exists an interesting effect: when all "logic" is disabled (i.e. there is no register encryption, see FLAG_NOCMD) and data to encrypt is a repeating byte/word/dword, then produced decryptor will be shorter than source data. Maximal compression for single polimorphic layer is 4 times, but it can be greater when using multilayer technique.
How decryptor length depends on number of layers
To create these pictures i was encrypting 5k NOP-buffer. Red color -- "logic" enabled, blue color -- logic disabled (FLAG_NOCMD). As you can see, on third layer achieved 25-times compression.
You can use KME everywhere -- ring3 and ring0, NT and win9X at least. Flat memory model, no segment registers used. There is no system calls inside of the engine, only register/memory manipulation. All parameters are passed on the stack. Engine's local variables are located in the stack too. Engine's code as well as produced decryptor's code is non-sensitive to offset displacement, 'coz there is no data used.
You can specify the following parameters:
- registers usage (min 1, max 7),
- commands usage,
- presence and parameters of JMPs (when pieces of code has random location in the decryptor and are linked with jmps),
and some other parameters.
Also you can specify RandSeed -- number to initialize random number generator. As a result all the produced decryptor depends on parameters you pass to engine, RandSeed and source data.
Decryptor is produced in a single pass, and no extra memory used to do it. As a result length of source data/decryptor is unlimited. So you can create decryptor of 100MB-length and start virus from such file every boot. It is also possible to create multilayer decryptors by multiple calls to KME. (see examples)
KME exists in three forms:
1. Compiled OBJ-file (KME32.OBJ and KME32.INT)
tasm YOURSRC.ASM tlink YOURSRC.OBJ KME32.OBJ
include KME32.INT extrn kme_main:PROC
2. Sources (KME32.INC and KME32.INT)
include KME32.INT include KME32.INC
3. As "binary include" file (KME32BIN.INC и KME32.INT)
include KME32.INT include KME32BIN.INC
KME has only one PUBLIC near-procedure, called kme_main. It has pascal-convention call, i.e. it ends with "RET xxxx" command. All 13 parameters are of DWORD-type.
push Flags ; flags, FLAG_XXX push CommandMask ; command set, CMD_XXX push RegMask ; register set, REG_XXX push RandSeed ; random number generator initializer push JmpProb ; (1/probability) of jmps. JMP if rnd(JmpProb)==0 used only if FLAG_NOJMPS is not specified push OutEntryPtr ; pointer to DWORD, where decryptor entrypoint will be stored. if FLAG_EIP0, 0 will be stored. push OutSizePtr ; pointer to DWORD with produced decryptor size w/o jmps -- ~InputSize*k here will be stored with jmps -- OutMaxSize will be stored push OutFiller ; character to initialize decryptor buffer with push OutMaxSize ; maximal decryptor buffer size push OutPtr ; pointer to decryptor buffer push InputEntry ; virus entry point (where to pass control to) push InputSize ; source data (virus) size push InputPtr ; source buffer call kme_main jc error
Registers unchanged, DF=0 (CLD)
CF=0 if all ok
CF=1 if error (no free space in output buffer)
All constants are defined in KME32.INT.
Flags (first parameter)
|FLAG_DEBUG||insert INT3 (0CCh) to decryptor start/end|
|FLAG_NOLOGIC||disable "logic" (register encryption)|
|FLAG_EIP0||disable random decryptor entrypoint selection (if FLAG_NOJMPS)|
|FLAG_NOSHORT||disable short-instructions for EAX|
CommandMask (second parameter)
Define command set which may be used in the decryptor.
|CMD_ALL||use all commands|
All commands of type CMD_xxx (not CMD2_xxx) may be globally disabled by FLAG_NOLOGIC in the Flags parameter.
If CMD2_ADD, CMD2_SUB and CMD2_XOR are all disabled at the same time, CMD2_XOR will be used.
Of course, some commands will be used anyway:
RegMask (third parameter)
Define set of registers which may be used in the decryptor. Totally 7 registers may be used (all 32-bit regs except ESP)
|REG_ALL||use all possible registers (EAX/EBX/ECX/EDX/ESI/EDI/EBP)|
If no registers defined, EAX will be used.
Entry points (InputEntry and OutputEntryPtr)
All entry points are relative to their buffers.
After data is decrypted, JMP (ESP+InputEntry) is executed.
At this time all registers from RegMask set are destructed, and virus with N-1 layers (assuming there were N layers) are pushed on the stack Size of all this shit can be calculated as sum of lengths of virus and all decryptors except first, but each length is dword-aligned ((InputSize+3) and (not 3)).
(c) 1999 Z0MBiE, http://z0mbie.host.sk