DexClassLoader解析微信朋友圈数据库

  • 前提
  • 工具
  • DexClassLoader
  • 扩展

前提

当我们拿到微信的朋友圈数据库的时候,我们能够直接打开,这个数据库并没有像EnMicroMsg数据库一样采用了加密方式。但是当我们想要获取其内容的时候,发现content和attrBuf加密了,这是个Blob型的字段,虽然我们可以直接用String(contentBlob) 的方式拿到字符串,但是显示的内容夹杂了许多乱码。如下图所示:

发送一个纯文本朋友圈,且内容为:hello

微信开发中的好友关系存储用的什么数据库 微信文件好友数据库_微信开发中的好友关系存储用的什么数据库


工具

root手机一部

DexClassLoader

打开上面的那个链接,翻到最后面,作者有列出两种方式来解析数据,而本文的主题就是使用DexClassLoader来解析数据,所以接下来以此来进行探究吧~

我们先来看看DexClassLoader的构造方法,把需要实例化该类的参数了解一下。

public DexClassLoader(String dexPath, String optioptimizedmizedDirectory, String librarySearchPath, ClassLoader parent)

dexPath: 需要载入的apk文件的位置,在本文中需要指向微信apk的位置。
optioptimizedmizedDirectory: 这个目录是apk的解压路径。
librarySearchPath: 目标类可能使用的c或者c++的库文件的存放路径。
parent: 就是一个ClassLoader对象了。

接下来我们就一步一步操作吧。

  1. 先找到微信apk的路径
    微信的apk是存放在 /data/app/ 目录下 com.tencent.mm+随机字符的文件夹中,那么我们就先写一个方法(由于方法简单就不展示),来遍历 /data/app/ 目录下包含com.tencent.mm字符的目录,再指到该目录下base.apk。
  2. 实例化DexClassLoader
    通过第1步,我们已经拿到微信apk的路径,那么我们就直接使用吧:
val dexClassLoader = DexClassLoader(
                        wxApkFile.absolutePath,
                        context.getDir("dex1", 0).absolutePath, 
                        null,
                        context.classLoader
                    )
  1. 加载TimeLineObject类
    根据上面的文章,我们可以知道,微信是通过TimeLineObject这个类,来解析content的,那么我们就直接加载该类,并且调用其parseFrom(byte[])方法
val timeLineObjects = dexClassLoader.loadClass("com.tencent.mm.protocal.protobuf.TimeLineObject")
 val parseMeths = timeLineObjects.getMethod("parseFrom", ByteArray::class.java)
 //contentBlob是从数据库中直接获取!
 val parsedObj = parseMeths.invoke(timeLineObjects.newInstance(), contentBlob)
  1. 无脑的翻代码
    使用jadx工具解析微信的apk,我们查看timeLineObjects这个类,他里面的字段有String、int和其他类类型的属性,其实我们现在就可以直接通过反射的方式来获取其字段的值,不过先等等,因为我发现一个特点:那就是timeLineObjects的其他字段类型(不包括String和int)和timeLineObjects一样,都是继承自一个
import com.tencent.mm.bt.a

当我们打开这个类,一下子就恍然大悟了!
该类中包含一个parseFrom(bye[])方法,我推测:所有继承该类的派生类都相当于是一个能够加密的储存数据的bean类
那么我们就直接创建一个递归方法来遍历该类,找到能够展示数据的字段吧

fun findAllDisplayData(obj: Any, map: HashMap<String, String>) {
        val fields = obj.javaClass.fields
        if (fields.isEmpty()) {
            return
        }
        fields.forEach {
        	//使用下方Log,可以获取所有字段的类型
            //Log.e("===>SnsType", "===>${it.type.name}")
            when (it.type.name) {
            	//获取String类型的字段
                "java.lang.String" -> {
                    val valueObj = it.get(obj)
                    if (valueObj != null) {
                        map["${obj.hashCode()}" + it.name] = valueObj as String
                    }
                }
                "long",
                "float",
                "boolean",
                "java.lang.Boolean",
                "int" -> {
                    val valueObj = it.get(obj)
                    if (valueObj != null) {
                        map["${obj.hashCode()}" + it.name] = valueObj.toString()
                    }
                }
                "java.util.LinkedList" -> {
                    Log.e("===>SnsDeFind", "卧槽!这是个LinkedList")
                    val valueObj = it.get(obj)
                    if (valueObj != null) {
                        val list = valueObj as LinkedList<*>
                        list.forEach { item ->
                            findAllStringAndInt(item, map)
                        }
                    }
                }
                else -> {
                    val valueObj = it.get(obj)
                    if (valueObj != null) {
                        findAllStringAndInt(valueObj, map)
                    }
                }
            }
        }
    }

当我们打印map中的数据的时候,还真就获取到了一些有用的数据~

微信开发中的好友关系存储用的什么数据库 微信文件好友数据库_微信朋友圈_02

扩展

以上就是SnsMicroMsg数据库中SnsInfo表中content字段的解析。

但是当我们想再次以同样的方式解析attrBuf的时候,却出错了。 于是我很快的翻阅微信源码,查找关键字段:attrBuf

微信开发中的好友关系存储用的什么数据库 微信文件好友数据库_微信开发中的好友关系存储用的什么数据库_03


如上图红框的内容,看到了很熟悉的方法,但是却是不同的类来调用,根据上面的推测,这个类极有可能是继承自com.tencent.mm.bt.a,所以赶紧找到该类:

微信开发中的好友关系存储用的什么数据库 微信文件好友数据库_微信开发中的好友关系存储用的什么数据库_04


果然是继承 com.tencent.mm.bt.a!!,那么要解析attrBuf字段的话,就使用dexClassloader来加载这个cbf的类,使用该类调用parseFrom(byte[])方法就能够拿到attrBuf中的数据了!