【Android】Jadx动态调试应用
1. 前言
Jadx已支持动态调试APP,但一直没试过,从逆向角度尝试走一遍流程并熟悉,方便日后翻阅。
2. 相关知识
2.1 动态调试原理
动态调试的原理可以概括为以下几个步骤:
-
启动应用程序进程:使用调试器或其他工具启动应用程序进程,并将其连接到调试器。
-
注入调试代码:在应用程序进程中注入调试代码,以便在应用程序运行时捕获相关信息。通常,调试代码会在应用程序的某些关键点设置断点,并在断点处暂停应用程序的执行。
一种常见的注入调试器代码的方法是使用操作系统提供的动态链接库(DLL)或共享库(SO)机制。在这种情况下,调试器会通过操作系统API加载一个特定的DLL或SO库,并将其注入到目标进程中。这个库可以包含调试器代码,也可以是其他形式的代码(例如钩子或代理),以便在进程运行时进行调试操作。
- 与调试器通信:一旦应用程序暂停,调试代码会与调试器通信,并将应用程序的状态和行为传递给调试器。调试器可以查看应用程序的栈、变量、线程等信息,并执行一些调试操作,例如单步执行、变量修改、内存查看等。
恢复应用程序执行:当调试器完成调试操作时,调试代码会通知应用程序继续执行,并从断点处恢复应用程序的执行。
2.2 调试器与目标进程通信
调试器与目标进程之间的通信可以通过以下几种方式实现:
-
基于套接字的通信:调试器和目标进程之间可以通过本地网络套接字进行通信。在应用程序中,调试器可以打开一个本地套接字,并监听来自目标进程的连接请求。一旦目标进程连接到套接字,调试器就可以向进程发送调试指令,并接收进程的执行状态和结果。
-
基于共享内存的通信:调试器和目标进程之间可以通过共享内存进行通信。在应用程序中,调试器可以创建一个共享内存区域,并将其映射到目标进程的地址空间中。一旦共享内存被映射到目标进程中,调试器就可以向内存中写入调试指令,并从内存中读取进程的执行状态和结果。
-
基于进程间通信的通信:调试器和目标进程之间可以通过进程间通信(IPC)机制进行通信。在应用程序中,调试器可以使用IPC机制(例如管道、消息队列、信号等)发送调试指令,并从目标进程中接收执行状态和结果。
2.3 debuggable
debuggable是一个Android应用程序的属性,它指定应用程序是否允许调试器连接到应用程序进程。当debuggable属性设置为true时,调试器可以连接到应用程序进程,并且可以查看和修改应用程序的状态和行为。
在开发和测试应用程序时,将debuggable属性设置为true通常很有用,因为它允许开发人员使用调试器来快速定位和解决问题。例如,开发人员可以使用调试器来查看变量的值、执行代码行、设置断点等等。
然而,在将应用程序发布到生产环境之前,应该将debuggable属性设置为false,以提高应用程序的安全性。如果应用程序的debuggable属性设置为true,那么黑客可以使用调试器来查看应用程序的敏感数据、执行恶意代码等等,这将对应用程序的安全性造成极大的威胁。
需要注意的是,如果应用程序被加固或者使用了其他防护技术,可能无法将debuggable属性设置为true。此外,某些Android系统版本可能会限制应用程序的debuggable属性,因此在设置debuggable属性之前,请确保您了解应用程序的环境和限制。
在Android 4.2及更高版本中,Google对debuggable属性进行了限制,只有当应用程序的签名与系统签名相同时,才允许将debuggable属性设置为true。这是为了提高应用程序的安全性,防止黑客利用调试器来窃取应用程序的敏感信息。
在Android 7.0及更高版本中,Google进一步加强了对debuggable属性的限制,只有在应用程序的AndroidManifest.xml文件中显式声明android:debuggable="true"时,才允许将debuggable属性设置为true。这是为了防止应用程序在生产环境中意外地启用了调试功能,从而降低了应用程序的安全性。
2.4 ro.debuggable
ro.debuggable是 Android 系统属性,它表示整个 Android 系统是否处于调试模式下。当设置为 1 时,整个系统可以被调试器连接并进行调试。当设置为 0 时,整个系统不能被调试器连接,这可以增加系统的安全性。
要将整个系统设置为调试模式,需要将系统属性 ro.debuggable 设置为 1。这可以通过在设备的命令行界面上运行以下命令来实现:
adb shell setprop ro.debuggable 1
通过将 ro.debuggable 设置为 1,整个 Android 系统就可以在调试模式下运行,并允许调试器连接到系统上的所有应用程序进行调试。
2.5 开发者模式-调试开关
开发者模式打开 debuggable 选项只是允许该应用程序在调试模式下运行,并允许该应用程序通过调试器进行调试。实际上,如果系统的 ro.debuggable 属性设置为 0,即使在开发者模式下打开了 debuggable 选项,该应用程序也无法在系统调试模式下进行调试。
动态调试复现
基于上述基础知识,如下提供两种调试思路
思路1:frida Hook debuggable属性值
ro.debuggable=0
debuggable=“false” 通过hook -》debuggable=“true”
使用Frida hook可以将应用程序的debuggable属性设置为true。
// 附加到指定应用程序并执行指定的函数
Java.perform(function () {
// 应用程序包名
var packageName = 'com.example.myapp';
// 修改后的 debuggable 参数值
var debuggableValue = true;
// 查找 Application 类
var Application = Java.use('android.app.Application');
// 修改 debuggable 参数值
Object.defineProperty(Application, 'debuggable', {
get: function () {
return debuggableValue;
},
set: function (value) {
debuggableValue = value;
}
});
// 打印调试信息
console.log(`Successfully set debuggable to ${debuggableValue}`);
});
这个脚本将hook android.app.Application类,并将debuggable属性设置为true。要在应用程序中执行此脚本,控制台中输入以下命令在启动时执行:
frida -U -f com.example.app -l script.js
接下来应该用jadx选择要调试的进程就行,但是我的设备出现了问题,我应用进程都是空的,不太清楚为什么。
思路2:修改系统ro.debuggable值
ro.debuggable=0 修改-> ro.debuggable=1
debuggable=“false” 通过hook -> debuggable=“true”
先看看本机ro.debuggable值
~ adb shell getprop ro.debuggable
2.1 方法1:mprop
https://github.com/wpvsyou/mprop
结果:失败
2.2 方法2:直接覆盖/system/build.prop
提示Read-only
结果:失败
2.3 方法3:magisk命令修改
magisk resetprop
结果:成功
但重启后失效
2.4 Magisk模块重置
然后就可以使用props命令进行修改了
我直接选择4,本地并没有ro.debuggable,所以我又选了5添加一个option
重启之后发现永久生效
结果:成功
jadx动态调试
回到最开始的问题,最开始的问题就是jadx的debug进程不存在,当我们修改了系统的debuggable后,jadx的进程列表会不一样吗?
答案是肯定的,我们已经可以选择进程了
分两步
先选择要调试的行数,然后选择smali代码
按下f2打断点,按f9开始监听,当程序逻辑走到断点处,会阻断并开始调试,如图可见调用栈和参数信息。
后话
至此,jadx动态调试也复现完成。可以看到当我们把系统的debuggable参数设置为1的时候,即便我们应用debuggable不等于true也是可以动态调试的。