郑州泰源数据恢复中心,手机数据恢复 导出微信聊天记录
2015-2-4 9:51:54
暂无图片。
详细介绍
首先,很容易发现数据库在/data/data/com.tencent.mm/MicroMsg/ 下,file之,显示为data…… 好吧看来被腾讯加密了。。
在/data/data/com.tencent.mm/里随便看,发现lib里有个libmmcrypto.so,大概就是用来加密的了。nm -D之,发现一堆sqlite3_*;稍微google下,觉得应该是用了sqlcipher。于是只用拿到密钥就好啦。
gdb
希望break到sqlite3_exec之类的函数来看到密钥。发现android自带的gdbserver有问题,attach上去后根本看不到调用栈,不明原因。于是手动编译了静态的arm构架的gdb,push上去,能attach和traceback了;但一旦下断点,目标进程就会蹦,不明原理。此想法失败。
替换掉libmmcrypto.so
希望用自己修改的sqlcipher来替换原来的库,把密钥直接打印出来。花了很多时间,交叉编译了不少东西,但最后微信在使用被换掉的库的情况下总还是要崩……
逆向+肉眼密钥算法
使用apktool逆向微信的app(需要注意的是使用apktol前应该安装framework,否则会各种错误;我在这里耗了好久)。grep 'PRAGMA key',真的有,前后看了下,发现貌似是把this中的一个东西作为了key。。。没找到生成密钥的算法……
injection
想到修改代码把key直接打出来。google了一会儿,发现了以下方法:
复制代码
然后再用logcat就能看到日志了。兴冲冲改好代码,打包,用signapk.jar签名,但进行安装时总是说Data exceeds UNCOMPRESS_DATA_MAX,会有一些资源找不到导致程序蹦掉。。google了很多解决方法都没成功。。
后来灵机一动,只把原来微信.apk里的classes.dex替换成修改过的版本,重新签名,it works,在log里看到密钥了!
一阵激动,于是把数据库拿到pc上,用本地编译的sqlcipher想直接读出来。。。但总还是失败。。难道腾讯还修改过加密算法…… 没有办法,想直接调用libmmcrypto.so。。结果发现我的工具链里的glibc和android系统里的版本不兼容,最终也没能实现交叉调用(这大概也是最初自己编译的sqlcipher不能被微信使用的原因)……
后来想起android貌似还有个ndk,于是糙快猛入门,编译了个executable出来,总算work了。。 运行过程中发现mmcrypto打出了些debug信息,估计真是被腾讯改过了;而且版本好老,木有sqlcipher_export ,还得自己手动写程序dump……
微信做了数据库加密,而且密钥跟机器相关(甚至可能还跟系统相关),一旦机器蹦了或丢了数据就没了,但不提供导出记录,呵呵……
附:dump数据库用的小程序(从sqlite3 里抠了不少代码……)
在/data/data/com.tencent.mm/里随便看,发现lib里有个libmmcrypto.so,大概就是用来加密的了。nm -D之,发现一堆sqlite3_*;稍微google下,觉得应该是用了sqlcipher。于是只用拿到密钥就好啦。
gdb
希望break到sqlite3_exec之类的函数来看到密钥。发现android自带的gdbserver有问题,attach上去后根本看不到调用栈,不明原因。于是手动编译了静态的arm构架的gdb,push上去,能attach和traceback了;但一旦下断点,目标进程就会蹦,不明原理。此想法失败。
替换掉libmmcrypto.so
希望用自己修改的sqlcipher来替换原来的库,把密钥直接打印出来。花了很多时间,交叉编译了不少东西,但最后微信在使用被换掉的库的情况下总还是要崩……
逆向+肉眼密钥算法
使用apktool逆向微信的app(需要注意的是使用apktol前应该安装framework,否则会各种错误;我在这里耗了好久)。grep 'PRAGMA key',真的有,前后看了下,发现貌似是把this中的一个东西作为了key。。。没找到生成密钥的算法……
injection
想到修改代码把key直接打出来。google了一会儿,发现了以下方法:
-
const-string v1, "!!!!!SQL: "
- invoke-static {v1, p1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I
然后再用logcat就能看到日志了。兴冲冲改好代码,打包,用signapk.jar签名,但进行安装时总是说Data exceeds UNCOMPRESS_DATA_MAX,会有一些资源找不到导致程序蹦掉。。google了很多解决方法都没成功。。
后来灵机一动,只把原来微信.apk里的classes.dex替换成修改过的版本,重新签名,it works,在log里看到密钥了!
一阵激动,于是把数据库拿到pc上,用本地编译的sqlcipher想直接读出来。。。但总还是失败。。难道腾讯还修改过加密算法…… 没有办法,想直接调用libmmcrypto.so。。结果发现我的工具链里的glibc和android系统里的版本不兼容,最终也没能实现交叉调用(这大概也是最初自己编译的sqlcipher不能被微信使用的原因)……
后来想起android貌似还有个ndk,于是糙快猛入门,编译了个executable出来,总算work了。。 运行过程中发现mmcrypto打出了些debug信息,估计真是被腾讯改过了;而且版本好老,木有sqlcipher_export ,还得自己手动写程序dump……
微信做了数据库加密,而且密钥跟机器相关(甚至可能还跟系统相关),一旦机器蹦了或丢了数据就没了,但不提供导出记录,呵呵……
附:dump数据库用的小程序(从sqlite3 里抠了不少代码……)
-
#include
-
#include
-
#include
-
#include
-
#include
-
#include
-
-
#define DECLARE(name) \
-
static typeof(name) *f_##name;
-
-
DECLARE(sqlite3_close)
-
DECLARE(sqlite3_column_text)
-
DECLARE(sqlite3_column_count)
-
DECLARE(sqlite3_errmsg)
-
DECLARE(sqlite3_exec)
-
DECLARE(sqlite3_finalize)
-
DECLARE(sqlite3_free)
-
DECLARE(sqlite3_initialize)
-
DECLARE(sqlite3_mprintf)
-
DECLARE(sqlite3_open_v2)
-
DECLARE(sqlite3_prepare)
-
DECLARE(sqlite3_shutdown)
-
DECLARE(sqlite3_snprintf)
-
DECLARE(sqlite3_step)
-
DECLARE(sqlite3_trace)
-
-
#define UNUSED_PARAMETER(name) (void)(name)
-
-
static int callback(void *NotUsed, int argc, char **argv, char **azColName){
-
UNUSED_PARAMETER(NotUsed);
-
int i;
-
for(i=0; i
-
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
-
}
-
printf("\n");
-
return 0;
-
}
-
/*
-
** An pointer to an instance of this structure is passed from
-
** the main program to the callback. This is used to communicate
-
** state and mode information.
-
*/
-
struct callback_data {
-
sqlite3 *db; /* The database */
-
int echoOn; /* True to echo input commands */
-
int statsOn; /* True to display memory stats before each finalize */
-
int cnt; /* Number of records displayed so far */
-
FILE *out; /* Write results here */
-
FILE *traceOut; /* Output for f_sqlite3_trace() */
-
int nErr; /* Number of errors seen */
-
int mode; /* An output mode setting */
-
int writableSchema; /* True if PRAGMA writable_schema=ON */
-
int showHeader; /* True to show column names in List or Column mode */
-
char *zDestTable; /* Name of destination table when MODE_Insert */
-
char separator[20]; /* Separator character for MODE_List */
-
int colWidth[100]; /* Requested width of each column when in column mode*/
-
int actualWidth[100]; /* Actual width of each column */
-
char nullvalue[20]; /* The text to print when a NULL comes back from
-
** the database */
-
const char *zDbFilename; /* name of the database file */
-
const char *zVfs; /* Name of VFS to use */
-
sqlite3_stmt *pStmt; /* Current statement if any. */
-
FILE *pLog; /* Write log output here */
-
};
-
-
/*
-
** Execute a query statement that will generate SQL output. Print
-
** the result columns, comma-separated, on a line and then add a
-
** semicolon terminator to the end of that line.
-
**
-
** If the number of columns is 1 and that column contains text "--"
-
** then write the semicolon on a separate line. That way, if a
-
** "--" comment occurs at the end of the statement, the comment
-
** won't consume the semicolon terminator.
-
*/
-
static int run_table_dump_query(
-
struct callback_data *p, /* Query context */
-
const char *zSelect, /* SELECT statement to extract content */
-
const char *zFirstRow /* Print before first row, if not NULL */
-
){
-
sqlite3_stmt *pSelect;
-
int rc;
-
int nResult;
-
int i;
-
const char *z;
-
rc = f_sqlite3_prepare(p->db, zSelect, -1, &pSelect, 0);
-
if( rc!=SQLITE_OK || !pSelect ){
-
fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, f_sqlite3_errmsg(p->db));
-
p->nErr++;
-
return rc;
-
}
-
rc = f_sqlite3_step(pSelect);
-
nResult = f_sqlite3_column_count(pSelect);
-
while( rc==SQLITE_ROW ){
-
if( zFirstRow ){
-
fprintf(p->out, "%s", zFirstRow);
-
zFirstRow = 0;
-
}
-
z = (const char*)f_sqlite3_column_text(pSelect, 0);
-
fprintf(p->out, "%s", z);
-
for(i=1; i
-
fprintf(p->out, ",%s", f_sqlite3_column_text(pSelect, i));
-
}
-
if( z==0 ) z = "";
-
while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
-
if( z[0] ){
-
fprintf(p->out, "\n;\n");
-
}else{
-
fprintf(p->out, ";\n");
-
}
-
rc = f_sqlite3_step(pSelect);
-
}
-
rc = f_sqlite3_finalize(pSelect);
-
if( rc!=SQLITE_OK ){
-
fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, f_sqlite3_errmsg(p->db));
-
p->nErr++;
-
}
-
return rc;
-
}
-
-
/*
-
** Compute a string length that is limited to what can be stored in
-
** lower 30 bits of a 32-bit signed integer.
-
*/
-
static int strlen30(const char *z){
-
const char *z2 = z;
-
while( *z2 ){ z2++; }
-
return 0x3fffffff & (int)(z2 - z);
-
}
-
-
-
/* zIn is either a pointer to a NULL-terminated string in memory obtained
-
** from malloc(), or a NULL pointer. The string pointed to by zAppend is
-
** added to zIn, and the result returned in memory obtained from malloc().
-
** zIn, if it was not NULL, is freed.
-
**
-
** If the third argument, quote, is not '\0', then it is used as a
-
** quote character for zAppend.
-
*/
-
static char *appendText(char *zIn, char const *zAppend, char quote){
-
int len;
-
int i;
-
int nAppend = strlen30(zAppend);
-
int nIn = (zIn?strlen30(zIn):0);
-
-
len = nAppend+nIn+1;
-
if( quote ){
-
len += 2;
-
for(i=0; i
-
if( zAppend[i]==quote ) len++;
-
}
-
}
-
-
zIn = (char *)realloc(zIn, len);
-
if( !zIn ){
-
return 0;
-
}
-
-
if( quote ){
-
char *zCsr = &zIn[nIn];
-
*zCsr++ = quote;
-
for(i=0; i
-
*zCsr++ = zAppend[i];
-
if( zAppend[i]==quote ) *zCsr++ = quote;
-
}
-
*zCsr++ = quote;
-
*zCsr++ = '\0';
-
assert( (zCsr-zIn)==len );
-
}else{
-
memcpy(&zIn[nIn], zAppend, nAppend);
-
zIn[len-1] = '\0';
-
}
-
-
return zIn;
-
}
-
-
/*
-
** This is a different callback routine used for dumping the database.
-
** Each row received by this callback consists of a table name,
-
** the table type ("index" or "table") and SQL to create the table.
-
** This routine should print text sufficient to recreate the table.
-
*/
-
static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){
-
int rc;
-
const char *zTable;
-
const char *zType;
-
const char *zSql;
-
const char *zPrepStmt = 0;
-
struct callback_data *p = (struct callback_data *)pArg;
-
-
UNUSED_PARAMETER(azCol);
-
if( nArg!=3 ) return 1;
-
zTable = azArg[0];
-
zType = azArg[1];
-
zSql = azArg[2];
-
-
if( strcmp(zTable, "sqlite_sequence")==0 ){
-
zPrepStmt = "DELETE FROM sqlite_sequence;\n";
-
}else if( strcmp(zTable, "sqlite_stat1")==0 ){
-
fprintf(p->out, "ANALYZE sqlite_master;\n");
-
}else if( strncmp(zTable, "sqlite_", 7)==0 ){
-
return 0;
-
}else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
-
char *zIns;
-
if( !p->writableSchema ){
-
fprintf(p->out, "PRAGMA writable_schema=ON;\n");
-
p->writableSchema = 1;
-
}
-
zIns = f_sqlite3_mprintf(
-
"INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"
-
"VALUES('table','%q','%q',0,'%q');",
-
zTable, zTable, zSql);
-
fprintf(p->out, "%s\n", zIns);
-
f_sqlite3_free(zIns);
-
return 0;
-
}else{
-
fprintf(p->out, "%s;\n", zSql);
-
}
-
-
if( strcmp(zType, "table")==0 ){
-
sqlite3_stmt *pTableInfo = 0;
-
char *zSelect = 0;
-
char *zTableInfo = 0;
-
char *zTmp = 0;
-
int nRow = 0;
-
-
zTableInfo = appendText(zTableInfo, "PRAGMA table_info(", 0);
-
zTableInfo = appendText(zTableInfo, zTable, '"');
-
zTableInfo = appendText(zTableInfo, ");", 0);
-
-
rc = f_sqlite3_prepare(p->db, zTableInfo, -1, &pTableInfo, 0);
-
free(zTableInfo);
-
if( rc!=SQLITE_OK || !pTableInfo ){
-
return 1;
-
}
-
-
zSelect = appendText(zSelect, "SELECT 'INSERT INTO ' || ", 0);
-
/* Always quote the table name, even if it appears to be pure ascii,
-
** in case it is a keyword. Ex: INSERT INTO "table" ... */
-
zTmp = appendText(zTmp, zTable, '"');
-
if( zTmp ){
-
zSelect = appendText(zSelect, zTmp, '\'');
-
free(zTmp);
-
}
-
zSelect = appendText(zSelect, " || ' VALUES(' || ", 0);
-
rc = f_sqlite3_step(pTableInfo);
-
while( rc==SQLITE_ROW ){
-
const char *zText = (const char *)f_sqlite3_column_text(pTableInfo, 1);
-
zSelect = appendText(zSelect, "quote(", 0);
-
zSelect = appendText(zSelect, zText, '"');
-
rc = f_sqlite3_step(pTableInfo);
-
if( rc==SQLITE_ROW ){
-
zSelect = appendText(zSelect, "), ", 0);
-
}else{
-
zSelect = appendText(zSelect, ") ", 0);
-
}
-
nRow++;
-
}
-
rc = f_sqlite3_finalize(pTableInfo);
-
if( rc!=SQLITE_OK || nRow==0 ){
-
free(zSelect);
-
return 1;
-
}
-
zSelect = appendText(zSelect, "|| ')' FROM ", 0);
-
zSelect = appendText(zSelect, zTable, '"');
-
-
rc = run_table_dump_query(p, zSelect, zPrepStmt);
-
if( rc==SQLITE_CORRUPT ){
-
zSelect = appendText(zSelect, " ORDER BY rowid DESC", 0);
-
run_table_dump_query(p, zSelect, 0);
-
}
-
free(zSelect);
-
}
-
return 0;
-
}
-
-
/*
-
** Run zQuery. Use dump_callback() as the callback routine so that
-
** the contents of the query are output as SQL statements.
-
**
-
** If we get a SQLITE_CORRUPT error, rerun the query after appending
-
** "ORDER BY rowid DESC" to the end.
-
*/
-
static int run_schema_dump_query(
-
struct callback_data *p,
-
const char *zQuery
-
){
-
int rc;
-
char *zErr = 0;
-
rc = f_sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr);
-
if( rc==SQLITE_CORRUPT ){
-
char *zQ2;
-
int len = strlen30(zQuery);
-
fprintf(p->out, "/****** CORRUPTION ERROR *******/\n");
-
if( zErr ){
-
fprintf(p->out, "/****** %s ******/\n", zErr);
-
f_sqlite3_free(zErr);
-
zErr = 0;
-
}
-
zQ2 = malloc( len+100 );
-
if( zQ2==0 ) return rc;
-
f_sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery);
-
rc = f_sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr);
-
if( rc ){
-
fprintf(p->out, "/****** ERROR: %s ******/\n", zErr);
-
}else{
-
rc = SQLITE_CORRUPT;
-
}
-
f_sqlite3_free(zErr);
-
free(zQ2);
-
}
-
return rc;
-
}
-
-
static void dump(struct callback_data *p)
-
{
-
/* When playing back a "dump", the content might appear in an order
-
** which causes immediate foreign key constraints to be violated.
-
** So disable foreign-key constraint enforcement to prevent problems. */
-
fprintf(p->out, "PRAGMA foreign_keys=OFF;\n");
-
fprintf(p->out, "BEGIN TRANSACTION;\n");
-
p->writableSchema = 0;
-
f_sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0);
-
p->nErr = 0;
-
run_schema_dump_query(p,
-
"SELECT name, type, sql FROM sqlite_master "
-
"WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'"
-
);
-
run_schema_dump_query(p,
-
"SELECT name, type, sql FROM sqlite_master "
-
"WHERE name=='sqlite_sequence'"
-
);
-
run_table_dump_query(p,
-
"SELECT sql FROM sqlite_master "
-
"WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0
-
);
-
if( p->writableSchema ){
-
fprintf(p->out, "PRAGMA writable_schema=OFF;\n");
-
p->writableSchema = 0;
-
}
-
f_sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
-
f_sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0);
-
fprintf(p->out, p->nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n");
-
}
-
-
int main(int argc, char **argv){
-
sqlite3 *db;
-
char *zErrMsg = 0;
-
int rc, i;
-
-
if( argc < 4 ){
-
fprintf(stderr, "Usage: %s library DATABASE SQL-STATEMENTs\n", argv[0]);
-
return 1;
-
}
-
-
void *hdl = dlopen(argv[1], RTLD_LAZY);
-
if (!hdl) {
-
fprintf(stderr, "failed to load lib: %s\n", dlerror());
-
return -1;
-
}
-
-
#define SOLVE(name) \
-
f_##name = dlsym(hdl, # name); \
-
if (!f_##name) { \
-
fprintf(stderr, "failed to resolve %s: %s\n", #name, dlerror()); \
-
return -1; \
-
}
-
-
SOLVE(sqlite3_close);
-
SOLVE(sqlite3_column_text);
-
SOLVE(sqlite3_column_count);
-
SOLVE(sqlite3_errmsg);
-
SOLVE(sqlite3_exec);
-
SOLVE(sqlite3_finalize);
-
SOLVE(sqlite3_free);
-
SOLVE(sqlite3_initialize);
-
SOLVE(sqlite3_mprintf);
-
SOLVE(sqlite3_open_v2);
-
SOLVE(sqlite3_prepare);
-
SOLVE(sqlite3_shutdown);
-
SOLVE(sqlite3_snprintf);
-
SOLVE(sqlite3_step);
-
SOLVE(sqlite3_trace);
-
-
f_sqlite3_initialize();
-
-
rc = f_sqlite3_open_v2(argv[2], &db,
-
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
-
if( rc ){
-
fprintf(stderr, "Can't open database: %s\n", f_sqlite3_errmsg(db));
-
f_sqlite3_close(db);
-
return 1;
-
}
-
for (i = 3; i < argc; i ++)
-
{
-
fprintf(stderr, "exec: %s\n", argv[i]);
-
if (!strncmp(argv[i], ".dump", 5)) {
-
struct callback_data p;
-
memset(&p, 0, sizeof(p));
-
p.out = fopen(argv[i] + 5, "w");
-
if (!p.out) {
-
fprintf(stderr, "failed to open file %s: %m\n", argv[i] + 5);
-
break;
-
}
-
p.db = db;
-
dump(&p);
-
fclose(p.out);
-
continue;
-
}
-
rc = f_sqlite3_exec(db, argv[i], callback, 0, &zErrMsg);
-
if( rc!=SQLITE_OK ) {
-
fprintf(stderr, "SQL error: %s\n", zErrMsg);
-
f_sqlite3_free(zErrMsg);
-
break;
-
}
-
}
-
f_sqlite3_close(db);
-
f_sqlite3_shutdown();
- return 0;