android怎么动态调试dex

android怎么动态调试dex,第1张

1.1 安装JDK

JAVA环境的搭建请自段启察行查找资料,这里不做详述。

1.2 安装Android SDK

下载地址:http://developer.android.com/sdk/index.html。

下载完安装包后解压到任意一目录,然后点击运行SDK Manager.exe,然后选择你需要的版本进行安装,如图:

1.3 安装Eclipse集成开发环境

下载地址:http://www.eclipse.org/downloads。选择Eclipse for Mobile Developers,解压到任意目录即可。

1.4 创建Android Virtual Device

动态调试可以用真实的手机来做调试环境,也可以用虚拟机来做调试环境,本文采用虚拟机环境。因此创建虚拟机步骤如下:

1打开Eclipse –>windows->Android Virtual Device

握茄2点击Create,然后选择各个参数如图:

这里Target 就是前面步骤中安装的SDK 选择任意你觉得喜欢的版本就可以。点击OK 就创建完毕。

1.5 安装 APK改之理

这个是一个很好用的辅助调试的软件,请自行搜索下载。

1.6 安装 IDA6.6

IDA6.6开始支持安卓APP指令的调试,现该版本已经提供免费下载安装,请自行搜搜。

0x02 Dalvik指令动态调试

2.1 准备工作

安卓APP应用程序后缀为apk,实际上是一个压缩包,我们把它改后缀为rar打开如图:

其中classes.dex是应用的主要执行程序,包含着所有Dalvik指令。我们用APK改之理打开apk,软件会自动对其进行反编译。反编译后会有很多smail文件,这些文件保存的就是APP的Dalvik指令。

在APK改之理里双击打开AndroidManifest.xml,为了让APP可调试,需要在application 标签里添加一句android:debuggable="true" 如图:

然后点击保存按钮,然后编译生成旁颤新的apk文件。接着打开Eclipse –>windows->Android Virtual Device,选择刚才创建的虚拟机,然后点击start,虚拟机便开始运行。偶尔如果Eclipse启动失败,报错,可以同目录下修改配置文件:

把配置参数原本为512的改为256 原本为1024的改为512,然后再尝试启动。

在SDK安装目录有个命令行下的调试工具adb shell,本机所在目录为E:\adt-bundle-windows-x86-20140702\sdk\platform-tools,把adb.exe注册到系统环境变量中,打开dos命令行窗口执行adb shell 就可以进入APP命令行调试环境,或者切换到adb所在目录来执行adb shell。

这里先不进入adb shell,在DOS命令行下执行命令:adb install d:\1.apk 来安装我们刚才重新编译好的APK文件。安装完毕会有成功提示。

2.2 利用IDA动态调试

将APP包里的classes.dex解压到任意一目录,然后拖进IDA。等待IDA加载分析完毕,点击Debugger->Debugger Options如图

按图所示勾选在进程入口挂起,然后点击Set specific options 填入APP包名称和入口activity 如图:

其中包的名称和入口activity 都可以通过APK改之理里的AndroidManifest.xml 文件获取:

1

2

3

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.simpleencryption">

<application android:allowBackup="true" android:debuggable="true" android:icon="@drawable/creakme_bg2" android:label="@string/app_name" android:theme="@style/AppTheme">

<activity android:label="@string/app_name" android:name=".MainActivity">

然后在IDA点击Debugger->Process Options

其他默认不变,端口这里改为8700。这里默认端口是23946,我在这里困扰了很久,就是因为这个端口没有改为8700所致。然后我们看看这个8700端口是怎么来的。在Android SDK里提供了一款工具DDMS,用来监视APP的运行状态和结果。在SDK的TOOLS目录有个DDMS.BAT的脚步,运行后就会启动DDMS。由于我的本机安装了SDK的ADT插件,DDMS集成到了Eclips中,打开Eclips->Open perspective->ddms就启动了DDMS。

如图所示:

在DDMS选中某个进程后面就会注释出它的调试端口,本机这里是8700。

到此所有的工作就准备就绪,然后就可以下断点来调试该APP了。我们在APK改之理中在com目录下查看smali文件 发现MainActivity.smali里有一个感兴趣的函数getPwdFromPic(),那么我们就对它下断以跟踪APP的运行。

在IDA里搜索字符串getPwdFromPic,发现onClick有调用该函数

我们在onClick 函数开始位置按F2下断如图:

然后点击上图中绿色三角形按钮启动调试如图:

调试过程中有一个问题出现了很多次,浪费了我大量的时间,就在写文章的时候, *** 作时还是遇到了这样的问题。就是点击启动后IDA提示can’t bind socket,琢磨了很久终于找到原因了,当打开过一次DDMS后 每次启动Eclips都会启动DDMS 而8700端口正是被这个DDMS给占用了,然后每次都会启动失败,解决办法就是 虚拟机运行起来后关闭掉Eclips,这时一切就正常了!

事例中是一个APP crackme 提示输入密码才能进入正确界面。这个时候我们输入123,点击登陆,IDA中断在了我们设置断点的地方,这时选中ida->debugger->use source level debugger,然后点击ida->debugger->debugger windows->locals打开本地变量窗口,如图:

然后按F7或F8单步跟踪程序流程,同时可以观察到变量值的变化,也可以在IDA右键选择图形视图,可以看到整个APP执行的流程图:

如上图所示 变量窗口中我们输入了123 被转化成的密码是么广亡,pw变量也显示出了正确的密码,其实这个时候已经很容易判断出正确密码了。

0x03 Andoid原生动态链接库动态调试

通常为了加密保护等措施,有时dex执行过程中会调用动态链接库文件,该文件以so为后缀,存在于APP文件包里。

这里我们以动态附加的方式来调试原生库。

3.1 准备工作

1、将IDA->dbgsrv目录下的android_server拷贝到虚拟机里,并赋予可执行权限

DOS命令分别为:

adb shell pull d:\ android_server /data/data/sv

adb shell chmod 755 /data/data/sv

2、启动调试服务器android_server

命令:adb shell /data/data/sv

服务器默认监听23946端口。

3、重新打开DOS窗口进行端口转发,命令:

adb forward tcp:23946 tcp:23946 如图:

3.2 利用IDA进行动态调试

1、虚拟机里启动要调试的APP 2、启动IDA,打开debugger->attach->remote Armlinux/andoid debugger

端口改为23946 其他保持不变,点击OK

如上图,选中要调试的APP 的数据包名,然后点击OK。

正常情况下,IDA会把APP进程挂起。

3、由于当前程序不是在动态链接库领空,这时我们要重新打开一个IDA,用它打开需要调试的so文件,找到需要下断的位置的文件偏移,并做记录,然后关闭后面打开的这个IDA。

4、在原IDA界面按下ctrl+s键,找到并找到需要调试的so,同时记录该文件的加载基址。然后点击OK 或者cancel按钮关闭对话框。

5、按下快捷键G 输入基址+文件偏移所得地址,点击OK 就跳转到SO文件需要下断的地方,这时按下F2键设置断点。当APP执行到此处时便可以断下来。

3.3 在反调试函数运行前进行动态调试

程序加载so的时候,会执行JNI_OnLoad函数,做一系列的准备工作。通常反调试函数也会放到JNI_OnLoad函数里。进行4.2中第2步时也许会遇到如下情况:

这时APP检测到了调试器,会自动退出,那么这时调试策略需要有所改变。

接着4.1第3步后,在DOS命令行执行命令:

adb shell am start -D -n com.yaotong.crackme/com.yaotong.crackme.MainActivity

来以调试模式启动APP 如图:

com.yaotong.crackme是APP包名称,com.yaotong.crackme.MainActivity是执行入口 这些可以用APK改之理查看。

这时由于APP还未运行,那么反调试函数也起不了作用,按照4.2中第2步把APP挂起。这时IDA会中断在某个位置

然后点击debugger->debugger opions设置如下:

点击OK 后按F9运行APP,然后再DOS命令下执行命令:

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

这时APP会断下来,然后按照4.2中的3、4、5补找到JNI_OnLoad函数的地址并下断,然后按F9 会中断下来。然后便可以继续动态跟踪调试分析。

姓名:罗学元    学号:21181214375     学院:广州研究院

【嵌牛导读】什么是ELF文件

【嵌牛鼻子】什么是ELF文件

【嵌牛提问】什么是ELF文件,它有哪些部分组成、每部分包含哪些信息

          ELF文件分为四个部分:elf header,program header table,section header table,dynamic symbol table。其中节头表(section header  table) 和 段头表(program header table) 中用到的数据相同,只是组织方式不同。

一、ELF header

每个ELF文件都必须存在一个ELF_Header,这里存放了很多重要的信息用来描述整个文件的组织,如: 版本信息,入口信息,偏移信息等,程序执行也必须依靠其提供的信息:

数据结构如下:

e_xxx 和上面对应表如下图:

其中数据类型如下:

二、Program header table 程序头表

        存储so文件运行时所需要的信息,这部分信息会直接被linker使用,用于加载so文件,告诉系统如何在内存中创建映像,在图中也可以看出来,有程序头部表才有段,有段就必须有程序头部表,其中存放各个段的基本信息(包括地址指针)

节到段的映射:

    链接视图是以节(section)为单位,执行视图是以乱姿配段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是哗指在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是册改执行来看的。

段(Segment): 就是将文件分成一段一段映射到内存中,段中通常包括一个或多个节区。

       那么为什么需要节和段两种视图? 当ELF文件被加载到内存中后,系统会将多个具有相同权限(flg值)section合并一个segment。 *** 作系统往往以页为基本单位来管理内存分配,一般页的大小为4096B,即4KB的大小。同时,内存的权限管理的粒度也是以页为单位,页内的内存是具有同样的权限等属性,并且 *** 作系统对内存的管理往往追求高效和高利用率这样的目标。ELF文件在被映射时,是以系统的页长度为单位的,那么每个section在映射时的长度都是系统页长度的整数倍,如果section的长度不是其整数倍,则导致多余部分也将占用一个页。而我们从上面的例子中知道,一个ELF文件具有很多的section,那么会导致内存浪费严重。这样可以减少页面内部的碎片,节省了空间,显著提高内存利用率。

readelf -S xxx # 用来查看可执行文件中有哪些section,如下图:

readelf --segments xxx # 可以查看该文件的执行视图,下图红框部分为上图的节信息在段中的显示:

    最后加载进内存的只有program header table 程序头表里的load段,其他都只是描述信息,加载过程中用到,但是最后加载进去内存的只有load段。

三、Section header table 节头部表

           类似与程序头部表,但与其相对应的是节区(Section);节区(Section): 将文件分成一个个节区,每个节区都有其对应的功能,如符号表,哈         希 表 等。

.relname和.relaname: 010Editor打开so,展现形式为下图,.rel.dyn 和 .rel.plt ,是用来重定向dyn和plt的,也就是静态情况下,存放偏移值,如果进行动态调试的时候,就会加上基址变成绝对地址(重定向)。

下面第二张图中,左边红框就是偏移值,右边红框只要把基址加进来,就是绝对地址,把基址加进来的过程就是重定向的过程:

.plt 程序链接表,用于做映射关系,拿到依赖so的绝对地址,做重定向的:

四、Dynamic symbol table

     这里是符号表,也就是会用到的所有函数名称表,包括自己写的函数和依赖的系统so中的函数,到时候.plt会对这部分重定向 。

在So动态链接后,读取ELF文件,发现无法读取Section Header中的名称列表。即,无法在 EShdr 中根据 e_shstrndx 找到Section对应的名字。

可是在 readelf -a lib.so 中,可以根据Section Header读取到Section对应的名称。

而读取出来的结果如下:

其中, shstrtab 和 strtab 的类型都是 STRTAB ,但是 shstrtab 仅仅只保存Section Name的字符串表,而 strtab 则包括其他的变量名、符号名等的字符串表。

运行日志:

运行后的结果可知:

通过 e_shoff 所计算的出来的 SHDR 的地址已经超出So加载的地址了。

SHDR - libart.so的页结束地址 = 7530b55520 - 7530a14000 = 141520 。

而在运行时候的动态链接是根据Segment来加载So中的文件,原因是希望尽可瞎橡能小的使用内存页面,并且提升加载速度。

于简禅是查看程序头部分,发现 LOAD 类型的段中,仅仅只有 .dynstr 这个字符串表会被加载到内存中。

也就是说:

在So动态链接到内存中时, .shstrtab 和 .strtab 这两个Table是并没有加载到内存中的。ld仅仅只会加载 .dynstr 这个Table就够用了。

并且从执行视图来看,ELF也不一定会有Section Header Table。

而且单个 Section 可能属于多个 Segment :

从Section Headers中可以看到:

可以看到 strtab 之后紧接着就是 shstrtab :

395ac + 170c = 3acb8

打开IDA Pro可以看到在文件的 395ac 处就是 strtab 的字符串表:

而在文件的 3acb8 处,可以看到是Section Header Name的字符串:

shstrtab 与 strtab 这两个表仅仅只是链接后保存在So文件中的,而在链接之后的执行视图层面,这两个字符串表不会磨咐旁被加载到内存中。

在 readelf 这个程序中,会在文件中根据 shstrtab 表的偏移量来查找Section对应的名称,然后输出文案。

并且,在执行视图中,可能没有 SHDR ,所以在链接完的文件中可以根据 SHDR 中的偏移量来找到对应的名字,而在加载到内存之后的执行视图中,不能按照 SHDR 来查找Section的名字了。

在 Android GOT Hook 该文中,则是通过对GOT表进行Hook,而查找GOT表的方式则是通过 Section Header 以及 shstr 来找到对应的Got表的偏移地址。

而在本人测试的过程中发现这方案在Runtime时并不可行,在动态链接后, Section Header 有可能不会加载,因为 Section Header 的加载可有可无,可能有些ROM会加载,有些ROM不会加载。

在 ELF中可以被修改又不影响执行的区域 该文中,本人比较认同。因为 Program Header 已经将需要加载的Section都决定了, Section Header 只是为了 readelf 这种方便读取ELF文件而存在。减少加载也可以减少内存以及文件映射所带来的损耗。

所以对于Got表的偏移量可以在静态的时候,通过 readelf 来读取Got表的偏移量,在Runtime的时候,通过基址加偏移量来获取Got表的地址。 Android下的GOT表Hook实现 这篇文章描述了3中Got表Hook的方式。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/12224627.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-22
下一篇 2023-05-22

发表评论

登录后才能评论

评论列表(0条)

保存