/* * 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; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import mereflect.MEClass; import mereflect.MEField; import org.jf.dexlib.FieldIdItem; import org.jf.dexlib.Code.Instruction; import andreflect.xml.XmlResAttrDecoder; import brut.androlib.AndrolibException; import brut.androlib.res.data.ResID; import brut.androlib.res.data.ResPackage; import brut.androlib.res.data.ResResSpec; import brut.androlib.res.data.ResResource; import brut.androlib.res.data.ResTable; import brut.androlib.res.data.value.ResArrayValue; import brut.androlib.res.data.value.ResBagValue; import brut.androlib.res.data.value.ResEnumAttr; import brut.androlib.res.data.value.ResFlagsAttr; import brut.androlib.res.data.value.ResFlagsAttr.FlagItem; import brut.androlib.res.data.value.ResIntValue; import brut.androlib.res.data.value.ResPluralsValue; import brut.androlib.res.data.value.ResReferenceValue; import brut.androlib.res.data.value.ResScalarValue; import brut.androlib.res.data.value.ResStyleValue; import brut.androlib.res.data.value.ResValue; import brut.androlib.res.decoder.ARSCDecoder; import brut.util.Duo; public class DexReferenceCache { public static class LoadConst { public DexMethod method; public Instruction instruction; public int pc; public LoadConst(DexMethod method, Instruction instruction, int pc) { this.method = method; this.pc = pc; this.instruction = instruction; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(method.getMEClass().getName() + "."); sb.append(method.getFormattedName()); sb.append("(" + method.getArgumentsString() + ")"); sb.append(" @ " + Integer.toHexString(instruction.codeAddress)); if (instruction.line != -1) { sb.append(" (line " + instruction.line + " )"); } return sb.toString(); } } public static class LoadConstRes extends LoadConst { public int resId; public LoadConstRes(int resId, DexMethod method, Instruction instruction, int pc) { super(method, instruction, pc); this.resId = resId; } } public static class LoadConstString extends LoadConst { public String string; public LoadConstString(String string, DexMethod method, Instruction instruction, int pc) { super(method, instruction, pc); this.string = string; } } public static class FieldAccess { public FieldIdItem fieldIdItem; public DexMethod method; public Instruction instruction; public int pc; public boolean isRead; public MEField field; public MEClass clazz; public FieldAccess(FieldIdItem fieldIdItem, DexMethod method, Instruction instruction, int pc, boolean isRead) { this.fieldIdItem = fieldIdItem; this.method = method; this.pc = pc; this.instruction = instruction; this.isRead = isRead; field = null; clazz = null; } public void setMEField(MEField field, MEClass clazz) { this.field = field; this.clazz = clazz; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(method.getMEClass().getName() + "."); sb.append(method.getFormattedName()); sb.append("(" + method.getArgumentsString() + ")"); sb.append(" @ " + Integer.toHexString(instruction.codeAddress)); if (instruction.line != -1) { sb.append(" (line " + instruction.line + " )"); } return sb.toString(); } } public static final String RESOURCE = "resources.arsc"; private ResTable m_resTable = null; private final boolean m_isMidlet; protected File m_file; private final HashMap<ResResSpec, DexResSpec> m_specMap = new HashMap<ResResSpec, DexResSpec>(); private final HashMap<Integer, HashSet<ResResSpec>> m_internalSpecsRef = new HashMap<Integer, HashSet<ResResSpec>>(); private final HashMap<Integer, HashSet<ResResSpec>> m_externalSpecsRef = new HashMap<Integer, HashSet<ResResSpec>>(); private final HashMap<Integer, ArrayList<LoadConstRes>> m_internalCodeRef = new HashMap<Integer, ArrayList<LoadConstRes>>(); private final HashMap<Integer, ArrayList<LoadConstRes>> m_externalCodeRef = new HashMap<Integer, ArrayList<LoadConstRes>>(); private final HashMap<String, ArrayList<LoadConstString>> m_constStringRef = new HashMap<String, ArrayList<LoadConstString>>(); private final ArrayList<FieldAccess> m_fieldAccessRef = new ArrayList<FieldAccess>(); public DexReferenceCache(File file, boolean isMidlet) { m_file = file; m_isMidlet = isMidlet; initResourceTable(); if (m_resTable != null && m_isMidlet) { checkBagReference(); } } public ResTable getResTable() { return m_resTable; } public Set<ResResSpec> getSpecs() { return m_specMap.keySet(); } public boolean hasSpec() { return m_specMap.keySet().size() != 0; } public DexResSpec getDexSpec(ResResSpec spec) { return m_specMap.get(spec); } public void putDexSpec(ResResSpec spec, DexResSpec dexSpec) { m_specMap.put(spec, dexSpec); } public boolean isSamePackage(int id) { if (m_resTable == null) { return false; } return m_resTable.hasPackage(new ResID(id).package_); } public ResResSpec getSpec(int id) { if (m_resTable == null) { return null; } try { return m_resTable.getResSpec(id); } catch (AndrolibException e) { } return null; } // below only for apk public HashSet<ResResSpec> findResInternalRefenence(int resId) { if (m_internalSpecsRef.containsKey(resId)) { return m_internalSpecsRef.get(resId); } else { return new HashSet<ResResSpec>(); } } public HashSet<ResResSpec> findResExternalRefenence(int resId) { if (m_externalSpecsRef.containsKey(resId)) { return m_externalSpecsRef.get(resId); } else { return new HashSet<ResResSpec>(); } } public Set<Integer> listResExternalReference() { return m_externalSpecsRef.keySet(); } public HashSet<ResResSpec> findResAndroidSystemReference() { HashSet<ResResSpec> result = new HashSet<ResResSpec>(); Iterator<Integer> i = m_externalSpecsRef.keySet().iterator(); while (i.hasNext()) { int id = i.next(); if (new ResID(id).package_ == XmlResAttrDecoder.ANDROID_PACKAGE_ID) { HashSet<ResResSpec> specs = m_externalSpecsRef.get(id); for (ResResSpec spec : specs) { result.add(spec); } } } return result; } public ArrayList<LoadConstRes> findCodeInternalRefenence(int resId) { if (m_internalCodeRef.containsKey(resId)) { return m_internalCodeRef.get(resId); } else { return new ArrayList<LoadConstRes>(); } } public ArrayList<LoadConstRes> findCodeExternalRefenence(int resId) { if (m_externalCodeRef.containsKey(resId)) { return m_externalCodeRef.get(resId); } else { return new ArrayList<LoadConstRes>(); } } public Set<Integer> listCodeExternalReference() { return m_externalCodeRef.keySet(); } public ArrayList<LoadConstRes> findCodeAndroidSystemReference() { ArrayList<LoadConstRes> result = new ArrayList<LoadConstRes>(); Iterator<Integer> i = m_externalCodeRef.keySet().iterator(); while (i.hasNext()) { int id = i.next(); if (new ResID(id).package_ == XmlResAttrDecoder.ANDROID_PACKAGE_ID) { ArrayList<LoadConstRes> loadConsts = m_externalCodeRef.get(id); for (LoadConstRes loadConst : loadConsts) { result.add(loadConst); } } } return result; } public ArrayList<LoadConstString> findConstString(String value) { if (m_constStringRef.containsKey(value)) { return m_constStringRef.get(value); } else { return new ArrayList<LoadConstString>(); } } public Set<String> listConstString() { return m_constStringRef.keySet(); } public void addCodeConstString(LoadConstString loadConstString) { if (!m_isMidlet) { return; } if (m_constStringRef.get(loadConstString.string) == null) { ArrayList<LoadConstString> load = new ArrayList<LoadConstString>(); m_constStringRef.put(loadConstString.string, load); } m_constStringRef.get(loadConstString.string).add(loadConstString); } public void addCodeReference(LoadConstRes loadConst) { if (!m_isMidlet) { return; } ResID resId = new ResID(loadConst.resId); HashMap<Integer, ArrayList<LoadConstRes>> cache; if (getSpec(resId.id) != null) { cache = m_internalCodeRef; } else { cache = m_externalCodeRef; } if (cache.get(loadConst.resId) == null) { ArrayList<LoadConstRes> specs = new ArrayList<LoadConstRes>(); cache.put(loadConst.resId, specs); } cache.get(loadConst.resId).add(loadConst); } public ArrayList<FieldAccess> getFieldAccesses() { return m_fieldAccessRef; } public void addFieldAccessReference(FieldAccess fieldAccess) { m_fieldAccessRef.add(fieldAccess); } public List<FieldAccess> findFieldAccesses(String name, String descriptor, String clazz) { ArrayList<FieldAccess> result = new ArrayList<FieldAccess>(); for (FieldAccess access : m_fieldAccessRef) { if (access.fieldIdItem.getFieldName().getStringValue().equals(name) && access.fieldIdItem.getFieldType().getTypeDescriptor().equals(descriptor) && Util.getClassName(access.fieldIdItem.getContainingClass().getTypeDescriptor()).equals(clazz)) { result.add(access); } } return result; } private void initResourceTable() { InputStream is = getResourceInputStream(); if (is != null) { try { ResTable resTable = new ResTable(null); ResPackage[] pkgs = ARSCDecoder.decode(is, false, true, resTable).getPackages(); if (pkgs.length != 1) { System.out.println("System resource package (package length > 1) from " + m_file.getName()); for (int i = 0; i < pkgs.length; i++) { System.out.println(" pkg[" + i + "] = " + pkgs[i].getName() + " (" + pkgs[i].getId() + ")"); } } m_resTable = resTable; for (ResPackage pkg : pkgs) { resTable.addPackage(pkg, true); } for (ResPackage pkg : m_resTable.listMainPackages()) { for (ResResSpec spec : pkg.listResSpecs()) { putDexSpec(spec, null); } } } catch (AndrolibException e) { e.printStackTrace(); } } } private InputStream getResourceInputStream() { try { ZipFile zipFile = new ZipFile(m_file); ZipEntry zipEntry = zipFile.getEntry(RESOURCE); if (zipEntry != null) { return zipFile.getInputStream(zipEntry); } } catch (ZipException e) { //ignore because it may not be apk file but a odex file } catch (IOException e) { //ignore because it may not be apk file but a odex file } return null; } private void addReference(ResResSpec resSpec, ResValue resValue) { if (resValue != null && resValue instanceof ResReferenceValue) { int value = ((ResReferenceValue) resValue).getValue(); ResID resId = new ResID(value); HashMap<Integer, HashSet<ResResSpec>> cache; if (getSpec(resId.id) != null) { cache = m_internalSpecsRef; } else { cache = m_externalSpecsRef; } if (cache.get(value) == null) { HashSet<ResResSpec> specs = new HashSet<ResResSpec>(); cache.put(value, specs); } cache.get(value).add(resSpec); } } private void checkBagReference() { for (ResPackage pkg : m_resTable.listMainPackages()) { for (ResResSpec spec : pkg.listResSpecs()) { for (ResResource res : spec.listResources()) { ResValue resValue = res.getValue(); if (resValue == null) { continue; } else if (resValue instanceof ResReferenceValue) { addReference(spec, resValue); } else if (resValue instanceof ResArrayValue) { for (ResScalarValue resScalarValue : ((ResArrayValue) resValue).mItems) { addReference(spec, resScalarValue); } } else if (resValue instanceof ResPluralsValue) { for (int i = 0; i < ((ResPluralsValue) resValue).mItems.length; i++) { ResScalarValue item = ((ResPluralsValue) resValue).mItems[i]; if (item == null) { continue; } addReference(spec, item); } } else if (resValue instanceof ResStyleValue) { for (Duo<ResReferenceValue, ResScalarValue> duoValue : ((ResStyleValue) resValue).mItems) { addReference(spec, duoValue.m1); addReference(spec, duoValue.m2); } } else if (resValue instanceof ResEnumAttr) { for (Duo<ResReferenceValue, ResIntValue> duoValue : ((ResEnumAttr) resValue).mItems) { addReference(spec, duoValue.m1); addReference(spec, duoValue.m2); } } else if (resValue instanceof ResFlagsAttr) { int length = ((ResFlagsAttr) resValue).mItems.length; for (int i = 0; i < length; i++) { FlagItem item = ((ResFlagsAttr) resValue).mItems[i]; addReference(spec, item.ref); } } if (resValue instanceof ResBagValue) { addReference(spec, ((ResBagValue) resValue).getParent()); } } } } } }