这个我实际上弄了很久了,一开始更新的时候,发现数据库操作都是在so里,那时候是在libkernel.so
里直接hook sqlcipher的密钥函数拿到的密钥,32位字符串,很容易让人联想到md5,但是没有找到在哪里计算的
最近又想着做一下,这时打开数据库的so就变了,这是easyFrida
的sofileopen
插件hook出来的结果,目前使用的是libbasic_share.so
这个so
不过这个so在apk包里面并没有,应该是运行的时候释放出来的
在应用目录里是可以拿到的
结果这个so的导出表里有md5相关的函数,而以前的libkernel
并没有,感谢qq!!!!!!!!!!!!!
而且很多以前没有函数名的函数,这次也能看到函数名了,就比如密钥初始化函数
hook一下拿到密钥(直接attach,然后在登录、查看个人信息等时机可以hook到)
setImmediate(function () {Java.perform(function () {function buf2str(buffer) {let result = '';const byteArray = new Uint8Array(buffer);for (let i = 0; i < byteArray.length; i++) {result += String.fromCharCode(byteArray[i]);}return result;}var baseAddr = Module.findBaseAddress('libbasic_share.so');console.log(`baseAddr:${ baseAddr }`);var keyFuncAddr = baseAddr.add(5083692);//nt_sqlite3_key_v2的地址Interceptor.attach(keyFuncAddr, {onEnter: function (args) {var nk = args[3].toInt32();var pk = args[2].readByteArray(nk);console.log('pKey--------->' + buf2str(pk));},onLeave: function (retval) {}});});
});
拿到的这个key,非常符合md5的结果,前面又有md5导出函数,干脆就看一下?
先hook了MD5DigestToBase16
,可以看到aa030bae477df5ced6093d4702523a3a
和d28951ee5178259c2916f46f37a14c30
,前者与数据库路径有关,后者就是数据库密钥
setImmediate(function () {function readStdString(str) {const isTiny = (str.readU8() & 1) === 0;// 检查是否为小字符串if (isTiny) {return str.add(1).readUtf8String(); // 读取小字符串}return str.add(2 * Process.pointerSize).readPointer().readUtf8String(); // 读取大字符串}Java.perform(function () {Interceptor.attach(Module.findExportByName('libbasic_share.so', '_ZN4xpng17MD5DigestToBase16ERKNS_9MD5DigestE'), {onEnter: function (args) {},onLeave: function (retval) {console.log('md5 ret----------->' + readStdString(retval));}});});
});
然后再看一下update函数的参数,能够看到到底是计算了哪些,能看到密钥对应的参数是c8d05c49e391e2e1d0bda579cc5250085sWvEGXu
,恰好是上一条u_bv0QrXXA4YqAB3PaYr3PpQ
的md5结果,而前面路径中的值则和这个结果也有关联,是md5(md5(u_bv0QrXXA4YqAB3PaYr3PpQ)+'nt_kernel')
,从而得到aa030bae477df5ced6093d4702523a3a
,那么密钥参数c8d05c49e391e2e1d0bda579cc5250085sWvEGXu
的后面几个字符串是哪来的?
setImmediate(function () {Java.perform(function () {Interceptor.attach(Module.findExportByName("libbasic_share.so",'_ZN4xpng9MD5UpdateEPA88_cPKhm'), {onEnter: function (args) {console.log('update data ------->', args[1].readUtf8String(args[2].toInt32()));},onLeave: function (retval) {}});});
})
居然都是在数据库里?
原来是在文件头写好的
那么密钥的算法就是md5(md5(u_bv0QrXXA4YqAB3PaYr3PpQ)+数据库文件头中的随机字符串)
,就是0x1208
和0x1a07
之间的字符串
那么现在u_bv0QrXXA4YqAB3PaYr3PpQ
这一串又是什么呢?其实这个就是QQ的uid,之前分析手机大师日志的时候,恰好看到了,从mmkv(读取这玩意儿还不是个容易事儿,得通过腾讯开源的mmkv/Python去编译。。索性搓了一个解析工具,省的换机器要重新编译)中读取用户的uid,实际上是通过QQ号(uin)去获取的
因此最终,数据库路径中的哈希=`md5(md5(uid)+nt_kernel),密钥=
md5(md5(uid)+文件头字符串)`
而要打开数据库的话,需要先把数据库的前1024字节删掉,然后进行解密,解密参数中,hmac算法应该是文件头中的sha1,kdf_iter是4000,其他都是sqlcipher4的配置
打开数据库后40800
字段是聊天记录,内容是protobuf编码,45101
是聊天内容
ForensicsTool集成了ntqq数据库解密
mmkvReader会同步上传至GitHub
想上传到之前刷到过的一个github,hook密钥就是从那学的,结果发现人家已经把算法搞出来了。。差距啊