Android_Reverse_Engineering
Android_Reverse_Engineering
¶Learning Link
Android Pentest是关于安卓渗透测试的,但是有一部分还是可以借鉴的
¶Android Application Framework
安卓是基于Linux内核和其他开源项目的修改版本的移动操作系统
¶安卓操作系统
¶硬件
Android的主要硬件平台是ARM,在以后的版本中也支持X86架构和X86-64架构
¶内核
截至2020年,Android使用Linux内核的4.4,4.9或4.14版本。Android Kernel 基于 Linux Kernel 的长期支持 (LTS)分支
¶文件系统
之前的Android使用YAFFS2文件系统,在Android2.3之后使用EXT4文件系统,虽然很多的OEM(原始设备制造厂商)已经尝试了F2FS,但以下的目录在任何Android都存在
-
Boot:包含内核,虚拟硬盘等
-
System:包含操作系统文件,其中包括Android UI和预安装的应用程序
-
Recovery:引导到操作系统的替代选项,允许恢复和备份分区
-
Data:保存用户数据,其子文件夹包括
- Android:默认用于应用程序缓存和保存的数据
- Alarms:警报的自定义音频文件
- Cardboard:包含VR文件的数据
- DCIM:相机拍摄的照片的视频
- Downloads:存放在互联网上下载的文件
- Notifications:某些应用通知的自定义提示音
- Musics,Movies:存储第三方的音乐和视频
- Pictures:存储第三方的图片
- Podcasts:使用播客应用时存储播客文件
- Videos:存储从第三方下载的视频
-
Cache:存储常用数据和应用组件
-
Misc:包含其他重要的系统设置信息
¶安卓架构
¶Kernel
它为用户提供了与硬件通信的接口。它包含程序用来指示硬件组件执行特定功能的基本驱动程序。这些驱动程序是音频,显示器,蓝牙等
¶Hardware Abstraction Layer
硬件抽象层 (HAL) 是代码的逻辑划分,用作计算机物理硬件与其软件之间的抽象层。它提供了一个设备驱动程序接口,允许程序与硬件进行通信
¶Libraries
位于内核的顶部,库为开发人员提供开发应用程序,资源文件甚至清单的支持。有一些原生库,如SSL,SQLite,Libc等,是原生代码有效执行任务所必需的。
¶Android Runtime
ART是Android操作系统使用的应用程序运行时环境,Runtime Environment是程序可以向计算机处理器发送指令并访问计算机主存(RAM)的状态。JAVA编写的Android应用程序,在编译期间首先转换为字节码,打包为APK和运行运行时
Android使用虚拟机来执行应用程序,以便将程序的执行和操作系统隔离开来,并免受恶意代码的侵害
在Android4.4之前,程序的运行是由DVM(Dalvik Virtual Machine)执行的,后来被Android Runtime替代
¶Application Framework
安卓由四大组件:Activity、Service、Broadcast Receiver、Content Provider
Android操作系统的整个功能集可以通过Java编写的API提供给开发人员,这些API是Android应用所需要的最重要的组件
- View System:主要用于构建应用程序的UI。包括列表、文本框和按键
- Resource Manager:提供对布局文件、图形等非代码资源的访问
- Notification Manager:允许应用在状态栏显示自定义警报
- Activity Manager:管理应用的生命周期
- Content Providers:使应用程序能够访问其他应用程序
¶System Applications
预装的核心应用程序集,用于基本功能,如短信,日历,互联网浏览,联系人等
¶编译与反编译
ART的主要编译过程如下
APK文件只是一个包含XML文件,dex代码,资源文件和其他文件的ZIP压缩包,需要反编译的时候需要先解包然后再反编译,使用APKLab即可
¶APK构建流程和执行过程
- .java文件中的java源代码通过javac转换为字节码(.class文件)
- 所有的.class文件都通过dx编辑器转换为.dex文件(Dalvik可执行文件)。DEX字节码独立于设备架构,需要转换为本机机器代码才能在设备上运行
- AAPT将资源(res文件夹)编译为二进制文件(resources.arsc),并且将已经编译的资源、非编译的资源、.dex文件打包到apk文件中
- 对应用程序进行签名,然后才能发布
如果Android使用的是Dalvik JIT编译器,那么每次运行程序时,他都会动态地将Dalvik字节码(也就是.dex文件)地一部分转换为字节码然后执行,随着程序的执行,将编译和缓存更多的字节码
如果是Android使用的是ART,那么在应用程序的安装阶段,他就会静态地将DEX字节码转换为机器代码,并且存储在设备的内存中,这是一次性事件
¶Smali代码
Smali是Dalvik VM内部执行的核心代码,是Dalvik自己的语法规范
Smali代码就是dex文件反编译之后的代码,所以说Smali语言是Android虚拟机的反汇编语言
我们可以通过修改Smali代码来修改APK运行逻辑,再重新编译打包成新的APK
¶APK的大致内容
-
AndroidMainfest.xml:二进制XML格式的清单文件,存储应用程序的软件包名称,版本组件和其他元数据
-
META_INF:清单,用于存储有关应用程序的元数据,它还包含APK的证书和前面
-
classes.dex:以dex格式编译的应用程序代码,Dalvik VM(相当关于Java中的JVM)可以识别和执行
-
res/:包含未编译成resources.arsc中的资源的文件夹
-
lib/:包含本地已编译代码文件-即本机代码库
-
assets/:应用程序的资产
-
resources.arsc:提前编译好的资源文件
¶APK程序的活动和入口
¶活动
活动是用来承载用户界面的容器,是Android的四大组件之一,我们再APP里面看到的页面就需要一个Activity,而页面之间的跳转就是Activity之间的跳转。比如,登陆页面是一个LoginActivity,注册页面是一个 RegisterActivity,当我们需要从登陆页面跳转到注册页面时,也就是 LoginActivity 通过 Intent 跳转到 RegisterActivity
¶入口
我们新建一个Android项目时会默认生成一个Activity,叫做 MainActivity,MainActivity就是这个项目的唯一页面,也就是APP的启动页面。每一个Activity都需要在AndroidManifest.xml文件中配置。每创建一个Activity都需要在这个文件中国注册
在AndroidManifest.xml文件中android.intent.action.MAIN会将Mainactivity注册为最先启动多个Activity,同时我们也可以在其中配置Activity的其他属性
¶Oncreate函数
Oncreate函数通常配置需要的信息,一个Activity启动回调的第一个函数就是Oncreate,Oncreate函数做一些Activity启动的一些必要的初始化的工作。有点像Java中的构造函数
¶Smali
¶学习链接
下不了断点可能是版本不对,下载最新版即可
直接使用APKLab解包(如果没有debuggable属性则需要先在application标签中加入android:debuggable=“true”,然后重新打包),然后以调试方式启动最后附加上去即可(这里我直接开启调试是失败的)
adb shell am satrt -D -n 包名/.主活动
¶smali学习
Java编译器将.java源文件编译为.class字节码文件,然后JVM将字节码解释为机器代码在目标机器上执行。DVM指的是DalVIk VM,在Android中,java类被打包为DEX字节码文件(.dex),DEX字节码经过Dalvik或者ART转为机器码进行执行,而smali就是dex文件反编译之后的汇编代码
JVM是基于栈帧的,也就是Stack-based,而DVM基于寄存器,也就是Register-based
¶smali代码
¶smali和java的对比
注释
1 | 在java中使用// |
类声明
1 | //在java中 |
方法声明
1 | //在java中 |
这里表示Onclick的属性是public、参数为View(因为View是对象,需要使用全包名路径),返回值为void(V)
全包名路径时java中的.被修改为/,并且使用L开头,以;结尾
字段声明
1 | //java中 |
字段取值赋值
1 | //smali中 |
方法调用
1 | //smali中,以invoke开头 |
方法取值
1 | //获取返回值首先需要调用方法invoke,然后接收返回值move |
smali和java基础数据类型对比
smali | java |
---|---|
B | byte |
S | short |
I | int |
J | long |
F | float |
D | double |
C | char |
Z | boolean |
V | void |
[ | 数组 |
L+全类名路径,用/分割 | object |
¶smali源码结构分析
先自己编写一个简单的APK
源代码如下
¶声明
类方法的声明
.super表示继承的类,.source是java源文件
实现的类
¶构造方法
java中自动生成无参构造器
.method和.end method一起使用,construct
.line 14表示在源码中的行数为14行,可以删除
invoke-direct是方法的调用,凡是私有方法或者构造方法统统使用invoke-direct,这里的invoke-direct其实就是调用父类的初始化方法
invoke-direct表示将p0参数传入后面的方法中,p0这里就是this指针,其实存在于构造器的参数列表中,将this传入后面的方法进行Object的初始化操作
invoke-direct {参数},方法所对应的全包名路径类; -> 方法名称(方法参数签名)方法返回值签名
return-void表示返回值为void
当返回值为String时,返回Object
const-string v0,“hello” 声明一个常量字符串
在方法声明之后的.locals可以理解为调用该方法需要使用到的变量
¶main方法
main函数的参数是[Ljava/lang/String;表明其参数为String数组
.param表明参数对应的名称为args
.line两个之间的smali代码表示java源代码中的一行代码
对应java中
¶smali代码中的for循环
首先使用const初始化两个常量,然后进行比较if-ge代表如果p1>=v0,则跳转到con_0分支,否则add-int/lit8 p1,p1,0x1表示将p1+0x1的值然后赋值给p1,即p1=p1+1,然后使用goto语句回到判断处
¶Toast在smali中的代码
调用实例方法/一般方法一般使用invoke-virtual,invoke-static调用静态方法
先获取参数,然后存储到{}中,接着使用->(这里->相当于Toast.makeText(p1,v1,v0))将参数传入到makeText方法中,makeText(参数签名)返回值签名
接收方法的返回值传递到p1然后show
¶关于方法返回的关键字
smali | 数据类型 |
---|---|
return | byte |
return | short |
return | int |
return-wide | long |
return | float |
return-wide | double |
return | char |
return | boolean |
return-void | void |
return-object | 数组 |
return-object | String |
¶静态代码块的smali代码
¶smali各种方法的调用
关键字
1 | invoke-virtual#非私有(private)实例方法的调用 |
编写一个类
1 | public class Test{ |
¶smali中对象的创建
1 |
|
1 | class Test{} |
¶数据的定义
主要有字符串数据、字节码数据、数据类型数据
1 | //字符串.smali中不能直接返回,需要先存储在容器中 |
¶字段的取值与赋值
1 | .filed |
同样的不同的数据类型也对应不同的类型
1 | .class public LTest;#声明类 |
¶条件跳转if
¶寄存器
内部寄存器声明,在Dalvik中,每个寄存器都是32位的,2个寄存器用于存储long和double
1 | .registers 数量#声明于方法内部 |
寄存器的两种命名方法-p命名法和v命名法,主要是使用p命名法
1 | //如果是v命名法,优先对局部变量进行声明 |
¶smali语法关键字
1 | .line N#表示与java源文件代码的映射关系,可删除 |
¶smali代码注入
¶IDA Dump Android应用内存
- 首先先将应用程序附加到ida上
- 然后配置调试器在不同事件触发的地方设置断点
- 加载完目标so文件之后
- 首先获取应用程序的PID,adb shell ps,然后在adb shell内cat /proc/PID/map获取so文件起始地址,可以使用|grep "so"过滤
- 然后编写IDAPython dump即可
在IDA中的Modules中能看到Odex文件,接下来可以从内存中Dump下来Dex文件,这是对抗动态加载壳的常用思路
¶Root
¶Objection
Objection可以快速完成诸如内存搜索、类和 模块搜索、方法Hook以及打印参数、返回值、调用栈等常用功能
Objection依托Frida完成了对应用的注入以及对函 数的Hook模板,使用时只需要将具体的类填充进去即可完成相应的 Hook测试
安装好之后,通过命令objection -g 包名 explore注入进程后即可进入REPL界面
在REPL界面中,按空格键就会提示可用的命令,出现提示之后通过上下选择键及回车键便可输入命令
-
help命令:在当前命令前加help之后再回车即可查看当前命令的解释信息
-
jobs命令:用于查看和管理当前所执行的Hook的任务,可以同时运行多项Hook任务
-
frida命令:查看Frida相关信息
-
内存漫游相关命令,Objection可以快速便捷地打印出内存中各种类地相关信息
- android hooking list classes
- android hooking search classes 关键字
- **android hooking search methods
**来从内存中获取所有包含关键字key的方法 - 搜索到我们感兴趣的类后可以使用android hooking list class_methods来查看类的所有方法
- android hooking list activities列出进程中所有的活动
- android hooking list services列出进程所有的service,对于其余两个组件,只需要修改为receivers和providers即可
-
Hook命令:**通过android hooking watch class_method
**对指定类进行Hook 还可以使用**–dump-args–dump-backtrace–dump-return**来打印函数的参数、调用栈以及返回值,默认会Hook对应方法的所有重载方法
-
Hook结束之后可以使用jobs kill pid来删除作业,jobs list列出所有作业
-
主动调用:基于最简单的Java.choose的实现,**android heap search instances
**来搜索实例,HashCode作为实例句柄来调用和执行函数 然后使用android heap execute HashCode Method,注意这里只能是无参的实例方法
-
主动调用有参实例方法:输入android heap evaluate HashCode Method之后需要自己编写脚本,其中clazz是该类的实例
-
启动活动命令:android intent launch_activity 活动
当我们无法使用USB进行连接时,还可以使用Objection进行网络模式连接
¶MT管理器
¶AS动态调试APK
¶Java
¶反射
反射是框架的设计灵魂,反射就是将类的各个组成部分封装为其他对象
首先我们先来看java代码文件在计算机中的经历的阶段
Java源文件首先通过javac编译为class文件,class文件中存储成员变量、构造方法、普通方法。然后将硬盘中的class类通过类加载器(ClassLoader)加载到内存中,而Java中有class类对象来存储字节码文件中的信息,字节码文件中主要的内容有成员变量、构造方法、成员方法,因为上述内容可能存在多个,所以使用数组进行存储。最后在运行时阶段构造成员。这样就将类中的各个部分封装为其他对象
反射的好处:可以在程序运行时阶段操作这些对象,同时可以解耦,提高程序的可扩展性,就像下面的输入提示,当我们输入"a.",程序会提示输入,这就是将String类的方法进行了封装Method[],然后遍历数组将所有的方法进行展示
¶反射的好处
我们可以通过在配置文件中写入要加载的类和需要调用的方法(使用集合存储),然后在程序中加载类和调用方法。在框架中需要经常地通过配置外部文件,在不修改源码的情况下来控制程序
¶类加载
分为静态加载和动态加载
- 静态加载:在编译时加载相关的类,如果没有则报错,即使我们在程序中可能不会用到这个类,但也必须编写类(在switch……case中创建对象,这样可能不会用到该类)。依赖性太强
- 动态加载:运行时加载需要的类,如果运行时不用该类则不报错
类加载时机
- 当创建对象时(new)静态加载
- 当子类被加载时 静态加载
- 调用类中的静态成员时 静态加载
- 通过反射 动态加载
¶获取Class对象的方式
- Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象,多用于配置文件,将类名定义在配置文件中,读取文件、加载类
- 当类已经加载进内存中 类名.class:通过类名的属性class获取,多用于参数的传递
- 当创建好对象,对象.getclass():getclass方法在object中定义着,所有对象都有这个方法。,多用于对象的获取获取字节码方式——常用
1 | public class Super01 { |
¶Class类对象的功能
-
获取成员变量们
- Field[] getFields()//获取多个
- Field getField(String name)//获取一个
- Field[] getDeclaredFields(),获取所有的成员变量,不管修饰符,此时我们就可以操作私有的成员变量
- Field getDeclareField(String name)
-
获取构造方法们
同样也有getconstructor等方法
-
获取成员方法们
同上
-
获取类名:String getName()
¶获取Field
1 | import java.lang.reflect.Field; |
获取到了两个对象
getFields()用来获取所有public的成员变量
获取到字段之后使用get和set对成员变量的值进行操作,参数为对象,因为成员变量是在对象内的
1 | public class Super01 { |
1 | public class Super01 { |
¶获取Constructor
1 | public class Super01 { |
¶获取Method
1 | public class Super01 { |
¶整个过程的使用
1 | public class Super01 { |
¶泛型
¶多态
解决代码复用性不高且不利于代码维护的问题
所谓的多态就是一个对象同时具备多种属性,比如小明既是学生也是人
下面以一个例子来看多态的好处,我们要实现主人喂动物这个操作,此时需要在master这个类中定义两种Feed方法
而是用多态只需要一个方法即可
父类类型 引用名=new 子类类型(),即为多态,也是多态中的向上转型 也就类似先在堆中new一个子类对象,接着使用父类的引用指向该对象地址,编译类型(编译时)看等号左边、运行类型(程序运行时)看等号右边,编译类型在定义对象时就确定了,运行类型是可以变化的。一个对象的编译类型和运行类型可以不一样,比如上面的dog定义为Animal类(编译类型),但在运行时指向Dog类(运行类型),成员变量是编译类型,方法是运行类型
此时可以访问父类的所有成员以及调用子类中重写父类的方法、但是不能访问子类的特有方法,如果要访问,只能再向下转型,要注意的是向下转型对象的类型必须一致,这个操作就相当于重写使用一个cat引用名指向new出来的Cat对象,但是不能使用cat引用名指向new出来的Dog对象
多态的前提是两个对象存在继承关系
¶接口
¶抽象
如果父类方法不确定如何进行方法体实现,那么这就是一个抽象方法。将图形作为父类,子类为长方形、圆形、三角形,我们可以通过不同的面积公式来求得各个图形的面积,但是我们没有办法直接求解图形的面积
此时就需要抽象方法来定义,父类定义抽象方法(不需要函数体,因为每个图形对应的方法都是不同的),然后子类继承父类后必须重写父类的抽象方法,但不用声明为abstract
抽象方法所在的类必须是抽象类,抽象类不能直接new对象,抽象方法的调用:先通过实现类完成对抽象方法的实现,再通过实现类的对象调用
测试代码
1 | public class Super01 { |
父类,print是抽象方法
1 | public abstract class Animal { |
两个子类,使用@Override对父类的抽象方法进行重写,也可以说是实现,将其具体化
1 | public class Wolf extends Animal { |
1 | public class Rabbit extends Animal { |
¶接口
接口就是一种公共的规范标准,相当于模板
比如USB接口,只要符合USB的标准就可以使用USB,打印机、U盘等,接口没有静态代码块和构造方法
¶抽象方法
1 | //接口中默认声明为public abstract |
1 | //接口中定义抽象方法 |
导入接口并使用implement表示对该接口进行实现,然后重写接口中所有的抽象方法,如果没有重写所有,则需要将实现类定义为抽象类
1 | import com.test.InterFace; |
最后创建实现类的对象后,通过实现类的对象调用即可
1 | Test test=new Test(); |
¶默认方法
1 | //由于接口中的抽象方法都需要实现,当我们需要在接口中添加方法时就需要修改使用了该接口的类 |
¶静态方法
静态方法:接口中不希望被被实现类使用的方法,关键字为static,只能通过接口名称调用,只能是public static
1 | public static void Method04(){ |
静态方法的调用
¶私有方法
Java9开始接口中允许定义私有方法,private的方法只有接口自己可以调用,不能被实现类或别人调用
1 | //普通私有方法 |
¶常量
常量关键字为public static final,final表明为这个值不可被修改
¶一个类实现多个接口
一个类的直接父类是唯一的,但是一个类可以同时实现多个接口
注意事项
- 如果实现类所实现的多个接口中存在多个重复的抽象方法,只需要覆盖重写一次即可
- 如果实现类没有覆盖重写所有抽象方法,则需要将实现类定义为抽象类
- 如果实现类所实现的多个接口中存在重复的默认方法,那么需要对默认方法进行覆盖重写
- 如果实现类的父类方法和接口中的默认方法冲突时,优先调用父类的方法
¶移动端攻防技术
¶Xposed框架介绍
Xposed更适用于长久化的使用,但是每次安装框架之后都需要重启,这也是其麻烦的一点
¶AS编写Xposed框架
Xposed框架本质上也是APK,但是我们需要让其被Xposed识别,所以我们先要安装好环境,由于Xposed很久没有发布了,不支持较高版本的Android8.0、8.1以上,但是Magisk有Edxposed来代替
¶抓包详解
在安卓App的逆向分析中,抓包通常是指通过一些手段来获取App与服务器之间传输的明文网络数据信息,我们可以通过获取到的信息快速定位关键接口函数的位置
主要有Hook抓包和中间人抓包
- Hook抓包:Hook抓包实际上是通过对发包函数的Hook来达到抓包的目的
- 中间人抓包:将一段完整的客户端-服务器的通信方式割裂为两段客户端-服务器通信
抓包的主要工具有Wireshark、BurpSuite、Charles、Fiddler,Fiddler不推荐使用
其中手机中输入的代理主机名应为下面这个地址,使用ipconfig获取
¶安卓攻防技术
- 动态加载方案:将u需要保护的代码单独编译成一个二进制文件,将其加密后保存在一个外部的二进制文件中。在外部程序运行的过程中再将保护的二进制文件解密并使用ClassLoader类加载器来动态加载和运行被保护的代码,Android中每个Java类都是由ClassLoader类加载器加载和运行的
- App加固:最难绕过的保护手段就是App加固
- Root检测
- NDK:将关键代码写入native层,Java层只作为加载器和调用端
- 云端存储数据
- 反调试:运行时检测和事先阻止,反调试
- 运行时检测:如果调用ptrace()函数进行进程附加,/proc/
/status文件中的TracePid变量会在进程被附加后由0变为附加进程的pid,如果此时代码本身单开一个线程对这个文件的TracePid值进行循环检测,异常时则退出进程,就做到了阻止进程被破解者调试 - 时间差检测:调试的时候指令执行时间较长,我们可以基于此进行检测
- 双进程保护:主要是基于一个进程最多只能被一个进程ptrace附加的特性,实现fork一个子进程ptrace,然后ptrace自己
- 运行时检测:如果调用ptrace()函数进行进程附加,/proc/
- 代码混淆
- 符号混淆:Google自带的混淆器ProGuard,主要是将有意义的名称改为a、b这种无符号的名称
- 压缩文件大小:只要修改App/build/grale文件,将buildTypes中的minifyEnabled对应的值改为true即可
- DexGuard:收费商业软件,是ProGuard的升级版,支持字符串加密、花指令、资源加密等
攻击:
- 静态分析和动态分析结合:IDA、GDB对so文件进行调试,Jeb、AS调试smali
- Hook和Trace
- 反反调试:手动patch检测代码逻辑后重新打包
¶App加固
App加固,类似动态加载,用加固厂商的壳程序包裹真实的App,在真实动态运行时再通过壳程序执行释放出来的真正的App
App加固的发展主要可以分为三个阶段
¶DEX整体加固
这个阶段的App加固的核心原理就是将DEX整体加密后动态加载,在对加密的文件解密之后调用DexClassLoader或者其他类加载函数来加载解密后的文件,由于对文件的操作过于明显,进阶为将加密的DEX在内存中进行加载的加固技术,但还是可以通过在内存中搜索DEX文件头或在加载DEX的函数上下断点、进行Hook就可以找到解密数据
由于DEX整体加固总是将代码数据完整地存储在一段内存中,只要绕过反注入和反调试技术即可获取到数据
¶Frida脱壳
主要是基于Hook libart.so导出的OpenMemory函数,只在Android8.0以下才有
¶代码抽取保护
这个阶段App加固的关键在于真正的代码数据并不与DEX的整体结构数据存储在一起,就算DEX被完整地dump出来,也无法看到真正的代码逻辑
核心原理是利用私有函数,通过对其自身进程的Hook来拦截函数被调用时的路径,在抽取的函数被真实调用之前,将无意义代码数据填充到对应的代码区中(将Dex文件中的指令编码部分与Dex文件主体分离并独立执行加密操作,而原先的指令转为NOP指令,这样加载进内存中的Dex反编译后代码部分就是空的)
代码抽取技术并不会对App中所有的函数进行抽取保护,特别是第三方库。并且,代码抽取技术通常在函数被第一次调用后就不再将函数内容重新置空,因此只需要在App运行时多处发几次程序逻辑,然后再进行DEX的dump即可得到更加完整的DEX文件
¶FART脱壳
对抗指令抽取的首要目标就是要获取正确的被抽取的方法指令,而方法指令被执行前一定会被解密,可以借助Android调用类方法的机制,通过系统加载指令的函数访问到内存中解密的指令,进而导出
ART环境中常用的脱壳点:脱动态加载壳的本质是要获取在内存中处于解密状态的Dex文件,因此需要准确定位Dex文件在内存中的位置和大小,ART加载链接类时,Android会先调用LoadClass()函数去加载Dex文件中的类,然后调用LoadClassMembers()函数去初始化类的所有变量以及函数对象
1 | void ClassLinker::LoadClassMembers(Thread* self, const DexFile& dex_file,const uint8_t* class_data,Handle<mirror::Class> klass,const OatFile::OatClass* oat_class) |
其中第二个参数就是对当前处理的dex对象的引用,在这个引用中我们可以得到Dex对象,从而获取Dex文件在内存中的地址以及长度
¶DexHunter脱壳
通过主动加载DEX中的所有类并dump处所有方法对应的代码,最后将代码重构再填充回被抽取的DEX中
¶VMP与Dex2C
将所有的Java代码变成最终的native代码
区别
¶Ollvm
https://jev0n.com/2022/07/08/ollvm-1.html
https://blog.quarkslab.com/deobfuscation-recovering-an-ollvm-protected-program.html
https://www.52pojie.cn/thread-1488350-1-1.html
https://mrt4ntr4.github.io/MODeflattener/
¶OLLVM环境配置与编译so文件
首先先配置好前面安装的NDK进行编译
然后创建jni目录,在jni目录下创建Android.mk,Application.mk,C/C++源文件
添加如下内容
Android.mk文件
1 | LOCAL_PATH := $(call my-dir) |
1 | //不同命令对应的混淆方式 |
Application.mk文件
1 | LOCAL_PATH := $(call my-dir) |
hello.cpp
1 |
|
接下来cd到jni目录中,执行ndk-build即可
然后在jni同路径下的libs目录即可找到编译完成的so文件
混淆效果展示