最近遇到一个软件数据库中离线数据进行了加密,希望通过这个例子让大家了解一个APP数据解析的工作流程。首先从豌豆荚网站(www.wandoujia.com)下载到该APP的APK安装包,然后通过ADB命令将APK安装到手机或者模拟器当中。将软件安装注册后,浏览文章下有个离线下载的功能,如下图所示。
当点击下载后,帖子就可以在“收藏/下载”页面离线观看了,如下图所示。
通过分析,发现这里下载的帖子存放在数据库NOTEDATA字段中并且内容是加密的。一般情况下,安卓程序的数据库存放在目录data/data/程序包名/databases目录下,这里的数据库可以通过ADB命令导出到电脑,并且内容是加密的,如下图所示。
现在我们需要解密数据库中NOTEDATA字段的数据。首先,用反编译工具jadx-gui载入APK文件(该工具现在有不依赖Java环境的版本,下载地址https://github.com/skylot/jadx/releases)。也可以考虑使用GDA工具代替,GDA是国内团队开发的反编译工具。不依赖Java环境,更新频率高,并且可以检测出APK加壳的厂商,笔者是习惯使用jadx-gui的搜索功能。
载入后,通过观察发现该APK没有加壳,直接点击jadx-gui工具界面的放大镜按钮打开搜索文本界面(或者直接单击工具栏中的“导航”->“搜索文本”),如下图所示。
该APP数据库本身是没有加密的,且只需要解密NOTEDATA字段的数据,所以在搜索文本的文本框内输入“NOTEDATA”,搜索结果如下图所示。
可以看到这里搜索到了代码中包含“NOTEDATA”的关键点有24个,说明程序操作数据库中“NOTEDATA”字段的数据大概率就在这24个关键位置中,这里就需要一些简单的判断了。//根据经验或者一个个去排除找到关键位置,这里搜索到24个相关位置,可以通过批量hook来判断想要的地方。
通过分析,在模拟器中访问离线下载的帖子时,程序应该是先从数据库中读取对应字段的数据,再解密显示出来,所以大概率会用到查询数据库的语句。根据判断,发现关键位置在上图节点第三行,代码如下:int columnIndex = query.getColumnIndex("NOTEDATA"); 在文本搜索界面双击代码所在行,跳转到代码位置,如下图所示。
从上图红色方框的代码看出,第一行是先将“NOTEDATA”的索引取出放到columnIndex变量,然后第二行通过query.getBlob(columnIndex)取出数据库中对应索引的加密数据,看到取出的数据作为encryptBytes方法的参数,然后encryptBytes的返回值又作为uncompress方法的参数传入,猜测是做了两次解密,这里也有一点技巧,因为有两次解密,第一次解密肯定是看不到明文的,因此先分析第二个方法uncompress,双击上图方框中的uncompress方法,跳转到uncompress方法所在的类,如下图所示。
可以从上图看到uncompress方法在类CompressEncrypt中,这里可以右键点击上图红色方框的CompressEncrypt类,会弹出“复制类名”,如下图所示。
复制类名发现完整类名是cn.tianya.util.CompressEncrypt。由于之前的分析都是通过代码看到的,并不确定程序真的运行了这里的代码,这里笔者通过frida进行hook来判断。运行模拟器,电脑打开终端(这里需要模拟器跟电脑都安装了相同版本的frida,并且电脑上安装了ADB和objection,这些工具网上有很多安装教程,这里不再赘述)。输入命令“adb shell”后按下回车键,连接模拟器,如下图所示。
然后输入命令 “cd data/local/tmp”,因为这个目录下有笔者安装的frida-server工具,并且重命名为o15,如下图所示。
进入到该目录后,继续输入命令“./o15”运行frida-server,运行完成之后,光标移到下一行,并且没有任何输出代表运行成功,如下图所示。
当模拟器的frida-server运行成功后,该终端就不需要管理了,最小化即可。在电脑上再开一个终端,在终端中输入命令“objection -g cn.tianya.light explore”并按下回车(其中cn.tianya.light是我们分析的APK程序的包名),frida会自动将模拟器中要分析的程序运行起来,如下图所示界面。
上图所显示界面代表objection运行成功,接下来通过hook判断程序是否运行到uncompress方法,在终端继续输入命令“android hooking watch class_method cn.tianya.util.CompressEncrypt.uncompress --dump-args --dump-backtrace --dump-return”,这里的命令可以学习objection工具了解,大概的作用就是hook了uncompress方法,并输出参数,调用堆栈和返回值。按下回车,结果如下图所示代表hook成功。
当hook成功后,我们在模拟器中点击浏览离线下载的帖子(这里不演示帖子内容),当我们访问离线内容,这时的终端会有内容输出,因为输出内容较多,这里只截了方法的参数和返回值的内容,如下图所示。
uncompress方法的参数
uncompress方法的返回值
发现返回值就是离线下载的内容,这时可以确定找到的是正确的代码位置。
这里继续分析两个加密方法具体实现。回到jadx-gui工具静态分析代码的位置,如下图所示。
双击上图红色方框内的方法,跳转到方法实现的位置。如下图所示。
uncompress方法中只有一个unzip方法,根据名字猜测就是一个解压zip的功能,再次双击unzip方法跳转到方法实现的代码位置,如下图所示。
根据代码分析得知,unzip方法就是解压zip数据的功能,那么umcompress方法也就是解压数据的功能,下面继续分析encryptBytes方法,如下图所示。
双击上图红色方框中的encryptBytes方法,界面跳转到encryptBytes方法的代码实现位置,如下图所示。
从上方图片代码中分析得知,encryptBytes方法传入的参数作为encryptData方法的参数,说明主要解密的代码在encryptData方法中,再次双击encryptData方法,跳转到对应代码实现,如下图所示。
从上图得知,方法encryptData被native修饰,说明了该方法的代码实现在C语言层,也就是说对应代码在jadx-gui中看不到,需要用IDA加载对应的so文件才能观察到代码,接下来的目标就是找encryptData方法所在的so文件。继续观察上图,发现红色方框内的System.loadLibrary方法参数是“tianya”,可以判断出encryptData方法所在so文件是libtianya.so。成功找到so文件名,就该找对应文件了,将分析的.apk安装包后缀改成.zip的压缩包,然后解压缩得到对应文件夹,如下图所示。
上图中我们重点关注lib文件夹,该文件夹下存放的就是APK中用到的so文件。双击打开lib文件夹,发现里面还有三个文件夹。这里三个文件夹下的so文件代码是一样的,这里打开“armeabi-v7a”文件夹,找到对应so文件,如下图所示。
找到文件,用IDA将so文件载入,载入成功后在函数窗口中搜索“Java”关键字,如下图所示。
从上图中函数窗口看到Java_cn_tianya_jni_EncryptJni_encryptData()函数,该函数就是我们在jadx-gui工具中分析的encryptData方法的实现,双击该函数,跳转到函数的代码位置,双击过去后发现是汇编代码,代码如下图所示。
这里的汇编看起来很是头疼,这里鼠标单击汇编代码所在页面,摁下键盘的F5键,或者单击“View”->“Open subview”->“Generate pseudocode”,如下图所示。
页面跳转到IDA翻译汇编代码到伪代码界面,如下图所示。
这里可能读者有点疑问,明明在jadx-gui工具中看到的encryptData方法只有一个参数,但在IDA中看到Java_cn_tianya_jni_EncryptJni_encryptData()函数有三个参数,是不是IDA分析错了呢,并不是的,因为涉及到一些开发的知识,这里不作介绍,只需要了解在IDA中
Java_cn_tianya_jni_EncryptJni_encryptData()函数的第三个参数就是jadx-gui中看到的encryptData方法的实际参数,上图中的bytes就是encryptData方法的参数,上图中的代码,真正解密数据在上图代码页面第9行j_EncryptData()函数里面,该函数第一个参数v6就是加密的数据,第二个参数就是加密数据的长度。双击上图中j_EncryptData()函数,进入函数内部代码,如下图所示。
跟进后,发现仅仅调用了EncryptData()函数,继续双击EncryptData()函数, 跳转到EncryptData()函数代码实现位置,如下图所示。
进入代码位置后,发现又调用其他函数j_j_ReOrderData(),再双击进入函数,跳转到对应代码位置,进去还是调用了一个函数j_ReOrderData(),继续双击进入函数代码位置,终于找到加密的代码,如下图所示。
这里代码比较简单,数据库中加密数据的解密也十分简单,就是将数据库中加密的字段读取出来,经过上图代码函数解密后(上方函数第一个参数是读取出来的密文,第二个参数是密文的长度),进行一次zip的解压缩即可得到明文。
来源:盘古石取证,作者:技术专家 黄碧波