• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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