当我们上传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;
}
}
ApkInfo
/**
* 特性实体
*/
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 + "]";
}
}
ImpliedFeature
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;
}
}
ApkUtil
如上所示,我做了个测试,我使用的是神庙逃亡的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();
}
}
}
ApkIconUtil
经测试,此方案可行,所以如果不需要解析图标的话,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));
}
}
}
IpaUtil
经测试,可以得到如下信息:
但是这时候,因为我使用的是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
ipin.py
最后三行代码是自己加上的,意思是把反序列的正常图片全部移到另一个文件夹里,这样做的好处是如果我们上传解析的很多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; } }
UploadUtil
然后通过这个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(); }
View Code
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"); }
View Code