- 对本书的赞誉
- 前言
- 基础篇
- 第 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 样本分析
22.3 逆向 so 文件
下面开始破解编译之后的 apk 文件。
22.3.1 获取应用的 so 文件
按照国际惯例,直接使用解压软件,获取应用 apk 中的 so 文件,如图 22-9 所示。
图 22-9 解压 so 文件
得到 libencrypt.so 文件之后,使用 IDA 打开它,如图 22-10 所示。
一般 so 中的函数方法名都是:Java_类名_方法名。那么这里直接搜 Java 关键字即可,或者使用 jd-gui 工具找到指定的 native 方法,如图 22-11 所示。
图 22-10 IDA 打开 so 文件
图 22-11 native 函数名
双击即可在右边的 IDA View 页面中看到 Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals 函数的指令代码,如图 22-12 所示。
可以简单分析一下这段指令代码:
·PUSH{r3-r7,lr}是保存 r3,r4,r5,r6,r7,lr 的值到内存的栈中,那么最后当执行完某操作后,想返回到 lr 指向的地方执行,当然要给 pc 了,因为 pc 保留下一条 CPU 即将执行的指令,只有给了 pc,下一条指令才会执行到 lr 指向的地方。
·pc:程序寄存器,保留下一条 CPU 即将执行的指令。
·lr:连接返回寄存器,保留函数返回后,下一条应执行的指令。
图 22-12 函数对应的 ARM 指令代码
这个和函数最后面的 POP{r3-r7,pc}是相对应的。
然后是调用了 strlen、malloc、strcpy 等系统函数,在每次使用 BLX 和 BL 指令调用这些函数的时候,都发现了一个规律:在调用它们之前一般都是用 MOV 指令来传递参数值的,比如这里的 R5 里面存储的就是 strlen 函数的参数,R0 就是 is_number 函数的参数,如下代码所示。这样分析之后,在后面的动态调试的过程中可以得到函数的入口参数值,这样就能得到一些重要信息。
在每次调用有返回值的函数之后的命令,一般都是比较指令,比如 CMP、CBZ,或者是 strcmp 等,如上面代码最后一行。这里是破解的突破点,因为一般加密再怎么厉害,最后比较的参数肯定是正确的密码(或者是正确的加密之后的密码)和输入的密码(或者是加密之后的输入密码),在这里就可以得到正确密码,或者是加密之后的密码。
到这里就分析完了 native 层的密码比较函数:Java_cn_wjdiankong_encryptdemo_MainActivity_isEquals。如果觉得上面的 ARM 指令看的吃力,可以使用 F5 键,查看它的 C 语言代码,如下所示:
这里可以看到,有两个函数是核心点。
·is_number 函数,这个函数看名字就能猜到是判断是不是数字,可以使用 F5 键,查看它对应的 C 语言代码,如下所示:
这里简单一看,主要是看 return 语句和 if 判断语句,看到这里有一个循环,然后获取_BYTE*这里地址的值,并且自增加一,然后存到 v2 中,如果 v3 为'\0'的话,就结束循环,然后做一次判断,就是 v2-48 是否大于 9,那么这里知道 48 对应的是 ASCII 中的数字 0,所以这里可以确定:用一个循环遍历_BYTE*这里存的字符串是否为数字串。
·get_encrypt_str 函数,这个函数看到名字就可以猜测,它是获取输入的密码加密之后的值,再次使用 F5 快捷键查看对应的 C 语言代码,如下所示:
首先是一个 if 语句,用来判断传递的参数是否为 NULL。如果是的话,直接返回;不是的话,使用 strlen 函数获取字符串的长度保存到 v2 中,然后使用 malloc 申请一块堆内存。首指针保存到 result,大小是 v2+1,也就是传递进来的字符串长度+1,然后就开始进入循环。首指针 result,赋值给 i 指针,开始循环,v3 是通过 v1-1 获取到的,就是函数传递进来字符串的地址,那么 v6 就是获取传递进来字符串的字符值,然后减去 48,赋值给 v7。
可以猜到了,这里想做字符转化,把 char 转化成 int 类型。继续往下看,如果 v6==48 的话,v7=1,也就是说这里如果遇到字符'0',就赋值 1。看到上面得到的 v7 值,被用来取 key_src 数组中的值,那么这里双击 key_src 变量,就跳转到了它的值地方。果不其然,这里保存了一个字符数组,看到它的长度正好是 18,如下代码所示,那么应该明白了,这里通过传递进来的字符串,循环遍历字符串,获取字符,然后转化成数字,再倒序获取 key_src 中的字符,保存到 result 中。然后返回。
这两个重要函数的功能为,一个是判断输入的内容是否为数字字符串,一个是通过输入的内容获取密码内容,然后和正确的加密密码 ssBCqpBssP 进行比较。
22.3.2 用 IDA 进行调试设置
本节就用动态调试方法来跟踪传入的字符串值,以及加密之后的值。看到打印 log 的函数,很难知道具体的参数和寄存器的值,所以这里需要开始调试,得知每个函数执行之后的寄存器的值,在用 IDA 进行调试 so 的时候,需要以下准备步骤。
1.获取 android_server 文件
在 IDA 安装目录\dbgsrv\android_server,如图 22-13 所示。
图 22-13 android_server 文件
这个文件是干嘛的呢?它是怎么运行的呢?下面来介绍一下。在前一章中介绍了关于 Android 中的调试原理,其实是使用 gdb 和 gdbserver 来做到的,gdb 和 gdbserver 在调试的时候,必须注入到被调试的程序进程中。但是非 root 设备的话,注入别的进程中只能借助于 run-as 这个命令。也就是说如果要调试一个应用进程的话,必须要注入它内部,那么用 IDA 调试 so 文件也是这个原理,需要注入进程才能进行调试。IDA 有类似于 gdbserver 这样的工具,那就是 android_server,需要运行在设备中,保证和 PC 端的 IDA 进行通信,比如获取设备的进程信息、具体进程的 so 内存地址、调试信息等。
把 android_server 保存到设备的/data 目录下,修改一下它的运行权限,然后必须在 root 环境下运行,因为要做注入进程操作,所以必须要 root:
注意,这里把它放在了/data 目录下,然后是./android_server 命令,这里提示了 IDA Android 32-bit,所以后面在打开 IDA 的时候一定要是 32 位的 IDA,不是 64 位的。IDA 在安装之后都是有两个可执行的程序,一个是 32 位,一个是 64 位的,如果没打开正确会报这样的错误,如图 22-14 所示。
图 22-14 报错信息
还有一类问题:error:only position independent executables(PIE)are supported 这主要是 Android 5.0 以上的编译选项默认开启了 pie,在 5.0 以下编译的原生应用不能运行,有两种解决办法,一种是用 Android 5.0 以下的手机进行操作,还有一种就是用 IDA6.6+版本即可。
然后开始监听了设备的 23946 端口,如果想让 IDA 和这个 android_server 进行通信,必须让 PC 端的 IDA 也连上这个端口,这时候就需要借助于 adb 的一个命令了:
这里就可以把 android_server 端口转发出去:
这时候,只要在 PC 端使用 IDA 连接上 23946 这个端口就可以了。到这里会有人好奇,为什么远程端的端口号也是 23946 呢?因为后面在使用 IDA 进行连接的时候,发现 IDA 把这个端口设置死了,就是 23946,可使用如下命令修改:android-server-p1234。
可以使用 netstat 命令查看端口 23946 的使用情况,看到是 ida 在使用这个端口
2.IDA 获取进程信息
上面准备好了 android_server,运行成功,下面就用 IDA 进行尝试连接,获取信息,进行进程附加注入。这时候需要再打开一个 IDA,之前打开一个 IDA 是用来分析 so 文件的,一般用于静态分析,要调试 so 的话,需要再打开一个 IDA。这里一般都是需要打开两个 IDA,也叫作双开 IDA 操作,采用动静结合策略,如图 22-15 所示。
图 22-15 双开 IDA
这里记得选择 Go 这个选项,就是不需要打开 so 文件了,进入一个空白页,如图 22-16 所示。
在 Debugger 选项卡中选择 Attach,看到有很多 debugger,可见 IDA 工具真的很强大,做到很多 debugger 的兼容,可以调试很多平台下的程序。这里选择 Android debugger,出现页面如图 22-17 所示。
图 22-16 选择 Debugger 选项
图 22-17 设置端口
这里可看到端口是 23946,所以上面用 adb forward 进行端口转发的时候是 23946。这里 PC 本地机就是调试端,所以 host 就是本机的 ip 地址:127.0.0.1,点击 OK,出现界面如图 22-18 所示。
图 22-18 进程信息列表
可以看到列出了设备中所有的进程信息,其实都是 android_server 干的事,获取设备进程信息传递给 IDA 进行展示。
注意,如果当初没有用 root 身份去运行 android_server:
IDA 就不会列举出设备的进程信息,如图 22-19 所示。
图 22-19 无进程列表信息
还有一个注意的地方,就是 IDA 和 android_server 版本一定要保持一致。
可以用 Ctrl+F 快捷键搜索需要调试的进程,这里必须运行要调试的进程,不然也是找不到这个进程的,如图 22-20 所示。
图 22-20 搜索进程
双击进程,即可进入调试页面,如图 22-21 所示。
图 22-21 进入调试页面
为什么会断在 libc.so 中呢?Android 系统中 libc 是 c 层中最基本的函数库,libc 中封装了 io、文件、socket 等基本系统调用。所有上层的调用都需要经过 libc 封装层。所以 libc.so 是最基本的,所以会断在这里。而且一些常用的系统 so,比如 linker,如图 22-22 所示。
图 22-22 linker 文件
这个 linker 是用于加载 so 文件的模块,所以后面再分析如何在.init_array 处下断点,还有一个就是 libdvm.so 文件,它包含了 dvm 中所有的底层加载 dex 的一些方法,如图 22-23 所示。
图 22-23 libdvm.so 文件
在后面动态调试需要 dump 出加密之后的 dex 文件,就需要调试这个 so 文件了。
3.找到函数地址下断点
使用 Ctrl+S 快捷键找到需要调试 so 的基地址:74FE4000,如图 22-24 所示。
图 22-24 查找基地址
然后通过另外一个 IDA 打开 so 文件,查看函数的相对地址:E9C,如下所示:
那么得到了函数的绝对地址就是:74FE4E9C,使用 G 键快速跳转到这个绝对地址,如图 22-25 所示。
图 22-25 跳转到绝对地址
跳转这个地址之后,开始下断点,点击最左边的绿色圆点即可下断点,如下所示:
然后点击左上角的绿色三角按钮运行,也可以使用 F9 键运行程序。
点击程序中的按钮,如图 22-26 所示。
图 22-26 点击程序中的按钮
触发 native 函数的运行,如下所示:
进入调试阶段了,这时候可以使用 F8 快捷键进行单步调试,用 F7 进行单步调试,如下所示:
点击 F8 进行单步调试,到达 is_number 函数调用处,看到 R0 是出入的参数值,可以查看 R0 寄存器的内容,然后看到是 123456,这个就是 Java 层传入的密码字符串。接着往下走,如下所示:
这里把 is_number 函数返回值保存到 R0 寄存中,然后调用 CBZ 指令,判断是否为 0,如果为 0 就跳转到 locret_74FE4EEC 处,查看 R0 寄存器的值不是 0,继续往下走,如图 22-27 所示。
图 22-27 查看寄存器值
看到了 get_encrypt_str 函数的调用,函数的返回值保存在 R1 寄存器中,查看内容为:zytyrTRA*B,那么看到,上层传递的:123456→zytyrTRA*B,前面静态分析了 get_encrypt_str 函数的逻辑,继续往下看,如下所示:
这里把上面得到的字符串和 ssBCqpBssP 作比较,那么 ssBCqpBssP 就是正确的加密密码了。那么现在的资源是:正确的加密密码为 ssBCqpBssP,加密密钥库为 zytyrTRA*BniqCPpVs,加密逻辑为 get_encrypt_str。
可以写一个逆向的加密方法,去解析正确的加密密码得到值即可。为了给大家一个破解的机会,这里就不公布正确答案了。
加密 apk 下载地址:
http://download.csdn.net/detail/jiangwei0910410003/9531638
22.3.3 IDA 调试的流程总结
到这里,就分析了如何破解 apk 的流程,下面来总结一下:
1)通过解压 apk 文件得到对应的 so 文件,然后使用 IDA 工具打开 so 文件,找到指定的 native 层函数。
2)通过 IDA 中的一些快捷键:F5、Ctrl+S、Y 等,静态分析函数的 ARM 指令,大致了解函数的执行流程。
3)再次打开一个 IDA 来进行调试 so 文件。
4)将 IDA 目录中的 android_server 拷贝到设备的指定目录下,修改 android_server 的运行权限,用 root 身份运行 android_server。
5)使用 adb forward 进行端口转发,让远程调试端 IDA 可以连接到被调试端。
6)使用 IDA 连接上转发的端口,查看设备的所有进程,找到需要调试的进程。
7)通过打开 so 文件,找到需要调试函数的相对地址,然后在调试页面使用 Ctrl+S 找到 so 文件的基地址,相加之后得到绝对地址,使用 G 键,跳转到函数的地址处,下好断点。点击运行或者点击 F9 键。
8)触发 native 层的函数,使用 F8 和 F7 快捷键进行单步调试,查看关键的寄存器中的值,比如函数的参数、函数的返回值等信息。
总结:在调试 so 文件的时候,需要双开 IDA,采用动静结合的方式进行分析。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论