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
17package dexfuzz.program;
18
19import dexfuzz.Log;
20import dexfuzz.MutationStats;
21import dexfuzz.Options;
22import dexfuzz.listeners.BaseListener;
23import dexfuzz.program.mutators.ArithOpChanger;
24import dexfuzz.program.mutators.BranchShifter;
25import dexfuzz.program.mutators.CmpBiasChanger;
26import dexfuzz.program.mutators.CodeMutator;
27import dexfuzz.program.mutators.ConstantValueChanger;
28import dexfuzz.program.mutators.ConversionRepeater;
29import dexfuzz.program.mutators.FieldFlagChanger;
30import dexfuzz.program.mutators.InstructionDeleter;
31import dexfuzz.program.mutators.InstructionDuplicator;
32import dexfuzz.program.mutators.InstructionSwapper;
33import dexfuzz.program.mutators.NewMethodCaller;
34import dexfuzz.program.mutators.NonsenseStringPrinter;
35import dexfuzz.program.mutators.PoolIndexChanger;
36import dexfuzz.program.mutators.RandomInstructionGenerator;
37import dexfuzz.program.mutators.SwitchBranchShifter;
38import dexfuzz.program.mutators.TryBlockShifter;
39import dexfuzz.program.mutators.ValuePrinter;
40import dexfuzz.program.mutators.VRegChanger;
41import dexfuzz.rawdex.ClassDataItem;
42import dexfuzz.rawdex.ClassDefItem;
43import dexfuzz.rawdex.CodeItem;
44import dexfuzz.rawdex.DexRandomAccessFile;
45import dexfuzz.rawdex.EncodedField;
46import dexfuzz.rawdex.EncodedMethod;
47import dexfuzz.rawdex.FieldIdItem;
48import dexfuzz.rawdex.MethodIdItem;
49import dexfuzz.rawdex.ProtoIdItem;
50import dexfuzz.rawdex.RawDexFile;
51import dexfuzz.rawdex.TypeIdItem;
52import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind;
53
54import java.io.BufferedReader;
55import java.io.BufferedWriter;
56import java.io.FileReader;
57import java.io.FileWriter;
58import java.io.IOException;
59import java.util.ArrayList;
60import java.util.HashMap;
61import java.util.List;
62import java.util.Map;
63import 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 */
72public 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   */
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
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   */
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   */
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   */
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   */
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
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   */
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   */
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
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
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
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
527  public List<Mutation> getMutations() {
528    return mutations;
529  }
530
531  /**
532   * Updates any CodeItems that need to be updated after mutation.
533   */
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
544  public void writeRawDexFile(DexRandomAccessFile file) throws IOException {
545    rawDexFile.write(file);
546  }
547
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   */
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   */
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   */
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