package de.ecspride.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Collections; import java.util.List; import org.xmlpull.v1.XmlPullParserException; import soot.Body; import soot.Scene; import soot.SootClass; import soot.SootMethod; import soot.Unit; import soot.jimple.AssignStmt; import soot.jimple.StringConstant; import soot.jimple.infoflow.android.axml.AXmlAttribute; import soot.jimple.infoflow.android.axml.AXmlHandler; import soot.jimple.infoflow.android.axml.AXmlNode; import soot.jimple.infoflow.android.axml.ApkHandler; import soot.jimple.infoflow.android.manifest.ProcessManifest; import de.ecspride.Settings; /** * This class contains methods to update the AndroidManifest.xml, * the code and resource files of the instrumented application to * add an Activity that will be the first Activity instantiated when * a user clicks on the icon to launch the application. * * @author alex * */ public class UpdateManifestAndCodeForWaitPDP { /** * Get the name of the main activity in the AndroidManifest.xml file * @param apkFileLocation * @return */ public static String getMainActivityName(String apkFileLocation) { String mainActivityName = null; try { ProcessManifest pm = new ProcessManifest(apkFileLocation); AXmlHandler axmlh = pm.getAXml(); // Find main activity and remove main intent-filter List<AXmlNode> anodes = axmlh.getNodesWithTag("activity"); for (AXmlNode an: anodes) { boolean hasMain = false; boolean hasLauncher = false; AXmlNode filter = null; AXmlAttribute aname = an.getAttribute("name"); String aval = (String)aname.getValue(); System.out.println("activity: "+ aval); for (AXmlNode ch : an.getChildren()) { System.out.println("children: "+ ch); } List<AXmlNode> fnodes = an.getChildrenWithTag("intent-filter"); for (AXmlNode fn: fnodes) { hasMain = false; hasLauncher = false; // check action List<AXmlNode> acnodes = fn.getChildrenWithTag("action"); for (AXmlNode acn: acnodes) { AXmlAttribute acname = acn.getAttribute("name"); String acval = (String)acname.getValue(); System.out.println("action: "+ acval); if (acval.equals("android.intent.action.MAIN")) { hasMain = true; } } // check category List<AXmlNode> catnodes = fn.getChildrenWithTag("category"); for (AXmlNode catn: catnodes) { AXmlAttribute catname = catn.getAttribute("name"); String catval = (String)catname.getValue(); System.out.println("category: "+ catval); if (catval.equals("android.intent.category.LAUNCHER")) { hasLauncher = true; filter = fn; } } if (hasLauncher && hasMain) { break; } } if (hasLauncher && hasMain) { // replace name with the activity waiting for the connection to the PDP System.out.println("main activity is: "+ aval); System.out.println("excluding filter: "+ filter); filter.exclude(); mainActivityName = aval; break; } } } catch (IOException | XmlPullParserException ex) { System.err.println("Could not read Android manifest file: " + ex.getMessage()); throw new RuntimeException(ex); } return mainActivityName; } /** * Get the package name of the application * @param apkFileLocation * @return */ public static String getApplicationPackageName(String apkFileLocation) { String packageName = null; try { ProcessManifest pm = new ProcessManifest(apkFileLocation); AXmlHandler axmlh = pm.getAXml(); // Find main activity and remove main intent-filter List<AXmlNode> anodes = axmlh.getNodesWithTag("manifest"); for (AXmlNode an: anodes) { boolean hasMain = false; boolean hasLauncher = false; AXmlNode filter = null; AXmlAttribute aname = an.getAttribute("package"); String aval = (String)aname.getValue(); packageName = aval; System.out.println("package: "+ packageName); break; } } catch (IOException | XmlPullParserException ex) { System.err.println("Could not read Android manifest file: " + ex.getMessage()); throw new RuntimeException(ex); } return packageName; } /** * Redirect the main activity in the AndroidManifiest.xml file * @param apkFileLocation * @return */ public static void redirectMainActivity(String apkFileLocation){ try { ProcessManifest pm = new ProcessManifest(apkFileLocation); AXmlHandler axmlh = pm.getAXml(); // add new 'main' intent-filter for our activity that waits for a pdp connection axmlh.getDocument().getRootNode(); List<AXmlNode> appnodes = axmlh.getNodesWithTag("application"); if (appnodes.size() != 1) { throw new RuntimeException("error: number of application node != 1 (= "+ appnodes.size() +")"); } AXmlNode appnode = appnodes.get(0); String attr_ns = "http://schemas.android.com/apk/res/android"; String tag_ns = null; System.out.println("attr_ns: "+ attr_ns +" tag_ns: "+ tag_ns); // remove MAIN and LAUNCHER filters from the original main activity, so we do not // end with having two launcher icons on the desktop { String mainActivityName = getMainActivityName(apkFileLocation); List<AXmlNode> activityNodes = axmlh.getNodesWithTag("activity"); AXmlNode mainActNode = null; for (AXmlNode n: activityNodes) { AXmlAttribute<?> name = n.getAttribute("name"); String value = (String) name.getValue(); if (value.equals(mainActivityName)) { mainActNode = n; break; } } if (mainActNode == null) { throw new RuntimeException("error: main activity not found in manifest!"); } // get filters AXmlNode filterToremove = null; List<AXmlNode> filters = mainActNode.getChildren(); for (AXmlNode fn: filters) { boolean hasMain = false; boolean hasLauncher = false; // check action List<AXmlNode> acnodes = fn.getChildrenWithTag("action"); for (AXmlNode acn: acnodes) { AXmlAttribute acname = acn.getAttribute("name"); String acval = (String)acname.getValue(); System.out.println("action: "+ acval); if (acval.equals("android.intent.action.MAIN")) { hasMain = true; } } // check category List<AXmlNode> catnodes = fn.getChildrenWithTag("category"); for (AXmlNode catn: catnodes) { AXmlAttribute catname = catn.getAttribute("name"); String catval = (String)catname.getValue(); System.out.println("category: "+ catval); if (catval.equals("android.intent.category.LAUNCHER")) { hasLauncher = true; } } if (hasLauncher && hasMain) { filterToremove = fn; break; } } if (filterToremove == null) { throw new RuntimeException("error: did not found filter with MAIN/LAUNCHER."); } mainActNode.removeChild(filterToremove); // filters.remove(filterToremove); // filterToremove.setParent(null); } AXmlNode mainActivity = new AXmlNode("activity", tag_ns, null); mainActivity.addAttribute(new AXmlAttribute<String>("name", "de.ecspride.javaclasses.WaitPDPActivity", attr_ns)); appnode.addChild(mainActivity); AXmlNode filter = new AXmlNode("intent-filter", tag_ns, null); mainActivity.addChild(filter); AXmlNode action = new AXmlNode("action", tag_ns, null); action.addAttribute(new AXmlAttribute<String>("name", "android.intent.action.MAIN", attr_ns)); filter.addChild(action); AXmlNode category = new AXmlNode("category", tag_ns, null); category.addAttribute(new AXmlAttribute<String>("name", "android.intent.category.LAUNCHER", attr_ns)); filter.addChild(category); byte[] newManifestBytes = axmlh.toByteArray(); FileOutputStream fileOuputStream = new FileOutputStream(Settings.sootOutput + File.separatorChar + "AndroidManifest.xml"); fileOuputStream.write(newManifestBytes); fileOuputStream.close(); } catch (IOException | XmlPullParserException ex) { System.err.println("Could not read Android manifest file: " + ex.getMessage()); throw new RuntimeException(ex); } } /** * * @param originalApk */ public static void replaceManifest(String originalApk) { File originalApkFile = new File(originalApk); String newManifest = Settings.sootOutput + File.separatorChar + "AndroidManifest.xml"; String targetApk = Settings.sootOutput + File.separatorChar + originalApkFile.getName(); File newMFile = new File(newManifest); try { ApkHandler apkH = new ApkHandler(targetApk); apkH.addFilesToApk(Collections.singletonList(newMFile)); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("error when writing new manifest: "+ e); } newMFile.delete(); } /** * * @param mainActivityClass * @param mainActivityClass */ public static void updateWaitPDPActivity(String packageName, String mainActivityClass) { if (mainActivityClass.startsWith(".")) { mainActivityClass = packageName + mainActivityClass; } SootClass sc = Scene.v().getSootClass("de.ecspride.javaclasses.WaitPDPActivity"); SootMethod sm = sc.getMethodByName("<init>"); Body b = sm.retrieveActiveBody(); for (Unit u: b.getUnits()) { if (u instanceof AssignStmt) { AssignStmt asg = (AssignStmt)u; if (asg.getRightOp() instanceof StringConstant) { StringConstant cst = (StringConstant)asg.getRightOp(); System.out.println("cst: "+ cst); if (cst.value.equals("")) { asg.setRightOp(StringConstant.v(mainActivityClass)); System.out.println("asg: "+ asg); } } } } } /** * * @param originalApk */ public static void addBackgroundFile(String originalApk) { File tempFile = null; try { File background_picture = new File("resources", "protect.png"); if (!background_picture.exists()) { // Load the file from the JAR URL fileURL = UpdateManifestAndCodeForWaitPDP.class.getResource("protect.png"); // Copy the file local tempFile = File.createTempFile("droidForce", null); InputStream is = fileURL.openStream(); try { Files.copy(is, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); background_picture = tempFile; } finally { is.close(); } } // By now, we must have a file if (background_picture == null ||!background_picture.exists()) throw new RuntimeException("Background image file not found"); File originalApkFile = new File(originalApk); String targetApk = Settings.sootOutput + File.separatorChar + originalApkFile.getName(); try { ApkHandler apkH = new ApkHandler(targetApk); apkH.addFilesToApk(Collections.singletonList(background_picture), Collections.singletonMap(background_picture.getPath(), "assets/protect.png")); } catch (IOException e) { e.printStackTrace(); throw new RuntimeException("error when adding background image: "+ e); } } catch (IOException ex) { System.err.println("File handling failed: " + ex.getMessage()); ex.printStackTrace(); } finally { if (tempFile != null) tempFile.delete(); } } }