您的位置:首页 > 移动开发 > 微信开发

Android数据库加解密逆向分析(三)——微信数据库密码破解

qq_24280381 2017-06-20 22:00 13 查看
接着上一篇文章,在上一篇文章中我们通过对Line数据库加密的逆向分析,了解到了对要写入到数据库中的数据加密,读取时再将读取出的数据解密这种Android上的数据库加密方式。这篇文章来通过介绍对微信数据库密码的破解来了解下对整个数据库加密的这种Android数据库加密方式。

一、安装、反编译微信,查找本地数据库

1、直接使用apktool反编译微信,这里微信的版本是6.5.8。虽然腾讯现在已经有了非常成熟的加固方案(腾讯乐固),但是却并没有使用到微信上,估计是因为微信对应用的稳定性和适配性等要求非常高,如果加壳的话虽然一定程度上可以增加破解的难度,但是稳定性可能会使稳定性下降,所以就没有对微信、QQ等进行加固。



2、使用jadx打开相应的3个dex文件,准备静态分析,这里只用到了classes.dex这一个dex。

3、反编译后将反编译出的微信项目整个导入到Android Studio中,为动态调试做准备。

4、在手机上安装微信,然后进入微信的私有目录/data/data/com.tencent.mm/中查找数据库保存的位置。最终在/data/data/com.tencent.mm/MicroMsg/5521446c2085c943efd63384da0129下找到了加密的数据库:



同时可以在微信的lib路径下找到用于加密数据库的so文件:



使用IDA PRO打开该so可以看到相应的对数据库进行加密的sqlite3函数:



二、静态分析,使用hook方式得到数据库的密码

1、首先寻找关键点,在Android中对数据库进行操作一定会用到SQLiteDatabase这个类,所以我们首先在Jadx中全局查找SQLiteDatabase:



可以看到微信中自己定义了一个SQLiteDatabase类:com.tencent.wcdb.database.SQLiteDatabase,打开该类:



可以看到,在这个类中有很多关于openDatabase和openOrCreateDatabase函数的重载,但是最终都会调用到open函数来打开数据库。

2、进入open函数,查看open函数的具体实现:



可以看到在open函数中调用openInner函数来打开数据库。

3、进入openInner函数:



在openInner中调用了数据库连接池类:SQLiteConnectionPool的open方法

4、进入数据库连接池的open方法:



可以看到,在open方法中传入的参数byte数组bArr就是该数据库的密码。

5、分析到这里,已经知道了数据库的密码是open函数的参数之一,我们可以直接hook open函数得到数据库的密码。这里我们使用Xposed框架来对该SQLiteConnectionPool的open函数进行hook,得到该函数的bArr参数,关于Xposed框架的使用可以参考我以前写的Xposed框架开发入门(一)Xposed框架开发入门(二)–使用Xposed框架实现Activity跳转拦截Xposed框架开发入门(三)–Android某输入法用户个人词库提取 这三篇文章:

在hook该open方法之前要得到该open方法所涉及的相关参数的类的Class对象:



得到了相关Class对象之后就可以对open方法进行hook了:



6、安装该模块,在Xposed中激活,然后重启手机,打开微信,在DDMS中查看Log输出:



在这里就得到了我们数据库的密码:7e3f795

7、使用改密码在SQLite Database Browser中打开该数据库:



这里是该数据库相关表以及相应的表创建语句,在BrowserData选项中选择一个表即可查看该表中的记录:



三、分析该密码的生成方式

经过前面的分析与操作,我们已经成功的得到该数据库的密码。但是这个密码看起来完全没有规律,所以我们可以去分析一下这个密码的生成过程,看看这个密码是如何生成的。

1、我们这里使用动态调试来分析这个密码的生成过程。首先我们可以发现这个密码是在调用SQLiteDatabase的openDatabase方法中传入的,所以在调用这个方法前肯定有这个密码的生成过程,密码生成后调用openDatabase方法传入密码参数,打开数据库。所以我们先在SQLiteDatabase的openDatabase方法处下断点,单步调试跑完openDatabase方法后就进入了调用该openDatabase方法的方法了。



在这里使用Android Studio动态调试程序时因为如果微信已经启动起来之后是不会再调用openDatabase方法的,所以应该以调试模式打开微信,然后端口转发这种启动调试的方法来进行调试:



此时以调试模式打开的微信是这样的:



2、经过一层层的跟踪,最终找到是在com.tencent.mm.bi.e.q()方法中直接调用openDatabase方法:



查看com.tencent.mm.bi.e.q()方法的Java代码:



可以看到密码bArr是由q方法的参数str2通过str2.getBytes()生成的。

3、继续跟踪调用com.tencent.mm.bi.e.q()方法的地方:





可以看到com.tencent.mm.bi.e.q()方法是在com.tencent.mm.bi.a.b()方法中调用的。在上面我们可以看到密码substring是由deviceID和com.tencent.mm.bi.a.b()方法的long类型参数j合并调用其getBytes()方法得到一个byte数组,然后调用g.n(byte[])方法,取其返回值的前七位作为密码。

4、进入g.n方法:



可以看到这里很明显就是一个计算MD5值的函数,所以密码应该是由deviceID和参数j合并的bytes值,取其MD5值的前7位作为密码。

5、知道了密码的生成规则之后,我们来看deviceID和参数j都是些什么。



在这里可以看到deviceID是调用p.getDeviceID方法生成的,所以进入该方法:



可以看到这个deviceID的值其实就是我们移动设备的IMEI值。

在Android中我们可以通过

TelephonyManager telephonyManager = (TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);
String imei = telephonyManager.getDeviceId();


来获取Android设备的IMEI值

6、下来跟踪参数j的生成过程,看参数j是如何生成的。继续跟踪查看com.tencent.mm.bi.a.b()方法的调用的地方:





继续跟踪查看调用com.tencent.mm.bi.a.a()方法的地方:





继续跟踪,查看调用com.tencent.mm.bi.g.a()方法的地方:



7、可以看到在com.tencent.mm.bi.kernal.a.um()方法中调用了com.tencent.mm.bi.g.a()方法,um方法比较长,如下所示:



可以看到这个参数j就是微信用户的uin,uin就是user information,是微信为每个用户分配的一个内部ID

在um方法的

int uf = heY.uf();


这里下断点,然后调试查看该uin也就是uf的值:



可以看到我使用的这个微信的uin的值为91120166

当然uin一般都是存放在本地的/data/data/com.tencent.mm/shared_prefs/下的SharedPreferences文件中:



打开该shared_perferences文件可以看到用户的uin就保存在里面:



8、到这里我们整个微信数据库密码的生成规则基本上已经明确了:

1、首先得到设备的IMEI和微信用户的uin

2、合并IMEI和uin,将合并后的字符串转为byte数组

3、然后对该byte数组计算MD5值,取计算出的MD5值的前7位作为数据库的密码

四、生成数据库的密码

在这里,我们在前面hook项目的基础上,构建生成密码的项目:

1、在layout中设置两个TextView用来分别显示设备的imei值和数据库密码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.zhayh.weixinhookdemo.MainActivity">

<TextView
android:id="@+id/tv_imei"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="imei:" />
<TextView
android:id="@+id/tv_password"
android:layout_below="@id/tv_imei"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="password:"/>

</RelativeLayout>


2、在项目中添加用来计算MD5值的类:WeixinMD5

package com.zhayh.weixinhookdemo;

import java.security.MessageDigest;

public class WeixinMD5 {
public static final String n(byte[] bArr) {
int i = 0;
char[] cArr = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
try {
MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update(bArr);
byte[] digest = instance.digest();
int length = digest.length;
char[] cArr2 = new char[(length * 2)];
int i2 = 0;
while (i < length) {
byte b = digest[i];
int i3 = i2 + 1;
cArr2[i2] = cArr[(b >>> 4) & 15];
i2 = i3 + 1;
cArr2[i3] = cArr[b & 15];
i++;
}
String str = new String(cArr2);
return str;
} catch (Exception e) {
return null;
}
}

}


3、MainActivity中计算数据库的密码:

package com.zhayh.weixinhookdemo;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
private TextView tv_imei = null;
private TextView tv_passwordKey;
private String uin = "91120166";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
String imei = telephonyManager.getDeviceId();

tv_imei = (TextView) findViewById(R.id.tv_imei);
tv_passwordKey = (TextView) findViewById(R.id.tv_password);
tv_imei.append(imei);

String str = WeixinMD5.n((tv_imei + uin).getBytes()).substring(0,7);

tv_passwordKey.append(str);

}
}


之后运行项目,得到结果如下:



可以看到这里计算出来的结果就是我们在前面hook出的密码的值。

五、总结

1、在这篇文章里面我们通过逆向分析微信数据库密码的生成过程了解了Android的数据库整体加密技术,在这篇文章中,我们首先发现了SQLiteDatabase的open方法的参数包含数据库的密码,所以我们直接使用hook的方式取得了数据库的密码,同时成功的使用hook取得的密码打开了加密的数据库。

2、然后我们使用动态调试跟踪了微信数据库密码的生成过程,发现了微信数据库密码的生成方式是由设备号IMEI和微信用户uin值合并去计算MD5取结果的前7位作为数据库的密码。

3、知道了密码的生成方式后,我们就编写程序,生成数据库的密码。

最后给出项目的github地址:https://github.com/zyh16143998882/weixinHookDemo