ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【HarmonyOS NEXT】鸿蒙 代码混淆 [打印本页]

作者: 南飓风    时间: 2024-10-2 04:10
标题: 【HarmonyOS NEXT】鸿蒙 代码混淆
代码混淆简介

针对工程源码的混淆可以低沉工程被破解攻击的风险,收缩代码的类与成员的名称,减小应用的大小。
DevEco Studio提供代码混淆的能力并默认开启,API 10及以上版本的Stage模型、编译模式为release时自动举行代码混淆。
使用约束


混淆范围

在应用工程中,代码混淆支持以下格式文件混淆,混淆后的缓存文件保存在模块目录下的build/[...]/release目录下。

开启代码混淆

代码混淆已经被集成了到SDK中,可以在DevEco Studio中很方便地使用。
代码混淆现在只提供名称混淆的能力(由于其它混淆能力会劣化性能)。 开启代码混淆可以混淆以下名称:

代码混淆默认使能对参数名和局部变量名的混淆。顶层作用域名称和属性名称的混淆是默认关闭的,由于默认打开大概会导致运行时错误。这些混淆功能通过混淆选项来开启它们。
创建一个新工程的时候,设置文件build-profile.json5中会自动天生以下内容:
  1. "arkOptions": {
  2. "obfuscation": {
  3. "ruleOptions": {
  4. "enable": true,
  5. "files": ["./obfuscation-rules.txt"],
  6. }
  7. }
  8. }
复制代码
创建一个新的library的时候,还会额外天生consumerFiles属性:
  1. "arkOptions": {
  2. "obfuscation": {
  3. "ruleOptions": {
  4. "enable": true,
  5. "files": ["./obfuscation-rules.txt"],
  6. }
  7. "consumerFiles": ["./consumer-rules.txt"]
  8. }
  9. }
复制代码
混淆功能默认开启,若被关闭渴望重新开启混淆必要满意条件: 属性ruleOptions.enable的值为true。
属性ruleOptions.files中指定的混淆设置文件会在构建HAP、HSP或HAR的时候收效。
属性consumerFiles中指定的混淆设置文件会在构建依靠这个library的模块时收效。 这些混淆设置文件的内容还会被合并到HAR包中的obfuscation.txt文件。
当构建HAP、HSP和HAR的时候,最终的混淆规则是当前构建模块的ruleOptions.files属性,依靠library的consumerFiles属性,以及依靠HAR包中的obfuscation.txt文件的合并。
如果构建的是HAR,HAR包中的obfuscation.txt是自身的consumerFiles属性, 依靠library的consumerFiles属性,以及依靠HAR包中的obfuscation.txt文件的合并。构建HAP、HSP不会天生obfuscation.txt。
混淆规则设置文件

在创建工程或library的时候,DevEco Studio会自动天生obfuscation-rules.txt和consumer-rules.txt文件,
但是它们默认不会包罗任何混淆规则。混淆规则可以写到这些文件中,或者其它自定义文件,
然后将文件路径放到ruleOptions.files和consumerFiles中,如下面的例子所示。
  1. "buildOption": {
  2. "arkOptions": {
  3. "obfuscation": {
  4. "ruleOptions": {
  5. "enable": true,
  6. "files": ["./obfuscation-rules.txt", "./myrules.txt"], //myrules.txt放入配置文件build-profile.json5同级目录下
  7. }
  8. "consumerFiles": ["./consumer-rules.txt", "./my-consumer-rules.txt"]
  9. }
  10. }
  11. }
复制代码
设置混淆规则

混淆规则分为两种范例,一种是混淆选项,一种是保留选项;前者是提供顶层作用域名称、属性名称、文件名称等多种混淆功能设置开关,后者是提供各种混淆功能的白名单设置能力。
混淆选项

-disable-obfuscation
关闭全部混淆。如果使用这个选项,那么构建出来的HAP、HSP或HAR将不会被混淆。
-enable-property-obfuscation
开启属性混淆。 如果使用这个选项,那么全部的属性名都会被混淆,除了下面场景:

-enable-toplevel-obfuscation
开启顶层作用域名称混淆。如果使用这个选项,那么全部的顶层作用域的名称都会被混淆,除了下面场景:

-enable-filename-obfuscation
开启文件/文件夹名称混淆。如果使用这个选项,那么全部的文件/文件夹名称都会被混淆,除了下面场景:

注意
由于体系会在应用运行时加载某些指定的文件,针对这类文件,开辟者必要手动在[-keep-file-name]选项中设置相应的白名单,防止指定文件被混淆,导致运行失败。
上述必要手动设置白名单的情况,包罗但不限于以了局景:

-enable-export-obfuscation
开启直接导入或导出的类或对象的名称和属性名混淆。如果使用这个选项,那么模块中的直接导入或导出的名称都会被混淆,除了下面场景:

注意
-compact
去除不须要的空格符和全部的换行符。如果使用这个选项,那么全部代码会被压缩到一行。
注意
release模式构建的应用栈信息仅包罗代码行号,不包罗列号,因此compact功能开启后无法依据报错栈中的行号定位到源码具体位置。
-remove-log
删除以了局景中对 console.*语句的调用,要求console.*语句返回值未被调用。
-print-namecache filepath
将名称缓存保存到指定的文件路径。名称缓存包罗名称混淆前后的映射。
注意
每次全量构建工程时都会天生新的namecache.json文件,因此您每次发布新版本时都要注意保存一个该文件的副本。
-apply-namecache filepath
复用指定的名称缓存文件。名字将会被混淆成缓存映射对应的名字,如果没有对应,将会被混淆成新的随机段名字。
该选项应该在增量编译场景中被使用。
默认情况下,DevEco Studio会在临时的缓存目录中保存缓存文件,而且在增量编译场景中自动应用该缓存文件。
缓存目录:build/cache/{...}/release/obfuscation
-remove-comments
删除文件中的全部解释,包罗单行、多行,及JsDoc解释。以了局景除外:
声明文件中,在-keep-comments中设置的类、方法、struct、罗列等名称上方的JsDoc解释。
注意
编译天生的源码文件中的解释默认会被全部删除,不支持设置保留。
保留选项

-keep-property-name [,identifiers,...]
指定想保留的属性名,支持使用名称类通配符。例如下面的例子:
  1. -keep-property-name
  2. age
  3. firstName
  4. lastName
复制代码
注意
该选项在开启-enable-property-obfuscation时收效
哪些属性名应该被保留?
为了保障混淆的正确性,建议保留全部不通过点语法访问的属性。
例子:
  1. var obj = {x0: 0, x1: 0, x2: 0};
  2. for (var i = 0; i <= 2; i++) {
  3. console.log(obj['x' + i]); // x0, x1, x2 应该被保留
  4. }
  5. Object.defineProperty(obj, 'y', {}); // y 应该被保留
  6. console.log(obj.y);
  7. obj.s = 0;
  8. let key = 's';
  9. console.log(obj[key]); // s 应该被保留
  10. obj.u = 0;
  11. console.log(obj.u); // u 可以被正确地混淆
  12. obj.t = 0;
  13. console.log(obj['t']); // 在开启字符串字面量属性名混淆时t和't'会被正确地混淆,但是建议保留
  14. obj['v'] = 0;
  15. console.log(obj['v']); // 在开启字符串字面量属性名混淆时'v'会被正确地混淆,但是建议保留
复制代码
对于间接导出的场景,例如export MyClass和let a = MyClass; export {a};,如果不想混淆它们的属性名,那么必要使用保留选项来保留这些属性名。另外,对于直接导出的类或对象的属性的属性名,例如下面例子中的name和age, 如果不想混淆它们,那么也必要使用保留选项来保留这些属性名。
  1. export class MyClass {
  2. person = {name: "123", age: 100};
  3. }
复制代码
没有在so库的d.ts文件中声明的API(例如示例中的foo),如果要在ArkTS/TS/JS文件中使用需手动保留API名称。
  1. import testNapi from 'library.so'
  2. testNapi.foo()
复制代码
使用到的json文件中的字段,必要手动保留。
  1. const jsonData = ('./1.json')
  2. let jsonStr = JSON.parse(jsonData)
  3. let jsonObj = jsonStr.jsonProperty // jsonProperty 需要保留
复制代码
使用到的数据库相干的字段,必要手动保留。
  1. const dataToInsert = {
  2. value1: 'example1', // value1 需要保留
  3. };
复制代码
-keep-global-name [,identifiers,...]
指定要保留的顶层作用域的名称,支持使用名称类通配符。例如,
  1. -keep-global-name
  2. Person
  3. printPersonName
复制代码
哪些顶层作用域的名称应该被保留?
在Javascript中全局变量是globalThis的属性。如果在代码中使用globalThis去访问全局变量,那么该变量名应该被保留。
示例:
  1. var a = 0;
  2. console.log(globalThis.a); // a 应该被保留
  3. function foo(){}
  4. globalThis.foo(); // foo 应该被保留
  5. var c = 0;
  6. console.log(c); // c 可以被正确地混淆
  7. function bar(){}
  8. bar(); // bar 可以被正确地混淆
  9. class MyClass {}
  10. let d = new MyClass(); // MyClass 可以被正确地混淆
复制代码
-keep-file-name [,identifiers,...]
指定要保留的文件/文件夹的名称(不必要写文件后缀),支持使用名称类通配符。例如,
  1. -keep-file-name
  2. index
  3. entry
复制代码
哪些文件名应该被保留?
  1. const module1 = require('./file1') // ArkTS不支持CommonJS语法,这种路径引用应该被保留
  2. const moduleName = './file2'
  3. const module2 = import(moduleName) // 动态引用方式无法识别moduleName是否是路径,应该被保留
复制代码
-keep-comments [,identifiers,...]
保留声明文件中元素上方的JsDoc解释,支持使用名称类通配符。例如想保留声明文件中Human类上方的JsDoc解释,可举行以下设置:
  1. -keep-comments
  2. Human
复制代码
注意
  1. /*
  2. * @class exportClass
  3. */
  4. export class exportClass {}
复制代码
-keep-dts filepath
保留指定路径的.d.ts文件中的名称。这里的文件路径可以是一个目录,这种情况下目录中全部.d.ts文件中的名称都会被保留。
如果在构建HAR时使用了这个选项,那么文件中的名称会被合并到末了的obfuscation.txt文件中。
-keep path
保留指定路径中的全部名称(例如变量名、类名、属性名等)不被混淆。这个路径可以是文件与文件夹,若是文件夹,则文件夹下的文件及子文件夹中文件都不混淆。
路径仅支持相对路径,./与../为相对于混淆设置文件所在目录,支持使用路径类通配符。
  1. [/code] [list=1]
  2. [*]-keep
  3. [*]./src/main/ets/fileName.ts // fileName.ts中的名称不混淆
  4. [*]../folder // folder目录下文件及子文件夹中的名称都不混淆
  5. [*]../oh_modules/json5 // 引用的三方库json5里全部文件中的名称都不混淆
  6. [/list] 注:该功能不影响文件名混淆-enable-filename-obfuscation的功能
  7. [b]保留选项支持的通配符[/b]
  8. [b]名称类通配符[/b]
  9. 名称类通配符使用方式如下:
  10. [table][tr]通配符含义示例[/tr][tr][td]?[/td][td]匹配恣意单个字符[/td][td]"AB?"能匹配"ABC"等,但不能匹配"AB"[/td][/tr][tr][td]*[/td][td]匹配恣意数量的恣意字符[/td][td]"*AB*"能匹配"AB"、"aABb"、"cAB"、"ABc"等[/td][/tr][/table] [b]使用示例[/b]:
  11. 保留全部以a开头的属性名称:
  12. [code]
  13. -keep-property-name
  14. a*
复制代码
保留全部单个字符的属性名称:
  1. -keep-property-name
  2. ?
复制代码
保留全部属性名称:
  1. -keep-property-name
  2. *
复制代码
路径类通配符
路径类通配符使用方式如下:
通配符含义示例?匹配恣意单个字符,除了路径分隔符/"../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文件不会被混淆:
  1. -keep
  2. ../a/b/*/c.ets
复制代码
表现路径../a/b/中全部文件夹(包罗子文件夹)中的c.ets文件不会被混淆:
  1. -keep
  2. ../a/b/**/c.ets
复制代码
表现路径../a/b/中,除了c.ets文件以外的其它文件都不会被混淆。其中,!不可单独使用,只能用来清除白名单中已有的情况:
  1. -keep
  2. ../a/b/
  3. !../a/b/c.ets
复制代码
表现路径../a/中的全部文件(不包罗子文件夹)不会被混淆:
  1. -keep
  2. ../a/*
复制代码
表现路径../a/下的全部文件夹(包罗子文件夹)中的全部文件不会被混淆:
  1. -keep
  2. ../a/**
复制代码
表现模块内的全部文件不会被混淆:
  1. -keep
  2. ./**
复制代码
注意
(1)以上选项,不支持设置通配符*、?、!作其它含义使用。
例如:
  1. class A {'*'= 1}
  2. -keep-property-name
  3. *
复制代码
此时*表现匹配恣意数量的恣意字符,设置效果为全部属性名称都不混淆,而不是只有*属性不被混淆。
(2)-keep选项中只允许使用/路径格式,不支持\或\\。
解释

可以使用#在混淆规则文件中举行解释。每行以#开头的文本会被当做是解释,例如下面的例子:
  1. # white list for MainAbility.ets
  2. -keep-global-name
  3. MyComponent
  4. GlobalFunction
  5. -keep-property-name # white list for dynamic property names
  6. firstName
  7. lastName
  8. age
复制代码
构建HAR时,解释不会被合并到末了的obfuscation.txt文件中。
混淆规则合并策略

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

当构建主工程的时候,这些文件中的混淆规则会按照下面的合并策略(伪代码)举行合并:
  1. let `listRules` 表示上面提到的所有混淆规则文件的列表
  2. let finalRule = {
  3. disableObfuscation: false,
  4. enablePropertyObfuscation: false,
  5. enableToplevelObfuscation: false,
  6. compact: false,
  7. removeLog: false,
  8. keepPropertyName: [],
  9. keepGlobalName: [],
  10. keepDts: [],
  11. printNamecache: string,
  12. applyNamecache: string
  13. }
  14. for each file in `listRules`:
  15. for each option in file:
  16. switch(option) {
  17. case -disable-obfuscation:
  18. finalRule.disableObfuscation = true;
  19. continue;
  20. case -enable-property-obfuscation:
  21. finalRule.enablePropertyObfuscation = true;
  22. continue;
  23. case -enable-toplevel-obfuscation:
  24. finalRule.enableToplevelObfuscation = true;
  25. continue;
  26. case -compact:
  27. finalRule.compact = true;
  28. continue;
  29. case -remove-log:
  30. finalRule.removeLog = true;
  31. continue;
  32. case -print-namecache:
  33. finalRule.printNamecache = #{指定的路径名};
  34. continue;
  35. case -apply-namecache:
  36. finalRule.applyNamecache = #{指定的路径名};
  37. continue;
  38. case -keep-property-name:
  39. finalRule.keepPropertyName.push(#{指定的名称});
  40. continue;
  41. case -keep-global-name:
  42. finalRule.keepGlobalName.push(#{指定的名称});
  43. continue;
  44. case -keep-dts:
  45. finalRule.keepDts.push(#{指定的路径});
  46. continue;
  47. }
  48. end-for
  49. end-for
复制代码
末了使用的混淆规则来自于对象finalRule。
如果构建的是HAR,那么最终的obfuscate.txt文件内容来自于主工程和当地依靠的library的consumerFiles选项,
以及依靠的HAR的obfuscate.txt文件的合并。合并策略和上面一样,除了以下的不同:

报错栈还原

经过混淆的应用程序中代码名称会发生更改,crash时打印的报错栈更难以明白,由于报错栈与源码不完全一致。开辟职员可使用DevEco Studio下令工具Command Line Tools中的hstack插件来还原源码堆栈。
说明


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-obfuscation HAR包文件/文件夹名称混淆
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保留声明文件中元素上方的JsDoc解释4.1.5.3-keep保留指定路径中的全部名称5.0.0.18通配符名称类和路径类的保留选项支持通配符5.0.0.24 如何查看混淆效果

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

如何排查功能非常

常见报错案例

开启-enable-property-obfuscation选项大概出现的问题
案例一:报错内容为 Cannot read property 'xxx' of undefined
  1. [/code] [list=1]
  2. [*]// 混淆前
  3. [*]const jsonData = ('./1.json')
  4. [*]let jsonStr = JSON.parse(jsonData)
  5. [*]let jsonObj = jsonStr.jsonProperty
  6. [*]
  7. [*]// 混淆后
  8. [*]const jsonData = ('./1.json')
  9. [*]let jsonStr = JSON.parse(jsonData)
  10. [*]let jsonObj = jsonStr.i
  11. [/list] 开启属性混淆后,"jsonProperty" 被混淆成随机字符 "i",但json文件中为原始名称,从而导致值为undefined。
  12. [b]办理方案:[/b] 使用-keep-property-name选项将json文件里的字段设置到白名单。
  13. [b]案例二:使用了数据库相干的字段,开启属性混淆后,出现报错[/b]
  14. 报错内容为 table Account has no column named a23 in 'INSET INTO Account(a23)'
  15. 代码里使用了数据库字段,混淆时该SQL语句中字段名称被混淆,但数据库中字段为原始名称,从而导致报错。
  16. [b]办理方案:[/b] 使用-keep-property-name选项将使用到的数据库字段设置到白名单。
  17. [b]开启-enable-export-obfuscation和-enable-toplevel-obfuscation选项大概出现的问题[/b]
  18. [b]当开启这两个选项时,主模块调用其他模块方法时涉及的方法名称混淆情况如下:[/b]
  19. [table][tr]主模块依靠模块导入与导出的名称混淆情况[/tr][tr][td]HAP/HSP[/td][td]HSP[/td][td]HSP和主模块是独立编译的,混淆后名称会不一致,因此都必要设置白名单[/td][/tr][tr][td]HAP/HSP[/td][td]当地HAR[/td][td]当地HAR与主模块一起编译,混淆后名称一致[/td][/tr][tr][td]HAP/HSP[/td][td]三方库[/td][td]三方库中导出的名称及其属性会被收集到白名单,因此导入和导出时都不会被混淆[/td][/tr][/table] HSP必要将给其他模块用的方法设置到白名单中。由于主模块里也必要设置雷同的白名单,以是推荐将HSP设置了白名单的混淆文件(假设名称为hsp-white-list.txt)添加到依靠它的模块的混淆设置项里,即下图files字段里。
  20. [align=center][color=#4d4d4d][size=16px][align=center][img=413,285]https://i-blog.csdnimg.cn/blog_migrate/b97abfadedcd1cf7f5ac8619ea8a3a11.png[/img][/align][/size][/color][/align]
  21. [b]案例一:动态导入某个类,类定义的地方被混淆,导入类名时却没有混淆,导致报错[/b]
  22. [code]
  23. // 混淆前
  24. export class Test1 {}
  25. let mytest = (await import('./file')).Test1
  26. // 混淆后
  27. export class w1 {}
  28. let mytest = (await import('./file')).Test1
复制代码
导出的类 "Test1" 是一个顶层作用域名,当 "Test1" 被动态使用时,它是一个属性。由于没有开启-enable-property-obfuscation选项,以是名称混淆了,但属性没有混淆。
办理方案: 使用-keep-global-name选项将 "Test1" 设置到白名单。
案例二:在使用namespace中的方法时,该方法定义的地方被混淆了,但使用的地方却没有被混淆,导致报错
  1. // 混淆前
  2. export namespace ns1 {
  3. export class person1 {}
  4. }
  5. import {ns1} from './file1'
  6. let person1 = new ns1.person1()
  7. // 混淆后
  8. export namespace a3 {
  9. export class b2 {}
  10. }
  11. import {a3} from './file1'
  12. let person1 = new a3.person1()
复制代码
namespace里的 "person1" 属于顶层作用域的class名称,通过 "ns1.person1" 来调用时,它是属于一个属性,由于未开启属性混淆,以是在使用它时没有被混淆。
办理方案:
案例三:使用了declare global,混淆后报语法错误
  1. // 混淆前
  2. declare global {
  3. var age : string
  4. }
  5. // 混淆后
  6. declare a2 {
  7. var b2 : string
  8. }
复制代码
报错内容为 SyntaxError: Unexpected token
办理方案: 使用-keep-global-name选项将__global设置到白名单中。
未开启-enable-string-property-obfuscation混淆选项,字符串字面量属性名却被混淆,导致字符串字面量属性名的值为undefined
  1. person["age"] = 22; // 混淆前
  2. person["b"] = 22; // 混淆后
复制代码
办理方案:
开启-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,导致路径找不到。


办理方案:
案例二:报错为 Cannot find module 'ets/appability/AppAbility' which is application Entry Point
由于体系会在应用运行时加载ability文件,用户必要手动设置相应的白名单,防止指定文件被混淆,导致运行失败。
办理方案: 使用-keep-file-name选项,将src/main/module.json5文件中,'srcEntry'字段所对应的路径设置到白名单中。
  1. -keep-file-name
  2. appability
  3. AppAbility
复制代码
使用-keep-global-name选项设置白名单时,大概会出现的问题
报错内容为 Cannot read properties of undefined (reading 'has')
办理方案: 将SDK更新至最低4.1.6.3版本。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4