Beginning Frida
Beginning Frida
Beginning Frida
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
Pablo.
THE AUTHOR
Linked: https://www.linkedin.com/in/rramirez/
Twitter: @patowc
Contact email: books@juliusdeane.com
HOW TO READ THIS BOOK
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”.
https://www.microsoft.com/en-
us/research/project/detours/
<script>
alert(‘Code block, multiline.’);
</script>
$ 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.
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.
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: https://virtualenv.pypa.io/en/latest/
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 .
As said before, “ source ” or just using the dot ( . ), will activate the
env. In the moment you need to get out, just type “ deactivate ”:
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.
Quick summary:
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:
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
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
https://github.com/jasongin/nvs
$ export NVS_HOME="/usr/local/nvs"
$ git clone https://github.com/jasongin/nvs "$NVS_HOME"
$ . "$NVS_HOME/nvs.sh" install
$ export NVS_HOME="$HOME/.nvs"
$ git clone https://github.com/jasongin/nvs "$NVS_HOME"
$ . "$NVS_HOME/nvs.sh" install
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:
$ 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.
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:
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).
$ command -v nvm
nvm
$
Exactly in the same way as with nvs , we can install node versions
right now:
$ 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
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 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.
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
We can test frida-ps , for example, one of the tools on the set:
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
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 ~$
Not sure if you know the command ldd , but the output
is very similar:
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.
rpc.exports.enumerateModules = function () {
return Process.enumerateModules();
};
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.
(frida-tools) frida2 ~$
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
-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:
{
/**
* 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) {
}
}
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).
https://man7.org/linux/man-pages/man7/vdso.7.html
(frida-tools) frida2 ~$
/*
* 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) {
}
}
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:
If we issue a ldd :
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>:
import frida
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()
-a MODULE!OFFSET, --add=MODULE!OFFSET
add MODULE!OFFSET
0000000000001169 <check_password>:
{
onEnter(log, args, state) {
log('sub_1169()');
},
onLeave(log, retval, state) {
}
}
Execute now:
Execute it:
The output:
And no, this was not the solution. We will try with 1
as true:
Output here:
#include <stdio.h>
#include <string.h>
int check_password(const char *secret_password, const char
*user_password) {
int i = 0;
return 1;
}
if (argc != 2) {
printf("One argument that is the password to unlock the crackme.\n");
return -1;
}
return 1;
}
(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)
(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)
(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/>.
(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)
(gdb) step
6 int i = 0;
(gdb) info args
secret_password = 0x555555556027 "verysecret"
user_password = 0x7fffffffe350 "AAAABBBBAAAA"
(gdb)
(gdb) x /s 0x555555556027
0x555555556027: "verysecret"
(gdb)
https://github.com/frida/frida/releases
https://github.com/frida/frida/releases/download/14.2.8/f
rida-gadget-14.2.8-linux-x86_64.so.xz
{
"interaction": {
"type": "listen",
"address": "127.0.0.1",
"port": 27042,
"on_load": "wait"
}
}
{
"interaction": {
"type": "script",
"path": "./gadget_preload.js"
}
}
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)
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).
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.
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
struct _GumRetEvent {
GumEventType type;
gpointer location;
gpointer target;
gint depth;
};
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).
import sys
import frida
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()
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:
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.
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 ):
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.
});
script.on('message', on_message)
script.load()
if __name__ == '__main__':
do_main("simple0")
Remember the introductory section about the Stalker events structures? What you
will see here when executing is this:
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.
]
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.
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()
if __name__ == '__main__':
do_main("simple0")
{"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:
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).
import sys
import frida
def do_main(my_process):
session = frida.attach(my_process)
script = session.create_script("""
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()
if __name__ == '__main__':
do_main("simple0")
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:
Our target is the executable “ crackme03 ”. But we have some helper tools that will
perform several actions to change its behavior:
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.
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 ).
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.
I suggest going for sem_getvalue with frida-trace and see what happens:
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.
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.
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() ).
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 ”.
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();
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;
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.
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:
I have added many options just for testing purposes, but any of these will work. See
our onLeave handler with the modifications:
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:
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 ):
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 ”:
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 .
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 ).
~# sys/install.sh
~$ sys/install.sh
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 .
[0x00001100]> iE
[Exports]
[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):
4 0x00002088 0x00002088 49 50 .rodata ascii You reached victory and glory, you are da'Hacker!
(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
4 0x00002088 0x00002088 49 50 .rodata ascii You reached victory and glory, you are da'Hacker!
[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
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:
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
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
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 ~$
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 ~$
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.
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
Or using netstat :
Or with lsof :
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 ~$
Killed
(frida-tools) frida2 ~$
We can use frida-trace . Let’s locate memory address
for the function “ is_correct ”:
WRONG!
bKey [b] was pressed.
WRONG!
cKey [c] was pressed.
WRONG!
Re-trace simple2 :
(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.
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 ~$
# We get a DEVICE
device = frida.get_device_manager().add_remote_device(
'{host}:{port}'.format(host=HOST,
port=PORT))
session.detach()
print("Detached.")
(frida-tools) frida2 ~$
https://docs.microsoft.com/en-
us/windows/win32/api/winuser/nf-winuser-
getclipboarddata
HANDLE GetClipboardData(
UINT uFormat
);
https://docs.microsoft.com/en-
us/windows/win32/api/winuser/nf-winuser-
openclipboard
BOOL OpenClipboard(
HWND hWndNewOwner
);
1. OpenClipboard .
2. GetClipboardData .
3. CloseClipboard (without arguments returning
BOOL if closed).
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))
script = session.create_script("""
console.log('[BEGIN] create_script');
var user32dll = Module.getBaseAddress("user32.dll");
console.log('[user32dll BASE address]: ' + user32dll);
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('[END] create_script');
""")
script.load()
session.detach()
print("Detached.")
import frida
import sys
import readchar
device = frida.get_device_manager().add_remote_device(
'{host}:{port}'.format(host=HOST,
port=PORT))
session = device.attach("notepad.exe")
script = session.create_script("""
...
""");
script.load()
We do load the script here, so it should work on the
attached process doing whatever is intended to do.
session.detach()
print("Detached.")
console.log('[BEGIN] create_script');
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.');
console.log('[END] create_script');
Line by line:
console.log('[BEGIN] create_script');
if(is_open !== 0) {
// Opened OK.
...
}
else{
...
}
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:
https://docs.microsoft.com/en-
us/windows/win32/dataxchg/clipboard-formats
https://docs.microsoft.com/en-
us/windows/win32/dataxchg/standard-clipboard-formats
CF_TEXT
console.log('[getClipboardData] Data=[' +
data_ptr.readUtf8String() + '].');
closeClipboard()
Executing the script we get the following output in our
console:
We win! Perfect!
https://docs.microsoft.com/en-
us/windows/win32/api/winuser/nf-winuser-
sendinput#parameters
frida
[Remote::simple2]->
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.
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
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.
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.
myInventedFunction(ptr(myStruct));
import frida
session = frida.attach("simple3")
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 :
0x0000: 00 00
0xfe00fe00: 00 fe 00 fe
0x000000fe: fe 00 00 00
https://docs.microsoft.com/en-
us/windows/win32/api/winuser/nf-winuser-sendinput
What ABI is?
my_function(allocated_return_value);
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.
https://en.wikipedia.org/wiki/X86_calling_conventions
https://en.wikipedia.org/wiki/Application_binary_interfa
ce
~/Downloads$ ./genymotion-3.2.0-linux_x64.bin
Installing for current user only. To install for all users, restart this
installer as root.
/home/user/Android/Sdk/platform-tools/adb
https://developer.android.com/studio/command-line
https://github.com/OWASP/owasp-mstg/tree/master/Crackmes
https://github.com/OWASP/owasp-mstg/tree/master/Crackmes/Android/Level_01
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):
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 ):
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:
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 .
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:
Ok, we have the necessary privileges now, so let’s copy the frida-server binary right
now:
Please note that I, intentionally, copied the ARM64 binary. See it:
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 :
In this case, ARM 32bit works as expected. Now run frida-server without
parameters:
generic_x86_arm:/ # /data/local/tmp/frida-server32
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.
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?
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
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).
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
***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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 ~$
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…
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:
Decompile the apk and explore it. This is not a book to learn how to decompile
Android applications, but just in few steps:
If you are interested in more information about apk decompiling, please look in a
search engine, as there are dozens of tutorials.
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() .
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.";
}
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);
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)
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.";
}
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);
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()
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
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;
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);
}
});
Then hook the m5a (remember, the real name is a() ) here:
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?).
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.
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.
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():
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.
<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: 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;
}
}
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.
bArr = C0000a.m0a(m7b("8d127684cbc37c17616d806cf50473cc"),
bArr = C0000a.m0a(m7b("8d127684cbc37c17616d806cf50473cc"),
The contents:
package p000sg.vantagepoint.p001a;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
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 :
***
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]->
https://developer.android.com/reference/javax/crypto/Cipher
With these errors I had the idea of replacing the virtual device. My
intuition suggests that this is the way…
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:
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:
Now we have the new emulator running, install the crackme , adb
root , copy the frida-server version (still x86 32 ) and start again:
Oh, well, this image is already rooted ? Well, to the TODO list but
not my focus right now. Push frida-server :
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");
}
Process terminated
[Android Emulator 5554::owasp.mstg.uncrackable1]->
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.
Yes, just this one will save you a lot of wasted time.
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");
}
[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!
return cipher.doFinal(paramArrayOfbyte2);
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.
https://stackoverflow.com/questions/3195865/converting-byte-
array-to-string-in-javascript
And see:
[BEGIN] retval
[object Object]
=== convert retval to string ===
[END] retval
[Android Emulator 5554::owasp.mstg.uncrackable1]-
>
Well, be positive. Not the case here. We are not messing everything
:)
https://reverseengineering.stackexchange.com/questions/17835/prin
t-b-byte-array-in-frida-js-script/22255#22255
[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
https://github.com/greatyao/dexdump
Virtual methods -
source_file_idx : -1 (unknown)
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
[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
https://man7.org/linux/man-pages/man3/printf.3.html
{
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() + '"]');
}
}
this.destination_address.readUtf8String()
{
onEnter(log, args, state) {
log(`strcmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}")`);
},
onLeave(log, retval, state) {
retval.replace(0);
}
}
{
onEnter(log, args, state) {
log('[*] BEFORE MANIPULATING ARGUMENTS');
log(`strcmp(s1="${args[0].readUtf8String()}",
s2="${args[1].readUtf8String()}")`);
args[1] = args[0];
Let’s see the result just pointing to the same ptr . The
output:
Enumerate, then:
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()}")`);
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()}")`);
“41 41 41 41 42 42 42 42 43 43 43 43”
#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;
}
const m = Process.enumerateModules()[0];
console.log(JSON.stringify(m));
const m = Process.enumerateModules()[0];
console.log(JSON.stringify(m));
https://radare.gitbooks.io/radare2book/content/search_b
ytes/intro.html
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));
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']))
rpc.exports = {}
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()
The output:
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()
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()
The output:
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')
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"
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)
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;
}
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')
if block is None:
print(" Memory block was NULL/NONE: excepted.")
counter += 1
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;
}
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')
if block is None:
print(" Memory block was NULL/NONE: excepted.")
counter += 1
...
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:
...
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']))
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
[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
script.on("message", message_handler);
send("gocode");
import sys
import frida
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()
#include <stdio.h>
#include <unistd.h>
int main() {
while(1){
printf("I'm alive\n");
sleep(5);
}
return 0;
}
import sys
import frida
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()
import sys
import frida
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()
sys.stdin.read()
The output:
As a quick summary:
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
I will execute the app and then check for running processes on the device:
***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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:
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);
}
});
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:
static {
System.loadLibrary("foo");
}
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.";
}
Looking for this.m we can see is created as a member of the class , type
private CodeCheck :
package sg.vantagepoint.uncrackable2;
What to do next? I will recall the use of objdump and similar tools here.
See it here:
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 ):
Let’s use our enumerate* scripts with this process. Test if this works:
Process.enumerateModules()
Process.enumerateModules({
onMatch: function(module){
console.log('Module name:' + module.name + " (" + "Base Address: " +
module.base.toString() + ")");
},
onComplete: function(){}
});
Execute it:
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:
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(){}
});
};
});
...
[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 .
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:
https://frida.re/docs/javascript-api/
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 + ")" );
});
[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 …
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);
}
}
});
}
https://developer.android.com/guide/components/activities/activity-
lifecycle
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();
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:
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;
}
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;
}
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.
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:
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:
------------------------------------------------
* Module name: [libc.so] (0xeb548000)
------------------------------------------------
[libc.so] Exports list:
...
Export: open (0xeb574ce0)
// int open(const char *pathname, int flags);
● 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:
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:
String.prototype.hexEncode = function(){}
String.prototype.hexDecode = function(){}
function arrayBuffer2string(buffer) {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
https://developer.mozilla.org/es/docs/Web/JavaScript/Referen
cia/Objetos_globales/ArrayBuffer
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']));
}
hook_send();
hook_recv();
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
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.
https://github.com/sensepost/objection
_ _ _ _
___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_|
|___|(object)inject(ion)
Options:
-N, --network Connect using a network connection instead of USB.
[default: False]
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 ~$
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.
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.
_ _ _ _
___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_| _| _| | . | |
|___|___| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.9.6
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
Directory 2021-02-07 09:03:43 GMT True True False 4.0 KiB ShortcutInfoCompatSaver_share_targets
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 :
File download and upload too, but we will see it in action later. Let’s enumerate all
application’s activities:
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:
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:
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:
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:
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).
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/
-c CODESHARE_URI, --codeshare=CODESHARE_URI
load CODESHARE_URI
And with this option you can call any codeshare’s script
when doing frida:
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:
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 !
/* ************************************************************
* 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 = '*';
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();
[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.
[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”.
[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.
[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”.
[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.
[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.
[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.
[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.
[56]
https://orangecyberdefense.com/uk/blog/uncategorized/r
everse-engineering-of-the-anubis-malware/, Reversing
the Anubis Malware.
[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.
[65] https://medium.com/@two06/fun-with-frida-
5d0f55dd331a, Fun with Frida (and keepass).
[68] https://es.slideshare.net/rootedcon/david-reguera-
new-w32-hooking-skills-rootedcon-2010, new w32
hooking skills - RootedCON 2010, David Reguera.
[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.
[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).
[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