greycodee / wechat-backup

微信聊天记录持久化备份本地硬盘,释放手机存储空间。
MIT License
3.25k stars 342 forks source link

未查询到索引表名 #11

Closed djerryz closed 2 years ago

djerryz commented 2 years ago
  1. get data and decrypt
    
    adb pull /data/data/com.tencent.mm/MicroMsg/03973XXX/EnMicroMsg.db /tmp/wexin/
    adb pull /data/data/com.tencent.mm/MicroMsg/03973XXX/WxFileIndex.db /tmp/wexin/

docker run --rm -v /tmp/wexin/:/wcdb greycodee/wcdb-sqlcipher -f EnMicroMsg.db -k XXX docker run --rm -v /tmp/wexin/:/wcdb greycodee/wcdb-sqlcipher -f WxFileIndex.db -k XXX

2022/07/21 17:32:11 开始解密... 2022/07/21 17:32:13 解密成功: ok 2022/07/21 17:32:13 明文数据库文件名: EnMicroMsg_plain.db

2022/07/21 17:33:30 开始解密... 2022/07/21 17:33:31 解密成功: ok 2022/07/21 17:33:31 明文数据库文件名: WxFileIndex_plain.db


2. execute main.go and get this error:

go run main.go -f '/tmp/wexin/' 2022/07/21 17:33:40 未查询到图片索引表名,sql: no rows in result set 2022/07/21 17:33:40 sql: no rows in result set exit status 1

greycodee commented 2 years ago

看看你的 WxFileIndex_plain.db 是否有数据

djerryz commented 2 years ago

有数据的,解密前1K.解密后7K,但是执行 go run main.go 命令后,大小变成了0

greycodee commented 2 years ago

解密前才 1 k大小?那应该是没数据的,空db,你可以用sqlite工具打开查看下

djerryz commented 2 years ago

-rwxrwxrwx 1 u0_a182 u0_a182 1.0K 2022-07-22 00:37 WxFileIndex.db -rwxrwxrwx 1 u0_a182 u0_a182 32K 2022-07-22 01:06 WxFileIndex.db-shm -rwxrwxrwx 1 u0_a182 u0_a182 17K 2022-07-22 01:06 WxFileIndex.db-wal -rwxrwxrwx 1 u0_a182 u0_a182 81 2022-07-22 00:37 WxFileIndex.db.ini

看起来WxFileIndex.db-shm和WxFileIndex.db-wal才有真正的数据。

我查询了这两个扩展相关的解释,得到下面相关的结论:

Referer:

djerryz commented 2 years ago

确实是没有数据内容在WxFileIndex.db当中的,这非常的奇怪,目前我的微信(8.0.24)是可以稳定使用的: Phone Info: 一加3,氧OS,Kali NetHuner, Root, Magisk Manager

>>> cursor = conn.execute("PRAGMA database_list;")
>>> for row in cursor:
...     print(row)
(0, 'main', '/root/bakweixinchat/tmp/WxFileIndex_plain.db')
>>> cursor = conn.execute("SELECT * FROM main.sqlite_master WHERE type='table';") or cursor = conn.execute("SELECT name FROM sqlite_temp_master WHERE type='table';")

>>>
>>> for row in cursor:
...     print(row)
无结果
djerryz commented 2 years ago

测试了很多次,包括重装,强制退出,数据迁移,WxFileIndex.db始终为1KB,是否是微信的业务逻辑有改变的原因

image

greycodee commented 2 years ago

你在微信上退出登陆后,然后在看看 WxFileIndex.db 里有没有数据。应该是wal模式下数据在没关闭连接前就没写进去?

djerryz commented 2 years ago

并没有作用,重启,登出等,目前我在思考是否有其他方式重建文件索引的方式,不用依赖WxFileIndex: 如,

图片

在EnMicroMsg.db 的message的 imgPath: image

文件

在EnMicroMsg.db 的message的 content:

<?xml version="1.0"?>\n<msg>\n\t<appmsg appid="XXX" sdkver="0">\n\t\t<title>xxx.docx</title>

其中title字段的xxx.docx可以和匹配Download目录的文件名匹配

表情

未测试

语音

未测试

视频

未测试

image

djerryz commented 2 years ago
import sqlite3
import os

values = ["0717590723228e1843b41b7106","2207231759461121"]
filename = "EnMicroMsg_plain.db"
with sqlite3.connect(filename) as conn:
    conn.row_factory = sqlite3.Row
    cursor = conn.cursor()    
    cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
    for tablerow in cursor.fetchall():
        table = tablerow[0]
        cursor.execute("SELECT * FROM {t}".format(t = table))
        for row in cursor:
            for field in row.keys():
                x = row[field]
                for avalue in values:
                    try:
                        avalue_int = int(avalue)
                    except:
                        avalue_int = "luanqibazao@@@"
                    if x == avalue or x==avalue_int:
                        print(table, field, x)
                    else:
                        pass

得到:

message imgPath 0717590723228e1843b41b7106
message imgPath 2207231759461121
voiceinfo FileName 0717590723228e1843b41b7106
voiceinfo ClientId 0717590723228e1843b41b7106
videoinfo2 filename 2207231759461121

在voiceinfo 和videoinfo2 中并没有媒体文件真实位置,只有msgsvrid看起来能继续发散,但是在EnMicroMsg表中也关联不出来path,似乎还是得借助其他db文件来匹配path.

djerryz commented 2 years ago

新的问题

测试还发现,发送媒体文件 语言和视频后,并没有 voice2 和 video这两个目录

分析可能和WxFileIndex一样,可能在某个地方缓存了数据,但是没有落盘到MicroMsg文件目录中,即不是实时存储的, 目前没有好的思路去验证这个猜想

但是测试:

等动作,数据都是没有落盘的,但聊天数据可以正常访问!

greycodee commented 2 years ago

并没有作用,重启,登出等,目前我在思考是否有其他方式重建文件索引的方式,不用依赖WxFileIndex: 如,

图片

在EnMicroMsg.db 的message的 imgPath: image

文件

在EnMicroMsg.db 的message的 content:

<?xml version="1.0"?>\n<msg>\n\t<appmsg appid="XXX" sdkver="0">\n\t\t<title>xxx.docx</title>

其中title字段的xxx.docx可以和匹配Download目录的文件名匹配

表情

未测试

语音

未测试

视频

未测试

image

可以不用依赖 WxFileIndex.db ,我以前写过一版不用依赖 WxfileIndex.db 的程序,你可以看提交历史

greycodee commented 2 years ago

具体规则如下

媒体文件存放位置 安卓手机在录音和视频 Android/data/com.tencent.mm/MicroMsg/ 下 图片在 /data/data/com.tencent.mm/MicroMsg/[32位字母]/image2/ 下

avatar文件索引 微信ID通过md5 32位加密后,前4位就是文件地址,每两位代表一个文件夹名,例如微信id:weixin 经过md5加密后是:C196266F837D14E0B693F961BEE37B66,那么这个微信的头像地址是:avatar/c1/96/user_c196266f837d14e0b693f961bee37b66.png

文件地址 视频地址:直接通过message 表后的imgPath查找到video文件夹查找对应的视频,封面图后缀为.jpg,视频后缀为:.mp4 语言地址:message 的 imgPath字段通过MD5加密后,前4个字母代表两级文件夹名,然后最终文件名是:msg_imgPath值.amr 图片地址:THUMBNAIL_DIRPATH://th_5a24c5d362dae72b0ad52d78767ba883,其中5a24代表 /5a/24文件夹下的,th5a24c5d362dae72b0ad52d78767ba883是图片文件名 message 的图片地址是压缩后的图片地址,如果要看没压缩的原图,需要通过WxFileIndex.db 查询文件地址 还可以通过拼接来获取未压缩的图片地址: 发送的图片: 文件名:自己的wxid++对方wxid+_+当前msgSvrid+backup 路径:文件名前4个字母,每两个字母一个文件夹层级 接收的图片: 文件名:对方wxid++自己的wxid+_+当前msgSvrid+_backup 路径:文件名前4个字母,每两个字母一个文件夹层级 1090519089 是文件格式 文件保存在微信个人文件夹下的Download里

djerryz commented 2 years ago

上面说的,没有voice2 和 video这两个目录, 通过提示,我在 /data/media/0/Android/data/com.tencent.mm/MicroMsg下找到了,非常感谢。

目前构造文件名的方式我稍后在我本地尝试实现下,最好能只依赖EnMicroMsg.db完成全部数据的提取~

greycodee commented 2 years ago

上面说的,没有voice2 和 video这两个目录, 通过提示,我在 /data/media/0/Android/data/com.tencent.mm/MicroMsg下找到了,非常感谢。

目前构造文件名的方式我稍后在我本地尝试实现下,最好能只依赖EnMicroMsg.db完成全部数据的提取~

通过这种方式,有时候可以找到源文件,有时候又找不到只能通过wxfileindex去找到,所以我以前写过一版,后来还是删掉了

djerryz commented 2 years ago

可以综合一下,如果wxfileindex没有的数据就通过上述方式做互补, golang不是很熟,混用了python表达补充的逻辑,稍晚本地测试下,如果测试没问题,后面提个PR

video:

func (wf WxFileIndex, em EnMicroMsg) GetVideoPath(msgId string) string {
    var path string
    querySql := fmt.Sprintf("select path from %s WHERE msgId=%s and msgSubType=1", wf.tableName, msgId)
    err := wf.db.QueryRow(querySql).Scan(&path)
    if err != nil {
        querySql := fmt.Sprintf("select imgPath from %s WHERE msgId=%s", "message", msgId)
        err1 := em.db.QueryRow(querySql).Scan(&path)
        if err1 != nil {
            log.Printf("未查询到视频,%s", err)
            return ""
        }
    }
    return MediaPathPrefix + strings.Join(strings.SplitAfter(path, "/")[1:], "")
}

image:

voice:

func (wf WxFileIndex, em EnMicroMsg) GetVoicePath(msgId string) string {
    var path string
        var raw_path string
    querySql := fmt.Sprintf("select path from %s WHERE msgId=%s", wf.tableName, msgId)
    err := wf.db.QueryRow(querySql).Scan(&path)
    if err != nil {
                  querySql := fmt.Sprintf("select imgPath from %s WHERE msgId=%s", "message", msgId)
          err1 := em.db.QueryRow(querySql).Scan(&raw_path)
                  path = md5(raw_path)
                  path = path[0:2] + "/" + path[2:4] +"/" + "msg_{}.amr".format(raw_path)
          if err1 != nil {
              log.Printf("未查询到语音,%s", err)
              return ""
          }
    } else {
        return MediaPathPrefix + strings.Join(strings.SplitAfter(path, "/")[1:], "")
    }
}

file:

func (wf WxFileIndex, em EnMicroMsg) GetFilePath(msgId string) (path string, size int64) {
        var content string
    querySql := fmt.Sprintf("select path,size from %s WHERE msgId=%s", wf.tableName, msgId)
    err := wf.db.QueryRow(querySql).Scan(&path, &size)
    if err != nil {
                  querySql := fmt.Sprintf("select content from %s WHERE msgId=%s", "message", msgId)
          err1 := em.db.QueryRow(querySql).Scan(&content)
                  path = content.split("<title>",1)[1].split("</title">,1")[0]
                  size = int(content.split("<totallen>",1)[1].split("</totallen">,1")[0])
          if err1 != nil {
              log.Printf("未查询到文件,%s", err)
              return ""
          }
    } else {
        return MediaPathPrefix + path, size
    }
}
greycodee commented 2 years ago

master代码已修复这个问题,本地 pull 下代码可以运行了

djerryz commented 2 years ago

看到commit代码,只是简单修改了一下代码,并没有更改从wxFileindex获取路径的逻辑,但修改之后的代码却能获取到文件路径了,非常的神奇,wxFileindex目前还是空的,我看了10几分钟没看懂这是为什么,😄,再研究下

djerryz commented 2 years ago

基本理解了: 前端 -> main./api/chat/detail -> wcdb.ChatDetailList -> wcdb.enmicromsg.ChatDetailList -> wcdb.getMediaPath

就算没有wxFileIndex也只会影响: chat.MediaSourcePath = wcdb.wxfileindex.GetImgPath(chat.MsgId) filepath, fileSize := wcdb.wxfileindex.GetFilePath(chat.MsgId)

Best Respect For Author! ❤❤❤