BruceFan's Blog

Stay hungry, stay foolish

0%

Android Debug

Android代码调试

与Java一样,Dalvik实现了一个标准的调试接口,称为Java调试线协议(Java Debug Wire Protocol, JDWP)。所有用来调试Dalvik和Java上的程序的工具都是基于此协议开发的。 深入Java调试体系
Android设备监视器(Monitor)和Dalvik调试监视服务器(DDMS)都采用了JDWP标准协议,它们用JDWP访问指定应用的信息(线程、堆使用情况、正在进行的方法调用)

1.调试应用程序
点击工具栏中的Debug As图标(like a bug)进入Debug界面,想要返回代码界面点击右上角的Java按钮。

左上角的小窗是各个栈帧,点击某个栈帧会在代码窗口显示附近代码。
2.显示framework层源代码
点击栈帧时可以显示Android framework层源代码:首先要正确初始化AOSP资料库。文档
下一步,为Eclipse创建类路径。在AOSP根目录下运行make idegen命令创建idegen.sh脚本,在顶层目录下创建excluded-paths文件,以排除顶层目录下不想包含的所有目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
^abi/.*
^external/.*
^packages/.*
^cts/.*
^art/.*
^dalvik/.*
^development/.*
^prebuilts/.*
^out/.*
^tools/.*
^sdk/.*
^libcore/.*
^gdk/.*
^hardware/.*
^device/.*
^kernel/*
^pdk/*
^developers/*

新建一个Java Project命名为AOSP Framework Source,取消Use Default Location复选框,指定AOSP根目录。
调试刚才的示例应用,右键栈帧窗口中的项目名,选择Edit Source Lookup...Add路径为Java Project,选上一步创建的AOSP Framework Source项目,再点击父栈帧时会显示framework层的代码。

3.伪造调试设备
对于原厂设备,启动Android SDK中自带的DDMS或Monitor只显示可调试进程。

使用eng配置生成的工程设备允许访问所有进程。eng与user或userdebug之间的主要区别是系统属性ro.secure和ro.debuggable,user和userdebug生成时将这两个值设置为1和0;而eng生成时为0和1。
修改已root设备以支持调试系统服务和预装应用并不复杂,介绍一种简单但不是永久有效的方法,设备重启后失效。首先,获取一份setpropex工具,此工具可以在已root设备上修改只读的系统属性:

1
2
3
4
5
6
7
shell@hammerhead:/data/local/tmp $ su
root@hammerhead:/data/local/tmp # ./setpropex ro.secure 0
root@hammerhead:/data/local/tmp # ./setpropex ro.debuggable 1
root@hammerhead:/data/local/tmp # getprop ro.secure
0
root@hammerhead:/data/local/tmp # getprop ro.debuggable
1

断开shell连接,在主机上使用adb root命令以root权限重启ADB守护进程。
最后重启所有依赖Dalvik VM的进程。在修改ro.debuggable属性后启动的任何进程都是可调试的。为了强制重启Android Dalvik层,可以简单地结束system_server进程:

1
2
root@hammerhead:/data/local/tmp # ps
root@hammerhead:/data/local/tmp # kill -9 system_server_pid

设备重启后,Monitor中会出现所有的Dalvik进程。
4.附加到其他进程
处于完全调试模式下的设备也支持实时调试任何Dalvik进程。在Eclipse启动并处于运行状态下,点击右上角的DDMS,在Devices窗口中选择目标进程,比如system_process。在Run菜单中选择Debug Configurations打开对话框,在对话框左边双击Remote Java Application新建一个链接,Name设为Attacher,Connect选项卡中Project设为AOSP Framework Source项目,Host设为127.0.0.1,Port设为8700
最后点击Apply,点击Debug。

调试原生代码

关于如何使用原生代码编程可以参考《Android C++高级编程》
1.使用Eclipse进行调试
打开要调试的目标项目,首先,需要告知Android生成的应用必须支持调试:选择Project->Properties,点开C/C++生成选项并选择Environment,点击Add按钮,变量名输入NDK_DEBUG,值输入1。点击OK就可以开始调试了。为确保新的环境变量生效,选择Project->Build All
先在Java代码调用Native代码之前下断点,点击Debug As开启调试,再在Native代码中想要调试的位置下断点进行调试:

2.使用AOSP进行调试
编译AOSP代码,烧录到Nexus5设备上,具体过程可以参考我的另一篇文章
将GDB服务器二进制文件上传到设备:

1
2
android4.4.2 $ adb push prebuilts/misc/android-arm/gdbserver/gdbserver /data/local/tmp
android4.4.2 $ adb shell chmod 755 /data/local/tmp/gdbserver

调试过程使用标准的TCP/IP连接将GDB客户端连接到GDB服务器上。建议通过USB使用ADB进行调试。用ADB的端口转发功能为GDB客户端打开一个管道:

1
android4.4.2 $ adb forward tcp:31337 tcp:31337

下一步将GDB服务器执行目标程序或附加至进程:

1
2
3
4
5
6
7
8
9
~ $ adb shell
# 启动应用,也可以手动点开,命令行显得更专(zhuang)业(bi)一点
root@hammerhead # am start -n com.bruce.jnitest/.MainActivity
root@hammerhead # ps
...
u0_a90 15078 28517 926056 41384 ffffffff 4009573c S com.bruce.jnitest
...
root@hammerhead # ./gdbserver --attach tcp:31337 15078
# 或执行目标程序: ./gdbserver tcp:31337 ./debugfile

打开另一个终端:

1
2
3
4
5
6
android4.4.2 $ cd prebuilts/gcc/linux-x86/arm/arm-eabi-4.7/bin
bin $ ./arm-eabi-gdb -q
(gdb) target remote :31337
Remote debugging using :31337
0x4009573c in ?? ()
(gdb)

在另一个终端中adb连接到设备,查看so库加载的基址:

1
2
3
4
5
6
root@hammerhead # cat /proc/15078/maps
...
74f1c000-74f20000 r-xp 00000000 b3:1c 1638414 /data/app-lib/com.bruce.jnitest-1/libJniTest.so
74f20000-74f21000 r--p 00003000 b3:1c 1638414 /data/app-lib/com.bruce.jnitest-1/libJniTest.so
74f21000-74f22000 rw-p 00004000 b3:1c 1638414 /data/app-lib/com.bruce.jnitest-1/libJniTest.so
...

从中可以看到so库的基址是74f1c000,在IDA中找到想要调试的代码地址,加上基址得到实际内存中代码的地址:

切换到gdb客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(gdb) break *0x74f1d998
Breakpoint 1 at 0x74f1d998
(gdb) x/i 0x74f1d998
0x74f1d998: mov r3, #0
(gdb) c
Continuing.
# 触发应用调用so库中的native代码
Program received signal SIGILL, Illegal instruction.
0x74f1d99c in ?? ()
(gdb) disas 0x74f1d99c,+20
Dump of assembler code from 0x74f1d99c to 0x74f1d9b0:
=> 0x74f1d99c: str r3, [r11, #-48] ; 0x30
0x74f1d9a0: b 0x74f1d9d0
0x74f1d9a4: ldr r2, [r11, #-48] ; 0x30
0x74f1d9a8: mvn r3, #27
0x74f1d9ac: lsl r2, r2, #2
End of assembler dump.
(gdb)

剩下就可以使用GDB命令进行调试了。
3.使用IDA调试
GDB对thumb指令支持不好,调试thumb指令时最好还是用IDA。

  • 打开ddms
    打开ddms才能打开调试端口,才能用jdb
  • adb push android_server /data/local/tmp/
    android_server在IDA安装目录的dbgsvr目录中
    1
    2
    3
    4
    adb shell
    cd /data/local/tmp
    chmod 777 android_server
    ./android_server
  • adb forward tcp:23946 tcp:23946
  • adb shell am start -D -n com.bruce.jnitest
  • IDA attach到目标应用上
    Debugger->Attach->Remote ARMLinux/Android debugger

    选择com.bruce.jnitest,进程过多可以ctrl+F查找
  • suspend on library loading
    Debugger->Debugger options...

    选择suspend on libaray load/unload,然后按F9继续执行。
  • jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

    弹出的框都点cancel就行了,在右边的Modules窗口中找到要调试的so库双击。

    在新窗口中找到想要调试的函数,右键添加断点,继续执行程序。(在linker时会停多次,继续执行即可)

    下面触发应用调用库函数,控制流即停在断点处

    有时在一个函数里无法使用F5,这时在函数中按P,IDA会把这段代码作为函数分析,再按F5即可。

reference
《Android安全攻防权威指南》
http://drops.wooyun.org/tips/6840