鸿蒙应用开发 ArkTS编译工具链(ArkGuard源码混淆工具)(二)

保留选项支持的通配符

名称类通配符

名称类通配符使用方式如下:

通配符含义示例
?匹配任意单个字符"AB?“能匹配"ABC"等,但不能匹配"AB”
*匹配任意数量的任意字符AB"能匹配"AB”、“aABb”、“cAB”、"ABc"等


使用示例:

保留所有以a开头的属性名称:

-keep-property-name
a*

保留所有单个字符的属性名称:

-keep-property-name
?

保留所有属性名称:

-keep-property-name
*

路径类通配符

路径类通配符使用方式如下:

通配符含义示例
?匹配任意单个字符,除了路径分隔符/“…/a?“能匹配”…/ab"等,但不能匹配”…/a/"
*匹配任意数量的任意字符,除了路径分隔符/“…/a*/c"能匹配”…/ab/c",但不能匹配"…/ab/d/s/c"
**匹配任意数量的任意字符“…/a**/c"能匹配”…/ab/c",也能匹配"…/ab/d/s/c"
!表示非,只能写在某个路径最前端,用来排除用户配置的白名单中已有的某种情况“!../a/b/c.ets"表示除”…/a/b/c.ets"以外


使用示例:

表示路径…/a/b/中所有文件夹(不包含子文件夹)中的c.ets文件不会被混淆:

-keep
../a/b/*/c.ets

表示路径…/a/b/中所有文件夹(包含子文件夹)中的c.ets文件不会被混淆:

-keep
../a/b/**/c.ets

表示路径…/a/b/中,除了c.ets文件以外的其它文件都不会被混淆。其中,!不可单独使用,只能用来排除白名单中已有的情况:

-keep
../a/b/
!../a/b/c.ets

表示路径…/a/中的所有文件(不包含子文件夹)不会被混淆:

-keep
../a/*

表示路径…/a/下的所有文件夹(包含子文件夹)中的所有文件不会被混淆:

-keep
../a/**

表示模块内的所有文件不会被混淆:

-keep
./**

注意:

(1)以上选项,不支持配置通配符*、?、!作其它含义使用。

例如:

class A {
  '*'= 1
}

-keep-property-name
*

此时表示匹配任意数量的任意字符,配置效果为所有属性名称都不混淆,而不是只有属性不被混淆。

(2)-keep选项中只允许使用/路径格式,不支持\或\。

注释
可以使用#在混淆规则文件中进行注释。每行以#开头的文本会被当做是注释,例如下面的例子:

# white list for MainAbility.ets
-keep-global-name
MyComponent
GlobalFunction

-keep-property-name # white list for dynamic property names
firstName
lastName
age

构建HAR时,注释不会被合并到最后的obfuscation.txt文件中。

混淆规则合并策略

一个工程中经常会有许多混淆规则文件,这些文件来自于:

  • 主工程的ruleOptions.files (这里主工程指的是正在构建的工程)
  • 本地依赖的library中的consumerFiles选项中指定的文件
  • 远程依赖的HAR包中的obfuscation.txt文件

当构建主工程的时候,这些文件中的混淆规则会按照下面的合并策略(伪代码)进行合并:

let `listRules` 表示上面提到的所有混淆规则文件的列表
let finalRule = {
    disableObfuscation: false,
    enablePropertyObfuscation: false,
    enableToplevelObfuscation: false,
    compact: false,
    removeLog: false,
    keepPropertyName: [],
    keepGlobalName: [],
    keepDts: [],
    printNamecache: string,
    applyNamecache: string
}
for each file in `listRules`:
    for each option in file:
        switch(option) {
            case -disable-obfuscation:
                finalRule.disableObfuscation = true;
                continue;
            case -enable-property-obfuscation:
                finalRule.enablePropertyObfuscation = true;
                continue;
            case -enable-toplevel-obfuscation:
                finalRule.enableToplevelObfuscation = true;
                continue;
            case -compact:
                finalRule.compact = true;
                continue;
            case -remove-log:
                finalRule.removeLog = true;
                continue;
            case -print-namecache:
                finalRule.printNamecache = #{指定的路径名};
                continue;
            case -apply-namecache:
                finalRule.applyNamecache = #{指定的路径名};
                continue;
            case -keep-property-name:
                finalRule.keepPropertyName.push(#{指定的名称});
                continue;
            case -keep-global-name:
                finalRule.keepGlobalName.push(#{指定的名称});
                continue;
            case -keep-dts:
                finalRule.keepDts.push(#{指定的路径});
                continue;
        }
    end-for
end-for

最后使用的混淆规则来自于对象finalRule。

如果构建的是HAR,那么最终的obfuscation.txt文件内容来自于自身和本地依赖的library的consumerFiles选项,以及依赖的HAR的obfuscation.txt文件的合并。

当consumerFiles指定的混淆配置文件中包含以下混淆规则时,这些混淆规则会被合并到HAR包的obfuscation.txt文件中,而其他混淆规则不会。

// 混淆选项
-enable-property-obfuscation
-enable-string-property-obfuscation
-enable-toplevel-obfuscation
-compact
-remove-log

// 保留选项
-keep-property-name
-keep-global-name

library中混淆注意事项

  1. 如果consumerFiles指定的混淆配置文件中包含上述混淆选项,当其他模块依赖该HAR包时,这些混淆选项会与主模块的混淆规则合并,从而影响主模块。因此不建议开发者在consumer-rules.txt文件中配置混淆选项,建议仅配置保留选项。
  2. 如果在consumerFiles指定的混淆配置文件中添加-keep-dts选项,会被转换成-keep-global-name和-keep-property-name。

报错栈还原

经过混淆的应用程序中代码名称会发生更改,crash时打印的报错栈更难以理解,因为报错栈与源码不完全一致。开发人员可使用DevEco Studio命令工具Command Line Tools中的hstack插件来还原源码堆栈,进而分析问题。反混淆工具需要使用应用编译过程中生成的sourceMaps.map文件以及混淆名称映射文件nameCache.json文件,因此请本地备份它们。

说明

  • 目前不支持在hvigor构建流程中插入自定义混淆插件
  • 混淆的HAR包被模块依赖,若模块开启混淆,则HAR包会被二次混淆
  • DevEco Studio右上角Product选项,将其中Build Mode选择release,可开启release编译模式

FAQ

混淆各功能上线SDK版本

混淆选项功能描述最低版本号
-disable-obfuscation关闭混淆4.0.9.2
-enable-property-obfuscation属性混淆4.0.9.2
-enable-string-property-obfuscation字符串字面量属性名混淆4.0.9.2
-enable-toplevel-obfuscation顶层作用域名称混淆4.0.9.2
-enable-filename-obfuscationHAR包文件/文件夹名称混淆
HAP/HSP文件/文件夹名称混淆
4.1.5.3
5.0.0.19
-enable-export-obfuscation向外导入或导出的名称混淆4.1.5.3
-compact去除不必要的空格符和所有的换行符4.0.9.2
-remove-log删除特定场景中的console.*4.0.9.2
-print-namecache将名称缓存保存到指定的文件路径4.0.9.2
-apply-namecache复用指定的名称缓存文件4.0.9.2
-remove-comments删除文件中所有注释4.1.5.3
-keep-property-name保留属性名4.0.9.2
-keep-global-name保留顶层作用域的名称4.0.9.2
-keep-file-name保留HAR包的文件/文件夹的名称
保留HAP/HSP包的文件/文件夹的名称
4.1.5.3
5.0.0.19
-keep-dts保留指定路径的.d.ts文件中的名称4.0.9.2
-keep-comments保留编译生成的声明文件中class, function, namespace, enum, struct, interface, module, type及属性上方的JsDoc注释4.1.5.3
-keep保留指定路径中的所有名称5.0.0.18
通配符名称类和路径类的保留选项支持通配符5.0.0.24

看到这如果还有不知道从哪里开始入手了解鸿蒙开发技术、想要更深的掌握鸿蒙开发技术知识点的朋友们,或者是转行求职人员还在为面试问题而犯难的,可以动动手指进来参考一下针对‌鸿蒙开发学习‌而设计的系统性学习方案,涵盖基础入门到进阶实战项目相关学习文档:https://docs.qq.com/doc/DSk9ZeU9RTUhETm53

如何查看混淆效果

开发人员可以在编译产物build目录中找到混淆后的文件,以及混淆生成的名称映射表及系统API白名单文件。

  1. 混淆后的文件目录:build/default/[…]/release/模块名
  2. 混淆名称映射表及系统API白名单目录:build/default/[…]/release/obfuscation
  • 名称映射表文件:nameCache.json,该文件记录了源码名称混淆的映射关系。
  • 系统API白名单文件:systemApiCache.json,该文件记录了SDK中的接口与属性名称,与其重名的源码不会被混淆。

如何排查功能异常

1. 先在obfuscation-rules.txt配置-disable-obfuscation选项关闭混淆,确认问题是否由混淆引起。

2. 若确认是开启混淆后功能出现异常,请先阅读文档了解 -enable-property-obfuscation 、 -enable-toplevel-obfuscation 、 -enable-filename-obfuscation 、 -enable-export-obfuscation 等混淆规则的能力以及哪些语法场景需要配置白名单来保证应用功能正常。下文简要介绍默认开启的四项选项功能,细节还请阅读对应选项的完整描述。

  • -enable-toplevel-obfuscation 为顶层作用域名称混淆开关。
  • -enable-property-obfuscation 为属性混淆开关,配置白名单的主要场景为网络数据访问、json字段访问、动态属性访问、调用so库接口等不能混淆场景,需要使用 -keep-property-name 来保留指定的属性名称。
  • -enable-export-obfuscation 为导出名称混淆,一般与1、2选项配合使用;配置白名单的主要场景为模块对外接口不能混淆,需要使用 -keep-global-name 来指定保留导出/导入名称。
  • -enable-filename-obfuscation 为文件名混淆,配置白名单的主要场景为动态import或运行时直接加载的文件路径,需要使用 -keep-file-name 来保留这些文件路径及名称。

3. 参考FAQ中的 常见报错案例 ,若是相似场景可参考对应的解决方法快速解决。
4. 若常见案例中未找到相似案例,建议依据各项配置功能正向定位(若不需要相应功能,可删除对应配置项)。
5. 应用运行时崩溃分析方法:

  • 打开应用运行日志或者点击DevEco Studio中出现的Crash弹窗,找到运行时崩溃栈。
  • 应用运行时崩溃栈中的行号为 编译产物 的行号,方法名也可能为混淆后名称;因此排查时建议直接根据崩溃栈查看编译产物,进而分析哪些名称不能被混淆,然后将其配置进白名单中。

6. 应用在运行时未崩溃但出现功能异常的分析方法(比如白屏):

  • 打开应用运行日志:选择HiLog,检索与功能异常直接相关的日志,定位问题发生的上下文。
  • 定位异常代码段:通过分析日志,找到导致功能异常的具体代码块。
  • 增强日志输出:在疑似异常的功能代码中,对处理的数据字段增加日志记录。
  • 分析并确定关键字段:通过对新增日志输出的分析,识别是否由于混淆导致该字段的数据异常。
  • 配置白名单保护关键字段:将确认在混淆后对应用功能产生直接影响的关键字段添加到白名单中。

常见报错案例

开启-enable-property-obfuscation选项可能出现的问题

案例一:报错内容为 Cannot read property ‘xxx’ of undefined

// 混淆前
const jsonData = ('./1.json')
let jsonStr = JSON.parse(jsonData)
let jsonObj = jsonStr.jsonProperty

// 混淆后
const jsonData = ('./1.json')
let jsonStr = JSON.parse(jsonData)
let jsonObj = jsonStr.i

开启属性混淆后,“jsonProperty” 被混淆成随机字符 “i”,但json文件中为原始名称,从而导致值为undefined。

解决方案: 使用-keep-property-name选项将json文件里的字段配置到白名单。

案例二:使用了数据库相关的字段,开启属性混淆后,出现报错

报错内容为 table Account has no column named a23 in ‘INSET INTO Account(a23)’

代码里使用了数据库字段,混淆时该SQL语句中字段名称被混淆,但数据库中字段为原始名称,从而导致报错。

解决方案: 使用-keep-property-name选项将使用到的数据库字段配置到白名单。

开启-enable-export-obfuscation和-enable-toplevel-obfuscation选项可能出现的问题

当开启这两个选项时,主模块调用其他模块方法时涉及的方法名称混淆情况如下:

主模块依赖模块导入与导出的名称混淆情况
HAP/HSPHSPHSP和主模块是独立编译的,混淆后名称会不一致,因此都需要配置白名单
HAP/HSP本地HAR本地HAR与主模块一起编译,混淆后名称一致
HAP/HSP三方库三方库中导出的名称及其属性会被收集到白名单,因此导入和导出时都不会被混淆

HSP需要将给其他模块用的方法配置到白名单中。因为主模块里也需要配置相同的白名单,所以推荐将HSP配置了白名单的混淆文件(假设名称为hsp-white-list.txt)添加到依赖它的模块的混淆配置项里,即下图files字段里。

案例一:动态导入某个类,类定义的地方被混淆,导入类名时却没有混淆,导致报错

// 混淆前
export class Test1 {}

let mytest = (await import('./file')).Test1

// 混淆后
export class w1 {}

let mytest = (await import('./file')).Test1


导出的类 “Test1” 是一个顶层作用域名,当 “Test1” 被动态使用时,它是一个属性。因为没有开启-enable-property-obfuscation选项,所以名称混淆了,但属性没有混淆。

解决方案: 使用-keep-global-name选项将 “Test1” 配置到白名单。

案例二:在使用namespace中的方法时,该方法定义的地方被混淆了,但使用的地方却没有被混淆,导致报错

// 混淆前
export namespace ns1 {
  export class person1 {}
}

import {ns1} from './file1'
let person1 = new ns1.person1()

// 混淆后
export namespace a3 {
  export class b2 {}
}

import {a3} from './file1'
let person1 = new a3.person1()

namespace里的 “person1” 属于顶层作用域的class名称,通过 “ns1.person1” 来调用时,它是属于一个属性,由于未开启属性混淆,所以在使用它时没有被混淆。

解决方案:

  1. 开启-enable-property-obfuscation选项。
  2. 将namespace里导出的方法使用-keep-global-name选项添加到白名单。

案例三:使用了declare global,混淆后报语法错误

// 混淆前
declare global {
  var age : string
}

// 混淆后
declare a2 {
  var b2 : string
}

报错内容为 SyntaxError: Unexpected token

解决方案: 使用-keep-global-name选项将__global配置到白名单中。

未开启-enable-string-property-obfuscation混淆选项,字符串字面量属性名却被混淆,导致字符串字面量属性名的值为undefined

person["age"] = 22; // 混淆前

person["b"] = 22; // 混淆后


解决方案:

  1. 确认是否有依赖的HAR包开启了字符串属性名混淆,若开启了,则会影响主工程,需将其关闭。
  2. 若不能关闭-enable-string-property-obfuscation选项,将属性名配置到白名单中。
  3. 若依赖HAR包未开启字符串属性名混淆,同时SDK版本小于4.1.5.3,请更新SDK。

开启-enable-filename-obfuscation选项后,可能会出现的问题

案例一:报错为 Error Failed to get a resolved OhmUrl for ‘D:code/MyApplication/f12/library1/pages/d.ets’ imported by ‘undefined’

工程的目录结构如下图所示,模块library1的外层还有目录 “directory”,开启文件名混淆后,“directory” 被混淆为f12,导致路径找不到。

解决方案:

  1. 如果工程的目录结构和报错内容都相似,请将SDK更新至最低5.0.0.26版本。
  2. 使用-keep-file-name将模块外层的目录名 “directory” 配置到白名单中。

案例二:报错为 Cannot find module ‘ets/appability/AppAbility’ which is application Entry Point

由于系统会在应用运行时加载ability文件,用户需要手动配置相应的白名单,防止指定文件被混淆,导致运行失败。

解决方案: 使用-keep-file-name选项,将src/main/module.json5文件中,'srcEntry’字段所对应的路径配置到白名单中。

-keep-file-name
appability
AppAbility

使用-keep-global-name选项配置白名单时,可能会出现的问题

报错内容为 Cannot read properties of undefined (reading ‘has’)

解决方案: 将SDK更新至最低4.1.6.3版本。

HAP与HSP依赖相同的本地源码HAR模块,可能会出现的问题

1. 若开启文件名混淆,会出现以下问题:

  • 问题一:单例功能异常问题。原因是HAP与HSP独立执行构建与混淆流程,本地源码HAR模块在HAP与HSP的包中可能会出现相同的文件名被混淆成不同文件名的情况。
  • 问题二:接口调用失败问题。原因是HAP与HSP独立执行构建与混淆流程,本地源码HAR模块在HAP与HSP的包中可能会出现不同的文件名被混淆成相同的文件名的情况。
  • 若开启-enable-export-obfuscation和-enable-toplevel-obfuscation选项,在应用运行时会出现加载接口失败的问题。

2. 原因是HAP与HSP独立执行构建与混淆流程,本地源码HAR模块中暴露的接口在HAP与HSP中被混淆成不同的名称。

解决方案:

  1. 将HAP与HSP共同依赖的本地源码HAR改造为 字节码HAR] ,这样此HAR在被依赖时不会被二次混淆。
  2. 将HAP与HSP共同依赖的本地源码HAR以 release模式构建打包 ,这样此HAR在被依赖时,其文件名与对外接口不会被混淆。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值