vmClass

A template class implementing a virtual machine (VM) for executing obfuscated bytecode. More...

Public Functions

TypeName
vm (const uint8_t *code)
vm (const uint8_t(&code)[N])
~vm ()
voidset_register (uint64_t value)
uint64_tget_register () const
uint64_tstart ()
voidpush (uint64_t v)

Private Properties

TypeName
detail::vm::stack_manager< StackSize >stack_
detail::vm::code_reader< StackSize >code_reader_
detail::vm::register_manager< StackSize >registers_

Detailed Description

The vm class provides a secure execution environment for running obfuscated code, typically in the form of bytecode. The class is designed to enhance software security by making it more difficult for attackers to reverse engineer or tamper with the code that it executes. This is achieved through various mechanisms, including the use of custom opcodes, stack-based execution, and integration with anti-debugging techniques.

Template Parameters

  • StackSize: The size of the stack used by the virtual machine. This defines the maximum number of values that can be stored in the VM's stack at any given time.

Public Functions

vm (const uint8_t *code)

Constructs a vm instance and initializes it with the provided bytecode.

Definition

libantispy::vm< StackSize >::vm

Detailed Description

The constructor takes a pointer to the bytecode that the virtual machine will execute. The bytecode is typically obfuscated to prevent easy analysis, and it consists of a sequence of instructions that the virtual machine can interpret and execute.

Parameters

  • code: A pointer to the bytecode that the virtual machine will execute. The bytecode must be pre-compiled and compatible with the virtual machine's instruction set.

vm (const uint8_t(&code)[N])

Constructs a vm instance and initializes it with a fixed-size array of bytecode.

Definition

libantispy::vm< StackSize >::vm

Detailed Description

This constructor allows the virtual machine (VM) to be initialized with a fixed-size array of bytecode. The array is passed by reference, ensuring that the bytecode is neither copied nor modified. The constructor utilizes perfect forwarding to forward the array to another constructor, which handles the initialization using a pointer to the array's first element. This approach provides a safe and efficient way to pass constant bytecode arrays to the VM while leveraging the existing constructor.

Template Parameters

  • N: The size of the bytecode array, which is deduced automatically by the compiler.

Parameters

  • code: A reference to the constant bytecode array. This array contains the sequence of instructions that the virtual machine will execute. The array must be of type uint8_t and have a size of N .

~vm ()

Virtual destructor for the vm class.

Definition

virtual libantispy::vm< StackSize >::~vm

Detailed Description

This destructor is responsible for properly cleaning up resources when an instance of the vm class is destroyed. Being virtual ensures that the destructor of any derived class will be called correctly, allowing for proper cleanup of derived class-specific resources. This is especially important in polymorphic scenarios where a pointer to a base class ( vm ) might actually point to a derived class object.

The destructor currently has an empty implementation, indicating that no specific cleanup actions are required by the vm class itself. However, this design allows for safe inheritance and extension of the vm class, where derived classes might allocate resources (e.g., dynamic memory, file handles, etc.) that require cleanup.

The vm class might manage resources indirectly (e.g., through members that automatically handle their own cleanup), or it might rely on derived classes to handle any necessary cleanup.

void set_register (uint64_t value)

Sets the value of a specific virtual machine (VM) register.

Definition

void libantispy::vm< StackSize >::set_register

Detailed Description

The set_register function assigns a given 64-bit value to a specified register within the virtual machine's register set. This function is templated on the vm_register type, allowing compile-time selection of the target register.

Template Parameters

Parameters

  • value: The 64-bit value to be stored in the specified register. This value will overwrite any previous value held in the register.

std::logic_errorIf the template parameter r is equal to vm_register::from_code .

This function performs a static_assert to ensure that the register r is not vm_register::from_code . This is because vm_register::from_code is reserved for internal use and should not be directly manipulated via this function. If the static_assert condition fails, it will trigger a compile-time error.

The template nature of this function allows the compiler to optimize register access. Since the register is specified as a template parameter, the compiler knows at compile time which register is being set, potentially eliminating overhead associated with runtime register selection.

This function directly modifies the internal state of the virtual machine by updating one of its registers. This could impact the execution state of the VM if the registers are used for storing execution context or computational results.

Example Usage:

set_register<vm_register::r1>(42); // Sets the value of register r1 to 42.

The register r must be a valid vm_register enum value other than vm_register::from_code .

The specified register will contain the new value provided by the value parameter.

uint64_t get_register () const

Retrieves the value of a specific virtual machine (VM) register.

Definition

uint64_t libantispy::vm< StackSize >::get_register

Detailed Description

The get_register function allows you to access the current value stored in a specific register within the virtual machine's register set. The register to be accessed is specified at compile time through the template parameter r , ensuring efficient access without the overhead of runtime selection.

Template Parameters

Returns

The 64-bit value currently stored in the specified register. The value is returned as an unsigned 64-bit integer.

std::logic_errorIf the template parameter r is equal to vm_register::from_code .

This function includes a static_assert to ensure that the register r is not vm_register::from_code . The vm_register::from_code is reserved for internal operations within the VM and should not be accessed directly. If the static_assert fails, it will result in a compile-time error.

The function's template-based design enables the compiler to optimize register access since the specific register is known at compile time. This can lead to improved performance by eliminating unnecessary branching or index calculations.

Example Usage:

uint64_t value = get_register<vm_register::r1>(); // Retrieves the value stored in register r1.

The register r must be a valid vm_register enum value other than vm_register::from_code .

The function returns the current value stored in the specified register.

uint64_t start ()

Starts the execution of the virtual machine (VM) until completion.

Definition

virtual uint64_t libantispy::vm< StackSize >::start

Detailed Description

The start function initiates the execution of the virtual machine, running it in a loop until a termination condition is met. During each iteration of the loop, the VM's instruction dispatcher is called to execute the next instruction in the code stream. The function continues to execute instructions until the dispatch function returns false , indicating that the execution should stop.

Returns

The value of the return_register register upon completion of the VM execution. The value is returned as a 64-bit unsigned integer ( uint64_t ). This value typically represents the result of the VM's execution.

The function is designed to loop indefinitely until the dispatch function returns false . The dispatch function is responsible for executing the current instruction and determining whether to continue execution.

The function assumes that the VM's state, including its registers and instruction pointer, has been correctly initialized before calling start . The result of the execution is stored in the return_register , which is a predefined register within the VM architecture designated for storing the final result.

The loop structure of this function is minimal, consisting of an empty statement within a do-while loop. This structure is designed for efficiency, with the core execution logic being handled entirely by the dispatch function.

The start function is marked as virtual , allowing derived classes to override it to implement custom VM start behaviors.

The VM must be properly initialized, and the code to be executed must be loaded and ready in the VM's instruction stream.

Upon completion, the VM will have executed all instructions, and the result will be stored in the return_register .

Example Usage:

uint64_t result = vm_instance.start(); // Start the VM execution and retrieve the result from the vm program.

Implementation Details:

  • The dispatch function is called in each iteration of the loop. It is responsible for decoding and executing the current instruction, updating the VM's state accordingly.

  • The loop terminates when dispatch returns false , which typically indicates that a halt instruction has been encountered or an error condition has been met.

  • The final value stored in the return_register is then returned as the result of the start function.

void push (uint64_t v)

Pushes a value onto the virtual machine's stack.

Definition

virtual void libantispy::vm< StackSize >::push

Detailed Description

The push function adds a 64-bit unsigned integer value ( uint64_t ) onto the VM's internal stack. The stack is an essential component of the virtual machine, used to store temporary values, return addresses, and other data required during the execution of the VM's code.

Parameters

  • v: The 64-bit unsigned integer value to be pushed onto the stack.

The function is marked as virtual , allowing derived classes to override it to implement custom behavior for pushing values onto the stack.

The stack is typically used to store intermediate results, function arguments, return addresses, and other temporary data needed during the execution of the VM's instructions.

The stack follows the Last-In-First-Out (LIFO) principle, meaning that the last value pushed onto the stack will be the first one to be popped off. This behavior is crucial for operations like function calls and arithmetic calculations in the VM.

The function directly interacts with the VM's stack, which is managed by a separate stack_ object. The stack_ object must be properly initialized and capable of handling the required stack operations, such as push and pop .

The push function is typically used in conjunction with other stack-related operations, such as pop , to manage data flow within the VM. For example, values may be pushed onto the stack before a function call and then popped off afterward to restore the previous state.

Proper use of the stack is crucial for the correct operation of the virtual machine. Incorrect stack management, such as mismatched push and pop operations, can lead to stack underflow or overflow, resulting in undefined behavior.

The stack must be properly initialized and capable of storing additional values. There must be sufficient space on the stack to accommodate the new value being pushed.

The value v will be added to the top of the stack, increasing the stack's size by one element.

Example Usage:

vm_instance.push(42); // Pushes the value 42 onto the VM's stack.

Implementation Details:

  • The push function interacts with the stack_ member of the VM class, which is responsible for managing the stack's contents.

  • The value v is passed directly to the push method of the stack_ object, which handles the actual storage of the value.

  • The stack is typically implemented as a dynamic data structure, such as a vector or a custom stack class, which grows as needed to accommodate new values.

  • This function does not return a value, but it modifies the state of the VM by altering the contents of the stack.

Private Properties

detail::vm::stack_manager< StackSize > stack_

Manages the stack operations within the virtual machine.

Definition

detail::vm::stack_manager<StackSize> libantispy::vm< StackSize >::stack_

Detailed Description

The stack_ member is an instance of detail::vm::stack_manager , which is responsible for managing the stack operations in the virtual machine (VM). The stack is a critical component of the VM, used to store temporary data, function arguments, return addresses, and other intermediate values required during the execution of the VM's instructions.

Template Parameters

  • StackSize: The size of the stack, determining the maximum number of elements that can be stored.

The stack_manager class encapsulates the logic for handling stack operations, including push , pop , and peek , ensuring that the stack is managed efficiently and securely.

The stack_ member provides an interface for interacting with the VM's stack. The stack follows the Last-In-First-Out (LIFO) principle, meaning the last value pushed onto the stack will be the first one to be popped off.

Proper management of the stack is crucial for the correct operation of the VM. The stack_manager ensures that stack operations are performed correctly, preventing issues such as stack overflow or underflow, which could lead to undefined behavior.

The StackSize template parameter defines the size of the stack, which is fixed at compile time. This size determines the maximum number of values that can be stored in the stack during the VM's execution.

The stack_ member is used internally by various VM methods, such as push , pop , and others, to manage the flow of data within the VM. It is not typically accessed directly by external code.

If the StackSize is too small, the stack might overflow, leading to potential data corruption or crashes. Conversely, if StackSize is too large, it may consume unnecessary memory.

Example Usage in VM:

stack_.push(42);         // Pushes the value 42 onto the stack.
uint64_t value = stack_.pop(); // Pops the value from the stack.

Implementation Details:

  • The stack_manager class provides a robust implementation for managing stack operations within the VM.

  • The stack_ member variable is instantiated with the template parameter StackSize , defining the stack's capacity.

  • The stack is typically implemented as an array or a dynamic data structure capable of handling push and pop operations efficiently.

  • The stack_manager class ensures that the stack's state is consistently maintained, preventing illegal operations that could compromise the VM's stability.

Design Considerations:

  • The size of the stack ( StackSize ) should be carefully chosen based on the expected workload of the VM.

  • The stack is a critical resource in the VM, and its management directly impacts the performance and reliability of the virtual machine.

detail::vm::code_reader< StackSize > code_reader_

Manages the reading and execution flow of the VM's bytecode.

Definition

detail::vm::code_reader<StackSize> libantispy::vm< StackSize >::code_reader_

Detailed Description

The code_reader_ member is an instance of detail::vm::code_reader , which is responsible for managing the reading and sequential execution of the bytecode within the virtual machine (VM). It serves as the instruction pointer, directing the flow of execution by fetching and decoding instructions from the bytecode.

Template Parameters

  • StackSize: The size of the stack, which determines the capacity of the stack that the code_reader_ interacts with during execution.

The code_reader_ handles the instruction-fetch cycle within the VM, including advancing the instruction pointer, reading operands, and jumping to different parts of the bytecode based on control flow instructions (e.g., jumps, calls, returns).

The code_reader_ is integral to the VM's operation as it ensures that the bytecode is executed correctly and in the proper sequence. It interacts closely with other components of the VM, such as the stack manager ( stack_ ) and registers, to facilitate the execution of instructions.

The code_reader_ member provides methods to fetch instructions, read operands, and manage the instruction pointer. It also supports complex control flow operations, such as conditional and unconditional jumps.

Mismanagement of the code_reader_ could lead to issues such as infinite loops, incorrect instruction execution, or jumping to invalid memory locations, potentially resulting in undefined behavior or crashes.

Example Usage in VM:

code_reader_.jump_rel(offset);  // Jumps to a new instruction relative to the current position.

Implementation Details:

  • The code_reader_ class is designed to efficiently manage the bytecode execution process, ensuring that each instruction is fetched, decoded, and executed in the correct order.

  • It is initialized with a pointer to the start of the bytecode, and it maintains an internal pointer to track the current position within the bytecode.

  • The code_reader_ provides methods for advancing the instruction pointer, reading immediate values, and handling control flow instructions such as jumps and calls.

Design Considerations:

  • The StackSize template parameter defines the stack's capacity, which is used by the code_reader_ to manage stack-related instructions.

  • The design of the code_reader_ ensures that it can efficiently handle a wide range of bytecode instructions, from simple arithmetic operations to complex control flow and function calls.

  • The code_reader_ must be carefully managed to ensure that the VM's execution flow remains consistent and accurate, especially when handling jumps, calls, and returns.

The code_reader_ interacts with other VM components, such as registers and the stack, to perform its operations. It must be in sync with these components to ensure correct execution of the bytecode.

The code_reader_ is crucial for implementing control flow in the VM, as it supports both conditional and unconditional jumps, as well as function calls and returns.

The code_reader_ is designed to be highly efficient, with minimal overhead in fetching and decoding instructions. This efficiency is critical for the overall performance of the VM.

The code_reader_ provides methods for both relative and absolute jumps, allowing for flexible control flow management within the VM.

detail::vm::register_manager< StackSize > registers_

Manages the virtual registers within the VM.

Definition

detail::vm::register_manager<StackSize> libantispy::vm< StackSize >::registers_

Detailed Description

The registers_ member is an instance of detail::vm::register_manager , which is responsible for managing the virtual registers used by the virtual machine (VM). These registers serve as the primary storage locations for operands and intermediate values during the execution of the VM's bytecode.

Template Parameters

  • StackSize: The size of the stack, which influences the configuration of the register manager within the VM.

The registers_ object provides methods to set, get, and manipulate the values stored in the virtual registers. These registers are integral to the VM's operation, facilitating the execution of arithmetic, logical, control flow, and other instructions by providing temporary storage and direct access to operands.

The registers_ member plays a critical role in the VM's execution process by holding the state of the VM across different instructions. It allows for the retention of values between operations and is essential for implementing various computational processes within the VM.

The registers_ is designed to be highly efficient, with direct access to register values, enabling the VM to execute instructions with minimal overhead.

Mismanagement of the registers_ could lead to incorrect execution of instructions, as well as unintended modification of critical values, potentially resulting in undefined behavior or program crashes.

Example Usage in VM:

uint64_t value = registers_.get_register<vm_register::r1>();  // Retrieves the value stored in register r1.
registers_.set_register<vm_register::r2>(value);  // Sets the value of register r2.

Implementation Details:

  • The registers_ class provides a collection of virtual registers, which are used by the VM to store operands, intermediate results, and other necessary data during bytecode execution.

  • The number and configuration of these registers are influenced by the StackSize template parameter, which defines the overall memory layout of the VM.

  • The registers_ class includes methods for directly accessing and modifying the contents of these registers, ensuring that the VM can efficiently perform its operations.

Design Considerations:

  • The register_manager class is designed to provide fast and efficient access to the virtual registers, which are frequently accessed during bytecode execution.

  • The design ensures that each register can be individually accessed and manipulated without affecting the overall state of the VM.

  • Registers are often used in pairs or groups for certain operations, and the register_manager class is optimized to support these access patterns.

The registers_ member is crucial for the correct functioning of the VM, as it stores the state of the machine across instruction executions. Registers may hold values such as operands for arithmetic operations, memory addresses for load/store operations, and flags for conditional branches.

The registers_ class is designed to work seamlessly with other components of the VM, such as the stack manager and code reader, to provide a coherent and efficient execution environment.

The registers_ are typically implemented as a fixed-size array of 64-bit values, with specific registers designated for particular purposes, such as the program counter, stack pointer, or return value register.

Design Constraints:

  • The registers_ must maintain consistency across different instructions and across different execution contexts, such as function calls and returns.

  • The class must handle register allocation and deallocation efficiently, minimizing overhead during execution.

The registers_ member is optimized for scenarios where frequent read and write operations are required, as is typical in a virtual machine environment.