diff --git a/common/config.go b/common/config.go index 466ce97..1623860 100644 --- a/common/config.go +++ b/common/config.go @@ -17,12 +17,16 @@ type Config struct { NASAddressHTTPS *string `xml:"nasAddressHttps,omitempty"` NASPortHTTPS string `xml:"nasPortHttps"` EnableHTTPS bool `xml:"enableHttps"` - EnableHTTPSExploit *bool `xml:"enableHttpsExploit,omitempty"` + EnableHTTPSExploitWii *bool `xml:"enableHttpsExploitWii,omitempty"` + EnableHTTPSExploitDS *bool `xml:"enableHttpsExploitDS,omitempty"` LogLevel *int `xml:"logLevel"` CertPath string `xml:"certPath"` KeyPath string `xml:"keyPath"` CertPathWii string `xml:"certDerPathWii"` KeyPathWii string `xml:"keyPathWii"` + CertPathDS string `xml:"certDerPathDS"` + WiiCertPathDS string `xml:"wiiCertDerPathDS"` + KeyPathDS string `xml:"keyPathDS"` APISecret string `xml:"apiSecret"` AllowDefaultDolphinKeys bool `xml:"allowDefaultDolphinKeys"` } @@ -57,9 +61,14 @@ func GetConfig() Config { } } - if config.EnableHTTPSExploit == nil { + if config.EnableHTTPSExploitWii == nil { enable := true - config.EnableHTTPSExploit = &enable + config.EnableHTTPSExploitWii = &enable + } + + if config.EnableHTTPSExploitDS == nil { + enable := true + config.EnableHTTPSExploitDS = &enable } if config.LogLevel == nil { diff --git a/config_example.xml b/config_example.xml index 376ff3e..02a44f3 100644 --- a/config_example.xml +++ b/config_example.xml @@ -10,7 +10,8 @@ 127.0.0.1 443 false - false + false + false fullchain.pem @@ -20,6 +21,11 @@ naswii-cert.der naswii-key.pem + + nas-cert.der + nwc.der + nas-key.pem + true diff --git a/nas/auth.go b/nas/auth.go index 8aced28..e0b7e85 100644 --- a/nas/auth.go +++ b/nas/auth.go @@ -30,6 +30,22 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request replyHTTPError(w, 400, "400 Bad Request") return } + + // Need to know this here to determine UTF-16 endianness (LE for DS, BE for Wii) + // unitcd 0 = DS, 1 = Wii + unitcdValues, ok := r.PostForm["unitcd"] + if !ok { + logging.Error(moduleName, "No unitcd in form") + replyHTTPError(w, 400, "400 Bad Request") + return + } + unitcdDecoded, err := common.Base64DwcEncoding.DecodeString(unitcdValues[0]) + if err != nil { + logging.Error(moduleName, "Invalid unitcd string in form") + replyHTTPError(w, 400, "400 Bad Request") + return + } + unitcdString := string(unitcdDecoded) fields := map[string]string{} for key, values := range r.PostForm { @@ -50,8 +66,14 @@ func handleAuthRequest(moduleName string, w http.ResponseWriter, r *http.Request if key == "ingamesn" { // Special handling required for the UTF-16 string var utf16String []uint16 - for i := 0; i < len(parsed)/2; i++ { - utf16String = append(utf16String, binary.BigEndian.Uint16(parsed[i*2:i*2+2])) + if unitcdString == "0" { + for i := 0; i < len(parsed)/2; i++ { + utf16String = append(utf16String, binary.LittleEndian.Uint16(parsed[i*2:i*2+2])) + } + } else { + for i := 0; i < len(parsed)/2; i++ { + utf16String = append(utf16String, binary.BigEndian.Uint16(parsed[i*2:i*2+2])) + } } value = string(utf16.Decode(utf16String)) } else { @@ -201,32 +223,6 @@ func login(moduleName string, fields map[string]string, isLocalhost bool) map[st return param } - cfc, ok := fields["cfc"] - if !ok { - logging.Error(moduleName, "No cfc in form") - param["returncd"] = "103" - return param - } - - cfcInt, err := strconv.ParseUint(cfc, 10, 64) - if err != nil || cfcInt > 9999999999999999 { - logging.Error(moduleName, "Invalid cfc string in form") - param["returncd"] = "103" - return param - } - - region, ok := fields["region"] - if !ok { - region = "ff" - } - - regionByte, err := hex.DecodeString(region) - if err != nil || len(regionByte) != 1 { - logging.Error(moduleName, "Invalid region byte in form") - param["returncd"] = "103" - return param - } - lang, ok := fields["lang"] if !ok { lang = "ff" @@ -239,12 +235,78 @@ func login(moduleName string, fields map[string]string, isLocalhost bool) map[st return param } - authToken, challenge := common.MarshalNASAuthToken(gamecd, userId, gsbrcd, cfcInt, regionByte[0], langByte[0], fields["ingamesn"], isLocalhost) - logging.Notice(moduleName, "Login", aurora.Cyan(strconv.FormatUint(userId, 10)), aurora.Cyan(gsbrcd), "ingamesn:", aurora.Cyan(fields["ingamesn"])) + unitcd, ok := fields["unitcd"] + if !ok { + logging.Error(moduleName, "No unitcd in form") + param["returncd"] = "103" + return param + } + + unitcdInt, err := strconv.ParseUint(unitcd, 10, 64) + if err != nil || unitcdInt > 1 { + logging.Error(moduleName, "Invalid unitcd string in form") + param["returncd"] = "103" + return param + } + + var authToken, challenge string + + switch unitcdInt { + // ds + case 0: + devname, ok := fields["devname"] + if !ok { + logging.Error(moduleName, "No devname in form") + param["returncd"] = "103" + return param + } + + // Only later DS games send this + ingamesn, ok := fields["ingamesn"] + if ok { + authToken, challenge = common.MarshalNASAuthToken(gamecd, userId, gsbrcd, 0, 0, langByte[0], ingamesn, isLocalhost) + logging.Notice(moduleName, "Login (DS)", aurora.Cyan(strconv.FormatUint(userId, 10)), aurora.Cyan(gsbrcd), "devname:", aurora.Cyan(devname), "ingamesn:", aurora.Cyan(ingamesn)) + } else { + authToken, challenge = common.MarshalNASAuthToken(gamecd, userId, gsbrcd, 0, 0, langByte[0], "", isLocalhost) + logging.Notice(moduleName, "Login (DS)", aurora.Cyan(strconv.FormatUint(userId, 10)), aurora.Cyan(gsbrcd), "devname:", aurora.Cyan(devname)) + } + + // wii + case 1: + cfc, ok := fields["cfc"] + if !ok { + logging.Error(moduleName, "No cfc in form") + param["returncd"] = "103" + return param + } + + cfcInt, err := strconv.ParseUint(cfc, 10, 64) + if err != nil || cfcInt > 9999999999999999 { + logging.Error(moduleName, "Invalid cfc string in form") + param["returncd"] = "103" + return param + } + + region, ok := fields["region"] + if !ok { + region = "ff" + } + + regionByte, err := hex.DecodeString(region) + if err != nil || len(regionByte) != 1 { + logging.Error(moduleName, "Invalid region byte in form") + param["returncd"] = "103" + return param + } + + authToken, challenge = common.MarshalNASAuthToken(gamecd, userId, gsbrcd, cfcInt, regionByte[0], langByte[0], fields["ingamesn"], isLocalhost) + logging.Notice(moduleName, "Login (Wii)", aurora.Cyan(strconv.FormatUint(userId, 10)), aurora.Cyan(gsbrcd), "ingamesn:", aurora.Cyan(fields["ingamesn"])) + } param["returncd"] = "001" param["challenge"] = challenge param["token"] = authToken + return param } diff --git a/nas/https.go b/nas/https.go index 20cd851..d276b2b 100644 --- a/nas/https.go +++ b/nas/https.go @@ -47,6 +47,7 @@ func (b bufferedConn) Read(p []byte) (int, error) { // Bare minimum TLS 1.0 server implementation for the Wii's /dev/net/ssl client // Use this with a certificate that exploits the Wii's SSL certificate bug to impersonate naswii.nintendowifi.net // See here: https://github.com/shutterbug2000/wii-ssl-bug +// https://github.com/KaeruTeam/nds-constraint // Don't use this for anything else, it's not secure @@ -55,6 +56,8 @@ func startHTTPSProxy(config common.Config) { nasAddr := *config.NASAddress + ":" + config.NASPort privKeyPath := config.KeyPath certsPath := config.CertPath + exploitWii := *config.EnableHTTPSExploitWii + exploitDS := *config.EnableHTTPSExploitDS logging.Notice("NAS-TLS", "Starting HTTPS server on", address) l, err := net.Listen("tcp", address) @@ -62,7 +65,7 @@ func startHTTPSProxy(config common.Config) { panic(err) } - if !*config.EnableHTTPSExploit { + if !(exploitWii || exploitDS) { // Only handle real TLS requests for { conn, err := l.Accept() @@ -78,63 +81,143 @@ func startHTTPSProxy(config common.Config) { } } - // Handle requests from Wii and regular TLS - cert, err := os.ReadFile(config.CertPathWii) - if err != nil { - panic(err) + // Handle requests from Wii, DS and regular TLS + var rsaKeyWii *rsa.PrivateKey + var serverCertsRecordWii []byte + if exploitWii { + certWii, err := os.ReadFile(config.CertPathWii) + if err != nil { + panic(err) + } + + rsaDataWii, err := os.ReadFile(config.KeyPathWii) + if err != nil { + panic(err) + } + + rsaBlockWii, _ := pem.Decode(rsaDataWii) + parsedKeyWii, err := x509.ParsePKCS8PrivateKey(rsaBlockWii.Bytes) + if err != nil { + panic(err) + } + + var ok bool + rsaKeyWii, ok = parsedKeyWii.(*rsa.PrivateKey) + if !ok { + panic("unexpected key type") + } + + serverCertsRecordWii = []byte{0x16, 0x03, 0x01} + + // Length of the record + certLenWii := uint32(len(certWii)) + serverCertsRecordWii = append(serverCertsRecordWii, []byte{ + byte((certLenWii + 10) >> 8), + byte(certLenWii + 10), + }...) + + serverCertsRecordWii = append(serverCertsRecordWii, 0xB) + + serverCertsRecordWii = append(serverCertsRecordWii, []byte{ + byte((certLenWii + 6) >> 16), + byte((certLenWii + 6) >> 8), + byte(certLenWii + 6), + }...) + + serverCertsRecordWii = append(serverCertsRecordWii, []byte{ + byte((certLenWii + 3) >> 16), + byte((certLenWii + 3) >> 8), + byte(certLenWii + 3), + }...) + + serverCertsRecordWii = append(serverCertsRecordWii, []byte{ + byte(certLenWii >> 16), + byte(certLenWii >> 8), + byte(certLenWii), + }...) + + serverCertsRecordWii = append(serverCertsRecordWii, certWii...) + + serverCertsRecordWii = append(serverCertsRecordWii, []byte{ + 0x16, 0x03, 0x01, 0x00, 0x04, 0x0E, 0x00, 0x00, 0x00, + }...) } - rsaData, err := os.ReadFile(config.KeyPathWii) - if err != nil { - panic(err) + var rsaKeyDS *rsa.PrivateKey + var serverCertsRecordDS []byte + if exploitDS { + certDS, err := os.ReadFile(config.CertPathDS) + if err != nil { + panic(err) + } + + rsaDataDS, err := os.ReadFile(config.KeyPathDS) + if err != nil { + panic(err) + } + + rsaBlockDS, _ := pem.Decode(rsaDataDS) + parsedKeyDS, err := x509.ParsePKCS8PrivateKey(rsaBlockDS.Bytes) + if err != nil { + panic(err) + } + + var ok bool + rsaKeyDS, ok = parsedKeyDS.(*rsa.PrivateKey) + if !ok { + panic("unexpected key type") + } + + wiiCertDS, err := os.ReadFile(config.WiiCertPathDS) + if err != nil { + panic(err) + } + + serverCertsRecordDS = []byte{0x16, 0x03, 0x00} + + // Length of the record + certLenDS := uint32(len(certDS)) + wiiCertLenDS := uint32(len(wiiCertDS)) + serverCertsRecordDS = append(serverCertsRecordDS, []byte{ + byte((certLenDS + wiiCertLenDS + 13) >> 8), + byte(certLenDS + wiiCertLenDS + 13), + }...) + + serverCertsRecordDS = append(serverCertsRecordDS, 0xB) + + serverCertsRecordDS = append(serverCertsRecordDS, []byte{ + byte((certLenDS + wiiCertLenDS + 9) >> 16), + byte((certLenDS + wiiCertLenDS + 9) >> 8), + byte(certLenDS + wiiCertLenDS + 9), + }...) + + serverCertsRecordDS = append(serverCertsRecordDS, []byte{ + byte((certLenDS + wiiCertLenDS + 6) >> 16), + byte((certLenDS + wiiCertLenDS + 6) >> 8), + byte(certLenDS + wiiCertLenDS + 6), + }...) + + serverCertsRecordDS = append(serverCertsRecordDS, []byte{ + byte(certLenDS >> 16), + byte(certLenDS >> 8), + byte(certLenDS), + }...) + + serverCertsRecordDS = append(serverCertsRecordDS, certDS...) + + serverCertsRecordDS = append(serverCertsRecordDS, []byte{ + byte(wiiCertLenDS >> 16), + byte(wiiCertLenDS >> 8), + byte(wiiCertLenDS), + }...) + + serverCertsRecordDS = append(serverCertsRecordDS, wiiCertDS...) + + serverCertsRecordDS = append(serverCertsRecordDS, []byte{ + 0x16, 0x03, 0x00, 0x00, 0x04, 0x0E, 0x00, 0x00, 0x00, + }...) } - rsaBlock, _ := pem.Decode(rsaData) - parsedKey, err := x509.ParsePKCS8PrivateKey(rsaBlock.Bytes) - if err != nil { - panic(err) - } - - rsaKey, ok := parsedKey.(*rsa.PrivateKey) - if !ok { - panic("unexpected key type") - } - - serverCertsRecord := []byte{0x16, 0x03, 0x01} - - // Length of the record - certLen := uint32(len(cert)) - serverCertsRecord = append(serverCertsRecord, []byte{ - byte((certLen + 10) >> 8), - byte(certLen + 10), - }...) - - serverCertsRecord = append(serverCertsRecord, 0xB) - - serverCertsRecord = append(serverCertsRecord, []byte{ - byte((certLen + 6) >> 16), - byte((certLen + 6) >> 8), - byte(certLen + 6), - }...) - - serverCertsRecord = append(serverCertsRecord, []byte{ - byte((certLen + 3) >> 16), - byte((certLen + 3) >> 8), - byte(certLen + 3), - }...) - - serverCertsRecord = append(serverCertsRecord, []byte{ - byte(certLen >> 16), - byte(certLen >> 8), - byte(certLen), - }...) - - serverCertsRecord = append(serverCertsRecord, cert...) - - serverCertsRecord = append(serverCertsRecord, []byte{ - 0x16, 0x03, 0x01, 0x00, 0x04, 0x0E, 0x00, 0x00, 0x00, - }...) - for { conn, err := l.Accept() if err != nil { @@ -144,12 +227,12 @@ func startHTTPSProxy(config common.Config) { logging.Info("NAS-TLS", "Receiving HTTPS request from", aurora.BrightCyan(conn.RemoteAddr())) moduleName := "NAS-TLS:" + conn.RemoteAddr().String() - go handleWiiTLS(moduleName, conn, nasAddr, privKeyPath, certsPath, serverCertsRecord, certLen, rsaKey) + go handleTLS(moduleName, conn, nasAddr, privKeyPath, certsPath, serverCertsRecordWii, rsaKeyWii, serverCertsRecordDS, rsaKeyDS) } } -// handleWiiTLS handles the TLS request from the Wii. It may call handleRealTLS if the request is from a modern web browser. -func handleWiiTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPath string, certsPath string, serverCertsRecord []byte, certLen uint32, rsaKey *rsa.PrivateKey) { +// handleTLS handles the TLS request from the Wii or the DS. It may call handleRealTLS if the request is from a modern web browser. +func handleTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPath string, certsPath string, serverCertsRecordWii []byte, rsaKeyWii *rsa.PrivateKey, serverCertsRecordDS []byte, rsaKeyDS *rsa.PrivateKey) { // Recover from panics defer func() { if r := recover(); r != nil { @@ -163,23 +246,60 @@ func handleWiiTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPa // Read client hello // fmt.Printf("Client Hello:\n") - for index := 0; index < 0x1D; index++ { - helloBytes, err := conn.Peek(index + 1) - if err != nil { - logging.Error(moduleName, "Failed to peek from client:", err) - return - } + var helloBytes []byte + if rsaKeyWii != nil { + index := 0 + for index = 0; index < 0x1D; index++ { + var err error + helloBytes, err = conn.Peek(index + 1) + if err != nil { + logging.Error(moduleName, "Failed to peek from client:", err) + return + } - if helloBytes[index] != []byte{ - 0x80, 0x2B, 0x01, 0x03, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, - 0x00, 0x35, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x09, 0x00, - 0x00, 0x05, 0x00, 0x00, 0x04, - }[index] { - logging.Info(moduleName, "Forwarding client hello:", aurora.Cyan(fmt.Sprintf("% X ", helloBytes))) - handleRealTLS(moduleName, conn, nasAddr, privKeyPath, certsPath) + if helloBytes[index] != []byte{ + 0x80, 0x2B, 0x01, 0x03, 0x01, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x35, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x09, 0x00, + 0x00, 0x05, 0x00, 0x00, 0x04, + }[index] { + break; + } + } + if (index == 0x1D) { + macFn, cipher, clientCipher := handleWiiTLSHandshake(moduleName, conn, serverCertsRecordWii, rsaKeyWii) + proxyConsoleTLS(moduleName, conn, nasAddr, VersionTLS10, macFn, cipher, clientCipher) return } } + + if rsaKeyDS != nil { + index := 0 + for index = 0; index < 0x0B; index++ { + var err error + helloBytes, err = conn.Peek(index + 1) + if err != nil { + logging.Error(moduleName, "Failed to peek from client:", err) + return + } + + if helloBytes[index] != []byte{ + 0x16, 0x03, 0x00, 0x00, 0x2F, 0x01, 0x00, 0x00, 0x2B, 0x03, 0x00, + }[index] { + break; + } + } + 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))) + handleRealTLS(moduleName, conn, nasAddr, privKeyPath, certsPath) +} + +func handleWiiTLSHandshake(moduleName string, conn bufferedConn, serverCertsRecord []byte, rsaKey *rsa.PrivateKey) (macFn macFunction, cipher *rc4.Cipher, clientCipher *rc4.Cipher) { // fmt.Printf("\n") clientHello := make([]byte, 0x2D) @@ -189,7 +309,7 @@ func handleWiiTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPa return } - finishHash := newFinishedHash() + finishHash := newFinishedHash(VersionTLS10) finishHash.Write(clientHello[0x2:0x2D]) // The random bytes are padded to 32 bytes with 0x00 (data is right justified) @@ -220,8 +340,8 @@ func handleWiiTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPa // fmt.Printf("Server Hello:\n% X\n", serverHello) finishHash.Write(serverHello[0x5:0x2F]) - finishHash.Write(serverHello[0x34 : 0x34+(certLen+10)]) - finishHash.Write(serverHello[0x34+(certLen+10)+5 : 0x34+(certLen+10)+5+4]) + finishHash.Write(serverHello[0x34 : 0x34+(len(serverCertsRecord)-14)]) + finishHash.Write(serverHello[0x34+(len(serverCertsRecord)-14)+5 : 0x34+(len(serverCertsRecord)-14)+5+4]) _, err = conn.Write(serverHello) if err != nil { @@ -304,7 +424,7 @@ func handleWiiTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPa // fmt.Printf("Master secret:\n% X\n", masterSecret) - _, serverMAC, clientKey, serverKey, _, _ := keysFromMasterSecret(masterSecret, clientRandom, serverRandom, 16, 16, 16) + _, serverMAC, clientKey, serverKey, _, _ := keysFromMasterSecret(VersionTLS10, masterSecret, clientRandom, serverRandom, 16, 16, 16) // fmt.Printf("Client MAC:\n% X\n", clientMAC) // fmt.Printf("Server MAC:\n% X\n", serverMAC) @@ -314,19 +434,19 @@ func handleWiiTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPa // fmt.Printf("Server IV:\n% X\n", serverIV) // Create the server RC4 cipher - cipher, err := rc4.NewCipher(serverKey) + cipher, err = rc4.NewCipher(serverKey) if err != nil { panic(err) } // Create the client RC4 cipher - clientCipher, err := rc4.NewCipher(clientKey) + clientCipher, err = rc4.NewCipher(clientKey) if err != nil { panic(err) } // Create the hmac cipher - macFn := hmac.New(md5.New, serverMAC) + macFn = macMD5(VersionTLS10, serverMAC) // Create the hmac cipher // clientMacFn := hmac.New(md5.New, clientMAC) @@ -352,6 +472,183 @@ func handleWiiTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPa _, err = conn.Write(finishedRecord) + return +} + +func handleDSSSLHandshake(moduleName string, conn bufferedConn, serverCertsRecord []byte, rsaKey *rsa.PrivateKey) (macFn macFunction, cipher *rc4.Cipher, clientCipher *rc4.Cipher) { + clientHello := make([]byte, 0x34) + _, err := io.ReadFull(conn.r, clientHello) + if err != nil { + logging.Error(moduleName, "Failed to read from client:", err) + return + } + + finishHash := newFinishedHash(VersionSSL30) + finishHash.Write(clientHello[0x5:0x34]) + + clientRandom := clientHello[0x0b:0x0b+0x20] + + serverHello := []byte{0x16, 0x03, 0x00, 0x00, 0x2A, 0x02, 0x00, 0x00, 0x26, 0x03, 0x00} + + serverRandom := make([]byte, 0x20) + _, err = rand.Read(serverRandom) + if err != nil { + logging.Error(moduleName, "Failed to generate random bytes:", err) + return + } + + serverHello = append(serverHello, serverRandom...) + + // Send an empty session ID + serverHello = append(serverHello, 0x00) + + // Select cipher suite TLS_RSA_WITH_RC4_128_MD5 (0x0004) + serverHello = append(serverHello, []byte{ + 0x00, 0x04, 0x00, + }...) + + // Append the certs record to the server hello buffer + serverHello = append(serverHello, serverCertsRecord...) + + // fmt.Printf("Server Hello:\n% X\n", serverHello) + + finishHash.Write(serverHello[0x5:0x2F]) + finishHash.Write(serverHello[0x34 : 0x34+(len(serverCertsRecord)-14)]) + finishHash.Write(serverHello[0x34+(len(serverCertsRecord)-14)+5 : 0x34+(len(serverCertsRecord)-14)+5+4]) + + _, err = conn.Write(serverHello) + if err != nil { + logging.Error(moduleName, "Failed to write to client:", err) + return + } + + // fmt.Printf("Client key exchange:\n") + buf := make([]byte, 0x1000) + index := 0 + // Read client key exchange (+ change cipher spec + finished) + for { + n, err := conn.Read(buf[index:]) + if err != nil { + logging.Error(moduleName, "Failed to read from client:", err) + return + } + + // fmt.Printf("% X ", buf[index:index+n]) + index += n + + // Check client key exchange header + if !bytes.HasPrefix([]byte{ + 0x16, 0x03, 0x00, 0x00, 0x84, 0x10, 0x00, 0x00, 0x80, + }, buf[:min(index, 0x09)]) { + logging.Error(moduleName, "Invalid client key exchange header:", aurora.Cyan(fmt.Sprintf("% X ", buf[:min(index, 0x09)]))) + return + } + + if index > 0x8B { + // Check change cipher spec + finished header + if !bytes.HasPrefix(buf[0x89:min(index, 0x89+0x0B)], []byte{ + 0x14, 0x03, 0x00, 0x00, 0x01, 0x01, 0x16, 0x03, 0x00, 0x00, 0x38, + }) { + logging.Error(moduleName, "Invalid client change cipher spec + finished header:", aurora.Cyan(fmt.Sprintf("%X ", buf[0x89:min(index, 0x89+0x0B)]))) + return + } + } + + if index == 0xCC { + buf = buf[:index] + break + } + + if index > 0xCC { + logging.Error(moduleName, "Invalid client key exchange length:", aurora.BrightCyan(index)) + return + } + } + // fmt.Printf("\n") + + encryptedPreMasterSecret := buf[0x09 : 0x09+0x80] + clientFinish := buf[0x94 : 0x94+0x38] + + finishHash.Write(buf[0x5 : 0x5+0x84]) + + // Decrypt the pre master secret using our RSA key + preMasterSecret, err := rsa.DecryptPKCS1v15(rand.Reader, rsaKey, encryptedPreMasterSecret) + if err != nil { + logging.Error(moduleName, "Failed to decrypt pre master secret:", err) + return + } + + // fmt.Printf("Pre master secret:\n% X\n", preMasterSecret) + + if len(preMasterSecret) != 48 { + logging.Error(moduleName, "Invalid pre master secret length:", aurora.BrightCyan(len(preMasterSecret))) + return + } + + if !bytes.Equal(preMasterSecret[:2], []byte{0x03, 0x00}) { + logging.Error(moduleName, "Invalid TLS version in pre master secret:", aurora.BrightCyan(preMasterSecret[:2])) + return + } + + clientServerRandom := append(bytes.Clone(clientRandom), serverRandom[:0x20]...) + + masterSecret := make([]byte, 48) + prf30(masterSecret, preMasterSecret, []byte("master secret"), clientServerRandom) + + // fmt.Printf("Master secret:\n% X\n", masterSecret) + + _, serverMAC, clientKey, serverKey, _, _ := keysFromMasterSecret(VersionSSL30, masterSecret, clientRandom, serverRandom, 16, 16, 16) + + // fmt.Printf("Client MAC:\n% X\n", clientMAC) + // fmt.Printf("Server MAC:\n% X\n", serverMAC) + // fmt.Printf("Client key:\n% X\n", clientKey) + // fmt.Printf("Server key:\n% X\n", serverKey) + // fmt.Printf("Client IV:\n% X\n", clientIV) + // fmt.Printf("Server IV:\n% X\n", serverIV) + + // Create the server RC4 cipher + cipher, err = rc4.NewCipher(serverKey) + if err != nil { + panic(err) + } + + // Create the client RC4 cipher + clientCipher, err = rc4.NewCipher(clientKey) + if err != nil { + panic(err) + } + + // Create the mac function + macFn = macMD5(VersionSSL30, serverMAC) + + // Create the mac function + // clientMacFn := macMD5(VersionSSL30, clientMAC) + + // Decrypt client finish + clientCipher.XORKeyStream(clientFinish, clientFinish) + finishHash.Write(clientFinish[:0x28]) + + // fmt.Printf("Client Finish:\n% X\n", clientFinish) + + // Send ChangeCipherSpec + _, err = conn.Write([]byte{0x14, 0x03, 0x00, 0x00, 0x01, 0x01}) + if err != nil { + panic(err) + } + + finishedRecord := []byte{0x16, 0x03, 0x00, 0x00, 0x28} + + out := finishHash.serverSum(masterSecret) + + // Encrypt the finished record + finishedRecord, _ = encryptTLS(macFn, cipher, append([]byte{0x14, 0x00, 0x00, 0x24}, out[:36]...), 0, finishedRecord) + + _, err = conn.Write(finishedRecord) + + return +} + +func proxyConsoleTLS(moduleName string, conn bufferedConn, nasAddr string, version uint16, macFn macFunction, cipher *rc4.Cipher, clientCipher *rc4.Cipher) { // Open a connection to NAS newConn, err := net.Dial("tcp", nasAddr) if err != nil { @@ -389,9 +686,9 @@ func handleWiiTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPa }() // Read encrypted content from the client and forward it to the HTTP server - index = 0 + index := 0 total := 0 - buf = make([]byte, 0x1000) + buf := make([]byte, 0x1000) for { n, err := conn.Read(buf[index:]) if err != nil { @@ -418,7 +715,7 @@ func handleWiiTLS(moduleName string, rawConn net.Conn, nasAddr string, privKeyPa return } - if buf[1] != 0x03 || buf[2] != 0x01 { + if (buf[1] != 0x03 || (version == VersionTLS10 && buf[2] != 0x01) || (version == VersionSSL30 && buf[2] != 0x00)) { logging.Error(moduleName, "Invalid TLS version") return } @@ -569,6 +866,11 @@ func handleRealTLS(moduleName string, conn net.Conn, nasAddr string, privKeyPath // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +const ( + VersionSSL30 = 0x0300 + VersionTLS10 = 0x0301 +) + // Split a premaster secret in two as specified in RFC 4346, Section 5. func splitPreMasterSecret(secret []byte) (s1, s2 []byte) { s1 = secret[0 : (len(secret)+1)/2] @@ -616,17 +918,55 @@ func prf10(result, secret, label, seed []byte) { } } +// prf30 implements the SSL 3.0 pseudo-random function, as defined in +// www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt section 6. +func prf30(result, secret, label, seed []byte) { + hashSHA1 := sha1.New() + hashMD5 := md5.New() + + done := 0 + i := 0 + // RFC5246 section 6.3 says that the largest PRF output needed is 128 + // bytes. Since no more ciphersuites will be added to SSLv3, this will + // remain true. Each iteration gives us 16 bytes so 10 iterations will + // be sufficient. + var b [11]byte + for done < len(result) { + for j := 0; j <= i; j++ { + b[j] = 'A' + byte(i) + } + + hashSHA1.Reset() + hashSHA1.Write(b[:i+1]) + hashSHA1.Write(secret) + hashSHA1.Write(seed) + digest := hashSHA1.Sum(nil) + + hashMD5.Reset() + hashMD5.Write(secret) + hashMD5.Write(digest) + + done += copy(result[done:], hashMD5.Sum(nil)) + i++ + } +} + // keysFromMasterSecret generates the connection keys from the master // secret, given the lengths of the MAC key, cipher key and IV, as defined in // RFC 2246, Section 6.3. -func keysFromMasterSecret(masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) { +func keysFromMasterSecret(version uint16, masterSecret, clientRandom, serverRandom []byte, macLen, keyLen, ivLen int) (clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV []byte) { + prf := prf10 + if version == VersionSSL30 { + prf = prf30 + } + seed := make([]byte, 0, len(serverRandom)+len(clientRandom)) seed = append(seed, serverRandom...) seed = append(seed, clientRandom...) n := 2*macLen + 2*keyLen + 2*ivLen keyMaterial := make([]byte, n) - prf10(keyMaterial, masterSecret, []byte("key expansion"), seed) + prf(keyMaterial, masterSecret, []byte("key expansion"), seed) clientMAC = keyMaterial[:macLen] keyMaterial = keyMaterial[macLen:] serverMAC = keyMaterial[:macLen] @@ -641,8 +981,8 @@ func keysFromMasterSecret(masterSecret, clientRandom, serverRandom []byte, macLe return } -func newFinishedHash() finishedHash { - return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), prf10} +func newFinishedHash(version uint16) finishedHash { + return finishedHash{sha1.New(), sha1.New(), md5.New(), md5.New(), version} } // A finishedHash calculates the hash of a set of handshake messages suitable @@ -655,7 +995,7 @@ type finishedHash struct { clientMD5 hash.Hash serverMD5 hash.Hash - prf func(result, secret, label, seed []byte) + version uint16 } func (h *finishedHash) Write(msg []byte) int { @@ -676,24 +1016,65 @@ func (h finishedHash) Sum() []byte { return h.client.Sum(out) } +// finishedSum30 calculates the contents of the verify_data member of a SSLv3 +// Finished message given the MD5 and SHA1 hashes of a set of handshake +// messages. +func finishedSum30(md5, sha1 hash.Hash, masterSecret []byte, magic [4]byte) []byte { + md5.Write(magic[:]) + md5.Write(masterSecret) + md5.Write(ssl30Pad1[:]) + md5Digest := md5.Sum(nil) + + md5.Reset() + md5.Write(masterSecret) + md5.Write(ssl30Pad2[:]) + md5.Write(md5Digest) + md5Digest = md5.Sum(nil) + + sha1.Write(magic[:]) + sha1.Write(masterSecret) + sha1.Write(ssl30Pad1[:40]) + sha1Digest := sha1.Sum(nil) + + sha1.Reset() + sha1.Write(masterSecret) + sha1.Write(ssl30Pad2[:40]) + sha1.Write(sha1Digest) + sha1Digest = sha1.Sum(nil) + + ret := make([]byte, len(md5Digest)+len(sha1Digest)) + copy(ret, md5Digest) + copy(ret[len(md5Digest):], sha1Digest) + return ret +} + // clientSum returns the contents of the verify_data member of a client's // Finished message. func (h finishedHash) clientSum(masterSecret []byte) []byte { + if h.version == VersionSSL30 { + return finishedSum30(h.clientMD5, h.client, masterSecret, [4]byte{0x43, 0x4c, 0x4e, 0x54}) + } + out := make([]byte, 12) - h.prf(out, masterSecret, []byte("client finished"), h.Sum()) + prf10(out, masterSecret, []byte("client finished"), h.Sum()) return out } // serverSum returns the contents of the verify_data member of a server's // Finished message. func (h finishedHash) serverSum(masterSecret []byte) []byte { + if h.version == VersionSSL30 { + return finishedSum30(h.serverMD5, h.server, masterSecret, [4]byte{0x53, 0x52, 0x56, 0x52}) + } + out := make([]byte, 12) - h.prf(out, masterSecret, []byte("server finished"), h.Sum()) + prf10(out, masterSecret, []byte("server finished"), h.Sum()) return out } -func encryptTLS(macFn hash.Hash, cipher *rc4.Cipher, payload []byte, seq uint64, record []byte) ([]byte, uint64) { - mac := tls10MAC(macFn, []byte{}, binary.BigEndian.AppendUint64([]byte{}, seq), record[:5], payload, nil) +func encryptTLS(macFn macFunction, cipher *rc4.Cipher, payload []byte, seq uint64, record []byte) ([]byte, uint64) { + mac := macFn.MAC([]byte{}, binary.BigEndian.AppendUint64([]byte{}, seq), record[:5], payload, nil) + record = append(append(bytes.Clone(record[:5]), payload...), mac...) cipher.XORKeyStream(record[5:], record[5:]) @@ -705,15 +1086,69 @@ func encryptTLS(macFn hash.Hash, cipher *rc4.Cipher, payload []byte, seq uint64, return record, seq + 1 } +type macFunction interface { + MAC(out, seq, header, data, extra []byte) []byte +} + +func macMD5(version uint16, key []byte) macFunction { + if version == VersionSSL30 { + mac := ssl30MAC{ + h: md5.New(), + key: make([]byte, len(key)), + } + copy(mac.key, key) + return mac + } + return tls10MAC{h: hmac.New(md5.New, key)} +} + // tls10MAC implements the TLS 1.0 MAC function. RFC 2246, Section 6.2.3. -func tls10MAC(h hash.Hash, out, seq, header, data, extra []byte) []byte { - h.Reset() - h.Write(seq) - h.Write(header) - h.Write(data) - res := h.Sum(out) +type tls10MAC struct { + h hash.Hash +} + +func (s tls10MAC) MAC(out, seq, header, data, extra []byte) []byte { + s.h.Reset() + s.h.Write(seq) + s.h.Write(header) + s.h.Write(data) + res := s.h.Sum(out) if extra != nil { - h.Write(extra) + s.h.Write(extra) } return res } + +// ssl30MAC implements the SSLv3 MAC function, as defined in +// www.mozilla.org/projects/security/pki/nss/ssl/draft302.txt section 5.2.3.1 +type ssl30MAC struct { + h hash.Hash + key []byte +} + +var ssl30Pad1 = [48]byte{0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36} + +var ssl30Pad2 = [48]byte{0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c} + +func (s ssl30MAC) MAC(out, seq, header, data []byte, extra []byte) []byte { + padLength := 48 + if s.h.Size() == 20 { + padLength = 40 + } + + s.h.Reset() + s.h.Write(s.key) + s.h.Write(ssl30Pad1[:padLength]) + s.h.Write(seq) + s.h.Write(header[:1]) + s.h.Write(header[3:5]) + s.h.Write(data) + out = s.h.Sum(out[:0]) + + s.h.Reset() + s.h.Write(s.key) + s.h.Write(ssl30Pad2[:padLength]) + s.h.Write(out) + return s.h.Sum(out[:0]) +} +