├── .editorconfig ├── .github └── workflows │ └── release.yml ├── .gitignore ├── decrypter ├── decrypt.go ├── format_from.go └── get_wxid.go ├── go.mod ├── go.sum ├── main.go ├── readme.md └── unpacker └── unpack.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = tab 8 | insert_final_newline = true 9 | max_line_length = 120 10 | tab_width = 4 11 | trim_trailing_whitespace = true 12 | 13 | [*.yml] 14 | indent_size = 2 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | push: 8 | tags: 9 | - 'v*' 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | - name: Setup Go 18 | uses: actions/setup-go@v4 19 | with: 20 | go-version-file: go.mod 21 | - name: Build 22 | run: | 23 | GOOS=windows GOARCH=amd64 go build 24 | - name: Release 25 | uses: softprops/action-gh-release@v1 26 | with: 27 | files: | 28 | wxapkg.exe 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | wxapkg.iml 3 | wxapkg.exe 4 | -------------------------------------------------------------------------------- /decrypter/decrypt.go: -------------------------------------------------------------------------------- 1 | package decrypter 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/sha1" 7 | "fmt" 8 | "golang.org/x/crypto/pbkdf2" 9 | "log" 10 | "os" 11 | ) 12 | 13 | type DecryptOptions struct { 14 | Wxid string 15 | Iv string 16 | Salt string 17 | WxapkgPath string 18 | DecWxapkgPath string 19 | } 20 | 21 | func Decrypt(options *DecryptOptions) error { 22 | dataByte, err := os.ReadFile(options.WxapkgPath) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | dk := pbkdf2.Key([]byte(options.Wxid), []byte(options.Salt), 1000, 32, sha1.New) 28 | block, _ := aes.NewCipher(dk) 29 | blockMode := cipher.NewCBCDecrypter(block, []byte(options.Iv)) 30 | originData := make([]byte, 1024) 31 | blockMode.CryptBlocks(originData, dataByte[6:1024+6]) 32 | 33 | afData := make([]byte, len(dataByte)-1024-6) 34 | var xorKey = byte(0x66) 35 | if len(options.Wxid) >= 2 { 36 | xorKey = options.Wxid[len(options.Wxid)-2] 37 | } 38 | for i, b := range dataByte[1024+6:] { 39 | afData[i] = b ^ xorKey 40 | } 41 | 42 | originData = append(originData[:1023], afData...) 43 | 44 | err = os.WriteFile(options.DecWxapkgPath, originData, 0666) 45 | if err != nil { 46 | return fmt.Errorf("write file error: %v", err) 47 | } 48 | 49 | return nil 50 | } 51 | 52 | const DefaultDecryptTo = "_decrypt" 53 | 54 | func DefaultDecrypt(from string, wxid string) error { 55 | return Decrypt(&DecryptOptions{ 56 | Wxid: wxid, 57 | Iv: "the iv: 16 bytes", 58 | Salt: "saltiest", 59 | WxapkgPath: from, 60 | DecWxapkgPath: from + DefaultDecryptTo, 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /decrypter/format_from.go: -------------------------------------------------------------------------------- 1 | package decrypter 2 | 3 | import "strings" 4 | 5 | func FormatFrom(from string) string { 6 | return strings.ReplaceAll(from, "\\", "/") 7 | } 8 | -------------------------------------------------------------------------------- /decrypter/get_wxid.go: -------------------------------------------------------------------------------- 1 | package decrypter 2 | 3 | import "strings" 4 | 5 | func GetWxid(from string) (string, bool) { 6 | parts := strings.Split(from, "/") 7 | 8 | var wxid string 9 | wxidIndex := len(parts) - 3 10 | if wxidIndex >= 0 { 11 | wxid = parts[wxidIndex] 12 | } 13 | 14 | return wxid, strings.HasPrefix(wxid, "wx") 15 | } 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zhuweiyou/wxapkg 2 | 3 | go 1.20 4 | 5 | require golang.org/x/crypto v0.11.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= 2 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= 3 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/zhuweiyou/wxapkg/decrypter" 6 | "github.com/zhuweiyou/wxapkg/unpacker" 7 | "os" 8 | ) 9 | 10 | func main() { 11 | defer func() { 12 | os.Stdin.Read(make([]byte, 1)) 13 | }() 14 | 15 | if len(os.Args) < 2 { 16 | fmt.Println("请查看 readme.md 使用说明") 17 | return 18 | } 19 | 20 | from := decrypter.FormatFrom(os.Args[1]) 21 | fmt.Println("from", from) 22 | 23 | wxid, needDecrypt := decrypter.GetWxid(from) 24 | if needDecrypt { 25 | fmt.Println("wxid", wxid) 26 | err := decrypter.DefaultDecrypt(from, wxid) 27 | if err != nil { 28 | fmt.Println(err) 29 | return 30 | } 31 | from += decrypter.DefaultDecryptTo 32 | } 33 | 34 | err := unpacker.Unpack(from) 35 | if err != nil { 36 | fmt.Println(err) 37 | return 38 | } 39 | 40 | fmt.Println("success") 41 | } 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # wxapkg 2 | 3 | PC 微信小程序一键解密和解包 (算法来源于网络) 4 | 5 | ## 使用方法 6 | 7 | 1. 在 [release](https://github.com/zhuweiyou/wxapkg/releases/) 下载 `wxapkg.exe` 文件 8 | 9 | 1. PC 微信 -> 设置 -> 文件管理 -> 打开文件夹 -> 找到小程序文件 `/WeChat Files/Applet/{wxid}/{n}/__APP__.wxapkg` 10 | 11 | 1. 鼠标拖动 `__APP__.wxapkg` 文件到 `wxapkg.exe` 即可完成解包, 将在当前目录输出解包后的文件夹 12 | 13 | ![演示GIF](https://github.com/zhuweiyou/wxapkg/assets/8413791/07a5cfa5-00c9-47b5-aaa3-ee42b878495f) 14 | -------------------------------------------------------------------------------- /unpacker/unpack.go: -------------------------------------------------------------------------------- 1 | package unpacker 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | ) 9 | 10 | type UnpackFile struct { 11 | NameLen uint32 12 | Name string 13 | Offset uint32 14 | Size uint32 15 | } 16 | 17 | const DefaultUnpackTo = "_unpack" 18 | 19 | func Unpack(from string) error { 20 | f, err := os.Open(from) 21 | if err != nil { 22 | return fmt.Errorf("error opening file: %v", err) 23 | } 24 | defer f.Close() 25 | 26 | root := filepath.Dir(from) 27 | name := filepath.Base(from) + DefaultUnpackTo 28 | 29 | var firstMark byte 30 | if err := binary.Read(f, binary.BigEndian, &firstMark); err != nil { 31 | return fmt.Errorf("error reading first header mark: %v", err) 32 | } 33 | fmt.Println("first header mark =", firstMark) 34 | 35 | var info1 uint32 36 | if err := binary.Read(f, binary.BigEndian, &info1); err != nil { 37 | return fmt.Errorf("error reading info1: %v", err) 38 | } 39 | fmt.Println("info1 =", info1) 40 | 41 | var indexInfoLength uint32 42 | if err := binary.Read(f, binary.BigEndian, &indexInfoLength); err != nil { 43 | return fmt.Errorf("error reading indexInfoLength: %v", err) 44 | } 45 | fmt.Println("indexInfoLength =", indexInfoLength) 46 | 47 | var bodyInfoLength uint32 48 | if err := binary.Read(f, binary.BigEndian, &bodyInfoLength); err != nil { 49 | return fmt.Errorf("error reading bodyInfoLength: %v", err) 50 | } 51 | fmt.Println("bodyInfoLength =", bodyInfoLength) 52 | 53 | var lastMark byte 54 | if err := binary.Read(f, binary.BigEndian, &lastMark); err != nil { 55 | return fmt.Errorf("error reading last header mark: %v", err) 56 | } 57 | fmt.Println("last header mark =", lastMark) 58 | 59 | if firstMark != 0xBE || lastMark != 0xED { 60 | return fmt.Errorf("it's not a wxapkg file") 61 | } 62 | 63 | var fileCount uint32 64 | if err := binary.Read(f, binary.BigEndian, &fileCount); err != nil { 65 | return fmt.Errorf("error reading fileCount: %v", err) 66 | } 67 | fmt.Println("fileCount =", fileCount) 68 | 69 | fileList := make([]UnpackFile, 0, fileCount) 70 | 71 | for i := uint32(0); i < fileCount; i++ { 72 | var data UnpackFile 73 | if err := binary.Read(f, binary.BigEndian, &data.NameLen); err != nil { 74 | return fmt.Errorf("error reading NameLen: %v", err) 75 | } 76 | 77 | nameBytes := make([]byte, data.NameLen) 78 | if _, err := f.Read(nameBytes); err != nil { 79 | return fmt.Errorf("error reading Name: %v", err) 80 | } 81 | data.Name = string(nameBytes) 82 | 83 | if err := binary.Read(f, binary.BigEndian, &data.Offset); err != nil { 84 | return fmt.Errorf("error reading Offset: %v", err) 85 | } 86 | 87 | if err := binary.Read(f, binary.BigEndian, &data.Size); err != nil { 88 | return fmt.Errorf("error reading Size: %v", err) 89 | } 90 | 91 | fmt.Println("readFile =", data.Name, "at Offset =", data.Offset) 92 | 93 | fileList = append(fileList, data) 94 | } 95 | 96 | for _, d := range fileList { 97 | d.Name = "/" + name + d.Name 98 | path := filepath.Join(root, filepath.Dir(d.Name)) 99 | 100 | if err := os.MkdirAll(path, os.ModePerm); err != nil { 101 | return fmt.Errorf("error creating directory: %v", err) 102 | } 103 | 104 | w, err := os.Create(filepath.Join(root, d.Name)) 105 | if err != nil { 106 | return fmt.Errorf("error creating file: %v", err) 107 | } 108 | 109 | _, err = f.Seek(int64(d.Offset), 0) 110 | if err != nil { 111 | w.Close() 112 | return fmt.Errorf("error seeking to Offset: %v", err) 113 | } 114 | 115 | buf := make([]byte, d.Size) 116 | _, err = f.Read(buf) 117 | if err != nil { 118 | w.Close() 119 | return fmt.Errorf("Error reading from file: %v", err) 120 | } 121 | 122 | _, err = w.Write(buf) 123 | if err != nil { 124 | return fmt.Errorf("error writing to file: %v", err) 125 | } 126 | 127 | w.Close() 128 | fmt.Println("writeFile =", filepath.Join(root, d.Name)) 129 | } 130 | 131 | return nil 132 | } 133 | --------------------------------------------------------------------------------