fix: invalid header when writing a ZipFile (#169)

This commit is contained in:
oSumAtrIX 2022-12-14 01:14:00 +01:00 committed by GitHub
parent c677eb9792
commit 6e703eb8e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 144 additions and 98 deletions

View File

@ -1,12 +1,37 @@
package app.revanced.cli.aligning package app.revanced.cli.aligning
import app.revanced.cli.command.MainCommand.logger 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.ZipAligner
import app.revanced.utils.signing.align.zip.ZipFile
import app.revanced.utils.signing.align.zip.structures.ZipEntry
import java.io.File import java.io.File
object Aligning { object Aligning {
fun align(inputFile: File, outputFile: File) { fun align(result: PatcherResult, inputFile: File, outputFile: File) {
logger.info("Aligning ${inputFile.name} to ${outputFile.name}") 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
)
}
} }
} }

View File

@ -151,16 +151,14 @@ internal object MainCommand : Runnable {
Adb(outputFile, patcher.context.packageMetadata.packageName, args.deploy!!, !pArgs.mount) Adb(outputFile, patcher.context.packageMetadata.packageName, args.deploy!!, !pArgs.mount)
} }
val patchedFile = File(pArgs.cacheDirectory).resolve("${outputFile.nameWithoutExtension}_raw.apk")
// start the patcher // start the patcher
Patcher.start(patcher, patchedFile, allPatches) val result = Patcher.start(patcher, allPatches)
val cacheDirectory = File(pArgs.cacheDirectory) val cacheDirectory = File(pArgs.cacheDirectory)
// align the file // align the file
val alignedFile = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk") val alignedFile = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk")
Aligning.align(patchedFile, alignedFile) Aligning.align(result, args.inputFile, alignedFile)
// sign the file // sign the file
val finalFile = if (!pArgs.mount) { val finalFile = if (!pArgs.mount) {

View File

@ -1,24 +1,17 @@
package app.revanced.cli.patcher package app.revanced.cli.patcher
import app.revanced.cli.command.MainCommand.args import app.revanced.patcher.PatcherResult
import app.revanced.cli.command.MainCommand.logger
import app.revanced.patcher.data.Context import app.revanced.patcher.data.Context
import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.Patch
import app.revanced.utils.filesystem.ZipFileSystemUtils
import app.revanced.utils.patcher.addPatchesFiltered import app.revanced.utils.patcher.addPatchesFiltered
import app.revanced.utils.patcher.applyPatchesVerbose import app.revanced.utils.patcher.applyPatchesVerbose
import app.revanced.utils.patcher.mergeFiles import app.revanced.utils.patcher.mergeFiles
import java.io.File
import java.nio.file.Files
internal object Patcher { internal object Patcher {
internal fun start( internal fun start(
patcher: app.revanced.patcher.Patcher, patcher: app.revanced.patcher.Patcher,
output: File,
allPatches: List<Class<out Patch<Context>>> allPatches: List<Class<out Patch<Context>>>
) { ): PatcherResult {
val inputFile = args.inputFile
// merge files like necessary integrations // merge files like necessary integrations
patcher.mergeFiles() patcher.mergeFiles()
// add patches, but filter incompatible or excluded patches // add patches, but filter incompatible or excluded patches
@ -26,28 +19,6 @@ internal object Patcher {
// apply patches // apply patches
patcher.applyPatchesVerbose() patcher.applyPatchesVerbose()
// write output file return patcher.save()
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()) }
}
} }
} }

View File

@ -1,28 +1,11 @@
package app.revanced.utils.signing.align package app.revanced.utils.signing.align
import app.revanced.utils.signing.align.zip.ZipFile import app.revanced.utils.signing.align.zip.structures.ZipEntry
import java.io.File
internal object ZipAligner { internal object ZipAligner {
private const val DEFAULT_ALIGNMENT = 4 private const val DEFAULT_ALIGNMENT = 4
private const val LIBRARY_ALIGNMENT = 4096 private const val LIBRARY_ALIGNMENT = 4096
fun align(input: File, output: File) { fun getEntryAlignment(entry: ZipEntry): Int? =
val inputZip = ZipFile(input) if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
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()
}
} }

View File

@ -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.ZipEndRecord
import app.revanced.utils.signing.align.zip.structures.ZipEntry import app.revanced.utils.signing.align.zip.structures.ZipEntry
import java.io.Closeable
import java.io.File import java.io.File
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.nio.channels.FileChannel 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() var entries: MutableList<ZipEntry> = mutableListOf()
private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw") private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
private var CDNeedsRewrite = false
private val compressionLevel = 5
init { init {
//if file isn't empty try to load entries //if file isn't empty try to load entries
@ -53,23 +59,24 @@ class ZipFile(val file: File) {
return buildList(numberOfEntries) { return buildList(numberOfEntries) {
for (i in 1..numberOfEntries) { for (i in 1..numberOfEntries) {
add(ZipEntry.fromCDE(filePointer).also add(
{ ZipEntry.fromCDE(filePointer).also
//for some reason the local extra field can be different from the central one {
it.readLocalExtra( //for some reason the local extra field can be different from the central one
filePointer.channel.map( it.readLocalExtra(
FileChannel.MapMode.READ_ONLY, filePointer.channel.map(
it.localHeaderOffset.toLong() + 28, FileChannel.MapMode.READ_ONLY,
2 it.localHeaderOffset.toLong() + 28,
2
)
) )
) })
})
} }
} }
} }
private fun writeCDE() { private fun writeCD() {
val CDEStart = filePointer.channel.position().toUInt() val CDStart = filePointer.channel.position().toUInt()
entries.forEach { entries.forEach {
filePointer.channel.write(it.toCDE()) filePointer.channel.write(it.toCDE())
@ -82,15 +89,17 @@ class ZipFile(val file: File) {
0u, 0u,
entriesCount, entriesCount,
entriesCount, entriesCount,
filePointer.channel.position().toUInt() - CDEStart, filePointer.channel.position().toUInt() - CDStart,
CDEStart, CDStart,
"" ""
) )
filePointer.channel.write(endRecord.toECD()) 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() entry.localHeaderOffset = filePointer.channel.position().toUInt()
filePointer.channel.write(entry.toLFH()) filePointer.channel.write(entry.toLFH())
@ -99,17 +108,45 @@ class ZipFile(val file: File) {
entries.add(entry) entries.add(entry)
} }
fun addEntryAligned(entry: ZipEntry, data: ByteBuffer, alignment: Int) { fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
//calculate where data would end up val compressor = Deflater(compressionLevel, true)
val dataOffset = filePointer.filePointer + entry.LFHSize compressor.setInput(data)
compressor.finish()
val mod = dataOffset % alignment val uncompressedSize = data.size
val compressedData =
ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger
//wrong alignment val compressedDataLength = compressor.deflate(compressedData)
if (mod != 0L) { val compressedBuffer =
//add padding at end of extra field ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
entry.localExtraField =
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt()) 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)
}
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) addEntry(entry, data)
@ -123,8 +160,17 @@ class ZipFile(val file: File) {
) )
} }
fun finish() { fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
writeCDE() 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() filePointer.close()
} }
} }

View File

@ -9,12 +9,12 @@ data class ZipEntry(
val version: UShort, val version: UShort,
val versionNeeded: UShort, val versionNeeded: UShort,
val flags: UShort, val flags: UShort,
val compression: UShort, var compression: UShort,
val modificationTime: UShort, val modificationTime: UShort,
val modificationDate: UShort, val modificationDate: UShort,
val crc32: UInt, var crc32: UInt,
val compressedSize: UInt, var compressedSize: UInt,
val uncompressedSize: UInt, var uncompressedSize: UInt,
val diskNumber: UShort, val diskNumber: UShort,
val internalAttributes: UShort, val internalAttributes: UShort,
val externalAttributes: UInt, val externalAttributes: UInt,
@ -22,7 +22,7 @@ data class ZipEntry(
val fileName: String, val fileName: String,
val extraField: ByteArray, val extraField: ByteArray,
val fileComment: String, val fileComment: String,
var localExtraField: ByteArray = ByteArray(0), //seperate for alignment var localExtraField: ByteArray = ByteArray(0), //separate for alignment
) { ) {
val LFHSize: Int val LFHSize: Int
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
@ -37,6 +37,27 @@ data class ZipEntry(
const val LFH_HEADER_SIZE = 30 const val LFH_HEADER_SIZE = 30
const val LFH_SIGNATURE = 0x04034b50u const val LFH_SIGNATURE = 0x04034b50u
fun createWithName(fileName: String): ZipEntry {
return ZipEntry(
0x1403u, //made by unix, version 20
0u,
0u,
0u,
0x0821u, //seems to be static time google uses, no idea
0x0221u, //same as above
0u,
0u,
0u,
0u,
0u,
0u,
0u,
fileName,
ByteArray(0),
""
)
}
fun fromCDE(input: DataInput): ZipEntry { fun fromCDE(input: DataInput): ZipEntry {
val signature = input.readUIntLE() val signature = input.readUIntLE()
@ -55,7 +76,7 @@ data class ZipEntry(
val fileNameLength = input.readUShortLE() val fileNameLength = input.readUShortLE()
var fileName = "" var fileName = ""
val extraFieldLength = input.readUShortLE() val extraFieldLength = input.readUShortLE()
var extraField = ByteArray(extraFieldLength.toInt()) val extraField = ByteArray(extraFieldLength.toInt())
val fileCommentLength = input.readUShortLE() val fileCommentLength = input.readUShortLE()
var fileComment = "" var fileComment = ""
val diskNumber = input.readUShortLE() val diskNumber = input.readUShortLE()
@ -63,7 +84,8 @@ data class ZipEntry(
val externalAttributes = input.readUIntLE() val externalAttributes = input.readUIntLE()
val localHeaderOffset = input.readUIntLE() val localHeaderOffset = input.readUIntLE()
val variableFieldsLength = fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt() val variableFieldsLength =
fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
if (variableFieldsLength > 0) { if (variableFieldsLength > 0) {
val fileNameBytes = ByteArray(fileNameLength.toInt()) val fileNameBytes = ByteArray(fileNameLength.toInt())
@ -77,7 +99,8 @@ data class ZipEntry(
fileComment = fileCommentBytes.toString(Charsets.UTF_8) fileComment = fileCommentBytes.toString(Charsets.UTF_8)
} }
flags = (flags and 0b1000u.inv().toUShort()) //disable data descriptor flag as they are not used flags = (flags and 0b1000u.inv()
.toUShort()) //disable data descriptor flag as they are not used
return ZipEntry( return ZipEntry(
version, version,
@ -134,8 +157,9 @@ data class ZipEntry(
val nameBytes = fileName.toByteArray(Charsets.UTF_8) val nameBytes = fileName.toByteArray(Charsets.UTF_8)
val commentBytes = fileComment.toByteArray(Charsets.UTF_8) val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
val buffer = ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size) val buffer =
.also { it.order(ByteOrder.LITTLE_ENDIAN) } ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
buffer.putUInt(CDE_SIGNATURE) buffer.putUInt(CDE_SIGNATURE)
buffer.putUShort(version) buffer.putUShort(version)
@ -163,4 +187,3 @@ data class ZipEntry(
return buffer return buffer
} }
} }