Function
Hooking for
Recon and
Exploitation
By: Sam Quinn
Finding vulnerabilities by reverse engineering software is often a difficult undertaking. This is especially true
when attempting to understand data being passed in and out of functions. In these scenarios, dynamic
debugging is often used to track and identify data moving between functions. Another option is the process of
hooking function calls. The term “hooking a function” is the process of changing the default flow of execution,
usually with the intent of either gathering information or changing the result of the hooked function entirely.
This technique analysis will walk through three examples to help explain the process of hooking functions to
retrieve useful information as well as modifying functions to a researcher’s benefit.
Prerequisites: odds of winning if the information of the winning
numbers were disclosed to the user before they enter
Linux System (a VM will work) their guesses. Since this exercise is about hooking
functions and using tools to instrument this process,
Frida tools installed (how to install here)
GDB or binary patching will not be covered, but also
All code can be found here could perform similar changes to “win.”
Example #2: Ultimately this led to our access violation crash. All
this instrumentation and debugging data was done
An Android Phone with root access (An on the target without ever having to restart the
emulator will work too) service. Because of the nature of how this application
was getting started, it did not lend itself well to
Getting Started: being started manually, and since it was running on
an Android, the LD_PRELOAD method of hooking
We will focus on a few examples highlighting the would have been a major pain. Frida was able to
basics of function hooking as well as some real-world connect to the running daemon without interrupting
case studies taken from ATR’s previous research. This anything and not triggering any watchdogs. With the
walk through is not an in-depth tutorial on how to run instrumentation Frida provided above it was easy to
the tools; instead, it details how these tools can be identify and determine the exploitability of the crash
used to supplement the research process. in question.
To get hands on experience with these tools and Because this is a Linux executable, with dynamic
techniques, Examples #1 and #2 are designed to be linking, generic C functions like “rand()” or
followed along with. “printf()” are linked to the system implementation,
commonly found in libc.so installed in the OS. This
Example 1: linking helps reduce the size of the executable,
In this example we will be working with a small minimizing the redundant code and can work on many
x86_64 ELF binary compiled for Linux. This program different systems and versions of libc.so. However,
is a simulated Powerball game where there are 6 because some of the functions are dynamically linked,
random numbers chosen (equivalent to the balls we can tell the OS to use our version of a function
in Powerball), and the user tries to guess these and disregard the true implementation of these
numbers. Depending on how many correct numbers functions. This can be done by simply assigning the
the user chooses, they will receive a cash reward. To LD_PRELOAD environment variable.
participate in this example, the code can be found To use LD_PRELOAD one must provide it a path to a
here. This game is just a silly exercise to help teach compiled shared object with a function declaration
the usefulness and power of hooking functions. that matches the desired function to be overwritten.
With the ridiculous odds of 1 in 292,201,338 to get
the jackpot, hooking functions can increase the
Function Hooking for Recon and Exploitation 2
In the powerball.c code, the rand() function is being
used to generate the winning balls. The LD_PRELOAD
variable can be used to overwrite this function to
include a printf call to disclose the winning number
before the user has a chance to guess. The code
shown in Figure 1, creates a rand() function with
the same function signature as the true rand(). This
is necessary as we are not modifying the Powerball
program. The hooking code also includes code that
calls the “true” rand() function, this isn’t necessary
but shows that usefulness of the dlsym() function
for this very task. Lastly, since LD_PRELOAD requires a
shared object file we must use specific compiler flags
to turn this code into a shared object that can be
imported into the Powerball game. Figure 2. Output from the power game after using LD_PRELOAD with our modified “rand()”
function.
As seen in Figure 2, the LD_PRELOAD environment
variable is useful and allowed the user to see each
number as it was randomly generated. The downside
of this is that each hooked function call must be
compiled into a “.so” object and have the target
application spawned with the LD_PRELOAD variable
present. Lastly the target application must be
dynamically linked or the LD_PRELOAD variable would
be ignored as no functions are being looked up.
With the basics of how hooking can be completed
Figure 1. Shared object code to implement “rand()” manually we can introduce dynamic instrumentation.
For this walk through we will focus on Frida and how
Once the new shared object is compiled and saved
it can overcome the issues presented by manually
into the LD_PRELOAD variable, when the game
hooking function calls. Frida is a very powerful tool
executes it will now run the modified rand() function
that can be used similar to LD_PRELOAD but can
which in turn calls the true rand() function, prints
overcome many of the limitations of the LD_PRELOAD
out the number, and then returns the now disclosed
method. Frida can attach to running programs and
number, as seen in Figure 2.
hook functions dynamically as well as functions from
a static binary.
Function Hooking for Recon and Exploitation 3
To achieve exactly as we did before with LD_PRELOAD, It almost seems like magic, how Frida transparently
the Frida code, written in JavaScript, is much simpler modifies the process to disclose the original random
using Frida’s Interceptor functionality. numbers and replace all with the value 12. While we
passed in our Frida script, Frida can also act as an
interpreter, which allows commands to be entered
directly into the Frida “shell” to perform all the
dynamic instrumentation needs.
Example 2:
Figure 3. Frida code to hook, print the value, and replace the return value of “rand()”
For a more realistic example, we can use Frida to hook
The code in Figure 3 tells Frida on exiting the functions within an Android application to disclose
“rand()” function to print the return value and then sensitive information. This example also highlights
replace the return value with 12. The replacing the the cross-compatibility Frida has with the platform-
return value is just to show the flexibility Frida has agnostic functionality of the hooking framework.
with its function hooking abilities. When reverse engineering Android applications,
it is often hard to follow code flow or determine
Now, to hook the function using the Frida script from
how variables are populated. One example is the
Figure 3, the application can be started normally. As
temporary credentials returned by Amazon’s AWS
seen in Figures 4 and 5, before typing “y” to play, start
Cognito services. The Cognito services provide user
Frida and pass the PID of the power executable and
access to AWS resources, usually for a limited time.
the script to Frida.
Sometimes these credentials have more access
than needed for default functionality and can give
a researcher a new attack vector to AWS resources.
Check out our blog here on the iParcelBox, where
we were able to use the AWS Cognito credentials to
access every iParcelBox deployed. While Frida can
orchestrate the unpinning of SSL certs to potentially
show the returned AWS Cognito credentials from a
network capture, it can sometimes be easier to just
use Frida to hook the function, and have it print the
credentials to the console. To follow along in this
Figure 4. Starting the game normally and hooking it with Frida example it will be left to the reader to find an Android
application that uses AWS Cognito services; there are
many of them out there, including the iParcelBox app
we investigated. The hooked java classes in Figure 6
will potentially need to be adjusted as well.
The IdToken and AccessToken are both provided
dynamically from the AWS Cognito server and would
require an exhaustive reverse engineering approach
to uncover the correct calls and destination URL
statically.
Figure 5. Hooking the power process with Frida and passing in a Frida script
Function Hooking for Recon and Exploitation 4
Yet, by simply hooking these functions and printing
the return value as shown in Figure 6, we can use
the application to log in to the Android application
normally, and have these credentials nicely printed to
the console as seen in Figure 7.
The possibilities of information that can be gathered
from dynamic hooking provided by Frida is limitless.
A few other interesting applications of using Frida to
hook within Android applications could be to gather
an encryption key when an encryption function is
called or hook a video game function to change
the health impact of an attack. Each of these could
Figure 6. Frida code to hook the AWS Cognito Token functions on Android
be done without having to modify the Android
applications at all.
Figure 7. Trimmed down output from the hooked AWS Cognito token functions
Example 3: it may have been a use after free vulnerability. Frida
was used to track each malloc() and free() call
For the last example, Frida will be used to help narrow to see where the memory address in question was
down a crash and determine the root cause. This is eventually leading to an access violation. This example
another real example from our team’s research into will be extremely hard to follow along with since after
the Peloton and later assigned CVE-2021-40526. You we reported this to Peloton they patched the system
can read more about this research here. In short, to no longer allow users to get root access to the
there was a read access violation that would occur device. But this example highlights the power that
only after sending more than one network packet Frida has to help narrow down crashes and touches
to the target. The team’s original thought was that on some advanced functionality.
Function Hooking for Recon and Exploitation 5
We used the following Frida code in Figure 8 to keep
track of each malloc and free call as well as the
addresses they are tied to and nicely print the info to
the console.
Figure 10. The data structure for each network connection
To view this information systematically in Frida, the
data structure can be accessed with the Frida code
from Figure 11. Because the target application in this
case had symbols, we were able to use the function
names within our Frida code. Once the first item in
the linked list address is returned from the “List_
NetGetFirstNode()” function, we can loop through
the linked list and print the values at each of the
Figure 8. Frida code to track each “malloc()” and “free()” call to determine if the crash was
structs members to disclose useful information.
a use after free vulnerability
Hooking the target process in question and sending
our crash case resulted in the following output shown
in Figure 9.
Figure 11. Frida code to print off each connection data structure from the linked list
The “List_NetGetFirstNode()” function was
called each time a write to the linked list would
occur and seemed to be a suitable candidate for
instrumentation. The results can be viewed in Figure
Figure 9. Frida output showing each malloced address and each address being freed
12.
We noticed that the access violation was coming
from what looks like a random address, which would
change every run. The next step was to track the
data structures within the malloced memory. The data
structure of interest was a doubly linked list that kept
track of each socket connection. Each socket was in
a structure as seen in Figure 10. Figure 12. Frida output showing the useful info from the connection data structure
Function Hooking for Recon and Exploitation 6
This shows that there are two active connections
and that they are pointing to each other. With this
information, the crash condition was again sent to
the target, and it was clear that at a certain point the
“socketfd” seems to get a weird value much greater
than what is considered normal for a file descriptor as
seen in Figure 13.
Figure 14. Frida output showing a very corrupted data structure
After sending a few more packets to the target, the
entire structure (including the previous and next Ultimately this led to our access violation crash. All
pointers) seems to get messed up: this instrumentation and debugging data was done
on the target without ever having to restart the
service. Because of the nature of how this application
was getting started, it did not lend itself well to
being started manually, and since it was running on
an Android, the LD_PRELOAD method of hooking
would have been a major pain. Frida was able to
connect to the running daemon without interrupting
anything and not triggering any watchdogs. With the
Figure 13. Frida output showing where the data structure is getting corrupted instrumentation Frida provided above it was easy to
identify and determine the exploitability of the crash
in question.
Concluding thoughts:
Hooking functions to bypass, modify, or simply gather information is a great tool that every security researcher
should have in their arsenal. While there are many other useful reasons to hook functions and many other tools
to do so, we hope that this can give some inspiration on what the technique of hooking can do for you in the
future.
To continue learning more about Frida checkout these resources.
Frida-boot
Frida Quick Start Guide
Getting Started with Frida : Hooking a Function and Replacing its Arguments
Copyright © 2022 Musarubra US LLC
APRIL 2022