跳到主要内容

ArkGuard混淆常见问题

如何排查功能异常

排查功能异常步骤

  1. 在obfuscation-rules.txt中配置-disable-obfuscation选项关闭混淆,确认问题是否由混淆引起。
  2. 若确认开启混淆后功能出现异常,请先阅读文档,了解模块已配置的混淆规则的能力和需要配置白名单的语法场景,以确保应用功能正常。下文简要介绍默认开启的四项选项功能,详情请参阅对应选项的完整描述。
    1. -enable-toplevel-obfuscation为顶层作用域名称混淆开关。
    2. -enable-property-obfuscation为属性混淆开关。配置白名单的主要场景包括网络数据访问、json字段访问、动态属性访问、调用so库接口等。需要使用-keep-property-name来保留指定的属性名称。
    3. -enable-export-obfuscation为导入/导出名称混淆。一般与-enable-toplevel-obfuscation和-enable-property-obfuscation选项配合使用。配置白名单的主要场景为模块对外接口不能混淆。需要使用-keep-global-name来保留指定的导出/导入名称。
    4. -enable-filename-obfuscation为文件名混淆。配置白名单的主要场景为动态import或运行时直接加载的文件路径。需要使用-keep-file-name来保留这些文件路径及名称。
  3. 排查需要配置的白名单场景时,推荐使用混淆助手配置保留选项,可以快速识别需要配置的保留选项和白名单字段。也可以参考以下典型报错案例,若遇到相似场景,可参照对应解决方法快速处理。
  4. 若以下报错案例中未找到相似场景,建议依据各项配置功能正向定位(若不需要相应功能,可删除对应配置项)。
  5. 应用运行时崩溃分析方法:
    1. 打开应用运行日志,或点击DevEco Studio中出现的Crash弹窗,找到运行时崩溃栈。
    2. 应用运行时异常栈中的行号为编译产物的行号,方法名也可能为混淆后名称;因此排查时建议直接根据异常栈查看编译产物,进而分析哪些名称不能被混淆,然后将其配置到白名单中。
  6. 应用在运行时未崩溃但出现功能异常(如白屏)的分析方法:
    1. 打开应用运行日志:选择HiLog,检索与功能异常直接相关的日志,定位问题发生的上下文。
    2. 定位异常代码段:分析日志,找到引发功能异常的代码块。
    3. 增强日志输出:在疑似异常的功能代码中,增加日志打印以检查数据是否正常。
    4. 分析并确定关键字段:通过分析新增的日志输出,判断数据异常是否由混淆导致。
    5. 配置白名单以保护关键字段:将混淆后对应用功能有直接影响的关键字段添加到白名单中。

排查非预期的混淆能力

若出现预期外的混淆效果,检查是否由于依赖的本地模块或三方库开启了某些混淆选项。

示例:

假设当前模块未配置-compact,但混淆的中间产物中代码都被压缩成一行,可按照以下步骤排查混淆选项:

  1. 查看当前模块的oh-package.json5中的dependencies,此字段记录了当前模块的依赖信息。
  2. 在依赖的模块/三方库中的混淆配置文件内检索"-compact":
    • 在本地依赖的library中的consumer-rules.txt文件中检索"-compact"。
    • 在工程目录下的oh_modules文件夹中,对全部的obfuscation.txt文件检索"-compact"。

从API version 18开始,主模块默认不合并三方库的obfuscation.txt文件中的混淆选项,保留选项仍然有效。

三方库中的consumer-rules.txt不建议配置以下开关选项。这些选项在主模块开启混淆时会生效,可能导致意外的混淆效果,甚至应用运行时崩溃。如果发现三方库的obfuscation.txt文件中包含以下开关选项,建议联系发布该三方库的团队删除这些选项并重新打包发布。

-enable-property-obfuscation

-enable-string-property-obfuscation

-enable-toplevel-obfuscation

-remove-log

-compact

典型报错案例及解决方案

报错信息为:Error message: Cannot read property xxx of undefined

问题现象

混淆规则配置如下所示:

-enable-property-obfuscation

示例代码如下:

// 示例JSON文件结构(ImportJson.json):
/*
{
"jsonObj": {
"jsonProperty": "value"
}
}
*/

// 混淆前
import jsonData from './ImportJson.json';
// ...
let jsonProp = jsonData.jsonObj.jsonProperty;
// 混淆后
import jsonData from "./test.json";

let jsonProp = jsonData.i.j;

问题原因

开启属性混淆后,源码会被混淆,但JSON文件不会。源码中通过jsonData.i访问属性时,由于属性名称已经被混淆,JSON数据中并不存在对应的字段,导致获取的值为undefined。

解决方案

将JSON文件中的字段配置到属性白名单中。示例如下:

-keep-property-name
jsonObj
jsonProperty

报错信息为:Error message: is not callable

场景一:导出namespace中的方法时,该方法定义处被混淆,调用时未被混淆

问题现象

混淆规则配置如下所示:

-enable-toplevel-obfuscation
-enable-export-obfuscation

示例代码如下:

// 混淆前
// ExportNs.ts
export namespace NS {
export function foo() { }
}
// import.ts
import { NS } from './ExportNs';
// ...
NS.foo();
// 混淆后
// export.ts
export namespace i {
export function j() {}
}

// import.ts
import { i } from './export';

i.foo();

问题原因

namespace中的foo属于export元素,当通过NS.foo调用时被视为属性。由于未开启-enable-property-obfuscation选项,导致foo在使用时未被混淆。

解决方案

方案一:开启-enable-property-obfuscation选项。

方案二:使用-keep-global-name选项将namespace中导出的方法配置到白名单中。示例如下:

-keep-global-name
foo

场景二:动态导入某个类,类定义处被混淆,调用时未被混淆

问题现象

混淆规则配置如下所示:

-enable-toplevel-obfuscation
-enable-export-obfuscation

示例代码如下:

// 混淆前
// ExportUtils.ts
export function add(a: number, b: number): number {
return a + b;
}
// main.ts
async function loadAndUseAdd() {
let result: number = 0;
try {
const mathUtils = await import('./ExportUtils');
result = mathUtils.add(2, 3);
console.info(`result = ${result}`);
} catch (error) {
console.error('Failure reason:', error);
}
}

loadAndUseAdd();
// 混淆后
// utils.ts
export function c1(d1: number, e1: number): number {
return d1 + e1;
}

// main.ts
async function i() {
try {
const a1 = await import("@normalized:N&&&entry/src/main/ets/pages/utils&");
const b1 = a1.addNum(2, 3);
}
catch (z) {
console.error('Failure reason:', z);
}
}
i();

问题原因

函数addNum在定义时位于顶层作用域,但通过.addNum访问时被视为属性。由于未开启-enable-property-obfuscation选项,导致addNum被使用时未进行混淆。

解决方案

方案一:开启-enable-property-obfuscation选项。

方案二:使用-keep-global-name选项将add配置到白名单中。示例如下:

-keep-global-name
addNum

场景三:调用so库的方法后导致crash

问题现象

混淆规则配置如下所示:

-enable-property-obfuscation
-enable-export-obfuscation

示例代码如下:

// src/main/cpp/types/libentry/Index.d.ts
export const addNum: (a: number, b: number) => number;
// example.ets
// 混淆前
import testNapi from 'libentry.so';
// ...
let sun = testNapi.addNum(1, 2);
// example.ets
// 混淆后
import testNapi from "@normalized:Y&&&libentry.so&";

testNapi.m();

问题原因

混淆工具仅支持js/ts/ets代码的混淆。so库中的方法定义在C++侧,因此这些方法在定义处不会被混淆,但在调用处会被混淆。

解决方案

将so库导出的方法配置到属性白名单中。示例如下:

-keep-property-name
addNum

报错信息为:'module1/file1' does not provide an export name 'x', which is imported by 'module2/file2'

问题现象

主模块和HSP模块的混淆规则配置如下所示:

-enable-toplevel-obfuscation
-enable-export-obfuscation

示例代码如下:

export function addNum(a: number, b: number) {
return a + b;
}
// 混淆前。
// hsp模块。
export { addNum } from '../utils/Calc';
// entry模块
import { addNum } from 'sharedlibrary';

addNum(1, 2);
// 混淆后
// hsp模块
export function b() {}

// entry模块
import { n } from '@normalized:N&myhsp&&myhsp/Index&';

n();

问题原因

当同时开启-enable-toplevel-obfuscation和-enable-export-obfuscation选项时,主模块与被调用模块的混淆情况如下:

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

由于HAP和HSP模块是独立编译,因此混淆后导入和导出名称不一致,从而导致HAP引用HSP的方法时报错。

解决方案

将HSP模块导出的方法配置到-keep-global-name下,并且需要在HSP的consumer-rules.txt和obfuscation-rules.txt文件中都进行对应配置。示例如下:

// consumer-rules.txt
-keep-global-name
addNum

// obfuscation-rules.txt
-keep-global-name
addNum

应用运行后无crash信息,但功能异常的情况

使用Record<string, Object>作为对象的类型定义时,属性被混淆

问题现象

parameters的类型为Record<string, Object>。开启属性混淆后,parameters对象中的linkSource属性被混淆,导致功能异常。

示例代码如下:

// 混淆前
import { Want } from '@kit.AbilityKit';
// ...
let petalMapWant: Want = {
bundleName: 'com.example.myapplication',
uri: 'maps://',
parameters: {
linkSource: 'com.other.app'
}
}
// 混淆后
import type Want from "@ohos:app.ability.Want";

let petalMapWant: Want = {
bundleName: 'com.example.myapplication',
uri: 'maps://',
parameters: {
i: 'com.other.app'
}
};

问题原因

示例中parameters的类型为Record<string, Object>,这仅表示以字符串为键的对象的泛型定义,未详细描述其内部属性。因此,混淆工具无法识别对象内部哪些属性不应被混淆,导致linkSource被混淆后,引发功能异常。

解决方案

将混淆后会出现问题的属性名配置到属性白名单中,示例如下:

-keep-property-name
linkSource

跨文件调用某属性,该属性在一个文件中保留,在另一个文件中被混淆

问题现象

混淆规则配置如下所示:

-enable-property-obfuscation
-keep
./file1.ts

在file2.ts中导入file1.ts的接口。该接口包含一个对象类型的属性。此对象属性在file1.ts中被保留,但在file2.ts中被混淆,导致调用时出现功能异常。

示例代码如下:

// 混淆前
// FileInside.ts
export interface MyInfo {
age: number;
address: {
city1: string;
}
}
// FileOutside.ts
import { MyInfo } from './FileInside';
// ...
const person: MyInfo = {
age: 20,
address: {
city1: 'shanghai'
}
}
// 混淆后
// file1.ts
export interface MyInfo {
age: number;
address: {
city1: string;
}
}

// file2.ts
import { MyInfo } from './file1';

const person: MyInfo = {
age: 20,
address: {
i: "shanghai"
}
}

问题原因

使用-keep选项保留file1.ts文件时,该文件中的代码不会被混淆。导出属性(如address)所属类型内的属性不会自动加入白名单,因此在其他文件中使用时会被混淆。

解决方案

方案一:使用interface定义该属性的类型,并使用export进行导出,这样该属性将被自动加入到属性白名单中。示例如下:

// FileOutside.ts
export interface AddressType {
city1: string
}
export interface MyInfo2 {
age: number;
address: AddressType;
}

方案二:使用-keep-property-name选项,将未直接导出的类型内的属性配置到属性白名单中。示例如下:

-keep-property-name
city1

未开启-enable-string-property-obfuscation,字符串字面量属性名却被混淆

问题现象

// 混淆前
const person = {
myAge: 18
}
person["myAge"] = 20;
// 混淆后
const person = {
myAge: 18
}
person["m"] = 20;

问题原因

主模块所依赖的其他模块中的consumer-rules.txt文件配置了-enable-string-property-obfuscation选项,主模块会合并该选项,导致字符串字面量属性名被混淆。

解决方案

从API version 18开始,主模块默认不会被三方库的混淆规则所影响,因此不会有这种情况。但如果API version低于18,可参考以下两种解决方案。

方案一:确认依赖的远程HAR包的obfuscation.txt文件中是否配置了-enable-string-property-obfuscation选项。若配置了则会影响主模块,需将其关闭。参考排查非预期的混淆能力

方案二:若工程复杂无法找到配置了该混淆选项的远程HAR包,可以将属性名直接配置到白名单中。

数据库相关的字段被混淆后导致功能异常

问题现象

HiLog日志中报错信息为:table Account has no column named a23 in 'INSERT INTO Account(a23)'。

问题原因

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

解决方案

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