Building Fast and Reliable Reverse Engineering Tools With Frida and Rust
Building Fast and Reliable Reverse Engineering Tools With Frida and Rust
2022 IEEE 18th International Conference on Intelligent Computer Communication and Processing (ICCP) | 978-1-6654-6437-6/22/$31.00 ©2022 IEEE | DOI: 10.1109/ICCP56966.2022.10053941
Abstract—Reverse engineering binary applications is a key of this method is that it allows the instrumentation of compiled
process for black-box security auditing and malware analysis. binaries at runtime (i.e. without recompiling them). The instru-
Frida is a reverse engineering framework based on dynamic mentation usually consists of inserting additional instructions
binary instrumentation that allows the user to create agents,
which are injected in the analyzed process, and can communicate into the binary at runtime to allow tracking various aspects of
with the user’s program. Frida is written in C and Vala and its behavior, such as how many times was a function called
offers high level bindings in Python and JavaScript. Dynamic and with what parameters. These instrumentation frameworks
languages allow fast development iteration, a key requirement also allow the modification of existing logic, for example
when trying to discover the inner workings of an application by changing one of the parameters or the return value of a
or protocol. The main disadvantages of such languages include
performance limitations and their error-prone nature due to lack function or removing a function call entirely. Furthermore,
of type checking. In this paper we address these limitations by these frameworks allow the tracing of the binary’s execution
building bindings in Rust, which aims to offer high performance at instruction level, which allows the creation of tools such
and numerous correctness guarantees while still maintaining as the well-known memcheck [8] for detecting errors related
reasonable development iteration speed. We show examples of to memory management and cachegrind [1] for performance
performance improvements and present a real use case to validate
the usability of the library. profiling via cache simulation, both built on top of Valgrind.
Index Terms—reverse engineering, dynamic binary analysis, Frida is also suitable for lightweight dynamic analysis of
dynamic binary instrumentation, high performance processes by allowing operations such as enumerating loaded
modules, retrieving the addresses of exported functions by
I. I NTRODUCTION name and hooking (inserting inspection code just before the
Reverse engineering is a key method for analyzing and function is called and just before it returns) or replacing
understanding the inner workings of compiled programs (bina- functions.
ries), most often without access to their source code. The main Furthermore, Frida is significantly different from the other
goals can be divided in two categories: a) black-box testing frameworks (PIN and Valgrind) in the following ways:
and security assessment of binaries, and b) behavioral analysis
1) it’s implemented in Vala with platform specific code in
of malware.
C, C++ and assembly, but the main API (Application
In [1], Nethercote identifies two dimensions of the program
Programming Interface) (i.e. the main way through which
analysis domain:
programmers interact with the framework) is exposed as
• how the analysis is performed: a) statically, without
Python and JavaScript bindings.
running the program, and b) dynamically, by running the 2) it has first class support for instrumenting applications
program on mobile devices, such as iOS and Android. Both PIN
• what is used as input to the analysis: a) the source code,
and Valgrind were designed for the main use case of
and b) the binary itself (without source code) analyzing “desktop” applications (i.e. applications com-
In a recent series of reports Tenaglia and Adams also give piled for Windows, Linux and MacOS operating systems,
an overview of the various reverse engineering methods and traditionally running on a processor with X86 or X64
associated tools in [2]. architecture).
Our paper focuses on dynamic analysis of binaries by using 3) it allows the instrumentation code running in remote
the Frida reverse engineering framework [3]. Frida enables processes to connect and communicate with a tool (also
dynamic analysis through DBI (Dynamic Binary Instrumenta- using Frida) over the internet.
tion), a technique used by the popular frameworks such as PIN
[4], Valgrind [5, 6] and DynamoRIO [7]. The major advantage Since this paper also focuses on the use case of analyzing
“desktop” applications, the main feature of interest is the
advantages and disadvantages that come with exposing the
978-1-6654-6437-6/22/$31.00 ©2022 IEEE main API of Frida through scripting languages. In short, their
289
Authorized licensed use limited to: Universiti Malaysia Perlis. Downloaded on December 19,2024 at 17:22:44 UTC from IEEE Xplore. Restrictions apply.
main advantage is allowing the developers to experiment and
iterate faster on their code thanks to their interactive nature
(i.e. they can be run directly, without the need for compilation),
less strict nature (i.e. they don’t require type annotations and
perform many automatic conversions) and dynamic evaluation
(i.e. an already running script can simply use the eval
function to load and run additional code).
Contributions
• We provide an overview of the Frida framework
• We analyze the performance overhead of using Frida with
Python and JavaScript versus using Frida with Rust
• We show how Rust can be used to prevent common
programming mistakes that existing language bindings
(C, C++, Python and JavaScript) allow Fig. 1: Architecture for the most common use case of Frida
• We present examples that analyze the advantages and
disadvantages of the Python and JavaScript bindings
versus the Rust bindings for common reverse engineering • injecting a shared library containing the instrumentation
and testing use cases code into a process
• a communication layer between the tool using
Outline
frida-core and the injected library built with
The rest of this paper is organized as follows. Section II frida-gum
positions our work in the field, while Section III gives a brief
The frida-gum2 library is used to build the instrumenta-
overview of Frida and Rust. Section IV presents the design an
tion tools that will be injected into the process of interest. The
implementation of the proposed solution in detail. Section V
main features include:
shows the results of the performance and usability evaluations.
Section VI concludes and describes the intended future work. • listing loaded modules and their exported functions
• inline function hooking and replacing, provided by the
II. R ELATED WORK Interceptor API
Due to its native support for mobile devices, the Frida • code tracing, provided by the Stalker API3
framework has seen many uses, most being in the security It is very important to note that components described
assessment of communication protocols used by IoT (Internet above are actually not the main “entrypoint” of the Frida
of Things) devices. Examples include: framework. Most of the documentation and examples use the
• reverse engineering the Android Nearby Connections Python and JavaScript bindings because they are much easier
protocol, presented by Antonioli et al. in [9] to use, compared to C and C++. As such, most developers will
• creating a reverse engineering framework for analyzing interact with Frida using frida-python (Python bindings
protocols used by Linux by IoT system, presented by for frida-core) or frida-node (JavaScript (Node.js)
Liu et al. in [10] bindings for frida-core) and GumJS (JavaScript bindings
• extending Frida to be able to interoperate with applica- for frida-gum).
tions written in the Swift language, presented by Kraus Furthermore, some of the functionality offered by GumJS
and Haupert in [11] (the JavaScript bindings) is not available when using the C
implementation (frida-gum), the most important one being
III. BACKGROUND the communication layer provided by frida-core.
A. Frida 2) Architecture of tools using Frida: Figure 1 shows how
the components described in the previous subsection interact
1) Project organization: The Frida project is split into
in the main use case of Frida (injecting instrumentation code
two main components, frida-core and frida-gum, both
that uses the JavaScript API into a process).
written in Vala, with platform specific code (e.g. the code that
Assuming that that the Frida Python package is installed,
interacts directly with the operating system) written in C and
all the developer has to do is:
assembly.
frida-core1 , as the name suggests, is the central com- 1) write the instrumentation code in JavaScript
ponent that is used as a base to build tools that use Frida or 2) write a Python script that finds or spawns a process of
libraries that expose Frida’s API in a given language (language interest and injects the code written in the previous step
bindings). It also provides features that aim to make the main 1 https://github.com/frida/frida-core
steps of dynamic binary analysis faster and easier: 2 https://github.com/frida/frida-gum
290
Authorized licensed use limited to: Universiti Malaysia Perlis. Downloaded on December 19,2024 at 17:22:44 UTC from IEEE Xplore. Restrictions apply.
B. Rust
291
Authorized licensed use limited to: Universiti Malaysia Perlis. Downloaded on December 19,2024 at 17:22:44 UTC from IEEE Xplore. Restrictions apply.
Overhead of an instrumented run of ripgrep
20.58±0.35s
Uninstrumented
20.0 Rust
Python and JavaScript
17.5
15.0
Execution time (s)
12.5
10.0
8.56±0.14s
7.71±0.10s 8.08±0.09s
7.5
5.0
3.29±0.14s
2.5 1.95±0.01s 1.96±0.01s 1.97±0.02s
0.41±0.00s 0.42±0.00s 0.42±0.00s 0.45±0.01s
0.0
ripgrep (85 files) tokio (580 files) clippy (1385 files) rust (21421 files)
Fig. 3: Performance
V. E VALUATION to the fact that both JavaScript and Python are dynamically
A. Performance typed scripting languages, while Rust is a compiled language
with strong, static typing, and low level control (like C and
To evaluate the change in performance impact caused C++). On the other hand, these features of Rust can also
by using the Rust bindings, the ripgrep program (recursive help speed up development by offering a better experience
searcher similar to grep) was instrumented to monitor all with IDE tooling (i.e. in general, autocomplete works better
calls to CreateFileW and CreateThread on Windows with strongly typed languages) and reducing the need for
when performing a search in repositories of various sizes. The debugging because many mistakes (typos, accessing non-
uninstrumented binary along with the tools that instrument it existent fields or methods) can be prevented at compile time.
(Python and JavaScript implementation and Rust implemen- For example, when processing messages sent by JavaScript
tation) were run 10 times, with 1 warmup run for each tool (Listing 2), each dictionary access or string comparison in
before each series of measurements. Note that the measured the message handling code Listing 3 has the potential to be
time include the time it takes for the tool to spawn the target mistyped.
process and wait for it to terminate.
The results presented in Figure 3 show that for up to send({tid, addr, id: cpu.rax})
1385 files the uninstrumented program takes about 420ms
to complete, with the Rust instrumentation it takes about Listing 2: Sending a message to the CLI tool from GumJS
1.97 seconds (4.5 fold slowdown) and with the Python and
JavaScript instrumentation it takes about 8 seconds (20 fold def on_message(self, message, data):
ty = message[’type’]
slowdown). For more than 20.000 files, the instrumentation if ty == ’send’:
code starts becoming a bottleneck, because even though the payload = message[’payload’]
runtime for the uninstrumented version doesn’t significantly print(payload)
elif ty == ’error’:
increase, the overhead of the instrumentation increases from a print("error", message)
4.5 fold slowdown to 7.3 fold slowdown and from a 20 fold else:
slowdown to a 45.3 fold slowdown for the Rust and Python raise Exception("Unknown message")
and JavaScript versions, respectively. Listing 3: Message handling code in frida-python
B. Usability
As one might expect, the implementation of Frida tools C. Real use case: Direct syscall detection
using JavaScript and Python will be more terse than the
functionally equivalent implementation in Rust. This is due To assess how well the library can be used to build tools
for a real use case, a tool that instruments programs to detect
4 https://github.com/frida/frida-rust system calls was implemented using the Python and JavaScript
292
Authorized licensed use limited to: Universiti Malaysia Perlis. Downloaded on December 19,2024 at 17:22:44 UTC from IEEE Xplore. Restrictions apply.
API of Frida, the Rust API of Frida and using the C++ API INS_InsertCall(
ins,
of PIN. IPOINT_BEFORE,
(AFUNPTR)printip,
On Windows, a system call is considered direct when the IARG_INST_PTR,
syscall instruction is outside the expected modules (ntdll.dll or IARG_REG_VALUE, REG_EAX,
win32u.dll). Thus, to detect direct system calls, an instrumen- IARG_THREAD_ID,
IARG_PTR, static_name,
tation callback can be inserted before each syscall instruction, IARG_END
and in the callback, we can inspect the processor state to obtain );
the address of the instruction (stored in RIP) and the syscall
number (stored in RAX). If the instruction is outside of the Listing 4: Inserting an instrumentation callback before an
expected modules, the syscall address and number are recorded instruction in PIN
in an output file.
VI. C ONCLUSIONS AND FUTURE WORK
To validate the correctness of the tool, we instrumented
Dynamic binary instrumentation is key tool in reverse
Dumpert5 , a tool for dumping the memory of LSASS (Local
engineering binaries without access to source code. There
Security Authority Server Service) on Windows using direct
exist multiple established frameworks such as Valgrind, PIN
system calls for detection evasion.
and DynamoRIO for creating tools for dynamic binary in-
The tools built with the Rust API of Frida and the C++ strumentation, but the require complex setup and require good
API of PIN were copied into a virtual machine and used knowledge of C/C++. The Frida framework breaks this pattern,
to instrument Dumpert. Both tools successfully detected all by introducing scriptable, zero-setup binary instrumentation,
direct system calls performed by the tool. Both tools were at the cost of the overhead incurred by scripting languages.
implemented in a way to produce output in the same format This project aims to serve as a middle ground, by creating
and the output was parsed by a script to confirm that the results Rust bindings for Frida, that offer low overhead and numerous
match. guarantees for the correctness of the instrumentation code.
For this use case, the main advantage of the Rust tool is that As future work we foresee three directions:
the tool can be easily packaged and deployed as one executable 1) Use the Rust API to add support for a second scripting
(by embedding the instrumentation dll in the command line language, with a different set of tradeoffs than JavaScript.
tool), while the PIN tool is much harder to deploy and use on For example, the Mun language6 is statically typed and
a virtual machine, since the runtime is not open source and ahead-of-time compiled, but has first class support for
there is no possibility to link the runtime and tool into a single hot reloading.
binary. 2) Add support for sockets as a transport layer for our
communication channel.
1) Usability: The code required to implement the direct 3) Extend the API to encapsulate more of the common oper-
system call detection tool in Rust, after being formatted with ations (e.g. parsing utf-16 string parameters on Windows)
the default settings of Rust’s recommended code formatter in a safe and convenient way.
(rustfmt) requires about twice as many lines of code as the
equivalent Python and JavaScript implementation (220 lines ACKNOWLEDGEMENTS
of Rust vs 101 lines of Python for the command line tool and The work for this paper has been supported in part by the
215 lines of Rust vs 64 lines of JavaScript for injected code). Computer Science Department of the Technical University of
Cluj-Napoca, Romania.
The PIN implementation with C++ for the direct syscall
detection tool is about 90 lines of code. This is due to R EFERENCES
the fact that PIN’s API is centered around implementing [1] N. Nethercote, “Dynamic binary analysis and instrumen-
instrumentation tools, with a fairly rich, platform independent tation,” University of Cambridge, Computer Laboratory,
API. The downsides of this approach is that associated Tech. Rep., 2004.
tools (i.e. a tool that finds the target process and injects the [2] S. Tenaglia and P. Adams, “Edge of the art in vulnera-
instrumentation code) have to be implemented separately bility research,” Two Six Labs, Tech. Rep., 2020.
and integrating other libraries can also be significantly more [3] O. A. V. Ravnås, “Frida: Dynamic instrumentation
difficult. PIN’s API also includes many variadic functions, toolkit for developers, reverse-engineers, and security
like the one shown in Listing 4, for which the correct usage researchers,” https://frida.re/.
can’t be checked by the C++ compiler, and as such require [4] C.-K. Luk, R. Cohn, R. Muth, H. Patil, A. Klauser,
special attention from the developer. G. Lowney, S. Wallace, V. J. Reddi, and K. Hazelwood,
“Pin: building customized program analysis tools with
dynamic instrumentation,” Acm sigplan notices, vol. 40,
no. 6, pp. 190–200, 2005.
5 https://github.com/outflanknl/Dumpert 6 https://mun-lang.org/
293
Authorized licensed use limited to: Universiti Malaysia Perlis. Downloaded on December 19,2024 at 17:22:44 UTC from IEEE Xplore. Restrictions apply.
[5] N. Nethercote and J. Seward, “Valgrind: A program
supervision framework,” Electronic notes in theoretical
computer science, vol. 89, no. 2, pp. 44–66, 2003.
[6] ——, “Valgrind: a framework for heavyweight dynamic
binary instrumentation,” ACM Sigplan notices, vol. 42,
no. 6, pp. 89–100, 2007.
[7] D. Bruening, T. Garnett, and S. Amarasinghe, “An infras-
tructure for adaptive dynamic optimization,” in Interna-
tional Symposium on Code Generation and Optimization,
2003. CGO 2003. IEEE, 2003, pp. 265–275.
[8] J. Seward and N. Nethercote, “Using valgrind to
detect undefined value errors with Bit-Precision,” in
2005 USENIX Annual Technical Conference (USENIX
ATC 05). Anaheim, CA: USENIX Association,
Apr. 2005. [Online]. Available: https://www.usenix.org/
conference/2005-usenix-annual-technical-conference/
using-valgrind-detect-undefined-value-errors-bit
[9] D. Antonioli, N. O. Tippenhauer, and K. Rasmussen,
“Nearby threats: Reversing, analyzing, and attacking
google’s’ nearby connections’ on android,” 2019.
[10] K. Liu, M. Yang, Z. Ling, H. Yan, Y. Zhang, X. Fu, and
W. Zhao, “On manually reverse engineering communica-
tion protocols of linux-based iot systems,” IEEE Internet
of Things Journal, vol. 8, no. 8, pp. 6815–6827, 2020.
[11] M. Kraus and V. Haupert, “The swift language from
a reverse engineering perspective,” in Proceedings of
the 2nd Reversing and Offensive-oriented Trends Sym-
posium, 2018, pp. 1–12.
294
Authorized licensed use limited to: Universiti Malaysia Perlis. Downloaded on December 19,2024 at 17:22:44 UTC from IEEE Xplore. Restrictions apply.