[经验分享] hap查看器的实现原理以及逆向解析应用名的初步方案 原创

westinyang 显示全部楼层 发表于 2023-10-28 10:25:57

前段时间,我开发了一个开源的跨平台的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);
}

解析应用名

视频讲解:https://www.bilibili.com/video/BV1jk4y1L7m3

  • 由于应用名实际的值并不在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

持续关注

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部