├── .DS_Store ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── Makefile ├── README.md ├── api └── api.go ├── db ├── enmicromsg.go ├── requeststruct.go ├── resultstruct.go ├── wcdb.go └── wxfileindex.go ├── dockerfile ├── abe.dockerfile ├── silkV3-decoder.dockerfile └── wcdb-sqlcipher.dockerfile ├── go.mod ├── go.sum ├── hook.py ├── index.html ├── main.go ├── main_test.go ├── static ├── css │ ├── main.17499858.css │ └── main.17499858.css.map └── js │ ├── 787.12d53414.chunk.js │ ├── 787.12d53414.chunk.js.map │ ├── main.fe264cf7.js │ ├── main.fe264cf7.js.LICENSE.txt │ └── main.fe264cf7.js.map ├── wcdb-parse-api.http └── web.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greycodee/wechat-backup/777f03010d2628ddce4eb34d0099d3145e542fe8/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release-Tag-Work 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Get version 16 | id: get_version 17 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//} 18 | - name: Set up Go 19 | uses: actions/setup-go@v2 20 | with: 21 | go-version: 1.16 22 | - name: Build 23 | run: | 24 | sudo apt-get install gcc-aarch64-linux-gnu 25 | sudo apt-get install gcc-mingw-w64-x86-64 26 | make 27 | - name: Create Release 28 | id: create_release 29 | uses: actions/create-release@master 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.TOKEN }} # 之前GitHub添加的Token 32 | with: 33 | tag_name: ${{ github.ref }} # (tag)标签名称 34 | release_name: wechat-backup-${{ steps.get_version.outputs.VERSION }} 35 | draft: false # 是否是草稿 36 | prerelease: false # 是否是预发布 37 | # 上传构建结果到 Release(把打包的tgz上传到Release) 38 | - name: build TAR PACKAGE 39 | run: | 40 | tar -czvf wechat-backup-linux.tar.gz ./dist/linux 41 | tar -czvf wechat-backup-arm64.tar.gz ./dist/linux-arm64 42 | tar -czvf wechat-backup-windows.tar.gz ./dist/windows 43 | - name: Upload Release Linux 44 | id: upload-release-linux 45 | uses: actions/upload-release-asset@master 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 48 | with: 49 | tag_name: ${{ github.ref }} # (tag)标签名称 50 | upload_url: ${{ steps.create_release.outputs.upload_url }} # 上传地址,通过创建Release获取到的 51 | asset_path: ./wechat-backup-linux.tar.gz # 要上传文件 52 | asset_name: wechat-backup_linux_${{ steps.get_version.outputs.VERSION }}.tar.gz # 上传后的文件名 53 | asset_content_type: application/gzip 54 | 55 | - name: Upload Release Arm64 56 | id: upload-release-arm64 57 | uses: actions/upload-release-asset@master 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 60 | with: 61 | tag_name: ${{ github.ref }} # (tag)标签名称 62 | upload_url: ${{ steps.create_release.outputs.upload_url }} # 上传地址,通过创建Release获取到的 63 | asset_path: ./wechat-backup-arm64.tar.gz # 要上传文件 64 | asset_name: wechat-backup_arm64_${{ steps.get_version.outputs.VERSION }}.tar.gz # 上传后的文件名 65 | asset_content_type: application/gzip 66 | - name: Upload Release windows 67 | id: upload-release-windows 68 | uses: actions/upload-release-asset@master 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 71 | with: 72 | tag_name: ${{ github.ref }} # (tag)标签名称 73 | upload_url: ${{ steps.create_release.outputs.upload_url }} # 上传地址,通过创建Release获取到的 74 | asset_path: ./wechat-backup-windows.tar.gz # 要上传文件 75 | asset_name: wechat-backup_windows_${{ steps.get_version.outputs.VERSION }}.tar.gz # 上传后的文件名 76 | asset_content_type: application/gzip -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | wechat-backup 3 | dist -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${fileDirname}" 13 | 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ooooooh灰灰 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export GO111MODULE=on 2 | binary_name=wechat-backup 3 | 4 | all: ${binary_name}.linux ${binary_name}.windows ${binary_name}.linux-arm64 5 | LDFLAGS = -s -w 6 | ifdef STATIC 7 | LDFLAGS += -linkmode external -extldflags '-static' 8 | CC = /usr/bin/musl-gcc 9 | export CC 10 | endif 11 | 12 | $(binary_name): 13 | go build -ldflags="$(LDFLAGS)" -o $(binary_name) . 14 | 15 | $(binary_name).linux: 16 | CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CGO_LDFLAGS="-static" go build -ldflags="$(LDFLAGS)" -o dist/linux/$(binary_name) . 17 | # Please execute the `apt install gcc-aarch64-linux-gnu` command before using it. 18 | $(binary_name).linux-arm64: 19 | CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc CGO_LDFLAGS="-static" go build -ldflags="$(LDFLAGS)" -o dist/linux-arm64/$(binary_name) . 20 | # Please execute the `apt install gcc-mingw-w64-x86-64` command before using it. 21 | $(binary_name).windows: 22 | CGO_ENABLED=1 GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc go build -ldflags="$(LDFLAGS)" -buildmode exe -o dist/windows/$(binary_name).exe . 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > ### *此项目停止维护,如需二次开发,请自行 fork* 2 | 3 | ## 效果图 4 |  5 | 6 | ## 使用流程 7 | > 详细说明在: https://blog.greycode.top/posts/android-wechat-bak/ 8 | 9 | 1. 手机聊天记录备份到电脑,在有 ROOT 权限的手机上登陆微信,电脑点击备份恢复,把聊天记录恢复到有 ROOT 的手机上。 10 | 2. 收集下面这些数据,然后放在**同一个文件夹下**: 11 | - image2 文件夹:里面存放着所有的微信聊天图片,位置在:/data/data/com.tencent.mm/MicroMsg/[32位字母]/image2 12 | - voice2 文件夹:里面存放着所有的微信语音,位置在:/sdcard/Android/data/com.tencent.mm/MicroMsg/[32位字母]/voice2 13 | - video 文件夹:里面存放着所有的微信视频,位置在:/sdcard/Android/data/com.tencent.mm/MicroMsg/[32位字母]/video 14 | - avatar 文件夹:里面存放着所有的微信头像,位置在:/data/data/com.tencent.mm/MicroMsg/[32位字母]/avatar 15 | - Download 文件夹: 微信的聊天发送的文件存放在这里,位置在:/sdcard/Android/data/com.tencent.mm/MicroMsg/Download 16 | - EnMicroMsg.db: 微信的数据库文件,位置在:/data/data/com.tencent.mm/MicroMsg/[32位字母]/EnMicroMsg.db 17 | - WxFileIndex.db: 微信的文件索引数据库文件,位置在:/data/data/com.tencent.mm/MicroMsg/[32位字母]/WxFileIndex.db 18 | 3. 获取解密 DB 的密钥。 19 | 4. 进行微信聊天数据 DB 的解密 20 | 5. 转换微信语音 21 | 6. 运行本程序,打开控制台输出的网址,就可以查看你的聊天记录了。 22 | 23 | ### 运行命令 24 | 25 | 可以直接运行: 26 | 27 | ```shell 28 | $ git clone https://github.com/greycodee/wechat-backup.git 29 | $ cd wechat-backup 30 | $ go run main.go -f '[替换成你的微信备份文件路径]' 31 | ``` 32 | 33 | 或者编译后运行: 34 | ```shell 35 | $ git clone https://github.com/greycodee/wechat-backup.git 36 | $ cd wechat-backup 37 | $ go build . 38 | $ ./wechat-backup -f '[替换成你的微信备份文件路径]' 39 | ``` 40 | 41 | > ~~注意⚠️:WxFileIndex.db 里面文数据表名老版本微信是 WxFileIndex2 ,新版本微信是 WxFileIndex3 ,请根据实际情况来设置代码 wxfileindex.go 文件中 SQL 查询的表名~~(已在代码中做处理) 42 | 43 | ## 快速解密微信DB 44 | 把要解密的微信 DB 所在文件夹挂在到容器的 `/wcdb` 上面。 45 | ```shell 46 | $ docker run --rm -v /Users/zheng/Documents:/wcdb greycodee/wcdb-sqlcipher -f DB名字 -k 解密密钥 47 | 48 | 2022/06/22 05:31:17 开始解密... 49 | 2022/06/22 05:31:28 解密成功: ok 50 | 2022/06/22 05:31:28 明文数据库文件名: EnMicroMsg_plain.db 51 | ``` 52 | 53 | ## 快速转换微信语音 amr 文件 54 | 把要转换的语音文件夹挂载到容器的 `/media` 目录上,然后执行下面的命令,就会自动将文件夹里的语音转换成 `mp3` 格式了。 55 | ```shell 56 | $ docker run --rm -v /Users/zheng/Documents/voice2:/media greycodee/silkv3-decoder 57 | 58 | /media/msg_491351061422dbfa9bb0a84104.amr 59 | -e [OK] Convert /media/msg_491351061422dbfa9bb0a84104.amr To /media/msg_491351061422dbfa9bb0a84104.mp3 Finish. 60 | ``` 61 | 62 | ## 手机没有 ROOT 解决方法 63 | 如果没有有 ROOT 的手机,根据 V 友的说法,使用安卓模拟器的话有一定的封号机率。这个还有一个方法就是使用手机自带的系统备份。 64 | 我自己是miui系统的手机,整理了大致方法如下: 65 | 66 | 1. 在手机设置里找到备份功能,然后备份微信应用数据 67 | 2. 备份的数据放在手机的 MIUI/backup/AllBackup/备份的日期/ 下 68 | 3. 里面有三个文件 分别是 .bak .zip .xml 结尾 69 | 4. zip 可以直接解压,然后获取聊天语音和聊天视频等文件 70 | 5. bak 需要使用 HEX 编辑器打开,然后将 41 4E 前的数据删除(就是这个文件的描述数据)。然后保存 71 | 6. 使用 abe 解包工具进行 .bak 文件的解包,开源工具地址:https://github.com/nelenkov/android-backup-extractor 72 | 7. 解压解包后生产的 tar 压缩包,然后从里面可以获取到 DB 等文件(相当于本来要ROOT后才能获取到的这些数据,现在可以直接获取了) 73 | 74 | > 我这边通过系统备份然后 bak 文件解包-解压后,文件路径在:apps/com.tencent.mm/r/MicroMsg/ 下,大家可以参考一下 75 | > 如果通过手机系统自带的备份来获取数据时,解密 DB 的密码可以通过这篇文章的方法一来获取: [获取 DB 解密密码](https://blog.greycode.top/posts/android-wechat-bak/#%E8%8E%B7%E5%8F%96-db-%E8%AE%BF%E9%97%AE%E5%AF%86%E7%A0%81) 76 | 77 | ## Q&A 78 | ### 语音和视频所在文件夹 79 | 在 `/data/data/com.tencent.mm/MicroMsg/[32位字母]` 文件夹下,有个 `account.mapping` 文件,里面对应的 `/sdcard/Android/data/com.tencent.mm/MicroMsg` 下文件夹的名称,里面存储了聊天视频和语音等文件。 80 | 81 | ### 一台设备登陆多个微信怎么查找 uin? 82 | 在 `/data/data/com.tencent.mm/shared_prefs` 文件夹下有个 `app_brand_global_sp.xml` 文件,里面存放着所有登陆过微信的 uin,然后可以用这些 uin 去解密对应的微信 DB。 83 | 84 | ## Star History 85 | 86 | [](https://star-history.com/?utm_source=bestxtools.com#greycodee/wechat-backup&Date) 87 | 88 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/greycodee/wechat-backup/db" 9 | ) 10 | 11 | const ( 12 | ListApi = "/api/chat/list" 13 | DetailApi = "/api/chat/detail" 14 | UserInfoApi = "/api/user/info" 15 | MyInfoApi = "/api/user/myinfo" 16 | ImgApi = "/api/media/img" 17 | VideoApi = "/api/media/video" 18 | VoiceApi = "/api/media/voice" 19 | ) 20 | 21 | type Api struct { 22 | wcdb *db.WCDB 23 | Engine *gin.Engine 24 | } 25 | 26 | func New(dbPath string) *Api { 27 | a := &Api{} 28 | a.wcdb = db.InitWCDB(dbPath) 29 | a.Engine = gin.New() 30 | a.Engine.Use(gin.Recovery()) 31 | 32 | return a 33 | } 34 | 35 | func (a Api) listHandler(c *gin.Context) { 36 | pageIndex, _ := strconv.Atoi(c.DefaultQuery("pageIndex", "1")) 37 | pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) 38 | name := c.Query("name") 39 | all, _ := strconv.ParseBool(c.DefaultQuery("all", "false")) 40 | 41 | result := a.wcdb.ChatList(pageIndex-1, pageSize, all, name) 42 | // 聊天列表 43 | c.JSON(200, result) 44 | } 45 | 46 | func (a Api) detailHandler(c *gin.Context) { 47 | pageIndex, _ := strconv.Atoi(c.DefaultQuery("pageIndex", "1")) 48 | pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) 49 | talker := c.Query("talker") 50 | c.JSON(200, a.wcdb.ChatDetailList(talker, pageIndex-1, pageSize)) 51 | } 52 | 53 | func (a Api) userInfoHandler(c *gin.Context) { 54 | userName := c.Query("username") 55 | c.JSON(200, a.wcdb.GetUserInfo(userName)) 56 | } 57 | 58 | func (a Api) myInfoHandler(c *gin.Context) { 59 | c.JSON(200, a.wcdb.GetMyInfo()) 60 | } 61 | 62 | func (a Api) imgHandler(c *gin.Context) { 63 | msgId := c.Query("msgId") 64 | c.JSON(200, a.wcdb.GetImgPath(msgId)) 65 | } 66 | 67 | func (a Api) videoHandler(c *gin.Context) { 68 | msgId := c.Query("msgId") 69 | c.JSON(200, a.wcdb.GetVideoPath(msgId)) 70 | } 71 | 72 | func (a Api) voiceHandler(c *gin.Context) { 73 | msgId := c.Query("msgId") 74 | c.JSON(200, a.wcdb.GetVoicePath(msgId)) 75 | } 76 | 77 | func (a Api) Router() http.Handler { 78 | a.Engine.GET(ListApi, a.listHandler) 79 | a.Engine.GET(DetailApi, a.detailHandler) 80 | a.Engine.GET(UserInfoApi, a.userInfoHandler) 81 | a.Engine.GET(MyInfoApi, a.myInfoHandler) 82 | a.Engine.GET(ImgApi, a.imgHandler) 83 | a.Engine.GET(VideoApi, a.videoHandler) 84 | a.Engine.GET(VoiceApi, a.voiceHandler) 85 | 86 | return a.Engine 87 | } 88 | -------------------------------------------------------------------------------- /db/enmicromsg.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "crypto/md5" 5 | "database/sql" 6 | "fmt" 7 | "log" 8 | "strings" 9 | 10 | _ "github.com/mattn/go-sqlite3" 11 | ) 12 | 13 | var MediaPathPrefix = "/media/" 14 | 15 | type EnMicroMsg struct { 16 | db *sql.DB 17 | myInfo UserInfo 18 | } 19 | 20 | func OpenEnMicroMsg(dbPath string) *EnMicroMsg { 21 | em := &EnMicroMsg{} 22 | db, err := sql.Open("sqlite3", dbPath) 23 | if err != nil { 24 | log.Fatalf("open db error: %v", err) 25 | } 26 | em.db = db 27 | em.myInfo = em.GetMyInfo() 28 | return em 29 | } 30 | 31 | func (em *EnMicroMsg) Close() { 32 | em.db.Close() 33 | } 34 | 35 | func (em EnMicroMsg) ChatList(pageIndex int, pageSize int, all bool, name string) *ChatList { 36 | result := &ChatList{} 37 | result.Total = 10 38 | result.Rows = make([]ChatListRow, 0) 39 | var queryRowsSqlTmp string 40 | var queryRowsSql string 41 | queryRowsSqlTmp = "select count(*) as msgCount,ifnull(rc.alias,'') as alias,msg.talker,ifnull(rc.nickname,'') as nickname,ifnull(rc.conRemark,'') as conRemark,ifnull(imf.reserved1,'') as reserved1,ifnull(imf.reserved2,'') as reserved2,msg.createtime from message msg left join rcontact rc on msg.talker=rc.username left join img_flag imf on msg.talker=imf.username " 42 | if name != "" { 43 | queryRowsSqlTmp = queryRowsSqlTmp + "where nickname like '%" + name + "%' or conRemark like '%" + name + "%'" 44 | } 45 | queryRowsSqlTmp = queryRowsSqlTmp + " group by msg.talker order by msg.createTime desc " 46 | queryRowsSql = queryRowsSqlTmp 47 | if !all { 48 | queryRowsSql = queryRowsSql + fmt.Sprintf("limit %d,%d", pageIndex*pageSize, pageSize) 49 | } 50 | rows, err := em.db.Query(queryRowsSql) 51 | if err != nil { 52 | fmt.Println(err) 53 | } 54 | defer rows.Close() 55 | for rows.Next() { 56 | var r ChatListRow 57 | r.UserType = 0 58 | err = rows.Scan(&r.MsgCount, &r.Alias, &r.Talker, &r.NickName, &r.ConRemark, &r.Reserved1, &r.Reserved2, &r.CreateTime) 59 | // 判断是否是群聊 60 | if len(strings.Split(r.Talker, "@")) == 2 && strings.Split(r.Talker, "@")[1] == "chatroom" { 61 | r.UserType = 1 62 | if r.NickName == "" { 63 | queryRoomSql := fmt.Sprintf("select displayname as nickname from chatroom where chatroomname='%s'", r.Talker) 64 | room, _ := em.db.Query(queryRoomSql) 65 | defer room.Close() 66 | for room.Next() { 67 | room.Scan(&r.NickName) 68 | } 69 | } 70 | } else if r.Talker[:3] == "gh_" { 71 | r.UserType = 2 72 | } 73 | 74 | if err != nil { 75 | log.Printf("未查询到聊天列表,%s", err) 76 | } 77 | r.LocalAvatar = em.getLocalAvatar(r.Talker) 78 | result.Rows = append(result.Rows, r) 79 | } 80 | 81 | queryTotalSql := "select count(*) as total FROM (" + queryRowsSqlTmp + ") as d" 82 | var total int 83 | err = em.db.QueryRow(queryTotalSql).Scan(&total) 84 | if err != nil { 85 | log.Printf("未查询到聊天列表总数,%s", err) 86 | } else { 87 | result.Total = total 88 | } 89 | 90 | return result 91 | } 92 | 93 | func (em EnMicroMsg) ChatDetailList(talker string, pageIndex int, pageSize int) *ChatDetailList { 94 | result := &ChatDetailList{} 95 | result.Total = 10 96 | result.Rows = make([]ChatDetailListRow, 0) 97 | queryRowsSql := fmt.Sprintf("SELECT ifnull(msgId,'') as msgId,ifnull(msgSvrId,'') as msgSvrId,type,isSend,createTime,talker,ifnull(content,'') as content,ifnull(imgPath,'') as imgPath FROM message WHERE talker='%s' order by createtime desc limit %d,%d", talker, pageIndex*pageSize, pageSize) 98 | rows, err := em.db.Query(queryRowsSql) 99 | if err != nil { 100 | fmt.Println(err) 101 | } 102 | defer rows.Close() 103 | for rows.Next() { 104 | var r ChatDetailListRow 105 | err = rows.Scan(&r.MsgId, &r.MsgSvrId, &r.Type, &r.IsSend, &r.CreateTime, &r.Talker, &r.Content, &r.ImgPath) 106 | if err != nil { 107 | log.Printf("未查询到聊天历史记录,%s", err) 108 | } 109 | // 表情图片 110 | if r.Type == 47 { 111 | r.EmojiInfo = em.GetEmojiInfo(r.ImgPath) 112 | } 113 | // em.getMediaPath(&r, wxfileindex) 114 | result.Rows = append(result.Rows, r) 115 | } 116 | return result 117 | } 118 | 119 | func (em EnMicroMsg) GetUserInfo(username string) UserInfo { 120 | r := UserInfo{} 121 | querySql := fmt.Sprintf("select rc.username,rc.alias,rc.conRemark,rc.nickname,ifnull(imf.reserved1,'') as reserved1,ifnull(imf.reserved2,'') as reserved2 from rcontact rc LEFT JOIN img_flag imf on rc.username=imf.username where rc.username='%s';", username) 122 | err := em.db.QueryRow(querySql).Scan(&r.UserName, &r.Alias, &r.ConRemark, &r.NickName, &r.Reserved1, &r.Reserved2) 123 | if err != nil { 124 | log.Printf("未查询到用户信息,%s", err) 125 | } else { 126 | r.LocalAvatar = em.getLocalAvatar(r.UserName) 127 | } 128 | r.LocalAvatar = em.getLocalAvatar(r.UserName) 129 | return r 130 | } 131 | 132 | func (em EnMicroMsg) GetMyInfo() UserInfo { 133 | r := UserInfo{} 134 | querySql := "select rc.username,rc.alias,ifnull(rc.conRemark,''),rc.nickname,ifnull(imf.reserved1,'') as reserved1,ifnull(imf.reserved2,'') as reserved2 from rcontact rc left join img_flag imf on rc.username=imf.username where rc.username=(select value from userinfo WHERE id = 2)" 135 | err := em.db.QueryRow(querySql).Scan(&r.UserName, &r.Alias, &r.ConRemark, &r.NickName, &r.Reserved1, &r.Reserved2) 136 | if err != nil { 137 | log.Printf("未查询到个人信息,%s", err) 138 | } else { 139 | r.LocalAvatar = em.getLocalAvatar(r.UserName) 140 | } 141 | return r 142 | } 143 | 144 | func (em EnMicroMsg) getLocalAvatar(username string) string { 145 | md5Str := fmt.Sprintf("%x", md5.Sum([]byte(username))) 146 | filePath := fmt.Sprintf("%savatar/%s/%s/user_%s.png", MediaPathPrefix, md5Str[0:2], md5Str[2:4], md5Str) 147 | return filePath 148 | } 149 | 150 | func (em EnMicroMsg) formatImagePath(path string) string { 151 | imgFileName := strings.Split(path, "://")[1] 152 | return fmt.Sprintf("%simage2/%s/%s/%s", MediaPathPrefix, imgFileName[3:5], imgFileName[5:7], imgFileName) 153 | } 154 | func (em EnMicroMsg) formatImageBCKPath(chat ChatDetailListRow) string { 155 | var imgFileName string 156 | if chat.IsSend == 0 { 157 | // 接收 158 | imgFileName = fmt.Sprintf("%s_%s_%s_backup", chat.Talker, em.myInfo.UserName, chat.MsgSvrId) 159 | } else { 160 | // 发送 161 | imgFileName = fmt.Sprintf("%s_%s_%s_backup", em.myInfo.UserName, chat.Talker, chat.MsgSvrId) 162 | } 163 | return fmt.Sprintf("%simage2/%s/%s/%s", MediaPathPrefix, imgFileName[0:2], imgFileName[2:4], imgFileName) 164 | } 165 | func (em EnMicroMsg) formatVoicePath(path string) string { 166 | p := md5.Sum([]byte(path)) 167 | md5Str := fmt.Sprintf("%x", p) 168 | // 这边原本后缀为amr格式,由于网页不能播放amr格式,所以要用转换工具转换格式,转换后为mp3格式,所以后缀为mp3 169 | return fmt.Sprintf("%svoice2/%s/%s/msg_%s.mp3", MediaPathPrefix, md5Str[0:2], md5Str[2:4], path) 170 | } 171 | func (em EnMicroMsg) formatVideoPath(path string) string { 172 | return fmt.Sprintf("%svideo/%s.mp4", MediaPathPrefix, path) 173 | } 174 | 175 | // 176 | func (em EnMicroMsg) GetEmojiInfo(imgPath string) EmojiInfo { 177 | emojiInfo := EmojiInfo{} 178 | querySql := fmt.Sprintf("select md5, cdnUrl,width,height from EmojiInfo where md5='%s'", imgPath) 179 | 180 | err := em.db.QueryRow(querySql).Scan(&emojiInfo.Md5, &emojiInfo.CDNUrl, &emojiInfo.W, &emojiInfo.H) 181 | if err != nil { 182 | log.Printf("未查询到Emoji,%s", err) 183 | } 184 | return emojiInfo 185 | } 186 | -------------------------------------------------------------------------------- /db/requeststruct.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | type ChatListRequestBody struct { 4 | All bool `json:"all"` 5 | PageIndex int `json:"pageIndex"` 6 | PageSize int `json:"pageSize"` 7 | } 8 | -------------------------------------------------------------------------------- /db/resultstruct.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | type ChatList struct { 4 | Total int `json:"total"` 5 | Rows []ChatListRow `json:"rows"` 6 | } 7 | 8 | type ChatListRow struct { 9 | Talker string `json:"talker"` 10 | Alias string `json:"alias"` 11 | NickName string `json:"nickname"` 12 | ConRemark string `json:"conRemark"` 13 | Reserved1 string `json:"reserved1"` 14 | Reserved2 string `json:"reserved2"` 15 | LocalAvatar string `json:"localAvatar"` 16 | MsgCount int `json:"msgCount"` 17 | CreateTime int64 `json:"createTime"` 18 | ChatType int `json:"chatType"` 19 | UserType int `json:"userType"` 20 | } 21 | 22 | type ChatDetailList struct { 23 | Total int `json:"total"` 24 | Rows []ChatDetailListRow `json:"rows"` 25 | } 26 | type ChatDetailListRow struct { 27 | MsgId string `json:"msgId"` 28 | MsgSvrId string `json:"msgSvrId"` 29 | Type int `json:"type"` 30 | IsSend int `json:"isSend"` 31 | CreateTime int64 `json:"createTime"` 32 | Talker string `json:"talker"` 33 | Content string `json:"content"` 34 | ImgPath string `json:"imgPath"` 35 | MediaPath string `json:"mediaPath"` 36 | MediaBCKPath string `json:"mediaBCKPath"` 37 | MediaSourcePath string `json:"mediaSourcePath"` 38 | FileInfo FileInfo `json:"fileInfo"` 39 | EmojiInfo EmojiInfo `json:"emojiInfo"` 40 | IsChatRoom bool `json:"isChatRoom"` 41 | UserInfo UserInfo `json:"userInfo"` 42 | } 43 | 44 | type UserInfo struct { 45 | UserName string `json:"userName"` 46 | Alias string `json:"alias"` 47 | ConRemark string `json:"conRemark"` 48 | NickName string `json:"nickName"` 49 | Reserved1 string `json:"reserved1"` 50 | Reserved2 string `json:"reserved2"` 51 | LocalAvatar string `json:"localAvatar"` 52 | } 53 | 54 | type FileInfo struct { 55 | FileName string `json:"fileName"` 56 | FileSize string `json:"fileSize"` 57 | FilePath string `json:"filePath"` 58 | FileExt string `json:"fileExt"` 59 | } 60 | 61 | type EmojiInfo struct { 62 | Md5 string `json:"md5"` 63 | CDNUrl string `json:"cdnUrl"` 64 | W int64 `json:"w"` 65 | H int64 `json:"h"` 66 | } 67 | -------------------------------------------------------------------------------- /db/wcdb.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type WCDB struct { 9 | enmicromsg *EnMicroMsg 10 | wxfileindex *WxFileIndex 11 | } 12 | 13 | func InitWCDB(basePath string) *WCDB { 14 | wcdb := &WCDB{} 15 | wcdb.enmicromsg = OpenEnMicroMsg(basePath + "/EnMicroMsg_plain.db") 16 | wcdb.wxfileindex = OpenWxFileIndex(basePath + "/WxFileIndex_plain.db") 17 | return wcdb 18 | } 19 | 20 | func (wcdb WCDB) ChatList(pageIndex int, pageSize int, all bool, name string) *ChatList { 21 | return wcdb.enmicromsg.ChatList(pageIndex, pageSize, all, name) 22 | } 23 | 24 | func (wcdb WCDB) ChatDetailList(talker string, pageIndex int, pageSize int) *ChatDetailList { 25 | result := wcdb.enmicromsg.ChatDetailList(talker, pageIndex, pageSize) 26 | detailList := make([]ChatDetailListRow, 0) 27 | isChatRoomFlag := false 28 | if len(strings.Split(talker, "@")) == 2 { 29 | isChatRoomFlag = strings.Split(talker, "@")[1] == "chatroom" 30 | } 31 | for _, v := range result.Rows { 32 | chatDetailListRow := wcdb.getMediaPath(v) 33 | chatDetailListRow.IsChatRoom = isChatRoomFlag 34 | username := v.Talker 35 | if v.Type != 268445456 && v.Type != 10000 { 36 | if isChatRoomFlag && v.IsSend == 0 { 37 | username = strings.Split(v.Content, ":")[0] 38 | chatDetailListRow.Content = v.Content[len(username)+2:] 39 | } 40 | 41 | if v.IsSend == 0 { 42 | chatDetailListRow.UserInfo = wcdb.enmicromsg.GetUserInfo(username) 43 | } else { 44 | chatDetailListRow.UserInfo = wcdb.enmicromsg.GetMyInfo() 45 | } 46 | } 47 | detailList = append(detailList, chatDetailListRow) 48 | } 49 | result.Rows = detailList 50 | return result 51 | } 52 | 53 | func (wcdb WCDB) GetUserInfo(username string) UserInfo { 54 | return wcdb.enmicromsg.GetUserInfo(username) 55 | } 56 | 57 | func (wcdb WCDB) GetMyInfo() UserInfo { 58 | return wcdb.enmicromsg.GetMyInfo() 59 | } 60 | 61 | func (wcdb WCDB) GetImgPath(msgId string) string { 62 | return wcdb.wxfileindex.GetImgPath(msgId) 63 | } 64 | 65 | func (wcdb WCDB) GetVideoPath(msgId string) string { 66 | return wcdb.wxfileindex.GetVideoPath(msgId) 67 | } 68 | 69 | func (wcdb WCDB) GetVoicePath(msgId string) string { 70 | return wcdb.wxfileindex.GetVoicePath(msgId) 71 | } 72 | 73 | // func (wcdb WCDB) GetFilePath(msgId string) string { 74 | // return wcdb.wxfileindex.GetFilePath(msgId) 75 | // } 76 | 77 | func (wcdb WCDB) getMediaPath(chat ChatDetailListRow) ChatDetailListRow { 78 | switch chat.Type { 79 | case 3: 80 | // 图片 81 | chat.MediaPath = wcdb.enmicromsg.formatImagePath(chat.ImgPath) 82 | chat.MediaBCKPath = wcdb.enmicromsg.formatImageBCKPath(chat) 83 | chat.MediaSourcePath = wcdb.wxfileindex.GetImgPath(chat.MsgId) 84 | case 34: 85 | // 语音 86 | chat.MediaPath = wcdb.enmicromsg.formatVoicePath(chat.ImgPath) 87 | case 43: 88 | // 视频 89 | chat.MediaPath = wcdb.enmicromsg.formatVideoPath(chat.ImgPath) 90 | case 1090519089: 91 | fileInfo := FileInfo{} 92 | filepath, fileSize := wcdb.wxfileindex.GetFilePath(chat.MsgId) 93 | fileInfo.FilePath = filepath 94 | fileInfo.FileSize = formatFileSize(fileSize) 95 | p := strings.Split(filepath, "/") 96 | if len(p) > 1 { 97 | fileName := p[len(p)-1] 98 | fileInfo.FileName = fileName 99 | fext := strings.Split(fileName, ".") 100 | if len(fext) > 1 { 101 | fileInfo.FileExt = fext[len(fext)-1] 102 | } 103 | } 104 | chat.FileInfo = fileInfo 105 | default: 106 | break 107 | } 108 | return chat 109 | } 110 | 111 | func formatFileSize(fileSize int64) (size string) { 112 | if fileSize < 1024 { 113 | //return strconv.FormatInt(fileSize, 10) + "B" 114 | return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1)) 115 | } else if fileSize < (1024 * 1024) { 116 | return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024)) 117 | } else if fileSize < (1024 * 1024 * 1024) { 118 | return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024)) 119 | } else if fileSize < (1024 * 1024 * 1024 * 1024) { 120 | return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024)) 121 | } else if fileSize < (1024 * 1024 * 1024 * 1024 * 1024) { 122 | return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024)) 123 | } else { //if fileSize < (1024 * 1024 * 1024 * 1024 * 1024 * 1024) 124 | return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024)) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /db/wxfileindex.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | type WxFileIndex struct { 11 | DBPath string 12 | db *sql.DB 13 | tableName string 14 | } 15 | 16 | func OpenWxFileIndex(dbPath string) *WxFileIndex { 17 | db, err := sql.Open("sqlite3", dbPath) 18 | if err != nil { 19 | log.Printf("未查询到 WxFileIndex.db 文件,%s", err) 20 | } 21 | // 查询表名 22 | var tableName string 23 | querySql := "SELECT name _id FROM sqlite_master WHERE type ='table' limit 1" 24 | err = db.QueryRow(querySql).Scan(&tableName) 25 | if err != nil { 26 | log.Printf("未查询到图片索引表名,%s", err) 27 | // log.Fatal(err) 28 | } else { 29 | log.Printf("文件索引表名: %s", tableName) 30 | } 31 | return &WxFileIndex{dbPath, db, tableName} 32 | } 33 | 34 | func (wf *WxFileIndex) Close() { 35 | wf.db.Close() 36 | } 37 | 38 | func (wf WxFileIndex) GetImgPath(msgId string) string { 39 | var path string 40 | querySql := fmt.Sprintf("select path from %s WHERE msgId=%s and msgSubType=20", wf.tableName, msgId) 41 | err := wf.db.QueryRow(querySql).Scan(&path) 42 | if err != nil { 43 | log.Printf("未查询到图片,%s", err) 44 | return "" 45 | } else { 46 | return MediaPathPrefix + strings.Join(strings.SplitAfter(path, "/")[1:], "") 47 | } 48 | 49 | } 50 | 51 | func (wf WxFileIndex) GetVideoPath(msgId string) string { 52 | var path string 53 | querySql := fmt.Sprintf("select path from %s WHERE msgId=%s and msgSubType=1", wf.tableName, msgId) 54 | err := wf.db.QueryRow(querySql).Scan(&path) 55 | if err != nil { 56 | log.Printf("未查询到视频,%s", err) 57 | return "" 58 | } else { 59 | return MediaPathPrefix + strings.Join(strings.SplitAfter(path, "/")[1:], "") 60 | } 61 | } 62 | 63 | func (wf WxFileIndex) GetVoicePath(msgId string) string { 64 | var path string 65 | querySql := fmt.Sprintf("select path from %s WHERE msgId=%s", wf.tableName, msgId) 66 | err := wf.db.QueryRow(querySql).Scan(&path) 67 | if err != nil { 68 | log.Printf("未查询到语音,%s", err) 69 | return "" 70 | } else { 71 | return MediaPathPrefix + strings.Join(strings.SplitAfter(path, "/")[1:], "") 72 | } 73 | } 74 | 75 | func (wf WxFileIndex) GetFilePath(msgId string) (path string, size int64) { 76 | querySql := fmt.Sprintf("select path,size from %s WHERE msgId=%s", wf.tableName, msgId) 77 | err := wf.db.QueryRow(querySql).Scan(&path, &size) 78 | if err != nil { 79 | log.Printf("未查询到文件,%s", err) 80 | return "", 0 81 | } else { 82 | return MediaPathPrefix + path, size 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /dockerfile/abe.dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.15-slim 2 | 3 | WORKDIR /abe 4 | RUN apt-get update \ 5 | && apt-get install -y wget \ 6 | && wget https://github.com/nelenkov/android-backup-extractor/releases/download/master-20220609062817-33a2f6c/abe.jar 7 | 8 | CMD [ "/bin/bash" ] -------------------------------------------------------------------------------- /dockerfile/silkV3-decoder.dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.10 2 | 3 | LABEL author="greycode" 4 | LABEL version="1.0.0" 5 | LABEL opensource="https://github.com/kn007/silk-v3-decoder" 6 | LABEL desc="silkV3 decoder,\ 7 | Please mount the folder address you want to decode to the /media directory of docker" 8 | LABEL silkv3-decoder="#!/bin/bash\ 9 | function lm_traverse_dir(){\ 10 | for file in `ls $1` \ 11 | do\ 12 | if [ -d $1"/"$file ] \ 13 | then\ 14 | lm_traverse_dir $1"/"$file $2\ 15 | else \ 16 | effect_name=$1"/"$file \ 17 | echo $effect_name \ 18 | sh converter.sh $effect_name $2\ 19 | fi\ 20 | done\ 21 | } \ 22 | lm_traverse_dir $1 $2\ 23 | " 24 | 25 | COPY silk-v3-decoder /silk-v3-decoder 26 | ENV PATH="/silk-v3-decoder:${PATH}" 27 | 28 | RUN apt-get update && apt-get install -y gcc g++ make ffmpeg 29 | RUN cd /silk-v3-decoder/silk && make && make decoder 30 | 31 | WORKDIR /silk-v3-decoder 32 | 33 | 34 | CMD ["silkv3-decoder","/media","mp3"] 35 | 36 | -------------------------------------------------------------------------------- /dockerfile/wcdb-sqlcipher.dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.16.0 2 | 3 | LABEL author="greycode" 4 | LABEL version="1.0.0" 5 | LABEL desc="wcdb from wechat,this is decipher sqlcipher for wcdb!" 6 | LABEL wcdb-sqlcipher-gits="https://gist.github.com/greycodee/255e5adcc06f698cdb1ded6166d5607a" 7 | 8 | COPY wcdb-sqlcipher /usr/local/bin/wcdb-sqlcipher 9 | 10 | RUN apk add gcc g++ make libffi-dev openssl-dev tcl git 11 | RUN git clone https://github.com/sqlcipher/sqlcipher.git \ 12 | && cd sqlcipher \ 13 | && ./configure --enable-tempstore=yes CFLAGS="-DSQLITE_HAS_CODEC" LDFLAGS="-lcrypto" \ 14 | && make \ 15 | && make install 16 | 17 | WORKDIR /wcdb 18 | ENTRYPOINT ["/usr/local/bin/wcdb-sqlcipher"] 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/greycodee/wechat-backup 2 | 3 | go 1.17 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.1 7 | github.com/mattn/go-sqlite3 v1.14.13 8 | ) 9 | 10 | require ( 11 | github.com/bytedance/sonic v1.9.1 // indirect 12 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 13 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 14 | github.com/gin-contrib/sse v0.1.0 // indirect 15 | github.com/go-playground/locales v0.14.1 // indirect 16 | github.com/go-playground/universal-translator v0.18.1 // indirect 17 | github.com/go-playground/validator/v10 v10.14.0 // indirect 18 | github.com/goccy/go-json v0.10.2 // indirect 19 | github.com/json-iterator/go v1.1.12 // indirect 20 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 21 | github.com/leodido/go-urn v1.2.4 // indirect 22 | github.com/mattn/go-isatty v0.0.19 // indirect 23 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 24 | github.com/modern-go/reflect2 v1.0.2 // indirect 25 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 26 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 27 | github.com/ugorji/go/codec v1.2.11 // indirect 28 | golang.org/x/arch v0.3.0 // indirect 29 | golang.org/x/crypto v0.21.0 // indirect 30 | golang.org/x/net v0.23.0 // indirect 31 | golang.org/x/sys v0.18.0 // indirect 32 | golang.org/x/text v0.14.0 // indirect 33 | google.golang.org/protobuf v1.33.0 // indirect 34 | gopkg.in/yaml.v3 v3.0.1 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 3 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 11 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 12 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 13 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 14 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 15 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 16 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 17 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 18 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 19 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 20 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 21 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 22 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 23 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 24 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 25 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 26 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 27 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 28 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 30 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 31 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 32 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 33 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 34 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 35 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 36 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 37 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 38 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 39 | github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I= 40 | github.com/mattn/go-sqlite3 v1.14.13/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= 41 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 44 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 45 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 46 | github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= 47 | github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= 48 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 49 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 51 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 52 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 53 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 54 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 55 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 56 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 57 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 58 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 59 | github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= 60 | github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 61 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 62 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 63 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 64 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 65 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 66 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 67 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 68 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 69 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 70 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 71 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 72 | golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= 73 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 74 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= 75 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 76 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 77 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 78 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 79 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 80 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 81 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 82 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 83 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 84 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 85 | golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= 86 | golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 87 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 88 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 89 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 90 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 91 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 92 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 93 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 94 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 96 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 97 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 98 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 99 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 100 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= 101 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 102 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 103 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 104 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 105 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 106 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 107 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 108 | golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 109 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 110 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 111 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 112 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 113 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 114 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 115 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 116 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 117 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 118 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 119 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 120 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 121 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 122 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 123 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 124 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 125 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 126 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 127 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 128 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 129 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 130 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 131 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 132 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 133 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 134 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 135 | -------------------------------------------------------------------------------- /hook.py: -------------------------------------------------------------------------------- 1 | import frida 2 | import sys 3 | 4 | jscode = """ 5 | Java.perform(function(){ 6 | var utils = Java.use("com.tencent.wcdb.database.SQLiteDatabase"); // 类的加载路径 7 | 8 | utils.openDatabase.overload('java.lang.String', '[B', 'com.tencent.wcdb.database.SQLiteCipherSpec', 'com.tencent.wcdb.database.SQLiteDatabase$CursorFactory', 'int', 'com.tencent.wcdb.DatabaseErrorHandler', 'int').implementation = function(a,b,c,d,e,f,g){ 9 | console.log("Hook start......"); 10 | var JavaString = Java.use("java.lang.String"); 11 | var database = this.openDatabase(a,b,c,d,e,f,g); 12 | send(a); 13 | console.log(JavaString.$new(b)); 14 | send("Hook ending......"); 15 | return database; 16 | }; 17 | 18 | }); 19 | """ 20 | 21 | 22 | def on_message(message,data): 23 | if message["type"] == "send": 24 | print("[*] {0}".format(message["payload"])) 25 | else: 26 | print(message) 27 | 28 | process = frida.get_remote_device() 29 | pid = process.spawn(['com.tencent.mm']) 30 | session = process.attach(pid) 31 | script = session.create_script(jscode) 32 | script.on('message',on_message) 33 | script.load() 34 | process.resume(pid) 35 | sys.stdin.read() -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 |