1 /* 2 * Copyright (C) 2017 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.mutators; 18 19 import dexfuzz.Log; 20 import dexfuzz.MutationStats; 21 import dexfuzz.program.MInsn; 22 import dexfuzz.program.MutatableCode; 23 import dexfuzz.program.Mutation; 24 import dexfuzz.rawdex.Instruction; 25 import dexfuzz.rawdex.Opcode; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Random; 30 31 public class InvokeChanger extends CodeMutator { 32 33 private static final Opcode[] INVOKE_LIST = { 34 Opcode.INVOKE_VIRTUAL, 35 Opcode.INVOKE_SUPER, 36 Opcode.INVOKE_DIRECT, 37 Opcode.INVOKE_STATIC, 38 Opcode.INVOKE_INTERFACE, 39 }; 40 41 private static final Opcode[] INVOKE_RANGE_LIST = { 42 Opcode.INVOKE_VIRTUAL_RANGE, 43 Opcode.INVOKE_SUPER_RANGE, 44 Opcode.INVOKE_DIRECT_RANGE, 45 Opcode.INVOKE_STATIC_RANGE, 46 Opcode.INVOKE_INTERFACE_RANGE, 47 }; 48 49 /** 50 * Every CodeMutator has an AssociatedMutation, representing the 51 * mutation that this CodeMutator can perform, to allow separate 52 * generateMutation() and applyMutation() phases, allowing serialization. 53 */ 54 public static class AssociatedMutation extends Mutation { 55 56 public int invokeCallInsnIdx; 57 58 @Override getString()59 public String getString() { 60 return Integer.toString(invokeCallInsnIdx); 61 } 62 63 @Override parseString(String[] elements)64 public void parseString(String[] elements) { 65 invokeCallInsnIdx = Integer.parseInt(elements[2]); 66 } 67 } 68 69 // The following two methods are here for the benefit of MutationSerializer, 70 // so it can create a CodeMutator and get the correct associated Mutation, as it 71 // reads in mutations from a dump of mutations. 72 @Override getNewMutation()73 public Mutation getNewMutation() { 74 return new AssociatedMutation(); 75 } 76 InvokeChanger()77 public InvokeChanger() { } 78 InvokeChanger(Random rng, MutationStats stats, List<Mutation> mutations)79 public InvokeChanger(Random rng, MutationStats stats, List<Mutation> mutations) { 80 super(rng, stats, mutations); 81 likelihood = 30; 82 } 83 84 // A cache that should only exist between generateMutation() and applyMutation(), 85 // or be created at the start of applyMutation(), if we're reading in mutations from 86 // a file. 87 private List<MInsn> invokeCallInsns = null; 88 generateCachedinvokeCallInsns(MutatableCode mutatableCode)89 private void generateCachedinvokeCallInsns(MutatableCode mutatableCode) { 90 if (invokeCallInsns != null) { 91 return; 92 } 93 94 invokeCallInsns = new ArrayList<MInsn>(); 95 96 for (MInsn mInsn : mutatableCode.getInstructions()) { 97 if (isInvokeCallInst(mInsn)) { 98 invokeCallInsns.add(mInsn); 99 } 100 } 101 } 102 103 @Override canMutate(MutatableCode mutatableCode)104 protected boolean canMutate(MutatableCode mutatableCode) { 105 for (MInsn mInsn : mutatableCode.getInstructions()) { 106 if (isInvokeCallInst(mInsn)) { 107 return true; 108 } 109 } 110 111 Log.debug("No invoke instruction in method, skipping..."); 112 return false; 113 } 114 115 @Override generateMutation(MutatableCode mutatableCode)116 protected Mutation generateMutation(MutatableCode mutatableCode) { 117 generateCachedinvokeCallInsns(mutatableCode); 118 119 int invokeCallInsnIdx = rng.nextInt(invokeCallInsns.size()); 120 121 AssociatedMutation mutation = new AssociatedMutation(); 122 mutation.setup(this.getClass(), mutatableCode); 123 mutation.invokeCallInsnIdx = invokeCallInsnIdx; 124 return mutation; 125 } 126 127 @Override applyMutation(Mutation uncastMutation)128 protected void applyMutation(Mutation uncastMutation) { 129 // Cast the Mutation to our AssociatedMutation, so we can access its fields. 130 AssociatedMutation mutation = (AssociatedMutation) uncastMutation; 131 MutatableCode mutatableCode = mutation.mutatableCode; 132 133 generateCachedinvokeCallInsns(mutatableCode); 134 135 MInsn invokeInsn = invokeCallInsns.get(mutation.invokeCallInsnIdx); 136 137 String oldInsnString = invokeInsn.toString(); 138 139 Opcode newOpcode = getDifferentInvokeCallOpcode(invokeInsn); 140 141 invokeInsn.insn.info = Instruction.getOpcodeInfo(newOpcode); 142 143 Log.info("Changed " + oldInsnString + " to " + invokeInsn); 144 145 stats.incrementStat("Changed invoke call instruction"); 146 147 // Clear cache. 148 invokeCallInsns = null; 149 } 150 getDifferentInvokeCallOpcode(MInsn mInsn)151 private Opcode getDifferentInvokeCallOpcode(MInsn mInsn) { 152 Opcode opcode = mInsn.insn.info.opcode; 153 if (isSimpleInvokeInst(opcode)) { 154 int index = opcode.ordinal() - Opcode.INVOKE_VIRTUAL.ordinal(); 155 int length = INVOKE_LIST.length; 156 return INVOKE_LIST[(index + 1 + rng.nextInt(length - 1)) % length]; 157 } else if (isRangeInvokeInst(opcode)) { 158 int index = opcode.ordinal() - Opcode.INVOKE_VIRTUAL_RANGE.ordinal(); 159 int length = INVOKE_RANGE_LIST.length; 160 return INVOKE_RANGE_LIST[(index + 1 + rng.nextInt(length - 1)) % length]; 161 } 162 return opcode; 163 } 164 isSimpleInvokeInst(Opcode opcode)165 private boolean isSimpleInvokeInst(Opcode opcode){ 166 return Opcode.isBetween(opcode, Opcode.INVOKE_VIRTUAL, Opcode.INVOKE_INTERFACE); 167 } 168 isRangeInvokeInst(Opcode opcode)169 private boolean isRangeInvokeInst(Opcode opcode){ 170 return Opcode.isBetween(opcode, Opcode.INVOKE_VIRTUAL_RANGE, Opcode.INVOKE_INTERFACE_RANGE); 171 172 } 173 isInvokeCallInst(MInsn mInsn)174 private boolean isInvokeCallInst(MInsn mInsn) { 175 Opcode opcode = mInsn.insn.info.opcode; 176 return isSimpleInvokeInst(opcode) || isRangeInvokeInst(opcode); 177 } 178 } 179