R2Pay Under the Microscope: Runtime Protections

r2pay challenge

R2Pay Under the Microscope: Runtime Protections Analyzing and Bypassing Android Runtime Protections Using Python and GDB Safety & Legal Notice : This content is provided for educational, research, and authorized security testing purposes only. Apply these techniques only to systems or applications you own or have explicit permission to assess, and only within isolated lab environments. The author disclaims any responsibility for misuse or illegal use of this material. I. Introduction The OWASP MASTG Android UnCrackable L4 (r2Pay) challenge implements multiple layers of runtime protections, including obfuscation, anti‑debugging checks, integrity verification, and defensive native code logic intended to resist analysis. These mechanisms are designed to slow down reverse engineering and block dynamic inspection of sensitive routines. In this article, we focus exclusively on bypassing these protections at the native x86_64 layer. Using GDB and Python‑assisted debugging, we identify where each defensive control is enforced and show how to neutralize or patch it to restore full observability of the program’s execution. The objective is to present a clear, practical workflow for disabling protection mechanisms in hardened Android native libraries. II. Adversary Model The protection mechanisms analyzed in this work are designed to defend against a skilled software adversary with near full control over the execution environment. Specifically, the assumed adversary is capable of: Debugging and runtime tracing, including the use of native debuggers (e.g., GDB or LLDB) to inspect memory, registers, and control flow at both the Java and native layers.  Dynamic instrumentation and function hooking, using frameworks such as Frida.  Executing the application in modified environments, including rooted devices or systems with altered runtime components.  Static and dynamic reverse engineering, including disassembly and analysis of native libraries and binary patching.  Analyzing obfuscated native code, where the native library exposes JNI_OnLoad but avoids exporting Java_* JNI symbols, instead registering methods at runtime via JNINativeMethod and using indirect dispatch. The adversary is assumed to have no access to source code or private signing keys, but may freely observe, modify, and interfere with the application at runtime. Network‑level attacks and backend compromise are considered out of scope; the focus of this work is strictly on local, runtime‑based attacks against the client application. In the context of this analysis, the adversary is realized by attaching GDB to the Android Zygote process, enabling observation and control of the application from process creation through native library initialization. III. Protection Architecture R2Pay application applies a multi-layered runtime protection strategy designed to defend the application throughout its entire lifecycle from native library loading to critical user interactions. Native Protection Anti-Debugging Anti-Root Anti-Instrumentation Self-Integrity Verification Execution Environment Validation Java Protection Anti-Root 1 Native-Level Protections Executed in init_array Segment. 2 Java-Level Protections Root detection using libtool-checker.so. 3 Runtime and UI-Level UI display and libnative-lib.so integrity check. 4 User Interaction Protections Button click triggers protections. crash INTEGRITY VIOLATION 1. Native-Level Protections (libnative-lib.so) The core security mechanisms are implemented in libnative-lib.so and are triggered at the init_array execution stage, ensuring protections are enforced as early as possible in the app lifecycle. These early checks are specifically designed to disrupt instrumentation and debugging tools, causing tools such as Frida and GDB to crash or fail during attachment. At this stage, the following protections are executed: Anti-Debug detection Anti-Frida detection Anti-Root detection Self-integrity check of libnative-lib.so Execution Environment Validation. This guarantees that the application starts only in a trusted runtime environment. 2. Java-Level Protections (libtool-checker.so) In parallel, Java-level protections are applied using libtool-checker.so, which is dedicated exclusively to root detection. This layer provides an additional safeguard at the Java runtime level, complementing the native checks and increasing resistance against bypass attempts. 3. Runtime and UI-Level Enforcement During application initialization, root detection is first performed at the native level through libnative-lib.so. When the GUI launch sequence starts, the root detection checks are executed again to ensure that no changes have occurred since startup. After the full user interface is displayed, libnative-lib.so conducts a self-integrity verification to confirm that the native library has not been patched or modified in memory. 4. User Interaction Protections At the final and most critical stage—when the user clicks a sensitive action button (e.g., GENERATE R2COIN)—the application invokes native protections again via libnative-lib.so, including: Anti-Debug checks Anti-Frida checks Self-integrity verification This ensures that even if an attacker manages to bypass earlier layers, runtime tampering or instrumentation is detected at the moment of sensitive operations. IV. Analyzing and Bypassing Protections In this article, we focus on the x86_64 version of libnative-lib.so, since our analysis is performed on an Android emulator, which runs x86_64 binaries. The detailed steps, along with a Python script to detect and capture execution traces using GDB, are available on our GitHub repository. 1. Native-Level Protections As discussed in the previous section, libnative-lib.so initializes its protection mechanisms from the .init_array segment. Analysis of this segment revealed three functions of interest. The first function,_datadiv_decode16813968421045149467, is responsible for decoding a list of keywords that are later reused by the protection logic. The two remaining functions, sub_AE00 and sub_12CAD0, implement core parts of the protection mechanisms. A closer inspection of these two functions shows that they invoke pthread_create to spawn multiple threads. Each thread executes the function sub_2EB00 or sub_174510, which acts as a monitoring routine responsible for running the various protection checks during the initialization phase. As a result, a straightforward way to neutralize these protections is to prevent the creation of these threads for example, by patching or intercepting calls to pthread_create, effectively disabling the monitoring routines at initialization time. In this article, we will examine in detail where each protection is triggered and discuss the techniques used to disable it. 1.1. Debugger Detection The debugger detection mechanism relies on the TracerPid field read from /proc/self/status. This protection is enforced at two stages: during application initialization and upon user interaction via a button click. A. Debugger Detection during Initialization The detection is performed in sub_2EB00 at loc_4B80B, where the TracerPid value is compared against 0. A non‑zero value indicates the presence of a debugger and triggers the protection. The

Android Malware Analysis in Action

This case study walks through Android malware analysis using static and dynamic analysis. It explains a safe lab setup and tools such as JADX, Frida, and Burp Suite, and it highlights key findings.