- 对本书的赞誉
- 前言
- 基础篇
- 第 1 章 Android 中锁屏密码加密算法分析
- 第 2 章 Android 中 NDK 开发
- 第 3 章 Android 中开发与逆向常用命令总结
- 第 4 章 so 文件格式解析
- 第 5 章 AndroidManifest.xml 文件格式解析
- 第 6 章 resource.arsc 文件格式解析
- 第 7 章 dex 文件格式解析
- 防护篇
- 第 8 章 Android 应用安全防护的基本策略
- 第 9 章 Android 中常用权限分析
- 第 10 章 Android 中的 run-as 命令
- 第 11 章 Android 中的 allowBackup 属性
- 第 12 章 Android 中的签名机制
- 第 13 章 Android 应用加固原理
- 第 14 章 Android 中的 so 加固原理
- 工具篇
- 第 15 章 Android 逆向分析基础
- 第 16 章 反编译神器 apktool 和 Jadx
- 第 17 章 Hook 神器 Xposed
- 第 18 章 脱壳神器 ZjDroid
- 第 19 章 Native 层 Hook 神器 Cydia Substrate
- 操作篇
- 第 20 章 静态方式逆向应用
- 第 21 章 动态调试 smali 源码
- 第 22 章 IDA 工具调试 so 源码
- 第 23 章 逆向加固应用
- 第 24 章 逆向应用经典案例分析
- 第 25 章 Android 中常见漏洞分析
- 第 26 章 文件加密病毒 Wannacry 样本分析
21.2 案例分析
因为逆向是动手操作的过程,所以这里用一个例子来介绍如何操作。本节用阿里 2014 年安全挑战赛的第一题:AliCrack_one.apk,如图 21-1 所示。
图 21-1 破解样本
这个题目输入密码就可以破解了,下面就来看看如何获取这个密码。
第一步:使用 apktool 来破解 apk
命令如下:
命令不做解释了,但是有一个参数必须带上,那就是:-d。因为这个参数代表反编译得到的 smali 是 Java 文件,这里指文件后缀名是 Java,如果不带这个参数,后缀名是 smali 的,但是 Eclipse 中是不会识别 smali 文件的,而能识别 Java 文件,所以这里一定要记得加上这个参数。
反编译成功之后,我们得到了一个 out 目录,如图 21-2 所示。
图 21-2 反编译之后的目录
源码都放在 smali 文件夹中,如图 21-3 所示,进入查看一下文件。
图 21-3 smali 代码目录
这里全是 Java 文件,其实只是后缀名为 Java 了,内容还是 smali 文件:
第二步:修改 AndroidManifest.xml 中的 debug 属性
上面反编译成功了,下面为了后续的调试工作,所以还是需要做两件事:
1)修改 AndroidManifest.xml 中的 android:debuggable=“true”。
这个属性在前面介绍 run-as 命令的时候也提到了,它标识这个应用是否是 debug 版本,这将会影响到这个应用是否可以被调试,所以这里必须设置成 true。
2)在入口处添加 waitForDebugger 代码进行调试等待。
这里说的入口处就是程序启动的地方,就是一般的入口 Activity。查找这个 Activity 的方法太多了,比如这里直接从上面得到的 AndroidManifest.xml 中找到,因为入口 Activity 的 action 和 category 是固定的:
当然还有其他方式,比如用 aapt 查看 apk 的内容方式,或者安装 apk 之后用 adb dumpsys activity top 命令查看。找到入口 Activity 之后,直接在它的 onCreate 方法的第一行加上 waitForDebugger 代码即可,找到对应的 MainActivity 的 smali 源码,然后添加一行代码:
这个是 smali 语法的,其实对应的 Java 代码就是:
注意:其实还有一种更方便的方式,就是使用 am 命令,以 debug 模式启动一个应用:
后面再进行调试的时候都会采用这种方式了。
第三步:回编译 apk 并且进行签名安装
代码如下:
还是使用 apktool 进行回编译:
编译完成之后,将得到 debug.apk 文件,但是这个 apk 是没有签名的,所以是不能安装的,那么下面需要进行签名。这里使用 Android 中的测试程序的签名文件和 sign.jar 工具进行签名,如图 21-4 所示。
图 21-4 签名工具 signapk.jar
命令如下:
签名之后,就可以进行安装了。
第四步:将 smali 源码导入到 Eclipse 中
这里新建一个 Java 项目,记住不是 Android 项目,因为最后的调试工作其实是借助于 Java 的调试器,然后勾选“Use default location”选项,选择 smali 源码目录,也就是上面反编译之后的 out 目录,点击完成,如图 21-5 所示。
图 21-5 Eclipse 中导入 smali 项目
导入源码之后的项目工程结构如图 21-6 所示。
主要看 MainActivity 类,如图 21-7 所示。
第五步:找到关键点,然后打断点
这一步要具体问题具体分析。比如这个例子中,当输入密码之后,肯定要点击按钮,然后触发密码的校验过程,那么要找到这个 button 的定义的地方,然后进入它的点击事件中就可以了。这里分为三步走:
1)使用 Eclipse 自带的 View 分析工具找到 Button 的 ResId,如图 21-8 所示。点击之后,需要等待一会,分析 View 之后的结果,如图 21-9 所示。
这里能够看到整个当前页面的全部布局,以及每个控件的属性值,需要找到 button 的 resource-id,这里看到定义是 @+id/button 这个值。
2)得到这个 ResId 之后,能否在 smali 项目中全局搜索这个值,就可以定位到这个 button 的定义的地方呢?如图 21-10 所示。然后看看搜到的结果,如图 21-11 所示。
是在资源文件中搜到了这个 id 的定义,这个 id 值对应的是 0x7F05003E。当然除了这种方式,还有一种方式能快速找到这个 id 对应的整型值,那就是在反编译之后的 values/public.xml 文件中,如图 21-12 所示。
这个文件很有用,它是整个 apk 中所有资源文件定义的映射内容,比如 drawable/string/anim/attr/id 等这些资源文件定义的值、名字和整型值对应的地方,如图 21-13 所示。
图 21-6 导入成功效果图
图 21-7 入口类代码
图 21-8 view 分析工具按钮
图 21-9 分析工具效果图
图 21-10 全局搜索关键字
图 21-11 全局搜索结果
图 21-12 public.xml 文件
图 21-13 public.xml 内容
这个文件很重要,是寻找突破口的关键点,比如有时候需要通过字符串内容来定位关键点,这里就可以通过 string 的定义来找到对应的整型值即可。
当找到了 button 对应的 id 值了之后,就可以用这个 id 值再一次全局搜索一下,因为 Android 中编译之后的 apk,在代码中用到的 ResId 都是用一个整型值代替的,这个整型值就是在 R 文件中做了定义,将资源的 id 和一个值对应起来,然后代码里面一般使用 R.id.button 这样的值,再编译出 apk 的时候,这个值就会被替换成对应的整型值,所以再全局搜索 0x7F05003E,如图 21-14 所示。搜索的结果如图 21-15 所示。
这里就定位到了代码中用到的这个 button。进入代码看看,如图 21-16 所示。在这里看到使用了 findViewById 的方式定义 Button,再简单分析一下 smali 语法,下面是给 button 添加一个按钮事件,这里用的是内部类 MainActivity$1,到这个类看看,它肯定实现了 OnClickListener 接口,那么直接搜 onClick 方法,如图 21-17 所示。
图 21-14 全局搜索 id 值
图 21-15 全局搜索 id 结果值
图 21-16 定位代码位置
图 21-17 点击事件方法
在这里就可以下个断点了,这里就是触发密码校验过程的地方。
第六步:运行程序,设置远程调试项目
在第五步中,找到了关键点,然后打上断点,下面就来运行程序,然后在 Eclipse 中设置远程调试的项目,运行程序。因为加入了 waitForDebug 的代码,所以启动的时候会出现一个 Wait debug 的对话框。不过,测试的时候,手机没有出现这个对话框,而是一个白屏,不过这不影响。程序运行起来之后,看看如何在 Eclipse 中设置远程调试项目,首先找到需要调试的程序对应远程调试服务端的端口,如图 21-18 所示。
图 21-18 调试端口图
这里要注意以下几点:第一点,在程序等待远程调试服务器的时候,前面会出现一个红色的小蜘蛛。第二点,在调试服务端我们会看到两个端口号 8600/8700,这里需要解释一下,为什么会有两个端口号呢?首先这里的端口号代表的是,远程调试服务器端的端口,下面简单来看一下 Java 中的调试系统,如图 21-19 所示。
图 21-19 Java 中的调试系统
这里有三个角色:
1)被调试的客户端。可以认为需要破解的程序就是客户端,如果一个程序可以被调试,当启动的时候,会有一个 JDWP 线程用来和远程调试服务端进行通信,如下所示:
需要破解的程序启动了 JDWP 线程,注意这个线程也只有当程序是 debug 模式下才有的,也就是 AndroidManifest.xml 中的 debug 属性值必须是 true 的时候,这也是一开始为什么要修改这个值的原因。
2)JDWP 协议(用于传输调试信息,比如调试的行号、当前的局部变量的信息等),这可以说明,为什么在一开始的时候,反编译成 Java 文件,因为为了 Eclipse 导入能够识别的 Java 文件,然后为什么能够调试呢?因为 smali 文件中有代码的行号和局部变量等信息,所以可以进行调试。
3)远程调试的服务端,一般是有 JVM 端,就是开启一个 JVM 程序来监听调试端,这里就可以认为是本地的 PC 机。当然必须有端口用来监听,那么上面的 8600 端口就是这个作用,而且端口是从 8600 开始,后续的程序端口号都是依次加 1 的,比如其他调试程序端口如图 21-20 所示。
图 21-20 调试端口
那么有了 8600 端口,为什么还有一个 8700 端口呢?它是干什么的?其实它的作用就是远程调试端备用的基本端口。不过,在实际过程中,还是建议使用程序独有的端口号 8600,可以查看 8600 和 8700 端口在远程调试端(本地 pc 机)的占用情况,如下所示。
8600 端口和 8700 端口号都是对应的 javaw 程序,javaw 程序就是启动一个 JVM 来进行监听的。到这里就弄清楚了 Java 中的调试系统以及远程调试的端口号。
注意,其实可以使用 adb jdwp 命令查看当前设备中可以被调试的程序的进程号信息,如下所示:
下面继续,知道了远程调试服务端的端口 8600 以及 ip 地址,这里就是本地 ip:localhost/127.0.0.1。可以在 Eclipse 中新建一个远程调试项目,将 smali 源码工程和设备中需要调试的程序关联起来。右击被调试的项目→选择 Debug Configurations,如图 21-21 所示。
图 21-21 设置调试项目
然后开始设置调试项目,如图 21-22 所示。
图 21-22 设置调试项目
选择 Romote Java Application,在 Project 中选择被调试的 smali 项目,在 Connection Type 中选择 SocketAttach 方式。其实还有一种方式是 Listener 的,如图 21-23 所示。这两种方式的区别如下所示:
·Listner 方式:调试客户端启动就准备好一个端口,当调试服务端准备好了,就连接这个端口进行调试。
·Attach 方式:调试服务端开始就启动一个端口,等待调试端来连接这个端口。
图 21-23 调试的两种方式
一般都是选择 Attach 方式来进行操作的。设置完远程调试的工程之后,开始运行,发现设备上的程序还是白屏,这是为什么呢?看看 DDMS 中调试程序的状态,如下所示:
关联到了这个进程,上面使用的是 8700 端口号,这时选中了这个进程,就把 smali 调试项目关联到了这个进程,破解的进程没响应了,立马改一下,用 8600 端口,如下所示:
这下成功了,看到红色的小蜘蛛变成绿色的了,说明调试端已经连接上远程调试服务端了。
注意:在设置远程调试项目的时候,一定要注意端口号的设置,不然没有将调试项目源码和调试程序关联起来,是没有任何效果的。
第七步:开始运行调试程序,进入调试
在程序的文本框中输入:gggg 内容,点击开始,如图 21-24 所示。
图 21-24 进入断点
到这里看到期待已久的调试界面出来了,到了开始的时候加的断点处,就可以开始调试了,使用 F6 单步调试,F5 单步跳入,F7 单步跳出进行操作。这里使用 v3 变量保存了输入的内容,如图 21-25 和图 21-26 所示。
图 21-25 查看变量值
图 21-26 查看变量值
这里有一个关键的地方,就是调用 MainActivity 的 getTableFromPic 方法,获取一个 String 字符串,从变量的值来看,貌似不是规则的字符串内容,这里先不用管了,继续往下走,如下所示。
这里又遇到一个重要的方法 getPwdFromPic,如图 21-27 所示,从字面意义上看,应该是获取正确的密码,用于后面的密码字符串比对。
图 21-27 查看密码内容
查看一下密码的内容,貌似也是一个不规则的字符串,但是可以看到和上面获取的 table 字符串内容格式很像,接着往下走。这里还有一个信息就是调用了系统的 Log 打印,log 的 tag 就是 v6 保存的值 lil,如下所示:
这时候看到 v3 是保存的输入密码,这里使用 utf-8 获取它的字节数组,然后传递给 access$0 方法:
使用 F5 进入这个方法:
在这个方法中,还有一个 bytesToAliSmsCode 方法,使用 F5 进入:
这个方法其实看上去还是很简单的,就是把传递进来的字节数组循环遍历,取出字节值,然后转化成 int 类型,再调用上面获取到的 table 字符串的 chatAt 来获取指定的字符,使用 StringBuilder 进行拼接,然后返回即可,如图 21-28 所示。
图 21-28 加密之后的内容
按 F7 跳出,查看,返回来加密的内容是“日日日日”,如图 21-29 所示,也就是说 gggg=>日日日日。
最后再往下走,可以看到进行代码比对的工作了。
图 21-29 代码比对方法
上面就分析完了所有的代码逻辑,还不算复杂,来梳理一下流程:
1)调用 MainActivity 中的 getTableFromPic 方法,获取一个 table 字符串。
可以进入看看这个方法的实现:
这里可以大体了解了,它是读取 asset 目录下的一个 logo.png 图片,然后获取图片的字节码再进行操作,得到一个字符串,那么从上面的分析可以知道,其实这里的 table 字符串类似于一个密钥库。
2)通过 MainActivity 中的 getPwdFromPic 方法获取正确的密码内容。
3)获取输入内容的 utf-8 的字节码,然后调用 access$0 方法,获取加密之后的内容。
4)access$0 方法中调用 bytesToAliSmsCode 方法,获取加密之后的内容。
这个方法是最核心的,通过分析知道,通过传递进来的字节数组,循环遍历数组,拿到字节转化成 int 类型,然后再调用密钥库字符串 table 的 charAt 得到字符,使用 StringBuilder 进行拼接。通过上面的分析之后,知道获取加密之后的输入内容和正确的密码内容做比较,那么现在有的资源是:密钥库字符串和正确的加密之后的密码,以及加密的逻辑。
那么破解思路就有了,相当于,知道了密钥库字符串,也知道了加密之后的字符组成的字符串,那么可以通过遍历加密之后的字符串,循环遍历,获取字符,然后再去密钥库找到指定的 index,然后再转成 byte,保存到字节数组,然后用 utf-8 获取一个字符串,那么这个字符串就是想要的密码。
下面就用代码来实现这个功能:
代码中的函数相当于上面加密函数的 bytesToAliSmsCode 的反向实现,运行结果如图 21-30 所示。
图 21-30 破解结果
得到了正确的密码,下面来验证一下,如图 21-31 所示。
图 21-31 验证结果
破解成功。
补充:刚刚在断点调试的时候,看到了代码中用了 Log 来打印日志,tag 是 lil,那么可以打印这个 log 看看结果,如下所示:
这里 table 是密钥库,pw 是正确的加密之后的密码,enPassword 是输入之后加密的密码。通过这个例子可以知道,在破解 apk 的时候,日志也是一个非常重要的信息。
提示:资料下载地址为 http://download.csdn.net/detail/jiangwei0910410003/9526113。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论