SlideShare a Scribd company logo
‫בס"ד‬
1
Android Patching &
Client-Side Security
Ariel Tubul
‫בס"ד‬
2
Table Of Contents
• Title page - page 1
• Table of contents - page 2
• Introduction - page 3
• Technical information about Android - pages 4-12
o Android as linux-based OS - page 4
o Android process spawning - pages 5-6
o Android zygote service - pages 7-8
o Users-based security model - page 9
o The APK format - pages 10-11
o Security motivation - page 12
• The research model - pages 13-14
• Client-side manipulations - pages 15-31
o Bypass SafetyNet/PlayIntegrity - page 15
o Modify SharedPreferences - page 16
o Memory scanners - pages 17-18
o Server-client model (gadgets) - pages 19-20
o Utilizing the server-client model - pages 21-23
o Language-level hooking - pages 24-26
o App Signatures & gms api - pages 27-28
o Patches with Kernel support - pages 29-30
o Abilities & limitations summary - page 31
• Client-side security and mitigations - pages 32-42
o Topic introduction - pages 32-33
o The Cat & Mouse Game - pages 34-38
o Game Changers + Mujde - pages 39-40
• Summery - page 41
• Disclaimer - page 42
• Bibliography - page 43
‫בס"ד‬
3
Introduction
This article is the result of theoretical research about the attack
surface of client-side logic in Android applications.
We'll get familiar with the OS and the runtime environment, how it
starts and manages processes, how applications are launched, how
are they developed, what common methodologies are used to
develop them, what restrictions and capabilities they have, and of
course – where and how can we manipulate them.
Manipulations on Android applications can help us debug them for
development purposes, intercept their behavior for research
purposes, and exploit apps to access client-side flows and logic which
are blocked from a legitimate user, allowing us to make the app
behave as we want and not as intended by the developer.
In addition to deeply understanding the working method of tools
used to dynamic and static patch and analyze Android applications,
we'll study common mitigations and workarounds to bypass them.
We are about to show how wrong is to rely on client-side security in
android systems, and how common is it in use by many apps, even
the secured ones. Client-side security in android is also trusted by
governments, despite the disadvantages which are well-known in the
cyber-security industry. To prove our claims, and show our phone is
missing security features to protect apps developers, we'll attach list
of tools which exploit the unsecured software installed in mobiles.
To finish with a good taste, will offer a few methods developers can
use to achieve security, less relying on client-side data which can be
manipulated by malicious users, developing apps that are much
harder to crack.
NOTE that this article is academic and theoretical, used to acquire
knowledge and raise awareness to cybersecurity.
You must use knowledge and tools wisely and legally.
‫בס"ד‬
4
The Android Operating System
In this section, we'll talk about the Android OS and Android mobile
devices, to acquire critical knowledge for the next research stages.
We'll talk about the OS in runtime and the boot process, in security
perspective, investigating the secured OS and the mistakes being
done by developers who trust it too much.
When describing android OS runtime environment from developer's
point of view, we must acknowledge that it is a linux-based OS.
Thus, processes are managed and synchronized in kernel, using the
task_struct structure, and supporting many linux features and
concepts (/proc filesystem, file descriptors, eventfd, fork, bpf, …).
For latter talk, we are interested in processes (especially user-
spawned processes we can control, like applications), what code can
we execute (and, of course, how can we execute code), what
permissions do we have (who manages them and how strict are
they), what internally occurs when we launch a new application and
other questions related to cyber security aspects about the client
side of android devices.
First of all, we'll get familiar with linux process spawning.
The posix_spawn requires that *nix OS will be able to spawn a new
process, (given file path, argv and envp; returning the pid of the new
subprocess). Linux's libc implemented the usermode fork function,
which is a wrapper for the fork-syscall (implementation defined, the
clone-syscall, as explained here).
The android-libc (named "Bionic") provide implementation
of the fork function that uses the clone-syscall.
This functionality creates a new process, with a new pid,
that inherits all of his parent's memory, permissions, file-
descriptors (which are handles to OS resources) and more.
‫בס"ד‬
5
After the call, the child and parent processes continue executing the
same code, but they won't affect each other's memory, as forked
process memory is copied on write. After forking a process and
creating a new subprocess, we can continue executing the same code
as the parent process, or starting a new application of our own.
To do so, we can use the exec* functions from Bionic (which uses the
execve-syscall), that replaces the binary executed by the current
process – with a new binary from provided path. The exec* functions
can reset the environment variables and command-line arguments of
the process, and close file-descriptors set FD_CLOEXEC. The whole
logic of creating subprocess in android is wrapped by easy-to-use
Java-Runtime exec method that returns Process object.
Now we know how to spawn a new process in android that execute
native binary, using the usermode wrappers for execve-syscall,
providing path of executable file with execute permissions, but how
can we spawn a new application process? For example, how can we
launch new process of the moovit application?
To answer this question, we must give it a context (do we execute
native code? Do we use adb-shell? Do we execute Java code? …).
No matter what runtime context do we have on the system,
spawning application process is internally much more complicated
than spawning native process. Before talking about implementation
details, we'll show examples starting the moovit application from adb
shell (moovit is Java app, with package named `com.tranzmate`).
Following example (from stackoverflow) uses the `monkey` script:
‫בס"ד‬
6
As we can see in the content of monkey script, it uses "app_process"
to start processes java packages. Another method is using the "am"
util, which starts the application given an activity class:
Following is the content of "am" script:
As we can see, both uses the "app_process" binary to execute code
from java packages (jar file, a zip collection of java class files):
The app_process binary is responsible for creating a new android
process that executes code of java application. In the "am" script we
can see that it can get, as command line parameter, the directory of
the loader binaries (e.g. /system/bin), the name of the java class, the
name of the method to invoke. It initializes JVM instance, loads
cache, requested packages and invokes the class's function.
The more common method of spawning application processes, is
using android-ui. Simple non-technological android users open their
device, click on an application icon, and it gets spawned.
The common between all of the methods is that they use Zygote to
spawn the application process they want to. But... what is zygote?
‫בס"ד‬
7
As we can see, zygote64 (in this example, pid=5456) is a child of the
init process (pid=1), and it has "/dev/null" as stdin, stdout, stderr:
Zygote64 has subprocesses (like WebViewZygote from android
webkit), but these are out of the scope for current discussion.
As seen in the pictures above, the "init" process starts "Zygote64"
service, which is the binary "app_process". The init process reads
configurations from "init.rc" files over the filesystem and spawn
processes/services according to it. This is the configuration that starts
the Zygote64 service, as seen in my smartphone:
From this configuration we can find that zygote service is nothing
more than java-executing process spawned by app_process. It runs
code from the java class Zygote, that has the command-and-control
local-socket "/dev/socket/zygote" which receives commands (client
for this socket can be found here) to start new applications.
The zygote service loads a lot of default java/native libraries,
common between many applications. By being forked, zygote allows
fast spawning of new applications. Instead of letting app_process
initialize the JVM, then fetch and load java/native libraries for each
new spawned application-process, zygote will simply fork itself, reset
permissions, and execute the code of the started application. Simply
send command to Zygote and it will rapidly spawn your app.
‫בס"ד‬
8
The Zygote class expose the function "forkAndSpecialize" which
handles the fork, settings for the subprocess app (uid, gids, nice-
name, …) and calls ZygoteHooks for dalvik integrations.
The processCommand method of ZygoteConnection class is an
interesting method, as it parses the command from zygote's
LocalSocket, closes not required file descriptors, and call
forkAndSpecialize to create the application subprocess.
The following flow chart (from "What the Zygote") briefly shows the
components we talked about (at the left: from the init process to the
native daemons and app_process that runs Zygote server) and the
flow of launching an application (red arrows at the right: from
clicking app's icon at the UI, to the "am" that sends command to
ZygoteConnection, which forks zygote using forkAndSpecialize).
‫בס"ד‬
9
We learned how a new application process is spawned in android,
but what can it do? What permissions does it have, what resources
can it access? What code can it execute?
To answer this question, let's take a look on the process we launched:
As shown, moovit (pid=20047) is the child of zygote64 (pid=5456):
The zygote64 service is, of course, the child of "init".
Sharp eyers would notice that after zygote fork, it changed the
application process owner to the user "u0_a504", and set a nice-
name to the process (cmdline is "com.transmate", although executed
binary is app_process):
Between executions, the owner user of the moovit process stayed
the same:
This behavior is resulted by the android security system, which sets
unique user for each installed application. During the installation of
an apk (android application installation file), a new user is created,
along with /data/data/<package name> folder, owned by that user:
This folder is accessible (r/w) by the app's user only, and persistent
app state (java "preferences", databases, state files) is stored here.
Whenever the application process is launched, zygote sets the
process identifiers to those of that user, thus preventing applications
from insecure modifications of the each other's state.
‫בס"ד‬
10
This fact makes the android system that secure, making application
developers feel very comfortable with storing their state plain in the
device. The android permissions system, relying on linux users'
permissions system, is as secured as the OS itself. Each application
user is very limited and can only access to its own resources, or a set
of global resources permitted by the OS or the user installed the app.
To get familiar with this set of global permissions requested by an
application, and additional information relevant to further research,
we'll first get to known the format of the application-installation file
(android package – apk file). Using the linux `file` command:
We can tell that apk file is basically a zip file, we can unpack using zip
unpacking utils like linux `unzip` command or `winrar` software:
A partial files-list of the unpacked moovit apk is attached above.
We'll explain a few formats and concepts relevant for our research:
1. assets – this folder usually contain application-artifacts related
to user interface, like icons, images displayed in the app,
html/css files, text font files, text files that are translated to
many languages (for the app to display text in the user's
language) and other graphical assets for human user.
It is usually very easy to patch assets from this app.
‫בס"ד‬
11
2. META-INF – this folder contains hashes of the apk files to make
sure they were not patched (MANIFEST.MF), and public
signatures of the manufacturer to validate installation from
reliable and original source. In case the apk is a "split apk" (apk
that rely on other apk files during the installation), this folder
will contain special signature of the company, to make sure
each sub-apk is signed by the same issuer signed the main one.
3. AndroidManifest.xml – this file is a binary-encoded xml file, can
be parsed by apktool/ axmldec tools. It contains information
about the package (like its name and version), features and
permissions required by the application, minimum/maximum
supported android version, required java packages, supported
intents, class of the main activity and so on. We hiddenly
encountered settings from this file above, as the "monkey"
script fetched the class name of the main activity from this
manifest file, instead getting as a command line argument like
the "am" script. The manifest file also contains an "application"
tag that sets the installer behavior with files from the apk.
Important settings of this tag contain the "extractNativeLibs"
setting that set whether or not native library files will be
extracted from the apk and available to load from the app.
4. classes*.dex – these files are bytecode packs of dalivk opcodes,
which represent the compiled java/kotlin code of the
application. Each dex file is a collection of compiled java classes
(similar to the jar format), and can be unpacked using java
decompilation/unpacking utilities, like d2j and jdec. The
bytecode format of these classes in android is called Dalvik
format, and is based on "smali" files, where each file represent
a compiled java class. The apktool script can unpack all the dex
classes into files tree, where each folder is named after the
package namespace, and each smali file is named after the class
it represents. As java is easily reversible, we can use reversing
tools like jadx for apk files, and feel like we read source code.
‫בס"ד‬
12
Now that we know how applications are installed, launched, and
store their state files, we are ready to talk about a few possible
vulnerabilities and how were they blocked (or, could be blocked).
First of all, let's talk about upgrade of installed application.
It is very common in the lifetime of android user to upgrade
applications he installed earlier, but he does not want the
information gathered about him to be deleted.
For example, player of the Subway Surfers game won't like to update
the application if it forgets his coins/highscore states, and a moovit
user won't remember it's credentials whenever he updates moovit.
To avoid this uncomfortable case, android allows installation of apk
files with the same package-name as already installed applications,
without removing their /data/data/<package> folder (state files
folder). Android moves the ownership on the app-user to the new
installed apk, and removes the old one. That way, the new installed
apk can access the state files of the old one, keeping the state
(preferences, databases, …) the same as it was before the upgrade.
An attacker (or an ethical pentester) would think about the following
attack: In case we'd like to patch the state of an application in our
phone (e.g. increase our Subway Surfers highscore), we would
reverse engineer the Subway Surfers apk (using jadx, for example),
and find how it stores it state about our in-app progress. Then, we
could implement our own apk, with the same package-name as the
original Subway Surfers apk, that will change this state file once
loaded. Now we can install Subway Surf and play it, let it store his
state files, then "upgrade" to our own application (that simply
patches the state file) and re-upgrade it back to the real Subway Surf
application. Practically, this attack would not work (and seems that
android was never vulnerable to this attack) as android applications
are signed by an issuer, and android won't allow installation of apk
that has the same package-name of previously installed app, but
another signature issuer. However, this attack example will be
relevant for us in the future.
‫בס"ד‬
13
The research model
Now that we know how the android system works and how
developers rely on the given security, it's time to add some powerful
utils to break assumptions developers have about the system, and
allow us use wider attack surface against android applications, to
research the client-side logics and flows, letting us exploiting flaws.
We'll talk about the model we'll use for the following researches (as
known as my personal smartphone), the software/hardware features
I used to create it, and the additional abilities it provides as
(especially the security-related ones).
Before talking about the non-trivial modifications I've done to my
device, we'll explain a tool we used earlier, the adb shell.
The Android Debug Bridge mechanism used to help developers
debug their applications, and android users access their device.
The adb protocol is pretty similar to ssh: it is managed by the adbd
server daemon in the phone, and lets clients connect to it using tcp
or usb. Using adb, we can have a shell access ("adb shell") and files
transfer ("adb pull" and "adb push"). Like many other android
applications/services, adb has its own user ("shell"):
The same access is used for connected adb shells:
Additional command is "adb root", which restarts the adbd server
with root permissions, allowing adb shell and file-transfer be more
privileged, as they have the same permissions as the linux "root"
user. We can't use "adb root" command on commercial phones:
Therefore, the following figure was taken from an emulated android:
‫בס"ד‬
14
We understood why and how powerful it is to have root access in
android, so we'll use it in our research model to research it's abilities.
My mobile device is a Samsung Galaxy S10 I've rooted using Magisk.
Using Frija, I've downloaded an extracted version of my Samsung
firmware, split to partitions (BL, AP, CP, CSC).
Then, using Magisk, I've patched the AP (Application Processor) and
resulted a patched version of android filesystem, that contains the
"su" binary for switching context to any user I want (including root).
Using android developer-mode, I've OEM-unlocked my device, to let
me install custom firmware. The OEM (original equipment
manufacturers) lock makes sure you only install firmware signed by
acknowledged issuer. When you unlock your OEM, a hardware fuse
goes off irreversibly, marking your device is no more reliable.
Using Odin (Samsung firmware installer), I've burnt the new firmware
(original BL, CP and CSC + patched AP) to my device, and enjoyed
rooted phone with Magisk root manager.
After that, I've enabled "Zygisk" – a Magisk feature that allows code
injection into zygote service. That way, we can inject code to all
application processes (as they'll be forked from zygote and contain
our injected code). We can also intercept the forked process in many
stages. This method of hooking and code injection from zygote is very
covert, as the application wouldn't have debugger attach or
something trackable like that.
Using LSposed Magisk module, I could use the hooking framework of
Xposed, which wraps hooks and interception in the context of the
hooked application, letting us execute java/kotlin code secretly.
It's all fun and games until someone wants to prevent you from
patching his application. Google created the SafetyNet API (which
was replaced by the PlayIntegrity API) to help developers know
whether the device running their application is safe or not.
‫בס"ד‬
15
Bypass Client-Side Security Features
Now that we are familiar with android system, and specifically our
research model, we can talk about methods to achieve full control
over what's running on our system.
First of all, we'll have to bypass SafetyNet/PlayIntegrity, because as
mentioned above, they report us to application developers as
malicious users, preventing us executing their apps in our system.
One application that is well known hater of rooted devices, is Google
Wallet, which prevents rooted smartphone from using it. This article
shows how to bypass the security features to use such applications.
I'll list & explain what tools I used to bypass it in our research model:
1. Magisk (root manager) + Zygisk (Magisk feature)
2. PlayIntegrityFix (Magisk module, shortly named PIF)
3. LSposed (Magisk module)
4. HideMyApplist (LSposed module, shortly named HMA)
We already know Magisk + Zygisk from the previous section, we'll
mention that together they allow us run privileged usermode
services, install native kernel modules and inject code to the zygote
service. Zygisk, together with LSposed, lets us hook every application
over the system, and control its logic flow. LSposed provides simple
modules system for both developers and users, to install and develop
hooks easily. The HMA module, hooks methods that return list of
installed applications over our system (for example, the method
getInstalledApplications) and deny (or filter the response) when
specific selected application call them. That way, we can hide the fact
we have Magisk app (or other "malicious" applications and binaries)
from applications that require secure environment to run. More
important role in this area plays the SafetyNet/PlayIntegrity API.
The PIF module globally intercept calls to these APIs, make the report
returned to the applications look the same as in a legitimate device.
To check what would these APIs report about us, we can use YASNC.
‫בס"ד‬
16
Now that we have a research model that can run android application
designed for modern devices (arm64, android 12, API 31), and we
have root access to the device (using "adb shell", executing the "su"
binary to have root shell), we can patch applications' state files and
databases. To do so, we can write a native/java application we'll
execute in root-user's context, that opens the target app's databases
(at /data/data/<package name>/databases/…) using SQLite for java
or SQLiteCpp. Fortunately, there are several tools that already
implemented parsing and editing SQLite databases, json/xml files and
java shared-preferences (the android way to store key-value
mapping, persistent between runs).
The best of them I know is "CheatDroid". It shows selectable list of
installed application, lets user browse all app's state files and open
them in matching editor. We'll show educational-purposes-only
example of usage, against the "Hill climb racing" game:
In figure 1, the game state before the
modification. Figure 2 shows the
shared-preferences (hillclimbprefs.xml)
that contain app's state. Figure 3
provide sample modification
could be made to shared-
preferences dictionary used, and
figure 4 shows the result.
NOTE that these manipulations
could sometimes be illegal, and such tools must be
used carefully and legally.
We'll notice that the shared-preference file edited by CheatDroid
could have been edited in
its raw xml-format, using
disk files manipulation, as
this xml file could be edited
using text-editor under root permissions.
1
2
3
4
‫בס"ד‬
17
The manipulations on state-files are very powerful (and can even take
place when the device is off, using disk manipulations), but they only
affect data stored by the app between different launches, and won't
help us attack applications' data in runtime. In addition, because of
locks and races, we can't safely modify state-files when the app is
running, and we must shut it down.
To be even more powerful than that, and being able to dynamically
patch application's memory (like global variables and functions), I'd
suggest the use of memory-scanners and patchers, like CheatEngine.
CheatEngine is a disassembler and memory-scanner, commonly used
to scan virtual memory in the target application and find virtual
address that contains the memory we searched for. The most
common use of CheatEngine is to patch programs that shows us a
numeric value (e.g. number of coins in the game) that could change
during runtime, and be displayed to us whenever it changes.
For example, to find the memory address in the remote process that
contains the number of coins, knowing the current value of it, we'll
apply a memory scan to find all addresses that store memory in the
pattern of the expected value.
Assume we currently have 0x857620 coins, and our system is little-
endian 64bit architecture. We do not know if the program stores the
coins-count variable as uint32_t or uint64_t, but due to system's
endianness, we can assume (in both cases) that the variable is places
in virtual-address aligned to 4bytes, and matches the hex bytes
pattern "207685" + "00"*. After listing all addresses matching this
pattern, we can continue playing the game and achieve more coins.
That way, the value in the correct address will be changed (e.g. get
increased to 0x857633). We'll apply another memory scan, to see
which of the previously found addresses matches now the new value.
‫בס"ד‬
18
After a few scans, we'll stay with very few addresses (usually one or
two), which for sure point to the coins-amount variable. Patching the
memory in that address will let us set the coins amount to any value
we'd like, without changing any persistent state-file. Then, the next
time the app will write its state-file, the content will contain the
patched coins-amount variable we set. This way helps us bypass
state-file mitigations (we'll discuss later), as the app stores it state-file
naturally, without any malicious manipulation.
We understood the power of memory-scanners, but even with root
permissions, how can we get r/w access to remote process memory?
At this stage, we'll show a few methods used by memory-scanners:
1. ptrace
2. /proc/<pid>/mem
3. process_vm_readv/process_vm_writev
The ptrace syscall is used by debuggers to attach to another process
and debug it, letting the debugger pause the traced process, change
its context and a lot of other debugging features, one of them is to
read/write DWORDs to remote virtual address.
The virtual file '/proc/<pid>/mem' allows opener to read/write to
remote virtual memory, as it was a real file. Simply open it with the
"open" syscall and "r+b" permissions, seek to the remote virtual
address you want, and read/write to the memory.
The syscalls (write by libc functions) process_vm_readv &
process_vm_writev lets the user read/write arbitrary sized buffers
from/to arbitrary virtual address of remote process.
Each of these methods can be utilized to r/w arbitrary-sized buffer at
any requested virtual address of a remote process. Usually, the
address we are interested is of native/java variable, therefore it'll be
stored in the heap, the stack or the data-section of the relevant
image. We'd like to memory-scan these address-ranges.
‫בס"ד‬
19
We know how to scan memory (r/w virtual memory of remote
process), what scanning method to use (searching for changing-value
again and again, reducing addresses count each search) and in what
sections is the relevant memory stored (heap/stack/image).
All left to do is to find what virtual-address ranges are mapped to
each section. To do so, we can use the linux feature of the '/proc' fs,
the '/proc/<pid>/maps' file! This file contains the mapping of
different memory regions of remote process, to the virtual addresses
they are mapped to, including address-range, permissions and
separation of native images to their elf sections. The integration of
these tools makes memory-scanner effective, fast and powerful.
There are many android memory-scanners can be used on rooted
devices, offering easy-to-use GUI for simple scans and patches.
In addition, there are memory-scanners that utilize server-client
model for scans and patches on non-rooted devices.
Using methodologies that we'll face later in this article, they access
the target process's memory without the need of root permission.
The server-client model is a common methodology to patch
applications in non-rooted devices dynamically. The client is the same
app/service used in rooted devices (for example, the GUI of the
memory-scanner, like cheat-engine). The additional component is the
server: instead of using root-requiring features from the "client" (e.g.
read memory of remote process), we'll inject a "server" to the target
process, which will be able to access the process's resources with no
root permission (as the code in the process can read/write to its own
memory). That way, the client will send commands to the server, and
we'll have access to the target application's resources in runtime,
being able to manipulate it, almost the same as in rooted devices.
It is a great solution for non-rooted devices, but there's one problem
with it - how is the server-component gets injected into the remote
process in first place? It isn't that simple, as the device isn't rooted.
‫בס"ד‬
20
How can we dynamically inject code to remote process when we
don't have root permissions? The simple answer is we can't, well,
unless we have privilege-escalation vulnerability over the device or a
remote-code-execution vulnerability in the specific target application.
If can't inject dynamically, let's inject statically!
The application's package is stored somewhere in the filesystem, if
we can inject the code to the package files in the disk, it will be
automatically loaded to the application's context in runtime.
Using the "pm" (package manager), we can find the directory where
package code is stored, together with signatures, metadata, native
libraries and ahead-of-time optimized bytecode. Code injection into
the apk or libs here will help us inject our server-code ("gadget"):
But, as you can see, this directory and its files aren't writable for
users out of the "system" group. Without root permissions (or
utilization of privileged installer), we can't write our files here.
The easiest method to bypass it would be patching the apk before it
is installed in the system. That way, we'll install an apk which loads
our gadget whenever it is launched. The application will behave
exactly the same as the original one, and let the client (e.g. memory
scanner) full access to the process's resources.
There are many disadvantages in this method, and these will be the
main topic of this article (bypasses of client-side security features in
android), but before turning to mitigations and bypasses, let's show
how server (gadget) injection is done.
‫בס"ד‬
21
For the simplicity of the explanation, we'll show the theoretical
methods using practical example: statically injection of frida-gadget
into hill-climb-racing game apk. First of all, we'll fetch the target apk
and download frida-gadget library in a version compatible with our
ABI from latest version of frida release.
Pull installed apk (using `adb pull`) and unpack it (using `apktool`):
Apktool decodes the AndroidManifest.xml to human readable xml
file. As we want to inject a native library to the apk, we'll set the
attribute "extractNativeLibs" in the manifest to "true":
Now we'll add the file "libfrida-gadget.so" to the "lib" folder, relevant
to our ABI. In our example, it'd be "lib/arm64-v8a/libfrida-gadget.so":
As we learnt earlier, when application is launched by zygote fork,
zygote will initiate an instance of the java/kotlin class set as main-
activity. If we pack the apk as it was modified until now, we'll result in
a new apk that will extract the injected library to the installation
path, but won’t load it, as zygote will only call the java code when the
application is launched.
To resolve this issue, we can inject bytecode to the unpacked java
class (as we know, java classes are compiled to bytecode and packed
in dex files, extracted by apktool). The injected bytecode will load are
native library (gadget) and we'll be able to utilize the server-client
model for our research purposes.
‫בס"ד‬
22
To inject the call to LoadLibrary (java function from System that loads
native libraries), we'll first find what is the main-activity of the app,
using the AndroidManifest.xml activity entry:
Now we can navigate to the disassembled file that contains the smali
code of this class (apktool unpacks the dex files to smali files, where
each part of the package name is a folder, thus the path of the
MainActivity is "smalicomfingersoftgameMainActivity.smali"):
In the picture above we can find the static constructor of the
MainActivity class. In java, static constructor is the function called
before initiating the first instance of the class, or invoking any
function of it. Injecting our opcodes to this function, specifically as
the first opcodes of this function, will make sure our code will be
executed before any opcode of the modified application. We'll load
our library here, using the following smali code (dalvik opcodes):
Which makes the static constructor as the java code:
And this was the final step to inject native library into an application
process, before it's code even got execution context!
‫בס"ד‬
23
As we already know how to inject native library (modifications
server) to an apk, making it behave the same as the original one, we
only need to find a way for the client to connect the server. The most
common method to enable this communication is using TCP sockets.
Therefore, we are only required to add the internet permission to the
AndroidManifest.xml, and we are good to do:
In our case, the internet permission already existed in the manifest,
so we didn't have to change anything. We have an unpacked apk,
with an embedded modification server library. Let's re-pack it!
Using apktool, we'll recreate the dex file from the smali classes (done
by the smali/baksmali assembler), and re-encode the manifest xml,
packing them together with the resources in a zip file.
Using zipalign from Google's binary tools for android developers,
we'll align and validate the zip format to match the requirements
from an apk file. As described in the tool's documentation, this is a
tool "that helps ensure that all uncompressed files in the archive are
aligned relative to the start of the file":
For being installed in android devices, the only step left is to sign the
application. Signing in the new signing-scheme is done by apksigner:
‫בס"ד‬
24
And we are done. Now, we have an apk that can be installed in non-
rooted android devices, and modify the process in runtime utilizing
the server-client model. An important thing to note is that we do not
have the same keystore used by the original manufacturer, as it is a
private-key used before the distribution, and the resulted patched
apk will have a different signature and a different issuer. This point is
critical and will be discussed later, as it touches many aspects of
client-side security and mitigations bypassing in android.
We'll note that the whole process described in the previous page, of
repackaging patched apk file that was unpacked by apktool, is a
technical and repetitive process, can be automated easily. Therefore,
I've created the buildapp tool (installed as pypi package):
Now that we have memory scanner (and patcher) in our arsenal, for
applications in both rooted and non-rooted devices, we are ready to
learn about a tool more powerful than only a memory-scanner – the
dynamic instrumentation toolkit, frida!
Using frida, we can dynamically hook and execute code in the context
of other processes, without debugging them! Frida is a portable tool
which support Windows, Linux, MacOS and Android. It provides an
API to control hooks in JavaScript/TypeScript, C, Swift, .NET and a few
other programming languages. In this article, we'll focus hooking
java/native code in android processes using javascript/typescript.
Frida implements the server-client model, supporting both rooted
and non-rooted devices. For non-rooted devices, it provides the
frida-gadget binary introduced before. Frida-gadget is a native
shared-library that serves as CnC server for frida client. The client of
this model can send javascript code to execute in a tiny js-interpreter,
embedded in the frida-gadget binary. This js code can use frida js-API,
which provides useful classes and functions for code interception.
‫בס"ד‬
25
Frida API supports interception of native code (using Interceptor),
letting us execute code before / after target function is called,
changing its parameters / return value, or calling other function
instead of the originally invoked one. It also provides useful functions
for debugging and dynamic reversing the application (like backtrace
from Thread utils), memory interception (using the Memory module)
and very powerful language-specific tools, for hooking non-native
language-level code and memory. The language-level features of
frida are created using frida-bridges, which officially implemented for
Java, Swift and ObjC. For example, frida can find all instances of a
java-class, hook methods and functions and even execute arbitrary
java code in the context of the application.
There are also non-official open-source implementations of bridges
for other languages, like the Il2Cpp framework, used by applications
developed in unity. These bridges make frida an extremely powerful
tool for patching applications and bypassing security mitigations used
by the manufacturer.
To utilize the server-client model and inject frida-gadget into an apk
for installation in non-rooted devices, we'll have to follow the
injection steps as explained in the previous section. In addition to the
server feature of frida-gadget (which executes js script it gets from a
tcp-socket), it can run autonomously, and inject the script it was
given once the gadget is loaded. That way, we'll be able to patch an
apk and create a new one, that behaves as we'd like. To automate the
injection an repackage process of frida-gadget and script into an apk,
I've created the apkmod tool (+ github action):
Frida also provides the frida-compile utility, to compile and optimize
js/ts scripts to be loaded by the script-interpreter that frida uses.
Additional binary set is Hluda repo, which is a compilation of frida
binaries for android, undetected by antiviruses / mitigations, as
binaries are slightly changed to bypass binary-signatures scanning.
‫בס"ד‬
26
After wrapping the discussion about frida-gadget for non-rooted
devices, we'll introduce the frida-server binary provided for rooted
android devices, which enables hooking any process over the system.
Once commanded to, it attach to a process (or spawn a new one) and
injects a native library (named frida-agent), which provides command
and control API for the frida client, the same as frida-gadget.
The frida server can be automatically started as a daemon during
device boot process, using the Magisk-frida module. The rooted
version of frida has many advantages over the non-rooted method.
The following chart compares the methods in a few aspects:
frida-server frida-gadget
storage usage Binary is stored in one file
over the fs
Each patched apk
contain the binary
package
signature &
integrity
Package signature is the
same, as we dynamically
inject library. No patches
are made to the apk, passes
strict integrity checks
Signature and issuer
are changed as we
repack the apk. Fails
basic integrity tests,
as the apk contains
hooking library
autonomously The server does nothing
until it commanded to
The gadget can
automatically
execute frida script
usability The server supports rooted
devices only
Supports both
rooted and non-
rooted devices
complexity Easy to use, can be started
automatically as service
Requires repacking
of each apk we'd like
to research/patch
Despite the differences between the methods, it is important to note
that both provide the exact same hooking API and abilities.
In addition to scripting API, frida has a code-share community where
developers share scripts used to bypass security features, like the
popular ssl-pinning bypass and android root-detection bypass.
‫בס"ד‬
27
The power of frida tools, and the differences between frida-gadget
and frida-server, leads us to the next topic of this section. To illustrate
the topic and the power of frida in language-level hooks, I'll show
script I used in one of my projects - ColorTweak. The ColorTweak
project is an educational-purposes-only patch (using frida-gadget) of
the ColorSwitch game developed in unity, compiled as Il2Cpp project.
This project contains the following frida script, injected to the original
apk using my patching tool, apkmod:
In the provides script, frida finds the Il2cpp runtime & symbols, and
the C# image it executes. Then it patches the "isInvincible" method of
the "ColorBall" class to return the value "true", letting the player of
pass any stage with no effort. Using frida-compile and apkmod, the
repo creates patched apk you can use in rooted/non-rooted devices.
For more information, documentation about the research and build
process, browse ColorTweak repo.
After we saw usage example of frida and understood its main role in
android-security world, we shall discuss the security-related
difference between frida-gadget and frida-server. the frida-gadget
method's main disadvantage is that it changes the content and
signature of the apk. It's important to note that applications might
contain anti-tamper features, usually when the app contains code
related to billing and purchases, which detect this patch.
Even if the developer is unaware to cyber-security, the packages he
uses might do. For example, the masabi API (bus-payment package)
used by moovit, validates its signature during runtime.
‫בס"ד‬
28
When such secured packages detect they were patched, they choose
whether to terminate the application process or block their specific
service, making the app (or the components uses secured-packages)
tamper-proof. Another secured package that validates its runtime
environment and app's signature is Google-Mobile-Services (gms).
During runtime, the java masabi package uses android's
PackageManager to fetch the signature of the apk. Then, it compares
the signature with hardcoded base64 string. This whole logic occurs
in the java code, executed on device. This method detects the
patches made by apkmod when it injected the frida script, but it can
be easily bypassed using frida hook or smali patches (e.g. make the
compare function return true, change the compared string, …).
The gms API also checks the signature of an apk that uses it, but it
compares it in much safer method: When an application is uploaded
to Google Play app-store, its signature is listed in Google's databases.
Then, when the application calls gms-api services and it connects a
socket with Google's servers, the gms api sends the fetched signature
to the remote server, where it validates it against the database.
When given unexpected signature, the server closes the socket and
the gms api is unusable. In addition to secured compare of the app
signature, Google's API fetches and compares the it in multiple
methods, thus bypassing it requires many advanced hooks, on both
native and java code. Bypassing Google's signatures check is very
hard, but we can't give up patching applications that uses Google
packages, as it would block us from patching almost any application
and providing a generic client-side security method, making this
whole article worthless.
Instead struggling with the gms api provided in Google's java package
"com.google.android.gms" (that strictly checks for app's signature),
we can patch the app to reference "org.microg.gms" (GmsCore by
MicroG) instead. MicroG provides gms api wrapper that doesn't
require correct signature to work.
‫בס"ד‬
29
After we experienced the benefits of methods used in rooted devices
over the non-rooted ones (both in usermode), we'll introduce
manipulation methods that utilize kernel features and abilities.
Utilizing kernel permissions can help us covertly inject code and hook
activities of applications and processes, without any usermode code
being able to detect suspicious behavior, code or memory. Running
code in kernel context can help us bypass many mitigations and work
under the radar of anti-tamper software or anti-virus.
The first method we'll introduce is syscall-hooks. Any usermode code
that accesses system resources is using a "syscall". Any file descriptor
open/read/write, event setting and any other operation is allowed to
occur only in kernel mode, therefore the kernel introduces a set of
APIs (syscalls) that can be invoked from usermode code (using the
syscall opcode, or "SVC" opcode in arm). As kernel memory is hidden
for read / write from usermode code, any hooking and manipulation
we'll set in the kernel will not be detectible by non-root usermode
code and mitigations, including the addresses of the syscall-handlers
themselves. In linux, the syscall handlers (kernel-mode functions) are
registered in a table named "sys_call_table", where each row is a
mapping between the syscall-index and the handler-address.
Using kernel module we can locate the table (with the kernel mode
api "kallsyms_lookup_name") and set table entries as we want. That
way, when anyone over the system uses the patched syscall, our
handler will be called. That way, we can hook functionality system-
wide and undetectably. This method is very common in linux
systems. For code example, you can refer this rootkit repo.
Another powerful tool for kernel mode interceptions is the kprobe
feature. This feature is commonly enabled in linux distributions,
including android. Using simple API, it lets us install hooks which will
be called before/after the function, and on fault/break. Unlike syscall-
hooks, it can hook any kernel symbol, not only syscalls. Example here.
‫בס"ד‬
30
As can be easily understood from the examples above, in both kernel
and usermode contexts, we can utilize a set of simple operations to
build extremely powerful tool for hooks and modifications. This set of
features (r/w memory, context [registers] setting, …) is provided by
debuggers. As a usermode-debugger can help us attack usermode
applications, a kernel-debugger can intercept both usermode and
kernel code / memory. This powerful tool also follows the server-
client model, letting usermode applications communicate with the
debugger, sending a set of commands and receiving responses.
Android kernel debuggers are not a commonly used tool, but with
the correct wrapper it can provide powerful interface, like frida, and
impeccable access, as it is very highly privileged. We won't deeply
discuss kernel debugging in this article, as user-friendly wrappers
rarely exist. Usage example is provided here.
Google themselves provides kernel debugging and tracing utils for
developers. They list of tracing tools and information about them is
provided in the trace section of android kernel documentation.
These tools include ftrace and atrace, letting us trace events and flow
of kernel methods and system applications, with filtering support for
both kotlin/java and native code.
Additional and main highly privileged modifications feature in the
linux-kernel area, is ebpf. Google provided ebpf support in android
kernel. The ebpf feature let's us install ebpf modules to the ebpf
interpreter in the kernel. After validation and installation of our ebpf
module, it will be able to access system resources from kernel
context, providing almost the same power as kernel-debugger. As it is
widely spread over different operating systems, ebpf is friendly
wrapped by many developers/researchers in ebpf rootkits.
‫בס"ד‬
31
Summary of research tools for security challenges
Before moving on to the next section, let's sum up the challenges and
solutions we encountered in android client-side security universe.
We first learned how application processes are started in the strictly
secured android operating system, and how effective in performance
is the zygote server, which forks itself and initiates an activity from
provided java package. That way, the java interpreter and
environment are initialized only once, to save time when spawning
new apps. To complete the set of loadable code objects, we showed
how can java code load native shared-library binaries.
After understanding the system, we built our research model and
faced many internal low-level features of android devices. Using the
privileges provided by the research model we investigated android
development methods and how they work internally in the system.
For example, after understanding how do developers store persistent
state, we found tools to patch the shared-preferences and databases
used by applications (when we have sufficient permissions).
We learnt about the apk format and how to modify apk files,
including utilities to inject native libraries into an apk, inter alia to
utilize the server-client model for dynamic patches. We studied the
power and limitations of this method, as it can only access usermode
applications' resources and is easily trackable (as we change the
signature of the installed apk).
As instances of the server-client model, we witnessed the impeccable
abilities of memory scanners and dynamic debuggers, like frida, and
demonstrated the use of apkmod – an injection tool that embeds the
frida server and script to create patched apk.
Finally, we discussed the disadvantages and missing covertness of
these methods, and showed how patches in kernel-mode help us
bypass security features and detectors.
‫בס"ד‬
32
Main Topic Introduction
Finally, after gathering knowledge about the environment we run in
and building a strong and generic toolset to manipulate and intercept
processes, we are ready to talk about the wild jungle called client-
side security in android applications.
As described previously, android developers have many assumptions
about the device they execute their code in, which makes them feel
comfortable about leaving their applications unsecure. Once we
showed our methodologies to hook and patch applications, we found
that these says does not hold water.
But what is actually the risk we are talking about?
We'll show a few examples to explain our motivation:
• Mobile games usually offer in-app purchases, to have more
gems/coins in the game. These apps (mostly games that let you
play offline) often store such values in shared-preferences state-
files (which are easily patchable), making the app insecure.
By hacking applications of this kind, an attacker can gain his
target coin value and avoid paying the developer for that.
No one notices when a single person do this immoral operation,
but when it is wide spread, companies lose money.
• Government applications use clock animation near online
certificates showed in applications, to prevent photoshop-fake
attacks (like the Israeli covid-tracing application). But, using
modification tools in android, one can modify the data showed
in the application, keeping app's same animation / behavior.
• Bank applications digitally sign documents produced by the
client. When this logic is executed in users' devices, hackers can
use android modifications to steal internal keys and mock the
signer, allowing themselves to sign fake documents.
• …
‫בס"ד‬
33
The list of risks and vulnerabilities is infinite, and there are a lot of
mitigations can be used against malicious clients. The main goal of
this article, which will be discussed in the current section, is to find
(and attempt to bypass) generic and strict mitigations can be used by
application-developers and OS-developers to make the system a safer
place, for both the developers and the users.
We'll map differences between how PC and android developers
relate to security issues, and explain why (and when) should android
developers take the less permissive approach.
Due to the tools and methodologies described in this article, fighting
attackers seems to be worthless, as they are too powerful as clients
and can do whatever they want with the code executing on their
devices. Well, despite their abilities, turns out there is a variety of
mitigations used against these hackers, effectively hinder client-side
attacks attempted on android applications.
But the harder we'll fight the attackers and harden our mitigations –
the harder they'll fight us back, by creating new bypass methods.
Actually, android client-side security isn't a losing game, but a more
of an endless cat & mouse game.
There are no winners in these wars, as the attackers will always
achieve their goals, it's just a question of how much time and
resources will the developer make them waist. Sometimes, it's an
attrition warfare until one of the sides surrenders: the developers
apply more mitigations (which waists the company's resources) and
the attackers create and exploit new methodologies (which waists
time from the attackers).
In the next few pages, we'll discuss vulnerable areas which are
commonly exploited in applications, mitigations used to hinder the
attackers and theoretical bypasses, which sometimes might take a lot
of time to develop.
‫בס"ד‬
34
The Cat & Mouse Game
Vulnerable Area
Applications store data over the device, to "remember" state (so
users won't have to enter their passwords each launch and games
remember our highscore and coins amount). Patching these state-
files can help attackers manipulate the target app.
Exploitation
As shown earlier, we can edit these files using root access to the
device. Commonly used state-files format (shared-preferences, xml,
json, sqlite3 databases, …) are easily patchable, as their format is
simple to view and modify (e.g. using CheatDroid).
Mitigations
1. One approach is that the developers should store checksum
over the state-file fields, using another filed in the state-file,
and validate it before accessing any data from the state-file.
2. Another approach, which is extremely popular between
Windows developers, says developers should create their own
format for state-files (usually binary format), making their state
harder to modify (and their app faster to parse the state-file).
Bypasses
1. The attacker can reverse-engineer the application to find out
how is the checksum calculated and modify the checksum filed
to be valid. Attackers may also patch the apk to skip checksum
validation or extract the checksum calculation component from
the apk to create valid checksums easily.
2. Attackers will have to extract the "store-logic" to create their
own data-packer. This bypass will require a lot of work to
exploit, making the mitigation a good one. An easier bypass will
be implied from the following vulnerable area.
‫בס"ד‬
35
Vulnerable Area
A much less "conventional" yet exploitable area is the RAM.
Applications store their dynamic runtime memory (even the memory
loaded from the state-files) in the RAM, then store the persistent
data-pieces in state-files over the disk. The point is, any memory was
loaded once to the RAM, where attackers can modify it.
Exploitation
As explained earlier, memory of all processes is accessible via the
kernel (by parsing page tables and accessing physical addresses) / by
privileged usermode code (the root user can access virtual memory
of any process using syscalls or virtual files that represent processes'
virtual memory). Using memory-scanner (e.g. CheatEngine) we can
find addresses that contain certain data, and manipulate it in RAM.
Mitigations
1. As we can't prevent attackers from accessing our runtime
memory (because the kernel can covertly r/w any process's
virtual address space, even if we set memory as read-only when
it shouldn't be changed), the best mitigation would be not to
use trivial data-types. For example, use "string" instead of "int"
when dealing with numbers, confusing the attacker who
searches for integer value in your process memory.
2. Another interesting approach is not to hold plain values in
memory when these are unused. Clear the memory when you
are done using it (e.g. memset to zero in the destructor) and
frequently garbage-collect variables. A more effective method
in this world is to scramble data when it gets written, and
descramble when its read.
‫בס"ד‬
36
Bypasses
1. Reverse-engineering the application, attacker can find what
data-types are used by the developer. Developers can (and
should) mangle and scramble package names when static-
linked, and strip their binaries to remove debug symbols and
strings that helps attackers to understand how is the program
written. Without interception points and symbols, it would take
attackers a lot of time to analyze and reverse the program.
Another bypass method for this mitigation would be to use
fuzzing-search for the certain memory they'd like to
manipulate. Some memory-scanners (e.g. GameGuardian) have
a fuzzing-search feature, exactly for these cases, where you do
not know what data-type is the memory you are searching for.
2. This mitigation doubles the challenge: attackers will now
struggle to both find the memory and manipulate it.
To find the memory, they'll have to search for the scrambled
content and hope it isn't currently accessed (as accessing the
content make it descrambled again). To patch the memory,
attackers will have to calculate the scrambled-view of the target
value they want to put. Both challenges might require the
attacker to find and extract the scramble-logic used by the
application. For the challenge of finding the scramble-function
used by the app, we'll introduce a new tool: time-travel-
debugging. Using TTD we can "record" the memory of the
application, and search for the plain value in the recorded
memory (when it gets descrambled). Watching the opcodes
that accessed the found address for write purposes, we can find
who descrambled it. Then, using hardware-breakpoint on
writes, we can pause the application when it attempts to write
a scrambled content, which will help us to find the scramble-
logic using backtrace.
‫בס"ד‬
37
Vulnerable Area
Many applications offer clients to pay for features within the app. It is
called "in-app purchase" (IAP), and we can mainly see it in games
where users can buy coins / skins and apps that has feature
specifically for paid premium users.
Exploitation
Using MITM framework, attackers can globally intercept the inter-
process-communication between Google-services and other
applications, and make the apps think the purchase completed
successfully without actually paying money to the company.
Utilizing this MITM, an android software named "freedom" was used
to make free in-app purchases on rooted devices (until it stopped
working). Since then, the cyber community released features in
LuckyPatcher to create patched version of APK files, where in-app-
purchase requests to Google-services are "inlined" in the patched
apk, with code that returns general "purchase-succeeded" responses.
Mitigations
1. Not only a mitigation to this case but a generally recommended
approach, is to perform and validate important data and
calculations in server-side. When Google-services sends
"purchase-succeeded" response, the application can (and
should) validate it with a server owned by the app's developer,
and make sure to provide the purchased item only for
legitimate clients.
This "server-approach" can serve as mitigation for many other
attacks. For example, storing user-state in server-side instead
using shared-preferences over the device, prevents state-file
modifications. Additionally, analyzing the user-state in server-
side and validate change-rate of values, mitigates RAM attacks.
2. Apps can check for threating environment and avoid running in
rooted devices or where threating applications are installed.
‫בס"ד‬
38
Bypasses
1. The server-side validations approach is the BEST method to
fight cyber-security attacks, as there's only a single-source-of-
truth which is ruled by the developer. It leaves no room for
client-side security (which we learned it can't be trusted at all),
and reduces the attack surface of the developed environment
(games, banking apps, communication and social media, …).
Other than hacking the server / finding API vulnerabilities, the
only bypass I'd suggest is to create an offline version of the app,
replacing references and calls to the original server with our
own responses or mock-server, to provide the desired behavior.
Not only does it take a lot of time and effort from the attacker
to develop such a server and reverse-engineer the app, but it is
resulted in a modified apk which isn't connected to the original
glob-wide environment, and it's not that fun to have your own
isolated app (e.g. your patched game can't let you play with real
users over the world, and patched bank-app won’t affect your
real bank account).
Therefore, this mitigation and development approach is the
best to use with critical data (like banking and investment
apps). Combined with strict API and a secured mitigated server,
developers who choose this approach are impenetrable, and
their apps are very strongly secured.
2. Escaping from threating environment and hacking tools is
theoretically secured approach that looks good on paper, but
when it comes to real life, it doesn't hold water. This approach
is used by banking and government apps, and they attempt to
fetch information about the devices and the installed
applications list. When we have root access to our device, we
can easily spoof any response they'd get from the android OS.
‫בס"ד‬
39
Game Changers + Mujde
After witnessing the endlessness of the client-side security Cat &
Mouse game between developers and attackers, we are ready to
introduce game-changer attack & protect methodologies.
The theoretical protection gamechanger we'll discuss is an aggressive
version of Google's signature checks we saw in gms API, and it can
prevent any static apk modification (e.g. injection of smali code or
utilization of the server-client model).
Just like the gms package, we'll validate the signature of the apk
during runtime. But, unlike Google, we'll fetch the signatures from
the RAM (where the actually running code is stored) and not from
the disk (as there are linux-mount tricks to make android load to
RAM a different apk than the one on disk, letting the attacker bypass
Google's verification: the apk over the disk is unmodified, but due to
the mount, linux may load and execute a modified apk).
Using Java code, we'll find the virtual address of the apk in RAM, and
read its content. As native libraries in the apk are stored
uncompressed (zip flag COMP_STORED), our code will be able to
make sure there were no modifications to assets, resources,
manifests, bytecode and native code. Making sure that there's one
and only apk file loaded to the process, validating its content with a
remote server, developers can prevent static patches to their apps.
In order to make sure no one patches the signature-verifier logic (like
LSPatched attempted to do against Google), we'll fetch and execute
the signature-verifier bytecode during runtime. This code can't be
statically patched, as it is dynamically fetched and not stored on disk.
To be even more secured than that, we'll dynamically fetch other
bytecode components (even those who aren't related to security
features), thus the signature-verifier won’t be easily detectible by
patchers who intercept the JVM bytecode interpreter.
‫בס"ד‬
40
I call this security feature a real game-changer, as it completely
prevents any modification to android applications over non-rooted
devices, letting the developers win the client-side security battle.
As we are about to show, rooted-devices can bypass even that strict
security test, but the developers are still considered the winners, as
approximately only 4% of the android devices are rooted. Therefore,
there's no large-scale attack that can hurt security-aware developers.
As just promised, we'll now discuss a theoretical bypass for the
security mitigation we introduced, in rooted android devices.
This bypass method utilizes Zygote to covertly inject code to android
processes, without modifying the app in RAM / disk, yet being
persistent and powerful, providing the simple frida hooking API.
With root access, we can inject code to Zygote (just like Zygisk does)
and hook the forkAndSpecialize method to detect when a specific
app is launched. When the process forked for the target application,
we can modify the JVM in memory (affecting the target process only,
due to copy-on-write feature applied in shared-libraries).
Hooking the JVM we can inject bytecode whenever we want, and
intercept the bytecode interpreter. By intercepting the interpreter,
we can change the context (smali registers) in RAM, thus no
modification takes place over the memory where the apk is stored,
without losing the powerful features of frida hooks in Java code.
Using hardware breakpoints, we can break on specific memory
address and pause threads before they execute code from it. This
feature lets us change the context and achieve behavior similar to
frida native interceptor, with native hooks.
To make it all persistent, without modifying any apk, we can utilize
Zygisk (like LSposed does) to automatically inject our code to specific
target applications. To implement this bypass tool, I develop LSposed
module called Mujde that'll automatically inject frida environment
and matching scripts to target applications.
‫בס"ד‬
41
Summary
This article explores client-side security in android applications,
highlighting vulnerabilities inherent to android's structure and the
challenges developers face in securing data against manipulation.
It delves into android as linux-based OS and popular attack methods
in these environments, demonstrating how client-side manipulations
can undermine security assumptions.
Through the analysis during the article, we learnt that android's
client-side logic cannot be fully secured against determined attackers.
The client-side mitigations often fail against sophisticated tools
available in the hacking community. While server-side validation and
encryption offer significant protection, constant innovation on both
sides means developers must continuously update security practices.
We'll sum up few advices for android developers:
• Utilize Server-Side Verification
Critical actions, especially purchases, should be server-side
validated to prevent client-side interference. This is always the
best security-related approach can be used by developers.
• Apply Code Obfuscation & Integrity Checks
To make your app harder to reverse, obfuscate code, strip binaries
and apply integrity checks to guard against tampering.
• Implement SSL Pinning
For secure communication with your server, and prevention of
attacks against third-party packages, enforce SSL pinning and
mitigate man-in-the-middle attacks.
• Choose Your Battles Wisely
Not any android application should be strictly secured, but you've
got to analyze threats and be aware of them. Developers of offline
single player android game can settle for simple mitigations, and
there's no need to overestimate security where its unrequired.
‫בס"ד‬
42
Disclaimer
This article was written for academic and educational purposes only.
It aims to increase cybersecurity awareness and provide insight into
android security strategies and vulnerabilities. This article provides
mitigation-suggestions and advices for developers, to show how
cybersecurity knowledge supports defensive/academic community.
The discussed techniques shouldn't be used for malicious intents or
unauthorized tampering with applications, as such actions may
violate legal statutes and ethical standards.
Both developers and researchers should utilize the provided
information responsibly, adhering to legal and ethical regulations and
guidelines in their countries.
‫בס"ד‬
43
Bibliography
• Zuo, C., & Lin, Z. (2022). Playing without Paying: Detecting
Vulnerable Payment Verification in Native Binaries of Unity Mobile
Games. 31st USENIX Security Symposium
• Rashidi, B., & Fung, C. J. (2015). A Survey of Android Security
Threats and Defenses
• Zhauniarovich, Y. (2014). Android Security (and Not) Internals
• Gunasekera, S., & Thomas, M. (2012). Android apps security
• Lardinois, S., & Legay, A. (2023). Modifying Android for Security
Analysis
• Samuel-Alberto, M. (2023). Privacy Concerns in Android Systems
• Vidas, T., & Zhang, C., & Christin, N. (2011). Toward a General
Collection Methodology for Android Devices

More Related Content

Similar to Android Patching & Client-Side CyberSecurity (20)

PDF
CNIT 128 Ch 4: Android
Sam Bowne
 
PDF
Leveraging Android's Linux Heritage
Opersys inc.
 
PPTX
Android Booting Sequence
Jayanta Ghoshal
 
PDF
Cc4201519521
IJERA Editor
 
PPTX
Introduction of Android Architecture
Bin Yang
 
PDF
Headless Android
Opersys inc.
 
PDF
Leveraging Android's Linux Heritage at ELC-E 2011
Opersys inc.
 
PPTX
Android village @nullcon 2012
hakersinfo
 
PDF
Ch1 hello, android
Jehad2012
 
PDF
Android Internals
Opersys inc.
 
PPTX
Android : a linux-based mobile operating system
Clément Escoffier
 
PPTX
Android Introduction on Java Forum Stuttgart 11
Lars Vogel
 
PPTX
Android Programming made easy
Lars Vogel
 
PPT
Android OS
Nitin Ramchandani
 
PPTX
[Wroclaw #1] Android Security Workshop
OWASP
 
PDF
Explore Android Internals
National Cheng Kung University
 
PDF
Developing Android Platform Tools
Opersys inc.
 
PDF
Leveraging Android's Linux Heritage at AnDevCon IV
Opersys inc.
 
PDF
ABS 2014 - The Growth of Android in Embedded Systems
Benjamin Zores
 
PPTX
Curso de Desenvolvimento Mobile - Android - Stack
Jackson F. de A. Mafra
 
CNIT 128 Ch 4: Android
Sam Bowne
 
Leveraging Android's Linux Heritage
Opersys inc.
 
Android Booting Sequence
Jayanta Ghoshal
 
Cc4201519521
IJERA Editor
 
Introduction of Android Architecture
Bin Yang
 
Headless Android
Opersys inc.
 
Leveraging Android's Linux Heritage at ELC-E 2011
Opersys inc.
 
Android village @nullcon 2012
hakersinfo
 
Ch1 hello, android
Jehad2012
 
Android Internals
Opersys inc.
 
Android : a linux-based mobile operating system
Clément Escoffier
 
Android Introduction on Java Forum Stuttgart 11
Lars Vogel
 
Android Programming made easy
Lars Vogel
 
Android OS
Nitin Ramchandani
 
[Wroclaw #1] Android Security Workshop
OWASP
 
Explore Android Internals
National Cheng Kung University
 
Developing Android Platform Tools
Opersys inc.
 
Leveraging Android's Linux Heritage at AnDevCon IV
Opersys inc.
 
ABS 2014 - The Growth of Android in Embedded Systems
Benjamin Zores
 
Curso de Desenvolvimento Mobile - Android - Stack
Jackson F. de A. Mafra
 

Recently uploaded (20)

PDF
Dimensions of Societal Planning in Commonism
StefanMz
 
PPTX
STAFF DEVELOPMENT AND WELFARE: MANAGEMENT
PRADEEP ABOTHU
 
PDF
Isharyanti-2025-Cross Language Communication in Indonesian Language
Neny Isharyanti
 
PPTX
Unit 2 COMMERCIAL BANKING, Corporate banking.pptx
AnubalaSuresh1
 
PPSX
Health Planning in india - Unit 03 - CHN 2 - GNM 3RD YEAR.ppsx
Priyanshu Anand
 
PPTX
Soil and agriculture microbiology .pptx
Keerthana Ramesh
 
PPT
Talk on Critical Theory, Part One, Philosophy of Social Sciences
Soraj Hongladarom
 
PDF
CEREBRAL PALSY: NURSING MANAGEMENT .pdf
PRADEEP ABOTHU
 
PDF
ARAL-Orientation_Morning-Session_Day-11.pdf
JoelVilloso1
 
PPTX
MENINGITIS: NURSING MANAGEMENT, BACTERIAL MENINGITIS, VIRAL MENINGITIS.pptx
PRADEEP ABOTHU
 
PPTX
How to Set Maximum Difference Odoo 18 POS
Celine George
 
PDF
community health nursing question paper 2.pdf
Prince kumar
 
PPTX
SPINA BIFIDA: NURSING MANAGEMENT .pptx
PRADEEP ABOTHU
 
PDF
0725.WHITEPAPER-UNIQUEWAYSOFPROTOTYPINGANDUXNOW.pdf
Thomas GIRARD, MA, CDP
 
PPTX
How to Manage Large Scrollbar in Odoo 18 POS
Celine George
 
PDF
SSHS-2025-PKLP_Quarter-1-Dr.-Kerby-Alvarez.pdf
AishahSangcopan1
 
PPTX
THE TAME BIRD AND THE FREE BIRD.pptxxxxx
MarcChristianNicolas
 
PDF
LAW OF CONTRACT (5 YEAR LLB & UNITARY LLB )- MODULE - 1.& 2 - LEARN THROUGH P...
APARNA T SHAIL KUMAR
 
PDF
The dynastic history of the Chahmana.pdf
PrachiSontakke5
 
PDF
The Constitution Review Committee (CRC) has released an updated schedule for ...
nservice241
 
Dimensions of Societal Planning in Commonism
StefanMz
 
STAFF DEVELOPMENT AND WELFARE: MANAGEMENT
PRADEEP ABOTHU
 
Isharyanti-2025-Cross Language Communication in Indonesian Language
Neny Isharyanti
 
Unit 2 COMMERCIAL BANKING, Corporate banking.pptx
AnubalaSuresh1
 
Health Planning in india - Unit 03 - CHN 2 - GNM 3RD YEAR.ppsx
Priyanshu Anand
 
Soil and agriculture microbiology .pptx
Keerthana Ramesh
 
Talk on Critical Theory, Part One, Philosophy of Social Sciences
Soraj Hongladarom
 
CEREBRAL PALSY: NURSING MANAGEMENT .pdf
PRADEEP ABOTHU
 
ARAL-Orientation_Morning-Session_Day-11.pdf
JoelVilloso1
 
MENINGITIS: NURSING MANAGEMENT, BACTERIAL MENINGITIS, VIRAL MENINGITIS.pptx
PRADEEP ABOTHU
 
How to Set Maximum Difference Odoo 18 POS
Celine George
 
community health nursing question paper 2.pdf
Prince kumar
 
SPINA BIFIDA: NURSING MANAGEMENT .pptx
PRADEEP ABOTHU
 
0725.WHITEPAPER-UNIQUEWAYSOFPROTOTYPINGANDUXNOW.pdf
Thomas GIRARD, MA, CDP
 
How to Manage Large Scrollbar in Odoo 18 POS
Celine George
 
SSHS-2025-PKLP_Quarter-1-Dr.-Kerby-Alvarez.pdf
AishahSangcopan1
 
THE TAME BIRD AND THE FREE BIRD.pptxxxxx
MarcChristianNicolas
 
LAW OF CONTRACT (5 YEAR LLB & UNITARY LLB )- MODULE - 1.& 2 - LEARN THROUGH P...
APARNA T SHAIL KUMAR
 
The dynastic history of the Chahmana.pdf
PrachiSontakke5
 
The Constitution Review Committee (CRC) has released an updated schedule for ...
nservice241
 
Ad

Android Patching & Client-Side CyberSecurity

  • 2. ‫בס"ד‬ 2 Table Of Contents • Title page - page 1 • Table of contents - page 2 • Introduction - page 3 • Technical information about Android - pages 4-12 o Android as linux-based OS - page 4 o Android process spawning - pages 5-6 o Android zygote service - pages 7-8 o Users-based security model - page 9 o The APK format - pages 10-11 o Security motivation - page 12 • The research model - pages 13-14 • Client-side manipulations - pages 15-31 o Bypass SafetyNet/PlayIntegrity - page 15 o Modify SharedPreferences - page 16 o Memory scanners - pages 17-18 o Server-client model (gadgets) - pages 19-20 o Utilizing the server-client model - pages 21-23 o Language-level hooking - pages 24-26 o App Signatures & gms api - pages 27-28 o Patches with Kernel support - pages 29-30 o Abilities & limitations summary - page 31 • Client-side security and mitigations - pages 32-42 o Topic introduction - pages 32-33 o The Cat & Mouse Game - pages 34-38 o Game Changers + Mujde - pages 39-40 • Summery - page 41 • Disclaimer - page 42 • Bibliography - page 43
  • 3. ‫בס"ד‬ 3 Introduction This article is the result of theoretical research about the attack surface of client-side logic in Android applications. We'll get familiar with the OS and the runtime environment, how it starts and manages processes, how applications are launched, how are they developed, what common methodologies are used to develop them, what restrictions and capabilities they have, and of course – where and how can we manipulate them. Manipulations on Android applications can help us debug them for development purposes, intercept their behavior for research purposes, and exploit apps to access client-side flows and logic which are blocked from a legitimate user, allowing us to make the app behave as we want and not as intended by the developer. In addition to deeply understanding the working method of tools used to dynamic and static patch and analyze Android applications, we'll study common mitigations and workarounds to bypass them. We are about to show how wrong is to rely on client-side security in android systems, and how common is it in use by many apps, even the secured ones. Client-side security in android is also trusted by governments, despite the disadvantages which are well-known in the cyber-security industry. To prove our claims, and show our phone is missing security features to protect apps developers, we'll attach list of tools which exploit the unsecured software installed in mobiles. To finish with a good taste, will offer a few methods developers can use to achieve security, less relying on client-side data which can be manipulated by malicious users, developing apps that are much harder to crack. NOTE that this article is academic and theoretical, used to acquire knowledge and raise awareness to cybersecurity. You must use knowledge and tools wisely and legally.
  • 4. ‫בס"ד‬ 4 The Android Operating System In this section, we'll talk about the Android OS and Android mobile devices, to acquire critical knowledge for the next research stages. We'll talk about the OS in runtime and the boot process, in security perspective, investigating the secured OS and the mistakes being done by developers who trust it too much. When describing android OS runtime environment from developer's point of view, we must acknowledge that it is a linux-based OS. Thus, processes are managed and synchronized in kernel, using the task_struct structure, and supporting many linux features and concepts (/proc filesystem, file descriptors, eventfd, fork, bpf, …). For latter talk, we are interested in processes (especially user- spawned processes we can control, like applications), what code can we execute (and, of course, how can we execute code), what permissions do we have (who manages them and how strict are they), what internally occurs when we launch a new application and other questions related to cyber security aspects about the client side of android devices. First of all, we'll get familiar with linux process spawning. The posix_spawn requires that *nix OS will be able to spawn a new process, (given file path, argv and envp; returning the pid of the new subprocess). Linux's libc implemented the usermode fork function, which is a wrapper for the fork-syscall (implementation defined, the clone-syscall, as explained here). The android-libc (named "Bionic") provide implementation of the fork function that uses the clone-syscall. This functionality creates a new process, with a new pid, that inherits all of his parent's memory, permissions, file- descriptors (which are handles to OS resources) and more.
  • 5. ‫בס"ד‬ 5 After the call, the child and parent processes continue executing the same code, but they won't affect each other's memory, as forked process memory is copied on write. After forking a process and creating a new subprocess, we can continue executing the same code as the parent process, or starting a new application of our own. To do so, we can use the exec* functions from Bionic (which uses the execve-syscall), that replaces the binary executed by the current process – with a new binary from provided path. The exec* functions can reset the environment variables and command-line arguments of the process, and close file-descriptors set FD_CLOEXEC. The whole logic of creating subprocess in android is wrapped by easy-to-use Java-Runtime exec method that returns Process object. Now we know how to spawn a new process in android that execute native binary, using the usermode wrappers for execve-syscall, providing path of executable file with execute permissions, but how can we spawn a new application process? For example, how can we launch new process of the moovit application? To answer this question, we must give it a context (do we execute native code? Do we use adb-shell? Do we execute Java code? …). No matter what runtime context do we have on the system, spawning application process is internally much more complicated than spawning native process. Before talking about implementation details, we'll show examples starting the moovit application from adb shell (moovit is Java app, with package named `com.tranzmate`). Following example (from stackoverflow) uses the `monkey` script:
  • 6. ‫בס"ד‬ 6 As we can see in the content of monkey script, it uses "app_process" to start processes java packages. Another method is using the "am" util, which starts the application given an activity class: Following is the content of "am" script: As we can see, both uses the "app_process" binary to execute code from java packages (jar file, a zip collection of java class files): The app_process binary is responsible for creating a new android process that executes code of java application. In the "am" script we can see that it can get, as command line parameter, the directory of the loader binaries (e.g. /system/bin), the name of the java class, the name of the method to invoke. It initializes JVM instance, loads cache, requested packages and invokes the class's function. The more common method of spawning application processes, is using android-ui. Simple non-technological android users open their device, click on an application icon, and it gets spawned. The common between all of the methods is that they use Zygote to spawn the application process they want to. But... what is zygote?
  • 7. ‫בס"ד‬ 7 As we can see, zygote64 (in this example, pid=5456) is a child of the init process (pid=1), and it has "/dev/null" as stdin, stdout, stderr: Zygote64 has subprocesses (like WebViewZygote from android webkit), but these are out of the scope for current discussion. As seen in the pictures above, the "init" process starts "Zygote64" service, which is the binary "app_process". The init process reads configurations from "init.rc" files over the filesystem and spawn processes/services according to it. This is the configuration that starts the Zygote64 service, as seen in my smartphone: From this configuration we can find that zygote service is nothing more than java-executing process spawned by app_process. It runs code from the java class Zygote, that has the command-and-control local-socket "/dev/socket/zygote" which receives commands (client for this socket can be found here) to start new applications. The zygote service loads a lot of default java/native libraries, common between many applications. By being forked, zygote allows fast spawning of new applications. Instead of letting app_process initialize the JVM, then fetch and load java/native libraries for each new spawned application-process, zygote will simply fork itself, reset permissions, and execute the code of the started application. Simply send command to Zygote and it will rapidly spawn your app.
  • 8. ‫בס"ד‬ 8 The Zygote class expose the function "forkAndSpecialize" which handles the fork, settings for the subprocess app (uid, gids, nice- name, …) and calls ZygoteHooks for dalvik integrations. The processCommand method of ZygoteConnection class is an interesting method, as it parses the command from zygote's LocalSocket, closes not required file descriptors, and call forkAndSpecialize to create the application subprocess. The following flow chart (from "What the Zygote") briefly shows the components we talked about (at the left: from the init process to the native daemons and app_process that runs Zygote server) and the flow of launching an application (red arrows at the right: from clicking app's icon at the UI, to the "am" that sends command to ZygoteConnection, which forks zygote using forkAndSpecialize).
  • 9. ‫בס"ד‬ 9 We learned how a new application process is spawned in android, but what can it do? What permissions does it have, what resources can it access? What code can it execute? To answer this question, let's take a look on the process we launched: As shown, moovit (pid=20047) is the child of zygote64 (pid=5456): The zygote64 service is, of course, the child of "init". Sharp eyers would notice that after zygote fork, it changed the application process owner to the user "u0_a504", and set a nice- name to the process (cmdline is "com.transmate", although executed binary is app_process): Between executions, the owner user of the moovit process stayed the same: This behavior is resulted by the android security system, which sets unique user for each installed application. During the installation of an apk (android application installation file), a new user is created, along with /data/data/<package name> folder, owned by that user: This folder is accessible (r/w) by the app's user only, and persistent app state (java "preferences", databases, state files) is stored here. Whenever the application process is launched, zygote sets the process identifiers to those of that user, thus preventing applications from insecure modifications of the each other's state.
  • 10. ‫בס"ד‬ 10 This fact makes the android system that secure, making application developers feel very comfortable with storing their state plain in the device. The android permissions system, relying on linux users' permissions system, is as secured as the OS itself. Each application user is very limited and can only access to its own resources, or a set of global resources permitted by the OS or the user installed the app. To get familiar with this set of global permissions requested by an application, and additional information relevant to further research, we'll first get to known the format of the application-installation file (android package – apk file). Using the linux `file` command: We can tell that apk file is basically a zip file, we can unpack using zip unpacking utils like linux `unzip` command or `winrar` software: A partial files-list of the unpacked moovit apk is attached above. We'll explain a few formats and concepts relevant for our research: 1. assets – this folder usually contain application-artifacts related to user interface, like icons, images displayed in the app, html/css files, text font files, text files that are translated to many languages (for the app to display text in the user's language) and other graphical assets for human user. It is usually very easy to patch assets from this app.
  • 11. ‫בס"ד‬ 11 2. META-INF – this folder contains hashes of the apk files to make sure they were not patched (MANIFEST.MF), and public signatures of the manufacturer to validate installation from reliable and original source. In case the apk is a "split apk" (apk that rely on other apk files during the installation), this folder will contain special signature of the company, to make sure each sub-apk is signed by the same issuer signed the main one. 3. AndroidManifest.xml – this file is a binary-encoded xml file, can be parsed by apktool/ axmldec tools. It contains information about the package (like its name and version), features and permissions required by the application, minimum/maximum supported android version, required java packages, supported intents, class of the main activity and so on. We hiddenly encountered settings from this file above, as the "monkey" script fetched the class name of the main activity from this manifest file, instead getting as a command line argument like the "am" script. The manifest file also contains an "application" tag that sets the installer behavior with files from the apk. Important settings of this tag contain the "extractNativeLibs" setting that set whether or not native library files will be extracted from the apk and available to load from the app. 4. classes*.dex – these files are bytecode packs of dalivk opcodes, which represent the compiled java/kotlin code of the application. Each dex file is a collection of compiled java classes (similar to the jar format), and can be unpacked using java decompilation/unpacking utilities, like d2j and jdec. The bytecode format of these classes in android is called Dalvik format, and is based on "smali" files, where each file represent a compiled java class. The apktool script can unpack all the dex classes into files tree, where each folder is named after the package namespace, and each smali file is named after the class it represents. As java is easily reversible, we can use reversing tools like jadx for apk files, and feel like we read source code.
  • 12. ‫בס"ד‬ 12 Now that we know how applications are installed, launched, and store their state files, we are ready to talk about a few possible vulnerabilities and how were they blocked (or, could be blocked). First of all, let's talk about upgrade of installed application. It is very common in the lifetime of android user to upgrade applications he installed earlier, but he does not want the information gathered about him to be deleted. For example, player of the Subway Surfers game won't like to update the application if it forgets his coins/highscore states, and a moovit user won't remember it's credentials whenever he updates moovit. To avoid this uncomfortable case, android allows installation of apk files with the same package-name as already installed applications, without removing their /data/data/<package> folder (state files folder). Android moves the ownership on the app-user to the new installed apk, and removes the old one. That way, the new installed apk can access the state files of the old one, keeping the state (preferences, databases, …) the same as it was before the upgrade. An attacker (or an ethical pentester) would think about the following attack: In case we'd like to patch the state of an application in our phone (e.g. increase our Subway Surfers highscore), we would reverse engineer the Subway Surfers apk (using jadx, for example), and find how it stores it state about our in-app progress. Then, we could implement our own apk, with the same package-name as the original Subway Surfers apk, that will change this state file once loaded. Now we can install Subway Surf and play it, let it store his state files, then "upgrade" to our own application (that simply patches the state file) and re-upgrade it back to the real Subway Surf application. Practically, this attack would not work (and seems that android was never vulnerable to this attack) as android applications are signed by an issuer, and android won't allow installation of apk that has the same package-name of previously installed app, but another signature issuer. However, this attack example will be relevant for us in the future.
  • 13. ‫בס"ד‬ 13 The research model Now that we know how the android system works and how developers rely on the given security, it's time to add some powerful utils to break assumptions developers have about the system, and allow us use wider attack surface against android applications, to research the client-side logics and flows, letting us exploiting flaws. We'll talk about the model we'll use for the following researches (as known as my personal smartphone), the software/hardware features I used to create it, and the additional abilities it provides as (especially the security-related ones). Before talking about the non-trivial modifications I've done to my device, we'll explain a tool we used earlier, the adb shell. The Android Debug Bridge mechanism used to help developers debug their applications, and android users access their device. The adb protocol is pretty similar to ssh: it is managed by the adbd server daemon in the phone, and lets clients connect to it using tcp or usb. Using adb, we can have a shell access ("adb shell") and files transfer ("adb pull" and "adb push"). Like many other android applications/services, adb has its own user ("shell"): The same access is used for connected adb shells: Additional command is "adb root", which restarts the adbd server with root permissions, allowing adb shell and file-transfer be more privileged, as they have the same permissions as the linux "root" user. We can't use "adb root" command on commercial phones: Therefore, the following figure was taken from an emulated android:
  • 14. ‫בס"ד‬ 14 We understood why and how powerful it is to have root access in android, so we'll use it in our research model to research it's abilities. My mobile device is a Samsung Galaxy S10 I've rooted using Magisk. Using Frija, I've downloaded an extracted version of my Samsung firmware, split to partitions (BL, AP, CP, CSC). Then, using Magisk, I've patched the AP (Application Processor) and resulted a patched version of android filesystem, that contains the "su" binary for switching context to any user I want (including root). Using android developer-mode, I've OEM-unlocked my device, to let me install custom firmware. The OEM (original equipment manufacturers) lock makes sure you only install firmware signed by acknowledged issuer. When you unlock your OEM, a hardware fuse goes off irreversibly, marking your device is no more reliable. Using Odin (Samsung firmware installer), I've burnt the new firmware (original BL, CP and CSC + patched AP) to my device, and enjoyed rooted phone with Magisk root manager. After that, I've enabled "Zygisk" – a Magisk feature that allows code injection into zygote service. That way, we can inject code to all application processes (as they'll be forked from zygote and contain our injected code). We can also intercept the forked process in many stages. This method of hooking and code injection from zygote is very covert, as the application wouldn't have debugger attach or something trackable like that. Using LSposed Magisk module, I could use the hooking framework of Xposed, which wraps hooks and interception in the context of the hooked application, letting us execute java/kotlin code secretly. It's all fun and games until someone wants to prevent you from patching his application. Google created the SafetyNet API (which was replaced by the PlayIntegrity API) to help developers know whether the device running their application is safe or not.
  • 15. ‫בס"ד‬ 15 Bypass Client-Side Security Features Now that we are familiar with android system, and specifically our research model, we can talk about methods to achieve full control over what's running on our system. First of all, we'll have to bypass SafetyNet/PlayIntegrity, because as mentioned above, they report us to application developers as malicious users, preventing us executing their apps in our system. One application that is well known hater of rooted devices, is Google Wallet, which prevents rooted smartphone from using it. This article shows how to bypass the security features to use such applications. I'll list & explain what tools I used to bypass it in our research model: 1. Magisk (root manager) + Zygisk (Magisk feature) 2. PlayIntegrityFix (Magisk module, shortly named PIF) 3. LSposed (Magisk module) 4. HideMyApplist (LSposed module, shortly named HMA) We already know Magisk + Zygisk from the previous section, we'll mention that together they allow us run privileged usermode services, install native kernel modules and inject code to the zygote service. Zygisk, together with LSposed, lets us hook every application over the system, and control its logic flow. LSposed provides simple modules system for both developers and users, to install and develop hooks easily. The HMA module, hooks methods that return list of installed applications over our system (for example, the method getInstalledApplications) and deny (or filter the response) when specific selected application call them. That way, we can hide the fact we have Magisk app (or other "malicious" applications and binaries) from applications that require secure environment to run. More important role in this area plays the SafetyNet/PlayIntegrity API. The PIF module globally intercept calls to these APIs, make the report returned to the applications look the same as in a legitimate device. To check what would these APIs report about us, we can use YASNC.
  • 16. ‫בס"ד‬ 16 Now that we have a research model that can run android application designed for modern devices (arm64, android 12, API 31), and we have root access to the device (using "adb shell", executing the "su" binary to have root shell), we can patch applications' state files and databases. To do so, we can write a native/java application we'll execute in root-user's context, that opens the target app's databases (at /data/data/<package name>/databases/…) using SQLite for java or SQLiteCpp. Fortunately, there are several tools that already implemented parsing and editing SQLite databases, json/xml files and java shared-preferences (the android way to store key-value mapping, persistent between runs). The best of them I know is "CheatDroid". It shows selectable list of installed application, lets user browse all app's state files and open them in matching editor. We'll show educational-purposes-only example of usage, against the "Hill climb racing" game: In figure 1, the game state before the modification. Figure 2 shows the shared-preferences (hillclimbprefs.xml) that contain app's state. Figure 3 provide sample modification could be made to shared- preferences dictionary used, and figure 4 shows the result. NOTE that these manipulations could sometimes be illegal, and such tools must be used carefully and legally. We'll notice that the shared-preference file edited by CheatDroid could have been edited in its raw xml-format, using disk files manipulation, as this xml file could be edited using text-editor under root permissions. 1 2 3 4
  • 17. ‫בס"ד‬ 17 The manipulations on state-files are very powerful (and can even take place when the device is off, using disk manipulations), but they only affect data stored by the app between different launches, and won't help us attack applications' data in runtime. In addition, because of locks and races, we can't safely modify state-files when the app is running, and we must shut it down. To be even more powerful than that, and being able to dynamically patch application's memory (like global variables and functions), I'd suggest the use of memory-scanners and patchers, like CheatEngine. CheatEngine is a disassembler and memory-scanner, commonly used to scan virtual memory in the target application and find virtual address that contains the memory we searched for. The most common use of CheatEngine is to patch programs that shows us a numeric value (e.g. number of coins in the game) that could change during runtime, and be displayed to us whenever it changes. For example, to find the memory address in the remote process that contains the number of coins, knowing the current value of it, we'll apply a memory scan to find all addresses that store memory in the pattern of the expected value. Assume we currently have 0x857620 coins, and our system is little- endian 64bit architecture. We do not know if the program stores the coins-count variable as uint32_t or uint64_t, but due to system's endianness, we can assume (in both cases) that the variable is places in virtual-address aligned to 4bytes, and matches the hex bytes pattern "207685" + "00"*. After listing all addresses matching this pattern, we can continue playing the game and achieve more coins. That way, the value in the correct address will be changed (e.g. get increased to 0x857633). We'll apply another memory scan, to see which of the previously found addresses matches now the new value.
  • 18. ‫בס"ד‬ 18 After a few scans, we'll stay with very few addresses (usually one or two), which for sure point to the coins-amount variable. Patching the memory in that address will let us set the coins amount to any value we'd like, without changing any persistent state-file. Then, the next time the app will write its state-file, the content will contain the patched coins-amount variable we set. This way helps us bypass state-file mitigations (we'll discuss later), as the app stores it state-file naturally, without any malicious manipulation. We understood the power of memory-scanners, but even with root permissions, how can we get r/w access to remote process memory? At this stage, we'll show a few methods used by memory-scanners: 1. ptrace 2. /proc/<pid>/mem 3. process_vm_readv/process_vm_writev The ptrace syscall is used by debuggers to attach to another process and debug it, letting the debugger pause the traced process, change its context and a lot of other debugging features, one of them is to read/write DWORDs to remote virtual address. The virtual file '/proc/<pid>/mem' allows opener to read/write to remote virtual memory, as it was a real file. Simply open it with the "open" syscall and "r+b" permissions, seek to the remote virtual address you want, and read/write to the memory. The syscalls (write by libc functions) process_vm_readv & process_vm_writev lets the user read/write arbitrary sized buffers from/to arbitrary virtual address of remote process. Each of these methods can be utilized to r/w arbitrary-sized buffer at any requested virtual address of a remote process. Usually, the address we are interested is of native/java variable, therefore it'll be stored in the heap, the stack or the data-section of the relevant image. We'd like to memory-scan these address-ranges.
  • 19. ‫בס"ד‬ 19 We know how to scan memory (r/w virtual memory of remote process), what scanning method to use (searching for changing-value again and again, reducing addresses count each search) and in what sections is the relevant memory stored (heap/stack/image). All left to do is to find what virtual-address ranges are mapped to each section. To do so, we can use the linux feature of the '/proc' fs, the '/proc/<pid>/maps' file! This file contains the mapping of different memory regions of remote process, to the virtual addresses they are mapped to, including address-range, permissions and separation of native images to their elf sections. The integration of these tools makes memory-scanner effective, fast and powerful. There are many android memory-scanners can be used on rooted devices, offering easy-to-use GUI for simple scans and patches. In addition, there are memory-scanners that utilize server-client model for scans and patches on non-rooted devices. Using methodologies that we'll face later in this article, they access the target process's memory without the need of root permission. The server-client model is a common methodology to patch applications in non-rooted devices dynamically. The client is the same app/service used in rooted devices (for example, the GUI of the memory-scanner, like cheat-engine). The additional component is the server: instead of using root-requiring features from the "client" (e.g. read memory of remote process), we'll inject a "server" to the target process, which will be able to access the process's resources with no root permission (as the code in the process can read/write to its own memory). That way, the client will send commands to the server, and we'll have access to the target application's resources in runtime, being able to manipulate it, almost the same as in rooted devices. It is a great solution for non-rooted devices, but there's one problem with it - how is the server-component gets injected into the remote process in first place? It isn't that simple, as the device isn't rooted.
  • 20. ‫בס"ד‬ 20 How can we dynamically inject code to remote process when we don't have root permissions? The simple answer is we can't, well, unless we have privilege-escalation vulnerability over the device or a remote-code-execution vulnerability in the specific target application. If can't inject dynamically, let's inject statically! The application's package is stored somewhere in the filesystem, if we can inject the code to the package files in the disk, it will be automatically loaded to the application's context in runtime. Using the "pm" (package manager), we can find the directory where package code is stored, together with signatures, metadata, native libraries and ahead-of-time optimized bytecode. Code injection into the apk or libs here will help us inject our server-code ("gadget"): But, as you can see, this directory and its files aren't writable for users out of the "system" group. Without root permissions (or utilization of privileged installer), we can't write our files here. The easiest method to bypass it would be patching the apk before it is installed in the system. That way, we'll install an apk which loads our gadget whenever it is launched. The application will behave exactly the same as the original one, and let the client (e.g. memory scanner) full access to the process's resources. There are many disadvantages in this method, and these will be the main topic of this article (bypasses of client-side security features in android), but before turning to mitigations and bypasses, let's show how server (gadget) injection is done.
  • 21. ‫בס"ד‬ 21 For the simplicity of the explanation, we'll show the theoretical methods using practical example: statically injection of frida-gadget into hill-climb-racing game apk. First of all, we'll fetch the target apk and download frida-gadget library in a version compatible with our ABI from latest version of frida release. Pull installed apk (using `adb pull`) and unpack it (using `apktool`): Apktool decodes the AndroidManifest.xml to human readable xml file. As we want to inject a native library to the apk, we'll set the attribute "extractNativeLibs" in the manifest to "true": Now we'll add the file "libfrida-gadget.so" to the "lib" folder, relevant to our ABI. In our example, it'd be "lib/arm64-v8a/libfrida-gadget.so": As we learnt earlier, when application is launched by zygote fork, zygote will initiate an instance of the java/kotlin class set as main- activity. If we pack the apk as it was modified until now, we'll result in a new apk that will extract the injected library to the installation path, but won’t load it, as zygote will only call the java code when the application is launched. To resolve this issue, we can inject bytecode to the unpacked java class (as we know, java classes are compiled to bytecode and packed in dex files, extracted by apktool). The injected bytecode will load are native library (gadget) and we'll be able to utilize the server-client model for our research purposes.
  • 22. ‫בס"ד‬ 22 To inject the call to LoadLibrary (java function from System that loads native libraries), we'll first find what is the main-activity of the app, using the AndroidManifest.xml activity entry: Now we can navigate to the disassembled file that contains the smali code of this class (apktool unpacks the dex files to smali files, where each part of the package name is a folder, thus the path of the MainActivity is "smalicomfingersoftgameMainActivity.smali"): In the picture above we can find the static constructor of the MainActivity class. In java, static constructor is the function called before initiating the first instance of the class, or invoking any function of it. Injecting our opcodes to this function, specifically as the first opcodes of this function, will make sure our code will be executed before any opcode of the modified application. We'll load our library here, using the following smali code (dalvik opcodes): Which makes the static constructor as the java code: And this was the final step to inject native library into an application process, before it's code even got execution context!
  • 23. ‫בס"ד‬ 23 As we already know how to inject native library (modifications server) to an apk, making it behave the same as the original one, we only need to find a way for the client to connect the server. The most common method to enable this communication is using TCP sockets. Therefore, we are only required to add the internet permission to the AndroidManifest.xml, and we are good to do: In our case, the internet permission already existed in the manifest, so we didn't have to change anything. We have an unpacked apk, with an embedded modification server library. Let's re-pack it! Using apktool, we'll recreate the dex file from the smali classes (done by the smali/baksmali assembler), and re-encode the manifest xml, packing them together with the resources in a zip file. Using zipalign from Google's binary tools for android developers, we'll align and validate the zip format to match the requirements from an apk file. As described in the tool's documentation, this is a tool "that helps ensure that all uncompressed files in the archive are aligned relative to the start of the file": For being installed in android devices, the only step left is to sign the application. Signing in the new signing-scheme is done by apksigner:
  • 24. ‫בס"ד‬ 24 And we are done. Now, we have an apk that can be installed in non- rooted android devices, and modify the process in runtime utilizing the server-client model. An important thing to note is that we do not have the same keystore used by the original manufacturer, as it is a private-key used before the distribution, and the resulted patched apk will have a different signature and a different issuer. This point is critical and will be discussed later, as it touches many aspects of client-side security and mitigations bypassing in android. We'll note that the whole process described in the previous page, of repackaging patched apk file that was unpacked by apktool, is a technical and repetitive process, can be automated easily. Therefore, I've created the buildapp tool (installed as pypi package): Now that we have memory scanner (and patcher) in our arsenal, for applications in both rooted and non-rooted devices, we are ready to learn about a tool more powerful than only a memory-scanner – the dynamic instrumentation toolkit, frida! Using frida, we can dynamically hook and execute code in the context of other processes, without debugging them! Frida is a portable tool which support Windows, Linux, MacOS and Android. It provides an API to control hooks in JavaScript/TypeScript, C, Swift, .NET and a few other programming languages. In this article, we'll focus hooking java/native code in android processes using javascript/typescript. Frida implements the server-client model, supporting both rooted and non-rooted devices. For non-rooted devices, it provides the frida-gadget binary introduced before. Frida-gadget is a native shared-library that serves as CnC server for frida client. The client of this model can send javascript code to execute in a tiny js-interpreter, embedded in the frida-gadget binary. This js code can use frida js-API, which provides useful classes and functions for code interception.
  • 25. ‫בס"ד‬ 25 Frida API supports interception of native code (using Interceptor), letting us execute code before / after target function is called, changing its parameters / return value, or calling other function instead of the originally invoked one. It also provides useful functions for debugging and dynamic reversing the application (like backtrace from Thread utils), memory interception (using the Memory module) and very powerful language-specific tools, for hooking non-native language-level code and memory. The language-level features of frida are created using frida-bridges, which officially implemented for Java, Swift and ObjC. For example, frida can find all instances of a java-class, hook methods and functions and even execute arbitrary java code in the context of the application. There are also non-official open-source implementations of bridges for other languages, like the Il2Cpp framework, used by applications developed in unity. These bridges make frida an extremely powerful tool for patching applications and bypassing security mitigations used by the manufacturer. To utilize the server-client model and inject frida-gadget into an apk for installation in non-rooted devices, we'll have to follow the injection steps as explained in the previous section. In addition to the server feature of frida-gadget (which executes js script it gets from a tcp-socket), it can run autonomously, and inject the script it was given once the gadget is loaded. That way, we'll be able to patch an apk and create a new one, that behaves as we'd like. To automate the injection an repackage process of frida-gadget and script into an apk, I've created the apkmod tool (+ github action): Frida also provides the frida-compile utility, to compile and optimize js/ts scripts to be loaded by the script-interpreter that frida uses. Additional binary set is Hluda repo, which is a compilation of frida binaries for android, undetected by antiviruses / mitigations, as binaries are slightly changed to bypass binary-signatures scanning.
  • 26. ‫בס"ד‬ 26 After wrapping the discussion about frida-gadget for non-rooted devices, we'll introduce the frida-server binary provided for rooted android devices, which enables hooking any process over the system. Once commanded to, it attach to a process (or spawn a new one) and injects a native library (named frida-agent), which provides command and control API for the frida client, the same as frida-gadget. The frida server can be automatically started as a daemon during device boot process, using the Magisk-frida module. The rooted version of frida has many advantages over the non-rooted method. The following chart compares the methods in a few aspects: frida-server frida-gadget storage usage Binary is stored in one file over the fs Each patched apk contain the binary package signature & integrity Package signature is the same, as we dynamically inject library. No patches are made to the apk, passes strict integrity checks Signature and issuer are changed as we repack the apk. Fails basic integrity tests, as the apk contains hooking library autonomously The server does nothing until it commanded to The gadget can automatically execute frida script usability The server supports rooted devices only Supports both rooted and non- rooted devices complexity Easy to use, can be started automatically as service Requires repacking of each apk we'd like to research/patch Despite the differences between the methods, it is important to note that both provide the exact same hooking API and abilities. In addition to scripting API, frida has a code-share community where developers share scripts used to bypass security features, like the popular ssl-pinning bypass and android root-detection bypass.
  • 27. ‫בס"ד‬ 27 The power of frida tools, and the differences between frida-gadget and frida-server, leads us to the next topic of this section. To illustrate the topic and the power of frida in language-level hooks, I'll show script I used in one of my projects - ColorTweak. The ColorTweak project is an educational-purposes-only patch (using frida-gadget) of the ColorSwitch game developed in unity, compiled as Il2Cpp project. This project contains the following frida script, injected to the original apk using my patching tool, apkmod: In the provides script, frida finds the Il2cpp runtime & symbols, and the C# image it executes. Then it patches the "isInvincible" method of the "ColorBall" class to return the value "true", letting the player of pass any stage with no effort. Using frida-compile and apkmod, the repo creates patched apk you can use in rooted/non-rooted devices. For more information, documentation about the research and build process, browse ColorTweak repo. After we saw usage example of frida and understood its main role in android-security world, we shall discuss the security-related difference between frida-gadget and frida-server. the frida-gadget method's main disadvantage is that it changes the content and signature of the apk. It's important to note that applications might contain anti-tamper features, usually when the app contains code related to billing and purchases, which detect this patch. Even if the developer is unaware to cyber-security, the packages he uses might do. For example, the masabi API (bus-payment package) used by moovit, validates its signature during runtime.
  • 28. ‫בס"ד‬ 28 When such secured packages detect they were patched, they choose whether to terminate the application process or block their specific service, making the app (or the components uses secured-packages) tamper-proof. Another secured package that validates its runtime environment and app's signature is Google-Mobile-Services (gms). During runtime, the java masabi package uses android's PackageManager to fetch the signature of the apk. Then, it compares the signature with hardcoded base64 string. This whole logic occurs in the java code, executed on device. This method detects the patches made by apkmod when it injected the frida script, but it can be easily bypassed using frida hook or smali patches (e.g. make the compare function return true, change the compared string, …). The gms API also checks the signature of an apk that uses it, but it compares it in much safer method: When an application is uploaded to Google Play app-store, its signature is listed in Google's databases. Then, when the application calls gms-api services and it connects a socket with Google's servers, the gms api sends the fetched signature to the remote server, where it validates it against the database. When given unexpected signature, the server closes the socket and the gms api is unusable. In addition to secured compare of the app signature, Google's API fetches and compares the it in multiple methods, thus bypassing it requires many advanced hooks, on both native and java code. Bypassing Google's signatures check is very hard, but we can't give up patching applications that uses Google packages, as it would block us from patching almost any application and providing a generic client-side security method, making this whole article worthless. Instead struggling with the gms api provided in Google's java package "com.google.android.gms" (that strictly checks for app's signature), we can patch the app to reference "org.microg.gms" (GmsCore by MicroG) instead. MicroG provides gms api wrapper that doesn't require correct signature to work.
  • 29. ‫בס"ד‬ 29 After we experienced the benefits of methods used in rooted devices over the non-rooted ones (both in usermode), we'll introduce manipulation methods that utilize kernel features and abilities. Utilizing kernel permissions can help us covertly inject code and hook activities of applications and processes, without any usermode code being able to detect suspicious behavior, code or memory. Running code in kernel context can help us bypass many mitigations and work under the radar of anti-tamper software or anti-virus. The first method we'll introduce is syscall-hooks. Any usermode code that accesses system resources is using a "syscall". Any file descriptor open/read/write, event setting and any other operation is allowed to occur only in kernel mode, therefore the kernel introduces a set of APIs (syscalls) that can be invoked from usermode code (using the syscall opcode, or "SVC" opcode in arm). As kernel memory is hidden for read / write from usermode code, any hooking and manipulation we'll set in the kernel will not be detectible by non-root usermode code and mitigations, including the addresses of the syscall-handlers themselves. In linux, the syscall handlers (kernel-mode functions) are registered in a table named "sys_call_table", where each row is a mapping between the syscall-index and the handler-address. Using kernel module we can locate the table (with the kernel mode api "kallsyms_lookup_name") and set table entries as we want. That way, when anyone over the system uses the patched syscall, our handler will be called. That way, we can hook functionality system- wide and undetectably. This method is very common in linux systems. For code example, you can refer this rootkit repo. Another powerful tool for kernel mode interceptions is the kprobe feature. This feature is commonly enabled in linux distributions, including android. Using simple API, it lets us install hooks which will be called before/after the function, and on fault/break. Unlike syscall- hooks, it can hook any kernel symbol, not only syscalls. Example here.
  • 30. ‫בס"ד‬ 30 As can be easily understood from the examples above, in both kernel and usermode contexts, we can utilize a set of simple operations to build extremely powerful tool for hooks and modifications. This set of features (r/w memory, context [registers] setting, …) is provided by debuggers. As a usermode-debugger can help us attack usermode applications, a kernel-debugger can intercept both usermode and kernel code / memory. This powerful tool also follows the server- client model, letting usermode applications communicate with the debugger, sending a set of commands and receiving responses. Android kernel debuggers are not a commonly used tool, but with the correct wrapper it can provide powerful interface, like frida, and impeccable access, as it is very highly privileged. We won't deeply discuss kernel debugging in this article, as user-friendly wrappers rarely exist. Usage example is provided here. Google themselves provides kernel debugging and tracing utils for developers. They list of tracing tools and information about them is provided in the trace section of android kernel documentation. These tools include ftrace and atrace, letting us trace events and flow of kernel methods and system applications, with filtering support for both kotlin/java and native code. Additional and main highly privileged modifications feature in the linux-kernel area, is ebpf. Google provided ebpf support in android kernel. The ebpf feature let's us install ebpf modules to the ebpf interpreter in the kernel. After validation and installation of our ebpf module, it will be able to access system resources from kernel context, providing almost the same power as kernel-debugger. As it is widely spread over different operating systems, ebpf is friendly wrapped by many developers/researchers in ebpf rootkits.
  • 31. ‫בס"ד‬ 31 Summary of research tools for security challenges Before moving on to the next section, let's sum up the challenges and solutions we encountered in android client-side security universe. We first learned how application processes are started in the strictly secured android operating system, and how effective in performance is the zygote server, which forks itself and initiates an activity from provided java package. That way, the java interpreter and environment are initialized only once, to save time when spawning new apps. To complete the set of loadable code objects, we showed how can java code load native shared-library binaries. After understanding the system, we built our research model and faced many internal low-level features of android devices. Using the privileges provided by the research model we investigated android development methods and how they work internally in the system. For example, after understanding how do developers store persistent state, we found tools to patch the shared-preferences and databases used by applications (when we have sufficient permissions). We learnt about the apk format and how to modify apk files, including utilities to inject native libraries into an apk, inter alia to utilize the server-client model for dynamic patches. We studied the power and limitations of this method, as it can only access usermode applications' resources and is easily trackable (as we change the signature of the installed apk). As instances of the server-client model, we witnessed the impeccable abilities of memory scanners and dynamic debuggers, like frida, and demonstrated the use of apkmod – an injection tool that embeds the frida server and script to create patched apk. Finally, we discussed the disadvantages and missing covertness of these methods, and showed how patches in kernel-mode help us bypass security features and detectors.
  • 32. ‫בס"ד‬ 32 Main Topic Introduction Finally, after gathering knowledge about the environment we run in and building a strong and generic toolset to manipulate and intercept processes, we are ready to talk about the wild jungle called client- side security in android applications. As described previously, android developers have many assumptions about the device they execute their code in, which makes them feel comfortable about leaving their applications unsecure. Once we showed our methodologies to hook and patch applications, we found that these says does not hold water. But what is actually the risk we are talking about? We'll show a few examples to explain our motivation: • Mobile games usually offer in-app purchases, to have more gems/coins in the game. These apps (mostly games that let you play offline) often store such values in shared-preferences state- files (which are easily patchable), making the app insecure. By hacking applications of this kind, an attacker can gain his target coin value and avoid paying the developer for that. No one notices when a single person do this immoral operation, but when it is wide spread, companies lose money. • Government applications use clock animation near online certificates showed in applications, to prevent photoshop-fake attacks (like the Israeli covid-tracing application). But, using modification tools in android, one can modify the data showed in the application, keeping app's same animation / behavior. • Bank applications digitally sign documents produced by the client. When this logic is executed in users' devices, hackers can use android modifications to steal internal keys and mock the signer, allowing themselves to sign fake documents. • …
  • 33. ‫בס"ד‬ 33 The list of risks and vulnerabilities is infinite, and there are a lot of mitigations can be used against malicious clients. The main goal of this article, which will be discussed in the current section, is to find (and attempt to bypass) generic and strict mitigations can be used by application-developers and OS-developers to make the system a safer place, for both the developers and the users. We'll map differences between how PC and android developers relate to security issues, and explain why (and when) should android developers take the less permissive approach. Due to the tools and methodologies described in this article, fighting attackers seems to be worthless, as they are too powerful as clients and can do whatever they want with the code executing on their devices. Well, despite their abilities, turns out there is a variety of mitigations used against these hackers, effectively hinder client-side attacks attempted on android applications. But the harder we'll fight the attackers and harden our mitigations – the harder they'll fight us back, by creating new bypass methods. Actually, android client-side security isn't a losing game, but a more of an endless cat & mouse game. There are no winners in these wars, as the attackers will always achieve their goals, it's just a question of how much time and resources will the developer make them waist. Sometimes, it's an attrition warfare until one of the sides surrenders: the developers apply more mitigations (which waists the company's resources) and the attackers create and exploit new methodologies (which waists time from the attackers). In the next few pages, we'll discuss vulnerable areas which are commonly exploited in applications, mitigations used to hinder the attackers and theoretical bypasses, which sometimes might take a lot of time to develop.
  • 34. ‫בס"ד‬ 34 The Cat & Mouse Game Vulnerable Area Applications store data over the device, to "remember" state (so users won't have to enter their passwords each launch and games remember our highscore and coins amount). Patching these state- files can help attackers manipulate the target app. Exploitation As shown earlier, we can edit these files using root access to the device. Commonly used state-files format (shared-preferences, xml, json, sqlite3 databases, …) are easily patchable, as their format is simple to view and modify (e.g. using CheatDroid). Mitigations 1. One approach is that the developers should store checksum over the state-file fields, using another filed in the state-file, and validate it before accessing any data from the state-file. 2. Another approach, which is extremely popular between Windows developers, says developers should create their own format for state-files (usually binary format), making their state harder to modify (and their app faster to parse the state-file). Bypasses 1. The attacker can reverse-engineer the application to find out how is the checksum calculated and modify the checksum filed to be valid. Attackers may also patch the apk to skip checksum validation or extract the checksum calculation component from the apk to create valid checksums easily. 2. Attackers will have to extract the "store-logic" to create their own data-packer. This bypass will require a lot of work to exploit, making the mitigation a good one. An easier bypass will be implied from the following vulnerable area.
  • 35. ‫בס"ד‬ 35 Vulnerable Area A much less "conventional" yet exploitable area is the RAM. Applications store their dynamic runtime memory (even the memory loaded from the state-files) in the RAM, then store the persistent data-pieces in state-files over the disk. The point is, any memory was loaded once to the RAM, where attackers can modify it. Exploitation As explained earlier, memory of all processes is accessible via the kernel (by parsing page tables and accessing physical addresses) / by privileged usermode code (the root user can access virtual memory of any process using syscalls or virtual files that represent processes' virtual memory). Using memory-scanner (e.g. CheatEngine) we can find addresses that contain certain data, and manipulate it in RAM. Mitigations 1. As we can't prevent attackers from accessing our runtime memory (because the kernel can covertly r/w any process's virtual address space, even if we set memory as read-only when it shouldn't be changed), the best mitigation would be not to use trivial data-types. For example, use "string" instead of "int" when dealing with numbers, confusing the attacker who searches for integer value in your process memory. 2. Another interesting approach is not to hold plain values in memory when these are unused. Clear the memory when you are done using it (e.g. memset to zero in the destructor) and frequently garbage-collect variables. A more effective method in this world is to scramble data when it gets written, and descramble when its read.
  • 36. ‫בס"ד‬ 36 Bypasses 1. Reverse-engineering the application, attacker can find what data-types are used by the developer. Developers can (and should) mangle and scramble package names when static- linked, and strip their binaries to remove debug symbols and strings that helps attackers to understand how is the program written. Without interception points and symbols, it would take attackers a lot of time to analyze and reverse the program. Another bypass method for this mitigation would be to use fuzzing-search for the certain memory they'd like to manipulate. Some memory-scanners (e.g. GameGuardian) have a fuzzing-search feature, exactly for these cases, where you do not know what data-type is the memory you are searching for. 2. This mitigation doubles the challenge: attackers will now struggle to both find the memory and manipulate it. To find the memory, they'll have to search for the scrambled content and hope it isn't currently accessed (as accessing the content make it descrambled again). To patch the memory, attackers will have to calculate the scrambled-view of the target value they want to put. Both challenges might require the attacker to find and extract the scramble-logic used by the application. For the challenge of finding the scramble-function used by the app, we'll introduce a new tool: time-travel- debugging. Using TTD we can "record" the memory of the application, and search for the plain value in the recorded memory (when it gets descrambled). Watching the opcodes that accessed the found address for write purposes, we can find who descrambled it. Then, using hardware-breakpoint on writes, we can pause the application when it attempts to write a scrambled content, which will help us to find the scramble- logic using backtrace.
  • 37. ‫בס"ד‬ 37 Vulnerable Area Many applications offer clients to pay for features within the app. It is called "in-app purchase" (IAP), and we can mainly see it in games where users can buy coins / skins and apps that has feature specifically for paid premium users. Exploitation Using MITM framework, attackers can globally intercept the inter- process-communication between Google-services and other applications, and make the apps think the purchase completed successfully without actually paying money to the company. Utilizing this MITM, an android software named "freedom" was used to make free in-app purchases on rooted devices (until it stopped working). Since then, the cyber community released features in LuckyPatcher to create patched version of APK files, where in-app- purchase requests to Google-services are "inlined" in the patched apk, with code that returns general "purchase-succeeded" responses. Mitigations 1. Not only a mitigation to this case but a generally recommended approach, is to perform and validate important data and calculations in server-side. When Google-services sends "purchase-succeeded" response, the application can (and should) validate it with a server owned by the app's developer, and make sure to provide the purchased item only for legitimate clients. This "server-approach" can serve as mitigation for many other attacks. For example, storing user-state in server-side instead using shared-preferences over the device, prevents state-file modifications. Additionally, analyzing the user-state in server- side and validate change-rate of values, mitigates RAM attacks. 2. Apps can check for threating environment and avoid running in rooted devices or where threating applications are installed.
  • 38. ‫בס"ד‬ 38 Bypasses 1. The server-side validations approach is the BEST method to fight cyber-security attacks, as there's only a single-source-of- truth which is ruled by the developer. It leaves no room for client-side security (which we learned it can't be trusted at all), and reduces the attack surface of the developed environment (games, banking apps, communication and social media, …). Other than hacking the server / finding API vulnerabilities, the only bypass I'd suggest is to create an offline version of the app, replacing references and calls to the original server with our own responses or mock-server, to provide the desired behavior. Not only does it take a lot of time and effort from the attacker to develop such a server and reverse-engineer the app, but it is resulted in a modified apk which isn't connected to the original glob-wide environment, and it's not that fun to have your own isolated app (e.g. your patched game can't let you play with real users over the world, and patched bank-app won’t affect your real bank account). Therefore, this mitigation and development approach is the best to use with critical data (like banking and investment apps). Combined with strict API and a secured mitigated server, developers who choose this approach are impenetrable, and their apps are very strongly secured. 2. Escaping from threating environment and hacking tools is theoretically secured approach that looks good on paper, but when it comes to real life, it doesn't hold water. This approach is used by banking and government apps, and they attempt to fetch information about the devices and the installed applications list. When we have root access to our device, we can easily spoof any response they'd get from the android OS.
  • 39. ‫בס"ד‬ 39 Game Changers + Mujde After witnessing the endlessness of the client-side security Cat & Mouse game between developers and attackers, we are ready to introduce game-changer attack & protect methodologies. The theoretical protection gamechanger we'll discuss is an aggressive version of Google's signature checks we saw in gms API, and it can prevent any static apk modification (e.g. injection of smali code or utilization of the server-client model). Just like the gms package, we'll validate the signature of the apk during runtime. But, unlike Google, we'll fetch the signatures from the RAM (where the actually running code is stored) and not from the disk (as there are linux-mount tricks to make android load to RAM a different apk than the one on disk, letting the attacker bypass Google's verification: the apk over the disk is unmodified, but due to the mount, linux may load and execute a modified apk). Using Java code, we'll find the virtual address of the apk in RAM, and read its content. As native libraries in the apk are stored uncompressed (zip flag COMP_STORED), our code will be able to make sure there were no modifications to assets, resources, manifests, bytecode and native code. Making sure that there's one and only apk file loaded to the process, validating its content with a remote server, developers can prevent static patches to their apps. In order to make sure no one patches the signature-verifier logic (like LSPatched attempted to do against Google), we'll fetch and execute the signature-verifier bytecode during runtime. This code can't be statically patched, as it is dynamically fetched and not stored on disk. To be even more secured than that, we'll dynamically fetch other bytecode components (even those who aren't related to security features), thus the signature-verifier won’t be easily detectible by patchers who intercept the JVM bytecode interpreter.
  • 40. ‫בס"ד‬ 40 I call this security feature a real game-changer, as it completely prevents any modification to android applications over non-rooted devices, letting the developers win the client-side security battle. As we are about to show, rooted-devices can bypass even that strict security test, but the developers are still considered the winners, as approximately only 4% of the android devices are rooted. Therefore, there's no large-scale attack that can hurt security-aware developers. As just promised, we'll now discuss a theoretical bypass for the security mitigation we introduced, in rooted android devices. This bypass method utilizes Zygote to covertly inject code to android processes, without modifying the app in RAM / disk, yet being persistent and powerful, providing the simple frida hooking API. With root access, we can inject code to Zygote (just like Zygisk does) and hook the forkAndSpecialize method to detect when a specific app is launched. When the process forked for the target application, we can modify the JVM in memory (affecting the target process only, due to copy-on-write feature applied in shared-libraries). Hooking the JVM we can inject bytecode whenever we want, and intercept the bytecode interpreter. By intercepting the interpreter, we can change the context (smali registers) in RAM, thus no modification takes place over the memory where the apk is stored, without losing the powerful features of frida hooks in Java code. Using hardware breakpoints, we can break on specific memory address and pause threads before they execute code from it. This feature lets us change the context and achieve behavior similar to frida native interceptor, with native hooks. To make it all persistent, without modifying any apk, we can utilize Zygisk (like LSposed does) to automatically inject our code to specific target applications. To implement this bypass tool, I develop LSposed module called Mujde that'll automatically inject frida environment and matching scripts to target applications.
  • 41. ‫בס"ד‬ 41 Summary This article explores client-side security in android applications, highlighting vulnerabilities inherent to android's structure and the challenges developers face in securing data against manipulation. It delves into android as linux-based OS and popular attack methods in these environments, demonstrating how client-side manipulations can undermine security assumptions. Through the analysis during the article, we learnt that android's client-side logic cannot be fully secured against determined attackers. The client-side mitigations often fail against sophisticated tools available in the hacking community. While server-side validation and encryption offer significant protection, constant innovation on both sides means developers must continuously update security practices. We'll sum up few advices for android developers: • Utilize Server-Side Verification Critical actions, especially purchases, should be server-side validated to prevent client-side interference. This is always the best security-related approach can be used by developers. • Apply Code Obfuscation & Integrity Checks To make your app harder to reverse, obfuscate code, strip binaries and apply integrity checks to guard against tampering. • Implement SSL Pinning For secure communication with your server, and prevention of attacks against third-party packages, enforce SSL pinning and mitigate man-in-the-middle attacks. • Choose Your Battles Wisely Not any android application should be strictly secured, but you've got to analyze threats and be aware of them. Developers of offline single player android game can settle for simple mitigations, and there's no need to overestimate security where its unrequired.
  • 42. ‫בס"ד‬ 42 Disclaimer This article was written for academic and educational purposes only. It aims to increase cybersecurity awareness and provide insight into android security strategies and vulnerabilities. This article provides mitigation-suggestions and advices for developers, to show how cybersecurity knowledge supports defensive/academic community. The discussed techniques shouldn't be used for malicious intents or unauthorized tampering with applications, as such actions may violate legal statutes and ethical standards. Both developers and researchers should utilize the provided information responsibly, adhering to legal and ethical regulations and guidelines in their countries.
  • 43. ‫בס"ד‬ 43 Bibliography • Zuo, C., & Lin, Z. (2022). Playing without Paying: Detecting Vulnerable Payment Verification in Native Binaries of Unity Mobile Games. 31st USENIX Security Symposium • Rashidi, B., & Fung, C. J. (2015). A Survey of Android Security Threats and Defenses • Zhauniarovich, Y. (2014). Android Security (and Not) Internals • Gunasekera, S., & Thomas, M. (2012). Android apps security • Lardinois, S., & Legay, A. (2023). Modifying Android for Security Analysis • Samuel-Alberto, M. (2023). Privacy Concerns in Android Systems • Vidas, T., & Zhang, C., & Christin, N. (2011). Toward a General Collection Methodology for Android Devices