working
This commit is contained in:
BIN
cmd/client/client.exe
Normal file
BIN
cmd/client/client.exe
Normal file
Binary file not shown.
41
cmd/client/main.go
Normal file
41
cmd/client/main.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"go-ts/internal/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
serverAddr := flag.String("server", "127.0.0.1:9987", "TeamSpeak 3 Server Address")
|
||||
nickname := flag.String("nickname", "GoCient", "Nickname")
|
||||
flag.Parse()
|
||||
|
||||
log.Printf("Starting TS3 Client...")
|
||||
log.Printf("Server: %s", *serverAddr)
|
||||
log.Printf("Nickname: %s", *nickname)
|
||||
|
||||
c := client.NewClient(*nickname)
|
||||
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
if err := c.Connect(*serverAddr); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for signals
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
log.Fatalf("Client Error: %v", err)
|
||||
case <-sigChan:
|
||||
log.Println("Shutting down...")
|
||||
}
|
||||
}
|
||||
332
cmd/fakeserver/main.go
Normal file
332
cmd/fakeserver/main.go
Normal file
@@ -0,0 +1,332 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"go-ts/pkg/protocol"
|
||||
|
||||
"filippo.io/edwards25519"
|
||||
)
|
||||
|
||||
type ServerState struct {
|
||||
Step int
|
||||
A1 [16]byte
|
||||
A2 [100]byte
|
||||
|
||||
Identity *ecdsa.PrivateKey
|
||||
|
||||
LicensePriv *edwards25519.Scalar
|
||||
LicensePub *edwards25519.Point
|
||||
LicenseBlock []byte
|
||||
|
||||
Alpha []byte
|
||||
Beta []byte
|
||||
SharedSecret []byte
|
||||
SharedIV []byte
|
||||
SharedMac [8]byte
|
||||
}
|
||||
|
||||
func main() {
|
||||
addr, _ := net.ResolveUDPAddr("udp", ":9988")
|
||||
conn, _ := net.ListenUDP("udp", addr)
|
||||
log.Println("FakeServer listening on :9988")
|
||||
|
||||
// 1. Generate Server Identity (P-256)
|
||||
privKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
|
||||
// 2. Generate Transport/License Key (Ed25519)
|
||||
var seed [32]byte
|
||||
rand.Read(seed[:])
|
||||
lPriv, _ := new(edwards25519.Scalar).SetBytesWithClamping(seed[:])
|
||||
lPub := new(edwards25519.Point).ScalarBaseMult(lPriv)
|
||||
|
||||
// 3. Create 'l' (License Block) - 32 random bytes for now
|
||||
lData := make([]byte, 32)
|
||||
rand.Read(lData)
|
||||
|
||||
state := &ServerState{
|
||||
Step: 0,
|
||||
Identity: privKey,
|
||||
LicensePriv: lPriv,
|
||||
LicensePub: lPub,
|
||||
LicenseBlock: lData,
|
||||
Alpha: make([]byte, 10),
|
||||
Beta: make([]byte, 54),
|
||||
}
|
||||
rand.Read(state.Alpha)
|
||||
rand.Read(state.Beta)
|
||||
rand.Read(state.A2[:])
|
||||
|
||||
buf := make([]byte, 4096)
|
||||
for {
|
||||
n, rAddr, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
data := make([]byte, n)
|
||||
copy(data, buf[:n])
|
||||
handlePacket(conn, rAddr, data, state)
|
||||
}
|
||||
}
|
||||
|
||||
func handlePacket(conn *net.UDPConn, addr *net.UDPAddr, data []byte, s *ServerState) {
|
||||
pkt, err := protocol.Decode(data, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if pkt.Header.PacketType() == protocol.PacketTypeInit1 {
|
||||
if len(pkt.Data) > 5 && pkt.Data[4] == 0x00 { // Step 0
|
||||
log.Println("Recv Step 0, Sending Step 1")
|
||||
sendStep1(conn, addr, s)
|
||||
} else if len(pkt.Data) > 5 && pkt.Data[4] == 0x02 { // Step 2
|
||||
log.Println("Recv Step 2, Sending Step 3")
|
||||
sendStep3(conn, addr, s)
|
||||
}
|
||||
} else if pkt.Header.PacketType() == protocol.PacketTypeCommand {
|
||||
decrypted, err := decryptHandshake(pkt)
|
||||
if err == nil {
|
||||
sStr := string(decrypted)
|
||||
if len(sStr) > 8 && sStr[0:8] == "clientek" {
|
||||
log.Printf("Recv clientek (Decrypted): %s", sStr)
|
||||
if err := s.processClientEk(decrypted); err != nil {
|
||||
log.Printf("Error processing clientek: %v", err)
|
||||
} else {
|
||||
log.Println("Shared Secret Derived. Waiting for clientinit.")
|
||||
}
|
||||
sendAck(conn, addr, pkt.Header.PacketID)
|
||||
return
|
||||
} else if len(sStr) > 12 && sStr[0:12] == "clientinitiv" {
|
||||
log.Println("Recv clientinitiv. Sending initivexpand2...")
|
||||
sendInitivexpand2(conn, addr, s)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.SharedSecret) > 0 {
|
||||
s.decryptClientInit(pkt)
|
||||
} else if err != nil && pkt.Header.FlagUnencrypted() == false {
|
||||
// log.Printf("Decrypt failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decryptHandshake(pkt *protocol.Packet) ([]byte, error) {
|
||||
key := protocol.HandshakeKey
|
||||
nonce := protocol.HandshakeNonce
|
||||
|
||||
meta := make([]byte, 5)
|
||||
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
||||
binary.BigEndian.PutUint16(meta[2:4], pkt.Header.ClientID)
|
||||
meta[4] = pkt.Header.Type
|
||||
|
||||
return protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
|
||||
}
|
||||
|
||||
func sendStep1(conn *net.UDPConn, addr *net.UDPAddr, s *ServerState) {
|
||||
buf := new(bytes.Buffer)
|
||||
binary.Write(buf, binary.BigEndian, int32(time.Now().Unix()))
|
||||
buf.WriteByte(0x01)
|
||||
rand.Read(s.A1[:])
|
||||
buf.Write(s.A1[:])
|
||||
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeInit1, buf.Bytes())
|
||||
pkt.Header.PacketID = 1
|
||||
copy(pkt.Header.MAC[:], []byte("TS3INIT1"))
|
||||
|
||||
encoded, _ := pkt.Encode(false)
|
||||
conn.WriteToUDP(encoded, addr)
|
||||
}
|
||||
|
||||
func sendStep3(conn *net.UDPConn, addr *net.UDPAddr, s *ServerState) {
|
||||
buf := new(bytes.Buffer)
|
||||
binary.Write(buf, binary.BigEndian, int32(time.Now().Unix()))
|
||||
buf.WriteByte(0x03)
|
||||
|
||||
x := make([]byte, 64)
|
||||
rand.Read(x)
|
||||
n := make([]byte, 64)
|
||||
rand.Read(n)
|
||||
buf.Write(x)
|
||||
buf.Write(n)
|
||||
binary.Write(buf, binary.BigEndian, uint32(0))
|
||||
buf.Write(s.A2[:])
|
||||
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeInit1, buf.Bytes())
|
||||
pkt.Header.PacketID = 2
|
||||
copy(pkt.Header.MAC[:], []byte("TS3INIT1"))
|
||||
|
||||
encoded, _ := pkt.Encode(false)
|
||||
conn.WriteToUDP(encoded, addr)
|
||||
}
|
||||
|
||||
func sendInitivexpand2(conn *net.UDPConn, addr *net.UDPAddr, s *ServerState) {
|
||||
lStr := base64.StdEncoding.EncodeToString(s.LicenseBlock)
|
||||
base64.StdEncoding.EncodeToString(s.Beta) // unused?
|
||||
betaStr := base64.StdEncoding.EncodeToString(s.Beta)
|
||||
|
||||
// Encode Omega (P-256 Public Key)
|
||||
pub := s.Identity.PublicKey
|
||||
omegaBytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y)
|
||||
omegaStr := base64.StdEncoding.EncodeToString(omegaBytes)
|
||||
|
||||
// Create Proof: Sign(SHA256(lBytes)) with Identity
|
||||
hash := sha256.Sum256(s.LicenseBlock)
|
||||
r, sb, err := ecdsa.Sign(rand.Reader, s.Identity, hash[:])
|
||||
if err != nil {
|
||||
log.Printf("Signing failed: %v", err)
|
||||
}
|
||||
|
||||
type ECDSASignature struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
sig := ECDSASignature{R: r, S: sb}
|
||||
proofBytes, _ := asn1.Marshal(sig)
|
||||
proofStr := base64.StdEncoding.EncodeToString(proofBytes)
|
||||
|
||||
// Send command
|
||||
cmd := fmt.Sprintf("initivexpand2 l=%s beta=%s omega=%s ot=1 proof=%s tvd=C",
|
||||
lStr, betaStr, omegaStr, proofStr)
|
||||
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeCommand, []byte(cmd))
|
||||
pkt.Header.PacketID = 3
|
||||
|
||||
// Encrypt with HandshakeKey
|
||||
key := protocol.HandshakeKey
|
||||
nonce := protocol.HandshakeNonce
|
||||
|
||||
// Meta S->C (3 bytes: PID(2)+Type(1))
|
||||
meta := make([]byte, 3)
|
||||
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
||||
meta[2] = pkt.Header.Type
|
||||
|
||||
encData, mac, _ := protocol.EncryptEAX(key, nonce, meta, pkt.Data)
|
||||
pkt.Data = encData
|
||||
copy(pkt.Header.MAC[:], mac)
|
||||
|
||||
encoded, _ := pkt.Encode(false)
|
||||
conn.WriteToUDP(encoded, addr)
|
||||
}
|
||||
|
||||
func sendAck(conn *net.UDPConn, addr *net.UDPAddr, pid uint16) {
|
||||
pkt := protocol.NewPacket(protocol.PacketTypeAck, nil)
|
||||
pkt.Header.PacketID = pid
|
||||
|
||||
// Encrypt ACK
|
||||
key := protocol.HandshakeKey
|
||||
nonce := protocol.HandshakeNonce
|
||||
meta := make([]byte, 3)
|
||||
binary.BigEndian.PutUint16(meta[0:2], pid)
|
||||
meta[2] = pkt.Header.Type
|
||||
|
||||
encData, mac, _ := protocol.EncryptEAX(key, nonce, meta, pkt.Data)
|
||||
pkt.Data = encData
|
||||
copy(pkt.Header.MAC[:], mac)
|
||||
|
||||
encoded, _ := pkt.Encode(false)
|
||||
conn.WriteToUDP(encoded, addr)
|
||||
}
|
||||
|
||||
func (s *ServerState) deriveSharedSecret(clientEkPub []byte) error {
|
||||
clientPoint, err := new(edwards25519.Point).SetBytes(clientEkPub)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid client point: %v", err)
|
||||
}
|
||||
|
||||
// Server negates client point
|
||||
clientPoint.Negate(clientPoint)
|
||||
|
||||
sharedPoint := new(edwards25519.Point).ScalarMult(s.LicensePriv, clientPoint)
|
||||
sharedBytes := sharedPoint.Bytes()
|
||||
|
||||
sharedBytes[31] ^= 0x80
|
||||
|
||||
hash := sha512.Sum512(sharedBytes)
|
||||
s.SharedSecret = hash[:]
|
||||
|
||||
s.SharedIV = make([]byte, 64)
|
||||
copy(s.SharedIV, s.SharedSecret)
|
||||
for i := 0; i < 10; i++ {
|
||||
s.SharedIV[i] ^= s.Alpha[i]
|
||||
}
|
||||
if len(s.Beta) >= 54 {
|
||||
for i := 0; i < 54; i++ {
|
||||
s.SharedIV[10+i] ^= s.Beta[i]
|
||||
}
|
||||
}
|
||||
|
||||
macHash := sha1.Sum(s.SharedIV)
|
||||
copy(s.SharedMac[:], macHash[0:8])
|
||||
|
||||
log.Printf("Shared Secret Derived! IV: %s", hex.EncodeToString(s.SharedIV))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerState) processClientEk(data []byte) error {
|
||||
str := string(data)
|
||||
start := "ek="
|
||||
idx := 0
|
||||
for i := 0; i < len(str)-len(start); i++ {
|
||||
if str[i:i+3] == start {
|
||||
idx = i + 3
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == 0 {
|
||||
return fmt.Errorf("no ek found")
|
||||
}
|
||||
|
||||
end := idx
|
||||
for i := idx; i < len(str); i++ {
|
||||
if str[i] == ' ' {
|
||||
end = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
ekStr := str[idx:end]
|
||||
ekBytes, err := base64.StdEncoding.DecodeString(ekStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.deriveSharedSecret(ekBytes)
|
||||
}
|
||||
|
||||
func (s *ServerState) decryptClientInit(pkt *protocol.Packet) {
|
||||
crypto := &protocol.CryptoState{
|
||||
SharedIV: s.SharedIV,
|
||||
SharedMac: s.SharedMac[:],
|
||||
GenerationID: 0,
|
||||
}
|
||||
|
||||
key, nonce := crypto.GenerateKeyNonce(&pkt.Header, true) // isClientToServer
|
||||
|
||||
meta := make([]byte, 5)
|
||||
binary.BigEndian.PutUint16(meta[0:2], pkt.Header.PacketID)
|
||||
binary.BigEndian.PutUint16(meta[2:4], pkt.Header.ClientID)
|
||||
meta[4] = pkt.Header.Type
|
||||
|
||||
dec, err := protocol.DecryptEAX(key, nonce, meta, pkt.Data, pkt.Header.MAC[:])
|
||||
if err != nil {
|
||||
log.Printf("Decryption Failed: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf(">>> DECRYPTED CLIENTINIT <<<\n%s\n", string(dec))
|
||||
}
|
||||
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