vmClass
A template class implementing a virtual machine (VM) for executing obfuscated bytecode. More...
Public Functions
Type | Name |
---|---|
vm (const uint8_t *code) | |
vm (const uint8_t(&code)[N]) | |
~vm () | |
void | set_register (uint64_t value) |
uint64_t | get_register () const |
uint64_t | start () |
void | push (uint64_t v) |
Private Properties
Type | Name |
---|---|
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 typeuint8_t
and have a size ofN
.
~vm ()
Virtual destructor for the vm
class.
Definition
virtual
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
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
r
: The register to be set, specified as a constant of typevm_register
. The register is chosen at compile time and must not bevm_register::from_code
.
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>; // 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
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
r
: The register to retrieve, specified as a constant of typevm_register
. This parameter is determined at compile time and must not bevm_register::from_code
.
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
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 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
returnsfalse
, which typically indicates that ahalt
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 thestart
function.
void push (uint64_t v)
Pushes a value onto the virtual machine's stack.
Definition
virtual void
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.; // Pushes the value 42 onto the VM's stack.
Implementation Details:
The
push
function interacts with thestack_
member of the VM class, which is responsible for managing the stack's contents.The value
v
is passed directly to thepush
method of thestack_
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>
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_.; // Pushes the value 42 onto the stack.
uint64_t value = stack_.; // 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 parameterStackSize
, defining the stack's capacity.The stack is typically implemented as an array or a dynamic data structure capable of handling
push
andpop
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>
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 thecode_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_.; // 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 thecode_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>
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_.; // Retrieves the value stored in register r1.
registers_.; // 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.