當我們上傳apk或者ipa文件的時候是要讀取到里面的一些信息的,而對於未知的apk或者ipa,我們無法直接獲取其中的包名、應用名、圖標等,這時候通過工具類來獲取信息就很有必要了。在這里,我總結了一下讀取apk,ipa文件的代碼和使用方法,大家可以參考下,取其精華、去其糟粕,以便適用於自己的需求。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 首先看一下使用到的工具
環境:Linux
apktool是google提供的apk編譯工具,需要Java運行環境,推薦使用JDK1.6或者JDK1.7;dd-plist-1.16這個jar包需要JDK1.5以上。
所以,建議JDK環境1.6以上。
解析apk:使用windows開發環境的時候直接下載aapt.exe即可
(1)aapt
(2)apktool
(3)apktool.jar (google提供)
解析ipa:
(1)dd-plist-1.16.jar(google提供)
(2)python 2.6
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2 解析apk
對於apk的解析,我第一個想到的是導入相應的jar包,然后用一些代碼就可以讀取出來,這樣的話就比較方便簡單。事實上,這樣做的確是可以讀取到一些信息的,比如說apk的包名、版本號、版本名,但是對於apk的名稱和圖標就讀取不到了,顯然這個不能滿足於我們的要求。所以,我希望可以找到一種能夠讀取盡量多信息的方法,通過在網上查詢比較,我選擇了aapt這個工具。盡管aapt工具很煩人,比我之前說的導jar包的方式繁瑣很多,但是我們需要解析未知apk的圖標,名稱,不得不使用這個aapt工具了。
2.1 在Linux下安裝aapt工具
apktool和aapt各種版本的下載地址:http://connortumbleson.com/apktool/
下載apktool
# wget https://raw.githubusercontent.com/iBotPeaches/Apktool/master/scripts/linux/apktool
下載apktool_2.2.1.jar並且重命名為apktool.jar
# wget http://connortumbleson.com/apktool/apktool_2.2.1.jar
# mv apktool_2.2.1.jar apktool.jar
下載aapt
http://connortumbleson.com/apktool/aapt/linux/aapt
新建/usr/local/apktool文件夾,將apktool,apktool.jar和aapt移進來
# mv apktool apktool.jar aapt /usr/local/apktool
賦予apktool,apktool.jar和aapt可執行權限
# chmod +x apktool apktool.jar aapt
將apktool加入環境變量,修改/etc/profile,在最后一行添加如下內容
export PATH="$PATH:/usr/local/apktool"
使環境變量立即生效
# source /etc/profile
使用aapt工具時可能報如下錯誤
aapt: /lib64/libc.so.6: version `GLIBC_2.14' not found (required by aapt)
這是因為缺少glibc-2.14,安裝一下就好了
# wget http://ftp.gnu.org/gnu/glibc/glibc-2.14.tar.gz
# tar zxvf glibc-2.14.tar.gz
# cd glibc-2.14
# mkdir build
# cd build
# ../configure --prefix=/opt/glibc-2.14
# make -j4
# make install
修改/etc/profile,在最后一行添加如下內容
export LD_LIBRARY_PATH=/opt/glibc-2.14/lib:$LD_LIBRARY_PATH
使環境變量立即生效
# source /etc/profile
給aapt相應權限
# chmod 755 aapt
apktool和aapt簡單使用方法
# apktool d /data/test.apk //反編譯apk
# aapt dump badging /data/test.apk //查看apk包詳細信息
2.2 建兩個實體類
這兩個實體類封裝了要解析apk的一些信息字段,比如說apk的包名、版本號、版本名、所需設備特性等。
代碼如下:

/** * apk實體信息 */ public class ApkInfo { public static final String APPLICATION_ICON_120 = "application-icon-120"; public static final String APPLICATION_ICON_160 = "application-icon-160"; public static final String APPLICATION_ICON_240 = "application-icon-240"; public static final String APPLICATION_ICON_320 = "application-icon-320"; /** * apk內部版本號 */ private String versionCode = null; /** * apk外部版本號 */ private String versionName = null; /** * apk的包名 */ private String packageName = null; /** * 支持的android平台最低版本號 */ private String minSdkVersion = null; /** * apk所需要的權限 */ private List<String> usesPermissions = null; /** * 支持的SDK版本。 */ private String sdkVersion; /** * 建議的SDK版本 */ private String targetSdkVersion; /** * 應用程序名 */ private String applicationLable; /** * 各個分辨率下的圖標的路徑。 */ private Map<String, String> applicationIcons; /** * 程序的圖標。 */ private String applicationIcon; /** * 暗指的特性。 */ private List<ImpliedFeature> impliedFeatures; /** * 所需設備特性。 */ private List<String> features; /** * 啟動界面 */ private String launchableActivity; public ApkInfo() { this.usesPermissions = new ArrayList<String>(); this.applicationIcons = new HashMap<String, String>(); this.impliedFeatures = new ArrayList<ImpliedFeature>(); this.features = new ArrayList<String>(); } /** * 返回版本代碼。 * * @return 版本代碼。 */ public String getVersionCode() { return versionCode; } /** * @param versionCode * the versionCode to set */ public void setVersionCode(String versionCode) { this.versionCode = versionCode; } /** * 返回版本名稱。 * * @return 版本名稱。 */ public String getVersionName() { return versionName; } /** * @param versionName * the versionName to set */ public void setVersionName(String versionName) { this.versionName = versionName; } /** * 返回支持的最小sdk平台版本。 * * @return the minSdkVersion */ public String getMinSdkVersion() { return minSdkVersion; } /** * @param minSdkVersion * the minSdkVersion to set */ public void setMinSdkVersion(String minSdkVersion) { this.minSdkVersion = minSdkVersion; } /** * 返回包名。 * * @return 返回的包名。 */ public String getPackageName() { return packageName; } public void setPackageName(String packageName) { this.packageName = packageName; } /** * 返回sdk平台版本。 * * @return */ public String getSdkVersion() { return sdkVersion; } public void setSdkVersion(String sdkVersion) { this.sdkVersion = sdkVersion; } /** * 返回所建議的SDK版本。 * * @return */ public String getTargetSdkVersion() { return targetSdkVersion; } public void setTargetSdkVersion(String targetSdkVersion) { this.targetSdkVersion = targetSdkVersion; } /** * 返回所需的用戶權限。 * * @return */ public List<String> getUsesPermissions() { return usesPermissions; } public void setUsesPermissions(List<String> usesPermission) { this.usesPermissions = usesPermission; } public void addToUsesPermissions(String usesPermission) { this.usesPermissions.add(usesPermission); } /** * 返回程序的名稱標簽。 * * @return */ public String getApplicationLable() { return applicationLable; } public void setApplicationLable(String applicationLable) { this.applicationLable = applicationLable; } /** * 返回應用程序的圖標。 * * @return */ public String getApplicationIcon() { return applicationIcon; } public void setApplicationIcon(String applicationIcon) { this.applicationIcon = applicationIcon; } /** * 返回應用程序各個分辨率下的圖標。 * * @return */ public Map<String, String> getApplicationIcons() { return applicationIcons; } public void setApplicationIcons(Map<String, String> applicationIcons) { this.applicationIcons = applicationIcons; } public void addToApplicationIcons(String key, String value) { this.applicationIcons.put(key, value); } public void addToImpliedFeatures(ImpliedFeature impliedFeature) { this.impliedFeatures.add(impliedFeature); } /** * 返回應用程序所需的暗指的特性。 * * @return */ public List<ImpliedFeature> getImpliedFeatures() { return impliedFeatures; } public void setImpliedFeatures(List<ImpliedFeature> impliedFeatures) { this.impliedFeatures = impliedFeatures; } /** * 返回應用程序所需的特性。 * * @return */ public List<String> getFeatures() { return features; } public void setFeatures(List<String> features) { this.features = features; } public void addToFeatures(String feature) { this.features.add(feature); } @Override public String toString() { return "ApkInfo [versionCode=" + versionCode + ",\n versionName=" + versionName + ",\n packageName=" + packageName + ",\n minSdkVersion=" + minSdkVersion + ",\n usesPermissions=" + usesPermissions + ",\n sdkVersion=" + sdkVersion + ",\n targetSdkVersion=" + targetSdkVersion + ",\n applicationLable=" + applicationLable + ",\n applicationIcons=" + applicationIcons + ",\n applicationIcon=" + applicationIcon + ",\n impliedFeatures=" + impliedFeatures + ",\n features=" + features + ",\n launchableActivity=" + launchableActivity + "\n]"; } public String getLaunchableActivity() { return launchableActivity; } public void setLaunchableActivity(String launchableActivity) { this.launchableActivity = launchableActivity; } }

/** * 特性實體 */ public class ImpliedFeature { /** * 所需設備特性名稱。 */ private String feature; /** * 表明所需特性的內容。 */ private String implied; public ImpliedFeature() { super(); } public ImpliedFeature(String feature, String implied) { super(); this.feature = feature; this.implied = implied; } public String getFeature() { return feature; } public void setFeature(String feature) { this.feature = feature; } public String getImplied() { return implied; } public void setImplied(String implied) { this.implied = implied; } @Override public String toString() { return "Feature [feature=" + feature + ", implied=" + implied + "]"; } }
2.3 apk的工具類
代碼如下:

/** * apk工具類。封裝了獲取Apk信息的方法。 * 包名/版本號/版本名/應用程序名/支持的android最低版本號/支持的SDK版本/建議的SDK版本/所需設備特性等 */ public class ApkUtil { public static final String VERSION_CODE = "versionCode"; public static final String VERSION_NAME = "versionName"; public static final String SDK_VERSION = "sdkVersion"; public static final String TARGET_SDK_VERSION = "targetSdkVersion"; public static final String USES_PERMISSION = "uses-permission"; public static final String APPLICATION_LABEL = "application-label"; public static final String APPLICATION_ICON = "application-icon"; public static final String USES_FEATURE = "uses-feature"; public static final String USES_IMPLIED_FEATURE = "uses-implied-feature"; public static final String SUPPORTS_SCREENS = "supports-screens"; public static final String SUPPORTS_ANY_DENSITY = "supports-any-density"; public static final String DENSITIES = "densities"; public static final String PACKAGE = "package"; public static final String APPLICATION = "application:"; public static final String LAUNCHABLE_ACTIVITY = "launchable-activity"; private ProcessBuilder mBuilder; private static final String SPLIT_REGEX = "(: )|(=')|(' )|'"; private static final String FEATURE_SPLIT_REGEX = "(:')|(',')|'"; /** * aapt所在的目錄。 */ //windows環境下直接指向appt.exe //比如你可以放在src下 private String mAaptPath = "src/main/java/aapt"; //linux下 //private String mAaptPath = "/usr/local/apktool/aapt"; public ApkUtil() { mBuilder = new ProcessBuilder(); mBuilder.redirectErrorStream(true); } /** * 返回一個apk程序的信息。 * * @param apkPath apk的路徑。 * @return apkInfo 一個Apk的信息。 */ public ApkInfo getApkInfo(String apkPath) throws Exception { System.out.println("================================開始執行命令========================="); //通過命令調用aapt工具解析apk文件 Process process = mBuilder.command(mAaptPath, "d", "badging", apkPath) .start(); InputStream is = null; is = process.getInputStream(); BufferedReader br = new BufferedReader( new InputStreamReader(is, "utf8")); String tmp = br.readLine(); try { if (tmp == null || !tmp.startsWith("package")) { throw new Exception("參數不正確,無法正常解析APK包。輸出結果為:\n" + tmp + "..."); } ApkInfo apkInfo = new ApkInfo(); do { setApkInfoProperty(apkInfo, tmp); } while ((tmp = br.readLine()) != null); return apkInfo; } catch (Exception e) { throw e; } finally { process.destroy(); closeIO(is); closeIO(br); } } /** * 設置APK的屬性信息。 * * @param apkInfo * @param source */ private void setApkInfoProperty(ApkInfo apkInfo, String source) { if (source.startsWith(PACKAGE)) { splitPackageInfo(apkInfo, source); } else if (source.startsWith(LAUNCHABLE_ACTIVITY)) { apkInfo.setLaunchableActivity(getPropertyInQuote(source)); } else if (source.startsWith(SDK_VERSION)) { apkInfo.setSdkVersion(getPropertyInQuote(source)); } else if (source.startsWith(TARGET_SDK_VERSION)) { apkInfo.setTargetSdkVersion(getPropertyInQuote(source)); } else if (source.startsWith(USES_PERMISSION)) { apkInfo.addToUsesPermissions(getPropertyInQuote(source)); } else if (source.startsWith(APPLICATION_LABEL)) { //windows下獲取應用名稱 apkInfo.setApplicationLable(getPropertyInQuote(source)); } else if (source.startsWith(APPLICATION_ICON)) { apkInfo.addToApplicationIcons(getKeyBeforeColon(source), getPropertyInQuote(source)); } else if (source.startsWith(APPLICATION)) { String[] rs = source.split("( icon=')|'"); apkInfo.setApplicationIcon(rs[rs.length - 1]); //linux下獲取應用名稱 apkInfo.setApplicationLable(rs[1]); } else if (source.startsWith(USES_FEATURE)) { apkInfo.addToFeatures(getPropertyInQuote(source)); } else if (source.startsWith(USES_IMPLIED_FEATURE)) { apkInfo.addToImpliedFeatures(getFeature(source)); } else { // System.out.println(source); } } private ImpliedFeature getFeature(String source) { String[] result = source.split(FEATURE_SPLIT_REGEX); ImpliedFeature impliedFeature = new ImpliedFeature(result[1], result[2]); return impliedFeature; } /** * 返回出格式為name: 'value'中的value內容。 * * @param source * @return */ private String getPropertyInQuote(String source) { int index = source.indexOf("'") + 1; return source.substring(index, source.indexOf('\'', index)); } /** * 返回冒號前的屬性名稱 * * @param source * @return */ private String getKeyBeforeColon(String source) { return source.substring(0, source.indexOf(':')); } /** * 分離出包名、版本等信息。 * * @param apkInfo * @param packageSource */ private void splitPackageInfo(ApkInfo apkInfo, String packageSource) { String[] packageInfo = packageSource.split(SPLIT_REGEX); apkInfo.setPackageName(packageInfo[2]); apkInfo.setVersionCode(packageInfo[4]); apkInfo.setVersionName(packageInfo[6]); } /** * 釋放資源。 * * @param c 將關閉的資源 */ private final void closeIO(Closeable c) { if (c != null) { try { c.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { try { String demo = "D:\\Java\\idea_app\\ReadApkAndIpa1\\src\\main\\java\\150211100441.apk"; ApkInfo apkInfo = new ApkUtil().getApkInfo(demo); System.out.println(apkInfo); } catch (Exception e) { e.printStackTrace(); } } public String getmAaptPath() { return mAaptPath; } public void setmAaptPath(String mAaptPath) { this.mAaptPath = mAaptPath; } }
如上所示,我做了個測試,我使用的是神廟逃亡的apk,然后解析到如下信息:
從上可以看到解析到的圖標信息是以字符串的格式存在的,所以我們還需要一個工具類,通過解析這個字符串解壓出icon圖片並且存放到磁盤上

/** * 通過ApkInfo 里的applicationIcon從APK里解壓出icon圖片並存放到磁盤上 */ public class ApkIconUtil { /** * 從指定的apk文件里獲取指定file的流 * * @param apkPath * @param fileName * @return */ public static InputStream extractFileFromApk(String apkPath, String fileName) { try { ZipFile zFile = new ZipFile(apkPath); ZipEntry entry = zFile.getEntry(fileName); entry.getComment(); entry.getCompressedSize(); entry.getCrc(); entry.isDirectory(); entry.getSize(); entry.getMethod(); InputStream stream = zFile.getInputStream(entry); return stream; } catch (IOException e) { e.printStackTrace(); } return null; } /** * 從指定的apk文件里解壓出icon圖片並存放到指定的磁盤上 * * @param apkPath apk文件路徑 * @param fileName apk的icon * @param outputPath 指定的磁盤路徑 * @throws Exception */ public static void extractFileFromApk(String apkPath, String fileName, String outputPath) throws Exception { InputStream is = extractFileFromApk(apkPath, fileName); File file = new File(outputPath); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file), 1024); byte[] b = new byte[1024]; BufferedInputStream bis = new BufferedInputStream(is, 1024); while (bis.read(b) != -1) { bos.write(b); } bos.flush(); is.close(); bis.close(); bos.close(); } /** * demo 獲取apk文件的icon並寫入磁盤指定位置 * * @param args */ public static void main(String[] args) { try { String apkPath = "D:\\Java\\idea_app\\ReadApkAndIpa1\\src\\main\\java\\shenmiaotaowang_966.apk"; /* if (args.length > 0) { apkPath = args[0]; }*/ ApkInfo apkInfo = new ApkUtil().getApkInfo(apkPath); System.out.println(apkInfo); long now = System.currentTimeMillis(); extractFileFromApk(apkPath, apkInfo.getApplicationIcon(), "D:\\image\\apkIcon" + now + ".png"); } catch (Exception e) { e.printStackTrace(); } } }
經測試,此方案可行,所以如果不需要解析圖標的話,ApkUtil就夠用了;如果需要解析圖標到本地的話,就需要用ApkIconUtil。
這是我解析到的圖標
apk的解析到這里就可以告一段落了!
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3 解析ipa
解析ipa基本屬性需要用到dd-plist-1.16.jar,有了這個jar包,我們解析ipa基本屬性就相對來說簡單一點了,可以通過NSDictionary根據相應的字段就可以解析到一些基本信息,比如包名之類的;
而對於icon的解析,首先我們先來看一下plist的結構:
我們可以層層遞進來解析圖標,先解析CFBundleIcons,然后CFBundlePrimaryIcon,再CFBundleIconFiles。
3.1 ipa工具類
完整代碼如下:

/** * 解析ipa的工具類 * 包名/版本名/版本號/應用名稱/應用展示名稱/所需IOS最低版本 */ public class IpaUtil { /** * * @param ipaURL 安裝包的絕對路徑 * @param path 指定圖標的存放位置 * @return */ public static Map<String, Object> readIPA(String ipaURL,String path) { Map<String, Object> map = new HashMap<String, Object>(); try { File file = new File(ipaURL); InputStream is = new FileInputStream(file); InputStream is2 = new FileInputStream(file); ZipInputStream zipIns = new ZipInputStream(is); ZipInputStream zipIns2 = new ZipInputStream(is2); ZipEntry ze; ZipEntry ze2; InputStream infoIs = null; NSDictionary rootDict = null; String icon = null; while ((ze = zipIns.getNextEntry()) != null) { if (!ze.isDirectory()) { String name = ze.getName(); if (null != name && name.toLowerCase().contains("info.plist")) { ByteArrayOutputStream _copy = new ByteArrayOutputStream(); int chunk = 0; byte[] data = new byte[1024]; while (-1 != (chunk = zipIns.read(data))) { _copy.write(data, 0, chunk); } infoIs = new ByteArrayInputStream(_copy.toByteArray()); rootDict = (NSDictionary) PropertyListParser.parse(infoIs); NSDictionary iconDict = (NSDictionary) rootDict.get("CFBundleIcons"); //獲取圖標名稱 while (null != iconDict) { if (iconDict.containsKey("CFBundlePrimaryIcon")) { NSDictionary CFBundlePrimaryIcon = (NSDictionary) iconDict.get("CFBundlePrimaryIcon"); if (CFBundlePrimaryIcon.containsKey("CFBundleIconFiles")) { NSArray CFBundleIconFiles = (NSArray) CFBundlePrimaryIcon.get("CFBundleIconFiles"); icon = CFBundleIconFiles.getArray()[0].toString(); if (icon.contains(".png")) { icon = icon.replace(".png", ""); } System.out.println("獲取icon名稱:" + icon); break; } } } break; } } } //根據圖標名稱下載圖標文件到指定位置 while ((ze2 = zipIns2.getNextEntry()) != null) { if (!ze2.isDirectory()) { String name = ze2.getName(); if (icon!=null){ if (name.contains(icon.trim())) { //圖片下載到指定的地方 FileOutputStream fos = new FileOutputStream(new File(path)); int chunk = 0; byte[] data = new byte[1024]; while (-1 != (chunk = zipIns2.read(data))) { fos.write(data, 0, chunk); } fos.close(); System.out.println("=================下載圖片成功"); break; } } } } //如果想要查看有哪些key ,可以把下面注釋放開 // for (String string : dictionary.allKeys()) { // System.out.println(string + ":" + dictionary.get(string).toString()); // } // 應用包名 NSString parameters = (NSString) rootDict.get("CFBundleIdentifier"); map.put("package", parameters.toString()); // 應用版本名 parameters = (NSString) rootDict.objectForKey("CFBundleShortVersionString"); map.put("versionName", parameters.toString()); //應用版本號 parameters = (NSString) rootDict.get("CFBundleVersion"); map.put("versionCode", parameters.toString()); //應用名稱 parameters = (NSString) rootDict.objectForKey("CFBundleName"); map.put("name", parameters.toString()); //應用展示的名稱 parameters = (NSString) rootDict.objectForKey("CFBundleDisplayName"); map.put("displayName", parameters.toString()); //應用所需IOS最低版本 //parameters = (NSString) rootDict.objectForKey("MinimumOSVersion"); //map.put("minIOSVersion", parameters.toString()); infoIs.close(); is.close(); zipIns.close(); } catch (Exception e) { e.printStackTrace(); map.put("code", "fail"); map.put("error", "讀取ipa文件失敗"); } return map; } public static void main(String[] args) { String ipaUrl = "D:\\Java\\idea_app\\ReadApkAndIpa1\\src\\main\\java\\com.baosight.securityCloudHD.ipa"; String imgPath = "D:\\image\\ipaIcon" + System.currentTimeMillis() + ".png"; Map<String, Object> mapIpa = IpaUtil.readIPA(ipaUrl,imgPath); for (String key : mapIpa.keySet()) { System.out.println(key + ":" + mapIpa.get(key)); } } }
經測試,可以得到如下信息:
但是這時候,因為我使用的是windows環境,下載的圖標就出現問題了(linux環境也有此問題,在mac環境下無此問題),可能是IOS對ipa進行壓縮的原因,圖標是黑色的。
如下所示:
網上找了很久,發現用ipin.py(python腳本)對圖片進行反序列化就可以恢復正常圖片,而且這個辦法也很簡單。
3.2 安裝python環境
首先下載python http://www.runoob.com/python/python-install.html
建議下載python2.x系列的,版本太高的話執行ipin.py可能會出問題,我下載的是python2.6。
下載好之后,解壓目錄,然后在環境變量上配置python的根目錄。
3.3 ipin.py展示
這個是我直接在網上下載的,並且做出了一點修改:

#--- # iPIN - iPhone PNG Images Normalizer v1.0 # Copyright (C) 2007 # # Author: # Axel E. Brzostowski # http://www.axelbrz.com.ar/ # axelbrz@gmail.com # # References: # http://iphone.fiveforty.net/wiki/index.php/PNG_Images # http://www.libpng.org/pub/png/spec/1.2/PNG-Contents.html # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # #--- from struct import * from zlib import * import stat import sys import os import shutil import glob def getNormalizedPNG(filename): pngheader = "\x89PNG\r\n\x1a\n" file = open(filename, "rb") oldPNG = file.read() file.close() if oldPNG[:8] != pngheader: return None newPNG = oldPNG[:8] chunkPos = len(newPNG) idatAcc = "" breakLoop = False # For each chunk in the PNG file while chunkPos < len(oldPNG): skip = False # Reading chunk chunkLength = oldPNG[chunkPos:chunkPos+4] chunkLength = unpack(">L", chunkLength)[0] chunkType = oldPNG[chunkPos+4 : chunkPos+8] chunkData = oldPNG[chunkPos+8:chunkPos+8+chunkLength] chunkCRC = oldPNG[chunkPos+chunkLength+8:chunkPos+chunkLength+12] chunkCRC = unpack(">L", chunkCRC)[0] chunkPos += chunkLength + 12 # Parsing the header chunk if chunkType == "IHDR": width = unpack(">L", chunkData[0:4])[0] height = unpack(">L", chunkData[4:8])[0] # Parsing the image chunk if chunkType == "IDAT": # Store the chunk data for later decompression idatAcc += chunkData skip = True # Removing CgBI chunk if chunkType == "CgBI": skip = True # Add all accumulated IDATA chunks if chunkType == "IEND": try: # Uncompressing the image chunk bufSize = width * height * 4 + height chunkData = decompress( idatAcc, -15, bufSize) except Exception, e: # The PNG image is normalized print e return None chunkType = "IDAT" # Swapping red & blue bytes for each pixel newdata = "" for y in xrange(height): i = len(newdata) newdata += chunkData[i] for x in xrange(width): i = len(newdata) newdata += chunkData[i+2] newdata += chunkData[i+1] newdata += chunkData[i+0] newdata += chunkData[i+3] # Compressing the image chunk chunkData = newdata chunkData = compress( chunkData ) chunkLength = len( chunkData ) chunkCRC = crc32(chunkType) chunkCRC = crc32(chunkData, chunkCRC) chunkCRC = (chunkCRC + 0x100000000) % 0x100000000 breakLoop = True if not skip: newPNG += pack(">L", chunkLength) newPNG += chunkType if chunkLength > 0: newPNG += chunkData newPNG += pack(">L", chunkCRC) if breakLoop: break return newPNG def updatePNG(filename): data = getNormalizedPNG(filename) if data != None: file = open(filename, "wb") file.write(data) file.close() return True return data def getFiles(base): global _dirs global _pngs if base == ".": _dirs = [] _pngs = [] if base in _dirs: return files = os.listdir(base) for file in files: filepath = os.path.join(base, file) try: st = os.lstat(filepath) except os.error: continue if stat.S_ISDIR(st.st_mode): if not filepath in _dirs: getFiles(filepath) _dirs.append( filepath ) elif file[-4:].lower() == ".png": if not filepath in _pngs: _pngs.append( filepath ) if base == ".": return _dirs, _pngs print "-----------------------------------" print " iPhone PNG Images Normalizer v1.0" print "-----------------------------------" print " " print "[+] Searching PNG files...", dirs, pngs = getFiles(".") print "ok" if len(pngs) == 0: print " " print "[!] Alert: There are no PNG files found. Move this python file to the folder that contains the PNG files to normalize." exit() print " " print " - %d PNG files were found at this folder (and subfolders)." % len(pngs) print " " while True: normalize = "y" if len(normalize) > 0 and (normalize[0] == "y" or normalize[0] == "n"): break normalized = 0 if normalize[0] == "y": for ipng in xrange(len(pngs)): perc = (float(ipng) / len(pngs)) * 100.0 print "%.2f%% %s" % (perc, pngs[ipng]) if updatePNG(pngs[ipng]): normalized += 1 print " " print "[+] %d PNG files were normalized." % normalized for filename in glob.glob(r'/F:/image/*.png'): shutil.move(filename,"/F:/image2") print filename
最后三行代碼是自己加上的,意思是把反序列的正常圖片全部移到另一個文件夾里,這樣做的好處是如果我們上傳解析的很多ipa文件,那么也會生成很多圖標,如果都放在反序列化的文件夾,而不移走的話,那么,程序每次都會去查找有哪些圖片需要反序列化,這些都是耗時的;還有一個好處就是及時將反序列好的圖片移到另一個文件夾,防止再次反序列化損壞圖片。
3.4 反序列化圖標
將ipin.fy文件放在image目錄下,並且沒有將ipa圖標進行反序列化是這樣的:
點shift+右鍵,然后點擊在此處打開窗口,在打開的命令窗口上輸入python ipin.py命令
然后就可以將上面黑色的圖標進行反序列化,得到下面這樣的:
但是,這邊得注意的是:
1.windows環境下如果得到的ipa圖標是黑色的,輸入一次命令后,會得到正常的,這時候不能再輸入上述的命令了,不然會損壞文件!!!
2.mac環境下不需要進行這些操作
ipa的解析到這里也可以告一段落了。
4.工具類的調用方法
4.1 apk工具類的調用
-
首先傳入一個apkPath(apk安裝包的絕對路徑:可以存放在項目根目錄,然后通過這個相對路徑獲取絕對路徑,再加上安裝包名即可),這邊分享一個處理上傳文件的工具類UploadUtil
public class UploadUtil { /** * 處理文件上傳 * * @param file * @param basePath * 存放文件的目錄的絕對路徑 servletContext.getRealPath("/upload") * @return */ public static String upload(MultipartFile file, String basePath) { String orgFileName = file.getOriginalFilename(); String fileName = System.currentTimeMillis() + "." + FilenameUtils.getExtension(orgFileName); try { File targetFile = new File(basePath, fileName); FileUtils.writeByteArrayToFile(targetFile, file.getBytes()); } catch (IOException e) { e.printStackTrace(); } return fileName; } }
-
然后通過這個apkPath調用ApkUtil的getApkInfo方法就可以得到apkInfo,這個apkInfo里面有包名,版本名,版本號,圖標等信息
- 最后調用ApkIconUtil的extractFileFromApk方法就可以將apk的圖標存放到指定的位置
-
偽代碼示例:
@Autowired private ServletContext context; public void do(MultipartFile file){ //根據項目根路徑得到這個根路徑的絕對路徑 String absolutePath = context.getRealPath(""); //項目根路徑的絕對路徑+安裝包名就是這個安裝包名的絕對路徑 String apkPath = absolutePath + "/" + UploadUtil.upload(file , absolutePath); String imaPath = System.currentTimeMillis() + ".png"; //得到apkInfo ApkInfo apkInfo = new ApkUtil.getApkInfo(apkPath); //將解析出來的圖標存放到項目根路徑下 ApkIconUtil.extractFileFromApk(apkPath, apkInfo.getApplicationIcon(), absolutePath + "/" + imaPath); //包名、版本名、版本號、應用名稱 String packageName = apkInfo.getPackageName(); String versionName = apkInfo.getVersionName(); String versionCode = apkInfo.getVersionCode(); String displayName = apkInfo.getApplicationLable(); }
4.2 ipa工具類的調用
- 調用IpaUtil的readIpa方法,這個方法有兩個參數,第一個參數是安裝包的絕對路徑,第二個參數是指定解析出來的圖標存放位置,得到的是一個map
- 然后就可以從這個map中取出包名、版本名、版本號、應用名稱
-
偽代碼示例
//接着上面偽代碼使用的do方法來寫,實際上可以根據apkPath包含".apk"或".ipa"來判斷是apk或者ipa public void do(MultipartFile file){ Map<String, Object> map = IpaUtil.readIPA(apkPath, absolutePath + "/" + imaPath); String packageName = (String) map.get("package"); String versionName = (String) map.get("versionName"); String versionCode = (String) map.get("versionCode"); String displayName = (String) map.get("displayName"); }