working
This commit is contained in:
138
cmd/proxy/main_design.go
Normal file
138
cmd/proxy/main_design.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TS3 Proxy to capture clientinit parameters
|
||||
func main() {
|
||||
var localAddr string
|
||||
var serverAddr string
|
||||
|
||||
flag.StringVar(&localAddr, "listen", ":9988", "Local listen address")
|
||||
flag.StringVar(&serverAddr, "server", "localhost:9987", "Real TeamSpeak server address")
|
||||
flag.Parse()
|
||||
|
||||
// Resolve addresses
|
||||
localUDP, err := net.ResolveUDPAddr("udp", localAddr)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid local address: %v", err)
|
||||
}
|
||||
serverUDP, err := net.ResolveUDPAddr("udp", serverAddr)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid server address: %v", err)
|
||||
}
|
||||
|
||||
// Listen on local port
|
||||
conn, err := net.ListenUDP("udp", localUDP)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to listen: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
log.Printf("Proxy listening on %s, forwarding to %s", localAddr, serverAddr)
|
||||
log.Println("Connect your Real TeamSpeak Client to 'localhost:9988' to capture credentials.")
|
||||
|
||||
// Map client addresses to server connections (for multiple clients support, simplifying for 1)
|
||||
// Simple forwarder: We only expect one client for this task.
|
||||
var clientAddr *net.UDPAddr
|
||||
|
||||
buf := make([]byte, 2048)
|
||||
|
||||
for {
|
||||
n, addr, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
log.Printf("Read error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if packet is from client or server
|
||||
if clientAddr == nil || addr.String() != clientAddr.String() {
|
||||
// New client or client packet
|
||||
// If it's from our known server (unlikely as we don't bind to serverAddr), it's a client.
|
||||
// But wait, we are listening on localUDP.
|
||||
// We need to dial the server to forward.
|
||||
|
||||
// We'll use a separate socket to talk to the server to keep track of sessions?
|
||||
// Simplest UDP proxy:
|
||||
// 1. Receive from Client -> Send to Server (using a dialer)
|
||||
// 2. Receive from Server -> Send to Client
|
||||
|
||||
// Let's reset for new clients to keep it simple
|
||||
clientAddr = addr
|
||||
handleClientPacket(conn, serverUDP, buf[:n], addr)
|
||||
} else {
|
||||
// Known client
|
||||
handleClientPacket(conn, serverUDP, buf[:n], addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need a persistent connection to the server to receive responses
|
||||
// In a simple loop, we can't easily run two blocking reads (from client and from server) without goroutines.
|
||||
// Let's structure:
|
||||
// Main loop reads from Client (ListenUDP).
|
||||
// On first packet, start a Goroutine that Dials the Server.
|
||||
// That goroutine reads from Server and writes to Client.
|
||||
// Main loop writes to Server using that connection.
|
||||
|
||||
var serverConn *net.UDPConn
|
||||
|
||||
func handleClientPacket(clientConn *net.UDPConn, serverAddr *net.UDPAddr, data []byte, clientAddr *net.UDPAddr) {
|
||||
// Inspect Packet for clientinit strings (Naive text search)
|
||||
// clientinit is encrypted in standard flow?
|
||||
// WAIT. If we proxy, the client and server negotiate encryption.
|
||||
// We CANNOT decrypt the traffic unless we perform a MitM attack on the Diffie-Hellman handshake.
|
||||
// TS3 uses ECDH. We cannot derive the shared secret just by passively observing.
|
||||
// We would need to:
|
||||
// 1. Intercept `initivexpand2` (Step 2/3/4?)
|
||||
// 2. Replace the Server's Public Key with OUR Public Key.
|
||||
// 3. Negotiate SharedSecret1 with Client.
|
||||
// 4. Negotiate SharedSecret2 with Server (using our Private Key).
|
||||
// 5. Decrypt Client packet -> Re-encrypt for Server.
|
||||
|
||||
// This is significantly complex.
|
||||
// BUT, `clientinit` might be sent *plaintext* in some legacy modes?
|
||||
// No, the user's issue is specifically about the encrypted handshake.
|
||||
|
||||
// However, `client_version` is sometimes sent in cleartext in the INIT1 packet?
|
||||
// No, INIT1 is just 4 timestamps + random bytes.
|
||||
|
||||
// WAIT. The very first packet is valid?
|
||||
// `clientinit` is sent AFTER the handshake (Step 6). It is definitely encrypted.
|
||||
|
||||
// If the user connects with a real client, the client calculates the SharedSecret using the REAL server's key.
|
||||
// We verify the packets pass through.
|
||||
// WE CANNOT READ THE PAYLOAD without a full MitM (replacing keys).
|
||||
|
||||
// IS THERE ANOTHER WAY?
|
||||
// Does the client send version in clear text anywhere?
|
||||
// Maybe in the UserAgent? (UDP doesn't have headers like HTTP)
|
||||
|
||||
// Proposal: Implement Full MitM.
|
||||
// We already have the ECDH logic implemented in Go!
|
||||
// We can act as the "Server" to the Client, and "Client" to the Server.
|
||||
// 1. Client connects to Proxy. Proxy acts as Server.
|
||||
// 2. Proxy sends its OWN Identity/Key to Client.
|
||||
// 3. Proxy connects to Real Server.
|
||||
// 4. Proxy completes handshake with Real Server.
|
||||
// 5. Client completes handshake with Proxy.
|
||||
// 6. Proxy decrypts Client's `clientinit`, LOGS IT, then re-encrypts for Server.
|
||||
|
||||
// This is the chosen path. It reuses our `handshake.go` and `license.go` logic.
|
||||
|
||||
inspectPacket(data)
|
||||
|
||||
// Forwarding logic placeholder (Passive won't work for decryption)
|
||||
}
|
||||
|
||||
func inspectPacket(data []byte) {
|
||||
// Check for "clientinit" ASCII pattern
|
||||
s := string(data)
|
||||
if strings.Contains(s, "clientinit") {
|
||||
log.Printf("[!] FOUND POSSIBLE CLIENTINIT (Plaintext?): %s", s)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user