• 前言
  • 正文
    • 经验回顾
    • 新的问题
    • 解决华为旧备份数据导出问题
    • 解密索引数据库
      • 先要解密微信消息库
      • 解密索引库
      • 从索引库恢复被删除的消息
  • 总结

前言

在电子数据取证过程中,对微信本地数据库的解密、提取与恢复是非常重要的工作内容。本文以华为mate系列手机和最新版的微信(7.0.3)为例,通过总结互联网上已经发表的文章经验,主要针对华为手机备份软件升级、微信7.0以后索引库加密以及通过索引库恢复被删除聊天记录等内容予以补充。

正文

经验回顾

网上有很多关于安卓微信本地数据库(7.0版本以前主要是EnMicroMsg.db)的解密教程,以及恢复已删除聊天记录的原理教程,由于微信的不断升级,很多教程的内容已经不符合实际需要了,通过验证,发现以下经验仍然可用:

  1. EnMicroMsg.db密码算法没有变化 ,这个密码算法仍然是IMEI与uin拼接后计算32位MD5值,然后取前7位(如果uin是负值也不需要变化);
  2. IMEI和最后一次登录的uin提取方法没有变化,IMEI在CompatibleInfo.cfg文件中,最后一次登录的uin在system_conf_prefs.xml文件中;

新的问题

随着安卓操作系统的不断升级,安全性越来越高,想通过root等方式获取手机存储镜像然后再进行数据恢复的方式越来越难了,因此大多数电子数据取证的厂家是通过手机厂商官方备份文件对手机数据在本机存储中备份后,通过导出的备份文件来进行数据提取和恢复。
问题一:华为手机的官方备份软件在8.0版本以后不再支持本机存储备份,需要通过OTG转接头在外部存储中备份。很多厂家采取的方法是对备份软件降级,然后仍然在本机存储备份后导出。另外8.0以前的备份是以sqlite数据库(.db)的形式存储的,而8.0以后的备份是以压缩文件(.tar)的形式存储的。对于手工分析来讲,新的备份机制更容易操作。但是如果是旧的备份方式,需要把存在数据库里的文件导出才能进行下一步工作。
问题二:微信7.0以后,对几个以前没有加密的数据库文件(尤其是对恢复数据最重要的索引库)进行了加密,而且经测试,所使用的密码不是EnMicroMsg.db加密所使用的密码。
问题三:网上很多文章对于通过索引库恢复被删除的聊天记录说明不够详细。

解决华为旧备份数据导出问题

使用低版本华为备份软件得到的微信备份文件为一个com.tencent.mm.apk文件和一个com.tencent.mm.db文件。
用sqlite数据库管理工具打开com.tencent.mm.db,发现只有三个表,其中apk_file_info表中储存了所有文件名和索引号,apk_file_data中则存储了文件数据。索引号为-1的是目录,索引号大于0的是有用的文件。

在apk_file_data中索引号相同的是同一个文件,每个文件被切成若干个8K以内的碎片进行存储,导出时需要拼接起来再导出。

导出文件数据的python代码如下:

import sqlite3
import os
conn = sqlite3.connect('com.tencent.mm.db')
cursor = conn.cursor()
cursor.execute("SELECT count(*) FROM apk_file_info")
all = cursor.fetchone()[0]
cursor.execute("SELECT file_path,file_index FROM apk_file_info")
result = cursor.fetchall()
count = 0
while (count < all):if (result[count][1] > 0):fullname = result[count][0]findex = result[count][1]dirname,filename = os.path.split(fullname)fpath="." + dirnamefname="." + fullnameisExists=os.path.exists(fpath)if not isExists:os.makedirs(fpath)with open(fname, "wb") as output_file:cursor.execute("SELECT count(*) FROM apk_file_data WHERE file_index = " + str(findex))total = cursor.fetchone()[0]cursor.execute("SELECT file_data FROM apk_file_data WHERE file_index = " + str(findex))acount=0while (acount < total):ablob = cursor.fetchone()output_file.write(ablob[0])acount = acount+1count = count + 1
cursor.close()
conn.close()

将代码保存为out.py后与com.tencent.mm.db文件放在同一目录下,python out.py运行即可在当前目录下导出所有文件。生成目录为data/data/com.tencent.mm。

比较懒,没有加注释和提示信息。实际使用时请自行添加提示信息和异常处理代码。如果导出文件数据较多程序效率比较低,可自行优化,代码仅供参考。

解密索引数据库

先要解密微信消息库

首先需要解密EnMicroMsg.db,以便提取微信id。因为CompatibleInfo.cfg是通过java的HashMap编码的,因此从此文件中提取IMEI值需要解码。没找到python解码java HashMap的代码,所以就用java代码凑合一下。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.security.MessageDigest;
import java.util.HashMap;
public class GetDBKey {public static void main(String[] args) {try {ObjectInputStream in = new ObjectInputStream(new FileInputStream("CompatibleInfo.cfg"));Object DL = in.readObject();HashMap hashWithOutFormat = (HashMap) DL;String s = String.valueOf(hashWithOutFormat.get(Integer.valueOf(258))); // 取手机的IMEISystem.out.println("IMEI:"+s);ObjectInputStream in1 = new ObjectInputStream(new FileInputStream("systemInfo.cfg"));Object DJ = in1.readObject();HashMap hashWithOutFormat1 = (HashMap) DJ;String t = String.valueOf(hashWithOutFormat1.get(Integer.valueOf(1))); // 取微信的uinSystem.out.println("uin:"+t);s = s + t; //合并到一个字符串s = encode(s); // MD5System.out.println("密码是 : " + s.substring(0, 7));in.close();in1.close();} catch (Exception e) {e.printStackTrace();}}public static String encode(String content){try {MessageDigest digest = MessageDigest.getInstance("MD5");digest.update(content.getBytes());return getEncode32(digest);}catch (Exception e){e.printStackTrace();}return null;}private static String getEncode32(MessageDigest digest){StringBuilder builder = new StringBuilder();for (byte b : digest.digest()){builder.append(Integer.toHexString((b >> 4) & 0xf));builder.append(Integer.toHexString(b & 0xf));}return builder.toString();}
}

把代码另存为GetDBKey.java,把CompatibleInfo.cfg和systemInfo.cfg(在com.tencent.mm/MicroMsg目录中)跟代码放在同一目录,编译运行后直接显示密码。如果需要看IMEI和uin,请自行添加输出代码。
另外一种方法是通过DENGTA_META.xml中的IMEI_DENGTA值(有的手机备份没有)和system_conf_prefs.xml中的system_config_prefs来提取IMEI和uin。这两个是明文,直接看就可以了。如果登录过多个微信账号,所有的uin都在app_brand_global_sp.xml中。这三个xml文件都在com.tencent.mm/shared_prefs目录下。
取得密码后,一种方法是使用sqlcipher2.1(CSDN有下载)GUI版直接打开。
第二种方法是通过sqlcipher命令行解密。第三种方法是通过程序代码解密。
python参考代码如下(需要先用pip install pysqlcipher3安装python的sqlcipher支持库才能引用):

from pysqlcipher3 import dbapi2 as sqlite
import hashlib
def decrypt( key ):conn = sqlite.connect( "EnMicroMsg.db" )c = conn.cursor()        c.execute( "PRAGMA key = '" + key + "';" )c.execute( "PRAGMA cipher_use_hmac = OFF;" )c.execute( "PRAGMA cipher_page_size = 1024;" )c.execute( "PRAGMA kdf_iter = 4000;" )c.execute( "ATTACH DATABASE 'EnMicroMsg-decrypted.db' AS wechatdecrypted KEY '';" )c.execute( "SELECT sqlcipher_export( 'wechatdecrypted' );" )c.execute( "DETACH DATABASE wechatdecrypted;" )c.close()
def generate_key():imei = "866666666666666"uin = "1919191919"key = hashlib.md5( str( imei ).encode("utf8") + str( uin ).encode("utf8")).hexdigest()[ 0:7 ]return key
def main(): key = generate_key()decrypt( key )
main()

将代码中的imei值和uin值替换成刚才获得的值即可。将EnMicroMsg.db与python程序放在同一目录下运行即可解密,生成的文件名为EnMicroMsg-decrypted.db。

解密索引库

微信索引库FTS5IndexMsg.db之前是不加密的,但到了微信7.0以后,索引库就变成了FTS5IndexMsg_encrypt.db,明显加密了。使用EnMicroMsg.db的密码进行解密失败,通过与几个电子取证公司的技术人员交流,了解到密码算法确实变了,而且加密参数也有变化。大体的情况是变成IMEI、uin、微信id三者拼接后的32位MD5值取前7位作为密码。涉及到产品细节无法透露,因此具体算法还需要自行研究。
先用sqlite管理工具将EnMicroMsg-decrypted.db打开,打开userinfo表,其中id为2对应的值为微信id。通常为wxid_xxxxxxxxxxxxxx格式。

这样的话至少素材已经齐了,接下来就是研究具体的算法了。
算法只能通过反编译apk文件来查找。
先从官网下载最新版的jadx 0.9.0zip版(不要下载exe版,因为需要调整运行参数)
将bin目录中的jadx-gui.bat的DEFAULT_JVM_OPTS参数里面Xms和Xmx分别调整到1024M和7G。参数调整后如下:

@rem Add default JVM options here. You can also use JAVA_OPTS and JADX_GUI_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xms1024M" "-Xmx7g" "-Dawt.useSystemAAFontSettings=lcd" "-Dswing.aatext=true" "-XX:+UseG1GC"

如果Xmx值小于7G则在反编译比较大的软件时jadx会出现假死状态。
不用开反混淆开关(其实无所谓,看个人习惯)进行反编译,然后全部保存。
利用文本编辑器(我用的是NotePad++,开源还好用)对反编译的java代码进行文件内容搜索,查找加密算法位置。
首先搜索“FTS5IndexMsg_encrypt.db”,发现com\tencent\mm\plugin\fts\d.java中有:

String absolutePath = new File(str, "FTS5IndexMicroMsg_encrypt.db").getAbsolutePath();

往下看,发现有这么一句:

this.lXS = SQLiteDatabase.openOrCreateDatabase(absolutePath, com.tencent.mm.a.g.u(stringBuilder.append(a.OZ()).append(q.Kc()).append(com.tencent.mm.model.q.Wt()).toString().getBytes()).substring(0, 7).getBytes(), null, null);

stringBuilder是一个字符串构造函数,连续拼接了三个字符串,经过一个运算后取了前7位,与了解到的情况相符。
通过分析发现:
com.tencent.mm.kernel.a.OZ()取uin;
com.tencent.mm.compatible.e.q.Kc()取DeviceID(即IMEI);
com.tencent.mm.model.q.Wt()取userinfo(即微信id);
com.tencent.mm.a.g.u()是MD5算法。
因此说明这个密码是将uin、IMEI、微信id连续拼接然后计算32位MD5值再取前7位作为密码。
这里需要注意的是,uin如果是负值不能直接进行拼接,要把它加上4294967296(最大无符号数),得到的正数作为最终的uin进行拼接。
另外,除了密码之外据说加密算法等参数也有变化,因此还需要继续搜索。
这次搜索sqlcipher参数“PRAGMA”,发现最终调用的是com\tencent\wcdb\database\SQLiteCipherSpec.java中的几个参数(wcdb对应wcdb.so文件,其实是改了名的sqlcipher.so),对应值分别为:

CIPHER_AES256CBC = "aes-256-cbc";
kdfIteration = 64000;
hmacEnabled = true;

另外经与厂商技术人员沟通,发现还有个参数需要调整:

setPageSize=4096;

这几个参数分别对应如下sqlcipher参数:

PRAGMA cipher = 'aes-256-cbc';
PRAGMA cipher_use_hmac = ON;
PRAGMA cipher_page_size = 4096;
PRAGMA kdf_iter = 64000;

综上所述,一切解密需要的素材都齐了,相应的python代码如下:

from os.path import isfile
# 用pip install pysqlcipher3安装python的sqlcipher支持库再引用
from pysqlcipher3 import dbapi2 as sqlite
import hashlib
import sys
import time
import logging
import relogging.basicConfig(filename='FTS5IndexMicroMsg_decrypt.log', format="%(asctime)s %(levelname)s: %(message)s", datefmt="%d-%b-%Y %I:%M:%S %p", level=logging.DEBUG)def decrypt( key ):logging.info( "连接数据库..." )conn = sqlite.connect( "FTS5IndexMicroMsg_encrypt.db" )c = conn.cursor()       c.execute( "PRAGMA key = '" + key + "';" )c.execute( "PRAGMA cipher = 'aes-256-cbc';" )c.execute( "PRAGMA cipher_use_hmac = ON;" )c.execute( "PRAGMA cipher_page_size = 4096;" )c.execute( "PRAGMA kdf_iter = 64000;" )try:logging.info( "正在解密..." )c.execute( "ATTACH DATABASE 'FTS5IndexMicroMsg_decrypt.db' AS fts5indexdecrypt KEY '';" )c.execute( "SELECT sqlcipher_export( 'fts5indexdecrypt' );" )c.execute( "DETACH DATABASE fts5indexdecrypt;" )logging.info( "正在分离数据库..." )c.close()status = 1except:c.close()status = 0return statusdef generate_key():imei = "866666666666666"logging.info( "IMEI: " + str( imei ))uin = "2377777777"logging.info( "UIN: " + str( uin ))account = "wxid_1l8w9yqrxxxxxx"logging.info( "account: " + str( account ))logging.info( "正在生成密钥..." ) key = hashlib.md5( str( uin ).encode("utf8") + str( imei ).encode("utf8") + str( account ).encode("utf8")).hexdigest()[ 0:7 ]logging.info( "密钥: " + key )return keydef db_hash():f = open( 'FTS5IndexMicroMsg_decrypt.db', 'rb' ).read()logging.info( "正在生成哈希值..." ) if len( f ) > 0:db_md5 = hashlib.md5( f ).hexdigest()logging.info( "FTS5IndexMicroMsg_decrypt.db MD5: " + db_md5 )db_sha1 = hashlib.sha1( f ).hexdigest()logging.info( "FTS5IndexMicroMsg_decrypt.db SHA1: " + db_sha1 )returndef main():    if not ( isfile( "FTS5IndexMicroMsg_encrypt.db" )):print ("##########")print ("'FTS5IndexMicroMsg_encrypt.db'不存在!")print ("正在退出脚本...")print ("##########")sys.exit()logging.info( "脚本启动..." )key = generate_key()status = decrypt( key )if status == 1:db_hash()print ("##########")print ("解密成功!")print ("解密文件: FTS5IndexMicroMsg_decrypt.db")print ("日志文件: FTS5IndexMicroMsg_decrypt.log")print ("##########")logging.info( "解密成功!" )logging.info( "解密文件: FTS5IndexMicroMsg_decrypt.db" )else:print ("##########")print ("解密失败!")print ("日志文件: FTS5IndexMicroMsg_decrypt.log")print ("##########")logging.info( "解密失败!" )logging.info( "正在退出脚本..." )main()

这次放了比较规范的代码,之前也是为了体现python的简洁,很多复杂功能没几行代码就搞定了。
只要把IMEI、uin、微信id换成之前取出来的即可。把加密数据库和python代码放在同一个目录中运行就可以得到解密数据库。

另外在搜索代码的过程中发现MicroMsgPriority.db也加密了,密码是uin、微信id、IMEI顺次拼接的32位MD5值取前7位。sqlcipher参数与索引库相同。可以简单修改以上代码就可以实现解密。

从索引库恢复被删除的消息

用winhex打开解密后的索引库:

在右侧显示区上方点击“ANSI ASCII”,选择“Unicode UTF-8”,向下滚动就可看到连续的中文,这些就是索引后的消息。其中包括已删除和未删除的内容。
经查阅资料并与厂商技术人员沟通,发现这些信息的存储格式开头如下:
aa bb 03 00 cc 或 aa bb 04 00 cc dd
如果一个区域里面aa的值一致,说明是正常未删除信息,如果aa值不一致,则说明是删除信息。bb是从7F到00顺序排列(偶尔有中断)相当于序号。如果是03则cc是后面正文长度,如果是04则cc dd是正文长度。
cc或者cc dd是varint格式,需要进行一定的变换之后才能得出长度值。
计算方法为:
cc->16进制转10进制->减13->除以2,如果结果是3的倍数,说明是中文,再除以3,得到的数值就是正文长度。如果除以2以后不是3的倍数,说明是英文(半角),这个得数就是英文正文长度。
例如:
55->85->72->36->12个汉字
19->25->12->6->2个汉字
17->23->10->5个英文
如果是cc dd要麻烦一些。先将cc dd都转成二进制,然后把cc的首位1和之后的所有0都去掉,把dd首位0去掉,然后拼到一起,再转成10进制->减13->除以2,如果结果是3的倍数,说明是中文,再除以3,得到的数值就是正文长度。如果除以2以后不是3的倍数,说明是英文(半角),这个得数就是英文正文长度。
例如:
81 23
81-> 10000001 23->00100011 合并为10100011,十进制是163;163->150->75->25个汉字
81 11
81->10000001 11->00010001 合并为10010001,十进制是145;145->132->66->22个汉字
这样的话,就可以通过程序把符合规则的内容全部导出来,就是被删除的消息。如果想确定交互双方,还需要配合其他数据分析。

如果有问题可以联系,15387172081,微信同号,1037512447 qq/微信

安卓微信本地数据库解密与删除聊天记录恢复 EnMicroMsg.db FTS5IndexMicroMsg_encrypt.db相关推荐

  1. 安卓微信本地数据库解密与删除聊天记录恢复完全教程

    安卓微信本地数据库解密与删除聊天记录恢复完全教程 前言 正文 经验回顾 新的问题 解决华为旧备份数据导出问题 解密索引数据库 先要解密微信消息库 解密索引库 从索引库恢复被删除的消息 总结 [原创内容 ...

  2. Android逆向_微信本地数据库解密与删除聊天记录恢复完全教程

    # 前言 在电子数据取证过程中,对微信本地数据库的解密.提取与恢复是非常重要的工作内容.本文以华为mate系列手机和最新版的微信(7.0.3)为例,通过总结互联网上已经发表的文章经验,主要针对**华为 ...

  3. 破解微信本地数据库,无法找回删除的聊天记录

    [说明:操蛋的微信从4.0后本地数据库都加密了,密码是IMEI+UIN取MD5 32位小写加密前7作为密码]  [数据库文件请ROOT后用RE浏览器得到] [UIN 文件路径:/data/data/ ...

  4. PC微信机器人之实战分析微信本地数据库获取密码

    今天主要讨论一下怎么找PC微信本地数据库的密钥,我们知道微信使用的数据库是sqlite3,然后数据库是经过 aes 加密的,我们需要找到aes的密钥,才能解密,然后进行数据库操作.思路是在微信登陆的时 ...

  5. Android打开微信本地数据库详细步骤二

    特别声明:本文章只是用于技术交流,不可用于非法行为. 阅读过上篇文章后,我想你一定去取出来微信本地数据库文件EnmicroMsg.db,但是取出来有什么用呢?又打不开,加密了.好比邂逅了个穿着铁内裤的 ...

  6. android应用卸载后,出现的本地数据库内容未删除现象

    现象:应用使用一段时间后,本地数据库和SharedPreferences中产生数据,将应用卸载后,再次安装,原有数据出现在应用中.因此,为了安全起见,需要将allowBackup标志值设置为false ...

  7. SQL server 数据库危险存储过程删除与恢复

    SQL的扩展的删除与恢复 删除 use master  exec sp_dropextendedproc 'xp_cmdshell'  exec sp_dropextendedproc 'xp_dir ...

  8. mysql数据库数据的删除与恢复

    Part 1 删除数据, truncate 和 delete  的区别 转自:http://blog.is36.com/mysql_difference_of_truncate_and_delete/ ...

  9. 安卓开发:本地数据库SQLite的使用

    上篇文章,讲述了安卓开发中文件存储和SharedPreferences存储方式.这里讲述SQLite数据库存储.我们的通讯录也存在与SQLite数据库中.存放的位置在:/data/data/com.a ...

  10. 微信小程序云开发之云数据库的数据删除

    前两篇我已经详细介绍了云数据库的部署以及数据库的增加和查询.今天我们就一起来探讨微信云数据库的数据删除. 修改操作和之前讲的那几种操作稍微有些不同.因为删除是根据在数据库里的id进行删除操作的.如下图 ...

最新文章

  1. java代码内创建mysql索引_Java Mysql数据库创建视图、索引、备份和恢复
  2. OPENCV背景细分background segmentation的实例(附完整代码)
  3. 导出Excel java
  4. [NewLife.Net]单机400万长连接压力测试
  5. python读取数据文件夹_使用python依次读取文件中的所有csv格式的数据
  6. c语言删除堆栈所有的结点,深入浅出数据结构C语言版(15)——优先队列(堆)(示例代码)...
  7. CAD如何快速计算面积并标注?CAD计算面积并标注
  8. 【Python 3.7】熟食店:创建一个名为 sandwich_orders 的列表,在其中包含各种三明治的名 字;再创建一个名为 finished_sandwiches 的空列表……
  9. jsp之${CTX}理解
  10. 【读书笔记】【目标:提升记忆力】申一帆记忆力课程
  11. 微软远程桌面0x104_win10系统远程桌面出现错误0x204的解决方法
  12. 《冷读术》值得一看--读书笔记
  13. 安全问题层出不穷 加密手机悄然兴起
  14. 持续集成与持续部署(五)01-TravisCI——使用简介-Travis CI 只支持 Github,提供的是持续集成服务 配置项目的.travis.yml文件
  15. 疫情已经结束,快递行业现状如何
  16. python计算化学浓度_计算化学操作流程-孙磊.pdf
  17. python之xlwt、xlrd和openpyxl
  18. mysql中双引号和单引号有什么区别
  19. 2018年总结,2019年展望!
  20. matlab 将矩阵中的NAN替换为0

热门文章

  1. Acme CAD ConverterDWG文件查看器 2021
  2. NSGA-Ⅱ算法原理
  3. hping3使用详解
  4. 空间相关分析(三) 局部莫兰指数的理解与计算
  5. 胖子哥的大数据之路(8)- 数据仓库命名规范
  6. 如何批量转换图片格式为png?
  7. Python:批量转换图片格式
  8. 西门子scout中文手册_西门子LOGO!是什么?它有哪些应用?
  9. 25. 谷粒商城订单系统
  10. 工具方法:java读取Excel合并单元格(简单实例)