Xposed框架开发入门(三)--Android某输入法用户个人词库提取

前面已经介绍了Xposed框架开发的基本原理与简单的使用方法(具体可以参考:Xposed框架开发入门(一)和Xposed框架入门开发(二)–使用Xposed框架实现Activity跳转拦截),同时在第一篇文章中还说到了我们这第三篇文章介绍的是Xposed框架在实际Android逆向分析时的用法,所以本篇文章我们就以Android平台的某狗输入法(到底是啥大家自己猜,下文全部以某输入法代替)为例子,介绍下使用Xposed框架来提取用户个人词库的过程。

好了,废话不多说,下面就开始我们的表演:

首先,当我们在安装某些输入法的时候是不是经常会遇到下面这个蛋疼的提示吧(大部分输入法都会有这样的提示):
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第1张图片
没错,就是收集我们的输入记录(包括信用卡号、密码等个人数据),虽然这样可以方便我们今后的输入,但是却或多或少的暴露了我们的隐私,关于这一点的讨论网上有很多,我们在这里不再深入地进行,我们这里主要说说它记录的这些数据吧。这些收集到的数据一般都会从本地设备上传到服务器中,当我们在客户端进行同步词库时会将这些数据以词库的形式下载下来并转化成本地词库,同时还会将本地最新收集到的数据上传到服务器中。一旦我们的个人数据落入不法分子手中,便会或多或少的泄露出我们的一些个人信息,对我们造成或多或少的损失。

虽然现在的大部分输入法基本上都对本地词库进行了加密处理,一般人即使得到了本地词库也无法进行解析,但是并不是代表着我们的个人词库就是100%的安全。我们这篇文章就是通过对个人词库的同步过程进行逆向分析,hook到关键函数点,从而得到用户个人词库。

一、下载某输入法,反编译得到Java代码

首先我们从网上下载到该输入法的安装包(版本v8.8),然后使用AndroidKiller反编译该输入法得到smali代码和资源文件:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第2张图片
当然虽然我们可以直接通过阅读smali代码来分析该程序,但为了提升效率还是推荐使用dex2jar+jd-gui打开该apk文件得到其java代码进行分析(也可以在AndroidKiller中用jd-gui打开smali查看java代码):
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第3张图片
得到使用jd-gui得到java代码后可以明显的看到该apk经过了初步的代码混淆,但是这并没有多大的关系,不是很影响我们的阅读。

二、使用该输入法,分析同步个人词库

在手机中打开该输入法,登录后在个人中心找到我的词库,在个人词库里面可以发现同步个人词库的Button。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第4张图片

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第5张图片

点击同步后(15:09点击)在本地文件下查找保存的结果。使用RE文件管理器,进入该输入法的的内部存储路径:/data/data/com.sohu.inputmethod.sogou/,同时查看修改时间为15:09的文件,推测该输入法的个人词库大概保存的位置在/files/dict/下。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第6张图片

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第7张图片
可以看到搜狗本地词库都是一些.bin文件,直接打开的话都是乱码,所以可以猜到这些文件都是搜狗自己的文件。同时可以看到一些关于usr的bin文件,可以猜想这些文件保存的可能是用户词库。所以接下来对搜狗进行逆向分析,看这些文件是如何生成。

三、使用TraceView分析同步词库过程中具体的方法调用

首先在手机中打开输入法,在个人中心中准备同步个人词库,然后打开ddms,对同步个人词库的过程进行traceview:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第8张图片
在上面图中每条应用的调试信息之所以会显示,我们这里使用的是Xposed框架的Xinstaller模块,安装激活Xinstaller这个应用后,在“其他设置”中点击“调试应用”便会在ddms中打开应用的调试信息。当然要是不使用Xinstaller的话可以自己在应用中打开他的Debug模式,否则直接打开ddms是不会出现这些应用的调试信息。

点击“Start Method Profiling”后在手机中点击同步按钮,开始同步。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第9张图片

同步完成后,在ddms中同样的位置点击“Stop Method Profiling”。
这里写图片描述

然后会自动跳出本次method profiling结果(traceview):
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第10张图片
可以看到在底部显示的几百上千个函数便是在此次用户词库同步过程中所有的函数调用,由于是所有的函数调用过程,所以这里有一大堆的函数,短时间是无法找到关键函数的,但涉及同步操作的函数都在这里,我们要做的就是将这个traceview结果与apk的静态分析相结合,从而寻找关键函数。

最终通过一些列的分析发现bua.onWork(HttpClient;bfd)V方法及其与之相关的方法最为可疑。(parent代表调用该方法的函数,children代表该方法调用的函数)
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第11张图片

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第12张图片

然后我们自己划出与bua.onWork(HttpClient;bfd)V方法有关的所有函数调用,如下图,由于方法调用比较复杂,所以需要放大查看。(这里是自己根据traceview结果画的,比较麻烦,但是易于我们分析整个同步过程。同时对于根据traceview作图,AndroidSDK提供了dmtracedump.exe(在AndroidSDK的platform-tools文件夹下)工具可以自动根据traceview文件画出函数调用图(命令为:dmtracedump -g test.png test.trace),但是bug比较大,画出的图有时会漏掉许多关键函数,所以这里选择自己画。)
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第13张图片

四、根据traceview结果,在jd-jui中查看对应的反编译出的java代码。

bua.onWork()函数如下所示:

可以看到图中用方框圈出的方法都是在上面自己画的函数调用图中的对应方法,结合调用图和java代码看出其中最为关键的代码便是图中的:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第14张图片

进入bua.a()I方法
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第15张图片
如图,很明显,int i = b();是将个人词库从网上下载下来并保存在本地词库,i = c();是将本地词库上传到服务器的个人词库中。我们这里只需要获取从服务器下载的个人词库,所以只需要分析int i = b();这个过程,我们可以先从自己画的函数调用图中宏观上来看看这个int i = b(),也就是bua.b()I方法,如图:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第16张图片

很明显,我们可以看到bua.b()I方法同样十分地复杂,所以我们暂时停下来,观察bua.a()I方法。观察上图中的bua.a()I方法,可以在其中看到有多个如下图所示的a(String)调用,其中参数String有许多提示信息。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第17张图片

这里写图片描述
点进bua.a(String)V可以看到此方法为空,但是在程序中多次调用此方法,同时还传入相应的提示字符串,那么可以猜到这个方法是开发人员在开发时的调试方法。同时a()方法在开发时必为输出Log信息的代码,在发布时将其中的Log代码又给删除掉。

既然是调试方法,虽然已经注释或删除掉了Log代码,但是其参数String还是传入了,所以该方法仍然可以被我们使用。

要重新使用该方法,使他可以重新打印出Log信息有两种方法。一种是在smali中向a(String)V方法中添加Log代码,然后回编译,安装。另一种是使用Hook来hook此a(String)V方法,得到其参数,在Log中将参数打印出来。接下来我们分别来对这两种方法进行尝试。

五、尝试通过回编译来打印Log信息

① 在Android Killer中找到找到该a方法,向其中加入调试代码:
加入调试代码后a方法应该变为:
这里写图片描述
对应的Smali代码改为:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第18张图片

② 然后尝试回编译
使用AndroidKiller回编译:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第19张图片

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第20张图片
回编译成功,在输出目录中得到回编译后的apk,然后安装到手机中,如图::
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第21张图片
额,好吧。很明显,这里在安装的时候应该进行了签名验证之类的验证,发现安装的是盗版的搜狗输入法。我们根据“你安装的是盗版搜狗输入法,请到搜狗官网下载”这句提示来查找它的验证方法。

③ 用Notepad++在反编译出的项目的res目录下打开它的string.xml和public.xml。在string.xml中查找提示,找到它的name,然后根据name在public.xml中查找它的资源id,然后根据找到的资源id在项目中查找验证方法。

OK,在string.xml中找到了这句话,发现它的name == check_key,然后在public.xml文件中查找“check_key”:

OK,此时也找到了name == check_key的字符串资源的id为:0x7f0b0514

在AndroidKiller中在整个项目搜索0x7f0b0514,结果如下图所示,可也确定其中验证方法一定是在smali\com\sohu\inputmethod\engine\IMEInterface.smali文件中。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第22张图片

点击该引用,进入smali\com\sohu\inputmethod\engine\IMEInterface.smali文件中引用该字符串的地方:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第23张图片
找到引用该字符串的方法为:
private constructor(Landroid/content/Context)V,很明显是该类的构造方法。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第24张图片

在jd-gui中打开该IMEInterface.class,找到它的构造方法,如下图所示:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第25张图片
由于在jd-gui中所有资源id都是以十进制表示的,将0x7f0b0514转化为十进制为:2131428628。
这里写图片描述

很明显,上图中的这句if语句就是验证的地方,可以看到这里是进行了对native方法的返回值进行了判断,若native_setup(SogouAppApplication.mAppContxet) == false,则为盗版软件,就会输出盗版软件的Toast。由于native方法比较难搞,所以我们直接对if判断条件这里进行修改,直接将if()中的 !native_setup(SogouAppApplication.mAppContxet)改为false。在AndroidKiller中找到这段,如下图所示:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第26张图片
很明显“move-result v0”代表的是:
v0 = native_setup(SogouAppApplication.mAppContxet)
“if-nez v0, :cond_0”代表的是:if(!v0)。
所以这里只需要将v0置为true就可以不用执行验证程序。修改后的smali如下图所示:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第27张图片

然后回编译,安装到手机,这次成功安装,也可以正常运行,但是登录后同步词库时虽然同步成功但是结果却是同步了0个用户词。好吧,看来上天不让你回编译,估计它在同步的时候应该在服务器那里还是进行了相关的验证,这种方式还是太麻烦了,同时回编译的apk还是存在一些bug的,所以放弃这条思路。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第28张图片

六、尝试使用Hook来得到Log信息

经过上一步的尝试,由于回编译打印Log的方法比较麻烦,所以换为hook打印。因为在前面已经找到了要hook的方法,所以这里直接对a(String)I方法的参数String进行hook.

在Android Studio中新建一个Hook项目,使用Xposed框架对a(String)I方法进行hook,代码如下所示:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第29张图片

编译打包将程序安装到手机中,在Xposed的本地服务里选中该程序,重启后激活。打开ddms,在搜狗输入法中点击同步个人词库,触发后在ddms中查看tag为“sogouTag”的Log信息,将结果保存到txt中。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第30张图片

七、分析Log信息

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第31张图片
从上图我们便可以看出整个下载用户词库的过程,首先将网上的个人词库下载到本地,然后通过changeUUDs2UsrDict()这个方法(这是一个native方法)将个人词库转化为本地的bin词库,转换成功后将临时保存的个人词库删除。

这里也可以成功解释我们在同步个人词库成功后发现它保存临时个人词库的文件夹是空的。如图:临时保存个人词库的位置为:/data/data/com.sohu.inputmethod.sogou/files/dict/uudstmp
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第32张图片

八、分析下载用户词库的过程

经过上面的分析过程已经明确了搜狗下载个人词库的主要过程,接下来结合代码来进行分析。

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第33张图片
根据Log的提示信息找到对应的代码处,如上图,在bua.a()I标示出的便是上面Log对应的代码,很明显其中的int i = b();代码便是下载的主要过程。转到bua.b()I方法:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第34张图片
I = this.mIC.b(localHashMap2);便是主要的下载过程,这里如果要深入分析用户词库是如何下载的话便需要进入这段代码。但是我们这里需要的是得到是删除前的用户词库,我们已经明确的知道了经过这段代码已经将用户词库下载了下来,接下来便是寻找主要的Hook点,然后通过Hook将删除前的用户词库拷贝出来。

九、寻找Hook点,拷贝出未删除的用户词库

这里写图片描述
首先根据“clear uuds folder === true”这句Log信息可以看到这里就是对uuds保存的个人词库进行清理的代码,那么便可以在这里尝试Hook,看能否得到用户词库。转到代码:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第35张图片
可以看到FileOperator.a(File,FileFilter)方法便是对应的删除方法,对其进行Hook,看能否得到用户的个人词库。
继续上次Hook bua.a(String)V方法的项目,加入Hook FileOperator.a(File,FileFilter)方法的代码:
① 创建复制文件的工具类:CopyFileUtil.class
其中包括两个静态方法,方法声明如下:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第36张图片

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第37张图片

② 添加Hook代码
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第38张图片

③ 安装激活该Xposed模块,运行搜狗同步个人词库后观察Logcat的输出:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第39张图片
发现此时虽然成功将uudstmp文件夹成功拷贝到SD卡中,但是uudstmp内部此时已经没有文件了。所以需要重新寻找可以Hook到用户词库的方法。

十、再次寻找可以hook的函数

再次分析bua.b()I方法,找到dbr.a(str3, Environment.UUDS_FOLDER_FULL_PATH);方法,借助前面的调用图,可以大概猜出这个方法的作用。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第40张图片

这里写图片描述

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第41张图片
这里在经过一层层的调用后最终调用了ZipInputStream那么就有可能和压缩有关,再结合之前对同步用户词库过程进行的抓包结果分析,可以得出在这个过程中,搜狗首先会从服务器下载个人词库的压缩包,然后经过dbr.a(str3, Environment.UUDS_FOLDER_FULL_PATH);这个方法将其解压出来,在经过changeUUDs2UsrDict()这个native方法将其转化为本地的bin词库文件,最后将临时文件删除。

编写Hook这个方法的代码,如下所示:

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第42张图片

运行后在Logcat中观察输出:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第43张图片

OK,此时可以看到uudstmp目录下文件夹的长度是2,也就是里面有两个文件,底下复制成功。进入destDir = /storage/emulated/0/test路径下查看文件:
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第44张图片

将给文件夹拷到电脑中查看。可以看到这个.txt文件就是记录我们用户词库的地方,可以看到这个词库文件主要就是记录了我们每个词条在词库中的序号、拼音、汉字以及使用的次数。
Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第45张图片

Xposed框架开发入门(三)--Android某输入法用户个人词库提取_第46张图片

总结:

经过一次又一次的分析和尝试之后我们终于将我们的用户词库搞出来了,当我们打开这个txt文件,看到里面的一条条词库的时候真是成就感爆棚。 OK,我们现在来总结一下这次用户词库提取的主要思路吧:

  1. 通过traceview得到我们整个同步过程中所有的函数调用;
  2. 逆向后静态分析该应用,结合traceview过程找到核心代码处
  3. 在核心代码处我们找到了原来的调试函数,然后我们通过hook该函数得到调试参数
  4. 根据这些调试参数我们得到了整个同步词库的流程,然后结合静态分析,找到了主要的下载函数
  5. 然后hook对下载后的词库进行操作的函数得到了下载后的词库

也许有些人会觉得有了这个方法之后那么以后就随便可以得到别人的用户词库了,那么我只能说真是图样图森破,我们这个方法的缺点还是比较大的。首先,你要得到他的手机,然后他的手机还要root,这样才可以安装我们这个Xposed模块然后才可以得到他的个人词库。。。

所以总的来说,只要输入法官方不泄露出我们的个人数据,那我们的个人数据还是比较安全的。所以又回到了文章开头所说的在安装输入法时会提示输入法会收集我们的输入记录的这个问题了,至于在明知它收集我们个人输入记录的时候使不使用该输入法就看个人了。

最后附上项目的github地址:https://github.com/zyh16143998882/sogouHook

你可能感兴趣的:(android逆向)