Google’s evolving security measures for Android apps have recently introduced a protection mechanism known as PairIP. At its core, PairIP extracts portions of an app’s Java code, encrypts or obfuscates it, and then runs that code inside a custom virtual machine (VM) implemented in a native shared library—libpairipcore.so. This architecture makes static analysis and binary patching significantly more challenging for attackers. In this article, we will break down the key components and techniques used by PairIP.
1. Overview of PairIP Protection
PairIP is designed as a multi-layered integrity protection solution. It is meant to:
- Prevent tampering: By isolating and executing sensitive code within a custom VM, it becomes much harder to modify the application’s behavior.
- Harden reverse-engineering: The native VM interprets obfuscated bytecode loaded from asset files, and its execution is coupled with multiple runtime integrity checks.
- Leverage anti‑debug and anti‑tampering techniques: PairIP employs dynamic system checks, including anti-Frida and file integrity validations.
Instead of running all Java code in the normal Dalvik/ART environment, apps protected with PairIP offload certain critical operations to a secure execution context provided by libpairipcore.so.
2. Java Integration: The Role of VMRunner
On the Java side, an integral class—often named VMRunner—manages the protected workflow. Key aspects include:
Native Library Loading

This call loads libpairipcore.so into the process, making its native methods available.
Native Method Invocation
VMRunner declares a native method such as:
When invoked (typically via a higher-level method like invoke()
), this method passes a byte array—read from the assets folder—and parameters to the native side for execution. This decouples critical logic from the static Dalvik bytecode.
Asset-based Bytecode
The protected code is stored as encrypted “pseudo-VM files” in the assets folder. VMRunner’s role is to load these files, then forward them to executeVM()
for dynamic interpretation.
See reference for a detailed analysis on the VMRunner and bytecode dispatch.
3. Native Implementation: Inside libpairipcore.so
The heart of PairIP’s protection is the native shared library libpairipcore.so. Its responsibilities include:
3.1. Registration of Native Methods
JNI_OnLoad and RegisterNatives: During library load, the native function JNI_OnLoad
is executed. Here, the library registers its native methods (including executeVM) with the Java VM via RegisterNatives
. Reverse engineers often hook into RegisterNatives
(using tools such as Frida) to locate the actual in-memory address of executeVM. For example, a Frida script can iterate over the registration table to find:

Refer to reference for a GitHub discussion that covers similar techniques.
3.2. Dynamic Code Restoration
Although one might expect to see the executeVM function statically at a certain offset, the library employs runtime “fix-ups” (and even decryption) to change function offsets. This means that even if you dump the untouched library, the actual logic might be hidden or transformed at runtime.
Integrity Checks and Anti-Debugging: Before the VM dispatch loop starts, libpairipcore.so executes a series of security checks:
- /proc/self/maps Iteration: It scans memory mappings to detect injected code (for example, by Frida).
- System Call Obfuscation: The native code makes multiple syscalls (e.g., using
openat
, lseek
, and direct calls to syscall
) that are intertwined with anti-debugger routines.
- Checksum Verification: Some functions perform integrity checks using hash functions (e.g., FNV-1). For instance:
This ensures that if the bytecode or data is modified, the VM dispatch will jump to an error routine.

See reference for additional discussion on anti-tampering measures in apps employing PairIP.
3.3. Virtual Machine Dispatching
After passing the integrity checks, executeVM enters its main loop—often implemented as a giant switch-case block:
- Opcode Processing: The VM reads opcodes from the decrypted asset files. Each opcode corresponds to a specific operation (e.g., arithmetic, memory access, or control flow changes). Before executing an opcode, the VM often recomputes a hash (using FNV-1) over a segment of the code. Only if the computed hash matches the expected value does it proceed to execute the opcode’s handler; otherwise, it jumps to a “fail” branch.
- Custom Instruction Set: Since the protected bytecode is not standard Dalvik code, the VM implements its own instruction set. This can include:
- Generic Operations: Simple load/store, arithmetic, and branching.
- Specialized Handlers: For operations like anti-tampering routines or integrity verifications.
- Layered Protection: The dual-level verification (hash checks, branch redirections) makes patching the VM non-trivial—even if an attacker patches out the integrity checks in the Java code, the native VM will still verify its internal state before executing sensitive operations.
A deeper dive into the VM’s opcode structure and dispatch routine is presented in reference
4. Reverse Engineering Challenges and Techniques
Because libpairipcore.so is intentionally obfuscated and packed with runtime fix-ups, reverse engineering it requires sophisticated techniques:
- Dynamic Dumping: Tools like Frida can intercept
RegisterNatives
and dump the in-memory version of the library after all runtime modifications.
- Symbol Recovery: Since many symbols are stripped, reverse engineers often rely on patterns (e.g., comparing offsets and using heuristic naming) to reconstitute function names and structures.
- Emulation and Concolic Analysis: Due to the VM’s custom instruction set, fully understanding the behavior of the protected code might involve emulating the opcode dispatch loop and reconstructing the original logic.
These challenges are discussed extensively in reference and related Hacker News threads.
5. Conclusion
PairIP represents an evolution in app integrity protection on Android. By offloading key parts of the application logic into a custom VM (implemented in libpairipcore.so) that performs rigorous anti-tampering and anti-debugging checks, Google (or app developers leveraging this protection) dramatically increase the difficulty of reverse-engineering and patching protected apps. Although techniques exist—such as dynamic hooking, memory dumping, and emulation—to analyze such mechanisms, the complexity of PairIP’s layered protections (especially its custom opcode dispatch and runtime fix-ups) ensures that it remains a formidable barrier against unauthorized modifications.
This detailed overview should provide a solid foundation for understanding how PairIP operates on Android and the critical role played by libpairipcore.so in enforcing runtime integrity and anti-tampering measures. If you have further questions or need additional details on any specific part, feel free to ask!
For more information, you can reach heysmmreseller support or heysmmprovider from live chat.
References