Learn

 <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

This activity defines Mainactivity class to launch on startup, .here signifies it is in same package.

It is considered main entry point with class launcher(launch this mainactivity class on startup).

Exported activities are activities that can be started by an app or adb.

ADB (Android Debug Bridge)

  1. adb shell drops us on android linux environment.

  2. adb push your_path android_pathto transfer files from your machine to android.

  3. adb pull android_path your_pathto fetch files from android machine to yours.

  4. adb install path_of_apkto install apk on android machine.

  5. adb shell pm list packagesto list all installed packages on device.

    1. Note: pmis package manager, shellallows us to run commands as if we were inside device shell.

  6. adb shell pm list packages -3to list only 3rd part apps.

  7. adb shell pm clear package_nameto clear all data of an app in phone but not uninstall it.

  8. adb shell dumpsys package package_nameto dump information about a package like permissions, activities exported, apk path etc.

  9. adb shell am start package_name/.activitynameto start an activity.

    1. Note: am is activity manager.

  10. adb uninstall package_nameto uninstall the app.

  11. adb shell pm path package_nameto get full path of apk on android file system.

  12. adb shell pm list packages -3 -f to get path of apk in same command.

If multiple emulators are running

adb devices -lshows list

adb -s device_name subcommand to insteract with that emulator.

or

adb subcommand -t transport_id to interact with emulator transport id instead of big name.

ADB Logcat

  1. adb logcatshows logs from all the things in device.

  2. adb logcat -v briefto see brief messgaes. It has multiple tags

    V    Verbose (default for <tag>)
    D    Debug (default for '*')
    I    Info
    W    Warn
    E    Error
    F    Fatal
    S    Silent (suppress all output)
  3. adb logcat -v brief "MainActivity:V *:S"we can filter logs specifically. this will only log verbose log of type mainactivity.

Android compilation process:

Java/Kotlin ------> .class files(using javac/kotlic)-------->classes.dex (using d8 compiler) it is android byte code.------------>machine code(using Dalvik virtual machine(older android)/ ART android run time)

Smali

Smali means assembly closely . Assembly language of dalvic bytecode.

baksmali : disassembler for dalvik bytecode. thats's what apktool tool does, disassembled .dex files to smali files.

Apktool

Installation: https://apktool.org/docs/install/

apktool d path_tok_apkto decompile an apk.

Recompiler and sign APK

  1. create the apk

From inside the folder where all files and folders are created after decompilation run:

apktool b this will drop a new apk file in distdirectory.

We can't install this file yet. Let's sign it first

  1. Create a keystore:

keytool -genkey -v -keystore research.keystore -alias research_key -keyalg RSA -keysize 2048 -validity 10000

keystore research.keystore file will contain our signing key research_key.

Confirm with yeson last question.

  1. Sign the APK:

jarsigner -verbose -keystore research.keystore app_name.apk research_key

  1. Troubleshooting Install Errors

INSTALL_PARSE_FAILED_NO_CERTIFICATES: There is still something wrong with the signature. Maybe you tried to install an unsigned apk or the chosen algorithm (eg. SHA1) gets rejected.

INSTALL_FAILED_INVALID_APK: Failed to extract native libraries

This error occurs with some versions of apktool if the app contains native-libraries. To fix it, edit the AndroidManifest.xml so that extractNativeLibs is set to true. Afterwards you need to repackage and re-sign your APK.

INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package package_name signatures do not match previously installed version; ignoring!

You will get this message if a version of the app signed with a different key is installed on the device. The simple solution is to delete the existing app.

Failed parse during installPackageLI:

Targeting R+ (version 30 and above) requires the resources.arsc of installed APKs to be stored uncompressed and aligned on a 4-byte boundary'

This happens on newer apps, try this alternative method using zipalign and apksigner coming with the specific version build tools:

$ apktool b
$ [...]/build-tools/34.0.0/zipalign -p -f -v 4 ./dist/<apktool_build>.apk aligned.apk
$ [...]/build-tools/34.0.0/apksigner sign --ks ./research.keystore ./aligned.apk

apksigner, zipalign are installed with Androistudio but you need to add build-tools/34.0.0to SYSTEM PATH.

jarsigner and keytool are installed when you install JDK from internet.

Decompiling

Reflection feature in java/kotlin allows decompilation easy and in very high level. Unless app is obfuscated.

Reflection enables code to kind of "self introspect" at run time to resolve symbols [variable, class names etc]

Start Emulator without Android studio

If you just want run emulator and nit android studio. Use emulatortool android studio. you will have to add it to your path.

emulator -list-avdsto list your mobile devices.

emulator -avd device_name to start your device.

emulator -tcpdump file.pcap -avd device_name to capture network dump of entire phone.

Note: you may need to disable wifi interface, as tcpdump might not be able to capture http packets on wifi interface.

Proxy

After configuring proxy in device, we need to install burp CA cert in android trusted certificated list to be able to perorm mitm with ssl traffic.

In latest android you can just browse to http://burpsuiteand download ca cert and install it by searching certificate in settings. This will install certificate in user trusted list which is not supported/trusted by most of the apps.

In older android device do not support der certificate. You will need to convert it to pem format first then install.

/system/etc/security/cacerts directory has all system certificates.

/data/misc/user/0/cacerts-added/directory has all user installed certificates.

In order to install our certificate as system cert we need root access.

  1. root android studio emulator. (search rootavd)

  2. have a rooted physical device

  3. use genymotion, it ocmes with rooted emulators.

  4. or use a non-google play image in android studio which allows you to be root.

Then

install certificate.

If you have a device with root access follow the following steps:

  1. Install the proxy certificate as a regular user certificate

  2. Ensure you are root (adb root), and execute the following commands in adb shell:

# Backup the existing system certificates to the user certs folder
cp /system/etc/security/cacerts/* /data/misc/user/0/cacerts-added/

# Create the in-memory mount on top of the system certs folder
mount -t tmpfs tmpfs /system/etc/security/cacerts

# copy all system certs and our user cert into the tmpfs system certs folder
cp /data/misc/user/0/cacerts-added/* /system/etc/security/cacerts/

# Fix any permissions & selinux context labels
chown root:root /system/etc/security/cacerts/*
chmod 644 /system/etc/security/cacerts/*
chcon u:object_r:system_file:s0 /system/etc/security/cacerts/*

This trick of adding your certificate to /system/etc/security/cacerts work till android 13 only after that android has made changes that it doesn't handle system certificate this way anymore.

Read this for android 14 fix and problem:

Here is script to automate this, once you have installed you certificate on Android 14 as user. And you have root access, run this bash script inside emulator:

# Create a separate temp directory, to hold the current certificates
# Otherwise, when we add the mount we can't read the current certs anymore.
mkdir -p -m 700 /data/local/tmp/tmp-ca-copy

# Copy out the existing certificates
cp /apex/com.android.conscrypt/cacerts/* /data/local/tmp/tmp-ca-copy/

# Create the in-memory mount on top of the system certs folder
mount -t tmpfs tmpfs /system/etc/security/cacerts

# Copy the existing certs back into the tmpfs, so we keep trusting them
mv /data/local/tmp/tmp-ca-copy/* /system/etc/security/cacerts/

# Copy our new cert in, so we trust that too
cp /data/misc/user/0/cacerts-added/* /system/etc/security/cacerts/

# Update the perms & selinux context labels
chown root:root /system/etc/security/cacerts/*
chmod 644 /system/etc/security/cacerts/*
chcon u:object_r:system_file:s0 /system/etc/security/cacerts/*

# Deal with the APEX overrides, which need injecting into each namespace:

# First we get the Zygote process(es), which launch each app
ZYGOTE_PID=$(pidof zygote || true)
ZYGOTE64_PID=$(pidof zygote64 || true)
# N.b. some devices appear to have both!

# Apps inherit the Zygote's mounts at startup, so we inject here to ensure
# all newly started apps will see these certs straight away:
for Z_PID in "$ZYGOTE_PID" "$ZYGOTE64_PID"; do
    if [ -n "$Z_PID" ]; then
        nsenter --mount=/proc/$Z_PID/ns/mnt -- \
            /bin/mount --bind /system/etc/security/cacerts /apex/com.android.conscrypt/cacerts
    fi
done

# Then we inject the mount into all already running apps, so they
# too see these CA certs immediately:

# Get the PID of every process whose parent is one of the Zygotes:
APP_PIDS=$(
    echo "$ZYGOTE_PID $ZYGOTE64_PID" | \
    xargs -n1 ps -o 'PID' -P | \
    grep -v PID
)

# Inject into the mount namespace of each of those apps:
for PID in $APP_PIDS; do
    nsenter --mount=/proc/$PID/ns/mnt -- \
        /bin/mount --bind /system/etc/security/cacerts /apex/com.android.conscrypt/cacerts &
done
wait # Launched in parallel - wait for completion here

echo "System certificate injected"

Install split apks

In modern devices and latest playstore versions, I noticed that playstore install apps in split apks instead of one single base.apk file.

And for analysis or you want all install this same app on a device with no access to playstor. You will have to pull all apks one by one and install them at all.

adb shell pm path package_name

e.g.

adb -t 1 shell pm path com.umdeos.bdwjdx 

package:/data/app/~~xZdyqSPoq06amWY0XJLMfg==/com.umdeos.bdwjdx-A-4Jk-fhIxiUYahBsLFERA==/base.apk
package:/data/app/~~xZdyqSPoq06amWY0XJLMfg==/com.umdeos.bdwjdx-A-4Jk-fhIxiUYahBsLFERA==/split_config.en.apk
package:/data/app/~~xZdyqSPoq06amWY0XJLMfg==/com.umdeos.bdwjdx-A-4Jk-fhIxiUYahBsLFERA==/split_config.x86_64.apk
package:/data/app/~~xZdyqSPoq06amWY0XJLMfg==/com.umdeos.bdwjdx-A-4Jk-fhIxiUYahBsLFERA==/split_config.xxhdpi.apk

You can now pull each apk and install them together using this command.

adb -t 2 install-multiple .\base.apk .\split_config.en.apk .\split_config.x86_64.apk .\split_config.xxhdpi.apk

This will install the original APK on new device.

If some patching is done in APKs you can use objection to sign them

objection signapk *.apk

There is another option called SAI split APKs installer app from playstore which is paid now. But you can use its older version for free from its github: https://github.com/Aefyr/SAI

  1. Using this app you create a backup of your target app then export the backup in apks format.

  2. Transfer this apks backup to second device and using the same SAI application import the backup and install the app.

Advanced Proxy:

We have seen that if we got root access on a device, we can install our ca cert in system trusted CA certificate list.

But, What if we can't get root on our device ? ? ?

In that case we can patch our target APK to trust user certificate too.

By default android 9 and above if application doesn't define CA trust configuration, ANdroid defaults to system certificate only.

But we can update this value to trust user certificate too.

In androidmanifest.xml check <application> tag.

<application android:allowBackup="false" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:extractNativeLibs="false" android:hardwareAccelerated="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:largeHeap="true" android:name="com.appx.core.Appx" android:networkSecurityConfig="@xml/network_security_config" android:preserveLegacyExternalStorage="true" android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher" android:supportsRtl="true" android:theme="@style/AppThemeOwn">

If android:networkSecurityConfig="@xml/network_security_config" is present in it which means application do already implement some custom config, we just have to update res/xml/network_security_config.xmlfile.

or if not add `android:networkSecurityConfig="@xml/network_security_config" to <application> tag among other entries and create `res/xml/network_security_config.xml file.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false">
        <trust-anchors>
            <certificates src="user" />
            <certificates src="system" />
        </trust-anchors>
    </base-config>
</network-security-config>

See we explicitly told system that this app trust "user" certificate.

Now Rebuild apk and sign the apk and install it which will alow you to intercept raffic without root access.

Note: In modern devices you will face problem of split apks and in that case

  1. Fetch all split files as discussed earlier.

  2. Most probably Androidmanifest.xml and rest of the config files will be base.apk file. So decompile the base.apk

  3. Make required changes.

  4. rebuild the base apk.

  5. Now you have rebuilt base.apk and split apks. Use Objection tool to sign all apks

objection signapk *.apk
  1. or you can maually sign and align 4 apks using zipalign and apksigner tool as discussed earlier. (You don't have to align other split apks as you didn't change them )

  2. When APKs are ready you can perform

adb install-multiple base.objection.apk split.sobjection.apk ........

You should be go to got from here. But take it with some grain of salt.

This way of patching might break some things functionality wise or you might not be able to install at all and get some errors. As apktool decompilation and rebulding process is not flawless. It's your skill to find the solution of your problem.

Use VpnService API

Sometime you might not be able to intercept traffic from your application even if certificates and proxy are configured propelry. Because application might be trying to bypass system proxy settings. e.g

Direct Sockets: Some apps use low-level socket APIs (e.g., Socket or OkHttp) to establish direct connections, bypassing the HTTP proxy set at the system level.

Then we can use android vpnservice API

Android's VPNService API allows apps to intercept and redirect all network traffic at network level.

We can install this app and set proxy to our burp suite and set DNS to "system" setting. Using this we can capture traffic as it forces application to reroute traffic through our tunnel as this app registers itself as vpnservice which tells android device to send all raw tcp packets through this gateway and get rewritten by rethink app.

Dynamic Instrumentation

Install frida: https://frida.re/

pip3 install frida-tools

Install Objection: https://github.com/sensepost/objection

pip3 install objection

If you are doing it in virtual environment and you are getting this error

ModuleNotFoundError: No module named 'pkg_resources'

Here is fix: pip install --upgrade setuptools

Hooking app with frida

There are two ways to got from here

  1. Patching the apk with frida

  2. Installing frida server on android and dynamically hooking app at runtime.

Method 1:

  1. Go to the official Frida releases page: https://github.com/frida/frida/releases.

  2. Download same architecture(android devicee) and version as frida-cli on your machine.

  3. You can check your device architecture using:

    adb shell getprop ro.product.cpu.ab
  4. Push frida server to android device

adb push frida-server /data/local/tmp/
  1. Give execute permission

adb shell chmod +x /data/local/tmp/frida-server
  1. strat the server

adb shell /data/local/tmp/frida-server &
  1. Use frida cli to interact with app.

  2. List installed packages.

frida-ps -Uai
  1. attach to a running app

frida -U Process_Name

Method 2:

To inject Frida into an APK we can use objection:

objection patchapk -s apk_name.apk

Objection will extract, patch, re-pack, align and sign the application, and so it's a very fast and easy way to get Frida running.

Note that the application will wait on launch for Frida to connect to it, so to start the application we have to run:

frida -U process_name

Frida REPL (Read-Eval-Print Loop)

This terminal you see is just JS interpreter which means it can run JS code.

Run a js script

frida -U -l script.js process_name

anychanges to your script are auto reoloaded in memory. Which can be configured

%autoreload on/off

Hooking Java Classes implementation

Jadx-gui provides option to copy class as frida snippet.

In Frida, the Java.perform function is a wrapper that ensures your script is executed within the context of a valid Java Virtual Machine (VM) thread. This is essential because certain operations, such as interacting with Java classes or calling methods, require access to the Java VM, which might not be immediately available when your script is loaded.

When you call Java.perform, Frida:

  1. Schedules your callback to run in a valid Java thread.

  2. Ensures the current thread is attached to the Java VM if it isn't already.

  3. Executes your code within the context of the valid thread.

Example Usage

Without Java.perform

If you attempt to run Java API code directly without Java.perform in REPL or using a script file:

let ExampleClass = Java.use("io.hextree.fridatarget.ExampleClass");
console.log(ExampleClass.$new().returnDecryptedString());

This will likely throw an error because the current thread might not be attached to the Java VM:

arduinoCopyEditjava.lang.RuntimeException: Unable to get current thread

With Java.perform

Using Java.perform, the script will ensure the thread is correctly attached:

javascriptCopyEditJava.perform(() => {
    let ExampleClass = Java.use("io.hextree.fridatarget.ExampleClass");
    console.log(ExampleClass.$new().returnDecryptedString());
});

Frida Script to hook Activity ->onResumefunction to track which Activity class we are in:

Java.perform(() => {
	console.log("Frida script started.");

    	let ExampleClass = Java.use("android.app.Activity");

	ExampleClass.onResume.implementation = function() {

	console.log("Called Activity: ", this.getClass().getName());

	this.onResume(); 
	//calling original onResume func after our custom 
	//instructions are executed. otherwise app will just crash as we dont have 
	//all the logic of onResume with us here. after this execution flow will come back
	//to our interception here. and execute next instruction.
	
	console.log("done.");

	};
	
});

Note: frida doesn't replace original function, it just intercepts function calls and allows you to modifiy behviour of this call. You can always access original function with thiskeyword.

Why You Can Still Access the Original Function:

  • Overriding vs. Replacing: You're overriding the function's implementation, not replacing it entirely. The original method still exists, and you're allowed to call it within your custom hook.

  • this keyword: The this keyword refers to the current instance of the InterceptionFragment class, which means you're invoking the original function (with the altered argument) through the same object. This keeps the rest of the object context intact.

Frida-Trace

a tool from frida toolkit to trace function calls from of loaded classes in memory and its return values.

frida-trace.exe -U -j 'io.hextree.*!*'  -J "*Annoying*!*" Fridatarget

Search for java class included by -j and ignore java classes included by -J .

(Format: classname!methodname)

using frida we can trace function calls in native libraries too (JNI)

frida-trace.exe -U -I 'libhextree.so' -j 'io.hextree.*!*'  -J "*Annoying*!*" Fridatarget

Note: If in frida-trace you dont' see any output after clicking button. RUn frida-trace again as it loads new things in memory which was not loaded previously.

example time:

using frida-trace or jadx we can identify functions being called for a challenge.

public class DiceGameFragment extends Fragment {
    private String[] diceMapping = {"⚀", "⚁", "⚂", "⚃", "⚄", "⚅"};
    private int[] diceViewMapping = {R.id.dice_1, R.id.dice_2, R.id.dice_3, R.id.dice_4, R.id.dice_5};

    public int randomDice() {
        Random random = new Random();
        int randomNumber = random.nextInt(6);
        return randomNumber;
    }

    public void rollDice() {
        boolean won = true;
        for (int i = 0; i < 5; i++) {
            TextView v = (TextView) getView().findViewById(this.diceViewMapping[i]);
            int dice = randomDice();
            if (dice != 5) {
                won = false;
            }
            v.setText(this.diceMapping[dice]);
        }
        if (won) {
        
        /// you win
        }
        
        else {
        
        //you lose
        
        }
        
        

You can see if dice roll is not equal to 5 then we will lose. let's hook randomDicefunciton here to return always 5.

Java.perform(() => {
	console.log("Frida script started.");

	let DiceGameFragment = Java.use("io.hextree.fridatarget.ui.DiceGameFragment");
	DiceGameFragment["randomDice"].implementation = function () {
	console.log(`DiceGameFragment.randomDice is called`);
	return 5;
	};
	
});

Which allows us to win.

Last updated