frida-mod tutorial

This tutorial introduces how to use frida-mod.
Frida-mod is trying to make us access functions more easily using Frida. We can call functions, hook and unhook functions.
These functions can come from:

  • Existing modules, the process has loaded these modules after boot. I will call this is get mode.
  • Our modules, we can write these modules in C/C++, and it only supports 2 file formats. .so files and .dll files. I will call this is load mode.

Prepare C/C++ source codes.

get mode

For demonstraction, I wrote sprintf function prototype in file libc.h. For simplicity, we’d better make these source files not depend on other files.

load mode

In theory, we can use module source codes directly, but because we use llvm to parse source code for further process, this compiler may be different than the compiler you compile the actual module. So we may need to do some modifications on it.

Generate typescript wrapper code.

I wrote a python util to generate typescript wrapper code. It’s modinfo2ts.py. You can run modinfo2ts.py --help to get its help page.

get mode

1
./utils/modinfo2ts.py -m get -o modinfos/libc.ts source/libc.h

-m flag specifies mode, get mode is for the existing module;
-o flag specifies the output typescript file name;
the last argument is the source file, and it supports multiple source files;

load mode

1
./utils/modinfo2ts.py -m load -b c/bins/win64.dll -o modinfos/libwin64.ts c/mod_win.cc

-m flag specifies mode, load mode is for our own modules;
-b flag specifies compiled module binary file;
-o flag specifies the output typescript file name;
the last argument is the source file, and it supports multiple source files, I use module source code here;

The generated code exports a mod variable.

Test generated typescript module

I wrote index.ts to test generated TS module.
We need to import module as follows

1
2
3
4
5
6
7
import { mod as libcmodinfo } from './modinfos/libc'
import { mod as liblinux_x64info } from './modinfos/liblinux_x64'
import { mod as liblinux_x86info } from './modinfos/liblinux_x86'
import { mod as libarm64info } from './modinfos/libarm64'
import { mod as libarm32info } from './modinfos/libarm32'
import { mod as libwin64info } from './modinfos/libwin64'
import { mod as libwin32info } from './modinfos/libwin32'

We need to use the alias to avoid the dupulcation of mod names.

Get mode

Function testLibcSprintf calls sprintf function in libc module.

1
2
3
4
5
6
7
8
9
10
11
// we need to specify the actual module name in here. 
// use the following code to get the module containing `sprintf`
// let p = Module.getExportByName(null,'sprintf)
// let m = Process.getModuleByAddress(p);
// console.log(JSON.stringify(m))
let libc = libcmodinfo.get(modname);
let buff = Memory.alloc(Process.pageSize);
// use libc.functions.sprintf.call to call sprintf
libc.functions.sprintf.call(buff, Memory.allocUtf8String("%s %d"), Memory.allocUtf8String('1 + 2 ='), ptr(1+2));
let resStr = buff.readUtf8String();
console.log('result', resStr);

Load mode

Function testLibAdd call add function implemented in our module

load module

I try to explain how to load the module on arm64 platform here. The passed parameters are slightly different on every platform.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let lib = libarm64info.load(
[ // an array of depended libraries. load function will try to resolve symbols at these libraries.

],
{ // a map for symbols, the key is symbol name, the value is symbol address,
// we can pass our defined frida NativeCall to the loaded module,
// and we can set some never used symbols to null.

_frida_puts : _frida_puts,

_ITM_registerTMCloneTable : ptr(0),
_ITM_deregisterTMCloneTable : ptr(0),
__gmon_start__ : ptr(0),
});

Call functions in our module

1
2
3
4
5
6
let a = 2;
let b = 3;
lib.functions.add.hook(); // hook add function
let res = lib.functions.add.call(a,b); // call add function
lib.functions.add.unhook(); // unhook
console.log('res', res);

Conclusion

I introduced how to access functions in modules using frida-mod. Hope this util can help you write frida code easier.

Todo

  • Fix the bug on win64 platform
  • support variable access
  • support struct parsing

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.