/*
* Copyright (C) 2012 Sony Mobile Communications AB
*
* This file is part of ApkAnalyser.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package andreflect.injection;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import jerl.bcm.inj.InjectionMethod;
import jerl.bcm.util.ClassInjContainer;
import jerl.bcm.util.InjectionUtil;
import mereflect.MEClass;
import org.jf.dexlib.CodeItem;
import org.jf.dexlib.CodeItem.EncodedCatchHandler;
import org.jf.dexlib.CodeItem.EncodedTypeAddrPair;
import org.jf.dexlib.CodeItem.TryItem;
import org.jf.dexlib.DebugInfoItem;
import org.jf.dexlib.DexFile;
import org.jf.dexlib.TypeListItem;
import org.jf.dexlib.Code.Instruction;
import org.jf.dexlib.Code.MultiOffsetInstruction;
import org.jf.dexlib.Code.OffsetInstruction;
import org.jf.dexlib.Code.Opcode;
import org.jf.dexlib.Debug.DebugInstructionIterator;
import org.jf.dexlib.Util.AccessFlags;
import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
import org.jf.dexlib.Util.ByteArrayInput;
import org.jf.dexlib.Util.SparseIntArray;
import util.JarFileModifier;
import analyser.gui.ProgressReporter;
import analyser.logic.BytecodeModificationMediator;
import andreflect.ApkClassContext;
import andreflect.DexClass;
import andreflect.DexMethod;
import andreflect.injection.abs.DalvikInjectionMethod;
import andreflect.sign.ApkSign;
public class DalvikBytecodeModificationMediator {
private static final String MOD_LIST_FILENAME = "modlist.cfg";
private static final String DEX_FILENAME = "classes.dex";
public static File performRegisteredModifications(BytecodeModificationMediator bmm,
ProgressReporter pr,
final ApkClassContext ctx,
final String midletNamePostfix) throws IOException, Exception {
Map<MEClass, ClassInjContainer> classInjectionsInContext = bmm.getModifications(ctx);
if (classInjectionsInContext != null) {
// perform bytecode modifications
modifyClasses(pr, ctx, classInjectionsInContext);
// create modification spec
final ByteArrayOutputStream modSpecOut = new ByteArrayOutputStream();
Iterator<MEClass> classI = classInjectionsInContext.keySet().iterator();
while (classI.hasNext()) {
MEClass clazz = classI.next();
ClassInjContainer injContainer = classInjectionsInContext.get(clazz);
InjectionUtil.deconstructClassToStream(injContainer,
new PrintStream(modSpecOut));
} // per class
ZipFile zipFile = new ZipFile(ctx.getFile().getAbsolutePath());
final List<String> newEntries = new ArrayList<String>();
newEntries.add(DEX_FILENAME);
if (zipFile.getEntry(MOD_LIST_FILENAME) == null) {
newEntries.add(MOD_LIST_FILENAME);
}
final List<String> excludeEntries = new ArrayList<String>();
excludeEntries.add(DEX_FILENAME);
// remove META-INF folder and all its contents
Enumeration<? extends ZipEntry> e = zipFile.entries();
while (e.hasMoreElements()) {
ZipEntry entry = e.nextElement();
if (!entry.isDirectory()) {
if (entry.getName().startsWith("META-INF")) {
excludeEntries.add(entry.getName());
}
}
}
JarFileModifier jfModder = new JarFileModifier(zipFile) {
@Override
public List<String> excludeEntries() {
return excludeEntries;
}
@Override
public List<String> newEntries() {
return newEntries;
}
@Override
public InputStream getNewEntry(String entryName, boolean modified) {
if (entryName.equals(MOD_LIST_FILENAME)) {
return new ByteArrayInputStream(modSpecOut.toByteArray());
} else if (entryName.equals(DEX_FILENAME)) {
return new ByteArrayInputStream(getAsByteArray(ctx.getDex()));
}
return null;
}
};
File out = jfModder.createModifiedJar();
File outSigned = new File(new ApkSign().sign(out));
// try{
// DexFile dex1 = new DexFile(outSigned);
// org.jf.baksmali.dump.dump(dex1, "c:\\dump_"+ctx.getFile().getName().replace('.', '_')+".txt", null, false);}
// catch(Exception e){
// System.out.println("exception during dump:"+e.getClass().getName()+","+e.getMessage());
// e.printStackTrace();
// }
return outSigned;
} else {
return null;
}
}
public static byte[] getAsByteArray(DexFile dexFile)
{
dexFile.place();
CodeItem codeitem;
for (Iterator<?> iterator = dexFile.CodeItemsSection.getItems().iterator(); iterator.hasNext(); codeitem.fixInstructions(true, true)) {
codeitem = (CodeItem) iterator.next();
}
dexFile.place();
ByteArrayAnnotatedOutput bytearrayannotatedoutput = new ByteArrayAnnotatedOutput();
dexFile.writeTo(bytearrayannotatedoutput);
byte abyte0[] = bytearrayannotatedoutput.toByteArray();
DexFile.calcSignature(abyte0);
DexFile.calcChecksum(abyte0);
return abyte0;
}
public static Map<String, byte[]> modifyClasses(ProgressReporter pr, ApkClassContext ctx,
Map<MEClass, ClassInjContainer> classInjections) throws IOException {
DalvikInjectCollection injectCollection = new DalvikInjectCollection(ctx.getDex());
// the modified bytecode per class name
Map<String, byte[]> modClasses = new HashMap<String, byte[]>();
Iterator<MEClass> classI = classInjections.keySet().iterator();
if (pr != null) {
pr.reportStart(classInjections.keySet().size());
}
// modify each class
int ci = 0;
while (classI.hasNext()) {
if (pr != null) {
pr.reportWork(ci++);
}
DexClass clazz = (DexClass) classI.next();
// get modifications for class
ClassInjContainer injContainer = classInjections.get(clazz);
// modify
String className = clazz.getName();
byte[] classData;
classData = preformInjection(ctx, injectCollection, injContainer.methodInjectionsToArray());
// and store it
modClasses.put(className, classData);
} // per class
if (pr != null) {
pr.reportEnd();
}
return modClasses;
}
private static byte[] preformInjection(ApkClassContext ctx, DalvikInjectCollection ic, InjectionMethod[] injections) {
byte[] b = null;
for (int i = 0; i < injections.length; i++) {
if (injections[i] instanceof DalvikInjectionMethod) {
DexMethod method = ((DalvikInjectionMethod) injections[i]).getMethod();
CodeItem codeItem = method.getEncodedMethod().codeItem;
if (codeItem != null) {
DalvikCodeItemVisitor.visit(((DalvikInjectionMethod) injections[i]), method, ic);
}
}
}
for (int i = 0; i < injections.length; i++) {
if (injections[i] instanceof DalvikInjectionMethod) {
fixRegisters(((DalvikInjectionMethod) injections[i]).getMethod(), ic);
}
}
return b;
}
public static void injectInsturctionsAtInstruction(DexMethod method, Instruction ins, ArrayList<Instruction> injectInstructions) {
CodeItem codeItem = method.getEncodedMethod().codeItem;
int index = -1;
Instruction[] instructions = codeItem.getInstructions();
for (int i = 0; i < instructions.length; i++) {
if (ins == instructions[i]) {
index = i;
break;
}
}
if (index != -1) {
injectInstructionsAtIndex(method, index, injectInstructions);
}
}
public static void injectInsturctionsAtNextInstruction(DexMethod method, Instruction ins, ArrayList<Instruction> injectInstructions) {
CodeItem codeItem = method.getEncodedMethod().codeItem;
int index = -1;
Instruction[] instructions = codeItem.getInstructions();
for (int i = 0; i < instructions.length; i++) {
if (ins == instructions[i]) {
index = i;
break;
}
}
if (index != -1
&& index + 1 < instructions.length) {
injectInstructionsAtIndex(method, index + 1, injectInstructions);
}
}
private static void injectInstructionsAtIndex(DexMethod method, int instructionIndex, ArrayList<Instruction> injectInstructions) {
CodeItem codeItem = method.getEncodedMethod().codeItem;
int injectLength = injectInstructions.size();
DebugInfoItem debugInfo = codeItem.getDebugInfo();
EncodedCatchHandler[] encodedCatchHandlers = codeItem.getHandlers();
TryItem[] tries = codeItem.getTries();
Instruction[] instructions = codeItem.getInstructions();
int[] originalInstructionCodeAddresses = new int[instructions.length + injectLength + 1];
int i = 0;
for (i = 0; i < originalInstructionCodeAddresses.length; i++) {
originalInstructionCodeAddresses[i] = -1;
}
SparseIntArray originalSwitchAddressByOriginalSwitchDataAddress = new SparseIntArray();
int currentCodeAddress = 0;
int injectCodeAddress = 0;
int skip = 0;
for (i = 0; i < instructions.length; i++) {
Instruction instruction = instructions[i];
if (instruction.opcode == Opcode.PACKED_SWITCH || instruction.opcode == Opcode.SPARSE_SWITCH) {
OffsetInstruction offsetInstruction = (OffsetInstruction) instruction;
int switchDataAddress = currentCodeAddress + offsetInstruction.getTargetAddressOffset();
if (originalSwitchAddressByOriginalSwitchDataAddress.indexOfKey(switchDataAddress) < 0) {
originalSwitchAddressByOriginalSwitchDataAddress.put(switchDataAddress, currentCodeAddress);
}
}
if (i == instructionIndex) {
skip = injectLength;
injectCodeAddress = currentCodeAddress;
}
originalInstructionCodeAddresses[i + skip] = currentCodeAddress;
currentCodeAddress += instruction.getSize(currentCodeAddress);
}
//add the address just past the end of the last instruction, to help when fixing up try blocks that end
//at the end of the method
originalInstructionCodeAddresses[i + skip] = currentCodeAddress;
////
ArrayList<Instruction> insList = new ArrayList<Instruction>(Arrays.asList(codeItem.getInstructions()));
for (i = injectLength - 1; i >= 0; i--) {
insList.add(instructionIndex, injectInstructions.get(i));
}
codeItem.updateCode(insList.toArray(new Instruction[insList.size()]));
instructions = codeItem.getInstructions();
////
final SparseIntArray originalAddressByNewAddress = new SparseIntArray();
final SparseIntArray newAddressByOriginalAddress = new SparseIntArray();
currentCodeAddress = 0;
for (i = 0; i < instructions.length; i++) {
Instruction instruction = instructions[i];
int originalAddress = originalInstructionCodeAddresses[i];
if (originalAddress != -1) {
originalAddressByNewAddress.append(currentCodeAddress, originalAddress);
newAddressByOriginalAddress.append(originalAddress, currentCodeAddress);
}
currentCodeAddress += instruction.getSize(currentCodeAddress);
}
//add the address just past the end of the last instruction, to help when fixing up try blocks that end
//at the end of the method
if (originalInstructionCodeAddresses[i] != -1) {
originalAddressByNewAddress.append(currentCodeAddress, originalInstructionCodeAddresses[i]);
newAddressByOriginalAddress.append(originalInstructionCodeAddresses[i], currentCodeAddress);
}
//update any "offset" instructions, or switch data instructions
currentCodeAddress = 0;
for (i = 0; i < instructions.length; i++) {
Instruction instruction = instructions[i];
if (instruction instanceof OffsetInstruction) {
OffsetInstruction offsetInstruction = (OffsetInstruction) instruction;
assert originalAddressByNewAddress.indexOfKey(currentCodeAddress) >= 0;
int originalAddress = originalAddressByNewAddress.get(currentCodeAddress);
int originalInstructionTarget = originalAddress + offsetInstruction.getTargetAddressOffset();
assert newAddressByOriginalAddress.indexOfKey(originalInstructionTarget) >= 0;
int newInstructionTarget = newAddressByOriginalAddress.get(originalInstructionTarget);
if (originalInstructionTarget == injectCodeAddress) {
newInstructionTarget = injectCodeAddress;
}
int newCodeAddress = (newInstructionTarget - currentCodeAddress);
if (newCodeAddress != offsetInstruction.getTargetAddressOffset()) {
offsetInstruction.updateTargetAddressOffset(newCodeAddress);
}
} else if (instruction instanceof MultiOffsetInstruction) {
//System.out.println(codeItem.dexMethod.getMEClass().getName()+ "->"+codeItem.dexMethod.getName() + " dlisyes: ");
MultiOffsetInstruction multiOffsetInstruction = (MultiOffsetInstruction) instruction;
assert originalAddressByNewAddress.indexOfKey(currentCodeAddress) >= 0;
int originalDataAddress = originalAddressByNewAddress.get(currentCodeAddress);
int originalSwitchAddress =
originalSwitchAddressByOriginalSwitchDataAddress.get(originalDataAddress, -1);
if (originalSwitchAddress == -1) {
throw new RuntimeException("This method contains an unreferenced switch data block at address " +
+currentCodeAddress + " and can't be automatically fixed.");
}
assert newAddressByOriginalAddress.indexOfKey(originalSwitchAddress) >= 0;
int newSwitchAddress = newAddressByOriginalAddress.get(originalSwitchAddress);
//System.out.println(" ori switch= " + originalSwitchAddress +" new switch= " + newSwitchAddress);
int[] targets = multiOffsetInstruction.getTargets();
for (int t = 0; t < targets.length; t++) {
int originalTargetCodeAddress = originalSwitchAddress + targets[t];
assert newAddressByOriginalAddress.indexOfKey(originalTargetCodeAddress) >= 0;
int newTargetCodeAddress = newAddressByOriginalAddress.get(originalTargetCodeAddress);
if (originalTargetCodeAddress == injectCodeAddress) {
newTargetCodeAddress = injectCodeAddress;
}
int newCodeAddress = newTargetCodeAddress - newSwitchAddress;
//System.out.println(" "+ t + " target= "+ targets[t]+ " ori= " + originalTargetCodeAddress + " new= "+ newTargetCodeAddress+" new code= " + newCodeAddress);
if (newCodeAddress != targets[t]) {
multiOffsetInstruction.updateTarget(t, newCodeAddress);
}
}
}
currentCodeAddress += instruction.getSize(currentCodeAddress);
}
if (debugInfo != null) {
final byte[] encodedDebugInfo = debugInfo.getEncodedDebugInfo();
ByteArrayInput debugInput = new ByteArrayInput(encodedDebugInfo);
CodeItem.DebugInstructionFixer debugInstructionFixer = codeItem.new DebugInstructionFixer(encodedDebugInfo,
newAddressByOriginalAddress);
// DebugInstructionFixer debugInstructionFixer = new DebugInstructionFixer(encodedDebugInfo,
// newAddressByOriginalAddress, injectCodeAddress);
DebugInstructionIterator.IterateInstructions(debugInput, debugInstructionFixer);
if (debugInstructionFixer.result != null) {
debugInfo.setEncodedDebugInfo(debugInstructionFixer.result);
}
}
if (encodedCatchHandlers != null) {
for (EncodedCatchHandler encodedCatchHandler : encodedCatchHandlers) {
if (encodedCatchHandler.getCatchAllHandlerAddress() != -1) {
assert newAddressByOriginalAddress.indexOfKey(encodedCatchHandler.getCatchAllHandlerAddress()) >= 0;
encodedCatchHandler.catchAllHandlerAddress =
newAddressByOriginalAddress.get(encodedCatchHandler.getCatchAllHandlerAddress());
if (encodedCatchHandler.getCatchAllHandlerAddress() == injectCodeAddress) {
throw new RuntimeException("Can not inject at the beginning of a catch block" +
+currentCodeAddress + "@" + method.getMEClass().getName() + "." + method.getName());
}
}
for (EncodedTypeAddrPair handler : encodedCatchHandler.handlers) {
assert newAddressByOriginalAddress.indexOfKey(handler.getHandlerAddress()) >= 0;
handler.handlerAddress = newAddressByOriginalAddress.get(handler.getHandlerAddress());
if (handler.getHandlerAddress() == injectCodeAddress) {
throw new RuntimeException("Can not inject at the beginning of a catch block" +
+currentCodeAddress + "@" + method.getMEClass().getName() + "." + method.getName());
}
}
}
}
if (tries != null) {
for (TryItem tryItem : tries) {
int startAddress = tryItem.getStartCodeAddress();
int endAddress = tryItem.getStartCodeAddress() + tryItem.getTryLength();
assert newAddressByOriginalAddress.indexOfKey(startAddress) >= 0;
// if (startAddress != injectCodeAddress){
tryItem.startCodeAddress = newAddressByOriginalAddress.get(startAddress);
// }
assert newAddressByOriginalAddress.indexOfKey(endAddress) >= 0;
tryItem.tryLength = newAddressByOriginalAddress.get(endAddress) - tryItem.getStartCodeAddress();
}
}
}
/*
private static class DebugInstructionFixer extends DebugInstructionIterator.ProcessRawDebugInstructionDelegate {
private int currentCodeAddress = 0;
private SparseIntArray newAddressByOriginalAddress;
private final byte[] originalEncodedDebugInfo;
public byte[] result = null;
private int injectCodeAddress;
public DebugInstructionFixer(byte[] originalEncodedDebugInfo, SparseIntArray newAddressByOriginalAddress, int injectCodeAddress) {
this.newAddressByOriginalAddress = newAddressByOriginalAddress;
this.originalEncodedDebugInfo = originalEncodedDebugInfo;
this.injectCodeAddress = injectCodeAddress;
}
@Override
public void ProcessAdvancePC(int startDebugOffset, int debugInstructionLength, int codeAddressDelta) {
currentCodeAddress += codeAddressDelta;
if (result != null) {
return;
}
int newCodeAddress = newAddressByOriginalAddress.get(currentCodeAddress, -1);
//The address might not point to an actual instruction in some cases, for example, if an AdvancePC
//instruction was inserted just before a "special" instruction, to fix up the addresses for a previous
//instruction replacement.
//In this case, it should be safe to skip, because there will be another AdvancePC/SpecialOpcode that will
//bump up the address to point to a valid instruction before anything (line/local/etc.) is emitted
if (newCodeAddress == -1) {
return;
}
if (newCodeAddress != currentCodeAddress
&& injectCodeAddress != currentCodeAddress) {
int newCodeAddressDelta = newCodeAddress - (currentCodeAddress - codeAddressDelta);
assert newCodeAddressDelta > 0;
int codeAddressDeltaLeb128Size = Leb128Utils.unsignedLeb128Size(newCodeAddressDelta);
//if the length of the new code address delta is the same, we can use the existing buffer
if (codeAddressDeltaLeb128Size + 1 == debugInstructionLength) {
result = originalEncodedDebugInfo;
Leb128Utils.writeUnsignedLeb128(newCodeAddressDelta, result, startDebugOffset+1);
} else {
//The length of the new code address delta is different, so create a new buffer with enough
//additional space to accomodate the new code address delta value.
result = new byte[originalEncodedDebugInfo.length + codeAddressDeltaLeb128Size -
(debugInstructionLength - 1)];
System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startDebugOffset);
result[startDebugOffset] = DebugOpcode.DBG_ADVANCE_PC.value;
Leb128Utils.writeUnsignedLeb128(newCodeAddressDelta, result, startDebugOffset+1);
System.arraycopy(originalEncodedDebugInfo, startDebugOffset + debugInstructionLength, result,
startDebugOffset + codeAddressDeltaLeb128Size + 1,
originalEncodedDebugInfo.length - (startDebugOffset + codeAddressDeltaLeb128Size + 1));
}
}
}
@Override
public void ProcessSpecialOpcode(int startDebugOffset, int debugOpcode, int lineDelta,
int codeAddressDelta) {
currentCodeAddress += codeAddressDelta;
if (result != null) {
return;
}
int newCodeAddress = newAddressByOriginalAddress.get(currentCodeAddress, -1);
assert newCodeAddress != -1;
if (newCodeAddress != currentCodeAddress
&& injectCodeAddress != currentCodeAddress) {
int newCodeAddressDelta = newCodeAddress - (currentCodeAddress - codeAddressDelta);
assert newCodeAddressDelta > 0;
//if the new code address delta won't fit in the special opcode, we need to insert
//an additional DBG_ADVANCE_PC opcode
if (lineDelta < 2 && newCodeAddressDelta > 16 || lineDelta > 1 && newCodeAddressDelta > 15) {
int additionalCodeAddressDelta = newCodeAddress - currentCodeAddress;
int additionalCodeAddressDeltaLeb128Size = Leb128Utils.signedLeb128Size(additionalCodeAddressDelta);
//create a new buffer with enough additional space for the new opcode
result = new byte[originalEncodedDebugInfo.length + additionalCodeAddressDeltaLeb128Size + 1];
System.arraycopy(originalEncodedDebugInfo, 0, result, 0, startDebugOffset);
result[startDebugOffset] = 0x01; //DBG_ADVANCE_PC
Leb128Utils.writeUnsignedLeb128(additionalCodeAddressDelta, result, startDebugOffset+1);
System.arraycopy(originalEncodedDebugInfo, startDebugOffset, result,
startDebugOffset+additionalCodeAddressDeltaLeb128Size+1,
result.length - (startDebugOffset+additionalCodeAddressDeltaLeb128Size+1));
} else {
result = originalEncodedDebugInfo;
result[startDebugOffset] = DebugInfoBuilder.calculateSpecialOpcode(lineDelta,
newCodeAddressDelta);
}
}
}
}
*/
public static void fixRegisters(DexMethod method, DalvikInjectCollection ic) {
if (method.getEncodedMethod() == null
|| method.getEncodedMethod().codeItem == null) {
return;
}
CodeItem codeItem = method.getEncodedMethod().codeItem;
int regCount = codeItem.getRegisterCount() - codeItem.registerOriginalCount;
if (regCount != 0) {
int parameterRegisterCount = codeItem.getParent().method.getPrototype().getParameterRegisterCount()
+ (((codeItem.getParent().accessFlags & AccessFlags.STATIC.getValue()) == 0) ? 1 : 0);
if (parameterRegisterCount != 0) {
//codeItem.registerCount += parameterRegisterCount;
boolean isStatic = (codeItem.getParent().accessFlags & AccessFlags.STATIC.getValue()) != 0;
//int regSrc = codeItem.registerCount - parameterRegisterCount;
int regSrc = codeItem.getRegisterCount();
codeItem.registerCount += parameterRegisterCount;
int regDest = codeItem.registerOriginalCount - parameterRegisterCount;
String paramShortDesc = null;
TypeListItem params = codeItem.getParent().method.getPrototype().getParameters();
if (params != null) {
paramShortDesc = codeItem.getParent().method.getPrototype().getParameters().getShortyString();
}
ArrayList<Instruction> injectInstructions = ic.injectCopyParamRegs(codeItem, regDest, regSrc, isStatic, paramShortDesc);
injectInstructionsAtIndex(method, 0, injectInstructions);
}
codeItem.registerOriginalCount = codeItem.getRegisterCount();
}
}
}