├── README.md ├── go.mod ├── main.go └── patch for 1.4 amd64 ├── CRACKER.md ├── go.mod └── main.go /README.md: -------------------------------------------------------------------------------- 1 | # XRAY CRACKER 2 | 3 | > 支持到 1.2.0 版本,官方在 1.3.0 版本中更换了授权验证机制 4 | 5 | 前几天长亭官方有个活动,可以领2个月的xray社区高级版证书,正好趁这个机会逆向分析了一下xray的证书算法,写了一个证书生成器 6 | 7 | 因为xray证书用到了rsa算法,所以需要替换xray程序中的公钥,将该功能也集成在工具中了 8 | 9 | 相关算法分析文章后面有空再写,这里先放出写好的工具 10 | 11 | ## 工具使用 12 | 13 | ### 查看帮助 14 | 15 | 使用 `-h` 查看帮助 16 | 17 | ``` 18 | PS > .\xray-cracker -h 19 | 破解xray高级版证书,使用 -h 参数查看使用帮助 20 | 21 | Usage of xray-cracker: 22 | -c string 23 | 替换xray程序内置公钥,需要指定xray程序文件路径 24 | -g string 25 | 生成一个永久license,需要指定用户名 26 | -p string 27 | 解析官方证书,需要指定证书路径 28 | ``` 29 | 30 | ### 生成证书 31 | 32 | 使用 `-g username` 生成永久证书 33 | 34 | ``` 35 | PS > .\xray-cracker -g "我叫啥" 36 | 破解xray高级版证书,使用 -h 参数查看使用帮助 37 | 38 | 证书已写入文件:xray-license.lic 39 | ``` 40 | 41 | ### 破解xray 42 | 43 | 使用 `-c path-to-xray` 修改xray内置公钥 44 | 45 | ``` 46 | PS > .\xray-cracker -c .\xray_windows_amd64.exe 47 | 破解xray高级版证书,使用 -h 参数查看使用帮助 48 | 49 | public key index: 16741321 50 | 文件写入成功: .\xray_windows_amd64.exe 51 | ``` 52 | 53 | > 工具虽然是windows平台下运行,但是照样可以破解其他平台xray 54 | > 目前xray最新版是1.0.0,现在全平台全版本通杀 55 | 56 | 57 | ## 破解效果 58 | 59 | 使用修改版xray和永久证书后,效果如下 60 | 61 | ``` 62 | PS > .\xray_windows_amd64.exe version 63 | 64 | __ __ _____ __ __ 65 | \ \ / / | __ \ /\ \ \ / / 66 | \ V / | |__) | / \ \ \_/ / 67 | > < | _ / / /\ \ \ / 68 | / . \ | | \ \ / ____ \ | | 69 | /_/ \_\ |_| \_\ /_/ \_\ |_| 70 | 71 | 72 | Version: 1.0.0/62161168/COMMUNITY-ADVANCED 73 | Licensed to 我叫啥, license is valid until 2099-09-09 08:00:00 74 | 75 | [xray 1.0.0/62161168] 76 | Build: [2020-06-13] [windows/amd64] [RELEASE/COMMUNITY-ADVANCED] 77 | Compiler Version: go version go1.14.1 linux/amd64 78 | License ID: 00000000000000000000000000000000 79 | User Name: 我叫啥/00000000000000000000000000000000 80 | Not Valid Before: 2020-06-12 00:00:00 81 | Not Valid After: 2099-09-09 08:00:00 82 | ``` 83 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zu1k/xray-crack 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "crypto/rand" 9 | "crypto/rsa" 10 | "crypto/sha256" 11 | "crypto/x509" 12 | "encoding/base64" 13 | "encoding/hex" 14 | "encoding/json" 15 | "encoding/pem" 16 | "flag" 17 | "fmt" 18 | "io/ioutil" 19 | "os" 20 | "strings" 21 | "time" 22 | ) 23 | 24 | var ( 25 | originPubKeyPem = `-----BEGIN PUBLIC KEY----- 26 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2K+PRbp/yumhaVnN92JS 27 | GuQiwj7df64jHAo8MvXLWjYxU/yvqB4LbGty8ymKQy33qaDNpu9jgE2s8cXrtftm 28 | /UcvwDb8sTqWXpDhxYhcvJM30agxz3/8VwNJ4JOvhk9Gn+msYIUz+gXZMBuUFKhi 29 | BOd6C2Pro03GYwVTNjfwH/Y9C5EfPKIKNU/5t2cYo+TuOBk5ooP+NTaDzB6rb7fd 30 | E5uuNnF21x3rdiI9rZcKPbuU97/0OWNcIUh5wfxPNWwcmjYmFuZcxk/7dOUD65s4 31 | pTplCoMLOelacB0l442dM4w2xNpn+Yg7i/ujmg37F+VguCZJWnoyImdhp/raccNG 32 | +wIDAQAB 33 | -----END PUBLIC KEY-----` 34 | newPublicKeyPem = `-----BEGIN PUBLIC KEY----- 35 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtKvyFB24hIEVwMs4Xi00 36 | FCW41tqELGYb7f63A/lAsBPVSOvGrQ5UzuKmttatQF/IDD9UcHqqbi+B80pydiGS 37 | eKJOaly0GuX6hfDd51/uo7E44LyzJSSBhTc1vtbL5JbNcapnxo4P6rJ1Uh9V7y8z 38 | pRvc1G2da00mQSYoIg/9ty21j4So+Fz/v37qhK50EEIeXGJZb4uz9I9iKCHaazjI 39 | Lf293Gzvp7EFEpZkKrh2VktKaERh+jHmJqEe0z7U/sz0cCa9ohS+TF5nxmkAZBel 40 | CwEMXjkjGnCWO3wXJoyrXMn1GY/ilNPDFT7rSZBKLEIi7PrBD1pVLGdq2zTboenV 41 | 6wIDAQAB 42 | -----END PUBLIC KEY-----` 43 | newPrivateKeyPem = `-----BEGIN RSA PRIVATE KEY----- 44 | MIIEpQIBAAKCAQEAtKvyFB24hIEVwMs4Xi00FCW41tqELGYb7f63A/lAsBPVSOvG 45 | rQ5UzuKmttatQF/IDD9UcHqqbi+B80pydiGSeKJOaly0GuX6hfDd51/uo7E44Lyz 46 | JSSBhTc1vtbL5JbNcapnxo4P6rJ1Uh9V7y8zpRvc1G2da00mQSYoIg/9ty21j4So 47 | +Fz/v37qhK50EEIeXGJZb4uz9I9iKCHaazjILf293Gzvp7EFEpZkKrh2VktKaERh 48 | +jHmJqEe0z7U/sz0cCa9ohS+TF5nxmkAZBelCwEMXjkjGnCWO3wXJoyrXMn1GY/i 49 | lNPDFT7rSZBKLEIi7PrBD1pVLGdq2zTboenV6wIDAQABAoIBAQCu3PSxr4pVBLLP 50 | JGFsFQggr9nUaS4f4rwJfswXlnibcratmzVxbTt7+TYuJF0OvyVZZToOm0q01lpJ 51 | 5LYfy6J+C2kl3I+csRXl6Rh8xgase+x252vj+Q86phLon/A7UBGLf8htDjYti4et 52 | chK0KtUramozV9xSbBsoVwvk2+FOFdiLsc+B3PyuydB0Lvov5EDBtZJ1GbnyWk/3 53 | c++aL+lkjQbIs11A4Nwp7hUdPmM/Va8VK+DqWxbFCIr6rli5d9VOE//EHJ7S7aPp 54 | +fxV9gyv1d0WBRNktH2t8O2JVn90379/EgWuonSlRG+HrhqZKrXIKuIFJEUmGUjs 55 | 8qJNzoERAoGBAOLjiGbXuOQHkshDqdB4xF1b4rvCBrr881dBAOnYqSyEUWuUD3et 56 | 4qX/7GaxWlSU2IteB+r5FyfEpmqUNmKVsgLkGh3lgeTU0Mss2+2xIAODNXvba8MV 57 | UIawpvDFnLN2HEY/d+LYycBjWDk+6B1+dGlPZIxXF+8HqGnlqyBNFjP/AoGBAMva 58 | WVB02FK4oXa8APTtvuQ2MP67Q95WdhZXdy8CEWnwJaknSTE3dXJ9nZZmFgHt54lo 59 | KjbGfIOSCLeCqXm3ZGs5HQr2kY/xJXDJga6uNh71w66/q/W2z+30FFzta6BjYE/8 60 | 3pB+P4vUUsp/vb3SkNfRKdcNrtoL29UYdXM7QG4VAoGAQqLw/MN+2fofchHtXf0a 61 | LxE9lkd2EpUYIxhEXGn1xc1W3HGv2UaIuphfpgmQribJMqV7Tde6pUNsXQEKuAmf 62 | Lpov0XgGnl6itAmIzlanQGDY5HedPr6T1/sqDKz9SPf3depOG6HwH0EOOEHxijgJ 63 | mKRos48gyGNHY1LA38vEKaECgYEAmu8fRsknyOdOwMFvMLiphyWw40pM8OVh5uUf 64 | TnkR5ySAWynitSdjelsCtNZuD5VTjtm+i9cbt5v8SA1k5X9/MQc9jaGNTIuJW0mr 65 | 6Km7tJgx29UNyzjgnAgQmfhQ/pvJDcIxHjz16z66lfG0slshfwYX+L0LkenFcRaf 66 | 3a7A72kCgYEAhSSGHVkCTGteSyKxhbMVqTlxQQQWZKv4b+usqss00CKgs3CAKL8H 67 | Crds7fq96xVDVCvxJGYMKQzG61MBa+e1f8YSdhl5EY1IltlHkZstgts7avG6MP6A 68 | xMNjyLp1b84s2VVXTpSFA7i6KEUhl4NjqhZTslJht5Dfiy2Mmvfk2so= 69 | -----END RSA PRIVATE KEY-----` 70 | pre2Bytes = []byte{0x00, 0x01} 71 | pre17Bytes, _ = hex.DecodeString("01710d238a47f02c748d11d1ed906b4e1f") 72 | aesKey = []byte{0x12, 0x23, 0x45, 0x56, 0x00, 0x87, 0x36, 0x93, 0x23, 0x86, 0x64, 0x76, 0x78, 0x67, 0x78, 0x00} 73 | aesIv = []byte{0x71, 0x0d, 0x23, 0x8a, 0x47, 0xf0, 0x2c, 0x74, 0x8d, 0x11, 0xd1, 0xed, 0x90, 0x6b, 0x4e, 0x1f} 74 | ) 75 | 76 | var ( 77 | licenseName string 78 | xrayFile string 79 | originLicense string 80 | ) 81 | 82 | func main() { 83 | fmt.Println(`破解xray高级版证书,使用 -h 参数查看使用帮助 84 | 本工具有效期到 2021-01-01 85 | `) 86 | validTime, _ := time.Parse("2006-01-02 15:04:05", "2021-01-01 00:00:00") 87 | nowTime := time.Now() 88 | if nowTime.After(validTime) { 89 | panic("本工具已失效") 90 | } 91 | 92 | flag.StringVar(&licenseName, "g", "", "生成一个永久license,需要指定用户名") 93 | flag.StringVar(&xrayFile, "c", "", "替换xray程序内置公钥,需要指定xray程序文件路径") 94 | flag.StringVar(&originLicense, "p", "", "解析官方证书,需要指定证书路径") 95 | 96 | flag.Parse() 97 | 98 | if originLicense != "" { 99 | parseAlready(originLicense) 100 | } 101 | 102 | if licenseName != "" { 103 | genNew(licenseName) 104 | } 105 | 106 | if xrayFile != "" { 107 | replacePublicKeyInXray(xrayFile) 108 | } 109 | 110 | //parseAlready("xray-license.lic") 111 | //genNew() 112 | } 113 | 114 | func parseAlready(licenseFile string) { 115 | // 加载公钥 116 | pubKey := importPublicKey(originPubKeyPem) 117 | 118 | // 解析 xray-license.lic 文件 119 | licenseFileData, err := ioutil.ReadFile(licenseFile) 120 | if err != nil { 121 | panic(err.Error()) 122 | } 123 | licenseString := string(licenseFileData) 124 | tmpStrings := strings.Split(licenseString, "\n") 125 | licenseString = "" 126 | for _, line := range tmpStrings { 127 | if !strings.HasPrefix(line, "#") && line != "" { 128 | licenseString += line 129 | } 130 | } 131 | //fmt.Println("your license:", licenseString) 132 | 133 | decode_data, err := base64.StdEncoding.DecodeString(licenseString) 134 | if err != nil { 135 | panic(err) 136 | } 137 | //fmt.Printf("pre 17: %x\n", decode_data[:17]) 138 | //fmt.Printf("pre 17: %x\n", pre17Bytes) 139 | 140 | // aes解密license 141 | // 总长度 487,前面17个字节是单独加上的,所以总共解密出 480个字节的数据 142 | aesDecData, err := Decrypt(decode_data[17:]) 143 | if err != nil { 144 | panic(err) 145 | } 146 | //fmt.Printf("AES DEC: %x\n", aesDecData) 147 | //fmt.Println(string(aesDecData)) 148 | 149 | // 后半部分是明文的json 150 | licensePlainJsonBytes := aesDecData[0x102:] 151 | //fmt.Println("license info json:", string(licensePlainJsonBytes)) 152 | 153 | license := License{} 154 | err = json.Unmarshal([]byte(licensePlainJsonBytes), &license) 155 | if err != nil { 156 | panic(err) 157 | } 158 | fmt.Println("license parsed:", license) 159 | 160 | // rsa 验证签名 pss 161 | sum := sha256.Sum256(licensePlainJsonBytes) 162 | //fmt.Println(sum) 163 | 164 | // rsa使用 sha256算法,对 aes解密后的数据第三个字节开始,到后面json明文前面为止是签名 165 | //fmt.Println("解析出来的签名:", aesDecData[2:0x102]) 166 | 167 | err = rsa.VerifyPSS(pubKey, crypto.SHA256, sum[:], aesDecData[2:0x102], nil) 168 | if err != nil { 169 | //fmt.Println(err.Error()) 170 | //panic(err.Error()) 171 | } else { 172 | fmt.Println("varify success") 173 | } 174 | } 175 | 176 | func genNew(name string) { 177 | validTime, err := time.Parse("2006-01-02 15:04:05", "2099-09-09 00:00:00") 178 | 179 | license := License{ 180 | LicenseId: "00000000000000000000000000000000", 181 | UserId: "00000000000000000000000000000000", 182 | UserName: name, 183 | Distribution: "COMMUNITY-ADVANCED", 184 | NotValidBefore: 1591891200, 185 | NotValidAfter: validTime.Unix(), 186 | } 187 | 188 | licensePlainJsonBytes, _ := json.Marshal(license) 189 | //licensePlainJson := string(licensePlainJsonBytes) 190 | 191 | //fmt.Println("明文license信息:", licensePlainJson) 192 | 193 | // rsa sign 194 | priKey := importPrivateKey(newPrivateKeyPem) 195 | //fmt.Println(priKey) 196 | //sha256sum 197 | sum := sha256.Sum256(licensePlainJsonBytes) 198 | signature, err := rsa.SignPSS(rand.Reader, priKey, crypto.SHA256, sum[:], nil) 199 | if err != nil { 200 | panic(err) 201 | } 202 | 203 | //fmt.Println("新签名", len(signature), signature) 204 | 205 | licenseInfoWithSign := append(signature, licensePlainJsonBytes...) 206 | aesEnc, err := Encrypt(append(pre2Bytes, licenseInfoWithSign...)) 207 | if err != nil { 208 | panic(err) 209 | } 210 | //fmt.Println(aesEnc) 211 | 212 | allBytes := append(pre17Bytes, aesEnc...) 213 | 214 | // 增加前17个字节的不知道干啥用的信息 215 | licenseText := base64.StdEncoding.EncodeToString(allBytes) 216 | //fmt.Println("你的新证书:\n") 217 | //fmt.Println(licenseText) 218 | 219 | fileText := `# xray license 220 | # 需要重命名为 xray-license.lic 和 xray 可执行程序放在同一个文件夹中 221 | # user_name: Chinese 222 | # distribution: COMMUNITY-ADVANCED 223 | # 仅对修改后的xray有效 224 | 225 | 226 | ` + licenseText + ` 227 | ` 228 | 229 | err = ioutil.WriteFile("xray-license.lic", []byte(fileText), os.ModePerm) 230 | if err == nil { 231 | fmt.Println("证书已写入文件:xray-license.lic") 232 | } 233 | } 234 | 235 | func Decrypt(decode_data []byte) ([]byte, error) { 236 | block, _ := aes.NewCipher(aesKey) 237 | blockMode := cipher.NewCBCDecrypter(block, aesIv) 238 | origin_data := make([]byte, len(decode_data)) 239 | blockMode.CryptBlocks(origin_data, decode_data) 240 | return unpad(origin_data), nil 241 | } 242 | 243 | func unpad(ciphertext []byte) []byte { 244 | length := len(ciphertext) 245 | //去掉最后一次的padding 246 | unpadding := int(ciphertext[length-1]) 247 | return ciphertext[:(length - unpadding)] 248 | } 249 | 250 | func Encrypt(text []byte) ([]byte, error) { 251 | block, _ := aes.NewCipher(aesKey) 252 | blockSize := block.BlockSize() 253 | originData := pad(text, blockSize) 254 | blockMode := cipher.NewCBCEncrypter(block, aesIv) 255 | crypted := make([]byte, len(originData)) 256 | blockMode.CryptBlocks(crypted, originData) 257 | //fmt.Println(len(originData)) 258 | return crypted, nil 259 | } 260 | 261 | func pad(ciphertext []byte, blockSize int) []byte { 262 | padding := blockSize - len(ciphertext)%blockSize 263 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 264 | return append(ciphertext, padtext...) 265 | } 266 | 267 | type License struct { 268 | LicenseId string `json:"license_id"` 269 | UserId string `json:"user_id"` 270 | UserName string `json:"user_name"` 271 | Distribution string `json:"distribution"` 272 | NotValidBefore int64 `json:"not_valid_before"` 273 | NotValidAfter int64 `json:"not_valid_after"` 274 | } 275 | 276 | func importPublicKey(key string) *rsa.PublicKey { 277 | block, _ := pem.Decode([]byte(key)) 278 | if block == nil { 279 | panic("unable to decode publicKey to request") 280 | } 281 | 282 | pub, _ := x509.ParsePKIXPublicKey(block.Bytes) 283 | return pub.(*rsa.PublicKey) 284 | } 285 | 286 | func importPrivateKey(key string) *rsa.PrivateKey { 287 | block, _ := pem.Decode([]byte(key)) 288 | privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) 289 | if err != nil { 290 | panic(err) 291 | } 292 | return privateKey 293 | } 294 | 295 | func replacePublicKeyInXray(filePath string) { 296 | origin, err := ioutil.ReadFile(filePath) 297 | if err != nil { 298 | panic(err) 299 | } 300 | 301 | //originPubKeyPem = strings.ReplaceAll(originPubKeyPem, "\r\n", "\n") 302 | originPubKeyPemBytes := []byte(originPubKeyPem) 303 | newPubKeyPemBytes := []byte(newPublicKeyPem) 304 | //fmt.Printf("%x\n", originPubKeyPemBytes) 305 | 306 | loc := bytes.Index(origin, originPubKeyPemBytes) 307 | fmt.Println("public key index:", loc) 308 | 309 | newFile := bytes.ReplaceAll(origin, originPubKeyPemBytes, newPubKeyPemBytes) 310 | err = ioutil.WriteFile(filePath, newFile, os.ModePerm) 311 | if err == nil { 312 | fmt.Println("文件写入成功:", filePath) 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /patch for 1.4 amd64/CRACKER.md: -------------------------------------------------------------------------------- 1 | # XRAY CRACKER 2 | 3 | ### 生成证书 4 | 5 | 使用 `-g username` 生成永久证书 6 | 7 | ```shell 8 | # ./xray-cracker -g kali 9 | 10 | 证书已写入文件:xray-license.lic 11 | ``` 12 | 13 | ### 破解 xray 14 | 15 | 目前 PubKey 是加密过的,加密算法很简单,但是那个函数是硬编码了几百个局部变量,替换一波后,两个一对进行加、减、异或等操作进行还原,看样子是用代码生成的加密函数代码然后编译的,如果花时间在这上面可能是个死局,因为可以在每一次编译前都重新生成一下代码 16 | 17 | 所以我选择从其他地方入手,很明显公钥是用来验证签名的,如此看来直接修改签名验证函数的返回值就行,golang 中 VerifyPSS 返回一个 err,如果`err==nil`就代表签名没问题,放在汇编里就是 `test 某寄存器` 然后 `setz`或`setnz`,改一下就行 18 | 19 | 20 | 21 | 目前支持版本: 22 | 23 | - amd64 windows 24 | - amd64 linux 25 | - amd64 Mac 26 | 27 | 使用 `-c path-to-xray` 自动patch二进制xray 28 | 29 | ```bash 30 | # ./xray-crack.exe -c xray_linux_amd64 31 | linux amd64 32 | [.text] offset: 0x1000, addr: 0x401000-0x11787e3 33 | Signature last index: 0xae2f2e 34 | Patch success: xray_linux_amd64 35 | ``` 36 | 37 | ## 破解效果 38 | 39 | 使用修改版 xray 和永久证书后,效果如下 40 | 41 | ```shell 42 | E:\tools\x64dbg\xray-1.4.5>xray_windows_amd64.exe version 43 | 44 | ____ ___.________. ____. _____.___. 45 | \ \/ /\_ __ \ / _ \ \__ | | 46 | \ / | _ _/ / /_\ \ / | | 47 | / \ | | \/ | \ \____ | 48 | \___/\ \ |____| /\____|_ / / _____/ 49 | \_/ \_/ \_/ \/ 50 | 51 | Version: 1.4.5/abf4f3df/COMMUNITY-ADVANCED 52 | Licensed to jas502n, license is valid until 2099-09-09 08:00:00 53 | 54 | [xray 1.4.5/abf4f3df] 55 | Build: [2020-11-18] [windows/amd64] [RELEASE/COMMUNITY-ADVANCED] 56 | Compiler Version: go version go1.14.4 linux/amd64 57 | License ID: 00000000000000000000000000000000 58 | User Name: jas502n/00000000000000000000000000000000 59 | Not Valid Before: 2020-06-12 00:00:00 60 | Not Valid After: 2099-09-09 08:00:00 61 | 62 | To show open source licenses, please use `osslicense` sub-command. 63 | 64 | E:\tools\x64dbg\xray-1.4.5> 65 | ``` 66 | -------------------------------------------------------------------------------- /patch for 1.4 amd64/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zu1k/xray-crack 2 | 3 | go 1.14 -------------------------------------------------------------------------------- /patch for 1.4 amd64/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto" 6 | "crypto/aes" 7 | "crypto/cipher" 8 | "crypto/rand" 9 | "crypto/rsa" 10 | "crypto/sha256" 11 | "crypto/x509" 12 | "debug/elf" 13 | "debug/macho" 14 | "debug/pe" 15 | "encoding/base64" 16 | "encoding/hex" 17 | "encoding/json" 18 | "encoding/pem" 19 | "flag" 20 | "fmt" 21 | "io/ioutil" 22 | "os" 23 | "strings" 24 | "time" 25 | ) 26 | 27 | var ( 28 | originPubKeyPem = `-----BEGIN PUBLIC KEY----- 29 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2K+PRbp/yumhaVnN92JS 30 | GuQiwj7df64jHAo8MvXLWjYxU/yvqB4LbGty8ymKQy33qaDNpu9jgE2s8cXrtftm 31 | /UcvwDb8sTqWXpDhxYhcvJM30agxz3/8VwNJ4JOvhk9Gn+msYIUz+gXZMBuUFKhi 32 | BOd6C2Pro03GYwVTNjfwH/Y9C5EfPKIKNU/5t2cYo+TuOBk5ooP+NTaDzB6rb7fd 33 | E5uuNnF21x3rdiI9rZcKPbuU97/0OWNcIUh5wfxPNWwcmjYmFuZcxk/7dOUD65s4 34 | pTplCoMLOelacB0l442dM4w2xNpn+Yg7i/ujmg37F+VguCZJWnoyImdhp/raccNG 35 | +wIDAQAB 36 | -----END PUBLIC KEY-----` 37 | newPublicKeyPem = `-----BEGIN PUBLIC KEY----- 38 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtKvyFB24hIEVwMs4Xi00 39 | FCW41tqELGYb7f63A/lAsBPVSOvGrQ5UzuKmttatQF/IDD9UcHqqbi+B80pydiGS 40 | eKJOaly0GuX6hfDd51/uo7E44LyzJSSBhTc1vtbL5JbNcapnxo4P6rJ1Uh9V7y8z 41 | pRvc1G2da00mQSYoIg/9ty21j4So+Fz/v37qhK50EEIeXGJZb4uz9I9iKCHaazjI 42 | Lf293Gzvp7EFEpZkKrh2VktKaERh+jHmJqEe0z7U/sz0cCa9ohS+TF5nxmkAZBel 43 | CwEMXjkjGnCWO3wXJoyrXMn1GY/ilNPDFT7rSZBKLEIi7PrBD1pVLGdq2zTboenV 44 | 6wIDAQAB 45 | -----END PUBLIC KEY-----` 46 | newPrivateKeyPem = `-----BEGIN RSA PRIVATE KEY----- 47 | MIIEpQIBAAKCAQEAtKvyFB24hIEVwMs4Xi00FCW41tqELGYb7f63A/lAsBPVSOvG 48 | rQ5UzuKmttatQF/IDD9UcHqqbi+B80pydiGSeKJOaly0GuX6hfDd51/uo7E44Lyz 49 | JSSBhTc1vtbL5JbNcapnxo4P6rJ1Uh9V7y8zpRvc1G2da00mQSYoIg/9ty21j4So 50 | +Fz/v37qhK50EEIeXGJZb4uz9I9iKCHaazjILf293Gzvp7EFEpZkKrh2VktKaERh 51 | +jHmJqEe0z7U/sz0cCa9ohS+TF5nxmkAZBelCwEMXjkjGnCWO3wXJoyrXMn1GY/i 52 | lNPDFT7rSZBKLEIi7PrBD1pVLGdq2zTboenV6wIDAQABAoIBAQCu3PSxr4pVBLLP 53 | JGFsFQggr9nUaS4f4rwJfswXlnibcratmzVxbTt7+TYuJF0OvyVZZToOm0q01lpJ 54 | 5LYfy6J+C2kl3I+csRXl6Rh8xgase+x252vj+Q86phLon/A7UBGLf8htDjYti4et 55 | chK0KtUramozV9xSbBsoVwvk2+FOFdiLsc+B3PyuydB0Lvov5EDBtZJ1GbnyWk/3 56 | c++aL+lkjQbIs11A4Nwp7hUdPmM/Va8VK+DqWxbFCIr6rli5d9VOE//EHJ7S7aPp 57 | +fxV9gyv1d0WBRNktH2t8O2JVn90379/EgWuonSlRG+HrhqZKrXIKuIFJEUmGUjs 58 | 8qJNzoERAoGBAOLjiGbXuOQHkshDqdB4xF1b4rvCBrr881dBAOnYqSyEUWuUD3et 59 | 4qX/7GaxWlSU2IteB+r5FyfEpmqUNmKVsgLkGh3lgeTU0Mss2+2xIAODNXvba8MV 60 | UIawpvDFnLN2HEY/d+LYycBjWDk+6B1+dGlPZIxXF+8HqGnlqyBNFjP/AoGBAMva 61 | WVB02FK4oXa8APTtvuQ2MP67Q95WdhZXdy8CEWnwJaknSTE3dXJ9nZZmFgHt54lo 62 | KjbGfIOSCLeCqXm3ZGs5HQr2kY/xJXDJga6uNh71w66/q/W2z+30FFzta6BjYE/8 63 | 3pB+P4vUUsp/vb3SkNfRKdcNrtoL29UYdXM7QG4VAoGAQqLw/MN+2fofchHtXf0a 64 | LxE9lkd2EpUYIxhEXGn1xc1W3HGv2UaIuphfpgmQribJMqV7Tde6pUNsXQEKuAmf 65 | Lpov0XgGnl6itAmIzlanQGDY5HedPr6T1/sqDKz9SPf3depOG6HwH0EOOEHxijgJ 66 | mKRos48gyGNHY1LA38vEKaECgYEAmu8fRsknyOdOwMFvMLiphyWw40pM8OVh5uUf 67 | TnkR5ySAWynitSdjelsCtNZuD5VTjtm+i9cbt5v8SA1k5X9/MQc9jaGNTIuJW0mr 68 | 6Km7tJgx29UNyzjgnAgQmfhQ/pvJDcIxHjz16z66lfG0slshfwYX+L0LkenFcRaf 69 | 3a7A72kCgYEAhSSGHVkCTGteSyKxhbMVqTlxQQQWZKv4b+usqss00CKgs3CAKL8H 70 | Crds7fq96xVDVCvxJGYMKQzG61MBa+e1f8YSdhl5EY1IltlHkZstgts7avG6MP6A 71 | xMNjyLp1b84s2VVXTpSFA7i6KEUhl4NjqhZTslJht5Dfiy2Mmvfk2so= 72 | -----END RSA PRIVATE KEY-----` 73 | licenseVersion2Byte = []byte{0x02} 74 | pre2Bytes = []byte{0x00, 0x01} 75 | aesKeyNew, _ = hex.DecodeString("B293C506E0C7F60353C604961837B810") 76 | ) 77 | 78 | var ( 79 | licenseName string 80 | originLicense string 81 | xrayFilePath string 82 | ) 83 | 84 | func main() { 85 | flag.StringVar(&licenseName, "g", "", "生成一个永久license,需要指定用户名") 86 | flag.StringVar(&originLicense, "p", "", "解析官方证书,需要指定证书路径") 87 | flag.StringVar(&xrayFilePath, "c", "", "patch xray,需要指定xray程序文件路径") 88 | 89 | flag.Parse() 90 | 91 | if originLicense != "" { 92 | parseAlready(originLicense) 93 | } 94 | 95 | if licenseName != "" { 96 | genNew(licenseName) 97 | } 98 | 99 | if xrayFilePath != "" { 100 | patch(xrayFilePath) 101 | } 102 | 103 | } 104 | 105 | func parseAlready(licenseFile string) { 106 | // 加载公钥 107 | pubKey := importPublicKey(originPubKeyPem) 108 | 109 | // 解析 xray-license.lic 文件 110 | licenseFileData, err := ioutil.ReadFile(licenseFile) 111 | if err != nil { 112 | panic(err.Error()) 113 | } 114 | licenseString := string(licenseFileData) 115 | tmpStrings := strings.Split(licenseString, "\n") 116 | licenseString = "" 117 | for _, line := range tmpStrings { 118 | if !strings.HasPrefix(line, "#") && line != "" { 119 | licenseString += line 120 | } 121 | } 122 | //fmt.Println("your license:", licenseString) 123 | 124 | base64DecodeData, err := base64.StdEncoding.DecodeString(licenseString) 125 | if err != nil { 126 | panic(err) 127 | } 128 | 129 | //fmt.Println("base64 decode data:", hex.EncodeToString(base64DecodeData)) 130 | 131 | licenseVersion := base64DecodeData[0] 132 | if licenseVersion == 2 { 133 | fmt.Println("version ok: 2") 134 | } 135 | 136 | //解密前有一个简单的变换处理 137 | right := len(base64DecodeData) - 1 138 | for l := 1; l < right; l++ { 139 | r := right - l 140 | if l >= r { 141 | break 142 | } 143 | base64DecodeData[l], base64DecodeData[r] = base64DecodeData[r], base64DecodeData[l] 144 | } 145 | //fmt.Println("trans bytes:", hex.EncodeToString(base64DecodeData)) 146 | 147 | // aes解密license 148 | // | 1B : version | 16B : aes iv | 480B : cipher | 149 | aesDecData, err := Decrypt(base64DecodeData[17:], base64DecodeData[1:17]) 150 | if err != nil { 151 | panic(err) 152 | } 153 | //fmt.Printf("AES DEC: %x\n", aesDecData) 154 | //fmt.Println(string(aesDecData)) 155 | 156 | //另一个异或变换 157 | for i := 0; i < len(aesDecData); i++ { 158 | aesDecData[i] = aesDecData[i] ^ 0x44 159 | } 160 | //fmt.Println("trans 2 :", hex.EncodeToString(aesDecData)) 161 | //fmt.Println("trans 2 string:", string(aesDecData)) 162 | 163 | // 后半部分是明文的json 164 | licensePlainJsonBytes := aesDecData[0x102:] 165 | //fmt.Println("license info json:", string(licensePlainJsonBytes)) 166 | //fmt.Println("pre2bytes:", hex.EncodeToString(aesDecData[:0x2])) 167 | 168 | license := License{} 169 | err = json.Unmarshal([]byte(licensePlainJsonBytes), &license) 170 | if err != nil { 171 | panic(err) 172 | } 173 | fmt.Println("license parsed:", license) 174 | 175 | // rsa 验证签名 pss 176 | sum := sha256.Sum256(licensePlainJsonBytes) 177 | //fmt.Println(sum) 178 | 179 | // rsa使用 sha256算法,对 aes解密后的数据第三个字节开始,到后面json明文前面为止是签名 180 | //fmt.Println("解析出来的签名:", aesDecData[2:0x102]) 181 | 182 | err = rsa.VerifyPSS(pubKey, crypto.SHA256, sum[:], aesDecData[2:0x102], nil) 183 | if err != nil { 184 | fmt.Println(err.Error()) 185 | } else { 186 | fmt.Println("varify success") 187 | } 188 | } 189 | 190 | func genNew(name string) { 191 | validTime, err := time.Parse("2006-01-02 15:04:05", "2099-09-09 00:00:00") 192 | 193 | license := License{ 194 | LicenseId: "00000000000000000000000000000000", 195 | UserId: "00000000000000000000000000000000", 196 | UserName: name, 197 | Distribution: "COMMUNITY-ADVANCED", 198 | NotValidBefore: 1591891200, 199 | NotValidAfter: validTime.Unix(), 200 | } 201 | 202 | licensePlainJsonBytes, _ := json.Marshal(license) 203 | //licensePlainJson := string(licensePlainJsonBytes) 204 | //fmt.Println("明文license信息:", licensePlainJson) 205 | 206 | // rsa sign 207 | priKey := importPrivateKey(newPrivateKeyPem) 208 | 209 | //sha256sum 210 | sum := sha256.Sum256(licensePlainJsonBytes) 211 | signature, err := rsa.SignPSS(rand.Reader, priKey, crypto.SHA256, sum[:], nil) 212 | if err != nil { 213 | panic(err) 214 | } 215 | 216 | licenseInfoWithSign := append(signature, licensePlainJsonBytes...) 217 | data2Enc := append(pre2Bytes, licenseInfoWithSign...) 218 | 219 | // 加密前一次异或 220 | for i := 0; i < len(data2Enc); i++ { 221 | data2Enc[i] = data2Enc[i] ^ 0x44 222 | } 223 | 224 | // session iv 225 | iv := make([]byte, 16) 226 | _, _ = rand.Read(iv) 227 | fmt.Println("temp aes iv:", hex.EncodeToString(iv)) 228 | aesEnc, err := Encrypt(data2Enc, iv) 229 | if err != nil { 230 | panic(err) 231 | } 232 | //fmt.Println(aesEnc) 233 | 234 | allBytes := append(iv, aesEnc...) 235 | allBytes = append(licenseVersion2Byte, allBytes...) 236 | 237 | // 左右交换 238 | right := len(allBytes) - 1 239 | for l := 1; l < right; l++ { 240 | r := right - l 241 | if l >= r { 242 | break 243 | } 244 | allBytes[l], allBytes[r] = allBytes[r], allBytes[l] 245 | } 246 | 247 | licenseText := base64.StdEncoding.EncodeToString(allBytes) 248 | 249 | fileText := `# xray license 250 | # 需要重命名为 xray-license.lic 和 xray 可执行程序放在同一个文件夹中 251 | # user_name: Chinese 252 | # distribution: COMMUNITY-ADVANCED 253 | # 仅对修改后的xray有效 254 | 255 | 256 | ` + licenseText + ` 257 | ` 258 | 259 | err = ioutil.WriteFile("xray-license.lic", []byte(fileText), os.ModePerm) 260 | if err == nil { 261 | fmt.Println("证书已写入文件:xray-license.lic") 262 | } 263 | } 264 | 265 | func Decrypt(decodeData []byte, iv []byte) ([]byte, error) { 266 | block, _ := aes.NewCipher(aesKeyNew) 267 | blockMode := cipher.NewCBCDecrypter(block, iv) 268 | origin_data := make([]byte, len(decodeData)) 269 | blockMode.CryptBlocks(origin_data, decodeData) 270 | return unpad(origin_data), nil 271 | } 272 | 273 | func unpad(ciphertext []byte) []byte { 274 | length := len(ciphertext) 275 | unpadding := int(ciphertext[length-1]) 276 | return ciphertext[:(length - unpadding)] 277 | } 278 | 279 | func Encrypt(text []byte, iv []byte) ([]byte, error) { 280 | block, _ := aes.NewCipher(aesKeyNew) 281 | blockSize := block.BlockSize() 282 | originData := pad(text, blockSize) 283 | blockMode := cipher.NewCBCEncrypter(block, iv) 284 | crypted := make([]byte, len(originData)) 285 | blockMode.CryptBlocks(crypted, originData) 286 | //fmt.Println(len(originData)) 287 | return crypted, nil 288 | } 289 | 290 | func pad(ciphertext []byte, blockSize int) []byte { 291 | padding := blockSize - len(ciphertext)%blockSize 292 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 293 | return append(ciphertext, padtext...) 294 | } 295 | 296 | type License struct { 297 | LicenseId string `json:"license_id"` 298 | UserId string `json:"user_id"` 299 | UserName string `json:"user_name"` 300 | Distribution string `json:"distribution"` 301 | NotValidBefore int64 `json:"not_valid_before"` 302 | NotValidAfter int64 `json:"not_valid_after"` 303 | } 304 | 305 | func importPublicKey(key string) *rsa.PublicKey { 306 | block, _ := pem.Decode([]byte(key)) 307 | if block == nil { 308 | panic("unable to decode publicKey to request") 309 | } 310 | 311 | pub, _ := x509.ParsePKIXPublicKey(block.Bytes) 312 | return pub.(*rsa.PublicKey) 313 | } 314 | 315 | func importPrivateKey(key string) *rsa.PrivateKey { 316 | block, _ := pem.Decode([]byte(key)) 317 | privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) 318 | if err != nil { 319 | panic(err) 320 | } 321 | return privateKey 322 | } 323 | 324 | var ( 325 | origin386Bytes, _ = hex.DecodeString("0F95C083F0018844245083C430C3") 326 | new386Bytes, _ = hex.DecodeString("0F94C083F0018844245083C430C3") 327 | originAmd64Bytes, _ = hex.DecodeString("000F84BE020000") 328 | newAmd64Bytes, _ = hex.DecodeString("000F85BE020000") 329 | originArmBytes, _ = hex.DecodeString("000050E30000A0E30100A013010020E254") 330 | newArmBytes, _ = hex.DecodeString("000050E30000A0E30100A0130100A0E354") 331 | originAArch64Bytes, _ = hex.DecodeString("1F001FEBE0079F9A000040D2E0C3") 332 | newAArch64Bytes, _ = hex.DecodeString("1F001FEBE0079F9A200080D2E0C3") 333 | ) 334 | 335 | func patch(filePath string) { 336 | var ( 337 | originBytes []byte 338 | newBytes []byte 339 | maxIndex uint64 = 0 340 | ) 341 | 342 | if elfFile, err := elf.Open(filePath); err == nil { 343 | switch elfFile.Machine { 344 | case elf.EM_386: 345 | fmt.Println("linux 386") 346 | originBytes = origin386Bytes 347 | newBytes = new386Bytes 348 | case elf.EM_X86_64: 349 | fmt.Println("linux amd64") 350 | originBytes = originAmd64Bytes 351 | newBytes = newAmd64Bytes 352 | case elf.EM_ARM: 353 | fmt.Println("linux arm") 354 | originBytes = originArmBytes 355 | newBytes = newArmBytes 356 | case elf.EM_AARCH64: 357 | fmt.Println("linux arm64") 358 | originBytes = originAArch64Bytes 359 | newBytes = newAArch64Bytes 360 | default: 361 | fmt.Println("Unsupported linux platform!!") 362 | } 363 | sections := elfFile.Sections 364 | for _, i := range sections { 365 | if i.Name == ".text" { 366 | maxIndex = i.Addr + i.Size 367 | fmt.Printf("[.text] offset: %#x, addr: %#x-%#x\n", i.Offset, i.Addr, maxIndex) 368 | } 369 | } 370 | } else if peFile, err := pe.Open(filePath); err == nil { 371 | switch peFile.Machine { 372 | case pe.IMAGE_FILE_MACHINE_AMD64: 373 | fmt.Println("windows amd64") 374 | originBytes = originAmd64Bytes 375 | newBytes = newAmd64Bytes 376 | case pe.IMAGE_FILE_MACHINE_I386: 377 | fmt.Println("windows i386") 378 | originBytes = origin386Bytes 379 | newBytes = new386Bytes 380 | default: 381 | fmt.Println("Unsupported windows platform!!") 382 | 383 | } 384 | } else if machoFile, err := macho.Open(filePath); err == nil { 385 | switch machoFile.Cpu { 386 | case macho.CpuAmd64: 387 | fmt.Println("darwin amd64") 388 | originBytes = originAmd64Bytes 389 | newBytes = newAmd64Bytes 390 | case macho.Cpu386: 391 | fmt.Println("darwin 386") 392 | originBytes = origin386Bytes 393 | newBytes = new386Bytes 394 | default: 395 | fmt.Println("Unsupported darwin platform!!") 396 | } 397 | } else { 398 | fmt.Println("Can NOT parse file") 399 | return 400 | } 401 | 402 | origin, err := ioutil.ReadFile(filePath) 403 | loc := bytes.LastIndex(origin, originBytes) 404 | 405 | if loc > 0 { 406 | fmt.Printf("Signature index: %#x\n", loc) 407 | newFile := replace(origin, newBytes, loc) 408 | err = ioutil.WriteFile(filePath, newFile, os.ModePerm) 409 | if err == nil { 410 | fmt.Println("Patch success:", filePath) 411 | } 412 | } else { 413 | fmt.Println("Can't find signature") 414 | } 415 | } 416 | 417 | func replace(origin, new []byte, index int) []byte { 418 | n := make([]byte, len(origin)) 419 | copy(n[:index], origin[:index]) 420 | copy(n[index:index+len(new)], new) 421 | copy(n[index+len(new):], origin[index+len(new):]) 422 | return n 423 | } --------------------------------------------------------------------------------