A new method to write CModule in Frida for hacking Android applications

Why a new method

I introduced how to use official CModule in Frida at last post. But I think it have some weaknesses to improve.

  • It can use C, this make us need to use ugly mangled function name when we want to call C++ functions
  • We need to embed C code in to Typescript code. So we can not find the error line quickly when C code does not pass compilation.

New method

In here, I will introduce a new method to write a CModule in Frida. Please note, this method only work with Android platform, and I only tested on Arm32 archtecture. In theory, Arm64 archtecture should work either. This method has the following stage:

  • Write a .so using Android NDK
  • Convert .so to a typescript module to hold all informations we needs when we want to load the .so
  • Load .so in Frida and call one function in it.
    I tried to load .so using Module.load(), but this method always failed on Android platform. I think it because the restrictions of new versions Android, I used Android 10.

Write a .so using Android NDK

First, we write a .so in C++, the following is the code in file main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//////////////////////////////////////////////////
// declaration of functions in frida
extern "C" void frida_log_callback(const char*);
extern "C" void frida_hexdump_callback(void*, unsigned int);

//////////////////////////////////////////////////
// declaration of functions in libMyGame.so
namespace cocos2d{
struct Application {
static Application* getInstance();
std::string& getVersion();
};
};


extern "C" void fun(void)
{
frida_log_callback("Hello World from so");
frida_log_callback("cocos2d application version:");
const std::string& version = cocos2d::Application::getInstance()->getVersion();
frida_log_callback(version.c_str());
return ;
}

Line 3-4, declares functions in Frida. And we need to add a options in our Android.mk

LOCAL_ALLOW_UNDEFINED_SYMBOLS := true

Without this options, we can not pass the link because the compiler can not find these functions.
Line 8-13, declares functions in libMyGame.so. We can use C++ syntax here, and make our code more readable. And we need to declare entire C++ class in libMyGame.so, just declare the functions we want to call.
Line 16-23, defines a function for Typescript code to call. It just does the same thing as we did in previous post, print out cocos2d application version string.
And we need to set correct options in Application.mk.

APP_STL := gnustl_static
APP_ABI=armeabi-v7a

If these options is not compatible with libMyGame.so, our function will crush. Frankly, I tried all APP_STL variables, and finally find only gnustl_static is compatible with this game.

Generate a Typescript file hold all information when loading

I wrote a python script. This python scripts use lief to parse .so file to get the following informations.

  • name, machine type
  • segments
    Only care about segments with load type, discard other segments
  • export symbols
  • relocations
  • ctors
    This is a list of functions, and we need to call these functions after loading.
  • dtors
    These functions should be called when unload the .so file, and I have not write code for it now.

This script generates final Typescript file using jinja2. The name of the template file is so2tsmodule.jinja

Load .so file

I wrote a method, named loadSo, in file soutils.ts. Method prototype as follows

1
export function loadSo(info:SoInfoType, syms?:{[key:string]:NativePointer}, libs?:string[]):LoadSoInfoType
  • info is a object we defined in the Typescript file we generated in the last stage.
  • syms is a list of symbols we pass to the load modules
  • libs is a list of library names. This method try to find symbols in these libraries.

We need to accomplish the following stages when load .so file:

  • Allocate buffer, and set the permission to rwx
  • Write all segments need to load to the buffer
  • Create a variable to hold all export symbols in the .so file
  • Handle relocations
    This stage is a little complicated. We need to find symbol addresses, and write correct number in the correct address accord to different recolcaion type.
  • Call ctors

Run the function in the .so file

The following is the Typescript code to do this. And it’s in file sotest.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let soname ="libMyGame.so"
let m = Process.getModuleByName(soname)
let loadm = soutils.loadSo(
mysoinfo.info,
{
frida_log_callback : fridautils.frida_log_callback,
frida_hexdump_callback : fridautils.frida_hexdump_callback,
},
[
soname,
]
);
console.log(JSON.stringify(loadm))

fridautils.runFunWithExceptHandling(()=>{
let fun = new NativeFunction(loadm.syms.fun,'void',[])
fun();
})

Line 3-12, loads the .so file.
We need to import generated Typescript file as a module using the following code

1
import * as mysoinfo from './mysoinfo'

Line 6-7 lists all functions defined in Frida and will be called in C++ code
Line 10 lists all libraries contained the functions will be called in C++ code
Line 16-17 calls function fun in the C++ code.

Compile & run

I wrote a makefile for compilation.
We can use the following command to compile.

1
$ make build_sotest

And use the following command to run.

1
$ make run

If everything is OK, we can see the output as follows

Hello World from so
cocos2d application version:
1.8.12

Conclusion

All code is in my github.

How to use CModule in Frida

In this article, I will try to introduce how to use CModule in Frida.
My test Android APK is a small game, named “Knife hit”, you can download it from APKpure website. This is a Cocos2d game, it has a native library named ‘libMyGame.so’. This library file has many game logic codes. It exports many functions. We will try to call one of them to get Cocos2d application version and print out it.

Why typescript?

Later version of Frida support typescript. And Frida provides a demo for starting of typescript coding. It use a NPM package, ‘frida-compile‘, for compiling of typescript code. Using typescript needs a compilation, it might take longer time to test the code, but we can avoid many issues in compilation. So I prefer typescript.

What is a CMoudule

This is its official documentation. In my option, we can write some functions in C, and Frida provides a way to invoke these functions. The following is the example in the official documentation.

1
2
3
4
5
6
7
8
9
10
11
12
const cm = new CModule(`
#include <stdio.h>

void hello(void) {
printf("Hello World from CModule\\n");
}
`);

console.log(JSON.stringify(cm));

const hello = new NativeFunction(cm.hello, 'void', []);
hello();

Line 1-7 create a CModule, it includes C code, and exports one function, ‘hello’ to print a welcome string.
Line 9 displays the information of the CModule. Frida is very smart, the C code should be compiled in here. And Frida should load the CModule in the memory space of the target process.
Line 11 convert function pointer to a NativeFunction for calling in Frida. So, cm.hello just is a NativePoiner.
Line 12 call hello function

Write our own CModule

Why not to use printf?

Typescript code and C code is actually run in the target process’ memory space. And Android process usually do not use printf. They use __android_log_print instead. But this function do not print message to Frida, we nee to run adb logcat to check its output. It’s not very convenience. We can write some functions in typescript to let C code to call.

Typescript functions for debugging

I wrote several helper functions in typescript, I put them in file fridautils.ts.

frida_log_callback

1
2
3
4
export let frida_log_callback = new NativeCallback(function(sp:NativePointer){
let s = sp.readUtf8String();
console.log(s)
},'void', ['pointer'])

This function has one argument of type NativePointer. C code passes a pointer to a string to this function.
Line 2 read a string from given address.
Line 3 outputs the string to Frida.

frida_log_cstring_callback

1
2
3
4
export let frida_log_cstring_callback = new NativeCallback(function(pString:NativePointer){
let s = pString.add(0x0).readPointer().readUtf8String(); // get string to a pointer to a std::string object
console.log(s)
},'void', ['pointer'])

Cocos2D stores the version string in a std::string. So we needs a function to extract the actual string from the std::string object and print it.
This function has only one argument, it also is of type NativePoiner, but it should be a pointer to a std::string object
Line 2 extracts the actual string from the given std::string object. Note: Android developers can specify different variable in their Application.mk file. Variables APP_ABI, APP_STL may determine different implementations of std::string class. So current method to extract a string may not work correctly when you hack other Android APKs.
Line 3 outputs extracted string to Frida.

Our own CModule

I wrote a CModule to show the Cocos2D application version string by using the functions has exist in libMyGame.so. I put code it file cmoduletest.ts.

Cocos2d functions for get version string

I read the official documentation in Cocos2d website. Class cocos2d::Application has 2 function we can use:

  • static Application* getInstance ()
    doc
    This function return a pointer to the current application instance.
  • const std::string& getVersion () const
    doc
    This function return a std::string reference to the version string. A C++ reference is a pointer in machine code actually.

Call Cocos2d functions

The following is the code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
let test0 = function() {
let soname ="libMyGame.so"
let m = Process.getModuleByName(soname)

let loadm = new CModule(`
void* _ZN7cocos2d11Application11getInstanceEv ();
const char* _ZN7cocos2d11Application10getVersionEv(void*);
extern void frida_log_callback(const char* s);
extern void frida_log_cstring_callback(void*);
void fun(void) {
frida_log_callback("Hello World from CModule");
void* pApplication = _ZN7cocos2d11Application11getInstanceEv();
const char* version = _ZN7cocos2d11Application10getVersionEv(pApplication);
frida_log_callback("cocos2d application version: ");
frida_log_cstring_callback((void*)version);
}
`,
{
frida_log_callback : fridautils.frida_log_callback,
frida_log_cstring_callback : fridautils.frida_log_cstring_callback,
// cocos2d::Application::getInstance
// use this static function to get a pointer to the current Application instance
_ZN7cocos2d11Application11getInstanceEv : m.getExportByName("_ZN7cocos2d11Application11getInstanceEv"),
// const char* cocos2d::Application::getVersion(void*)
// use this member function of class Application to get a version string
_ZN7cocos2d11Application10getVersionEv: m.getExportByName("_ZN7cocos2d11Application10getVersionEv"),
});

console.log(JSON.stringify(loadm));
fridautils.runFunWithExceptHandling(()=>{
let fun = new NativeFunction(loadm.fun, 'void',[])
fun();
})
}

Line 6-16 is the C code.
Line 6-7 declares the Cocos2d functions we will using. Because we are in C, so we only can use mangled function names.
Line 8-9 declares the helper functions we created in Typescript.
Line 10-16, we create a function named fun to do our actual work
Line 12, we get the current application instance address using function cocos2d::Application::getInstance, this function is a static function, and it has no argument, we store the result to variable pApplication;
Line 13, we get a pointer to a std::string object using function cocos2d::Application::getVersion, this function is a member function, and it needs a this pointer as its first argument. We just pass variable pApplication to it.
Line 14-15. We print version string using helper functions.
LIne 19-26. We needs to pass all functions to the CModule using a dictionary. In the dictionary, key is the function name, and value is the function address. We call Module.getExportByName to get the functions address in the libMyGame.so

Compile & run

I wrote a makefile for compilation.
We can use the following cmd to compile.

1
$ make build_cmoduletest

And use the following cmd to run.

1
$ make run

If everything is OK, we can see the output as follows

Hello World from CModule
cocos2d application version:
1.8.12
This game’s Cocos2d application version is 1.8.12

Conclusion

Now, we know how to call the existing functions using CModule. Cocos2D exports a lot of functions in its native library file. We can do many things using these functions.
All code is in my github