回复
#2023盲盒+码# hap查看器的实现原理以及逆向解析应用名的初步方案 原创
westinyang
发布于 2023-8-25 15:01
浏览
0收藏
【本文正在参加 2023「盲盒」+码有奖征文活动】 https://ost.51cto.com/posts/25284
@toc
关于作者:I’m westinyang
序言
前段时间,我开发了一个开源的跨平台的hap查看器,支持win、mac、linux,可以解析查看API9+ Stage模型的应用,后续还做了一个android版的(开源地址和软件截图见文章底部)。那今天我们来讲一下核心功能的具体实现原理,以及逆向解析应用名的初步方案是如何形成的。
解析应用基本信息
- 这点实现起来并不困难,因为hap解压后有一个名为
module.json
的明文的描述文件,这里包含了应用的基本信息,我们只需要通过程序读取并解析json读取需要的值即可,下面给出一些java代码片段
HapInfo hapInfo = new HapInfo();
ZipFile zipFile = null;
try {
hapInfo.hapFilePath = hapFilePath;
zipFile = new ZipFile(hapFilePath);
// 读取module.json
JSONObject module = getEntryToJsonObject(zipFile, "module.json");
// app.
hapInfo.packageName = module.getByPath("app.bundleName", String.class);
hapInfo.versionName = module.getByPath("app.versionName", String.class);
hapInfo.versionCode = module.getByPath("app.versionCode", String.class);
hapInfo.vendor = module.getByPath("app.vendor", String.class);
hapInfo.minAPIVersion = module.getByPath("app.minAPIVersion", String.class);
hapInfo.targetAPIVersion = module.getByPath("app.targetAPIVersion", String.class);
hapInfo.apiReleaseType = module.getByPath("app.apiReleaseType", String.class);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("HAP file parse failed");
} finally {
IoUtil.close(zipFile);
}
解析应用图标
我们需要先遍历module.abilities
下所有的Ability信息,优先去找与启动AbilityName
一致的Ability信息,如果找不到默认取第一个,然后再解析子属性中的icon
的相对路径值,前面拼上 resources/base/media/
,后面拼上 .png
就是完整的图标路径了,然后根据此路径读取并加载图片即可。
// module.
hapInfo.mainElement = module.getByPath("module.mainElement", String.class);
// 解析图标
JSONArray moduleAbilities = module.getByPath("module.abilities", JSONArray.class);
JSONObject targetAbility = null;
try {
targetAbility = (JSONObject) moduleAbilities.get(0);
} catch (Exception ignore) {}
for (Object item : moduleAbilities) {
JSONObject ability = (JSONObject) item;
if (hapInfo.mainElement.equals(ability.get("name", String.class))) {
targetAbility = ability;
break;
}
}
if (targetAbility != null) {
String iconName = targetAbility.get("icon", String.class).split(":")[1];
String iconPath = String.format("resources/base/media/%s.png", iconName);
hapInfo.iconPath = iconPath;
hapInfo.iconBytes = getEntryToBytes(zipFile, iconPath);
hapInfo.icon = getEntryToImage(zipFile, iconPath);
}
解析应用名
- 由于应用名实际的值并不在json描述中,而是被编译构建打包到了
resources.index
文件中。 - 每一个hap安装包内都有一个名为
resources.index
的文件,这个就是应用工程里面所有资源文件最终打包的产物,是用一个名为 restool 的开源工具进行构建的,同时restool
其实就在sdk安装目录下的toolchains子目录中。 - 关键是这个并不像分析安卓的apk,有成熟的
apktool
等逆向工具能快速准确地反编译看到明文的资源文件 - 思来想去,还是先看看有没有什么粗糙的方法先把它读出来吧
- 我们使用
010Editor
分别打开几个应用内的resources.index
文件,在十六进制视图
下做一些分析和对比,尝试寻找有是否有一定的规律 - 最终得出了一个不是特别完善的初步解决方案,请参考下面的一些分析对照和代码片段
// 特征分析(??????代表应用名称)
00 2C 00 00 00 09 00 00 00 03 00 00 01 0D 00 ?????? 00 13 00 // 设备信息
00 24 00 00 00 09 00 00 00 03 00 00 01 05 00 ?????? 00 13 00 // F-OH
00 28 00 00 00 09 00 00 00 03 00 00 01 10 00 ?????? 00 0C 00 // 搜狗输入法
// 关键特征
00 00 00 09 00 00 00 03 00 00 01
// 伪正则
00 ?? 00 00 00 09 00 00 00 03 00 00 01 ?? 00 ?????? 00 ?? 00
// 正则
(00.{2}0000000900000003000001.{2}00)(.*?)(00.{2}00)
// Java代码片段
byte[] resBytes = getEntryToBytes(zipFile, "resources.index");
String resHex = HexUtil.encodeHexStr(resBytes).toUpperCase();
String appNameHex = ReUtil.get("(00.{2}0000000900000003000001.{2}00)(.*?)(00.{2}00)", resHex, 2);
String appName = HexUtil.decodeHexStr(appNameHex);
- 使用这个正则表达式把分组中的第二部分取出来,再从十六进制内容转换为字符串,最终得到的就是我们想要的应用名
- 怎么说呢,这种方法算是另辟蹊径吧,弊端就是不可能适用于所有restool版本所编译构建的hap资源文件,即便是相同版本,也有可能会存在读取不够准确的情况
- 如果能根据开源的restool写出对应的资源反编译工具,才算是更好的解决方案~
开源地址和软件截图
电脑版(跨平台)
https://gitee.com/ohos-dev/hap-viewer
手机版(Android)
https://gitee.com/ohos-dev/hap-viewer-android
持续关注
- 关于作者:I’m westinyang
- 哔哩哔哩:个人主页
©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2024-1-18 11:50:09修改
赞
2
收藏
回复
相关推荐