When you start looking at mobile application security, one distinction appears almost immediately: static analysis and dynamic analysis.

The names make them sound more complicated than they are.

Static analysis means examining an application without actually running it.

Dynamic analysis means examining what the application does while it is running.

Both approaches can reveal security problems. Both also have blind spots.

And if your goal is to understand the actual behavior of a mobile application rather than simply run a scanner and export a report, you normally need both.

In this article, we’ll look at the practical workflow from an AppSec/security specialist’s point of view, primarily using Android examples. Most of the methodology also applies to iOS, although the tooling and platform restrictions are different.

Important: Only analyze applications and systems you own or have permission to test.


The APK Is Already Evidence

One useful thing about mobile security is that the application itself gives you a surprisingly large amount of information.

An Android APK is not some completely opaque executable.

It is essentially a package containing things such as:

  • compiled application code;
  • AndroidManifest.xml;
  • resources;
  • certificates;
  • native libraries;
  • configuration files;
  • assets.

Before launching the application, we can already start answering security questions.

For example:

  • What permissions does the app request?
  • What components are exported?
  • Does it allow cleartext traffic?
  • Are there hardcoded API endpoints?
  • Are API keys or tokens embedded in resources?
  • Does it contain native libraries?
  • Does the application attempt to detect root?
  • Does it contain certificate-pinning logic?
  • Are debug features accidentally enabled?
  • What third-party SDKs are included?

This is the territory of static analysis.


Static Analysis

Static analysis is the process of examining an application without executing it.

For Android applications, the first step is usually obtaining the APK and unpacking or decompiling it.

A very simple toolkit might include:

jadx
apktool
apkanalyzer
apksigner
grep
strings

Different tools answer different questions.

There is no single “reverse engineering button.”


Start With the Manifest

The Android manifest is one of the highest-value files in an initial review.

With apktool:

apktool d application.apk -o application_decoded

Then inspect:

application_decoded/AndroidManifest.xml

You might find permissions such as:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Permissions alone are not vulnerabilities.

The interesting question is whether they make sense for the application’s functionality and how the application actually uses them.

The manifest can also reveal application components:

<activity>
<service>
<receiver>
<provider>

Pay particular attention to components exposed outside the application.

For example:

android:exported="true"

An exported activity is not automatically vulnerable either.

But now you have something worth testing dynamically:

Can another application or ADB invoke this component in a way the developer did not expect?

That is a good example of static analysis generating a hypothesis for dynamic analysis.


Decompile the Code

For Android, jadx is usually my first choice for quickly understanding application logic.

jadx-gui application.apk

Or:

jadx -d source application.apk

You can now search for interesting strings and APIs.

Useful search terms often include:

http://
https://
api
token
secret
password
Authorization
Bearer
WebView
setJavaScriptEnabled
TrustManager
HostnameVerifier
CertificatePinner
root
debug
SharedPreferences
getExternalStorage

Do not treat this as a magic vulnerability keyword list.

The goal is to understand the application’s architecture.

For example, you may discover:

https://api.example.com/v2/

Then find the networking client responsible for communicating with it.

Then find an authorization interceptor.

Then discover how the access token is obtained.

That is already much more useful than a generic scanner telling you that the application “uses network communication.”


Look for Hardcoded Secrets --- Carefully

One of the easiest static checks is searching for interesting strings.

For example:

grep -RniE "api[_-]?key|secret|token|password" application_decoded/

You can also run:

strings application.apk | grep -i token

But this produces a lot of noise.

Not every API key is a secret.

Some identifiers are intentionally shipped with the client. Some keys are restricted by package name, signing certificate, API scope, server-side policy, or quota.

So finding:

AIza...

does not automatically mean:

Critical vulnerability! Production credentials exposed!

The real question is:

What can somebody actually do with this value?

Security analysis is about impact, not keyword matching.


Native Libraries Matter Too

Not all application logic lives in Java or Kotlin.

Check:

lib/arm64-v8a/
lib/armeabi-v7a/
lib/x86_64/

You may find .so libraries containing:

  • cryptographic logic;
  • anti-tampering checks;
  • root detection;
  • proprietary protocols;
  • embedded configuration;
  • JNI implementations.

A quick first pass:

strings libsomething.so

For deeper work, tools such as Ghidra can help analyze native code.

This is also where static analysis becomes significantly more difficult.

Decompiled Java is often relatively readable.

Optimized ARM64 assembly is a different experience entirely.


Static Analysis Has a Major Problem

Static analysis tells you what the application can potentially do.

It does not necessarily tell you what it actually does.

Imagine finding this endpoint:

https://analytics.example.com/upload

Interesting.

But several questions remain:

  • When is it called?
  • What triggers it?
  • What data is sent?
  • Is authentication required?
  • Does it happen before login?
  • Does it happen in the background?
  • Does the app send device identifiers?
  • Is the payload encrypted at the application layer?
  • Does behavior change depending on account state?

Reading code may answer some of these questions.

Running the application can answer them much faster.

Now we move to dynamic analysis.


Dynamic Analysis

Dynamic analysis means observing and manipulating the application while it executes.

This is where the app stops being a collection of files and starts behaving like a system.

A basic Android lab might contain:

  • an emulator or test device;
  • ADB;
  • Burp Suite, mitmproxy, or Charles;
  • Frida;
  • Objection;
  • logcat;
  • optionally a rooted emulator/device.

ADB alone already gives you a lot.

Check the connected device:

adb devices

Find the application:

adb shell pm list packages | grep example

Inspect package information:

adb shell dumpsys package com.example.app

Launch an activity:

adb shell am start -n com.example.app/.MainActivity

Now we can compare what we saw statically with what actually happens at runtime.


Watch the Logs

One of the simplest dynamic-analysis tools is:

adb logcat

You would be surprised how often applications leak useful information into logs.

Look for:

  • URLs;
  • authentication errors;
  • stack traces;
  • internal object values;
  • tokens;
  • user identifiers;
  • API responses;
  • debug messages.

You can filter output:

adb logcat | grep -i example

or:

adb logcat | grep -iE "token|auth|http|error"

Again, the point is not just to grep random security words.

Perform an action in the application and observe what changes.

For example:

  1. clear the logs;
  2. open the login screen;
  3. authenticate;
  4. perform one specific action;
  5. inspect the resulting logs.

This makes the result reproducible.

That matters enormously when reporting security findings.


Observe Network Traffic

Network inspection is probably one of the most useful parts of dynamic mobile analysis.

Configure the device or emulator to use an interception proxy such as Burp Suite or mitmproxy.

Now perform normal application actions:

Launch app

Login

Open profile

Change setting

Upload file

Watch the corresponding requests.

You may discover:

  • undocumented API endpoints;
  • excessive information returned by the server;
  • insecure authorization;
  • sensitive information in requests;
  • weak session handling;
  • unnecessary analytics;
  • unexpected third-party services.

This is where application behavior becomes much easier to understand.

Instead of guessing how the networking layer works from decompiled code, you can observe:

POST /api/v1/profile
Authorization: Bearer ...
Content-Type: application/json

and examine the actual request and response.


And Then Certificate Pinning Appears

Sometimes you configure your proxy and everything works.

Sometimes the app immediately refuses to connect.

One common reason is certificate pinning.

Static analysis may already have given you clues.

For example, you might find references to:

CertificatePinner
TrustManager
X509Certificate
checkServerTrusted

Dynamic instrumentation tools such as Frida are useful here because they allow you to inspect or alter application behavior at runtime in an authorized test environment.

This introduces an important difference between the two approaches.

Static analysis asks:

Where is this security control implemented?

Dynamic analysis asks:

What happens if I interact with this control while the application is running?


Runtime Instrumentation With Frida

Frida is one of the most useful tools for dynamic mobile application analysis.

It allows you to inject JavaScript into a running process and hook functions.

For example, suppose the application contains:

boolean isDeviceRooted()

Static analysis might show you the implementation.

Dynamic instrumentation can let you observe when the method is called and, in a test environment, change its return value.

Conceptually:

SomeClass.isDeviceRooted.implementation = function () {
    console.log("isDeviceRooted() called");
    return false;
};

This can help answer questions such as:

  • Is root detection actually executed?
  • Which check causes the application to stop?
  • Does behavior change after bypassing the check?
  • Is the control client-side only?
  • What code path becomes reachable afterward?

This is much more interesting than simply declaring:

The application has root detection.

We want to understand its security impact.


Objection: Faster Interactive Exploration

Objection builds on Frida and provides an interactive interface for many common mobile-testing tasks.

For example, during an authorized Android assessment you can use it to explore:

  • activities;
  • services;
  • application classes;
  • file storage;
  • runtime methods;
  • common platform protections.

Frida gives you flexibility.

Objection often gives you speed.

I would not treat them as competing tools.

Use whichever gets you to the answer faster.


Behavior Is More Than Network Traffic

A common mistake is to think dynamic analysis means only intercepting HTTP requests.

Mobile applications interact with the operating system constantly.

You should also observe things such as:

  • files created by the application;
  • databases;
  • SharedPreferences;
  • clipboard usage;
  • screenshots;
  • notifications;
  • deep links;
  • intents;
  • background services;
  • WebViews;
  • external storage;
  • IPC between applications;
  • runtime permissions.

For example, after logging in, inspect the application’s data directory on an appropriate test device.

You might discover:

shared_prefs/
databases/
files/
cache/

Then ask:

What changed after authentication?

Maybe a token appears in SharedPreferences.

Maybe a SQLite database contains user information.

Maybe cached API responses remain after logout.

Maybe a supposedly temporary file never gets deleted.

These are behavioral findings even though no network attack is involved.


Build Tests Around Actions

Randomly clicking through an application while Burp and logcat are running is better than nothing.

But a structured workflow is much more useful.

Treat application behavior almost like a test case.

For example:

Scenario: User Logout

Before logout

Check:

Authentication token
Cookies
SharedPreferences
Database
Cached files
Running services

Perform

Tap Logout

After logout

Check the same things again.

Then try:

  • replaying an old request;
  • reopening a protected activity;
  • using the Back button;
  • restarting the application;
  • invoking a deep link;
  • restoring the application from background.

Now you are not simply “doing security testing.”

You are testing a specific security property:

Does logout actually terminate the user’s authenticated state?

This is where QA-style thinking becomes extremely useful in AppSec.


Static + Dynamic Is the Real Workflow

The strongest workflow is not:

Static analysis
OR
Dynamic analysis

It is:

Static analysis

Create hypotheses

Dynamic analysis

Observe behavior

Return to code

Explain why behavior occurs

Verify impact

Suppose static analysis reveals:

<activity
    android:name=".AdminActivity"
    android:exported="true" />

That is interesting.

Now dynamically test:

adb shell am start -n com.example.app/.AdminActivity

What happens?

Possibility 1:

The activity opens, but immediately verifies authentication.

Probably fine.

Possibility 2:

The activity opens and exposes administrative functionality.

Now you may have a real security issue.

The manifest gave you the lead.

Runtime behavior gave you the evidence.


Another Example: Deep Links

Static analysis might reveal:

<data
    android:scheme="example"
    android:host="payment" />

Now you know the application accepts something like:

example://payment

But that alone tells you very little.

Dynamic testing can answer:

  • What parameters does the handler accept?
  • Does it require authentication?
  • Can parameters alter sensitive actions?
  • Can another application trigger it?
  • Does it load arbitrary URLs?
  • Does it expose internal application screens?

You can invoke a test deep link with ADB:

adb shell am start \
  -a android.intent.action.VIEW \
  -d "example://payment"

This is the recurring pattern:

Static analysis tells you where to look. Dynamic analysis tells you what happens.


Automated Scanners Are Useful --- But They Are Not the Analysis

Tools such as MobSF can automate a significant part of the initial inspection.

That is useful.

You can quickly get information about:

  • permissions;
  • exported components;
  • certificates;
  • URLs;
  • trackers;
  • insecure configurations;
  • code patterns.

But scanner output should be treated as input to an investigation, not the final result.

A scanner might say:

Application allows cleartext traffic.

That sounds bad.

But does the production application actually send sensitive data over HTTP?

Maybe yes.

Maybe the configuration exists for a local development endpoint that is never reachable in production.

The difference matters.

Likewise, a scanner may find a hardcoded key.

Your job is to determine whether the key has meaningful privileges.

Security findings need context.


What Static Analysis Is Good At

Static analysis is particularly useful for discovering:

  • application attack surface;
  • exported components;
  • permissions;
  • deep-link handlers;
  • embedded URLs;
  • hardcoded configuration;
  • suspicious API usage;
  • cryptographic implementations;
  • third-party libraries;
  • root/emulator detection;
  • certificate-pinning implementations;
  • hidden or unused functionality.

It is also excellent for answering:

Where in the code is this behavior implemented?


What Dynamic Analysis Is Good At

Dynamic analysis is particularly useful for understanding:

  • actual network communication;
  • runtime authentication behavior;
  • session handling;
  • local data changes;
  • application state transitions;
  • runtime protections;
  • WebView behavior;
  • API interaction;
  • component exposure;
  • behavior under manipulated conditions.

It answers a different question:

What actually happens when I do this?


Static Analysis Can Lie to You

Not literally, of course.

But decompiled code can easily lead you toward the wrong conclusion.

You may see:

if (isRooted()) {
    blockApplication();
}

and assume rooted devices are blocked.

Then you run the application on a rooted test device and discover the code path is never executed in the production build.

Or perhaps it only executes for a specific feature.

Or the decompiler reconstructed the code poorly.

Or the relevant method belongs to an unused library.

Code presence is not proof of behavior.


Dynamic Analysis Can Lie to You Too

Runtime testing has the opposite problem.

You only observe the paths you execute.

If you never trigger a particular feature, you may never see its behavior.

Maybe an insecure endpoint is called only:

  • after seven days;
  • when push notification data arrives;
  • for premium accounts;
  • on a specific Android version;
  • when the device enters a certain state;
  • after a remote feature flag changes.

Dynamic analysis shows reality --- but only the reality you managed to trigger.

Static analysis can expose paths you did not know existed.

That is why the approaches complement each other so well.


A Practical Mobile AppSec Workflow

For a new Android application, I would roughly approach it like this.

1. Identify the package

Collect:

APK
Package name
Version
Version code
Signing certificate
Target SDK
Minimum SDK

Check the signature:

apksigner verify --print-certs application.apk

2. Inspect the manifest

Look at:

Permissions
Exported activities
Services
Receivers
Providers
Deep links
Backup settings
Network security configuration
Debug settings

3. Decompile

Open the APK in jadx.

Search for:

Endpoints
Authentication logic
Storage
WebViews
Crypto
Root detection
Pinning
Interesting SDKs

4. Run the application

Use a controlled emulator or device.

Watch:

adb logcat

Inspect package state and interact with exposed components.

5. Proxy the traffic

Observe what the application actually sends and receives.

Map UI actions to API calls.

6. Inspect local state

Compare files and application state before and after important actions.

7. Instrument interesting code

Use Frida or Objection when runtime behavior needs deeper inspection.

8. Verify findings

Do not report:

Potentially insecure exported activity.

when you can test the activity and explain exactly what an attacker could achieve.


Think in Hypotheses, Not Tools

This is probably the biggest difference between simply learning security tools and actually doing useful application security work.

Do not start with:

What can I do with Frida today?

Start with:

I think the application stores the session locally after logout. How can I prove or disprove that?

Then choose the tool.

Maybe the answer requires:

ADB + filesystem inspection

Another hypothesis may require:

jadx + Burp

Another:

apktool + ADB intents

Another:

Frida

Tools are implementation details.

The investigation is the important part.


Evidence Matters

A useful AppSec finding should be reproducible.

Instead of writing:

The app may expose sensitive functionality through an exported activity.

Show:

adb shell am start -n com.example.app/.InternalActivity

Then explain:

1. No authentication is required.
2. The activity displays account information.
3. It can be launched by another application.
4. The behavior reproduces after a clean installation.

Now developers have something they can understand, reproduce, and fix.

This is one area where software-testing experience translates extremely well into security.

A good security report looks surprisingly similar to a good bug report.

You still need:

Preconditions
Steps
Actual result
Expected security behavior
Evidence
Impact

The difference is that you also need to think about an attacker.


Final Thoughts

Static and dynamic analysis are not two competing ways to inspect a mobile application.

They answer different questions.

Static analysis tells you:

What exists inside the application?

Dynamic analysis tells you:

What does the application actually do?

The interesting work happens when you connect them.

Find an exported component statically.

Invoke it dynamically.

Find certificate-pinning code statically.

Observe the network behavior dynamically.

Find token-storage logic statically.

Log in, log out, restart the app, and inspect what survives dynamically.

Find a suspicious WebView configuration statically.

Then determine what content can actually reach that WebView at runtime.

That loop is what turns reverse engineering into application security analysis.

And you do not need twenty tools to start.

For Android, a very capable initial toolkit is already:

ADB
jadx
apktool
Burp Suite or mitmproxy
Frida
Objection

Learn what the application is supposed to do.

Form a hypothesis.

Inspect the code.

Run the application.

Manipulate the environment.

Collect evidence.

Then explain the security impact.

That is much closer to real AppSec work than pressing Scan and waiting for a red report.