nmmp Security Toolkit

repository·master·Indexed 22 days ago

https://github.com/maoabc/nmmp

A security toolkit for Android that protects DEX files by executing them within a custom virtual machine (nmmvm). It includes nmm-protect for converting DEX bytecode into C structures and randomizing opcodes to hinder reverse engineering, and nmmvm, the core dex-vm implementation. The toolkit supports hardening APK, AAB, and AAR files, and includes a bundled version of libffi for portable foreign function interfaces.

Tokens
5.5K
Snippets
18
Records
27
Agent score
78%

What's inside nmmp

  1. Overview of nmmp

    master

    nmmp is a security project designed to protect Android DEX files by running Dalvik bytecode on a custom dex-vm. This increases the difficulty of reverse engineering.

    The project consists of two main components:

    1. nmm-protect: A Java-based project that converts DEX data into C structures and randomizes opcodes. It generates an NDK project which, when compiled, produces a hardened APK.
    2. nmmvm: An Android project containing the dex-vm implementation and various Dalvik instruction tests.
  2. What is libffi?

    master

    libffi (Foreign Function Interface) is a library that provides a portable, high-level programming interface to various machine-dependent calling conventions. It allows a program to call functions with arguments specified at runtime, acting as a bridge between an interpreter (or another language) and compiled code.

    Note: libffi provides the lowest machine-dependent layer. A layer above it is typically required to handle type conversions between different languages.

  3. Understand the nmmvm execution model

    master

    The nmmvm is the core dex virtual machine implementation. The entry point for execution is the vmInterpret function.

    To implement custom hardening or testing, you interact with two primary structures:

    1. vmCode: Provides the execution context, including instructions (insns), instruction size (insnsSize), registers (regs), register type flags (reg_flags), and exception handlers (triesHandlers).
    2. vmResolver: A collection of function pointers used to resolve symbols at runtime (fields, methods, types, classes, and constant strings).

    By providing custom implementations for these two structures, you can control how the VM interprets bytecode and resolves symbols.

    jvalue vmInterpret(
            JNIEnv *env,
            const vmCode *code,
            const vmResolver *dvmResolver
    );
    
    typedef struct {
        const u2 *insns;             // instructions
        const u4 insnsSize;          // instruction size
        regptr_t *regs;              // registers
        u1 *reg_flags;               // register data type flags (e.g., object marker)
        const u1 *triesHandlers;     // exception table
    } vmCode;
    
    typedef struct {
        const vmField *(*dvmResolveField)(JNIEnv *env, u4 idx, bool isStatic);
        const vmMethod *(*dvmResolveMethod)(JNIEnv *env, u4 idx, bool isStatic);
        const char *(*dvmResolveTypeUtf)(JNIEnv *env, u4 idx);
        jclass (*dvmResolveClass)(JNIEnv *env, u4 idx);
        jclass (*dvmFindClass)(JNIEnv *env, const char *type);
        jstring (*dvmConstantString)(JNIEnv *env, u4 idx);
    } vmResolver;
  4. Build nmm-protect from source

    master

    To build the nmm-protect fatjar manually:

    1. Clone the repository.
    2. Navigate to the nmm-protect directory.
    3. Run the Gradle build tasks.

    The resulting fatjar will be located in build/libs.

    git clone https://github.com/maoabc/nmmp.git
    cd nmmp/nmm-protect
    ./gradlew arsc:build
    ./gradlew build
  5. Configure environment for nmm-protect

    master

    To use nmm-protect (via vm-protect.jar), you must have JDK, Android SDK, and NDK installed. You need to configure the following environment variables:

    • ANDROID_SDK_HOME: Path to your Android SDK.
    • ANDROID_NDK_HOME: Path to your Android NDK.
    • CMAKE_PATH (Optional): Path to your CMake binary. If not configured, it defaults to /bin/cmake.
    export ANDROID_SDK_HOME=/opt/android-sdk
    export ANDROID_NDK_HOME=/opt/android-sdk/ndk/22.1.7171670
    export CMAKE_PATH=/opt/android-sdk/cmake/3.18.1/
  6. Harden an AAB using nmm-protect

    master

    To harden an Android App Bundle (AAB), use the aab command. The output must then be signed using jarsigner or signflinger.

    Command Syntax: java -jar vm-protect-xxx.jar aab <test.aab> <convertRules.txt>

    Signing: jarsigner -keystore ~/.myapp.jks -storepass pass -keypass pass test-protect.aab keyAlias

    # Harden AAB
    java -jar vm-protect-xxx.jar aab test.aab convertRules.txt
    
    # Sign the resulting AAB
    jarsigner -keystore ~/.myapp.jks -storepass pass -keypass pass test-protect.aab keyAlias
  7. Harden an APK using nmm-protect

    master

    Use the vm-protect.jar to convert an APK. The command requires the input APK, a conversion rules file, and a mapping file.

    Command Syntax: java -jar vm-protect-xxx.jar apk <input.apk> <convertRules.txt> <mapping.txt>

    Output: A build directory is created in the same location as the input APK. It contains:

    • The hardened APK: build/input-protect.apk
    • A complete C project (dex2c) based on CMake.
    • Generated .dex files used during processing.

    Post-processing:

    • The generated APK must be aligned (using zipalign or zipflinger) and signed with apksigner to be installable.

    Note: On the first run, a tools directory is created next to the JAR file containing a config.json which can be edited to configure SDK and NDK paths.

    # Harden APK
    java -jar vm-protect-xxx.jar apk input.apk convertRules.txt mapping.txt
    
    # Sign the resulting APK
    apksigner sign --ks ~/.myapp.jks build/input-protect-align.apk
  8. Build libffi on Windows with MSVC

    master

    You can build libffi on Windows using Microsoft's Visual C++ compiler by using the msvcc.sh wrapper script during configuration.

    Standard 32-bit build:

    path/to/configure CC=path/to/msvcc.sh CXX=path/to/msvcc.sh LD=link CPP="cl -nologo -EP" CPPFLAGS="-DFFI_BUILDING_DLL"

    64-bit build: Use the -m64 flag with the wrapper:

    path/to/configure CC="path/to/msvcc.sh -m64" CXX="path/to/msvcc.sh -m64" LD=link CPP="cl -nologo -EP" CPPFLAGS="-DFFI_BUILDING_DLL"

    Using clang-cl (LLVM):

    path/to/configure CC="path/to/msvcc.sh -clang-cl" CXX="path/to/msvcc.sh -clang-cl" LD=link CPP="clang-cl -EP"

    Note for MingW environments: If building with MSVC under MingW, you may need to manually remove the line in configure that sets fix_srcfile_path to cygpath, as cygpath is not present in MingW.

    # Example 64-bit MSVC build command
    ./configure CC="path/to/msvcc.sh -m64" CXX="path/to/msvcc.sh -m64" LD=link CPP="cl -nologo -EP" CPPFLAGS="-DFFI_BUILDING_DLL"
  9. Install libffi via Autotools

    master

    To build libffi on most systems, you must first configure the distribution using the configure program.

    Prerequisites:

    • A C99 compatible compiler.
    • If building from git-hosted sources, you must run ./autogen.sh first (requires autoconf, automake, and libtool).
    • GNU make is required for the build process.
    • DejaGNU is required to run tests via make check.

    Build Steps:

    1. Run ./configure with desired options.
    2. Run make to build.
    3. Run make check to verify the installation.
    4. Run make install to install library and header files.
    # Standard build flow
    ./configure
    make
    make check
    make install