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

Author

Meng Xipeng

Posted on

2022-06-27

Updated on

2023-10-05

Licensed under

Comments