How a Virtual Machine Is Built

The previous lesson said a virtualizing obfuscator replaces your code with instructions for a machine that does not exist, plus a small interpreter that runs them. That word, machine, does a lot of quiet work, and it is worth taking apart. A virtual machine here is not mysterious. It is four ordinary pieces of state and one loop that moves between them. Once you can name the four pieces, the whole idea stops being a black box.

A real CPU, described in four words

Before the invented machine, look at the real one under your desk. A CPU has memory full of instructions, a register that remembers which instruction is next, a place to hold values it is working on, and a loop burned into silicon that fetches the next instruction, figures out what it means, and carries it out. That is the whole shape of every processor ever built. A software virtual machine copies that exact shape in a few hundred lines of ordinary code.

If that felt abstract, use a kitchen. The recipe card is the list of instructions. Your finger on the current line is the pointer that remembers where you are. The counter space where ingredients pile up is the working area. And you, reading one line and doing it before moving your finger down, are the loop. A virtual machine is a very literal, very patient cook.

Part one: the bytecode array

The first part is the program itself, but not as text. It is a flat list of tiny numbered instructions, each one an operation code (the opcode) and sometimes a value to work on (the operand). This list is just data sitting in an array. Nothing about it looks like your original function. There are no names, no plus signs, no visible if-statements, only entries like push this number or add the top two values.

Part two: the instruction pointer

The machine needs to remember where it is in that list. That is the instruction pointer, usually a single number that starts at zero and counts up. Read instruction zero, then instruction one, then two. When a jump instruction runs, it does one special thing: it sets the pointer to somewhere else in the list instead of the next slot. That single move, writing a new number into the pointer, is how loops and branches exist inside a machine that otherwise only counts upward.

Part three: the stack

The machine needs somewhere to hold values mid-calculation. The common choice is a stack: a pile where you only ever add to the top or take from the top. To compute two plus three, you push two, push three, then run add, which takes the top two values off the pile and pushes their sum back. The stack is scratch paper. It is empty when the program starts and, for a clean expression, empty again when it finishes, holding only the final answer for a moment.

Part four: the dispatch loop

The last part is the engine that ties the other three together. It is a loop that does the same three things forever: fetch the instruction the pointer is aimed at, decode which operation it is, and execute that operation against the stack. Then it moves the pointer and goes again, until it runs off the end or hits a stop instruction. This loop is the only code in the whole output that is genuinely running. Your logic is not running. Your logic is the data being fed to it.

Step the machine above by hand and watch all four parts at once. The list on the left is the bytecode array. The highlight moving down it is the instruction pointer. The column on the right is the stack, growing and shrinking as values are pushed and combined. And the act of pressing step is you playing the role of the dispatch loop, one fetch-decode-execute at a time.

Notice what an attacker actually receives. They get the dispatch loop as readable code, because it has to be there to run anything, and they get the bytecode array as a wall of numbers. What they do not get is your program in any recognizable form. The loop is generic and reveals nothing specific about your logic. The array reveals everything, but only to someone who has first reconstructed what each opcode number means, which is a machine nobody documented and which the next build may renumber.

There is a subtlety worth stating precisely. The dispatch loop is an interpreter, and interpreters are slow relative to running native code, because every one of your original operations now costs a fetch, a decode, a branch to the right handler, and stack traffic on top of the actual work. That overhead is the price of the protection and the reason virtualization is applied selectively rather than to an entire program. The cost lesson later in this level makes that tradeoff concrete.

Four parts, one loop. A bytecode array holds the program as data, an instruction pointer tracks position, a stack holds values in flight, and a dispatch loop fetches, decodes, and executes forever. Every obfuscation VM you will ever meet is a variation on exactly this skeleton.

Frequently asked questions

Is an obfuscation virtual machine the same as VirtualBox or VMware?

No. Those virtualize a whole computer so you can run an operating system inside another. An obfuscation VM is a tiny interpreter, often a few hundred lines, that runs one made-up instruction format. The word virtual machine is shared, but the scale and purpose are completely different.

Why a stack instead of named variables inside the VM?

A stack machine needs almost no bookkeeping: operations just take from the top and push results back, so the interpreter stays small and the bytecode stays compact. Some machines use registers instead, which the stack-versus-register lesson in this level compares directly. Both are common and both work.

Does the attacker get to read the dispatch loop?

Yes, and that is unavoidable, because the loop has to exist as runnable code to run anything. What they do not get for free is the meaning of the opcode numbers or your logic. The loop is generic; the value is in the undocumented, per-build instruction encoding it interprets.

Keep learning

  • What Is Bytecode Virtualization?
  • How a Stack Machine Runs Bytecode
  • How Attackers Actually Deobfuscate Code
  • VM Obfuscation vs Identifier Renaming
  • How Lua Obfuscation Actually Works
  • Lua Obfuscator
  • JavaScript Obfuscator
  • Python Obfuscator

All lessons