Beginning Frida

Download as pdf or txt
Download as pdf or txt
You are on page 1of 396

Beginning Frida

Learning Frida use on Linux and (just a bit


on) Wintel and Android systems with
Python and JavaScript

Román Ramírez Giménez


No part of this book may be reproduced, stored in a
retrieval system, or transmitted in any form or by any
means, electronic, mechanical, photocopying, recording
or otherwise, without the prior written permission of the
author and/or copyright holders.

Frida tool, logo and all related materials and references


from https://frida.re and https://github.com/frida are
owned by its team and Ole André Vadla Ravnås.
Cover design: Román Ramírez Giménez

Original title: Beginning Frida


Series: Frida, hooking and other tools #1
Copyright © 2021 Román Ramírez Giménez
All rights reserved.

Imprint: Independently published

TO MY FAMILY
Always there.
Always making me feel happy. Always making me feel
sad. Always making me feel angry. Always making me
feel quiet.
Always making me.

ACKNOWLEDGMENTS

My very first recognition is to Ole André


(https://twitter.com/oleavr). This extraordinary tool you
created is something that amazed me. It is useful,
powerful, and easy. THANKS.

The next to Leon Jacobs (https://twitter.com/leonjza) as


Objection is a wonderful tool and I learnt a lot following
its source code.

Naturally, to Pablo San Emeterio for the foreword and


because he spent his valuable time reading and
commenting on this book.

To Alfonso Muñoz, Andrés Tarascó, Daniel Solís, José


Antonio Guasch, Lucas Varela and Omar Benbouazza,
who helped a lot checking, reading, and making
suggestions.

Finally to Abraham Pasamar for a wonderful radare2


cheat-sheet ;)
Thanks to you all!
FOREWORD BY PABLO SAN
EMETERIO

When my friends and acquaintances ask me about


my work, it is not difficult to explain some of the tasks
that are carried out in the day-to-day work of a
cybersecurity professional. Security audits, code
reviews, preventing the use of insecure passwords, two-
factor authentication or malware analysis are areas
whose usefulness is easily understood and, in some
cases even, familiar. However, when I explain to
someone one of the most fascinating topics in the world
of cybersecurity, such as hooking, I have the feeling that
it is not easy for my interlocutor to understand what I
am explaining and the implications of hooking. The
possibility of modifying the behavior of a program
without having access to its source code or even of an
entire operating system is usually seen as something
risky and with a utility that will hardly compensate that
risk and effort. However, it is tremendously powerful
and something that both attackers and defenders try to
use on a regular basis in their solutions.

Hooking is a set of techniques that allows the


programmer who uses them to "subscribe or hook" to
certain events that occur in an operating system or
program. But this category of techniques is not limited
only to detecting when these events occur in order to
have a record of them. In some cases, they make it
possible to know the arguments with which a system
function is invoked, to modify these arguments and even
to modify the responses generated by these functions.
This is where the greatness of hooking lies and where it
requires the reader to broaden their horizons and begin
to savor the immense possibilities that these techniques
allow. Malware developers are well versed in this type
of technique, which allows them to detect a user's
keystrokes or mouse clicks. And in other cases, they
even modify the responses that some operating system
functions return, so that their malware goes unnoticed
by the administration tools.

Thanks to this type of techniques, we presented in


BlackHat a tool (Kung-Fu Malware) that extensively
uses hooking techniques to modify the behavior of all
the programs that start on a computer. Since a lot of
malware implements techniques, whose objective is to
detect when the software is running in a virtual
environment, in order to evade the automatic analysis
that many security manufacturers do by means of
sandboxes. Kung-Fu Malware makes a "physical"
computer believe that any software that starts on it is
running in a virtualized environment. With these
techniques, if a malicious code were to infect our
computer, it would become a "sick carrier" of malware,
which, thanks to its own anti-VM protections, would not
activate its malicious payload.

It has been more than ten years since I started the


Master's degree in Cybersecurity that changed my
professional career. When I started those studies, I
expected to face technological challenges and learn
multiple techniques of vulnerability exploitation or
malware analysis. The truth is that I discovered that in
the world of cybersecurity not everything is a matter of
0s and 1s. There is a lot of planning, management,
training, metrics, and indicators. But there were some
topics that were technical that I really enjoyed. Of all the
professors in the technical modules, one professor stood
out above the rest. Román was a hacker, and that is how
he presented himself on a television program that was
aired at the time. There was something in that interview
that caught the attention of the presenter, he was a
hacker with a tie. Something totally different from the
stereotype commonly accepted by society. But
underneath that suit, there is a person with a great
technical knowledge that left the presenter and many
viewers reflecting on the state of computer security of
their home computers.

As if that was not enough, he took all of us students


to a security conference held every year in Madrid.
RootedCON, a conference that has grown over time to
become a reference within the conferences and that
allows to visualize the enormous talent in cybersecurity
that exists in the Spanish-speaking community.

Before finishing his classes, the pressure to do a


Master's Thesis began to arrive, and I wanted it to be a
challenge for me. Years before I had observed security
software that was able to know when a program
connected to the internet or opened a port to wait for
connections. It was like a firewall on the computer, but
it allowed or denied connections but instead of doing so
based on IPs or ports, it allowed or denied access based
on the program that wanted to access the Internet. This
behavior really caught my attention as it was somehow
totally groundbreaking and hid something magical.
Being able to know when a program was using functions
that allowed communication with the Internet and
modifying the responses received to these functions
according to the answers given by the user. This concept
fascinated me so much that it made me wonder if it was
something at a global level or could it be done only on
certain programs, could I modify the behavior of other
system functions such as reading and writing files?
Could I hide processes from the eyes of tools like the
task manager? That was the challenge I set myself in my
master's thesis and I had no doubts about who would be
the mentor I needed to bring it to a successful
conclusion. That hacker in a tie quickly understood my
purpose and helped me in one of my first cybersecurity
investigations of the many that I have had the pleasure
of sharing with him during these 10 years.

When he told me that he was writing an introductory


book on hooking using Frida and proposed to me to
write this foreword I did not hesitate, I threw myself into
devouring the book. Dear reader, you will soon discover
the power and versatility of Frida if you follow the paths
that Román has so cleverly written.
Frida is a tool that has traditionally been associated
with the world of mobile application research, but as the
reader will soon discover, it can also be used on
traditional computers or even on the well-known
Raspberry Pi. Frida allows you to create hooks on any
program. The reader of this book has before him the
keys to a world in which only his imagination sets the
limits, because once he starts to enter this world, nothing
prevents him from imagining new possibilities to
improve the security of a system.

The author of the book provides an introduction for


all audiences to this tool on different operating systems.
Throughout the 14 chapters, the enormous possibilities
offered by Frida are shown in a simple and didactic way.
The reader can practice the concepts presented with
practical examples to guide him in these first steps into
the world of hooking. During the development of the
examples, some of the first difficulties that the reader
must face in these projects are also shown.

Dear reader, I can only recommend that you enjoy


every step you take into the burrow of this limitless
world that is hooking and encourage you to continue
chasing that white rabbit with suit, tie, hat, with a
padlock tattooed on one of its front legs and who is
always in a hurry...

Pablo.
THE AUTHOR

Román Ramírez has over 27 years of technology


experience. With some software development
experience across several programming languages,
including C/C++, Java, PHP, Perl, Go, Python and a bit
of JavaScript. And most of his experience on
cybersecurity: digital and computer forensics, hacking
and penetration testing, threat modelling and
cybersecurity and risk management.

He is one of the co-founders and co organizers of


RootedCON, one of the most relevant cybersecurity
congresses in Europe (Madrid, Spain). He led the
cybersecurity operations team in Ferrovial for ten years
and, since 2019, Román is flying on his own as a trusted
advisor on technology, digital transformation, and
cybersecurity.

Linked: https://www.linkedin.com/in/rramirez/
Twitter: @patowc
Contact email: books@juliusdeane.com
HOW TO READ THIS BOOK

A must-read disclaimer here is that I’m not a


professional developer. So, please, be tolerant with
antipatterns and ugly code. This book is an introduction
and, mainly, about my own learning path with Frida.
Then, here you can see a list of all the chapters, with
a brief description of what you will find there. Please,
skip whichever section you want, as I am trying to keep
everything self-contained in every chapter. Of course,
there might be several references to previous chapters,
but I think they will not disturb you when reading.

Introduction. It is just about the typical book details,


errata, why I’m writing and conventions.
→ Skip this introduction if you want to go directly
for Frida stuff.

Chapter 1. My working (virtual) environment. In this


chapter we will discuss how to set up a working
environment to be more productive. As I use to mix
Python and JavaScript stacks, I tend to use virtual
environments (virtualenv, for python, and nodeenv, for
JavaScript). I think it is interesting for people who are in
several projects at the same time. If you carefully set
your virtual environments you can play with different
stacks and ideas without messing things (very important
when developing complex frida-scripts, no matter if
Python or JavaScript).
→ You can skip this chapter if you are not interested
in setting up virtual environments or have experience
doing so.

Chapter 2. First steps with Frida. Moving around


with Frida resources, steps I took to read the tutorials,
play with examples etc.
→ You can skip this section if you know how to
install Frida and the basic use.

Chapter 3. Frida (awesome) tools → frida-trace. The


first approach to Frida use, focusing on frida-trace and
solving an easy crackme.
→ My recommendation is not to skip this chapter. It
may look basic for some experienced users but will
place some interesting concepts you may deal with.

Chapter 4. More in frida-trace. Improving examples


and knowledge with frida-trace and adding some quick
details on debugging with gdb.
→ You may skip this chapter if you feel comfortable
with frida-trace.

Chapter 5. Our final steps with traces. We will


conclude the tracing chapters adding some knowledge
on frida-gadte and frida-stalker.
→ It is not recommended to skip this chapter.
Chapter 6. Other tools within Frida. We will develop
knowledge on several other tools included with frida-
tools and the framework.
→ It is not recommended to skip this chapter if you
have not played with cli tools.

Chapter 7. The powerful frida-server. With frida-


server we will be able to trace and hook in remote (in
both remote systems using TCP/IP or in mobile devices
connected through USB or other connections).
→ It is not recommended to skip this chapter.

Chapter 8. A parenthesis on NativeFunction. Very


important chapter on NativeFunction use.
→ Please do not skip this chapter. There you will
find several relevant concepts on NativeFunctions and
NativePointers.

Chapter 9. Android and Frida. We will introduce the


use of an Android emulator and a test lab to play with
Frida and Android. Among other things, we will learn
how to bypass root detection.
→ It is not recommended to skip this chapter.

Chapter 10. More Frida capabilities. We will invest


some time on several concepts that may require
explanation and examples.
→ You may skip this chapter if you feel all basic
concepts are properly understood.
Chapter 11. Some improvements to code. A chapter
focused on creating better frida-python scripts.
→ You may skip this chapter if you are not interested
in Python.

Chapter 12. Another Android crackme. Just to


reaffirm the knowledges on Android.
→ You may skip this chapter if you feel comfortable
with your Android and Frida skills.

Chapter 13. Telegram and Objection. In this chapter


we will play with an amazing tool called Objection.
Focusing on “cracking” Telegram, we will learn how to
do several things.
→ It is not recommended to skip this chapter.

Chapter 14. Codeshare. We will take a look at Frida's


Codeshare community, where scripts can be shared and
invoked directly from the command line.
→ You may skip this chapter if not interested in
external scripts or sharing in Codeshare.

Chapter <EOT>: future work. Just a final comment


in the End of Transmission, about future projects and
learning ideas.
INTRODUCTION

My profile if I must circumscribe to a specific one, is on


architecture, systems, and cybersecurity. But to support
several tasks and activities in this career, I had to use
different programming skills. So, if a label on dev is
required, I will describe myself as a backend developer.
And not a good one. I have been coding since 1993 in
different languages (started with a mixed C/C++ profile,
then moved to Perl, Java, PHP, C#... with different
flavors of shell scripting too). I tested Haskell (oh my
god), Rust, Ruby… And I like Python and Go a lot (a
lot, I mean it). I do things that work, but I do not
consider myself a coding artist (not a professional dev).

Many times, as I mentioned in the disclaimer, I am in


the antipattern league and, many others, my code may
not look beautiful. But I build things that work and,
seriously, I am happy with my level in this specific area.

So, I understand how things work in systems and


processes, and I tend to code thinking in architectures
that are linked to these systems: a lot of focus on
business messaging infrastructures (IBM MQseries,
RabbitMQ, Kafka, 0MQ, Redis… in the order I learnt
about them) and maniac encapsulation of everything; no
surprise I love containers and REST-ed models.
Honestly, I prefer architecture to coding, but coding is
part of building architectures.
But my weakest area is front. In all the areas: I am awful
at UX, awful at UI, awful at front-end browser
development, in front native apps, in design, in HTML,
CSS… please, if you take a deep look into anything I
developed in frontend, be merciful.

Before entering an introduction of what I know (a bit)


and what I do not know (a lot) on debugging and
instrumentation, I think it is a good idea to explain what
debugging, reverse engineering, Dynamic
Instrumentation, Python, JavaScript and NodeJS are.

Debugging, from Wikipedia


(https://en.wikipedia.org/wiki/Debugging): is the
process of finding and resolving bugs (defects or
problems that prevent correct operation) within
computer programs, software, or systems. Typically
following program execution flow and inspecting
memory. Microprocessor registers and several other
low-level details.

Reverse engineering, also called back engineering, is a


process in which software, hardware, machinery,
structures, and even biological entities, are analyzed and
deconstructed to extract the design patterns and
information that were used to build them. Through this
process it is supposed you will understand how the
specific element was created and how it works.
Dynamic instrumentation, or Dynamic Binary
Instrumentation (DBI),
https://en.wikipedia.org/wiki/Instrumentation_(compute
r_programming): refers to the measure of a product's
performance, to diagnose errors, and to write trace
information. A way of making an analysis of an
application or product to evaluate metrics, understand
how it works and, even, modify its behavior. The main
characteristics of DBI that make it different from
debugging and other types of reverse engineering is that
it is expected to be transparent to the application or
element being analyzed.

For Python I will lend the definition directly from


python.org website: Python is an interpreted, object-
oriented, high-level programming language with
dynamic semantics. Its high-level built-in data
structures, combined with dynamic typing and dynamic
binding, make it very attractive for Rapid Application
Development, as well as for use as a scripting or glue
language to connect existing components together.

JavaScript is, stealing again the definition from the


Mozilla website (https://developer.mozilla.org/en-
US/docs/Web/JavaScript): “JavaScript (JS) is a
lightweight, interpreted, or just-in-time compiled
programming language with first-class functions. While
it is most well-known as the scripting language for Web
pages, many non-browser environments also use it, such
as Node.js, Apache CouchDB and Adobe Acrobat.
JavaScript is a prototype-based, multi-paradigm, single-
threaded, dynamic language, supporting object-oriented,
imperative, and declarative (e.g. functional
programming) styles. Read more about JavaScript”.

NodeJS is, in their own words, and took from its website
(https://nodejs.org/en/about/): “As an asynchronous
event-driven JavaScript runtime, Node.js is designed to
build scalable network applications. In the following
"hello world" example, many connections can be
handled concurrently. Upon each connection, the
callback is fired, but if there is no work to be done,
Node.js will sleep”.

Apart from JavaScript, Python is the other main


resource we should manage to work with if we want to
be successful with Frida. NodeJS is not a requirement,
but it will be useful as we can use modules and
enhancements that will make our life easier.
Why Frida and instrumentation
Not going to introduce Frida in detail here (see Chapter
2) but want to add some thoughts about why I consider
Frida interesting.

Because of learning. Because I want to understand how


to manipulate processes in several ways and with Frida,
I can do that following a black box strategy. And with a
high return of investment from my time and efforts.

Anyway, the most important reason is that I want to be


autonomous and develop my own tools. For example, an
API to hook application internal messages to be
consumed from outside the device (no matter if a mobile
device, Android, or Linux box, that are my main
platforms). So, I invest some time in learning about
tools that can help me in my projects. And as I am
learning for myself, why not take some notes and write
about my progress for others?

A very good question is, why instrumentation?


Typically, a static analysis tool like IDA Pro may be
used, or a dynamic debugger like OllyDbg, Windbg,
radare2 and similar. The main advantage of an
instrumentation tool is that it is supposed to be
transparent to the binary in execution. And ease of use:
this is one of the major reasons.

Microsoft has a library called Detours which can be


used specifically to hook API calls and applications.
You may find Detour’s documentation here:

https://www.microsoft.com/en-
us/research/project/detours/

But why invest time on Frida learning and not in


Detours or other tools or DBI or whatever? Ok. I will
enumerate the five main reasons that caught my
attention and made me invest all this time learning about
this specific framework:

Frida is easier than some other reverse


engineering tools: I have basic knowledge of
IDA Pro, GHidra, radare2, gdb, peda, and
some other debugging and DBI tools. But
the learning curve is hard if this is not your
main activity, so I’m always looking for
alternative tools that simplify this and let me
get the results faster.
Quick-wins: without coding a lot, you can
get quick results with automatic hooking of
syscalls and APIs (frida-trace, frida-
discover, frida-stalker…).
Linux and Android (and iOS, and a bit of
wintel): as said, these are my main platforms
and I want to be able to play with them.
Python and JavaScript binding: excellent
news, as I like python a lot and I am
competent using JavaScript.
No binary modification: I do not need to
modify the binary, like in other tools or
frameworks.

And there are many other reasons we will be explaining,


with examples in many cases, through this book.
Who is this book for
This book is written for cybersecurity professionals,
developers or beginners on reverse engineering who
want to learn the initial concepts and uses of the
powerful Frida instrumentation framework.
Expert reversers, hackers and fine code artists may not
be interested in reading this book, as I wrote it from the
perspective of an absolute beginner, and many of the
concepts may appear too simple for them (even wrong,
in some particular examples). But I do recommend
reading, anyway, as we will focus on doing practical
things.

So, even experienced reverse engineers will find some


interesting things anyway. Even in topics not directly
related to Frida, I hope.
To make reading the book worth
This book follows a very pragmatic approach. Not every
single option and configuration in Frida is covered, but
what it was useful for me in every learning step I took.
If you want to read reference details, please, do check
Frida’s online documentation.

Some knowledge on debugging, instrumentation, Python


and JavaScript may be helpful, but it is not a
requirement. I tried to be really broad in descriptions
and explanations, but it will make it easier if you already
have this knowledge (anyway, if you know a lot, this
book will be very basic for you).

Naturally, you need a computer, Internet connection,


idea of how to find resources in search engines and, of
course, a lot of passion for learning!
Book conventions
There are several styles of text that distinguish between
different kinds of information. I will include here a list
of examples with a comment for you to identify the
meaning.

Source code will be formatted in three ways:

<script>alert(‘Inline 1’);</script>: for inline source


code. Calibri Cursive, 11 points.

<script>alert('Inline 2');</script> : alternate for inline


source code. Consolas, 11 point, colors and grey
background.

<script>
alert(‘Code block, multiline.’);
</script>

And the previous format (Consolas, 9 point, colors, and


grey background), for code blocks that are multiline, the
content of source code files, shell commands sequences
etc. Whatever that looks like something to tell a
computer :) If shell commands, they will start with $ or
with a descriptive string corresponding to the prompt
(PS1) and the $. For example:

$ ls -la
(python3-virtualenv) (node-virtualenv) $ ls -la
If there is a term, word of element that we want to mark
or put your attention on, we will use highlighted,
sometimes bold within code blocks (const
critical_variable;let irrelevant_variable;). As this book
will have several editions (color or grayscale) when
possible, the highlighting will be remarked with changes
on font or using underline.

And for no special reason, I will use cursive or bold to


mark specific elements if I consider it necessary or think
they will mark a difference on the paragraph.

For information, warnings, and alerts, you may find


these boxes:

For errors or risks we must be aware, or


things that are messed or going to be
messed soon. In summary: do not do that!

Opinions or conclusions that I feel are


positive. Or when something goes well in
a demo or code.
Just informative, without any alarm nor
confirmation of success associated. Just
things without my emotions.

Some other relevant icons I might use to enrich the


explanations:
Details on fonts
Just in case you face problems with the fonts, here you
have the list for every type of text:

Normal text (body): Palatino Linotype.


11points typically, that may be increased or
decreased for specific paragraphs.
Titles:
Header 1: for chapters, Palatino
Linotype, 22 points, bold, under
scripted (thick gray line).
Header 2: for sections within chapters,
Garamond, 18 points, bold.
Header 3: for specific concepts in
sections, Garamond, 14 points, light
gray.
GitHub repository

All examples for this book can be found at:

https://github.com/juliusdeane/beginningfrida
Scope of this book
Everything is focused on Frida 14.2.8 (or 14.2.12,
depending on if you perform a pip update). To be able to
play with different ideas and concepts, we will include
several chapters and sections on different strategies,
virtual environment tools, development environments
and IDEs etcetera. But the focus is Frida to be used on
Linux and Android mobile platforms.
Contact
Any feedback from readers is absolutely welcome.
General comments: If you have questions about
whatever the topics of this book, just mention the book
title in the subject of a message and email it to me at
books@juliusdeane.com.

Errata: If you find errata or anything you believe I have


to modify in the text, please contact me with the subject
“book errata” or similar. Same email account:
books@juliusdeane.com.

Collaborations: I am a strong believer in Join Ventures


and collaboration projects. If you think we can do
something better than this book (for sure!) or, even, start
a radical new project, do not hesitate in contacting me.
Please, I mean it, DO CONTACT ME with ideas about
new projects. Again, this email address will work:
books@juliusdeane.com.
Leave a Review!
Please leave a review on Amazon. Every review you
add to a book will help me in reaching more people so,
please, please, please, do add a review. And be honest: I
need the truth. I will keep track of the reviews so I can
improve the book and be a better writer.

Anyway, you can add comments to the GitHub repo or


to the email address I mentioned above.
CHAPTER 1. MY WORKING
(VIRTUAL) ENVIRONMENT

Before any other step in learning about the main topic,


Frida, I think it is a good idea to know how to set up a
working environment that will not have collisions nor
interactions with the host or real environment where you
work.

The main purpose of this initial chapter is to configure


virtual environments where we can “break” things
without disturbing the real host. In the moment you get
the habit of working within virtual environments, you
want to do so in every project, I promise :)

When we feel comfortable with the virtual environment,


we will pass to the process of setting up Frida and our
first steps (Chapter 2).

Main tools we will review in this chapter are:

Virtualenv: to set an isolated environment


for frida and frida-tools.
Nodeenv (with virtualenv): similar to
virtualenv but for running NodeJS isolated
environments.
Nvs: the same, but an alternative for
nodeenv.
Nvm: the same.
Working in virtual environments
As I said I have experience as a backend developer with different
technologies. Nowadays, focused in python3, Django and Flask, but
with several other languages and architectures.

When you are working in several projects at the same time, you
need to be able to keep dependencies, libraries and modules
“contained”, so it is very typical to work on virtual environments.
This is where, in python scenarios, Virtualenv tool helps and saves a
lot of time.

Virtualenv is mainly a python tool that many people use to do a lot


of different things, mostly creating auto contained python projects
that will drag all the dependencies. But for the purposes of this
document, I will focus on creating a virtual environment to contain
Frida projects, test cases and crackmes, and keep them apart from
the real host that contains them.

Virtualenv: https://virtualenv.pypa.io/en/latest/

If you do not have virtualenv installed in your system, it is as easy


as installing using pip or pipx . The main difference is that pipx
will install virtualenv in an isolated environment avoiding potential
collisions and problems with the host system when updating or
upgrading. Naturally, pip / pipx are not the only tools you can use
to install virtualenv. You can use system package managers, wheel ,
sdist … And even, you can work with virtualenvs without installing
it, just invoking a zipapp , https://bootstrap.pypa.io/virtualenv.pyz.

If you use the zipapp , creating an env is as easy as:

frida~$ python virtualenv.pyz my-virtualenv

As I use virtualenv a lot in all my devices, I tend to perform a full


install in the real host with access for everyone; usually I’m jumping
from one user to another. My typical installation is through the
standard pip install:

frida~$ pip install virtualenv

A quick note here: when you have python2 and python3 living
together in the same host, you should understand there exist pip
and pip3 . As we are always working in virtualenvs , there is no
mixed environment, we only have python3. But you may face
scenarios where both versions are installed. Remember this snip
about pip / pip3 .

The, creating venvs is as easy as:

frida~$ virtualenv my-virtualenv-1

By routine, I use to add the “ -p python3 ” flags when invoking


virtualenv, as in:

frida~$ virtualenv -p python3 my-virtualenv-1

Python2 is not supported so far, so it is actually not necessary, and I


do it because I have a bad habit. Don’t be like Román, try to avoid
bad habits :) But: Be like Román, remember you may find mixed
environments with python2 and python3.

Now, to activate a virtualenv you just use “ source ” or “ . ”,


invoking the activate script in <your_virtual_env>/bin/activate. For
example:

frida~$ source my-virtualenv-for-node/bin/activate


frida~$ . my-virtualenv-for-node/bin/activate
Quick summary:

frida~$ virtualenv my-virtualenv-for-node


frida~$ source my-virtualenv-for-node/bin/activate
(my-virtualenv-for-node) frida~$

As said before, “ source ” or just using the dot ( . ), will activate the
env. In the moment you need to get out, just type “ deactivate ”:

(my-virtualenv-for-node) frida~$ deactivate


frida~$

When I started using virtual environments, I had no idea about the


“ deactivate ” call, so I tend to close the terminal directly. Don’t be
like Román, read about all the details of the tools you use.

Virtualenv is a fantastic tool, and we will use it extensively.

Nodeenv
Now that we have the knowledge about virtualenv and how it
works, let me introduce nodeenv. Is a powerful tool that will install
all the NodeJS dependencies for you. If you have not installed
node and its dependencies by yourself, you don’t have an idea of
how painful this is. And the worst is when you need different
releases and module versions to be able to support different projects.
Painful.

So, again, having virtual environments for your projects is a good


approach. For me is a must. Like in virtualenv, nodeenv will let you
do so.
You can install nodeenv using pip:

frida~$ pip install nodeenv

Or easy_ install , among several other tools. For example:

frida~$ sudo easy_install nodeenv

Now, you may be wondering why I talked previously about


virtualenv if we are going to work with nodeenv instead. This is
because nodeenv can be directly integrated with virtualenv. And this
is my typical scenario in my devices, I create a virtualenv first,
where I put all python packages (and even non python packages) I
may need, activate it, and then create different nodeenvs : normally,
one env for the stable, long term support (LTS, in the moment of
writing this book, 12.16.1) and another for the latest-dev (13.12.0).

Quick summary:

frida~$ virtualenv my-virtualenv-for-node


frida~$ source my-virtualenv-for-node/bin/activate
(my-virtualenv-for-node) frida~$ pip install nodeenv
Collecting nodeenv
Using cached nodeenv-1.5.0-py2.py3-none-any.whl (21 kB)
Installing collected packages: nodeenv
Successfully installed nodeenv-1.5.0

I’m within the python virtual environment, installing nodeenv


package. Now, let’s see some basic functions of nodeenv:

(my-virtualenv-for-node) frida~$ nodeenv --list


0.1.14 0.1.15 0.1.16 0.1.17 0.1.18 0.1.19 0.1.20 0.1.21
0.1.22 0.1.23 0.1.24 0.1.25 0.1.26 0.1.27 0.1.28 0.1.29
0.1.30 0.1.31 0.1.32 0.1.33 0.1.90 0.1.91 0.1.92 0.1.93
0.1.94 0.1.95 0.1.96 0.1.97 0.1.98 0.1.99 0.1.100 0.1.101
...
14.13.1 14.14.0 14.15.0 14.15.1 14.15.2 14.15.3 14.15.4 15.0.0
15.0.1 15.1.0 15.2.0 15.2.1 15.3.0 15.4.0 15.5.0 15.5.1
15.6.0 15.7.0
It will list all the supported versions for NodeJS that can be installed
with this awesome tool. Let’s create a LTS environment:

(my-virtualenv-for-node) frida~$ nodeenv --list


0.1.14 0.1.15 0.1.16 0.1.17 0.1.18
(my-virtualenv-for-node) frida~$ nodeenv --node=14.15.4 node14154
* Install prebuilt node (14.15.4) ..... done.
(my-virtualenv-for-node) frida~$ source node14154/bin/activate
(node14154) (my-virtualenv-for-node) frida~$

As you can see, our node version matches exactly what we


requested when creating the nodeenv. I absolutely love this tool: it
saves a lot of time for coders that are in the same step (newbie,
beginner) and do not have the (repeated) experience of installing a
professional NodeJS environment.

And you can do a lot of powerful things with the nodeenv options.
For example, we can set specific versions for npm or install iojs
instead of node . See some examples:

(my-virtualenv-for-node) frida~$ nodeenv --node=0.4.3 --npm=0.3.17


node043_npm0317
(my-virtualenv-for-node) frida~$ nodeenv --iojs --list
1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.1.0 1.2.0 1.3.0
1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2
1.6.3 1.6.4 1.7.1 1.8.1 1.8.2 1.8.3 1.8.4 2.0.0
2.0.1 2.0.2 2.1.0 2.2.0 2.2.1 2.3.0 2.3.1 2.3.2
2.3.3 2.3.4 2.4.0 2.5.0 3.0.0 3.1.0 3.2.0 3.3.0
3.3.1
...

The last options you need to know about nodeenv are the ones that
let you install from prebuilt packages or from source. As, again, I’m
mainly a newbie on Node development, my focus is not on making
custom installations (yet), so I’m always installing prebuilt
packages. But if you need to install something from source (for
example, to disable ssl support, as you can see in the nodeenv
examples) just:
(my-virtualenv-for-node) frida~$ nodeenv --node=0.10.25 --source --without-ssl
node01025_no_ssl

By default, it will install prebuilt, so you do not need to pass the


option, but to be exhaustive:

(my-virtualenv-for-node) frida~$ nodeenv --node=0.10.25 --prebuilt node01025

My suggestion is to create separate environments supporting your


specific needs. As I said, I tend to install one LTS and one
development “containers”.

Hope this chapter was useful for you and helped introduce
interesting information about having multiple development
environments with virtualenv and nodeenv .

You can find a lot of useful information and details, about nodeenv
package, on the PYPI site. The package is at:

https://pypi.org/project/nodeenv/

NVS

Another option for having multiple versions of node running in the


same host is nvs . You can find the project repo here:

https://github.com/jasongin/nvs

Nvs is a tool that is developed in JavaScript itself, and it makes it


easy to switch from one Node version to another. We will see later
that it might be a better option for us, if we are going to use
VisualStudio Code , as there are several detailed configurations to
take advantage of it.
Nvs can be installed system wide, this means, for every user in the
system, or locally for an specific user. I prefer the local installation
as one of my premises is having the minimum interaction with the
host system.

For a system wide installation:

$ export NVS_HOME="/usr/local/nvs"
$ git clone https://github.com/jasongin/nvs "$NVS_HOME"
$ . "$NVS_HOME/nvs.sh" install

For a local install in my user home:

$ export NVS_HOME="$HOME/.nvs"
$ git clone https://github.com/jasongin/nvs "$NVS_HOME"
$ . "$NVS_HOME/nvs.sh" install

For example, in my Linux box:

Image 1. Installing nvs in my local home

Now we can add node versions as needed. Let’s add the latest
version:
$ nvs add latest
Downloading
[#####################################################################]
100%
Extracting
[#####################################################################]
100%
Added at: ~/.nvs/node/13.12.0/x64/bin/node
To use this version now: nvs use node/13.12.0/x64
$

Or, if we prefer to use LTS (as in our case scenarios):

$ nvs add lts


Downloading
[#####################################################################]
100%
Extracting
[#####################################################################]
100%
Added at: ~/.nvs/node/14.15.4/x64/bin/node
To use this version now: nvs use node/14.15.4/x64
$

Now to use the version we want, just issue:

$ nvs use node/12.16.2/x64


PATH += ~/.nvs/node/12.16.2/x64/bin
$ node -v
v12.16.2

or:

$ nvs use node/13.12.0/x64


PATH -= ~/.nvs/node/12.16.2/x64/bin
PATH += ~/.nvs/node/13.12.0/x64/bin
$ node -v
v13.12.0
$
Note the line in bold-red above, as when we were using an already
set version of node, nvs will remove it (minus, -) and then set the
new desired one.

So, we achieved the same as with virtualenv/nodeenv in a quick,


easy way. There are many options in nvs , but I like this one that
creates a menu:

$ nvs menu
.--------------------------------.
| Select a version |
+--------------------------------+
| [a] node/13.12.0/x64 [current] |
| b) node/12.16.2/x64 (Erbium) |
| |
| ,) Download another version |
| .) Don't use any version |
'--------------------------------'
Type a hotkey or use Down/Up arrows then Enter to choose an item.

Visual Studio Code can use NVS . It makes an excellent tool to


select a specific node version when launching or debugging. See in
the Chapter 6 the details about how to integrate properly nvs with
VSCode through launch.json .

NVM

And, finally, our last option is to install nvm :

https://github.com/nvm-sh/nvm

The process is quite easy. You should use the official install script
that we can invoke in a one-liner command:

$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash


Image 2. Installing nvm with the install script

Please, close the terminal and open it again, so you make sure will
reload your configurations (or issue a “ source .bash_profile ” if you
are working with such file).

We can verify nvm is available:

$ command -v nvm
nvm
$

The nvm output confirms, so everything is ok so far.

Exactly in the same way as with nvs , we can install node versions
right now:

$ nvm install node


Downloading and installing node v13.12.0...
Downloading https://nodejs.org/dist/v13.12.0/node-v13.12.0-linux-x64.tar.xz...
#####################################################################
100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v13.12.0 (npm v6.14.4)
Creating default alias: default -> node (-> v13.12.0)
$

Or install specific versions with “ nvm install 0.1.4 ”. To know what


versions are available, you do so with “ls-remote” that lists
compiled versions:

$ nvm ls-remote
v0.1.14
v0.1.15
V0.1.16
...
...
iojs-v1.0.0
iojs-v1.0.1
iojs-v1.0.2
...
...
v6.16.0 (LTS: Boron)
v6.17.0 (LTS: Boron)
v6.17.1 (Latest LTS: Boron)
v7.0.0
...
...
v13.10.1
v13.11.0
-> v13.12.0

The, to use your “virtual” environment just issue:

$ nvm use node


manpath: can't set the locale; make sure $LC_* and $LANG are correct
Now using node v13.12.0 (npm v6.14.4)
$

You can ignore the warning in bold-red, as this is because I’m


mangling environment variables to be able to switch from “ es_ES ”
to “ en_US ” and capture the screenshots ;)
When I started this text, my preferred solution was nodeenv . Now,
I tend to use more and more nvm , as it is easier for me to manage
quick installations. But the specific tool is not relevant apart from
specific use cases, the hot topic here is the capability of running
virtual, isolated, environments from different purposes and
applications.
CHAPTER 2. FIRST STEPS WITH FRIDA

From here, we will start with a brief introduction of what Frida is. In their
website (https://frida.re/) they have a definition: Dynamic instrumentation
toolkit for developers, reverse-engineers, and security researchers.

The history behind this awesome project can be found here,


https://frida.re/docs/history/, and I recommend reading it, as it is not only
revealing, but will introduce you to other tools and concepts. I want to
congratulate and thank Ole André V. Ravnås for this amazing framework,
tool, swiss knife, and a lot of other compliments I can send.

With Frida we can instrument applications settings watches, hooks and


breakpoints on several points, even logging and modifying messages, buffers
and, practically any functionality of the application. For almost every relevant
operating system we may look for.

The very first step is, naturally, to install Frida. And now we have a question:
what to install, Frida or frida-tools package? (quick answer: frida-tools by
now).

When I started learning about Frida, this was my first stopper, as in many
sources they pointed to one or another. I took the decision of trying both
approaches and understanding what was happening in the guts.

See when installing Frida alone:

(frida) frida~$ pip install frida


Collecting frida
Downloading frida-14.2.8.tar.gz (7.5 kB)
Building wheels for collected packages: frida
Building wheel for frida (setup.py) ... done
Created wheel for frida: filename=frida-14.2.8-cp38-cp38-linux_x86_64.whl size=20902659
sha256=96ccb65551d4f726bd60fd1660a99a92a381a3f57bf9d4aefe3322025c5b378c
Stored in directory:
/home/user/.cache/pip/wheels/92/02/19/2cf1b7e5c6a8bd761f3140fd5ed602ffb16f9aed38b70ec054
Successfully built frida
Installing collected packages: frida
Successfully installed frida-14.2.8
(frida) frida~$ frida
frida: command not found
So, maybe this was not the best choice :) See what happens if we install
frida-tools instead:

frida~$ . venvs/frida-tools/bin/activate
(frida-tools) frida~$ pip install frida-tools
Collecting frida-tools
Using cached frida-tools-9.1.0.tar.gz (35 kB)
Collecting colorama<1.0.0,>=0.2.7
Using cached colorama-0.4.4-py2.py3-none-any.whl (16 kB)
Collecting frida<15.0.0,>=14.2.0
Using cached frida-14.2.8-cp38-cp38-linux_x86_64.whl
Collecting prompt-toolkit<4.0.0,>=2.0.0
Downloading prompt_toolkit-3.0.14-py3-none-any.whl (359 kB)
|████████████████████████████████| 359 kB 8.7 MB/s
Collecting pygments<3.0.0,>=2.0.2
Using cached Pygments-2.7.4-py3-none-any.whl (950 kB)
Collecting wcwidth
Downloading wcwidth-0.2.5-py2.py3-none-any.whl (30 kB)
Building wheels for collected packages: frida-tools
Building wheel for frida-tools (setup.py) ... done
Created wheel for frida-tools: filename=frida_tools-9.1.0-py3-none-any.whl size=38578
sha256=8f35176979c5b26d9e83a95520d575d87d36fca262d977e1ecae181193cecf53
Stored in directory:
/home/user/.cache/pip/wheels/b5/da/8c/2bdeaf1dac67122233b93ecbe4ae4114c6c1546cf374b1e0ef
Successfully built frida-tools
Installing collected packages: wcwidth, pygments, prompt-toolkit, frida, colorama, frida-tools
Successfully installed colorama-0.4.4 frida-14.2.8 frida-tools-9.1.0 prompt-toolkit-3.0.14
pygments-2.7.4 wcwidth-0.2.5
(frida-tools) frida~$ frida
Usage: frida [options] target

frida: error: target file, process name or pid must be specified

This time it seems we were successful. Frida exists as a command in the


environment but, more important, among other packages installed, when
issued an install for frida-tools the Frida package is installed too, so we can
skip the previous scenario where we installed Frida alone (and remove all
these tutorials that start with this package).

The typical actions here to verify everything works as expected:

(frida-tools) frida~$ frida --version


14.2.8
(frida-tools) frida~$

frida-tools installs frida 14.2.8. Now this book was written,


the latest version was 14.2.12. You may update it after the
installation of frida-tools.
(frida-tools) frida~$ pip install frida==14.2.12
Collecting frida==14.2.12
Downloading frida-14.2.12.tar.gz (7.6 kB)
Building wheels for collected packages: frida
Building wheel for frida (setup.py) ... done
Created wheel for frida: filename=frida-14.2.12-cp38-cp38-linux_x86_64.whl size=20913140
sha256=c1f3959278343e2c0820e6240e5bb260be65c8ad26bb608aedf63f920b97ee8d
Stored in directory:
/home/user/.cache/pip/wheels/08/dd/f7/b5c7005122dd4a1f524bf0d727d01932c89466c1ddc22fc616
Successfully built frida
Installing collected packages: frida
Attempting uninstall: frida
Found existing installation: frida 14.2.8
Uninstalling frida-14.2.8:
Successfully uninstalled frida-14.2.8
Successfully installed frida-14.2.12
(frida-tools) frida~$

We can test frida-ps , for example, one of the tools on the set:

(frida-tools) frida~$ frida-ps


PID Name
------ -----------------------------
4083 Enpass
3589 at-spi-bus-launcher
3666 at-spi2-registryd
143881 bash
149637 bash
3385 dbus-daemon
...
3925 xdg-document-portal
3670 xdg-permission-store
(frida-tools) frida~$

List of processes with process id to attach if we want to do so.

The explanation:
Frida package is just the python bindings for Frida . If you
install it, you can be able to do almost everything from a
Python script, invoking all Frida capabilities.
frida-tools : is the CLI tools package. Yes, it will install
Python bindings and also several interesting tools that will
make our life happier.

If you want to install directly from binary releases or are curious (good!)
about the inners of the project, please, visit their GitHub repo at:
https://github.com/frida/.

The main reason that I made the introduction about nodeenv , nvm and nvs
is because you can install NodeJS bindings too ( npm install frida ). We will
work in some approaches to this in a specific chapter.
Frida command line

If you want to start directly playing with the tools and


do actual stuff, skip this section and advance to “Simple
Linux application”.

I was not sure where to put this section. If here, a lot of


options that we are not using until several chapters have
passed, will appear. This may lead to confusion or an
excess of information that might not be necessary. But,
if not here, I may cite options and concepts or, worse, I
can assume you are familiar with specific details that
you are not.

I will make a quick and brief description of the


command line options, and we will be using them along
the book. If you miss something, please, let me know.

(frida-tools) frida4 ~$ frida


Usage: frida [options] target

frida: error: target file, process name or pid must be specified


(frida-tools) frida4 ~$

If we execute Frida alone, this is the output we get. We


need to pass a target file, a process name or a process
pid to start.

See with --help :

(frida-tools) frida4 ~$ frida --help


Usage: frida [options] target

Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-F, --attach-frontmost
attach to frontmost application
-n NAME, --attach-name=NAME
attach to NAME
-p PID, --attach-pid=PID
attach to PID
--stdio=inherit|pipe stdio behavior when spawning (defaults to
"inherit")
--aux=option set aux option when spawning, such as "uid=
(int)42"
(supported types are: string, bool, int)
--realm=native|emulated
realm to attach in
--runtime=qjs|v8 script runtime to use
--debug enable the Node.js compatible script debugger
--squelch-crash if enabled, will not dump crash report to console
-O FILE, --options-file=FILE
text file containing additional command line options
-l SCRIPT, --load=SCRIPT
load SCRIPT
-P PARAMETERS_JSON, --parameters=PARAMETERS_JSON
parameters as JSON, same as Gadget
-C CMODULE, --cmodule=CMODULE
load CMODULE
-c CODESHARE_URI, --codeshare=CODESHARE_URI
load CODESHARE_URI
-e CODE, --eval=CODE evaluate CODE
-q quiet mode (no prompt) and quit after -l and -e
--no-pause automatically start main thread after startup
-o LOGFILE, --output=LOGFILE
output to log file
--eternalize eternalize the script before exit
--exit-on-error exit with code 1 after encountering any
exception in
the SCRIPT
(frida-tools) frida4 ~$

A quick review of the potential arguments:

--version: shows the version.


-h or --help: this output.
-D ID or --device=ID: connect to the device
with this ID. Will show details when playing
with Android apps.
-U: use usb mode. If more than one device is
available, it will not work and you will need
to issue a -D to select one.
-R or --remote: to connect to a remote frida-
server. We will use this one in many
examples. Is a very powerful tool.
-H HOST or --host=HOST: when connecting
to a remote frida-server. The format is <ip
address>:<port>.
-f FILE or --file=FILE: spawns (executes
from within frida) a file.
-F or --attach-frontmost: will attach to the
frontmost application in mobile devices.
This means the application on top (it might
be, for example, Google now or google
search, be careful doing assumptions of what
is in front). Does not work on local systems.
-n NAME or --name=NAME: attach to the
process with this name.
-p PID or --attach-pid=PID: attach to this
process PID.
--stdio=inherit or pipe: stdio behavior when
spawning.
--aux=option: set aux option when
spawning, such as “uid=(int)42” (supported
types are string, bool, int).
--realm=native|emulated: on mobile devices
you may select native or emulated (running
the app not with native code).
--runtime=qjs|v8: select JavaScript runtime,
v8 or QuickJS. QuickJS is the default in
latest versions, but you may need to use v8
for old applications that may not support
ES2020 (EcmaScript 2020). In newer
versions of Frida qjs is replaced by duq.
--debug: will show extra debug information
(for example, where the Chrome inspector
server is running, etc).
--squelch-crash: will silence crash reports on
the console.
-O FILE or --options-file=FILE: you can
load extra arguments from this file.
-l SCRIPT or --load=SCRIPT: load a
JavaScript script from this file. Very
powerful option to keep things organized
and our chores right.
-P PARAMETERS_JSON or --
parameters=PARAMETERS_JSON: you can
pass parameters to the JavaScript
environment using JSON. Will be in the
format of -P ‘{“parm1”: “value1”}’ and
available as parameters.parm1.
-C CMODULE, --cmodule=CMODULE:
-c CODESHARE_URI, --
codeshare=CODESHARE_URI: you can
load REMOTE scripts from Frida’s
codeshare here. We will see details in
chapters ahead.
-e CODE or --eval=CODE: if you pass
JavaScript code there it will be evaluated
when running the session.
-q: quiet mode. It will exist after -l or -e,
instead of going to the interactive cli.
--no-pause: continue running the process
instead of landing on the frida cli suspending
it and asking for you to issue a %resume.
-o LOGFILE, --output=LOGFILE: save to
this file all the output.
--eternalize: you can choose not to exit from
the agent on, for example, frida-server, so
the agent will still be available for new
sessions.
--exit-on-error: will exit on any exception
within the script.
Simple Linux application

We will attach to a running application called


“ simple0 ” (source code is available in the repository).
You may attach to whichever the app you prefer (for
example “ cat ” as the Frida documentation shows) but
our simple application is very descriptive or every step
taken, so you can press a key and see the effect on the
Frida .

Run our simple0 application or the application you


selected (take note that you will need to replace
simple0 name within the script). Then run this script:
simple0.py .

See the result on our execution:

(frida-tools) frida~$ python simple0.py


Traceback (most recent call last):
File "simple0.py", line 6, in <module>
session = frida.attach("simple0")
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/__init__.py", line 62, in attach
return get_local_device().attach(target, *args, **kwargs)
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/core.py", line 26, in wrapper
return f(*args, **kwargs)
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/core.py", line 156, in attach
return Session(self._impl.attach(self._pid_of(target), *args,
**kwargs))
frida.PermissionDeniedError: unable to access process with pid
150592 due to system restrictions; try `sudo sysctl
kernel.yama.ptrace_scope=0`, or run Frida as root
We do not have permissions to attach to processes that
are not our direct children. We have two options here,
one to elevate to root or, the other, apply a change to the
ptrace_scope to set it to 0. The default value in 1 for
ptrace_scope is a security feature in Linux kernel:
disallows the possibility of attaching to processes not
directly created by us (our children).

If we change the kernel variable, we will be able not


only to attach to our children but other running
processes.

What to do? I do not have an answer. On one hand, I


dislike running things as root, nothing to say about
running debug and hacking tools with this privilege
level. On the other hand, I dislike modifying the kernel
security features, even if for just some minutes. But
having to decide, I prefer setting the ptrace_scope to 0
and NOT making the change persistent, so in the next
boot it will return back to the safe original level.

(frida-tools) frida~$ sudo sysctl kernel.yama.ptrace_scope=0


[sudo] password for user:
kernel.yama.ptrace_scope = 0
(frida-tools) frida~$

Try again to execute the script:

(frida-tools) frida~$ python simple0.py


['simple0', 'linux-vdso.so.1', 'libc-2.31.so', 'ld-2.31.so', 'libpthread-
2.31.so', 'frida-agent-64.so', 'libdl-2.31.so', 'libresolv-2.31.so', 'librt-
2.31.so', 'libm-2.31.so']
(frida-tools) frida~$

Not sure if you know the command ldd , but the output
is very similar:

(frida-tools) frida~$ ldd simple0


linux-vdso.so.1 (0x00007ffeccafb000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7b145e3000)
/lib64/ld-linux-x86-64.so.2 (0x00007f7b147f8000)
(frida-tools) frida~$

Here ldd details include the base address for the


module (we can do the same using
module.base.toString() as we will see later) but the list
is mostly the same, with some differences:

libc-2.31.so vs libc.so.6: Frida shows the real


name of the library, but ldd shows the name
used by the linker when the binary was created
(is a symbolic link).
ld-2.31.so vs ld-linux-x86-64.so.2: exactly the
same.

Humm, so we confirmed a couple of libraries here. And


the others? In dynamic binaries, ld.so may load
additional libraries in runtime. Those libraries may not
appear on linkers information directly, as may be loaded
later.

Let me check the difference between simple0 and


simple0_static :
(frida-tools) frida~$ ldd simple0_static
not a dynamic executable
(frida-tools) frida~$

Ok, ldd states that simple0_static is not a dynamic


executable, so not much information here to solve the
mystery. And what happens with Frida? Use the
alternate script simple0_static.py (remember to execute
the simple0_static binary to be able to act on it):

(frida-tools) frida~$ python simple0_static.py


Traceback (most recent call last):
File "simple0_static.py", line 6, in <module>
session = frida.attach("simple0_static")
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/__init__.py", line 62, in attach
return get_local_device().attach(target, *args, **kwargs)
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/core.py", line 26, in wrapper
return f(*args, **kwargs)
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/core.py", line 156, in attach
return Session(self._impl.attach(self._pid_of(target), *args,
**kwargs))
frida.NotSupportedError: unable to inject library into process
without libc
(frida-tools) frida~$

I will introduce the use of another tool called objdump :

(frida-tools) frida~$ objdump -p simple0 | grep 'NEEDED'


NEEDED libc.so.6
We are not lucky here. When we search for the string
“ NEEDED ” on the objdump output we will find the
imports associated with the binary. Let’s test in the static
one:

(frida-tools) frida~$ objdump -p simple0_static | grep 'NEEDED'


(frida-tools) frida~$

Oh. No output. This is not helping, right? What to do


next? /proc is here to save us :) One of the reasons to
create simple0 / simple0_static this way was to keep
them running while performing tricks with Frida and
other tools. As the process is still in operation, we must
have its information in /proc/<pid>/… First step,
identify the process PID.

(frida-tools) frida~$ ps ax|grep simple


3082 tty1 Sl 0:00 /usr/libexec/ibus-engine-simple
3835 ? Sl 0:09 /usr/libexec/ibus-engine-simple
209274 pts/1 S+ 0:00 ./simple0_static
209672 pts/0 S+ 0:00 grep --color=auto simple
(frida-tools) frida~$

You can issue a directory listing there:

(frida-tools) frida~$ ls -la /proc/209274


total 0
dr-xr-xr-x 9 user user 0 Jan 30 19:11 .
dr-xr-xr-x 468 root root 0 Jan 28 07:49 ..
-r--r--r-- 1 user user 0 Jan 30 19:21 arch_status
dr-xr-xr-x 2 user user 0 Jan 30 19:21 attr
-rw-r--r-- 1 user user 0 Jan 30 19:21 autogroup
-r-------- 1 user user 0 Jan 30 19:11 auxv
-r--r--r-- 1 user user 0 Jan 30 19:21 cgroup
--w------- 1 user user 0 Jan 30 19:21 clear_refs
-r--r--r-- 1 user user 0 Jan 30 19:11 cmdline
-rw-r--r-- 1 user user 0 Jan 30 19:21 comm
-rw-r--r-- 1 user user 0 Jan 30 19:21 coredump_filter
-r--r--r-- 1 user user 0 Jan 30 19:21 cpu_resctrl_groups
-r--r--r-- 1 user user 0 Jan 30 19:21 cpuset
lrwxrwxrwx 1 user user 0 Jan 30 19:21 cwd -> /home/user/Frida/0
-r-------- 1 user user 0 Jan 30 19:21 environ
lrwxrwxrwx 1 user user 0 Jan 30 19:11 exe ->
/home/user/Frida/0/simple0_static
dr-x------ 2 user user 0 Jan 30 19:21 fd
dr-x------ 2 user user 0 Jan 30 19:21 fdinfo
-rw-r--r-- 1 user user 0 Jan 30 19:21 gid_map
-r-------- 1 user user 0 Jan 30 19:21 io
-r--r--r-- 1 user user 0 Jan 30 19:21 limits
-rw-r--r-- 1 user user 0 Jan 30 19:21 loginuid
dr-x------ 2 user user 0 Jan 30 19:21 map_files
-r--r--r-- 1 user user 0 Jan 30 19:11 maps
-rw------- 1 user user 0 Jan 30 19:21 mem
-r--r--r-- 1 user user 0 Jan 30 19:21 mountinfo
-r--r--r-- 1 user user 0 Jan 30 19:21 mounts
-r-------- 1 user user 0 Jan 30 19:21 mountstats
dr-xr-xr-x 62 user user 0 Jan 30 19:21 net
dr-x--x--x 2 user user 0 Jan 30 19:21 ns
-r--r--r-- 1 user user 0 Jan 30 19:21 numa_maps
-rw-r--r-- 1 user user 0 Jan 30 19:21 oom_adj
-r--r--r-- 1 user user 0 Jan 30 19:21 oom_score
-rw-r--r-- 1 user user 0 Jan 30 19:21 oom_score_adj
-r-------- 1 user user 0 Jan 30 19:21 pagemap
-r-------- 1 user user 0 Jan 30 19:21 patch_state
-r-------- 1 user user 0 Jan 30 19:21 personality
-rw-r--r-- 1 user user 0 Jan 30 19:21 projid_map
lrwxrwxrwx 1 user user 0 Jan 30 19:21 root -> /
-rw-r--r-- 1 user user 0 Jan 30 19:21 sched
-r--r--r-- 1 user user 0 Jan 30 19:21 schedstat
-r--r--r-- 1 user user 0 Jan 30 19:21 sessionid
-rw-r--r-- 1 user user 0 Jan 30 19:21 setgroups
-r--r--r-- 1 user user 0 Jan 30 19:21 smaps
-r--r--r-- 1 user user 0 Jan 30 19:21 smaps_rollup
-r-------- 1 user user 0 Jan 30 19:21 stack
-r--r--r-- 1 user user 0 Jan 30 19:11 stat
-r--r--r-- 1 user user 0 Jan 30 19:21 statm
-r--r--r-- 1 user user 0 Jan 30 19:11 status
-r-------- 1 user user 0 Jan 30 19:21 syscall
dr-xr-xr-x 3 user user 0 Jan 30 19:21 task
-rw-r--r-- 1 user user 0 Jan 30 19:21 timens_offsets
-r--r--r-- 1 user user 0 Jan 30 19:21 timers
-rw-rw-rw- 1 user user 0 Jan 30 19:21 timerslack_ns
-rw-r--r-- 1 user user 0 Jan 30 19:21 uid_map
-r--r--r-- 1 user user 0 Jan 30 19:21 wchan
(frida-tools) frida~$

The relevant file here is “maps”. See contents:

(frida-tools) frida~$ cat /proc/209274/maps


00400000-00401000 r--p 00000000 fd:05 15073282
/home/user/Frida/0/simple0_static
00401000-00495000 r-xp 00001000 fd:05 15073282
/home/user/Frida/0/simple0_static
00495000-004bc000 r--p 00095000 fd:05 15073282
/home/user/Frida/0/simple0_static
004bd000-004c0000 r--p 000bc000 fd:05 15073282
/home/user/Frida/0/simple0_static
004c0000-004c3000 rw-p 000bf000 fd:05 15073282
/home/user/Frida/0/simple0_static
004c3000-004c4000 rw-p 00000000 00:00 0
00f13000-00f36000 rw-p 00000000 00:00 0 [heap]
7ffd5394e000-7ffd5396f000 rw-p 00000000 00:00 0
[stack]
7ffd539e9000-7ffd539ed000 r--p 00000000 00:00 0
[vvar]
7ffd539ed000-7ffd539ef000 r-xp 00000000 00:00 0
[vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0
[vsyscall]
(frida-tools) frida~$
Oh, forgot this was the static version. Stop
simple0_static and, please, run again simple0 . Then,
find the process PID and show maps contents.

(frida-tools) frida2 ~$ cat /proc/210659/maps


5622bb246000-5622bb247000 r--p 00000000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb247000-5622bb248000 r-xp 00001000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb248000-5622bb249000 r--p 00002000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb249000-5622bb24a000 r--p 00002000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb24a000-5622bb24b000 rw-p 00003000 fd:05
15078269 /home/user/Frida/0/simple0
5622bb839000-5622bb85a000 rw-p 00000000 00:00 0
[heap]
7f1aefa3b000-7f1aefa60000 r--p 00000000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa60000-7f1aefbd8000 r-xp 00025000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefbd8000-7f1aefc22000 r--p 0019d000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc22000-7f1aefc23000 ---p 001e7000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc23000-7f1aefc26000 r--p 001e7000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc26000-7f1aefc29000 rw-p 001ea000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc29000-7f1aefc2f000 rw-p 00000000 00:00 0
7f1aefc4b000-7f1aefc4c000 r--p 00000000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc4c000-7f1aefc6f000 r-xp 00001000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc6f000-7f1aefc77000 r--p 00024000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc78000-7f1aefc79000 r--p 0002c000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc79000-7f1aefc7a000 rw-p 0002d000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc7a000-7f1aefc7b000 rw-p 00000000 00:00 0
7fffeaec7000-7fffeaee8000 rw-p 00000000 00:00 0
[stack]
7fffeaf62000-7fffeaf66000 r--p 00000000 00:00 0 [vvar]
7fffeaf66000-7fffeaf68000 r-xp 00000000 00:00 0
[vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0
[vsyscall]
(frida-tools) frida2 ~$

Perfect. Now this looks different. If you see, now we


have an inventory of the process map in memory and,
yes, we will reach the goal right now and see the light!
(get the answer). Now, execute Frida again, but with a
slightly modified version of the script. Use
simple0_wait_key.py :

(frida-tools) frida~$ python simple0_wait_key.py


['simple0', 'linux-vdso.so.1', 'libc-2.31.so', 'ld-2.31.so', 'libpthread-
2.31.so', 'frida-agent-64.so', 'libdl-2.31.so', 'libresolv-2.31.so', 'librt-
2.31.so', 'libm-2.31.so']

Press a key to stop this process and detach.

Here Frida is waiting for a key before detaching from


the process. Check now the contents of “maps” and pay
attention to what seems different:

(frida-tools) frida2 ~$ cat /proc/210659/maps


5622bb246000-5622bb247000 r--p 00000000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb247000-5622bb248000 r-xp 00001000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb248000-5622bb249000 r--p 00002000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb249000-5622bb24a000 r--p 00002000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb24a000-5622bb24b000 rw-p 00003000 fd:05
15078269 /home/user/Frida/0/simple0
5622bb839000-5622bb85a000 rw-p 00000000 00:00 0
[heap]
7f1ad4000000-7f1ad4021000 rw-p 00000000 00:00 0
7f1ad4021000-7f1ad8000000 ---p 00000000 00:00 0
7f1adc000000-7f1adc021000 rw-p 00000000 00:00 0
7f1adc021000-7f1ae0000000 ---p 00000000 00:00 0
7f1ae0000000-7f1ae0021000 rw-p 00000000 00:00 0
7f1ae0021000-7f1ae4000000 ---p 00000000 00:00 0
7f1ae6ffe000-7f1ae6fff000 ---p 00000000 00:00 0
7f1ae6fff000-7f1ae77ff000 rw-p 00000000 00:00 0
7f1ae77ff000-7f1ae7800000 ---p 00000000 00:00 0
7f1ae7800000-7f1ae8000000 rw-p 00000000 00:00 0
7f1ae8000000-7f1ae8021000 rw-p 00000000 00:00 0
7f1ae8021000-7f1aec000000 ---p 00000000 00:00 0
7f1aec0b2000-7f1aec132000 rw-p 00000000 00:00 0
7f1aec132000-7f1aec133000 ---p 00000000 00:00 0
7f1aec133000-7f1aec973000 rw-p 00000000 00:00 0
7f1aec973000-7f1aec974000 ---p 00000000 00:00 0
7f1aec974000-7f1aed174000 rw-p 00000000 00:00 0
7f1aed174000-7f1aed175000 ---p 00000000 00:00 0
7f1aed175000-7f1aed9d5000 rw-p 00000000 00:00 0
7f1aed9d5000-7f1aed9e4000 r--p 00000000 fd:05 9832583
/usr/lib/x86_64-linux-gnu/libm-2.31.so
7f1aed9e4000-7f1aeda8b000 r-xp 0000f000 fd:05 9832583
/usr/lib/x86_64-linux-gnu/libm-2.31.so
7f1aeda8b000-7f1aedb22000 r--p 000b6000 fd:05 9832583
/usr/lib/x86_64-linux-gnu/libm-2.31.so
7f1aedb22000-7f1aedb23000 r--p 0014c000 fd:05 9832583
/usr/lib/x86_64-linux-gnu/libm-2.31.so
7f1aedb23000-7f1aedb24000 rw-p 0014d000 fd:05 9832583
/usr/lib/x86_64-linux-gnu/libm-2.31.so
7f1aedb24000-7f1aedb28000 r--p 00000000 fd:05 9832595
/usr/lib/x86_64-linux-gnu/libresolv-2.31.so
7f1aedb28000-7f1aedb38000 r-xp 00004000 fd:05 9832595
/usr/lib/x86_64-linux-gnu/libresolv-2.31.so
7f1aedb38000-7f1aedb3b000 r--p 00014000 fd:05 9832595
/usr/lib/x86_64-linux-gnu/libresolv-2.31.so
7f1aedb3b000-7f1aedb3c000 ---p 00017000 fd:05 9832595
/usr/lib/x86_64-linux-gnu/libresolv-2.31.so
7f1aedb3c000-7f1aedb3d000 r--p 00017000 fd:05 9832595
/usr/lib/x86_64-linux-gnu/libresolv-2.31.so
7f1aedb3d000-7f1aedb3e000 rw-p 00018000 fd:05 9832595
/usr/lib/x86_64-linux-gnu/libresolv-2.31.so
7f1aedb3e000-7f1aedb40000 rw-p 00000000 00:00 0
7f1aedb40000-7f1aef103000 r-xp 00000000 fd:05 14942648
/tmp/frida-b180ae47172a1399a77c0aa35d152e45/frida-agent-
64.so
7f1aef103000-7f1aef18d000 r--p 015c2000 fd:05 14942648
/tmp/frida-b180ae47172a1399a77c0aa35d152e45/frida-agent-
64.so
7f1aef18d000-7f1aef1a5000 rw-p 0164c000 fd:05 14942648
/tmp/frida-b180ae47172a1399a77c0aa35d152e45/frida-agent-
64.so
7f1aef1a5000-7f1aef1fb000 rw-p 00000000 00:00 0
7f1aef1fb000-7f1aef1fc000 ---p 00000000 00:00 0
7f1aef1fc000-7f1aef9fc000 rw-p 00000000 00:00 0
7f1aef9fc000-7f1aefa03000 r--p 00000000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa03000-7f1aefa11000 r-xp 00007000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa11000-7f1aefa12000 rwxp 00015000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa12000-7f1aefa14000 r-xp 00016000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa14000-7f1aefa19000 r--p 00018000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa19000-7f1aefa1a000 r--p 0001c000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa1a000-7f1aefa1b000 rw-p 0001d000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa1b000-7f1aefa1f000 rw-p 00000000 00:00 0
7f1aefa23000-7f1aefa33000 rw-p 00000000 00:00 0
7f1aefa33000-7f1aefa34000 r--p 00000000 00:00 0
7f1aefa34000-7f1aefa3b000 rwxp 00000000 00:00 0
7f1aefa3b000-7f1aefa60000 r--p 00000000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa60000-7f1aefa61000 rwxp 00025000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa61000-7f1aefa81000 r-xp 00026000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa81000-7f1aefa82000 rwxp 00046000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa82000-7f1aefa84000 r-xp 00047000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa84000-7f1aefa85000 rwxp 00049000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa85000-7f1aefb21000 r-xp 0004a000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefb21000-7f1aefb22000 rwxp 000e6000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefb22000-7f1aefbd8000 r-xp 000e7000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefbd8000-7f1aefc22000 r--p 0019d000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc22000-7f1aefc23000 ---p 001e7000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc23000-7f1aefc26000 r--p 001e7000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc26000-7f1aefc29000 rw-p 001ea000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc29000-7f1aefc2f000 rw-p 00000000 00:00 0
7f1aefc2f000-7f1aefc30000 r--p 00000000 00:00 0
7f1aefc30000-7f1aefc31000 rw-p 00000000 00:00 0
7f1aefc31000-7f1aefc32000 r--p 00000000 00:00 0
7f1aefc32000-7f1aefc33000 rw-p 00000000 00:00 0
7f1aefc33000-7f1aefc34000 r--p 00000000 00:00 0
7f1aefc34000-7f1aefc35000 rw-p 00000000 00:00 0
7f1aefc35000-7f1aefc38000 r--p 00000000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc38000-7f1aefc3c000 r-xp 00003000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc3c000-7f1aefc3d000 r--p 00007000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc3d000-7f1aefc3e000 ---p 00008000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc3e000-7f1aefc3f000 r--p 00008000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc3f000-7f1aefc40000 rw-p 00009000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc40000-7f1aefc41000 r--p 00000000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc41000-7f1aefc43000 r-xp 00001000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc43000-7f1aefc44000 r--p 00003000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc44000-7f1aefc45000 r--p 00003000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc45000-7f1aefc46000 rw-p 00004000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc46000-7f1aefc47000 r-xp 00000000 00:00 0
7f1aefc47000-7f1aefc48000 rw-p 00000000 00:00 0
7f1aefc48000-7f1aefc49000 ---p 00000000 00:00 0
7f1aefc49000-7f1aefc4b000 rw-p 00000000 00:00 0
7f1aefc4b000-7f1aefc4c000 r--p 00000000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc4c000-7f1aefc6f000 r-xp 00001000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc6f000-7f1aefc77000 r--p 00024000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc78000-7f1aefc79000 r--p 0002c000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc79000-7f1aefc7a000 rw-p 0002d000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc7a000-7f1aefc7b000 rw-p 00000000 00:00 0
7fffeaec7000-7fffeaee8000 rw-p 00000000 00:00 0
[stack]
7fffeaf62000-7fffeaf66000 r--p 00000000 00:00 0 [vvar]
7fffeaf66000-7fffeaf68000 r-xp 00000000 00:00 0
[vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0
[vsyscall]
(frida-tools) frida2 ~$

If we compare the libraries, we can identify some that


are new into the process environment. Please, take note I
will list here only the NEW libraries Frida injected (yes,
the difference in the results from one tool to another is
because Frida does some stuff on the process to inject
itself):

libm-2.31.so: curious. Libm includes the


implementation of the references from math.h.
So, mathematical functions?
libresolv-2.31.so: is the resolver library.
Domain names to IP and all related
capabilities..
libpthread-2.31.so: is the POSIX threading
library. So now, our simple application is
“thread aware” thanks to Frida.
librt-2.31.so: POSIX.1b Realtime Extensions
library. Makes sense, as Frida is going to use
several realtime functions for sure.
libdl-2.31.so: is the dynamic linking library.
Frida is injecting this lib so it can load in
runtime whichever library may be a
requirement to perform actions.
frida-agent-64.so: this is obviously the Frida
injected element.
We have the answer. Ldd and objdump may give
interesting information about the dynamic linking on a
binary but process “ maps ” (remember
/proc/PID/maps ) is always better. Additionally. Frida
injects several libraries onto the process that are not
directly related to it.

A good recommendation if you need to get process state


and information, is to always get the process
information BEFORE doing anything with Frida and
keep it safe to be able to compare.

Finally, get out of the simple0_wait_key.py and see


what happens with the process:

(frida-tools) frida2 ~$ cat /proc/210659/maps


5622bb246000-5622bb247000 r--p 00000000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb247000-5622bb248000 r-xp 00001000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb248000-5622bb249000 r--p 00002000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb249000-5622bb24a000 r--p 00002000 fd:05 15078269
/home/user/Frida/0/simple0
5622bb24a000-5622bb24b000 rw-p 00003000 fd:05
15078269 /home/user/Frida/0/simple0
5622bb839000-5622bb85a000 rw-p 00000000 00:00 0
[heap]
7f1ad4000000-7f1ad4021000 rw-p 00000000 00:00 0
7f1ad4021000-7f1ad8000000 ---p 00000000 00:00 0
7f1adc000000-7f1adc021000 rw-p 00000000 00:00 0
7f1adc021000-7f1ae0000000 ---p 00000000 00:00 0
7f1ae0000000-7f1ae0021000 rw-p 00000000 00:00 0
7f1ae0021000-7f1ae4000000 ---p 00000000 00:00 0
7f1ae67fd000-7f1ae67fe000 ---p 00000000 00:00 0
7f1ae67fe000-7f1ae6ffe000 rw-p 00000000 00:00 0
7f1ae8000000-7f1ae8021000 rw-p 00000000 00:00 0
7f1ae8021000-7f1aec000000 ---p 00000000 00:00 0
7f1aec132000-7f1aec133000 ---p 00000000 00:00 0
7f1aec133000-7f1aec933000 rw-p 00000000 00:00 0
7f1aec973000-7f1aec974000 ---p 00000000 00:00 0
7f1aec974000-7f1aed174000 rw-p 00000000 00:00 0
7f1aef1fb000-7f1aef1fc000 ---p 00000000 00:00 0
7f1aef1fc000-7f1aef9fc000 rw-p 00000000 00:00 0
7f1aef9fc000-7f1aefa03000 r--p 00000000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa03000-7f1aefa11000 r-xp 00007000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa11000-7f1aefa12000 rwxp 00015000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa12000-7f1aefa14000 r-xp 00016000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa14000-7f1aefa19000 r--p 00018000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa19000-7f1aefa1a000 r--p 0001c000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa1a000-7f1aefa1b000 rw-p 0001d000 fd:05 9832594
/usr/lib/x86_64-linux-gnu/libpthread-2.31.so
7f1aefa1b000-7f1aefa1f000 rw-p 00000000 00:00 0
7f1aefa3b000-7f1aefa60000 r--p 00000000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa60000-7f1aefa61000 rwxp 00025000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa61000-7f1aefa81000 r-xp 00026000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa81000-7f1aefa82000 rwxp 00046000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa82000-7f1aefa84000 r-xp 00047000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa84000-7f1aefa85000 rwxp 00049000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefa85000-7f1aefb21000 r-xp 0004a000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefb21000-7f1aefb22000 rwxp 000e6000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefb22000-7f1aefbd8000 r-xp 000e7000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefbd8000-7f1aefc22000 r--p 0019d000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc22000-7f1aefc23000 ---p 001e7000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc23000-7f1aefc26000 r--p 001e7000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc26000-7f1aefc29000 rw-p 001ea000 fd:05 9832581
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f1aefc29000-7f1aefc2f000 rw-p 00000000 00:00 0
7f1aefc35000-7f1aefc38000 r--p 00000000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc38000-7f1aefc3c000 r-xp 00003000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc3c000-7f1aefc3d000 r--p 00007000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc3d000-7f1aefc3e000 ---p 00008000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc3e000-7f1aefc3f000 r--p 00008000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc3f000-7f1aefc40000 rw-p 00009000 fd:05 9832596
/usr/lib/x86_64-linux-gnu/librt-2.31.so
7f1aefc40000-7f1aefc41000 r--p 00000000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc41000-7f1aefc43000 r-xp 00001000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc43000-7f1aefc44000 r--p 00003000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc44000-7f1aefc45000 r--p 00003000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc45000-7f1aefc46000 rw-p 00004000 fd:05 9832582
/usr/lib/x86_64-linux-gnu/libdl-2.31.so
7f1aefc4b000-7f1aefc4c000 r--p 00000000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc4c000-7f1aefc6f000 r-xp 00001000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc6f000-7f1aefc77000 r--p 00024000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc78000-7f1aefc79000 r--p 0002c000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc79000-7f1aefc7a000 rw-p 0002d000 fd:05 9832577
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f1aefc7a000-7f1aefc7b000 rw-p 00000000 00:00 0
7fffeaec7000-7fffeaee8000 rw-p 00000000 00:00 0
[stack]
7fffeaf62000-7fffeaf66000 r--p 00000000 00:00 0 [vvar]
7fffeaf66000-7fffeaf68000 r-xp 00000000 00:00 0
[vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0
[vsyscall]

What? Frida agent is gone but the new libraries that


Frida injected onto the process context are still there.
Remember our suggestion to get all the information
around the binary and the process BEFORE using
Frida? Now this environment is dirty and if you look at
the memory maps you may be fooled.

Just as a curiosity, if you perform a ldd onto the frida-


agent (on the temporary copy attached to the process,
/tmp/frida-b180ae47172a1399a77c0aa35d152e45/frida-
agent-64.so ), this is what you will see:

(frida-tools) frida2 ~$ ldd frida-agent-64.so


linux-vdso.so.1 (0x00007ffeda12b000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2
(0x00007f62b891e000)
libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2
(0x00007f62b8902000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f62b88f7000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0
(0x00007f62b88d4000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6
(0x00007f62b8785000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f62b8593000)
/lib64/ld-linux-x86-64.so.2 (0x00007f62b9ffd000)

No mystery.

Now we will get into the Frida scripts we have used and
give some brief explanations for you to get the idea of
what was happening here.

First, let’s walk over the “ simple0.py ” python script,


adding some notes and comments, so we can understand
some pieces of what we did:

# Here we import frida framework bindings for python.


import frida
# We create a method that will be used in the future.
def on_message(message, data):
print("[on_message] message:", message, "data:", data)

# We attach to our application simple0 (or the name of the one


# you selected to attach.
session = frida.attach("simple0")

# We create an script to enumerate module imports (exports)


# in the attached application.
# Remember Frida API is made on JavaScript.
script = session.create_script("""
rpc.exports.enumerateModules = function () {
return Process.enumerateModules();
};
""")
# We link the "message" event to our on_message handler.
script.on("message", on_message)

# We load the script (remember, enumerateModules)


script.load()
# We print all modules that resulted on the execution
# of the script.
print([m["name"] for m in script.exports.enumerate_modules()])

Python line per line:

import frida: the module import to be able to


load Frida bindings on python.
def on_message(message, data): a handler
function that we will attack to “message” (see
below) that will process a message and show
some information. Not doing much in this
examples right now.
session = frida.attach("simple0"): here we
attach to simple0 with the Frida function to do
so.
script = session.create_script(: here we add the
JavaScript code we want to execute when
attached.
script.on("message", on_message): we connect
our handler to the “message” event (or
activity).
script.load(): load the script we created onto
the session.
print([m["name"] for m in
script.exports.enumerate_modules()]): print the
name for every module enumerated.
Few lines of code that perform a lot of powerful things
on an application. Now look over the JavaScript code:

rpc.exports.enumerateModules = function () {
return Process.enumerateModules();
};

This code will assign a function to the


rpc.exports.enumerateModules method. Its value will
be the returned results from
Process.enumerateModules() . And as assigned to
exports, then could be called from python using the
script.exports.enumerate_modules() call we listed
before. We will see another way of achieving this,
without exporting the JavaScript function (printing
from within the JavaScript engine).

I think this is a lot of information to this point. We will


stop here on the code and applications and make some
descriptions and comments on how Frida works.
How Frida works

Before going on modes of operation, let’s see what is


running in the Frida inner guts. Frida is a powerful
dynamic instrumentation tool. But, not only powerful,
but simple to use (well, simple in relative terms if you
think about hooking, injecting, and modifying a process
context by yourself). Gum engine, the instrumentation
core for Frida, is what enables all this magic.

Gum is written in C for speed and efficiency and


naturally Frida has native C bindings to be used. Then
JavaScript engine is built for simplicity and bindings for
Python, Swift, C#... to facilitate the integration and use
in whichever the language you are used to.

If you are interested in Gum, here you may find a lot of


useful resources:
https://frida.re/docs/c-api/

Gum is not the focus for this text but Frida use cases, so
we are not going deep in its capabilities, but remember
the C api is likely to evolve and have changes that may
be encapsulated by the higher-level ones, so it is always
a good recommendation to work with it plus the
bindings on Python or JavaScript.

GumJS is the intermediate level that builds this


abstraction around Gum-C, so it can be used in a higher-
level syntax. Then, bindings are built over GumJs .
We have several options when working with Frida. If we
have an already built binary and we want to control it in
different ways, we can play with it in “Injected mode”.
In general, no matter if the process is already running
(spawn), going to be spawned or it is run by us through
Frida, this is the typical mode. Managed by frida-core
here.

“Embedded mode” is used when we cannot inject Frida


onto the context. For example, in mobile devices where
we run without the required privileges or “jailed”. In
this scenario, frida-gadget is what we are going to use
(libfrida-gadget.so). This is not easy, as we will need to
patch the application. Modifying source code or doing
binary patching on the application itself or in one of its
libraries. Even using pre-loader strategies like in
LD_PRELOAD , LD_LIBRARY_PATH ,
LD_RUN_PATH or DYLD_INSERT_LIBRARIES .

Finally, we can use the “Preload mode” as the use of the


variables mentioned in the embedded paragraph above.
Depending on the specific operating system linkers can
be controlled through environment variables or linker
configurations. In preload, we set a Frida library to load
before the actual one, so Frida can take control of the
linker-loading process. In several scenarios this will be
our unique possibility.

Summary up to this point. It is quite important to


remember several things:
objdump tool.
ldd tool.
Before starting with Frida, remember to issue
a: sudo sysctl kernel.yama.ptrace_scope=0.
Remember, in Linux boxes you can always
look for process details in /proc/<PID>/...
There are several modes of operation with
Frida: injected, embed and preload.
CHAPTER 3. FRIDA TOOLS FRIDA-
TRACE

It is a good idea to make a quick review of Frida tools,


before entering the bindings and the API and, of course,
programming our own instrumentation tools.

There are many tools in the Frida framework, and we


will visit everyone in the following chapters, but I
considered didactic to focus on frida-trace : starting
with this excellent tool if one of the typical approaches
to Frida, and it is a very good learning path for
beginners.

Other tools we will see are: frida-ps , frida-ls-devices ,


frida-discover … Take note, all the functionality
provided by these extra Frida tools (like frida-ps ), can
be achieved with our own scripts in Python or in
JavaScript .
Frida-trace
With frida-trace we can quickly monitor usage of
functions, syscalls or API calls. Just call it passing what
we want to monitor as a parameter ( -i <string pattern> )
and the name of the executable (look the following
example):

(frida-tools) frida2 ~$ frida-trace --decorate -i "print*" ./simple1


Instrumenting...
printf_size: Auto-generated handler at
"/home/user/Frida/1/__handlers__/libc_2.31.so/printf_size.js"
printf: Auto-generated handler at
"/home/user/Frida/1/__handlers__/libc_2.31.so/printf.js"
printf_size_info: Auto-generated handler at
"/home/user/Frida/1/__handlers__/libc_2.31.so/printf_size_info.js"
Press an initial key, please:
Started tracing 3 functions. Press Ctrl+C to stop.
1
The key code you pressed was [49]
The key was [1]
Now, we will ask you to press numbers from 0 to 1:
Press [0], please:
Press [0], please:
/* TID 0x44440 */
8780 ms printf(format="The key code you pressed was [%d]") [libc-
2.31.so]
8780 ms printf(format="The key was [%c]") [libc-2.31.so]
8780 ms printf(format="Press [%c], please:") [libc-2.31.so]
8780 ms printf(format="Press [%c], please:") [libc-2.31.so]
2
3
Press [0], please:
Press [0], please:
13948 ms printf(format="Press [%c], please:") [libc-2.31.so]
13948 ms printf(format="Press [%c], please:") [libc-2.31.so]
0
0
Press [1], please:
Press [1], please:
17277 ms printf(format="Press [%c], please:") [libc-2.31.so]
17277 ms printf(format="Press [%c], please:") [libc-2.31.so]
...
Press [8], please:
Press [8], please:
65965 ms printf(format="Press [%c], please:") [libc-2.31.so]
65965 ms printf(format="Press [%c], please:") [libc-2.31.so]
8
Press [9], please:
Press [9], please:
66404 ms printf(format="Press [%c], please:") [libc-2.31.so]
66404 ms printf(format="Press [%c], please:") [libc-2.31.so]
9
Process terminated

(frida-tools) frida2 ~$

Let me make a quick review of frida-trace command


line options and see the main purpose for the most
relevant:

(frida-tools) frida2 ~$ frida-trace


Usage: frida-trace [options] target

frida-trace: error: target file, process name or pid must be specified


# without parameters informs us that it need a target

(frida-tools) frida2 ~$ frida-trace --help


Usage: frida-trace [options] target

Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-F, --attach-frontmost
attach to frontmost application
-n NAME, --attach-name=NAME
attach to NAME
-p PID, --attach-pid=PID
attach to PID
--stdio=inherit|pipe stdio behavior when spawning (defaults to
"inherit")
--aux=option set aux option when spawning, such as "uid=
(int)42"
(supported types are: string, bool, int)
--realm=native|emulated
realm to attach in
--runtime=qjs|v8 script runtime to use
--debug enable the Node.js compatible script debugger
--squelch-crash if enabled, will not dump crash report to console
-O FILE, --options-file=FILE
text file containing additional command line options
-I MODULE, --include-module=MODULE
include MODULE
-X MODULE, --exclude-module=MODULE
exclude MODULE
-i FUNCTION, --include=FUNCTION
include FUNCTION
-x FUNCTION, --exclude=FUNCTION
exclude FUNCTION
-a MODULE!OFFSET, --add=MODULE!OFFSET
add MODULE!OFFSET
-T, --include-imports
include program's imports
-t MODULE, --include-module-imports=MODULE
include MODULE imports
-m OBJC_METHOD, --include-objc-method=OBJC_METHOD
include OBJC_METHOD
-M OBJC_METHOD, --exclude-objc-method=OBJC_METHOD
exclude OBJC_METHOD
-j JAVA_METHOD, --include-java-method=JAVA_METHOD
include JAVA_METHOD
-J JAVA_METHOD, --exclude-java-method=JAVA_METHOD
exclude JAVA_METHOD
-s DEBUG_SYMBOL, --include-debug-symbol=DEBUG_SYMBOL
include DEBUG_SYMBOL
-q, --quiet do not format output messages
-d, --decorate add module name to generated onEnter log
statement
-S PATH, --init-session=PATH
path to JavaScript file used to initialize the session
-P PARAMETERS_JSON, --parameters=PARAMETERS_JSON
parameters as JSON, exposed as a global named
'parameters'
-o OUTPUT, --output=OUTPUT
dump messages to file

They are basically the same options as when invoking


frida. But we have here some relevant ones to put our
attention in:

-I MODULE, --include-module=MODULE:
instruct frida-trace to include all the methods
identified in this particular module.
-X MODULE, --exclude-module=MODULE:
the same, but excluding the module.
-i FUNCTION or --include=FUNCTION:
include a particular function. In the opening
example we did so with -i “print*” (yes, you
can use patterns).
-x FUNCTION or --exclude=FUNCTION: the
same, but to exclude.
-a MODULE!OFFSET or --
add=MODULE!OFFSET: you can refer to a
module and an offset within it. Very powerful
and we will use it in several examples.
-T or --include-imports: with this parameter,
frida-trace will try to identify process/binary
imports and will auto generate hook handlers
for methods detected.
-t MODULE or --include-module-
imports=MODULE: the same but for the
specific MODULE only.
-m OBJC_METHOD or --include-objc-
method=OBJC_METHOD: for iOS, to include
specific objC methods.
-M OBJC_METHOD or --exclude-objc-
method=OBJC_METHOD: the same, but to
exclude the method, naturally.
-j JAVA_METHOD or --include-java-
method=JAVA_METHOD: for Android java
methods (include).
-J JAVA_METHOD, --exclude-java-
method=JAVA_METHOD: for Android java
methods (exclude).
-s DEBUG_SYMBOL, --include-debug-
symbol=DEBUG_SYMBOL: to include debug
symbols to be traced.
-S PATH or --init-session=PATH: this is a
really useful (and powerful) capability that
let’s us load several modules with global
definitions and variables to be available for the
session. We will use this function in the final
chapters, when wrapping up all our own
methods, functions and ideas.
Not sure if you know strace . Strace is a tool that can
be used to track the behavior of a binary, inspecting its
syscalls so, for example, you can verify accessed files
(and detect if one is missing or where a configuration is
being read) like in this example:

(frida-tools) frida2 ~$ strace ./simple1


execve("./simple1", ["./simple1"], 0x7fffd8d6abb0 /* 50 vars */) = 0
brk(NULL) = 0x55a24a12e000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc0b873f00) = -1 EINVAL
(Argumento inválido)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No existe el
archivo o el directorio)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
=3
fstat(3, {st_mode=S_IFREG|0644, st_size=112822, ...}) = 0
mmap(NULL, 112822, PROT_READ, MAP_PRIVATE, 3, 0) =
0x7f016ddd3000
close(3) =0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6",
O_RDONLY|O_CLOEXEC) = 3
read(3,
"\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360q\2\0\0\0\0\0"...,
832) = 832
pread64(3,
"\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"...,
784, 64) = 784
pread64(3,
"\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0",
32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\363\377?
\332\200\270\27\304d\245n\355Y\377\t\334"..., 68, 880) = 68
fstat(3, {st_mode=S_IFREG|0755, st_size=2029224, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f016ddd1000
pread64(3,
"\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"...,
784, 64) = 784
pread64(3,
"\4\0\0\0\20\0\0\0\5\0\0\0GNU\0\2\0\0\300\4\0\0\0\3\0\0\0\0\0\0\0",
32, 848) = 32
pread64(3, "\4\0\0\0\24\0\0\0\3\0\0\0GNU\0\363\377?
\332\200\270\27\304d\245n\355Y\377\t\334"..., 68, 880) = 68
mmap(NULL, 2036952, PROT_READ,
MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f016dbdf000
mprotect(0x7f016dc04000, 1847296, PROT_NONE) = 0
mmap(0x7f016dc04000, 1540096, PROT_READ|PROT_EXEC,
MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) =
0x7f016dc04000
mmap(0x7f016dd7c000, 303104, PROT_READ,
MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19d000) =
0x7f016dd7c000
mmap(0x7f016ddc7000, 24576, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) =
0x7f016ddc7000
mmap(0x7f016ddcd000, 13528, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) =
0x7f016ddcd000
close(3) =0
arch_prctl(ARCH_SET_FS, 0x7f016ddd2540) = 0
mprotect(0x7f016ddc7000, 12288, PROT_READ) = 0
mprotect(0x55a24966b000, 4096, PROT_READ) = 0
mprotect(0x7f016de1c000, 4096, PROT_READ) = 0
munmap(0x7f016ddd3000, 112822) =0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...})
=0
brk(NULL) = 0x55a24a12e000
brk(0x55a24a14f000) = 0x55a24a14f000
write(1, "Press an initial key, please:\n", 30Press an initial key, please:
) = 30
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...})
=0
read(0,

I have highlighted the “read” function, as we are going


to monitor this specific call in our next example.
Be careful with the parameters you pass as
they can generate unexpected behaviors if
the pattern matches multiple things.

(frida-tools) frida2 ~$ frida-trace -i read ./simple1


Instrumenting...
read: Auto-generated handler at
"/home/user/Frida/1/__handlers__/libc_2.31.so/read.js"
read: Auto-generated handler at
"/home/user/Frida/1/__handlers__/libpthread_2.31.so/read.js"
Press an initial key, please:
Started tracing 2 functions. Press Ctrl+C to stop.
/* TID 0x44caf */
112 ms read(fd=0x0, buf=0x557baa877f60, count=0x400)
1
The key code you pressed was [49]
The key was [1]
Now, we will ask you to press numbers from 0 to 1:
Press [0], please:
Press [0], please:
39239462 ms read(fd=0x0, buf=0x557baa877f60, count=0x400)

Press [9], please:
Press [9], please:
40092915 ms read(fd=0x0, buf=0x557baa877f60, count=0x400)
9
Process terminated

Our invocation has placed TWO handlers, one for


“ read ” and another for “pth read ”. Remember always
to be specific with the matching criteria.

Every time we have called frida-trace it has created


default handlers for us. Look on what are the contents of
an example one:
/*
* Auto-generated by Frida. Please modify to match the signature of
read.
* This stub is currently auto-generated from manpages when available.
*
* For full API reference, see: https://frida.re/docs/javascript-api/
*/

{
/**
* Called synchronously when about to call read.
*
* @this {object} - Object allowing you to store state for use in
onLeave.
* @param {function} log - Call this function with a string to be
presented to the user.
* @param {array} args - Function arguments represented as an array
of NativePointer objects.
* For example use args[0].readUtf8String() if the first argument is a
pointer to a C string encoded as UTF-8.
* It is also possible to modify arguments by assigning a
NativePointer object to an element of this array.
* @param {object} state - Object allowing you to keep state across
function calls.
* Only one JavaScript function will execute at a time, so do not
worry about race-conditions.
* However, do not use this to store function arguments across
onEnter/onLeave, but instead
* use "this" which is an object for keeping state local to an
invocation.
*/
onEnter(log, args, state) {
log(`read(fd=${args[0]}, buf=${args[1]}, count=${args[2]})`);
},

/**
* Called synchronously when about to return from read.
*
* See onEnter for details.
*
* @this {object} - Object allowing you to access state stored in
onEnter.
* @param {function} log - Call this function with a string to be
presented to the user.
* @param {NativePointer} retval - Return value represented as a
NativePointer object.
* @param {object} state - Object allowing you to keep state across
function calls.
*/
onLeave(log, retval, state) {
}
}

The moment the syscall or function we were going to


watch is called, onEnter handler is in place. The
moment it finishes, onLeave is executed. Make a
review of onEnter :

log(`read(fd=${args[0]}, buf=${args[1]},
count=${args[2]})`): will print a logline with
the File Descriptor number (fd), the content of
the buffer processed by read function and the
number of bytes managed to read.
39239462 ms read(fd=0x0,
buf=0x557baa877f60, count=0x400): this is
the output of the logline we set with the
previous JavaScript invocation. FD 0x0 is
stdin, buf address and count is size read in
hexadecimal (0x400 → 1024 bytes).

If we take a look on the read syscall definition (unistd.h)


we will see interesting things:
ssize_t read(int fd, void *buf, size_t count);

So we receive the function parameters within our


handler, in the order they are placed in the original call:
int fd → args[0], void *buf → args[1] and size_t count
→ args[2]).

The return value typed as ssize_t , well, will be on our


onLeave handler ( retval ). Just the return type should
suggest we will return the amount of data read, and in
the syscall definition we read that 0 will be returned if
no-data was read, -1 if an error and read data len if
anything was read to the buffer ( buf ).

We are going to change the read handler a bit, adding a


log to catch the retval . See the changes on the script:

onLeave(log, retval, state) {


// Log the retval to see function exit result.
log(`retval(${retval})`);
}

And the execution:

(frida-tools) frida2 ~$ frida-trace -i read ./simple1


Instrumenting...
read: Loaded handler at
"/home/user/Frida/1/__handlers__/libc_2.31.so/read.js"
read: Loaded handler at
"/home/user/Frida/1/__handlers__/libpthread_2.31.so/read.js"
Press an initial key, please:
Started tracing 2 functions. Press Ctrl+C to stop.
/* TID 0x4cf2a */
5 ms read(fd=0x0, buf=0x562d05220f60, count=0x400)
1
The key code you pressed was [49]
The key was [1]
Now, we will ask you to press numbers from 0 to 1:
Press [0], please:
Press [0], please:
3131 ms retval(0x2)
3131 ms read(fd=0x0, buf=0x562d05220f60, count=0x400)

I found it interesting to highlight the stop message from


frida-trace : “Started tracing 2 functions. Press Ctrl+C to
stop.”

Be careful when issuing a control-C as, yes, you will


stop frida-trace execution, but depending on the
application logic you may fall into a race condition or
unexpected behaviors. In the simple0 and simple1
examples, frida-trace will detach, but the process will
still be running in the background. Always take this in
mind when you feel the temptation to break the
execution with a control-C , I mean it. You never know
what will happen with a new application and the
unexpected consequences of releasing Frida handlers.
Our first crackme

Let’s use frida-trace for something more relevant than


inspecting syscalls, ok? We are going to solve the
crackme01 from the crackmes directory.

You have the source code there, so just opening the C


file will give you the secret (verysecret), but just
imagine this is a binary we found without the source
code. No cheats allowed.

My very first test to this binary will be a strings


command:

(frida-tools) frida2 ~$ strings crackme01


/lib64/ld-linux-x86-64.so.2
libc.so.6
strncmp
puts
printf
strlen
__cxa_finalize
__libc_start_main
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
[]A\A]A^A_
verysecret
One argument that is the password to unlock the crackme.
[%s] is not the right password.
You win! Perfect! ([%s] was a correct password)
...
.rodata
.eh_frame_hdr
.eh_frame
.init_array
.fini_array
.dynamic
.data
.bss
.comment

Have omitted several strings that are not necessary for


this example, so do not worry if your output is different
from what is shown in the example.

Reading just plain text it does not seem to have anything


strange or very dangerous like socket, execve , or
strange buffers. As this is an unknown binary that came
from an unknown source, please, do not execute it in a
real environment. Use a virtual machine so you can
contain undesirable effects within. Please, I repeat,
always execute things that you did not control in
sandboxes and virtual machines to avoid awful
consequences.

My next step will be to issue a ldd command (but from


strings I have a good idea of some of the libraries that
are in place, like libc , the vdso and the linker library):

(frida-tools) frida2 ~$ ldd crackme01


linux-vdso.so.1 (0x00007fff24fe8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f90e0998000)
/lib64/ld-linux-x86-64.so.2 (0x00007f90e0bad000)
(frida-tools) frida2 ~$
Not going to enter on many details about
vDSO ( virtual dynamic shared object ). Just say the
kernel injects this library in every process space so it
can take advantage of these extensions (typically is the
standard libc which will use it and you have nothing to
understand there, just standard C). If you are interested
in, please, read:

https://man7.org/linux/man-pages/man7/vdso.7.html

So, libc is what ldd shows. From the strings output we


may confirm this so we will assume is true. Now, I will
execute the binary (remember, sandbox or virtual
machine) to see what happens in several scenarios:

(frida-tools) frida2 ~$ ./crackme01


One argument that is the password to unlock the crackme.
(frida-tools) frida2 ~$ ./crackme01 one
[one] is not the right password.
(frida-tools) frida2 ~$ ./crackme01 two
[two] is not the right password.
(frida-tools) frida2 ~$ ./crackme01 verysecret
You win! Perfect! ([verysecret] was a correct password)

(frida-tools) frida2 ~$

Ok, we have these options:

With no parameters: an information message


saying we need to pass a parameter that is the
password.
With a wrong password (one, two) a failed
error message.
With the proper password, we win! perfect!

Here we can stop and think about what may happen


within the binary. Stop and think.

Ok. There should be a way to compare the argument


with the expected password, so we can think of standard
functions like strcmp or strncmp , or, maybe, a custom
function to perform the comparison without a standard
function.

From the strings output we can take note of strncmp as


the most probable candidate, but just to be sure, we will
invoke frida-trace with both strcmp and strncmp as
the functions to be hooked. See it:

(frida-tools) frida2 ~$ frida-trace -i strncmp -i strcmp ./crackme01


Instrumenting...
strncmp: Auto-generated handler at
"/home/user/Frida/crackmes/__handlers__/libc_2.31.so/strncmp.js"
strcmp: Auto-generated handler at
"/home/user/Frida/crackmes/__handlers__/libc_2.31.so/strcmp.js"
One argument that is the password to unlock the crackme.
Started tracing 2 functions. Press Ctrl+C to stop.
Process terminated
(frida-tools) frida2 ~$

As we have not passed any parameter, we get the


information message we commented above.
Let’s open the default handler frida-trace created for us
and see (just going to strncmp directly but feel free to
inspect strcmp too):

/*
* Auto-generated by Frida. Please modify to match the signature of
strncmp.
* This stub is currently auto-generated from manpages when available.
*
* For full API reference, see: https://frida.re/docs/javascript-api/
*/

{
/**
* Called synchronously when about to call strncmp.
*
* @this {object} - Object allowing you to store state for use in
onLeave.
* @param {function} log - Call this function with a string to be
presented to the user.
* @param {array} args - Function arguments represented as an array
of NativePointer objects.
* For example use args[0].readUtf8String() if the first argument is a
pointer to a C string encoded as UTF-8.
* It is also possible to modify arguments by assigning a
NativePointer object to an element of this array.
* @param {object} state - Object allowing you to keep state across
function calls.
* Only one JavaScript function will execute at a time, so do not
worry about race-conditions.
* However, do not use this to store function arguments across
onEnter/onLeave, but instead
* use "this" which is an object for keeping state local to an
invocation.
*/
onEnter(log, args, state) {
log(`strncmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}",
n=${args[2]})`);
},
/**
* Called synchronously when about to return from strncmp.
*
* See onEnter for details.
*
* @this {object} - Object allowing you to access state stored in
onEnter.
* @param {function} log - Call this function with a string to be
presented to the user.
* @param {NativePointer} retval - Return value represented as a
NativePointer object.
* @param {object} state - Object allowing you to keep state across
function calls.
*/
onLeave(log, retval, state) {
}
}

In the onEnter, the strncmp syscall parameters are


translated to the logline:

log(`strncmp(s1="${args[0].readUtf8String()}"
, s2="${args[1].readUtf8String()}",
n=${args[2]})`): s1 is str1, s2 is str2 and n is
the number of characters to compare (taken
from the strncmp definition. See below).
int strncmp(const char *str1, const char *str2,
size_t n): str1, str2 and n.
The expected return values from the syscall
are: 0 when str1 and str2 are equal, > 0 if str1
is greater than str2 and < 0 if the reverse.
Of course, we are interested in the str1 and str2
parameters, so let’s see what happens when we pass a
string. To be able to make sure to differentiate between
our string and the “secret” one, we will use
AAAABBBBAAAA as the parameter:

(frida-tools) frida2 ~$ frida-trace -i strncmp -i strcmp ./crackme01


AAAABBBBAAAA
Instrumenting...
strncmp: Loaded handler at
"/home/user/Frida/crackmes/__handlers__/libc_2.31.so/strncmp.js"
strcmp: Loaded handler at
"/home/user/Frida/crackmes/__handlers__/libc_2.31.so/strcmp.js"
[AAAABBBBAAAA] is not the right password.
Started tracing 2 functions. Press Ctrl+C to stop.
/* TID 0x4dc34 */
4 ms strncmp(s1="AAAABBBBAAAA", s2="verysecret",
n=0xa)
Process terminated
(frida-tools) frida2 ~$

So, in s2, we have the “ verysecret ” string we were


looking for. We win! Perfect!

Just to complete the circle, we will pass the correct


password and see:

(frida-tools) frida2 ~$ frida-trace -i strncmp -i strcmp ./crackme01


verysecret
Instrumenting...
strncmp: Loaded handler at
"/home/user/Frida/crackmes/__handlers__/libc_2.31.so/strncmp.js"
strcmp: Loaded handler at
"/home/user/Frida/crackmes/__handlers__/libc_2.31.so/strcmp.js"
You win! Perfect! ([verysecret] was a correct password)

Started tracing 2 functions. Press Ctrl+C to stop.


/* TID 0x4dcb8 */
3 ms strncmp(s1="verysecret", s2="verysecret", n=0xa)
Process terminated

Do you think we have another way of solving this


challenge? Think for a second… Yes! You are right!
Why not change the return value from strncmp so any
string will be a valid password? Good idea.

(frida-tools) frida2 ~$ frida-trace -i strncmp -i strcmp ./crackme01


notvalidpassword
Instrumenting...
strncmp: Loaded handler at
"/home/user/Frida/crackmes/__handlers__/libc_2.31.so/strncmp.js"
strcmp: Loaded handler at
"/home/user/Frida/crackmes/__handlers__/libc_2.31.so/strcmp.js"
You win! Perfect! ([notvalidpassword] was a correct password)

Started tracing 2 functions. Press Ctrl+C to stop.


/* TID 0x4df80 */
3 ms strncmp(s1="notvalidpassword", s2="verysecret", n=0xa)
Process terminated

How was this magic done? Remember I commented


about the return values from the strncmp syscall ? That
0 means strings were equal? And remember about the
“ onLeave ” method that you have onto the hooking
handler? This is where we have to plant our always-
equal change. Please, read carefully the next lines of
code, because they will require an explanation:
onLeave(log, retval, state) {
// We will assign retval a 0 to override the strncmp real result.
// strncmp result will be ALWAYS equal.
// No, just assigning with retval = 0 will NOT work.
retval.replace(0);
}

No matter what retval has, we will issue a .replace(0)


that will end returning our desired always-equal state for
any comparison. And again, we win! Perfect!

But why not just change retval with an assignment?


Why is it necessary to do it through a replace? Actually,
retval is not a value, is a NativePointer that refers to a
memory position with the real result from strncmp .

We can do several things to manipulate this address,


clone or redirect to another memory location (with
ptr() , as we will see later). At this point, just remember
you must issue a replace instead of an assignment.

Summary up to this point. It is quite important to


remember these things:

With frida-trace we can attach to (or execute


directly) an application, looking for specific
functions and watching what happens when
they are invoked. We can even manipulate it
and modify the behaviour.
onEnter: will be called with the parameters
passed to the function call (remember to have
syscalls documentation close to you).
onLeave: will be called when the function call
is about to return, with some parameters with
special interest in retval.
Retval is a NativePointer, not a value:
remember to use replace instead of assignment.
CHAPTER 4. MORE ON FRIDA-TRACE

We saw details and uses for frida-trace that is a


fantastic tool to do quick things on applications. Just to
recall the most important concepts, we will attack a new
application (crackme02) to solve it using two
approaches.

Please, repeat with me the process we developed in


the previous chapter:

(frida-tools) frida2 ~$ strings crackme02


/lib64/ld-linux-x86-64.so.2
libc.so.6
puts
printf
__cxa_finalize
__libc_start_main
GLIBC_2.2.5
_ITM_deregisterTMCloneTable
__gmon_start__
_ITM_registerTMCloneTable
u+UH
[]A\A]A^A_
[%s] is not a valid password.
verysecret
One argument that is the password to unlock the crackme.
[%s] was the right password, you win! Perfect!
...
main
check_password
...
.dynamic
.data
.bss
.comment

Now we do not see any reference to strncmp as in


the crackme01 , but under main, we can see a function
check_password that is promising.

If we issue a ldd :

(frida-tools) frida2 ~$ ldd crackme02


linux-vdso.so.1 (0x00007ffd61d0d000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff2a38a9000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff2a3abe000)
(frida-tools) frida2 ~$

Nothing special here. What to do next? As we saw


this check_password function, why not use objdump
to try to get information about it? If you remember, we
used “ objdump -p ” to see details about libraries linked
and so on.

Objdump can disassemble the binary to assembler


opcodes, so we can even see how it will work. Let’s
disassemble looking for check_password specifically:

(frida-tools) frida2 ~$ objdump -d crackme02|more


crackme02: file format elf64-x86-64

Disassembly of section .init:

0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub $0x8,%rsp
1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax
# 3fe8 <__gmon_start__>
...
0000000000001169 <check_password>:
1169: f3 0f 1e fa endbr64
116d: 55 push %rbp
116e: 48 89 e5 mov %rsp,%rbp
1171: 48 83 ec 20 sub $0x20,%rsp
1175: 48 89 7d e8 mov %rdi,-0x18(%rbp)
1179: 48 89 75 e0 mov %rsi,-0x20(%rbp)
117d: c7 45 fc 00 00 00 00 movl $0x0,-0x4(%rbp)
1184: eb 47 jmp 11cd <check_password+0x64>
1186: 8b 45 fc mov -0x4(%rbp),%eax
1189: 48 63 d0 movslq %eax,%rdx
118c: 48 8b 45 e8 mov -0x18(%rbp),%rax
1190: 48 01 d0 add %rdx,%rax
1193: 0f b6 10 movzbl (%rax),%edx
1196: 8b 45 fc mov -0x4(%rbp),%eax
1199: 48 63 c8 movslq %eax,%rcx
119c: 48 8b 45 e0 mov -0x20(%rbp),%rax
11a0: 48 01 c8 add %rcx,%rax
11a3: 0f b6 00 movzbl (%rax),%eax
11a6: 38 c2 cmp %al,%dl
11a8: 74 1f je 11c9 <check_password+0x60>
11aa: 48 8b 45 e0 mov -0x20(%rbp),%rax
11ae: 48 89 c6 mov %rax,%rsi
11b1: 48 8d 3d 50 0e 00 00 lea 0xe50(%rip),%rdi
# 2008 <_IO_stdin_used+0x8>
11b8: b8 00 00 00 00 mov $0x0,%eax
11bd: e8 ae fe ff ff callq 1070 <printf@plt>
11c2: b8 00 00 00 00 mov $0x0,%eax
11c7: eb 31 jmp 11fa <check_password+0x91>
11c9: 83 45 fc 01 addl $0x1,-0x4(%rbp)
11cd: 8b 45 fc mov -0x4(%rbp),%eax
11d0: 48 63 d0 movslq %eax,%rdx
11d3: 48 8b 45 e8 mov -0x18(%rbp),%rax
11d7: 48 01 d0 add %rdx,%rax
11da: 0f b6 00 movzbl (%rax),%eax
11dd: 84 c0 test %al,%al
11df: 74 14 je 11f5 <check_password+0x8c>
11e1: 8b 45 fc mov -0x4(%rbp),%eax
11e4: 48 63 d0 movslq %eax,%rdx
11e7: 48 8b 45 e0 mov -0x20(%rbp),%rax
11eb: 48 01 d0 add %rdx,%rax
11ee: 0f b6 00 movzbl (%rax),%eax
11f1: 84 c0 test %al,%al
11f3: 75 91 jne 1186 <check_password+0x1d>
11f5: b8 01 00 00 00 mov $0x1,%eax
11fa: c9 leaveq
11fb: c3 retq

Ok, ignore the assembler right now and get the idea
that we have a custom function here that is checking the
password somehow. We will no longer be able to hook
on strncmp or strcmp , but we should focus on this
function instead. Please, take note of this string, as it
will be relevant for the future:

0000000000001169 <check_password>:

But now, how to manage this crackme then? First, go


for the evident:

(frida-tools) frida2 ~$ frida-trace -i check_password ./crackme02


Instrumenting...
Started tracing 0 functions. Press Ctrl+C to stop.
Process terminated
(frida-tools) frida2 ~$

But we fail. There are no functions being traced, so


this attempt to hook on “ check_password ” seems not to
work. What next?

Let’s recover our enumerate modules script, but with


some changes. As we are not attaching to an already
running process, but we are going to run it from a shell
with parameters, we will use “ spawn ”. See it:

(frida-tools) frida2 ~$ cat crackme02.py

import frida

def on_message(message, data):


print("[on_message] message:", message, "data:", data)

process_pid = frida.spawn("crackme02")
session = frida.attach(process_pid)

script = session.create_script("""
Process.enumerateModules({
onMatch: function(module){
console.log('Module name: ' + module.name +
" (" + "Base Address: " + module.base.toString() + ")");
},
onComplete: function(){}
});
""")

script.on("message", on_message)
script.load()

When calling spawn, Frida will return the process id


( process_pid ) that is what we are going to pass to
attach. Execute it:
(frida-tools) frida2 ~$ python crackme02.py
Module name: crackme02 (Base Address: 0x55e912526000)
Module name: linux-vdso.so.1 (Base Address: 0x7ffdcfd8d000)
Module name: libc-2.31.so (Base Address: 0x7fcf8a1d4000)
Module name: ld-2.31.so (Base Address: 0x7fcf8a3e4000)
Module name: libpthread-2.31.so (Base Address: 0x7fcf8a195000)
Module name: frida-agent-64.so (Base Address: 0x7fcf882d9000)
Module name: libdl-2.31.so (Base Address: 0x7fcf8a3d9000)
Module name: libresolv-2.31.so (Base Address: 0x7fcf882bd000)
Module name: librt-2.31.so (Base Address: 0x7fcf8a3ce000)
Module name: libm-2.31.so (Base Address: 0x7fcf8816e000)
One argument that is the password to unlock the crackme.

Ok, the module names are what we saw in previous


examples, libc , vdso , rt … but more interesting,
crackme02 IS a module name.

Now we have to introduce a new parameter for


frida-trace : -a .

If you issue a “--help”, you will see this text:

-a MODULE!OFFSET, --add=MODULE!OFFSET
add MODULE!OFFSET

This means that we can hook the execution on a


MODULE, and in a specific OFFSET of this module.
What does this mean?

A module is a section or context with executable


code that can be referenced by memory pointers or by
other means. For example, if the module is exporting
specific methods, we can invoke these exported names
(like in strncmp before).

What is an offset? Is a memory position that is


referenced from the base pointer (the origin, the
beginning) of a module. If we know where a module
starts in memory, we can reference specific sections of
code using this position and an offset. Remember I
asked you to take note of the result from objdump ?

0000000000001169 <check_password>:

Objdump helped us to identify an offset


( 0000000000001169 or, simpler, 0x1169 ) where our
“ check_password ” function lives. And you know
what? You already have the module name. Yes! It was
crackme02 . So, remember, MODULE!OFFSET will
set to: crackme02!0x1169 (we will need to escape the
exclamation mark in the UNIX shell).

And that is, now we can invoke frida-trace with the


following syntax:

(frida-tools) frida2 ~$ frida-trace -a crackme02\!0x1169 ./crackme02


Instrumenting...
sub_1169: Auto-generated handler at
"/home/user/Frida/crackmes/02/__handlers__/crackme02/sub_1169.js"
One argument that is the password to unlock the crackme.
Started tracing 1 function. Press Ctrl+C to stop.
Process terminated
(frida-tools) frida2 ~$
# Now we are going to execute it directly (we will review
# handlers below).

(frida-tools) frida2 ~$ frida-trace -a crackme02\!0x1169 ./crackme02 a


Instrumenting...
sub_1169: Loaded handler at
"/home/user/Frida/crackmes/02/__handlers__/crackme02/sub_1169.js"
[a] is not a valid password.
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x4f384 */
3 ms sub_1169()
Process terminated
(frida-tools) frida2 ~$

Now Frida says we are tracing 1 function, so it seems


we are on the good way. It has auto generated a handler
so we are going to check it (I will remove comments to
make it smaller, sorry. If you want to check the full
example, look at the source code):

{
onEnter(log, args, state) {
log('sub_1169()');
},
onLeave(log, retval, state) {
}
}

This is the JavaScript code, and it matches the


printed statuses we saw when we executed it, as just
printed “ sub_1169() ”.

In this point we believe we are hooking the proper


function (or the proper memory position, being more
accurate) but we need to get the details about strings and
what is being compared there, isn’t it?

Frida-trace has no way (yet) to identify which


parameters are passed to this function, as it has not a
reference where to check. But we can just shoot over our
intuition and think in this way:

If this is a string comparison function, it


should have at least ONE parameter: the user
provided string.
It is highly likely that there are TWO: the
user provided string and the secret string to
compare.
It is a possibility that we have THREE: the
user provided string and the secret string to
compare and the length (if the developer
created something like strncmp).

What to do? Well, let’s add more log printing args[0],


args[1] and args[2] and see what the application
exposes. Our code for onEnter will be:

onEnter(log, args, state) {


log('sub_1169()');
log('check_password(format="${args[0].readUtf8String()}") [first parameter]`);
log(`check_password(format="${args[1].readUtf8String()}") [second parameter]`);
log(`check_password(format="${args[2].readUtf8String()}") [third parameter]`);
},
What we receive in the args list are just memory
positions (pointers) that cannot be used directly as
“variables”. We must tell Frida what we expect to read
at one particular position. This is the reason why we use
the syntax args[0].readUtf8String() instead of
something like args[0] directly. Naturally, we can read
other variable types like integers ( readInt ) and, of
course, we can read other string formats like C, ansi or
Utf16.

Execute now:

(frida-tools) frida2 ~$ frida-trace -a crackme02\!0x1169 ./crackme02 a


Instrumenting...
sub_1169: Loaded handler at
"/home/user/Frida/crackmes/02/__handlers__/crackme02/sub_1169.js"
[a] is not a valid password.
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x4f9e6 */
5 ms sub_1169()
5 ms check_password(format="verysecret") [first parameter]
5 ms check_password(format="a") [second parameter]
5 ms check_password(format="a") [third parameter]
Process terminated

CONGRATULATIONS! We win! Perfect! We got


the secret password, the first parameter in the function
call (if you check the .c source, int
check_password(const char *secret_password, const
char *user_password) { ).

The second parameter is the string we passed. The


third seems to be a repetition of the second and may be
discarded (or not). Following this logic sequence, we
will add a fourth logline to see what it prints.

Our new onEnter :

onEnter(log, args, state) {


log('sub_1169()');
log(`check_password(format="${args[0].readUtf8String()}") [first parameter]`);
log(`check_password(format="${args[1].readUtf8String()}") [second parameter]`);
log(`check_password(format="${args[2].readUtf8String()}") [third parameter]`);

// Next line will have an explosion :)


log(`check_password(format="${args[3].readUtf8String()}") [fourth parameter]`);
},

Execute it:

(frida-tools) frida2 ~$ frida-trace -a crackme02\!0x1169 ./crackme02 a


Instrumenting...
sub_1169: Loaded handler at
"/home/user/Frida/crackmes/02/__handlers__/crackme02/sub_1169.js"
Started tracing 1 function. Press Ctrl+C to stop.
[a] is not a valid password.
{'type': 'error', 'description': "Error: can't decode byte 0xf3 in
position 0", 'stack': "Error: can't decode byte 0xf3 in position
0\n at <anonymous> (frida/runtime/core.js:127)\n
at onEnter (<input>:28)\n at call (native)\n at
invokeNativeHandler (agent.ts:308)\n at onEnter
(agent.ts:273)", 'fileName': 'frida/runtime/core.js', 'lineNumber':
127, 'columnNumber': 1}
/* TID 0x4fad9 */
3 ms sub_1169()
3 ms check_password(format="verysecret") [first parameter]
3 ms check_password(format="a") [second parameter]
3 ms check_password(format="a") [third parameter]
Process terminated
(frida-tools) frida2 ~$
Oh. We got an error. And it makes sense, as what
Frida is doing is taking parameters passed to the
function call from the stack, so whatever is reading on
the fourth position in this case is not a string.

What do you think our next step should look like?


Absolutely yes: we are going to play with the response
from this function.

We have several options here, but the easiest one is to


make suppositions. We can assume that the
check_password method will return something like true
or false. Typically, in C, true is something different from
0 and false is zero. But if the developer is mimicking
strncmp behavior maybe we are on a different scenario:
0 will be true and other values will be false.

Test it with a modified onLeave (we will assume 0


is true, here):

onLeave(log, retval, state) {


retval.replace(0);
}

The output:

(frida-tools) frida2 ~$ frida-trace -a crackme02\!0x1169 ./crackme02 a


Instrumenting...
sub_1169: Loaded handler at
"/home/user/Frida/crackmes/02/__handlers__/crackme02/sub_1169.js"
Started tracing 1 function. Press Ctrl+C to stop.
[a] is not a valid password.
/* TID 0x4fc7d */
3 ms sub_1169()
3 ms check_password(format="verysecret") [first parameter]
3 ms check_password(format="a") [second parameter]
3 ms check_password(format="a") [third parameter]
Process terminated

And no, this was not the solution. We will try with 1
as true:

onLeave(log, retval, state) {


retval.replace(1);
}

Output here:

(frida-tools) frida2 ~$ frida-trace -a crackme02\!0x1169 ./crackme02 a


Instrumenting...
sub_1169: Loaded handler at
"/home/user/Frida/crackmes/02/__handlers__/crackme02/sub_1169.js"
Started tracing 1 function. Press Ctrl+C to stop.
[a] is not a valid password.
[a] was the right password, you win! Perfect!
/* TID 0x4fd12 */
3 ms sub_1169()
3 ms check_password(format="verysecret") [first parameter]
3 ms check_password(format="a") [second parameter]
3 ms check_password(format="a") [third parameter]
Process terminated

Why two error messages, one with a failure and the


other with a success? This is because the crackme02.c
logic works that way. First, it compares in a loop and if
any of the characters from one string is different in the
other, prints the error message and returns with a 0.
Then, we replaced the retval value AFTER this print,
and the final evaluation checked the result, that now was
true, and printed the success message.

The conclusion? We win! Perfect!

Remember I mentioned gdb? We are going to check


the program execution with gdb to see what was
happening on the execution and have an excuse to
introduce the debugger use :). But, before entering gdb,
please, read some notes about the debugging process
and the source code.

If you do not have gdb installed on your


machine, please, look for the installation
instructions for your specific system. In
my Linux (I work with Debian ,
Ubuntu and Arch typically) you should
issue something line: apt-get install gdb ,
apt install gdb or pacman -S gdb (if using
pacman package manager when, for
example, in Arch Linux ).

The application source code is:

#include <stdio.h>
#include <string.h>
int check_password(const char *secret_password, const char
*user_password) {
int i = 0;

while(secret_password[i] != 0 && user_password[i] != 0) {


if (secret_password[i] != user_password[i]) {
printf("[%s] is not a valid password.\n", user_password);
return 0;
}
i++;
}

return 1;
}

int main(int argc, char *argv[], char *envp[]) {


int is_valid_password = 0;
char *the_password = "verysecret";

if (argc != 2) {
printf("One argument that is the password to unlock the crackme.\n");
return -1;
}

is_valid_password = check_password(the_password, argv[1]);


if(is_valid_password != 0) {
printf("[%s] was the right password, you win! Perfect!\n", argv[1]);
return 0;
}

return 1;
}

The binary was compiled including debug symbols


(with gcc -o crackme02 crackme.c -g ) so it will be
easier to trace on gdb.
The target to debug is, mainly, the check_password
function, so we will focus on it without entering in many
details about gdb and its uses.

(frida-tools) frida2 ~$ gdb ./crackme02


GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".


Type "apropos word" to search for commands related to "word"...
Reading symbols from ./crackme02...
(gdb)

This is the typical loading of a binary to be debugged


in gdb . Now, we can run the application with “ run ” or,
if we want to pass parameters, “ run a ”. See it:

(gdb) run
Starting program: /home/user/Frida/crackmes/02/crackme02
One argument that is the password to unlock the crackme.
[Inferior 1 (process 327425) exited with code 0377]
(gdb)

And with “a” as a parameter:


(gdb) run a
Starting program: /home/user/Frida/crackmes/02/crackme02 a
[a] is not a valid password.
[Inferior 1 (process 327445) exited with code 01]

Ok, to improve our capability of detecting the parameter on the stack,


we will pass AAAABBBBAAAA as the string to be compared:
(gdb) run AAAABBBBAAAA
Starting program: /home/user/Frida/crackmes/02/crackme02
AAAABBBBAAAA
[AAAABBBBAAAA] is not a valid password.
[Inferior 1 (process 327510) exited with code 01]
(gdb)

But wait a minute. How can we see the stack if the


program is exiting when the string comparison fails? We
can use a breakpoint.

A breakpoint in a debugger is an instruction that will


stop the execution of the application at the given point.
And… which point will be it? Yes, of course ,
check_password function. Even, as we already know
the offset for this function, we may put a breakpoint
directly on the offset. See both options:

(gdb) run
Starting program: /home/user/Frida/crackmes/02/crackme02
One argument that is the password to unlock the crackme.
[Inferior 1 (process 327635) exited with code 0377]
(gdb) break check_password
Breakpoint 1 at 0x555555555169
(gdb)

One important comment: if you remember the offset


we got from objdump was 0x1169 , not the one that is
being highlighted. This is because, after the first
execution, gdb now has a reference of where things are
mapped on memory.

Let me explain this a bit. The first time we open gdb


(without running the application), the debugger will
only have the offset related to the .text section in the
binary (related to zero). But after running the
application, the operating system loader will be called
and the .text section will be remapped within the virtual
memory by the operating system, and this information
will be available to gdb at this moment.

To make things simpler, please, get out of gdb and


open it again to get a “clean” scenario. Then see what
happens after we set the breakpoint:

(gdb) quit
(frida-tools) frida2 ~$ gdb ./crackme02
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".


Type "apropos word" to search for commands related to "word"...
Reading symbols from ./crackme02...
(gdb) br check_password
Breakpoint 1 at 0x1169: file crackme02.c, line 5.

Now it is exactly the same address we identified with


objdump (remember, this is a reference to a start on 0,
not the real memory position that will be relocated by
the OS loader). Note I’ve used a short version of break
here, br . The breakpoint is set in the relative-to-zero
memory position on: 0x1169 .

Now, with the breakpoint set, we can run the program


again:

(gdb) run AAAABBBBAAAA


Starting program: /home/user/Frida/crackmes/02/crackme02
AAAABBBBAAAA

Breakpoint 1, check_password (secret_password=0xf0b5ff <error:


Cannot access memory at address 0xf0b5ff>,
user_password=0x555555554040 "\006")
at crackme02.c:5
5 int check_password(const char *secret_password, const char
*user_password) {
(gdb)

I will issue a “info frame” instruction, to see the


details of where we are and the related information we
have:

(gdb) info frame


Stack level 0, frame at 0x7fffffffdef0:
rip = 0x555555555169 in check_password (crackme02.c:5); saved rip
= 0x555555555258
called by frame at 0x7fffffffdf30
source language c.
Arglist at 0x7fffffffdee0, args: secret_password=0xf0b5ff <error:
Cannot access memory at address 0xf0b5ff>,
user_password=0x555555554040 "\006"
Locals at 0x7fffffffdee0, Previous frame's sp is 0x7fffffffdef0
Saved registers:
rip at 0x7fffffffdee8
(gdb)

Just ignore the error message “<error: Cannot


access memory at address 0xf0b5ff>” (we will see later
more details on debugging) and focus on the frame
information we get from gdb . But, what a frame is? A
frame contains the details passed to a function called:
arguments given to the function, local variables, and the
address at which the function is executing. When an
application starts, only one frame will be found on the
stack related to the main function. Is called the initial
frame or the outermost frame .

Whenever a function is called, new frames will be


added and, even, can be accessed using its number. For
example:

(gdb) frame
#0 check_password (secret_password=0xf0b5ff <error: Cannot access
memory at address 0xf0b5ff>, user_password=0x555555554040
"\006")
at crackme02.c:5
5 int check_password(const char *secret_password, const char
*user_password) {
See this “#0”? This is the frame number, and we can use
it in the debugger just issuing a frame 0 instruction:

(gdb) frame 0
#0 check_password (secret_password=0xf0b5ff <error: Cannot access
memory at address 0xf0b5ff>, user_password=0x555555554040
"\006")
at crackme02.c:5
5 int check_password(const char *secret_password, const char
*user_password) {
(gdb)

Ok, now we get an idea of what the frame is, we can


check associated details like local variables and other
contextual information. For example, with info args :

(gdb) info args


secret_password = 0xf0b5ff <error: Cannot access memory at address
0xf0b5ff>
user_password = 0x555555554040 "\006"

And with info locals:

(gdb) info locals


i = 32767

We have two parameters for this function call,


“secret_password” and “user_password” and a local
variable called “i”. As we are not within the function
yet, we put the breakpoint at the starting memory
address, not within it, and we won’t have many details.
But if we advance to the next instruction with “step” we
will see something different.

Before doing so, we will inspect the processor


registers to confirm that we are not within the function
but on its start. Issue info reg :

(gdb) info reg


rax 0x555555556027 93824992239655
rbx 0x555555555290 93824992236176
rcx 0x555555555290 93824992236176
rdx 0x7fffffffe350 140737488347984
rsi 0x7fffffffe350 140737488347984
rdi 0x555555556027 93824992239655
rbp 0x7fffffffdf20 0x7fffffffdf20
rsp 0x7fffffffdee8 0x7fffffffdee8
r8 0x0 0
r9 0x7ffff7fe0d50 140737354009936
r10 0x7 7
r11 0x2 2
r12 0x555555555080 93824992235648
r13 0x7fffffffe010 140737488347152
r14 0x0 0
r15 0x0 0
rip 0x555555555169 0x555555555169 <check_password>
eflags 0x212 [ AF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0

What is highlighted above is the RIP (Instruction


Pointer register that refers to the location of the next
instruction that is going to be executed) that points, as
we said, to the starting point of the function: memory
address 0x555555555169 . Now issue a “ step ”
command to gdb :

(gdb) step
6 int i = 0;
(gdb) info args
secret_password = 0x555555556027 "verysecret"
user_password = 0x7fffffffe350 "AAAABBBBAAAA"
(gdb)

We see the parameters, yes, but let’s confirm what’s


on RIP. Again, info reg:

(gdb) info reg


rax 0x555555556027 93824992239655
rbx 0x555555555290 93824992236176
rcx 0x555555555290 93824992236176
rdx 0x7fffffffe350 140737488347984
rsi 0x7fffffffe350 140737488347984
rdi 0x555555556027 93824992239655
rbp 0x7fffffffdee0 0x7fffffffdee0
rsp 0x7fffffffdec0 0x7fffffffdec0
r8 0x0 0
r9 0x7ffff7fe0d50 140737354009936
r10 0x7 7
r11 0x2 2
r12 0x555555555080 93824992235648
r13 0x7fffffffe010 140737488347152
r14 0x0 0
r15 0x0 0
rip 0x55555555517d 0x55555555517d
<check_password+20>
eflags 0x206 [ PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0

The next instruction in the instruction pointer is


check_password address plus 20 (in hexadecimal, 32 in
decimal) so we confirm we are within the function.

We can see the memory structure of this program


with info proc map:

(gdb) info proc map


process 331928
Mapped address spaces:

Start Addr End Addr Size Offset objfile


0x555555554000 0x555555555000 0x1000
0x0 /home/user/Frida/crackmes/02/crackme02
0x555555555000 0x555555556000 0x1000
0x1000 /home/user/Frida/crackmes/02/crackme02
0x555555556000 0x555555557000 0x1000
0x2000 /home/user/Frida/crackmes/02/crackme02
0x555555557000 0x555555558000 0x1000
0x2000 /home/user/Frida/crackmes/02/crackme02
0x555555558000 0x555555559000 0x1000
0x3000 /home/user/Frida/crackmes/02/crackme02
0x7ffff7db9000 0x7ffff7dde000 0x25000 0x0
/usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7dde000 0x7ffff7f56000 0x178000 0x25000
/usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7f56000 0x7ffff7fa0000 0x4a000 0x19d000
/usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa0000 0x7ffff7fa1000 0x1000 0x1e7000
/usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa1000 0x7ffff7fa4000 0x3000 0x1e7000
/usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa4000 0x7ffff7fa7000 0x3000 0x1ea000
/usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa7000 0x7ffff7fad000 0x6000 0x0
0x7ffff7fc9000 0x7ffff7fcd000 0x4000 0x0 [vvar]
0x7ffff7fcd000 0x7ffff7fcf000 0x2000 0x0 [vdso]
0x7ffff7fcf000 0x7ffff7fd0000 0x1000 0x0
/usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7fd0000 0x7ffff7ff3000 0x23000 0x1000
/usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ff3000 0x7ffff7ffb000 0x8000 0x24000
/usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffc000 0x7ffff7ffd000 0x1000 0x2c000 /usr/lib/x86_64-
linux-gnu/ld-2.31.so
0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0x2d000
/usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffe000 0x7ffff7fff000 0x1000 0x0
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]

And even search for a string in a range, for example,


looking for “verysecret” with find:

(gdb) find 0x555555554000,0x555555559000,"verysecret"


0x555555556027
warning: Unable to access 12249 bytes of target memory at
0x555555556028, halting search.
1 pattern found.

Ignore the warning and check for the result found.


The instruction x let us show what is in a memory
address and with the /s modifier for the format we ask
to show it as a string (is by default string if no format
modifier applied):

(gdb) x /s 0x555555556027
0x555555556027: "verysecret"
(gdb)

I think it is enough debugging by now, shall we


advance to the next chapter where we will finish playing
with frida-trace and see other tools we have in the
arsenal.

Summary up to this point. It is quite important to


remember these things:

With frida-trace we can also hook functions


that are not identified from an exports list or
in a well-known library like libc. Identifying
the position in memory and passing the
module name (for example, the executable
itself) and the memory address, we can
create a hook for this specific point.
Remember the syntax: frida-trace -a
module!position in memory, like in frida-
trace -a crackme02!0x1169.
Gdb is a debugging tool that can be used to
watch the execution of an application. We
can get a lot of information on what’s going
on in memory, registers, stack...
CHAPTER 5. OUR FINAL STEPS WITH
TRACES

Maybe you remember we discussed alternate ways of


loading Frida onto the application context. To create an
example, we will make a copy of the Crackme02 to a
02bis copy; check the repository for the sources. To
help in the explanation you will find the complete
sources here in this chapter.

Imagine we cannot attach to nor spawn a process with


Frida, and we need to rely on the preload to control the
execution. This scenario makes little sense as if we can
use LD_ PRELOAD is likely that we will be able to
spawn. Maybe attaching is not possible (cannot issue a
sudo sysctl kernel.yama.ptrace_scope=0 ?).

Anyway, no matter the scenario, we need to learn how


to preload. Depending on the operating system there are
several options for the variable to be passed when
preloading a library. In Mac OSX , for example, you
may use DYLD _INSERT_LIBRARIES, and in
Linux , LD_ PRELOAD as we mentioned before.

We are not actually discussing frida-trace here, but how


to use frida-gadget (that is equally a way of tracing).
Frida-gadget

The dynamic library that we can pass in the environment


(or even inject in process context) is frida-gadget . We
will need to download the specific version for our
operating system, architecture, and type of binary. To do
so, visit:

https://github.com/frida/frida/releases

There you will find different options for frida-gadget .


For example, in our examples:

https://github.com/frida/frida/releases/download/14.2.8/f
rida-gadget-14.2.8-linux-x86_64.so.xz

But you will need to download whichever option that


fits for your environment: Android , OSX , x86-32 ,
x64 ... in the end, our scripts will have the same
structure and basic elements, having different activities
depending on what we are tracing at every moment.

Once we have the frida-gadget library it is important to


consider renaming it. There exist some protection tools
and mechanisms that may raise alerts or even block if
they find Frida name in a resource. For the examples on
this book this is not necessary, but in the Wild you may
face problems with anti-malware or defender tools on
devices so it might be a good policy to rename gadget to
whatever the name you prefer. And, even better, not
only renaming the file but making our own compiled
binary with modifications to invalidate signature
detections.

Only one IMPORTANT thing: whenever you use frida-


gadget in preload mode, no matter the name, you must
use a configuration file with the extension “.config”
appended to the name, replacing the library extension
(so, dylib , dll ,...).. For example, if the name was
“ frida-gadget.so ”, you will need a config file with the
name “ frida-gadget.config ”. See in our Crackme
directory:

(frida-tools) frida2 ~$ ls -la


total 23044
drwxrwxr-x 2 user user 4096 Feb 1 16:57 .
drwxrwxr-x 6 user user 4096 Feb 1 16:57 ..
-rwxrwxr-x 1 user user 19680 Feb 1 12:54 crackme02
-rw-rw-r-- 1 user user 839 Feb 1 10:24 crackme02.c
-rw-rw-r-- 1 user user 478 Feb 1 10:46 crackme02.py
-rw-rw-r-- 1 user user 83 Feb 1 15:32 frida-gadget.config
-rwxr-xr-x 1 user user 23544840 Feb 1 15:24 frida-gadget.so
-rw-rw-r-- 1 user user 872 Feb 1 16:22 gadget_preload.js
-rwxrwxr-x 1 user user 66 Feb 1 16:57 run_patched.sh
(frida-tools) frida2 ~$

Ok. Before showing details about the configuration file,


please remember how we pass a preloaded library to an
executable file. In the examples we will use the
LD_ PRELOAD environment variable, but there are
other options. Check your operating system specific
capabilities on this matter.
frida-gadget config file

The default configuration for frida-gadget is quite


simple and focused on getting everything properly set
for you and then wait until a connection is made:

{
"interaction": {
"type": "listen",
"address": "127.0.0.1",
"port": 27042,
"on_load": "wait"
}
}

If you pay attention to the “type” field it reads “listen”


(the default behavior). Here you can put three potential
options:

“listen”: so, it will listen for connections.


“script”: so, we are going to directly launch a
script, so we are not in a blocking mode
interrupting the application flow until we take
decisions from the connection. You will need
to configure a path from where to load our
script.
“script-directory”: in this case you may create
a directory with several scripts (and, important,
with their own .config configuration files) so
they are loaded for multiple processes or
system-wide. You will need to configure a
directory path where the scripts are located.
In our configuration file we are going to replicate the
same solution we built for Crackme02 , but to be
automatically activated through the preload. Let’s take a
look at the file:

(frida-tools) frida2 ~$ cat frida-gadget.config

{
"interaction": {
"type": "script",
"path": "./gadget_preload.js"
}
}

The “path” value is a local file to our directory ( ./ ) so


we should be there when we execute the example. If not
in the same directory, please use absolute path in this
value (something like “ path ”:
“/home/user/gadget_preload.js ”).

This will set this JavaScript file to be used by frida-


gadget when the process starts, so there is no block to
the process execution, but actions configured in the
code.

Now let’s look at the contents of the JavaScript file, as


there are several relevant differences from the previous
ones.

(frida-tools) frida2 ~$ cat gadget_preload.js


rpc.exports = {
init: function (stage, parameters) {
console.log('[init]', stage, JSON.stringify(parameters));
// Interceptor.attach(Module.getExportByName(null, ''), {
Interceptor.attach(Module.findBaseAddress('crackme02').add(0x1169), {
onEnter: function (args) {
var arg1 = args[0].readUtf8String();
var arg2 = args[1].readUtf8String();
console.log(`check_password(format="${arg1}") [1]`);
console.log(`check_password(format="${arg2}") [2]`);
},
onLeave(retval, state) {
retval.replace(1);
}
});
},
dispose: function () {
console.log('[dispose]');
}
};

If you see the main differences with our previous


JavaScript file, here the handlers are within an init :
call running from an explicit Interceptor.attach call.

It is very typical to call the Interceptor like the example


below:

Interceptor.attach(Module.getExportByName(null, 'open'),

This will find “open” in the exports and attach to it. But
as you may remember, in this crackme02 we did not
identify the “ check_password ” function and we need to
attach through the offset (memory address) of the
function we want to watch. So, we need a slightly
different syntax here to do the same as in Chapter 4:

Interceptor.attach(Module.findBaseAddress('crackme02').add(0x1169)

So, we pass ‘ crackme02’ as the name for the module


and set the offset with add(0x1169) . Pay attention to
this as it will be very useful for binaries where we do
not have evident exports to hook on.

The next relevant change is that parameters to onEnter


and onLeave are different. No log passed as the first
parameter, so what we pass is just the argument list (we
will use the log feature through console.log ,
console.warn and console.error ).

onEnter: function (args) {}.


onLeave(retval, state) {}

And, finally, we have a dispose function that will be


called the moment we detach the session:

dispose: function () {
console.log('[dispose]');
// console.warn('or a warning...');
}
Frida-stalker

Stalker is the code tracing engine from Frida . You can follow threads using it, so
you can intercept functions, blocks and even instructions. Anything that is executed
can be followed and traced (even recompiled dynamically).

If you are used to instrumentation, you may compare Stalker to DynamoRIO


(https://dynamorio.org/) or event to the famous and well-known PIN framework
from Intel :

https://software.intel.com/content/www/us/en/develop/articles/pin-a-dynamic-binary-
instrumentation-tool.html

It is powerful, as we can use c-code (using CModule ) but it is also easy, as we can
skip low-level using the JavaScript API. It has so many capabilities that we are just
performing a brief introduction in this book, to have several dedicated chapters on the
next book. Sorry if you were expecting more on Stalker, but we will stop in the
introduction.

For the following examples we will be playing with simple0 application


( simple_apps/0/simple0 ) through the stalker[01234].py scripts that you can found
there.

Stalker events

In Stalker different event types have, naturally, different event data structures. You
may search for information at:

https://github.com/frida/frida-gum/blob/master/gum/gumevent.h

For example, the RET event:

struct _GumRetEvent {
GumEventType type;

gpointer location;
gpointer target;
gint depth;
};

Or the CALL event:

struct _GumCallEvent {
GumEventType type;

gpointer location;
gpointer target;
gint depth;
};

Keep this reference to be able to understand what information you are receiving in
the “events” argument (see below).

Stalker basic usage

The most basic use of Stalker is directly attaching it to a thread id ( stalker0.py ):

import sys
import frida

def on_message(message, data):


print("[%s] -> %s" % (message, data))

def do_main(my_process):
session = frida.attach(my_process)
script = session.create_script("""
const mainThread = Process.enumerateThreads()[0];
Stalker.follow(mainThread.id, {
events: {
call: true,
ret: false, exec: false, block: false, compile: false
},
onCallSummary(summary) {
console.log('--- BEGIN onCallSummary ---');
console.log(JSON.stringify(summary));
console.log('--- //END onCallSummary ---');
},
});
""")

script.on('message', on_message)
script.load()

input('[!] Press <Enter> to detach.\n\n')


session.detach()

if __name__ == '__main__':
do_main("simple0")

Let’s focus on the JavaScript code (the Python part you already know more or less)
and explain it step by step:

const mainThread = Process.enumerateThreads()[0]: we get here the


main thread form enumerateThreads.
Stalker.follow(mainThread.id, {: this is the simplest invocation for
Stalker, first passing the thread id we want to follow and, second, a
configuration structure.
events: { call: true, ret: false, exec: false, block: false, compile: false }:
this structure indicates that we are going to follow only function calls
(call: true).
onCallSummary(summary) {: we catch the onCallSummary handler.
console.log(JSON.stringify(summary)): print in console a prettified
version of the summary argument.

If we see the result when invoking this one on our


simple0 ( simple_apps/0/simple0 ):

(frida-tools) frida2 ~$ python3 stalker0.py


[!] Press <Enter> to detach.

--- BEGIN onCallSummary ---


{"0x7fbd3cb941d0":5,"0x7fbd3caa8390":6,"0x5577740e7090":1,"0x7fbd3cb16f00":4,"0x7fbd3cb1a760":6,"0x
--- //END onCallSummary ---

The output is the destination memory addresses called (remember, call: true ) and the
number of times they were called.

Typically, you will use onCallSummary to skip resources and going deep on event
structures if you only want to get the statistical information.

Just this simple script is giving us a lot of information about the execution of this
process, as we can directly dedicate more time to what is more called, for example.

More complex usage of Stalker

But if we need to get more detailed information about what is being called, we may
use the onReceive handler. See the example below ( stalker1.py ):

(frida-tools) frida2 ~$ python3 stalker0.py


[!] Press <Enter> to detach.
import sys
import frida

def on_message(message, data):


print("[%s] -> %s" % (message, data))

def do_main(my_process):
session = frida.attach(my_process)
script = session.create_script("""
const mainThread = Process.enumerateThreads()[0];
Stalker.follow(mainThread.id, {
events: {
call: true,
ret: false, exec: false, block: false, compile: false
},
onReceive: function (events) {
console.log("--- BEGIN onReceive ---");
var calls = Stalker.parse(events, {
annotate: true, // this to show event type.
});

for (var i = 0; i < calls.length; i++) {


console.log(JSON.stringify( calls[i] ));
}
console.log("--- //END onReceive ---");
}
});
""")

script.on('message', on_message)
script.load()

input('[!] Press <Enter> to detach.\n\n')


session.detach()

if __name__ == '__main__':
do_main("simple0")

Remember the introductory section about the Stalker events structures? What you
will see here when executing is this:

(frida-tools) frida2 ~$ python3 stalker2.py


[!] Press <Enter> to detach.

--- BEGIN onReceive ---


["call","0x5595addd921b","0x5595addd9080",0]
["call","0x7fa59b595eba","0x7fa59b5aa9e0",1]
["call","0x7fa59b5aaa80","0x7fa59b556390",2]
["call","0x7fa59b5aae15","0x7fa59b504c50",2]
["call","0x7fa59b5aaaef","0x7fa59b5c3750",2]
["call","0x7fa59b5c37ad","0x7fa59b5565a0",3]
["call","0x7fa59b5ab6f0","0x7fa59b58f760",2]
["call","0x7fa59b5ab92a","0x7fa59b5c3750",2]
["call","0x7fa59b5c37ad","0x7fa59b5565a0",3]
["call","0x7fa59b5ab10f","0x7fa59b556390",2]
["call","0x7fa59b5ab163","0x7fa59b5c3750",2]
["call","0x7fa59b5c37ad","0x7fa59b5565a0",3]
["call","0x7fa59b5c3832","0x7fa59b5c4f00",3]
["call","0x7fa59b5c4acd","0x7fa59b5c2fe0",4]
["call","0x7fa59b5c3008","0x7fa59b6421d0",5]
["call","0x7fa59b642202","0x7fa59b5c8760",6]
["call","0x7fa59b64222f","0x7fa59b5c87c0",6]
["call","0x7fa59b5aad25","0x7fa59b504cc0",2]
["call","0x5595addd9232","0x5595addd9080",0]
["call","0x7fa59b595eba","0x7fa59b5aa9e0",1]
["call","0x7fa59b5aaa80","0x7fa59b556390",2]
["call","0x7fa59b5aae15","0x7fa59b504c50",2]
["call","0x7fa59b5aaaef","0x7fa59b5c3750",2]
["call","0x7fa59b5c37ad","0x7fa59b5565a0",3]
["call","0x7fa59b5ada69","0x7fa59b5c5b60",2]
["call","0x7fa59b5c500e","0x7fa59b5c4a20",3]
["call","0x7fa59b5c4acd","0x7fa59b5c2fe0",4]
["call","0x7fa59b5c3008","0x7fa59b6421d0",5]
["call","0x7fa59b642202","0x7fa59b5c8760",6]
["call","0x7fa59b64222f","0x7fa59b5c87c0",6]
["call","0x7fa59b5ab10f","0x7fa59b556390",2]
["call","0x7fa59b5ab163","0x7fa59b5c3750",2]
["call","0x7fa59b5c37ad","0x7fa59b5565a0",3]
["call","0x7fa59b5c3832","0x7fa59b5c4f00",3]
["call","0x7fa59b5c4acd","0x7fa59b5c2fe0",4]
["call","0x7fa59b5c3008","0x7fa59b6421d0",5]
["call","0x7fa59b642202","0x7fa59b5c8760",6]
["call","0x7fa59b64222f","0x7fa59b5c87c0",6]
["call","0x7fa59b5aad25","0x7fa59b504cc0",2]
["call","0x5595addd923e","0x5595addd9070",0]
["call","0x7fa59b5b85af","0x7fa59b556460",1]
--- //END onReceive ---
...

Please remember the structure for CALL ( struct _GumCallEvent ) and see the
elements in every line printed: “ call ” (corresponds to GumEventType type ),
“ 0x… ” ( gpointer location ), “ 0x… ” ( gpointer target ) and an integer ( gint
depth ).

[
"call", // type
"0x7fa59b5b85af", // location
"0x7fa59b556460", // target
1 // depth.
]

The relevant part in our new script is this section:

var calls = Stalker.parse(events, {


annotate: true, // this to show event type.
});

Whenever we receive events from the onReceive handler, we must parse them using
Stalker.parse . This will give us an event list (in the expected format for call, ret, etc.).

You may even create a call tree. It might be a good idea for you to create it by
yourself (if you prefer to go faster, see stalker2.py example).

Stalker transform

I was wondering if I should leave this section in this beginner’s book. This part is
complex (but powerful) and not sure if the scope of this text justifies it. But
introducing Stalker without talking about the “ transform ” capabilities is like not
showing anything. So here it is.

A simple example is this one ( stalker3.py ):


import sys
import frida

def on_message(message, data):


print("[%s] -> %s" % (message, data))

def do_main(my_process):
session = frida.attach(my_process)
script = session.create_script("""
const mainThread = Process.enumerateThreads()[0];
Stalker.follow(mainThread.id, {
events: {
call: true,
ret: false, exec: false, block: false, compile: false
},
transform(iterator) {
let instruction = null;
while ((instruction = iterator.next()) !== null) {
console.log( JSON.stringify(instruction) )
iterator.keep();
}
}
});
""")

script.on('message', on_message)
script.load()

input('[!] Press <Enter> to detach.\n\n')


session.detach()

if __name__ == '__main__':
do_main("simple0")

The output is long, so I will focus only on a few lines as samples:

(frida-tools) frida2 ~$ python3 stalker3.py


{"address":"0x7f20dca3e142","next":"0x6","size":6,"mnemonic":"cmp","opStr":"rax, -0x1000","operands":
[{"type":"reg","value":"rax","size":8},{"type":"imm","value":"-4096","size":8}],"regsRead":[],"regsWritten":
["rflags"],"groups":[]}
{"address":"0x7f20dca3e148","next":"0x2","size":2,"mnemonic":"ja","opStr":"0x7f20dca3e1a0","operands":
[{"type":"imm","value":"139779117408672","size":8}],"regsRead":["rflags"],"regsWritten":[],"groups":
["branch_relative","jump"]}
[!] Press <Enter> to detach.

{"address":"0x7f20dca3e14a","next":"0x1","size":1,"mnemonic":"ret","opStr":"","operands":[],"regsRead":
["rsp"],"regsWritten":["rsp"],"groups":["ret","mode64"]}
{"address":"0x7f20dc9c0d1f","next":"0x3","size":3,"mnemonic":"test","opStr":"rax, rax","operands":
[{"type":"reg","value":"rax","size":8},{"type":"reg","value":"rax","size":8}],"regsRead":[],"regsWritten":
["rflags"],"groups":[]}
{"address":"0x7f20dc9c0d22","next":"0x2","size":2,"mnemonic":"jle","opStr":"0x7f20dc9c0d70","operands":
[{"type":"imm","value":"139779116895600","size":8}],"regsRead":["rflags"],"regsWritten":[],"groups":
["branch_relative","jump"]}
...
It is not necessary to show all the output: you get the idea. The relevant part of the
new script is this one:

transform(iterator) {
let instruction = null;
while ((instruction = iterator.next()) !== null) {
console.log( JSON.stringify(instruction) )
iterator.keep();
}
}

Transform receives an iterator. This iterator will follow the instructions flow and as
“ .next() ” is invoked, will return an instruction structure (see the output).

This pattern is exactly the same as in the script, but removing logs and extra
variables:

while(iterator.next() !== null) iterator.keep();

Will follow the execution flow without doing anything. I thought it was interesting to
show this example to be able to understand how “ transform ” works.

But where transform is powerful is where you can directly track instructions
execution and, when specified, insert your own code in the execution flow. If you
take a look on the example in the Frida’s documentation (here,
https://frida.re/docs/javascript-api/#stalker), you can see how the thread is followed
and new code is inserted before a “ ret ” (from a call).

The following code ( stalker4.py ) is a simplified version of the recompilation seen


before:

import sys
import frida

def on_message(message, data):


print("[%s] -> %s" % (message, data))

def do_main(my_process):
session = frida.attach(my_process)
script = session.create_script("""

function onMatch (context) {


console.log('=====> CALL OUT [Match] pc=' + context.pc +
' rax=' + context.rax.toInt32() );
}

var appModule = Process.enumerateModulesSync()[0];


var appStart = appModule.base;
var appEnd = appStart.add(appModule.size);
const mainThread = Process.enumerateThreads()[0];
Stalker.follow(mainThread.id, {
events: {
call: true,
ret: false, exec: false, block: false, compile: false
},
transform(iterator) {
let instruction = iterator.next();
const startAddress = instruction.address;
const isAppCode = startAddress.compare(appStart) >= 0 &&
startAddress.compare(appEnd) === -1;

do {
if (isAppCode && instruction.mnemonic === 'ret') {
var mnem_i = instruction.mnemonic;
console.log('*****> RET identified as mnemonic: ' + mnem_i);
iterator.putCallout(onMatch);
}

iterator.keep();
} while ((instruction = iterator.next()) !== null);
}
});
""")

script.on('message', on_message)
script.load()

input('[!] Press <Enter> to detach.\n\n')


session.detach()

if __name__ == '__main__':
do_main("simple0")

The relevant code to see here is:

if (isAppCode && instruction.mnemonic === 'ret') {


var mnen_i = instruction.mnemonic;
console.log('*****> RET identified as mnemonic: ' + mnen_i);
iterator.putCallout(onMatch);
}

The if will check if we are within the application code section (between
module.base and module.base + module.size ). Then will check if the instruction
mnemonic is “ ret ”.

If so, will show a debug message (console.log) and add a CALL OUT invoking
JavaScript native function ( function onMatch ).

The very first time I saw Stalker working I got stunned! You can intercept execution
flow instruction per instruction and if the conditions are what we are expecting, we
can assemble opcodes directly there or even invoke JavaScript from there.
An even easier approach is to do all this through CModule and c-code (you can
check on Frida’s documentation), But we will leave all this for the next book, as we
will dedicate several chapters to Stalker and recompilation.
Crakme03 with semaphore

We will make an analysis on this crackme repeating all the steps in both frida-trace
and frida-gadget , setting everything to solve the challenge. In this case, a value is
being read from a semaphore and if it is 31337 , we will win. If not, we will be
mere mortals.

There is not much difference between this example and the previous, but the use of
semaphores and the complexity of an intermediate value ( sem_value ) that is going
to be checked. But it is the same process: look for modules, identify function calls,
see where to add our handlers, and manipulate the application’s behavior.

If you don’t know what a semaphore is, just get the idea that semaphores are a
category of inter process communication that allow processes (and threads) to
synchronize actions (mainly) and values (not very typical). Is an integer value kept
in a special region (and linked to a /dev/shm/<semaphore_name> file) that cannot
have a value below 1 (cannot be 0). We can control the value increasing it or
decreasing it (but we cannot set it directly).

Before going hacking , let me add some comments about the contents of this
directory:

(frida-tools) frida2 ~$ ls -la


total 112
drwxrwxr-x 2 user user 4096 feb 2 08:38 .
drwxrwxr-x 7 user user 4096 feb 2 07:55 ..
-rwxrwxr-x 1 user user 16936 feb 2 08:38 crackme03
-rw-rw-r-- 1 user user 773 feb 2 08:11 crackme03.c
-rw-rw-r-- 1 user user 478 feb 2 08:22 crackme03.py
-rwxrwxr-x 1 user user 16896 feb 2 08:38 crackme03_read
-rw-rw-r-- 1 user user 616 feb 2 08:31 crackme03_read.c
-rwxrwxr-x 1 user user 16944 feb 2 08:38 crackme03_set_1
-rw-rw-r-- 1 user user 761 feb 2 08:38 crackme03_set_1.c
-rwxrwxr-x 1 user user 16952 feb 2 08:38 crackme03_set_31337
-rw-rw-r-- 1 user user 653 feb 2 08:36 crackme03_set_31337.c
-rw-rw-r-- 1 user user 207 feb 2 08:28 Makefile
(frida-tools) frida2 ~$

Our target is the executable “ crackme03 ”. But we have some helper tools that will
perform several actions to change its behavior:

crackme03_read: will read the semaphore value and print.


crackme03_set_1: will set the semaphore value to 1.
crackme03_set_31337: will set the semaphore value to 31337, the
success condition for crackme03.

These helper tools are for you to verify the behavior and confirm that the semaphore
is being set and that values are 1 or 31337 depending on the executable used.
As semaphores do not have a direct set value method, we used
sem_post and sem_wait to increment and decrement the value
in loops. Is like a… hack, not very clean, but it works for our
purpose in this use case.

If in any moment you want to verify the semaphore exists, you


can issue an “ ls ” directly to the shm virtual device in /dev
(we set the name as “ /fridasem ” and it may look like
“ /dev/shm/sem.fridasem ”):

(frida-tools) frida2 ~$ ls -la /dev/shm


total 4
drwxrwxrwt 2 root root 60 feb 2 08:09 .
drwxr-xr-x 22 root root 5020 feb 1 23:06 ..
-rw-rw---- 1 user user 32 feb 2 08:09 sem.fridasem
(frida-tools) frida2 ~$

Ok, now let’s show modules within crackme03 :

(frida-tools) frida2 ~$ python crackme03.py


Module name: crackme03 (Base Address: 0x55ed5c695000)
Module name: linux-vdso.so.1 (Base Address: 0x7ffe1cecd000)
Module name: libpthread-2.31.so (Base Address: 0x7fc71f625000)
Module name: libc-2.31.so (Base Address: 0x7fc71f433000)
Module name: ld-2.31.so (Base Address: 0x7fc71f666000)
Module name: frida-agent-64.so (Base Address: 0x7fc71d574000)
Module name: libdl-2.31.so (Base Address: 0x7fc71f65b000)
Module name: libresolv-2.31.so (Base Address: 0x7fc71d53c000)
Module name: librt-2.31.so (Base Address: 0x7fc71f650000)
Module name: libm-2.31.so (Base Address: 0x7fc71d3ed000)
Sem value right now is = [1]
Not achieved mere mortal...
(frida-tools) frida2 ~$

Check with objdump (the flag DT_NEEDED in the ELF ):

(frida-tools) frida2 ~$ objdump -p crackme03


...
NEEDED libpthread.so.0
NEEDED libc.so.6
...
(frida-tools) frida2 ~$

We can confirm that in this case, libpthread is present. And not just because Frida
injects it on the package of tools used, but because it is linked.

This may suggest that threads or something similar might be in use ( IPC ,
semaphores ).

See the strings result:


(frida-tools) frida2 ~$ strings crackme03|less
/lib64/ld-linux-x86-64.so.2
libpthread.so.0
_ITM_deregisterTMCloneTable
_ITM_registerTMCloneTable
sem_open
sem_getvalue
libc.so.6
exit
puts
__stack_chk_fail
printf
...
/fridasem
sem_open: error cannot open [%s]
sem_getvalue: error cannot get value.
Sem value right now is = [%d]
You reached victory and glory, you are da'Hacker!
Not achieved mere mortal...
...
.init_array
.fini_array
.dynamic
.data
.bss
.comment

Ok. We highlighted several interesting things there that can be traced. The most
evident is sem_getvalue as it seems the method to obtain the integer to be compared
with the super secret number 31337.

Additionally, we highlighted error and success messages that might be interesting to


search if using gdb or another debugging tool. But let’s work with Frida more and
less with standard debuggers (until we enter radare2 universe ;) ).

I suggest going for sem_getvalue with frida-trace and see what happens:

(frida-tools) frida2 ~$ frida-trace -i sem_getvalue ./crackme03


Instrumenting...
sem_getvalue: Auto-generated handler at
"/home/user/Frida/crackmes/03/__handlers__/libpthread_2.31.so/sem_getvalue.js"
Sem value right now is = [1]
Not achieved mere mortal...
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0xc40c */
82 ms sem_getvalue(sem=0x7f8c89b50000, sval=0x7ffd981dd87c)
Process terminated
(frida-tools) frida2 ~$

Ok, seems it was the right decision. Before improving our JavaScript handler, let me
take a look over the sem_getvalue API definition to understand arguments and what
is going to be passed to the handler. So… awesome online manual here:
https://man7.org/linux/man-pages/man3/sem_getvalue.3.html

Let me quote:

#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);
// Link with -pthread.

We have here a couple of interesting things. On one hand we know why the linker
includes pthread library (as it is needed by sem_getvalue , and in general for all
semaphore related functions). On the other, naturally, the function itself.

Two arguments: sem_t *sem and int *sval . The first is the semaphore itself (that
should be made available through a previous call with sem_open). The second is an
integer value passed as a pointer (so it might be a value passed by reference). Be
aware that Frida set the proper names on the log line when creating the handler, so it
obviously knows about this call.

Now let’s modify our handler to be aware of those arguments:

onEnter(log, args, state) {


var sem_item = args[0]; // sem_t *sem
var sem_value_reference = args[1]; // int *s_val <- by reference, remember.
var pointer_read = sem_value_reference.readPointer();
var pointer_value = sem_value_reference.readInt();

log(`[onEnter] sem_getvalue(sem=${sem_item},
sval=${sem_value_reference})`);
log("[onEnter] pointer_read = " + pointer_read);
log("[onEnter] pointer_value = " + pointer_value);
log("------------------------");
},

Is the same as the autogenerated handler, but having separate variables for every
argument as we want to do things with them (and it is simpler to use variable names
than references to arg[1] etc.). I also added a separator line from “ onEnter ” to
“ onLeave ” (visual improvement).

Please, stop and think about how all this works now. Take some time.

Ok. As we said, sem_getvalue will return the value read from the semaphore by
reference in the second argument, s_val ( int *s_val , to clarify). This means when
we are trying to manipulate this return value to achieve our goal, it won’t be as
simple as replacing retval like in the previous examples.

What can be done here? Let me introduce “ this ”.

This is an object that can be used to share information from onEnter to onLeave
handlers, so we can pass strings, integers and, in general, any value from the moment
we enter the handler, to the moment we leave.

If you read carefully the JavaScript code we added for the onEnter handler, you may
see that we are logging different values: the arguments, of course, but we created two
extra variables called pointer_read (and we set the value as arg[1].readPointer() )
and pointer_value (and we set the value as arg[1].readInt() ).

When I say arg[1].readPointer() or arg[1].readInt() , that corresponds, in our


JavaScript code, to sem_value_reference.readPointer() and to
sem_value_reference.readInt() ; as we assigned arg[1] to sem_value_reference .

If we execute now and see the log lines:

(frida-tools) frida2 ~$ frida-trace -i sem_getvalue ./crackme03


Instrumenting...
sem_getvalue: Loaded handler at
"/home/user/Frida/crackmes/03/__handlers__/libpthread_2.31.so/sem_getvalue.js"
Sem value right now is = [1]
Not achieved mere mortal...
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0xcfc6 */
3 ms [onEnter] sem_getvalue(sem=0x7fbd0f891000,
sval=0x7ffe4e4e802c)
3 ms [onEnter] pointer_read = 0xf89100000000000
3 ms [onEnter] pointer_value = 0
3 ms ------------------------
Process terminated
(frida-tools) frida2 ~$

We print the value of s_val when entering the function call, right? But what we want
to achieve is not only printing things in beautiful colors, but to get the message
confirming we are not only handsome, but hackers. We need to modify this value.

We will do it on the onLeave handler, but to be able to do so, we need to share the
s_val argument from enter. Here is where we will use “ this ”.

If we add these new lines to our handlers:

onEnter(log, args, state) {


var sem_item = args[0]; // sem_t *sem
var sem_value_reference = args[1]; // int *s_val <- by reference, remember.
var pointer_read = sem_value_reference.readPointer();
var pointer_value = sem_value_reference.readInt();

log(`[onEnter] sem_getvalue(sem=${sem_item}, sval=${sem_value_reference})`);


log("[onEnter] pointer_read = " + pointer_read);
log("[onEnter] pointer_value = " + pointer_value);
log("------------------------");

this.pointer_to_sem_value = sem_value_reference;
},
onLeave(log, retval, state) {
var sem_value_reference = this.pointer_to_sem_value;
var pointer_read = sem_value_reference.readPointer();
var pointer_value = sem_value_reference.readInt();

log("[onLeave] retval: " + retval);


log("[onLeave] Here sem_value pointer: " + this.pointer_to_sem_value);

log("[onLeave] pointer_read = " + pointer_read);


log("[onLeave] pointer_value = " + pointer_value);
}

In onEnter handler we assign the argument (is a NativePointer ) to


this.pointer_to_sem_value and, later, in onLeave handler, we get this value from
this to create the sem_value_reference variable. Then we log to confirm we are
getting results. Execute and see:

(frida-tools) frida2 ~$ frida-trace -i sem_getvalue ./crackme03


Instrumenting...
sem_getvalue: Loaded handler at
"/home/user/Frida/crackmes/03/__handlers__/libpthread_2.31.so/sem_getvalue.js"
Sem value right now is = [1]
Not achieved mere mortal...
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0xd0d2 */
3 ms [onEnter] sem_getvalue(sem=0x7f9a35491000, sval=0x7ffd986a613c)
3 ms [onEnter] pointer_read = 0x3549100000000000
3 ms [onEnter] pointer_value = 0
3 ms ------------------------
3 ms [onLeave] retval: 0x0
3 ms [onLeave] Here sem_value pointer: 0x7ffd986a613c
3 ms [onLeave] pointer_read = 0x3549100000000001
3 ms [onLeave] pointer_value = 1
Process terminated
(frida-tools) frida2 ~$

Well, you must feel proud if you get this output right now. We learnt a new thing on
passing information from onEnter to onLeave . But still not successful in hacking
this stuff.

What now? Naturally, we need to modify the value. When learning about the options
I tested many things. From direct assignments like:

sem_value_reference = 10;

To creating new native pointers, like in:

sem_value_reference = new NativePointer(10);


sem_value_reference = ptr(10);

But they crashed. It was necessary to dedicate some time to read documentation and
see if we can make it clear. No, it was not as easy as I expected. Have to say that
Frida documentation is… a bit difficult to read sometimes.

I spent some more time looking through snippets, source code examples and other
projects and got lost. No results on this. So… I got back to the documentation, heh.

When talking about NativePointer , the reference is here:

https://frida.re/docs/javascript-api/#nativepointer

Pay attention (more attention than me, if you want to do something useful) to the
WRITE functions that can be found there:

If we have a NativePointer it is supposed that we can write using these methods. As


we already know we need to write an integer, the most probable candidates to be
successful are writeInt , writeU* , writeS* etc. any that can write a numeric value
(our goal is to set 31337 so we have not to worry about signed or unsigned numbers
in this level).

I have added many options just for testing purposes, but any of these will work. See
our onLeave handler with the modifications:

onLeave(log, retval, state) {


var sem_value_reference = this.pointer_to_sem_value;
var pointer_read = sem_value_reference.readPointer();
var pointer_value = sem_value_reference.readInt();
var new_value = 1;

log("[onLeave] retval: " + retval);


log("[onLeave] Here sem_value pointer: " + this.pointer_to_sem_value);

log("[onLeave] pointer_read = " + pointer_read);


log("[onLeave] pointer_value = " + pointer_value);

log("----- going to change here -----");


sem_value_reference.writeU32(new_value);
sem_value_reference.writeS32(new_value);
sem_value_reference.writeInt(new_value);
log(" * CHANGED TO: " + new_value);
}

To save time in performing tests and changes I’ve created a variable, new_value , to
hold the value we want to set. Then, I placed some calls to .writeU32 , .writeS32
and .writeInt (as said, just for testing, you may keep just one).

If we run frida-trace again, we expect to see the message that we have changed the
value, CHANGED TO (with a 1):
(frida-tools) frida2 ~$ frida-trace -i sem_getvalue ./crackme03
Instrumenting...
sem_getvalue: Loaded handler at
"/home/user/Frida/crackmes/03/__handlers__/libpthread_2.31.so/sem_getvalue.js"
Sem value right now is = [1]
Not achieved mere mortal...
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0xd479 */
3 ms [onEnter] sem_getvalue(sem=0x7f450418a000,
sval=0x7ffcdd55b34c)
3 ms [onEnter] pointer_read = 0x418a00000000000
3 ms [onEnter] pointer_value = 0
3 ms ------------------------
3 ms [onLeave] retval: 0x0
3 ms [onLeave] Here sem_value pointer: 0x7ffcdd55b34c
3 ms [onLeave] pointer_read = 0x418a00000000001
3 ms [onLeave] pointer_value = 1
3 ms ----- going to change here -----
3 ms * CHANGED TO: 1
Process terminated
(frida-tools) frida2 ~$

It seems to work. Our next step is to try to set the value that will give us our much-
desired prize after this glorious battle.

We can cheat here and directly set 31337 . Just change var new_value = 1; to var
new_value = 31337; . See it:

(frida-tools) frida2 ~$ frida-trace -i sem_getvalue ./crackme03


Instrumenting...
sem_getvalue: Loaded handler at
"/home/user/Frida/crackmes/03/__handlers__/libpthread_2.31.so/sem_getvalue.js"
Sem value right now is = [31337]
You reached victory and glory, you are da'Hacker!
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0xd5e3 */
3 ms [onEnter] sem_getvalue(sem=0x7fd9658fe000,
sval=0x7fff6a69354c)
3 ms [onEnter] pointer_read = 0x658fe00000000000
3 ms [onEnter] pointer_value = 0
3 ms ------------------------
3 ms [onLeave] retval: 0x0
3 ms [onLeave] Here sem_value pointer: 0x7fff6a69354c
3 ms [onLeave] pointer_read = 0x658fe00000000001
3 ms [onLeave] pointer_value = 1
3 ms ----- going to change here -----
3 ms * CHANGED TO: 31337
Process terminated
(frida-tools) frida2 ~$ (frida-tools) frida2 ~$

And yes! We are hackers! But cheaters, as we only know about this 31337 value
because it is in this book (is a recursive reference, I know the number because I put
the number on this book and I use the book to set the examples with… forget about
this). We had ex machina help from the writer. So… no hacking at all.

Before going into another approach, without cheating, can we do this in onEnter ?
Well, with the knowledge we have to this point, no. Take in mind that our onEnter
handler is being executed before the real native function is called, so any changes we
apply to s_val there will be lost after the real sem_getvalue call. This is the reason
why we performed the changes on the onLeave handler (when the sem_getvalue is
completed).

Now, as we have no idea of the reference number to use to break the crackme (no
idea, I repeat, stay in this state of mind), what we are going to do is to find the value
that is going to be compared with the semaphore in the crackme , and when we got
it, change our JavaScript handler to crack the crackme and become da’Hacker.

Before digging details on the binary with gdb (or another tool ;)), please issue an
objdump to get all symbol information ( objdump --syms ):

(frida-tools) frida2 ~$ objdump --syms crackme03

crackme03: file format elf64-x86-64

SYMBOL TABLE:
0000000000000318 l d .interp 0000000000000000 .interp
0000000000000338 l d .note.gnu.property 0000000000000000 .note.gnu.property
0000000000000358 l d .note.gnu.build-id 0000000000000000 .note.gnu.build-id
000000000000037c l d .note.ABI-tag 0000000000000000 .note.ABI-tag
00000000000003a0 l d .gnu.hash 0000000000000000 .gnu.hash
00000000000003c8 l d .dynsym 0000000000000000 .dynsym
00000000000004e8 l d .dynstr 0000000000000000 .dynstr
00000000000005b8 l d .gnu.version 0000000000000000 .gnu.version
00000000000005d0 l d .gnu.version_r 0000000000000000 .gnu.version_r
0000000000000620 l d .rela.dyn 0000000000000000 .rela.dyn
00000000000006e0 l d .rela.plt 0000000000000000 .rela.plt
0000000000001000 l d .init 0000000000000000 .init
0000000000001020 l d .plt 0000000000000000 .plt
0000000000001090 l d .plt.got 0000000000000000 .plt.got
00000000000010a0 l d .plt.sec 0000000000000000 .plt.sec
0000000000001100 l d .text 0000000000000000 .text
0000000000001368 l d .fini 0000000000000000 .fini
0000000000002000 l d .rodata 0000000000000000 .rodata
00000000000020d8 l d .eh_frame_hdr 0000000000000000 .eh_frame_hdr
0000000000002120 l d .eh_frame 0000000000000000 .eh_frame
0000000000003d80 l d .init_array 0000000000000000 .init_array
...
0000000000000000 l df *ABS* 0000000000000000 crackme03.c
0000000000000000 l df *ABS* 0000000000000000 crtstuff.c
...
0000000000003f90 l O .got 0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000001000 l F .init 0000000000000000 _init
...
0000000000000000 F *UND* 0000000000000000 sem_open@@GLIBC_2.2.5
0000000000001100 g F .text 000000000000002f _start
...
00000000000011e9 g F .text 0000000000000102 main
0000000000000000 F *UND* 0000000000000000 sem_getvalue@@GLIBC_2.2.5
...
Symbols may refer to a section (for example .text, .data, …) if they are associated to
one, may be absolute (*ABS* , not part of another section) or undefined (*UND* )
when the section is referenced but it is not defined (yet, for dynamic linked libraries,
for example).

You can read every line as: symbol address (number), flags (l if local, g if global, u if
unique global, not global nor local with a space, or both global and local with ! ),
another flag field referring if the symbols is a function ( F ), a file ( f ), an object ( O )
or just a normal symbol (with a space). Not going in a lot of details. If you want to
learn more about this, check objdump documentation and details about syms .

The line that may interest us is where sem_getvalue is. Some comments:

Address is 0000000000000000.
No flags (space) so it is not global nor local.
F, for function.
*UND* as the section is undefined at this moment.

We know some details, but not very much to be able to play something interesting
yet. We can get information on dynamic symbols with objdump too. Instead of
calling “ --syms ” we will invoke “ --dynamic-syms ”:

(frida-tools) frida2 ~$ objdump --dynamic-syms crackme03

crackme03: file format elf64-x86-64

DYNAMIC SYMBOL TABLE:


0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 puts
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.4 __stack_chk_fail
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 printf
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 sem_open
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 sem_getvalue
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 exit
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalize

Here we see that sem_open and sem_getvalue are dynamic objects ( D , for
dynamic) of type “function” ( F , for function). But there add no much additional
information about our crackme .

Ok, let me introduce here a new tool: radare2 (https://rada.re/n/).

Gdb is an excellent tool to debug things, but radare2 is so powerful that many
manual tasks you may follow in gdb , can be automated with little effort on radare2 .
I’m not very good at using radare2, but I’ll try to show some interesting usages
applied to our examples, applications and crackmes . Hope I improve my knowledge
through this book, and, in the future, I can extend it with r2frida ( radare2 extensions
for integrating Frida ).

The recommended way to install radare2 is to clone the


repository (https://github.com/radareorg/radare2.git) and issue a
install command (for the whole system or for a single user).

Install for the whole system (root privileges):

~# sys/install.sh

For your user only:

~$ sys/install.sh

Let’s start getting information from our binary:

(frida-tools) frida2 ~$ r2 crackme03


[0x00001100]> iI
arch x86
baddr 0x0
binsz 14947
bintype elf
bits 64
canary true
class ELF64
compiler GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
crypto false
endian little
havecode true
intrp /lib64/ld-linux-x86-64.so.2
laddr 0x0
lang c
linenum true
lsyms true
machine AMD x86-64 architecture
maxopsz 16
minopsz 1
nx true
os linux
pcalign 0
pic true
relocs true
relro full
rpath NONE
sanitiz false
static false
stripped false
subsys linux
va true
[0x00001100]>

Radare2 gives us a lot of detail about the binary and its characteristics. To focus on
what is relevant, we see: it is x86 ( arch x86 ), is an ELF ( bintype elf ), is 64 bits
( bits 64 ), little endian, C language ( lang c ) and it is not stripped , nor static .

Let’s find exports within this ELF :

[0x00001100]> iE
[Exports]

nth paddr vaddr bind type size lib name


――――――――――――――――――――――――――――――――――――――――――――――――――――――
46 0x00001360 0x00001360 GLOBAL FUNC 5 __libc_csu_fini
50 ---------- 0x00004010 GLOBAL NOTYPE 0 _edata
51 0x00001368 0x00001368 GLOBAL FUNC 0 _fini
55 0x00003000 0x00004000 GLOBAL NOTYPE 0 __data_start
57 0x00003008 0x00004008 GLOBAL OBJ 0 __dso_handle
58 0x00002000 0x00002000 GLOBAL OBJ 4 _IO_stdin_used
59 0x000012f0 0x000012f0 GLOBAL FUNC 101 __libc_csu_init
60 ---------- 0x00004018 GLOBAL NOTYPE 0 _end
62 0x00001100 0x00001100 GLOBAL FUNC 47 _start
63 ---------- 0x00004010 GLOBAL NOTYPE 0 __bss_start
64 0x000011e9 0x000011e9 GLOBAL FUNC 258 main
67 ---------- 0x00004010 GLOBAL OBJ 0 __TMC_END__

[0x00001100]>

Nothing particularly useful here, apart from “ main ” if we want to say something.

Radare2 has a set of cli tools that can be used for several things. If you remember,
we issued a “ strings ” command to extract all the text strings found within the binary.
With radare2 tool, rabin2 , we can extract more meaningful strings, focused on
.rodata section (less noise that printing all symbols in raw):

(frida-tools) frida2 ~$ rabin2 -z ./crackme03


[Strings]
nth paddr vaddr len size section type string
―――――――――――――――――――――――――――――――――――――――――――――
0 0x00002008 0x00002008 9 10 .rodata ascii /fridasem

1 0x00002018 0x00002018 32 33 .rodata ascii sem_open: error cannot open [%s]

2 0x00002040 0x00002040 37 38 .rodata ascii sem_getvalue: error cannot get value.

3 0x00002068 0x00002068 30 31 .rodata ascii Sem value right now is = [%d]\n

4 0x00002088 0x00002088 49 50 .rodata ascii You reached victory and glory, you are da'Hacker!

5 0x000020ba 0x000020ba 27 28 .rodata ascii Not achieved mere mortal...

(frida-tools) frida2 ~$
Using rabin2 is the same as using “ iz ” within the radare2 interpreter:

[0x00001100]> iz
[Strings]
nth paddr vaddr len size section type string
-------------------------------------------------------
0 0x00002008 0x00002008 9 10 .rodata ascii /fridasem

1 0x00002018 0x00002018 32 33 .rodata ascii sem_open: error cannot open [%s]

2 0x00002040 0x00002040 37 38 .rodata ascii sem_getvalue: error cannot get value.

3 0x00002068 0x00002068 30 31 .rodata ascii Sem value right now is = [%d]\n

4 0x00002088 0x00002088 49 50 .rodata ascii You reached victory and glory, you are da'Hacker!

5 0x000020ba 0x000020ba 27 28 .rodata ascii Not achieved mere mortal...

[0x00001100]>

And more interesting, you may filter matches for a given substring. For example,
“ Hacker ”:

[0x00001100]> iz~Hacker
4 0x00002088 0x00002088 49 50 .rodata ascii You reached victory and glory, you are da'Hacker!
[0x00001100]>

Perfect. Confirmed we have the string in the binary we have loaded. As we assume
we have a main function here, we can position it in memory. In radare2 “ s ” means
“ seek ”:

[0x00001100]> s main
[0x000011e9]>

If you see, when “seeking” for main, we changed the memory address where our
cursor in radare2 cli was: was 0x1100 and then was 0x11e9 ( 0x00001100 →
0x000011e9 ).

Just to test, we may seek to the position that our target string was located, 0x2088
and see what we can find there:

[0x000011e9]> s 0x2088
[0x00002088]> pd 2
;-- str.You_reached_victory_and_glory__you_are_da_Hacker:
0x00002088 .string "You reached victory and glory, you are da'Hacker!" ; len=50
;-- str.Not_achieved_mere_mortal...:
0x000020ba .string "Not achieved mere mortal..." ; len=28

The “ pd 2 ” command asks radare2 to show us TWO (2) instructions from the point
we are located, so it shows the definition of two strings, one for success, another for
failure.
Let’s back to main ( s main ) and show a bunch of instructions from there:

[0x000011e9]> s main
[0x000011e9]> pd 18
;-- main:
0x000011e9 f3 invalid
0x000011ea 0f invalid
0x000011eb 1e invalid
0x000011ec fa cli
0x000011ed 55 push rbp
0x000011ee 4889e5 mov rbp, rsp
0x000011f1 4883ec40 sub rsp, 0x40
0x000011f5 897ddc mov dword [rbp - 0x24], edi
0x000011f8 488975d0 mov qword [rbp - 0x30], rsi
0x000011fc 488955c8 mov qword [rbp - 0x38], rdx
0x00001200 64488b042528. mov rax, qword fs:[0x28]
0x00001209 488945f8 mov qword [rbp - 8], rax
0x0000120d 31c0 xor eax, eax
0x0000120f c745ec000000. mov dword [rbp - 0x14], 0
0x00001216 b901000000 mov ecx, 1
0x0000121b bab0010000 mov edx, 0x1b0
0x00001220 be40000000 mov esi, 0x40
; segment.PHDR

0x00001225 488d3ddc0d00. lea rdi, qword str.fridasem


; 0x2008 ; "/fridasem"

We see here a string “ /fridasem ” is referenced (is the name for the semaphore.
Remember you can find it on /dev/shm/ ). Ok, just using the disassembler we can
follow program execution and take an attentive look at our Hacker-success string
appearing.

But disassembling is not the most comfortable way of doing this. You can use visual
mode and use the arrows to go forward and back in the execution flow. To enter
visual mode you can use “ v ” and, important, to exit it use ESC or “ q ”:

[0x00001100]> v

The very first thing you will notice on the visual mode is that you have three panels
and a menu on top:
To the left, the biggest one, is the disassembly.
To the right, on the top area, you may find functions information. The
title “Functions (afl)” is suggesting that you can find the same
information with the “afl” command. We will dig on this later.
And in the bottom-right corner, you have the symbols list (isq command).

You can change the active panel using the TAB key, so if you press it several times
you will move around the windows. The arrow keys are functional, so you can move
up, down, right and left (in the disassembly panel this is a must if your screen is not
wide enough, and you cannot see the annotations).

At this point you are not interested in other panels than the disassembly, so you can
press ENTER to zoom; make sure this is the active panel. If not, you can press
ENTER again to exit the zoom-mode and select the proper panel with TAB . Or,
even better, use TAB to cycle panels on zoom-mode :)

If your terminal allows you to do so, you can use the mouse to click on panels and
select them. But it is quicker to use TAB and ENTER , anyway.

When in disassembly zoom-mode, you can move around with the keys, remember.
So, press arrow down and watch carefully until you see something relevant (for
example, a reference to the Hacker-success string we are looking for):
Look of this code here, where we found the string. Try to read it in English (if you
know assembly language this is silly for you, but for people who are not used to
reverse engineering or to code in assembler, well…).

See it closer:

Reading it in English from memory position 0x12af to memory position


0x12cb ( 0x000012af → 0x000012cb ):

1. cmp eax, 0x7a69: it is comparing the value in the register eax with a fixed
value 0x7a69. We will get back to this later.
2. jne 0x12c4: jump if not equal to memory position 0x12c4. This means,
well, if the comparison says that they are not equal, jump to this memory
particular position.
3. lea rdi, qword str.You_reached_victory_and_glory__you_are_da_Hacker:
this is an assembler instruction mnemonic for “load effective address”
(lea). This will load the memory address of the string
str.You_reached_victory_and_glory__you_are_da_Hacker onto the rdi
register (as far as you should know now, will put the string in the proper
place to be used).
4. call sym.imp.puts: call instruction in assembler invokes a function (calls it)
by going to its memory location.
5. jmp 0x12d0: is a jump (jmp) with no conditions. It will jump to this
memory location yes or yes (it is called unconditional jump).
6. lea rdi, qword str.Not_achieved_mere_mortal…: the same lea instruction
we mentioned before but in this case loading the failure string onto rdi
register.
7. call sym.imp.puts: again calling a function associated with the symbol
sym.imp.puts (will put a string on the screen).

So, will compare the value in eax (is the value we read with the function
sem_getvalue ) with the hexadecimal value 0x7a69 . Then, if equal, will put the
string “ You reached victory and glory, you are da’Hacker ” in the screen and after
that will jump to address location 0x12d0 (after the other instructions that are
putting on screen the failure message).

If not equal, put the string “ Not achieved mere mortal… ” on the screen.

What is the mysterious 0x7a69 value? In decimal is 31337 that is very typical in
hacking scene as you can read it as eleet (and 1337 , as leet ). Elite . And this is the
number we want for our comparison, so with it, we can edit our handler in
JavaScript to achieve the goal.
Summary up to this point. It is quite important to remember these things:

Radare2 is a powerful tool that can be used for many things when
debugging.
We can hook whatever the function we want with Frida: it is not relevant
if they are local or imported, nor if they are semaphores or comparisons.
When the result value in onLeave is not placed in the retval, we can also
work with pointers and memory locations, even rewriting them.
The item “this” can be used to pass values from onEnter to onLeave.
VERY IMPORTANT.
There is no single tool to be used: use gdb, objdump, strings, ldd, radare2
and, of course, frida to help you in achieving your goals and solving
challenging problems.
CHAPTER 6. OTHER TOOLS WITHIN
FRIDA

This will be a short chapter with a quick review of the


rest of the cli tools packed with frida-tools package. I
beg you some patience, as many of the awesome
functionalities on these tools will be intentionally
omitted until we reach Chapter 7 (where we will unveil
frida-server and remote capabilities).
frida-ps

Is an alternative to the standard ps a tool to enumerate


running processes on a *ix box. If we request help and
usage:

(frida-tools) frida2 ~$ frida-ps --help


Usage: frida-ps [options]

Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-O FILE, --options-file=FILE
text file containing additional command line options
-a, --applications list only applications
-i, --installed include all installed applications
-j, --json output results as JSON

Without arguments it will show a list of running


processes where we can attach to:

(frida-tools) frida2 ~$ frida-ps


PID Name
----- -----------------------------
3355 at-spi-bus-launcher
3434 at-spi2-registryd
9872 bash
3150 dbus-daemon
3360 dbus-daemon
3468 dconf-service
3474 evolution-addressbook-factory
3584 evolution-alarm-notify
3459 evolution-calendar-factory
3449 evolution-source-registry
65549 firefox
66123 firefox
66167 firefox
66196 firefox
3233 gdm-x-session
72787 gnome-calendar
3263 gnome-session-binary

In several places I’ve seen they refer to this tool as with


the capability of filtering processes where Frida is able
to attach to, but this is not exact. If you remember
simple0 application, we generated a static version of the
binary: simple0_static .

See what happens when we issue a frida-ps :

(frida-tools) frida2 ~$ frida-ps


PID Name
----- -----------------------------
...
80292 python
80102 simple0_static
3641 snap-store
3138 systemd

And see the result of trying to attach to this process from


both a script and frida-trace :

(frida-tools) frida2 ~$ python simple0_static.py


Traceback (most recent call last):
File "simple0_static.py", line 6, in <module>
session = frida.attach("simple0_static")
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/__init__.py", line 62, in attach
return get_local_device().attach(target, *args, **kwargs)
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/core.py", line 26, in wrapper
return f(*args, **kwargs)
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/core.py", line 156, in attach
return Session(self._impl.attach(self._pid_of(target), *args,
**kwargs))
frida.NotSupportedError: unable to inject library into process
without libc
(frida-tools) frida2 ~$

(frida-tools) frida2 ~$ frida-trace -p 80102


Failed to attach: unable to inject library into process without libc
(frida-tools) frida2 ~$

One interesting option is the JSON output, with -j :

(frida-tools) frida2 ~$ frida-ps -j


[
{
"pid": 41188,
"name": "Process2"
},
...
{
"pid": 80102,
"name": "simple0_static"
}
]
(frida-tools) frida2 ~$

And several other arguments oriented to the use of Frida


with remote capabilities or in mobile devices through
USB .
We will leave frida-ps by now, as we will move soon
onto the use of frida-server and remote instrumentation
in both mobile and Linux. But let’s make a quick review
on frida-kill , first.
frida-kill

We can use this tool as the standard kill command on


UNIX , but it can take both a process PID (numeric) or
a name as argument (as in killall ).

(frida-tools) frida2 ~$ frida-kill --help


Usage: frida-kill [options] process

Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-O FILE, --options-file=FILE
text file containing additional command line options
(frida-tools) frida2 ~$

We run our simple0_static process in one terminal:

(frida-tools) frida2 ~$ ./simple0_static


Press an initial key, please:
Killed
(frida-tools) frida2 ~$

This output was the result of issuing a kill using the


process PID :

(frida-tools) frida2 ~$ frida-kill 88442

Same behavior exactly if using the name:


(frida-tools) frida2 ~$ frida-kill simple0_static

But even better, we can use a pattern to match processes.


For example, a wildcard:

(frida-tools) frida2 ~$ frida-kill simple0_stat*

Or other different shell patterns:

(frida-tools) frida2 ~$ frida-kill simple[012]_static

As with frida-ps, frida-kill has many other interesting


options we can use with mobile devices or when using
frida-server , in remote.
frida-ls-devices

This tool will enumerate available devices to connect


using USB . If we ask for help:

(frida-tools) frida2 ~$ frida-ls-devices --help


Usage: frida-ls-devices [options]

Options:
--version show program's version number and exit
-h, --help show this help message and exit
-O FILE, --options-file=FILE
text file containing additional command line options
(frida-tools) frida2 ~$

As we are not yet connecting any mobile devices, if we


ask for the list:

(frida-tools) frida2 ~$ frida-ls-devices


Id Type Name
------ ------ ------------
local local Local System
socket remote Local Socket

Again, please be patient as we will improve all these


tools soon.
frida-discover

This tool allows us to investigate a process to detect


internal functions in an application. Then they can be
traced by using scripts or frida-trace .

(frida-tools) frida2 ~$ frida-discover --help


Usage: frida-discover [options] target

Options:
--version show program's version number and exit
-h, --help show this help message and exit
-D ID, --device=ID connect to device with the given ID
-U, --usb connect to USB device
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
-f FILE, --file=FILE spawn FILE
-F, --attach-frontmost
attach to frontmost application
-n NAME, --attach-name=NAME
attach to NAME
-p PID, --attach-pid=PID
attach to PID
--stdio=inherit|pipe stdio behavior when spawning (defaults to "inherit")
--aux=option set aux option when spawning, such as "uid=(int)42"
(supported types are: string, bool, int)
--realm=native|emulated
realm to attach in
--runtime=qjs|v8 script runtime to use
--debug enable the Node.js compatible script debugger
--squelch-crash if enabled, will not dump crash report to console
-O FILE, --options-file=FILE
text file containing additional command line options
(frida-tools) frida2 ~$
In the next chapter we will play with frida-discover to
watch a process ( notepad.exe in Windows 10 ) and try
to identify different functions, calls and elements to be
traced.

Many times, using frida-discover saves a lot of time


when dealing with APIs we are not sure how they work
or to make a deep identification of invoked function
calls that may not be predicted in an initial, manual,
analysis.
CHAPTER 7. THE POWERFUL FRIDA-
SERVER

With frida-server we can trace remote applications


from our local console. You will need to download the
proper binary (or compile yourself) for the platform you
need to instrument. There exist several options by
default for Android , Linux , iOS , Mac OSX and
Wintel . Naturally, for both 32 and 64 bts, ARM , …

In my test lab, I will show you how to start the server


and connect to it with several devices and architectures.

(frida-tools) frida2 ~$ ./frida-server-14.2.9-linux-x86_64 --help


Usage:
frida [OPTION?]

Help Options:
-h, --help Show help options

Application Options:
--version Output version information and exit
-l, --listen=ADDRESS Listen on ADDRESS
-d, --directory=DIRECTORY Store binaries in DIRECTORY
-D, --daemonize Detach and become a daemon
-P, --disable-preload Disable preload optimization
-C, --ignore-crashes Disable native crash reporter integration
-v, --verbose Be verbose

For example, to start with a server running in a Linux


x86_64 :
(frida-tools) frida2 ~$ ./frida-server-14.2.9-linux-x86_64 -l
127.0.0.1:54321

And that’s all :)

To verify if the server is running, we can look for the


process and listening socket in many ways. See some
examples starting with, obviously, our favorite tool
frida-ps :

(frida-tools) frida2 ~$ frida-ps |grep frida-server


95795 frida-server-14.2.9-linux-x86_64

Or using netstat :

(frida-tools) frida2 ~$ netstat -antp|grep frida


(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
tcp 0 0 127.0.0.1:54321 0.0.0.0:* LISTEN
95795/./frida-serve
(frida-tools) frida2 ~$

Or with lsof :

(frida-tools) frida2 ~$ lsof |grep frida-server


frida-ser 95795 user txt REG 253,5 54449752
14943180 /home/user/Frida/simple_apps/2/frida-server-14.2.9-linux-
x86_64
frida-ser 95795 95796 frida-ser user txt REG
253,5 54449752 14943180
/home/user/Frida/simple_apps/2/frida-server-14.2.9-linux-x86_64
frida-ser 95795 95797 frida-ser user txt REG
253,5 54449752 14943180
/home/user/Frida/simple_apps/2/frida-server-14.2.9-linux-x86_64
frida-ser 95795 95807 frida-ser user txt REG
253,5 54449752 14943180
/home/user/Frida/simple_apps/2/frida-server-14.2.9-linux-x86_64
(frida-tools) frida2 ~$

Server is running and we can connect from remote tools.


Remember this interesting frida-ps that we did not
review in detail? Hey, now we can. Let’s issue a remote
list of processes through the server connection. Not sure
about the specific options? Ask for help:

(frida-tools) frida2 ~$ frida-ps --help


Usage: frida-ps [options]

Options:
...
-R, --remote connect to remote frida-server
-H HOST, --host=HOST connect to remote frida-server on HOST
...
-j, --json output results as JSON
(frida-tools) frida2 ~$

As we are not connecting using USB but a TCP/IP


socket ( 127.0.0.1 on port 54321 ), the proper option to
use is “ -H 127.0.0.1:54321 ” or “ --
host=127.0.0.1:54321 ”. The other option that may seem
evident, -R (--remote ) is for the USB
instrumentation, remember.

(frida-tools) frida2 ~$ frida-ps -H 127.0.0.1:54321


PID Name
----- --------------------------------
3355 at-spi-bus-launcher
3434 at-spi2-registryd
9872 bash
...
94606 firefox
95795 frida-server-14.2.9-linux-x86_64
...
3683 xdg-document-portal
3438 xdg-permission-store
(frida-tools) frida2 ~$

We can issue the same “ ps ” for a frida-server running


in a Windows 10 box:

And this is the result:

(frida-tools) frida2 ~$ frida-ps -H 192.168.1.18:54321


PID Name
----- ------------------------------------------------------------------
14332 Adobe CEF Helper.exe
11864 Adobe Desktop Service.exe
7144 AdobeIPCBroker.exe
6956 AdobeNotificationClient.exe
9084 Agent.exe
6396 AppVShNotify.exe
19268 Battle.net.exe
20392 Battle.net.exe
21328 Battle.net.exe
18856 Battle.net.exe
5804 CCLibrary.exe
9552 CCXProcess.exe
11028 ClamTray.exe
13120 CompPkgSrv.exe
3336 CoreSync.exe
12264 Creative Cloud Helper.exe
9888 Creative Cloud.exe
15152 HPNETW~1.EXE
10056 LockApp.exe
14600 Microsoft.Photos.exe
8384 RuntimeBroker.exe
9032 RuntimeBroker.exe
...
21420 RuntimeBroker.exe
11072 ScanToPCActivationApp.exe
8684 SearchUI.exe
11164 SecurityHealthHost.exe
10640 SecurityHealthSystray.exe
6404 SettingSyncHost.exe
6644 ShellExperienceHost.exe
8204 StartMenuExperienceHost.exe
15964 SystemSettingsBroker.exe
10376 WINWORD.EXE
8504 WindowsInternal.ComposableShell.Experiences.TextInput.InputApp.exe
19820 YourPhone.exe
22336 backgroundTaskHost.exe
21772 cmd.exe
20612 conhost.exe
10588 dllhost.exe
7556 explorer.exe
15212 explorer.exe
6568 firefox.exe
...
22328 frida-server-14.2.9-windows-x86_64.exe
8936 hpwuschd2.exe
568 node.exe
12696 node.exe
8092 nxclient.bin
2476 sihost.exe
16240 smartscreen.exe
9376 splwow64.exe
6408 svchost.exe
6492 svchost.exe
6828 taskhostw.exe
(frida-tools) frida2 ~$

(World of Warcraft, Alliance, druid, if you are


wondering about the Battle.net processes)

With the same syntax for the connection, we may use


frida-discover (again from my Linux accessing frida-
server in remote Windows , remember):

(frida-tools) frida2 ~$ frida-discover -H 127.0.0.1:54321 simple2


Tracing 1 threads. Press ENTER to stop.
Process terminated
Stopping...
(frida-tools) frida2 ~$

And, of course, frida-kill :

(frida-tools) frida2 ~$ frida-kill -H 127.0.0.1:54321 simple2


(frida-tools) frida2 ~$

# in the other terminal


(frida-tools) frida2 ~$ ./simple2
Please, BE CAREFUL with ctrl-c as it will not return back your
terminal (issue a stty cooked)
Press keys to test the password: exit with Q.

Killed
(frida-tools) frida2 ~$
We can use frida-trace . Let’s locate memory address
for the function “ is_correct ”:

(frida-tools) frida2 ~$ objdump -d simple2|grep is_correct


00000000000011a9 <is_correct>:

This one: 0x11a9 . And see if we can trace passing it to


frida-trace with -a option:

(frida-tools) frida2 ~$ frida-trace -H 127.0.0.1:54321 -a


simple2\!0x11a9 simple2
Instrumenting...
sub_11a9: Auto-generated handler at
"/home/user/Frida/simple_apps/2/__handlers__/simple2/sub_11a9.js"
Started tracing 1 function. Press Ctrl+C to stop.

Now in the terminal (remember, we are assuming it is in


a remote server, no matter we are connecting to
localhost) we press several keys:

(frida-tools) frida2 ~$ ./simple2


Please, BE CAREFUL with ctrl-c as it will not return back your
terminal (issue a stty cooked)
Press keys to test the password: exit with Q.

WRONG!
bKey [b] was pressed.
WRONG!
cKey [c] was pressed.
WRONG!

And see the default log lines for simple2!0x11a9 :


Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x17a52 */
24491 ms sub_11a9()
26251 ms sub_11a9()
26867 ms sub_11a9()

Now, you know what to do, isn’t? We edit this


sub_11a9() , modify the onLeave handler, replace the
return ( retval ) with the proper value and see everything
going perfect (or not ;) ).

Our new handler (modified directly on


__handlers__/simple2/sub_11a9.js ):

onLeave(log, retval, state) {


retval.replace(1);
}

Re-trace simple2 :

(frida-tools) frida2 ~$ frida-trace -H 127.0.0.1:54321 -a


simple2\!0x11a9 simple2
Instrumenting...
sub_11a9: Loaded handler at
"/home/user/Frida/simple_apps/2/__handlers__/simple2/sub_11a9.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x17ccd */
2080 ms sub_11a9()
2760 ms sub_11a9()
3888 ms sub_11a9()
4208 ms sub_11a9()
4545 ms sub_11a9()
4865 ms sub_11a9()
5249 ms sub_11a9()
5656 ms sub_11a9()
5968 ms sub_11a9()
6329 ms sub_11a9()
Process terminated

All our keystrokes were considered CORRECT, but we


failed in the password. So, something else is happening
here (we will get back to this simple2 on next chapter):

(frida-tools) frida2 ~$ ./simple2


Please, BE CAREFUL with ctrl-c as it will not return back your
terminal (issue a stty cooked)
Press keys to test the password: exit with Q.

xKey [x] was pressed.


CORRECT!
xKey [x] was pressed.
CORRECT!
xKey [x] was pressed.
CORRECT!
xKey [x] was pressed.
CORRECT!
xKey [x] was pressed.
CORRECT!
xKey [x] was pressed.
CORRECT!
xKey [x] was pressed.
CORRECT!
xKey [x] was pressed.
CORRECT!
xKey [x] was pressed.
CORRECT!
xKey [x] was pressed.
CORRECT!
YOU FAIL! So sorry...

(frida-tools) frida2 ~$
Yes, we can trace a remote application and do the
interesting things we did with our JavaScript
handlers… just running the frida-server tool.

The behavior was exactly the same, with the main


difference that the output was not in the same terminal
as we were attaching to the remote.
frida-ps (again but in linux-ARM)

We have installed a frida-server binary on a Raspberry


PI 400 (it is awesome, keyboard with the RPI within
and you only need to connect to an HDMI screen and
voila!).

RPI400 is an ARMv7 rev 3 processor, so we


downloaded the proper server (this one frida-server-
14.2.10-linux-armhf.xz ) and started it. This is frida-ps
working:

(frida-tools) frida3 ~$ frida-ps -H 192.168.1.140:54321


PID Name
---- --------------------------------
717 -bash
4782 bash
8529 bash
8079 chromium-browser-v7
8106 chromium-browser-v7
8107 chromium-browser-v7
8110 chromium-browser-v7
8132 chromium-browser-v7
588 dbus-daemon
8526 frida-server-14.2.10-linux-armhf
764 gvfs-afc-volume-monitor
752 gvfs-goa-volume-monitor
756 gvfs-gphoto2-volume-monitor
760 gvfs-mtp-volume-monitor
738 gvfs-udisks2-volume-monitor
623 gvfsd
628 gvfsd-fuse
826 gvfsd-trash
646 lxpanel
642 lxpolkit
580 lxsession
1433 lxterminal
746 menu-cached
639 openbox
648 pcmanfm
674 pulseaudio
670 python3
658 sh
569 systemd
663 xcompmgr
(frida-tools) frida3 ~$

Just to recall. Anything you can do with Frida on a


local machine, can be done in a remote one no matter
the architecture; you can compile your own frida-
server version too. We will work in MIPS platforms and
several others in the next book “More on Frida”.
frida-discover (again)

Let’s take back frida-discover here and see what we


can get from frida-server and notepad.exe on a
Windows 10 box . This will take some time. This is the
reason it has its own title in the chapter. Maybe it is a
good idea to make a pause here and get back when you
have almost one hour to dedicate.

So, we will open notepad on Windows 10 and, then,


connect to frida-server requesting discover to examine
the process:

It is a Spanish version of the operating system, but you


get the idea.
Now invoke frida-discover and see:

(frida-tools) frida2 ~$ frida-discover -H 192.168.1.18:54321 -n


notepad.exe
Tracing 5 threads. Press ENTER to stop.

We will type some identifiable strings on notepad


( AAAA , BBBB and AAAA ):

And press ENTER to see results of discovery (sorry, I


cut the results as it will require several pages of listing):

(frida-tools) frida2 ~$ frida-discover -H 192.168.1.18:54321 -n


notepad.exe
Tracing 5 threads. Press ENTER to stop.
shcore.dll
Calls Function
113 sub_39760
74 sub_b170
...
1 sub_87f0
1 sub_75d8
...
1 sub_8584

USER32.dll
Calls Function
399 sub_131a0
399 GetWindowLongPtrW
364 GetPropW
308 TranslateAcceleratorW
...
49 SendMessageW
44 GetParent
36 sub_13810
...
17 sub_3528f
...
1 ReleaseCapture

win32u.dll
Calls Function
417 NtUserGetProp
...
1 NtGdiQueryFontAssocInfo

gdi32full.dll
Calls Function
1411 sub_119d0
809 sub_40360
...
51 ScriptStringAnalyse
...
1 sub_b9a0
1 sub_31898
...
1 sub_28360

GDI32.dll
Calls Function
1327 GdiGetEntry
...
3 CreateSolidBrush

COMCTL32.dll
Calls Function
848 sub_8eab0
...
1 sub_f8cb0

uxtheme.dll
Calls Function
125 sub_27c60
...
3 sub_179b8

ntdll.dll
Calls Function
5297 sub_8cb90
1367 RtlLeaveCriticalSection
...
1 ZwSetInformationKey

msvcrt.dll
Calls Function
2 memcpy_s
2 memmove

KERNELBASE.dll
Calls Function
178 LocalUnlock
178 LocalLock
...
1 sub_8c2bc
KERNEL32.DLL
Calls Function
1560 TlsGetValue
262 TlsSetValue
181 GetCurrentThreadId
2 MulDiv
1 TermsrvOpenRegEntry

IMM32.DLL
Calls Function
112 ImmUnlockIMCC
...
16 ImmSetCompositionWindow

MSCTF.dll
Calls Function
112 sub_48f10
...
16 sub_2a870
16 sub_13aa4

CoreMessaging.dll
Calls Function
6 sub_b628
...
1 sub_102c0

notepad.exe
Calls Function
75 sub_20400
30 sub_853c

Stopping...
(frida-tools) frida2 ~$

See the difference in a real application from our


simples and crackmes ? Frida-discover is identifying a
lot (a lot) of API calls and functions that may or may
not be interesting for us to manipulate and instrument
the process.

The “Calls” number is, of course, the


number of times this API call or function
has been invoked, to make it simpler to
follow execution flow.

This is the moment where I’ll share a secret my family


has been keeping for centuries. In the moment I unveil
this, your life will change forever. This is a point of no
return. Are you ready?

Ok. There is no magic here. If you want to manage the


process to do what you want, you should dedicate time
and investigate what, how and why these calls are made.
With which arguments, which return values, messages
passed, errors, warnings, and success messages… There
is no inner “talent” nor wizardry nor guru knowledge:
just investing a lot of time doing this analysis and
understanding what’s happening there. After a lot of
processes, you will have what they call “intuition”. Not
because it is a magical ability but because you have seen
thousands of function calls and see patterns there.

So, our next step is to take decisions on what to focus on


this (almost infinite) list of possibilities. Before going
for handlers, I think it is a good idea to enumerate the
modules on notepad.exe. We have several ways to do so,
but maybe it is a good idea to create a script and show
you how to connect to a frida-server with Python
bindings.
frida-remote and Python/JavaScript scripts

Now we’ve created some scripts to be able to enumerate


items in the remote host. The first one, just to enumerate
modules (as we already saw in previous chapters).

See the source:

(frida-tools) frida2 ~$ cat notepad_remote.py


import frida
import sys
import readchar

# frida-server remote IP address


HOST = "192.168.1.18"
# frida-server listening port
PORT = "54321"

# We get a DEVICE
device = frida.get_device_manager().add_remote_device(
'{host}:{port}'.format(host=HOST,
port=PORT))

# and attach the session using this device.


session = device.attach("notepad.exe")

# Then we call the enumerate modules script.


script = session.create_script("""
Process.enumerateModules({
onMatch: function(module){
console.log('Module name: ' + module.name + " -> " + "addr: " +
module.base.toString());
},
onComplete: function(){}
});
""")
script.load()

print("Press a key to detach...")


readchar.readkey()

session.detach()
print("Detached.")

(frida-tools) frida2 ~$

Not rocket science. You know all the details so far. If we


execute the script:

(frida-tools) frida2 ~$ python notepad_remote.py


Module name: notepad.exe -> addr: 0x7ff790560000
Module name: ntdll.dll -> addr: 0x7ffe97640000
Module name: KERNEL32.DLL -> addr: 0x7ffe97540000
...
Module name: msvcp_win.dll -> addr: 0x7ffe94f70000
Module name: ucrtbase.dll -> addr: 0x7ffe952f0000
Module name: USER32.dll -> addr: 0x7ffe96040000
Module name: msvcrt.dll -> addr: 0x7ffe96e50000
...
Module name: uxtheme.dll -> addr: 0x7ffe92a90000
Module name: clbcatq.dll -> addr: 0x7ffe96b30000
Module name: MrmCoreR.dll -> addr: 0x7ffe865c0000
...
Module name: ntmarta.dll -> addr: 0x7ffe93620000
Module name: iertutil.dll -> addr: 0x7ffe88f30000
Module name: ole32.dll -> addr: 0x7ffe96510000
Module name: CRYPTBASE.DLL -> addr: 0x7ffe93f00000
Module name: frida-agent.dll -> addr: 0x7ffe29670000
Module name: CRYPT32.dll -> addr: 0x7ffe94d90000
...
Module name: IPHLPAPI.DLL -> addr: 0x7ffe93a40000
Press a key to detach...
Detached.
(frida-tools) frida2 ~$

Naturally, frida-agent.dll IS a module within the scope


of Notepad , as we are instrumenting it to enumerate all
the other modules. Let me explain the results, for you to
take note of how the JavaScript is invoked and which
object you get with the “ module ” result.

Right now, I want to focus on USER32.DLL because I


want to do some evil things to Notepad, and I need some
functions from this module. So, I will enumerate module
exports, just to see all the functions and variables.

See our enumerate exports script listing functions (and


maybe variables) in USER32 . Again, my apologies for
the long listing, but I consider interesting keeping it
intact for educational reasons (for you to see the results):

(frida-tools) frida2 ~$ python notepad_remote_enumerate_exports.py


=== USER32.DLL FOUND ===
Module name: user32.dll -> addr: 0x7ffe96040000
=== USER32 export list ===
...
[F] BroadcastSystemMessage -> 0x7ffe960c1720
[F] BroadcastSystemMessageA -> 0x7ffe960c1720
...
[F] ChangeDisplaySettingsW -> 0x7ffe960bff10
[F] ChangeMenuA -> 0x7ffe960af540
[F] ChangeMenuW -> 0x7ffe960af5e0
...
[F] CharToOemBuffA -> 0x7ffe960b30a0
[F] CharToOemBuffW -> 0x7ffe960b30f0
[F] ClipCursor -> 0x7ffe960720f0
[F] CloseClipboard -> 0x7ffe9606b220
[F] CloseDesktop -> 0x7ffe96072100
[F] CloseGestureInfoHandle -> 0x7ffe9608cbf0
...
[F] DwmValidateWindow -> 0x7ffe96072320
[F] EditWndProc -> 0x7ffe9609d3f0
[F] EmptyClipboard -> 0x7ffe96070c20
[F] EnableMenuItem -> 0x7ffe96060e20
...
[F] FillRect -> 0x7ffe960635a0
[F] FindWindowA -> 0x7ffe960ba4b0
[F] FindWindowExA -> 0x7ffe96064c60
[F] FindWindowExW -> 0x7ffe96065480
[F] FindWindowW -> 0x7ffe96063d80
[F] FlashWindow -> 0x7ffe960bd8b0
[F] FlashWindowEx -> 0x7ffe96072440
[F] FrameRect -> 0x7ffe9606f160
...
[F] GetClipboardAccessToken -> 0x7ffe960724e0
[F] GetClipboardData -> 0x7ffe96069dd0
[F] GetClipboardFormatNameA -> 0x7ffe960ba500
...
[F] LoadImageA -> 0x7ffe9606e750
[F] OffsetRect -> 0x7ffe96051d70
[F] OpenClipboard -> 0x7ffe9606a330
[F] OpenDesktopA -> 0x7ffe96068500
...
[F] SendIMEMessageExW -> 0x7ffe960bade0
[F] SendInput -> 0x7ffe96072f80
[F] SendMessageA -> 0x7ffe96068380
...
[F] TranslateMessage -> 0x7ffe96059e40
...
[F] gapfnScSendMessage -> 0x7ffe960c7940
[F] keybd_event -> 0x7ffe960c0e20
[F] mouse_event -> 0x7ffe9606aa30
[F] wsprintfA -> 0x7ffe960665f0
...
Press a key to detach...
Detached.
(frida-tools) frida2 ~$
There are a lot of interesting functions there. I’m going
to focus on a few of them, just to show examples of how
to invoke NativeFunction (new concept!) based on the
export location in a module (or by address).

Let’s start with GetClipboardData .

https://docs.microsoft.com/en-
us/windows/win32/api/winuser/nf-winuser-
getclipboarddata

HANDLE GetClipboardData(
UINT uFormat
);

If we review the API carefully, we can get the


definition, return value and parameters.

Return value is HANDLE: frida does not have


a “HANDLE” type, but we can use unsigned
int for this purpose and, as we are in a x86_64,
we will use a unsigned 64bits int, uint64.
uFormat argument of type UINT: so we will
use unsigned int (uint64, too). And we will
need to understand what “uFormat” is for.

But more important, it is necessary to read the details of


how this function should be invoked. For example, and
very critical, before invoking this function we must
“open the clipboard” first. Now we have to check the
details of OpenClipboard , naturally.

https://docs.microsoft.com/en-
us/windows/win32/api/winuser/nf-winuser-
openclipboard

BOOL OpenClipboard(
HWND hWndNewOwner
);

Here we have BOOL as return value (we will use an


integer , int64 ) and an argument named
hWndNewOwner with a type HWND (we will use
another integer , int64 ).

The logical process here is:

1. OpenClipboard .
2. GetClipboardData .
3. CloseClipboard (without arguments returning
BOOL if closed).

With this process in mind, we should work in our script.


See the code here (we will enter deep on every relevant
line later):

import frida
import sys
import readchar
# frida-server remote IP address
HOST = "192.168.1.18"
# frida-server listening port
PORT = "54321"

# We get a DEVICE
device = frida.get_device_manager().add_remote_device(
'{host}:{port}'.format(host=HOST,
port=PORT))

# and attach the session using this device.


session = device.attach("notepad.exe")

script = session.create_script("""
console.log('[BEGIN] create_script');
var user32dll = Module.getBaseAddress("user32.dll");
console.log('[user32dll BASE address]: ' + user32dll);

console.log('[user32.dll BEFORE ensure init]: ' + user32dll);


Module.ensureInitialized("user32.dll");
console.log('[user32.dll AFTER ensure init]: ' + user32dll);

// STEP 0. Open the clipboard.


// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-openclipboard
console.log('[CLIPBOARD] Going to open the clipboard with a NativeFunction');
var openClipboard_ptr = Module.findExportByName('user32.dll',
'OpenClipboard');
console.log('[user32.openClipboard_ptr] after. Address: ' + openClipboard_ptr);

var openClipboard = new NativeFunction(openClipboard_ptr,


// the memory address
'int64',
// return type (Bool)
['int64']);
// HWND - window handler
console.log('[openClipboard] NativeFunction set.');
var is_open = openClipboard(0);
// NULL to open for the current task.

if(is_open !== 0) {
console.log('[CLIPBOARD] IS OPEN.');
// OK, is open, read.
var closeClipboard_ptr = Module.findExportByName('user32.dll',
'CloseClipboard');
var closeClipboard = new NativeFunction(closeClipboard_ptr,
// the memory address
'int64',
// return type (Bool)
[]);
// No arguments.
console.log('[closeClipboard] NativeFunction set.');
var getClipboardData_ptr = Module.findExportByName(
'user32.dll',
'GetClipboardData'
);
var getClipboardData = new NativeFunction(getClipboardData_ptr,
//the memory address
'uint64',
// return HANDLE
['uint64']);
// uint format (1)
console.log('[getClipboardData] NativeFunction set.');
// We need to pass CF_TEXT (==1) for the clipboard format.
// https://docs.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats

console.log('[getClipboardData] Going to read data from the clipboard.');


var data = getClipboardData(1); // get clipboard data
var data_ptr = ptr(data);
console.log('[getClipboardData] Data=[' + data_ptr.readUtf8String() + '].');
closeClipboard()
}
else {
console.log('[CLIPBOARD] IS *NOT OPEN*: ERROR, exit.');
}

console.log('[END] create_script');
""")

script.load()

print("Press a key to detach...")


readchar.readkey()

session.detach()
print("Detached.")

In this case we are going to read the script block by


block: it is important to give all the necessary
explanations for every detail without making any
assumption of how much or how less knowledge the
person reading this book has.

import frida
import sys
import readchar

First import is for frida , obviously. Second, “ import


sys ”, is here because we may want to add sys.argv[]
(arguments for the script) to be evaluated. For example,
to pass memory locations directly as parameters; after
making an analysis with objdump , gdb , radare2 or
our preferred method of binary analysis.

# frida-server remote IP address


HOST = "192.168.1.18"
# frida-server listening port
PORT = "54321"

We define here the frida-server IP address and the


related port. Remember, this IP address belongs to a
Windows 10 host. The port was chosen with just one
premise: high port over 1024 to avoid elevated
privileges and run as a standard user.

device = frida.get_device_manager().add_remote_device(
'{host}:{port}'.format(host=HOST,
port=PORT))

Here we call the device_manager(), assigning a remote


device by HOST and PORT to the variable device.
This variable will hold all what we need to attach.

session = device.attach("notepad.exe")

We attach the session to “ notepad.exe ” using the


remote device. In the next chapters, when playing with
Android devices, we will see how to add several
devices to the manager, both in remote servers and
connected locally through USB .

script = session.create_script("""
...
""");

We define here the script to be loaded later. I have


omitted the JavaScript source as we will go in detail
below (formatted as JavaScript code, and not as
python-string syntax).

script.load()
We do load the script here, so it should work on the
attached process doing whatever is intended to do.

print("Press a key to detach...")


readchar.readkey()

We print a message recalling the user to press a key to


detach the session. Then, we read a single character
using the readchar module: readchar.readkey() .

session.detach()
print("Detached.")

As the user demands, we detach the session and print a


confirmation message when getting out the script.

This is the basic script skeleton we have seen several


times in this book. Now we are going to review the
JavaScript code to be executed within the
“ notepad.exe ” context. We will apply JavaScript color
syntax to help in understanding.

console.log('[BEGIN] create_script');

var user32dll = Module.getBaseAddress("user32.dll");


console.log('[user32dll BASE address]: ' + user32dll);

console.log('[user32.dll BEFORE ensure init]: ' + user32dll);


Module.ensureInitialized("user32.dll");
console.log('[user32.dll AFTER ensure init]: ' + user32dll);

// STEP 0. Open the clipboard.


// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-openclipboard
console.log('[CLIPBOARD] Going to open the clipboard with a NativeFunction');
var openClipboard_ptr = Module.findExportByName('user32.dll',
'OpenClipboard');
console.log('[user32.openClipboard_ptr] after. Address: ' + openClipboard_ptr);

var openClipboard = new NativeFunction(openClipboard_ptr,


// the memory address
'int64',
// return type (Bool)
['int64']);
// HWND - window handler
console.log('[openClipboard] NativeFunction set.');

var is_open = openClipboard(0);


// NULL to open for the current task.

if(is_open !== 0) {
console.log('[CLIPBOARD] IS OPEN.');
// OK, is open, read.
var closeClipboard_ptr = Module.findExportByName('user32.dll',
'CloseClipboard');
var closeClipboard = new NativeFunction(closeClipboard_ptr,
//the memory address
'int64',
//return type (Bool)
[]);
//No arguments.
console.log('[closeClipboard] NativeFunction set.');

var getClipboardData_ptr = Module.findExportByName('user32.dll',


'GetClipboardData');
var getClipboardData = new NativeFunction(getClipboardData_ptr,
//the memory address
'uint64',
// return HANDLE
['uint64']);
// uint format (1)
console.log('[getClipboardData] NativeFunction set.');

// We need to pass CF_TEXT (==1) for the clipboard format.


// https://docs.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats

console.log('[getClipboardData] Going to read data from the clipboard.');


var data = getClipboardData(1); // get clipboard data
var data_ptr = ptr(data);
console.log('[getClipboardData] Data=[' + data_ptr.readUtf8String() + '].');
closeClipboard()
}
else {
console.log('[CLIPBOARD] IS *NOT OPEN*: ERROR, exit.');
}

console.log('[END] create_script');

I have reduced some fonts on texts and comments to


make it easier to read and fit onto the available page
space, sorry.

Line by line:

console.log('[BEGIN] create_script');

I tend to add many log lines to follow execution. You


may remove all of them if you consider too much or
they are not useful for you. Is the typical console.log()
used in JavaScript .

var user32dll = Module.getBaseAddress("user32.dll");


console.log('[user32dll BASE address]: ' + user32dll);

Here we get the base location (memory address) for the


module “ user32.dll ”. Then, we print the value in a
logline just to follow execution (debug purposes); note
that this is a string.

console.log('[user32.dll BEFORE ensure init]: ' + user32dll);


Module.ensureInitialized("user32.dll");
console.log('[user32.dll AFTER ensure init]: ' + user32dll);

This step may not be required, but I was just curious


about the ensureInitialized() and, anyway, doing no
harm I consider it interesting to keep. This function
makes sure that the module is available to Frida . Note
that the parameter is a string.

// STEP 0. Open the clipboard.


// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-
winuser-openclipboard
console.log('[CLIPBOARD] Going to open the clipboard with a NativeFunction');
var openClipboard_ptr = Module.findExportByName('user32.dll',
‘OpenClipboard');
console.log('[user32.openClipboard_ptr] after. Address: ' + openClipboard_ptr);

var openClipboard = new NativeFunction(openClipboard_ptr,


// the memory address
'int64',
// return type (Bool)
['int64']);
// HWND - window handler
console.log('[openClipboard] NativeFunction set.');

You will see this construction repeated several times in


the source code: once per user32.function we want to be
able to call from our JavaScript code.
This specific call,
Module.findExportByName('user32.dll',
'OpenClipboard') , will search for a function (or a
variable, an export) named OpenClipboard in
user32.dll module. If found, will return a
NativePointer to this location in memory. If not found
will return null .

If you do not know the module name or are unsure of a


specific one, you may pass null as the first argument but
note this is expensive in resources, as Frida will look
extensively to match a compatible exportName . Try to
avoid using null as possible.

After getting the pointer to the function, we need to


build a JavaScript function to be used from our engine.
See next line:

var openClipboard = new NativeFunction(openClipboard_ptr,


// the memory address
'int64',
// return type (Bool)
['int64']);
// HWND - window handler

Awesome! Frida is… WONDERFUL. As we checked


the API definition for openClipboard , we got an idea of
return value, parameters and all this stuff. Here it is
quite important to look at Frida's supported types, as
BOOL , HANDLE or HWND are not included.
As you can see, we have “transformed” the parameters
to what we thought may fit when passing the
information to the function call. BOOL and HWND as
int64 .

You can review what int64 means for Frida here:


https://frida.re/docs/javascript-api/#int64

Dissecting NativeFunction we will understand:

First argument is a NativePointer to memory


address where we think the function is. We
may even pass here a fixed string like
ptr(“0x7a69”).
Second argument is the expected return type:
remember, Frida does not have a BOOL type,
so we need to use whatever fits. A 64bit
integer is valid for a lot of things so…
Third parameter is a list (an array) with the
required arguments we need to pass to the
function call: again, HWND is not a supported
type in Frida, so after reading a bit on
Microsoft’s developer documentation, int64
seems the logical option.

Pay attention to the difference between


openClipboard_ptr and openClipboard .
The latter is the “real” function call we
have created with a new NativeFunction .
The other is just a pointer (a
NativePointer ) to the position where the
function begins in memory.

This is the way we get access to these methods (loaded


in modules that are available for our process, in this
case: notepad ).

var is_open = openClipboard(0);


// NULL (0) to open for the current task.

if(is_open !== 0) {
// Opened OK.
...
}
else{
...
}

Here we invoke the actual function (remember,


openClipboard is the function, openClipboard_ptr is a
pointer). We pass NULL (the integer 0 ) as the
argument. It will return NULL ( 0 ) if we had an error,
or something different from NULL (different from 0 )
if successful. Typical if-else pattern where we check if
we were successful with the variable “ is_open ”
asserting was different from 0.

If we were successful opening the clipboard, we will get


into this:

console.log('[CLIPBOARD] IS OPEN.');
// OK, is open, read.
So, we confirm the Clipboard is open and can proceed to
trying to steal the content:

var closeClipboard_ptr = Module.findExportByName('user32.dll',


'CloseClipboard');
var closeClipboard = new NativeFunction(closeClipboard_ptr,
//the memory address
'int64',
//return type (Bool)
[]);
//No arguments.
console.log('[closeClipboard] NativeFunction set.');

Here we repeat the construction to “build” our native


function “ closeClipboard ” in the same way as with
“ openClipboard ”. You know the process: go for
Microsoft’s documentation, find this API , check return
value and parameters and transform it to match Frida’s
capabilities.

Return type is BOOL , so int64 . And has no


arguments.

IMPORTANT: you must pass the


arguments parameter, even when empty.
This is the reason I’m passing an empty
list ( [] ).

var getClipboardData_ptr =Module.findExportByName('user32.dll',


'GetClipboardData');
var getClipboardData = new NativeFunction(getClipboardData_ptr,
//the memory address
'uint64',
// return HANDLE
['uint64']);
// uint format (1)
console.log('[getClipboardData] NativeFunction set.');

The same process here. Return type is HANDLER


(unsigned integer, uint64 , just to test) and it requires
the format ( UINT → uint64 ) of the content we expect
from the Clipboard. Yes, you got it right: you can even
retrieve other contents and not only text. I dare you to
copy images and steal them with a script. I double dare
you.

Clipboard formats can be consulted here:

https://docs.microsoft.com/en-
us/windows/win32/dataxchg/clipboard-formats

https://docs.microsoft.com/en-
us/windows/win32/dataxchg/standard-clipboard-formats

For the example we will focus on this one:

CF_TEXT

“Text format. Each line ends with a carriage


return/linefeed ( CR-LF ) combination. A null character
signals the end of the data. Use this format for ANSI
text.”
And the constant CF_TEXT has a value of 1 (so this is
what we are going to pass to our function call to get text
from the remote clipboard).

Now we invoke the function and read the data:

var data = getClipboardData(1); // get clipboard data

But data is not “text”. Is a memory location (string


memory location, not NativePointer ) with the contents
set by the function call, so we need to transform it to a
pointer:

var data_ptr = ptr(data);

Now we have data_ptr as a NativePointer to this


memory location:

console.log('[getClipboardData] Data=[' +
data_ptr.readUtf8String() + '].');

Of course, the text content is from


data_ptr.readUtf8String() : we just added console.log to
print it on the screen to confirm we are stealing the
content.

Finally, as we are nice people, we close the remote


Clipboard and finish our duties:

closeClipboard()
Executing the script we get the following output in our
console:

(frida-tools) frida2 ~$ python notepad_remote_steal_clipboard.py


[BEGIN] create_script
[user32dll BASE address]: 0x7ffe96040000
[user32.dll BEFORE ensure init]: 0x7ffe96040000
[user32.dll AFTER ensure init]: 0x7ffe96040000
[CLIPBOARD] Going to open the clipboard with a NativeFunction
[user32.openClipboard_ptr] after. Address: 0x7ffe9606a330
[openClipboard] NativeFunction set.
[CLIPBOARD] IS OPEN.
[closeClipboard] NativeFunction set.
[getClipboardData] NativeFunction set.
[getClipboardData] Going to read data from the clipboard.
[getClipboardData] Data=[AAAA
BBBB
AAAA
].
[END] create_script
Press a key to detach...
Detached.
(frida-tools) frida2 ~$

We win! Perfect!

Now, yes, we can get access to any native API or


function. We have evidence with the clipboard contents.
And imagine… be ambitious, think big… what if we
hook functions to send keystrokes or mouse movements
and clicks?

This function, SendInput , was in our list (and the


obsolete previous functions too):
[F] SendInput -> 0x7ffe96072f80
[F] keybd_event -> 0x7ffe960c0e20
[F] mouse_event -> 0x7ffe9606aa30

Here you have the documentation… It might be a good


idea to play with it and see if you can inject keystrokes
and control applications in remote:

https://docs.microsoft.com/en-
us/windows/win32/api/winuser/nf-winuser-
sendinput#parameters
frida

And, hey, did you notice that we skipped one critical


tool in the set? Yes, it was Frida . Yes, just “ Frida ”. We
saw the command line options at the beginning of this
book, but not much more. As this is an interactive tool
that we can use to trace and instrument manually. And,
of course, can be connected to a remote server too.
Look:

(frida-tools) frida2 ~$ frida -H 127.0.0.1:54321 -n simple2


____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/

[Remote::simple2]->

Frida let us do the same as with frida-trace and gadget,


but like a REPL ( Read-Eval-Print-Loop ). When
executing things, if not configured to do something
different will leave us in a client command interpreter to
do things.

For example, when running a binary, it will suspend it


and show the console asking us to issue a “ %resume ”
to complete the application startup and run. Look:
(frida-tools) frida2 ~$ frida ./crackme01
____
/ _ | Frida 14.2.12 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `./crackme01`. Use %resume to let the main thread start
executing!
[Local::crackme01]->

In the cli can type any Frida’s supported instruction


and it will autocomplete:

(frida-tools) frida2 ~$ frida ./crackme01


...
Spawned `./crackme01`. Use %resume to let the main thread start
executing!
[Local::crackme01]-> Module

Next is a screenshot, not formatted text (I want to show


you the real colors when autocomplete is in place):

Complete the command to find “ write ” export in the


application context:

[Local::crackme01]-> Module.findExportByName(null, "write");


"0x7fe525ab11d0"
[Local::crackme01]->

We will leave Frida interactive here and see how to use


it in a future chapter. But take note of its existence as we
will recall it to do some interesting things.

Summary up to this point. It is quite important to


remember these things:

Python scripts on frida are quite powerful. I


mean it. You can do almost whatever you want
instrumenting the process.
Using frida-server we can attach a session
through TCP/IP to a remote server no matter
where it is, and no matter the operating system
(well, if Linux, Windows, QNX, Android,
OSX or iOS).
We can create a NativeFunction from a module
loaded in our instrumented process. This
NativeFunction can use any exported API. We
will see that we can even create
NativeFunction to memory locations not
exported as symbols, but that are functions
indeed.
CHAPTER 8. A PARENTHESIS IN
NATIVEFUNCTION

We are going to stop here for a while, as I consider


critical the concepts around creating NativeFunction
for several API scenarios.

If you remember with the clipboard example, the


arguments and return values were quite simple:
HANDLE , BOOL , HWND … so for us int64 or
uint64 . Easy ones.

But there are many functions that require complex


structures as arguments (for example, SendInput ;) ).
How can we manage to pass this if Frida has only
simple types? And take in mind that JavaScript does
not have a “ struct ” type.

Well, the answer is not the best, but one that works: we
cannot pass a struct, but we can build a struct in a
memory location and pass this as a reference to the
function.

For example, let’s take a look on this struct:

typedef struct my_INVENTED_STRUCT {


USHORT counter;
ULONG starCount;
ULONG blackholeCount;
} INVENTED_STRUCT, *INVENTED_HEADER;
Here we have three members of the struct: unsigned
short , unsigned long and unsigned long .

PAY ATTENTION to the architecture and word size, as


LONG in 32bit means 32bits ( 4 bytes ) and in 64bits
means 64bits ( 8 bytes ). Short is easier as it is 2
bytes in both 32 and 64 .

How can we build this structure in Frida to be passed


to a function to be executed consistently? Forging in
memory, naturally.

Look at this example ( 32 bits! ):

const INVENTED_STRUCT_SIZE = 10;


// 1 short = 2 bytes, 2 x long = 8 bytes = 10 bytes.

Remember again: 32 BITS ←

var myStruct = Memory.alloc(INVENTED_STRUCT_SIZE);


// create a NativePointer

myStruct is now a NativePointer to a memory region


of INVENTED_STRUCT_SIZE ( 10 bytes in 32bits )
and we can add values to this memory region using
“ add() ” to move the memory cursor the number of
bytes we pass as argument ( .add(0x02) ) and the set the
value with writeU32() . Look:

myStruct.writeU16(0x0000);
// We write an unsigned int 16 to the beginning of the struct.
// Every 0 is a nibble, 4 bits so: 0-0-0-0 -> 16bits.
// We want "counter" to start in 0: 0x000

var mystruct_plus_2 = myStruct.add(0x02);


// We have to advance the pointer 2 bytes, short len.
// 2 bytes = 16bits (0-0-0-0, 4 nibbles)

mystruct_plus_2.writeU32(0xFE00FE00);
// We write an unsigned int 32 to the beginning of the struct + 2 bytes.
// We want "starCount" to be 4,261,477,888 in decimal.

var mystruct_plus_2_plus_4 = mystruct_plus_2.add(0x04);


// We have to advance the pointer 4 bytes, long len (32 bits)
// 4 bytes = 32bits (8 nibbles)

mystruct_plus_2_plus_4.writeU32(0x000000fe);
// We write an unsigned int 32 to the beginning of the struct + 6 bytes.
// We want "blackholeCount" to be 254 in decimal.

Now we have 10 bytes in a memory location pointed


by myStruct , where we set several values to initialize
it. Then, we can pass the “ struct ” to a function call:

myInventedFunction(ptr(myStruct));

And that’s it.

In simple_apps/3 you will find a test application that


does nothing (is just a while(1) ) to be executed to let
Frida attach to it. You have a script too,
create_struct_in_memory.py , that will show you how
we create the struct and then read it. Look:

import frida
session = frida.attach("simple3")

# The invented struct we want to build:


# typedef struct my_INVENTED_STRUCT {
# USHORT counter;
# ULONG starCount;
# ULONG blackholeCount;
# } INVENTED_STRUCT, *INVENTED_HEADER;
script = session.create_script("""
const INVENTED_STRUCT_SIZE = 10;
var myStruct = Memory.alloc(INVENTED_STRUCT_SIZE);

console.log('[myStruct] BASE address: ' + myStruct);

myStruct.writeU16(0x0000);
var mystruct_plus_2 = myStruct.add(0x02);
console.log('[myStruct] BASE address: ' +
mystruct_plus_2 + ' +2 bytes');

mystruct_plus_2.writeU32(0xFE00FE00);
var mystruct_plus_2_plus_4 = mystruct_plus_2.add(0x04);
console.log('[myStruct] BASE address: ' +
mystruct_plus_2_plus_4 + ' +6 bytes');

mystruct_plus_2_plus_4.writeU32(0x000000fe);

// Now read:
var buffer_read = Memory.readByteArray(myStruct,
INVENTED_STRUCT_SIZE);
console.log(hexdump(buffer_read, {
offset: 0,
length: INVENTED_STRUCT_SIZE,
header: true,
ansi: false
}));
""")

script.load()
session.detach()
If you execute “ simple3 ” in one terminal and the
Frida script in another, this will be seen on the results
from Frida :

(frida-tools) frida2 ~$ python create_struct_in_memory.py


[myStruct] BASE address: 0x7f8b6479e0a0
[myStruct] BASE address: 0x7f8b6479e0a2 +2 bytes
[myStruct] BASE address: 0x7f8b6479e0a6 +6 bytes
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 00 00 00 fe 00 fe fe 00 00 00 ..........
(frida-tools) frida2 ~$

Please, remember we are in a little-endian environment


here, so what you read is the same we wrote but
inverted:

0x0000: 00 00
0xfe00fe00: 00 fe 00 fe
0x000000fe: fe 00 00 00

In the alternate option, create_struct_in_memory64.py ,


we consider we are on 64bits architecture so long is 8
bytes in length. The result of the script is:

(frida-tools) frida2 ~$ python3 create_struct_in_memory64.py


[myStruct] BASE address: 0x7f8b6479de40
[myStruct] BASE address: 0x7f8b6479de42 +2 bytes
[myStruct] BASE address: 0x7f8b6479de4a +10 bytes
0 1 2 3 4 5 6 7 8 9 A B C D E F
0123456789ABCDEF
00000000 00 00 00 fe 00 fe 00 00 00 00 fe 00 00 00 00 00 ................
00000010 00 00 ..
(frida-tools) frida2 ~$
Just check the source code by yourself :)

And now, why this parenthesis-chapter? Remember the


SendInput challenge? Here you have a struct to pass
INPUT list to the function. I dare, I double dare (again)!

typedef struct tagINPUT {


DWORD type;
union {
MOUSEINPUT mi;
KEYBDINPUT ki;
HARDWAREINPUT hi;
} DUMMYUNIONNAME;
} INPUT, *PINPUT, *LPINPUT;

If you need more details, read documentation:

https://docs.microsoft.com/en-
us/windows/win32/api/winuser/nf-winuser-sendinput
What ABI is?

NativeFunction has an extra parameter for the ABI .

An ABI is an Application Binary Interface . It helps in


the definition of how data structures are to be shared or
accessed on low level, on machine code, that is
hardware architecture-dependent format.

Typically, an ABI should take care of sizes,


alignments, layout and structure of items, parameters,
return values and so on.

Imagine that we are not sure of which architecture or


convention model a piece of code is using. You may
need to choose a different convention to be able to
receive return values. For you information, in the
NativeFunction section of Frida documentation there
is a very interesting example about return values that are
larger than Process.pointerSize
(https://frida.re/docs/javascript-api/).

This scenario may happen on C++ and the typical


ABI may return the value on a pre-allocated space
created before the actual function call, so it is passed on
the first parameter. If you review the example code is
easy to follow. I copied the idea with a simpler example:

const my_function = new NativeFunction(my_function_ptr,


'void',
['pointer']);
My_function does not return anything ( void ) and the
first (and only) argument will be the return value for this
specific ABI .

const allocated_return_value = Memory.alloc(20 * 1024 * 1024);

We allocate 20MB of memory space for the return


value and pass it as the argument:

my_function(allocated_return_value);

For this beginner’s book it is too much to introduce


ABI in detail, but it is important for you to understand
that you may face functions and exports that may need
extra configuration for NativeFunction , choosing a
particular ABI for a particular method or architecture.

If the ABI is not the right one you may face strange
errors related to the stack not properly set or cleared,
unparseable parameters and things like that. If you start
noticing “strange” failures on code you have tested
thousands of times, please do not forget about the ABI .

Frida has the default ABI, “default”, but you can define
NativeFunction with a particular one to meet the
necessary convention you may find. On the supported
ABI list:

default
Windows 32-bit: sysv, stdcall, thiscall, fastcall,
mscdecl.
Windows 64-bit: win64
UNIX x86: sysv, unix64.
UNIX ARM: sysv, vfp.

If you need to define the ABI , for example, stdcall :

var my_func_with_ABI = new NativeFunction(my_func_ptr,


"void",
[],
"stdcall");

You may read extended information on (not a


requirement to complete this book):

https://en.wikipedia.org/wiki/X86_calling_conventions
https://en.wikipedia.org/wiki/Application_binary_interfa
ce

Summary up to this point. It is quite important to


remember these things:

We now know how to alloc memory on Frida


and how to deal with it, writing and reading.
Hexdump is a wonderful tool to represent
memory chunks, take note of it.
We can build complex structures with these
memory forging tools, so we can use later on
functions that need this kind of arguments.
CHAPTER 9. ANDROID AND FRIDA

There exists a lot (A LOT) of documentation on mobile


application instrumentation on the Internet. When I
started learning Frida this was my first surprise: not
very much on Linux native applications and a lot
around Android and iOS .

I dedicated time to myself to decide: to include or not to


include a specific chapter for Frida on mobile devices.

The main reason against including the chapter is that


there exist a lot (did I mention that? A LOT?) of
examples, blogs, papers, examples, source code,
projects… with Android / iOS focus for Frida . Not
sure if it makes sense adding my own text… that might
not be better than most of these spectacular efforts.

On the other hand, a beginner's introduction to Frida


without mobile devices instrumentation may seem a
“poor” job. I considered a requirement for a minimal
quality for this book. And here it is.

I’ll focus on Android but mostly everything will be the


same on iOS , with the evident difference in APIs and
the use of ObjC instead of Java . In the future it is a
possibility I’ll invest more time on Apple’s platforms
(both OSX and iOS ) so maybe I can work in a
continuation or second part of this book. Not sure yet.
You may not understand this comment right now, but
please, remember it: READ THE FRIDA
DOCUMENTATION ABOUT ANDROID. You will
see why later.
The test lab

I will focus on two types of virtual devices: one type


emulated in Android Studio
(https://developer.android.com/studio/run/emulator),
typical and standard for everyone playing with
Android , and another one using Genymotion
(https://www.genymotion.com).

AndroidStudio installation is easy. Just download the


package from https://developer.android.com/studio, and
install it. In my host (remember, Linux ) I’ll need to
install a package like this one:

user ~$ ls -lah android-studio-ide-201.7042882-linux.tar.gz


-rw-rw-r-- 1 user user 883M feb 4 07:50 android-studio-ide-
201.7042882-linux.tar.gz

Uncompress the package the way you prefer and then,


move the <uncompressed package>/android-studio to a
location where you use to install your applications. In
my case, I like to put everything in /opt :

user ~$ sudo mv android-studio-ide-201.7042882-linux/android-studio/


/opt
[sudo] password for user:
user ~$

Then you can start the studio from (remember, in my


scenario is from /opt , but maybe you installed it on
/usr/local/ or another location):
user ~$ /opt/android-studio/bin/studio.sh

Follow the instructions to set up and wait until it


downloads everything and finishes with the settings.
You will need to install different versions of the SDK
and emulator images, depending on what we want to test
(you will see if you try to solve the Android
crackmes ).

Not going in details about the installation, please refer to


the documentation (and we will see later more details on
Android Studio AVDs ). For example, in the url below
you may learn how to setup an AVD ( Android Virtual
Device ):
https://developer.android.com/studio/run/managing-
avds#createavd

Genymotion is just as easy, but with a particular


requirement: it needs VirtualBox to be installed. Without
VirtualBox Genymotion will not work at all.

Download the installer and execute it (as user, only for


you, as root, for all users). Look:

~/Downloads$ ./genymotion-3.2.0-linux_x64.bin
Installing for current user only. To install for all users, restart this
installer as root.

Now you can run the Genymotion binary (in my


computer I have no VirtualBox installed so we will see
an error):
I moved to another host where I virtualize things and
have a working copy of VirtualBox and:
With the login window opened, just introduce your
credentials to connect to Genymotion service. From
there, we will need to select a license to work with
Genymotion : Personal Use or with a commercial
license.

By now we will select Personal Use, but if you intend to


use this tool in a professional way, it may require the
paid license.
You will need to accept the License, and then you will
enter the dashboard where you can create devices in
several formats:
I will reveal here a cheat in advance for the following
pages as I will encourage you to select Android 9 for
your Genymotion devices (READ THE
DOCUMENTATION ABOUT ANDROID, do you
remember?).

We will work as we don’t have all the information, so


we will fail several times on the virtual device
definition, as frida-server compatibility is very
dependent on Android APIs . This is very important,
but you should read the next pages pretending you have
not read this paragraph.
There are several other steps here, but play by yourself,
and reach the emulator running:
We will not use Genymotion for this book’s test lab,
but I do recommend its use because it has many options
and powerful features.
ADB

Now is the turn of ADB , as we will make an intensive


use of this tool. The typical place where ADB ( Android
Debug Bridge ) will be installed is:

/home/user/Android/Sdk/platform-tools/adb

It may depend on your home directory, the sdk version


and several other details. It is always recommended to
read the documentation about file locations here:

https://developer.android.com/studio/command-line

For the rest of this book, we will assume you have a


working lab with the emulator, ADB and, naturally,
Frida .
Running the crackme: UnCrackable-Level1

The UnCrackable-Level1 we are using on this section was downloaded from


OWASP crackmes :

https://github.com/OWASP/owasp-mstg/tree/master/Crackmes
https://github.com/OWASP/owasp-mstg/tree/master/Crackmes/Android/Level_01

Thank you OWASP for setting this repository up!

My typical run for the emulator is:

frida4 ~$ /home/user/Android/Sdk/emulator/emulator -avd Pixel_3a_API_30_x86 -netdelay none -netspeed


full
emulator: Android emulator version 30.3.5.0 (build_id 7033400) (CL:N/A)
handleCpuAcceleration: feature check for hvf
cannot add library /home/user/Android/Sdk/emulator/qemu/linux-x86_64/lib64/vulkan/libvulkan.so: failed
added library /home/user/Android/Sdk/emulator/lib64/vulkan/libvulkan.so
cannot add library /home/user/Android/Sdk/emulator/lib64/vulkan/libvulkan.so.1: full
emulator: INFO: GrpcServices.cpp:288: Started GRPC server at 127.0.0.1:8554, security: Local
emulator: ERROR: AdbHostServer.cpp:102: Unable to connect to adb daemon on port: 5037
emulator: INFO: boot completed
emulator: INFO: boot time 20338 ms
emulator: Increasing screen off timeout, logcat buffer size to 2M.
emulator: ERROR: AdbHostServer.cpp:102: Unable to connect to adb daemon on port: 5037
emulator: Revoking microphone permissions for Google App.

Here you have my Google Pixel running :) Now, install the apk onto the device
( adb install <application>.apk ) and run it. This kind of app usually does not show an
evident icon. Just searching for the name will show it and we can start directly from
there:

Once we run the application it will show a very interesting message informing us that
it has detected a Rooted device (“ Root detected ”). It will be very difficult to
“debug” without root using Frida… Our evil plans are coming to an awful end…
Or not.

Wait for a while before going for the challenge, and let’s see if we can connect Frida
to the emulator and check all our tools.

See what Frida detects with the emulator connected (and, to add more fun, we
connected another real device to the list):

(frida-tools) frida4 ~$ frida-ls-devices


Id Type Name
---------------- ------ ---------------------
local local Local System
emulator-5554 usb Android Emulator 5554
4100bbc0764ef000 usb SM T210
socket remote Local Socket
(frida-tools) frida4 ~$

The proper device, naturally, is emulator-5554 . From Frida options, if you


remember when we asked for help, we saw a “ -U ” flag to connect to USB devices,
and a “ -D ID ” flag to select the device we want to use (for example, -D emulator-
5554 ).

Test that adb is working fine (note I’m using a relative path with ./adb , it may be
different in your installation or even you may be using an absolute path like ~$ adb ):

(frida-tools) frida4 ~$ ./adb shell


adb: more than one device/emulator

Oh no! Multiple devices here. What can we do? Adb has an option to select the
device ID (with -s ) to select the one we want to work with:

(frida-tools) frida4 ~$ ./adb -s emulator-5554 shell


generic_x86_arm:/ $

Not sure if you noticed the detail, but the emulator is running an x86 arm device. In
the Android 11 images it will be able to translate arm instructions to x86 without
even feeling something is happening there. You can read about this feature here:

https://developer.android.com/studio/releases/emulator#support_for_arm_binaries_on
_android_9_and_11_system_images

Just to verify:

generic_x86_arm:/ $ uname -a
Linux localhost 5.4.61-android11-0-00791-gbad091cc4bf3-ab6833933 #1 SMP PREEMPT 2020-09-14
14:42:20 i686
generic_x86_arm:/ $ uname -m
i686
generic_x86_arm:/ $
Anyway, for what is our business, we can interact with the emulator without any
interference in our learning process with Frida .

Now, using the adb tool we will “ root ” the device:

(frida-tools) frida4 ~$ ./adb -s emulator-5554 root


restarting adbd as root

What has happened here? Mostly every device images in emulators are ready to be
rooted (or even are rooted by default), as their purpose is for diagnostics, debugging
and testing. The consequence of this typical use is that root privileges are a default
mode of use. And it is implemented just restarting the adbd daemon, on the device
side, with the proper privileges. If we check our id now:

(frida-tools) frida4 ~$ ./adb -s emulator-5554 shell


generic_x86_arm:/ # id
uid=0(root) gid=0(root) groups=0(root),
1004(input),1007(log),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet
context=u:r:su:s0
generic_x86_arm:/ #

Ok, we have the necessary privileges now, so let’s copy the frida-server binary right
now:

(frida-tools) frida4 ~$ ./adb -s emulator-5554 push frida-server /data/local/tmp/frida-server


frida-server: 1 file pushed, 0 skipped. 46.9 MB/s (41350448 bytes in 0.841s)
(frida-tools) frida4 ~$ adb shell
generic_x86_arm:/ # ls -la /data/local/tmp/frida-server
-rw-rw-rw- 1 root root 41350448 2021-02-03 20:06 /data/local/tmp/frida-server

Please note that I, intentionally, copied the ARM64 binary. See it:

generic_x86_arm:/ # file /data/local/tmp/frida-server


/data/local/tmp/frida-server: ELF shared object, 64-bit LSB arm64, dynamic (/system/bin/linker64), for
Android 21, built by NDK r22 (7026061), stripped

And now, I will try to start it:

generic_x86_arm:/ # chmod +x /data/local/tmp/frida-server

Need to set the executable bit on the file, naturally. Then try to run:

generic_x86_arm:/ # /data/local/tmp/frida-server
/system/bin/sh: /data/local/tmp/frida-server: not executable: 64-bit ELF file
1|generic_x86_arm:/ #
So 64bits ARM binary is not running on the device. Repeat the process with the
arm32 :

(frida-tools) frida4 ~$ ./adb push frida-server32 /data/local/tmp


frida-server32: 1 file pushed, 0 skipped. 45.2 MB/s (17653456 bytes in 0.373s)
(frida-tools) frida4 ~$ ./adb shell
generic_x86_arm:/ # chmod +x /data/local/tmp/frida-server32
generic_x86_arm:/ # /data/local/tmp/frida-server32 --version
14.2.10

In this case, ARM 32bit works as expected. Now run frida-server without
parameters:

generic_x86_arm:/ # /data/local/tmp/frida-server32

We should be able now to run a frida-ps and see results:

(frida-tools) frida4 ~$ frida-ps -D emulator-5554


PID Name
---- ---------------------------------------------------------------------------
6268 abb
6252 adbd
213 android.hardware.atrace@1.0-service
297 android.hardware.audio.service.ranchu
...
406 drmserver
6771 frida-server-14.2.10-android-x86
437 gatekeeperd
...
495 netmgr
6708 owasp.mstg.uncrackable1
184 qemu-props
...
292 zygote
(frida-tools) frida4 ~$

Yes! We win… almost perfect! Almost, because we want to trace the application now
and see what it is happening in the guts. And crack it. Because we love to crack
things.

Naturally, the target must be this one and no other:

6708 owasp.mstg.uncrackable1

What next? You already know what to do. We can try to discover, for example. Or
directly go hook a known API. Take some moments to decide on the next step.

Well, yeah. You may uncompress the APK (is a zipfile ), convert DEX classes
with dex2jar , then use a decompiler like JAD , jd-gui or others… and look for the
APIs . But… where is the challenge there? Why not testing discover?

(frida-tools) frida3 ~$ frida-discover -D emulator-5554 -n owasp.mstg.uncrackable1


Failed to attach: unable to inject library into process without libc

Oh.

I do not have any idea of what is happening. Frida-server is running (verified on the
adb shell) and I’m just requesting something without a script… Hum. Will stop the
server and start it again but with verbose mode ( -v ), so I can see the details when
running:

generic_x86_arm:/data/local/tmp # ./frida-server32 -v
Unable to preload: Unable to inject library into process without libc

Humm… it looks ugly. What is happening here?

To save you time: this is not the right binary to push onto the device. Remember the
“ uname -m ” result? Was i686 … and we ignored it and copied arm32bits instead
(that started, and let us do several things, but is not working as expected, as you can
see).

Let’s try with the Android x86_64 binary first:

generic_x86_arm:/data/local/tmp # ./frida-server-14.2.10-android-x86_64
/system/bin/sh: ./frida-server-14.2.10-android-x86_64: No such file or directory

Ok, NO. This is clearly not 64bits . Now with the frida-server-android-x86-32bits :

generic_x86_arm:/data/local/tmp # ./frida-server-14.2.10-android-x86 -v

Ok! No error. See what happens with discover:

(frida-tools) frida3 ~$ frida-discover -D emulator-5554 -n owasp.mstg.uncrackable1


Tracing 16 threads. Press ENTER to stop.

WE WIN! WE ARE THE… oh no!

Process crashed: Trace/BPT trap

***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint:
'google/sdk_gphone_x86_arm/generic_x86_arm:11/RSR1.201013.001/6903271:userdebug/dev-keys'
Revision: '0'
ABI: 'x86'
Timestamp: 2021-02-05 12:18:21+0100
pid: 3505, tid: 3540, name: RenderThread >>> owasp.mstg.uncrackable1 <<<
uid: 10153
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'RenderThread Looper POLL_ERROR!'
eax 00000000 ebx 00000db1 ecx 00000dd4 edx 00000006
edi 00000206 esi c7f8ca50
ebp f6cbeb90 esp c7f8c9f8 eip f6cbeb99
backtrace:
#00 pc 00000b99 [vdso] (__kernel_vsyscall+9)
#01 pc 0001f739 <anonymous:bfd11000>
***
Stopping...
(frida-tools) frida3 ~$

We re-open the application and test again:

(frida-tools) frida4 ~$ frida-discover -D emulator-5554 -n owasp.mstg.uncrackable1


Tracing 21 threads. Press ENTER to stop.
Process terminated
Stopping...
(frida-tools) frida4 ~$

Frida server builds are VERY depending on context. Different firmwares , different
Android versions may lead to different contexts and errors. This frida-server or the
Stalker (we already talked about Stalker ) version is colliding with something in the
AVD ( Android Virtual Device , remember).

(Do you remember about that important thing? READ THE DOCUMENTATION,
right? If not reading it, you will get fooled by answers that may not be related to your
particular issue)

If you look for this error in Frida issues you will see that there are many reports on
different platforms, with different applications. For example, look at this one:

https://github.com/frida/frida-java-bridge/issues/145

Or this one:

https://github.com/frida/frida-gum/issues/518

We have several options here. Try to go ahead without frida-discover , testing trace
and several other approaches or, as alternative, try to run the application on a
different virtual device, with a different image, Android version, and firmware
version…

By the way, the application has crashed, and it is closed:

(frida-tools) frida3 ~$ frida-trace -D emulator-5554 -i open -n owasp.mstg.uncrackable1


Failed to spawn: unable to find process with name 'owasp.mstg.uncrackable1'
Confirm this is a problem located on the application (just to see if the problem is
bigger):

(frida-tools) frida3 ~$ frida-ps -D emulator-5554|grep chrome


7723 com.android.chrome
7831 com.android.chrome:privileged_process0
8616 com.android.chrome:sandboxed_process0:org.chromium.content.app.SandboxedPro
7778 com.android.chrome_zygote
(frida-tools) frida3 ~$

Ok, we found Chrome. See if we can trace “open”:

(frida-tools) frida3 ~$ frida-trace -D emulator-5554 -i open -n com.android.chrome


Instrumenting...
open: Auto-generated handler at "/home/user/Frida/crackmes/android/01/__handlers__/libc.so/open.js"
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x1fa8 */
15339 ms open(pathname="/data/user/0/com.android.chrome/app_chrome/.com.google.Chrome.PtJv7j",
flags=0xc2)
15340 ms open(pathname="/data/user/0/com.android.chrome/app_chrome/.com.google.Chrome.PtJv7j",
flags=0x1)
/* TID 0x1e8e */
65026 ms open(pathname="/proc/self/statm", flags=0x0)
65026 ms open(pathname="/proc/7723/status", flags=0x80000)
65027 ms open(pathname="/proc/7723/stat", flags=0x80000)
65027 ms open(pathname="/proc/self/status", flags=0x80000)
65028 ms open(pathname="/proc/self/clear_refs", flags=0x1)
65028 ms open(pathname="/proc/self/pagemap", flags=0x0)

Oh, Frida works and the operating system works and Chrome works. So, the
problem is not a BIG one, but in another category.

I made the decision to stay with this emulator (I will regret this decision later) and
use alternate ways. Yes, we will perform static analysis to decompile the apk and
see API calls and intents. But, before doing any other thing, run the application
again and see if we can trace:

(frida-tools) frida4 ~$ frida -U --no-pause -f owasp.mstg.uncrackable1


____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable1`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable1]->
Well, confirmed, it works. In theory we can control this process and play with it.

Decompile the apk and explore it. This is not a book to learn how to decompile
Android applications, but just in few steps:

1. Rename the apk to zip: cp UnCrackable-Level1.apk UnCrackable-


Level1.zip
2. Extract this zipfile contents to a directory, for example, UnCrackable-
Level1/..
3. Copy the file classes.dex to the directory where you have the dex2jar tool.
4. Perform: d2j-dex2jar.bat (or sh) classes.dex
5. Open classes_dex2jar.jar (the output) with, for example, JD-GUI or JAD.

If you are interested in more information about apk decompiling, please look in a
search engine, as there are dozens of tutorials.

Once we get something readable, please go first for the AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>


<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1"
android:versionName="1.0" package="owasp.mstg.uncrackable1">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28"/>
<application android:theme="@style/AppTheme"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="true">
<activity android:label="@string/app_name"
android:name="p000sg.vantagepoint.uncrackable1.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

In typical Android applications, the MAIN intent is the entry point to the
application, so if we hook this, supposedly we will be able to trace when running.

Android and Java applications are a bit different in the way of hooking them, as
instead of calling the Interceptor directly, we will use the “ Java ” object. Typically,
we will use Java.perform() or Java.use() .

See an example python script with Java.perform() below:

frida4 ~$ cat crackme01.py

import frida, sys

def on_message(message, data):


if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)

jscode = """
Java.perform(function () {
// owasp.mstg.uncrackable1:
// p000sg.vantagepoint.uncrackable1.MainActivity is the entry point to the application.
// we hook it.
console.log("[Java.perform] Going to use MainActivity.");
var mainActivity_hook = Java.use('p000sg.vantagepoint.uncrackable1.MainActivity');

if(!mainActivity_hook) {
console.log("[Java.perform] ERROR: we *do not* have MainActivity hook.:");
throw "MainActivity not available here. ERROR.";
}

console.log("[Java.perform] We have MainActivity hook. Watch onCreate:");


var onCreate = mainActivity_hook.onCreate;

if(!onCreate) {
console.log("[Java.perform] ERROR: we *do not have* onCreate.");
throw "ERROR: no onCreate found.";
}

onCreate.implementation = function(v) {
console.log("[.onCreate] BEGIN");

console.log(v);

console.log("[.onCreate] END (call real handler)");


onCreate.call(this, v);
}
});
"""

device = None
all_devices = frida.enumerate_devices()

for dv in all_devices:
# Device(id="emulator-5554", name="Android Emulator 5554", type='usb')
if dv.type != 'usb':
continue

if dv.id == "emulator-5554":
device = dv
break

if device:
process_pid = device.spawn(["owasp.mstg.uncrackable1"])
process = device.attach(process_pid)
script = process.create_script(jscode)
script.on('message', on_message)

print('[*] Hooking crackme 01! Press any key to exit...')


script.load()

The output when executing it:


frida4 ~$ cat crackme01.py

import frida, sys

def on_message(message, data):


if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)

jscode = """
Java.perform(function () {
// owasp.mstg.uncrackable1:
// p000sg.vantagepoint.uncrackable1.MainActivity is the entry point to the application.
// we hook it.
console.log("[Java.perform] Going to use MainActivity.");
var mainActivity_hook = Java.use('p000sg.vantagepoint.uncrackable1.MainActivity');

if(!mainActivity_hook) {
console.log("[Java.perform] ERROR: we *do not* have MainActivity hook.:");
throw "MainActivity not available here. ERROR.";
}

console.log("[Java.perform] We have MainActivity hook. Watch onCreate:");


var onCreate = mainActivity_hook.onCreate;

if(!onCreate) {
console.log("[Java.perform] ERROR: we *do not have* onCreate.");
throw "ERROR: no onCreate found.";
}

onCreate.implementation = function(v) {
console.log("[.onCreate] BEGIN");

console.log(v);

console.log("[.onCreate] END (call real handler)");


onCreate.call(this, v);
}
});
"""

device = None
all_devices = frida.enumerate_devices()

for dv in all_devices:
# Device(id="emulator-5554", name="Android Emulator 5554", type='usb')
if dv.type != 'usb':
continue

if dv.id == "emulator-5554":
device = dv
break

if device:
process_pid = device.spawn(["owasp.mstg.uncrackable1"])
process = device.attach(process_pid)
script = process.create_script(jscode)
script.on('message', on_message)
print('[*] Hooking crackme 01! Press any key to exit...')
script.load()

The output when executing it:

(frida-tools) frida4 ~$ python crackme01.py


[*] Hooking crackme0x01! Press any key to exit...

And see the emulator screen when running the script:

At this moment we can say hurrah! Our script attached to the crackme process on
the device, and it seems that it hooked the execution and stopped it somehow. But it
is hanging here with no activity (well, maybe it is good news as we do not see the
“ Root ” message, heh).

Up to this point, it is a good idea to check the source code for the associated intent
(remember, MainActivity , the entry point) and see what we can understand from
there. Open the file:

<UnCrackable-Level1_source>/sources/p000sg/vantagepoint/uncrackable1/MainActivity.java

The contents (Java source code, naturally):


package p000sg.vantagepoint.uncrackable1;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import owasp.mstg.uncrackable1.R;
import p000sg.vantagepoint.p001a.C0001b;
import p000sg.vantagepoint.p001a.C0002c;

/* renamed from: sg.vantagepoint.uncrackable1.MainActivity */


public class MainActivity extends Activity {
/* renamed from: a */
private void m5a(String str) {
AlertDialog create = new AlertDialog.Builder(this).create();
create.setTitle(str);
create.setMessage("This is unacceptable. The app is now going to exit.");
create.setButton(-3, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
System.exit(0);
}
});
create.setCancelable(false);
create.show();
}

/* access modifiers changed from: protected */


public void onCreate(Bundle bundle) {
if (C0002c.m2a() || C0002c.m3b() || C0002c.m4c()) {
m5a("Root detected!");
}
if (C0001b.m1a(getApplicationContext())) {
m5a("App is debuggable!");
}
super.onCreate(bundle);
setContentView(R.layout.activity_main);
}

public void verify(View view) {


String str;
String obj = ((EditText) findViewById(R.id.edit_text)).getText().toString();
AlertDialog create = new AlertDialog.Builder(this).create();
if (C0005a.m6a(obj)) {
create.setTitle("Success!");
str = "This is the correct secret.";
} else {
create.setTitle("Nope...");
str = "That's not it. Try again.";
}
create.setMessage(str);
create.setButton(-3, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
create.show();
}
}
Checking the source code we see here that the onCreate method performs some
checks to determine if the device is rooted (or the process is running with root) and, if
is the case, calls a function m5a (original name a() . The decompiler renamed it:
this is important).

This function m5a (remember, a() ) shows an alert dialog and, then, exits the
application with System.exit(0);

Ok, instead of dealing with onCreate , why not go directly for this function and
change it to do nothing? Instead of invoking System.exit() just print a message on
console (to make sure we can follow what is happening).

As I feel the behavior is different when running python scripts, this time I will just
create the JavaScript as noroot.js file, and load it directly into the application
context with the frida repl (“ frida ”). Show it:

Java.perform(function() {
var mainActivity = Java.use("sg.vantagepoint.uncrackable1.MainActivity");
mainActivity.a.implementation = function(s) {
console.log("SEE IF WE CAN CONTROL THE FUNCTION. Argument=: " + s);
}
});

We already know about Java.perform() . We create a variable mainActivity and


associate the return value from Java.use(<the main activity in the application>) .

Then hook the m5a (remember, the real name is a() ) here:

mainActivity.a.implementation = function(s) {//}

Our replacement is not showing any alert message, nor exiting the application. Just a
console.log to be able to track, as we said.

Note: to make things simpler, I disconnected all the devices but the emulator (I can
just use “ frida -U ” instead of “ frida -D emulator-5554 ”, right?).

See the output in our console:

(frida-tools) frida4 ~$ frida -U --no-pause -l noroot.js -f owasp.mstg.uncrackable1


____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable1`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable1]-> SEE IF WE CAN CONTROL THE FUNCTION.
Argument=: Root detected!
[Android Emulator 5554::owasp.mstg.uncrackable1]->

Thank you for using Frida!


(frida-tools) frida4 ~$

WE WIN! PERFECT! Even without looking at the emulator screen we can confirm
our message to the console as printed, so our handler was called successfully.

Obviously, we can have no life without checking what was happening on the device’s
screen:

This is the definitive confirmation that we achieved the goal (well, the first goal): we
bypassed the root check from the application.

Quick review of what we executed:

frida -U --no-pause -l noroot.js -f owasp.mstg.uncrackable1

Frida command:
-U: use a connected USB device (the emulator). In other invocations we
did -D emulator-5554 because more than one device was available.
--no-pause: this tells Frida repl to execute the script to the end without
suspending the execution flow.
-l noroot.js: load the JavaScript handler.
-f owasp.mstg.uncrackable1: we instruct frida (and frida-server) to spawn
the application under our control.

Summary up to this point. It is quite important to remember these things:

Java.perform() let us control the Dalvik java virtual machine in the


device through frida-server.
Be careful with the versions of the frida-server binary. Many times it
might not be evident which one is the right one..
We can create a standalone script to be loaded in frida with “-l”.
Second part with UnCrackable-Level1

With the root bypass, now we have to solve the real challenge. It is
necessary to enter a valid secret to win. In the same Java source file
where we found m5a ( a() ), there exists another function called
verify():

public void verify(View view) {


String str;
String obj = ((EditText) findViewById(R.id.edit_text))
.getText().toString();
AlertDialog create = new AlertDialog.Builder(this).create();
if (C0005a.m6a(obj)) {
create.setTitle("Success!");
str = "This is the correct secret.";
} else {
create.setTitle("Nope...");
str = "That's not it. Try again.";
}
create.setMessage(str);
create.setButton(-3, "OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
create.show();
}

The typical strategies here are, in one hand, to always return True :
yes, no matter the string passed, everything validates as correct.
And, on the other hand, to intercept the comparison to reveal the
secret.

The target method to investigate is C0005a.m6a(obj) , no matter


the strategy we want to follow, as in both cases we want to control
this function behavior.

The module where the function lives is in:

<UnCrackable-
Level1_source>/sources/p000sg/vantagepoint/uncrackable1/C0005a.java
See the source code:

package p000sg.vantagepoint.uncrackable1;

import android.util.Base64;
import android.util.Log;
import p000sg.vantagepoint.p001a.C0000a;

/* renamed from: sg.vantagepoint.uncrackable1.a */


public class C0005a {
/* renamed from: a */
public static boolean m6a(String str) {
byte[] bArr;
byte[] bArr2 = new byte[0];
try {
bArr = C0000a.m0a(m7b("8d127684cbc37c17616d806cf50473cc"),
Base64.decode("5UJiFctbmgdboLXmpL12mkno8HT4Lv8dlat8FxR2GOc=",
0));
} catch (Exception e) {
Log.d("CodeCheck", "AES error:" + e.getMessage());
bArr = bArr2;
}
return str.equals(new String(bArr));
}

/* renamed from: b */
public static byte[] m7b(String str) {
int length = str.length();
byte[] bArr = new byte[(length / 2)];
for (int i = 0; i < length; i += 2) {
bArr[i / 2] = (byte) ((Character.digit(str.charAt(i), 16) << 4)
+ Character.digit(str.charAt(i + 1), 16));
}
return bArr;
}
}

VERY IMPORTANT: pay attention to the


“renamed” comments, as they are relevant.

For example, the module C0005a (class) is the name that the JAD
decompiler set for:
sg.vantagepoint.uncrackable1.a

And the method m6a is actually the a() method. So, using the full
namespace for the method, what we should hook is:

sg.vantagepoint.uncrackable1.a.a

If you follow the function logic, this is the more important step:

str.equals(new String(bArr)

Here we can find the comparison between the text provided by the
user, and the actual string that is to be compared in the application.
Our goal is to access bArr value, in the end, as “ str ” of the text
from the user.

The value from bArr is the result of:

bArr = C0000a.m0a(m7b("8d127684cbc37c17616d806cf50473cc"),

The easier approach is to try to intercept the return value (remember


retval ?) on C0000a.m0a() . This function is defined in this source
file:

bArr = C0000a.m0a(m7b("8d127684cbc37c17616d806cf50473cc"),

The contents:

package p000sg.vantagepoint.p001a;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

/* renamed from: sg.vantagepoint.a.a */


public class C0000a {
/* renamed from: a */
public static byte[] m0a(byte[] bArr, byte[] bArr2) {
SecretKeySpec secretKeySpec = new SecretKeySpec(bArr,
"AES/ECB/PKCS7Padding");
Cipher instance = Cipher.getInstance("AES");
instance.init(2, secretKeySpec);
return instance.doFinal(bArr2);
}
}

Without investing much time in the function logic, what is relevant


here is that we know there is a module C0000a (renamed from
sg.vantagepoint.a.a ) that has a method m0a (renamed from a() ).
Our goal is to retrieve the return value from sg.vantagepoint.a.a.a()
and print in on the screen, console.log or send() .

Just to make sure that this is the proper function to hook, I


rechecked with JD-GUI to confirm the API location. I tend to use
JD-GUI and JAD without thinking much which tool to use in
every case. I like JAD because it is powerful, but sometimes it
renames functions with wrong names, so this is because I use jd-
gui in parallel.

If we open the classes.dex (the decompiled version from


dex2jar ):
Ok, the function is sg.vantagepoint.a.a.a , confirmed. We will
invoke Java.use(“sg.vantagepoint.a.a”) and the handle a().

If we reuse the “ noroot.js ” script and fall onto the repl cli
console, we can test if we can hook this function and play with the
retval :

Spawned `owasp.mstg.uncrackable1`. Resuming main thread!


[Android Emulator 5554::owasp.mstg.uncrackable1]-> SEE IF WE CAN CONTROL
THE FUNCTION. Argument=[Root detected!]
[Android Emulator 5554::owasp.mstg.uncrackable1]-> var org =
Java.use("sg.vantagepoint.a.a");
[Android Emulator 5554::owasp.mstg.uncrackable1]-> console.log(org);
<class: sg.vantagepoint.a.a>
[Android Emulator 5554::owasp.mstg.uncrackable1]-> org.a.implementation =
function(a, b) { console.log("org.a");r
eturn "a";}
function
[Android Emulator 5554::owasp.mstg.uncrackable1]->
[Android Emulator 5554::owasp.mstg.uncrackable1]->
[Android Emulator 5554::owasp.mstg.uncrackable1]->
[Android Emulator 5554::owasp.mstg.uncrackable1]-> org.a
Error: Implementation for a expected return value compatible with [B
at ne (frida/node_modules/frida-java-bridge/lib/class-factory.js:614)
at <anonymous> (frida/node_modules/frida-java-bridge/lib/class-factory.js:592)
Process crashed: java.lang.NullPointerException: Attempt to get length of null array

***
FATAL EXCEPTION: main
Process: owasp.mstg.uncrackable1, PID: 9936
java.lang.IllegalStateException: Could not execute method for android:onClick
at android.view.View$DeclaredOnClickListener.onClick(View.java:6268)
at android.view.View.performClick(View.java:7448)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at android.view.View$DeclaredOnClickListener.onClick(View.java:6263)
... 11 more
Caused by: java.lang.NullPointerException: Attempt to get length of null array
at java.lang.StringFactory.newStringFromBytes(StringFactory.java:46)
at sg.vantagepoint.uncrackable1.a.a(Unknown Source:50)
at sg.vantagepoint.uncrackable1.MainActivity.verify(Unknown Source:26)
... 13 more
***
[Android Emulator 5554::owasp.mstg.uncrackable1]->

Thank you for using Frida!

If the handler is executed we will see “ org.a ” on screen (and it was


there). First part is solved: we handled the function. But we are
raising an exception because the return value type.

It is a good idea to inspect what is the expected return value in the


source code. If we check in the API documentation:

https://developer.android.com/reference/javax/crypto/Cipher

It seems that Cipher.doFinal() returns a byte[] . If we “cook” a


byte [] in the return, maybe it will work. For example, using:
Java.array('byte', “password”);
But here I had a lot (A LOT) of errors, exceptions and different
problems related to bytes , byte arrays , memory access
violations… I started to get crazy with this crackme .

With these errors I had the idea of replacing the virtual device. My
intuition suggests that this is the way…

(READ THE DOCUMENTATION AND SAVE A LOT OF TIME)

And, as I’m going to use a new one, I took the decision of investing
time in testing different Android versions in different architectures.
I have created several devices to test. Look at the list:

frida4$ ./emulator -list-avds


Pixel_3a_API_30_x86
Pixel_API_22_Lollipop5.1ARM

Now we have one Pixel3a with x86 Android 11 and a Pixel with
Android 5.1 Lollipop ARM (API version 22). Let’s create a new
one and see, step by step, the process (I’ll use Android Studio AVD
Manager).
Select “Create Virtual Device” on bottom-left:

This time a Pixel 2 and click “Next”:


Android Lollipop, 5.0 (x86):

Now we have the new emulator running, install the crackme , adb
root , copy the frida-server version (still x86 32 ) and start again:

(frida-tools) frida4 ~$ ./adb devices


List of devices attached
LCL7N19506001553 device
emulator-5554 device

(frida-tools) frida4 ~$ ./adb -s emulator-5554 install UnCrackable-Level1.apk


Performing Push Install
UnCrackable-Level1.apk: 1 file pushed, 0 skipped. 21.5 MB/s (66651 bytes in 0.003s)
pkg: /data/local/tmp/UnCrackable-Level1.apk
Success
(frida-tools) frida4 ~$

Now adb root :

(frida-tools) frida4 ~$ ./adb -s emulator-5554 root


adbd is already running as root
(frida-tools) frida4 ~$

Oh, well, this image is already rooted ? Well, to the TODO list but
not my focus right now. Push frida-server :

(frida-tools) frida4 ~$ ./adb -s emulator-5554 push frida-server-14.2.10-android-x86


/data/local/tmp
frida-server-14.2.10-android-x86: 1 file pushed, 0 skipped. 176.1 MB/s (42913428
bytes in 0.232s)
(frida-tools) frida4 ~$

Set permissions ( chmod +x not working but chmod 777 is):

(frida-tools) frida4 ~$ ./adb -s emulator-5554 shell


root@generic_x86:/ # cd /data/local/tmp
root@generic_x86:/ # chmod +x frida-server-14.2.10-android-x86
Bad mode
root@generic_x86:/ # chmod 777 frida-server-14.2.10-android-x86
root@generic_x86:/ # ./frida-server-14.2.10-android-x86 -v

Now we are ready to test our installation again. Before doing


anything more complex, test the basic noroot.js:

(frida-tools) frida4 ~$ frida -D emulator-5554 --no-pause -l noroot.js -f


owasp.mstg.uncrackable1
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable1`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable1]-> SEE IF WE CAN CONTROL
THE FUNCTION. Argument=: Root detected!
[Android Emulator 5554::owasp.mstg.uncrackable1]->

Ok. To this point we have a new environment where I have


confirmed the basic noroot.js works. We can go now to evolve…
wait a minute!

As this is a new environment, may it be a good idea to test frida-


discover again? YES.

(frida-tools) frida4 ~$ frida-discover -D emulator-5554 -n owasp.mstg.uncrackable1


Tracing 21 threads. Press ENTER to stop.
Process terminated
Stopping...
(frida-tools) frida4 ~$

YES and YES. We have advanced as it is not raising an exception.


Not exactly the best behavior as the application closed on the device
when trying to interact, but this gives hope of going forward.

We will test now with an alternative version of the noroot.js :


noroot_and_secret_fail.js .

See the code (cat noroot_and_secret_fail.js):

(frida-tools) frida4 ~$ cat noroot_and_secret_fail.js

Java.perform(function() {
var mainActivity = Java.use("sg.vantagepoint.uncrackable1.MainActivity");
mainActivity.a.implementation = function(s) {
console.log("SEE IF WE CAN CONTROL THE FUNCTION. Argument=[" + s + "]\n");
}

var base_class_secret = Java.use("sg.vantagepoint.a.a");


console.log("[base_class_secret] " + base_class_secret);

var original_function = base_class_secret.a;


console.log("[base_class_secret.a] " + original_function);

base_class_secret.a.implementation = function(str1, str2) {


console.log("[.a] Invoke bArr method.");
return original_function(str1, str2);
}
});

And the execution (yes, fails, of course):

(frida-tools) frida4 ~$ frida -D emulator-5554 --no-pause -l noroot_and_secret_fail.js


-f owasp.mstg.uncrackable1
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable1`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable1]-> [base_class_secret] <class:
sg.vantagepoint.a.a>
[base_class_secret.a] function e() {
[native code]
}
SEE IF WE CAN CONTROL THE FUNCTION. Argument=[Root detected!]

Process terminated
[Android Emulator 5554::owasp.mstg.uncrackable1]->

Thank you for using Frida!


(frida-tools) frida4 ~$
And the application crashes on the emulator, but worse, the
emulator’s operating system reloads, and an ugly sound comes out
from the headphones (almost killing me, you know). It crashed
badly.

Not sure what to do next. I am upset with the tests and not sure if
the problem is in Frida , in the emulator or in me. “The only
winning move is not to play”, said Joshua on Wargames. But I will
not surrender.

We need to discard things (first, in the technology and, finally, on


my side). And my intuition says that the problem is in the emulator,
the device, the API version… that is interacting with Frida in
undesirable ways.

After evaluating several approaches (from erasing an old Motorola


device to going to a shop and buying a cheap mobile phone), I read
some comments on the Internet. Well, in the Frida website they
say their testing was made using a Pixel with Android 9 . Ehhh…
maybe starting reading this was a good idea… several hours ago
(remember, right? READ-THE-DOCUMENTATION-ABOUT-
ANDROID).

I created a new device, FridaRecommendedPixel90 , based on a


Pixel with Android 9 . Follow the previous instructions for
creating the AVD and then, repeat the debugging process:

(frida-tools) frida4 ~$ ./adb install UnCrackable-Level1.apk


(frida-tools) frida4 ~$ ./adb root
(frida-tools) frida4 ~$ ./adb push frida-server-14.2.10-android-x86
(frida-tools) frida4 ~$ ./adb shell
generic_x86_arm:/data/local/tmp # cd /data/local/tmp
generic_x86_arm:/data/local/tmp # chmod 777 frida-server-14.2.10-android-x86
generic_x86_arm:/data/local/tmp # ./frida-server-14.2.10-android-x86 -v

Now, I’ll start with my script noroot_and_secret_fails.js that just


hooks the function and returns the value of calling the real function
(was crashing in all the other options). See:
(frida-tools) frida4 ~$ frida -U --no-pause -l noroot_and_secret_fail.js -f
owasp.mstg.uncrackable1
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable1`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable1]-> [base_class_secret] <class:
sg.vantagepoint.a.a>
[base_class_secret.a] function e() {
[native code]
}
SEE IF WE CAN CONTROL THE FUNCTION. Argument=[Root detected!]

[Android Emulator 5554::owasp.mstg.uncrackable1]->


[Android Emulator 5554::owasp.mstg.uncrackable1]->

It worked. IT WAS NOT ME! (well, maybe a bit)

Here you may expect a “we win! Perfect!” or something similar


because I found a way to complete the tests and now everything
works and life seems brighter now. But you are not taking in count
the hours (HOURS) I invested creating different AVDs to test and
see where it will work. LESSONS LEARNT:

RTFM: read the documentation from the Frida creator


and use an AVD based on Pixel with Android 9. SAVE
YOUR LIFE, DO IT.

Yes, just this one will save you a lot of wasted time.

Read the Frida’s documentation. I wasted a lot of


time plyaing with emulators just because I read
quick, not paying much attention to the versions.
Now we have something that seems to work (I’m not daring to
confirm it before seeing the results on screen, you know?) we will
try to print the returned value from the original function. The
JavaScript file (and I will delete my previous attempt,
noroot_secret_and_final.js):

Java.perform(function() {
var mainActivity = Java.use("sg.vantagepoint.uncrackable1.MainActivity");

mainActivity.a.implementation = function(s) {
console.log("SEE IF WE CAN CONTROL THE FUNCTION. Argument=[" + s + "]\n");
}

var base_class_secret = Java.use("sg.vantagepoint.a.a");


console.log("[base_class_secret] " + base_class_secret);

var original_function = base_class_secret.a;


console.log("[base_class_secret.a] " + original_function);

base_class_secret.a.implementation = function(str1, str2) {


// return this.a(str1, str2);
var retval = this.a(str1, str2);
console.log("[BEGIN] retval");
console.log(retval);
console.log("[END] retval");
return retval;
}
});

The output on screen:

(frida-tools) frida4 ~$ frida -U --no-pause -l noroot_and_secret2.js -f


owasp.mstg.uncrackable1
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable1`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable1]-> [base_class_secret] <class:
sg.vantagepoint.a.a>
[base_class_secret.a] function e() {
[native code]
}
SEE IF WE CAN CONTROL THE FUNCTION. Argument=[Root detected!]

[BEGIN] retval
[object Object]
[END] retval

Now it works. Well, we don’t see the secret yet, as the results seem
to be an object, but we know we can print it. WE WIN! ALMOST
PERFECT!

If we check the original Java source we decompiled, the function


returns:

return cipher.doFinal(paramArrayOfbyte2);

It returns whatever cipher.doFinal() returns. See in the


documentation:

byte[] doFinal()
Finishes a multiple-part encryption or decryption operation, depending on how this
cipher was initialized.

We will need to convert the return value from a byte array. Ok, next
station: converting from byte[] to something printable.

I’m tired and too lazy to invest time on this. If we directly go to


StackOverflow we must find something… for sure. And yes, one of
the first links:

https://stackoverflow.com/questions/3195865/converting-byte-
array-to-string-in-javascript

I will test this function:


function bin2String(array) {
var result = "";
for (var i = 0; i < array.length; i++) {
result += String.fromCharCode(parseInt(array[i], 2));
}
return result;
}

And see:

(frida-tools) frida4 ~$ frida -U --no-pause -l noroot_and_secret3.js -f


owasp.mstg.uncrackable1
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable1`. Resuming main thread!
[base_class_secret] <class: sg.vantagepoint.a.a>
[base_class_secret.a] function e() {
[native code]
}
SEE IF WE CAN CONTROL THE FUNCTION. Argument=[Root detected!]

[BEGIN] retval
[object Object]
=== convert retval to string ===

[END] retval
[Android Emulator 5554::owasp.mstg.uncrackable1]-
>

Thank you for using Frida!


(frida-tools) frida4 ~$

Excellent, we FAILED AGAIN.

Why? Because cut&pasting from Stackoverflow works well in


many situations, but in others is a mess. And not only a mess for
this piece of code you took from there, but a mess a couple of
hundred lines below because you actually messed everything.

Well, be positive. Not the case here. We are not messing everything
:)

Digging more and more, I finally reached this Stackoverflow thread


where a particular comment clarifies A LOT what was really
happening:

https://reverseengineering.stackexchange.com/questions/17835/prin
t-b-byte-array-in-frida-js-script/22255#22255

The byte array contained negative integers so just applying a mask


to 0xff it will solve the problems. And…now we have a
noroot_secret_and_final.js script that I guarantee that works. See it
running in the device. I put my argument and click “Verify”:
Now the results on screen:

(frida-tools) frida4 ~$ frida -U --no-pause -l noroot_and_secret_final.js -f


owasp.mstg.uncrackable1
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable1`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable1]-> [base_class_secret] <class:
sg.vantagepoint.a.a>
[base_class_secret.a] function e() {
[native code]
}
SEE IF WE CAN CONTROL THE FUNCTION. Argument=[Root detected!]

[BEGIN] retval
[object Object]
[73,32,119,97,110,116,32,116,111,32,98,101,108,105,101,118,101]
=== convert retval to string ===
The secret was: I want to believe
[END] retval

(Now) WE WIN! PERFECT!

I put the password, “I want to believe” and:


Other tools

There is another interesting tool to play with Android


applications that is called dexdump . Can be installed
directly with many package managers in Linux ( apt
install dexdump ). You have the project repository here:

https://github.com/greatyao/dexdump

See its use:

(frida-tools) frida4 ~$ dexdump -d classes.dex |less


Processing 'classes.dex'...
Opened 'classes.dex', DEX version '035'
Class #0 -
Class descriptor : 'Lsg/vantagepoint/a/a;'
Access flags : 0x0001 (PUBLIC)
Superclass : 'Ljava/lang/Object;'
Interfaces -
Static fields -
Instance fields -
Direct methods -
#0 : (in Lsg/vantagepoint/a/a;)
name : 'a'
type : '([B[B)[B'
access : 0x0009 (PUBLIC STATIC)
code -
registers :4
ins :2
outs :3
insns size : 22 16-bit code units
00077c: |[00077c] sg.vantagepoint.a.a.a:([B[B)[B
00078c: 2200 1c00 |0000: new-instance v0,
Ljavax/crypto/spec/SecretKeySpec; // type@001c
000790: 1a01 0d00 |0002: const-string v1,
"AES/ECB/PKCS7Padding" // string@000d
000794: 7030 2400 2001 |0004: invoke-direct {v0, v2,
v1}, Ljavax/crypto/spec/SecretKeySpec;.<init>:
([BLjava/lang/String;)V // method@0024
00079a: 1a02 0b00 |0007: const-string v2, "AES" //
string@000b
00079e: 7110 2200 0200 |0009: invoke-static {v2},
Ljavax/crypto/Cipher;.getInstance:(Ljav
...
000ba6: 4f04 0103 |0025: aput-byte v4, v1, v3
000baa: d802 0202 |0027: add-int/lit8 v2, v2, #int 2
// #02
000bae: 28e0 |0029: goto 0009 // -0020
000bb0: 1101 |002a: return-object v1
catches : (none)
positions :
locals :

Virtual methods -
source_file_idx : -1 (unknown)

We can search directly for “ Main ” (obviously,


MainActivity ):

(frida-tools) frida4 ~$ dexdump -d classes.dex |grep Main


Class descriptor : 'Lsg/vantagepoint/uncrackable1/MainActivity$1;'
#0 : (in
Lsg/vantagepoint/uncrackable1/MainActivity$1;)
type : 'Lsg/vantagepoint/uncrackable1/MainActivity;'
#0 : (in
Lsg/vantagepoint/uncrackable1/MainActivity$1;)
type :
'(Lsg/vantagepoint/uncrackable1/MainActivity;)V'
0008dc: |[0008dc]
sg.vantagepoint.uncrackable1.MainActivity.1.<init>:
(Lsg/vantagepoint/uncrackable1/MainActivity;)V
With dexdump it is not necessary to process with
dex2jar and then open the JAR in a decompiler like
JAD , but just using the tool on the classes.dex we can
trace and disassemble.

Another interesting tool is FRIDA-DEXDump that can


work as a standalone tool or as a plugin within the
Objection framework (we will visit this awesome
project later).

FRIDA_DEXDump can be found here:

https://github.com/hluwa/FRIDA-DEXDump

(frida-tools) frida4 ~$ frida-ps -U|grep owas


4545 owasp.mstg.uncrackable1
(frida-tools) frida4 ~$ frida-dexdump -p 4545
--------------------------------------------------------------------------
____________ ___________ ___ ______ _______ _______ | ___| ___ \_ _|
_ \/ _ \ | _ \ ___\ \ / / _ \ | |_ | |_/ / | | | | | / /_\ \______| | | | |__ \ V /| | | |_ _ _ __ ___
_ __
| _| | / | | | | | | _ |______| | | | __| / \| | | | | | | '_ ` _ \| '_ \
| | | |\ \ _| |_| |/ /| | | | | |/ /| |___/ /^\ \ |/ /| |_| | | | | | | |_) | \_| \_| \_|\___/|___/ \_| |_/ |___/ \____/\/ \/___/
\__,_|_| |_| |_| .__/
||
|_|
https://github.com/hluwa/FRIDA-
DEXDump
--------------------------------------------------------------------------
02-16/18:10:15 INFO [DEXDump]: found target [4545]
owasp.mstg.uncrackable1
sh: 1: adb: not found
[DEXDump]: DexSize=0x1598,
DexMd5=cc732ff5ac58432d1d950d13e6dcd302,
SavePath=/home/user/Frida/crackmes/android/01/UnCrackable-
Level1/owasp.mstg.uncrackable1/0xdc3f4028.dex
(frida-tools) frida4 ~$

If we check the contents of the output file:

(frida-tools) frida4 ~$ file


/home/user/Frida/crackmes/android/01/UnCrackable-
Level1/owasp.mstg.uncrackable1/0xdc3f4028.dex
/home/user/Frida/crackmes/android/01/UnCrackable-
Level1/owasp.mstg.uncrackable1/0xdc3f4028.dex: Dalvik dex file
version 035

If you pass the “ -d ” option, it will try to dump


additional DEXs from the running process (for
example, libraries or modules loaded later):

(frida-tools) frida4 ~$ frida-dexdump -p 4545 -d


--------------------------------------------------------------------------
____________ ___________ ___ ______ _______ _______ | ___| ___ \_ _|
_ \/ _ \ | _ \ ___\ \ / / _ \ | |_ | |_/ / | | | | | / /_\ \______| | | | |__ \ V /| | | |_ _ _ __
___ _ __
| _| | / | | | | | | _ |______| | | | __| / \| | | | | | | '_ ` _ \| '_ \
| | | |\ \ _| |_| |/ /| | | | | |/ /| |___/ /^\ \ |/ /| |_| | | | | | | |_) | \_| \_| \_|\___/|___/ \_| |_/ |___/ \____/\/ \/___/
\__,_|_| |_| |_| .__/
||
|_|
https://github.com/hluwa/FRIDA-
DEXDump

--------------------------------------------------------------------------
02-16/18:15:34 INFO [DEXDump]: found target [4545]
owasp.mstg.uncrackable1
sh: 1: adb: not found
02-16/18:15:35 INFO [DEXDump]: deep search mode is enable,
maybe wait long time.
[DEXDump]: DexSize=0x1f4,
DexMd5=81771bd0395aeacffdca620ddd91d44a,
SavePath=/home/user/Frida/crackmes/android/01/UnCrackable-
Level1/owasp.mstg.uncrackable1/0x70d4f35f.dex
...
[DEXDump]: DexSize=0x1e9369,
DexMd5=d84f79e138810a449e0f3f2f21323a1d,
SavePath=/home/user/Frida/crackmes/android/01/UnCrackable-
Level1/owasp.mstg.uncrackable1/0x70d52cd3.dex
[DEXDump]: DexSize=0x1598,
DexMd5=cc732ff5ac58432d1d950d13e6dcd302,
SavePath=/home/user/Frida/crackmes/android/01/UnCrackable-
Level1/owasp.mstg.uncrackable1/0xdc3f4028.dex
[DEXDump]: DexSize=0x1fd8,
DexMd5=688071c5ba7372ba801af99c8965843f,
SavePath=/home/user/Frida/crackmes/android/01/UnCrackable-
Level1/owasp.mstg.uncrackable1/0xdc3f4028.dex

This DEX exported files can be read with different


tools.Wwith radare2 :

(frida-tools) frida4 ~$ r2 -A 0x70d52c43.dex


[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] find and analyze function preludes (aap)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
-- This computer has gone to sleep.
[0x00000000]>

We can do several things with this file, but it is not the


scope of this book. Just to show “something”, look for
functions:

[0x00000000]> afl
0x00000000 4 34 -> 54 fcn.00000000
0x0000022c 123 1013 -> 892 fcn.0000022c
0x00009432 21 462 -> 161 fcn.00009432
0x0000cc36 10 129 -> 72 fcn.0000cc36
0x00000442 8 125 -> 35 fcn.00000442
0x00002046 2 9 fcn.00002046

Summary up to this point. It is quite important to


remember these things:

READ CAREFULLY THE DEVELOPER


RECOMMENDATIONS FOR THE TEST
PLATFORM. DO IT. I MEAN IT. Will not
forget ever: Pixel and Android 9.
We can create virtual devices with the Android
Virtual Device Manager. And it is not
necessary to run the complete Android Studio:
you can just use the emulator alone.
We learnt how to hook Java functions on an
Android application, get the values and, in
general, do the same as we did on other
platforms.
There are a lot of tools and frameworks to
analyze mobile applications. It may be a good
idea for you to dedicate some time to search
documentation, tutorials and this kind of
materials, if you want to improve your skills.
CHAPTER 10. MORE FRIDA
CAPABILITIES

Within the crackmes for simple applications, we can


find crackme04 . It is quite simple code with one and
only goal: to store a password in memory for us to
locate it.

Using objdump we confirm what is NEEDED :

(frida-tools) frida4 ~$ objdump -p ./crackme04|grep NEEDED


NEEDED libc.so.6
(frida-tools) frida4 ~$

In this example we will check the disassembly to


identify functions and methods involved. I’ll skip
everything not in main to save some time:

(frida-tools) frida4 ~$ objdump -d crackem04|grep main


00000000000011e9 <main>:
11e9: f3 0f 1e fa endbr64
11ed: 55 push %rbp
11ee: 48 89 e5 mov %rsp,%rbp
11f1: 48 83 ec 50 sub $0x50,%rsp
11f5: 89 7d cc mov %edi,-0x34(%rbp)
11f8: 48 89 75 c0 mov %rsi,-0x40(%rbp)
11fc: 48 89 55 b8 mov %rdx,-0x48(%rbp)
1200: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
1207: 00 00
1209: 48 89 45 f8 mov %rax,-0x8(%rbp)
120d: 31 c0 xor %eax,%eax
120f: c7 45 d0 01 00 00 00 movl $0x1,-0x30(%rbp)
1216: c7 45 d4 00 00 00 00 movl $0x0,-0x2c(%rbp)
121d: 48 8d 45 da lea -0x26(%rbp),%rax
1221: ba 0f 00 00 00 mov $0xf,%edx
1226: be 00 00 00 00 mov $0x0,%esi
122b: 48 89 c7 mov %rax,%rdi
122e: e8 9d fe ff ff callq 10d0 <memset@plt>
1233: 48 8d 45 e9 lea -0x17(%rbp),%rax
1237: ba 0f 00 00 00 mov $0xf,%edx
123c: be 00 00 00 00 mov $0x0,%esi
1241: 48 89 c7 mov %rax,%rdi
1244: e8 87 fe ff ff callq 10d0 <memset@plt>
1249: 48 b8 76 65 72 79 76 movabs $0x7972657679726576,%rax
1250: 65 72 79
1253: 48 89 45 da mov %rax,-0x26(%rbp)
1257: c7 45 e2 73 65 63 72 movl $0x72636573,-0x1e(%rbp)
125e: 66 c7 45 e6 65 74 movw $0x7465,-0x1a(%rbp)
1264: c6 45 e8 00 movb $0x0,-0x18(%rbp)
1268: 48 8d 55 da lea -0x26(%rbp),%rdx
126c: 48 8d 45 e9 lea -0x17(%rbp),%rax
1270: 48 8d 35 91 0d 00 00 lea 0xd91(%rip),%rsi
# 2008 <_IO_stdin_used+0x8>
1277: 48 89 c7 mov %rax,%rdi
127a: b8 00 00 00 00 mov $0x0,%eax
127f: e8 6c fe ff ff callq 10f0 <sprintf@plt>
1284: 48 8d 45 e9 lea -0x17(%rbp),%rax
1288: 48 89 c6 mov %rax,%rsi
128b: 48 8d 3d 79 0d 00 00 lea 0xd79(%rip),%rdi
# 200b <_IO_stdin_used+0xb>
1292: b8 00 00 00 00 mov $0x0,%eax
1297: e8 24 fe ff ff callq 10c0 <printf@plt>
129c: 83 7d cc 02 cmpl $0x2,-0x34(%rbp)
12a0: 74 13 je 12b5 <main+0xcc>
12a2: 48 8d 3d 7f 0d 00 00 lea 0xd7f(%rip),%rdi
# 2028 <_IO_stdin_used+0x28>
12a9: e8 f2 fd ff ff callq 10a0 <puts@plt>
12ae: b8 ff ff ff ff mov $0xffffffff,%eax
12b3: eb 6e jmp 1323 <main+0x13a>
12b5: 83 7d d0 01 cmpl $0x1,-0x30(%rbp)
12b9: 74 fa je 12b5 <main+0xcc>
12bb: 48 8b 45 c0 mov -0x40(%rbp),%rax
12bf: 48 83 c0 08 add $0x8,%rax
12c3: 48 8b 10 mov (%rax),%rdx
12c6: 48 8d 45 e9 lea -0x17(%rbp),%rax
12ca: 48 89 d6 mov %rdx,%rsi
12cd: 48 89 c7 mov %rax,%rdi
12d0: e8 0b fe ff ff callq 10e0 <strcmp@plt>
12d5: 85 c0 test %eax,%eax
12d7: 75 26 jne 12ff <main+0x116>
12d9: 48 8b 45 c0 mov -0x40(%rbp),%rax
12dd: 48 83 c0 08 add $0x8,%rax
12e1: 48 8b 00 mov (%rax),%rax
12e4: 48 89 c6 mov %rax,%rsi
12e7: 48 8d 3d 82 0d 00 00 lea 0xd82(%rip),%rdi
# 2070 <_IO_stdin_used+0x70>
12ee: b8 00 00 00 00 mov $0x0,%eax
12f3: e8 c8 fd ff ff callq 10c0 <printf@plt>
12f8: b8 00 00 00 00 mov $0x0,%eax
12fd: eb 24 jmp 1323 <main+0x13a>
12ff: 48 8b 45 c0 mov -0x40(%rbp),%rax
1303: 48 83 c0 08 add $0x8,%rax
1307: 48 8b 00 mov (%rax),%rax
130a: 48 89 c6 mov %rax,%rsi
130d: 48 8d 3d 8c 0d 00 00 lea 0xd8c(%rip),%rdi
# 20a0 <_IO_stdin_used+0xa0>
1314: b8 00 00 00 00 mov $0x0,%eax
1319: e8 a2 fd ff ff callq 10c0 <printf@plt>
131e: b8 01 00 00 00 mov $0x1,%eax
1323: 48 8b 4d f8 mov -0x8(%rbp),%rcx
1327: 64 48 33 0c 25 28 00 xor %fs:0x28,%rcx
132e: 00 00
1330: 74 05 je 1337 <main+0x14e>
1332: e8 79 fd ff ff callq 10b0
<__stack_chk_fail@plt>
1337: c9 leaveq
1338: c3 retq
1339: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)

We have several options there to check. A good idea


might be to hook the first callq (a call to a routine,
method, function…) and see what happens. In order,
memset comes first. Not interesting and we will jump
directly to sprintf .

This function in the printf family (see API here):

https://man7.org/linux/man-pages/man3/printf.3.html

In particular, sprintf has this definition:

int sprintf(char *str, const char *format, ...);

The main purpose is to format a string like in a standard


printf , but copying the result in a string (the *str
pointer ). The target pointer must have enough allocated
space (maybe the memsets are there to nullify the
memory reserved. Format is the typical format and the
extra arguments must fit the variables described by this
field.

A quick question: if we compile the binary without the


“ -g ” option (debug symbols) and strip then to remove
extra information, will objdump be useful anyway?

(frida-tools) frida4 ~$ file crackme04


crackme04: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=28ab073a46fa9cd16234e27d71d3d2ecc1874004, for
GNU/Linux 3.2.0, with debug_info, not stripped

(frida-tools) frida4 ~$ file crackme04_nog


crackme04_nog: ELF 64-bit LSB shared object, x86-64, version 1
(SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=c0381ee2c539cd40646eb1d71557d149a4ef53b8, for
GNU/Linux 3.2.0, stripped
(frida-tools) frida4 ~$

YES. objdump will show us invocations to well-known


APIs. Here I want to introduce a couple of tools: “ nm ”
and “ readelf ”. I encourage you to play a bit with them
if you want to stay learning on Linux .

Now we know some details about a target function, let’s


invoke frida-trace and see what happens. Our handler:

{
onEnter(log, args, state) {
this.destination_address = args[0];
log("Format=[" + args[1].readUtf8String() + "]");
log("Parameter=[" + args[2].readUtf8String() + "]");
},
onLeave(log, retval, state) {
log("sprintf address=[" + this.destination_address + "]");
log('sprintf result=["' +
this.destination_address.readUtf8String() + '"]');
}
}

See the difference between printing the


destination_address ( this.destination_address ) and the
string value of the destination address:

this.destination_address.readUtf8String()

We will execute the crackme04 and:


(frida-tools) frida4 ~$ frida-trace -i sprintf -f crackme04
Instrumenting...
sprintf: Loaded handler at
"/home/user/Frida/crackmes/04/__handlers__/libc_2.31.so/sprintf.js"
Variable location: [0x7ffeaa65d4fa]
One argument that is the address of the password to unlock the
crackme.
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x4eb03 */
5 ms Format=[%p]
5 ms Parameter=[veryverysecret]
5 ms sprintf address=[0x7ffeaa65d509]
5 ms sprintf result=["0x7ffeaa65d4fa"]
Process terminated
(frida-tools) frida4 ~$

So, the crackme04 prints a “Variable location” that is


the result of the sprintf . And is the parameter to solve
the crackme . But… as we have ASLR (Address
randomization), how can we get the variable location
before running the process? Nah, we need to hook and
manipulate it to achieve success.

In 0x12d0 , in objdump output, we see a strcmp


invocation. This is the obvious method to hook and
replace retval to return 0 (equal) and win. We already
did this, so we are not learning anything new here. Just
to verify we can, test with this handler:

{
onEnter(log, args, state) {
log(`strcmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}")`);
},
onLeave(log, retval, state) {
retval.replace(0);
}
}

And the output:

(frida-tools) frida4 ~$ frida-trace -i strcmp -f crackme04 fake


Instrumenting...
strcmp: Loaded handler at
"/home/user/Frida/crackmes/04/__handlers__/libc_2.31.so/strcmp.js"
Variable location: [0x7ffe2969179a]
[fake] was the right address, you win! Perfect!
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x4f593 */
3 ms strcmp(s1="0x7ffe2969179a", s2="fake")
Process terminated
(frida-tools) frida4 ~$

Ok, nothing new in our learning backpack. But we want


to do something different new here. Instead of returning
a controlled value, what if we replace the comparison
parameter, so in the moment the comparison is made,
always compare with the proper value.

Yes, it is a silly thing, as I have both arguments in the


comparison, just copy one over another and that’s all…
well, good idea, and it will be excellent for the learning
purposes. We will do both things :)

First approach: make both elements in comparison


the same.
Just copy one argument over the other. As we know that
argument 1 ( str1 ) is the desired address, just copy this
value on argument 2. And, please, do not forget to
remove the retval replacement on onLeave :

{
onEnter(log, args, state) {
log('[*] BEFORE MANIPULATING ARGUMENTS');
log(`strcmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}")`);
args[1] = args[0];

log('[*] AFTER MANIPULATING ARGUMENTS');


log(`strcmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}")`);
},
onLeave(log, retval, state) {
// Remove the cheat here as must be resolved in OnEnter.
// retval.replace(0);
}
}

Take in mind we are not actually copying a value on the


other, but making the destination address the same in
both variables, so they both reference the same content
(the same string).

It is very important to understand that, as it is


dangerous to copy directly one element on another:
they might not be the same size and it may overflow. If
we really want to copy, instead of pointing to the same
value in memory, we must follow a procedure like this
one:
1. Get args[0] size.
2. Allocate a new ArrayBuffer this size.
3. Use this buffer like pointing args[1] = new
ArrayBuffer.

Do NOT copy directly values to args


elements if you are not sure of the
length/size they have. Remember always
the pattern creating a new ArrayBuffer or
reserving memory and then using a
pointer.

Let’s see the result just pointing to the same ptr . The
output:

(frida-tools) frida4 ~$ frida-trace -i strcmp -f crackme04 fake


Instrumenting...
strcmp: Loaded handler at
"/home/user/Frida/crackmes/04/__handlers__/libc_2.31.so/strcmp.js"
Variable location: [0x7ffe7b84345a]
[fake] was the right address, you win! Perfect!
Started tracing 1 function. Press Ctrl+C to stop.
/* TID 0x510ef */
3 ms [*] BEFORE MANIPULATING ARGUMENTS
3 ms strcmp(s1="0x7ffe7b84345a", s2="fake")
3 ms [*] AFTER MANIPULATING ARGUMENTS
3 ms strcmp(s1="0x7ffe7b84345a", s2="0x7ffe7b84345a")
Process terminated
(frida-tools) frida4 ~$

Easy. We “replaced” (make both values point to the


same place) values without anything complicated.
Second approach: use sprintf and strcmp together

We will move to the Frida repl and use a script, first,


to enumerate the modules and exports in them (so we
confirm where sprintf is loaded) and, then, a
resolve.js script where everything will be magic :).

Enumerate, then:

(frida-tools) frida4 ~$ frida -l enumerate_modules.js --no-pause


./crackme04 a|grep sprintf
(libc-2.31.so) export: vasprintf (0x7f80d0bf1e20)
(libc-2.31.so) export: sprintf (0x7f80d0bc7fa0)
(libc-2.31.so) export: __vsprintf_chk (0x7f80d0c93f00)
(libc-2.31.so) export: vsprintf (0x7f80d0beb2b0)
(libc-2.31.so) export: _IO_vsprintf (0x7f80d0beb2b0)
(libc-2.31.so) export: _IO_sprintf (0x7f80d0bc7fa0)
(libc-2.31.so) export: asprintf (0x7f80d0bc8070)
(libc-2.31.so) export: __vasprintf_chk (0x7f80d0c95760)
(libc-2.31.so) export: __sprintf_chk (0x7f80d0c93e30)
(libc-2.31.so) export: __asprintf (0x7f80d0bc8070)
(libc-2.31.so) export: __asprintf_chk (0x7f80d0c956a0)

So we need to hook libc-2.31.so!sprintf . See:

let sprintf_addr = null;


let sprintf_value = null;
const sprintf_ptr = Module.findExportByName('libc-2.31.so', 'sprintf');
const strcmp_ptr = Module.findExportByName('libc-2.31.so', 'strcmp');

function hook(function_ptr, function_actions) {


Interceptor.attach(function_ptr, function_actions);
}

hook(sprintf_ptr,
{
onEnter: function(args, state) {
console.log("sprintf.onEnter:");
this.destination_address = args[0];
},
onLeave:function(retval, state) {
sprintf_addr = this.destination_address;
sprintf_value = this.destination_address.readUtf8String();
}
}
);

hook(strcmp_ptr,
{
onEnter:function(args, state) {
console.log('[*] BEFORE MANIPULATING ARGUMENTS');
console.log(`strcmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}")`);

console.log('[*] sprintf_value and sprintf_addr:');


console.log(' sprintf_addr: ' + sprintf_addr);
console.log(' sprintf_value: ' + sprintf_value);
args[1] = sprintf_addr;

console.log('[*] AFTER MANIPULATING ARGUMENTS (with previous value)');


console.log(`strcmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}")`);
},
onLeave:function(retval, state) {
// Remove the cheat here as must be resolved in OnEnter.
// retval.replace(0);
}
}
);
And the resulting output:

(frida-tools) frida4 ~$ frida -l resolve.js --no-pause ./crackme04 fake


____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `./crackme04 fake`. Resuming main thread!

Variable location: [0x7ffcd824116a]


[fake] was the right address, you win! Perfect!
sprintf.onEnter:
[Local::crackme04]-> [*] BEFORE MANIPULATING ARGUMENTS
strcmp(s1="0x7ffcd824116a", s2="fake")
[*] sprintf_value and sprintf_addr:
sprintf_addr: 0x7ffcd8241179
sprintf_value: 0x7ffcd824116a
[*] AFTER MANIPULATING ARGUMENTS (with previous value)
strcmp(s1="0x7ffcd824116a", s2="0x7ffcd824116a")
Process terminated
[Local::crackme04]->

Thank you for using Frida!


(frida-tools) frida4 ~$

We achieved the goal. What is different from other


crackmes and techniques we already used? Follow me
making focus on several parts of the script:

function hook(function_ptr, function_actions) {


Interceptor.attach(function_ptr, function_actions);
}
Here we created a wrapper to call Interceptor.attach for
any function pointer we pass, with the defined function
actions we set in the second parameter. We are on the
way of creating our toolset library with several
functionalities.

hook(sprintf_ptr,
{
onEnter: function(args, state) {
console.log("sprintf.onEnter:");
this.destination_address = args[0];
},
onLeave:function(retval, state) {
sprintf_addr = this.destination_address;
sprintf_value = this.destination_address.readUtf8String();
}
}
);

In the hook for sprintf , in onLeave , we got a value


from the function call arguments and then, we set onto a
global variable. This way we can read the variable when
the strcmp hook is called and use it to replace the
comparison, so we are always successful. No matter the
argument we pass when invoking crackme04, it will
result in success.

hook(strcmp_ptr,
{
onEnter:function(args, state) {
console.log('[*] BEFORE MANIPULATING ARGUMENTS');
console.log(`strcmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}")`);

console.log('[*] sprintf_value and sprintf_addr:');


console.log(' sprintf_addr: ' + sprintf_addr);
console.log(' sprintf_value: ' + sprintf_value);
args[1] = sprintf_addr;

console.log('[*] AFTER MANIPULATING ARGUMENTS (with previous value)');


console.log(`strcmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}")`);
},
onLeave:function(retval, state) {
// Remove the cheat here as must be resolved in OnEnter.
// retval.replace(0);
}
}

Of course, in a real application we should have a way to


make sure we are not messing with a real strcmp that
may not be related to whatever we want to control. In
this example, just checking for null before assigning the
value args[1] may be a good idea.

We confirmed that we can hook several functions at the


same time, making them work together and
“collaborate” in achieving the goal.
Scanning memory

There are two basic functions in Frida’s JavaScript


API that let us search for things in memory,
Memory.scan() and its synchronous version,
scanSync() .

Both functions will require an initial memory location


(base), a size to define the limit of the search and a
pattern defined as hexadecimal bytes separated by
spaces.

For example, if I want to search the string


AAAABBBBCCCC , my pattern will look like this one
(remember, A → 41, B → 42 and C → 43):

“41 41 41 41 42 42 42 42 43 43 43 43”

If search space (size) is big, it might be a good idea to


use the async version ( Memory.scan ) and add match
handlers there. But maybe we need to stop the execution
until a match appears, so in this case we may use
scanSync .

Matches are an object with two fields:

address: a NativePointer to a memory location


where the pattern starts.
size: the matched size from address.
In the “ dumpmes/01/ ” directory we have a couple of
example scripts showing both uses. The application we
are going to deal with is:

#include <stdio.h>
#include <unistd.h>

int main() {
const char *secret1 = "This is a very secret secret.";
const char *secret2 = "There's no business like show business.";
const char *secret3 = "There's no business like computer business.";

while(1) {
sleep(5);
}
return 0;
}

And the target string we are going to search is


“ business ” ("62 75 73 69 6e 65 73 73" ). See the async
example (search_business.js):

const m = Process.enumerateModules()[0];
console.log(JSON.stringify(m));

const pattern = "62 75 73 69 6e 65 73 73";

Memory.scan(m.base, m.size, pattern, {


onMatch(address, size) {
console.log('[Memory.scan().onMatch] match: addr=' +
address + " / size=" + size);

// if we want only the first match, just issue a return here.


return true;
},
onComplete() {
console.log('Memory.scan() complete');
}
});

Following the script we see:

Variable m is the result of getting the first


module in the enumerateModules result (the
program itself).
We define our pattern: const pattern = "62 75
73 69 6e 65 73 73";
Then we scan with an asynchronous strategy,
adding a handler for matches (onMatch).
When onComplete handler is called, we show
a confirmation and exit.

See the alternative option with a sync strategy:

const m = Process.enumerateModules()[0];
console.log(JSON.stringify(m));

const pattern = "62 75 73 69 6e 65 73 73";

var results = Memory.scanSync(m.base, m.size, pattern);

console.log('[Memory.scan().onMatch] match: addr=' +


m.base + " / size=" + m.size);

results.forEach( (item) => {


console.log(' [Memory.scanSync()]: match: addr=' +
item.address + " / size=" + item.size);
});
Important: patterns can have wildcard characters and,
even, search masks in the radare2 mask format; you
can check details here:

https://radare.gitbooks.io/radare2book/content/search_b
ytes/intro.html

The “wildcard” is the ? character, so if we create a


pattern like “41 42 ?? 41” will match AB*A , for
example ( AB1A , AB2A , ABZA , …). And, very
important, the mask is not byte-only, you may add a
question mark for a nibble: “41 41 ?F 41” .

It might be a good idea for you to have a conversion


function if, for example, you need to pass text strings as
patterns. Look at this simple example
(search_business_strings.js):

function string2pattern(search_string) {
var pattern = [];
for (var n = 0, l = search_string.length; n < l; n ++) {
var hex_char = Number(search_string.charCodeAt(n))
.toString(16);
pattern.push(hex_char);
}
return pattern.join(' ');
}

const m = Process.enumerateModules()[0];
console.log(JSON.stringify(m));

const search_string = "business";


// const pattern = "62 75 73 69 6e 65 73 73";
const pattern = string2pattern(search_string);
console.log('String=[' + search_string + '] Pattern=[' + pattern + ']');

Memory.scan(m.base, m.size, pattern, {


onMatch(address, size) {
console.log('[Memory.scan().onMatch] match: addr=' +
address + " / size=" + size);

// if we want only the first match, just issue a return here.


return true;
},
onComplete() {
console.log('Memory.scan() complete');
}
});

If you plan to use radare2 and Frida together, with the


r2frida plugin (we are not covering this in this book but
in the next one, “More on Frida”), you have very good
news, as you can perform searches with all the power of
both tools.

Imagine I start a session in my emulator with device ID


0, searching for the word “ business ” on the target
crackme application:

(frida-tools) frida4 ~$ r2 frida://0/Crackme


[0x00000000]> \/ business
Searching 8 bytes: 62 75 73 69 6E 65 73 73
hits: 32
0x5606c63c7033 hit0_0 business like show business.
0x5606c63c7046 hit0_1 business like show business.FrRr
0x5606c63c705b hit0_2 business like computer business.
Summary up to this point. It is quite important to
remember these things:

We can use Memory.scan/scanSync to search


for patterns in memory pages.
You can even use masks to make your matches
more probable when we can guess part of a
pattern or there might be different matches
with some differences.
With r2frida we can perform searches using all
the power of radare2 combined with Frida
dynamic instrumentation.
CHAPTER 11. SOME IMPROVEMENTS TO
CODE

At this point, you may be wondering why in some scenarios


we load JavaScript directly with -l, and why in others we use
Python scripts.

There is no reason at all. I was pivoting from one approach to


another, for you to see options. But I think it is really
interesting to dedicate time to improve Python scripts adding
more powerful capabilities to them.

Additionally, this chapter may be considered an extension of


the previous one, because we are extending our knowledge on
memory pages and using Frida to read (and write) memory
regions. Even assemble.

Look over this Python script:

import frida

process_pid = frida.spawn("dumpme01")
session = frida.attach(process_pid)

script = session.create_script("""
'use strict';

rpc.exports = {
memoryranges: function (permission_mask) {
return Process.enumerateRangesSync(permission_mask);
}
};
""")

script.load()
myagent = script.exports

read_memory_ranges = myagent.memoryranges('r--')

for r in read_memory_ranges:
print("Read memory range: [{base}]".format(base=r['base']))

Will spawn “ dumpme01 ” (in dumpmes/01/dumpme01 ) and


attach to the process PID. Then will load a script. Putting the
spot on the script, let me comment some parts:

rpc.exports = {}

This block is quite interesting. Any function that you add


inside this block, in the format of <name>: function
(<arguments>){} will be exported to the python context.
This means that you can create JavaScript functions that can
be exported to the python environment and be used like
standard python methods.

See this simple python script ( dumpme01_simple.py ) and


export:

import frida

process_pid = frida.spawn("dumpme01")
session = frida.attach(process_pid)

script = session.create_script("""
'use strict';

rpc.exports = {
printme: function () {
console.log("PRINT ME: dumpme01");
}
};
""")
script.load()
myagent = script.exports
myagent.printme()

We create a “ printme ” function that is exported to python


context and, then, used like another method within the agent.

The output:

(frida-tools) frida4 ~$ python dumpme01_simple.py


PRINT ME: dumpme01

See the next example ( dumpme01_simple2.py ) where we


have TWO functions:

import frida

process_pid = frida.spawn("dumpme01")
session = frida.attach(process_pid)

script = session.create_script("""
'use strict';

rpc.exports = {
printme: function () {
console.log("PRINT ME: dumpme01");
},
printAgain: function(with_name) {
console.log("PRINT ME: dumpme01 (" + with_name + ")");
}
};
""")

script.load()
myagent = script.exports
myagent.printme()

# Now, with parameter.


myagent.print_again("Julie")

If you pay attention to the function highlighted, in the


exported JavaScript definition we set this name: printAgain .
And in the python context we invoked print_again . This is
important, as when naming functions, you should remember
that the upper case will be replaced by <lowercase>_ .

The output (again):

(frida-tools) frida4 ~$ python dumpme01_simple2.py


PRINT ME: dumpme01
PRINT ME: dumpme01 (Julie)

The last version of the script ( dumpme01_simple3.py ):

import frida

process_pid = frida.spawn("dumpme01")
session = frida.attach(process_pid)

script = session.create_script("""
'use strict';

function printme() {
console.log("PRINT ME: dumpme01");
}

rpc.exports = {
printme: printme,
printAgain: function(with_name) {
console.log("PRINT ME: dumpme01 (" + with_name + ")");
},
printAgainAndagain: function(with_name) {
console.log("PRINT ME (dup): dumpme01 (" + with_name + ")");
}
};
""")
script.load()
myagent = script.exports
myagent.printme()

# Now, with parameter.


myagent.print_again("Julie")

# See convention in <lower>_


myagent.print_again_andagain("Deane")

See naming conventions on the highlighted functions and in


the first one ( printme ) we defined the function out of the
rpc.exports and assigned normally (just for you to see that we
can use symbols that are in the global scope).

The output:

(frida-tools) frida4 ~$ python dumpme01_simple3.py


PRINT ME: dumpme01
PRINT ME: dumpme01 (Julie)
PRINT ME (dup): dumpme01 (Deane)

Getting back to the original script in the beginning of this


chapter, when we defined the function “ memoryranges ”
within rpc.exports , what we did is to export to the python
context the results of invoking
“ Process.enumerateRangesSync(permission_mask) ”, with
the provided permission mask (for example, the mask for read
only memory pages with ‘r--’ ).

And from this moment, we can use the function in our


python scripts, so we no longer depend on running “ frida -l ”
or “ frida-trace ” or whatever the tool. Just use the python
script that can be connected to whatever we want.
See an improved version of the original script
( dumpme01_v2.py ):

import frida

process_pid = frida.spawn("dumpme01")
session = frida.attach(process_pid)

script = session.create_script("""
'use strict';

rpc.exports = {
memoryRanges: function (permission_mask) {
return Process.enumerateRangesSync(permission_mask);
},
extractMemory: function (source_address, size) {
return Memory.readByteArray(ptr(source_address), size);
}
};
""")

script.load()
myagent = script.exports

read_memory_ranges = myagent.memory_ranges('r--')
readwrite_memory_ranges = myagent.memory_ranges('rw-')
execute_memory_ranges = myagent.memory_ranges('--x')

print("[READ] memory blocks:")


for r in read_memory_ranges:
print("[{base}] size: {size}".format(base=r['base'],
size=r['size']))

print("[READ/WRITE] memory blocks:")


for r in readwrite_memory_ranges:
print("[{base}] size: {size}".format(base=r['base'],
size=r['size']))

print("[EXECUTION] memory blocks:")


for r in execute_memory_ranges:
print("[{base}] size: {size}".format(base=r['base'],
size=r['size']))
We have improved the script in several ways:

Used the upper-case (to lower_) convention for


exported functions.
Added a new export called “extractMemory”.
And executed the memory_ranges() with three
different page-permissions masks, so we get three
lists (read-only, read-write and execution).

Next step is to actually dump the memory pages to disk. The


idea is to create a directory structure this way:

output/<dumpme_name>/read/0x<page base memory address>


/0x<page base memory address>
/0x<page base memory address>
...
output/<dumpme_name>/write/0x<page base memory address>
...
output/<dumpme_name>/exec/0x<page base memory address>
...

We will save to the disk the first dumpme contents


( dumpme01 ) and show some details with tools like
hexdump and others.

The modified script contents:

import os
import pathlib
import frida
process_pid = frida.spawn("dumpme01")
session = frida.attach(process_pid)

script = session.create_script("""
'use strict';

rpc.exports = {
memoryRanges: function (permission_mask) {
return Process.enumerateRangesSync(permission_mask);
},
extractMemory: function (source_address, size) {
return Memory.readByteArray(ptr(source_address), size);
}
};
""")

script.load()
myagent = script.exports

read_memory_ranges = myagent.memory_ranges('r--')
readwrite_memory_ranges = myagent.memory_ranges('rw-')
execute_memory_ranges = myagent.memory_ranges('--x')

base_path = "./output/dumpme01"

read_base_path = os.path.join(base_path, 'read')


write_base_path = os.path.join(base_path, 'write')
exec_base_path = os.path.join(base_path, 'exec')

pathlib.Path(base_path).mkdir(parents=True,
exist_ok=True)

pathlib.Path(read_base_path).mkdir(parents=True,
exist_ok=True)

pathlib.Path(write_base_path).mkdir(parents=True,
exist_ok=True)

pathlib.Path(exec_base_path).mkdir(parents=True,
exist_ok=True)

print("[READ] memory blocks:")


for r in read_memory_ranges:
output_file = os.path.join(read_base_path, r['base'])
print("[{base}] size: {size}".format(base=r['base'],
size=r['size']))

with open(output_file, 'wb') as OUTPUT_FILE:


OUTPUT_FILE.write(myagent.extract_memory(r['base'],
r['size']))

print("[READ/WRITE] memory blocks:")


for r in readwrite_memory_ranges:
output_file = os.path.join(write_base_path, r['base'])
print("[{base}] size: {size}".format(base=r['base'],
size=r['size']))

with open(output_file, 'wb') as OUTPUT_FILE:


OUTPUT_FILE.write(myagent.extract_memory(r['base'],
r['size']))

print("[EXECUTION] memory blocks:")


for r in execute_memory_ranges:
exec_base_path = os.path.join(base_path, 'exec')
output_file = os.path.join(exec_base_path, r['base'])
print("[{base}] size: {size}".format(base=r['base'],
size=r['size']))

with open(output_file, 'wb') as OUTPUT_FILE:


OUTPUT_FILE.write(myagent.extract_memory(r['base'],
r['size']))

Not complicated. For every memory page identified, we read


it and then dump it to the proper directory. Now we will face
some problems with permissions to access specific pages. See
the output and I will add some explanations:

(frida-tools) frida4 ~$ python dumpme01_v3.py


[READ] memory blocks:
[0x562c185b5000] size: 4096
[0x562c185b6000] size: 4096
[0x562c185b7000] size: 4096
...
[0x7f7191773000] size: 4096
[0x7f7191774000] size: 4096
[0x7f7191775000] size: 4096
[0x7fff5490f000] size: 135168
[0x7fff54950000] size: 16384
Traceback (most recent call last):
File "dumpme01_v3.py", line 55, in <module>
OUTPUT_FILE.write(myagent.extract_memory(r['base'], r['size']))
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/core.py", line 401, in method
return script._rpc_request('call', js_name, args, **kwargs)
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/core.py", line 26, in wrapper
return f(*args, **kwargs)
File "/DATA/DEV/venvs/frida-tools/lib/python3.8/site-
packages/frida/core.py", line 333, in _rpc_request
raise result[2]
frida.core.RPCException: Error: access violation accessing
0x7fff54951000
at extractMemory (/script1.js:9)
at apply (native)
at <anonymous> (frida/runtime/message-dispatcher.js:13)
at c (frida/runtime/message-dispatcher.js:23)
(frida-tools) frida4 ~$

We got an exception trying to read 0x7fff54951000 . But…


supposedly we had READ access to this page, isn’t? If you
try to execute several times the error will always raise in this
specific memory address: 0x7fff54950000 . My first intuition
was that the problem may be ASLR related, but it makes no
sense, as in the moment the process is loaded, there is no page
reallocation if time passes. Just to make this sure I tried
empirically.

In the dumpme01_v4.py we enumerated memory pages three


times, adding a ten second delay between every enumeration.
The results are in a text file that we have split in three
different others, so we can compare them (diffing). If the
memory relocation is something that happens automatically,
we should see differences:
(frida-tools) frida4 ~$ diff dumpme_v4_1.txt dumpme_v4_2.txt
1c1
< (1) [READ] memory blocks:
---
> (2) [READ] memory blocks:
(frida-tools) frida4 ~$ diff dumpme_v4_1.txt dumpme_v4_3.txt
1c1
< (1) [READ] memory blocks:
---
> (3) [READ] memory blocks:

No differences, apart from the first line that identifies the


iteration (the number enclosed in [] ). This memory
relocation is not happening because of a timed event or
anything this way. And my whatever-related-to-ASLR seems
not to be the answer.

I tried several versions of the script to check if I can identify


the issue and, in version v5 , I found that even catching the
exception in JavaScript, another exception was being thrown
in Python (what?). See v5 code:

import os
import pathlib
import frida

process_pid = frida.spawn("dumpme01")
session = frida.attach(process_pid)

script = session.create_script("""
'use strict';

rpc.exports = {
memoryRanges: function (permission_mask) {
return Process.enumerateRangesSync(permission_mask);
},
extractMemory: function (source_address, size) {
if(!source_address){
console.log("NO valid source_address.");
return null;
}

var memoryPage = null;


try {
memoryPage = Memory.readByteArray(ptr(source_address),
size);
}
catch(error){
return null;
}

return memoryPage;
}
};
""")

script.load()
myagent = script.exports

read_memory_ranges = myagent.memory_ranges('r--')
readwrite_memory_ranges = myagent.memory_ranges('rw-')
execute_memory_ranges = myagent.memory_ranges('--x')

print("[READ] memory blocks:")


counter = 0
for r in read_memory_ranges:
block = None;
print("({counter}) [{base}] size: {size}".format(counter=counter,
base=r['base'],
size=r['size']))
try:
block = myagent.extract_memory(r['base'], r['size'])
except Exception as e:
print("Exception when: [{base}] [{size}]".format(base=r['base'],
size=r['size']))
print("E: [%s]" % str(e))
continue

if block is None:
print(" Memory block was NULL/NONE: excepted.")
counter += 1

And see the output:

(frida-tools) frida4 ~$ python3 dumpme01_v5.py


[READ] memory blocks:
(0) [0x556b9ad3a000] size: 4096
(1) [0x556b9ad3b000] size: 4096
(2) [0x556b9ad3c000] size: 4096
...
(71) [0x7fff6091f000] size: 16384
Exception when: [0x7fff6091f000] [16384]
E: [TypeError: cannot read property 'then' of null
at <anonymous> (frida/runtime/message-dispatcher.js:14)
at c (frida/runtime/message-dispatcher.js:23)]
(71) [0x7fff60923000] size: 8192
(frida-tools) frida4 ~$

I’m beginning to be upset with this as I’m not getting the


error… I breathed. Now I’m going to test this particular
address to see what is happening. My next steps will be:

If an exception reading the block:


call protect to try to set read permission on this
specific page and see what happens ().
If protect-read is successful, then read.
If protect-read is not successful, assume we do
not have permission to access this memory
location.
If not exception, read normally.

Next version, dumpme01_v6.py :

import os
import pathlib
import frida
process_pid = frida.spawn("dumpme01")
session = frida.attach(process_pid)

script = session.create_script("""
'use strict';

rpc.exports = {
memoryRanges: function (permission_mask) {
return Process.enumerateRangesSync(permission_mask);
},
extractMemory: function (source_address, size) {
if(!source_address){
console.log("NO valid source_address.");
return null;
}

var memoryPage = null;


try {
memoryPage = Memory.readByteArray(ptr(source_address),
size);
console.log("memoryPage was read! (compare with python context)");
}
catch(error){
console.log("Catched error: " + error);
console.log(" ***> try to protect and set READ permission.");

var protect_result = Memory.protect(ptr(source_address),


size,
'r--');
if(protect_result === true){
console.log("Memory.protect->read successful. Do read.");
memoryPage = Memory.readByteArray(ptr(source_address),
size);
}
else {
console.log("Memory.protect->read ERROR (" +
protect_result + "). Skip.");
memoryPage = null;
}
}

return memoryPage;
}
};
""")

script.load()
myagent = script.exports

read_memory_ranges = myagent.memory_ranges('r--')
readwrite_memory_ranges = myagent.memory_ranges('rw-')
execute_memory_ranges = myagent.memory_ranges('--x')

print("[READ] memory blocks:")


counter = 0
for r in read_memory_ranges:
block = None;
print("({counter}) [{base}] size: {size}".format(counter=counter,
base=r['base'],
size=r['size']))
try:
block = myagent.extract_memory(r['base'], r['size'])
except Exception as e:
print("Exception when: [{base}] [{size}]".format(base=r['base'],
size=r['size']))
print("E: [%s]" % str(e))
continue

if block is None:
print(" Memory block was NULL/NONE: excepted.")
counter += 1

Look at the output and my despair:

[READ] memory blocks:


(0) [0x5617f2111000] size: 4096
memoryPage was read! (compare with python context)
(1) [0x5617f2112000] size: 4096
memoryPage was read! (compare with python context)
(2) [0x5617f2113000] size: 4096
...
(71) [0x7ffe1fff7000] size: 16384
Catched error: Error: access violation accessing 0x7ffe1fff8000
***> try to protect and set READ permission.
Memory.protect->read successful. Do read.
Exception when: [0x7ffe1fff7000] [16384]
E: [Error: access violation accessing 0x7ffe1fff8000
at extractMemory (/script1.js:26)
at apply (native)
at <anonymous> (frida/runtime/message-dispatcher.js:13)
at c (frida/runtime/message-dispatcher.js:23)]
(71) [0x7ffe1fffb000] size: 8192
memoryPage was read! (compare with python context)
(frida-tools) frida4 ~$

What? I set READ on this page with protect, but we got an


access violation again? Hmmm… well, anyway we learnt a
new thing: Memory.protect() .

var protect_result = Memory.protect(ptr(source_address), size, 'r--');

With Memory.protect() we can change permissions on


specific memory pages when we need to improve our access
or, even, make it executable (exploiting mode). The better,
Frida creates an abstraction for us to do so in every supported
operating system, so we do not need specific implementations
for every target platform.

Modified the code a bit to make sure it is not reraising another


exception or something else:

...
var protect_result = Memory.protect(ptr(source_address), size, 'r--');
if(protect_result === true){
console.log("Memory.protect->read successful. Do read.");

try{
memoryPage = Memory.readByteArray(ptr(source_address),
size);
}
catch(second_error) {
memoryPage = null;
}

if(!memoryPage) {
console.log("memoryPage: is null again?.");
}
else {
console.log("memoryPage: is NOT null.");
}

return memoryPage;
}
else {
console.log("Memory.protect->read ERROR (" +
protect_result + "). Skip.");
memoryPage = null;
}

But:

(71) [0x7fff961c6000] size: 16384


Catched error: Error: access violation accessing 0x7fff961c7000
***> try to protect and set READ permission.
Memory.protect->read successful. Do read.
memoryPage: is null again?.
Exception when: [0x7fff961c6000] [16384]
E: [TypeError: cannot read property 'then' of null
at <anonymous> (frida/runtime/message-dispatcher.js:14)
at c (frida/runtime/message-dispatcher.js:23)]
(71) [0x7fff961ca000] size: 8192
memoryPage was read! (compare with python context)
(frida-tools) frida4 ~$

Just because I’m curious, I’ll invoke radare2 to see the


memory map from an external debugger. See:

(frida-tools) frida4 ~$ r2 -d ./dumpme01


Process with PID 434360 started...
= attach 434360 434360
bin.baddr 0x55f1473d8000
Using 0x55f1473d8000
asm.bits 64
[0x7fb3fb013100]> dm
0x000055f1473d8000 - 0x000055f1473d9000 - usr 4K s r--
/home/user/Frida/dumpmes/01/dumpme01
/home/user/Frida/dumpmes/01/dumpme01 ; sym.imp.__cxa_finalize
0x000055f1473d9000 - 0x000055f1473da000 - usr 4K s r-x
/home/user/Frida/dumpmes/01/dumpme01
/home/user/Frida/dumpmes/01/dumpme01 ;
map.home_user_Frida_dumpmes_01_dumpme01.r_x
0x000055f1473da000 - 0x000055f1473db000 - usr 4K s r--
/home/user/Frida/dumpmes/01/dumpme01
/home/user/Frida/dumpmes/01/dumpme01 ;
map.home_user_Frida_dumpmes_01_dumpme01.r
0x000055f1473db000 - 0x000055f1473dd000 - usr 8K s rw-
/home/user/Frida/dumpmes/01/dumpme01
/home/user/Frida/dumpmes/01/dumpme01 ;
map.home_user_Frida_dumpmes_01_dumpme01.rw
0x00007fb3fb012000 - 0x00007fb3fb013000 - usr 4K s r--
/usr/lib/x86_64-linux-gnu/ld-2.31.so /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007fb3fb013000 - 0x00007fb3fb036000 * usr 140K s r-x
/usr/lib/x86_64-linux-gnu/ld-2.31.so /usr/lib/x86_64-linux-gnu/ld-2.31.so ;
map.usr_lib_x86_64_linux_gnu_ld_2.31.so.r_x
0x00007fb3fb036000 - 0x00007fb3fb03e000 - usr 32K s r--
/usr/lib/x86_64-linux-gnu/ld-2.31.so /usr/lib/x86_64-linux-gnu/ld-2.31.so ;
map.usr_lib_x86_64_linux_gnu_ld_2.31.so.r
0x00007fb3fb03f000 - 0x00007fb3fb041000 - usr 8K s rw-
/usr/lib/x86_64-linux-gnu/ld-2.31.so /usr/lib/x86_64-linux-gnu/ld-2.31.so ;
map.usr_lib_x86_64_linux_gnu_ld_2.31.so.rw
0x00007fb3fb041000 - 0x00007fb3fb042000 - usr 4K s rw- unk0 unk0 ;
map.unk0.rw
0x00007ffdd14c5000 - 0x00007ffdd14e6000 - usr 132K s rw- [stack] [stack]
; map.stack_.rw
0x00007ffdd156d000 - 0x00007ffdd1571000 - usr 16K s r-- [vvar] [vvar] ;
map.vvar_.r
0x00007ffdd1571000 - 0x00007ffdd1573000 - usr 8K s r-x [vdso] [vdso] ;
map.vdso_.r_x
0xffffffffff600000 - 0xffffffffff601000 - usr 4K s --x [vsyscall] [vsyscall] ;
map.vsyscall_.__x
[0x7fb3fb013100]>

Another option (and easier to understand for our purpose) is


to show a more visual memory map in radare2 with “ dm= ”:
[0x7fb3fb013100]> dm=
map 132K - 0x00007ffdd14c5000 |-----------------------| 0x00007ffdd14e6000
rw- [stack]
map 16K - 0x00007ffdd156d000 |-----------------------| 0x00007ffdd1571000 r-
- [vvar]
map 8K - 0x00007ffdd1571000 |-----------------------| 0x00007ffdd1573000 r-
x [vdso]
map 4K - 0x00007fb3fb012000 |-----------------------| 0x00007fb3fb013000 r-
- /usr/lib/x86_64-linux-gnu/ld-2.31.so
map 140K * 0x00007fb3fb013000 |-----------------------| 0x00007fb3fb036000
r-x /usr/lib/x86_64-linux-gnu/ld-2.31.so
map 32K - 0x00007fb3fb036000 |-----------------------| 0x00007fb3fb03e000 r-
- /usr/lib/x86_64-linux-gnu/ld-2.31.so
map 8K - 0x00007fb3fb03f000 |-----------------------| 0x00007fb3fb041000
rw- /usr/lib/x86_64-linux-gnu/ld-2.31.so
map 4K - 0x00007fb3fb041000 |-----------------------| 0x00007fb3fb042000
rw- unk0
map 4K - 0xffffffffff600000 |-----------------------| 0xffffffffff601000 --x
[vsyscall]
map 4K - 0x000055f1473d8000 |#####------------------| 0x000055f1473d9000
r-- /home/user/Frida/dumpmes/01/dumpme01
map 4K - 0x000055f1473d9000 |----######-------------|
0x000055f1473da000 r-x /home/user/Frida/dumpmes/01/dumpme01
map 4K - 0x000055f1473da000 |---------#####---------| 0x000055f1473db000
r-- /home/user/Frida/dumpmes/01/dumpme01
map 8K - 0x000055f1473db000 |-------------##########|
0x000055f1473dd000 rw- /home/user/Frida/dumpmes/01/dumpme01
[0x7fb3fb013100]>

Radare2 will output in colors: green for pages with the


execution bit set ( ‘??x’ ), red for pages read-write ( ‘rw-’ ),
but this may be a book version without colors, sorry.

We can invoke r2 passing the command ( -c ‘<cmd>’ ) and


making it exit after its result ( -q , quiet), like in:
(frida-tools) frida4 ~$ r2 -d ./dumpme01 -c 'dm=' -q
Process with PID 434946 started...
= attach 434946 434946
...
map 16K - 0x00007fff79d1a000 |----------------------#| 0x00007fff79d1e000 r--
[vvar]
map 8K - 0x00007fff79d1e000 |----------------------#| 0x00007fff79d20000 r-x
[vdso]
(frida-tools) frida4 ~$

I had the idea of adding a delay on the Frida execution and


combining the Frida output from our script to the map we get
from r2 . Just to compare. In the middle of this… I checked
again the documentation and saw the “protection” field on the
object returned from the enumerateRanges . Changed a bit
( dumpme01_v7.py ) to show the permissions:

...
read_memory_ranges = myagent.memoryranges('r--')

for r in read_memory_ranges:
print("Read memory range: [{base}] size: [{size}] perms:
[{perms}]".format(base=r['base'],
size=r['size'],
perms=r['protection']))

(frida-tools) frida4 ~$ python3 dumpme01_v7.py


Read memory range: [0x56325fb5b000] size: [4096] perms: [r--]
...
Read memory range: [0x7ffd04b3c000] size: [16384] perms: [r--]
Read memory range: [0x7ffd04b40000] size: [8192] perms: [r-x]

Well, yes, do read the documentation. I added now enriched information (even
the page counter):

...
(72) Read memory range: [0x7fff34550000] size: [8192] perms: [r-x]
Ok. Let’s test this absurd thing with the delay and compare
Frida map, /proc/<pid>/map and r2 map. New version
dumpme01_v8.py:

import time
import frida

process_pid = frida.spawn("dumpme01")
session = frida.attach(process_pid)

script = session.create_script("""
'use strict';

rpc.exports = {
memoryranges: function (permission_mask) {
return Process.enumerateRangesSync(permission_mask);
}
};
""")

script.load()
myagent = script.exports

read_memory_ranges = myagent.memoryranges('---')

time.sleep(30)

counter = 0
for r in read_memory_ranges:
print("({counter}) Read memory range: [{base}] size: [{size}] perms:
[{perms}]".format(counter=counter,
base=r['base'],
size=r['size'],
perms=r['protection']))
counter += 1

I was wondering how to set a “ show-me-all-pages ” mask.


My first idea was something like ‘???’ , but raised an
exception. The next one, naturally, was ‘---’ . The
documentation is clear (or not): ‘rw-’ means “must be at
least readable and writable”. If at least read/write with this
mask, means that - is match any.

I added a time.sleep(30) to stop for 30 seconds, time enough


to cat /proc/<pid>/map and to execute r2 and get the results.
See it:

[console 1 - execute binary]


$ ./dumpme01_sleep

[console 2 - Frida script (dumpme01_v8.py]


(frida-tools) frida4 ~$ python dumpme01_v8.py
(0) Read memory range: [0x55d8ea146000] size: [4096] perms: [r--]
(1) Read memory range: [0x55d8ea147000] size: [4096] perms: [r-x]
(2) Read memory range: [0x55d8ea148000] size: [4096] perms: [r--]
(3) Read memory range: [0x55d8ea149000] size: [4096] perms: [r--]
...
(28) Read memory range: [0x7f059d4cc000] size: [4096] perms: [r--]
(29) Read memory range: [0x7f059d4cd000] size: [4096] perms: [rw-]
(30) Read memory range: [0x7f059d4ce000] size: [16384] perms: [r--]
...
(52) Read memory range: [0x7f059dd74000] size: [4096] perms: [rwx]
...
(86) Read memory range: [0x7ffc849ce000] size: [8192] perms: [r-x]
(87) Read memory range: [0xffffffffff600000] size: [4096] perms: [--x]
(frida-tools) frida4 ~$

[console 2 - proc/maps]
$ cat /proc/437614/maps
55d8ea146000-55d8ea147000 r--p 00000000 fd:05 15337471
/home/user/Frida/dumpmes/01/dumpme01_sleep
55d8ea147000-55d8ea148000 r-xp 00001000 fd:05 15337471
/home/user/Frida/dumpmes/01/dumpme01_sleep
55d8ea148000-55d8ea149000 r--p 00002000 fd:05 15337471
/home/user/Frida/dumpmes/01/dumpme01_sleep
55d8ea149000-55d8ea14a000 r--p 00002000 fd:05 15337471
/home/user/Frida/dumpmes/01/dumpme01_sleep
55d8ea14a000-55d8ea14b000 rw-p 00003000 fd:05 15337471
/home/user/Frida/dumpmes/01/dumpme01_sleep
7f059dd2b000-7f059dd50000 r--p 00000000 fd:05 9830520
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f059dd50000-7f059dec8000 r-xp 00025000 fd:05 9830520
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f059dec8000-7f059df12000 r--p 0019d000 fd:05 9830520
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f059df12000-7f059df13000 ---p 001e7000 fd:05 9830520
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f059df13000-7f059df16000 r--p 001e7000 fd:05 9830520
/usr/lib/x86_64-linux-gnu/libc-2.31.so
7f059df16000-7f059df19000 rw-p 001ea000 fd:05 9830520
/usr/lib/x86_64-linux-gnu/libc-2.31.so

7f059df19000-7f059df1f000 rw-p 00000000 00:00 0

7f059df3c000-7f059df3d000 r--p 00000000 fd:05 9830476


/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f059df3d000-7f059df60000 r-xp 00001000 fd:05 9830476
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f059df60000-7f059df68000 r--p 00024000 fd:05 9830476
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f059df69000-7f059df6a000 r--p 0002c000 fd:05 9830476
/usr/lib/x86_64-linux-gnu/ld-2.31.so
7f059df6a000-7f059df6b000 rw-p 0002d000 fd:05 9830476
/usr/lib/x86_64-linux-gnu/ld-2.31.so

7f059df6b000-7f059df6c000 rw-p 00000000 00:00 0

7ffc848f9000-7ffc8491a000 rw-p 00000000 00:00 0


[stack]

7ffc849ca000-7ffc849ce000 r--p 00000000 00:00 0


[vvar]

7ffc849ce000-7ffc849d0000 r-xp 00000000 00:00 0


[vdso]

ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]

[consola 3 - radare2 dm=]


(frida-tools) frida4~$ r2 -d 437614 -c 'dm=' -q
= attach 437614 437614
bin.baddr 0x55d8ea146000
Using 0x55d8ea146000
asm.bits 64

map 132K - 0x00007ffc848f9000 |-----------------------| 0x00007ffc8491a000


rw- [stack]

map 16K - 0x00007ffc849ca000 |-----------------------| 0x00007ffc849ce000 r--


[vvar]

map 8K - 0x00007ffc849ce000 |-----------------------| 0x00007ffc849d0000 r-x


[vdso]

map 148K - 0x00007f059dd2b000 |-----------------------| 0x00007f059dd50000


r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
map 1.5M * 0x00007f059dd50000 |-----------------------| 0x00007f059dec8000
r-x /usr/lib/x86_64-linux-gnu/libc-2.31.so
map 296K - 0x00007f059dec8000 |-----------------------| 0x00007f059df12000
r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so
map 4K - 0x00007f059df12000 |-----------------------| 0x00007f059df13000 --
- /usr/lib/x86_64-linux-gnu/libc-2.31.so
map 12K - 0x00007f059df13000 |-----------------------| 0x00007f059df16000 r-
- /usr/lib/x86_64-linux-gnu/libc-2.31.so
map 12K - 0x00007f059df16000 |-----------------------| 0x00007f059df19000
rw- /usr/lib/x86_64-linux-gnu/libc-2.31.so

map 24K - 0x00007f059df19000 |-----------------------| 0x00007f059df1f000


rw- unk0

map 4K - 0x00007f059df3c000 |-----------------------| 0x00007f059df3d000 r--


/usr/lib/x86_64-linux-gnu/ld-2.31.so
map 140K - 0x00007f059df3d000 |-----------------------| 0x00007f059df60000
r-x /usr/lib/x86_64-linux-gnu/ld-2.31.so
map 32K - 0x00007f059df60000 |-----------------------| 0x00007f059df68000 r-
- /usr/lib/x86_64-linux-gnu/ld-2.31.so
map 4K - 0x00007f059df69000 |-----------------------| 0x00007f059df6a000 r--
/usr/lib/x86_64-linux-gnu/ld-2.31.so
map 4K - 0x00007f059df6a000 |-----------------------| 0x00007f059df6b000
rw- /usr/lib/x86_64-linux-gnu/ld-2.31.so

map 4K - 0x00007f059df6b000 |-----------------------| 0x00007f059df6c000


rw- unk1
map 4K - 0x000055d8ea146000 |#----------------------| 0x000055d8ea147000
r-- /home/user/Frida/dumpmes/01/dumpme01_sleep
map 4K - 0x000055d8ea147000 |#----------------------| 0x000055d8ea148000
r-x /home/user/Frida/dumpmes/01/dumpme01_sleep
map 4K - 0x000055d8ea148000 |#----------------------| 0x000055d8ea149000
r-- /home/user/Frida/dumpmes/01/dumpme01_sleep
map 4K - 0x000055d8ea149000 |#----------------------| 0x000055d8ea14a000
r-- /home/user/Frida/dumpmes/01/dumpme01_sleep
map 4K - 0x000055d8ea14a000 |#----------------------| 0x000055d8ea14b000
rw- /home/user/Frida/dumpmes/01/dumpme01_sleep

map 4K - 0xffffffffff600000 |#######################| 0xffffffffff601000


--x [vsyscall]

As I have no better explanation for this, I think it might be


related to ASLR when reading many memory pages or I
don’t know. See this in Android :
https://android.googlesource.com/platform/bionic/+/e0848bbf
896ad1f704c48c0da9ff4fb397644dac

We will investigate this issue in detail in the next book, “More


on Frida”.
Messages from JavaScript to Python

I intentionally omitted the use of messages with “ send ”


in most of my scripts. I was waiting for this section, just
to add an explanation of how to pass messages and even
binary blobs from JavaScript to Python context.

The process is quite simple. When we want to send


information from one context to another we will use
“send”. On Python we will create a handler for
messages arriving from JavaScript :

def message_handler(message, data):


print(message)

script.on("message", message_handler);

And in JavaScript we will deliver the message with


send:

send("gocode");

We can illustrate the whole process with examples.


Please, look for the “ messenger/ ” directory on the
repository. Here you will find a python script,
fire_forget.py :

import sys
import frida

def on_message(message, data):


print("[on_message] message:", message, "data:", data)

process_pid = frida.spawn("messenger")
session = frida.attach(process_pid)

script = session.create_script("""
send(31337);
send("[gocode]");
send("[gocode] invalid variable: " + does_not_exist);
""")

script.on("message", on_message)
script.load()

sys.stdin.read()

And a binary (compiled from messenger.c ) named


“ messenger ”:

#include <stdio.h>
#include <unistd.h>

int main() {
while(1){
printf("I'm alive\n");
sleep(5);
}

return 0;
}

If we execute the script:

(frida-tools) frida4 ~$ python3 fire_forget.py


[on_message] message: {'type': 'send', 'payload': 31337} data: None
[on_message] message: {'type': 'send', 'payload': '[gocode]'} data: None
[on_message] message: {'type': 'error', 'description': "ReferenceError:
'does_not_exist' is not defined", 'stack': "ReferenceError:
'does_not_exist' is not defined\n at <eval> (/script1.js:4)",
'fileName': '/script1.js', 'lineNumber': 4, 'columnNumber': 1} data:
None

We see the expected: every send is received and printed


from python . When an error or exception happens on
JavaScript , the message will be sent anyway, but we
will receive the error information in the message in
python’s side.

Press any key and pass to the next script,


fire_forget2.py :

import sys
import frida

def on_message(message, data):


print("[on_message] message:", message, "data:", data)

process_pid = frida.spawn("messenger")
session = frida.attach(process_pid)

script = session.create_script("""
send(31337);
recv("ping", function(message){
console.log("Message: [" + message + "]");
send("PONG!");
});
""")

script.on("message", on_message)
script.load()
script.post({"type": "ping", "payload": "PAYLOAD"})
script.post({"type": "ping", "payload": "* PAYLOAD2 *"})

sys.stdin.read()

Execute the script and pay special attention to the


second payload-message ( * PAYLOAD2 * ), as you
will never see this message:

(frida-tools) frida4 ~$ python fire_forget2.py


[on_message] message: {'type': 'send', 'payload': 31337} data: None
Message: [[object Object]]
[on_message] message: {'type': 'send', 'payload': 'PONG!'} data: None

The handler for recv will only process a message


ONCE. So, if you expect to receive multiple messages
for a given type, you will need to reinstall the handler in
every call. Look at the third script fire_forget3.py :

import sys
import frida

def on_message(message, data):


print("[on_message] message:", message, "data:", data)

process_pid = frida.spawn("messenger")
session = frida.attach(process_pid)

script = session.create_script("""
function recv_ping(message){
console.log("Message: [" +
JSON.stringify(message) + "]");
send("PONG!");
recv("ping", recv_ping);
}

send(31337);
recv("ping", recv_ping);
""")

script.on("message", on_message)
script.load()

script.post({"type": "ping", "payload": "PAYLOAD"})


script.post({"type": "ping", "payload": "* PAYLOAD2 *"})
script.post({"type": "ping", "payload": "* PAYLOAD3 *"})

sys.stdin.read()

In this case we created a dedicated function called


recv_ping , and when this handler function finishes, we
set it again to receive “ ping ” messages.

The output:

(frida-tools) frida4 ~$ python fire_forget3.py


[on_message] message: {'type': 'send', 'payload': 31337} data: None
Message: [{"type":"ping","payload":"PAYLOAD"}]
[on_message] message: {'type': 'send', 'payload': 'PONG!'} data: None
Message: [{"type":"ping","payload":"* PAYLOAD2 *"}]
[on_message] message: {'type': 'send', 'payload': 'PONG!'} data: None
Message: [{"type":"ping","payload":"* PAYLOAD3 *"}]
[on_message] message: {'type': 'send', 'payload': 'PONG!'} data: None

All this behavior is asynchronous so handlers will


activate when messages arrive, without blocking the rest
of the execution. But if we need to be blocking, we can
“ wait ” (stopping the execution flow) until the proper
message arrives.

You may create a recv handler, assign to a variable and


the call.wait() . In my opinion this is a dangerous
practice as not only the JavaScript premises are based on
being async, but the execution flow in the application
itself may be in the same way. Adding blocking handlers
may lead to complex logic in interceptors or handlers
and, for sure, unexpected behaviors and problems
difficult to troubleshoot.

As a quick summary:

With rpc.exports we can map JavaScript


activities to python methods, so we can build
JavaScript code that can be reused in more
complex python applications.
Using messages, we can attach an
asynchronous flow of information from the
JavaScript context to the Python one. Imagine,
for example, connecting this flow to an AMQP
platform (RabbitMQ, Kafka, 0MQ…) or, even,
to a logstash flow.
CHAPTER 12. ANOTHER ANDROID CRACKME

Just to get back for a while on the educational Frida and Android , we
will solve the second Android crackme from OWASP :

https://github.com/OWASP/owasp-
mstg/tree/master/Crackmes/Android/Level_02

This time we will try to be brief and pragmatic on explanations. Trying to


make this chapter shorter and focused on what’s important.

Once the APK is downloaded, install it to the device:

(frida-tools) frida4 ~$ ./adb install UnCrackable-Level2.apk

I will execute the app and then check for running processes on the device:

(frida-tools) frida4 ~$ frida-ps -U


PID Name
---- ---------------------------------------------------
4102 adbd
1628 android.hardware.audio@2.0-service
1764 android.hardware.biometrics.fingerprint@2.1-service
1629 android.hardware.broadcastradio@1.1-service
1630 android.hardware.camera.provider@2.4-service
...
5385 com.android.chrome:sandboxed_process0
5337 com.android.chrome:webview_service
5268 com.android.defcontainer
2311 com.android.phone
2903 com.android.providers.calendar
...
2823 com.google.process.gapps
2527 com.google.process.gservices
1832 createns
1863 dhcpclient
1881 dhcpserver
1745 drmserver
4158 frida-server-14.2.10-android-x86
1760 gatekeeperd
1626 healthd
...
1752 mediaserver
1753 netd
1842 netmgr
5594 owasp.mstg.uncrackable2
5621 owasp.mstg.uncrackable2
1761 perfprofd
...
1574 vndservicemanager
1576 vold
1627 vr_hwc
2059 webview_zygote
1756 wificond
2197 wpa_supplicant
1743 zygote
(frida-tools) frida4 ~$

This time there are TWO instances of the application running:


owasp.mstg.uncrackable2 . Humm…

If we try with frida-discover it will complain about that:

(frida-tools) frida4 ~$ frida-discover -U -n sg.vantagepoint.uncrackable2


Failed to spawn: unable to find process with name 'sg.vantagepoint.uncrackable2'
(frida-tools) frida4 ~$ frida-discover -U -n owasp.mstg.uncrackable2
Failed to spawn: ambiguous name; it matches: owasp.mstg.uncrackable2 (pid: 5594),
owasp.mstg.uncrackable2 (pid: 5621)
(frida-tools) frida4 ~$

It complains as we have two processes instead one. Stopping the


application and spawning it from the tool is not useful too:

(frida-tools) frida4 ~$ frida-discover -U -f owasp.mstg.uncrackable2


Tracing 8 threads. Press ENTER to stop.
Process crashed: Bad access due to invalid address

***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint:
'google/sdk_gphone_x86_arm/generic_x86_arm:9/PSR1.180720.122/6736742:userdebug/dev-
keys'
Revision: '0'
ABI: 'x86'
pid: 5864, tid: 5905, name: tg.uncrackable2 >>> owasp.mstg.uncrackable2 <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x3d0f14
Abort message: 'Failed to resolve labels'
eax d601a313 ebx 003d0f00 ecx f2afed98 edx d4177978
edi d4177978 esi fffcc0d0
ebp fffcc088 esp d4177960 eip d5f17028

backtrace:
#00 pc 00002028 <anonymous:d5f15000>
***
Stopping...
(frida-tools) frida4 ~$

We will try to bypass the root check with our original noroot.js
(remember to change the package name to uncrackable2 ) script and see
what happens:

(frida-tools) frida4 ~$ frida -U --no-pause -l noroot.js -f owasp.mstg.uncrackable2


____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable2`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable2]-> Error: a(): has more than one
overload, use .overload(<signature>) to choose from:
.overload('java.lang.String')
.overload('sg.vantagepoint.uncrackable2.MainActivity', 'java.lang.String')
at X (frida/node_modules/frida-java-bridge/lib/class-factory.js:563)
at K (frida/node_modules/frida-java-bridge/lib/class-factory.js:558)
at set (frida/node_modules/frida-java-bridge/lib/class-factory.js:925)
at <anonymous> (/noroot.js:2)
at <anonymous> (frida/node_modules/frida-java-bridge/lib/vm.js:16)
at _performPendingVmOps (frida/node_modules/frida-java-bridge/index.js:238)
at <anonymous> (frida/node_modules/frida-java-bridge/index.js:230)
at apply (native)
at ne (frida/node_modules/frida-java-bridge/lib/class-factory.js:613)
at <anonymous> (frida/node_modules/frida-java-bridge/lib/class-factory.js:592)

Oh. It seems as owasp.mstg.uncrackable2.MainActivity.a() collides with


another definition for a() , so this is because it asks us to select which
one to hook using the overload() method… or maybe we can hook both,
but one step and then another.

I will hook the first in order: .overload('java.lang.String') . Look on our


new noroot.js:

Java.perform(function() {
var mainActivity = Java.use("sg.vantagepoint.uncrackable2.MainActivity");

//mainActivity.a.implementation = function(s) {
mainActivity.a.overload("java.lang.String").implementation = function(s)
{
console.log("SEE IF WE CAN CONTROL THE FUNCTION. Argument=: " + s);
}
});

Run with the script:

(frida-tools) frida4 ~$ frida -U --no-pause -l noroot_overload.js -f owasp.mstg.uncrackable2


____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable2`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable2]-> SEE IF WE CAN CONTROL THE
FUNCTION. Argument=: Root detected!

First step solved. Before going for the secret comparison, please, let me
show you something in the source code that made me suspect there were
dangerous curves there:

This piece of code is within MainActivity :

static {
System.loadLibrary("foo");
}

If you check documentation about this function


( java.lang.Runtime.loadLibrary(String filename) ), it will say that the
main purpose is for loading a native library. Not a java library, a native
one (like in libc.so ). The string passed is the name of the library to be
loaded. If we look for the string “ foo ” on the files within the application
APK :

(frida-tools) frida4 ~$ find .|grep foo


./UnCrackable-Level2/lib/x86_64/libfoo.so
./UnCrackable-Level2/lib/x86/libfoo.so
./UnCrackable-Level2/lib/armeabi-v7a/libfoo.so
./UnCrackable-Level2/lib/arm64-v8a/libfoo.so
(frida-tools) frida4 ~$
Oh, oh, x86 , x86_64 , arm and arm64 libraries there. I can bet this
will not be easy. Seems that it is confirmed, and this application is using a
native library.

You can find this code here:

Let’s follow the procedure we performed on the previous Android


crackme , and look for the verify() method. There we see this:

if (this.m.a(str)) {
alertDialog.setTitle("Success!");
str = "This is the correct secret.";
} else {
alertDialog.setTitle("Nope...");
str = "That's not it. Try again.";
}

When verify is called, it passes the str (our argument I guess) to


this.m.a() and if the return is True (or not zero ) it will give us the
success message.

Looking for this.m we can see is created as a member of the class , type
private CodeCheck :

public class MainActivity extends c {


private CodeCheck m;
Go directly for CodeCheck:

package sg.vantagepoint.uncrackable2;

public class CodeCheck {


private native boolean bar(byte[] paramArrayOfbyte);

public boolean a(String paramString) {


return bar(paramString.getBytes());
}
}

Ok, this.m.a(str) calls CodeCheck.a(str) that, finally, reaches bar(str)


that is defined as native boolean (remember True or not zero IS true).

Naturally, bar should be within foo .

What to do next? I will recall the use of objdump and similar tools here.
See it here:

(frida-tools) frida4 ~$ objdump -p ./UnCrackable-Level2/lib/x86/libfoo.so

./UnCrackable-Level2/lib/x86/libfoo.so: file format elf32-i386

Program Header:
PHDR off 0x00000034 vaddr 0x00000034 paddr 0x00000034 align 2**2
filesz 0x00000100 memsz 0x00000100 flags r--
LOAD off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**12
filesz 0x00002144 memsz 0x00002144 flags r-x
LOAD off 0x00002ec0 vaddr 0x00003ec0 paddr 0x00003ec0 align 2**12
filesz 0x00000144 memsz 0x00000149 flags rw-
DYNAMIC off 0x00002ec8 vaddr 0x00003ec8 paddr 0x00003ec8 align 2**2
filesz 0x00000100 memsz 0x00000100 flags rw-
NOTE off 0x00000134 vaddr 0x00000134 paddr 0x00000134 align 2**2
filesz 0x000000bc memsz 0x000000bc flags r--
EH_FRAME off 0x00001d78 vaddr 0x00001d78 paddr 0x00001d78 align 2**2
filesz 0x000003cc memsz 0x000003cc flags r--
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4
filesz 0x00000000 memsz 0x00000000 flags rw-
RELRO off 0x00002ec0 vaddr 0x00003ec0 paddr 0x00003ec0 align 2**2
filesz 0x00000140 memsz 0x00000140 flags rw-

Dynamic Section:
PLTGOT 0x00003fc8
PLTRELSZ 0x00000058
JMPREL 0x000004e8
PLTREL 0x00000011
REL 0x000004d0
RELSZ 0x00000018
RELENT 0x00000008
RELCOUNT 0x00000003
SYMTAB 0x000001f0
SYMENT 0x00000010
STRTAB 0x00000300
STRSZ 0x00000118
HASH 0x00000418
NEEDED libm.so
NEEDED libdl.so
NEEDED libc.so
SONAME libfoo.so
FINI_ARRAY 0x00003ec0
FINI_ARRAYSZ 0x00000008
FLAGS 0x00000008
FLAGS_1 0x00000001
VERSYM 0x00000470
VERDEF 0x00000494
VERDEFNUM 0x00000001
VERNEED 0x000004b0
VERNEEDNUM 0x00000001

Version definitions:
1 0x01 0x08d6776f libfoo.so

Version References:
required from libc.so:
0x00050d63 0x00 02 LIBC

Try disassembling (not going to copy all the output, but just what relates
to our “function of interest”, bar ):

(frida-tools) frida4 ~$ objdump -d ./UnCrackable-Level2/lib/x86/libfoo.so|grep bar


00000f60 <Java_sg_vantagepoint_uncrackable2_CodeCheck_bar@@Base>:
f6c: e8 00 00 00 00 call f71
<Java_sg_vantagepoint_uncrackable2_CodeCheck_bar@@Base+0x11>
f89: 75 7c jne 1007
<Java_sg_vantagepoint_uncrackable2_CodeCheck_bar@@Base+0xa7>
fee: 75 17 jne 1007
<Java_sg_vantagepoint_uncrackable2_CodeCheck_bar@@Base+0xa7>
1005: 74 17 je 101e
<Java_sg_vantagepoint_uncrackable2_CodeCheck_bar@@Base+0xbe>
1014: 75 0c jne 1022
<Java_sg_vantagepoint_uncrackable2_CodeCheck_bar@@Base+0xc2>
1020: eb e7 jmp 1009
<Java_sg_vantagepoint_uncrackable2_CodeCheck_bar@@Base+0xa9>
(frida-tools) frida4 ~$});
Confirmed. Bar , the native ba r, is inside the native library foo . Can
you imagine what this means? Oh, yes! I’m happy because we will get
back to these ancient chapters where we played with enumerateModules
and exports and this stuff.

Let’s use our enumerate* scripts with this process. Test if this works:

Process.enumerateModules()

Note: enumerating modules in an application consumes resources and if


you need to check module addresses many times, maybe it is a good idea
to use ModuleMap : var mmap = new ModuleMap([filter]); . Then, just
call update when you need to refresh (but just when you need it, not
enumerating every time).

For enumerate, we created a very simple script:

Process.enumerateModules({
onMatch: function(module){
console.log('Module name:' + module.name + " (" + "Base Address: " +
module.base.toString() + ")");
},
onComplete: function(){}
});

Execute it:

(frida-tools) frida4 ~$ frida -U --no-pause -l enumerate_modules.js -f


owasp.mstg.uncrackable2
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawning `owasp.mstg.uncrackable2`...
Module name: app_process32 (Base Address: 0x589aa000)
Module name: libandroid_runtime.so (Base Address: 0xf4f4b000)
Module name: libbinder.so (Base Address: 0xf456a000)
...
Module name: libc.so (Base Address: 0xf36c0000)
Module name: libm.so (Base Address: 0xf31c6000)
...
Module name: android.hardware.configstore@1.0.so (Base Address: 0xf6145000)
...
Module name: libOpenglSystemCommon.so (Base Address: 0xe4e4b000)
...
Module name: libwebviewchromium_loader.so (Base Address: 0xe4a26000)
Module name: frida-agent-32.so (Base Address: 0xdaa1a000)
Module name: linux-vdso.so.1 (Base Address: 0xf7037000)
Module name: linker (Base Address: 0xf7038000)
Spawned `owasp.mstg.uncrackable2`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable2]->

If we run the script without the root check bypass, it seems as though the
native library is not loaded yet. So, let’s improve noroot_overload.js to
noroot_overload_and_modules.js and print the modules after the check is
bypassed:

(frida-tools) frida4 ~$ frida -U --no-pause -l noroot_overload_and_modules.js -f


owasp.mstg.uncrackable2
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable2`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable2]-> SEE IF WE CAN CONTROL THE
FUNCTION. Argument=: Root detected!
...
Module name: frida-agent-32.so (Base Address: 0xdaa1a000)
Module name: base.odex (Base Address: 0xd5e2a000)
Module name: libfoo.so (Base Address: 0xd5cdf000)
Module name: linux-vdso.so.1 (Base Address: 0xf7037000)
Module name: linker (Base Address: 0xf7038000)

There it is! At 0xd5cdf000 . We will improve the script once more to


print exports if the module name is exactly “ libfoo.so ”.

The code:

Java.perform(function() {
var mainActivity = Java.use("sg.vantagepoint.uncrackable2.MainActivity");
mainActivity.a.overload("java.lang.String").implementation =
function(s) {
console.log("SEE IF WE CAN CONTROL THE FUNCTION. Argument=: " + s);
Process.enumerateModules({
onMatch: function(module){
if(module.name === 'libfoo.so') {
console.log("[libfoo.so] FOUND. Export list:");
console.log("-------------------------------");
module.enumerateExports().forEach(function(item)
{
console.log("Export: " + item.name +
" (" + item.address + ")" );
});
}
},
onComplete: function(){}
});
};

});

The (simplified) output:

...
[Android Emulator 5554::owasp.mstg.uncrackable2]-> SEE IF WE CAN CONTROL THE
FUNCTION. Argument=: Root detected!
[libfoo.so] FOUND. Export list:
-------------------------------
Export: Java_sg_vantagepoint_uncrackable2_CodeCheck_bar (0xd5dfaf60)
Export: Java_sg_vantagepoint_uncrackable2_MainActivity_init (0xd5dfaf30)
[Android Emulator 5554::owasp.mstg.uncrackable2]->
Just two exports: the init function (in every dynamic library) and bar .

You know we can enumerate exports on a module, but do you know we


can enumerateImports too?

If bar is doing something around string comparison, we can bet it will use
a function like strcmp or strncmp . If we can dig into the imports and
see… Well, it will define our strategy as we already know how to hook
things and replace retval , aren’t we?

The module object, apart from the enumerateExports() method, has two
additional functions that are interesting for us right now:

enumerateImports: where we will get a list of which


module/function imports are related to the module.
enumerateSymbols: enumerates symbols of a module,
returning an array of objects with extended properties about
every symbol (name, address, size, …).

Remember to read the Frida JavaScript API documentation here:

https://frida.re/docs/javascript-api/

Let’s add the enumerateImports and enumerateSymbols to our script.


We will create a copy called:

noroot_overload_and_modules_all.js :

Java.perform(function() {
var mainActivity = Java.use("sg.vantagepoint.uncrackable2.MainActivity");

mainActivity.a.overload("java.lang.String").implementation = function(s) {
console.log("SEE IF WE CAN CONTROL THE FUNCTION. Argument=: " + s);
Process.enumerateModules({
onMatch: function(module){
if(module.name === 'libfoo.so') {
console.log("[libfoo.so] FOUND.");
console.log("[libfoo.so] Export list:");
console.log("-------------------------------");
module.enumerateExports().forEach(function(item) {
console.log("Export: " + item.name +
" (" + item.address + ")" );
});
console.log("[libfoo.so] Import list:");
console.log("-------------------------------");
module.enumerateImports().forEach(function(item)
{
console.log("Import: " + item.name +
" (" + item.address + ")" );
});

console.log("[libfoo.so] Symbols list:");


console.log("-------------------------------");
module.enumerateSymbols().forEach(function(item)
{
console.log("Symbol: " + item.name +
" (" + item.address + ")" );
});
}
},
onComplete: function(){}
//
});
};
});

The (even more simplified) output:

[libfoo.so] FOUND.
[libfoo.so] Export list:
-------------------------------
Export: Java_sg_vantagepoint_uncrackable2_CodeCheck_bar (0xd5ccdf60)
Export: Java_sg_vantagepoint_uncrackable2_MainActivity_init (0xd5ccdf30)
[libfoo.so] Import list:
-------------------------------
Import: waitpid (0xf36f3d60)
Import: __cxa_atexit (0xf3752010)
Import: __cxa_finalize (0xf3752170)
Import: __stack_chk_fail (0xf3735db0)
Import: fork (0xf3752300)
Import: getppid (0xf3737320)
Import: _exit (0xf3736bd0)
Import: ptrace (0xf36edc70)
Import: strncmp (0xf36d7ca0)
Import: pthread_create (0xf374eab0)
Import: pthread_exit (0xf374f1a0)
[libfoo.so] Symbols list:
-------------------------------

And our bet was a shot in the bullseye: strncmp . From this point we
already know what to do: we did in the examples working with the
crackmes …

The process is simple:

1. Get strncmp address and create a ptr to it.


2. Hook the function.
3. Create onLeave handler.
4. Replace retval with what is relevant for us. As bar() function
returns a Boolean value and we are interested in the value being
True, we will replace it with a 0 (strcmp and strncmp will return 0
if strings are equal).

Go for it. We have created a new script called noroot_intercept.js where


the magic is built. See the JavaScript source with comments:
Java.perform(function(){
var mainActivity = Java.use("sg.vantagepoint.uncrackable2.MainActivity");

// Firts, bypass the root check. This is trivial for us now...


mainActivity.a.overload("java.lang.String").implementation = function(s) {
console.log("SEE IF WE CAN CONTROL THE FUNCTION. Argument=: " + s);
}

// We will hook on onResume.


mainActivity.onResume.implementation = function() {
// Always remember to call the original function.
// There are many ways to do so, but using this.onResumen()
// is the simplest.
this.onResume();

// We already know libfoo.so and strncmp will be available.


const strncmp_ptr = Module.findExportByName('libfoo.so', 'strncmp');
// Important: we are starting to separate functions and code blocks
// that we use more than once. Here we create a "hook"
// just passing the _ptr.
// In the next version we will make it
// even more general.
hook_strncmp(strncmp_ptr);

// For the future...


// Interceptor.detachAll();
}
});

// Our hooking function. Just pass the _ptr


// and we will call the Interceptor.
function hook_strncmp(strncmp_address) {
Interceptor.attach(strncmp_address, {
onEnter: function(args) {
var str1 = null;
var str2 = null;
// Get the size passed on the call.
var argument_size = args[2].toInt32();

// We already know the secret and its length.


// This is just to reduce overhead and exit early
// if calling makes no sense.
if(argument_size != 23) {
return;
}

// Just to make sure str1|2 will not break execution flow.


try{
str1 = args[0].readUtf8String(argument_size)
} catch(error) {
return;
}

try{
str2 = args[1].readUtf8String(argument_size)
} catch(error) {
return;
}

this.please_replace_retval = false;

if (str1.includes("Julius Deane") ) {
console.log('[strncmp.onEnter] BEGIN');
console.log(' strncmp("' + str1 + '", "' + str2 +
'", ' + argument_size + ');');
console.log('[strncmp.onEnter] END');

this.please_replace_retval = true;
}
},
onLeave: function(retval) {
if(this.please_replace_retval === true) {
retval.replace(0);
}
}
});
}

We need to explain, a bit, what is the application’s activity lifecycle. You


read the Android documentation here:

https://developer.android.com/guide/components/activities/activity-
lifecycle

To avoid extending a lot in the explanation, a quick summary related to


the previous source code is what we are going to do. There are three
stages on the lifecycle:

onCreate(): when the activity is launched, this is the first


method to be called. Just once.
onStart(): immediately after the onCreate, onStart is called.
This method can be the next step, after the onRestart(), if the
activity is restarted. After this call the application passes to the
Created state.
onRestart(): if the activity is restarted. After this call the
application passes to the Started state.
onResume(): this event is called any time the activity comes
back from background or for being suspended for reasons that
typically are related to the user changing to another application
or resource. It is the next step after onPause(). After this call
the application passes to the Resumed state.
onPause(): is called when the activity is losing focus and
something else is passing to foreground. After an onPause, if
the activity receives focus again, onResume() will be invoked.
This is our target for this exercise. After this call the
application passes to the Paused state.
onStop(): the activity passes to Stopped state. This means that
it must free any resources related to the visibility. The
application is not yet visible for the user, so it must save
resources related to what the user can see. It let us keep the
application working in the background.
onDestroy(): it is invoked just before the activity is finished.

The most typical hooking strategy is to go for onResume , because it is


the event we will receive always, no matter the state of the application. If
you think about it, if we focus on onCreate and the application is already
started, our code will not be executed. The same with onStart : it will be
called by a restart, but if the user or the system is not doing so, we can
stay waiting for long… Of course, neither onPause , onStop , nor
onDestroy should be of our interest.

The next image shows the activity flow, events, and states.
This is main reason we “attacked” the onResume implementation:

mainActivity.onResume.implementation = function() {
// Always remember to call the original function.
// There are many ways to do so, but using this.onResumen()
// is the simplest.
this.onResume();

// We already know libfoo.so and strncmp will be available.


const strncmp_ptr = Module.findExportByName('libfoo.so',
'strncmp');
// Important: we are starting to separate functions
// and code blocks
// that we use more than once.
// Here we create a "hook" just passing
// the _ptr.
// In the next version we will make it even more general.
hook_strncmp(strncmp_ptr);

// For the future...


// Interceptor.detachAll();
}
Contents are simple. We will call the original onResume first:
this.onResume() . And then, we will retrieve the memory location for
strncmp , within libfoo so we can hook the function. Instead of hooking
directly, we created a method: hook_strncmp(address) .

In the next chapters we will be tidying up the code of the previous ones,
creating modules we can import, more general and more abstract
functions and methods… changes to make the code cleaner and easier to
read, but more importantly, easier to reuse.

Our hook:

var argument_size = args[2].toInt32();

// We already know the secret and its length.


// This is just to reduce overhead and exit early
// if calling makes no sense.
if(argument_size != 23) {
return;
}

We retrieve the size argument ( strncmp has three parameters: string1 ,


string2 , size to be compared) and set argument_size . To avoid entering
in every single string comparison of the application, we cheat here. As we
know the length of the secret string, we just put a fixed length to compare
and if not equal, exit from the hook the sooner the better.

There are many strategies here, but in my case, as I was adding a lot of
visual debug information, it was a mess if printing for just a one (1)
character comparison.

try{
str1 = args[0].readUtf8String(argument_size)
} catch(error) {
return;
}

Convert the string arguments referenced by args[0] and args[1] ,


carefully catching exceptions if they are raised. If strings are not to be
compared as UTF8 string, exit here and. Same for both arguments.

this.please_replace_retval = false;
This is a very interesting flag we are adding just to make a decision when
entering onLeave . We will extend the explanation in just a few
paragraphs.

if (str1.includes("Julius Deane") ) {
console.log('[strncmp.onEnter] BEGIN');
console.log(' strncmp("' + str1 + '", "' + str2 +
'", ' + argument_size + ');');
console.log('[strncmp.onEnter] END');

this.please_replace_retval = true;
}

The comparison from str1 to “ Julius Deane ” in str1.includes(“Julius


Deane”) if just checking that we passed a string that includes this name
within. If you don’t know who Julius Deane is, please, read William
Gibson’s Neuromancer (here you may find some details about this
character, https://williamgibson.fandom.com/wiki/Julius_Deane ).

If we type a string exactly 23 characters long with the string Julius Deane
within, we will log to console the strncmp arguments, that is the
comparison between our user provided argument and the desired secret.

Additionally, we are setting the flag for onLeave to true, so whatever is


happening within the handler, should consider please_replace_retval as
True.

Finally, the onLeave handler:

onLeave: function(retval) {
if(this.please_replace_retval === true) {
retval.replace(0);
}
}

Easy. If the flag is True, we replace the retval with a 0 (the return value
from strcmp / strncmp when strings are equal).

Please, test the script and see. The left screenshot shows an INVALID
password (it includes the string Julius Deane but has no proper length).
The right one is the “we-win-perfect!”.
On our Frida console:

(frida-tools) frida4 ~$ frida -U -l noroot_intercept.js --no-pause -f owasp.mstg.uncrackable2


____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
.... object? -> Display information about 'object'
.... exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Spawned `owasp.mstg.uncrackable2`. Resuming main thread!
[Android Emulator 5554::owasp.mstg.uncrackable2]-> SEE IF WE CAN CONTROL THE
FUNCTION. Argument=: Root detected!
[strncmp.onEnter] BEGIN
strncmp("Julius Deane is the top", "Thanks for all the fish", 23);
[strncmp.onEnter] END

Summary up to this point. It is quite important to remember these things:

We saw that Android applications can invoke native libraries.


But looking for modules and exports/imports/symbols, we can
hook these native functions anyway.
We now know about the overload() method that lets us choose
which one to hook among the overloaded methods in a class.
We learnt about the onResume event in the activity lifecycle.
We started to generalize our capabilities wrapping them in
functions that can be invoked, instead of adding raw code. In
the future, we will be able to create modules and pack them
(using a tool called frida-compile) to be reused.
CHAPTER 13. TELEGRAM Y
OBJECTION

A brief analysis of the send and receive functions in the


Telegram application is something that may help us in
understanding some concepts about data conversions
and, better, is an excuse to start wrapping functions on a
JavaScript file with a lot of functionality. We can later
import this file, instead of repeating new code every
time.

Additionally, we will explore the use of the Objection


framework for this task too. It is very powerful and
really useful, you’ll see.
Playing a bit with Telegram

Initially, we will follow the standard process we followed in


the previous chapters. First, see the process list and check for
the Telegram item.

(frida-tools) frida4 ~$ frida-ps -U


PID Name
---- ---------------------------------------------------
5525 adbd
1628 android.hardware.audio@2.0-service
1767 android.hardware.biometrics.fingerprint@2.1-service
...
1748 drmserver
5535 frida-server-14.2.10-android-x86
...
1650 logcat
5537 logcat
...
1756 netd
1842 netmgr
4432 org.telegram.messenger
1764 perfprofd
1645 qemu-props
...
1745 surfaceflinger
...
2262 wpa_supplicant
1746 zygote
(frida-tools) frida4 ~$

Ok, now we will attach to the process id and see exports,


imports, and symbols. For this, we will create a generic
function that we can call at any time. See our initial
JavaScript code:

And the results. Note that I have removed a lot of text, as the
list of exports, imports and symbols in Telegram is almost
infinite. If you want to make a closer look, please read the file
“ process_guts_js_results.txt ” in the repository. A brief
version of the output:

(frida-tools) frida4 ~$ frida -U -p 6175 --no-pause -l process_guts.js >


process_guts_js_results.txt
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/
Attaching...
* Module name: [app_process32] (0x59e3c000)
------------------------------------------------
[app_process32] Exports list:
Export: AddSpecialSignalHandlerFn (0x59e3f180)
...
[app_process32] Import list:
------------------------------------------------
Import: __cxa_atexit (0xeb5da010)
...
Symbol: __dl___loader_dlsym (0xece58490)
Symbol: __dl___loader_dlvsym (0xece584c0)
Symbol: __dl___loader_remove_thread_local_dtor (0xece589a0)
Symbol: __dl_rtld_db_dlactivity (0xece77770)
------------------------------------------------

When running frida-trace intercepting send / recv / open


function calls, it will create handlers automatically, we
already know that. Most related handlers will be associated
with libc . So, to save some time, let’s examine libc.so-send ,
recv , open ...

------------------------------------------------
* Module name: [libc.so] (0xeb548000)
------------------------------------------------
[libc.so] Exports list:
...
Export: open (0xeb574ce0)
// int open(const char *pathname, int flags);

Export: recv (0xeb576520)


// ssize_t recv(int sockfd, void *buf, size_t len, int flags);

Export: send (0xeb576cc0)


// ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// Auto-logline from frida-trace:
// log(`send(sockfd=${args[0]},
// buf=${args[1]},
// len=${args[2]},
// flags=${args[3]})`);

By now, let’s work on send() and recv() functions to see if


we can log the details sent on every request. Types for send
are:

● sockfd → int.
● buf → void* (Pointer).
● len → size_t (integer)
● flags → int.

Recv is not very different and the handler is quite in the same
way. Look at our script:

var send_ptr = Module.findExportByName('libc.so', 'send');


var recv_ptr = Module.findExportByName('libc.so', 'recv');
var send_func = new NativeFunction(send_ptr,
'int',
['int',
'pointer',
'int',
'int']);
var recv_func = new NativeFunction(recv_ptr,
'int',
['int',
'pointer',
'int',
'int']);

// Steal hexEncode and hexDecode from this thread:


// https://stackoverflow.com/questions/21647928/javascript-unicode-string-to-hex
String.prototype.hexEncode = function(){
var hex, i;

var result = "";


for (i=0; i<this.length; i++) {
hex = this.charCodeAt(i).toString(16);
result += ("000"+hex).slice(-4);
}

return result;
}

String.prototype.hexDecode = function(){
var j;
var hexes = this.match(/.{1,4}/g) || [];
var back = "";
for(j = 0; j<hexes.length; j++) {
back += String.fromCharCode(parseInt(hexes[j], 16));
}

return back;
}

function arrayBuffer2string(buffer) {
return String.fromCharCode.apply(null,
new Uint8Array(buffer));
}

function hook_send() {
Interceptor.replace(send_ptr,
new NativeCallback(
function (fd, buf, len, flags) {
var buffer = Memory.readByteArray(buf, len);
var result = send_func(fd, buf, len, flags);

console.log('send: [' +
arrayBuffer2string(buffer).hexEncode() + ']' );
return result;
},
'int', ['int', 'pointer', 'int', 'int']));
}

function hook_recv() {
Interceptor.replace(recv_ptr,
new NativeCallback(
function (fd, buf, len, flags) {
var result = recv_func(fd, buf, len, flags);
if (result > -1) {
var buffer = Memory.readByteArray(buf, result);
console.log('recv: [' +
arrayBuffer2string(buffer).hexEncode()+
']' );
}
else {
console.log('recv: null');
}
return result
},
'int', ['int', 'pointer', 'int', 'int']));
}

hook_send();
hook_recv();

Step by step:

var send_ptr = Module.findExportByName('libc.so', 'send');


var recv_ptr = Module.findExportByName('libc.so', 'recv');
var send_func = new NativeFunction(send_ptr,
'int',
['int',
'pointer',
'int',
'int']);
var recv_func = new NativeFunction(recv_ptr,
'int',
['int',
'pointer',
'int',
'int']);

We get pointers to the location of libc.so!send and


libc.so!recv , then create NativeFunction to these pointers
called, naturally, send_func() and recv_func() . Function
arguments are as we described before from the libc
API ( int , pointer , int , int ). Both functions return an
integer .

String.prototype.hexEncode = function(){}
String.prototype.hexDecode = function(){}

Then we define hexEncode and hexDecode (code that we


found in Stackoverflow, yes).

function arrayBuffer2string(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}

This is a quick method to convert from JavaScript’s


ArrayBuffer to a String . You can read about this type here:

https://developer.mozilla.org/es/docs/Web/JavaScript/Referen
cia/Objetos_globales/ArrayBuffer

Why this function? Because instead of using a readString*


function, we will use:

var buffer = Memory.readByteArray(buf, len);

To avoid problems with null bytes or characters that cannot be


handled directly. Notice the Uint8Array type casting, as it is
relevant. We are working with BYTES , so Uint8 .
Now we create two hook functions, send / recv :

function hook_send() {
Interceptor.replace(send_ptr,
new NativeCallback(
function (fd, buf, len, flags) {
var buffer = Memory.readByteArray(buf, len);
var result = send_func(fd, buf, len, flags);

console.log('send: [' +
arrayBuffer2string(buffer).hexEncode()+']' );

return result;
},
'int', ['int', 'pointer', 'int', 'int']));
}

IMPORTANT: here we are using the


Interceptor.replace() method, where we
REPLACE the original function at “ send_ptr ”
with our new handler content with a
NativeCallback .

In the replacement function, obviously, we will call the


original function ( send_func() , as we named it when creating
the NativeFunction ).

And, finally, we do stuff with the arguments passed to the API


call. Easy, isn't it?

The last step is to perform the actual hooking:

hook_send();
hook_recv();

If we test the script:


(frida-tools) frida4 ~$ frida -U --no-pause -p 8307 -l
hook_network_functions.js
____
/ _ | Frida 14.2.8 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
....
. . . . More info at https://www.frida.re/docs/home/

[Android Emulator 5554::PID::8307]-> send:


[006500b2001f000d00a1004e009..]
recv:
[000f007c00e9003800b00084001200b700830038005600c6009e00b300f300...]
send:
[e300ef00a2001b007200fa0034006200af005a00fe00170006007a00abfe00a...]
send:
[00c2001d002c00cd00d40050004d006500ab0043001e00bd002800b9001a...]
recv: [008e00fb007e00bc]
recv: [0064002deeff0a12]
[Android Emulator 5554::PID::8307]->

We are not investing time on interpreting the data as it is


encrypted when sent (apart from the potential TLS channel
that we may suspect is there too).

Nothing really new on the previous scripts, but important


concepts not to forget:

send/recv→buffer → arrayBuffer2string →
hexEncode: this pattern will be repeated on mostly
every function we call from now. The buffer
argument will be converted from ArrayBuffer to
string and, then, to hex representation (to avoid
binary rendering on the screen).
Objection

Objection ( object and injection → objection ) is an awesome framework to perform a


lot of discovery actions with Frida . You can see some screenshots here:

https://github.com/sensepost/objection/wiki/Screenshots

I left this section for the latest chapters, to avoid limiting your learning process on
Frida by providing you an easier way of doing things and hooks. First, understand
how to do these things yourself in raw, low-level Frida, and now, see a powerful tool
that will save time.

The main repository for the project can be found here:

https://github.com/sensepost/objection

And the easiest way to install it is using pip :

(frida-tools) frida4 ~$ pip install objection


...
Successfully built objection configobj terminaltables
Installing collected packages: tabulate, six, terminaltables, configobj, ptyprocess, MarkupSafe, cli-helpers,
Werkzeug, sqlparse, pexpect, Jinja2, itsdangerous, click, litecli, flask, delegator.py, objection
Successfully installed Jinja2-2.11.3 MarkupSafe-1.1.1 Werkzeug-1.0.1 cli-helpers-2.1.0 click-7.1.2
configobj-5.0.6 delegator.py-0.1.1 flask-1.1.2 itsdangerous-1.1.0 litecli-1.5.0 objection-1.9.6 pexpect-4.8.0
ptyprocess-0.7.0 six-1.15.0 sqlparse-0.4.1 tabulate-0.8.7 terminaltables-3.1.0

(frida-tools) frida4 ~$ objection


Checking for a newer version of objection...
Usage: objection [OPTIONS] COMMAND [ARGS]...

_ _ _ _
___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_|
|___|(object)inject(ion)

Runtime Mobile Exploration


by: @leonjza from @sensepost

By default, communications will happen over USB, unless the --network


option is provided.

Options:
-N, --network Connect using a network connection instead of USB.
[default: False]

-h, --host TEXT [default: 127.0.0.1]


-p, --port INTEGER [default: 27042]
-ah, --api-host TEXT [default: 127.0.0.1]
-ap, --api-port INTEGER [default: 8888]
-g, --gadget TEXT Name of the Frida Gadget/Process to connect to.
[default: Gadget]

-S, --serial TEXT A device serial to connect to.


-d, --debug Enable debug mode with verbose output. (Includes
agent source map in stack traces)

--help Show this message and exit.

Commands:
api Start the objection API server in headless mode.
device-type Get information about an attached device.
explore Start the objection exploration REPL.
patchapk Patch an APK with the frida-gadget.so.
patchipa Patch an IPA with the FridaGadget dylib.
run Run a single objection command.
version Prints the current version and exists.
(frida-tools) frida4 ~$

We have two main modes of working with objection:

1. Connect to an existing frida-server: and we will need to instruct objection to


use our application, “the gadget” with -g flag (or --gadget). Here the gadget
refers to the application name (org.telegram.messenger in this case).
2. Patch an existing APK and then connect to ir with objection: in the moment
you patch the APK and install onto the device, objection will be able to
connect to it using the gadget name “Gadget” (that will be set to the app).

The first approach is quicker, as we will use an existing Frida module to work with
the application. But the second may be better if we want to perform early
instrumentation.

One interesting example for this early instrumentation is in the documentation:

$ objection explore --startup-command 'ios jailbreak simulate'

Simulates a jailbroken device (answers True to the typical test looking for /bin/sh
in iOS ). All happening in an early loading stage, so we can manipulate the
application before it does anything else.

As we already run our frida-server , we will go for the -g :

(frida-tools) frida4 ~$ objection -g org.telegram.messenger explore


Using USB device `Android Emulator 5554`
Agent injected and responds ok!

_ _ _ _
___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.9.6

Runtime Mobile Exploration


by: @leonjza from @sensepost

[tab] for command suggestions


org.telegram.messenger on (google: 9) [usb] #

We can list files on our working directory:

org.telegram.messenger on (google: 9) [usb] # ls


Type Last Modified Read Write Hidden Size Name
--------- ----------------------- ------ ------- -------- --------- --------------------------------------------------------------------------------------------
File 2021-02-07 09:00:18 GMT True True False 0.0 B generatefid.lock
File 2021-02-08 07:28:18 GMT True True False 3.6 MiB cache4.db
File 2021-02-08 09:52:42 GMT True True False 5.3 MiB cache4.db-wal
File 2021-02-07 09:00:19 GMT True True False 514.0 B PersistedInstallation.W0....json
File 2021-02-08 09:52:42 GMT True True False 32.0 KiB cache4.db-shm
File 2021-02-08 09:39:38 GMT True True False 5.7 KiB tgnet.dat

Directory 2021-02-07 11:53:56 GMT True True Fals 4.0 KiB account1

File 2021-02-07 09:00:18 GMT True True False 5.9 KiB bluebubbles.attheme

Directory 2021-02-07 11:53:56 GMT True True False 4.0 KiB account2

File 2021-02-08 09:56:31 GMT True True False 540.0 B stats2.dat


File 2021-02-07 18:47:48 GMT True True False 40.0 B dc2conf.dat
File 2021-02-07 09:00:19 GMT True True False 282.0 KiB remote_en.xml
File 2021-02-08 09:26:54 GMT True True False 40.0 B dc4conf.dat

Directory 2021-02-07 09:03:43 GMT True True False 4.0 KiB ShortcutInfoCompatSaver_share_targets

File 2021-02-08 09:35:27 GMT True True False 40.0 B dc1conf.dat


File 2021-02-07 09:03:46 GMT True True False 40.0 B dc5conf.dat
File 2021-02-07 11:06:07 GMT True True False 153.0 B frc_1:..._firebase_activate.json

Readable: True Writable: True


org.telegram.messenger on (google: 9) [usb] #

Wow! This is like the meterpreter Frida version, isn’t it?

Sorry because I reduced font size to fit file names and I cut a couple of file names
that were long. These two files:

PersistedInstallation.W0RFRkGVRFRe+TTo3NjAzNDgwMzM3NzE2YW5kcm1pZDpmNmFmZDdiNjdlYW
frc_1:750346033671:android:e6a4d7b57bae9861_firebase_activate.json

It even informs us that we can read ( Readable: True ) and write ( Writable: True ) to
this location. Naturally, if we have ls, we have pwd :

org.telegram.messenger on (google: 9) [usb] # pwd


Current directory: /data/user/0/org.telegram.messenger/files

File download and upload too, but we will see it in action later. Let’s enumerate all
application’s activities:

org.telegram.messenger on (google: 9) [usb] # android hooking list activities


com.google.android.gms.auth.api.signin.internal.SignInHubActivity
com.google.android.gms.common.api.GoogleApiActivity
com.microsoft.appcenter.distribute.DeepLinkActivity
org.telegram.messenger.GoogleVoiceClientActivity
org.telegram.messenger.OpenChatReceiver
org.telegram.ui.BubbleActivity
org.telegram.ui.CallsActivity
org.telegram.ui.ExternalActionActivity
org.telegram.ui.IntroActivity
org.telegram.ui.LaunchActivity
org.telegram.ui.PopupNotificationActivity
org.telegram.ui.ShareActivity
org.telegram.ui.VoIPFeedbackActivity
org.telegram.ui.VoIPPermissionActivity

Found 14 classes
org.telegram.messenger on (google: 9) [usb] #

We can invoke an activity. Yes, I repeat, we can invoke, from here, directly, one of
the activities on the list. See the execution through the console and what happens on
the device:

org.telegram.messenger on (google: 9) [usb] # android intent launch_activity org.telegram.ui.IntroActivity


(agent) Starting activity org.telegram.ui.IntroActivity...
(agent) Activity successfully asked to start.
org.telegram.messenger on (google: 9) [usb] #

On the left, our newly created group to test Frida in telegram . We are in this group
waiting for things to happen, unchained from our Objection shell . On the right, the
device after calling the activity using Objection:

We successfully put the Telegram application on the initial activity: IntroActivity .


With little effort and in a clean way from the objection shell. The next image is a
screenshot of the source code showing the action:
Another interesting thing with objection is that you can use (and create) plugins.
Look at the documentation here:

https://github.com/sensepost/objection/wiki/Plugins

An interesting example plugin is this one that monitors the clipboard contents:

https://github.com/SpeedyFireCyclone/objection-android-clipboard

As you may remember, we created a similar tool for windows and the Notepad.exe .
Just for you to understand the powerful capabilities we can achieve with Frida , we
are going to test it. Clone the repo:

(frida-tools) frida4 ~$ git clone https://github.com/SpeedyFireCyclone/objection-android-clipboard.git


Cloning into 'objection-android-clipboard'...
remote: Enumerating objects: 26, done.
remote: Total 26 (delta 0), reused 0 (delta 0), pack-reused 26
Unpacking objects: 100% (26/26), 56.05 KiB | 451.00 KiB/s, done.
(frida-tools) frida4 ~$

Now load the plugin:

org.telegram.messenger on (google: 9) [usb] # plugin load objection-android-clipboard


Loaded plugin: clipboard
org.telegram.messenger on (google: 9) [usb] #

Loaded. Now activate the plugin and see the effects:

org.telegram.messenger on (google: 9) [usb] # plugin clipboard monitor


(agent) Warning! This module is still broken. A pull request fixing it would be awesome!
org.telegram.messenger on (google: 9) [usb] #
(agent) [pasteboard-monitor] Data: test string

In the Android device:


Simple and powerful, isn’t it? Just a few clicks and a plugin, instead of creating
native functions and building arguments to hook in windows.

And finally, as promised, let’s play with file download and upload. First, the “ cat ”
version with one of the files with a long name:

org.telegram.messenger on (google: 9) [usb] # file cat


frc_1:750346033671:android:e6a4d7b57bae9861_firebase_activate.json
Downloading
/data/user/0/org.telegram.messenger/files/frc_1:750346033671:android:e6a4d7b57bae9861_firebase_activate.js
to /tmp/tmp7919hl9t.file
Streaming file from device...
Writing bytes to destination...
Successfully downloaded
/data/user/0/org.telegram.messenger/files/frc_1:750346033671:android:e6a4d7b57bae9861_firebase_activate.js
to /tmp/tmp7919hl9t.file
====
{"configs_key":
{"ipconfig":"","ipconfigv2":"","ipconfigv3":""},"fetch_time_key":1312695967256,"abt_experiments_key":
[],"personalization_metadata_key":{}}====
org.telegram.messenger on (google: 9) [usb] #

And check the other long-one (and see JWT tokens appear!):
org.telegram.messenger on (google: 9) [usb] # file cat
PersistedInstallation.W0RFRkGVRFRe+TTo3NjAzNDgwMzM3NzE2YW5kcm1pZDpmNmFmZDdiNjdlYW
Downloading
/data/user/0/org.telegram.messenger/files/PersistedInstallation.W0RFRkGVRFRe+TTo3NjAzNDgwMzM3NzE
/tmp/tmpd8jveh03.file
Streaming file from device...
Writing bytes to destination...
Successfully downloaded
/data/user/0/org.telegram.messenger/files/PersistedInstallation.W0RFRkGVRFRe+TTo3NjAzNDgwMzM3NzE
/tmp/tmpd8jveh03.file
====
{"Fid":"eF8WTdy1trqYMmaKOtefgg","Status":3,"AuthToken":"eyJ...","RefreshToken":"2_...","TokenCreation
org.telegram.messenger on (google: 9) [usb] #

Oh my god: AuthToken and RefreshToken here! We can build a tool to steal all
JWT tokens from mobile applications… because know what? At least the Refresh
token should be available, in some kind of persistent storage, to be able to reconnect
transparently to the backends without asking the user for credentials again...

A new idea to add to the TODO for the next book: JWT token stealer ;)

I find this tool wonderful and want to give my congratulations to the developer, Leon
Jacobs, that in the moment of writing this book was in the Orange Cyberdefense's
Ethical Hacking Team called Sensepost (repos here: https://github.com/sensepost).

Summary up to this point. It is quite important to remember these things:

Objection is a powerful framework that we are going to use extensively


in the following chapters (upload, download, …).
CHAPTER 14. CODESHARE

Our last chapter is not about pure Frida and hooking, but
the use of an external resource. Codeshare is a wonderful
idea that as when installing a package with pip , cpan etc.
you can take advantage of other’s ideas to improve your
own Frida warfare. You can find it in this url:

https://codeshare.frida.re/

There you will find several scripts and tricks to do very


interesting things with Frida , like disabling certificate
pinning, debug SSL/TLS connections or hook specific
functions in several platforms. A lot of them focused on
mobile, so it is difficult to get stuck on anything with
Android , iOs and Frida .

As they cite in codeshare’s homepage: "If I have seen


further, it is by standing on the shoulders of giants" (from
Sir Issac Newton).

The most surprising things that you can do with codeshare is


to invoke it directly from the command line tool!

If you remember in the Frida’s help in the initial chapters,


there was an option -C :

-c CODESHARE_URI, --codeshare=CODESHARE_URI
load CODESHARE_URI
And with this option you can call any codeshare’s script
when doing frida:

(frida-tools) frida4 ~$ frida --codeshare J-jaeyoung/getchildpid -f


./crackme01
...
Spawning `./crackme01`...
Hello! This is the first time you're running this particular snippet, or the
snippet's source code has changed.

Project Name: getChildPid


Author: @J-jaeyoung
Slug: J-jaeyoung/getchildpid
Fingerprint:
da70ce1b375ac90a6e3b69882d4244342284ce13826cf5560a6d6146bf327af7
URL: https://codeshare.frida.re/@J-jaeyoung/getchildpid

Are you sure you'd like to trust this project? [y/N]

I invoked a simple script that watches “ fork ” and gives you


the child PID . But Frida , as this is the first time I request
this one from Codeshare , asks me for confirmation:
imagine you call whatever remote script without further
confirmation. Well, I checked the source code before
requesting the script but, anyway, a shiver ran down my
spine.

I will say Yes here:

Adding fingerprint
da70ce1b375ac90a6e3b69882d4244342284ce13826cf5560a6d6146bf327af7
to the trust store! You won't be prompted again unless the code changes.
Spawned `./crackme01`. Use %resume to let the main thread start executing!
[Local::crackme01]->
Our crackme01 has no fork calls, so the script is doing
nothing at all. Move to the crackme05 directory and test
again with this binary:

(frida-tools) frida4 ~$ frida --codeshare J-jaeyoung/getchildpid -f


./crackme05
...
Spawned `./crackme05`. Use %resume to let the main thread start
executing!
[Local::crackme05]-> %resume
[Local::crackme05]-> I'm the father: older and astonishing...
[FATHER]: Hello family!
I'm the child: younger and handsome...
[CHILD]: Hello family!
Start fork...
[child pid] 78033
End fork...
[Local::crackme05]->

The highlighted strings came from the script execution (see


the contents in Codeshare here,
https://codeshare.frida.re/@J-jaeyoung/getchildpid/):

//[Usage] frida --codeshare J-jaeyoung/getchildpid [bash_pid]

var fork = Module.findExportByName(null, "fork")

Interceptor.attach(fork, {
onEnter: function(args) {
console.log("Start fork...")
},
onLeave: function(retval) {
var pid = parseInt(retval.toString(16), 16)
console.log("[child pid] ", pid)
console.log("End fork...")
}
})
And, even better, naturally, you can create your own script
that not only will be available for every Frida user that
wants to use them, but for you too. One of my problems is
that I have some kind of a digital Diogenes syndrome, and I
tend to keep anything in my storages. It is rare for me to
delete anything… and having repositories, GitHub and, of
course, Frida codeshare , helps me in organizing
IMPORTANT things that can be lost in the ocean of other
irrelevant and useless projects I keep. So, Codeshare !

To share your scripts in codeshare you will need to register


at the website clicking on the “ Login ” link you can find on
top. Then, you will see:

There is a connector with GitHub . If you have a GitHub


account, just allow codeshare (just requests the email
information) and get in. Then you can go to create and share
your ideas there:
I will create (it is already there if you check codeshare) a
new project.

Imagine this scenario: I have a very critical application that


was built a long time ago and I do not own the source code,
nor be able to modify it in any way. This application logs
very sensitive information to a logfile, md5 tokens for very
secret transactions and I need people, without the proper
authorization level to see these secrets, to perform
maintenance and management operations accessing those
logfiles.

I created a censorship filter to be able to replace with


asterisks several characters on the md5 string. This way,
the non-authorized users can see the logfiles but the md5
secrets are protected. To cover most of the options, we
directly focused on write syscall , instead of fprintf or any
other “higher level” functions.

The JavaScript source code:


// PLATFORM: libc based (Linux, *ix).
//[Usage] frida --codeshare juliusdeane/censor-hashes-on-write <your binary>

/* ************************************************************
* Just a proof of concept supporting md5 only.
*
* Really easy to improve for multi-hashes.
**************************************************************
*/
// md5, but it is easy to create regex for uuid, sha, blowfish...
const md5_regex = /[a-fA-F0-9]{32}/gi;

// how many chars on the original hash we want to keep (at the end)
const KEEP_ORIGINAL_CHARS = 4;
// which is the replacement character.
const REPLACEMENT_CHAR = '*';

// We pass a string and lenToReplace chars at the end will be replaced


// with the replacementChar.
function replace_string(string, lenToReplace, replacementChar) {
return string.substring(0,lenToReplace).split("").map(item => item =
replacementChar).join("").concat(string.substring(lenToReplace, string.length));
}

// This is a callback that is invoked by string.replace() so it will


// transform the string using replace_string() if matches md5_regex.
function censor_hash(str, p1, offset, s){
const lenToReplace = str.length - KEEP_ORIGINAL_CHARS;
return replace_string(str, lenToReplace, REPLACEMENT_CHAR);
}

// write is the low level call to write to the file.


// Even when using fprintf the system will invoke write.
var write_ptr = Module.findExportByName(null, "write")

Interceptor.attach(write_ptr, {
onEnter: function(args) {
// ssize_t write(int fd, const void *buf, size_t count);
// args[0], args[1], args[2]
// Parse a UTF8 string onto source_string.
const source_string = args[1].readUtf8String();

// If matches mdg_regex, apply censor_hash.


var replacement_string = source_string.replace(md5_regex,
censor_hash);
// Use Frida's magic to replace the string in the argument.
args[1].writeUtf8String(replacement_string);
},
onLeave: function(retval) {
// by now do nothing.
}
})

Now that we have a working piece of code, follow the steps


with me. Go to codeshare and click on Create new project:
https://codeshare.frida.re/new

There you will see a screen like this one:

Paste your source code in the box and click on “ Create


project ”. That’s all! From this moment you will be able to
call your script from codeshare as any other script.
Summary up to this point. It is quite important to remember
these things:

We can call remote scripts from codeshare using -


c or --codeshare.
Naturally, we can create our own scripts and make
them available for other Frida users in the world.
This is an excellent way of tidying up and
improving our code (as we know there will be
people using our scripts, may invest more time on
avoiding crashes and ugly code).
CHAPTER <EOT>. FUTURE WORK

This is the final chapter, EOT (End of Transmission). I


enjoyed a lot while learning about Frida but enjoyed
even more writing everything here. Thanks a lot,
because you are the reason why I spent time on this: it is
important to learn, but more important to share your
ideas and thoughts with others. In the first draft of this
book, there were like 26 chapters and around 900 pages.
It was a crazy idea to try to publish this in a single book,
so this is the main reason for splitting it. I tried to keep
the most relevant things for people who are initiating
and passed more advanced topics to the next
publication.

Now, I want more. And started wondering: “how to do


more things?”. While reading materials to write this
book I got caught with some ideas for plugins,
automated tools and, in the end, creating something like
Objection , that is an awesome framework to develop
really powerful actions on mobile apps. Maybe I will
dedicate some time to create scripts and populate my
repository at Frida’s codeshare , as I find this idea very
powerful.

Markdown format is another good idea. Several book


writers do recommend it. The last time I used something
like markdown or LaTeX was almost 25 years ago,
but this a new challenge (and the benefits on having the
capability of transforming from markdown to several
different formats seems to be worthwhile).

Or I’ll invest some time to hook Electron apps or


trying to play with iOS . After the magic I’ve seen on
manipulating games, even focusing on things like
World of Warcraft and discovering hidden worlds, as
Carlos Hernández (see references) does.

What I absolutely will do is to complete my


understanding of the inline assembly syscalls in mobile
devices and the use and abuse of CModule .

The next book, “More on Frida” that I’m writing in


collaboration with Pablo San Emeterio (thanks Pablo!),
will incorporate ideas on Windows keylogger using
frida-server without privileges (and in user space,
obviously), Mettle , exploiting and a test lab with
MIPS platform and several other ideas we think will be
interesting.

Thanks a lot for reading and, please, add


a review!
ANNEX. REFERENCES
[0] https://frida.re, Frida.

[1] https://github.com/lief-project/LIEF, LIEF project.

[2] https://rada.re/n/, Radare2.

[3] https://medium.com/@oleavr/anatomy-of-a-code-
tracer-b081aadb0df8, Code tracer.

[4] https://github.com/dweinstein/awesome-frida,
Awesome Frida resources.

[5] https://frida.re/slides/ncn-2015-cross-platform-
reversing-with-frida.pdf, Cross Platform Reversing with
Frida, NcN 2015.

[6] https://codeshare.frida.re/browse, Frida codeshare


browse.

[7] https://github.com/sensepost/objection, Objection.

[8] https://repo.zenk-
security.com/Conferences/ZeroNights/23-Ravnas.pdf,
Cross-platform reversing with Frida.

[9]
https://lief.quarkslab.com/doc/stable/tutorials/09_frida_l
ief.html, “How to use Frida on a non-rooted device”.
[10] https://fadeevab.com/frida-gadget-injection-on-
android-no-root-2-methods/, “Frida Injected non-rooted
devices”.

[11] https://koz.io/using-frida-on-android-without-root/,
“Using Frida on Android without root”.

[12] https://dpnishant.github.io/appmon/, Appmon


Hooking Framework.

[13]
https://awesomeopensource.com/project/dweinstein/awe
some-frida

[14] https://man7.org/linux/man-
pages/man7/vdso.7.html

[15]
http://file.digitalinterruption.com/Prototyping%20and%
20reverse%20engineering%20with%20frida_bsides.pdf

[16]
https://en.wikipedia.org/wiki/Application_binary_interfa
ce, ABI.

[17] https://frida.re/docs/javascript-api/#interceptor,
Frida Interceptor details.

[18] https://www.fuzzysecurity.com/tutorials/29.html,
Frida Registry explorer on Windows.
[19] https://tobis.dk/blog/reverse-engineering-android-
apps-with-frida-tools/, Objection tool.

[20] https://darvincitech.wordpress.com/tag/anti-frida/,
AntiFrida.

[21] https://starlabs.sg/blog/2020/11/instrumenting-
adobe-reader-with-frida/, Instrumenting Adobe Reader
with Frida.

[22]
https://stackoverflow.com/questions/58491568/how-do-
you-hook-a-native-stripped-library-using-frida, Register
natives.

[23] https://github.com/chame1eon/jnitrace, JNI trace.

[24] https://mschwaig.github.io/2018/03/21/live-coding-
notes-on-dynamic-instrumentation-with-frida, Some
notes on Frida (and interactive with the REPL).

[25]
https://crackinglandia.wordpress.com/2015/11/10/anti-
instrumentation-techniques-i-know-youre-there-frida/,
Anti-instrumentation, “I know you are there, Frida”.

[26] https://github.com/pwndbg/pwndbg, Pwndbg.

[27] https://github.com/sensepost/objection, Objection.


[28] https://github.com/hookmaster/frida-all-in-one,
Frida all-in-one.

[29] https://github.com/dpnishant/appmon, Appmon.

[30]
https://lief.quarkslab.com/doc/stable/tutorials/03_elf_ch
ange_symbols.html, Playing with ELF symbols.

[31] https://github.com/OWASP/owasp-
mstg/tree/master/Crackmes, Owasp Crackmes.

[32] https://github.com/num1r0/android_crackmes,
Other Android crackmes.

[33] https://github.com/Nightbringer21/fridump,
Fridump.

[34] https://github.com/iGio90/Dwarf, Dwarf debugger.

[35] https://github.com/OALabs/frida-extract, Frida


extract (dump PE).

[36] https://github.com/frida/cryptoshark, Cryptoshark.

[37] https://summit-labs.frida.ninja/, Frida Ninja test lab.

[38] https://cronop-
io.github.io/posts/binary%20analysis/2020-06-25-
dreamchess_frida/, Dreamchess with Frida.
[39] https://sensepost.com/blog/2019/hacking-doom-for-
fun-health-and-ammo/, Frida Doom.

[40] http://www.cycript.org/, Cycript.

[41] https://github.com/nowsecure/frida-cycript, Frida


cycript.

[42] https://www.cyberpunk.rs/symbolic-execution-tool-
manticore, Manticore.

[43] https://labs.f-secure.com/archive/needle-how-to/,
Needle for iOS.

[44] https://github.com/freehuntx/frida-ex-
nativefunction, Extended NativeFunction.

[45] https://www.cyberpunk.rs/pyrebox-python-
scriptable-reverse-engineering-sandbox, PyREBox
Sandbox.

[46] https://erev0s.com/blog/frida-code-snippets-for-
android/, Frida snippets for Android.

[47]
https://www.programmersought.com/article/772815227
50/, Wechat hook.

[48]
https://en.wikipedia.org/wiki/Address_space_layout_ran
domization, ASLR.
[49] https://sensepost.com/blog/2019/mettle-your-ios-
with-frida/, Meterpreter and Frida.

[50] https://github.com/rapid7/mettle, Meterpreter


native-code implementation.

[51] https://www.youtube.com/watch?
v=ZmV6TqKH2YE, Fantastic places and where to find
them (hacking videogames), Carlos Hernandez.

[52] https://www.youtube.com/watch?v=wH3Yh6nxheI,
Mission Impossible Forbidden Areas, Carlos Hernandez.

[53] http://getrobot.net/, Robot (Native Cross-platform


System Automation).

[54] https://github.com/bannsec/revenge/, REVENGE.

[55] https://github.com/scanmem/scanmem, Scanmem.

[56]
https://orangecyberdefense.com/uk/blog/uncategorized/r
everse-engineering-of-the-anubis-malware/, Reversing
the Anubis Malware.

[57] https://github.com/davuxcom/frida-scripts, Very


useful Frida scripts for Windows.

[58] https://github.com/sensepost/frida-windows-
playground/blob/master/SetWindowsHookExA_keylogg
er.js, Frida Windows Keylogger with
SetWindowsHookExA.

[59] https://github.com/killswitch-
GUI/SetWindowsHookEx-
Keylogger/blob/master/SetWindowsHookEx-
Keylogger/SetWindowsHookEx-
Keylogger/SetWindowsHookEx-Keylogger.cpp#L31,
Parsing key press in Windows (Hook).

[60] https://github.com/iddoeldor/frida-
snippets/tree/master/scripts, Interesting Frida scripts.

[61]
https://developer.aliyun.com/mirror/npm/package/frida-
inject/, Frida-inject.

[62] https://github.com/Nightbringer21/fridump,
Fridumper (universal memory dumper).

[63] https://versprite.com/blog/exploiting-vyprvpn-for-
macos/, Exploiting VyrVPN with Frida script.

[64] https://github.com/federicodotta/Brida, Brida: Burp


+ Frida.

[65] https://medium.com/@two06/fun-with-frida-
5d0f55dd331a, Fun with Frida (and keepass).

[66] “HOOK SECURITY FRAMEWORK”, Master


Thesis, Pablo San Emeterio, UPM, 2011.
[67]
https://dl.acm.org/doi/abs/10.1145/3180496.3180605?
download=true, Design and Implementation of a Highly
Adaptable Android Transparent Encryption Framework.

[68] https://es.slideshare.net/rootedcon/david-reguera-
new-w32-hooking-skills-rootedcon-2010, new w32
hooking skills - RootedCON 2010, David Reguera.

[69] http://phrack.org/issues/65/10.html#article, phook -


The PEB Hooker, David Reguera and Juan Carlos
Montes.

[70] https://github.com/viva-frida/Awesome--Frida-UI,
Awesome Frida UI.

[71]
https://gist.github.com/oleavr/a22d675b76e7509cd2c9,
Frida from npm.

[72] https://www.ayrx.me/frida-wasm-experiments,
Frida an Web Assembly.

[73] https://jsonplaceholder.typicode.com/, JSON Fake


REST service.

[74] https://github.com/nccgroup/house, House mobile


framework.
[75] https://github.com/nowsecure/frida-fs, Frida stream
file contents.

[76] https://github.com/nowsecure/frida-memory-
stream, Frida stream memory block.

[77] https://github.com/hluwa/FRIDA-DEXDump,
FRIDA-DEXDump.

[78]
https://gist.github.com/JamesHagerman/8d7bfac873fa6b
0109b2e68f58d34f35, radare2 and ARM.

[79] https://github.com/MobSF/Mobile-Security-
Framework-MobSF, Mobile Security Framework.

[80] https://mobile-security.gitbook.io/mobile-security-
testing-guide/appendix/0x08-testing-tools, Mobile
Security testing tools.

[81] https://github.com/radareorg/radare2/issues/16788,
issues with offset when remote debugging.

[82]
https://media.defense.gov/2019/Jul/16/2002158062/-1/-
1/0/CSI-LIMITING-PTRACE-ON-PRODUCTION-
LINUX-SYSTEMS.PDF, Yama-ptrace scope set to 3
(no ptrace at all).

[83] https://dustri.org/b/archives.html, Really interesting


reversing/radare2 resources.
[84] https://awakened1712.github.io/hacking/hacking-
frida/, Frida cheatsheet.

[85] https://rehex.ninja/posts/frida-cheatsheet/, Another


Frida cheatsheet.

[86] http://labe.felk.cvut.cz/~stepan/33OSD/files/e1-
syscalls-inline-asm.pdf, Inline Assembly.

[87] https://mederc.blogspot.com/2019/09/mimikatz-
v220-post-exploitation-tool-to.html, Post-exploitation
tools.

[88] https://i.blackhat.com/USA-20/Thursday/us-20-
Burgess-Detecting-Access-Token-Manipulation.pdf,
Frida uses to manipulate access tokens.

[89] https://movaxbx.ru/2019/02/19/bypass-edrs-
memory-protection-introduction-to-hooking/, EDR
evasion with hooks and Frida.

[90]
https://github.com/sensepost/objection/tree/master/plugi
ns/mettle, Objection plugin to load mettle in iOS
(awesome).

[91] https://versprite.com/blog/application-
security/frida-engage-part-one-building-an-elf-parser-
with-frida/, ELF parser with Frida.
[92] https://github.com/MarioVilas/winappdbg,
WinAppDbg by Mario Vilas: framework for Windows
instrumentation using python.

[93] https://parsiya.net/blog/2017-11-09-winappdbg-
part-1-basics/, WinAppDbg basics (Python
instrumentation).

[94] https://medium.com/@oleavr/anatomy-of-a-code-
tracer-b081aadb0df8, “Anatomy of a code tracer”, by
Ole André (Frida creator).
Table of Contents
How to read this book 13
Introduction 18
Chapter 1 . My working ( virtual ) environment 35
Chapter 2 . First steps with frida 49
Chapter 3 . Frida tools → frida - trace 82
Chapter 4 . More on frida - trace 105
Chapter 5 . Our final steps with traces 131
Chapter 6 . Other tools within frida 165
Chapter 7 . The powerful frida - server 175
Chapter 8 . A parenthesis in NativeFunction 219
Chapter 9 . Android and Frida 230
Chapter 10 . More Frida capabilities 283
Chapter 11 . Some improvements to code 304
Chapter 12 . Another Android crackme 336
Chapter 13 . Telegram y Objection 356
Chapter 14 . Codeshare 373
Chapter < EOT > . Future work 381

You might also like