diff --git a/CHANGES b/CHANGES index b9a49679..713d0d80 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,6 @@ v1.5.3 (TBA) +-Updated to smali/baksmali to v1.4.2 +-Fixed (issue #396) - Correctly handle android:debuggable while in debug mode. v1.5.2 (Released February 2 - 2013) Codename: Bug Fixes -Fixed (issue #299) - output smali filename errors to screen during rebuild instead of filestream diff --git a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java index 5879bb89..794cec60 100644 --- a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java +++ b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/ClassPath.java @@ -44,8 +44,6 @@ import static org.jf.dexlib.ClassDataItem.EncodedField; import static org.jf.dexlib.ClassDataItem.EncodedMethod; public class ClassPath { - public static boolean dontLoadClassPath = false; - private static ClassPath theClassPath = null; /** @@ -263,10 +261,6 @@ public class ClassPath { @Nonnull public static ClassDef getClassDef(String classType, boolean createUnresolvedClassDef) { - if (dontLoadClassPath) { - return null; - } - ClassDef classDef = theClassPath.classDefs.get(classType); if (classDef == null) { //if it's an array class, try to create it @@ -543,7 +537,7 @@ public class ClassPath { } public ClassDef getSuperclass() { - throw unresolvedValidationException(); + return theClassPath.javaLangObjectClassDef; } public int getClassDepth() { @@ -599,6 +593,10 @@ public class ClassPath { private final int classDepth; + // classes can only be public or package-private. Internally, any private/protected inner class is actually + // package-private. + private final boolean isPublic; + private final VirtualMethod[] vtable; //this maps a method name of the form method(III)Ljava/lang/String; to an integer @@ -641,6 +639,7 @@ public class ClassPath { implementedInterfaces.add(ClassPath.getClassDef("Ljava/lang/Cloneable;")); implementedInterfaces.add(ClassPath.getClassDef("Ljava/io/Serializable;")); isInterface = false; + isPublic = true; vtable = superclass.vtable; methodLookup = superclass.methodLookup; @@ -658,6 +657,7 @@ public class ClassPath { this.superclass = null; implementedInterfaces = null; isInterface = false; + isPublic = true; vtable = null; methodLookup = null; instanceFields = null; @@ -671,6 +671,7 @@ public class ClassPath { this.superclass = ClassPath.getClassDef("Ljava/lang/Object;"); implementedInterfaces = new TreeSet(); isInterface = false; + isPublic = true; vtable = superclass.vtable; methodLookup = superclass.methodLookup; @@ -685,6 +686,7 @@ public class ClassPath { protected ClassDef(UnresolvedClassInfo classInfo) { classType = classInfo.classType; + isPublic = classInfo.isPublic; isInterface = classInfo.isInterface; superclass = loadSuperclass(classInfo); @@ -738,6 +740,10 @@ public class ClassPath { return this.isInterface; } + public boolean isPublic() { + return this.isPublic; + } + public boolean extendsClass(ClassDef superclassDef) { if (superclassDef == null) { return false; @@ -1226,6 +1232,7 @@ public class ClassPath { private static class UnresolvedClassInfo { public final String dexFilePath; public final String classType; + public final boolean isPublic; public final boolean isInterface; public final String superclassType; public final String[] interfaces; @@ -1239,6 +1246,7 @@ public class ClassPath { classType = classDefItem.getClassType().getTypeDescriptor(); + isPublic = (classDefItem.getAccessFlags() & AccessFlags.PUBLIC.getValue()) != 0; isInterface = (classDefItem.getAccessFlags() & AccessFlags.INTERFACE.getValue()) != 0; TypeIdItem superclassType = classDefItem.getSuperclass(); diff --git a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java index be689ccd..d4cf3a8e 100644 --- a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java +++ b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/DeodexUtil.java @@ -64,19 +64,21 @@ public class DeodexUtil { return inlineMethodResolver.resolveExecuteInline(instruction); } - public FieldIdItem lookupField(ClassPath.ClassDef classDef, int fieldOffset) { - ClassPath.FieldDef field = classDef.getInstanceField(fieldOffset); + public FieldIdItem lookupField(ClassPath.ClassDef accessingClass, ClassPath.ClassDef instanceClass, + int fieldOffset) { + ClassPath.FieldDef field = instanceClass.getInstanceField(fieldOffset); if (field == null) { return null; } - return parseAndResolveField(classDef, field); + return parseAndResolveField(accessingClass, instanceClass, field); } private static final Pattern shortMethodPattern = Pattern.compile("([^(]+)\\(([^)]*)\\)(.+)"); - public MethodIdItem lookupVirtualMethod(ClassPath.ClassDef classDef, int methodIndex) { - String method = classDef.getVirtualMethod(methodIndex); + public MethodIdItem lookupVirtualMethod(ClassPath.ClassDef accessingClass, ClassPath.ClassDef instanceClass, + int methodIndex) { + String method = instanceClass.getVirtualMethod(methodIndex); if (method == null) { return null; } @@ -91,20 +93,20 @@ public class DeodexUtil { String methodParams = m.group(2); String methodRet = m.group(3); - if (classDef instanceof ClassPath.UnresolvedClassDef) { + if (instanceClass instanceof ClassPath.UnresolvedClassDef) { //if this is an unresolved class, the only way getVirtualMethod could have found a method is if the virtual //method being looked up was a method on java.lang.Object. - classDef = ClassPath.getClassDef("Ljava/lang/Object;"); - } else if (classDef.isInterface()) { - classDef = classDef.getSuperclass(); - assert classDef != null; + instanceClass = ClassPath.getClassDef("Ljava/lang/Object;"); + } else if (instanceClass.isInterface()) { + instanceClass = instanceClass.getSuperclass(); + assert instanceClass != null; } - return parseAndResolveMethod(classDef, methodName, methodParams, methodRet); + return parseAndResolveMethod(accessingClass, instanceClass, methodName, methodParams, methodRet); } - private MethodIdItem parseAndResolveMethod(ClassPath.ClassDef classDef, String methodName, String methodParams, - String methodRet) { + private MethodIdItem parseAndResolveMethod(ClassPath.ClassDef accessingClass, ClassPath.ClassDef definingClass, + String methodName, String methodParams, String methodRet) { StringIdItem methodNameItem = StringIdItem.lookupStringIdItem(dexFile, methodName); if (methodNameItem == null) { return null; @@ -197,14 +199,14 @@ public class DeodexUtil { return null; } - ClassPath.ClassDef methodClassDef = classDef; + ClassPath.ClassDef methodClassDef = definingClass; do { TypeIdItem classTypeItem = TypeIdItem.lookupTypeIdItem(dexFile, methodClassDef.getClassType()); if (classTypeItem != null) { MethodIdItem methodIdItem = MethodIdItem.lookupMethodIdItem(dexFile, classTypeItem, protoItem, methodNameItem); - if (methodIdItem != null) { + if (methodIdItem != null && checkClassAccess(accessingClass, methodClassDef)) { return methodIdItem; } } @@ -214,7 +216,28 @@ public class DeodexUtil { return null; } - private FieldIdItem parseAndResolveField(ClassPath.ClassDef classDef, ClassPath.FieldDef field) { + private static boolean checkClassAccess(ClassPath.ClassDef accessingClass, ClassPath.ClassDef definingClass) { + return definingClass.isPublic() || + getPackage(accessingClass.getClassType()).equals(getPackage(definingClass.getClassType())); + } + + private static String getPackage(String classRef) { + int lastSlash = classRef.lastIndexOf('/'); + if (lastSlash < 0) { + return ""; + } + return classRef.substring(1, lastSlash); + } + + /** + * + * @param accessingClass The class that contains the field reference. I.e. the class being deodexed + * @param instanceClass The inferred class type of the object that the field is being accessed on + * @param field The field being accessed + * @return The FieldIdItem of the resolved field + */ + private FieldIdItem parseAndResolveField(ClassPath.ClassDef accessingClass, ClassPath.ClassDef instanceClass, + ClassPath.FieldDef field) { String definingClass = field.definingClass; String fieldName = field.name; String fieldType = field.type; @@ -229,7 +252,7 @@ public class DeodexUtil { return null; } - ClassPath.ClassDef fieldClass = classDef; + ClassPath.ClassDef fieldClass = instanceClass; ArrayList parents = new ArrayList(); parents.add(fieldClass); @@ -248,7 +271,7 @@ public class DeodexUtil { } FieldIdItem fieldIdItem = FieldIdItem.lookupFieldIdItem(dexFile, classTypeItem, fieldTypeItem, fieldNameItem); - if (fieldIdItem != null) { + if (fieldIdItem != null && checkClassAccess(accessingClass, fieldClass)) { return fieldIdItem; } } @@ -283,7 +306,8 @@ public class DeodexUtil { private void loadMethod(DeodexUtil deodexUtil) { ClassPath.ClassDef classDef = ClassPath.getClassDef(classType); - this.methodIdItem = deodexUtil.parseAndResolveMethod(classDef, methodName, parameters, returnType); + this.methodIdItem = deodexUtil.parseAndResolveMethod(classDef, classDef, methodName, parameters, + returnType); } public String getMethodString() { diff --git a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java index 17bf86a3..9edf37b1 100644 --- a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java +++ b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Code/Analysis/MethodAnalyzer.java @@ -3578,7 +3578,14 @@ public class MethodAnalyzer { return false; } - FieldIdItem fieldIdItem = deodexUtil.lookupField(objectRegisterType.type, fieldOffset); + ClassPath.ClassDef accessingClass = + ClassPath.getClassDef(this.encodedMethod.method.getContainingClass(), false); + if (accessingClass == null) { + throw new ExceptionWithContext(String.format("Could not find ClassDef for current class: %s", + this.encodedMethod.method.getContainingClass())); + } + + FieldIdItem fieldIdItem = deodexUtil.lookupField(accessingClass, objectRegisterType.type, fieldOffset); if (fieldIdItem == null) { throw new ValidationException(String.format("Could not resolve the field in class %s at offset %d", objectRegisterType.type.getClassType(), fieldOffset)); @@ -3621,12 +3628,16 @@ public class MethodAnalyzer { } MethodIdItem methodIdItem = null; + ClassPath.ClassDef accessingClass = + ClassPath.getClassDef(this.encodedMethod.method.getContainingClass(), false); + if (accessingClass == null) { + throw new ExceptionWithContext(String.format("Could not find ClassDef for current class: %s", + this.encodedMethod.method.getContainingClass())); + } if (isSuper) { - ClassPath.ClassDef classDef = ClassPath.getClassDef(this.encodedMethod.method.getContainingClass(), false); - assert classDef != null; - - if (classDef.getSuperclass() != null) { - methodIdItem = deodexUtil.lookupVirtualMethod(classDef.getSuperclass(), methodIndex); + if (accessingClass.getSuperclass() != null) { + methodIdItem = deodexUtil.lookupVirtualMethod(accessingClass, accessingClass.getSuperclass(), + methodIndex); } if (methodIdItem == null) { @@ -3634,10 +3645,10 @@ public class MethodAnalyzer { //of from the superclass (although the superclass method is still what would actually be called). //And so the MethodIdItem for the superclass method may not be in the dex file. Let's try to get the //MethodIdItem for the method in the current class instead - methodIdItem = deodexUtil.lookupVirtualMethod(classDef, methodIndex); + methodIdItem = deodexUtil.lookupVirtualMethod(accessingClass, accessingClass, methodIndex); } } else{ - methodIdItem = deodexUtil.lookupVirtualMethod(objectRegisterType.type, methodIndex); + methodIdItem = deodexUtil.lookupVirtualMethod(accessingClass, objectRegisterType.type, methodIndex); } if (methodIdItem == null) { diff --git a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Util/ByteArrayInput.java b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Util/ByteArrayInput.java index e74a62ec..add68ee8 100644 --- a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Util/ByteArrayInput.java +++ b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Util/ByteArrayInput.java @@ -40,6 +40,9 @@ public class ByteArrayInput /** >= 0; current read cursor */ private int cursor; + /* buffer for reading UTF-8 strings */ + private char[] buffer = null; + /** * Constructs an instance with the given data * @@ -291,7 +294,11 @@ public class ByteArrayInput //skip the terminating null cursor++; - return Utf8Utils.utf8BytesToString(data, startPosition, byteCount); + if (buffer == null || buffer.length < byteCount) { + buffer = new char[byteCount]; + } + + return Utf8Utils.utf8BytesToString(buffer, data, startPosition, byteCount); } /** {@inheritDoc} */ diff --git a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Util/Utf8Utils.java b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Util/Utf8Utils.java index 0011bc50..8e0adfab 100644 --- a/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Util/Utf8Utils.java +++ b/brut.apktool.smali/dexlib/src/main/java/org/jf/dexlib/Util/Utf8Utils.java @@ -68,23 +68,16 @@ public final class Utf8Utils { return result; } - private static char[] tempBuffer = null; - /** * Converts an array of UTF-8 bytes into a string. * - * This method uses a global buffer to avoid having to allocate one every time, so it is *not* thread-safe - * + * @param buffer a buffer to hold the chars as they are read. Make sure the length of the array is at least 'length' * @param bytes non-null; the bytes to convert * @param start the start index of the utf8 string to convert * @param length the length of the utf8 string to convert, not including any null-terminator that might be present * @return non-null; the converted string */ - public static String utf8BytesToString(byte[] bytes, int start, int length) { - if (tempBuffer == null || tempBuffer.length < length) { - tempBuffer = new char[length]; - } - char[] chars = tempBuffer; + public static String utf8BytesToString(char[] buffer, byte[] bytes, int start, int length) { int outAt = 0; for (int at = start; length > 0; /*at*/) { @@ -157,11 +150,11 @@ public final class Utf8Utils { return throwBadUtf8(v0, at); } } - chars[outAt] = out; + buffer[outAt] = out; outAt++; } - return new String(chars, 0, outAt); + return new String(buffer, 0, outAt); } /** diff --git a/brut.apktool.smali/smali/src/main/antlr3/smaliTreeWalker.g b/brut.apktool.smali/smali/src/main/antlr3/smaliTreeWalker.g index fb142428..2fdf715b 100644 --- a/brut.apktool.smali/smali/src/main/antlr3/smaliTreeWalker.g +++ b/brut.apktool.smali/smali/src/main/antlr3/smaliTreeWalker.g @@ -36,6 +36,7 @@ options { @header { package org.jf.smali; +import com.google.common.collect.ImmutableSortedMap; import java.util.HashMap; import java.util.LinkedList; import java.util.regex.*; @@ -1616,23 +1617,26 @@ annotation_element returns[StringIdItem elementName, EncodedValue elementValue] }; subannotation returns[TypeIdItem annotationType, StringIdItem[\] elementNames, EncodedValue[\] elementValues] - : {ArrayList elementNamesList = new ArrayList(); - ArrayList elementValuesList = new ArrayList();} + : {ImmutableSortedMap.Builder elementBuilder = + ImmutableSortedMap.naturalOrder();} ^(I_SUBANNOTATION class_type_descriptor (annotation_element { - elementNamesList.add($annotation_element.elementName); - elementValuesList.add($annotation_element.elementValue); + elementBuilder.put($annotation_element.elementName, $annotation_element.elementValue); } )* ) { + ImmutableSortedMap elementMap = elementBuilder.build(); + $annotationType = $class_type_descriptor.type; - $elementNames = new StringIdItem[elementNamesList.size()]; - elementNamesList.toArray($elementNames); - $elementValues = new EncodedValue[elementValuesList.size()]; - elementValuesList.toArray($elementValues); + + $elementNames = new StringIdItem[elementMap.size()]; + $elementValues = new EncodedValue[elementMap.size()]; + + elementMap.keySet().toArray($elementNames); + elementMap.values().toArray($elementValues); }; field_literal returns[FieldIdItem value] diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java index 5dcc40d2..8a3f74c0 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/Androlib.java @@ -228,9 +228,6 @@ public class Androlib { if (!working.exists()) { return false; } - if (flags.get("debug")) { - LOGGER.warning("Debug mode not available."); - } File stored = new File(appDir, APK_DIRNAME + "/classes.dex"); if (flags.get("forceBuildAll") || isModified(working, stored)) { LOGGER.info("Copying classes.dex file..."); @@ -379,7 +376,13 @@ public class Androlib { if (!flags.get("forceBuildAll")) { LOGGER.info("Checking whether resources has changed..."); } + File apkDir = new File(appDir, APK_DIRNAME); + + if (flags.get("debug")) { + mAndRes.remove_application_debug(new File(apkDir,"AndroidManifest.xml").getAbsolutePath()); + } + if (flags.get("forceBuildAll") || isModified(newFiles(APK_MANIFEST_FILENAMES, appDir), newFiles(APK_MANIFEST_FILENAMES, apkDir))) { diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java index 2553b226..f360dba5 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/AndrolibResources.java @@ -38,6 +38,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; @@ -46,6 +47,7 @@ import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.apache.commons.io.IOUtils; import org.xml.sax.SAXException; @@ -151,6 +153,48 @@ final public class AndrolibResources { } } + public void remove_application_debug(String filePath) + throws AndrolibException { + + // change application:debug to true + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory + .newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(filePath.toString()); + + Node application = doc.getElementById("application"); + + // load attr + NamedNodeMap attr = application.getAttributes(); + Node debugAttr = attr.getNamedItem("debug"); + + // remove application:debug + if (debugAttr != null) { + attr.removeNamedItem("debug"); + } + + // save manifest + TransformerFactory transformerFactory = TransformerFactory + .newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(new File(filePath)); + transformer.transform(source, result); + + } catch (ParserConfigurationException ex) { + throw new AndrolibException(ex); + } catch (SAXException ex) { + throw new AndrolibException(ex); + } catch (IOException ex) { + throw new AndrolibException(ex); + } catch (TransformerConfigurationException ex) { + throw new AndrolibException(ex); + } catch (TransformerException ex) { + throw new AndrolibException(ex); + } + } + public void adjust_package_manifest(ResTable resTable, String filePath) throws AndrolibException { @@ -182,7 +226,6 @@ final public class AndrolibResources { nodeAttr.setNodeValue(packageInfo.get("cur_package")); // re-save manifest. - // fancy an auto-sort :p TransformerFactory transformerFactory = TransformerFactory .newInstance(); Transformer transformer = transformerFactory.newTransformer();