diff options
Diffstat (limited to 'tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java')
-rw-r--r-- | tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java b/tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java new file mode 100644 index 0000000..34d609a --- /dev/null +++ b/tools/dexfuzz/src/dexfuzz/rawdex/OffsetTracker.java @@ -0,0 +1,513 @@ +/* + * 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.rawdex; + +import dexfuzz.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class allows the recording of both Offsettable items (that is, items that can be + * referred to by an offset somewhere else in the file - RawDexObjects) and Offsets. + * The idea in a nutshell is that for every Offsettable item we read, we remember + * its original position in the file using a map, and the order in which the Offsettables were + * written out. We also remember every Offset we read in, and its value. Then, after reading + * the whole file, we use the map to find the Offsettable it pointed at. + * Then, as we write out the file, for every Offsettable we write out, we record its new position, + * using the order we collected earlier. For every Offset we write out, we look at its Offsettable + * to see where it was written. If it hasn't been written yet, then we write out a blank value + * for the time being, remember where that blank value was written, and put the Offset into a + * table for patching once everything has been written out. + * There are some variables (index_after_map_list, restore_point) used for remembering certain + * points to jump forward and back to, because we cannot read and write the file out in exactly + * the same order. + * TODO: Perhaps it makes more sense to just reorder the offsettable_table once it's been read, + * in preparation for the order in which the file is written out? + * Finally, we provide methods for adding new Offsettable items into the right place in the order + * table. + */ +public class OffsetTracker { + /** + * A map from the original offset in the input DEX file to + * the Offsettable it points to. (That Offsettable will contain + * the actual item, and later on the new offset for the item when + * the item is written out. + */ + private Map<Integer, Offsettable> offsettableMap; + + /** + * A table of all Offsettables. We need to ensure we write out + * all items in the same order we read them in, to make sure we update + * the Offsettable.new_position field with the correct value wrt to + * the original_position field. + */ + private List<Offsettable> offsettableTable; + + /** + * A table of all offsets that is populated as we read in the DEX file. + * As the end, we find the correct Offsettable for the Offset in the above + * map, and associate them. + */ + private List<Offset> needsAssociationTable; + + /** + * A table of all offsets that we could not write out an updated offset for + * as we write out a DEX file. Will be checked after writing is complete, + * to allow specific patching of each offset's location as at that point + * all Offsettables will have been updated with their new position. + */ + private List<Offset> needsUpdateTable; + + /** + * Tracks how far we are through the offsettable_table as we write out the file. + */ + private int offsettableTableIdx; + + /** + * Because we write in a slightly different order to how we read + * (why? to read, we read the header, then the map list, and then use the map + * list to read everything else. + * when we write, we write the header, and then we cannot write the map list + * because we don't know where it will go yet, so we write everything else first) + * so: we remember the position in the offsettable_table after we read the map list, + * so we can skip there after we write out the header. + */ + private int indexAfterMapList; + + /** + * Related to index_after_map_list, this is the index we save when we're jumping back to + * write the map list. + */ + private int restorePoint; + + /** + * Create a new OffsetTracker. Should persist between parsing a DEX file, and outputting + * the mutated DEX file. + */ + public OffsetTracker() { + offsettableMap = new HashMap<Integer,Offsettable>(); + offsettableTable = new ArrayList<Offsettable>(); + needsAssociationTable = new ArrayList<Offset>(); + needsUpdateTable = new ArrayList<Offset>(); + } + + /** + * Lookup an Item by the offset it had in the input DEX file. + * @param offset The offset in the input DEX file. + * @return The corresponding Item. + */ + public RawDexObject getItemByOffset(int offset) { + return offsettableMap.get(offset).getItem(); + } + + /** + * As Items are read in, they call this function once they have word-aligned the file pointer, + * to record their position and themselves into an Offsettable object, that will be tracked. + * @param file Used for recording position into the new Offsettable. + * @param item Used for recording the relevant Item into the new Offsettable. + */ + public void getNewOffsettable(DexRandomAccessFile file, RawDexObject item) throws IOException { + Offsettable offsettable = new Offsettable(item, false); + offsettable.setOriginalPosition((int) file.getFilePointer()); + offsettableMap.put(offsettable.getOriginalPosition(), offsettable); + offsettableTable.add(offsettable); + } + + /** + * As Items read in Offsets, they call this function with the offset they originally + * read from the file, to allow later association with an Offsettable. + * @param originalOffset The original offset read from the input DEX file. + * @return An Offset that will later be associated with an Offsettable. + */ + public Offset getNewOffset(int originalOffset) throws IOException { + Offset offset = new Offset(false); + offset.setOriginalOffset(originalOffset); + needsAssociationTable.add(offset); + return offset; + } + + /** + * Only MapItem should call this method, when the MapItem that points to the header + * is read. + */ + public Offset getNewHeaderOffset(int originalOffset) throws IOException { + Offset offset = new Offset(true); + offset.setOriginalOffset(originalOffset); + needsAssociationTable.add(offset); + return offset; + } + + /** + * Call this after reading, to associate Offsets with Offsettables. + */ + public void associateOffsets() { + for (Offset offset : needsAssociationTable) { + if (offset.getOriginalOffset() == 0 && !(offset.pointsAtHeader())) { + offset.setPointsAtNull(); + } else { + offset.pointTo(offsettableMap.get(offset.getOriginalOffset())); + if (!offset.pointsToSomething()) { + Log.error(String.format("Couldn't find original offset 0x%x!", + offset.getOriginalOffset())); + } + } + } + needsAssociationTable.clear(); + } + + /** + * As Items are written out into the output DEX file, this function is called + * to update the next Offsettable with the file pointer's current position. + * This should allow the tracking of new offset locations. + * This also requires that reading and writing of all items happens in the same order + * (with the exception of the map list, see above) + * @param file Used for recording the new position. + */ + public void updatePositionOfNextOffsettable(DexRandomAccessFile file) throws IOException { + if (offsettableTableIdx == offsettableTable.size()) { + Log.errorAndQuit("Not all created Offsettable items have been added to the " + + "Offsettable Table!"); + } + Offsettable offsettable = offsettableTable.get(offsettableTableIdx); + offsettable.setNewPosition((int) file.getFilePointer()); + offsettableTableIdx++; + } + + /** + * As Items are written out, any writing out of an offset must call this function, passing + * in the relevant offset. This function will write out the offset, if the associated + * Offsettable has been updated with its new position, or else will write out a null value, and + * the Offset will be stored for writing after all Items have been written, and all + * Offsettables MUST have been updated. + * @param offset The offset received from getNewOffset(). + * @param file Used for writing out to the file. + * @param useUleb128 Whether or not the offset should be written in UINT or ULEB128 form. + */ + public void tryToWriteOffset(Offset offset, DexRandomAccessFile file, boolean useUleb128) + throws IOException { + if (!offset.isNewOffset() && (!offset.pointsToSomething())) { + if (useUleb128) { + file.writeUleb128(0); + } else { + file.writeUInt(0); + } + return; + } + + if (offset.readyForWriting()) { + if (useUleb128) { + file.writeUleb128(offset.getNewPositionOfItem()); + } else { + file.writeUInt(offset.getNewPositionOfItem()); + } + } else { + offset.setOutputLocation((int) file.getFilePointer()); + if (useUleb128) { + file.writeLargestUleb128(offset.getOriginalOffset()); + offset.setUsesUleb128(); + } else { + file.writeUInt(offset.getOriginalOffset()); + } + needsUpdateTable.add(offset); + } + } + + /** + * This is called after all writing has finished, to write out any Offsets + * that could not be written out during the original writing phase, because their + * associated Offsettables hadn't had their new positions calculated yet. + * @param file Used for writing out to the file. + */ + public void updateOffsets(DexRandomAccessFile file) throws IOException { + if (offsettableTableIdx != offsettableTable.size()) { + Log.errorAndQuit("Being asked to update dangling offsets but the " + + "correct number of offsettables has not been written out!"); + } + for (Offset offset : needsUpdateTable) { + file.seek(offset.getOutputLocation()); + if (offset.usesUleb128()) { + file.writeLargestUleb128(offset.getNewPositionOfItem()); + } else { + file.writeUInt(offset.getNewPositionOfItem()); + } + } + needsUpdateTable.clear(); + } + + /** + * Called after writing out the header, to skip to after the map list. + */ + public void skipToAfterMapList() { + offsettableTableIdx = indexAfterMapList; + } + + /** + * Called once the map list needs to be written out, to set the + * offsettable table index back to the right place. + */ + public void goBackToMapList() { + restorePoint = offsettableTableIdx; + offsettableTableIdx = (indexAfterMapList - 1); + } + + /** + * Called once the map list has been written out, to set the + * offsettable table index back to where it was before. + */ + public void goBackToPreviousPoint() { + if (offsettableTableIdx != indexAfterMapList) { + Log.errorAndQuit("Being asked to go to the point before the MapList was written out," + + " but we're not in the right place."); + } + offsettableTableIdx = restorePoint; + } + + /** + * Called after reading in the map list, to remember the point to be skipped + * to later. + */ + public void rememberPointAfterMapList() { + indexAfterMapList = offsettableTable.size(); + } + + private void updateHeaderOffsetIfValid(Offset offset, Offsettable previousFirst, + Offsettable newFirst, String offsetName) { + if (offset.pointsToThisOffsettable(previousFirst)) { + offset.pointToNew(newFirst); + } else { + Log.errorAndQuit("Header " + offsetName + " offset not pointing at first element?"); + } + } + + private void addTypeListsToMapFile(RawDexFile rawDexFile, Offsettable typeListOffsettable) { + // Create a MapItem for the TypeLists + MapItem typeListMapItem = new MapItem(); + typeListMapItem.offset = new Offset(false); + typeListMapItem.offset.pointToNew(typeListOffsettable); + typeListMapItem.type = MapItem.TYPE_TYPE_LIST; + typeListMapItem.size = 1; + + // Insert into the MapList. + // (first, find the MapItem that points to StringDataItems...) + int idx = 0; + for (MapItem mapItem : rawDexFile.mapList.mapItems) { + if (mapItem.type == MapItem.TYPE_STRING_DATA_ITEM) { + break; + } + idx++; + } + // (now insert the TypeList MapItem just before the StringDataItem one...) + rawDexFile.mapList.mapItems.add(idx, typeListMapItem); + } + + private void addFieldIdsToHeaderAndMapFile(RawDexFile rawDexFile, + Offsettable fieldOffsettable) { + // Add the field IDs to the header. + rawDexFile.header.fieldIdsOff.unsetNullAndPointTo(fieldOffsettable); + rawDexFile.header.fieldIdsSize = 1; + + // Create a MapItem for the field IDs. + MapItem fieldMapItem = new MapItem(); + fieldMapItem.offset = new Offset(false); + fieldMapItem.offset.pointToNew(fieldOffsettable); + fieldMapItem.type = MapItem.TYPE_FIELD_ID_ITEM; + fieldMapItem.size = 1; + + // Insert into the MapList. + // (first, find the MapItem that points to MethodIdItems...) + int idx = 0; + for (MapItem mapItem : rawDexFile.mapList.mapItems) { + if (mapItem.type == MapItem.TYPE_METHOD_ID_ITEM) { + break; + } + idx++; + } + // (now insert the FieldIdItem MapItem just before the MethodIdItem one...) + rawDexFile.mapList.mapItems.add(idx, fieldMapItem); + } + + + private void updateOffsetsInHeaderAndMapFile(RawDexFile rawDexFile, + Offsettable newFirstOffsettable) { + Offsettable prevFirstOffsettable = null; + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i) == newFirstOffsettable) { + prevFirstOffsettable = offsettableTable.get(i + 1); + break; + } + } + if (prevFirstOffsettable == null) { + Log.errorAndQuit("When calling updateMapListOffsets, could not find new " + + "first offsettable?"); + } + + // Based on the type of the item we just added, check the relevant Offset in the header + // and if it pointed at the prev_first_offsettable, make it point at the new one. + // NB: if it isn't pointing at the prev one, something is wrong. + HeaderItem header = rawDexFile.header; + if (newFirstOffsettable.getItem() instanceof StringIdItem) { + updateHeaderOffsetIfValid(header.stringIdsOff, prevFirstOffsettable, + newFirstOffsettable, "StringID"); + } else if (newFirstOffsettable.getItem() instanceof TypeIdItem) { + updateHeaderOffsetIfValid(header.typeIdsOff, prevFirstOffsettable, + newFirstOffsettable, "TypeID"); + } else if (newFirstOffsettable.getItem() instanceof ProtoIdItem) { + updateHeaderOffsetIfValid(header.protoIdsOff, prevFirstOffsettable, + newFirstOffsettable, "ProtoID"); + } else if (newFirstOffsettable.getItem() instanceof FieldIdItem) { + updateHeaderOffsetIfValid(header.fieldIdsOff, prevFirstOffsettable, + newFirstOffsettable, "FieldID"); + } else if (newFirstOffsettable.getItem() instanceof MethodIdItem) { + updateHeaderOffsetIfValid(header.methodIdsOff, prevFirstOffsettable, + newFirstOffsettable, "MethodID"); + } else if (newFirstOffsettable.getItem() instanceof ClassDefItem) { + updateHeaderOffsetIfValid(header.classDefsOff, prevFirstOffsettable, + newFirstOffsettable, "ClassDef"); + } + + // Now iterate through the MapList's MapItems, and see if their Offsets pointed at the + // prev_first_offsettable, and if so, make them now point at the new_first_offsettable. + for (MapItem mapItem : rawDexFile.mapList.mapItems) { + if (mapItem.offset.pointsToThisOffsettable(prevFirstOffsettable)) { + Log.info("Updating offset in MapItem (type: " + mapItem.type + ") after " + + "we called insertNewOffsettableAsFirstOfType()"); + mapItem.offset.pointToNew(newFirstOffsettable); + } + } + } + + private void insertOffsettableAt(int idx, Offsettable offsettable) { + offsettableTable.add(idx, offsettable); + if (indexAfterMapList > idx) { + indexAfterMapList++; + } + if (restorePoint > idx) { + restorePoint++; + } + } + + /** + * If we're creating our first TypeList, then IdCreator has to call this method to + * ensure it gets put into the correct place in the offsettable table. + * This assumes TypeLists always come before StringDatas. + */ + public Offsettable insertNewOffsettableAsFirstEverTypeList(RawDexObject item, + RawDexFile rawDexFile) { + // We find the first StringDataItem, the type lists will come before this. + Log.info("Calling insertNewOffsettableAsFirstEverTypeList()"); + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem() instanceof StringDataItem) { + Offsettable offsettable = new Offsettable(item, true); + insertOffsettableAt(i, offsettable); + addTypeListsToMapFile(rawDexFile, offsettable); + return offsettable; + } + } + Log.errorAndQuit("Could not find any StringDataItems to insert the type list before."); + return null; + } + + /** + * If we're creating our first FieldId, then IdCreator has to call this method to + * ensure it gets put into the correct place in the offsettable table. + * This assumes FieldIds always come before MethodIds. + */ + public Offsettable insertNewOffsettableAsFirstEverField(RawDexObject item, + RawDexFile rawDexFile) { + // We find the first MethodIdItem, the fields will come before this. + Log.info("Calling insertNewOffsettableAsFirstEverField()"); + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem() instanceof MethodIdItem) { + Offsettable offsettable = new Offsettable(item, true); + insertOffsettableAt(i, offsettable); + addFieldIdsToHeaderAndMapFile(rawDexFile, offsettable); + return offsettable; + } + } + Log.errorAndQuit("Could not find any MethodIdItems to insert the field before."); + return null; + } + + /** + * If we're creating a new Item (such as FieldId, MethodId) that is placed into the + * first position of the relevant ID table, then IdCreator has to call this method to + * ensure it gets put into the correct place in the offsettable table. + */ + public Offsettable insertNewOffsettableAsFirstOfType(RawDexObject item, + RawDexFile rawDexFile) { + Log.debug("Calling insertNewOffsettableAsFirstOfType()"); + int index = getOffsettableIndexForFirstItemType(item); + if (index == -1) { + Log.errorAndQuit("Could not find any object of class: " + item.getClass()); + } + Offsettable offsettable = new Offsettable(item, true); + insertOffsettableAt(index, offsettable); + updateOffsetsInHeaderAndMapFile(rawDexFile, offsettable); + return offsettable; + } + + /** + * IdCreator has to call this method when it creates a new IdItem, to make sure it + * gets put into the correct place in the offsettable table. IdCreator should + * provide the IdItem that should come before this new IdItem. + */ + public Offsettable insertNewOffsettableAfter(RawDexObject item, RawDexObject itemBefore) { + Log.debug("Calling insertNewOffsettableAfter()"); + int index = getOffsettableIndexForItem(itemBefore); + if (index == -1) { + Log.errorAndQuit("Did not find specified 'after' object in offsettable table."); + } + Offsettable offsettable = new Offsettable(item, true); + insertOffsettableAt(index + 1, offsettable); + return offsettable; + } + + private int getOffsettableIndexForFirstItemType(RawDexObject item) { + Class<?> itemClass = item.getClass(); + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem().getClass().equals(itemClass)) { + return i; + } + } + return -1; + } + + private int getOffsettableIndexForItem(RawDexObject item) { + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem() == item) { + return i; + } + } + return -1; + } + + /** + * Given a RawDexObject, get the Offsettable that contains it. + */ + public Offsettable getOffsettableForItem(RawDexObject item) { + for (int i = 0; i < offsettableTable.size(); i++) { + if (offsettableTable.get(i).getItem() == item) { + return offsettableTable.get(i); + } + } + return null; + } +} |