package com.wj.dexknife.shell.apkparser.parser; import com.wj.dexknife.shell.apkparser.bean.DexClass; import com.wj.dexknife.shell.apkparser.exception.ParserException; import com.wj.dexknife.shell.apkparser.struct.StringPool; import com.wj.dexknife.shell.apkparser.struct.dex.DexClassStruct; import com.wj.dexknife.shell.apkparser.struct.dex.DexHeader; import com.wj.dexknife.shell.apkparser.utils.Buffers; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** * parse dex file. * current we only get the class name. * see: * http://source.android.com/devices/tech/dalvik/dex-format.html * http://dexandroid.googlecode.com/svn/trunk/dalvik/libdex/DexFile.h * * @author dongliu */ public class DexParser { private ByteBuffer buffer; private ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; private static final int NO_INDEX = 0xffffffff; private DexClass[] dexClasses; public DexParser(ByteBuffer buffer) { this.buffer = buffer.duplicate(); this.buffer.order(byteOrder); } public void parse() { // read magic String magic = new String(Buffers.readBytes(buffer, 8)); if (!magic.startsWith("dex\n")) { return; } int version = Integer.parseInt(magic.substring(4, 7)); // now the version is 035 if (version < 35) { // version 009 was used for the M3 releases of the Android platform (November�CDecember 2007), // and version 013 was used for the M5 releases of the Android platform (February�CMarch 2008) throw new ParserException("Dex file version: " + version + " is not supported"); } // read header DexHeader header = readDexHeader(); header.setVersion(version); // read string pool long[] stringOffsets = readStringPool(header.getStringIdsOff(), header.getStringIdsSize()); // read types int[] typeIds = readTypes(header.getTypeIdsOff(), header.getTypeIdsSize()); // read classes DexClassStruct[] dexClassStructs = readClass(header.getClassDefsOff(), header.getClassDefsSize()); StringPool stringpool = readStrings(stringOffsets); String[] types = new String[typeIds.length]; for (int i = 0; i < typeIds.length; i++) { types[i] = stringpool.get(typeIds[i]); } dexClasses = new DexClass[dexClassStructs.length]; for (int i = 0; i < dexClasses.length; i++) { dexClasses[i] = new DexClass(); } for (int i = 0; i < dexClassStructs.length; i++) { DexClassStruct dexClassStruct = dexClassStructs[i]; DexClass dexClass = dexClasses[i]; dexClass.setClassType(types[dexClassStruct.getClassIdx()]); if (dexClassStruct.getSuperclassIdx() != NO_INDEX) { dexClass.setSuperClass(types[dexClassStruct.getSuperclassIdx()]); } dexClass.setAccessFlags(dexClassStruct.getAccessFlags()); } } /** * read class info. */ private DexClassStruct[] readClass(long classDefsOff, int classDefsSize) { buffer.position((int) classDefsOff); DexClassStruct[] dexClassStructs = new DexClassStruct[classDefsSize]; for (int i = 0; i < classDefsSize; i++) { DexClassStruct dexClassStruct = new DexClassStruct(); dexClassStruct.setClassIdx(buffer.getInt()); dexClassStruct.setAccessFlags(buffer.getInt()); dexClassStruct.setSuperclassIdx(buffer.getInt()); dexClassStruct.setInterfacesOff(Buffers.readUInt(buffer)); dexClassStruct.setSourceFileIdx(buffer.getInt()); dexClassStruct.setAnnotationsOff(Buffers.readUInt(buffer)); dexClassStruct.setClassDataOff(Buffers.readUInt(buffer)); dexClassStruct.setStaticValuesOff(Buffers.readUInt(buffer)); dexClassStructs[i] = dexClassStruct; } return dexClassStructs; } /** * read types. */ private int[] readTypes(long typeIdsOff, int typeIdsSize) { buffer.position((int) typeIdsOff); int[] typeIds = new int[typeIdsSize]; for (int i = 0; i < typeIdsSize; i++) { typeIds[i] = (int) Buffers.readUInt(buffer); } return typeIds; } private StringPool readStrings(long[] offsets) { // read strings. // buffer some apk, the strings' offsets may not well ordered. we sort it first StringPoolEntry[] entries = new StringPoolEntry[offsets.length]; for (int i = 0; i < offsets.length; i++) { entries[i] = new StringPoolEntry(i, offsets[i]); } String lastStr = null; long lastOffset = -1; StringPool stringpool = new StringPool(offsets.length); for (StringPoolEntry entry : entries) { if (entry.getOffset() == lastOffset) { stringpool.set(entry.getIdx(), lastStr); continue; } buffer.position((int) entry.getOffset()); lastOffset = entry.getOffset(); String str = readString(); lastStr = str; stringpool.set(entry.getIdx(), str); } return stringpool; } /* * read string identifiers list. */ private long[] readStringPool(long stringIdsOff, int stringIdsSize) { buffer.position((int) stringIdsOff); long offsets[] = new long[stringIdsSize]; for (int i = 0; i < stringIdsSize; i++) { offsets[i] = Buffers.readUInt(buffer); } return offsets; } /** * read dex encoding string. */ private String readString() { // the length is char len, not byte len int strLen = readVarInts(); return Buffers.readString(buffer, strLen); } /** * read Modified UTF-8 encoding str. * * @param strLen the java-utf16-char len, not strLen nor bytes len. */ @Deprecated private String readString(int strLen) { char[] chars = new char[strLen]; for (int i = 0; i < strLen; i++) { short a = Buffers.readUByte(buffer); if ((a & 0x80) == 0) { // ascii char chars[i] = (char) a; } else if ((a & 0xe0) == 0xc0) { // read one more short b = Buffers.readUByte(buffer); chars[i] = (char) (((a & 0x1F) << 6) | (b & 0x3F)); } else if ((a & 0xf0) == 0xe0) { short b = Buffers.readUByte(buffer); short c = Buffers.readUByte(buffer); chars[i] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F)); } else if ((a & 0xf0) == 0xf0) { //throw new UTFDataFormatException(); } else { //throw new UTFDataFormatException(); } if (chars[i] == 0) { // the end of string. } } return new String(chars); } /** * read varints. * * @return * @throws IOException */ private int readVarInts() { int i = 0; int count = 0; short s; do { if (count > 4) { throw new ParserException("read varints error."); } s = Buffers.readUByte(buffer); i |= (s & 0x7f) << (count * 7); count++; } while ((s & 0x80) != 0); return i; } private DexHeader readDexHeader() { // check sum. skip buffer.getInt(); // signature skip Buffers.readBytes(buffer, DexHeader.kSHA1DigestLen); DexHeader header = new DexHeader(); header.setFileSize(Buffers.readUInt(buffer)); header.setHeaderSize(Buffers.readUInt(buffer)); // skip? Buffers.readUInt(buffer); // static link data header.setLinkSize(Buffers.readUInt(buffer)); header.setLinkOff(Buffers.readUInt(buffer)); // the map data is just the same as dex header. header.setMapOff(Buffers.readUInt(buffer)); header.setStringIdsSize(buffer.getInt()); header.setStringIdsOff(Buffers.readUInt(buffer)); header.setTypeIdsSize(buffer.getInt()); header.setTypeIdsOff(Buffers.readUInt(buffer)); header.setProtoIdsSize(buffer.getInt()); header.setProtoIdsOff(Buffers.readUInt(buffer)); header.setFieldIdsSize(buffer.getInt()); header.setFieldIdsOff(Buffers.readUInt(buffer)); header.setMethodIdsSize(buffer.getInt()); header.setMethodIdsOff(Buffers.readUInt(buffer)); header.setClassDefsSize(buffer.getInt()); header.setClassDefsOff(Buffers.readUInt(buffer)); header.setDataSize(buffer.getInt()); header.setDataOff(Buffers.readUInt(buffer)); buffer.position((int) header.getHeaderSize()); return header; } public DexClass[] getDexClasses() { return dexClasses; } }