Skip to content

Commit

Permalink
feat: 新增重新打包wxapkg文件功能
Browse files Browse the repository at this point in the history
  • Loading branch information
Ackites committed Aug 6, 2024
1 parent 5c09c84 commit 4c516e3
Show file tree
Hide file tree
Showing 9 changed files with 298 additions and 17 deletions.
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
- [x] Wxml代码还原
- [x] Wxss代码还原
- [x] Hook小程序,动态调试,开启小程序F12
- [x] 重新打包小程序,可破解小程序
- [x] 监视将要打包的文件夹,并自动打包
- [ ] 支持小游戏
- [ ] 敏感数据导出

Expand Down Expand Up @@ -87,6 +89,24 @@

<img src="./images/img9.jpg" width="60%">

### 重新打包运行,可破解小程序

```shell
<本程序> -repack=<输入目录> [-out=<输出目录或文件>] [-watch]
```

<img src="./images/img10.png" width="30%">

#### 效果示例

**修改前**

<img src="./images/img11.png" width="30%">

**修改后**

<img src="./images/img12.png" width="30%">

## 安装

- 下载最新版本的[release](https://github.com/Ackites/KillWxapkg/releases)
Expand All @@ -108,7 +128,7 @@
## 用法

> -id=<输入AppID> -in=<输入文件1,输入文件2> 或 -in=<输入目录> -out=<输出目录>
> [-ext=<文件后缀>] [-restore] [-pretty] [-noClean] [-help] [-hook] [-save]
> [-ext=<文件后缀>] [-restore] [-pretty] [-noClean] [-help] [-hook] [-save] [-repack=<输入目录>] [-watch]
### 参数说明
- `-id string`
Expand All @@ -134,9 +154,15 @@
- 是否清理反编译的中间文件,默认清理
- `-hook`
- 是否Hook小程序,动态调试,开启F12,默认不Hook
- 注意:目前仅支持Windows
- **注意:目前仅支持Windows**
- `-save`
- 是否保存解密后的文件,默认不保存
- `-repack string`
- 重新打包目录路径
- 例:-repack="C:\Users\mi\Desktop\Applet\64"
- **注意:目前仅支持一次打包一个文件,同时仅支持未被解析的源文件(未使用-restore)**
- `-watch`
- 是否监听将要打包的文件夹,并自动打包,默认不监视
- `-help`
- 显示帮助信息

Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ go 1.22
require (
github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c
github.com/dop251/goja v0.0.0-20240731150404-c665f0b58f6e
github.com/fsnotify/fsnotify v1.7.0
github.com/tdewolff/parse/v2 v2.7.15
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4
golang.org/x/crypto v0.25.0
golang.org/x/net v0.27.0
golang.org/x/text v0.16.0
)

require (
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sys v0.22.0 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dop251/goja v0.0.0-20240731150404-c665f0b58f6e h1:jhfevGje1Gw4uDNv30Kj+tl/SpHb4La9InZOnwMrNUs=
github.com/dop251/goja v0.0.0-20240731150404-c665f0b58f6e/go.mod h1:o31y53rb/qiIAONF7w3FHJZRqqP3fzHUr1HqanthByw=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
Expand All @@ -18,6 +20,8 @@ golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
Expand Down
Binary file added images/img10.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/img11.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/img12.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
237 changes: 237 additions & 0 deletions internal/pack/pack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package pack

import (
"encoding/binary"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"

"github.com/fsnotify/fsnotify"
)

func Repack(path string, watch bool, outputDir string) {
// 过滤空白字符
path = strings.TrimSpace(path)
outputDir = strings.TrimSpace(outputDir)

// 如果是目录,则打包目录
if fileInfo, err := os.Stat(path); err != nil || !fileInfo.IsDir() {
log.Printf("错误: %s 不是一个有效的目录\n", path)
return
}

// 打包目录
err := packWxapkg(path, outputDir)
if err != nil {
log.Printf("错误: %v\n", err)
return
}

if watch {
watchDir(path, outputDir)
}

return
}

type WxapkgFile struct {
NameLen uint32
Name string
Offset uint32
Size uint32
}

// 打包文件到 wxapkg 格式
func packWxapkg(inputDir string, outputDir string) error {
var files []WxapkgFile
var totalSize uint32

// 检查 outputDir 是否存在及其类型
outputInfo, err := os.Stat(outputDir)
outputFile := ""
if err != nil {
if os.IsNotExist(err) {
// 如果 outputDir 不存在,假设它是一个文件路径或一个需要创建的目录
if filepath.Ext(outputDir) == "" {
// 创建目录
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("无法创建输出目录: %w", err)
}
outputFile = filepath.Join(outputDir, "output.wxapkg")
} else {
outputFile = outputDir
}
} else {
return fmt.Errorf("无法访问输出目录: %w", err)
}
} else {
if outputInfo.IsDir() {
outputFile = filepath.Join(outputDir, "output.wxapkg")
} else {
outputFile = outputDir
}
}

// 计算文件列表
err = filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

// 排除目录
if info.IsDir() {
return nil
}

// 排除 .wxapkg 文件
if filepath.Ext(path) == ".wxapkg" {
return nil
}

relPath, _ := filepath.Rel(inputDir, path)
relPath = "/" + filepath.ToSlash(relPath) // 确保路径以 '/' 开头,并且使用 Unix 风格的路径分隔符
size := uint32(info.Size())

files = append(files, WxapkgFile{
NameLen: uint32(len(relPath)),
Name: relPath,
Offset: totalSize, // 预计算文件偏移量
Size: size,
})

totalSize += size
return nil
})
if err != nil {
return fmt.Errorf("计算文件列表失败: %w", err)
}

// 创建输出文件
outFile, err := os.Create(outputFile)
if err != nil {
return fmt.Errorf("创建输出文件失败: %w", err)
}
defer func(outFile *os.File) {
err := outFile.Close()
if err != nil {
log.Printf("关闭输出文件失败: %v\n", err)
}
}(outFile)

// 写入文件头
if err := binary.Write(outFile, binary.BigEndian, byte(0xBE)); err != nil {
return fmt.Errorf("写入文件头标记失败: %w", err)
}

info1 := uint32(0) // 示例值
if err := binary.Write(outFile, binary.BigEndian, info1); err != nil {
return fmt.Errorf("写入 info1 失败: %w", err)
}

// 计算索引段长度,包含每个文件的元数据长度和文件名长度
var indexInfoLength uint32
for _, file := range files {
indexInfoLength += 4 + uint32(len(file.Name)) + 4 + 4 // NameLen + Name + Offset + Size
}

if err := binary.Write(outFile, binary.BigEndian, indexInfoLength); err != nil {
return fmt.Errorf("写入索引段长度失败: %w", err)
}

bodyInfoLength := totalSize
if err := binary.Write(outFile, binary.BigEndian, bodyInfoLength); err != nil {
return fmt.Errorf("写入数据段长度失败: %w", err)
}

if err := binary.Write(outFile, binary.BigEndian, byte(0xED)); err != nil {
return fmt.Errorf("写入文件尾标记失败: %w", err)
}

// 写入文件数量
fileCount := uint32(len(files))
if err := binary.Write(outFile, binary.BigEndian, fileCount); err != nil {
return fmt.Errorf("写入文件数量失败: %w", err)
}

// 写入索引段
for _, file := range files {
if err := binary.Write(outFile, binary.BigEndian, file.NameLen); err != nil {
return fmt.Errorf("写入文件名长度失败: %w", err)
}
if _, err := outFile.Write([]byte(file.Name)); err != nil {
return fmt.Errorf("写入文件名失败: %w", err)
}
// 加上 18 字节文件头长度和索引段长度
if err := binary.Write(outFile, binary.BigEndian, file.Offset+indexInfoLength+18); err != nil {
return fmt.Errorf("写入文件偏移量失败: %w", err)
}
if err := binary.Write(outFile, binary.BigEndian, file.Size); err != nil {
return fmt.Errorf("写入文件大小失败: %w", err)
}
}

// 写入数据段
for _, file := range files {
func() {
f, err := os.Open(filepath.Join(inputDir, file.Name[1:])) // 去掉路径开头的 '/' 以正确打开文件
if err != nil {
log.Printf("打开文件失败: %v\n", err)
return
}
defer func(f *os.File) {
err := f.Close()
if err != nil {
log.Printf("关闭文件失败: %v\n", err)
}
}(f)

if _, err = io.Copy(outFile, f); err != nil {
log.Printf("写入文件内容失败: %v\n", err)
}
}()
}

return nil
}

func watchDir(inputDir string, outputDir string) {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Println("ERROR: ", err)
return
}
defer func(watcher *fsnotify.Watcher) {
err := watcher.Close()
if err != nil {
log.Println("ERROR: ", err)
}
}(watcher)

done := make(chan bool)
go func() {
for {
select {
case event := <-watcher.Events:
if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Remove == fsnotify.Remove {
log.Println("检测到文件变化: ", event.Name)
if err := packWxapkg(inputDir, outputDir); err != nil {
log.Println("打包失败: ", err)
} else {
log.Println("打包成功")
}
}
case err := <-watcher.Errors:
log.Println("ERROR: ", err)
}
}
}()

err = watcher.Add(inputDir)
if err != nil {
log.Println("ERROR: ", err)
}
<-done
}
24 changes: 12 additions & 12 deletions internal/unpack/unpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,18 +154,18 @@ func UnpackWxapkg(data []byte, outputDir string) ([]string, error) {
return nil, <-errChan
}

const configJSON = `{
"description": "See https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
"setting": {
"urlCheck": false
}
}`

// 保存 project.config.json
configFile := filepath.Join(outputDir, "project.private.config.json")
if err := os.WriteFile(configFile, []byte(configJSON), 0755); err != nil {
return nil, fmt.Errorf("保存文件 %s 失败: %w", configFile, err)
}
//const configJSON = `{
// "description": "See https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
// "setting": {
// "urlCheck": false
// }
// }`

// 保存 project.private.config.json
//configFile := filepath.Join(outputDir, "project.private.config.json")
//if err := os.WriteFile(configFile, []byte(configJSON), 0755); err != nil {
// return nil, fmt.Errorf("保存文件 %s 失败: %w", configFile, err)
//}

return filelistNames, nil
}
Expand Down
Loading

0 comments on commit 4c516e3

Please sign in to comment.