Metamorphic Engines
Metamorphic engine theory and its applications.
Metamorphic engines consist of code that is capable of producing a mutated version of its own binary on each execution. This new copy is logically equivalent, meaning that the program can endlessly produce new copies of itself while retaining the exact same functionality. These engines behave similarily to biological viruses in the wild as they are able to evade traditional signature detection. However, heuristic analysis remains an effective strategy against viruses that employ metamorphic engines.
Biology
Metamorphosis is the change of the form or nature of a thing into a completely different one, by natural or supernatural means. Many organisms on this planet undergo this change in their life during their development stage.
Metamorphic engines have many ties to biological processes. Viruses that develop enough mutations to bypass the protections obtained from previous strains are more likely to trick the immune system. Whether the protections from previous strains originated from natural immunity or a vaccine, the ability for these mechanisms to identify and prevent a new, highly mutated version of a biological adversary is a challenging task.
Compared to a biological virus, metamorphic engines do not have to deal with the constraints of the natural world. Code mutations are designed to avoid detections by signature-based detection antivirus programs.
Mutations
There are several techniques used to mutate a binary in a metamorphic way.
Garbage Code Insertion
Metamorphic engines can insert instances of inline assembly into its initial compiled binary. The NOP instructions are replaced with random junk instructions when the binary is executed. Subsequent executions replace this section of code with more variants of garbage code. The structure of the payload allows the instances to be detected within the binary file even after the NOP instructions have been overwritten. The engine also performs checks before replacing the assembly to ensure that the metamorphosis will not corrupt the binary by overwriting any essential instructions. The following code snippet shows an example of how to set up garbage code insertion using inline assembly in Rust.
unsafe{
asm!(
".code32",
"push eax",
"nop",
"nop",
"nop",
"nop",
"nop",
"nop",
"nop",
"nop",
"pop eax",
);
}
Register Usage Exchange
Register usage exchange replaces registers without changing the overall function of the code. Metamorphic engines should check to ensure that the program will not be corrupted before exchanging the registers. The stack register (SP) is excluded because of this.
A simple example of a register change can be seen below.
; Original
push eax
...
pop eax
; Updated
push ebx
...
pop ebx
PUSH and POP instructions were used by the metamorphic engine to perform register usage exchanges. The following Intel x86 Assembler Instruction Set Opcode Table shows that the PUSH and POP instructions are correlated as the respective registers for each have an constant offset of 8. For example, PUSH eax = 50 and POP eax = 58.
These registers can be exchanged by the metamorphic engine by keeping track of the register offset as seen in the table below.
EAX | ECX | EDX | EBX | ESP | EBP | ESI | EDI |
---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Equivalent Instruction Exchange
Equivalent instruction exchange replaces certain instructions that result in the same state as performing a different instruction. For example, there are multiple ways to zero a register in Assembly.
xor eax, eax
mov eax, 0
Although there are some fine-grained differences between these two instructions, it effectively demonstrates the concept that there are almost always multiple means to an end, no matter how low-level the code is. Just to note, I would like to emphasize that best practices should always be taken into consideration. However, diving into those details is out of the scope of this post.
Independent Instruction Reordering
Although most instructions must be executed sequentially to ensure correct program execution, it is possible for some instructions to be reordered and maintain the same functionality. A metamorphic engine should be able to correctly identify these cases and potentially perform mutations. For example, two XOR instructions could be swapped if they do not use the same register(s).
; Original instructions
xor eax, eax
xor ebx, ebx
; Reordered instructions
xor ebx, ebx
xor eax, eax
It is important that the engine accounts for any internal patterns that are used by other metamorphic techniques. For example, garbage code insertion could rely on a PUSH and a POP instruction having a constant offset, but reordering the PUSH instruction with a NOP instruction that came just before it would change the offset. This would render the garbage code insertion ineffective for that set of instructions unless a more sophisticated strategy than checking for a constant offset was implemented.