1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dexfuzz.program; 18 19 import dexfuzz.Log; 20 import dexfuzz.MutationStats; 21 import dexfuzz.Options; 22 import dexfuzz.listeners.BaseListener; 23 import dexfuzz.program.mutators.ArithOpChanger; 24 import dexfuzz.program.mutators.BranchShifter; 25 import dexfuzz.program.mutators.CmpBiasChanger; 26 import dexfuzz.program.mutators.CodeMutator; 27 import dexfuzz.program.mutators.ConstantValueChanger; 28 import dexfuzz.program.mutators.ConversionRepeater; 29 import dexfuzz.program.mutators.FieldFlagChanger; 30 import dexfuzz.program.mutators.InstructionDeleter; 31 import dexfuzz.program.mutators.InstructionDuplicator; 32 import dexfuzz.program.mutators.InstructionSwapper; 33 import dexfuzz.program.mutators.NewMethodCaller; 34 import dexfuzz.program.mutators.NonsenseStringPrinter; 35 import dexfuzz.program.mutators.PoolIndexChanger; 36 import dexfuzz.program.mutators.RandomInstructionGenerator; 37 import dexfuzz.program.mutators.SwitchBranchShifter; 38 import dexfuzz.program.mutators.TryBlockShifter; 39 import dexfuzz.program.mutators.ValuePrinter; 40 import dexfuzz.program.mutators.VRegChanger; 41 import dexfuzz.rawdex.ClassDataItem; 42 import dexfuzz.rawdex.ClassDefItem; 43 import dexfuzz.rawdex.CodeItem; 44 import dexfuzz.rawdex.DexRandomAccessFile; 45 import dexfuzz.rawdex.EncodedField; 46 import dexfuzz.rawdex.EncodedMethod; 47 import dexfuzz.rawdex.FieldIdItem; 48 import dexfuzz.rawdex.MethodIdItem; 49 import dexfuzz.rawdex.ProtoIdItem; 50 import dexfuzz.rawdex.RawDexFile; 51 import dexfuzz.rawdex.TypeIdItem; 52 import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind; 53 54 import java.io.BufferedReader; 55 import java.io.BufferedWriter; 56 import java.io.FileReader; 57 import java.io.FileWriter; 58 import java.io.IOException; 59 import java.util.ArrayList; 60 import java.util.HashMap; 61 import java.util.List; 62 import java.util.Map; 63 import java.util.Random; 64 65 /** 66 * After the raw DEX file has been parsed, it is passed into this class 67 * that represents the program in a mutatable form. 68 * The class uses a CodeTranslator to translate between the raw DEX form 69 * for a method, and the mutatable form. It also controls all CodeMutators, 70 * deciding which ones should be applied to each CodeItem. 71 */ 72 public class Program { 73 /** 74 * The RNG used during mutation. 75 */ 76 private Random rng; 77 78 /** 79 * The seed that was given to the RNG. 80 */ 81 public long rngSeed; 82 83 /** 84 * The parsed raw DEX file. 85 */ 86 private RawDexFile rawDexFile; 87 88 /** 89 * The system responsible for translating from CodeItems to MutatableCode and vice-versa. 90 */ 91 private CodeTranslator translator; 92 93 /** 94 * Responsible for adding new class ID items, method ID items, etc. 95 */ 96 private IdCreator idCreator; 97 98 /** 99 * A list of all the MutatableCode that the CodeTranslator produced from 100 * CodeItems that are acceptable to mutate. 101 */ 102 private List<MutatableCode> mutatableCodes; 103 104 /** 105 * A list of all MutatableCode items that were mutated when mutateTheProgram() 106 * was called. updateRawDexFile() will update the relevant CodeItems when called, 107 * and then clear this list. 108 */ 109 private List<MutatableCode> mutatedCodes; 110 111 /** 112 * A list of all registered CodeMutators that this Program can use to mutate methods. 113 */ 114 private List<CodeMutator> mutators; 115 116 /** 117 * Used if we're loading mutations from a file, so we can find the correct mutator. 118 */ 119 private Map<Class<? extends CodeMutator>, CodeMutator> mutatorsLookupByClass; 120 121 /** 122 * Tracks mutation stats. 123 */ 124 private MutationStats mutationStats; 125 126 /** 127 * A list of all mutations used for loading/dumping mutations from/to a file. 128 */ 129 private List<Mutation> mutations; 130 131 /** 132 * The listener who is interested in events. 133 * May be a listener that is responsible for multiple listeners. 134 */ 135 private BaseListener listener; 136 137 /** 138 * Given a maximum number of mutations that can be performed on a method, n, 139 * give up after attempting (n * this value) mutations for any method. 140 */ 141 private static final int MAXIMUM_MUTATION_ATTEMPT_FACTOR = 10; 142 143 /** 144 * Construct the mutatable Program based on the raw DEX file that was parsed initially. 145 */ Program(RawDexFile rawDexFile, List<Mutation> previousMutations, BaseListener listener)146 public Program(RawDexFile rawDexFile, List<Mutation> previousMutations, 147 BaseListener listener) { 148 this.listener = listener; 149 150 idCreator = new IdCreator(rawDexFile); 151 152 // Set up the RNG. 153 rng = new Random(); 154 if (Options.usingProvidedSeed) { 155 rng.setSeed(Options.rngSeed); 156 rngSeed = Options.rngSeed; 157 } else { 158 long seed = System.currentTimeMillis(); 159 listener.handleSeed(seed); 160 rng.setSeed(seed); 161 rngSeed = seed; 162 } 163 164 if (previousMutations != null) { 165 mutations = previousMutations; 166 } else { 167 // Allocate the mutations list. 168 mutations = new ArrayList<Mutation>(); 169 170 // Read in the mutations if we need to. 171 if (Options.loadMutations) { 172 // Allocate the mutators lookup table. 173 mutatorsLookupByClass = new HashMap<Class<? extends CodeMutator>, CodeMutator>(); 174 loadMutationsFromDisk(Options.loadMutationsFile); 175 } 176 } 177 178 // Allocate the mutators list. 179 mutators = new ArrayList<CodeMutator>(); 180 181 this.rawDexFile = rawDexFile; 182 183 mutatableCodes = new ArrayList<MutatableCode>(); 184 mutatedCodes = new ArrayList<MutatableCode>(); 185 186 translator = new CodeTranslator(); 187 188 mutationStats = new MutationStats(); 189 190 // Register all the code mutators here. 191 registerMutator(new ArithOpChanger(rng, mutationStats, mutations)); 192 registerMutator(new BranchShifter(rng, mutationStats, mutations)); 193 registerMutator(new CmpBiasChanger(rng, mutationStats, mutations)); 194 registerMutator(new ConstantValueChanger(rng, mutationStats, mutations)); 195 registerMutator(new ConversionRepeater(rng, mutationStats, mutations)); 196 registerMutator(new FieldFlagChanger(rng, mutationStats, mutations)); 197 registerMutator(new InstructionDeleter(rng, mutationStats, mutations)); 198 registerMutator(new InstructionDuplicator(rng, mutationStats, mutations)); 199 registerMutator(new InstructionSwapper(rng, mutationStats, mutations)); 200 registerMutator(new NewMethodCaller(rng, mutationStats, mutations)); 201 registerMutator(new NonsenseStringPrinter(rng, mutationStats, mutations)); 202 registerMutator(new PoolIndexChanger(rng, mutationStats, mutations)); 203 registerMutator(new RandomInstructionGenerator(rng, mutationStats, mutations)); 204 registerMutator(new SwitchBranchShifter(rng, mutationStats, mutations)); 205 registerMutator(new TryBlockShifter(rng, mutationStats, mutations)); 206 registerMutator(new ValuePrinter(rng, mutationStats, mutations)); 207 registerMutator(new VRegChanger(rng, mutationStats, mutations)); 208 209 associateClassDefsAndClassData(); 210 associateCodeItemsWithMethodNames(); 211 212 int codeItemIdx = 0; 213 for (CodeItem codeItem : rawDexFile.codeItems) { 214 if (legalToMutate(codeItem)) { 215 Log.debug("Legal to mutate code item " + codeItemIdx); 216 int mutatableCodeIdx = mutatableCodes.size(); 217 mutatableCodes.add(translator.codeItemToMutatableCode(this, codeItem, 218 codeItemIdx, mutatableCodeIdx)); 219 } else { 220 Log.debug("Not legal to mutate code item " + codeItemIdx); 221 } 222 codeItemIdx++; 223 } 224 } 225 registerMutator(CodeMutator mutator)226 private void registerMutator(CodeMutator mutator) { 227 if (mutator.canBeTriggered()) { 228 Log.debug("Registering mutator " + mutator.getClass().getSimpleName()); 229 mutators.add(mutator); 230 } 231 if (Options.loadMutations) { 232 mutatorsLookupByClass.put(mutator.getClass(), mutator); 233 } 234 } 235 236 /** 237 * Associate ClassDefItem to a ClassDataItem and vice-versa. 238 * This is so when we're associating method names with code items, 239 * we can find the name of the class the method belongs to. 240 */ associateClassDefsAndClassData()241 private void associateClassDefsAndClassData() { 242 for (ClassDefItem classDefItem : rawDexFile.classDefs) { 243 if (classDefItem.classDataOff.pointsToSomething()) { 244 ClassDataItem classDataItem = (ClassDataItem) 245 classDefItem.classDataOff.getPointedToItem(); 246 classDataItem.meta.classDefItem = classDefItem; 247 classDefItem.meta.classDataItem = classDataItem; 248 } 249 } 250 } 251 252 /** 253 * For each CodeItem, find the name of the method the item represents. 254 * This is done to allow the filtering of mutating methods based on if 255 * they have the name *_MUTATE, but also for debugging info. 256 */ associateCodeItemsWithMethodNames()257 private void associateCodeItemsWithMethodNames() { 258 // Associate method names with codeItems. 259 for (ClassDataItem classDataItem : rawDexFile.classDatas) { 260 261 String className = ""; 262 if (classDataItem.meta.classDefItem != null) { 263 int typeIdx = classDataItem.meta.classDefItem.classIdx; 264 TypeIdItem typeIdItem = rawDexFile.typeIds.get(typeIdx); 265 className = rawDexFile.stringDatas.get(typeIdItem.descriptorIdx).getString() + "."; 266 } 267 268 // Do direct methods... 269 // Track the current method index with this value, since the encoding in 270 // each EncodedMethod is the absolute index for the first EncodedMethod, 271 // and then relative index for the rest... 272 int methodIdx = 0; 273 for (EncodedMethod method : classDataItem.directMethods) { 274 methodIdx = associateMethod(method, methodIdx, className); 275 } 276 // Reset methodIdx for virtual methods... 277 methodIdx = 0; 278 for (EncodedMethod method : classDataItem.virtualMethods) { 279 methodIdx = associateMethod(method, methodIdx, className); 280 } 281 } 282 } 283 284 /** 285 * Associate the name of the provided method with its CodeItem, if it 286 * has one. 287 * 288 * @param methodIdx The method index of the last EncodedMethod that was handled in this class. 289 * @return The method index of the EncodedMethod that has just been handled in this class. 290 */ associateMethod(EncodedMethod method, int methodIdx, String className)291 private int associateMethod(EncodedMethod method, int methodIdx, String className) { 292 if (!method.codeOff.pointsToSomething()) { 293 // This method doesn't have a code item, so we won't encounter it later. 294 return methodIdx; 295 } 296 297 // First method index is an absolute index. 298 // The rest are relative to the previous. 299 // (so if methodIdx is initialised to 0, this single line works) 300 methodIdx = methodIdx + method.methodIdxDiff; 301 302 // Get the name. 303 MethodIdItem methodIdItem = rawDexFile.methodIds.get(methodIdx); 304 ProtoIdItem protoIdItem = rawDexFile.protoIds.get(methodIdItem.protoIdx); 305 String shorty = rawDexFile.stringDatas.get(protoIdItem.shortyIdx).getString(); 306 String methodName = className 307 + rawDexFile.stringDatas.get(methodIdItem.nameIdx).getString(); 308 309 // Get the codeItem. 310 if (method.codeOff.getPointedToItem() instanceof CodeItem) { 311 CodeItem codeItem = (CodeItem) method.codeOff.getPointedToItem(); 312 codeItem.meta.methodName = methodName; 313 codeItem.meta.shorty = shorty; 314 codeItem.meta.isStatic = method.isStatic(); 315 } else { 316 Log.errorAndQuit("You've got an EncodedMethod that points to an Offsettable" 317 + " that does not contain a CodeItem"); 318 } 319 320 return methodIdx; 321 } 322 323 /** 324 * Determine, based on the current options supplied to dexfuzz, as well as 325 * its capabilities, if the provided CodeItem can be mutated. 326 * @param codeItem The CodeItem we may wish to mutate. 327 * @return If the CodeItem can be mutated. 328 */ legalToMutate(CodeItem codeItem)329 private boolean legalToMutate(CodeItem codeItem) { 330 if (!Options.mutateLimit) { 331 Log.debug("Mutating everything."); 332 return true; 333 } 334 if (codeItem.meta.methodName.endsWith("_MUTATE")) { 335 Log.debug("Code item marked with _MUTATE."); 336 return true; 337 } 338 Log.debug("Code item not marked with _MUTATE, but not mutating all code items."); 339 return false; 340 } 341 getNumberOfMutationsToPerform()342 private int getNumberOfMutationsToPerform() { 343 // We want n mutations to be twice as likely as n+1 mutations. 344 // 345 // So if we have max 3, 346 // then 0 has 8 chances ("tickets"), 347 // 1 has 4 chances 348 // 2 has 2 chances 349 // and 3 has 1 chance 350 351 // Allocate the tickets 352 // n mutations need (2^(n+1) - 1) tickets 353 // e.g. 354 // 3 mutations => 15 tickets 355 // 4 mutations => 31 tickets 356 int tickets = (2 << Options.methodMutations) - 1; 357 358 // Pick the lucky ticket 359 int luckyTicket = rng.nextInt(tickets); 360 361 // The tickets are put into buckets with accordance with log-base-2. 362 // have to make sure it's luckyTicket + 1, because log(0) is undefined 363 // so: 364 // log_2(1) => 0 365 // log_2(2) => 1 366 // log_2(3) => 1 367 // log_2(4) => 2 368 // log_2(5) => 2 369 // log_2(6) => 2 370 // log_2(7) => 2 371 // log_2(8) => 3 372 // ... 373 // so to make the highest mutation value the rarest, 374 // subtract log_2(luckyTicket+1) from the maximum number 375 // log2(x) <=> 31 - Integer.numberOfLeadingZeros(x) 376 int luckyMutation = Options.methodMutations 377 - (31 - Integer.numberOfLeadingZeros(luckyTicket + 1)); 378 379 return luckyMutation; 380 } 381 382 /** 383 * Returns true if we completely failed to mutate this method's mutatable code after 384 * attempting to. 385 */ mutateAMutatableCode(MutatableCode mutatableCode)386 private boolean mutateAMutatableCode(MutatableCode mutatableCode) { 387 int mutations = getNumberOfMutationsToPerform(); 388 389 Log.info("Attempting " + mutations + " mutations for method " + mutatableCode.name); 390 391 int mutationsApplied = 0; 392 393 int maximumMutationAttempts = Options.methodMutations * MAXIMUM_MUTATION_ATTEMPT_FACTOR; 394 int mutationAttempts = 0; 395 boolean hadToBail = false; 396 397 while (mutationsApplied < mutations) { 398 int mutatorIdx = rng.nextInt(mutators.size()); 399 CodeMutator mutator = mutators.get(mutatorIdx); 400 Log.info("Running mutator " + mutator.getClass().getSimpleName()); 401 if (mutator.attemptToMutate(mutatableCode)) { 402 mutationsApplied++; 403 } 404 mutationAttempts++; 405 if (mutationAttempts > maximumMutationAttempts) { 406 Log.info("Bailing out on mutation for this method, tried too many times..."); 407 hadToBail = true; 408 break; 409 } 410 } 411 412 // If any of them actually mutated it, excellent! 413 if (mutationsApplied > 0) { 414 Log.info("Method was mutated."); 415 mutatedCodes.add(mutatableCode); 416 } else { 417 Log.info("Method was not mutated."); 418 } 419 420 return ((mutationsApplied == 0) && hadToBail); 421 } 422 423 /** 424 * Go through each mutatable method in turn, and attempt to mutate it. 425 * Afterwards, call updateRawDexFile() to apply the results of mutation to the 426 * original code. 427 */ mutateTheProgram()428 public void mutateTheProgram() { 429 if (Options.loadMutations) { 430 applyMutationsFromList(); 431 return; 432 } 433 434 // Typically, this is 2 to 10... 435 int methodsToMutate = Options.minMethods 436 + rng.nextInt((Options.maxMethods - Options.minMethods) + 1); 437 438 // Check we aren't trying to mutate more methods than we have. 439 if (methodsToMutate > mutatableCodes.size()) { 440 methodsToMutate = mutatableCodes.size(); 441 } 442 443 // Check if we're going to end up mutating all the methods. 444 if (methodsToMutate == mutatableCodes.size()) { 445 // Just do them all in order. 446 Log.info("Mutating all possible methods."); 447 for (MutatableCode mutatableCode : mutatableCodes) { 448 if (mutatableCode == null) { 449 Log.errorAndQuit("Why do you have a null MutatableCode?"); 450 } 451 mutateAMutatableCode(mutatableCode); 452 } 453 Log.info("Finished mutating all possible methods."); 454 } else { 455 // Pick them at random. 456 Log.info("Randomly selecting " + methodsToMutate + " methods to mutate."); 457 while (mutatedCodes.size() < methodsToMutate) { 458 int randomMethodIdx = rng.nextInt(mutatableCodes.size()); 459 MutatableCode mutatableCode = mutatableCodes.get(randomMethodIdx); 460 if (mutatableCode == null) { 461 Log.errorAndQuit("Why do you have a null MutatableCode?"); 462 } 463 if (!mutatedCodes.contains(mutatableCode)) { 464 boolean completelyFailedToMutate = mutateAMutatableCode(mutatableCode); 465 if (completelyFailedToMutate) { 466 methodsToMutate--; 467 } 468 } 469 } 470 Log.info("Finished mutating the methods."); 471 } 472 473 listener.handleMutationStats(mutationStats.getStatsString()); 474 475 if (Options.dumpMutations) { 476 writeMutationsToDisk(Options.dumpMutationsFile); 477 } 478 } 479 writeMutationsToDisk(String fileName)480 private void writeMutationsToDisk(String fileName) { 481 Log.debug("Writing mutations to disk."); 482 try { 483 BufferedWriter writer = new BufferedWriter(new FileWriter(fileName)); 484 for (Mutation mutation : mutations) { 485 MutationSerializer.writeMutation(writer, mutation); 486 } 487 writer.close(); 488 } catch (IOException e) { 489 Log.errorAndQuit("IOException while writing mutations to disk..."); 490 } 491 } 492 loadMutationsFromDisk(String fileName)493 private void loadMutationsFromDisk(String fileName) { 494 Log.debug("Loading mutations from disk."); 495 try { 496 BufferedReader reader = new BufferedReader(new FileReader(fileName)); 497 while (reader.ready()) { 498 Mutation mutation = MutationSerializer.readMutation(reader); 499 mutations.add(mutation); 500 } 501 reader.close(); 502 } catch (IOException e) { 503 Log.errorAndQuit("IOException while loading mutations from disk..."); 504 } 505 } 506 applyMutationsFromList()507 private void applyMutationsFromList() { 508 Log.info("Applying preloaded list of mutations..."); 509 for (Mutation mutation : mutations) { 510 // Repopulate the MutatableCode field from the recorded index into the Program's list. 511 mutation.mutatableCode = mutatableCodes.get(mutation.mutatableCodeIdx); 512 513 // Get the right mutator. 514 CodeMutator mutator = mutatorsLookupByClass.get(mutation.mutatorClass); 515 516 // Apply the mutation. 517 mutator.forceMutate(mutation); 518 519 // Add this mutatable code to the list of mutated codes, if we haven't already. 520 if (!mutatedCodes.contains(mutation.mutatableCode)) { 521 mutatedCodes.add(mutation.mutatableCode); 522 } 523 } 524 Log.info("...finished applying preloaded list of mutations."); 525 } 526 getMutations()527 public List<Mutation> getMutations() { 528 return mutations; 529 } 530 531 /** 532 * Updates any CodeItems that need to be updated after mutation. 533 */ updateRawDexFile()534 public boolean updateRawDexFile() { 535 boolean anythingMutated = !(mutatedCodes.isEmpty()); 536 for (MutatableCode mutatedCode : mutatedCodes) { 537 translator.mutatableCodeToCodeItem(rawDexFile.codeItems 538 .get(mutatedCode.codeItemIdx), mutatedCode); 539 } 540 mutatedCodes.clear(); 541 return anythingMutated; 542 } 543 writeRawDexFile(DexRandomAccessFile file)544 public void writeRawDexFile(DexRandomAccessFile file) throws IOException { 545 rawDexFile.write(file); 546 } 547 updateRawDexFileHeader(DexRandomAccessFile file)548 public void updateRawDexFileHeader(DexRandomAccessFile file) throws IOException { 549 rawDexFile.updateHeader(file); 550 } 551 552 /** 553 * Used by the CodeMutators to determine legal index values. 554 */ getTotalPoolIndicesByKind(PoolIndexKind poolIndexKind)555 public int getTotalPoolIndicesByKind(PoolIndexKind poolIndexKind) { 556 switch (poolIndexKind) { 557 case Type: 558 return rawDexFile.typeIds.size(); 559 case Field: 560 return rawDexFile.fieldIds.size(); 561 case String: 562 return rawDexFile.stringIds.size(); 563 case Method: 564 return rawDexFile.methodIds.size(); 565 case Invalid: 566 return 0; 567 default: 568 } 569 return 0; 570 } 571 572 /** 573 * Used by the CodeMutators to lookup and/or create Ids. 574 */ getNewItemCreator()575 public IdCreator getNewItemCreator() { 576 return idCreator; 577 } 578 579 /** 580 * Used by FieldFlagChanger, to find an EncodedField for a specified field in an insn, 581 * if that field is actually defined in this DEX file. If not, null is returned. 582 */ getEncodedField(int fieldIdx)583 public EncodedField getEncodedField(int fieldIdx) { 584 if (fieldIdx >= rawDexFile.fieldIds.size()) { 585 Log.debug(String.format("Field idx 0x%x specified is not defined in this DEX file.", 586 fieldIdx)); 587 return null; 588 } 589 FieldIdItem fieldId = rawDexFile.fieldIds.get(fieldIdx); 590 591 for (ClassDefItem classDef : rawDexFile.classDefs) { 592 if (classDef.classIdx == fieldId.classIdx) { 593 ClassDataItem classData = classDef.meta.classDataItem; 594 return classData.getEncodedFieldWithIndex(fieldIdx); 595 } 596 } 597 598 Log.debug(String.format("Field idx 0x%x specified is not defined in this DEX file.", 599 fieldIdx)); 600 return null; 601 } 602 } 603