Android 程序的动态调试

  • 编译生成原生程序
    • 使用ndk-build编译原生程序
    • 使用Eclipse自动编译原生程序
  • 使用IDA Pro调试Android原生程序
    • 远程运行调试Android原生可执行程序
    • 远程附加调试Android原生动态链接库
  • 总结IDA调试的流程
  • 一些问题及解决方法

编译生成原生程序

  • NDK简介:

    NDK(英语:Native Development Kit,简称NDK)是一种基于原生程序接口的软件开发工具。通过此工具开发的程序直接以本地语言运行,而非虚拟机。因此只有Java等基于虚拟机运行的语言的程序才会有原生开发工具包。

  • NDK是一系列工具的集合:

    NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的;

    NDK集成了交叉编译器,并提供了相应的.mk文件以隔离CPU、平台、ABI等差异,开发人员只需要简单修改.mk文件,指出“哪些文件需要编译”、“编译特性要求”等,就可以创建出.so文件;

    NDK可以自动地将.so文件和Java应用一起打包,极大地减轻了开发人员的打包工作。

  • 我们为什么要使用NDK?

    a. 代码的保护。由于apk的java层代码很容易被反编译,而C/C++库的反汇编难度较大;

    b. 可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的;

    c. 提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率;

    d. 便于移植。用C/C++写出的库可以方便地在其他嵌入式平台上再次使用。

使用ndk-build编译原生程序

总体思路:

在Eclipse中建立一个Android工程,在工程的根目录下新建一个名称为jni的文件夹,将编写好的 test.c文件复制到此文件夹中,或者直接在jni文件夹下新建一个文件,命名为test.c,接着编写ndk-build所需的脚本文件。

Android.mk文件是工程的编译脚本,告知编译系统关于源文件的一些信息,描述了编译原生程序所需的编译选项、头文件、源文件等等。

一个Android.mk文件由若干条定义语句组成,此文件中常用到的几个语句包括:

  • LOCAL_PATH := $(call my-dir)
    Android.mk文件开头必须先定义好LOCAL_PATH变量。LOCAL_PATH定义了本地源码的路径,它用于在开发树中查找源文件。call my-dir指定了调用my-dir宏,这个宏函数是编译系统提供的,用于返回Android.mk文件本身所在的路径。

  • include $(CLEAR_VARS)
    CLEAR_VARS由编译系统提供,指定让编译系统清除掉一些已经定义过的宏(如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等等),这些宏的定义都是全局的。当一个GNU MAKE在编译多个模块时,必须清除并重新设置它们。

  • LOCAL_ARM_MODE := arm
    LOCAL_ARM_MODE指定生成的原生程序所使用的ARM指令模式,取值为arm或者thumb。arm表示使用32位的arm指令系统,thumb表示使用16位的arm指令系统。

  • LOCAL_MODULE :=test
    LOCAL_MODULE指定模块的名称,即原生程序生成后的文件名。注意,如果生成共享库模块,编译系统会自动产生合适的前缀和后缀,对于本例中的源文件最终将生成名为libtest.so的共享库。

  • LOCAL_SRC_FILES := test.c
    LOCAL_SRC_FILES指定将要编译打包进模块中的C\C++源代码文件列表,此处只有一个test.c文件。

  • include $(BUILD_SHARED_LIBRARY)
    此语句用来指定生成的文件类型。BUILD_EXECUTABLE表示生成可执行程序,BUILD_SHARED_LIBRARY表示生成动态库BUILD_STATIC_LIBRARY表示生成静态库

实验步骤:

①安装Android NDK:

在电脑上安装NDK,我使用的版本为android-ndk-r8-windows-x86_64。安装完成后,新建一个系统变量,命名为NDK_ROOT,值为安装路径,最后将%NDK_ROOT%添加到环境变量Path中后,Android NDK安装完成。

在DOS中输入ndk-build进行测试,输出如下信息,说明Android NDK已经正常安装了。




②新建一个Android工程,命名为JNI,右键工程JNI,新建一个文件夹,将其命名为jni。

新建Android工程JNI,如图所示:


在工程中新建一个文件夹,命名为jni,如图所示:


添加文件夹jni后的工程结构如图所示:


③在JNI工程的jni文件夹下新建两个文件,按下图所示命名:


其中Android.mk的内容如图所示:


test.c的内容如图所示:


④Android.mk与test.c两个文件编写完后,在DOS窗口下进入工程下的jni目录。输入ndk-build命令,在工程根目录下生成了一个libs文件夹,其中的armeabi文件夹中就存放着生成的可执行文件或者共享库文件(.so文件)。

  • Android.mk中最后一行为BUILD_EXECUTABLE时,可以生成名为test的可执行程序;

  • 而Android.mk中最后一行为BUILD_SHARED_LIBRARY时,可以生成名为libtest.so的共享库文件。

在DOS中,进入工程文件夹下包含Android.mk与test.c两个文件的jni目录,如图所示:


当Android.mk中最后一行为上面的 BUILD_EXECUTABLE 时,输入 ndk-build 命令:


执行命令后,在工程根目录下生成了一个libs文件夹,其中的armeabi文件夹中生成了名为test的可执行程序,如图所示:


将Android.mk中最后一行修改为 BUILD_SHARED_LIBRARY,如图所示:


再次执行ndk-build命令,生成共享库文件libtest.so如图:


在工程根目录下的libs\armeabi文件夹中生成了名为libtest.so的共享库文件,如下图所示:


将生成的可执行程序test复制到模拟器中,在命令行窗口下输入命令让其运行,如图所示:


可见成功输出了字符串“JNI Test!”。

使用Eclipse自动编译原生程序

由于C/C++代码需要用ndk-build来进行编译,而java代码需要用Android sdk编译,所以为了开发快捷,需要在每次更改完C语言代码后可以自动编译C语言为.so库。使用Eclipse自动编译原生程序的原理依旧是使用ndk-build工具,但是自动化的操作会使原生程序的开发更加高效。

①在Eclipse中配置NDK:

要想使用Eclipse自动编译原生程序,首先需要在Eclipse中对NDK进行配置。打开Eclipse,进入Eclipse窗口中,在 Window—>Prefernces—>Android—>NDK 选择NDK Location的存放路径后,点击Apply and Close,如图所示:

②Eclipse自动编译原生程序:

首先,新建一个Build,此后编写代码时,保存修改后,Eclipse会自动编译生成原生程序。

第一步,在JNI工程上右键选择Properties,点击Builders选项,再点击Builders选项页右侧的New按钮,然后双击Program项打开Edit Configuration对话框,在对话框的Name栏输入Builder的名称,这里输入“JNI_Builder”,在 Location 栏输入“F:\android-ndk-r8\ndk-build.cmd” 设置要执行的命令,点击Working Directory右侧的Browse Workspace按钮选择JNI工程,最后点击Apply按钮应用更改,操作完成后,效果如图所示:


然后,单击Refresh标签,勾选“Refresh resources upon completion”复选框,如图所示:


下一步,单击Build Options标签,勾选“During auto builds”与“Specify working set of relevant resources”复选框,点击“Specify Resources”按钮,勾选JNI工程的jni目录,点击finish按钮,点击OK按钮关闭“Edit Configuration”对话框,如图所示:


最后,点击OK按钮关闭Properties对话框,这时JNI工程就会自动编译,可以在libs\armeabi目录下生成test.c文件的可执行文件test,如图所示:


而若将Android.mk中最后一行修改为BUILD_SHARED_LIBRARY,则Eclipse则会自动将其编译为libtest.so文件,如图所示:


以后若每次在Eclipse中对jni目录下的任何文件进行修改或保存,都会触发JNI_Builder重新编译原来的工程,直接简便。

但如果是使用ndk-build手动编译工程,那么每次修改完jni目录下的文件后都要在命令行窗口下对工程进行重新手动编译,相对来说就比较麻烦。

使用IDA Pro调试Android原生程序

现在的许多App为了安全或者效率的问题,会把一些重要的功能放到native层。一般在Android中,native层使用的是so库文件。

使用NDK开发能够编译C/C++程序,最终生成so文件。而.so文件是一个二进制文件,我们是无法直接分析.so文件的,所以这里需要用到IDA Pro。IDA Pro能够对so文件进行反汇编,从而将二进制代码转化为汇编语言,利用IDA Pro的F5功能还能将汇编语言反编译成C/C++程序。

使用IDA Pro调试Android原生程序一般有远程运行远程附加两种方式,远程运行调试用来调试原生可执行程序,远程附加调试用来调试Android原生动态链接库

远程运行调试Android原生可执行程序

首先,需要用到前面实验编写的原生可执行程序实例test,以及IDA Pro软件目录下的android_server文件,找到后我把它们放到了一个新创建的名为tmp的文件夹中。

android_server所在路径为:


然后,进入tmp文件夹所在路径,将test与android_server两个文件复制到模拟器的data/local/tmp目录中,在DOS中执行以下命令可以对文件进行上传:

adb push test /data/local/tmpadb push android_server /data/local/tmp

执行结果如下图所示:


之后进入终端查看上传是否成功:


如上图所示,可以看到两个文件已经上传完成。

然后,执行以下两行命令给两个文件加上可执行权限:

chmod 755 /data/local/tmp/testchmod 755 /data/local/tmp/android_server

如图所示:


接着执行“./android_server”,以启动android_server,如下图所示:


打开另一个命令窗口,然后再看,这里监听了设备的23946端口,那么如果要想让IDA Pro和这个android_server进行通信,必须让PC端的IDA也连上这个端口,这时候就需要借助于adb命令:

adb forward tcp:远端设备端口号(进行调试程序端) tcp:本地设备端口(被调试程序端)

这里端口号均为23946,然后就可以把android_server端口转发出去了:

这时,我们只要在PC端使用IDA Pro连接23946这个端口就可以了,因为后面在使用IDA Pro进行连接的时候,IDA Pro把这个端口设置死了,就是23946,所以我们没办法自定义这个端口。

执行后,启动IDA Pro,这里要使用IDA Android 32-bit,所以在打开IDA的时候一定要是32位的IDA,不能是64位的,否则保存后就会有两个可执行程序,一个是32位,一个是64位,如果打开不正确则会报错。

点击菜单栏“Debugger->Run->Remote AmLinux/Android debugger”,打开调试程序对话框。这里看到,端口是给定的:23946,不能进行修改,所以上面的adb forward进行端口转发的时候必须是23946。这里PC本地机就是调试端,所以host就是本机的ip地址:127.0.0.1

在Application栏输入“/data/local/tmp/test”,在Directory栏输入“/data/local/tmp”,在Hostname栏输入“localhost”或者“127.0.0.1”,如下图所示 :


设置完成后点击OK按钮,IDA Pro就会远程执行test,并自动切换到调试界面,如下图所示:

远程附加调试Android原生动态链接库

调试Android原生动态链接库需要先安装并运行包含该动态链接库的程序,然后使用IDA ‍Pro远程附加程序进程的方式来进行调试。

编写实例程序JavaCTest,导出为APK文件,如图所示:


将其导入到Android模拟器中,界面如图所示:


点击“Change Text”按钮后,程序会调用动态链接库libjavac.so库中的stringFromJNI()方法返回一个字符串“Java C Test!”,并且更改字符串“Hello Xidain!”为此字符串。

接下来,对libjavac.so中stringFromJNI()方法的执行过程进行动态调试。执行下面的命令启动IDA Pro的Android调试服务器:

adb shell ./android_server

命令执行成功后,Android调试服务器会监听23946端口,结果如下图所示:


打开另一个命令行窗口,输入以下命令进行端口转发。

adb forward tcp:23946 tcp:23946

启动IDA Pro,点击菜单项“Debugger→Attach→Remote ARMLinux/Android debugger”,打开调试程序的设置对话框。在Hostname栏中输入127.0.0.1,如下图所示:


然而,此时代码并没有运行在动态链接库部分,要想调试动态链接库,就得为动态链接库中的函数设置断点。将JavaCTest.apk解压,找到libjavac.so文件,开启另一个IDA实例并载入它,找到stringFromJNI() 方法的代码,如下图所示:


从上图反汇编的代码中可以看出,stringFromJNI()方法的代码起始处位于 0x00002050 处。回到IDA Pro的调试窗口,按下“Ctrl+S”快捷键打开段选择对话框,查找libjavac.so动态链接库的基地址,此处的基地址为 0x00001F3C,如下图所示:


这里的跳转地址是可以计算的,如果想要跳转到某函数,然后下断点,那么可以使用 CTRL+S 查找到.so文件的内存开始的基地址,然后再用IDA View中查看该函数对应的相对地址,相加就是绝对地址,然后跳转即可。

对于一般的基地址,只要程序没有退出,在运行中它的值就不会改变,因为在程序的数据已经加载到内存中后,基地址就不会改变,除非程序退出后又重新运行,把数据又重新加载到内存中。同时相对地址也是不会改变的,只有在修改.so文件的情况下,如果文件大小改变了,相对地址可能会改变,其他情况下不会改变,相对地址就是数据在整个.so文件中的位置。

内存地址等于基地址加上偏移地址,由此可以计算出stringFromJNI()方法的内存地址为 0x00003F8C。关闭段选择对话框,按下快捷键G,打开地址跳转对话框,在“Jump to address”栏中输入 0x00003F8C,如下图所示:


点击OK按钮,程序会跳转到 stringFromJNI() 方法所在的代码行,并自动分析方法的代码。在 0x00003F8C 行按下快捷键F2设置一个断点,被设置断点的代码行会被红色标记,如下图所示:


设置好断点后,回到程序中点击“Change Text”按钮,程序就会自动中断在 0x00003F8C 行上,接下来就可以调试动态链接库中的原生程序了。

总结IDA调试的流程

  1. 解压APK文件,得到对应的so文件,然后使用IDA工具打开so,找到指定的native层函数;
  2. 通过IDA中的一些快捷键:F5, Ctrl+S, Y 等,静态分析函数的ARM指令,大致了解函数的执行流程;
  3. 再次打开一个IDA来进行调试so;
    ①将IDA目录中的android_server拷贝到设备的指定目录下,修改android_server的运行权限,以Root身份运行android_server;
    ②使用adb forward进行端口转发,让远程调试端的IDA可以连接到被调试端;
    ③使用IDA连接转发的端口,查看设备的所有进程,找到需要调试的进程;
    ④通过打开so文件,找到需要调试的函数的相对地址,然后在调试页面使用 Ctrl+S 找到so文件的基地址,相加之后得到绝对地址,使用 G 键,跳转到函数的地址处,下好断点,点击运行;
    ⑤触发native层的函数,并使用 F8F7 进行单步调试,查看关键寄存器的值,例如函数的参数或者函数的返回值等信息。

一些问题及解决方法

1.问题:

一开始,我在运行IDA Pro的Android服务器时,DOS命令下总是提示:not executable:magic 7F45,如图所示:


最初我以为是执行权限的问题。然而在修改权限后,依旧不能运行。把文件清空后,重新安装,还是不能奏效。

在查找了相关资料、反复调试后,我终于找到了出错原因:
因为我在Android.mk中使用的是arm的ABI,而我的Android模拟器却使用的是x86的内核,当然不能正常运行。

所以需要安装对应ABI为arm的虚拟机才可以。

2.解决方法:

在AVDM中,安装一个ABI为 arm 的Android模拟器,如下图所示:


安装好的新设备如图所示:


Android模拟器安装好后,进行验证如下:


到此,可以看到Android服务器已经打开了 23946 端口,并进行监听,说明安装成功。

更多相关文章

  1. Mac上如何使用adb命令进行操作?(Android(安卓)studio 环境变量配置
  2. Android内核的简单分析
  3. Android学习小结
  4. Android(安卓)adb的使用略解
  5. 大仙说道之Android(安卓)studio实现Service AIDL
  6. Android(安卓)Studio gradle配置详解
  7. android日记
  8. Android(安卓)中自定义属性(attr.xml,TypedArray)的使用
  9. Android的多媒体框架OpenCore(PacketVideo)介绍

随机推荐

  1. Android下 读写文件
  2. android recover 系统代码分析 -- 选择进
  3. 2020最新Android大厂面试真题大全(附答案)
  4. 详解Android(安卓)消息处理机制
  5. Android(安卓)UI组件框架AndroidMaterial
  6. android layout_weight的使用
  7. 为Android系统定制重启功能
  8. android OpenGL开发使用JPCT-AE引擎显示3
  9. 学习Android Handler消息传递机制
  10. Android新手入门1