Introduction
In this article, we’ll cover Self-mutating/self-modifying malware with the simplest obfuscation techniques out there, covering some characteristics of both polymorphic and metamorphic. Since I’ve discussed malware in previous articles, I’ll stick to the usual routine: giving a brief overview of how the malware operates, providing a few examples, and then a detailed explanation. Source Code
So, what’s the deal with “Metamorphic”? Well, making malware that can’t be easily detected is quite challenging. You have to change how the code looks without altering what it does, especially if you’re starting from scratch. This is where “Polymorphism” comes into play. It involves various techniques, like switching up the packer used. In short, it makes detecting the malware a time-consuming and resource-intensive task. The malware encrypts its original code to evade pattern recognition and uses self-modification to conceal its inner workings. Now, metamorphic malware takes it a step further by transforming itself into an equivalent form. Then, why aren’t there more metamorphic malware? Simple: they are extremely difficult to make, as I show in this article, Inspired by this Post.
You need to have some experience with C and low level assembly. You must also be very familiar with the Linux, All of the discussion here is pretty complicated, but I’ll try to make it as easy to follow as possible.
Overview
Metamorphic malware has the ability to transform into numerous variants while spreading by automatically obfuscating parts of its executable code. This involves actions like adding instructions of different lengths, incorporating unnecessary instructions and loops, and ultimately, register swapping – the highest level of self-mutation, a significant step towards achieving perfect stealth, and the most efficient path to assembly heaven. This essentially means that every aspect of the code undergoes mutation, making it exceptionally challenging to detect. As demonstrated in this article, we’ll explore the importance of having the right ideas and objectives, along with the various options and implications of design choices.
Alright, let’s dig into the main objective of this malware, spreading! now, why create such a code? Well, This is not something you would ever do outside of exploring a curiosity. So, we’re putting this malware to use.
Before the updates, this malware starts by scanning directories, searching for executable files that have not yet been infected. Once identified, it takes action by overwriting them with its modified code. After that, it subtly runs the original executable from a saved copy, making it appear untouched. Not much harm, right? Except for one small thing – it can be altered to perform anything from opening a reverse shell to code injection.
How it works
Initially, it loads a copy of itself into memory, focusing on the code within the .text section where the main code resides. The aim is to meticulously examine this code for any irregularities or hidden junk instructions. Once these irregularities are identified, Vx substitutes them with randomly generated counterparts. These new instructions are inserted within the functions marked by “JUNKLEN” sections, which are randomly selected for modification.
What you’re essentially doing is moving a value to a register or adding the content of one register to a variable. This simplifies how you approach coding metamorphism (which, in fact, is similar to coding polymorphism). All the instructions and groups of instructions act as macros for the operation you truly want to execute. The aim here is to train yourself to view the code as a series of instructions that aren’t tied to the final code, but rather as operations necessary to accomplish a larger task.
-
Writing Instructions : Writing assembly instructions into a buffer under specific conditions. It simplifies the details of the assembly language and focuses on the main goal of generating instructions based on factors like available space and the required instruction type.
-
Reading Instructions : In this task, assembly instructions are read from the buffer and assessed for their validity using specific criteria. It simplifies the individual instruction details and the identification of valid instructions through patterns or specific characteristics.
-
Replacing Junk Code : Identifying sequences of “junk” code within the assembly and substituting them with updated instructions. It simplifies the specific instructions related to the junk code and concentrates on the broader objective of replacing outdated code segments with new ones to enhance the code’s efficiency or obscure its behavior.
After altering the code in memory, Vx saves the updated data back onto the system. With each execution, Vx generates a fresh copy of itself, injecting nonsensical assembly code and assigning it a unique name each time. In the transformation process, portions of the original code are substituted with randomly generated instructions. These replacements follow a simple pattern: starting with a basic operation, adding more operations, and then restoring everything to its original state. To clarify, rather than replacing itself every time it runs, Vx maintains its modifications.
// Define assembly instructions as macros
#define B_PUSH_RAX ".byte 0x50\n\t" // push rax
#define B_PUSH_RBX ".byte 0x53\n\t" // push rbx
#define B_POP_RAX ".byte 0x58\n\t" // pop rax
#define B_POP_RBX ".byte 0x5b\n\t" // pop rbx
#define B_NOP ".byte 0x48,0x87,0xc0\n\t" // REX.W xchg rax,rax
// Encoded binary bytes in hex for runtime identification
#define H_PUSH 0x50 // push + reg
#define H_POP 0x58 // pop + reg
#define H_NOP_0 0x48 // --------------------
#define H_NOP_1 0x87 // REX.W xchg rax,rax |
#define H_NOP_2 0xC0 // --------------------
// Macro for injecting the junk assembly sequence
#define JUNK_ASM(B_PUSH, B_NOP, JUNKLEN) \
__asm__(B_PUSH) \
__asm(B_NOP H_NOP_0 H_NOP_1 H_NOP_2 H_NOP) \
__asm(B_NOP H_NOP_0 H_NOP_1 H_NOP_2 H_NOP) \
__asm(B_NOP H_NOP_0 H_NOP_1 H_NOP_2 H_NOP JUNKLEN-4 "nop\n\t")
// Macro for randomly calling one of the varying junk assembly functions
#define RANDOM_CALL() (rand() % 5)
we code with macros!. It’s the magic stuff, This setup adds unpredictability to the code, giving it that polymorphic flavor. So, there you have it - a sneak peek into how we shake things up in the code, making it its own polymorphic tune.
The blocks of junk assembly instructions follow this pattern so they can be recognized:
r1
is a random register selected from: RAX, RBX, RCX, or RDXr2
is another random register selected from: RAX, RBX, RCX, or RDX
Notice the JUNK_ASM
macro calls inserted at random points within the code. These calls serve as markers indicating where our malware may make modifications.
We’ve implemented a function dedicated to inspecting instructions within our malware. Here’s how it operates: after identifying the registers associated with PUSH
and POP
operations at the ends of sequences, it validates instructions at specific positions. Its primary task is to determine whether an instruction matches any of our predefined “junk” operations while ensuring alignment with the provided parameter.
When a match is found, the function returns the length of the instruction. If no match is found, it returns nothing. This function helps in accurately handling instructions, distinguishing valid ones from invalid ones.
Now, The main loop of our malware. This loop is crucial for identifying and replacing sequences of junk operations. It searches for a PUSH
command followed by a POP
command on the same register, typically eight bytes apart, as defined by our constant, JUNKLEN
. This process involves identifying and rectifying these sequences within the assembly code.
During the first execution of the malware, outdated assembly sequences are replaced with updated ones. Notably, the functions containing these unique assembly segments are invoked in a random sequence, introducing an element of unpredictability.
Simplistic
Simply put, the Vx
scans through its own binary to identify sequences of “intelligent garbage”: do-nothing code that is inserted as if it were part of the algorithm. These sections consist of assembly instructions that serve to obscure the malware’s true behavior and confuse analysis. Upon detecting these junk code sequences, it replaces them with freshly generated instructions.
These new instructions are entirely random and lack any real impact on the malware’s functionality. However, their presence adds a layer of ‘complexity,’
void _entry(void){
JUNK_ASM;
}
Operations and logic are preserved. The malware can execute and spread without any issue. It also possesses the ability to dynamically mutate its code with each execution. Following the mutation process, it propagates itself into other executable files within the same directory, carrying with it the mutated versions of its code.
“Emulator”
Alright, let’s cover some of the functionalities. First things first, the command executes to hide the original executable file, embedding the malware within it. It also embeds ‘Vx’ in the executable to ensure its stealthy presence within the system. Of course, this isn’t exactly stealthy, but you get the idea here, We’re not writing an actual malware, So once ‘Vx’ is embedded in the executable and a copy of the original executable is hidden, it creates a hidden copy of the original executable with a prefix ‘.vx_’. Then, it makes the original executable file executable and writes the modified code into it.
To make it a more interesting, we’ve added my favorite anti-debugging technique INT3 Trap Shellcode triggers a breakpoint interrupt
, By incorporating INT3 instructions within the shellcode
, it attempts to interrupt and disrupt the debugging process, making it difficult for a debugger to analyze the code flow.
“no debugger” but manually assign it. This technique known as stack string technique, Manually assigning strings that are constructed on the stack at runtime is an easy yet lazy way to obscure string data within a program. It involves blending string data with opaque operand instructions. You’ll notice the MOV instructions transferring constant values into adjacent locations on the stack, as shown:
while we’re messing around with Anti-Analysis, let’s add in a Self-Deleting feature too. I’m pretty sure I’ve shown this trick somewhere, but can’t really remember where. Anyway, it’s easy in Linux. Just start a child process that runs a separate thread to delete the executable file. You can trigger it by checking a condition or just making a simple function call,
00101ee9 e8 88 fb CALL execute_bash
00101eee 48 8b 45 f0 MOV RAX,qword ptr [RBP + local_18]
00101ef2 48 8b 00 MOV RAX,qword ptr [RAX]
00101ef5 48 89 c7 MOV RDI,RAX
00101ef8 e8 82 f8 CALL SelfDelete
00101efd b8 00 00 MOV EAX,0x0
00101f02 c9 LEAVE
00101f03 c3 RET
As you can see we call it after we execute malware to Execute the original, hidden executable, so the actual vx will be deleted and left with only the infected dummy, (vx) continues its execution and eventually exits. The child process, responsible for self-deletion, persists until it successfully deletes the executable file and terminates itself.
!!! Alright, let’s give it a try. First, let’s write a simple dummy code, compile it, and put it in the directory. Then, let’s test our propagation example, which will embed ‘vx’ into the dummy example, essentially infecting the dummy with its morphed code:
As seen above, ‘vx’ successfully embedded itself into the dummy code, overwriting it with its infected, morphed version. The original dummy is then executed from a hidden file it was copied to during the propagation phase, disguising the fact that the actual executable was infected. Essentially, each propagation employs a unique version of the morphed code, You can take this further by adding more functionalities and techniques, like code injection and persistence. Although the code is designed for changes, We’ve touched on the idea of self-modifying/mutating malware and code morphing, but this only skims the surface of true Metamorphism and self-mutating code. Let’s not jump the gun.
I recommend running the generated code through a debugger instead of just executing it directly and hoping for the best. (VM), you can easily disable the Anti-Debugging astuce or bypass ;). Additionally, you can introduce points in the code where you can pop into the assembly view and examine the generated code.
That’s all for now. I hope you learned something from this revisit of the article “Metamorphic Malware.” When it comes to malware, a little bit of obfuscation will usually get you by. You don’t really need to write a Mutation engine, as it introduces unnecessary complexity that doesn’t really help maintain the malware’s features and functionalities, Metamorphism the strongest technique ever ideated, ever created. So keep it simple and clean. Until next time!