带大家过一遍如何分析常见java层的数据库加密密钥
确定数据库是否加密,找到数据库路径,对于有度这个样本,其数据库位于/data/data/im.xinda.youdu/files/youdu/db/
路径下,该路径会存在1个或多个文件夹,用于存储不同用户的数据,其格式为buin_用户uin_user_用户gid
,其中uin
和gid
是解密的关键,请看后续分析
进行逆向分析前,先看一下应用的lib目录里都有些什么东西,可以很明显地看到libwcdb.so
这个依赖,显然这个应用大概率是使用了wcdb来完成数据库操作,我们只需要跟一下wcdb的数据库打开操作,即可找到密钥计算方式
使用jadx进行反编译,找到wcdb的com.tencent.wcdb.database.SQLiteOpenHelper
类,在这里可以看到封装了一个获取数据库的方法,其中调用了openOrCreateDatabase
去打开数据库,第3个参数就是密钥
跟一下就能看到,实际是要调用com.tencent.wcdb.database.SQLiteDatabase.openDatabase
这个方法去打开数据库
这个方法中的第二个参数就是密钥
在com.tencent.wcdb.database.SQLiteDatabase
下又有很多基于openDatabase
封装的方法,都是用于接收不同的参数去打开数据库,所以只需要查看这些方法(包括com.tencent.wcdb.database.SQLiteOpenHelper
类中的一些方法)的引用即可
这样可以找到疑似的引用
咱们一步步往上跟,这里的第3个参数是密钥
这个方法有2个引用
第一个跟下去是直接拿已经计算好的密钥,可以暂时不去管
第二个跟下去,第4个参数是密钥
继续跟,这边第3个参数是密钥
这个跟一下,第一个实际上就是之前直接获取密钥的那个,不去管他,后面两个跟哪个都能拿到结果
先跟第二个吧,这边直接看密钥计算的方法
跟一层其实就比较清晰了,传入YDAccountInfo
,获取其中的两个值,然后和其他内容一起算sha1得到密钥
yDAccountInfo.b()
其实就是uin
,yDAccountInfo.a()
其实就是gid
而前面那一段,其实是获取了设备的deviceID
,通常返回的是IMEI或者MEID
那么密钥实际上就是SHA1(deviceID的hashcode+uin+601216000547603300+gid)
的结果
如果是跟第3个,则是直接传入了uin
和gid
,从哪来的呢?
跟一下a2
其实就知道了,是将数据库目录名进行拆分,分别获取到uin
和gid
,也就是我开头所说的
现在的问题是,deviceID
如何获取?实际上应用都会将数据进行保存,这里就是保存的操作
每次获取前,实际都是先从文件读取看看有没有,没有才再次获取
那么从这里往下跟,就能找到数据所在目录是global
这里有account_info和deviceId,可以拿到密钥计算所需要的内容
接下来计算密钥
from hashlib import sha1def java_string_hashcode(s):h = 0for char in s:h = (31 * h + ord(char)) & 0xFFFFFFFFif h > 0x7FFFFFFF:h -= 0x100000000return hhashcode = java_string_hashcode('863640031394918')salt = '601216000547603300'
uin = '14104680'
gid = '100022'
_sha1 = sha1()
_sha1.update((str(hashcode)+uin+salt+gid).encode('utf8'))
print(_sha1.hexdigest())
#6d2ac6f687955b27373cdeb422b4737d1f7b7c92
在im.xinda.youdu.lib.xutils.DbUtils
中还定义了数据库采用默认的SQLCipher3
参数进行加密
成功解密数据库
逆向分析就到此结束,此外还能通过对wcdb的数据库打开方法进行hook,从而达到对该框架的密钥通杀(要想hook已经提取出的应用的密钥,可以使用仿真
的方式,即在模拟器中安装目标应用,将提取出的数据覆盖到模拟器中,大多数情况下都可以正常打开(断网状态))
setImmediate(function () {Java.perform(function () {function bytesToString(arr) {var str = '';arr = new Uint8Array(arr);for (var i in arr) {str += String.fromCharCode(arr[i]);}return str;}let SQLiteDatabase = Java.use('com.tencent.wcdb.database.SQLiteDatabase');SQLiteDatabase['openDatabase'].overload('java.lang.String', '[B', 'com.tencent.wcdb.database.SQLiteCipherSpec', 'com.tencent.wcdb.database.SQLiteDatabase$CursorFactory', 'int', 'com.tencent.wcdb.DatabaseErrorHandler', 'int').implementation = function (str, bArr, sQLiteCipherSpec, cursorFactory, i, databaseErrorHandler, i2) {console.log(`SQLiteDatabase.openDatabase is called: str=${ str }, bArr=${ bytesToString(bArr) }, sQLiteCipherSpec=${ sQLiteCipherSpec }, cursorFactory=${ cursorFactory }, i=${ i }, databaseErrorHandler=${ databaseErrorHandler }, i2=${ i2 }`);let result = this['openDatabase'](str, bArr, sQLiteCipherSpec, cursorFactory, i, databaseErrorHandler, i2);console.log(`SQLiteDatabase.openDatabase result=${ result }`);return result;};});
});
easyFrida已经支持wcdb的hook
这种非常用的(内部通讯工具),我就不加到ForensicsTool里了