DexClassLoader解析微信朋友圈数据库
前提
当我们拿到微信的朋友圈数据库的时候,我们能够直接打开,这个数据库并没有像EnMicroMsg数据库一样采用了加密方式。但是当我们想要获取其内容的时候,发现content和attrBuf加密了,这是个Blob型的字段,虽然我们可以直接用String(contentBlob) 的方式拿到字符串,但是显示的内容夹杂了许多乱码。如下图所示:
发送一个纯文本朋友圈,且内容为:hello
可以看到图上白圈内就是hello的字段,且还有我的微信号
为了找到解密方法,我查了一些资料,这里分享一个很好的文章,然后此文章也是因此而派生出来的~
https://www.jianshu.com/p/0704e0d58531
工具
root手机一部
DexClassLoader
打开上面的那个链接,翻到最后面,作者有列出两种方式来解析数据,而本文的主题就是使用DexClassLoader来解析数据,所以接下来以此来进行探究吧~
我们先来看看DexClassLoader的构造方法,把需要实例化该类的参数了解一下。
public DexClassLoader(String dexPath, String optioptimizedmizedDirectory, String librarySearchPath, ClassLoader parent)
dexPath: 需要载入的apk文件的位置,在本文中需要指向微信apk的位置。
optioptimizedmizedDirectory: 这个目录是apk的解压路径。
librarySearchPath: 目标类可能使用的c或者c++的库文件的存放路径。
parent: 就是一个ClassLoader对象了。
接下来我们就一步一步操作吧。
- 先找到微信apk的路径
微信的apk是存放在 /data/app/ 目录下 com.tencent.mm+随机字符的文件夹中,那么我们就先写一个方法(由于方法简单就不展示),来遍历 /data/app/ 目录下包含com.tencent.mm字符的目录,再指到该目录下base.apk。 - 实例化DexClassLoader
通过第1步,我们已经拿到微信apk的路径,那么我们就直接使用吧:
val dexClassLoader = DexClassLoader(
wxApkFile.absolutePath,
context.getDir("dex1", 0).absolutePath,
null,
context.classLoader
)
- 加载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)
- 无脑的翻代码
使用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中的数据的时候,还真就获取到了一些有用的数据~
扩展
以上就是SnsMicroMsg数据库中SnsInfo表中content字段的解析。
但是当我们想再次以同样的方式解析attrBuf的时候,却出错了。 于是我很快的翻阅微信源码,查找关键字段:attrBuf
如上图红框的内容,看到了很熟悉的方法,但是却是不同的类来调用,根据上面的推测,这个类极有可能是继承自com.tencent.mm.bt.a,所以赶紧找到该类:
果然是继承 com.tencent.mm.bt.a!!,那么要解析attrBuf字段的话,就使用dexClassloader来加载这个cbf的类,使用该类调用parseFrom(byte[])方法就能够拿到attrBuf中的数据了!