写在前面
之前写过一系列微信数据库的文章,包括找句柄、获取数据库密钥、调用sqlite3_exec查询、备份、解密等。但是一直不知道怎么直接操作加密的库,近来发现腾讯开源了WCDB,几个平台的微信数据库都是以这个作为底层,Windows微信也不例外,遂拉代码,编译,记录下打开数据库后执行的一系列PRAGMA命令,再使用普通的sqlcipher执行相似操作,也可以打开。
编译WCDB
前置操作
1、拉取WCDB
2、在WCDB目录下拉取sqlcipher并检出035036eb02f68c2978ae18693427cd0f786df93e分支
3、在WCDB目录下拉取zstd并检出69036dffe50f385bd3b7b187e3fd230f4b2ef97e分支
4、安装cmake
5、安装Visual Studio(我使用的是VS2022)
注:因为在WCDB中已经提供了openssl的二进制文件,所以这里不需要拉取源码
构建解决方案

然后打开解决方案,平台配置选择Release,然后右键ALL_BUILD,构建完成后可以在build\Relese中找到生成的WCDB.dll、WCDB.lib和其他静态库。
使用WCDB打开微信数据库
以下是一个小demo,可以把上一步操作中生成的WCDB项目中的附加包含目录拷贝到自己的项目中,这样就不会缺头文件,然后还需要链接WCDB.lib。
吐槽一下,本来我是打算写个sql查询的,无奈文档实在是一坨,搞不明白为什么execute的入参为什么要一个Statement,而且还难以构造,应该是我太菜了,无奈写了个tableExists。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #include<iostream>#include<string>#include<WCDBCpp.h>static std::string bytesFromHex(const std::string& _Src) { std::string _Out; for (unsigned int i = 0; i < _Src.length(); i += 2) { _Out.push_back(std::stoi(_Src.substr(i, 2), 0, 16)); } return _Out;}int main() { int rc = 0; // 这个key是微信服务器下发的,请想办法获取自己的key std::string szDbKey = "0C1ADFFBE53345BAA59C748B7660EAC8542AD78C64FC422F9B278E5336EE3EB0"; std::string binDbKey = bytesFromHex(szDbKey); WCDB::UnsafeData usDbKey((unsigned char*)binDbKey.data(), binDbKey.size()); std::string szDbPath = R"(D:\CPLUSPLUS\wcdb_test\MicroMsg.db)"; WCDB::Database database(szDbPath); // pc端使用的pagesize是4096字节 database.setCipherKey(usDbKey, 4096, WCDB::Database::Version3); if (!database.canOpen() || !database.isOpened()) goto wrapup; std::cout << database.tableExists("Contact").value() << std::endl; wrapup: database.close(); return 0;} |
PRAGMA命令
之后我在所有的PRAGMA字符串打了断点,发现调用顺序大致如下:
1、PRAGMA wal_autocheckpoint = 0;
2、PRAGMA checkpoint_fullfsync = true;
3、PRAGMA temp_store = 1;
4、PRAGMA journal_mode = "WAL";
5、database.setCipherKey
6、PRAGMA cipher_compatibility = 3;
7、PRAGMA cipher_page_size = 4096;
有以下几处需要注意:
1、第四步会出现错误信息,因为此时还无法操作数据库。
2、第五步可以使用腾讯提供的sqlcipher中的sqlite3_key_v2函数代替,使用同样的key。原生sqlcipher暂不知是否有效。
3、第五步可以用PRAGMA key = "x'...'";,但是不能直接使用setCipherKey函数所使用的key,需要经过一些转换,这个后面会提及。
使用sqlcipher打开微信数据库
这里使用的sqlcipher是编译WCDB时生成的静态库,如果要做尝试,您还需要配置openssl。以下是一个demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | // 需要在预处理器中添加下面两个宏// SQLITE_WCDB// SQLITE_HAS_CODEC#include<iostream>#include<string>#include<sqlcipher/sqlite3.h>#include<sqlcipher/sqlite3_wcdb.h>static std::string bytesFromHex(const std::string& _Src) { std::string _Out; for (unsigned int i = 0; i < _Src.length(); i += 2) { _Out.push_back(std::stoi(_Src.substr(i, 2), 0, 16)); } return _Out;}static int callback(void* notUsed, int argc, char** argv, char** azColName){ for (int i = 0; i < argc; i++) std::cout << azColName[i] << ":" << (argv[i] ? argv[i] : "NULL") << "\t"; std::cout << std::endl; return 0;}int main() { int rc = 0; std::string szDbKey = "0C1ADFFBE53345BAA59C748B7660EAC8542AD78C64FC422F9B278E5336EE3EB0"; std::string binDbKey = bytesFromHex(szDbKey); std::string szDbPath = R"(D:\CPLUSPLUS\wcdb_test\MicroMsg.db)"; sqlite3* db = nullptr; char* errmsg = nullptr; char* zKey = nullptr; int nKey = 0; int index = 0; rc = sqlite3_open_v2(szDbPath.c_str(), &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_MAINDB_READONLY, 0); if (rc != SQLITE_OK) { std::cout << "打开或创建数据库失败" << std::endl; goto wrapup; } rc = sqlite3_exec(db, R"(PRAGMA wal_autocheckpoint = 0;)", nullptr, nullptr, &errmsg); std::cout << rc << std::endl; rc = sqlite3_exec(db, R"(PRAGMA checkpoint_fullfsync = true;)", nullptr, nullptr, &errmsg); std::cout << rc << std::endl; rc = sqlite3_exec(db, R"(PRAGMA temp_store = 1;)", nullptr, nullptr, &errmsg); std::cout << rc << std::endl; // sqlite3_key和sqlite3_key_v2都有效 rc = sqlite3_key(db, binDbKey.c_str(), (int)binDbKey.size()); std::cout << rc << std::endl; rc = sqlite3_exec(db, R"(PRAGMA cipher_compatibility = 3;)", nullptr, nullptr, &errmsg); std::cout << rc << std::endl; rc = sqlite3_exec(db, R"(PRAGMA cipher_page_size = 4096;)", nullptr, nullptr, &errmsg); std::cout << rc << std::endl; // 因为我知道这一步在前面会出错所以放到后面了 rc = sqlite3_exec(db, R"(PRAGMA journal_mode = "WAL";)", nullptr, nullptr, &errmsg); std::cout << rc << std::endl; // 这一步的`binary`我随便写的,不知道该写什么 index = sqlcipher_find_db_index(db, "binary"); // 这里的index只要是0就会有返回 sqlite3CodecGetKey(db, index, (void**)&zKey, &nKey); rc = sqlite3_exec(db, "SELECT COUNT(*) FROM Contact;", callback, NULL, &errmsg); if (rc != SQLITE_OK) { std::cout << "SQL语句执行时发生错误: " << errmsg << std::endl; goto wrapup; }wrapup: rc = sqlite3_close(db); std::cout << rc << std::endl; return 0;} |
大家可以注意到在demo中使用了一个sqlite3CodecGetKey函数,这个函数,我开始以为它会返回设置的密钥,但是却得到了一个长度为96的hex字符串,这里就很迷惑了,在迷惑的时候我尝试把那个字符串作为key并使用PRAGMA命令输入,发现可以成功打开数据库,接下来就是找到key的生成,并验证普通的sqlcipher能否使用上述流程打开数据库。
Key的生成
这一步就简单说下思路吧,很久之前网上就有某大佬写的离线解密数据库的博客,这里面其实就有计算Key的过程。
1、读取数据库文件前16字节
2、使用hkdf对原始密钥扩展64000次,salt是第一步读取的内容,扩展后的密钥的进行hex得到Key前64位
3、第二步得到的Key和hex后的salt拼接起来,就是该数据库的访问密钥
也难怪那些Sqlcipher查看工具无法打开Windows微信数据库,因为每个数据库本质上有不同的Key。
使用pysqlcipher3打开微信数据库
编译pysqlcipher3
此处略过,可以参考网上的指南,但介绍踩过的两个坑。
1、执行python setup.py build_amalgamation报错,可以修改setup.py中的quote_argument函数,实际上是宏定义的问题。
1 2 3 4 | def quote_argument(arg): # quote = '"' if sys.platform != 'win32' else '\\"' quote = '"' if sys.platform != 'win32' else '"' return quote + arg + quote |
2、from pysqlcipher3 import dbapi2报错_sqlite3找不到指定的模块,解决方案是拷贝libcrypto-1_1-x64.dll到pysqlcipher3安装目录。
打开数据库
这里还是提供一个demo,相信大家一看就能懂。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | import hashlibfrom pysqlcipher3 import dbapi2 as sqlite3KEY_SIZE = 32SALT_SIZE = 16DEFAULT_PAGESIZE = 4096DEFAULT_ITER = 64000def make_cipher_key(db_path, password): f = open(db_path, "rb") mac_salt = f.read(SALT_SIZE) f.close() rawKey = bytes.fromhex(password) mac_key = hashlib.pbkdf2_hmac('sha1', rawKey, mac_salt, DEFAULT_ITER, KEY_SIZE) return (mac_key + mac_salt).hex() def open_wcdb(db_path, password): # 扩展出真实的密钥 realKey = make_cipher_key(db_path, password) _db = sqlite3.connect(db_path) _db.execute("PRAGMA wal_autocheckpoint = 0;") _db.execute("PRAGMA checkpoint_fullfsync = true;") _db.execute("PRAGMA temp_store = 1;") _db.execute("PRAGMA key = \"x'{}'\";".format(realKey)) # 这两步操作必须在set database key之后 _db.execute("PRAGMA cipher_compatibility = 3;") _db.execute("PRAGMA cipher_page_size = 4096;") _db.execute("PRAGMA journal_mode = \"WAL\";") return _db if __name__ == "__main__": file_path = r"D:\MicroMsg.db" # 这个是微信服务器下发的key key = "0C1ADFFBE53345BAA59C748B7660EAC8542AD78C64FC422F9B278E5336EE3EB0" db = open_wcdb(file_path, key) result = db.execute("SELECT userName FROM Contact limit 100;").fetchall() print(result) |
使用第三方数据库查看工具打开
工具是DB Browser for SQLCipher。
用真实的Key(选择rawKey,前两个字符必须是0x)+自定义配置(4096+64000+SHA1+SHA1),也可以成功打开。
写在后面
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
若有侵权,请联系我立即删除。
PC微信4.0
变化不大,主要是sqlcipher版本的变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | import hashlibfrom pysqlcipher3 import dbapi2 as sqlite3KEY_SIZE = 32SALT_SIZE = 16DEFAULT_PAGESIZE = 4096# 迭代次数变更为256000DEFAULT_ITER = 256000def make_cipher_key(db_path, password): f = open(db_path, "rb") mac_salt = f.read(SALT_SIZE) f.close() rawKey = bytes.fromhex(password) # 密钥扩展算法变更为sha512 mac_key = hashlib.pbkdf2_hmac('sha512', rawKey, mac_salt, DEFAULT_ITER, KEY_SIZE) return (mac_key + mac_salt).hex() def open_wcdb(db_path, password): # 扩展出真实的密钥 realKey = make_cipher_key(db_path, password) _db = sqlite3.connect(db_path) _db.execute("PRAGMA wal_autocheckpoint = 0;") _db.execute("PRAGMA checkpoint_fullfsync = true;") _db.execute("PRAGMA temp_store = 1;") _db.execute("PRAGMA key = \"x'{}'\";".format(realKey)) # 这两步操作必须在set database key之后 _db.execute("PRAGMA cipher_compatibility = 4;") # sqlcipher版本变化为4 _db.execute("PRAGMA cipher_page_size = {};".format(DEFAULT_PAGESIZE)) _db.execute("PRAGMA journal_mode = \"WAL\";") return _db if __name__ == "__main__": file_path = r"D:\contact.db" # 这个是微信服务器下发的key key = "0C1ADFFBE53345BAA59C748B7660EAC8542AD78C64FC422F9B278E5336EE3EB0" db = open_wcdb(file_path, key) |
DB Browser for SQLCipher使用sqlcipher4默认配置即可。
