Files
got-mod-customserver-server/cmd/proxy/main.go

442 lines
10 KiB
Go
Raw Normal View History

2026-01-14 21:33:21 +01:00
package main
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/binary"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"math/big"
"net"
"net/http"
"os"
"strings"
"sync"
"time"
)
// Config
var (
TargetHTTPHost = "https://api.asmodee.net"
TargetTCPHost = "got.games.asmodee.net"
TargetTCPPort = "2445"
ProxyHTTPPort = ":8080"
ProxyTCPPort = ":3000"
LogFileName = ""
LogFile *os.File
LogMutex sync.Mutex
)
// Log Entry Structure
type LogEntry struct {
Timestamp string `json:"timestamp"`
Protocol string `json:"protocol"` // HTTP or TCP
Type string `json:"type"` // REQUEST or RESPONSE
Summary string `json:"summary"` // e.g. "GET /v1/foo" or "Packet 123"
Data interface{} `json:"data"` // Structured data or raw info
}
// Write to JSONL
func logJSON(entry LogEntry) {
entry.Timestamp = time.Now().Format(time.RFC3339Nano)
LogMutex.Lock()
defer LogMutex.Unlock()
bytes, err := json.Marshal(entry)
if err == nil {
if LogFile != nil {
LogFile.Write(bytes)
LogFile.WriteString("\n")
} else {
fmt.Println(string(bytes))
}
}
}
func main() {
flag.StringVar(&TargetHTTPHost, "target-http", "https://api.asmodee.net", "Target HTTP Host")
flag.StringVar(&TargetTCPHost, "target-tcp-host", "got.games.asmodee.net", "Target TCP Host")
flag.StringVar(&TargetTCPPort, "target-tcp-port", "2445", "Target TCP Port")
flag.Parse()
LogFileName = fmt.Sprintf("session_%s.jsonl", time.Now().Format("20060102_150405"))
f, err := os.Create(LogFileName)
if err != nil {
log.Fatal(err)
}
LogFile = f
defer LogFile.Close()
fmt.Printf("Starting Proxy...\nLog: %s\nHTTP Target: %s\nTCP Target Default: %s:%s\n", LogFileName, TargetHTTPHost, TargetTCPHost, TargetTCPPort)
// Start HTTP Proxy
go startHTTPProxy()
// Start TCP Proxy
startTCPProxy()
}
// --- HTTP Proxy ---
func startHTTPProxy() {
http.HandleFunc("/", handleHTTPRequest)
fmt.Println("[HTTP] Listening on " + ProxyHTTPPort)
log.Fatal(http.ListenAndServe(ProxyHTTPPort, nil))
}
func handleHTTPRequest(w http.ResponseWriter, r *http.Request) {
// Log Request
bodyBytes, _ := ioutil.ReadAll(r.Body)
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Restore for forwarding
logJSON(LogEntry{
Protocol: "HTTP",
Type: "REQUEST",
Summary: fmt.Sprintf("%s %s", r.Method, r.URL.Path),
Data: map[string]interface{}{
"headers": r.Header,
"body": string(bodyBytes),
},
})
// Forward Request
targetURL := TargetHTTPHost + r.URL.Path
if r.URL.RawQuery != "" {
targetURL += "?" + r.URL.RawQuery
}
proxyReq, err := http.NewRequest(r.Method, targetURL, bytes.NewBuffer(bodyBytes))
if err != nil {
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
// Copy headers
for name, values := range r.Header {
for _, value := range values {
proxyReq.Header.Add(name, value)
}
}
// Remove Hop-by-hop headers
proxyReq.Header.Del("Host") // Let http client set it
// Might need to set Host header manually if target expects it
// proxyReq.Host = ...
client := &http.Client{}
resp, err := client.Do(proxyReq)
if err != nil {
// Log Error
logJSON(LogEntry{
Protocol: "HTTP",
Type: "ERROR",
Summary: "Forwarding Failed",
Data: err.Error(),
})
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
defer resp.Body.Close()
respBody, _ := ioutil.ReadAll(resp.Body)
// Log Response
logJSON(LogEntry{
Protocol: "HTTP",
Type: "RESPONSE",
Summary: fmt.Sprintf("%s %d", r.URL.Path, resp.StatusCode),
Data: map[string]interface{}{
"status": resp.StatusCode,
"headers": resp.Header,
"body": string(respBody),
},
})
// Sniff for TCP Info
// Look for "server": { "host": ... } or "HostName": ...
sniffTCPInfo(respBody)
// Write Response
for name, values := range resp.Header {
for _, value := range values {
w.Header().Add(name, value)
}
}
w.WriteHeader(resp.StatusCode)
w.Write(respBody)
}
func sniffTCPInfo(body []byte) {
// Search for "host":"..." and "port":... pattern
s := string(body)
if strings.Contains(s, "\"host\"") && strings.Contains(s, "\"port\"") {
idxHost := strings.Index(s, "\"host\"")
if idxHost != -1 {
// Find value
startQuote := strings.Index(s[idxHost:], ":") + idxHost + 1
startQuote = strings.Index(s[startQuote:], "\"") + startQuote
endQuote := strings.Index(s[startQuote+1:], "\"") + startQuote + 1
if startQuote != -1 && endQuote != -1 {
extractedHost := s[startQuote+1 : endQuote]
if extractedHost != "" {
fmt.Printf("[PROXY] Sniffed TCP Host: %s\n", extractedHost)
TargetTCPHost = extractedHost
}
}
}
idxPort := strings.Index(s, "\"port\"")
if idxPort != -1 {
// Find value (number)
colon := strings.Index(s[idxPort:], ":") + idxPort
// Find first digit after colon
startNum := -1
endNum := -1
for i := colon + 1; i < len(s); i++ {
c := s[i]
if c >= '0' && c <= '9' {
if startNum == -1 {
startNum = i
}
} else if startNum != -1 {
// End of number
endNum = i
break
}
}
if startNum != -1 {
if endNum == -1 {
endNum = len(s)
}
extractedPort := s[startNum:endNum]
fmt.Printf("[PROXY] Sniffed TCP Port: %s\n", extractedPort)
TargetTCPPort = extractedPort
}
}
}
}
func findHostPortRecursive(obj interface{}) {
switch v := obj.(type) {
case map[string]interface{}:
if h, ok := v["host"].(string); ok {
TargetTCPHost = h
fmt.Printf("[PROXY] Sniffed TCP Host: %s\n", h)
}
if p, ok := v["port"].(float64); ok { // JSON numbers are float64 in generic interface
TargetTCPPort = fmt.Sprintf("%d", int(p))
fmt.Printf("[PROXY] Sniffed TCP Port: %s\n", TargetTCPPort)
}
for _, val := range v {
findHostPortRecursive(val)
}
case []interface{}:
for _, val := range v {
findHostPortRecursive(val)
}
}
}
// --- TCP Proxy ---
func startTCPProxy() {
cert, err := generateSelfSignedCert()
if err != nil {
log.Fatal(err)
}
listener, err := tls.Listen("tcp", ProxyTCPPort, cert)
if err != nil {
log.Fatalf("TCP Listen failed: %v", err)
}
fmt.Println("[TCP] Listening on " + ProxyTCPPort)
for {
conn, err := listener.Accept()
if err != nil {
log.Println("TCP Accept error:", err)
continue
}
go handleTCPConn(conn)
}
}
func handleTCPConn(clientConn net.Conn) {
defer clientConn.Close()
// Connect to Real Server
targetAddr := fmt.Sprintf("%s:%s", TargetTCPHost, TargetTCPPort)
fmt.Printf("[TCP] New Connection. Forwarding to %s\n", targetAddr)
// Use TLS to connect to Real Server
serverConn, err := tls.Dial("tcp", targetAddr, &tls.Config{
InsecureSkipVerify: true, // We assume official server cert is fine but we skip verify to avoid setup
})
if err != nil {
log.Printf("Failed to dial target %s: %v\n", targetAddr, err)
return
}
defer serverConn.Close()
// Pipe
var wg sync.WaitGroup
wg.Add(2)
// Client -> Server
go func() {
defer wg.Done()
interceptAndLogTCP(clientConn, serverConn, "CLIENT->SERVER")
}()
// Server -> Client
go func() {
defer wg.Done()
interceptAndLogTCP(serverConn, clientConn, "SERVER->CLIENT")
}()
wg.Wait()
}
func interceptAndLogTCP(src, dst net.Conn, direction string) {
// We need to parse frames to log them effectively?
// The protocol is: 4 bytes length + Payload.
// Payload: Varint PacketID, Varint RequestID, Payload.
// We will try to read in a buffer loop, parse length prefix, log, then forward.
// But simply copying stream `io.Copy` is safer for latency.
// However, we want to LOG. So we MUST read packet by packet.
header := make([]byte, 4)
for {
// Read Head
_, err := io.ReadFull(src, header)
if err != nil {
break
}
length := binary.BigEndian.Uint32(header)
// Read Body
body := make([]byte, length)
_, err = io.ReadFull(src, body)
if err != nil {
break
}
// Write to Dst
// Write header + body
dst.Write(header)
dst.Write(body)
// Log
parseAndLogPacket(direction, body)
}
}
func parseAndLogPacket(direction string, data []byte) {
// Basic Decode of Packet Wrapper
// Packet ID (Field 1, Varint)
// Payload (Field 3, Bytes) -> Message
r := bytes.NewReader(data)
packetID := int64(-1)
var payloadBytes []byte
for {
tag, err := binary.ReadUvarint(r)
if err != nil {
break
}
fieldNum := tag >> 3
wireType := tag & 7
if fieldNum == 1 && wireType == 0 {
packetID, _ = binary.ReadVarint(r) // ReadVarint for int64
} else if fieldNum == 3 && wireType == 2 {
l, _ := binary.ReadUvarint(r)
payloadBytes = make([]byte, l)
r.Read(payloadBytes)
} else {
// Skip
skip(r, wireType)
}
}
// If we found payload, parse Message
requestID := int64(-1)
if payloadBytes != nil {
pr := bytes.NewReader(payloadBytes)
for {
tag, err := binary.ReadUvarint(pr)
if err != nil {
break
}
fieldNum := tag >> 3
wireType := tag & 7
if fieldNum == 1 && wireType == 0 { // Message.request_number
requestID, _ = binary.ReadVarint(pr)
} else {
skip(pr, wireType)
}
}
}
logJSON(LogEntry{
Protocol: "TCP",
Type: direction,
Summary: fmt.Sprintf("Packet %d | Req %d | Len %d", packetID, requestID, len(data)),
Data: map[string]interface{}{
"packet_id": packetID,
"request_id": requestID,
"hex": hex.EncodeToString(data),
},
})
}
func skip(r *bytes.Reader, wireType uint64) {
switch wireType {
case 0:
binary.ReadUvarint(r)
case 1:
r.Seek(8, io.SeekCurrent)
case 2:
l, _ := binary.ReadUvarint(r)
r.Seek(int64(l), io.SeekCurrent)
case 5:
r.Seek(4, io.SeekCurrent)
}
}
// --- Cert Gen (Copied) ---
func generateSelfSignedCert() (*tls.Config, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, err
}
template := x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{Organization: []string{"Proxy"}},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Hour * 24),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
DNSNames: []string{"localhost"},
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return nil, err
}
return &tls.Config{Certificates: []tls.Certificate{{Certificate: [][]byte{derBytes}, PrivateKey: priv}}}, nil
}