Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# tmp
# CrPatch v4.6.0
Test Repository
322 changes: 322 additions & 0 deletions crpatch_4.6.0.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,322 @@
//go:build ignore

package main

import (
"bytes"
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"log"
"math"
"os"
"runtime"
"strings"
"time"
"unsafe"
)

func patch() {
var name = `cloudreve`
if len(os.Args) == 3 {
name = os.Args[2]
} else if runtime.GOOS == `windows` {
name += `.exe`
}

f, err := os.OpenFile(name, os.O_RDWR, 0666)
if err != nil {
log.Fatalln(`无法加载程序文件:`, err)
}
defer f.Close()

offset, err := FindOffset(f, StringToBytes(`LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF0UXRuZmpOTGxuVnFTRG0rYXJNSApiaTFqQ2Zjam9WdzlTMnA5c2FhcEI3M1Z3YmdIT2FNT0lvbS9TMFNwQlpIYVZuamt3MDkzZHMraFhSam9ZRGFKCk1wLzlDMzlSWVJUL0svVHozYWJudFRXa2NrNk9EL3BhbGhqZWh4YXVqajJHdlprS2tPdW1lUXNxTzNjT3ZjcUEKb3JOd1ZCS1FtRGNkOE1nNlVIMHBnVjU1OEUwS1ZMazYxZzlWTlR4S09NS2Jvc1FXZ2xrc1ZvMjBGeUYzMko3WApzcExwcGR4WWF1U2NaYTk4NVNyKzd6QzV2NmNvTktZeDcrTjA4TkxQSk5PdW0vSko3T0RQSFJxVFUvd1hzSjd0Cm9ET01WSk9ncHF2UXVzV1ljbTdjUWU4Z25YYTlna0d3M1pqV1AwMm1zWXd6V3pjOHdFUFlaRk8wY2YwQXB6K3QKRXdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t`))
if err != nil {
log.Fatalln(`无法读取程序文件:`, err)
}
if offset == -1 {
log.Fatalln(`未找到数据偏移量,可能是已打补丁或程序更新`)
}
log.Println(`找到数据偏移量:`, offset)

_, err = f.WriteAt(StringToBytes(`LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE1azlxa3pqTkkwNlc1ckg1WjFRRgpROVA4T2ltVytxTDhJOW5EZ1AwYnRKUzZFVzh4NW5GTkg4OEorNjJ5VXAxeEI1SXdpTVB2Qzl5QS82ekp6OW5WCk1udENJMU14WkNoQnpwSHZTakFieklqb0prY3hqOTJUZnpuVFNTOFROenVJU2luUHhZRWQ1Z1RLUE1GbCtiQzMKZ0R1VHQxSFNJVlUvNkhVMTZjRzFGMEpBZlZNcDN4RmV4b0pBdGQ4eDBMa0VsUTdESEhHQkJ5Y1ZpUm5rTngzTwpmTjFRRFMva2pQOUo5R0RvZVhRWitXZjVrN3BMMVB1blhHUmNmUEc1YjZpdDdnNXhWUGQ2cWJlTmpkMW9vUXdHCm9WMVI2WXhieXpuRnNGOFczRFpQeXVYOS9vdlNoSXdzc0tHZmY1WDhPa3dNNUwvVUhuRjhNWHdIN2QxM2ZRR20KMHdJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t`), offset)
if err != nil {
log.Fatalln(`无法写入程序文件:`, err)
}
log.Println(`补丁写入成功!`)
}

func license() {
// 1. 序列化 LicenseContent 为 JSON(即 GCM 明文)
licenseContent := &LicenseContent{
RootDomains: []string{`cloudreve.org`},
Domains: []string{`plus.cloudreve.org`, `127.0.0.1`, `localhost`},
UserID: `2018110303550773058`,
ValidSince: time.Now(),
ExpireAt: time.Unix(math.MaxInt32, 0),
VolEnabledDomain: []string{`cloudreve.org`},
VolLicense: `eyJkb21haW5zIjpbImNsb3VkcmV2ZS5vcmciXSwic2lnbmVkX2F0IjoiMjAyNi0wMS0wNFQxOTowMTowOS40NjQxMDg2MzUrMDg6MDAifQ==`,
VolSign: `g8GD5F40cojyZaU6kMAjCHYBPmoI3RQ/45MyhruHRLXzCvLDLKgMZESgcm9QQRN8CxARReoBf3PwaayRQ0LzvMe0hWt5tAbZp3ilbca+09CUx3Sew7XADP0glXIzFS0ECigFTM8um5rgAnUgF2SNp5p5WDpcPTN/+nGVgpQ1eqJCqrOGzOmW7fSWa3ZkFzg5qIMQIDH6dkja8H4heG8mvPqYmMpjJ451aFJjnRDbNW7F3/lVVQbyE6nrb5jNRgxxgKp2D3uiq4KIohK6AheboEgPdnVdujwQJqj+bSSqOt0APMWOzDwMdch9zY+zt+8KkA1FVWyxhqRZPK+9sQkwOw==`,
}
if len(os.Args) == 3 {
licenseContent.Domains = append(strings.Split(os.Args[2], `,`), licenseContent.Domains...)
}
content, _ := json.Marshal(licenseContent)

// 2. AES-GCM 加密
aesKey, _ := HexDecode(StringToBytes(`93d384b2423f248457acd577f1e9dbc9576373da3272b49a424715f0829d5f89`))
aesCipher, _ := aes.NewCipher(aesKey)
gcm, _ := cipher.NewGCM(aesCipher)

nonce := RandomBytes(gcm.NonceSize())
ciphertext := gcm.Seal(nil, nonce, content, nil)
contentEnc := append(nonce, ciphertext...)

// 3. 计算 SHA1 摘要(与 loadLicense 中 crypto_sha1_Sum 对应)
sum := SumSHA1(content)

// 4. 使用 RSA 私钥对 SHA1 摘要签名(与 validateWirthRSA 逆向逻辑对应)
pb, _ := pem.Decode(StringToBytes("-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA5k9qkzjNI06W5rH5Z1QFQ9P8OimW+qL8I9nDgP0btJS6EW8x\n5nFNH88J+62yUp1xB5IwiMPvC9yA/6zJz9nVMntCI1MxZChBzpHvSjAbzIjoJkcx\nj92TfznTSS8TNzuISinPxYEd5gTKPMFl+bC3gDuTt1HSIVU/6HU16cG1F0JAfVMp\n3xFexoJAtd8x0LkElQ7DHHGBBycViRnkNx3OfN1QDS/kjP9J9GDoeXQZ+Wf5k7pL\n1PunXGRcfPG5b6it7g5xVPd6qbeNjd1ooQwGoV1R6YxbyznFsF8W3DZPyuX9/ovS\nhIwssKGff5X8OkwM5L/UHnF8MXwH7d13fQGm0wIDAQABAoIBABrjHJsRX4mGrEFE\nd8xjV7YCcojLwC+EhZEd5CAT7H8Zedt9MsMnaQyUnCLAtXfFn6N1oqrkzOyCkqDG\nqEzWrngVQbRkCvkZZg1apifwE96+APQvDY8xG/4XXx5FG6YwEgoudW4mU7o9xupu\n3ZvX0KRs0CLvr2tqy2o0/NVMYvrS7YN0VMt0j3DCK4wrY++nNRbV0G5rmIAxmarK\nGUv8wx8JD4M+bPw6LqiGhqtO9fxOeZuN+AwOsAndpn181haw0I3yDZviykqMwqRs\n7OEOK8UExT3D9BNEJVdBBgt0wxxSNknkJbULx0+8gNpGJRKT7nZ8rD8E4mBzD2NV\nBZuDTDECgYEA+VKdaLsCr5ygk2n+y/6Id8Knq/b0TpS67WK4HxcUmhJmOa/jl/1J\n3kthbAC/ZjqxyqHQyV+exnHxGwgwfSc5pvUogG2XmR/BRVoshQXaGQtd5JNQK002\nOD1GSzhhrXVYDik0tm3TGNrqWmNNxuHz7hLhktI+mLXG1xmG3/UkqSkCgYEA7Hpz\nGC02o3m6uDnxue/uNUo5nBt70+4vu7o/gSHXPu3SYWeBW8N/p5ihSmveppYcEist\nAteki8agFGorr1VJE2TRnMnIZzLug1Gm39wBN3Vj1LWWtAPPzbZWr5WvAah07AyI\nvm28PoMcMrcRDiO+1xoJQnqi7owWoXmdh0jtw5sCgYATi+iPV2oEfpEQPZUeRRxJ\n11bx4nCQ4JQvHDg3J8YTtjGRgDqlpdPuu5Yts3lN+HgL3NfTkc2O3Uv7xI6YoUSh\n1EKSnwTtmlG6sDEhVK4enIeF7g6w9mKz0gn6x7bQtIxsv8pEKyQLpM4OvA5S80/+\ncDiDHDyQGGA4aOXsFPoE0QKBgQDdVKJCn5bDHNzpLrqTOABw+h6/gG2+ZvFgOCrV\n1vGaIYdFyVWmqGzjVRSpDhsfOeHeE3EWdvS4KliLou9uNFmoADW8FJQwGthoKlUQ\nst6kpIeif68kn2iwdhfeUS+yUpPUlsqpyRVmQNp0kSkyXbxxoRnHkfbLZGyOB8Cr\n7VM0PwKBgG0RfIVLmcI2PslpZD0uVTVt3insBp/lbUQMbRGD5wCXzQNCJjOSICVG\nTeg2r6BsmLmRRwaK4Kfl2mk90lfF5PDQwoDmxzfj5XaAzjETaLpuTWf0v7azldT6\n2f/M9N+oH79iACS1JeO647coOsayLnpJsr7ks2DvQcy4cH/tw5dG\n-----END RSA PRIVATE KEY-----\n\n"))
priKey, _ := x509.ParsePKCS1PrivateKey(pb.Bytes)

sig, err := rsa.SignPKCS1v15(rand.Reader, priKey, crypto.SHA1, sum)
if err != nil {
log.Fatalln(`无法签名授权文件:`, err)
}

// 5. 组装 License 结构
license := &License{
Content: contentEnc,
Signature: sig,
}
// 6. JSON 序列化 License
data, _ := json.Marshal(license)
// 7. Base64 编码(与 loadLicense 开头的 DecodeString 对应)
enc := Base64Encode(base64.StdEncoding, data)

err = os.WriteFile(`license`, enc, 0666)
if err != nil {
log.Fatalln(`无法写入授权文件:`, err)
}
log.Println(`授权文件写入成功!`)
}

func valid() {
licenseTxt, err := os.ReadFile(`license`)
if err != nil {
log.Fatalln(`无法读取授权文件:`, err)
}

licenseTxt, _ = Base64Decode(base64.StdEncoding, licenseTxt)

var license *License
err = json.Unmarshal(licenseTxt, &license)
if err != nil {
log.Fatalln(`无法解析授权文件:`, err)
}

aesKey, _ := HexDecode(StringToBytes(`93d384b2423f248457acd577f1e9dbc9576373da3272b49a424715f0829d5f89`))
aesCipher, _ := aes.NewCipher(aesKey)
gcm, _ := cipher.NewGCM(aesCipher)

plain, err := gcm.Open(nil, license.Content[:gcm.NonceSize()], license.Content[gcm.NonceSize():], nil)
if err != nil {
log.Fatalln(`无法解密文件内容:`, err)
}
fmt.Println(BytesToString(plain))

pb, _ := pem.Decode(StringToBytes("-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5k9qkzjNI06W5rH5Z1QF\nQ9P8OimW+qL8I9nDgP0btJS6EW8x5nFNH88J+62yUp1xB5IwiMPvC9yA/6zJz9nV\nMntCI1MxZChBzpHvSjAbzIjoJkcxj92TfznTSS8TNzuISinPxYEd5gTKPMFl+bC3\ngDuTt1HSIVU/6HU16cG1F0JAfVMp3xFexoJAtd8x0LkElQ7DHHGBBycViRnkNx3O\nfN1QDS/kjP9J9GDoeXQZ+Wf5k7pL1PunXGRcfPG5b6it7g5xVPd6qbeNjd1ooQwG\noV1R6YxbyznFsF8W3DZPyuX9/ovShIwssKGff5X8OkwM5L/UHnF8MXwH7d13fQGm\n0wIDAQAB\n-----END PUBLIC KEY-----\n"))
pub, _ := x509.ParsePKIXPublicKey(pb.Bytes)

err = rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.SHA1, SumSHA1(plain), license.Signature)
if err != nil {
log.Fatalln(`签名验证失败!`)
}
log.Println(`是有效的破解版授权文件!`)
}

func script() {
var err error
var sh bool = runtime.GOOS != `windows`
if len(os.Args) == 3 {
sh = os.Args[2] == `sh`
}
if sh {
err = os.WriteFile(`start.sh`, StringToBytes("#!/bin/bash\n\nexport CR_LICENSE_KEY=\"2018110303550773058\"\nexport CR_OFFLINE_LICENSE=$(cat license)\n\nexec ./cloudreve\n"), 0666)
} else {
err = os.WriteFile(`start.bat`, StringToBytes("@echo off\nsetlocal enabledelayedexpansion\n\nset \"CR_LICENSE_KEY=2018110303550773058\"\nset \"CR_OFFLINE_LICENSE=\"\n\nfor /f \"usebackq delims=\" %%a in (\"license\") do (\n set \"line=%%a\"\n set \"CR_OFFLINE_LICENSE=!CR_OFFLINE_LICENSE!!line!\"\n)\n\ncmd /c cloudreve.exe\n"), 0666)
}
if err != nil {
log.Fatalln(`写入文件失败:`, err)
}
log.Println(`启动脚本写入成功!`)
}

func main() {
os.Stdout.WriteString("\n# CrPatch v4.6.0 by CloudrevePlus\n\n")

const help = "Usage:\n 1. patch {file} - Patch Cloudreve exe\n 2. license {subDomain1,subDomain2} - Generate license file\n 3. valid (license) - Valid psedo license file?\n 4. script {sh|bat} - Generate startup script\n\n"

if len(os.Args) == 1 {
os.Stdout.WriteString(help)
return
}

switch os.Args[1] {
case `patch`:
patch()
case `license`:
license()
case `valid`:
valid()
case `script`:
script()
default:
os.Stdout.WriteString(help)
return
}
}

/// util

func FindOffset(f *os.File, target []byte) (int64, error) {
const chunkSize = 64 * 1024 // 64KB
buf := make([]byte, chunkSize)
var offset int64 = 0

// 用于处理跨块匹配
var overlap []byte

for {
n, err := f.Read(buf)
if n > 0 {
data := append(overlap, buf[:n]...)

// 搜索目标内容
idx := bytes.Index(data, target)
if idx != -1 {
return offset + int64(idx) - int64(len(overlap)), nil
}

// 保留末尾 overlap
if len(target)-1 < len(data) {
overlap = data[len(data)-(len(target)-1):]
} else {
overlap = data
}

offset += int64(n)
}

if err == io.EOF {
break
}
if err != nil {
return -1, err
}
}

return -1, nil
}

/// types

type License struct {
Content []byte
Signature []byte
}

type LicenseContent struct {
RootDomains []string `json:"root_domains"`
Domains []string `json:"domains"`
UserID string `json:"user_id"`
ValidSince time.Time `json:"valid_since"`
ExpireAt time.Time `json:"expire_at"`
VolEnabledDomain []string `json:"vol_enabled_domain"`
VolLicense string `json:"vol_license"`
VolSign string `json:"vol_sign"`
}

/// pkg

//= bytesconv.go

// StringToBytes converts string to byte slice without a memory allocation.
// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
func StringToBytes(s string) []byte {
return unsafe.Slice(unsafe.StringData(s), len(s))
}

// BytesToString converts byte slice to string without a memory allocation.
// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
func BytesToString(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
}

//= encoding.go

//# base64

// Base64 编码
func Base64Encode(enc *base64.Encoding, src []byte) []byte {
dst := MakeNoZero(enc.EncodedLen(len(src)))
enc.Encode(dst, src)
return dst
}

// Base64 解码
func Base64Decode(enc *base64.Encoding, src []byte) ([]byte, error) {
dst := MakeNoZero(enc.DecodedLen(len(src)))
n, err := enc.Decode(dst, src)
return dst[:n], err
}

//# hex

// Hex 编码
func HexEncode(src []byte) []byte {
dst := MakeNoZero(hex.EncodedLen(len(src)))
hex.Encode(dst, src)
return dst
}

// Hex 解码
func HexDecode(src []byte) ([]byte, error) {
dst := MakeNoZero(hex.DecodedLen(len(src)))
_, err := hex.Decode(dst, src)
return dst, err
}

//# hash

// sha1
func SumSHA1(src []byte) []byte {
s := sha1.Sum(src)
return s[:]
}

//= random.go

// 生成随机字节
func RandomBytes(size int) []byte {
buf := MakeNoZero(size)
rand.Read(buf)
return buf
}

//= util_v1.20.go

// MakeNoZero 生成长度和容量为 n 的切片,此为Go1.20兼容版本。
func MakeNoZero(n int) []byte {
return make([]byte, n)
}
Binary file added crpatch_4.6.0_linux_amd64v2
Binary file not shown.
Binary file added crpatch_4.6.0_windows_amd64v2.exe
Binary file not shown.