mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-03-21 17:44:58 -05:00
Send SBCM exploit to client + support dev auth after login
This commit is contained in:
parent
978a7d3956
commit
5710b20b28
|
|
@ -3,10 +3,11 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsbrcd string, profileId uint32, ngDeviceId uint32) (User, bool) {
|
||||
|
|
@ -52,7 +53,7 @@ func LoginUserToGPCM(pool *pgxpool.Pool, ctx context.Context, userId uint64, gsb
|
|||
user.LastName = *lastName
|
||||
}
|
||||
|
||||
if expectedNgId != nil {
|
||||
if expectedNgId != nil && user.NgDeviceId != 0 {
|
||||
user.NgDeviceId = *expectedNgId
|
||||
if ngDeviceId != 0 && user.NgDeviceId != ngDeviceId {
|
||||
logging.Error("DATABASE", "NG device ID mismatch for profile", aurora.Cyan(user.ProfileId), "- expected", aurora.Cyan(fmt.Sprintf("%08x", user.NgDeviceId)), "but got", aurora.Cyan(fmt.Sprintf("%08x", ngDeviceId)))
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ package database
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"math/rand"
|
||||
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -85,6 +86,15 @@ func (user *User) UpdateProfileID(pool *pgxpool.Pool, ctx context.Context, newPr
|
|||
return err
|
||||
}
|
||||
|
||||
func (user *User) UpdateDeviceID(pool *pgxpool.Pool, ctx context.Context, newDeviceId uint32) error {
|
||||
_, err := pool.Exec(ctx, UpdateUserNGDeviceID, user.ProfileId, newDeviceId)
|
||||
if err == nil {
|
||||
user.NgDeviceId = newDeviceId
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetUniqueUserID() uint64 {
|
||||
// Not guaranteed unique but doesn't matter in practice if multiple people have the same user ID.
|
||||
return uint64(rand.Int63n(0x80000000000))
|
||||
|
|
|
|||
|
|
@ -169,9 +169,7 @@ func (g *GameSpySession) authAddFriend(command common.GameSpyCommand) {
|
|||
func (g *GameSpySession) setStatus(command common.GameSpyCommand) {
|
||||
status := command.CommandValue
|
||||
|
||||
if g.QR2IP != 0 {
|
||||
qr2.ProcessGPStatusUpdate(g.QR2IP, status)
|
||||
}
|
||||
qr2.ProcessGPStatusUpdate(g.User.ProfileId, g.QR2IP, status)
|
||||
|
||||
statstring, ok := command.OtherValues["statstring"]
|
||||
if !ok {
|
||||
|
|
|
|||
|
|
@ -213,6 +213,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
sessions[g.User.ProfileId] = g
|
||||
mutex.Unlock()
|
||||
|
||||
g.AuthToken = authToken
|
||||
g.LoginTicket = common.MarshalGPCMLoginTicket(g.User.ProfileId)
|
||||
g.SessionKey = rand.Int31n(290000000) + 10000000
|
||||
g.GameCode = gamecd
|
||||
|
|
@ -224,7 +225,7 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
g.ModuleName += "/" + common.CalcFriendCodeString(g.User.ProfileId, "RMCJ")
|
||||
|
||||
// Notify QR2 of the login
|
||||
qr2.Login(g.User.ProfileId, ingamesn, cfc, g.Conn.RemoteAddr().String(), g.NeedsExploit, g.DeviceAuthenticated)
|
||||
qr2.Login(g.User.ProfileId, gamecd, ingamesn, cfc, g.Conn.RemoteAddr().String(), g.NeedsExploit, g.DeviceAuthenticated)
|
||||
|
||||
payload := common.CreateGameSpyMessage(common.GameSpyCommand{
|
||||
Command: "lc",
|
||||
|
|
@ -245,6 +246,42 @@ func (g *GameSpySession) login(command common.GameSpyCommand) {
|
|||
g.sendFriendRequests()
|
||||
}
|
||||
|
||||
func (g *GameSpySession) exLogin(command common.GameSpyCommand) {
|
||||
payloadVer, payloadVerExists := command.OtherValues["payload_ver"]
|
||||
signature, signatureExists := command.OtherValues["wwfc_sig"]
|
||||
deviceId := uint32(0)
|
||||
|
||||
if !payloadVerExists || payloadVer != "1" {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The payload version is invalid.",
|
||||
Fatal: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if !signatureExists {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "Missing authentication signature.",
|
||||
Fatal: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if deviceId = verifySignature(g.AuthToken, signature); deviceId == 0 {
|
||||
g.replyError(GPError{
|
||||
ErrorCode: ErrLogin.ErrorCode,
|
||||
ErrorString: "The authentication signature is invalid.",
|
||||
Fatal: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
g.DeviceAuthenticated = true
|
||||
qr2.SetDeviceAuthenticated(g.User.ProfileId)
|
||||
}
|
||||
|
||||
func IsLoggedIn(profileID uint32) bool {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type GameSpySession struct {
|
|||
LoggedIn bool
|
||||
DeviceAuthenticated bool
|
||||
Challenge string
|
||||
AuthToken string
|
||||
LoginTicket string
|
||||
SessionKey int32
|
||||
GameCode string
|
||||
|
|
@ -89,7 +90,7 @@ func (g *GameSpySession) closeSession() {
|
|||
if g.LoggedIn {
|
||||
qr2.Logout(g.User.ProfileId)
|
||||
if g.QR2IP != 0 {
|
||||
qr2.ProcessGPStatusUpdate(g.QR2IP, "0")
|
||||
qr2.ProcessGPStatusUpdate(g.User.ProfileId, g.QR2IP, "0")
|
||||
}
|
||||
g.sendLogoutStatus()
|
||||
}
|
||||
|
|
@ -105,7 +106,7 @@ func (g *GameSpySession) closeSession() {
|
|||
|
||||
// Handles incoming requests.
|
||||
func handleRequest(conn net.Conn) {
|
||||
session := GameSpySession{
|
||||
session := &GameSpySession{
|
||||
Conn: conn,
|
||||
User: database.User{},
|
||||
ModuleName: "GPCM",
|
||||
|
|
@ -172,6 +173,7 @@ func handleRequest(conn net.Conn) {
|
|||
session.Conn.Write([]byte(`\ka\\final\`))
|
||||
})
|
||||
commands = session.handleCommand("login", commands, session.login)
|
||||
commands = session.handleCommand("wwfc_exlogin", commands, session.exLogin)
|
||||
commands = session.ignoreCommand("logout", commands)
|
||||
|
||||
if len(commands) != 0 && session.LoggedIn == false {
|
||||
|
|
|
|||
48
qr2/group.go
48
qr2/group.go
|
|
@ -2,11 +2,12 @@ package qr2
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"strconv"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
|
|
@ -112,11 +113,48 @@ func ProcessGPResvOK(cmd common.MatchCommandDataResvOK, senderIP uint64, senderP
|
|||
return processResvOK(moduleName, cmd, from, to)
|
||||
}
|
||||
|
||||
func ProcessGPStatusUpdate(senderIP uint64, status string) {
|
||||
if status == "0" || status == "1" || status == "3" || status == "4" {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
func ProcessGPStatusUpdate(profileID uint32, senderIP uint64, status string) {
|
||||
moduleName := "QR2/GPStatus:" + strconv.FormatUint(uint64(profileID), 10)
|
||||
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
login, exists := logins[profileID]
|
||||
if !exists || login == nil {
|
||||
logging.Error(moduleName, "Received status update for non-existent profile ID", aurora.Cyan(profileID))
|
||||
return
|
||||
}
|
||||
|
||||
session := login.Session
|
||||
if session == nil {
|
||||
if senderIP == 0 {
|
||||
logging.Info(moduleName, "Received status update for profile ID", aurora.Cyan(profileID), "but no session exists")
|
||||
return
|
||||
}
|
||||
|
||||
// Login with this profile ID
|
||||
session, exists = sessions[senderIP]
|
||||
if !exists || session == nil {
|
||||
logging.Info(moduleName, "Received status update for profile ID", aurora.Cyan(profileID), "but no session exists")
|
||||
return
|
||||
}
|
||||
|
||||
if !session.setProfileID(moduleName, strconv.FormatUint(uint64(profileID), 10)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Send the client message exploit if not received yet
|
||||
if status != "0" && status != "1" && !session.ExploitReceived && session.Login != nil && session.Login.NeedsExploit {
|
||||
sessionCopy := *session
|
||||
|
||||
mutex.Unlock()
|
||||
logging.Notice(moduleName, "Sending SBCM exploit to DNS patcher client")
|
||||
sendClientExploit(moduleName, sessionCopy)
|
||||
mutex.Lock()
|
||||
}
|
||||
|
||||
if status == "0" || status == "1" || status == "3" || status == "4" {
|
||||
session := sessions[senderIP]
|
||||
if session == nil || session.GroupPointer == nil {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ package qr2
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"net"
|
||||
"strings"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
func heartbeat(moduleName string, conn net.PacketConn, addr net.Addr, buffer []byte) {
|
||||
|
|
@ -16,13 +17,20 @@ func heartbeat(moduleName string, conn net.PacketConn, addr net.Addr, buffer []b
|
|||
values := strings.Split(string(buffer[5:]), "\u0000")
|
||||
|
||||
payload := map[string]string{}
|
||||
unknowns := []string{}
|
||||
for i := 0; i < len(values); i += 2 {
|
||||
if len(values[i]) == 0 || values[i][0] == '+' {
|
||||
continue
|
||||
}
|
||||
|
||||
payload[values[i]] = values[i+1]
|
||||
logging.Info(moduleName, aurora.Cyan(values[i]).String()+":", aurora.Cyan(values[i+1]))
|
||||
|
||||
if values[i] == "unknown" {
|
||||
unknowns = append(unknowns, values[i+1])
|
||||
continue
|
||||
}
|
||||
|
||||
payload[values[i]] = values[i+1]
|
||||
}
|
||||
|
||||
realIP, realPort := common.IPFormatToString(addr.String())
|
||||
|
|
@ -41,7 +49,8 @@ func heartbeat(moduleName string, conn net.PacketConn, addr net.Addr, buffer []b
|
|||
|
||||
lookupAddr := makeLookupAddr(addr.String())
|
||||
|
||||
if statechanged, ok := payload["statechanged"]; ok {
|
||||
statechanged, ok := payload["statechanged"]
|
||||
if ok {
|
||||
if statechanged == "1" {
|
||||
// TODO: This would be a good place to run the server->client message exploit
|
||||
// for DNS patcher games that require code patches. The status code should be
|
||||
|
|
@ -62,9 +71,29 @@ func heartbeat(moduleName string, conn net.PacketConn, addr net.Addr, buffer []b
|
|||
return
|
||||
}
|
||||
|
||||
if payload["gamename"] == "mariokartwii" && len(unknowns) > 0 {
|
||||
// Try to login using the first unknown as a profile ID
|
||||
// This makes it possible to execute the exploit on the client sooner
|
||||
profileId := unknowns[0]
|
||||
logging.Notice(moduleName, "Attempting to use unknown as profile ID", aurora.Cyan(profileId))
|
||||
|
||||
mutex.Lock()
|
||||
session, sessionExists := sessions[lookupAddr]
|
||||
if !sessionExists {
|
||||
logging.Error(moduleName, "Session not found")
|
||||
} else {
|
||||
session.setProfileID(moduleName, profileId)
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
|
||||
if !session.Authenticated {
|
||||
logging.Notice(moduleName, "Sending challenge")
|
||||
sendChallenge(conn, addr, session, lookupAddr)
|
||||
return
|
||||
} else if !session.ExploitReceived && session.Login != nil && session.Login.NeedsExploit && statechanged == "1" {
|
||||
logging.Notice(moduleName, "Sending SBCM exploit to DNS patcher client")
|
||||
sendClientExploit(moduleName, session)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package qr2
|
|||
|
||||
type LoginInfo struct {
|
||||
ProfileID uint32
|
||||
GameCode string
|
||||
InGameName string
|
||||
ConsoleFriendCode uint64
|
||||
GPPublicIP string
|
||||
|
|
@ -12,11 +13,12 @@ type LoginInfo struct {
|
|||
|
||||
var logins = map[uint32]*LoginInfo{}
|
||||
|
||||
func Login(profileID uint32, inGameName string, consoleFriendCode uint64, publicIP string, needsExploit bool, deviceAuthenticated bool) {
|
||||
func Login(profileID uint32, gameCode string, inGameName string, consoleFriendCode uint64, publicIP string, needsExploit bool, deviceAuthenticated bool) {
|
||||
mutex.Lock()
|
||||
|
||||
logins[profileID] = &LoginInfo{
|
||||
ProfileID: profileID,
|
||||
GameCode: gameCode,
|
||||
InGameName: inGameName,
|
||||
ConsoleFriendCode: consoleFriendCode,
|
||||
GPPublicIP: publicIP,
|
||||
|
|
|
|||
17
qr2/main.go
17
qr2/main.go
|
|
@ -2,11 +2,12 @@ package qr2
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
"net"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
|
||||
"github.com/logrusorgru/aurora/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -21,6 +22,8 @@ const (
|
|||
KeepAliveRequest = 0x08
|
||||
AvailableRequest = 0x09
|
||||
ClientRegisteredReply = 0x0A
|
||||
|
||||
ClientExploitReply = 0x10
|
||||
)
|
||||
|
||||
var masterConn net.PacketConn
|
||||
|
|
@ -116,6 +119,10 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
|||
|
||||
case ClientMessageAckRequest:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("CLIENT_MESSAGE_ACK"))
|
||||
|
||||
// In case ClientExploitReply is lost, this can be checked as well
|
||||
// This would be sent either after the payload is downloaded, or the client is already patched
|
||||
session.ExploitReceived = true
|
||||
return
|
||||
|
||||
case KeepAliveRequest:
|
||||
|
|
@ -131,7 +138,13 @@ func handleConnection(conn net.PacketConn, addr net.Addr, buffer []byte) {
|
|||
return
|
||||
|
||||
case ClientRegisteredReply:
|
||||
logging.Notice(moduleName, "Command:", aurora.Cyan("CLIENT_REGISTERED"))
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("CLIENT_REGISTERED"))
|
||||
break
|
||||
|
||||
case ClientExploitReply:
|
||||
logging.Notice(moduleName, "Command:", aurora.Yellow("CLIENT_EXPLOIT_ACK"))
|
||||
|
||||
session.ExploitReceived = true
|
||||
break
|
||||
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import (
|
|||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
"wwfc/common"
|
||||
"wwfc/logging"
|
||||
|
||||
|
|
@ -271,3 +273,55 @@ func SendClientMessage(senderIP string, destSearchID uint64, message []byte) {
|
|||
logging.Error(moduleName, "Error sending message:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func sendClientExploit(moduleName string, sessionCopy Session) {
|
||||
if len(sessionCopy.Login.GameCode) != 4 || !common.IsUppercaseAlphanumeric(sessionCopy.Login.GameCode) {
|
||||
logging.Error(moduleName, "Invalid game code:", aurora.Cyan(sessionCopy.Login.GameCode))
|
||||
return
|
||||
}
|
||||
|
||||
exploit, err := os.ReadFile("payload/sbcm/" + "payload." + sessionCopy.Login.GameCode + ".bin")
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Error reading exploit file", aurora.Cyan(sessionCopy.Login.GameCode), "-", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
mutex.Lock()
|
||||
session, sessionExists := sessions[makeLookupAddr(sessionCopy.Addr.String())]
|
||||
if !sessionExists {
|
||||
logging.Error(moduleName, "Session not found")
|
||||
return
|
||||
}
|
||||
|
||||
packetCount := session.PacketCount + 1
|
||||
session.PacketCount = packetCount
|
||||
mutex.Unlock()
|
||||
|
||||
// Now send the exploit
|
||||
payload := createResponseHeader(ClientMessageRequest, sessionCopy.SessionID)
|
||||
payload = append(payload, []byte{0, 0, 0, 0}...)
|
||||
binary.BigEndian.PutUint32(payload[len(payload)-4:], packetCount)
|
||||
payload = append(payload, exploit[0xB:]...)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
_, err = masterConn.WriteTo(payload, sessionCopy.Addr)
|
||||
if err != nil {
|
||||
logging.Error(moduleName, "Error sending message:", err.Error())
|
||||
}
|
||||
|
||||
// Resend the message if no ack after 2 seconds
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
mutex.Lock()
|
||||
session, sessionExists := sessions[makeLookupAddr(sessionCopy.Addr.String())]
|
||||
if !sessionExists || session.ExploitReceived || session.Login == nil || !session.Login.NeedsExploit {
|
||||
mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
mutex.Unlock()
|
||||
logging.Notice(moduleName, "Resending SBCM exploit to DNS patcher client")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,18 +20,19 @@ const (
|
|||
)
|
||||
|
||||
type Session struct {
|
||||
SessionID uint32
|
||||
SearchID uint64
|
||||
Addr net.Addr
|
||||
Challenge string
|
||||
Authenticated bool
|
||||
Login *LoginInfo
|
||||
LastKeepAlive int64
|
||||
Endianness byte // Some fields depend on the client's endianness
|
||||
Data map[string]string
|
||||
PacketCount uint32
|
||||
ReservationID uint64
|
||||
GroupPointer *Group
|
||||
SessionID uint32
|
||||
SearchID uint64
|
||||
Addr net.Addr
|
||||
Challenge string
|
||||
Authenticated bool
|
||||
Login *LoginInfo
|
||||
ExploitReceived bool
|
||||
LastKeepAlive int64
|
||||
Endianness byte // Some fields depend on the client's endianness
|
||||
Data map[string]string
|
||||
PacketCount uint32
|
||||
ReservationID uint64
|
||||
GroupPointer *Group
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user