adb + Frida操作方法总结

本文多借鉴于SWDD师傅的博客哇,感谢师傅~

0x01 adb简介

一、什么是adb

ADB 全称为 Android Debug Bridge,起到调试桥的作用,是一个客户端-服务器端程序。其中客户端是用来操作的电脑,服务端是 Android 设备。

ADB 也是 Android SDK 中的一个工具,可以直接操作管理 Android 模拟器或者真实的 Android 设备。

二、为什么要用adb

运行设备的 shell(命令行)
管理模拟器或设备的端口映射
计算机和设备之间上传/下载文件
可以对设备的应用进行卸载安装等
在 App 遇到 ANR/Crash 等 bug 时,可以通过 ADB 来抓取日志

简而言之,ADB 就是连接 Android 手机与 PC 端的桥梁,所以ADB又称为安卓调试桥(注意:是安卓,不是iOS),可以让用户在电脑上对手机进行全面的操作!

三、adb命令详解

1、基本命令

adb version : 显示adb版本

adb help :帮助信息,查看adb所支持的所有命令

adb devices:查看当前连接的设备,已连接的设备会显示出来

adb get-serialno:也可以查看设备号

2、权限命令

adb root:获取Android管理员(root用户)的权限。

注意:一般测试机可使用root权限。

Android版本9以上,不支持商用机使用root权限,但可以修改底层一些配置

adb shell:登录设备 shell,该命令将登录设备的shell(内核),登录shell后,可以使用 cd,ls,rm 等Linux命令

3、建立连接

adb -d:如果同时连了usb,又开了模拟器,连接当前唯一通过usb连接的安卓设备

adb -e shell:指定当前连接此电脑的唯一的一个模拟器

adb -s <设备号> shell:当电脑插多台手机或模拟器时,指定一个设备号进行连接

exit:退出

adb kill-server:杀死当前adb服务,如果连不上设备时,杀掉重启。(没事不要用它)

adb start-server:杀掉后重启

四、使用问题详解

解决ADB卡在等待调试器的问题 Waiting For Debugger is waiting for the debugger to attach.

调试 Android 应用时,习惯于看到“等待调试器”对话框。 此对话框将显示,直到调试器附加到要调试的应用的进程,以便对其进行调试。

有时,由于各种未知和奇怪的原因,可能会发生此对话框不会消失的情况,因此无法继续调试会话。

解决方案可能是重新启动您的设备/模拟器,并杀死/重新启动Android Studio。 这是一个相当沉重的解决方案,会让您浪费大量时间。

您只需在终端中运行以下代码即可避免所有这些重新启动:

adb shell am clear-debug-app

然后重启一下adb服务,正常连接调试设备即可

0x02 Frida简介

一、什么是Frida

Frida 是一款基于 Python + JavaScript 的 Hook 与调试框架。

Firda 是一款易用的跨平 Hook 工具, Java 层到 Native 层的 Hook 无所不能,是一种 动态 的插桩工具,可以插入代码到原生 App 的内存空间中,动态的去监视和修改行为,原生平台包括 Win、Mac、Linux、Android、iOS 全平台。

静态二进制插桩:在程序执行前插入额外的代码和数据,生成一个永久改变的可执行文件。
动态二进制插桩:在程序运行时实时地插入额外代码和数据,对可执行文件没有任何永久改变。
Frida 的功能如同油猴插件,通过油猴插件编写的脚本可以直接改变网页的编码方式。

大部分 App 对于我们来说都是黑盒,对 App 进行逆向和动态调试、或自动化分析、需要不断的进行动态调试,Frida 通过使用 Python 注入 JavaScript 脚本,都是通过 JS 脚本来操作设备上的 Java代码。

如果需要持久化的 Hook 还是需要通过 Xposed 等框架,但是 Frida 的动态和灵活性对逆向和自动化逆向提供了很大帮助。

对于逆向操作破解来说方便,安装环境和配置环境要求简单兼容性好。
选择动态调试函数会遇到各种反调试、崩溃,相比xposed而言,frida优势是其动态执行不需要重启。
因为Frida大致原理是手机端安装一个server程序,然后把手机端的端口转到PC端,PC端写python脚本进行通信,而python脚本中需要hook的代码采用javascript语言。

二、Frida的用途

访问进程的内存

  • Frida 可以直接访问并修改目标进程的内存内容。这对于调试和逆向工程非常有用,特别是在需要分析或修改应用程序数据时。

应用程序运行时覆盖功能

  • 通过 Frida,开发者可以在运行时覆盖应用程序的某些功能。这意味着可以在不修改源代码的情况下,改变应用程序的行为,进行功能测试和故障排除。

从导入的类调用函数

  • Frida 允许开发者在运行时调用应用程序中的任意函数,包括那些从外部库导入的函数。这对于调试和功能扩展非常有用,因为可以直接与应用程序的内部逻辑交互。

动态 Hook 跟踪、拦截函数

  • Frida 的动态 Hook 功能使得开发者可以拦截并修改函数调用。这意味着可以在函数调用之前或之后执行自定义代码,记录调用信息,甚至改变函数的输入输出。这对于安全研究、调试和性能分析都非常有帮助。

三、如何使用

Frida 分为客户端和服务端。

客户端:PC(控制端)

服务器:手机设备(被控制端)

客户端编写的 Python 代码,用于连接远程设备,提交要注入的 JS 代码到服务端,接受服务端发来的消息。

服务端中需要用 JS 代码注入到目标进程,操作内存数据,给客户端发送消息。

使用命令frida-ps查看客户端的进程情况,使用命令frida-ps -U查看服务端的进程情况。

0x03 Frida 实战(正文开始)

一、前备知识

Hook(Hooking)简介

让我们从非常基础的知识开始。

什么是钩子?

Hook是指拦截和修改应用程序或Android系统中函数或方法行为的过程。例如,我们可以钩取我们应用程序中的一个方法,并通过插入我们自己的实现来改变其功能。

现在,让我们尝试在一个应用程序中钩取一个方法。我们将使用JavaScript API 来完成这个任务,但值得注意的是,Frida也支持Python的调用,我们同样可以使用python代码并插入js片段来实现frida的一系列操作。

二、使用Hook修改被调用的方法的逻辑,返回值,传入参数

1、基本模板

首先展示一下注入的模板:

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function() {

var <class_reference> = Java.use("<package_name>.<class>");
<class_reference>.<method_to_hook>.implementation = function(<args>) {

/*
我们自己的方法实现
*/

}

})
  • Java.perform 是 Frida 中用于创建一个特殊上下文的函数,让你的脚本能够与 Android 应用程序中的 Java 代码进行交互。它就像是打开了一扇门,让你能够访问并操纵应用程序内部运行的 Java 代码。一旦进入这个上下文,你就可以执行诸如钩取方法或访问 Java 类等操作来控制或观察应用程序的行为。
  • var <class_reference> = Java.use("<package_name>.<class>");
    在这里,你声明一个变量 <class_reference> 来表示目标 Android 应用程序中的一个 Java 类。你使用 Java.use 函数指定要使用的类,该函数接受类名作为参数。<package_name> 表示 Android 应用程序的包名,<class> 表示你想要与之交互的类。
  • <class_reference>.<method_to_hook>.implementation = function(<args>) {}
    在所选的类内部,通过 <class_reference>.<method_to_hook> 符号访问你想要钩取的方法。这是你可以定义自己的逻辑以在钩取的方法被调用时执行的地方。<args> 表示传递给函数的参数。

来个实例如下:

其中<package_name> :com.ad2001.frida0x1

image-20240804110252428

2、例题讲解

通过Jadx分析Frida-labs 0x1

onCreate方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(C0570R.layout.activity_main);
final EditText editText = (EditText) findViewById(C0570R.C0573id.editTextTextPassword);
this.f103t1 = (TextView) findViewById(C0570R.C0573id.textview1);
final int i = get_random();
((Button) findViewById(C0570R.C0573id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.ad2001.frida0x1.MainActivity.1
@Override // android.view.View.OnClickListener
public void onClick(View view) {
String obj = editText.getText().toString();
if (TextUtils.isDigitsOnly(obj)) {
MainActivity.this.check(i, Integer.parseInt(obj));
} else {
Toast.makeText(MainActivity.this.getApplicationContext(), "Enter a valid number !!", 1).show();
}
}
});
}

可以发现,在onCreate方法中,有一个监听事件,监听了button的点击,当按钮点击下去之后,程序首先判断输入是不是数字,是数字的话,就将其从string转化为int,再进入check中与i比较,因此我们需要检查check方法。

check方法

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
void check(int i, int i2) {
if ((i * 2) + 4 == i2) {
Toast.makeText(getApplicationContext(), "Yey you guessed it right", 1).show();
StringBuilder sb = new StringBuilder();
for (int i3 = 0; i3 < 20; i3++) {
char charAt = "AMDYV{WVWT_CJJF_0s1}".charAt(i3);
if (charAt < 'a' || charAt > 'z') {
if (charAt >= 'A') {
if (charAt <= 'Z') {
charAt = (char) (charAt - 21);
if (charAt >= 'A') {
}
charAt = (char) (charAt + 26);
}
}
sb.append(charAt);
} else {
charAt = (char) (charAt - 21);
if (charAt >= 'a') {
sb.append(charAt);
}
charAt = (char) (charAt + 26);
sb.append(charAt);
}
}
this.f103t1.setText(sb.toString());
return;
}
Toast.makeText(getApplicationContext(), "Try again", 1).show();
}

本方法显而易见就是检查输入是否能够满足i*2 + 4 == i2,如果满足则将flag输出到f103t1所绑定的textView控件上,其中用于判断的i则来自get_random。

get_random

1
2
3
int get_random() {
return new Random().nextInt(100);
}

显而易见,本方法就只是普通的返回一个随机数。

Hook begin!

对于本样例程序,首先我们可以直接hook程序逻辑。更改随机产生的值为一个固定值。或者hook check方法更改check方法传入的参数

Hook get_random方法
实现代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function hook(){
var MainActivity = Java.use("com.ad2001.frida0x1.MainActivity");
MainActivity.get_random.implementation = function (){
return 0;
}
}

function main(){
Java.perform(function (){
hook();
})
}

setImmediate(main);

代码解释如下:

  1. 首先定义了一个名为hook的JavaScript函数,其中包含了对目标应用特定方法的hook逻辑。
    • hook函数通过Frida的Java API来获取目标应用中的MainActivity类。
    • 然后,它通过Java.use()方法获取了MainActivity类的引用,使得我们可以访问该类的方法。
    • 最后,hook函数将MainActivity类中的get_random方法进行了修改。它用自定义的实现替换了原有方法的实现,使得每次调用get_random方法时都返回固定值0。
  2. 接着定义了一个名为main的JavaScript函数,其中包含了Frida的Java.perform()方法,用于执行指定的hook逻辑。
  3. 最后,通过setImmediate()函数调用main函数,确保在Frida脚本启动后立即执行。