For the complete documentation index, see llms.txt. This page is also available as Markdown.

Solution Overview-Protection Scheme for ARM Thumb Object Files and Static Libraries

introduce highly secure protection approach to protect ARM Thumb Object file/Static lib

Summary

This document presents a customized protection scheme of Virbox Protector designed for ARM‑based embedded environments, specifically targeting the Thumb instruction set. and fully supporting static libraries and object files generated by the IAR compiler.

Since many embedded vendors rely on IAR IDE for commercial ARM embedding device development, this compatibility ensures that .o and .a files produced by IAR can be protected without modifying the customer’s toolchain or build workflow.

Virbox Protector provides a comprehensive workflow for securing object files (.o) and static libraries (.a) commonly used in customer delivery scenarios.

The solution integrates multiple protection techniques—including code virtualization, obfuscation, watermark embedding, function‑symbol optimization, and object‑file merging—to enhance resistance against reverse engineering while maintaining compatibility with ARM toolchains and constrained flash environments.

To address customer feedback regarding file size expansion, especially in systems with strict flash limits (e.g., 128 KB), this document also details several optimization strategies. These include reducing redundant virtual machines, trimming high‑impact obfuscation features, and improving function parsing to minimize unnecessary branches.

All test data, comparisons, and risk assessments included in this document serve as reference points. Actual results may vary depending on the customer’s real application, build environment, and selected protection configuration.

Thumb Virtual Machine Function Verification Workflow

Product Background

Virbox Protector supports function‑level protection for object (.obj) files, including both code obfuscation and code virtualization:

  • Code obfuscation transforms original instructions into randomized and unreadable instruction fragments through techniques such as equivalent substitution, immediate value encryption, indirect jumps, fake branches, junk instruction insertion, and instruction slicing. Obfuscation does not change the fundamental characteristics of the instruction set: ARM instructions still execute as ARM, and Thumb instructions still execute as Thumb on the system. Since the original execution flow is preserved, obfuscation is not affected by special hardware environments.

  • Code virtualization converts the original assembly instructions within a function into custom virtual instructions. These instructions are executed inside a custom virtual machine at runtime, simulating memory access, conditional branching, register states, and more. This requires precise instruction parsing and handling of different instruction formats. Virbox Protector uses the A32 instruction set to implement a virtual machine interpreter capable of handling A32, T16 (Thumb), and T32 (Thumb‑2) instructions. However, some embedded processors (e.g., Cortex‑M4 based on the ARMv7E‑M architecture) cannot execute A32 instructions, which prevents the protected program from being properly virtualized on such devices.

To expand the protection coverage and enhance security on specific processors, Virbox Protector introduces a customized virtual machine implementation that supports virtualization of Thumb instructions on these platforms.

Mechanism

For object files containing Thumb instructions, the Virbox Protector uses T16 and T32 instructions to implement a virtual machine interpreter for the T16 (Thumb) and T32 (Thumb‑2) instruction sets. During protection, the Thumb virtual machine is enabled by default, allowing protected Thumb‑based object files to run correctly on specific processors (such as Cortex‑M4 processors based on the ARMv7E‑M architecture).

Feature Testing

Test Environment

Android NDK Compilation

When compiling ARM32 programs with the Android NDK, adding the -mthumb parameter instructs the compiler to generate Thumb‑instruction binaries. This allows the resulting program to run on Android devices while still producing Thumb‑based object files, enabling us to perform testing even without embedded processor hardware.

Test Procedure:

  1. Compile the Thumb‑instruction .obj file using the Android NDK.

  2. Use the Virbox Protector to protect the .obj file, selecting the target functions for code virtualization.

  3. Inspect the protection results of the Thumb instruction set in the protected .obj file.

  4. Verify whether the protected program can be successfully linked and compiled, and confirm that the resulting executable runs correctly.


IAR Embedded Workbench Compilation

Test Procedure:

  1. Compile the Thumb‑instruction .obj file using the IAR Embedded Workbench Compilation

  2. Use the Virbox Protector to protect the .obj file, selecting the target functions for code virtualization.

  3. Inspect the linking behavior of the protected .obj file when generating the final executable.

  4. Inspect the protection results of the Thumb instruction set in the protected .obj file.

Project Decompiled Comparison (Before & After Protection)

Decompiled View Before Protection

Locate the function intended for virtualization and directly decompile it to observe the original assembly instructions.

View the C–style pseudocode generated after decompiling the function.

Decompiled project After Protection

  1. Invocation Process

  • Locate the virtualized function. When the program executes the instruction BL sub_D8E4, double‑clicking BL sub_D8E4 saves the return address and jumps to B.W loc_13626.

  • As shown in the figure:

  1. Intermediate Jump

  • Double‑click B.W loc_13626 to continue the jump (an unconditional branch that does not affect the LR register) to the new location. The B.W instruction is used in Thumb‑2 mode.

  • As shown in the figure:

  1. Final Execution

  • After reaching loc_13626, the program begins executing the actual instructions at this location. The resulting instruction sequence (the final target code) consists entirely of virtualized Thumb instructions.

  • As shown in the figure:

View the C‑style pseudocode generated after decompiling the function.

Performance Testing

File Size Increment Test

This test evaluates the file size increase before and after protection. Using a sample file (system_machine.o), code virtualization is applied to observe the file size changes when virtualizing: All functions versus virtualizing: Only selected functions.

See the table below:

Number of Virtualized Functions

Original File Size

File Size After Protection

Increase

Any 10 selected functions

101,161

157,828

56,667

Any 30 selected functions

101,161

213,516

112,355

All functions (134 total)

101,161

443,532

342,371

Notes:

1) Compare the file size before and after protection, and choose an appropriate protection strategy based on the actual device constraints. 2) The more functions selected for virtualization, the larger the resulting file size and the greater the performance impact. Adjust dynamically according to the program’s actual requirements.

Startup Time

1. Description

Using the test demo as an example, compare the performance of the original program and the program after virtualizing 11 functions.

2. Results

Note: Performance overhead varies depending on the specific code logic.

Startup time of the original program:

Note:

  1. The titles of the four columns in the table above are:

Process ID (PID) , Turnaround Time, Waiting Time, Response Time;

  1. For total 5 Process (PID) above:

Average Turnaround Time: 4.80s;

Average Waiting Time: -1.80s;

Average Response Time: 3.80s;

program execution time:0.004531s

Startup time of the fully virtualized program:

Note:

For total 5 Process (PID) with all of function virtualization above:

Average Turnaround Time: 4.60s;

Average Waiting Time: -4.40s;

Average Response Time: 3.60s;

program execution time:0.007879s

Compatibility Testing

Runtime Environment: Android 7 – Android 16

Test Program: Android ARM32 (Thumb instructions)

Description: The software is tested across Android devices running different system versions to ensure stable operation.

Device Model

System

Result

Xiaomi MIX

Android 7

Pass

Huawei nova youth

Android 8

Pass

Xiaomi MIX 2

Android 9

Pass

Xiaomi 10

Android 12

Pass

Google Pixel 6

Android 16

Pass

Oppo Find X8

Android 16

Pass

OnePlus 13

Android 16

Pass

Multi‑VM Virtualization Function Verification

Mechanism

During protection, the Virbox Protector tool converts each basic block of a function into virtual machine pseudocode, which is then interpreted and executed by the VM interpreter at runtime.

In this enhanced mechanism, multiple virtual machine instances can be created during protection. Each function is randomly assigned to a different VM instance, and each VM uses a distinct pseudocode encoding scheme. This significantly increases the overall security level.

Feature Testing

Test Steps:

  1. Compile Thumb‑instruction .obj files using the IAR and Green Hills toolchains.

  2. Use the Virbox Protector (debug version with symbols) to protect the .obj file. Select the functions to be virtualized and configure the number of VM instances (e.g., 4 VMs) in the advanced options.

  3. Examine the behavior of the linked executable generated from the protected .obj files.

  4. Inspect the protection effect on the Thumb instruction set within the protected .obj files.

Verification of Protection

Note: The debug version of the Virbox Protector (with symbols) is intended only for testing the multi‑VM functionality. The protected .obj files generated by this version must not be distributed externally.

Use the Virbox protector (debug version with symbols) to protect the .obj file. After protection, load the resulting .obj file into IDA and search for the vm_init function. The number of occurrences indicates how many VM instances are being used. For example, the following case shows that 8 VM instances were generated:

Decompiled View After Protection

  1. Locate the protected function. The program execution reaches the instruction

  1. Double‑click to jump to BL vm_init_0.

  1. Click X to enter the vm_init_0 function and observe the differences in its instructions.

  2. Following the same steps, you can compare the behavior of the other VM instances.

File Size Increment Test

The file LP_D13_PTG_app_test.a is protected to evaluate the file size increase before and after applying protection.

Number of VM Instances

Number of Functions

Merge Option

File Size

Size Increase

Original Program

/

/

366k

/

1 VM

37

Enabled

509k

143k

1 VM

37

Disabled

554k

188k

4 VMs

37

Enabled

662k

296k

4 VMs

37

Disabled

706k

340k

8 VMs

37

Enabled

866k

500k

8 VMs

37

Disabled

911k

545k

Watermark Function Verification

Description

The Virbox Protector implements this feature by embedding custom data into the .obj file (object file or static library) during the protection process. These data fields can be configured in the tool interface. After the protected .obj file is compiled and linked, the embedded data will remain in the final binary as a watermark.

By detecting whether the binary contains this watermark, it is possible to determine whether the static library has been linked, thereby identifying the user of the static library and preventing unauthorized third‑party usage.

Waremark Setting

Virbox Protector Version: 3.5.1.21416

Steps to set Watermark:

  1. Drag the .obj file into the Virbox Protector GUI tool.

  2. In the Protection options of the Virbox Protector GUI tool, the watermark configuration panel is displayed. The watermark value can be set using hexadecimal characters.

Note: The watermark value must contain an even number of hexadecimal characters. Odd‑length values are not allowed; otherwise, the protection process will fail.

Verification of Watermark setting

  1. Use the 010 Editor tool to inspect the watermark configured during protection. After parsing the static library or binary file, directly search for the corresponding Hex Bytes.

  2. Enter the configured watermark value in the search field. If the watermark is stored in the .text code section after protection, the configured watermark bytes can be located within the code.

Function Symbol Optimization

Issue Description:

When an .obj file is dragged into the Virbox Protector GUI tool, some function names displayed in the function list contain ??, as shown in the example below:

Optimization Approach:

The ?? ... [number] symbols generated by the IAR compiler are no longer treated as individual functions. In practice, this merges all branch functions into a single function, eliminating the appearance of branch‑level entries and preventing function names from being displayed as ??. As a result, the total number of functions is reduced, and each optimized function contains more instructions compared to the pre‑optimization branch functions.

Protector Version

Total Functions

3.5.1.21385

506

3.5.1.21416

115

Virbox Protector Version fixed issue:

In version 3.5.1.21416, the function parsing result appears as shown below:

Excessive File Size Increase After Protection

For some of embedding device flash, the maximum available flash space is 128 KB. When more than 40 functions are selected for code obfuscation, the compiler reports insufficient flash space. Therefore, optimizations have been applied to the standard obfuscation feature and the object‑file merging feature to reduce the final protected program size.

Code Obfuscation Optimization Plan

Since the maximum flash capacity is confirmed to be 128 KB, the core objective is to maximize security (including future virtualization support) while ensuring the protected binary does not exceed the 128 KB limit. The current version adopts the following adjustment strategy:

Obfuscation Adjustment Strategy

Note: The scoring scale is 1–10, where a higher score indicates a greater impact.

Obfuscation Method

Description

Impact Analysis

Impact Score

Trimmed

Equivalent Substitution

Replaces original instructions with semantically equivalent but structurally different instructions.

Increases code complexity and typically increases code size.

No change

No

Immediate Value Encryption

Encrypts constants (immediate values) and decrypts them at runtime.

Prevents direct inspection of constants in the binary, increases static analysis difficulty, may increase code size.

No change

No

Indirect Jumps

Replaces direct control‑flow jumps with indirect jumps (e.g., function pointers or jump tables).

Makes disassembly targets inaccurate, increases analysis difficulty, may increase code size.

No change

No

Fake Branches

Inserts non‑executed branches (dead code) to confuse control flow.

Requires analysts to distinguish real vs. fake branches; significantly increases code size.

No change

No

Dummy Instructions

Inserts meaningless (garbage) instructions to disrupt logical flow.

Increases code complexity and significantly increases code size.

Security Impact: 2/10 Space Impact: 8/10

Yes (reduced quantity)

Instruction Slicing

Splits a single instruction into multiple fragments, possibly placed in different locations.

Makes logic more scattered and harder to understand; may increase code size.

No change

No

Anti‑Run‑Trace

Inserts hidden probes to detect single‑step debugging; triggers incorrect instructions when illegal debugging is detected.

Adds probes that increase code size; mainly counters IDA Pro trace features; unnecessary for bare‑metal systems.

Security Impact: 0/10 Space Impact: 6/10

Yes (removed)

Comparison Before and After Optimization

Test Procedure:

Using the pre‑optimization version (3.5.0.21352) and the post‑optimization version (3.5.1.21361), code obfuscation was applied to a sample program (system_machine.o). The file size changes were measured when applying obfuscation to all functions and to selected functions.

Reference Data:

Number of Obfuscated Functions

Program Size (3.5.0.21352)

Program Size (3.5.1.21361)

Reduction

Original Program

101,161

101,161

/

All Functions (134)

485,796

360,548

125,248

Any 30 Functions

222,820

180,428

42,392

Any 10 Functions

131,292

114,068

17,224

Recommendation:

The above data is for reference only. Version 3.5.1.21361 available, and users should evaluate based on their own program structure and selected functions to determine whether the optimized VBP version meets the flash space requirement.

Object‑File Merging Optimization Plan

Optimization Strategy

Since a static library (.a) contains multiple object files (.o), it is recommended to use the object‑file merging feature. This feature has been optimized as follows:

  • Before Optimization (Version 3.5.1.21385):

    The merging process protected each .o file first and then merged them. As a result, after virtualization, each .o file contained its own VM instance. This caused a significant increase in file size because multiple identical VMs were embedded across different object files.

  • After Optimization (Version 3.5.1.21416):

    The merging process now merges all .o files before applying protection. This means the merged .o file is virtualized only once, resulting in a single VM instance. Reducing the number of VMs directly decreases the overall file size impact.

Comparison Before and After Optimization

Test Procedure:

Using the pre‑optimization version (3.5.1.21385) and the post‑optimization version (3.5.1.21416), code virtualization was applied to a sample static library (test_app.a) with the merge object files option enabled. The file size changes were measured when virtualizing all functions and when virtualizing only selected functions.

Reference Data:

Virtualization Count (3.5.1.21416)

Program Size (Before Object‑File Merging)

Program Size (After Merging)

Reduction

Original Program

373,982

373,982

/

All Functions (97 functions)

1,089,224

731,644

357,580

Any 30 Functions

541,392

443,308

98,084

Any 10 Functions

400,248

302,444

97,804

Reference Data (Version 3.5.1.21385)

Virtualization Count (3.5.1.21385)

Program Size (Before Object‑File Merging)

Program Size (After Merging)

Reduction

Original Program

373,982

373,982

/

All Functions (298 functions)

1,276,584

1,229,884

46,700

Any 30 Functions

443,312

396,612

46,700

Any 10 Functions

391,016

344,300

46,716

Compared with version 3.5.1.21385, version 3.5.1.21416 shows a larger size increase when object‑file merging is disabled and virtualization is applied. This is because the new version optimizes the previous issue where functions were split into multiple branch entries. Although the total number of functions is reduced, each function body becomes larger. As a result, when merging is not enabled, the size increase is more noticeable than before.

Recommendation:

The above data is for reference only. Version 3.5.1.21416 is available, and users should evaluate based on their own program structure and selected functions to determine whether the optimized VBP version meets the flash space requirement.

Comments

Since the test program cannot fully represent the customer’s real application scenarios, the test results may differ from actual usage conditions. The above results are for reference only. It is recommended to rely on the results obtained from real‑world usage and the actual test environment to make final assessments.

Last updated