首页
社区
课程
招聘
mac ida7.0 调试 Android10 应用崩溃解决方案
2024-4-22 12:57 2276

mac ida7.0 调试 Android10 应用崩溃解决方案

2024-4-22 12:57
2276

0 背景

ida 调试时经常需要加载 待调试动态库 之前 attach 到进程,常用的方式是以调试模式(adb shell am set-debug-app -w [--persistent] <packagename>)启动 app,使其处于 Waiting For Debugger 状态,然后 ida attach 到 app,最后使用 jdb -attach 命令使 app 度过 Waiting For Debugger,之后就可以在 ida 中进行调试了。

但是这个模式有些缺点:

  1. 需要查找 pid,更新 tcp、jdwp 映射,尤其在需要多次调试时,频繁查找更新很烦
  2. jdb -attach 时遇到 java.io.IOExceptio VirtualMachineManagerImpl.createVirtualMachine 问题直接导致程序崩溃,这个在 Android 10 和 Android 12 上必现(其它版本没有试过),Android 9 就没有问题

第 1 个缺点还能忍受,第 2 个缺点就很致命,因为有些软件只有在 Android 10 及之上版本才能运行。

第 2 个问题有大神给出这个 解决方案 没看懂...,所以想到根据 Waiting For Debugger 机制的系统实现方式,修改下源码,这样可以不使用 jdb -attach 也能达到同样的效果。

1 方案目标

自主控制 app 启动是否进入阻塞状态,等待 ida attach 后解除阻塞状态。

2 实现

2.1 环境

手机:pixel 3, AOSP 12.0.0_r34
虚拟机:VMware® Workstation 17 Player(17.5.1 build-23298084)

2.2 系统实现

am set-debug-app 命令入手查找 Waiting For Debugger 系统实现,最终找到 set-debug-app 执行位置

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
private void handleBindApplication(AppBindData data) {
    ...
    if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
    // XXX should have option to change the port.
    Debug.changeDebugPort(8100);
    if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
        Slog.w(TAG, "Application " + data.info.getPackageName()
                + " is waiting for the debugger on port 8100...");
 
        IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.showWaitingForDebugger(mAppThread, true); // 显示弹窗
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
 
        Debug.waitForDebugger(); // 阻塞线程
 
        try {
            mgr.showWaitingForDebugger(mAppThread, false); // 隐藏弹窗
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
 
    } else {
        Slog.w(TAG, "Application " + data.info.getPackageName()
                + " can be debugged on port 8100...");
    }
    ...
}

阻塞线程实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void waitForDebugger() {
    if (!VMDebug.isDebuggingEnabled()) {
        //System.out.println("debugging not enabled, not waiting");
        return;
    }
    if (isDebuggerConnected())
        return;
 
    // if DDMS is listening, inform them of our plight
    System.out.println("Sending WAIT chunk");
    byte[] data = new byte[] { 0 };     // 0 == "waiting for debugger"
    Chunk waitChunk = new Chunk(ChunkHandler.type("WAIT"), data, 0, 1);
    DdmServer.sendChunk(waitChunk);
 
    mWaiting = true;
    while (!isDebuggerConnected()) {
        try { Thread.sleep(SPIN_DELAY); } // SPIN_DELAY = 200
        catch (InterruptedException ie) {}
    }
    mWaiting = false;
    ....
}

弹窗实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static final int WAIT_FOR_DEBUGGER_UI_MSG = 6;
...
case WAIT_FOR_DEBUGGER_UI_MSG: {
    synchronized (ActivityManagerService.this) {
        ProcessRecord app = (ProcessRecord)msg.obj;
        if (msg.arg1 != 0) {
            if (!app.waitedForDebugger) {
                Dialog d = new AppWaitingForDebuggerDialog(
                        ActivityManagerService.this,
                        mUiContext, app);
                app.waitDialog = d;
                app.waitedForDebugger = true;
                d.show();
            }
        } else {
            if (app.waitDialog != null) {
                app.waitDialog.dismiss();
                app.waitDialog = null;
            }
        }
    }
} break;

2.3 方案实现

模拟系统实现增加一套调试逻辑,难点在于 ActivityThread 如何知道当前 thread 是否需要等待,以及如何通知 ActivityThread 取消等待,跨进程通讯处理比较麻烦,简单起见使用系统属性处理。

增加系统属性

  1. lgd.dbg.process:<list<string>> : 当前包名列表,ActivityThread 匹配到列表中的值就 sleep
  2. lgd.dbg.sleepSeconds: <int> :ActivityThread sleep 秒数

这种缺点就是,重启设备后,这些属性值会被重置成默认值。
修改 init.rc 增加系统属性,此处不修改也行,可在 adb shell 中直接 setprop 添加此值。

1
2
3
4
5
6
# Define default initial receive window size in segments.
setprop net.tcp.default_init_rwnd 60
 
# lgd props
setprop lgd.dbg.process none
setprop lgd.dbg.sleepSeconds 30

修改 ActivityThread 处理这俩属性

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
private void handleBindApplication(AppBindData data) {
    ...
    StrictMode.initThreadDefaults(data.appInfo);
    StrictMode.initVmDefaults(data.appInfo);
     
    // 自定义逻辑开始 >>>
    String lgdDebugProcessStr = SystemProperties.get("lgd.dbg.process", "none");
    if (!"none".equals(lgdDebugProcessStr)) {
        String curProcessName  = data.processName;
        int lgdDebugSleepSeconds = SystemProperties.getInt("lgd.dbg.sleepSeconds", 30);
        String[] lgdDPs = lgdDebugProcessStr.split(",");
        for (String pn : lgdDPs) {
            if (pn.equals(curProcessName)) {
                try {
                    for (int i = 0; i < lgdDebugSleepSeconds; i++) {
                        Log.i("tag4LGD", curProcessName + " keep sleep: " + (lgdDebugSleepSeconds - i));
                        Thread.sleep(1000);
                    }
                    Log.i("tag4LGD", curProcessName + " running....");
                } catch (InterruptedException e) {
                    Log.e("tag4LGD", curProcessName + " do not want to sleep..", e);
                }
            }
        }
    }
    // >>> 自定义逻辑结束
 
    if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
    ...
}

3 测试

Windows 11 ida7.5 以及 mac ida7.0 测试可行,但是 threads 窗口只显示一个线程,有大神 提到 ida android_server 判定是否加载 libc 是通过固定路径 /system/lib[64]/libc.so 比较的,Android 10 引入的模块化特性导致 libc 路径发生变化,所以匹配逻辑有问题,官方这个在 ida7.4sp1 版本增加了 IDA_LIBC_PATH 解决了这个问题。

ida7.4sp1 及之后版本只需要在执行 android_server64 之前,export ·IDA_LIBC_PATH· 就可以了,比如

1
2
3
4
# adb shell 中执行如下操作
export IDA_LIBC_PATH=/apex/com.android.runtime/lib64/bionic/libc.so
 
/data/local/tmp/android_server64 -p9999

很遗憾,mac 是我的主力开发工具,使用的是 7.0 版本,所以需要修改 android_server64 二进制文件解决。经过调试 android_sever64 验证修改二进制的方式确实可以显示其他线程,调试及修改方式如下:

调试

  1. android_server64 拖入 ida64,查找字符串 /system/lib64/libc.so,找到引用位置,可以看到如下汇编,打断点
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    .text:000000000003AC68                loc_3AC68                               ; CODE XREF: sub_3AC00+A4↓j
    .text:000000000003AC68 74 1E 40 F9                    LDR             X20, [X19,#0x38]
    .text:000000000003AC6C E1 03 15 AA                    MOV             X1, X21
    .text:000000000003AC70 E0 03 14 AA                    MOV             X0, X20
    .text:000000000003AC74 7F 79 FF 97                    BL              .strcmp
    .text:000000000003AC78 80 02 00 34                    CBZ             W0, loc_3ACC8     ; 打断点
    .text:000000000003AC7C E0 03 14 AA                    MOV             X0, X20
    .text:000000000003AC80 E1 03 17 AA                    MOV             X1, X23
    .text:000000000003AC84 7B 79 FF 97                    BL              .strcmp
    .text:000000000003AC88 00 02 00 34                    CBZ             W0, loc_3ACC8
  2. 打开另一个 ida64,随便调试个 so 库(注意两个 android_server64 要使用两个端口,不然无法启动)
  3. 调试第二个 so 库后,会走到第 1 步打的断点上
  4. 执行 CBZ 命令前,主动修改 x0 寄存器,将其值设置为 0
  5. 观察第二个 ida threads 窗口,可以看到所有线程都现实出来了

所以,修改二进制只需要将断点处的 CBZ 修改成 CBNZ 就可以了,打补丁过程

  1. 定位到 CBZ 在 android_server64 位置
  2. 观察 CBZCBNZ 机器码,发现只有第 24 位有区别,即将当前 CBZ(80 02 00 34)改为 CBNZ(80 02 00 35)。
  3. 打开 ida Hex 视图编辑并保存
  4. 将修改后的 android_server64 push 到手机上运行后已经可以看到其他线程

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2024-4-23 19:13 被LGD丶ynlyxy编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (5)
雪    币: 19461
活跃值: (29125)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2024-4-23 14:41
2
1
感谢分享
雪    币: 6
活跃值: (1292)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
雨之后 2024-4-25 14:10
3
1
解决方案:   使用Windows
雪    币: 16154
活跃值: (5941)
能力值: ( LV13,RANK:861 )
在线值:
发帖
回帖
粉丝
大帅锅 4 2024-4-26 17:02
4
0
我给出的那个patch方案是你用ida附加后,直接将 __dl_notify_gdb_of_load 函数中两个 BL              rtld_db_dlactivity给NOP掉,然后用jdb附加
雪    币: 16154
活跃值: (5941)
能力值: ( LV13,RANK:861 )
在线值:
发帖
回帖
粉丝
大帅锅 4 2024-4-26 17:07
5
0
其实有更为简单的方式,但是有一定的缺点,可以用frida spwan的方式,然后用ida附加,然后再%resume,但是得看它有没有相应的检测方式,有的话需要过
雪    币: 204
活跃值: (51)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
LGD丶ynlyxy 2024-4-26 21:12
6
0
大帅锅 我给出的那个patch方案是你用ida附加后,直接将 __dl_notify_gdb_of_load 函数中两个 BL rtld_db_dlactivity给NOP掉,然后用 ...
是的,但是每次都 jdb -attach 也很不方便,不知道是不是我使用的问题,我用 jdb -attach 后加不了断点,后续调查原因,如果可以同时断点调试,还得需要调整解决方式,但是最终解决肯定是修改系统源码或者使用 magisk 模块解决,依赖外部工具会分散注意力,有的时候会遇到频繁启停调试时,外部工具会很麻烦。
游客
登录 | 注册 方可回帖
返回