Android
Things to look @ during Android app review
basic services
content provider
broadcast receiver
webview
Intent filters
repacking a android app
ssl pinning
Android manifest
check permissions app needed
check if any component is debuggable cat manifest | grep debuggable
find exported components and , those are potential inputs to application
find "exported:true".
content provider, broadcast receiver are example of exported
Unprotected activities
cat manifest | grep exported="true"
Emulator setup
install android studio
`/Users/$USER/Library/Android/sdk/emulator/emulator -list-avds` will list all the available devices (we can install more from android studio)
` sudo /Users/$USER/Library/Android/sdk/emulator/emulator -writable-system -avd Nexus_6_API_25 -netdelay none -netspeed full -http-proxy 127.0.0.1:8081` to run AVD
[READ MORE https://developer.android.com/studio/run/emulator-commandline]
Install burp certificate to emulator
1. Download cacert.der file from burp
2. openssl x509 -inform DER -in cacert.der -out cacert.pem
3. filename=`openssl x509 -inform PEM -subject_hash_old -in cacert.pem | head -1`.0
4. mv cacert.pem $filename
5. adb root
6. adb remount
7. adb push $filename /system/etc/security/cacerts/
8. adb shell chmod 644 /system/etc/security/cacerts/$filename
9. adb shell reboot
validate by checking for portswigger cred in Settings -> Security -> Trusted Credentials.
Resetting Android device
$ cd /Users/username/.android/avd/<device>.avd/
$ rm userdata.img # this is block with user info.
Decompiling
use jadx
Start Point of application
In the AndroidManifest.xml
<activity android:name="com.employroll.www.employroll.login.Luncher" android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
Activity with intent-filter name android.intent.action.MAIN
are the First one to launched. in this case Launcher.java
file will be called when app is run.
Proxy Requests
Proxy requests : Android App -> My Computer -> Internet
And Then we can use Burp in our computer to change/edit/repeat do whatever with requests.
In Computer
* Start burp * Proxy -> Options * Edit -> Allow All Interfaces and bind to port say 8080. * Ifconfig and get IP Addr(say 192.168.1.8)
In Android
* Connect to router(ABC) * Setting->Wifi->ABC Setting * Advanced Options -> proxy -> * IP = 192.168.1.8 * Port = 8080
Done
Files to investigate
Dynamic analysis
$ ls /data/data/[com.file]/
app_textures
app_webview
cache
databases
lib -> /data/app/jakhar.aseem.diva-1/lib/x86
shared_prefs
Here imp. files are
* shared_prefs : contains shared stored files
* databases : sqlite database info
Static analysis
* strings.xml * AndroidManifest.xml
adb commands
list connected devices
adb devices
get shell
adb shell
install apk to device
adb install file.apk
transfer file from PC to android
adb push /source /tmp/destination
See logs
adb logcat
get root
adb root
remote adb
On mobile
$ adb push $NDK_ROOT/prebuilt/android-x86_64/gdbserver/gdbserver /data/local/tmp/
$ adb forward tcp:1234 tcp:1234
$ adb shell
$ /data/local/tmp/gdbsever :1234 --attach `pidof com.google.ctf.pwn.tridroid`
On Desktop
(gdb) target remote localhost:1234
(gdb) c
Sources & Sinks
Sources
registerReceiver
---- ANDROID CODE TO REGISTER -----
this.broadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("com.google.ctf.pwn.tridroid.SET_NAME")) {
MainActivity.this.editText.setText(new String(Base64.getDecoder().decode(intent.getStringExtra("data")), StandardCharsets.UTF_8));
} else if (intent.getAction().equals("com.google.ctf.pwn.tridroid.SET_FLAG")) {
MainActivity.this.flag = new String(Base64.getDecoder().decode(intent.getStringExtra("data").trim()), StandardCharsets.UTF_8).trim();
}
}
};
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.google.ctf.pwn.tridroid.SET_NAME");
intentFilter.addAction("com.google.ctf.pwn.tridroid.SET_FLAG");
registerReceiver(this.broadcastReceiver, intentFilter);
---- Invoking EVENT -----
$ adb shell
adb $ am broadcast -a com.google.ctf.pwn.tridoid.SET_NAME -e data "hello world"
Intents
Sinks
webView.loadUrl
this.webView.getSettings().setJavaScriptEnabled(true); // means we can execute JS
this.webView.getSettings().setAllowFileAccess(true); // means we can access file system
this.webView.getSettings().setAllowFileAccessFromFileURLs(true); // means we can access file:// url
this.webView.setWebViewClient(new WebViewClient());
this.webView.setWebChromeClient(new WebChromeClient());
this.webView.addJavascriptInterface(this, "bridge"); // add external interface (x86)
this.webView.loadUrl("file:///android_asset/index.html"); // URL to load
frida
## Step 1
step 1 is to load frida server script to device and connect to it### ON Mobile* adb push /source /tmp/frida-server-x86
adb shell
cd /tmp
bash frida-server-x86 &
## ON Laptop
frida -U -f com.hackerone.thermostat --no-pause
## dynamic link custom scriptfile.js
setImmediate(function(){
Java.perform(function() {
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
var context = currentApplication.getApplicationContext(); // Extra Code Goes Here
});
});
> Usage :
$ frida -U -f com.employroll.www.employroll -l ./file.js --no-pause
## Hooking onto function
* package : jakhar.aseem.diva * class : InsecureDataStorage1Activity * Func to override : saveCredentials with 1 argument($init for consrtructor)
so Java.use('jakhar.aseem.diva.InsecureDataStorage1Activity');
file.js below
setImmediate(function(){
Java.perform(function() {
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
var context = currentApplication.getApplicationContext();
var p1 = Java.use('jakhar.aseem.diva.InsecureDataStorage1Activity');
p1.saveCredentials.implementation =function(x){
console.log("Hook stop : "+JSON.stringify(Object.getOwnPropertyNames(this)));
var ret_value = this.saveCredentials(x);
return ret_value;
};
});
});
## Changing variable values
* package : jakhar.aseem.diva * class : InsecureDataStorage1Activity * varibale Name : gps_latitude_valfile.js
setImmediate(function(){
Java.perform(function() {
var currentApplication = Java.use("android.app.ActivityThread").currentApplication();
var context = currentApplication.getApplicationContext();
var p1 = Java.use('jakhar.aseem.diva.InsecureDataStorage1Activity');
console.log("GPS Value : ",p1.gps_latitude_val.value) // changing value
p1.gps_latitude_val.value = "14.231";
});
});
Common bugs:
SQLi
IDOR
authentication issue
insecure uploads
broadcast reciever
content provider
insecure store of secure info/ secrets System API's should be used to store sensitive info. Keyschains in IOS and smartLock in android is correct way of storing sensitive info
Check for screenshot
when a app goes to background , OS takes screenshot to show in app switcher which is publicly availble. check if any private info can be leaked using this
Sensitive data leak
Look at local app data if anything useful
weak crypto / Hardcoded crypto
Folder structure
| AndroidManifest.xml | - resouces/ | /Layouts : check if there is admin layouts, debug layouts | /images : nothing here
Cross app scripting https://hackerone.com/reports/401793 loadUrl + evaluateJS
Intent-redirection https://hackerone.com/reports/951691 fix: check both activity class and package name of intent before redirecting
Intent Broadcasting broadcasting intent is IPC mechanism to communicate with other app. If there is some sensitive data send that can be intercepted by other apps, its a problem * chck jazzy wu Fix: if you know who this broadcast is for, use setClass, setComponent, setName to make sure it only goes to target
Custom permission type https://hackerone.com/reports/440749
Path traversal durin file saving, and attacker can control path, we can override other files
zip file can have relative paths and unzipping this will again PT
OAuth response_type=token is bad. Use response_type=code
Last updated