Taking a Prediction APK Apart: A Ten-Minute Method

There is a whole category of Android apps built around a single claim: that they can forecast the next value produced by a live server-side system. The claim is testable with standard tooling, and the check takes about ten minutes.

What follows is a method rather than a verdict on any one app. It generalises to anything you want to inspect — a utility, a game helper, a "booster" — and it is worth having in your hands because it answers a question that reviews and screenshots cannot.

The examples below come from one corner of that category, because it is unusually consistent. These apps almost all target the same game: a crash round where a multiplier climbs from 1.00x until it stops, with the history of past rounds displayed on screen. A short cycle and a visible history are what make it a convenient thing to attach a forecasting claim to, and the resulting apps are built from the same handful of parts, which makes them a good first exercise.

The toolkit

Three tools cover almost everything.

apktool decodes resources and turns the binary AndroidManifest.xml back into readable form.

jadx decompiles the DEX bytecode into Java you can read and search. The GUI version has a global search box that is the single most useful feature here.

mitmproxy or Charles captures traffic, though as you will see, you often do not need it.

Everything below runs on a file you downloaded yourself, on your own machine.

Step one: read the manifest

apktool d app.apk -o out/

Open out/AndroidManifest.xml and look at the permission list.

An app that genuinely queries a remote server needs one line:

<uses-permission android:name="android.permission.INTERNET"/>

If that line is absent, the question is settled before you read a single line of code. The app cannot reach any server, so whatever it displays is produced locally. This happens more often than you would guess.

While you are in there, check res/xml/network_security_config.xml if it exists, and note the minSdkVersion. A very low minSdk usually means the app was built from an old template.

For a faster first look, aapt dump badging app.apk prints permissions, launchable activity and SDK levels without unpacking anything.

Step two: look for the network stack

Open the APK in jadx-gui and use global search for the things that cannot be renamed by an obfuscator:

  • okhttp3
  • HttpURLConnection
  • Retrofit
  • WebSocket
  • https://

Framework and library class names survive ProGuard and R8. Application classes get renamed to a, b, c, but okhttp3.OkHttpClient stays okhttp3.OkHttpClient, because renaming it would break the dependency. That asymmetry is what makes this search reliable even on obfuscated builds.

Two results are common. Either you find a network stack pointing at an ad SDK and nothing else, or you find no outbound calls at all beyond crash reporting.

If the app is a WebView wrapper, the code will be thin and the content will sit in assets/. Open that folder and read the bundled HTML and JavaScript directly — it is not compiled, and the logic is right there.

Step three: find where the number comes from

Search the decompiled source for:

  • Math.random
  • java.util.Random
  • SecureRandom
  • nextInt
  • System.currentTimeMillis

Then look at how the result reaches the screen. The recurring pattern is a local random value, a Handler.postDelayed or CountDownTimer that spaces out the display, and a progress animation between the two.

Decompiled, it usually fits in twenty lines and reads close to this:

public void onClick(View v) {
    this.progress.setVisibility(View.VISIBLE);
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            double d = 1.0d + (Math.random() * 9.0d);
            a.this.result.setText(String.format("%.2f", Double.valueOf(d)));
            a.this.progress.setVisibility(View.GONE);
        }
    }, 3000L);
}

The three-second delay is worth noticing on its own. A local computation returns instantly; the pause exists so the output arrives at the pace of something that did work.

When the code will not read

Two things get in the way of static analysis, and they call for different responses.

Obfuscation renames classes and can encrypt string constants. Renaming does not stop the search described above, because library names survive. Encrypted strings do hide URLs, but the decryption routine has to exist somewhere in the same APK — find the method that returns a String from a byte[] and the constants become readable again.

Packing is a bigger obstacle. A packed app ships a small classes.dex whose only job is to load a native library that unpacks the real code at runtime. You can spot it in seconds: classes.dex is unusually small, Application.attachBaseContext calls System.loadLibrary, and lib/arm64-v8a/ holds a .so much larger than the Java side.

At that point static analysis stops being cheap. It is worth saying plainly that this is where most people should stop as well — and that the airplane-mode test below still returns a clean answer regardless of how the code is packaged.

Step four: the airplane-mode test

This is the fastest check and needs no tooling at all.

Put the device in airplane mode and open the app. If it still produces output, nothing is arriving from a server, because nothing can. One minute, no decompiler, and the result is not open to interpretation.

If the app does require connectivity, capture the traffic and look at where it goes. On Android 7 and above, user-installed CA certificates are not trusted by default for app traffic, so the practical route is an emulator rather than a phone.

The setup is short. Create an AVD on a Google APIs image — not a Google Play image, which blocks root — and start it with emulator -avd NAME -writable-system. Then adb root, adb remount, and push the proxy's CA into /system/etc/security/cacerts/ under its subject-hash filename. From there mitmproxy sees the plaintext.

Even without decrypting anything, the DNS lookups alone tell you which hosts are involved, and that is often enough. Calls going only to an analytics or ad SDK answer a different but related question: they show where the revenue comes from, which is worth knowing when you are trying to work out what the app is for.

Reading the result precisely

It is worth being exact about what each finding establishes, because that is the part people get wrong.

No INTERNET permission — conclusive. Output is local.

INTERNET present, no calls except to an ad SDK — strong. The app connects, but not to anything that could carry game state.

Output continues in airplane mode — conclusive for that session.

Obfuscated code you cannot fully read — not evidence either way on its own, but the framework-class search above still works, and so does the airplane-mode test.

A screenshot, a review, or a video — establishes nothing about the code, in either direction. This is the gap the method closes.

The distinction matters most where these apps are distributed hardest. Aviator runs on operators across East and Southern Africa, and in markets like Malawi the apps arrive as APK files shared directly rather than through a store, which is exactly the case this method was written for. Regional guides have started documenting the same tests — an aviator predictor breakdown written for that market lists which checks are reproducible and which only look like evidence, and it lines up with the four steps above.

Where the line sits

Worth stating once, because it comes up. Decompiling an application you downloaded, on your own device, to understand what it does is ordinary practice — it is how security review, malware triage and compatibility work all get done. Redistributing a modified build, or republishing someone's code, is a different activity governed by different rules.

Keeping the two apart also keeps the write-up useful. A report saying "the app produces output with the radio off, here is the manifest and here is the decompiled method" is reproducible by anyone. A report saying "this app is fake" is not.

The short version

Unpack with apktool, read the permission list, search jadx for okhttp3 and Math.random, then run the app in airplane mode. Four steps, ten minutes, and you replace an opinion with something you can repeat.

The technique is worth practising on apps you already trust. Reading a decompiled manifest and following a value from a random call to a text view is a small skill, and it stays useful long after the specific app that prompted it is gone.