Mastering Frida: A Guide to Fixing Early Instrumentation in Gadget Mode

Introduction

One of the challenges faced in Frida’s Gadget mode is an issue where the process you’re injecting into resumes immediately after injecting the JavaScript. This behavior prevents early instrumentation, which can be crucial for certain analysis tasks. In this blog post, I will guide you through a workaround to fix this issue.

When it comes to dynamic analysis and instrumentation of applications, Frida stands out as an indispensable tool in the kit of developers, reverse engineers, and security researchers alike. Its Gadget mode, in particular, offers unparalleled flexibility, allowing users to inject custom scripts into their target applications seamlessly. However, there’s a snag in the system that’s been tripping up professionals—early instrumentation.

Imagine setting up a meticulous script, ready to unravel the inner workings from the very inception of a process only to find that Frida has already allowed the application to resume before your instrumentation could catch the critical initial functions. This challenge has been particularly pronounced with the latest release, version 16.1.11, posing a significant obstacle to those needing to hook functions at the outset of program execution.

This blog post is forged from the need for a solution—crafted for persistence, it’s a beacon of hope for those grappling with this issue. Herein, we’ll walk through the intricacies of Frida’s Gadget mode and unveil a strategic approach to modify the resume_on_attach flag, a crucial step in gaining the control you require for early instrumentation. By the end of this guide, you’ll have mastered the method to hold the execution reins, ensuring your scripts are primed to hook at the starting line.

Join me as we delve into this technical deep-dive, transforming a point of frustration into a triumph of technical prowess.

Choosing Gadget Mode over Server Mode

In gernal, Frida provides 2 modes for injection, Sever mode and Dadget mode. I am debug an ARM32 program on an embedded patlform. And I tested, the server mode is not very reliable , may because the limit RAM on the dev board, and the dev board may reboot after I start frida-server, It’s very annonying, So I select Gadget mode,and reboot issue goes away. Frida offers two distinct modes for script injection: Server Mode and Gadget Mode. When working with ARM32 programs on embedded platforms, one must consider system constraints. My experience revealed that Server Mode could be unstable, potentially due to the limited RAM available on the development board. This instability often resulted in the board rebooting after initiating frida-server, which was disruptive to the debugging process.

Given these challenges, I opted for Gadget Mode. This alternative proved to be more stable under the same conditions, eliminating the frustrating reboot issues. Gadget Mode’s lightweight footprint makes it better suited for environments with resource limitations, thereby providing a more seamless and reliable debugging experience for ARM32 embedded systems.

The Solution: Modifying resume_on_attach

The root of this issue lies within the default behavior of Frida’s Gadget mode, which is controlled by the resume_on_attach field of the ControlChannel class. This field dictates whether the process should resume immediately after Frida attaches to it.

Step by Step Fix

Here’s a step-by-step guide to addressing this issue:

1. Modifying the ControlChannel Class

Locate the ControlChannel class definition within the frida-core/lib/gadget/gadget.vala file. Search for the resume_on_attach field and change its default value to false.

1
2
3
4
class ControlChannel {
private bool resume_on_attach = false; // Change from true to false
// ...
}

2. Compile Frida with Modified Code

After making this change, recompile Frida to incorporate the modification. Ensure you have all required dependencies and follow the standard build instructions provided in the Frida documentation. This github repository provides docker images for compiling Frida.

3. Using the Modified libGadget.so

Replace the original libGadget.so with the newly compiled version. With this change, the process will no longer resume immediately, allowing your JavaScript to hook functions at the very start of the execution.

4. Utilizing Python Script for Process Resumption in Frida Gadget Mode

While working in Frida Gadget mode, a common approach to inject a JavaScript payload into the target process would involve using the frida command from the Frida-tools package, like so:

1
frida -H <Board IP address> -n Gadget -l <Javascript file>

Executing this command initiates the injection, but it doesn’t automatically resume the process once the JavaScript file is loaded. Attempts to resume the process manually with %resume in the Frida REPL might not yield success.

To overcome this hurdle, I developed a Python script—a more reliable solution. The script not only injects the JavaScript code but also effectively resumes the process afterwards. Below is the functional part of the script where the device.resume(pid); command is used to resume the process:

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
# Python script to inject JavaScript and resume process in Frida Gadget mode
import frida

# Establish a connection to the target device
device = frida.get_device_manager().add_remote_device("<Board IP address>")

# Specify the target process identifier (replace 'pid' with the actual process ID)
pid = 'pid'

# Path to the JavaScript file you want to inject
js_file_path = "<Javascript file>"

# Read the JavaScript code from the file
with open(js_file_path, 'r') as file:
js_code = file.read()

# Inject the JavaScript code into the process
session = device.attach(pid)
script = session.create_script(js_code)

# Load and execute the JavaScript code
script.load()

# Resume the process to continue its execution with the injected code
device.resume(pid)

print("JavaScript injected and process resumed.")

By employing this script, we ensure that the injection and process flow are controlled as expected, thus maintaining the stability of the debugging session.

Conclusion

This blog post addressed early instrumentation challenges in Frida’s Gadget mode and provided a solution by modifying the resume_on_attach flag. Gadget mode was recommended over Server mode for ARM32 programs on embedded platforms due to its stability. A Python script was introduced for injecting JavaScript and resuming the process, ensuring a controlled debugging session. Overall, this post offers a solution for fixing early instrumentation in Frida, empowering developers and researchers to gain insights into application behavior from the beginning.

Hacking Cocos2D Android Games using Frida

Disclaimer

Please note that the information provided in this blog post is intended for educational and informational purposes only. Any application of the techniques and tools mentioned in this post must be carried out responsibly and in accordance with local and international regulations and laws.

Attempting to hack software, including games, without express permission from the owners is strictly illegal and unethical. The author and the publishers of this blog do not condone, encourage or endorse any illegal activity, and any actions taken based on the contents of this blog will be solely at your own risk.

We strongly encourage readers to use this information to enhance their understanding of the underlying mechanisms of software and to promote improved security, rather than for ulterior purposes. This blog, its author, and its publishers bear no responsibility for misuse of the information provided.

Introduction

Hey there! Ever wondered how exciting it would be to peek under the hood of your favorite Android games, particularly the ones built using cocos2d? Well, this blog post will walk you through just that. But we’re not talking about cheats or shortcuts, we’re diving into game hacking — the good kind.

First off, why cocos2d? It’s a user-friendly, open-source software perfect for building games. On the other hand, Frida is a programmer’s secret weapon, akin to a multi-tool. It’s a powerful toolkit that allows you to weave your code seamlessly into an already-running process. This incredible ability to inject and inspect code in real-time, without causing a ruckus, makes Frida an essential instrument for any coder’s toolbelt.

Now, don’t worry if you aren’t a coding whiz. This guide is meant for anyone curious about how games work. A basic grip on the Android system, a touch of Typescript, a little bit of C++ and a whole lot of enthusiasm are all you need to level up. Ready for the ride? Let’s get started!

Create C++ module for hacking

To illustrate, I make use of a 2D game. This game adopts a unique format, defined by its developers, to house its animations. The asset files associated are denoted with a .ROM extension.

Each of these asset files can encompass multiple actions. In turn, each action is made up of several steps. Intriguingly, each step in this context represents an image. These images, which constitute the steps, might exhibit differing dimensions.

I plan on developing a C++ module that can be injected into the process for hacking. A detailed outline of this procedure can be found on my blog post.

Static analysis

Static analysis, also known as code review, is the process of examining source code without executing it, focusing instead on its structure, dependencies, and patterns. It is a widely accepted preliminary step in understanding the functionality of any software or application, including games built on frameworks like cocos2d.

Begin by familiarizing yourself with the cocos2d framework. Cocos2d is an open-source software development kit for developing games and other graphical applications. I can get cocos2d’s code from here: github.com/cocos2d/cocos2d-x.

Thoroughly reviewing the source code of the framework greatly benefits our endeavor to understand software in-depth, an essential step for ethical hacking. However, we should acknowledge that the openness of open-source platforms does present a double-edged sword. Despite its numerous advantages, it also opens a gateway for potential misuse. The exposure of the source code presents an intimate understanding of a software’s underlying mechanisms, which, while essential for improving and learning, can be exploited if it falls into the wrong hands.Reviewing the framework source code is very helpful for our hacking work. So I should say open source has its dark side. source code provides valuable insight into the underlying mechanisms of software.

Extracting information from binary

Typically, Android games developed using the Cocos2D framework incorporate a shared library known as libcocos2dcpp.so. This integral component houses the core logic underlying the game’s operations and interactions, serving as a centerpiece for the game’s design and execution.
I plan to conduct a detailed examination of this file in order to glean more comprehensive insights about the underlying mechanics of this Android game. This in-depth analysis will allow me to understand its structure and functionality more precisely.

file

1
2
$ file libcocos2dcpp.so
libcocos2dcpp.so: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /system/bin/linker, stripped

The result derived from executing the file command implies that the file is an ELF file, and is a ARM32 archtecture.

Ghidra

I utilized Ghidra for analyzing the shared library under examination. This comprehensive software reverse-engineering tool revealed that the library exports numerous symbols that can be invoked. A symbol that particularly caught our attention is _ZN7cocos2d14cocos2dVersionEv. This symbol is associated with a C++ function cocos2d::cocos2dVersion().

However, it’s important to note that the symbol name only provides information about the function’s namespace, name, and argument types. We can’t ascertain the return type solely from the symbol’s name. Fortunately though, we have access to the source code. By cross-referencing the function within the source code, I was able to determine that its return type is char*.

The following code illustrates the cocos2d version is cocos2d-x 3.3rc0

1
2
3
4
namespace cocos2d {
char* cocos2dVersion();
}
LOG_INFO(" Cocos2d version: %s", cocos2d::cocos2dVersion());

Once we precisely identify the version of the Cocos2d framework being used, it opens up the possibility to retrieve the exactly corresponding version of the source code.

Define C++ classes

In the process of extracting images from asset files, I lean on the capabilities of a particular C++ class called ActionGroupClass during the Ghidra analysis phase. This class not only streamlines the extraction process but also boasts of several utility methods.
The following is the class define I infered from Ghidra

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

namespace cocos2d{
struct Texture2D;
}

struct ActionGroupClass{
unsigned char _0x0[0x102];
unsigned short _totalActionCount;
ActionGroupClass();
void vQuickLoad(unsigned char*, bool, int, int);
void vGetBaseXY(short, float*, float*);
int GetTotalStep(short);
void vGetVectorXY(short, short, short*, short*);
bool GetPictureIndex(short, short, unsigned char*);
~ActionGroupClass();
cocos2d::Texture2D* GetTexture2DwithAction(short, short);
};
static_assert(offsetof(ActionGroupClass, _totalActionCount) ==0x102, "_totalActionCount not at offset 0x102");

This class, ActionGroupClass, is designed and implemented by game developers, which means it isn’t found directly within the Cocos2D framework.
Game developers have also defined several global variables, each of which is of the ActionGroupClass type. These variables are utilized throughout the game for various purposes, depending on the specific characteristics and methods defined within the ActionGroupClass.
We can utilize these global variables to extract images from asset files. In addition, the shared library should contain code within the .init section. This code is responsible for calling the constructor method of the ActionGroupClass on these global variables. This initialization is essential in setting up the initial state for these global instances.
In addition, modern C++ provides a way to call a constructor on an already allocated memory. This is called placement new. Here’s how it works:
In C++, the new keyword typically allocates memory and then constructs an object in the allocated memory. However, there are times when you want to separate these two operations. For example, when you have already allocated memory and just want to construct an object at that memory location you can use “placement new”.
Here is the general syntax:

1
2
3
4
5
6

#include <new>
...

auto* p = (unsigned char*)&FishActionGroup;
auto* fishActionGroup = new((void*)p) ActionGroupClass();

We can use this way to call constructor method in these global variables.

Dynamic analysis

Dynamic analysis involves the live examination of a system while it’s operational. In terms of hacking a cocos2d Android game using the Frida toolkit, dynamic analysis enables us to inspect and alter the game while it’s being actively played. This can provide insights that are not available during static analysis—when the program or game is not running.

Read string for a pointer to std::string.

When dealing with the Android NDK, there are numerous elements involved when implementing the Standard Template Library (STL). Since I’m utilizing a recent version of the NDK, the conventional method of extracting a string from a std::string variable using std::string::c_str() is unfortunately not applicable. Hence, a different approach is needed. After a detailed analysis of the memory layout connected to a std::string variable within the process, I have developed the following function to accurately extract the required string.

1
2
3
4
5
const char* getStringCStr(std::string& str){                                                                                                 
auto* p = (unsigned char*) &str;
auto* s= *(char**)&p[0x00];
return s;
}

Get RTTI

The shared library has been configured with Run-Time Type Information (RTTI) enabled. This setup allows us to extract RTTI using a pointer referring to a class directly. To streamline this process, I have authored the subsequent function specifically tailored for this task.

1
2
3
4
5
6
#include <typeinfo>
const char* getInstanceTypeName(void* ptr){
void** p = *(void***)ptr;
std::type_info& t =*(std::type_info*)p[-1];
return t.name();
}

Extract images from a asset file

Moving forward, let’s deftly invoke the inherent functions of our key class, ActionGroupClass, to systematically acquire all pertinent information concerning the graphic elements.

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

char* romdata = read_file_into_memory("/data/local/tmp/weapon1.rom") ;
if(romdata){
LOG_INFOS("%p", romdata);
fishActionGroup->vQuickLoad((unsigned char*)romdata, true, 30, 30);
free(romdata);
romdata = NULL;
auto totalActionCount = fishActionGroup->_totalActionCount;
LOG_INFOS(" totalActionCount %d", totalActionCount);
for(auto action=0; action<totalActionCount; action++){
auto totalStep = fishActionGroup->GetTotalStep(action);
float x, y;
fishActionGroup->vGetBaseXY(action, &x, &y);
LOG_INFOS(" action %d ,total step %d %f %f ", action, totalStep, x, y );
short stepx, stepy;
for(auto step = 0; step < totalStep; step++){
fishActionGroup->vGetVectorXY(action, step, &stepx, &stepy);
unsigned char pic;
auto success = fishActionGroup->GetPictureIndex(action, step, &pic);
LOG_INFOS(" action %d/%d step %d/%d base %f %f step %d %d pic %d ", action, totalActionCount, step, totalStep, x, y, stepx, stepy, pic);
auto* tex = fishActionGroup->GetTexture2DwithAction(action, step);
auto w = tex->getPixelsWide();
auto h = tex->getPixelsHigh();
auto* format = tex->getStringForFormat();
LOG_INFOS(" action %d/%d step %d/%d texture %p %d %d %s", action, totalActionCount, step, totalStep, tex, w, h, format);
}
}
}

Here’s an illustrative breakdown of its operations:

The first segment is devoted to reading a ROM file, specifically ‘weapon1.rom’, and storing it into memory. The in-memory address of this acquired data is logged for debugging purposes.

Next, the vQuickLoad method of fishActionGroup, an instance of ActionGroupClass, is invoked. This quickly loads in the binary content from the ROM data into our action group. Meanwhile, the parameters ‘30’ represent the default size in pixels for each sprite in the action.

The memory allocated to ‘romdata’ is subsequently cleared using ‘free’ to prevent memory leaks, and its pointer is nullified for safety.

Thereafter, we acquire the total count of actions stored within our action group by calling the _totalActionCount function. For every action, two tasks are performed: Firstly, the total number of steps related to that action are gathered. Secondly, the base coordinates (x, y) for that action are obtained via vGetBaseXY.

Each step per action is scrutinized next. This includes determining its directional vector (stepx, stepy) and the picture index related to each step. Moreover, the texture associated with each step of the action is also retrieved.

Finally, this section delves into extracting comprehensive details about said texture: the dimensions (width and height in pixels) and the format. This information, alongside other relevant details, is logged for every step in every action.

This meticulous process guarantees a thorough understanding of the movements, graphical elements, sizes, and properties involved, which immensely aids in manipulating or changing game assets for your desired outcome as part of your game hacking initiative.
After obtaining the pointers to Texture2D, it becomes possible to invoke the built-in functions within the class to save the graphic data to disk. However, elaborating on the specifics of this process falls outside the boundaries of this blog post. Therefore, we won’t delve into that here.

Conclusion

In this blog post, we explored the world of hacking Cocos2D Android games using Frida. We emphasized responsible and ethical use of the techniques and tools discussed. We covered static analysis, examining source code, and dynamic analysis for inspecting games while running. We discussed the extraction of images from asset files using the ActionGroupClass. Remember, unauthorized hacking is illegal and unethical. Use this knowledge responsibly to enhance your understanding of software and promote improved security. Happy exploring!

Hacking Android Game using Frida - Part3

Hacking Android Game with Frida (Part3)

In the previous blog post, we discussed how to create a NDK project to interact with an Android game. In this follow-up article, we will delve deeper into the game’s internals by call fucntions in libmain.so.

Analyzing libmain.so

We have chosen a game for analysis because its libmain.so contains a multitude of interesting functions. To examine the symbols exported by the library, execute the following command:

1
readelf -sW libmain.so

In our case, the library exports over 36,000 symbols. In theory, we can call each exported symbol using C++. This presents an exciting opportunity for exploration.

Conducting Static and Dynamic Analysis of libmain.so

Before calling these functions from C++, it is essential to establish a goal. In our current scenario, our goal is relatively simple: we want to list all assets in the game, including their names and types. To accomplish this, we need to extract relevant class definitions from libmain.so. For static analysis, we will employ Ghidra, while Frida will assist us in adding hooks to key functions for inspecting the game’s internal state and memory data. Please note that this process is complex and time-consuming, but the results are well worth the effort. While we won’t delve into the entire analysis process in this blog, we will share the final results. The following are the classes we extracted for listing all assets in the game:

Note: These class definitions are highly dependent on the version of libmain.so used. The MD5 hash of the libmain.so file we worked with is e721395e3e327899a5a55ea4fb422a1c. Additionally, to ensure compatibility with a C compiler and facilitate analysis in Ghidra, I have utilized __cplusplus macros in the code. Ghidra supports C code, and by using these macros, we can import the code into Ghidra for easier analysis. This allows us to leverage Ghidra’s capabilities while working with the codebase.

VuAsset

1
2
3
4
5
6
7
8
9
10
11
// VuAsset
struct VuAsset
{
void * _vtab; // 0x00
#if defined(__cplusplus)
std::string _name; // 0x08
#else
unsigned char _name[0x18];
#endif

};

The VuAsset class has a member variable, _name, of type std::string at offset 0x08, used to store the names of assets. In the ARM64 platform, a pointer occupies 8 bytes.

VuAssetDB

1
2
3
4
5
6
7
8
9
10
11
12
13
// VuAssetDB  size 0xe0
struct VuAssetDB{

unsigned char _0x0[0x40 ]; // 0x00

#if defined(__cplusplus)
std::map<std::string, std::vector<std::string>> _assetNames; // 0x40
#else
unsigned char _assets[0x18];
#endif

// ...
};

The VuAssetDB class contains a member variable of type std::map<std::string, std::vector<std::string>> at offset 0x40, used to store the names of assets. The key of this variable is of type std::string, storing asset type names, while the value is of type std::vector<std::string>, storing asset names of the same type. We will utilize this class to print all asset names and types in the game.

VuAssetFactory

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
// VuAssetFactory size 0x190
struct VuAssetFactory {
unsigned char _0x0[0x38 ]; // 0x00

#if defined(__cplusplus)
std::vector<std::string> _assetTypeNames; // 0x38
std::map<std::string, void*> _assetTypeInfos; // 0x50
#else
unsigned char _asssetTypeNames[0x18];
unsigned char _asssetTypeInfos[0x18];
#endif

VuAssetDB* _vuAssetDB; // 0x68

unsigned char _0x70[0x8];

#if defined(__cplusplus)
std::unordered_map<unsigned int, VuAsset*> _loadedAssets; // 0x78
#else
unsigned char _asssetTypeNames[0x28];
#endif


#if defined(__cplusplus)
static VuAssetFactory* mpInterface;

#endif
// ...
};

The VuAssetFactory class includes a member variable of type std::vector<std::string> at offset 0x38, used to store asset type names. We can utilize this member variable to print all asset type names. Additionally, this class has a member variable of type VuAssetDB* at offset 0x68 to store the pointer to VuAssetDB. Furthermore, it contains a member variable of type std::unordered_map<unsigned int, VuAsset*> at offset 0x78, used to store loaded assets. The key of this variable is of type unsigned int, representing the asset hash calculated by asset name, and the valueis of type VuAsset*, storing the asset pointer. Lastly, the class includes a static member variable of type VuAssetFactory* to store the global instance of VuAssetFactory. This class follows the singleton design pattern, allowing us to obtain the global VuAssetFactory instance using mpInterface.

Helper Function for Obtaining the Actual Class Name of VuAsset Children

Based on our analysis, we have observed that VuAsset acts as a base class for other asset-related classes. To simplify our tasks, we have implemented a helper function that allows us to retrieve the actual class names. Please find the code snippet below:

1
2
3
4
5
6
static const std::type_info& getTypeInfoOfInstance_ndk(void* p)               
{
p = *(void**)p;
p = ((void**)p)[-1];
return *(std::type_info*)p;
}

Given that libgame.so is compiled with the RTTI (Run-Time Type Information) option, we can utilize the getTypeInfoOfInstance_ndk function to obtain the class information of instances. It’s important to note that we cannot directly use the typeid operator to retrieve runtime type information (RTTI) of an object. This is because libgame.so contains the RTTI information, and when we write our C++ code in libmousebot.so, the typeid operator will provide the RTTI information from libmousebot.so, which may lead to incorrect results. Since we don’t have visibility into how the derived classes of VuAsset are defined, the getTypeInfoOfInstance_ndk function helps us retrieve the accurate class information from libgame.so.

Now, it’s time to rock! In this section, we will demonstrate how to print all asset names and types using functions provided by libgame.so. This will serve as a great starting point for exploring Frida’s capabilities and the powerful combination of Frida and C++.

  1. Get global pointer to the instance of VuAssetFactory
    To begin, we need to obtain the global pointer to the instance of VuAssetFactory. This can be achieved with the following code snippet:

    1
    auto* pVuAssetFactory = VuAssetFactory::mpInterface; 
  2. Get the instance of VuAssetDB
    Next, we retrieve the instance of VuAssetDB using the pointer obtained in the previous step. The code is as follows:

    1
    auto* pVuAssetDB = pVuAssetFactory->_vuAssetDB;
  3. List all assets in VuAssetDB
    In this step, we iterate through all the assets in VuAssetDB and print their names and types. The code snippet below demonstrates this process:

    1
    2
    3
    4
    5
    6
    7
     for( auto it = pVuAssetDB->_assetNames.begin(); it != pVuAssetDB->_assetNames.end(); ++it){                                                              
    auto& assetType = it->first;
    auto& names = it->second;
    for(auto it1=names.begin(); it1!=names.end(); ++it1){
    LOG_INFOS(" %s : %s", assetType.c_str(), it1->c_str());
    }
    }

    Here, the LOG_INFOS macro is used to print the asset information, which internally calls the _frida_log function.
    The resulting output will be similar to the following:

    1
    2
    3
    4
    5
    <...>
    [/mnt/work/work.2023/frida-hackinggame/jni/mousebot.cc:104] VuTextureAsset : UI/Icons/Inventory_Prize
    [/mnt/work/work.2023/frida-hackinggame/jni/mousebot.cc:104] VuTextureAsset : UI/Icons/Inventory_Skin
    [/mnt/work/work.2023/frida-hackinggame/jni/mousebot.cc:104] VuTextureAsset : UI/Icons/KeyCard_A
    <...>
  4. List All Loaded Assets in the Game
    In this final step, we will list all the loaded assets in the game. Although VuAssetDB contains all the assets, it only provides basic information for each asset. During the game runtime, the actual asset data is loaded on demand.
    The code snippet below demonstrates how to iterate through the loaded assets and print their information:

    1
    2
    3
    4
    5
    6
    7
    8
    auto& v = pVuAssetFactory->_loadedAssets;
    for(auto it = v.begin(); it != v.end(); ++it){
    auto* pAsset = it->second;
    auto& k = it->first;
    auto& assetTypeInfo = getTypeInfoOfInstance_ndk(pAsset);
    const char* assetTypeName = assetTypeInfo.name();
    LOG_INFOS(" %x pAsset %p assetTypeName %s assetName %s", k, pAsset, assetTypeName, pAsset->_name.c_str());
    }

    In this code, we use the getTypeInfoOfInstance_ndk function to retrieve the actual class name of each asset instance. The resulting output will be similar to the following:

    1
    2
    3
    4
    5
    <...>
    [/mnt/work/work.2023/frida-hackinggame/jni/mousebot.cc:118] e1fc0284 pAsset 0x77e86e1e40 assetTypeName 18VuStaticModelAsset assetName Env/Hall/Straight_16m
    [/mnt/work/work.2023/frida-hackinggame/jni/mousebot.cc:118] 93b383af pAsset 0x77c03b1f00 assetTypeName 15VuMaterialAsset assetName Paper/Cardboard_Wall
    [/mnt/work/work.2023/frida-hackinggame/jni/mousebot.cc:118] 9f8e3cbb pAsset 0x77b8be9810 assetTypeName 15VuTemplateAsset assetName Tile_Hall_Hazard/Hall_Hazard_Stomper_2x_16m
    <...>

You can find the complete source code in the repository

Conclusion

Congratulations! This marks the end of our exploration series. In this final log, we have successfully printed all asset information in the game using the functions provided by libgame.so. This serves as a solid foundation for further exploration of Frida’s capabilities. The combination of Frida and C++ opens up a world of possibilities, allowing us toperform various interesting tasks. With the knowledge gained from this series, you can continue your journey of hacking and exploring the game.

Remember, Frida and C++ together are a powerful toolset that can enable us to accomplish a wide range of tasks. The ability to access and manipulate game assets opens up exciting possibilities for customization, analysis, and experimentation.

Happy hacking, and enjoy your adventures in the world of game exploration!

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.

Hacking Android game using Frida - Part1

Hacking Android Games with Frida (Part1)

Disclaimer:

The techniques and information presented in this serial of blogs are strictly for educational purposes. Unauthorized use of these techniques for illicit activities, including game hacking, is strictly prohibited. The author and publisher do not condone or support any form of illegal or unethical behavior. It is essential to respect the intellectual property rights of game developers and adhere to the terms of service. Any actions taken based on the knowledge gained from this blog are solely at the reader’s own risk, and the author and publisher are not liable for any misuse or legal consequences that may arise.

Introduction:

Mobile gaming has become a massive industry, and many players strive to gain an edge by tweaking or modifying Android games. One powerful tool for game hacking is Frida, an open-source dynamic instrumentation framework. In this serial of blogs, we will guide you through the process of creating a Frida project using TypeScript, enabling you to hack Android games and unlock new possibilities.
I will put all source codes in here.

The Game for Testing:

For the purpose of testing and learning, we will use “MouseBot”, a popular and addictive runner game developed by Vector Unit. You can download the Android version of the game for free from the Play Store here. It’s free.

Prerequisites:

To follow along with this tutorial, you’ll need the following:

  • A basic understanding of TypeScript.
  • Node.js installed on your machine.
  • A rooed Android device or emulator.
  • Frida installed on your machine.
  • Test game , MouseBot, installed in your machine.

Step 1: Setting Up the Project

  • Create a new directory for your Frida project.
  • Open a terminal or command prompt and navigate to the project directory.
  • Create a file named package.json in the project directory. You can view its content from here.
  • Create a file named tsconfig.json in the project directory. You can view its content from here.
  • Install the Frida library for TypeScript and frida-compile by executing the following command, I have write these libraries in this package.json:
    1
    npm i 

Step 2: Writing the Frida Script

  • Inside your project directory, create a new TypeScript file, index.ts:
  • Open the file in your preferred code editor.
    Write the Frida script to print Hello world to the console.
    1
    2
    3
    4
    5
    6
    const test = ()=>{
    console.log('hello world')
    }

    console.log('##################################################')
    test()

Step 3: Running the Frida Script

  • Save the game-hack.ts file.
  • Open a terminal or command prompt and navigate to the project directory.
  • Create a file named Makefile in the project directory. Its content is shown as follows:
    1
    2
    all:
    ./node_modules/frida-compile/bin/compile.js -o _agent.js index.ts
  • Compile the TypeScript file into JavaScript using the following command:
    1
    make
    This command will create a file named _agent.js in the project directory.
  • Run the Frida script using the following command:
  • Setup your Android device, start frida-server. This page has detailed instructions for this step.
  • Launch MouseBot on your Android device or emulator.
  • Execute the following command to inject the Frida script into the game process:
    1
    frida -U -l _agent.js -n 'MouseBot'
    If everything goes well, Frida will inject the script into the game process. and you will see Hello world in the console.

Conclusion:

In this blog, we explored the initial steps of creating a Frida project using TypeScript and injecting a basic Frida script into an Android game. We emphasized the importance of using this knowledge responsibly and for educational purposes only. In the next blog of this series, we will delve deeper into Frida’s capabilities and explore more advanced game hacking techniques. Stay tuned and happy hacking!

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

An inlinehook library using Frida

This article introduces an inline hook library using Frida, and you can download all codes about this at this link. And I only implement it on ARM64 architecture.

Concepts

In my opinion, inlinehook just like put a breakpoint in debugger. Users can hook at any instructions, and executes any codes once the hook is triggered.

How to implement it

Basic steps

  • Put a jump code at the hook point, to jump to our trampoline code.
  • Trampoline code does the following:
    • Save current registers.
    • Invoke hook handler function.
    • Restore all registers.
    • Execute instructions at hook point.
    • Jump back to next instruction after the hook point.

Implementation using Frda

How to add hook

Before add inline hook, user should prepare the following things:

  • Hook point
    1
    const hook_ptr = <Address of the instruction will be hooked> ;
    We can add hooks at any instruction.
  • Hook handle function
    1
    2
    3
    const frida_fun = new NativeCallback(function(para1:NativePointer, sp:NativePointer){
    console.log('para1', para1, 'sp', sp);
    },'void',['pointer','pointer'])
    This function has two arguments, all are of type NativePointer. The first argument is named para1, and we can assign it when we add hook, the second argument is named sp, of value in the sp register, we can access values in the stack by this argument.
  • Memory for trampoline code
    1
    const trampoline_ptr = Memory.alloc(trampoline_len)
    We can allocate a new memory for trampoline code. And because allocated memory is usually far from the hook point, so we have to use a long jump instruction at the hook point. Long jump instruction usually occupies more bytes. For using near jump instruction, we can try to find a cave near the hook point, and use the found cave to store trampoline code.

    Warning: when we allocate memory, do not use a local variable for the returned pointer, use a global variable instead. Javascript’s garbage collection mechanism will free this memory automatically once the program is out of the local variable’s scope, and will crash the process.

  • Parameter1 pass to hook handle function
    1
    const para1 = <parameter1 pass to hook handle function>;
    We can pass a parameter to the hook handle function, and this parameter is of type NativePointer

Now invoke InlineHooker.inlineHookPatch to add a inline hook, just as follows

1
let sz = InlineHooker.inlineHookPatch(trampoline_ptr,hook_ptr, hook_fun_ptr, para1);

This function return a number, indicating the length of the trampoline code.

Code relocation

For inline hook, we need to move some instructions from one address to another. And we need to rewrite some instructions, just like B/BL instruction in ARM64 architecture.
I am using the code in this link for rewriting instructions. I port the C code in this file to Typescript code. This file is my ported code. ARM64 instruction set is relatively easy, and Thumb/Arm32 is too complex, we may port its code in the future.
This file exports this method to rewrite code

1
export let  sh_a64_rewrite=(buf:NativePointer, inst:number, pc:NativePointer ):number=>

This method has 3 arguments

  • buf, the target address of current instruction
  • inst, current instruction
  • pc, the source address of current instruction
    This method also return a variable of type number, to indicate how many instructions has written to target address. We actually need to rewrite several instructions for one B instruction to avoid change its behavior.

Conclusion

I wrote an inline hook library using Frida. Inline Hooking is not very easy, specially in some architecture, I only implemented on ARM64, and will do more work on other architecture.

Inline hook with Frida (Thumb version)

Interceptor.attach in Frida only can hook one function. So the first argument, target, should be a function address. In my opinion, inline hook just let users can hook arbitrary instruction at any address. In this article, I will try to introduce a method to implement inline hook using Frida. and only implement for Thumb binary so far.

Method

To implement inline hook, we need to do the following thing:

  • Save original bytes at hook target
  • Write a hook handle function in .so file in C/C++.
  • Write a trampoline code in Thumb. And put original bytes into trampoline code.
  • Put a BL instruction at hook target.

I will describe every stage in detail, and for convenience, I will user the following terminology:

Term Description
hook_ptr The address of hook target, same as the target argument in Interceptor.attach
hook_fun_ptr The address of hook handle function, we define this function in .so file
tempoline_ptr The address of trampoline code

So if we set every code correctly, once CPU reaches hook_ptr, CPU will call trampoline code as a function. And the trampoline code will call hook handle function and execute the original instruction copy from hook_ptr. And we can do anything we want in hook handle function.

Save original bytes at hook_ptr

We will put a BL instruction at this address, so we need to save original bytes at first. In Thumb, a BL instruction occupies 4 bytes. So we need to save at least 4 bytes. It may include 1-2 Thumb instructions at hook_ptr, and we must be careful in selection of a hook_ptr.

  • Avoid odd hook_ptr
  • Do not select a hook_ptr at a middle of a Thumb instruction
  • Do not select a hook_ptr at a 2 bytes Thumb instruction followed by a 4 bytes Thumb instruction

I dissembled libMyGame.so with the following command:

1
arm-linux-gnueabihf-objdump -S /tmp/libMyGame.so | tee /tmp/libMyGame.s

And, there are 2 files named libMyGame.so in this game. We should use libMyGame.so file in the path lib/armeabi-v7a. Because this file is according to Thumb version. I selected hook_ptr at 0x267782:

26777e: 9a03 ldr r2, [sp, #12]
267780: 6823 ldr r3, [r4, #0]
267782: 4630 mov r0, r6 @ here, our hook_ptr
267784: 429a cmp r2, r3
267786: d001 beq.n 26778c <_ZN7cocos2d11Application10getVersionEv@@Base+0x3c>

This hook_ptr is in the function cocos2d::Application::getVersion. So when call this getVersion function, just like we did in previous post, CPU should hit this hook_ptr. Now, we saved original bytes 30 46 9a 42.
Note: arm-linux-gnueabihf-objdump displays instruction bytes from high address to low address. We also can use hexdump to show instruction byte, it’s in a clearer way. The command is:

1
hexdump -C -n 4 -s $((0x267782)) /tmp/libMyGame.so

The output should be as follows:

00267782 30 46 9a 42 |0F.B|
00267786

Write a hook handle function.

The prototype of the hook handle function is as follows, and it’s in the file, main.cpp

1
extern "C" void hook_fun(void* baseaddress, void* sp);

This function has 2 arguments. The 1st argument, baseaddress, is the base address of the libMyGame.so, and the 2nd argument, sp, is the value int register SP. Trampoline code will pass these 2 arguments to the hook handle function. baseaddress let us can access any data or function in libMyGame.so very easily. Trampoline code will store most CPU registers in the stack, so we can access these register values using sp.

Write trampoline code

This code is in Thumb assembly. I list the code in here, and I put the offset of instructions at the beginning of every line.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
0x0:	push	{r0, r1, r2, r3, r4, r5, r6, r7}
0x2: push.w {r8, sb, sl, fp, ip, lr}
0x6: mrs r0, apsr
0xa: push {r0}
0xc: nop
0xe: mov r1, sp
0x10: ldr r0, [pc, #0x14] @ --> 0x28
0x12: ldr r4, [pc, #0x18] @ --> 0x2c
0x14: blx r4
0x16: pop {r0}
0x18: msr cpsr_fc, r0
0x1c: pop.w {r8, sb, sl, fp, ip, lr}
0x20: pop {r0, r1, r2, r3, r4, r5, r6, r7}
0x22: nop
0x24: nop
0x26: bx lr
0x28: nop
0x2a: nop
0x2c: nop
0x2e: nop

0x0-0xa, save registers to stack, r0-r12, LR , and CPSR. We can learn more on ARM registers with this page.
0xe, set the 2nd argument,
0x10, set the 1nd argument, we load the 1st argument from 0x28, and we will put the base address of libMyGame.so at 0x28 using Frida.
0x12, load the address of the hook handle function to R4, and we will put hook_fun_ptr at 0x2c using Frida.
0x14, call hook handle function
0x16-0x20, load registers from stack.
0x22-0x24, run original instructions at here. We will put saved original bytes at here using Frida.
0x26, return
0x28-0x2a, We store the base address of libMyGame.so at here.
0x2c-0x2e, We store the address of hook handle function at here.
Whole trampoline code occupies 0x30 bytes.

About selection of trampoline_ptr

We can simple using Memory.alloc for trampoline code. Actually, we use near call instruction. This makes trampoline_ptr can not be too far from hook_ptr. We can find a cave to put our trampoline code in libMyGame.so. CAVE MINER is a great tool for this task. But I just do it manually for this sample case. I use the following command to list all segments in libMyGame.so:

1
readelf -l  /tmp/libMyGame.so

The following is the output snippet:

Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x00000034 0x00000034 0x00120 0x00120 R 0x4
INTERP 0x000154 0x00000154 0x00000154 0x00013 0x00013 R 0x1
[Requesting program interpreter: /system/bin/linker]
LOAD 0x000000 0x00000000 0x00000000 0x691630 0x691630 R E 0x1000
LOAD 0x691f38 0x00692f38 0x00692f38 0x36b0c 0x42fac RW 0x1000
DYNAMIC 0x6bc6c8 0x006bd6c8 0x006bd6c8 0x00150 0x00150 RW 0x4

The first load segment is end at 0x691630, and the second load segment is begin at 0x691f38. So cave at 0x691630 is perfect for our tempoline code.

Put a BL instruction at hook target.

We just use a near instruction to call trampoline code at hook_ptr, using thumbwriter in Frida for this.

Summary

I wrote a method in patchutils.js, it’s defined as follows:

1
export function putThumbHookPatch(trampoline_ptr:NativePointer, hook_ptr:NativePointer, hook_fun_ptr:NativePointer, para1:NativePointer, origin_inst?:number[]):number
  • The 1st argument, trampoline_ptr is the address of trampoline code
  • The 2nd argument, hook_ptr is the address of hook target
  • The 3rd argument, hook_fun_ptr is the address of our hook handle function
  • The 4th argument, para1 is the 1st parameter to our hook handle function, it will be assign to base address of libMyGame.so in caller
  • The 5th argument, origin_inst is the saved original bytes at hook_ptr
    And it returns the length of the trampoline code
    I also wrote a test method in inlinehooktest.ts.

Compile & run

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

1
$ make build_inlinehooktest

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:
#################### Hook Begin ##############################
hook function from so
baseaddress 0xc8902000
CPSR 0x600e0010
R8 0x00000000
R9 0xbbdf8c18
R10 0xbbdf8b80
R11 0xbbdf8b90
R12 0xf3811ce8
LR 0xc8b69787
R0 0xbbdf8b70
R1 0x05c43127
R2 0x05c43127
R3 0x05c43127
R4 0xf3813260
R5 0xbbdf8b70
R6 0xdcc854b8
R7 0xbbdf8b88
#################### Hook end ##############################
1.8.12

The hook handle function just do the following,

  • print base address
  • dump values of all registers stored in the stack

Conclusion

This method still has many way to improve. And it only support Thumb. If you have any idea or issues, please feel free to let me know. I’m always glad to hack and discuss with others.
All code is in my github.

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