之前写过一系列微信数据库的文章,包括找句柄、获取数据库密钥、调用sqlite3_exec
查询、备份、解密等。但是一直不知道怎么直接操作加密的库,近来发现腾讯开源了WCDB,几个平台的微信数据库都是以这个作为底层,Windows微信也不例外,遂拉代码,编译,记录下打开数据库后执行的一系列PRAGMA
命令,再使用普通的sqlcipher
执行相似操作,也可以打开。
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
和其他静态库。
以下是一个小demo,可以把上一步操作中生成的WCDB
项目中的附加包含目录拷贝到自己的项目中,这样就不会缺头文件,然后还需要链接WCDB.lib
。
吐槽一下,本来我是打算写个sql查询的,无奈文档实在是一坨,搞不明白为什么execute的入参为什么要一个Statement,而且还难以构造,应该是我太菜了,无奈写了个tableExists
。
之后我在所有的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
是编译WCDB
时生成的静态库,如果要做尝试,您还需要配置openssl
。以下是一个demo:
大家可以注意到在demo中使用了一个sqlite3CodecGetKey
函数,这个函数,我开始以为它会返回设置的密钥,但是却得到了一个长度为96的hex字符串,这里就很迷惑了,在迷惑的时候我尝试把那个字符串作为key并使用PRAGMA
命令输入,发现可以成功打开数据库,接下来就是找到key的生成,并验证普通的sqlcipher
能否使用上述流程打开数据库。
这一步就简单说下思路吧,很久之前网上就有某大佬写的离线解密数据库的博客,这里面其实就有计算Key的过程。
1、读取数据库文件前16字节
2、使用hkdf对原始密钥扩展64000次,salt是第一步读取的内容,扩展后的密钥的进行hex得到Key前64位
3、第二步得到的Key和hex后的salt拼接起来,就是该数据库的访问密钥
也难怪那些Sqlcipher查看工具无法打开Windows微信数据库,因为每个数据库本质上有不同的Key。
此处略过,可以参考网上的指南,但介绍踩过的两个坑。
1、执行python setup.py build_amalgamation
报错,可以修改setup.py
中的quote_argument
函数,实际上是宏定义的问题。
2、from pysqlcipher3 import dbapi2
报错_sqlite3
找不到指定的模块,解决方案是拷贝libcrypto-1_1-x64.dll
到pysqlcipher3
安装目录。
这里还是提供一个demo,相信大家一看就能懂。
工具是Database Viewer For Sqlite
。
用真实的Key(选择rawKey,前两个字符必须是0x
)+自定义配置(4096+64000+SHA1+SHA1
),也可以成功打开。
本文章中所有内容仅供学习交流使用,不用于其他任何目的,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!
若有侵权,请联系我立即删除。
#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;
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);
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;
}
#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;
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);
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;
}
#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;
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;
index = sqlcipher_find_db_index(db,
"binary"
);
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;
}
#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;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2024-4-28 09:56
被ljc545w编辑
,原因: 改正一处编辑错误