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

fix: invalid header when writing a ZipFile #169

Merged
merged 1 commit into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions src/main/kotlin/app/revanced/cli/aligning/Aligning.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
package app.revanced.cli.aligning

import app.revanced.cli.command.MainCommand.logger
import app.revanced.patcher.PatcherResult
import app.revanced.utils.signing.align.ZipAligner
import app.revanced.utils.signing.align.zip.ZipFile
import app.revanced.utils.signing.align.zip.structures.ZipEntry
import java.io.File

object Aligning {
fun align(inputFile: File, outputFile: File) {
fun align(result: PatcherResult, inputFile: File, outputFile: File) {
logger.info("Aligning ${inputFile.name} to ${outputFile.name}")
ZipAligner.align(inputFile, outputFile)

if (outputFile.exists()) outputFile.delete()

ZipFile(outputFile).use { file ->
result.dexFiles.forEach {
file.addEntryCompressData(
ZipEntry.createWithName(it.name),
it.stream.readBytes()
)
}

result.resourceFile?.let {
file.copyEntriesFromFileAligned(
ZipFile(it),
ZipAligner::getEntryAlignment
)
}

file.copyEntriesFromFileAligned(
ZipFile(inputFile),
ZipAligner::getEntryAlignment
)
}
}
}
6 changes: 2 additions & 4 deletions src/main/kotlin/app/revanced/cli/command/MainCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -151,16 +151,14 @@ internal object MainCommand : Runnable {
Adb(outputFile, patcher.context.packageMetadata.packageName, args.deploy!!, !pArgs.mount)
}

val patchedFile = File(pArgs.cacheDirectory).resolve("${outputFile.nameWithoutExtension}_raw.apk")

// start the patcher
Patcher.start(patcher, patchedFile, allPatches)
val result = Patcher.start(patcher, allPatches)

val cacheDirectory = File(pArgs.cacheDirectory)

// align the file
val alignedFile = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk")
Aligning.align(patchedFile, alignedFile)
Aligning.align(result, args.inputFile, alignedFile)

// sign the file
val finalFile = if (!pArgs.mount) {
Expand Down
35 changes: 3 additions & 32 deletions src/main/kotlin/app/revanced/cli/patcher/Patcher.kt
Original file line number Diff line number Diff line change
@@ -1,53 +1,24 @@
package app.revanced.cli.patcher

import app.revanced.cli.command.MainCommand.args
import app.revanced.cli.command.MainCommand.logger
import app.revanced.patcher.PatcherResult
import app.revanced.patcher.data.Context
import app.revanced.patcher.patch.Patch
import app.revanced.utils.filesystem.ZipFileSystemUtils
import app.revanced.utils.patcher.addPatchesFiltered
import app.revanced.utils.patcher.applyPatchesVerbose
import app.revanced.utils.patcher.mergeFiles
import java.io.File
import java.nio.file.Files

internal object Patcher {
internal fun start(
patcher: app.revanced.patcher.Patcher,
output: File,
allPatches: List<Class<out Patch<Context>>>
) {
val inputFile = args.inputFile

): PatcherResult {
// merge files like necessary integrations
patcher.mergeFiles()
// add patches, but filter incompatible or excluded patches
patcher.addPatchesFiltered(allPatches)
// apply patches
patcher.applyPatchesVerbose()

// write output file
if (output.exists()) Files.delete(output.toPath())
inputFile.copyTo(output)

val result = patcher.save()
ZipFileSystemUtils(output).use { outputFileSystem ->
// replace all dex files
result.dexFiles.forEach {
logger.info("Writing dex file ${it.name}")
outputFileSystem.write(it.name, it.stream.readAllBytes())
}

result.resourceFile?.let {
logger.info("Writing resources...")

ZipFileSystemUtils(it).use { resourceFileSystem ->
val resourceFiles = resourceFileSystem.getFile(File.separator)
outputFileSystem.writePathRecursively(resourceFiles)
}
}

result.doNotCompress?.let { outputFileSystem.uncompress(*it.toTypedArray()) }
}
return patcher.save()
}
}
23 changes: 3 additions & 20 deletions src/main/kotlin/app/revanced/utils/signing/align/ZipAligner.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
package app.revanced.utils.signing.align

import app.revanced.utils.signing.align.zip.ZipFile
import java.io.File
import app.revanced.utils.signing.align.zip.structures.ZipEntry

internal object ZipAligner {
private const val DEFAULT_ALIGNMENT = 4
private const val LIBRARY_ALIGNMENT = 4096

fun align(input: File, output: File) {
val inputZip = ZipFile(input)
val outputZip = ZipFile(output)

for (entry in inputZip.entries) {
val data = inputZip.getDataForEntry(entry)

if (entry.compression == 0.toUShort()) {
val alignment = if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT

outputZip.addEntryAligned(entry, data, alignment)
} else {
outputZip.addEntry(entry, data)
}
}

outputZip.finish()
}
fun getEntryAlignment(entry: ZipEntry): Int? =
if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
}
102 changes: 74 additions & 28 deletions src/main/kotlin/app/revanced/utils/signing/align/zip/ZipFile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ package app.revanced.utils.signing.align.zip

import app.revanced.utils.signing.align.zip.structures.ZipEndRecord
import app.revanced.utils.signing.align.zip.structures.ZipEntry
import java.io.Closeable
import java.io.File
import java.io.RandomAccessFile
import java.nio.ByteBuffer
import java.nio.channels.FileChannel
import java.util.zip.CRC32
import java.util.zip.Deflater

class ZipFile(val file: File) {
class ZipFile(file: File) : Closeable {
var entries: MutableList<ZipEntry> = mutableListOf()

private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
private var CDNeedsRewrite = false

private val compressionLevel = 5

init {
//if file isn't empty try to load entries
Expand Down Expand Up @@ -53,23 +59,24 @@ class ZipFile(val file: File) {

return buildList(numberOfEntries) {
for (i in 1..numberOfEntries) {
add(ZipEntry.fromCDE(filePointer).also
{
//for some reason the local extra field can be different from the central one
it.readLocalExtra(
filePointer.channel.map(
FileChannel.MapMode.READ_ONLY,
it.localHeaderOffset.toLong() + 28,
2
add(
ZipEntry.fromCDE(filePointer).also
{
//for some reason the local extra field can be different from the central one
it.readLocalExtra(
filePointer.channel.map(
FileChannel.MapMode.READ_ONLY,
it.localHeaderOffset.toLong() + 28,
2
)
)
)
})
})
}
}
}

private fun writeCDE() {
val CDEStart = filePointer.channel.position().toUInt()
private fun writeCD() {
val CDStart = filePointer.channel.position().toUInt()

entries.forEach {
filePointer.channel.write(it.toCDE())
Expand All @@ -82,15 +89,17 @@ class ZipFile(val file: File) {
0u,
entriesCount,
entriesCount,
filePointer.channel.position().toUInt() - CDEStart,
CDEStart,
filePointer.channel.position().toUInt() - CDStart,
CDStart,
""
)

filePointer.channel.write(endRecord.toECD())
}

fun addEntry(entry: ZipEntry, data: ByteBuffer) {
private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
CDNeedsRewrite = true

entry.localHeaderOffset = filePointer.channel.position().toUInt()

filePointer.channel.write(entry.toLFH())
Expand All @@ -99,17 +108,45 @@ class ZipFile(val file: File) {
entries.add(entry)
}

fun addEntryAligned(entry: ZipEntry, data: ByteBuffer, alignment: Int) {
//calculate where data would end up
val dataOffset = filePointer.filePointer + entry.LFHSize
fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
val compressor = Deflater(compressionLevel, true)
compressor.setInput(data)
compressor.finish()

val uncompressedSize = data.size
val compressedData =
ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger

val compressedDataLength = compressor.deflate(compressedData)
val compressedBuffer =
ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())

val mod = dataOffset % alignment
compressor.end()

val crc = CRC32()
crc.update(data)

entry.compression = 8u //deflate compression
entry.uncompressedSize = uncompressedSize.toUInt()
entry.compressedSize = compressedDataLength.toUInt()
entry.crc32 = crc.value.toUInt()

addEntry(entry, compressedBuffer)
}

//wrong alignment
if (mod != 0L) {
//add padding at end of extra field
entry.localExtraField =
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
private fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) {
alignment?.let {
//calculate where data would end up
val dataOffset = filePointer.filePointer + entry.LFHSize

val mod = dataOffset % alignment

//wrong alignment
if (mod != 0L) {
//add padding at end of extra field
entry.localExtraField =
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
}
}

addEntry(entry, data)
Expand All @@ -123,8 +160,17 @@ class ZipFile(val file: File) {
)
}

fun finish() {
writeCDE()
fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
for (entry in file.entries) {
if (entries.any { it.fileName == entry.fileName }) continue //don't add duplicates

val data = file.getDataForEntry(entry)
addEntryCopyData(entry, data, entryAlignment(entry))
}
}

override fun close() {
if (CDNeedsRewrite) writeCD()
filePointer.close()
}
}
}
Loading