Hooking Native Methods
Native methods—written in C or C++ and compiled into machine code—are often used in Android applications for performance-critical tasks,
accessing low-level system resources, or handling sensitive operations. While powerful, these methods can also introduce vulnerabilities or become
targets for exploitation.
By this point, you're already familiar with the concept of hooking. In this section, we'll apply hooking speci�cally to native methods to observe their
behavior at runtime. In parallel, we'll use static analysis to disassemble and review native libraries included in the app, allowing us to understand their
logic and identify potential vulnerabilities from both dynamic and static perspectives.
We'll primarily use an Android Virtual Device (AVD) for this example, though any physical or emulated Android device will work. Let's begin by
connecting to the device via ADB and installing the application.
Hooking Native Methods
rl1k@htb[/htb]$ adb connect
rl1k@htb[/htb]$ adb install [Link]
Performing Streamed Install
Success
The application presents the user a riddle: I am full and strong! What can you do about it?.
Let's examine the application using JADX to see how it works. Reading the source code reveals the MainActivity class.
Hooking Native Methods
rl1k@htb[/htb]$ jadx-gui [Link]
We notice that in the last line of the onCreate() method, a broadcast receiver, is registered dynamically:
Code: java
registerReceiver(this.f1756z, new IntentFilter("[Link].BATTERY_LOW"));
Reading further into the class, we see that the inner class a extends BroadcastReceiver.
As discussed in previous sections, a broadcast receiver can function both as an application component and an interprocess communication (IPC)
mechanism. In this case, it acts as a component that listens for a speci�c system broadcast: [Link].BATTERY_LOW. When this intent is
received, it in�uences the app's behavior depending on the device's battery status. ✎
The receiver invokes the native method getInfo(String str) in the following line:
Code: java
str = [Link](d.d("XDR"));
But only if the following condition is satis�ed.
Code: java
if ([Link]("Is_on").equals("yes")) {
...
This checks whether the Intent contains an Extra named "Is_on" with the value "yes".
Since the broadcast can originate from either the system or a third-party app, we can manually trigger it using ADB. Doing so will cause the app to start
and call the native method.
Hooking Native Methods
rl1k@htb[/htb]$ adb shell am broadcast -a "[Link].BATTERY_LOW" --es "Is_on" "yes"
Broadcasting: Intent { act=[Link].BATTERY_LOW flg=0x400000 (has extras) }
Broadcast completed: result=0
However, this screen doesn't reveal much—just a toast message saying "Look me inside." To further investigate, we need to analyze the app's native
code. First, decompress the APK to extract the native libraries:
Hooking Native Methods
rl1k@htb[/htb]$ unzip [Link] -d angler
<SNIP>
Archive: [Link]
inflating: angler/META-INF/com/android/build/gradle/[Link]
extracting: angler/assets/dexopt/[Link]
extracting: angler/assets/dexopt/[Link]
inflating: angler/[Link]
inflating: angler/lib/arm64-v8a/[Link]
inflating: angler/lib/armeabi-v7a/[Link]
inflating: angler/lib/x86/[Link] ✎
inflating: angler/lib/x86_64/[Link]
inflating: angler/[Link]
The output con�rms the presence of the library �le: angler/lib/arm64-v8a/[Link]. Let's examine it with Ghidra.
Hooking Native Methods
rl1k@htb[/htb]$ ghidrarun
Once Ghidra starts:
1. Go to File → New Project, choose a name (e.g., Test), and click Finish.
2. Then go to File → Import File and select [Link] from angler/lib/arm64-v8a/.
3. Accept the default settings in the pop-ups and click OK.
4. Open the imported file by double-clicking it or clicking the green dragon icon.
5. Confirm any prompts and click Analyze.
After the analysis completes, navigate to the Symbol Tree, expand the Functions folder, and locate
Java_com_example_angler_MainActivity_getInfo.
Based on the JNI naming convention, this function corresponds to the native getInfo() method we previously identi�ed in the app's Java code using
JADX.
Reading the contents of the function Java_com_example_angler_MainActivity_getInfo in Ghidra’s Decompile window reveals that it calls two other
functions: illusion(pcVar1) and ne(pcVar2).
Double-clicking ne(pcVar2) opens the corresponding code, where we �nd a key comparison using the strcmp function.
The following line compares two strings: param_1 and pbVar10.
Code: java
iVar9 = strcmp(param_1,(char *)pbVar10);
The strcmp function returns 0 if the two strings are equal. If this condition is met (iVar9 == 0), the code proceeds to construct a message string
containing You found the flag using std::__ndk1::basic_string<>.
Referring back to the Java code in MainActivity, we saw that the param_1 value passed to strcmp comes from the return value of the method
d.d("XDR"), as used in the line:
Code: java
str = [Link](d.d("XDR"));
To fully understand what the native function is comparing param_1 against, we need to determine the value of pbVar10, the second argument passed to
strcmp. This requires the identi�cation of the memory address where strcmp is invoked during execution.
Due to runtime protection mechanisms like ASLR (Address Space Layout Randomization), we cannot determine the absolute address of strcmp
statically in Ghidra. However, we can calculate it at runtime by adding the o�set of the strcmp call—which we can get by hovering over the instruction in
the Listing window—to the base address of the native library, which we'll retrieve dynamically when the app is running.
Now that we know the o�set (44ab0h), we can start crafting our JS script. In a �le called [Link], we add the following code.
Code: js ✎
// Delay execution to ensure module is loaded
setTimeout(function() {
// Dynamically find module base address
var libanglerBase = [Link]('[Link]');
if (!libanglerBase) {
[Link]('[Link] module not found!');
return;
}
// Calculate specific strcmp call address using offset from Ghidra
var strcmpCallOffset = ptr('0x44ab0');
var strcmpCallAddress = [Link](strcmpCallOffset);
// Attach interceptor to the specific strcmp call
[Link](strcmpCallAddress, {
// Hook entering the function
onEnter: function(args) {
[Link]('');
[Link]('Parameter 1:', [Link](args[0]));
[Link]('Parameter 1:', [Link](args[1]));
},
// Hook leaving the function
onLeave: function(retval) {
[Link]('Specific strcmp call returned:', retval.toInt32());
}
});
}, 1000); // 1 second delay to wait for module load
Notice that we changed the o�set from 44ab0h to 0x44ab0. This is due to JavaScript useing the pre�x 0x to denote hexadecimal numbers. Additionally,
the h su�x is a notation used in some assembly or legacy languages representing hexadecimal format.
Let's break down the script in more detail to understand what each part does.
Instruction Description
setTimeout This delays the execution of the script by 1000 milliseconds (1 second). The delay ensures that the native module
([Link]) has been fully loaded into memory before the script attempts to locate it. If we try to attach the
interceptor too early, the module may not exist in memory yet, causing the script to fail.
[Link]('[Link]') Searches for the base memory address of the [Link] library loaded into the target process. This base
address is essential for calculating the absolute location of the target function call (strcmp) by adding the o�set
obtained from Ghidra.
ptr('0x44ab0') Converts the hexadecimal string '0x44ab0' into a NativePointer object. This value represents the o�set
within the library where the strcmp call is located.
[Link](strcmpCallOffset) Adds the o�set to the base address of the library, producing the absolute address of the speci�c strcmp call that
we want to hook.
[Link](...) Hooks into the calculated memory address. When this address is reached during the app's execution (i.e., when
strcmp is called), the onEnter and onLeave callbacks de�ned in this section will be triggered.
[Link](args[0]) Reads the two string arguments passed to strcmp from memory. These are C-style strings (null-terminated), and
[Link](args[1]) Frida provides this helper to read them correctly.
[Link](...) Outputs the arguments and the return value to the terminal. This allows us to see exactly what strings are being
compared and what the result of the comparison is.
Now that we understand what the script is doing, and we already know the package name ([Link]), let's go ahead and execute it using
Frida:
Hooking Native Methods
rl1k@htb[/htb]$ frida -U -l [Link] -f [Link] 16s ≡
____ ✎
/ _ | Frida 16.1.11 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at [Link]
. . . .
. . . . Connected to Android Emulator (id=emulator-5554)
Spawned `[Link]`. Resuming main thread!
[Android Emulator 5554::[Link] ]->
Frida will attach and wait for the strcmp function to be called, so we send the broadcast again.
Hooking Native Methods
rl1k@htb[/htb]$ adb shell am broadcast -a "[Link].BATTERY_LOW" --es "Is_on" "yes"
Broadcasting: Intent { act=[Link].BATTERY_LOW flg=0x400000 (has extras) }
Broadcast completed: result=0
Once the broadcast is received, the application will automatically start and execute the native getInfo() method, which eventually calls strcmp. When
this happens, the hooked function is triggered, and Frida prints the intercepted values to the terminal.
Hooking Native Methods
rl1k@htb[/htb]$
<SNIP>
. . . . Connected to Android Emulator (id=emulator-5554)
Spawned `[Link]`. Resuming main thread!
[Android Emulator 5554::[Link] ]->
[Android Emulator 5554::[Link] ]->
Parameter 1: HTB
Parameter 1: 4854427b796f755f3472335f676f6f645f34745f6830306b316e397d
The hook is successful. Both parameters are printed, with the �rst being HTB and the second
4854427b796f755f3472335f676f6f645f34745f6830306b316e397d. Based on its format and character set, we can identify with certainty that the second
value is a hex-encoded string. To decode it and reveal its actual content, we'll update the script to include a conversion function that transforms
hexadecimal into ASCII.
Here is the updated [Link].
Code: js
// Delay execution to ensure module is loaded
setTimeout(function() {
// Dynamically find module base address
var libanglerBase = [Link]('[Link]');
if (!libanglerBase) {
[Link]('[Link] module not found!');
return;
}
// Calculate specific strcmp call address using offset from Ghidra
var strcmpCallOffset = ptr('0x44ab0');
var strcmpCallAddress = [Link](strcmpCallOffset);
// Attach interceptor to the specific strcmp call
[Link](strcmpCallAddress, {
// Hook entering the function
onEnter: function(args) {
var param1 = [Link](args[0]);
var param2 = [Link](args[1]); ✎
var flag = hexToASCII(param2);
[Link]('');
[Link]('Parameter 1:', param1);
[Link]('Parameter 2:', flag);
},
// Hook leaving the function
onLeave: function(retval) {
[Link]('Specific strcmp call returned:', retval.toInt32());
}
});
}, 1000); // 1 second delay to wait for module load
// Define a function to convert hexadecimal to ASCII string
function hexToASCII(hex) {
var str = ''; // Initialize an empty string for the result
for (var i = 0; i < [Link]; i += 2) { // Iterate over hex string two characters at a time
var v = parseInt([Link](i, 2), 16); // Convert each hex pair to decimal
if (v) str += [Link](v); // Convert decimal to character and append to result string
}
return str; // Return the resulting ASCII string
}
In this iteration, the functionhexToASCII(hex) has been added, which converts the hexadecimal string to ASCII. Save the changes and run Frida once
more.
Hooking Native Methods
rl1k@htb[/htb]$ frida -U -l [Link] -f [Link] х INT 22s ≡
<SNIP>
. . . . Connected to Android Emulator (id=emulator-5554)
Spawned `[Link]`. Resuming main thread!
[Android Emulator 5554::[Link] ]->
Again, we send the broadcast.
Hooking Native Methods
rl1k@htb[/htb]$ adb shell am broadcast -a "[Link].BATTERY_LOW" --es "Is_on" "yes"
Broadcasting: Intent { act=[Link].BATTERY_LOW flg=0x400000 (has extras) }
Broadcast completed: result=0
At last, the output reveals the ASCII representation of the second parameter.
Hooking Native Methods
<SNIP>
. . . . Connected to Android Emulator (id=emulator-5554)
Spawned `[Link]`. Resuming main thread!
[Android Emulator 5554::[Link] ]->
Parameter 1: HTB
Parameter 2: HTB{h0ok1ng_n4t1v3_funct10ns!}
Connect to Pwnbox
Your own web-based Parrot Linux instance to play our labs.
Pwnbox Location
UK 28ms
✎
Terminate Pwnbox to switch location
Start Instance
/ 1 spawns left
Waiting to start...
Enable step-by-step solutions for all questions
Questions
Cheat Sheet
Answer the question(s) below to complete this Section and earn cubes!
+5
What is the value of the second parameter passed to the strcmp function?
Submit your answer here...
+10 Streak pts Submit
hook_native_method.zip
Previous Next
Cheat Sheet
ဿ Go to Questions
?
Table of Contents
Enumerating and Exploiting Installed Apps
Introduction
Enumerating Local Storage
Exported Activities
Insecure Logging
Pending Intents
Exploiting WebViews
✎
Insecure Library Load Through Deep Linking
Dynamic Code Instrumentation
Hooking Java Methods
Altering Method Values
Hooking Native Methods
Bypassing Detection Mechanisms
Authentication Token Manipulation
Intercepting HTTP/HTTPS Requests
Intercepting API Calls
IDOR Attack
SSL/TLS Certi�cate Pinning Bypass
Skills Assessments
Skills Assessment
My Workstation
OFFLINE
Start Instance
/ 1 spawns left