/* * Copyright (C) 2012 Sony Mobile Communications AB * * This file is part of ApkAnalyser. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package andreflect; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import mereflect.CollaborateClassContext; import mereflect.MEClass; import mereflect.MEClassContext; import mereflect.MEMethod; import mereflect.UnknownClass; import org.jf.baksmali.Adaptors.MethodItem; import org.jf.baksmali.Adaptors.Format.ArrayDataMethodItem; import org.jf.baksmali.Adaptors.Format.InstructionMethodItemFactory; import org.jf.dexlib.AnnotationItem; import org.jf.dexlib.AnnotationSetItem; import org.jf.dexlib.ClassDataItem; import org.jf.dexlib.ClassDefItem; import org.jf.dexlib.EncodedArrayItem; import org.jf.dexlib.FieldIdItem; import org.jf.dexlib.Item; import org.jf.dexlib.ItemType; import org.jf.dexlib.MethodIdItem; import org.jf.dexlib.StringIdItem; import org.jf.dexlib.TypeIdItem; import org.jf.dexlib.Code.Instruction; import org.jf.dexlib.Code.InstructionWithReference; import org.jf.dexlib.Code.Analysis.AnalyzedInstruction; import org.jf.dexlib.Code.Analysis.ClassPath; import org.jf.dexlib.Code.Analysis.ClassPath.ClassDef; import org.jf.dexlib.Code.Analysis.ClassPath.PrimitiveClassDef; import org.jf.dexlib.Code.Analysis.MethodAnalyzer; import org.jf.dexlib.Code.Format.ArrayDataPseudoInstruction; import org.jf.dexlib.Code.Format.Instruction21c; import org.jf.dexlib.Code.Format.Instruction21h; import org.jf.dexlib.Code.Format.Instruction31c; import org.jf.dexlib.Code.Format.Instruction31i; import org.jf.dexlib.EncodedValue.AnnotationEncodedSubValue; import org.jf.dexlib.EncodedValue.ArrayEncodedValue; import org.jf.dexlib.EncodedValue.BooleanEncodedValue; import org.jf.dexlib.EncodedValue.ByteEncodedValue; import org.jf.dexlib.EncodedValue.CharEncodedValue; import org.jf.dexlib.EncodedValue.DoubleEncodedValue; import org.jf.dexlib.EncodedValue.EncodedValue; import org.jf.dexlib.EncodedValue.FloatEncodedValue; import org.jf.dexlib.EncodedValue.IntEncodedValue; import org.jf.dexlib.EncodedValue.LongEncodedValue; import org.jf.dexlib.EncodedValue.ShortEncodedValue; import org.jf.dexlib.EncodedValue.StringEncodedValue; import org.jf.dexlib.EncodedValue.TypeEncodedValue; import org.jf.dexlib.EncodedValue.ValueType; import org.jf.dexlib.Util.AccessFlags; import org.jf.dexlib.Util.ExceptionWithContext; import analyser.gui.MainFrame; import andreflect.definition.DexClassDefinition; public final class DexReader { public static DexClass readClassFile(DexResource classResource, boolean isodex) { //System.out.println("[DexClassReader] reading "+classResource.getClassName()); ClassDefItem classDefItem = classResource.getClassDefItem(); ClassDataItem classDataItem = classDefItem.getClassData(); DexClass c = null; assert classDefItem != null; c = new DexClass(classDefItem); c.setResource(classResource); c.setMajorVersion(0); //ignored c.setMinorVersion(0); //ignored int access_dex = classDefItem.getAccessFlags(); int accessflag = (access_dex & AccessFlags.PUBLIC.getValue()) != 0 ? DexClass.ACC_PUBLIC : 0; accessflag |= (access_dex & AccessFlags.FINAL.getValue()) != 0 ? DexClass.ACC_FINAL : 0; accessflag |= (access_dex & AccessFlags.INTERFACE.getValue()) != 0 ? DexClass.ACC_INTERFACE : 0; accessflag |= (access_dex & AccessFlags.ABSTRACT.getValue()) != 0 ? DexClass.ACC_ABSTRACT : 0; c.setAccessFlags(accessflag); c.setThisClassIndex(0); //ignored c.setSuperClassIndex(0); //ignored // Interfaces c.setInterfaceIndices(null); //ignored if (classDataItem != null) { // Fields int staticFieldLen = (classDataItem.getStaticFields() != null) ? classDataItem.getStaticFields().length : 0; int instanceFieldLen = (classDataItem.getInstanceFields() != null) ? classDataItem.getInstanceFields().length : 0; int fLen = staticFieldLen + instanceFieldLen; DexField[] fields = new DexField[fLen]; int fieldIdx = 0; //static initial values EncodedArrayItem encodedStaticInitializers = classDefItem.getStaticFieldInitializers(); EncodedValue[] staticInitializers; if (encodedStaticInitializers != null) { staticInitializers = encodedStaticInitializers.getEncodedArray().values; } else { staticInitializers = new EncodedValue[0]; } ClassDataItem.EncodedField[] encodedStaticFields = classDataItem.getStaticFields(); for (int i = 0; i < staticFieldLen; i++) { ClassDataItem.EncodedField field = encodedStaticFields[i]; EncodedValue encodedValue = null; if (i < staticInitializers.length) { encodedValue = staticInitializers[i]; } fields[fieldIdx] = readFieldInfo(c, field, encodedValue); //System.out.println("[DexClassReader] field static:"+ fields[fieldIdx].getName()); fieldIdx++; } ClassDataItem.EncodedField[] encodedInstanceFields = classDataItem.getInstanceFields(); for (int i = 0; i < instanceFieldLen; i++) { ClassDataItem.EncodedField field = encodedInstanceFields[i]; fields[fieldIdx] = readFieldInfo(c, field, null); //System.out.println("[DexClassReader] field instance:"+ fields[fieldIdx].getName()); fieldIdx++; } c.setFields(fields); // Methods int directMethodLen = (classDataItem.getDirectMethods() != null) ? classDataItem.getDirectMethods().length : 0; int virtualMethodLen = (classDataItem.getVirtualMethods() != null) ? classDataItem.getVirtualMethods().length : 0; int mLen = directMethodLen + virtualMethodLen; DexMethod[] methods = new DexMethod[mLen]; int methodIdx = 0; ClassDataItem.EncodedMethod[] directMethods = classDataItem.getDirectMethods(); for (int i = 0; i < directMethodLen; i++) { ClassDataItem.EncodedMethod method = directMethods[i]; methods[methodIdx] = readMethodInfo(c, method, isodex); //System.out.println("[DexClassReader] method direct:"+ methods[methodIdx].getName()); methodIdx++; } ClassDataItem.EncodedMethod[] virtualMethods = classDataItem.getVirtualMethods(); for (int i = 0; i < virtualMethodLen; i++) { ClassDataItem.EncodedMethod method = virtualMethods[i]; methods[methodIdx] = readMethodInfo(c, method, isodex); //System.out.println("[DexClassReader] method virtual:"+ methods[methodIdx].getName()); methodIdx++; } c.setMethods(methods); } else { c.setFields(new DexField[0]); c.setMethods(new DexMethod[0]); //System.out.println("[DexClassReader] class no data item " + c.getName()); } return c; } protected static DexField readFieldInfo(DexClass clazz, ClassDataItem.EncodedField field, EncodedValue encodedValue) { DexField res = null; res = new DexField(clazz, field); int access_dex = field.accessFlags; int accessflag = (access_dex & AccessFlags.PUBLIC.getValue()) != 0 ? DexField.ACC_PUBLIC : 0; accessflag |= (access_dex & AccessFlags.FINAL.getValue()) != 0 ? DexField.ACC_FINAL : 0; accessflag |= (access_dex & AccessFlags.PRIVATE.getValue()) != 0 ? DexField.ACC_PRIVATE : 0; accessflag |= (access_dex & AccessFlags.PROTECTED.getValue()) != 0 ? DexField.ACC_PROTECTED : 0; accessflag |= (access_dex & AccessFlags.STATIC.getValue()) != 0 ? DexField.ACC_STATIC : 0; accessflag |= (access_dex & AccessFlags.TRANSIENT.getValue()) != 0 ? DexField.ACC_TRANSIENT : 0; accessflag |= (access_dex & AccessFlags.VOLATILE.getValue()) != 0 ? DexField.ACC_VOLATILE : 0; res.setAccessFlags(accessflag); res.setNameIndex(0); //ignored res.setDescriptorIndex(0); //ignored res.setAttributes(null); //ignored if (encodedValue != null) { switch (encodedValue.getValueType()) { case VALUE_BOOLEAN: res.m_constant = new Boolean(((BooleanEncodedValue) encodedValue).value); break; case VALUE_BYTE: res.m_constant = new Long(((ByteEncodedValue) encodedValue).value); break; case VALUE_CHAR: res.m_constant = new Character(((CharEncodedValue) encodedValue).value); break; case VALUE_DOUBLE: res.m_constant = new Double(((DoubleEncodedValue) encodedValue).value); break; case VALUE_FLOAT: res.m_constant = new Float(((FloatEncodedValue) encodedValue).value); break; case VALUE_INT: res.m_constant = new Long(((IntEncodedValue) encodedValue).value); break; case VALUE_LONG: res.m_constant = new Long(((LongEncodedValue) encodedValue).value); break; case VALUE_SHORT: res.m_constant = new Long(((ShortEncodedValue) encodedValue).value); break; case VALUE_STRING: res.m_constant = new String(((StringEncodedValue) encodedValue).value.getStringValue()); break; default: break; } } return res; } protected static DexMethod readMethodInfo(DexClass clazz, ClassDataItem.EncodedMethod method, boolean isodex) { DexMethod res = null; res = new DexMethod(clazz, method); int access_dex = method.accessFlags; int accessflag = (access_dex & AccessFlags.PUBLIC.getValue()) != 0 ? DexMethod.ACC_PUBLIC : 0; accessflag |= (access_dex & AccessFlags.FINAL.getValue()) != 0 ? DexMethod.ACC_FINAL : 0; accessflag |= (access_dex & AccessFlags.PRIVATE.getValue()) != 0 ? DexMethod.ACC_PRIVATE : 0; accessflag |= (access_dex & AccessFlags.PROTECTED.getValue()) != 0 ? DexMethod.ACC_PROTECTED : 0; accessflag |= (access_dex & AccessFlags.STATIC.getValue()) != 0 ? DexMethod.ACC_STATIC : 0; accessflag |= (access_dex & AccessFlags.NATIVE.getValue()) != 0 ? DexMethod.ACC_NATIVE : 0; accessflag |= (access_dex & AccessFlags.ABSTRACT.getValue()) != 0 ? DexMethod.ACC_ABSTRACT : 0; accessflag |= (access_dex & AccessFlags.STRICTFP.getValue()) != 0 ? DexMethod.ACC_STRICT : 0; accessflag |= (access_dex & (AccessFlags.SYNCHRONIZED.getValue() | AccessFlags.DECLARED_SYNCHRONIZED.getValue())) != 0 ? DexMethod.ACC_SYNCHRONIZED : 0; res.setAccessFlags(accessflag); //System.out.println("[DexClassReader] method="+ method.method.getMethodString()+" acc="+accessflag + " oriacc="+ access_dex); res.setNameIndex(0); //ignored res.setDescriptorIndex(0); //ignored res.setAttributes(null); //ignored res.m_dexInvokations = new ArrayList<MEMethod.Invokation>(); res.setExceptions(new MEClass[0]); DexReferenceCache desRefCache = ((ApkClassContext) clazz.getResource().getContext()).getDexReferenceCache(); if ((accessflag & (DexMethod.ACC_ABSTRACT | DexMethod.ACC_NATIVE)) == 0 && method.codeItem != null && clazz.getResource().getContext().isMidlet() == true) { try { method.codeItem.registerOriginalCount = method.codeItem.getRegisterCount(); Instruction[] instructions = method.codeItem.getInstructions(); int currentCodeAddress = 0; List<AnalyzedInstruction> analysedInstructions = null; if (isodex) { prepareClassPath(clazz); //TODO: do not support customlized inline table now, could be read from device later. MethodAnalyzer methodAnalyser = new MethodAnalyzer(method, isodex, null); methodAnalyser.analyze(); analysedInstructions = methodAnalyser.getInstructions(); } for (int i = 0; i < instructions.length; i++) { Instruction instruction = instructions[i]; instruction.codeAddress = currentCodeAddress; instruction.line = -1; instruction.deodexedInstruction = analysedInstructions != null ? analysedInstructions.get(i).getInstruction() : instruction; //add pc currentCodeAddress += instruction.getSize(currentCodeAddress); //method invocations boolean isVirtual = false; boolean isInterface = false; boolean isInvoke = false; //boolean isOdexInvoke = false; switch (instruction.deodexedInstruction.opcode) { /* case INVOKE_VIRTUAL_QUICK: case INVOKE_VIRTUAL_QUICK_RANGE: isOdexInvoke = true; */ case INVOKE_VIRTUAL: case INVOKE_VIRTUAL_RANGE: isVirtual = true; isInvoke = true; break; case INVOKE_INTERFACE: case INVOKE_INTERFACE_RANGE: isInterface = true; isInvoke = true; break; /* case EXECUTE_INLINE: case EXECUTE_INLINE_RANGE: case INVOKE_DIRECT_EMPTY: case INVOKE_SUPER_QUICK: case INVOKE_SUPER_QUICK_RANGE: isOdexInvoke = true; */ case INVOKE_SUPER: case INVOKE_DIRECT: case INVOKE_STATIC: case INVOKE_DIRECT_RANGE: case INVOKE_SUPER_RANGE: case INVOKE_STATIC_RANGE: isInvoke = true; break; default: break; } InstructionWithReference instructionRef = null; if (isInvoke == true && instruction instanceof InstructionWithReference) { instructionRef = (InstructionWithReference) instruction; }/*else if(isOdexInvoke == true && methodAnalyser!= null){ AnalyzedInstruction analysedIns = analysedInstructions.get(i); Instruction ins = analysedIns.getInstruction(); if (ins instanceof InstructionWithReference){ instructionRef = (InstructionWithReference)ins; } }*/ if (instructionRef != null) { Item<?> item = instructionRef.getReferencedItem(); if (item.getItemType() == ItemType.TYPE_METHOD_ID_ITEM) { MethodIdItem methodIdItem = (MethodIdItem) item; TypeIdItem methodClass = methodIdItem.getContainingClass(); MEMethod.Invokation inv = new MEMethod.Invokation(Util.getClassName(methodClass.getTypeDescriptor()), methodIdItem.getMethodName().getStringValue(), methodIdItem.getPrototype().getPrototypeString(), instruction.codeAddress, i, isVirtual, isInterface, instruction); //System.out.println("[DexClassReader] invoke:" + Util.getClassName(methodClass.getTypeDescriptor()) + "->" +methodIdItem.getMethodName().getStringValue() + "->" +methodIdItem.getPrototype().getPrototypeString()); res.m_dexInvokations.add(inv); continue; } } //field accesses boolean read = true; Item<?> item = null; switch (instruction.deodexedInstruction.opcode) { case IPUT: case IPUT_WIDE: case IPUT_OBJECT: case IPUT_BOOLEAN: case IPUT_BYTE: case IPUT_CHAR: case IPUT_SHORT: case SPUT: case SPUT_WIDE: case SPUT_OBJECT: case SPUT_BOOLEAN: case SPUT_BYTE: case SPUT_CHAR: case SPUT_SHORT: read = false; item = ((InstructionWithReference) instruction).getReferencedItem(); break; /* case IPUT_QUICK: case IPUT_WIDE_QUICK: case IPUT_OBJECT_QUICK: //for gingerbread case IPUT_VOLATILE: case IPUT_WIDE_VOLATILE: case IPUT_OBJECT_VOLATILE: case SPUT_VOLATILE: case SPUT_WIDE_VOLATILE: case SPUT_OBJECT_VOLATILE: { Instruction deodexedIns = analysedInstructions.get(i).getInstruction(); if (deodexedIns instanceof InstructionWithReference){ read = false; item = ((InstructionWithReference)deodexedIns).getReferencedItem(); } break; } */ case IGET: case IGET_WIDE: case IGET_OBJECT: case IGET_BOOLEAN: case IGET_BYTE: case IGET_CHAR: case IGET_SHORT: case SGET: case SGET_WIDE: case SGET_OBJECT: case SGET_BOOLEAN: case SGET_BYTE: case SGET_CHAR: case SGET_SHORT: read = true; item = ((InstructionWithReference) instruction).getReferencedItem(); break; /* case IGET_QUICK: case IGET_WIDE_QUICK: case IGET_OBJECT_QUICK: //for gingerbread case IGET_VOLATILE: case IGET_WIDE_VOLATILE: case IGET_OBJECT_VOLATILE: case SGET_VOLATILE: case SGET_WIDE_VOLATILE: case SGET_OBJECT_VOLATILE: { Instruction deodexedIns = analysedInstructions.get(i).getInstruction(); if (deodexedIns instanceof InstructionWithReference){ read = true; item = ((InstructionWithReference)deodexedIns).getReferencedItem(); } break; } */ default: break; } if (item != null) { desRefCache.addFieldAccessReference(new DexReferenceCache.FieldAccess((FieldIdItem) item, res, instruction, instruction.codeAddress, read)); continue; } //const access for resources int literal = 0; switch (instruction.deodexedInstruction.opcode) { case CONST: literal = (int) ((Instruction31i) instruction).getLiteral(); break; case CONST_HIGH16: literal = (int) ((Instruction21h) instruction).getLiteral() << 16; break; default: break; } if (literal != 0) { desRefCache.addCodeReference(new DexReferenceCache.LoadConstRes(literal, res, instruction, instruction.codeAddress)); continue; } MethodItem methodItem = InstructionMethodItemFactory.makeInstructionFormatMethodItem(res.getDefinition(), method.codeItem, instruction.codeAddress, instruction); if (methodItem instanceof ArrayDataMethodItem) { ArrayDataMethodItem arrayDataItem = (ArrayDataMethodItem) methodItem; Iterator<ArrayDataPseudoInstruction.ArrayElement> iterator = arrayDataItem.instruction.getElements(); while (iterator.hasNext()) { ArrayDataPseudoInstruction.ArrayElement element = iterator.next(); if (element.elementWidth == 4) { int id = 0; for (int j = 0; j < 4; j++) { id |= (element.buffer[element.bufferIndex + j] & 0xFF) << (j * 8); } //FIXME cannot locate array item correctly desRefCache.addCodeReference(new DexReferenceCache.LoadConstRes(id, res, instructions[0], instructions[0].codeAddress)); } } } String constSting = null; switch (instruction.deodexedInstruction.opcode) { case CONST_STRING: constSting = ((StringIdItem) ((Instruction21c) instruction).getReferencedItem()).getStringDataItem().getStringValue(); break; case CONST_STRING_JUMBO: constSting = ((StringIdItem) ((Instruction31c) instruction).getReferencedItem()).getStringDataItem().getStringValue(); break; default: break; } if (constSting != null) { desRefCache.addCodeConstString(new DexReferenceCache.LoadConstString(constSting, res, instruction, instruction.codeAddress)); } } res.getDefinition().prepareLineNumbers(); int exceptionSize = 0; ArrayList<MEClass> exceptions = new ArrayList<MEClass>(); DexClassDefinition def = clazz.getDefinition(); AnnotationSetItem annotationSet = def.getAnnotationSetItem(method); if (annotationSet != null) { for (AnnotationItem annotationItem : annotationSet.getAnnotations()) { if (annotationItem.getEncodedAnnotation().annotationType.getTypeDescriptor().equals("Ldalvik/annotation/Throws;")) { AnnotationEncodedSubValue encodedAnnotation = annotationItem.getEncodedAnnotation(); for (int m = 0; m < encodedAnnotation.names.length; m++) { //System.out.println("[DexClassReader] annType:"+ annotationItem.getEncodedAnnotation().annotationType.getTypeDescriptor()); EncodedValue v = encodedAnnotation.values[m]; if (v.getValueType() == ValueType.VALUE_ARRAY) { ArrayEncodedValue array = (ArrayEncodedValue) v; EncodedValue[] values = array.values; for (EncodedValue encodedValue : values) { String name = null; if (encodedValue.getValueType() == ValueType.VALUE_TYPE) { name = ((TypeEncodedValue) encodedValue).value.getTypeDescriptor(); MEClass exceptionClass; try { //System.out.println("[DexClassReader] exceptionClass: "+ Util.getClassName(name)); //bugfix, if a new exception class throw itself in some method, it will overflow. if (Util.getClassName(name).equals(clazz.getName())) { exceptionClass = clazz; } else { exceptionClass = clazz.getResource().getContext().getMEClass(Util.getClassName(name)); } } catch (ClassNotFoundException cnfe) { exceptionClass = new UnknownClass(Util.getClassName(name), clazz.getResource()); } exceptions.add(exceptionClass); exceptionSize++; } } } } } } res.setExceptions(exceptions.toArray(new MEClass[exceptionSize])); } } catch (IllegalArgumentException e) { if (!e.getMessage().equals("The method has no code")) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } } return res; } public static void prepareClassPath(DexClass clazz) { ApkClassContext thisContext = (ApkClassContext) ((clazz.getResource().getContext())); if (ClassPath.theClassPath == null || ClassPath.dexFile != thisContext.getDex()) { System.out.println("[DexClassReader] Classpath reset for " + thisContext.getFile().getPath()); ClassPath.theClassPath = null; ClassPath.theClassPath = new ClassPath(); ClassPath.theClassPath.tempClasses = new LinkedHashMap<String, ClassPath.TempClassInfo>(); CollaborateClassContext ctx = MainFrame.getInstance().getResolver().getReferenceContext(); MEClassContext[] contexts = ctx.getContexts(); for (MEClassContext context : contexts) { if (context instanceof ApkClassContext) { ApkClassContext apkContext = (ApkClassContext) context; ClassPath.theClassPath.loadDexFile(apkContext.getFile().getPath(), apkContext.getDex()); } } ClassPath.theClassPath.loadDexFile(thisContext.getFile().getPath(), thisContext.getDex()); if (ClassPath.theClassPath.tempClasses.containsKey("Ljava/lang/Object;")) { ClassDef classDef = null; try { classDef = ClassPath.loadClassDef("Ljava/lang/Object;"); assert classDef != null; } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, "Can not load java/lang/object"); } ClassPath.theClassPath.javaLangObjectClassDef = classDef; } else { throw new RuntimeException("Can not find java/lang/object"); } for (String classType : ClassPath.theClassPath.tempClasses.keySet()) { ClassDef classDef = null; try { classDef = ClassPath.loadClassDef(classType); assert classDef != null; } catch (Exception ex) { throw ExceptionWithContext.withContext(ex, String.format("Error while loading ClassPath class %s", classType)); } } for (String primitiveType : new String[] { "Z", "B", "S", "C", "I", "J", "F", "D" }) { ClassDef classDef = new PrimitiveClassDef(primitiveType); ClassPath.theClassPath.classDefs.put(primitiveType, classDef); } ClassPath.theClassPath.tempClasses = null; ClassPath.dexFile = thisContext.getDex(); } } }