Skip to content

fix: tolerate spurious encrypted zip CEN headers on decode - #4208

Open
atirna wants to merge 10 commits into
iBotPeaches:mainfrom
atirna:fix/encrypted-cen-header-4028
Open

atirna wants to merge 10 commits into
iBotPeaches:mainfrom
atirna:fix/encrypted-cen-header-4028

Conversation

@atirna

@atirna atirna commented Aug 16, 2026

Copy link
Copy Markdown

Description

I added a dedicated brut.j.zip module for classic single-disk APK ZIPs that only have the spurious encrypted-entry flag which Android accepts but java.util.zip.ZipFile rejects.

ZipRODirectory now swaps its backing ZipFile for RepairingZipFile. The repair stays inside the ZIP layer, clears only that flag in a temporary copy, and leaves the source archive unchanged. Prefixed ZIP offsets and EOCD signatures inside comments are handled; ZIP64 and multi-disk inputs remain explicitly unsupported instead of being partially rewritten. No Apktool decode or rebuild code changes are needed.

The new module is included in Maven publication metadata so brut.j.dir resolves org.apktool:brut.j.zip with the project version.

Fixes #4028

Verification

  • ./gradlew :brut.j.zip:test on Java 8, 11 and 17 (2/2 each)
  • ./gradlew :brut.apktool:apktool-lib:test (186/186)
  • ./gradlew :brut.j.zip:generatePomFileForMavenJavaPublication :brut.j.dir:generatePomFileForMavenJavaPublication

atirna added 2 commits August 17, 2026 00:54
APKs that AOSP accepts can set the encrypted-entry bit (or bogus compression
methods) without actually encrypting entries. java.util.zip rejects those CEN
headers while libziparchive ignores them.

Repair headers in a temp copy when ZipFile fails with invalid CEN header, so
apktool d works on the same class of packages reported in iBotPeaches#4028.

Fixes iBotPeaches#4028

Signed-off-by: Atirna <288419661+atirna@users.noreply.github.com>
Track the user path separately from the repaired zip source, delete the
repair copy after decode, and set zip64 validation bypass for library
entrypoints. Extend the regression test to cover apktool.yml and rebuild
output naming.

Signed-off-by: Atirna <288419661+atirna@users.noreply.github.com>
@iBotPeaches

Copy link
Copy Markdown
Owner

Always thought we'd need a complex return of ZipUtils, but this creation with AI might be a shortened version.

@atirna
atirna force-pushed the fix/encrypted-cen-header-4028 branch from e38997c to f7cef94 Compare August 17, 2026 06:00
@atirna

atirna commented Aug 18, 2026

Copy link
Copy Markdown
Author

yeah its a shorter path than wrapping ZipUtils. decode still repairs CEN/local flags in a temp copy when java.util.zip rejects encrypted-entry bits that AOSP accepts, then it keeps the original apkFileName for rebuild.

@iBotPeaches

Copy link
Copy Markdown
Owner

So ultimately its a choice whether maintaining a chunk of code is worth it vs an alternative library. I'm sure @IgorEisberg has some opinions as well

@IgorEisberg

IgorEisberg commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

So here are my impressions:

  1. It's not Apktool's job to fix objectively invalid ZIPs.
  2. If Android accepts objectively invalid ZIPs, and we want to replicate that, then we should include our own ZIP handling library under a dedicated module, like brut.j.zip, where we have full control over its behavior.
  3. This is way too many logic changes in ApkDecoder and ZipRODirectory. Hard to follow and maintain this path juggling that was added for the sole purpose of handling a tiny subset of intentionally manipulated APKs.
  4. This smells like AI slop. Even the test is way longer than it should be.

@iBotPeaches

Copy link
Copy Markdown
Owner

If we make brut.j.zip - I imagine the userland changes should be just swapping out the backing ZipFile in the brut.j.dir. Nothing in Apktool userland should change, since its all hopefully routed through ExtFile.

@iBotPeaches iBotPeaches left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed a bit. Onboard if we introduce a new area (brut.j.zip) and can do with no userland changes, as everything should be handled via ExtFile.

If you have to change anything elsewhere. Please let me know what led to that and why.

atirna added 4 commits August 28, 2026 12:28
Per review: introduce a dedicated brut.j.zip module owning the tolerant
zip behavior, and reduce the change outside it to swapping the backing
ZipFile in brut.j.dir. ApkDecoder and ZipUtils return to their original
shape; decode routes through ExtFile -> ZipRODirectory as before.

RepairingZipFile repairs central-directory / local-header fields AOSP
tolerates (spurious encrypted bit, non-DEFLATE/STORE methods) in a temp
copy and removes it on close. Clean archives open in place.

The old apktool-lib test needed the full testapp apparatus; the module
test builds a minimal zip and flips the header bits directly.

Refs iBotPeaches#4028
The method rewrite relabeled unknown compression methods as stored,
which corrupts entries whose bytes are actually deflated. The repair now
only clears the encrypted-entry bit from CEN and LFH general purpose
flags, which is the behavior issue iBotPeaches#4028 needs; unknown methods surface
the underlying zip error instead of producing garbage.

Also fixes the byte-reversed CEN signature scan in the module test.
Add the new zip module to the Maven publication set so brut.j.dir resolves it with the project version instead of unspecified. Simplify the ZIP mutation fixture and assert that repair leaves the original archive byte-identical.

Signed-off-by: Atirna <288419661+atirna@users.noreply.github.com>
@atirna

atirna commented Aug 28, 2026

Copy link
Copy Markdown
Author

moved this into a dedicated brut.j.zip module. only ZipRODirectory swaps its backing ZipFile now, the repair stays out of Apktool userland and preserves the source bytes. also wired the new module into Maven publication metadata. tests are 3/3 for brut.j.zip and 186/186 for apktool-lib.

Signed-off-by: Atirna <288419661+atirna@users.noreply.github.com>
@atirna

atirna commented Aug 28, 2026

Copy link
Copy Markdown
Author

also redesigned the zip test in 3df5889. it now uses one fixed 138-byte malformed fixture instead of rebuilding/parsing zip headers in test code, so the file is down from 125 to 75 lines and keeps only the repair + reject cases. brut.j.zip is 2/2 and apktool-lib is 186/186.

@iBotPeaches

Copy link
Copy Markdown
Owner

thank you! We are working API 37 right now, but will come back and visit this in a few days.

Signed-off-by: Atirna <288419661+atirna@users.noreply.github.com>
@atirna

atirna commented Aug 28, 2026

Copy link
Copy Markdown
Author

did one more full boundary review in 7accf90. prefixed zip offsets and EOCD signatures inside comments are handled now, temp copies are cleaned on repair/open/close failures, and the fallback only runs for the exact encrypted-entry error. ZIP64/multi-disk are rejected explicitly instead of partly rewritten. the 2 tests pass on Java 8/11/17 and apktool-lib is 186/186.

@IgorEisberg

Copy link
Copy Markdown
Collaborator

Not exactly what I meant by "our own ZIP handling library", but this could actually be serviceable until/if we decide to have our own ZIP library that "just works" similarly to Android.

@iBotPeaches

Copy link
Copy Markdown
Owner

One thing I'm curious about. Is our goal more-so to repair the Zip so the existing library can handle it? Or can we go low enough level that our parser just ignores the things (much like AOSP) does for like encrypted bit, general access bit, etc?

I'm guessing there are limitations in Java on how low level you can go. Which is probably why we had to byte-adapt our way around a recovery.

Or is the ZipSpec roughly easy enough that we have to build our own small library much like I think ARSCLib/JadX did in order to have full control.

@IgorEisberg

IgorEisberg commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

One thing I'm curious about. Is our goal more-so to repair the Zip so the existing library can handle it? Or can we go low enough level that our parser just ignores the things (much like AOSP) does for like encrypted bit, general access bit, etc?

I'm guessing there are limitations in Java on how low level you can go. Which is probably why we had to byte-adapt our way around a recovery.

Or is the ZipSpec roughly easy enough that we have to build our own small library much like I think ARSCLib/JadX did in order to have full control.

Of course, I'd prefer opening those ZIPs like Android does without making any fixes or copies.
Doesn't seem low-level at all but definitely requires a modified ZipFile copy. All it takes is removing this.
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/zip/ZipFile.java#L1215-L1217

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.

[BUG] Invalid CEN header (encrypted entry)

3 participants