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.Opcode; 25 import dexfuzz.rawdex.formats.ContainsPoolIndex; 26 import dexfuzz.rawdex.formats.ContainsPoolIndex.PoolIndexKind; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 import java.util.Random; 31 32 /** 33 * Mutator NewInstanceChanger changes the new instance type in a method to 34 * any random type from the pool. 35 */ 36 public class NewInstanceChanger extends CodeMutator { 37 38 /** 39 * Every CodeMutator has an AssociatedMutation, representing the 40 * mutation that this CodeMutator can perform, to allow separate 41 * generateMutation() and applyMutation() phases, allowing serialization. 42 */ 43 public static class AssociatedMutation extends Mutation { 44 public int newInstanceToChangeIdx; 45 public int newInstanceTypeIdx; 46 47 @Override 48 public String getString() { 49 StringBuilder builder = new StringBuilder(); 50 builder.append(newInstanceToChangeIdx).append(" "); 51 builder.append(newInstanceTypeIdx); 52 return builder.toString(); 53 } 54 55 @Override 56 public void parseString(String[] elements) { 57 newInstanceToChangeIdx = Integer.parseInt(elements[2]); 58 newInstanceTypeIdx = Integer.parseInt(elements[3]); 59 } 60 } 61 62 // The following two methods are here for the benefit of MutationSerializer, 63 // so it can create a CodeMutator and get the correct associated Mutation, as it 64 // reads in mutations from a dump of mutations. 65 @Override 66 public Mutation getNewMutation() { 67 return new AssociatedMutation(); 68 } 69 70 public NewInstanceChanger() {} 71 72 public NewInstanceChanger(Random rng, MutationStats stats, List<Mutation> mutations) { 73 super(rng, stats, mutations); 74 likelihood = 10; 75 } 76 77 // A cache that should only exist between generateMutation() and applyMutation(), 78 // or be created at the start of applyMutation(), if we're reading in mutations from 79 // a file. 80 private List<MInsn> newInstanceCachedInsns = null; 81 82 private void generateCachedNewInstanceInsns(MutatableCode mutatableCode) { 83 if (newInstanceCachedInsns != null) { 84 return; 85 } 86 87 newInstanceCachedInsns = new ArrayList<MInsn>(); 88 89 for (MInsn mInsn : mutatableCode.getInstructions()) { 90 if (mInsn.insn.info.opcode == Opcode.NEW_INSTANCE) { 91 newInstanceCachedInsns.add(mInsn); 92 } 93 } 94 } 95 96 @Override 97 protected boolean canMutate(MutatableCode mutatableCode) { 98 // Cannot change the pool index with only one type. 99 if (mutatableCode.program.getTotalPoolIndicesByKind(PoolIndexKind.Type) < 2) { 100 Log.debug("Cannot mutate, only one type, skipping..."); 101 return false; 102 } 103 104 for (MInsn mInsn : mutatableCode.getInstructions()) { 105 if (mInsn.insn.info.opcode == Opcode.NEW_INSTANCE) { 106 return true; 107 } 108 } 109 Log.debug("No New Instance in method, skipping..."); 110 return false; 111 } 112 113 @Override 114 protected Mutation generateMutation(MutatableCode mutatableCode) { 115 generateCachedNewInstanceInsns(mutatableCode); 116 117 int newInstanceIdxInCache = rng.nextInt(newInstanceCachedInsns.size()); 118 MInsn newInstanceInsn = newInstanceCachedInsns.get(newInstanceIdxInCache); 119 int oldTypeIdx = (int) newInstanceInsn.insn.vregB; 120 int newTypeIdx = 0; 121 int totalPoolIndices = mutatableCode.program.getTotalPoolIndicesByKind(PoolIndexKind.Type); 122 if (totalPoolIndices < 2) { 123 Log.errorAndQuit("Less than two types present, quitting..."); 124 } 125 126 while (newTypeIdx == oldTypeIdx) { 127 newTypeIdx = rng.nextInt(totalPoolIndices); 128 } 129 130 AssociatedMutation mutation = new AssociatedMutation(); 131 mutation.setup(this.getClass(), mutatableCode); 132 mutation.newInstanceToChangeIdx = newInstanceIdxInCache; 133 mutation.newInstanceTypeIdx = newTypeIdx; 134 return mutation; 135 } 136 137 @Override 138 protected void applyMutation(Mutation uncastMutation) { 139 // Cast the Mutation to our AssociatedMutation, so we can access its fields. 140 AssociatedMutation mutation = (AssociatedMutation) uncastMutation; 141 MutatableCode mutatableCode = mutation.mutatableCode; 142 143 generateCachedNewInstanceInsns(mutatableCode); 144 145 MInsn newInstanceInsn = newInstanceCachedInsns.get(mutation.newInstanceToChangeIdx); 146 147 ContainsPoolIndex poolIndex = ((ContainsPoolIndex)newInstanceInsn.insn.info.format); 148 149 poolIndex.setPoolIndex(newInstanceInsn.insn, mutation.newInstanceTypeIdx); 150 151 Log.info("Changed the type of " + newInstanceInsn.toString() + 152 " to " + mutation.newInstanceTypeIdx); 153 154 int foundNewInstanceInsnIdx = 155 foundInsnIdx(mutatableCode, newInstanceCachedInsns.get(mutation.newInstanceToChangeIdx)); 156 157 changeInvokeDirect(foundNewInstanceInsnIdx, mutation); 158 159 stats.incrementStat("Changed new instance."); 160 161 // Clear cache. 162 newInstanceCachedInsns = null; 163 } 164 165 /** 166 * Try to find the invoke-direct/ invoke-direct-range instruction that follows 167 * the new instance instruction and change the method ID of the instruction. 168 * @param foundInsnIdx 169 * @param uncastMutation 170 */ 171 protected void changeInvokeDirect(int foundInsnIdx, Mutation uncastMutation) { 172 AssociatedMutation mutation = (AssociatedMutation) uncastMutation; 173 MutatableCode mutatableCode = mutation.mutatableCode; 174 if (foundInsnIdx == -1 || 175 foundInsnIdx + 1 == mutatableCode.getInstructionCount()) { 176 return; 177 } 178 179 MInsn insn = mutatableCode.getInstructionAt(foundInsnIdx + 1); 180 if (isInvokeInst(insn)) { 181 ContainsPoolIndex poolIndex =((ContainsPoolIndex)insn.insn.info.format); 182 long oldMethodIdx = poolIndex.getPoolIndex(insn.insn); 183 String className = mutatableCode.program.getTypeString(mutation.newInstanceTypeIdx); 184 String methodName = mutatableCode.program.getMethodString((int) oldMethodIdx); 185 String shorty = mutatableCode.program.getMethodProto((int) oldMethodIdx); 186 187 // Matches the type of the invoke with the randomly changed type of the prior new-instance. 188 // This might create a lot of verification failures but still works many times. 189 // TODO: Work on generating a program which finds a valid type. 190 int methodId = mutatableCode.program.getNewItemCreator(). 191 findOrCreateMethodId(className, methodName, shorty); 192 193 poolIndex.setPoolIndex(insn.insn, mutation.newInstanceTypeIdx); 194 195 insn.insn.vregB = methodId; 196 197 Log.info("Changed " + oldMethodIdx + " to " + methodId); 198 } 199 } 200 201 protected boolean isInvokeInst(MInsn mInsn) { 202 return (mInsn.insn.info.opcode == Opcode.INVOKE_DIRECT || 203 mInsn.insn.info.opcode == Opcode.INVOKE_DIRECT_RANGE); 204 } 205 206 // Check if there is an new instance instruction, and if found, return the index. 207 // If not, return -1. 208 protected int foundInsnIdx(MutatableCode mutatableCode, MInsn newInstanceInsn) { 209 int i = 0; 210 for (MInsn mInsn : mutatableCode.getInstructions()) { 211 if (mInsn == newInstanceInsn) { 212 return i; 213 } 214 i++; 215 } 216 return -1; 217 } 218 } 219