250 lines
10 KiB
Go
250 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
|
|
pkgaes "github.com/perfect-panel/server/pkg/aes"
|
|
)
|
|
|
|
func main() {
|
|
// 通讯密钥
|
|
communicationKey := "c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx"
|
|
|
|
// 真实 Nginx 日志数据 - 从用户提供的日志中选取
|
|
sampleLogs := []string{
|
|
// 加密的下载请求 - 不同平台
|
|
`172.245.180.199 - - [02/Feb/2026:04:35:47 +0000] "GET /v1/common/client/download?data=JetaR6P9e8G5lZg2KRiAhV6c%2FdMilBtP78bKmsbAxL8%3D&time=2026-02-02T04:35:15.032000 HTTP/1.1" 200 201 "https://www.hifastvpn.com/" "AdsBot-Google (+http://www.google.com/adsbot.html)"`,
|
|
`172.245.180.199 - - [02/Feb/2026:04:35:47 +0000] "GET /v1/common/client/download?data=%2FFTAxtcEd%2F8T2MzKdxxrPfWBXk4pNPbQZB3p8Yrl8XQ%3D&time=2026-02-02T04:35:15.031000 HTTP/1.1" 200 181 "https://www.hifastvpn.com/" "AdsBot-Google (+http://www.google.com/adsbot.html)"`,
|
|
`172.245.180.199 - - [02/Feb/2026:04:35:47 +0000] "GET /v1/common/client/download?data=i18AVRwlVSuFrbf4NmId0RcTbj0tRJIBFHP0MxLjDmI%3D&time=2026-02-02T04:35:15.033000 HTTP/1.1" 200 201 "https://www.hifastvpn.com/" "AdsBot-Google (+http://www.google.com/adsbot.html)"`,
|
|
`172.245.180.199 - - [02/Feb/2026:04:50:50 +0000] "GET /v1/common/client/download?platform=mac HTTP/1.1" 200 113 "https://gethifast.net/" "Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)"`,
|
|
`172.245.180.199 - - [02/Feb/2026:04:50:50 +0000] "GET /v1/common/client/download?platform=windows HTTP/1.1" 200 117 "https://gethifast.net/" "Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)"`,
|
|
`172.245.180.199 - - [02/Feb/2026:05:24:16 +0000] "GET /v1/common/client/download?data=XfZsgEqUUQ0YBTT51ETQp2wheSvE4SRupBfYbiLnJOc%3D&time=2026-02-02T05:24:15.462000 HTTP/1.1" 200 181 "https://www.hifastvpn.com/" "Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"`,
|
|
// 真实用户下载
|
|
`172.245.180.199 - - [02/Feb/2026:02:15:16 +0000] "GET /v1/common/client/download?data=XIZiz7c4sbUGE7Hl8fY6O2D5QKaZqx%2Fg81uR7kjenSg%3D&time=2026-02-02T02:15:16.337000 HTTP/1.1" 200 201 "https://hifastvpn.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"`,
|
|
`172.245.180.199 - - [02/Feb/2026:02:18:09 +0000] "GET /v1/common/client/download?data=aB0HistwZTIhxJh6yIds%2B6knoyZC17KyxaXvyd3Z5LY%3D&time=2026-02-02T02:18:06.301000 HTTP/1.1" 200 201 "https://hifastvpn.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36"`,
|
|
// 实际文件下载
|
|
`111.55.176.116 - - [02/Feb/2026:02:19:02 +0000] "GET /v1/common/client/download/file/android-1.0.0.apk HTTP/2.0" 200 18546688 "https://hifastvpn.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36"`,
|
|
`111.249.202.38 - - [02/Feb/2026:03:14:46 +0000] "GET /v1/common/client/download/file/mac-1.0.0.dmg HTTP/2.0" 200 72821392 "https://hifastvpn.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 12.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.7091.96 Safari/537.36"`,
|
|
// Windows 用户
|
|
`172.245.180.199 - - [02/Feb/2026:02:23:55 +0000] "GET /v1/common/client/download?data=t8OIVjnZx1N7w5ras4oVH9V0wz4JYlR7849WYKvbj9E%3D&time=2026-02-02T02:23:56.110000 HTTP/1.1" 200 201 "https://hifastvpn.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.7149.88 Safari/537.36"`,
|
|
// Mac 用户
|
|
`172.245.180.199 - - [02/Feb/2026:03:14:10 +0000] "GET /v1/common/client/download?data=mGKSxZtL7Ptf30MgFzBJPIsURC%2FkOf2lOGaXQOQ5Ft8%3D&time=2026-02-02T03:14:07.667000 HTTP/1.1" 200 181 "https://hifastvpn.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 12.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.7091.96 Safari/537.36"`,
|
|
// Android 移动端
|
|
`172.245.180.199 - - [02/Feb/2026:03:19:41 +0000] "GET /v1/common/client/download?data=y7gttvd%2BoKf9%2BZUeNTsOvuFHwOLFBByrNjkvhPkVykg%3D&time=2026-02-02T03:19:42.192000 HTTP/1.1" 200 201 "https://hifastvpn.com/" "Mozilla/5.0 (Linux; Android 15; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.7559.59 Mobile Safari/537.36"`,
|
|
`183.171.68.186 - - [02/Feb/2026:03:19:47 +0000] "GET /v1/common/client/download/file/android-1.0.0.apk HTTP/1.1" 200 179890 "https://hifastvpn.com/" "Mozilla/5.0 (Linux; Android 15; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.7559.59 Mobile Safari/537.36"`,
|
|
}
|
|
|
|
// 如果命令行提供了参数,使用命令行参数
|
|
if len(os.Args) > 1 {
|
|
sampleLogs = os.Args[1:]
|
|
}
|
|
|
|
fmt.Println("=== Nginx 下载日志解密工具 ===")
|
|
fmt.Printf("通讯密钥: %s\n\n", communicationKey)
|
|
|
|
// 统计数据
|
|
stats := make(map[string]int)
|
|
successCount := 0
|
|
|
|
for i, logLine := range sampleLogs {
|
|
// 提取日志条目
|
|
entry := extractLogEntry(logLine)
|
|
if entry.Data == "" && entry.Platform == "" {
|
|
fmt.Printf("--- 日志 #%d ---\n", i+1)
|
|
fmt.Println("⚠️ 跳过: 未找到 data 或 platform 参数\n")
|
|
continue
|
|
}
|
|
|
|
// 如果有 platform 参数(非加密),直接使用
|
|
if entry.Platform != "" {
|
|
fmt.Printf("--- 日志 #%d ---\n", i+1)
|
|
fmt.Printf("📍 IP地址: %s\n", entry.IP)
|
|
fmt.Printf("🌐 来源: %s\n", entry.Referer)
|
|
fmt.Printf("🔓 平台: %s (未加密)\n\n", entry.Platform)
|
|
stats[entry.Platform]++
|
|
successCount++
|
|
continue
|
|
}
|
|
|
|
// 处理加密的 data 参数
|
|
if entry.Data == "" {
|
|
continue
|
|
}
|
|
|
|
// URL 解码
|
|
decodedData, err := url.QueryUnescape(entry.Data)
|
|
if err != nil {
|
|
fmt.Printf("--- 日志 #%d ---\n", i+1)
|
|
fmt.Printf("❌ 错误: URL 解码失败: %v\n\n", err)
|
|
continue
|
|
}
|
|
|
|
// 提取 nonce (IV) - 从 time 参数转换
|
|
nonce := extractNonceFromTime(entry.Time)
|
|
|
|
// AES 解密
|
|
plainText, err := pkgaes.Decrypt(decodedData, communicationKey, nonce)
|
|
if err != nil {
|
|
fmt.Printf("--- 日志 #%d ---\n", i+1)
|
|
fmt.Printf("❌ 错误: 解密失败: %v\n", err)
|
|
fmt.Printf(" IP: %s, Nonce: %s\n\n", entry.IP, nonce)
|
|
continue
|
|
}
|
|
|
|
// 解析 JSON 获取平台信息
|
|
var result map[string]interface{}
|
|
if err := json.Unmarshal([]byte(plainText), &result); err == nil {
|
|
if platform, ok := result["platform"].(string); ok {
|
|
stats[platform]++
|
|
}
|
|
}
|
|
|
|
fmt.Printf("--- 日志 #%d ---\n", i+1)
|
|
fmt.Printf("📍 IP地址: %s\n", entry.IP)
|
|
fmt.Printf("🌐 来源: %s\n", entry.Referer)
|
|
fmt.Printf("🔓 解密内容: %s\n\n", plainText)
|
|
successCount++
|
|
}
|
|
|
|
// 输出统计信息
|
|
if successCount > 0 {
|
|
fmt.Println("=" + strings.Repeat("=", 50))
|
|
fmt.Printf("📊 统计信息 (成功解密: %d)\n", successCount)
|
|
fmt.Println("=" + strings.Repeat("=", 50))
|
|
for platform, count := range stats {
|
|
fmt.Printf(" %s: %d 次\n", platform, count)
|
|
}
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
// LogEntry 表示解析后的日志条目
|
|
type LogEntry struct {
|
|
IP string
|
|
Data string
|
|
Time string
|
|
Referer string
|
|
Platform string
|
|
}
|
|
|
|
// extractLogEntry 从日志行中提取所有关键信息
|
|
func extractLogEntry(logLine string) *LogEntry {
|
|
entry := &LogEntry{}
|
|
|
|
// 提取 IP 地址(第一个字段)
|
|
parts := strings.Fields(logLine)
|
|
if len(parts) > 0 {
|
|
entry.IP = parts[0]
|
|
}
|
|
|
|
// 提取 Referer 和 User-Agent
|
|
// Nginx combined 格式:... "请求" 状态码 字节数 "Referer" "User-Agent"
|
|
// 需要找到最后两对引号
|
|
quotes := []int{}
|
|
for i := 0; i < len(logLine); i++ {
|
|
if logLine[i] == '"' {
|
|
quotes = append(quotes, i)
|
|
}
|
|
}
|
|
|
|
// 至少需要 6 个引号: "GET ..." "Referer" "User-Agent"
|
|
if len(quotes) >= 6 {
|
|
// 倒数第 4 和第 3 个引号之间是 Referer
|
|
refererStart := quotes[len(quotes)-4]
|
|
refererEnd := quotes[len(quotes)-3]
|
|
entry.Referer = logLine[refererStart+1 : refererEnd]
|
|
|
|
// 倒数第 2 和第 1 个引号之间是 User-Agent
|
|
// 如果需要也可以提取
|
|
// uaStart := quotes[len(quotes)-2]
|
|
// uaEnd := quotes[len(quotes)-1]
|
|
// entry.UserAgent = logLine[uaStart+1 : uaEnd]
|
|
}
|
|
|
|
// 查找 ? 后面的查询字符串
|
|
idx := strings.Index(logLine, "?")
|
|
// 如果没有查询参数,检查是否是直接文件下载
|
|
if idx == -1 {
|
|
// 检查是否包含 /v1/common/client/download/file/
|
|
filePrefix := "/v1/common/client/download/file/"
|
|
fileIdx := strings.Index(logLine, filePrefix)
|
|
if fileIdx != -1 {
|
|
// 提取文件名部分
|
|
// URL 形式可能是: /v1/common/client/download/file/Hi%E5%BF%ABVPN-windows-1.0.0.exe HTTP/1.1
|
|
// 需要截取到空格
|
|
pathStart := fileIdx + len(filePrefix)
|
|
pathEnd := strings.Index(logLine[pathStart:], " ")
|
|
if pathEnd != -1 {
|
|
filePath := logLine[pathStart : pathStart+pathEnd]
|
|
// URL 解码
|
|
decodedPath, err := url.QueryUnescape(filePath)
|
|
if err == nil {
|
|
// 转换为小写以便匹配
|
|
lowerPath := strings.ToLower(decodedPath)
|
|
if strings.Contains(lowerPath, "windows") || strings.HasSuffix(lowerPath, ".exe") {
|
|
entry.Platform = "windows"
|
|
} else if strings.Contains(lowerPath, "mac") || strings.HasSuffix(lowerPath, ".dmg") {
|
|
entry.Platform = "mac"
|
|
} else if strings.Contains(lowerPath, "android") || strings.HasSuffix(lowerPath, ".apk") {
|
|
entry.Platform = "android"
|
|
} else if strings.Contains(lowerPath, "ios") || strings.HasSuffix(lowerPath, ".ipa") {
|
|
entry.Platform = "ios"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return entry
|
|
}
|
|
|
|
queryStr := logLine[idx+1:]
|
|
// 截取到空格或 HTTP/
|
|
endIdx := strings.Index(queryStr, " ")
|
|
if endIdx != -1 {
|
|
queryStr = queryStr[:endIdx]
|
|
}
|
|
|
|
// 解析查询参数
|
|
params := strings.Split(queryStr, "&")
|
|
for _, param := range params {
|
|
kv := strings.SplitN(param, "=", 2)
|
|
if len(kv) != 2 {
|
|
continue
|
|
}
|
|
switch kv[0] {
|
|
case "data":
|
|
entry.Data = kv[1]
|
|
case "time":
|
|
entry.Time = kv[1]
|
|
case "platform":
|
|
entry.Platform = kv[1]
|
|
}
|
|
}
|
|
|
|
return entry
|
|
}
|
|
|
|
// extractNonceFromTime 从 time 参数中提取 nonce
|
|
// time 格式: 2026-02-02T04:35:15.032000
|
|
// 需要转换为纳秒时间戳的十六进制
|
|
func extractNonceFromTime(timeStr string) string {
|
|
if timeStr == "" {
|
|
return ""
|
|
}
|
|
|
|
// URL 解码
|
|
decoded, err := url.QueryUnescape(timeStr)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
// 简化处理:直接使用整个时间字符串作为 nonce
|
|
// 因为原始代码使用 time.Now().UnixNano() 的十六进制
|
|
// 但是从日志中我们无法准确还原原始的 nonce
|
|
// 所以尝试使用 time 字符串本身
|
|
return decoded
|
|
}
|