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.

Author

Meng Xipeng

Posted on

2022-06-28

Updated on

2023-10-05

Licensed under

Comments