环境准备
- 夜神模拟器
- frida-server + frida
- andriod studio (可选)
东西准备的不算多,adb用夜神模拟器自带的就可以,别的就不多赘述了,别的博主那里已经写的非常清楚了,唯一要关注一点的是在新版夜神模拟器默认的端口是62025,adb只有连这个端口才能上的去,和网上说的有些差异。

对安卓的基本认识
文件层面
- Class
和我们平常理解的字节码运行机制不一样,在Android平台上,
.class文件不能直接在Android设备上运行,因为Android使用了自己的运行时环境(最初是Dalvik虚拟机,后来转为ART),而不是标准的Java虚拟机(JVM)。在Android开发过程中,Java源代码会被编译成.class文件,然后再通过工具将这些.class文件转换成Dalvik可执行的.dex(Dalvik Executable)文件。.dex文件包含了Dalvik虚拟机可以理解和执行的指令集。 - Dex dex文件是Android平台上的可执行文件。在编译Java代码之后,通过Android平台上的工具可以将Java字节码转换成Dex字节码。
- Apk apk文件是Android上的安装文件。一个Android安装包包含了与某个Android应用程序相关的所有文件。apk文件将AndroidManifest.xml文件、应用程序代码(.dex文件)、资源文件和其他文件打成一个压缩包。
三者关系大致如以下表格所示。
| 文件类型 | 描述 |
|---|---|
| APK | Android 应用程序包(Android Package),包含了应用的所有组件和资源文件,是安装和分发应用的主要格式,APK的组成如下所示。 |
| 核心的字节码文件(Dex 文件):存储了应用的字节码指令,以供 Android 设备的 Dalvik 或 ART 虚拟机执行。 | |
| 资源文件:包括应用所需的图片、布局、字符串等资源文件。 | |
| 清单文件(Manifest 文件):描述了应用的组件、权限要求等信息。 | |
| 签名文件:APK 文件经过数字签名,用于验证应用的来源和完整性。 | |
| Class | Java 字节码文件,生成自 Java 源代码编译后的结果。Class 文件包含了应用程序的类、方法、字段等信息。在 Android 开发中,这些 Class 文件会被转换成 Dex 文件后才能在 Dalvik 或 ART 虚拟机上执行。 |
| Dex | Dex(Dalvik Executable)文件是 Android 平台上特定的字节码文件格式,用于在 Dalvik 或 ART 虚拟机上执行。Dex 文件是为了优化在 Android 设备上的内存和执行效率而设计的。它将来自多个 Class 文件的字节码合并到一个文件中,并采用一些优化技术,例如压缩和跨方法优化。应用的所有 Class 文件经过转换,最终会生成一个 Dex 文件,该文件包含了应用的所有字节码指令。 |
抽象层次介绍
-
Java层 : 由Android 应用程序的主要开发层,包含了应用程序的各种功能和组件的实现逻辑。以下是 Java 层中常见的组件
- Activity层 : 负责管理应用程序中的一个界面或一个屏幕,处理用户的输入、事件响应和生命周期管理等。【这个用的可多了】
- Service层 : 在后台执行长时间运行的操作,没有用户界面,用于处理一些独立于界面的任务,如播放音乐、下载文件等。
- BroadcastReceiver层:用于接收系统或其他应用程序发送的广播消息,可以在应用程序之间传递信息。
- ContentProvider层:用于管理应用程序的数据共享,允许其他应用程序访问数据,如联系人、媒体文件等。
- Fragment(片段):可以看作是 Activity 的一部分,用于构建灵活的用户界面,可以独立管理自己的生命周期和事件。
- Intent(意图):用于在不同组件之间进行通信和传递数据,如启动另一个 Activity 或发送广播等。
-
Native层: 也被称为 C/C++ 层,用于编写性能敏感的组件。在 Native 层,开发人员可以使用 C/C+和相关的库来实现一些底层功能。常见的 Native 层库包括 OpenGL ES(图形渲染)、OpenSL ES(音频处理)等。Native 层的代码通常会编译成库文件,以.so(共享对象)文件的形式存在 ( Native层和Java层的交互依赖so来实现 )
-
Linux 内核层: Linux 内核层是 Android 操作系统的基础,Android 是基于 Linux 内核开发的。Linux 内核层负责管理硬件设备、提供系统服务和驱动等功能。它包括了设备驱动程序、文件系统、内存管理、进程管理、网络协议栈等。Linux 内核层提供了 Android 平台上的基本功能,允许应用程序通过系统服务和底层接口与硬件进行交互。
Frida使用
基本命令
模拟器端 (启动frida-server)
1. adb connect 127.0.0.1:620252. adb root3. adb shell4. cd /data/local/tmp5. ./fs模拟器端(查看进程包名)
1.adb shell2.ps -elffrida-client
1.frida -U -f 包名 -l 要执行的js脚本 //未启动的app2.frida -U 包名 - l 要执行的js脚本 //已启动的appHook模板
- 目标函数替换
Java.perform(function() { var TargetClass = Java.use('目标className'); TargetClass.目标methodName.implementation = function() { console.log('targetMethod is hooked'); };});实战案例
分享一道简单题吧,2015年的一道猜拳的CTF题,这里通过jadx反编译得到以下源码。
package com.example.seccon2015.rock_paper_scissors;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.view.View;import android.widget.Button;import android.widget.TextView;import java.util.Random;
public class MainActivity extends Activity implements View.OnClickListener { Button P; Button S; int flag; int m; int n; Button r; int cnt = 0; private final Handler handler = new Handler(); private final Runnable showMessageTask = new Runnable() { public void run() { TextView tv3 = (TextView) MainActivity.this.findViewById(R.id.textView3); if (MainActivity.this.n - MainActivity.this.m == 1) { MainActivity.this.cnt++; tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt)); } else if (MainActivity.this.m - MainActivity.this.n == 1) { MainActivity.this.cnt = 0; tv3.setText("LOSE +0"); } else if (MainActivity.this.m == MainActivity.this.n) { tv3.setText("DRAW +" + String.valueOf(MainActivity.this.cnt)); } else if (MainActivity.this.m < MainActivity.this.n) { MainActivity.this.cnt = 0; tv3.setText("LOSE +0"); } else { MainActivity.this.cnt++; tv3.setText("WIN! +" + String.valueOf(MainActivity.this.cnt)); } if (1000 == MainActivity.this.cnt) { tv3.setText("SECCON{" + String.valueOf((MainActivity.this.cnt + MainActivity.this.calc()) * 107) + "}"); } MainActivity.this.flag = 0; } }; public native int calc(); static { System.loadLibrary("calc"); } @Override // android.app.Activity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.P = (Button) findViewById(R.id.button); this.S = (Button) findViewById(R.id.button3); this.r = (Button) findViewById(R.id.buttonR); this.P.setOnClickListener(this); this.r.setOnClickListener(this); this.S.setOnClickListener(this); this.flag = 0; } @Override // android.view.View.OnClickListener public void onClick(View v) { if (this.flag != 1) { this.flag = 1; TextView tv3 = (TextView) findViewById(R.id.textView3); tv3.setText(""); TextView tv = (TextView) findViewById(R.id.textView); TextView tv2 = (TextView) findViewById(R.id.textView2); this.m = 0; Random rm = new Random(); this.n = rm.nextInt(3); String[] ss = {"CPU: Paper", "CPU: Rock", "CPU: Scissors"}; tv2.setText(ss[this.n]); if (v == this.P) { tv.setText("YOU: Paper"); this.m = 0; } if (v == this.r) { tv.setText("YOU: Rock"); this.m = 1; } if (v == this.S) { tv.setText("YOU: Scissors"); this.m = 2; } this.handler.postDelayed(this.showMessageTask, 1000L); } }}虽然从来没学过安卓,但是因为咱们学过英语,所以大体还是能够猜出来代码的意思的,需要我们猜赢1000次才能拿到flag,中间连平局都不能有。因为这里的关键逻辑在onClick这里,我们直接hook它就行,但在hook之前还需要我们查出来包名,虽然这里用jadx直接能看到,但还是演示下正常的做法。
adb shell am monitor在shell下输完以上命令后,再到模拟器里面打开要测试的app就会自己包名就会自己出来哩

考虑到实际情况下,直接代审查函数也挺麻烦的,这里演示一下如何通过frida来查找当前已经调用的函数,缩小代审范围,首先先准备好以下脚本当传家宝(要是会用objection也行,这里就当我没说就行)
Java.perform(function() { var packageName = "com.example.seccon2015.rock_paper_scissors"; Java.enumerateLoadedClasses({ onMatch: function(className) { if (className.startsWith(packageName)) { var targetClass = Java.use(className); var methods = targetClass.class.getDeclaredMethods(); console.log("Class: " + className); for (var i = 0; i < methods.length; i++) { console.log(methods[i].toString()); } console.log("-------------------------------------"); } }, onComplete: function() {} });});不知道为什么,这个模拟器用spwan模式老是崩,这里就用attach模式来做演示吧,先用adb连上去查一下pid
adb shellps -elf|grep "com.example.seccon2015.rock_paper_scissors"这里查出来是8590,接着直接运行frida,得到以下结果,可以看到已经通过内存漫游,找到了并打印了我们自定义的函数

然后再附上我们这里用来hook onClick()的脚本
Java.perform(function () { var MainActivity = Java.use('com.example.seccon2015.rock_paper_scissors.MainActivity'); var onClick = MainActivity.onClick; onClick.implementation = function (v) { send('onClick'); // Call the original onClick handler this.m.value = 0; this.n.value = 1; this.cnt.value = 999; console.log('Done:' + JSON.stringify(this.cnt)); };});返回结果如下,手快,多按了几下
得到flag
参考链接
https://www.anquanke.com/post/id/197657#h3-6
Frida Hook Android App 笔记 | clicker’s blogs (sleepydogyp.github.io)