diff --git a/common/config.go b/common/config.go index 1623860..7ce75fe 100644 --- a/common/config.go +++ b/common/config.go @@ -29,6 +29,7 @@ type Config struct { KeyPathDS string `xml:"keyPathDS"` APISecret string `xml:"apiSecret"` AllowDefaultDolphinKeys bool `xml:"allowDefaultDolphinKeys"` + ServerName string `xml:"serverName,omitempty"` } func GetConfig() Config { @@ -39,6 +40,7 @@ func GetConfig() Config { var config Config config.AllowDefaultDolphinKeys = true + config.ServerName = "WiiLink" err = xml.Unmarshal(data, &config) if err != nil { @@ -65,7 +67,7 @@ func GetConfig() Config { enable := true config.EnableHTTPSExploitWii = &enable } - + if config.EnableHTTPSExploitDS == nil { enable := true config.EnableHTTPSExploitDS = &enable diff --git a/gamestats/main.go b/gamestats/main.go new file mode 100644 index 0000000..b08bfe5 --- /dev/null +++ b/gamestats/main.go @@ -0,0 +1,40 @@ +package gamestats + +import ( + "context" + "fmt" + "net/http" + "wwfc/common" + "wwfc/logging" + + "github.com/jackc/pgx/v4/pgxpool" + "github.com/logrusorgru/aurora/v3" +) + +var ( + ctx = context.Background() + pool *pgxpool.Pool +) + +func StartServer() { + // Get config + config := common.GetConfig() + + common.ReadGameList() + + // Start SQL + dbString := fmt.Sprintf("postgres://%s:%s@%s/%s", config.Username, config.Password, config.DatabaseAddress, config.DatabaseName) + dbConf, err := pgxpool.ParseConfig(dbString) + if err != nil { + panic(err) + } + + pool, err = pgxpool.ConnectConfig(ctx, dbConf) + if err != nil { + panic(err) + } +} + +func HandleRequest(w http.ResponseWriter, r *http.Request) { + logging.Info("GSTATS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr)) +} diff --git a/nas/https.go b/nas/https.go index d276b2b..a4bfb4f 100644 --- a/nas/https.go +++ b/nas/https.go @@ -20,6 +20,7 @@ import ( "net" "os" "strings" + "time" "wwfc/common" "wwfc/logging" @@ -65,6 +66,15 @@ func startHTTPSProxy(config common.Config) { panic(err) } + setupRealTLS(privKeyPath, certsPath) + // Reread the private key and certs on a regular interval + go func() { + for { + time.Sleep(24 * time.Hour) + setupRealTLS(privKeyPath, certsPath) + } + }() + if !(exploitWii || exploitDS) { // Only handle real TLS requests for { @@ -204,7 +214,7 @@ func startHTTPSProxy(config common.Config) { }...) serverCertsRecordDS = append(serverCertsRecordDS, certDS...) - + serverCertsRecordDS = append(serverCertsRecordDS, []byte{ byte(wiiCertLenDS >> 16), byte(wiiCertLenDS >> 8), @@ -224,7 +234,7 @@ func startHTTPSProxy(config common.Config) { panic(err) } - logging.Info("NAS-TLS", "Receiving HTTPS request from", aurora.BrightCyan(conn.RemoteAddr())) + // logging.Info("NAS-TLS", "Receiving HTTPS request from", aurora.BrightCyan(conn.RemoteAddr())) moduleName := "NAS-TLS:" + conn.RemoteAddr().String() go handleTLS(moduleName, conn, nasAddr, privKeyPath, certsPath, serverCertsRecordWii, rsaKeyWii, serverCertsRecordDS, rsaKeyDS) @@ -262,10 +272,10 @@ func handleTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPath 0x00, 0x35, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x09, 0x00, 0x00, 0x05, 0x00, 0x00, 0x04, }[index] { - break; + break } } - if (index == 0x1D) { + if index == 0x1D { macFn, cipher, clientCipher := handleWiiTLSHandshake(moduleName, conn, serverCertsRecordWii, rsaKeyWii) proxyConsoleTLS(moduleName, conn, nasAddr, VersionTLS10, macFn, cipher, clientCipher) return @@ -285,17 +295,17 @@ func handleTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPath if helloBytes[index] != []byte{ 0x16, 0x03, 0x00, 0x00, 0x2F, 0x01, 0x00, 0x00, 0x2B, 0x03, 0x00, }[index] { - break; + break } } - if (index == 0x0B) { + if index == 0x0B { macFn, cipher, clientCipher := handleDSSSLHandshake(moduleName, conn, serverCertsRecordDS, rsaKeyDS) proxyConsoleTLS(moduleName, conn, nasAddr, VersionSSL30, macFn, cipher, clientCipher) return } } - logging.Info(moduleName, "Forwarding client hello:", aurora.Cyan(fmt.Sprintf("% X ", helloBytes))) + // logging.Info(moduleName, "Forwarding client hello:", aurora.Cyan(fmt.Sprintf("% X ", helloBytes))) handleRealTLS(moduleName, conn, nasAddr, privKeyPath, certsPath) } @@ -486,7 +496,7 @@ func handleDSSSLHandshake(moduleName string, conn bufferedConn, serverCertsRecor finishHash := newFinishedHash(VersionSSL30) finishHash.Write(clientHello[0x5:0x34]) - clientRandom := clientHello[0x0b:0x0b+0x20] + clientRandom := clientHello[0x0b : 0x0b+0x20] serverHello := []byte{0x16, 0x03, 0x00, 0x00, 0x2A, 0x02, 0x00, 0x00, 0x26, 0x03, 0x00} @@ -715,7 +725,7 @@ func proxyConsoleTLS(moduleName string, conn bufferedConn, nasAddr string, versi return } - if (buf[1] != 0x03 || (version == VersionTLS10 && buf[2] != 0x01) || (version == VersionSSL30 && buf[2] != 0x00)) { + if buf[1] != 0x03 || (version == VersionTLS10 && buf[2] != 0x01) || (version == VersionSSL30 && buf[2] != 0x00) { logging.Error(moduleName, "Invalid TLS version") return } @@ -758,6 +768,36 @@ func proxyConsoleTLS(moduleName string, conn bufferedConn, nasAddr string, versi } } +var realTLSConfig *tls.Config + +func setupRealTLS(privKeyPath string, certsPath string) { + // Read server key and certs + + serverKey, err := os.ReadFile(privKeyPath) + if err != nil { + logging.Error("NAS-TLS", "Failed to read server key:", err) + return + } + + serverCerts, err := os.ReadFile(certsPath) + if err != nil { + logging.Error("NAS-TLS", "Failed to read server certs:", err) + return + } + + cert, err := tls.X509KeyPair(serverCerts, serverKey) + if err != nil { + logging.Error("NAS-TLS", "Failed to parse server certs:", err) + return + } + + config := tls.Config{ + Certificates: []tls.Certificate{cert}, + } + + realTLSConfig = &config +} + // handleRealTLS handles the TLS request legitimately using crypto/tls func handleRealTLS(moduleName string, conn net.Conn, nasAddr string, privKeyPath string, certsPath string) { // Recover from panics @@ -767,30 +807,13 @@ func handleRealTLS(moduleName string, conn net.Conn, nasAddr string, privKeyPath } }() - // Read server key and certs - // TODO: Cache this - serverKey, err := os.ReadFile(privKeyPath) - if err != nil { - panic(err) + if realTLSConfig == nil { + return } - serverCerts, err := os.ReadFile(certsPath) - if err != nil { - panic(err) - } + tlsConn := tls.Server(conn, realTLSConfig) - cert, err := tls.X509KeyPair(serverCerts, serverKey) - if err != nil { - panic(err) - } - - config := tls.Config{ - Certificates: []tls.Certificate{cert}, - } - - tlsConn := tls.Server(conn, &config) - - err = tlsConn.Handshake() + err := tlsConn.Handshake() if err != nil { return } @@ -959,7 +982,7 @@ func keysFromMasterSecret(version uint16, masterSecret, clientRandom, serverRand if version == VersionSSL30 { prf = prf30 } - + seed := make([]byte, 0, len(serverRandom)+len(clientRandom)) seed = append(seed, serverRandom...) seed = append(seed, clientRandom...) @@ -1104,7 +1127,7 @@ func macMD5(version uint16, key []byte) macFunction { // tls10MAC implements the TLS 1.0 MAC function. RFC 2246, Section 6.2.3. type tls10MAC struct { - h hash.Hash + h hash.Hash } func (s tls10MAC) MAC(out, seq, header, data, extra []byte) []byte { @@ -1151,4 +1174,3 @@ func (s ssl30MAC) MAC(out, seq, header, data []byte, extra []byte) []byte { s.h.Write(out) return s.h.Sum(out[:0]) } - diff --git a/nas/main.go b/nas/main.go index 8a95abf..4e6e650 100644 --- a/nas/main.go +++ b/nas/main.go @@ -9,6 +9,7 @@ import ( "strings" "wwfc/api" "wwfc/common" + "wwfc/gamestats" "wwfc/logging" "wwfc/nhttp" "wwfc/sake" @@ -20,6 +21,8 @@ import ( var ( ctx = context.Background() pool *pgxpool.Pool + + serverName string ) func StartServer() { @@ -38,6 +41,8 @@ func StartServer() { panic(err) } + serverName = config.ServerName + address := *config.NASAddress + ":" + config.NASPort if config.EnableHTTPS { @@ -49,6 +54,7 @@ func StartServer() { } var regexSakeHost = regexp.MustCompile(`^([a-z\-]+\.)?sake\.gs\.`) +var regexGamestatsHost = regexp.MustCompile(`^([a-z\-]+\.)?gamestats2?\.gs\.`) var regexStage1URL = regexp.MustCompile(`^/w([0-9])$`) func handleRequest(w http.ResponseWriter, r *http.Request) { @@ -59,6 +65,13 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { return } + // Check for *.gamestats(2).gs.* or gamestats(2).gs.* + if regexGamestatsHost.MatchString(r.Host) { + // Redirect to the gamestats server + gamestats.HandleRequest(w, r) + return + } + moduleName := "NAS:" + r.RemoteAddr // Handle conntest server @@ -67,6 +80,37 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { return } + // Handle DWC auth requests + if r.URL.String() == "/ac" || r.URL.String() == "/pr" || r.URL.String() == "/download" { + handleAuthRequest(moduleName, w, r) + return + } + + // Handle /nastest.jsp + if r.URL.Path == "/nastest.jsp" { + handleNASTest(w) + return + } + + // Check for /payload + if strings.HasPrefix(r.URL.String(), "/payload") { + logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr)) + handlePayloadRequest(moduleName, w, r) + return + } + + // Stage 1 + if match := regexStage1URL.FindStringSubmatch(r.URL.String()); match != nil { + val, err := strconv.Atoi(match[1]) + if err != nil { + panic(err) + } + + logging.Info("NAS", "Get stage 1:", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr)) + downloadStage1(w, val) + return + } + // Check for /api/groups if r.URL.Path == "/api/groups" { api.HandleGroups(w, r) @@ -97,43 +141,42 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { return } - if r.URL.String() == "/ac" || r.URL.String() == "/pr" || r.URL.String() == "/download" { - handleAuthRequest(moduleName, w, r) - return - } - - // Check for /payload - if strings.HasPrefix(r.URL.String(), "/payload") { - handlePayloadRequest(moduleName, w, r) - return - } - - // Stage 1 - if match := regexStage1URL.FindStringSubmatch(r.URL.String()); match != nil { - val, err := strconv.Atoi(match[1]) - if err != nil { - panic(err) - } - - downloadStage1(w, val) - return - } - logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr)) replyHTTPError(w, 404, "404 Not Found") } func replyHTTPError(w http.ResponseWriter, errorCode int, errorString string) { - response := "\n" - response += "