NAS: Use specialized TLS connection instead of piping data

This commit is contained in:
Palapeli
2026-04-09 07:58:20 -04:00
parent 1fcb4da3a3
commit dbed54993e
2 changed files with 96 additions and 109 deletions

View File

@@ -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
}

View File

@@ -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.