如何優雅地管理微信數據庫?

最近每天在隔離點蹲着,發現隔離點的護士小姐姐每天兩次在羣裏扒聊天記錄統計一兩百號人的體溫真是太南了,所以想寫個程序幫小姐姐自動收集,今天剛好隔離期滿,也算是給這段特殊的經歷留個紀念。

這篇文章主要內容是:

  • 如何找到微信本地緩存數據庫存放地址
    • Mac OS 關閉 SIP 系統完整性保護
    • lldb 斷點調試得到緩存數據庫地址
  • 如何打開數據庫
    • lldb 斷點調試得到數據庫密碼
    • 使用 DB Browser for SQLite 打開數據庫並重設密碼
  • 微信本地緩存數據庫的結構介紹
    • Contact - wccontact_new2.db - 好友信息
    • Group - group_new.db - 羣聊和羣成員信息
    • Message - {msg_0.db - msg_9.db} - 聊天記錄和公衆號文章
    • Favorites - favorites.db - 收藏
  • 如何解析數據庫並提取目標信息

找到微信本地緩存數據庫存放地址並獲取數據庫密碼

捷徑

對於Mac OS 系統,一個 short answer 是

/Users/xxx/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/

打開後,可以看到:
在這裏插入圖片描述
這裏需要重點關注的是看起來很像 md5 碼形式的文件,每個文件都代表一個曾經在你的電腦上登陸過並留下緩存的微信賬號,有了下面將會介紹的解碼方法,你可以逐個打開解析,確認到底哪個賬號是你要找的。

總體來說,Windows 系統同理。

LLDB 調試

在沒有任何信息的情況下,我們如何找到一個獲取數據庫地址的系統性方法?答案在於LLDB斷點調試。

什麼是 LLDB?

LLDB is a next generation, high-performance debugger. It is built as a set of reusable components which highly leverage existing libraries in the larger LLVM Project, such as the Clang expression parser and LLVM disassembler.
LLDB is the default debugger in Xcode on Mac OS X and supports debugging C, Objective-C and C++ on the desktop and iOS devices and simulator.
All of the code in the LLDB project is available under the standard LLVM License, an open source “BSD-style” license.

簡言之,LLDB是一個有着 REPL(交互式解析器) 的特性和 C++ |Python 插件的開源調試器新一代高性能調試器。隨着Xcode5的發佈,LLDB調試器成爲macOS系統調試的基礎部分。對於開源和其他非基於GUI的應用程序調試的開發,可以將終端窗口中的LLDB用作傳統的命令行調試器。

這裏的主要的信息是:

  • LLDB 是一個內建於 ISO 終端窗口的命令行調試器
  • LLDB 可以和系統運行的進程進行交互調試

這意味着,使用 LLDB 我們可以通過在命令行打斷點來獲得正在運行的進程的後臺信息。這也是我們可以通過 LLDB 來尋找微信數據庫地址以及獲取訪問密碼的主要原因。

準備工作:關閉SIP系統完整性保護

系統完整性保護(SIP)是 OS X El Capitan 及更高版本所採用的一項安全技術,旨在幫助防止潛在惡意軟件修改 Mac 上受保護的文件和文件夾,但這也造成了安裝某些特殊版本軟件的或者做特殊修改的時候權限不足。在這裏就體現在使用 LLDB 調試時候,所有的調試語句都會被系統拒絕,因此在正式進行調試之前,一個重要的準備工作就是檢查系統完整性保護(SIP)的開啓狀態,如果開啓的話,要把它關閉。

  • 檢查 SIP 的開啓狀態
    在終端裏輸入 csrutil status 回車,如果看到:

    System Integrity Protection status: enabled.

    這說明的 SIP 已經開啓,如果要繼續調試的話,需要關閉。如果是 System Integrity Protection status: disabled. 則說明 SIP 已經處於關閉狀態,可以直接進行調試。

  • 關閉 SIP

    • 重啓,並在開機的時候長按 CommandR

    • 進入系統恢復狀態

    • 點擊屏幕頂部工具欄上的 實用工具,選擇終端

    • 在終端中輸入 csrutil disable 回車,會出現

      Successfully disabled System Integrity Protection. Please restart the machine for the changes to take effect.

    • 再次重啓生效

  • 記得在調試後按照同樣的步驟輸入 csrutil enable 重新開啓 SIP 惹

LLDB 獲取微信數據庫地址
  • 打開微信,但是不要登錄

  • 在命令行裏輸入 lldb -p $(pgrep WeChat)

    (lldb) process attach --pid 77855
    Process 77855 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
        frame #0: 0x00007fff6e878dfa libsystem_kernel.dylib`mach_msg_trap + 10
    libsystem_kernel.dylib`mach_msg_trap:
    ->  0x7fff6e878dfa <+10>: retq   
        0x7fff6e878dfb <+11>: nop    
    
    libsystem_kernel.dylib`mach_msg_overwrite_trap:
        0x7fff6e878dfc <+0>:  movq   %rcx, %r10
        0x7fff6e878dff <+3>:  movl   $0x1000020, %eax          ; imm = 0x1000020 
    Target 0: (WeChat) stopped.
    
    Executable module set to "/Applications/WeChat.app/Contents/MacOS/WeChat".
    Architecture set to: x86_64h-apple-macosx-.
    
  • 這時候微信的進程被我們暫停了,需要在命令行中輸入 c 回車,可以看到:

    Process 77855 resuming

  • 掃碼登陸微信

  • 輸入 br set -n '[WCTDatabase initWithPath:]

    Breakpoint 1: where = WCDB`-[WCTDatabase(Database) initWithPath:], address = 0x000000010e54120a
    
  • 進入 聊天備份與恢復 頁面點擊恢復聊天記錄到手機 觸發斷點

    Process 78136 stopped
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
        frame #0: 0x000000010e54120a WCDB`-[WCTDatabase(Database) initWithPath:]
    WCDB`-[WCTDatabase(Database) initWithPath:]:
    ->  0x10e54120a <+0>: pushq  %rbp
        0x10e54120b <+1>: movq   %rsp, %rbp
        0x10e54120e <+4>: pushq  %r15
        0x10e541210 <+6>: pushq  %r14
    Target 0: (WeChat) stopped.
    
  • 在命令行輸入 po $arg3

    /Users/xxxx/Library/Containers/com.tencent.xinWeChat/Data/Library/ApplicationSupport/com.tencent.xinWeChat/2.0b4.0.9/Backup/d9381f8bfa1ab8fa0f5e54b2858dffc3/EA2CC6CD-FCD2-4570-9CAC-9C95FF7E348B/Backup.db

  • 以上可以得到微信數據庫的本地存儲地址,確切地來說是微信備份文件的存儲地址,往上一層文件夾就可以找到微信好友和聊天記錄數據庫

在這裏插入圖片描述

LLDB 獲取微信數據庫密碼
  • 接着上面的操作在命令行裏輸入 br set -n sqlite3_key

    Breakpoint 2: 2 locations.
    
  • 輸入 memory read --size 1 --format x --count 32 $rsi

    0x7fff78a12bc9: 0x69 0x6e 0x59 0x74 0x57 0x69 0x74 0x68
    0x7fff78a12bd1: 0x50 0x61 0x34 0x68 0x3a 0x00 0x73 0x65
    0x7fff78a12bd9: 0x74 0x53 0x70 0x65 0x65 0x64 0x4d 0x75
    0x7fff78a12be1: 0x6c 0x74 0x69 0x70 0x6c 0x69 0x65 0x72
    
  • 按照以下的步驟處理上面的輸出即可得到 64 位密碼:

    • 只保留 : 右邊的數據

      0x69 0x6e 0x59 0x74 0x57 0x69 0x74 0x68
      0x50 0x61 0x34 0x68 0x3a 0x00 0x73 0x65
      0x74 0x53 0x70 0x65 0x65 0x64 0x4d 0x75
      0x6c 0x74 0x69 0x70 0x6c 0x69 0x65 0x72
      
    • 刪掉所有的 0x

      69 6e 59 74 57 69 74 68
      50 61 34 68 3a 00 73 65
      74 53 70 65 65 64 4d 75
      6c 74 69 70 6c 69 65 72
      
    • 刪掉所有的空格和換行

      696e597457697468506134683a0073657453706565644d756c7469706c696572
      
    • 以上就是打開數據庫的 64 位密碼啦,該密碼適用於聊天記錄,好友信息,羣聊成員等各個數據庫

打開數據庫並重設密碼

微信存儲數據用的是輕量級的數據庫工具 SQLite ,有很多軟件可以打開,這裏以 DB Browser for SQLite 爲例。比如我雙擊聊天記錄數據庫 msg_0.db,會出現以下界面:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7slG29fg-1585744780662)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401184709226.png)]

注意:

  • Encryption settings 選擇 SQLClipher 3 defaults
  • 右邊的密碼形式選擇 Raw key
  • 密碼填寫 0x 加上之前獲取的 64 位密碼,所以一共是 66 位

如果正確操作的話,這裏應該可以打開數據庫了。這裏我們來看下數據庫的結構,這裏可以看出這個數據庫裏一共有 144 張表,每張表對應一個微信好友/羣聊/公衆號的聊天記錄。

在這裏插入圖片描述

一個典型的表的屬性如下:
在這裏插入圖片描述

最重要的幾個屬性爲:

  • msgCreateTime 聊天時間戳
  • msgContent 聊天內容
  • messageType 聊天類型 文字爲1

點擊瀏覽數據可以進行預覽:
在這裏插入圖片描述
爲了避免每次打開都輸入密碼,我們可以移除數據庫的密碼。在 DB Browser for SQLite 中的具體操作是 工具 - 設置加密 - OK,即直接重設爲空密碼,這樣也方便我們進一步提取數據。

在這裏插入圖片描述

本地存儲的微信數據庫裏都有什麼?

我翻了翻幾個文件夾,認爲以下四項最有分析意義,當然還有其他的小夥伴們可以自行發掘。

  • Contact - wccontact_new2.db - 好友信息
  • Group - group_new.db - 羣聊和羣成員信息
  • Message - {msg_0.db - msg_9.db} - 聊天記錄和公衆號文章
  • Favorites - favorites.db - 收藏
微信好友/公衆號

首先來看聯繫人數據庫 wccontact_new2.db,這裏主要就是一張表 WCContact,這裏面存儲了我們加的微信好友和關注的公衆號的信息,主要是暱稱和微信號 m_nsUsrName。一般公衆號以 gh_ 開頭。這裏的 m_nsUsrName 非常重要,因爲聊天數據庫裏的表名都是 md5 編碼後的 m_nsUsrName。比如公衆號 廣發證券研究 的 m_nsUsrName 爲 gh_24e4252623cf,md5 編譯後即爲 41cbc56f1e10ab139339a40a4df2132d,因此聊天記錄數據庫裏的 Chat_41cbc56f1e10ab139339a40a4df2132d 即爲這個公衆號的全部歷史信息。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2DAMOPT6-1585744780692)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401185959693.png)]

羣聊/羣成員

group_new.db 數據庫裏有兩張表,分別是 GroupContactGroupMember。其中 GroupContact 和微信好友數據庫很像,只不過裏面存儲的是羣聊名稱和羣聊 m_nsUsrName。用法和聯繫人數據庫一樣,都是通過對 m_nsUsrName 進行 md5 編譯索引到聊天記錄數據庫裏的表。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-BmL95Kys-1585744780693)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401195131067.png)]

GroupMember 表裏包含了你加羣裏所有羣成員的微信號和暱稱,不管有沒有加過好友。所以一般如果聯繫人數據庫裏有幾百個的話,這個表裏往往有幾千上萬條記錄。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LsJWBgFs-1585744780694)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401195259014.png)]

收藏

favorites.db 裏面有 8 張表,其中最重要的是兩個,FavoriteItemTableFavoriteSearchTable

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-L46rj09X-1585744780703)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401195806062.png)]
FavoriteSearchTable 給了收藏的標題和 localID。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZUpnovu6-1585744780704)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401200058596.png)]
FavoriteItemTable 給了收藏時間戳,收藏內容鏈接,以及收藏內容來源用戶等,並且可以和 FavoriteSearchTable 通過 localID 互相索引。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6i4ewQJb-1585744780705)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401200229562.png)]

聊天記錄

見上文

如何解析數據庫並提取目標信息?

使用解密腳本打開數據庫:

from pysqlcipher import dbapi2 as sqlite
output = 'output_db_whole.db'
key = 'a3c77a9'
conn = sqlite.connect(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("SELECT name FROM sqlite_master WHERE type='table'")
c.execute("ATTACH DATABASE '" + output + "' AS db KEY '';")
c.execute("SELECT sqlcipher_export('db');")
c.execute("DETACH DATABASE db;")
conn.close()

從羣聊數據庫裏提取羣聊列表,使用 transmd5 函數獲得羣聊索引序號 md5 編碼,並寫入文本文件中:

import sqlite3
import time, datetime
import hashlib

conn = sqlite3.connect('group_new.db')
print("Opened database successfully")

def transmd5(string):
	m = hashlib.md5(string.encode(encoding='UTF-8')).hexdigest()
	return(m)

def transfertime(timeStamp):
	timeArray = time.localtime(timeStamp)
	otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
	return(otherStyleTime)

cursor = conn.execute("SELECT m_nsUsrName, nickname, m_nsFullPY, m_nsChatRoomMemList from GroupContact")
f = open('groupcontact.txt','w')
for row in cursor:
	for i in range(3):
		f.write(row[i]+';')
	f.write(transmd5(row[0]))
	f.write('\n')

print("Operation done successfully")
conn.close()

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YUEzSPk8-1585744780710)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401201319956.png)]

從以上結果裏面,我們可以得到我們想要的羣聊在數據庫中的 md5 編碼 Chat_aa309112204d5fd125c5a8bad609ff25。同時提取羣聊成員列表,準備與聊天記錄進行合併:

conn = sqlite3.connect('group_new.db')

cursor = conn.execute("SELECT m_nsUsrName, nickname from GroupMember")
f = open('groupmember.txt','w')
for row in cursor:
	for i in range(2):
		f.write(row[i]+';')
	f.write('\n')

conn.close()

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4CUXUqGQ-1585744780714)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401202149160.png)]

由於聊天記錄被自動拆分到了 10 個數據庫文件 msg_0.db - msg_9.db 裏,需要遍歷所有的數據庫文件獲取每個數據庫裏所有的表名才能確定我們要的聊天記錄到底存儲在哪個文件裏。

def sheetname(i):
	conn = sqlite3.connect('msg_%s.db'%i)
	return(conn)

def getdata(conn):
	cursor = conn.execute("select name from sqlite_master where type='table'")
	tab_name=cursor.fetchall()
	tab_name=[line[0] for line in tab_name]
	return(tab_name)

f = open('sheetname.txt','a')
for j in range(10):
	conn = sheetname(j)
	tab_name = getdata(conn)
	for i in tab_name:
		f.write(i+','+'sheet_%s'%j+'\n')
    
conn.close()

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8NderBKA-1585744780715)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401201816684.png)]

經過這一步,我們可以精確定位想要的羣聊到底在哪個數據庫文件裏的哪張表裏。於是可以從聊天數據庫裏提取指定羣聊信息的聊天記錄,轉換時間戳,使用正則表達式篩選符合指定信息的聊天記錄,並寫入文本文件中:

conn = sqlite3.connect('msg_0.db')

def transfertime(timeStamp):
	timeArray=time.localtime(timeStamp)
	otherStyleTime=time.strftime("%Y-%m-%d %H:%M:%S",timeArray)
	return(otherStyleTime)

cursor=conn.execute("SELECT msgCreateTime,messageType,msgContent from Chat_aa309112204d5fd125c5a8bad609ff25 where messageType=1")

f = open('chatrecord.txt','w')

for row in cursor:
	a = re.findall('[\d]+.*[\d]+\.[\d]',row[2].split(':')[-1])
	if a != []:
		f.write("%s;%s;%s;\n"%(transfertime(row[0]),row[2].split(':')[0],a))
		
conn.close()

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5uAg1FCf-1585744780716)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401201003038.png)]
如果不通過正則表達式篩選的話,得到的就是文本格式的聊天記錄:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-XHL7FkLJ-1585744780717)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401202532163.png)]

這裏存在一個問題是,聊天記錄裏只有每個人的微信號,沒有暱稱,因此我們需要把這張表和羣聊成員表合併,並導出最終的結果:

import pandas as pd
import csv

df1 = pd.read_table('/Users/mengjiexu/PycharmProjects/wx/chatrecord.txt',sep = ';')
df1.columns = ['timestamp','wxindex','record','']
df2 = pd.read_table('/Users/mengjiexu/PycharmProjects/wx/groupmember.txt',sep = ';',error_bad_lines=False,quoting=csv.QUOTE_NONE)
df2.columns = ['wxindex','nickname','']
df = pd.merge(df1,df2,on = 'wxindex',how='left')
df.to_csv('resultspd.csv',mode ='w',encoding='gb18030')

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nfy9xIZz-1585744780719)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401202426435.png)]

如果對自己的好友分佈感興趣,還可以導出自己的好友和公衆號列表,並進行進一步的分析:

conn = sqlite3.connect('wccontact_new2.db')

cursor = conn.execute("SELECT m_nsUsrName, nickname, m_nsFullPY, m_nsAliasName  from WCContact")
#df = pd.DataFrame(cursor, columns=['username','nickname','fullpy','aliasname'])
#df.to_csv('contact.csv', sep=',', mode='a', encoding='utf8')
f = open('contact.txt','a')
for row in cursor:
	for i in range(3):
		f.write(row[i]+',')
	f.write('\n')

conn.close()

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-EmnGRZlN-1585744780720)(/Users/mengjiexu/Library/Application Support/typora-user-images/image-20200401202802754.png)]

參考鏈接

  1. https://www.jianshu.com/p/0f41c120160d
  2. https://jingyan.baidu.com/article/f0e83a255eea0622e591013d.html
  3. https://www.jianshu.com/p/90224ab9cdf2
  4. http://xferris.cn/dao-chu-wei-xin-bei-fen-de-mac/
  5. https://www.jianshu.com/p/93bbcda3133a
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章