init commit
This commit is contained in:
441
cmd/proxy/main.go
Normal file
441
cmd/proxy/main.go
Normal file
@@ -0,0 +1,441 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user