From dbed54993e427fb5dec6962923b205f67480636a Mon Sep 17 00:00:00 2001 From: Palapeli <26661008+mkwcat@users.noreply.github.com> Date: Thu, 9 Apr 2026 07:58:20 -0400 Subject: [PATCH] NAS: Use specialized TLS connection instead of piping data --- nas/listener.go | 47 ++++---------- nas/tls.go | 158 +++++++++++++++++++++++++----------------------- 2 files changed, 96 insertions(+), 109 deletions(-) diff --git a/nas/listener.go b/nas/listener.go index eaeb920..3c94fb4 100644 --- a/nas/listener.go +++ b/nas/listener.go @@ -23,12 +23,6 @@ type nasInConn struct { in io.Reader } -type nasIOConn struct { - net.Conn - in io.Reader - out io.Writer -} - func (c nasInConn) Read(b []byte) (int, error) { return c.in.Read(b) } func (c nasInConn) Close() error { @@ -39,21 +33,6 @@ func (c nasInConn) Close() error { return err } -func (c nasIOConn) Read(b []byte) (int, error) { return c.in.Read(b) } - -func (c nasIOConn) Write(b []byte) (int, error) { return c.out.Write(b) } - -func (c nasIOConn) Close() error { - // Don't actually close the underlying connection here - if closer, ok := c.in.(io.Closer); ok { - _ = closer.Close() - } - if closer, ok := c.out.(io.Closer); ok { - _ = closer.Close() - } - return nil -} - func (l *httpListener) Accept() (net.Conn, error) { conn, err := l.Listener.Accept() if err != nil { @@ -103,25 +82,23 @@ func (l *tlsListener) Accept() (net.Conn, error) { return nil, err } - // We're gonna need like 3 pipes here, from client -> tls -> filter -> server - readFromServer, writeFromServer := io.Pipe() - readToServer, writeToServer := io.Pipe() - readToFilter, writeToFilter := io.Pipe() + tlsConn := tlsConnection{ + Conn: conn, + } + pr, pw := io.Pipe() go func() { - r := bufio.NewReader(readToFilter) - if err := filterDuplicateHost(writeToServer, r); err != nil { - _ = writeToServer.CloseWithError(err) + r := bufio.NewReader(&tlsConn) + if err := filterDuplicateHost(pw, r); err != nil { + _ = pw.CloseWithError(err) return } - _, err := io.Copy(writeToServer, r) - _ = writeToServer.CloseWithError(err) + _, err := io.Copy(pw, r) + _ = pw.CloseWithError(err) }() - go handleIncomingTLS(conn, readFromServer, writeToFilter) - return &nasIOConn{ - Conn: conn, - in: readToServer, - out: writeFromServer, + return &nasInConn{ + Conn: &tlsConn, + in: pr, }, nil } diff --git a/nas/tls.go b/nas/tls.go index 36fe3c8..9481091 100644 --- a/nas/tls.go +++ b/nas/tls.go @@ -30,7 +30,7 @@ import ( // 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 +// Don't use this TLS implementation for anything else, it's not secure var ( rsaKeyWii *rsa.PrivateKey @@ -226,43 +226,39 @@ func setupExploitDS(config common.Config) { }...) } -// handleIncomingTLS handles incoming requests from the listener. -func handleIncomingTLS(rawConn net.Conn, fromServer *io.PipeReader, toServer *io.PipeWriter) { - moduleName := "NAS-TLS:" + rawConn.RemoteAddr().String() - - var err error - closed := false - if rsaKeyWii == nil && rsaKeyDS == nil { - // Only handle real TLS requests - err, closed = proxyRealTLS(rawConn, fromServer, toServer) - } else { - err, closed = handleTLS(moduleName, rawConn, fromServer, toServer) - } - - toServer.CloseWithError(err) - if !closed { - _ = rawConn.Close() - } +type tlsConnection struct { + net.Conn + handshake bool + tlsConn io.ReadWriter } -var errUnexpectedBytes = errors.New("unexpected bytes") - -func readBytes(r io.Reader, expected []byte, consumed []byte) ([]byte, error) { - index := len(consumed) - buf := make([]byte, len(expected)) - copy(buf, consumed) - - for index < len(expected) { - n, err := r.Read(buf[index:]) - if err != nil { - return buf[:index+n], err +func (c *tlsConnection) Read(b []byte) (int, error) { + if !c.handshake { + if err := c.handleTLSHandshake(); err != nil { + return 0, err } - if !bytes.Equal(buf[index:index+n], expected[index:index+n]) { - return buf[:index+n], errUnexpectedBytes - } - index += n } - return buf, nil + + return c.tlsConn.Read(b) +} + +func (c *tlsConnection) Write(b []byte) (int, error) { + if !c.handshake { + if err := c.handleTLSHandshake(); err != nil { + return 0, err + } + } + + return c.tlsConn.Write(b) +} + +func (c *tlsConnection) Close() error { + if c.tlsConn != nil { + if tlsConn, ok := c.tlsConn.(*tls.Conn); ok { + return tlsConn.Close() + } + } + return c.Conn.Close() } var ( @@ -276,31 +272,39 @@ var ( } ) -// 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, conn net.Conn, fromServer *io.PipeReader, toServer *io.PipeWriter) (error, bool) { - // Recover from panics +// handleTLSHandshake handles the TLS request from the Wii or the DS, and creates tlsConn for further communication. +// It may call handleRealTLS if the request is from a modern web browser. +func (c *tlsConnection) handleTLSHandshake() error { + moduleName := "NAS-TLS:" + c.Conn.RemoteAddr().String() + + // Recover from panics. TODO: is this really necessary? defer func() { if r := recover(); r != nil { logging.Error(moduleName, "Panic:", r) } }() + if rsaKeyDS == nil && rsaKeyWii == nil { + // Only handle real TLS connections + return c.handleRealTLSHandshake(c.Conn) + } + // Read client hello var peekMatchWii, peekMatchDS bool consumed := []byte{} var err error if rsaKeyDS != nil { - consumed, err = readBytes(conn, dsClientHelloPrefix, consumed) + consumed, err = readBytes(c.Conn, dsClientHelloPrefix, consumed) peekMatchDS = err == nil } if rsaKeyWii != nil && err == errUnexpectedBytes { - consumed, err = readBytes(conn, wiiClientHelloPrefix, consumed) + consumed, err = readBytes(c.Conn, wiiClientHelloPrefix, consumed) peekMatchWii = err == nil } if err != nil && err != errUnexpectedBytes { - return err, false + return err } var macFn macFunction @@ -308,58 +312,53 @@ func handleTLS(moduleName string, conn net.Conn, fromServer *io.PipeReader, toSe var version uint16 switch { case peekMatchWii: - macFn, cipher, clientCipher, err = handleWiiTLSHandshake(moduleName, conn) + macFn, cipher, clientCipher, err = handleWiiTLSHandshake(moduleName, c.Conn) version = VersionTLS10 case peekMatchDS: - macFn, cipher, clientCipher, err = handleDSSSLHandshake(moduleName, conn) + macFn, cipher, clientCipher, err = handleDSSSLHandshake(moduleName, c.Conn) version = VersionSSL30 case err == errUnexpectedBytes: - return proxyRealTLS(nasInConn{ - Conn: conn, - in: io.MultiReader(bytes.NewReader(consumed), conn), - }, fromServer, toServer) + return c.handleRealTLSHandshake(nasInConn{ + Conn: c.Conn, + in: io.MultiReader(bytes.NewReader(consumed), c.Conn), + }) } if err != nil { - return err, false + return err } if macFn == nil || cipher == nil || clientCipher == nil { - return errors.New("invalid TLS handshake result"), false + return errors.New("invalid TLS handshake result") } - tlsConn := &consoleTLSConn{ - Conn: conn, + c.tlsConn = &consoleTLSConn{ + Conn: c.Conn, MacFn: macFn, Cipher: cipher, ClientCipher: clientCipher, Version: version, } - return tlsConn.proxyConsoleTLS(fromServer, toServer) + c.handshake = true + return nil } -// proxyRealTLS handles the TLS request legitimately using crypto/tls -func proxyRealTLS(rawConn net.Conn, fromServer *io.PipeReader, toServer *io.PipeWriter) (err error, closed bool) { - defer toServer.CloseWithError(err) - +// handleRealTLSHandshake handles the TLS request handshake legitimately using crypto/tls, and creates tlsConn +func (c *tlsConnection) handleRealTLSHandshake(rawConn net.Conn) error { if realTLSConfig == nil { - return errors.New("realTLSConfig is not set"), false + return errors.New("realTLSConfig is not set") } tlsConn := tls.Server(rawConn, realTLSConfig) - err = tlsConn.Handshake() + err := tlsConn.Handshake() if err != nil { _ = tlsConn.Close() - return err, false + return err } - - go func() { - _, err = io.Copy(tlsConn, fromServer) - _ = tlsConn.Close() - }() - _, err = io.Copy(toServer, tlsConn) - return err, true + c.tlsConn = tlsConn + c.handshake = true + return nil } type consoleTLSConn struct { @@ -434,15 +433,6 @@ func (c *consoleTLSConn) Write(b []byte) (n int, err error) { return c.Conn.Write(record) } -func (c *consoleTLSConn) proxyConsoleTLS(fromServer *io.PipeReader, toServer *io.PipeWriter) (err error, closed bool) { - go func() { - _, err = io.Copy(c, fromServer) - _ = c.Close() - }() - _, err = io.Copy(toServer, c) - return err, true -} - func handleWiiTLSHandshake(moduleName string, conn io.ReadWriter) (macFn macFunction, cipher *rc4.Cipher, clientCipher *rc4.Cipher, err error) { clientHello := append(wiiClientHelloPrefix, make([]byte, 0x2-len(wiiClientHelloPrefix))...) _, err = io.ReadFull(conn, clientHello[len(wiiClientHelloPrefix):]) @@ -767,6 +757,26 @@ func handleDSSSLHandshake(moduleName string, conn io.ReadWriter) (macFn macFunct return } +var errUnexpectedBytes = errors.New("unexpected bytes") + +func readBytes(r io.Reader, expected []byte, consumed []byte) ([]byte, error) { + index := len(consumed) + buf := make([]byte, len(expected)) + copy(buf, consumed) + + for index < len(expected) { + n, err := r.Read(buf[index:]) + if err != nil { + return buf[:index+n], err + } + if !bytes.Equal(buf[index:index+n], expected[index:index+n]) { + return buf[:index+n], errUnexpectedBytes + } + index += n + } + return buf, nil +} + // The following functions are modified from the crypto standard library // // Copyright (c) 2009 The Go Authors. All rights reserved.