/* * Copyright (C) 2014 The Android Open Source Project * * 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 dexfuzz.program; import dexfuzz.Log; import dexfuzz.rawdex.FieldIdItem; import dexfuzz.rawdex.MethodIdItem; import dexfuzz.rawdex.Offset; import dexfuzz.rawdex.Offsettable; import dexfuzz.rawdex.ProtoIdItem; import dexfuzz.rawdex.RawDexFile; import dexfuzz.rawdex.RawDexObject.IndexUpdateKind; import dexfuzz.rawdex.StringDataItem; import dexfuzz.rawdex.StringIdItem; import dexfuzz.rawdex.TypeIdItem; import dexfuzz.rawdex.TypeItem; import dexfuzz.rawdex.TypeList; import java.util.ArrayList; import java.util.List; /** * Responsible for the finding and creation of TypeIds, MethodIds, FieldIds, and StringIds, * during mutation. */ public class IdCreator { private RawDexFile rawDexFile; public IdCreator(RawDexFile rawDexFile) { this.rawDexFile = rawDexFile; } private int findProtoIdInsertionPoint(String signature) { int returnTypeIdx = findTypeId(convertSignatureToReturnType(signature)); String[] parameterListStrings = convertSignatureToParameterList(signature); TypeList parameterList = null; if (parameterListStrings.length > 0) { parameterList = findTypeList(parameterListStrings); } if (returnTypeIdx < 0) { Log.errorAndQuit("Did not create necessary return type before finding insertion " + "point for new proto!"); } if (parameterListStrings.length > 0 && parameterList == null) { Log.errorAndQuit("Did not create necessary parameter list before finding insertion " + "point for new proto!"); } int protoIdIdx = 0; for (ProtoIdItem protoId : rawDexFile.protoIds) { if (returnTypeIdx < protoId.returnTypeIdx) { break; } if (returnTypeIdx == protoId.returnTypeIdx && parameterListStrings.length == 0) { break; } if (returnTypeIdx == protoId.returnTypeIdx && parameterListStrings.length > 0 && protoId.parametersOff.pointsToSomething() && parameterList.comesBefore( (TypeList) protoId.parametersOff.getPointedToItem())) { break; } protoIdIdx++; } return protoIdIdx; } private int findMethodIdInsertionPoint(String className, String methodName, String signature) { int classIdx = findTypeId(className); int nameIdx = findString(methodName); int protoIdx = findProtoId(signature); if (classIdx < 0 || nameIdx < 0 || protoIdx < 0) { Log.errorAndQuit("Did not create necessary class, name or proto strings before finding " + " insertion point for new method!"); } int methodIdIdx = 0; for (MethodIdItem methodId : rawDexFile.methodIds) { if (classIdx < methodId.classIdx) { break; } if (classIdx == methodId.classIdx && nameIdx < methodId.nameIdx) { break; } if (classIdx == methodId.classIdx && nameIdx == methodId.nameIdx && protoIdx < methodId.protoIdx) { break; } methodIdIdx++; } return methodIdIdx; } private int findTypeIdInsertionPoint(String className) { int descriptorIdx = findString(className); if (descriptorIdx < 0) { Log.errorAndQuit("Did not create necessary descriptor string before finding " + " insertion point for new type!"); } int typeIdIdx = 0; for (TypeIdItem typeId : rawDexFile.typeIds) { if (descriptorIdx < typeId.descriptorIdx) { break; } typeIdIdx++; } return typeIdIdx; } private int findStringDataInsertionPoint(String string) { int stringDataIdx = 0; for (StringDataItem stringData : rawDexFile.stringDatas) { if (stringData.getSize() > 0 && stringData.getString().compareTo(string) >= 0) { break; } stringDataIdx++; } return stringDataIdx; } private int findFieldIdInsertionPoint(String className, String typeName, String fieldName) { int classIdx = findTypeId(className); int typeIdx = findTypeId(typeName); int nameIdx = findString(fieldName); if (classIdx < 0 || typeIdx < 0 || nameIdx < 0) { Log.errorAndQuit("Did not create necessary class, type or name strings before finding " + " insertion point for new field!"); } int fieldIdIdx = 0; for (FieldIdItem fieldId : rawDexFile.fieldIds) { if (classIdx < fieldId.classIdx) { break; } if (classIdx == fieldId.classIdx && nameIdx < fieldId.nameIdx) { break; } if (classIdx == fieldId.classIdx && nameIdx == fieldId.nameIdx && typeIdx < fieldId.typeIdx) { break; } fieldIdIdx++; } return fieldIdIdx; } private int createMethodId(String className, String methodName, String signature) { if (rawDexFile.methodIds.size() >= 65536) { Log.errorAndQuit("Referenced too many methods for the DEX file."); } // Search for (or create) the prototype. int protoIdx = findOrCreateProtoId(signature); // Search for (or create) the owning class. // NB: findOrCreateProtoId could create new types, so this must come // after it! int typeIdIdx = findOrCreateTypeId(className); // Search for (or create) the string representing the method name. // NB: findOrCreateProtoId/TypeId could create new strings, so this must come // after them! int methodNameStringIdx = findOrCreateString(methodName); // Create MethodIdItem. MethodIdItem newMethodId = new MethodIdItem(); newMethodId.classIdx = (short) typeIdIdx; newMethodId.protoIdx = (short) protoIdx; newMethodId.nameIdx = methodNameStringIdx; // MethodIds must be ordered. int newMethodIdIdx = findMethodIdInsertionPoint(className, methodName, signature); rawDexFile.methodIds.add(newMethodIdIdx, newMethodId); // Insert into OffsetTracker. if (newMethodIdIdx == 0) { rawDexFile.getOffsetTracker() .insertNewOffsettableAsFirstOfType(newMethodId, rawDexFile); } else { MethodIdItem prevMethodId = rawDexFile.methodIds.get(newMethodIdIdx - 1); rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newMethodId, prevMethodId); } Log.info(String.format("Created new MethodIdItem for %s %s %s, index: 0x%04x", className, methodName, signature, newMethodIdIdx)); // Now that we've potentially moved a lot of method IDs along, all references // to them need to be updated. rawDexFile.incrementIndex(IndexUpdateKind.METHOD_ID, newMethodIdIdx); // All done, return the index for the new method. return newMethodIdIdx; } private int findMethodId(String className, String methodName, String signature) { int classIdx = findTypeId(className); if (classIdx == -1) { return -1; } int nameIdx = findString(methodName); if (nameIdx == -1) { return -1; } int protoIdx = findProtoId(signature); if (nameIdx == -1) { return -1; } int methodIdIdx = 0; for (MethodIdItem methodId : rawDexFile.methodIds) { if (classIdx == methodId.classIdx && nameIdx == methodId.nameIdx && protoIdx == methodId.protoIdx) { return methodIdIdx; } methodIdIdx++; } return -1; } /** * Given a fully qualified class name (Ljava/lang/System;), method name (gc) and * and signature (()V), either find the MethodId in our DEX file's table, or create it. */ public int findOrCreateMethodId(String className, String methodName, String shorty) { int methodIdIdx = findMethodId(className, methodName, shorty); if (methodIdIdx != -1) { return methodIdIdx; } return createMethodId(className, methodName, shorty); } private int createTypeId(String className) { if (rawDexFile.typeIds.size() >= 65536) { Log.errorAndQuit("Referenced too many classes for the DEX file."); } // Search for (or create) the string representing the class descriptor. int descriptorStringIdx = findOrCreateString(className); // Create TypeIdItem. TypeIdItem newTypeId = new TypeIdItem(); newTypeId.descriptorIdx = descriptorStringIdx; // TypeIds must be ordered. int newTypeIdIdx = findTypeIdInsertionPoint(className); rawDexFile.typeIds.add(newTypeIdIdx, newTypeId); // Insert into OffsetTracker. if (newTypeIdIdx == 0) { rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newTypeId, rawDexFile); } else { TypeIdItem prevTypeId = rawDexFile.typeIds.get(newTypeIdIdx - 1); rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newTypeId, prevTypeId); } Log.info(String.format("Created new ClassIdItem for %s, index: 0x%04x", className, newTypeIdIdx)); // Now that we've potentially moved a lot of type IDs along, all references // to them need to be updated. rawDexFile.incrementIndex(IndexUpdateKind.TYPE_ID, newTypeIdIdx); // All done, return the index for the new class. return newTypeIdIdx; } private int findTypeId(String className) { int descriptorIdx = findString(className); if (descriptorIdx == -1) { return -1; } // Search for class. int typeIdIdx = 0; for (TypeIdItem typeId : rawDexFile.typeIds) { if (descriptorIdx == typeId.descriptorIdx) { return typeIdIdx; } typeIdIdx++; } return -1; } /** * Given a fully qualified class name (Ljava/lang/System;) * either find the TypeId in our DEX file's table, or create it. */ public int findOrCreateTypeId(String className) { int typeIdIdx = findTypeId(className); if (typeIdIdx != -1) { return typeIdIdx; } return createTypeId(className); } private int createString(String string) { // Didn't find it, create one... int stringsCount = rawDexFile.stringIds.size(); if (stringsCount != rawDexFile.stringDatas.size()) { Log.errorAndQuit("Corrupted DEX file, len(StringIDs) != len(StringDatas)"); } // StringData must be ordered. int newStringIdx = findStringDataInsertionPoint(string); // Create StringDataItem. StringDataItem newStringData = new StringDataItem(); newStringData.setSize(string.length()); newStringData.setString(string); rawDexFile.stringDatas.add(newStringIdx, newStringData); // Insert into OffsetTracker. // (Need to save the Offsettable, because the StringIdItem will point to it.) Offsettable offsettableStringData = null; if (newStringIdx == 0) { offsettableStringData = rawDexFile.getOffsetTracker() .insertNewOffsettableAsFirstOfType(newStringData, rawDexFile); } else { StringDataItem prevStringData = rawDexFile.stringDatas.get(newStringIdx - 1); offsettableStringData = rawDexFile.getOffsetTracker() .insertNewOffsettableAfter(newStringData, prevStringData); } // Create StringIdItem. StringIdItem newStringId = new StringIdItem(); newStringId.stringDataOff = new Offset(false); newStringId.stringDataOff.pointToNew(offsettableStringData); rawDexFile.stringIds.add(newStringIdx, newStringId); // Insert into OffsetTracker. if (newStringIdx == 0) { rawDexFile.getOffsetTracker() .insertNewOffsettableAsFirstOfType(newStringId, rawDexFile); } else { StringIdItem prevStringId = rawDexFile.stringIds.get(newStringIdx - 1); rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newStringId, prevStringId); } Log.info(String.format("Created new StringIdItem and StringDataItem for %s, index: 0x%04x", string, newStringIdx)); // Now that we've potentially moved a lot of string IDs along, all references // to them need to be updated. rawDexFile.incrementIndex(IndexUpdateKind.STRING_ID, newStringIdx); // All done, return the index for the new string. return newStringIdx; } private int findString(String string) { // Search for string. int stringIdx = 0; for (StringDataItem stringDataItem : rawDexFile.stringDatas) { if (stringDataItem.getSize() == 0 && string.isEmpty()) { return stringIdx; } else if (stringDataItem.getSize() > 0 && stringDataItem.getString().equals(string)) { return stringIdx; } stringIdx++; } return -1; } /** * Given a string, either find the StringId in our DEX file's table, or create it. */ public int findOrCreateString(String string) { int stringIdx = findString(string); if (stringIdx != -1) { return stringIdx; } return createString(string); } private int createFieldId(String className, String typeName, String fieldName) { if (rawDexFile.fieldIds.size() >= 65536) { Log.errorAndQuit("Referenced too many fields for the DEX file."); } // Search for (or create) the owning class. int classIdx = findOrCreateTypeId(className); // Search for (or create) the field's type. int typeIdx = findOrCreateTypeId(typeName); // The creation of the typeIdx may have changed the classIdx, search again! classIdx = findOrCreateTypeId(className); // Search for (or create) the string representing the field name. int fieldNameStringIdx = findOrCreateString(fieldName); // Create FieldIdItem. FieldIdItem newFieldId = new FieldIdItem(); newFieldId.classIdx = (short) classIdx; newFieldId.typeIdx = (short) typeIdx; newFieldId.nameIdx = fieldNameStringIdx; // FieldIds must be ordered. int newFieldIdIdx = findFieldIdInsertionPoint(className, typeName, fieldName); rawDexFile.fieldIds.add(newFieldIdIdx, newFieldId); // Insert into OffsetTracker. if (newFieldIdIdx == 0 && rawDexFile.fieldIds.size() == 1) { // Special case: we didn't have any fields before! rawDexFile.getOffsetTracker() .insertNewOffsettableAsFirstEverField(newFieldId, rawDexFile); } else if (newFieldIdIdx == 0) { rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newFieldId, rawDexFile); } else { FieldIdItem prevFieldId = rawDexFile.fieldIds.get(newFieldIdIdx - 1); rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newFieldId, prevFieldId); } Log.info(String.format("Created new FieldIdItem for %s %s %s, index: 0x%04x", className, typeName, fieldName, newFieldIdIdx)); // Now that we've potentially moved a lot of field IDs along, all references // to them need to be updated. rawDexFile.incrementIndex(IndexUpdateKind.FIELD_ID, newFieldIdIdx); // All done, return the index for the new field. return newFieldIdIdx; } private int findFieldId(String className, String typeName, String fieldName) { int classIdx = findTypeId(className); if (classIdx == -1) { return -1; } int typeIdx = findTypeId(typeName); if (typeIdx == -1) { return -1; } int nameIdx = findString(fieldName); if (nameIdx == -1) { return -1; } int fieldIdIdx = 0; for (FieldIdItem fieldId : rawDexFile.fieldIds) { if (classIdx == fieldId.classIdx && typeIdx == fieldId.typeIdx && nameIdx == fieldId.nameIdx) { return fieldIdIdx; } fieldIdIdx++; } return -1; } /** * Given a field's fully qualified class name, type name, and name, * either find the FieldId in our DEX file's table, or create it. */ public int findOrCreateFieldId(String className, String typeName, String fieldName) { int fieldIdx = findFieldId(className, typeName, fieldName); if (fieldIdx != -1) { return fieldIdx; } return createFieldId(className, typeName, fieldName); } /** * Returns a 1 or 2 element String[]. If 1 element, the only element is the return type * part of the signature. If 2 elements, the first is the parameters, the second is * the return type. */ private String[] convertSignatureToParametersAndReturnType(String signature) { if (signature.charAt(0) != '(' || !signature.contains(")")) { Log.errorAndQuit("Invalid signature: " + signature); } String[] elems = signature.substring(1).split("\\)"); return elems; } private String[] convertSignatureToParameterList(String signature) { String[] elems = convertSignatureToParametersAndReturnType(signature); String parameters = ""; if (elems.length == 2) { parameters = elems[0]; } List parameterList = new ArrayList(); int typePointer = 0; while (typePointer != parameters.length()) { if (elems[0].charAt(typePointer) == 'L') { int start = typePointer; // Read up to the next ; while (elems[0].charAt(typePointer) != ';') { typePointer++; } parameterList.add(parameters.substring(start, typePointer + 1)); } else { parameterList.add(Character.toString(parameters.charAt(typePointer))); } typePointer++; } return parameterList.toArray(new String[]{}); } private String convertSignatureToReturnType(String signature) { String[] elems = convertSignatureToParametersAndReturnType(signature); String returnType = ""; if (elems.length == 1) { returnType = elems[0]; } else { returnType = elems[1]; } return returnType; } private String convertSignatureToShorty(String signature) { String[] elems = convertSignatureToParametersAndReturnType(signature); StringBuilder shortyBuilder = new StringBuilder(); String parameters = ""; String returnType = ""; if (elems.length == 1) { shortyBuilder.append("V"); } else { parameters = elems[0]; returnType = elems[1]; char returnChar = returnType.charAt(0); // Arrays are references in shorties. if (returnChar == '[') { returnChar = 'L'; } shortyBuilder.append(returnChar); } int typePointer = 0; while (typePointer != parameters.length()) { if (parameters.charAt(typePointer) == 'L') { shortyBuilder.append('L'); // Read up to the next ; while (parameters.charAt(typePointer) != ';') { typePointer++; if (typePointer == parameters.length()) { Log.errorAndQuit("Illegal type specified in signature - L with no ;!"); } } } else if (parameters.charAt(typePointer) == '[') { // Arrays are references in shorties. shortyBuilder.append('L'); // Read past all the [s while (parameters.charAt(typePointer) == '[') { typePointer++; } if (parameters.charAt(typePointer) == 'L') { // Read up to the next ; while (parameters.charAt(typePointer) != ';') { typePointer++; if (typePointer == parameters.length()) { Log.errorAndQuit("Illegal type specified in signature - L with no ;!"); } } } } else { shortyBuilder.append(parameters.charAt(typePointer)); } typePointer++; } return shortyBuilder.toString(); } private Integer[] convertParameterListToTypeIdList(String[] parameterList) { List typeIdList = new ArrayList(); for (String parameter : parameterList) { int typeIdx = findTypeId(parameter); if (typeIdx == -1) { return null; } typeIdList.add(typeIdx); } return typeIdList.toArray(new Integer[]{}); } private TypeList createTypeList(String[] parameterList) { TypeList typeList = new TypeList(); List typeItemList = new ArrayList(); // This must be done as two passes, one to create all the types, // and then one to put them in the type list. for (String parameter : parameterList) { findOrCreateTypeId(parameter); } // Now actually put them in the list. for (String parameter : parameterList) { TypeItem typeItem = new TypeItem(); typeItem.typeIdx = (short) findOrCreateTypeId(parameter); typeItemList.add(typeItem); } typeList.list = typeItemList.toArray(new TypeItem[]{}); typeList.size = typeItemList.size(); // Insert into OffsetTracker. if (rawDexFile.typeLists == null) { // Special case: we didn't have any fields before! Log.info("Need to create first type list."); rawDexFile.typeLists = new ArrayList(); rawDexFile.getOffsetTracker() .insertNewOffsettableAsFirstEverTypeList(typeList, rawDexFile); } else { TypeList prevTypeList = rawDexFile.typeLists.get(rawDexFile.typeLists.size() - 1); rawDexFile.getOffsetTracker().insertNewOffsettableAfter(typeList, prevTypeList); } // Finally, add this new TypeList to the list of them. rawDexFile.typeLists.add(typeList); return typeList; } private TypeList findTypeList(String[] parameterList) { Integer[] typeIdList = convertParameterListToTypeIdList(parameterList); if (typeIdList == null) { return null; } if (rawDexFile.typeLists == null) { // There's no type lists yet! return null; } for (TypeList typeList : rawDexFile.typeLists) { if (typeList.size != typeIdList.length) { continue; } boolean found = true; int idx = 0; for (TypeItem typeItem : typeList.list) { if (typeItem.typeIdx != typeIdList[idx]) { found = false; break; } idx++; } if (found && idx == parameterList.length) { return typeList; } } return null; } private TypeList findOrCreateTypeList(String[] parameterList) { TypeList typeList = findTypeList(parameterList); if (typeList != null) { return typeList; } return createTypeList(parameterList); } private int createProtoId(String signature) { String shorty = convertSignatureToShorty(signature); String returnType = convertSignatureToReturnType(signature); String[] parameterList = convertSignatureToParameterList(signature); if (rawDexFile.protoIds.size() >= 65536) { Log.errorAndQuit("Referenced too many protos for the DEX file."); } TypeList typeList = null; Offsettable typeListOffsettable = null; if (parameterList.length > 0) { // Search for (or create) the parameter list. typeList = findOrCreateTypeList(parameterList); typeListOffsettable = rawDexFile.getOffsetTracker().getOffsettableForItem(typeList); } // Search for (or create) the return type. int returnTypeIdx = findOrCreateTypeId(returnType); // Search for (or create) the shorty string. int shortyIdx = findOrCreateString(shorty); // Create ProtoIdItem. ProtoIdItem newProtoId = new ProtoIdItem(); newProtoId.shortyIdx = shortyIdx; newProtoId.returnTypeIdx = returnTypeIdx; newProtoId.parametersOff = new Offset(false); if (parameterList.length > 0) { newProtoId.parametersOff.pointToNew(typeListOffsettable); } // ProtoIds must be ordered. int newProtoIdIdx = findProtoIdInsertionPoint(signature); rawDexFile.protoIds.add(newProtoIdIdx, newProtoId); // Insert into OffsetTracker. if (newProtoIdIdx == 0) { rawDexFile.getOffsetTracker().insertNewOffsettableAsFirstOfType(newProtoId, rawDexFile); } else { ProtoIdItem prevProtoId = rawDexFile.protoIds.get(newProtoIdIdx - 1); rawDexFile.getOffsetTracker().insertNewOffsettableAfter(newProtoId, prevProtoId); } Log.info(String.format("Created new ProtoIdItem for %s, index: 0x%04x", signature, newProtoIdIdx)); // Now that we've potentially moved a lot of proto IDs along, all references // to them need to be updated. rawDexFile.incrementIndex(IndexUpdateKind.PROTO_ID, newProtoIdIdx); // All done, return the index for the new proto. return newProtoIdIdx; } private int findProtoId(String signature) { String shorty = convertSignatureToShorty(signature); String returnType = convertSignatureToReturnType(signature); String[] parameterList = convertSignatureToParameterList(signature); int shortyIdx = findString(shorty); if (shortyIdx == -1) { return -1; } int returnTypeIdx = findTypeId(returnType); if (returnTypeIdx == -1) { return -1; } // Only look for a TypeList if there's a parameter list. TypeList typeList = null; if (parameterList.length > 0) { typeList = findTypeList(parameterList); if (typeList == null) { return -1; } } int protoIdIdx = 0; for (ProtoIdItem protoId : rawDexFile.protoIds) { if (parameterList.length > 0) { // With parameters. if (shortyIdx == protoId.shortyIdx && returnTypeIdx == protoId.returnTypeIdx && typeList.equals(protoId.parametersOff.getPointedToItem())) { return protoIdIdx; } } else { // Without parameters. if (shortyIdx == protoId.shortyIdx && returnTypeIdx == protoId.returnTypeIdx) { return protoIdIdx; } } protoIdIdx++; } return -1; } private int findOrCreateProtoId(String signature) { int protoIdx = findProtoId(signature); if (protoIdx != -1) { return protoIdx; } return createProtoId(signature); } }