How it works

APK Hound is a mini pipeline I built that analyzes an APK file and produces a call graph of the network API calls most likely to be found in malware. It combines static analysis, implemented with Soot and FlowDroid, with dynamic analysis, implemented with Frida. The two case studies below run Android malware through it: AndroRAT and iBanking.

The static stage uses FlowDroid to set up the Android application in a way Soot can understand, while Soot constructs the call graph. The pipeline then compares the edges in the call graph against a network API list containing methods from java.net, javax.net, javax.net.ssl and android.net. The result is a list of network API calls that includes the caller method and the called method.

The dynamic stage uses Frida to hook methods at runtime, and it hooks the network APIs that static analysis detected. The final result shows which APIs were actually called during runtime and what parameters were passed to them.

  1. Build the call graphFlowDroid sets the APK up for Soot, and Soot constructs the call graph.
  2. Match against the network API listEach edge is compared with the list, producing caller and callee pairs.
  3. Hook the matches with FridaThe detected network methods are hooked while the app runs in an emulator.
  4. Read the runtime outputWhich APIs actually fired, and the parameters passed to them.

Results at a glance

AndroRATiBanking
Call graph353 edges1,588 edges
Static hits4 network API edges1 valid edge (a second, null edge was skipped)
Hooked with Fridajava.net.Socket.<init>(String, int)URL.openConnection() and HttpClient.execute()
Runtime resultIntercepted the C2 connection; the address is hardcoded and privateopenConnection() never ran; execute() was intercepted, calling /android/sms/ping.php on a private C2 address
EmulatorAndroid 13Android 7.0

AndroRAT

Static analysis

I fed the AndroRAT APK into the static analysis pipeline, and it detected the following network API calls:

<out.Connection: boolean connect()><java.net.Socket: void <init>(java.lang.String,int)>
<out.Connection: boolean connect()><java.net.Socket: java.io.InputStream getInputStream()>
<out.Connection: boolean connect()><java.net.Socket: java.io.OutputStream getOutputStream()>
<in.Receiver: void <init>(java.net.Socket)><java.net.Socket: java.io.InputStream getInputStream()>
Static analysis output for Androrat.apk: a 353-edge call graph and four network API hits.

The most interesting one is <out.Connection: boolean connect()> -> <java.net.Socket: void <init>(java.lang.String,int)>. out.Connection.connect() calls the Socket constructor that takes a host string and a port, which most probably means this part of the malware is establishing the connection to the C2 server. For that reason, it's the method I hooked with Frida.

Setting up the Frida server

Running frida-server on the emulator. PowerShell rejects a bare &, so it has to go in quotes.

Installing the APK then failed with an error saying the malware targets a deprecated version of the Android SDK. I was about to reinstall the AVD with an older API, but I found that the --bypass-low-target-sdk-block flag overrides the check:

The install fails with INSTALL_FAILED_DEPRECATED_SDK_VERSION, then succeeds with the bypass flag.

AndroRAT's package name is my.app.client, which is a suspiciously generic name for any app. This is the Frida hook I used on it:

hook.js · java.net.Socket.$init
Java.perform(function () {
  const className = "java.net.Socket";
  const methodName = "$init";
  const targetClass = Java.use(className);

  const targetMethod = targetClass[methodName].overload("java.lang.String", "int");

  targetMethod.implementation = function (parameter1, parameter2) {
    send({
      api: className + "." + methodName,
      parameters: parameter1.toString() + parameter2.toString()
    });
    return targetMethod.call(this, parameter1, parameter2);
  };
});
roadblocks

Frida kept crashing the APK. I changed the hook repeatedly with no success. Changing Frida versions helped a tiny bit: Frida now attached to Chrome, but it still failed on the malware, and other malware samples kept crashing too. Logcat kept showing the same error: JNI DETECTED ERROR IN APPLICATION: jobject is an invalid JNI transition frame reference.

After 3 to 4 days of being stuck, I downgraded to Android 13 just to see if anything would change, and to my surprise, it worked. Bypassing the SDK restriction wasn't a good idea after all.

Then AndroRAT wouldn't execute any of its code even though Frida was attaching. I tried tracing the call graph, which didn't help, and after a few hours the fix turned out to be ridiculously simple: click the button in AndroRAT's GUI that starts it.

Two Frida runs against my.app.client, both ending in "Process terminated" right after the main thread resumes.

Results of the Frida hook

The hook catches java.net.Socket.$init. The hook concatenates its two arguments, so the host and port run together in the output.

AndroRAT uses this constructor to connect to the C2 server, and here the address it tries to reach is a local IP. I also opened the APK in JADX to see it for myself, and the address is hardcoded inside AndroRAT's code:

JADX view of loadPreferences() in ProcessCommand, with the default IP and port highlighted.
C2 host
192.168.0.12 (private, hardcoded)
C2 port
9999
Found by
Frida hook on Socket.<init>, confirmed in JADX

iBanking

The second sample is iBanking, an Android banking Trojan and spyware. It can intercept SMS messages, which is one of its most significant features since banks commonly use SMS for two-factor authentication. It can also collect contact and device information, monitor calls, and communicate with C2 servers.

The flow is the same as in the AndroRAT case study. I start with static analysis using Soot/FlowDroid to construct a call graph and detect network API calls. If all of iBanking's interesting network methods are detected, I tailor the Frida hook from the AndroRAT case study to them. I also analyze the APK in JADX to look for network APIs that Soot/FlowDroid misses, and add those to the list.

Static analysis

I passed the APK to Soot and two edges were detected. The first was null and Soot skipped it, so the only call it detected was java.net.URL.openConnection(), called in upload_file():

<com.soft360.web.AudioRecorder: void upload_file()><java.net.URL: java.net.URLConnection openConnection()>
Static analysis output for the iBanking APK: a 1,588-edge call graph, one skipped null edge, one network API hit.

This is most likely a method written to upload audio files to the C2 server, possibly recorded calls. Those calls might be aimed at intercepting two-factor authentication codes that banks send through a phone call, since iBanking is a banking trojan that cares mostly about financial credentials.

However, I don't believe this is the only networking method the malware uses, so I dug deeper with JADX. I found two more interesting things.

Finding 1: the C2 address

I traced the C2 server address from upload_file() all the way back to arrays.xml. It's 192.168.1.56, a private IP. This is probably a sample configured for development and testing, since a public C2 address reachable over the internet wouldn't be a private IP.

The tracing path was:

upload_file()→ R.array.adm_domens→ arrays.xml→ 192.168.1.56

upload_file() contains this line:

String urlServer = "http://" + AService.context.getResources().getStringArray(R.array.adm_domens)…

The context is initialized in onCreate() of the AService class, and the line retrieves the ID of adm_domens through the R class. Looking up adm_domens in JADX took me to arrays.xml, which contains this:

arrays.xml: the adm_domens array holds 192.168.1.56.

Finding 2: the Apache HTTP client

The second finding is another way the malware communicates with the C2 server. It imports the Apache HTTP client library in several parts of the code, so I added the relevant methods to the network API list and ran the static analysis again.

roadblock: execute() missing from the call graph

The most valuable method in that library, execute(), wouldn't be loaded into the call graph. I found that out by printing the entire call graph when string matching failed to detect the method. The other constructors and methods from the Apache HTTP client library did show up, which points to the problem being specific to execute().

To debug it, I wrote a separate Java Soot class that prints the Jimple representation of the caller method, to check whether Soot is even interpreting the DEX bytecode correctly.

Jimple of the caller method, with the HttpClient.execute() call highlighted.

Soot does detect the execute() call here, but it still doesn't load it into the call graph. I suspect the issue is with Soot itself, so I continued the analysis and hooked it later with Frida. The methods I hooked were:

java.net.URL.openConnection() and org.apache.http.client.HttpClient.execute()

Dynamic analysis

On modern versions of Android the process kept crashing, so I downgraded to Android 7.0. Installing iBanking again then produced a different error:

SecurityException: the app isn't allowed to send the android.provider.Telephony.SMS_RECEIVED broadcast.

Android shut down the line containing sendBroadcast(i) because the app had no permission to run it. I fixed it by unpacking the APK with apktool, patching that line out, and building it again.

The app finally ran, and it showed some German text:

The app's German-language screen, titled "Bankbescheinigung" (bank certificate) and referencing Postbank.

After that, I finally got to run Frida without it crashing like it always does:

Frida attached to com.BioTechnology.iClientsService4, and this time the process survives.

The first method I wanted to hook, openConnection(), never executed, so I moved on to execute(). That one did run, and I intercepted it along with its parameters. The method tries to communicate with the C2 server at the /android/sms/ping.php endpoint.

Frida intercepts DefaultHttpClient.execute, and the parameter is http://192.168.1.56/android/sms/ping.php.
C2 host
192.168.1.56 (private, read from arrays.xml)
Endpoint
/android/sms/ping.php
Found by
Frida hook on HttpClient.execute(), after JADX and Jimple analysis

A note on AI usage

I used AI in limited ways in some parts of this project. The repetitive parts that required no real cognitive work were done with AI: building the network API list quickly, tracing the call graph, and reading logs.

I also used AI to help me understand concepts and tools, then implemented the logic behind Soot, FlowDroid and the Frida hook myself.