Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding automatic patching for semi-corrupted APKs #2298

Merged
merged 3 commits into from
Oct 10, 2024

Conversation

qfalconer
Copy link
Contributor

Threat actors are yet again exploiting another lenient-ism in the Android APK loader machinery. I've recently found two APKs whose AndroidManifest.xml ZIP headers was semi-corrupted in a way that prevents ordinary tools (including Jadx) from correctly processing them. Android was loading them just fine, though.

Corruption of the headers

The APKs are attached in the ZIP below (password: infected) and belongs to two (presumably) very different TAs: one comes from a campaign against Russians using OnlyFans as a bait, the other targets the user of an Italian bank. Thus, I'm inclined to believe this corruptions are intentional and will be used again in the future.

Since APKs are just ZIPs, a little knowledge of the PKZIP format is required. These APKs have the following anomalies:

  • The compression method set for a corrupted entry in the Central Directory Header (CDH) is invalid. The compression is specified as a 16-bit number, a random one is used. The original compression method was none (i.e. no compression at all). I guess Android default to that in case of an invalid value.
  • The compressed size set for a corrupted entry in the CDH is invalid. I assume Android just look at the uncompressed size file in case of no compression.
  • The same two corruptions of above are also applied at the Local Header level.
  • The Local Header for a corrupted entities also have invalid extra data. I guess Android just skip that (since it's useless).

Jadx patch

This corruption can be undone, this PR attempts to introduce such undoing into Jadx. I'm not totally satisfied with the approach I took but it was the quickest.

Since the APK is visited everywhere with the standard ZipFile class, I opted for subclassing it, copying the original file into a temporary one and preprocessing it before calling the super constructor with the path of the temporary file. This allows for a drop-in replacement of java.util.zip.ZipFile with jadx.core.utils.files.ZipFile. And that's just what the code in this PR does.

The drawback is that a temporary file is created if the APK is detected as corrupted (if it is not, no temporary file is created). The temporary file is not deleted since it couldn't find a way to store the temporary filename in the instance without hacky code (it will be deleted by the OS, though).
Note however that the APK signature verification panel shows an exception because the original file is passed to the Android framework and this time it cannot correctly process it. I don't know if this is a show stopper.

I also forgot to comment the code 😳.

If somebody have suggestions they are welcome.

How the APK is patched

For the sake of completeness, the patching is done as follow when an invalid compression method is found:

  1. Set the compression method to none in the Local and Central Directory headers.
  2. Set the compressed size to equal the uncompressed size in the Local and Central Directory headers.
  3. Set the extra len field to 0 in the Local Header and move the local data back by that amount.

Before

before

After

after

semi-corrupted-apks.zip

@qfalconer qfalconer marked this pull request as ready for review October 10, 2024 09:09
@skylot
Copy link
Owner

skylot commented Oct 10, 2024

@qfalconer great work! 👍

I'm not totally satisfied with the approach I took but it was the quickest.

This is actually a very good approach: it allows to easily fall back to default implementation if not needed and easy to improve current code as a drop-in replacement.

This PR also fixes #2171 🎉
I tried to implement my own zip reader, and it worked for most zip files, but it still not support many features like Zip64. Also, it is hard to fall back to default zip parser, so I have not finished it 😢

As a possible improvement for future: we can replace input files directly in JadxArgs, so all future processing uses patched file directly (now zip file opens and got patched 7 times because of how plugins processing files 🤣).

@skylot skylot merged commit 964bd62 into skylot:master Oct 10, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants