Hacking Android game using Frida - Part2

Hacking Android Games with Frida (Part2)

In the previous blog post, we discussed how to create a Frida Typescript project to interact with an Android game. In this follow-up article, we will delve deeper into the game’s internals by using the NDK (Native Development Kit) and explore the process of hacking Android games using Frida.

NDK Version enumeration

To begin, we need to identify the NDK version used to compile the game’s native library (libmain.so). Follow these steps:
1. Navigate to the game data directory on the Android device. The path may vary, but an example path is:

1
cd /data/app/com.vectorunit.mercury.googleplay-hN_B8AKQmUeVXiBbDZ2Vvg==/lib/arm64

Note: Replace com.vectorunit.mercury.googleplay-hN_B8AKQmUeVXiBbDZ2Vvg== with the actual game ID on your device.
2. In the game data directory, we can find a file libmain.so. Copy it to PC.
3. Extract strings from libmain.so using the following command:

1
strings -tx libmain.so

Look for the version strings and commit IDs in the command output. For example:

1
2
3
ad4dac Android (7714059, based on r416183c1) clang version 12.0.8 (https://android.googlesource.com/toolchain/llvm-project c935d99d7cf2016289302412d708641d52d2f7ee)
ad4e4a Android (4691093 based on r316199) clang version 6.0.2 (https://android.googlesource.com/toolchain/clang 183abd29fc496f55536e7d904e0abae47888fc7f) (https://android.googlesource.com/toolchain/llvm 34361f192e41ed6e4e8f9aca80a4ea7e9856f327) (based on LLVM 6.0.2svn)
ad4f52 Linker: LLD 12.0.8 (/buildbot/src/android/llvm-r416183/out/llvm-project/lld c935d99d7cf2016289302412d708641d52d2f7ee)

Perform a Google search using the version strings and commit IDs to determine the NDK version. In this example, I assume the game was compiled with NDK r17c.
4. Download the according NDK from here, and uncompress it to your PC.

NOTE: NDK r17c is an older version and may not work with newer versions of Linux. Refer this page for potential issues of to run clang++ on NDK r17c. The provided solution in that post can help resolve the issue.

Create helper function in Typescript for C++ code

Next, we will create two helper functions in Typescript that can be called from C++ code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const _frida_log = new NativeCallback(function(sp:NativePointer){
console.log(sp.readUtf8String());
}, 'void', ['pointer']);

const _frida_hexdump = new NativeCallback(function(sp:NativePointer, sz:number){
console.log(
hexdump(sp, {
offset: 0,
length: sz,
header: true,
ansi: false,
})
);
}, 'void', ['pointer','uint']);
  • _frida_log: This function outputs a string and accepts a pointer to the string.
  • _frida_hexdump:This function performs a hexdump of a block of memory and accepts a pointer to the memory and its length.

Build the NDK .so file

To build the NDK shared library (.so file), follow these steps:

  1. Create a subdirectory named jni.
  2. Navigate to the jni directory.
  3. Create a C++ file named mousebot.cc with the following content:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    extern "C" {
    void _frida_log(const char* p);
    void _frida_hexdump(void* p, unsigned int i);
    }

    extern "C" int test(void* base){

    _frida_log("Hello from C++ !");
    _frida_hexdump(base, 0x20);

    return 0;
    }
  • The declarations for the helper functions are enclosed in extern “C” to avoid C++ name mangling.
  • The test function accepts a pointer to memory, calls the helper functions, and returns 0.
  1. Create Android.mk file with the following content:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)
    LOCAL_MODULE:= mousebot
    LOCAL_SRC_FILES := mousebot.cc
    LOCAL_C_INCLUDES :=
    LOCAL_LDLIBS :=
    LOCAL_CFLAGS=
    LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
    LOCAL_SHARED_LIBRARIES =
    include $(BUILD_SHARED_LIBRARY)
  • This file instructs NDK to build a shared library. I set LOCAL_ALLOW_UNDEFINED_SYMBOLS to true to avoid compiler to complain about undefined symbols for our defined helper functions. We implements the helper functions in Typescript. We have no library for compiler to link.
  1. Create Application.mk file with the following content:
    1
    2
    APP_PLATFORM=android-27
    APP_ABI=arm64-v8a
  • To determine the Android API level of your device, use the following command:
    1
    adb shell getprop  | grep api_level
  • To determine the CPU ABI, use the following command:
    1
    adb shell getporop ro.product.cpu.abi
  1. Create Makefile file with the following content:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ifndef NDKPATH
    $(error NDKPATH not set)
    endif

    all: build_android

    build_android:
    ${NDKPATH}/ndk-build V=1

    clean:
    ${NDKPATH}/ndk-build clean
    This Makefile instructs the NDK to build the shared library, while enabling verbose mode with the V=1 flag. Make sure to export the NDKPATH environment variable with the path to your actual NDK installation. By running make, the shared library will be built, and you can find it in the libs/arm64-v8a/libmousebot.so directory.

Convert .so file to typescript module

Next, we’ll convert the generated .so file into a TypeScript module. To accomplish this, we have provided a Python script called so2ts.py, which you can find here. The script performs the following steps:

  1. Parse the .so file using LIEF library
  2. Generate a .ts file that loads the .so file manually:
  • Allocates memory for the .so file, and loads it into the allocated memory, set ting the permissions to rwx.
  • Applies hot patches to the .so file using the information from the relocation sectin.
  • Invokes constoructors of the .so file.

To run the script, execute the following command:

1
./utils/so2ts.py -b libs/arm64-v8a/libmousebot.so -o modinfos/libmousebot.ts

The generated TypeScript file will be located at modinfos/libmousebot.ts.

Call the Test function in Typescript

Now that we have the converted TypeScript module, we can proceed to call the C++ function from TypeScript. Here’s how you can accomplish this:

import the generated .ts file

In your TypeScript code index.ts, import the generated .ts file as follows:

1
import {mod as libmousebotinfo} from './modinfos/libmousebot'

Load the .so file

Add the following code to index.ts to load the .so file:

1
2
3
4
5
6
7
8
const soname ='libmain.so'
const lib = libmousebotinfo.load([
soname,
],{
_frida_log ,
_frida_hexdump ,

})

In the code snippet above, soname represents the name of the original libmain.so file. Since the game process loads this .so file during boot, we pass soname to libmousebotinfo.load to enable our .so file to resolve symbols from this library. Additionally, we pass _frida_log and _frida_hexdump to libmousebotinfo.load to provide these two functions to our .so file.

Call test function

To call the test function in libmousebot.so, use the following TypeScript code:

1
2
const m = Process.getModuleByName(soname);
new NativeFunction(lib.symbols.test, 'int', ['pointer'])(m.base);

In the code snippet above, we obtain the base address of libmain.so using Process.getModuleByName. Then, we invoke the test function in libmousebot.so using lib.symbols.test, where lib.symbols includes all the symbols exported by libmousebot.so.

Recompile index.ts and inject the Frida script

With all the necessary code prepared, we can recompile index.ts and inject the Frida script into the game process. Use the following command:

1
frida -U -l _agent.js -n 'MouseBot'

When executed, the console will display the following output:

1
2
3
4
5
##################################################
Hello from C++ !
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
7a26ace000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 .ELF............
7a26ace010 03 00 b7 00 01 00 00 00 d4 04 72 00 00 00 00 00 ..........r.....

Conclusion

In this tutorial, we have learned how to build a shared library using the Android NDK and call C++ functions from TypeScript. By leveraging Frida, we successfully integrated the functionalities of libmousebot.so with the existing libmain.so of the game process. This opens up possibilities for further exploration and utilization of Frida’s capabilities.

In the next blog post, we will delve deeper into Frida’s extensive feature set and explore how to call functions in libmain.so from our custom libmousebot.so.

Thank you for following along! If you have any further questions or need assistance, feel free to reach out.

Author

Meng Xipeng

Posted on

2023-08-29

Updated on

2023-10-05

Licensed under

Comments