移动端安全 鸿蒙app分析初探(arkanalyzer)
北海 发表于 广东 移动安全 894浏览 · 2024-10-21 04:42

前言

1. 鸿蒙应用简介

开发语言

鸿蒙应用的开发语言包括:Java,TypeScript,ArkTs,C/C++。在鸿蒙开发的早期,Java是被广泛使用的语言之一。TS在鸿蒙开发中的应用是随着HarmonyOS 3.1开发者预览版的发布开始逐渐受到关注的。华为在2022 年11月4日的华为开发者大会2022(together)上发布了HarmonyOS3.1开发者预览版本SDK全面升级为 ArkTS 声明式应用开发。ArkTS是在 TypeScript(TS)的基础上,匹配ArkUI框架,扩展了声明式 UI语法和轻量化并发机制,以提供更高的性能和开发效率。目前在鸿蒙应用开发中,ArkTS 正逐渐成为最主流的开发语言之一。

简单了解TypeScript,ArkTs

基本环境

首先要安装nodesjs npm包管理工具
https://www.runoob.com/nodejs/nodejs-install-setup.html

TypeScript

TypeScript是一种由微软开发的开源编程语言,是基于JavaScript的扩展语言。它是JavaScript的超集,为JavaScript添加了静态类型检查和其他高级特性. TypeScript需要编译为JavaScript,然后交给浏览器或Javascript运行环境执行
TypeScript文件后缀是.ts,想要运行ts文件,首先要在全局环境中安装TypeScript的命令

npm install -g typescript

然后就可以通过tsc命令编译单个ts文件,等到js文件

tsc example.ts

最后运行js即可

ArkTS

TypeScript是在JavaScript基础上通过添加类型定义扩展而来的,而ArkTS则是TypeScript的进一步扩展。TypeScript深受开发者的喜爱,因为它提供了一种更结构化的JavaScript编码方法。ArkTS旨在保持TypeScript的大部分语法,为现有的TypeScript开发者实现无缝过渡,让移动开发者快速上手ArkTS。
ArkTS文件的后缀是.ets
ArkTS语法详情:
https://developer.huawei.com/consumer/cn/training/course/slightMooc/C101717496870909384

app架构

鸿蒙应用的Stage模型是 HarmonyOS 中的一种应用模型,目前在鸿蒙生态中是主推且不断演进的模型。华为官方对 Stage 模型高度重视,将其作为 HarmonyOS 3.1 及后续版本应用开发的主要模型,投入了大量的资源进行推广和优化。
下面我们将从开发态、编译态、发布态三个阶段了解一下鸿蒙Stage模型应用程序包结构

开发态

一个鸿蒙项目可以包含一个或多个 module。entry 模块是一种比较重要的模块类型,通常作为应用的入口模块。

我们可以使用deveco stduio创建或的打开一个项目(本文是打开一个已经编写过的demo)
(deveco studio使用教程参考官方教程:
https://developer.huawei.com/consumer/cn/training/course/slightMooc/C101717494752698457)

  • AppScope目录由DevEco Studio自动生成,不可更改
  • entry目录包含应用的主入口等关键代码和资源,存放着entry模块的文件
  • hvigor目录包含构建工具的特定配置信息,用于调整构建的行为和参数,它帮助管理构建过程,确保应用能够正确地编译、打包和部署

鸿蒙制定UIAbility组件,用于包含UI,主要用于和用户交互。UIAbility组件是系统调度的基本单元,为应用提供绘制界面的窗口。一个应用可以包含一个或多个UIAbility组件。例如,在支付应用中,可以将入口功能和收付款功能分别配置为独立的UIAbility。我们打开entry->src->main->module.json5

{
  "module": {
    // ...
    "abilities": [
      {
        "name": "EntryAbility", // UIAbility组件的名称
        "srcEntry": "./ets/entryability/EntryAbility.ets", // UIAbility组件的入口路径
        "description": "$string:EntryAbility_desc", // UIAbility组件的描述信息
        "icon": "$media:icon", // UIAbility组件的图标
        "label": "$string:EntryAbility_label", // UIAbility组件的标签
        "startWindowIcon": "$media:icon", // UIAbility组件启动页面图标资源文件的索引
        "startWindowBackground": "$color:start_window_background", // UIAbility组件启动页面背景颜色资源文件的索引
        // ...
      }
    ]
  }
}

我们可以看到./ets/entryability/EntryAbility.ets是UIAbility的入口路径(新项目默认)
EntryAbility.ets中定义了UIAbility在各个生命周期的运行逻辑(包括Create、Foreground、Background、Destroy四个状态如下图:)

我们打开EntryAbility.ets


在UIAbility的onWindowStageCreate()生命周期回调中,通过WindowStage对象的loadContent()方法设置启动页面,即框着windowStage.loadContent('pages/FirstPage', (err, data) (新项目是pages/Index)
这个启动页面就是一打开程序看到的页面。

src->main->ets->pages文件夹内则是各个页面的UI文件,里面包含布局,图形,动画,交互事件以及自定义能力。

一般的,鸿蒙项目中的各种常量都会放到src->main->ets->common->constants文件夹下

编译态

项目中不同类型的Module编译后会生成对应的HAP、HAR、HSP等文件,开发态视图与编译态视图的对照关系如下:

从开发态到编译态,Module中的文件会发生如下变更:
ets目录:ArkTS源码编译生成.abc文件。
resources目录:AppScope目录下的资源文件会合入到Module下面资源目录中,如果两个目录下存在重名文件,编译打包后只会保留AppScope目录下的资源文件。
module配置文件:AppScope目录下的app.json5文件字段会合入到Module下面的module.json5文件之中,编译后生成HAP或HSP最终的module.json文件。

发布态

发布态包结构通常主要包含以下几个部分:
一、应用安装包(.app)结构
META-INF目录:存放应用的签名信息和清单文件等。其中的 MANIFEST.MF文件包含了对应用包中所有文件的签名摘要,用于验证应用的完整性和真实性。
resources目录:存储应用的资源文件,如图片、布局文件、字符串资源等。这些资源可以根据不同的设备配置和语言进行适配,为应用提供良好的用户体验。
libs目录:包含应用所需的库文件,可能是针对特定硬件平台或功能的库。这些库文件可以提高应用的性能和功能扩展性。
entry.hap文件:这是应用的主模块文件,包含应用的入口代码和主要功能。当用户安装应用时,系统会首先加载这个文件,并启动应用的主界面。
二、应用模块包(.hap)结构
code目录:存放应用模块的代码文件,如ArkTS、JavaScript等编程语言实现的业务逻辑代码。这些代码文件定义了应用模块的功能和行为。
resources目录:与应用安装包中的resources 目录类似,存储应用模块的资源文件,用于模块的界面展示和交互。
libs目录:如果应用模块需要特定的库文件,可以放在这个目录下。这些库文件可以是第三方库或者针对特定功能的自定义库。

staged模型应用包结构详情参考官方文档:https://developer.huawei.com/consumer/cn/training/course/slightMooc/C101717497122909477

2. Arkanalyzer

工具地址:https://gitee.com/openharmony-sig/arkanalyzer

环境配置

node.js npm包管理工具安装
https://www.runoob.com/nodejs/nodejs-install-setup.html

安装好nodejs环境之后,以管理员身份打开命令行进入ArkAnalyzer项目文件夹的根目录
运行命令安装项目所需模块:

npm install

没出现error即为成功

使用IDE打开项目(本文使用pycharm)
打开根目录下的package.json配置文件,运行vitest测试框架,没有出现错误说明Arkanalyzer测试用例可以成功运行

项目简介

ArkAnalyzer是针对基于 ArkTS 语言开发的鸿蒙原生应用的静态代码分析框架。图1展示了其基本工作原理。目前,ArkAnalyzer的输入为 ArkTS 文件(即后缀为 ets 的文件)。ArkAnalyzer会先为ArkTS代码生成一个抽象语法树(AST),接着遍历这颗语法树并生成一个Scene数据结构。这个Scene 数据结构对代码结构进行了抽象,用户可通过该数据结构快速获取 ArkTS 项目中某个具体的类、函数或者属性。接下来,ArkAnalyzer为每一个函数生成一个控制流程图(CFG),用户可基于此图进行控制流相关的分析。基于CFG,ArkAnalyzer进一步实现方法调用图的生成(CG),并基于此支持用户实现数据流分析。

ArkAnalyzer-IR(三地址码)简介

临时变量命名规则
临时变量命名采用“$temp”+number 的形式,其中 number 为临时变量为所在的 CFG 中的零
食变量序号
代码简化(循环消减、去语法糖化)
源代码中的循环,包括 while,for,for of,for in 在 CFG 中全部改为代码块配合 if 语句的
形式。
语法糖指编程语言中提供的一种简化或更加直观的语法结构,这种语法结构能够使得代码
更加易读、易写,但并不增加新的语言功能。TS 中的语法糖包括匿名函数和对集合元素处理
时用到的 for-each 函数等。在下图的例子中,myArray 是一个数组,使用 forEach 方法遍历数
组,对于数组中的每个元素,都执行一个匿名函数。在下半可以看到方舟分析器将匿名函数显
示定义出来,命名为 AnonymousFunc$desuagring$0,for-each 使用时直接调用。

Scene数据结构

Scene类为ArkAnalyzer 的核心类,用户可以通过该类访问所分析代码(项目)的所有信息,包括文件列表、类列表、方法列表、属性列表等。Scene类具体数据结构如下图所示。

模块介绍

获取基本信息

ex01.1:获取所有文件

为获取项目中所有的文件,我们使用 Scene 类的 getFiles() 方法。以下是示例代码和输出

let files: ArkFile[] = projectScene.getFiles();
let filenames: string[] = files.map(file=>file.getName());
console.log(filenames);

ex01.2:获取命名空间

为获取项目中定义的所有命名空间,我们使用 Scene 类的 getNamespaces() 方法。以下是示例代码和输出

let namespaces: ArkNamespace[] = projectScene.getNamespaces();
let namespaceNames: string[] = namespaces.map(namespace => namespace.getName());
console.log(namespaceNames)

ex01.3:获取所有类

要获取项目中定义的所有类,我们使用 Scene 类的 getClasses() 方法。以下是示例代码和输出

let classes: ArkClass[] = projectScene.getClasses();
let classNames: string[] = classes.map(cls => cls.getName());
console.log(classNames);

ex01.4:获取所有属性

为获取特定类中定义的所有属性,我们首先需要获取该类的 ArkClass,然后调用其getFields()方法。以下是示例代码和输出

let classes: ArkClass[] = projectScene.getClasses();

let BackClass: ArkClass = classes[2];
let fields: ArkField[] = BackClass.getFields();
let fieldNames: string[] = fields.map(fld => fld.getName());
console.log(fieldNames);

ex01.5:获取所有方法

为获取特定类中定义的所有方法,我们首先需要获取该类的 ArkClass,然后调用其getMethods() 方法。以下是示例代码和输出

let classes: ArkClass[] = projectScene.getClasses();

let BackClass: ArkClass = classes[2];
let methods: ArkMethod[] = BackClass.getMethods();
let methodNames: string[] = methods.map(mthd => mthd.getName());
console.log(methodNames);

同样的,也可以从 Scene 中获取其内部的全部方法

let methods1: ArkMethod[] = projectScene.getMethods();
let methodNames1: string[] = methods1.map(mthd => mthd.getName());
console.log(methodNames1);

ex01.6:获取方法 CFG

通过 ArkMethod 的 getBody()方法获取方法体,再通过 getCfg()方法可以获取方法的 CFG,示例如下所示

let methods: ArkMethod[] = projectScene.getMethods();
let methodCfg: Cfg = methods[0].getBody().getCfg();

下面我们简要介绍 ArkAnalyzer 中 CFG 的数据结构设计。
属性:

  • blocks:一个 Set<basicblock> 集合,存储了图中所有的基本块。</basicblock>
  • stmtToBlock:一个 Map<Stmt, BasicBlock> 映射,关联每个语句(Stmt)到它所属的基本块。
  • startingStmt:表示图中的起始语句。这个起始语句用于标识控制流图的入口点。
  • defUseChains:存储定义-使用链的数组。
  • declaringMethod:存储声明了这个 CFG 的方法。
  • declaringClass:存储声明了这个 CFG 的类。

方法:

  • getStmts:遍历所有基本块,收集并返回图中所有的语句。
  • getBlocks、getStartingBlock、getStartingStmt 提供了获取基本块集合、起始块和起始语句的方法。
  • getDefUseChains:返回定义-使用链的集合。

控制流分析

ex02:打印调用图

let callGraph = new CallGraph(projectScene)
let callGraphBuilder = new CallGraphBuilder(callGraph, projectScene)
callGraphBuilder.buildClassHierarchyCallGraph(entryPoints)
callGraph.dump("out/cg/cg.dot")

数据流分析

ex03.1:SSA 观察

获取scene后,可对scene下的所有方法进行SSA处理,处理前需要使用inferType()函数进行类型推导,SSA 处理代码示例如

let staticSingleAssignmentFormer = new
StaticSingleAssignmentFormer();
...
const arkMethod: ArkMethod;
const body = arkMethod.getBody();
staticSingleAssignmentFormer.transformBody(body);

ex03.2: Def-use chain

定义使用链(Def-use chain)是一种分析技术,用于追踪数据在程序中的流动路径。它从数据的定义点开始,跟踪其在程序中的传播和使用情况,以确定可能存在的安全漏洞。
获取指定的ArkMethod后,可通过如下方式获取Def-Use Chain。每个chain有三个属性,分别是value(变量),def(定义语句)use(使用语句)。需要注意的是,Def-Use Chain只适

const body = arkMethod.getBody()
if (body) {
// 这里可以安全地使用 body,因为它是 ArkBody 类型的对象
    const cfg = body.getCfg()
    cfg.buildDefUseChain()
    for (const chain of cfg.getDefUseChains()){
        console.log("variable: "+chain.value.toString()+", def: "+chain.def.toString()+", use: "+chain.use.toString());
    }
} else {
    // 处理没有可用的 ArkBody 对象的情况
    console.log("=")
}

函数间数据流分析

ex04: 空指针检测

空指针分析可以检查代码是否存在程序试图使用空指针(即指向不存在对象的指针)时,导致程序崩溃或出现未定义的行为。例如程序用到了a.b,如果a为undefined或者null会导致程序崩溃。下面的代码演示了如何对程序进行空指针分析,入口函数为第一个文件的“U2”,检测从这个函数开始到结束是否有空指针的属性调用。

const defaultMethod = projectScene.getFiles()[0].getDefaultClass().getDefaultArkMethod();
let method = ModelUtils.getMethodWithName("U2",defaultMethod!);
method = defaultMethod;
if(method){
    // @ts-ignore
    const problem = new UndefinedVariableChecker([...method.getCfg().getBlocks()][0].getStmts()[method.getParameters().length],method);
    const solver = new UndefinedVariableSolver(problem, projectScene);
    solver.solve();
}

3. 案例运行

下面举几个简单的示例,更多的由各位大佬继续深入探索。
app demo地址:https://gitee.com/harmonyos_codelabs/NewsData

获取所有文件


修改./tests/AppTestConfig.json里的项目路径,改为要测试的app demo路径
例如:


执行命令 node -r ts-node/register AppTest.ts路径

获取所有类

说明:ArkAnalyzer 会为每一个文件和命名空间分别创建一个默认类,记为_DEFAULT_ARK_CLASS

获取所有属性

获取所有方法

打印控制流程图


这段代码是生成的tests\resources\callgraph\cha_rta_test下的main.ts的调用图的.dot文件

在GraphvizOnline上转化成图

补充:
arkanalyzer是一个还在发展的项目,本文只是初步介绍,欢迎大佬们不断探索和改进。

参考资料

https://www.runoob.com/nodejs/nodejs-install-setup.html
https://developer.huawei.com/consumer/cn/teaching-video/
https://gitee.com/organizations/harmonyos_codelabs/projects
https://gitee.com/openharmony-sig/arkanalyzer/blob/1dc42044c72db8c3d8eca66b19a6f9a1333586e2/docs/quickstart.pdf

0 条评论
某人
表情
可输入 255