Frida的甜蜜初体验

Uncategorized
2.1k words

环境准备

  • 夜神模拟器
  • 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
2
3
4
5
1. adb connect 127.0.0.1:62025
2. adb root
3. adb shell
4. cd /data/local/tmp
5. ./fs

模拟器端(查看进程包名)

1
2
1.adb shell
2.ps -elf

frida-client

1
2
1.frida -U -f 包名 -l 要执行的js脚本 //未启动的app
2.frida -U 包名 - l 要执行的js脚本 //已启动的app

Hook模板

  • 目标函数替换
    1
    2
    3
    4
    5
    6
    Java.perform(function() {
    var TargetClass = Java.use('目标className');
    TargetClass.目标methodName.implementation = function() {
    console.log('targetMethod is hooked');
    };
    });

实战案例

分享一道简单题吧,2015年的一道猜拳的CTF题,这里通过jadx反编译得到以下源码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
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直接能看到,但还是演示下正常的做法。

1
adb shell am monitor

在shell下输完以上命令后,再到模拟器里面打开要测试的app就会自己包名就会自己出来哩

考虑到实际情况下,直接代审查函数也挺麻烦的,这里演示一下如何通过frida来查找当前已经调用的函数,缩小代审范围,首先先准备好以下脚本当传家宝(要是会用objection也行,这里就当我没说就行)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
adb shell
ps -elf|grep "com.example.seccon2015.rock_paper_scissors"

这里查出来是8590,接着直接运行frida,得到以下结果,可以看到已经通过内存漫游,找到了并打印了我们自定义的函数

然后再附上我们这里用来hook onClick()的脚本

1
2
3
4
5
6
7
8
9
10
11
12
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)