Skip to content

Instantly share code, notes, and snippets.

@Nublo
Created March 17, 2023 16:26
Show Gist options
  • Save Nublo/4078c2c0eb273b0ceae4eca1f3fcda65 to your computer and use it in GitHub Desktop.
Save Nublo/4078c2c0eb273b0ceae4eca1f3fcda65 to your computer and use it in GitHub Desktop.
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.charset.StandardCharsets
/**
* https://android.googlesource.com/platform/dalvik.git/+/master/tools/dexdeps/src/com/android/dexdeps
*
* Extracts information about methods and fields from .dex file
*/
internal class DexParser(
private val dexFile: RandomAccessFile
) {
private val tmpBuf = ByteArray(INT_SIZE_IN_BYTES)
private var isBigEndian = false
@Throws(IOException::class)
fun load(): DexInfo {
val headerItem = parseHeaderItem()
return DexInfo(
methods = headerItem.methodIdsSize,
fields = headerItem.fieldIdsCount,
)
}
@Throws(IOException::class)
@Suppress("MagicNumber")
private fun parseHeaderItem(): HeaderItem {
dexFile.seek(0)
verifyMagic()
val headerItem = HeaderItem()
/*
* Read the endian tag, so we properly swap things as we read
* them from here on.
*/
dexFile.seek((DEX_FILE_MAGIC_LENGTH + 4 + 20 + 4 + 4).toLong())
headerItem.endianTag = readInt()
isBigEndian = if (headerItem.endianTag == ENDIAN_CONSTANT) {
false
} else if (headerItem.endianTag == REVERSE_ENDIAN_CONSTANT) {
/* file is big-endian (!), reverse future reads */
true
} else {
throw IOException("Endian constant has unexpected value " + Integer.toHexString(headerItem.endianTag))
}
dexFile.seek((DEX_FILE_MAGIC_LENGTH + 4 + 20).toLong()) // magic, checksum, signature
/*headerItem.fileSize =*/readInt()
/*headerItem.headerSize =*/readInt()
/*headerItem.endianTag =*/readInt()
/*headerItem.linkSize =*/readInt()
/*headerItem.linkOff =*/readInt()
/*headerItem.mapOff =*/readInt()
/*headerItem.stringIdsSize =*/ readInt()
/*headerItem.stringIdsOff =*/readInt()
/*headerItem.typeIdsSize =*/ readInt()
/*headerItem.typeIdsOff =*/readInt()
/*headerItem.protoIdsSize =*/readInt()
/*headerItem.protoIdsOff =*/readInt()
headerItem.fieldIdsCount = readInt()
/*headerItem.fieldIdsOff =*/readInt()
headerItem.methodIdsSize = readInt()
/*headerItem.methodIdsOff =*/readInt()
/*headerItem.classDefsSize =*/ readInt()
/*headerItem.classDefsOff =*/readInt()
/*headerItem.dataSize =*/readInt()
/*headerItem.dataOff =*/readInt()
return headerItem
}
@Throws(IOException::class)
private fun verifyMagic() {
val magic = ByteArray(DEX_FILE_MAGIC_LENGTH)
dexFile.readFully(magic)
if (!verifyMagic(magic)) {
throw IOException("Magic number is wrong -- are you sure this is a DEX file?")
}
}
/**
* Reads a signed 32-bit integer, byte-swapping if necessary.
*/
@Throws(IOException::class)
@Suppress("MagicNumber")
private fun readInt(): Int {
dexFile.readFully(tmpBuf, 0, INT_SIZE_IN_BYTES)
return if (isBigEndian) {
tmpBuf[3].toInt()
.and(0xff)
.or(tmpBuf[2].toInt().and(0xff).shl(8))
.or(tmpBuf[1].toInt().and(0xff).shl(16))
.or(tmpBuf[0].toInt().and(0xff).shl(24))
} else {
tmpBuf[0].toInt()
.and(0xff)
.or(tmpBuf[1].toInt() and 0xff shl 8)
.or(tmpBuf[2].toInt() and 0xff shl 16)
.or(tmpBuf[3].toInt() and 0xff shl 24)
}
}
private fun verifyMagic(magic: ByteArray): Boolean {
return magic.contentEquals(DEX_FILE_MAGIC_v035) ||
magic.contentEquals(DEX_FILE_MAGIC_v037) ||
magic.contentEquals(DEX_FILE_MAGIC_v038) ||
magic.contentEquals(DEX_FILE_MAGIC_v039)
}
/**
* Holds the contents of a header_item.
*/
internal class HeaderItem {
var endianTag = 0
var fieldIdsCount = 0
var methodIdsSize = 0
}
companion object {
private const val DEX_FILE_MAGIC_LENGTH = 8
/* expected magic values */
private val DEX_FILE_MAGIC_v035 = "dex\n035\u0000".toByteArray(StandardCharsets.US_ASCII)
// Dex version 036 skipped because of an old dalvik bug on some versions
// of android where dex files with that version number would erroneously
// be accepted and run. See: art/runtime/dex_file.cc
// V037 was introduced in API LEVEL 24
private val DEX_FILE_MAGIC_v037 = "dex\n037\u0000".toByteArray(StandardCharsets.US_ASCII)
// V038 was introduced in API LEVEL 26
private val DEX_FILE_MAGIC_v038 = "dex\n038\u0000".toByteArray(StandardCharsets.US_ASCII)
// V039 was introduced in API LEVEL 28
private val DEX_FILE_MAGIC_v039 = "dex\n039\u0000".toByteArray(StandardCharsets.US_ASCII)
private const val ENDIAN_CONSTANT = 0x12345678
private const val REVERSE_ENDIAN_CONSTANT = 0x78563412
private const val INT_SIZE_IN_BYTES = 4
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment