harmony逆向分析实践
北海 历史精选 1284浏览 · 2025-03-17 07:33

开局一个京麟的babyharmony.hap。

看看能不能启动这个hap。打开鸿蒙系统模拟器,尝试拖拽安装,但是失败了QAQ





arkts层

于是只能尝试查看里面代码了 .hap文件是华为鸿蒙操作系统(HarmonyOS)特有的应用程序包格式。它类似于Android的.apk文件或iOS的.ipa 文件,用于在鸿蒙系统上安装和运行应用程序。.hap 文件包含了应用程序的代码、资源、第三方库和配置文件等

类似安卓,同样可以把.hap后缀改成.zip,解压即可查看到里面结构
进入ets文件夹,里面这个.abc文件即为方舟字节码 方舟字节码(Ark Bytecode)是华为鸿蒙操作系统(HarmonyOS)中的一种新型字节码格式,由方舟编译器(Ark Compiler)将ArkTS、TS或JS代码编译成的二进制产物,后缀为.abc 由于目前没有成熟的鸿蒙逆向工具,于是只能先尝试将方舟字节码以txt的形式打开,如果没有进行混淆加密处理的话,是可以看到ets源码的



同时可以借助abc_decompiler观察module的结构,abc-decompiler基于jadx和abcde实现的鸿蒙abc/方舟字节码的反编译工具。它将方舟字节码反编译成java代码,个人感觉在许多调用鸿蒙ArkUI或者内置库的地方难以观察(狗头保命,本人菜狗,大佬亲喷) 地址:https://github.com/ohos-decompiler/abc-decompiler

直接将module.abc拖入工具界面,可以看到这个项目的entryability目录下有一个entryability.ets,pages目录下有一个index.ets



entryAbility:在鸿蒙系统中,EntryAbility是应用的入口点,类似于Android中的Activity。它负责承载应用的核心功能和用户界面,并处理用户交互。每个EntryAbility实例对应最近任务列表中的一个任务,可以包含多个页面来实现不同功能模块。EntryAbility的生命周期包括创建(Create)、前台(Foreground)、后台(Background)和销毁(Destroy)等状态,系统会在不同状态之间转换时调用相应的生命周期回调函数

pages:页面是基本的UI元素,它们承载用户界面并对用户交互做出响应。页面可以包含文本、图像、表格、超链接等基本元素。

知道结构后,我们回到notepad中可以找到entryability.ets,可以发现entryability中配置了pages/Index作为入口页



接着在index.ets的部分中找到了Index,嘿嘿您猜怎么着,看到了flag,应该是上道儿了。这部分UI代码应该是显示一个flag的提交框
继续往下看,发现了检查flag的check函数, var c = testNapi.check(this.flag, value);





native层正向了解

找一下这个testNapi



这个testNapi就是鸿蒙native层的接口了。

为了更好理解鸿蒙native接口,我们可以尝试使用鸿蒙原生开发工具deveco studio创建一个native c++项目



鸿蒙在加载so时,首先会进入RegisterEntryModule函数,调用napi_module_register方法,将模块demoMoudle注册到系统中,并调用模块初始化函数。





napi_module有两个关键属性:一个是.nm_register_func,定义模块初始化函数;另一个是.nm_modname,定义模块的名称,也就是ArkTS侧引入的so库的名称,模块系统会根据此名称来区分不同的so。



在init函数中会实现ArkTS接口与C++接口的绑定和映射 napi_define_properties函数的主要功能是根据desc数组中提供的信息,在exports对象上定义相应的属性

C++接口的定义



arkts接口的定义在index.d.ts文件中

CMakeLists.txt文件用于配置CMake打包参数



ArkTS侧通过import引入Native侧包含处理逻辑的so来使用C/C++的方法



现在回到逆向代码,这里是导入了entry库,并将其赋值给testNapi变量,于是我们进入libs中寻找libentry.so







native层逆向分析

直接把libentry.so弄进ida开始分析check

根据注册流程,系统会进入RegisterEntryModule函数,于是看导出部分



进入RegisterEntryModule,napi_module_register中的&unk_8210就是模块指针


键入unk_8210,看到entry了


键入对应地址








发现了check函数


进入check,看嘛了。下面是结合网上大佬的文章,修改了一下参数名(太菜了qaq)




check函数分析



先来说一下两个napi的接口

napi_env env:代表了函数被调用时所处的环境 napi_ref ref:指向已经创建好的、指向特定JavaScript对象的引用,通过这个引用,函数就能知道要去获取哪个被引用的JavaScript对象对应的napi_value napi_value* result:这是一个输出参数,通过指针的形式传递。函数执行成功后,会将与传入的napi_ref对应的 napi_value(也就是被引用的那个JavaScript对象在N-API中的表示形式)存储到result所指向的内存位置中

napi_env env:代表了当前函数调用所处的运行环境 napi_value recv:在JavaScript中,函数内部可以通过this关键字来访问调用该函数时的上下文对象,在 N-API 里通过这个recv参数来传递相应的this值给要调用的JavaScript函数 napi_value func:指定要调用的那个JavaScript函数在N-API中的表示形式 size_t argc:用于表示后面argv参数所指向的数组中元素的个数,也就是要传递给被调用的JavaScript函数的参数个数 const napi_value* argv:通过这个参数,可以将多个JavaScript值(以napi_value形式表示)按照顺序传递给目标函数,就如同在JavaScript代码中直接调用函数并传入相应参数一样 napi_value* result:这是一个输出参数,通过指针形式传递。当被调用的JavaScript函数执行完毕后,如果有返回值,那么这个返回值会以napi_value的形式被存储到result所指向的内存位置中

这些代码就是在ArkTS源码区注册的回调函数,Native层的napi_call_function函数可以通过序号调用这些ArkTs层的代码

下面梳理一下check中的关键点

一开始会初始化一些值,包括targetidx,然后进入Label19


到Label19,获取bin中对应索引位置的序号,然后通过序号获取arkts对应的函数存到reg_method_0中,然后进入Label34



到了Label34,获取bin中对应索引位置的序号,然后通过序号获取arkts对应的函数存到reg_method_1,然后调用


接着会对keyvalue进行判断 如果keyvalue为2



如果keyvalue为1

如果keyvalue为0



如果method_0_ret不为0之后会进行加密,这个加密里面有9个case,大致都是使用switch_case_key对flag的每个字符进行右移,异或,换位等相关操作。只要弄清楚了bin_i和switch_case_key,可以尝试使用load-elf加载(https://github.com/IchildYu/load-elf)





这个switch_case_key是reg_method_1的返回值

使用python模拟一下加密部分之前的代码

bin文件在resources的rawfile中


执行后可以看到调用流程

switch_case_key是method_1_ret,我们要找出method_1_ret的值 在arkts中找对应序号的注册函数,看看第一个对应的

这个逻辑一眼顶针了一眼盯帧,method_1_ret要跟a相等, 修改一下之前的模拟代码,即可获取到bin_i和switch_case_key的元组了

结果如下:

接着就可以结合load-elf跑加密部分了 安装load-elf git clone https://github.com/IchildYu/load-elf.git



编译lib gcc ./x64_main.c -o lib -g -ldl -masm=intel -shared -fPIC



x64_main.c源码

运行exp.py

image.png








参考文章

https://bbs.kanxue.com/thread-282037-1.htm

https://ycznkvrmzo.feishu.cn/docx/ZqU0dU0h2oW3eFxZtZMctShFnyh

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