Multiple bugs
Opened this issue · 2 comments
Hey @HarpyWar @ian-travers,
While playing around with the server myself, I noticed a few bugs:
- if you add in the nfsu.conf room-names with a space, the room name is shown correctly in-game. But if you enter it you and other people are invisible in the lobby. (e.g. room name "Circuit 1". If you use "Circuit1" it works.)
- the server is not checking for the password? I can use any password to login into my account
- in the lobby if you try to "Send Challenge" to an other player in the lobby, the game crashes
@DanielCarter1615 room name with a space is a known bug. Same as server side password checking. Apparently no one figured out what encryption method is used in the game. Feel free to take part in the Head to Head discussion. Perhaps there is someone who can solve this problem.
Here is my 2 cents about password encryption.
Client
Client generates session key (usually static string - "Public key") then hex-encode it and sends data to server.
Later client gets 'server key' from the server and saves it for the future use.
void LobbyApiUpdate(LobbyApiRefT *pRef) {
...
int iKind;
int iCode;
char *pData;
char *pTag;
char strBuf[256];
...
int iSize = ProtoAriesPeek(pRef->pAries, &iKind, &iCode, &pData);
...
if ( iKind == 0xFFFFFFFF && iCode == 0xFFFFFFFF ) {
strBuf[0] = 0;
TagFieldSetAddress(strBuf, 256, "ADDR", pRef->uLocalAddr);
TagFieldSetNumber(strBuf, 256, "PORT", ProtoAriesStatus(pRef->pAries, 'lprt', 0, 0));
ProtoAriesSend(pRef->pAries, 'addr', 0, strBuf, -1);
strBuf[0] = 0;
TagFieldSetBinary(strBuf, 256, "SKEY", "Public Key", 10);
ProtoAriesSend(pRef->pAries, 'skey', 0, strBuf, -1);
}
...
if ( iKind == 'skey' ) {
pTag = TagFieldFind(pData, "SKEY");
TagFieldGetBinary(pTag, pRef->strSessionKey, 16);
}
}
btw, LobbyApiUpdate
implements FSM with loop, above code might look strange because bunch of code was omitted.
Later client will encrypt password before sending it to server:
int LobbyApiRequestCB(LobbyApiRefT *pRef, int iKind, char *pReqData, void *pCallback, void *pUserCBData) {
char *pTag;
char strPass[32];
char strData[768];
...
LobbyApiQueueRefT *queue = LobbyApiEnqueueCallback(pRef, iKind, pCallback, pUserCBData);
if ( queue )
{
if ( iKind == 'auth' || iKind == 'acct' || iKind == 'pass' )
{
TagFieldDupl(strData, 768, pRef->strData);
TagFieldMerge(strData, 768, pReqData);
pTag = TagFieldFind(strData, aPass_0);
TagFieldGetString(pTag, &strPass[1], 31, "");
strPass[0] = '~';
CryptSSC2StringEncrypt(&strPass[1], 31, &strPass[1], pRef->strSessionKey, 16, 16);
TagFieldSetString(strData, 768, "PASS", strPass);
pReqData = strData;
}
if ( iKind == 'snap' )
{
...
}
if ( iKind == 'move' )
{
...
}
LobbyApiSendRequest(pRef, iKind, pReqData, queue->??);
return(queue->??);
} else
{
NetPrintfCode("lobbyapi: request queue overflow; '%c%c%c%c' request discarded\n", iKind >> 24, iKind >> 16, iKind >> 8, iKind);
return(-2);
}
}
Tilda (~) is the flag for the password encryption.
Server
First, server takes public key, generates server 'session key' (or secret key?), saves it for current session and sends it to client:
void SlaveService_handleSkeyMessage(this, request, session) {
char *pTag;
char strSecretKey[16];
MD5_CTX context;
char strPublicKey[256];
pTag = TagFieldFind(request->pRequestData, "PKEY");
TagFieldGetBinary(pTag, strPublicKey, 256);
// basically here goes messy code of pseudo-random generator for strSecretKey using MD5 hashing algo
memcpy(session->strSessionKey, strSecretKey, sizeof(session->strSessionKey));
request->pResponseData[0] = 0;
TagFieldSetBinary(request->pResponseData, request->iResponseDataSize, "SKEY", strSecretKey, 16);
...
}
Although password data is ascii string, it is treated as binary data.
Then server gets auth
message and handles it like this:
void SlaveService_handleAuthMessage(this, request, session) {
...
char *pTag;
int iPurchBan;
int iTOS;
int iShare;
char strGameTag[20];
char strName[32];
char strPass[32];
char strXUID[28];
char strRegKey[68];
char strMachineID[128];
char strMAC[128];
pTag = TagFieldFind(request->pRequestData, "TOS");
iTOS = TagFieldGetNumber(pTag, 0);
pTag = TagFieldFind(request->pRequestData, "SHARE");
iShare = TagFieldGetNumber(pTag, 0);
pTag = TagFieldFind(request->pRequestData, "NAME");
TagFieldGetString(pTag, strName, 32, "");
pTag = TagFieldFind(request->pRequestData, "GTAG");
TagFieldGetString(pTag, strGameTag, 20, "");
pTag = TagFieldFind(request->pRequestData, "XUID");
TagFieldGetString(pTag, strXUID, 28, "");
pTag = TagFieldFind(request->pRequestData, "PURCHBAN");
iPurchBan = TagFieldGetNumber(pTag, 1);
pTag = TagFieldFind(request->pRequestData, "PASS");
TagFieldGetString(pTag, strPass, 32, "");
pTag = TagFieldFind(request->pRequestData, "REGKEY");
TagFieldGetString(pTag, strRegKey, 68, "");
if ( strPass[0] == '~' ) {
CryptSSC2StringDecrypt(strPass, 32, &strPass[1], session->strSessionKey, 16, 16);
}
pTag = TagFieldFind(request->pRequestData, "MID");
TagFieldGetString(pTag, strMachineID, 128, "");
pTag = TagFieldFind(request->pRequestData, "MAC");
TagFieldGetString(pTag, strMAC, 128, "");
...
}
Here along with other data server gets password, checks (~) for encryption and decrypts it putting it in the same buffer. Then server has password to check.
/*H**************************************************************************/
/*!
\File cryptssc2.c
*/
/***************************************************************************H*/
/*** Include files ***********************************************************/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/*** Defines **********************************************************************/
/*** Macros ***********************************************************************/
/*** Type Definitions *************************************************************/
struct CryptSSC2T {
unsigned char sBox[256];
unsigned char uSwap;
unsigned int uCrc;
};
/*** Function Prototypes **********************************************************/
/*** Variables ********************************************************************/
const static uint_least32_t Crc32Table[256] = {
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};
static CryptSSC2T _CryptSSC2_State;
/*** Private Functions ************************************************************/
/*** Public Functions *************************************************************/
void CryptSSC2Init(CryptSSC2T *pRef, char *pKey, int iKeySize, int iIter) {
static int static_CryptSSC2_bHelloInit = 0;
if (static_CryptSSC2_bHelloInit == 0) {
static_CryptSSC2_bHelloInit = 1;
CryptSSC2Init(&_CryptSSC2_State, "hello world",-1,10);
}
if (iIter < 0) {
iIter = -iIter;
} else {
for (int i = 0; i < 256; i++)
pRef->sBox[i] = i;
pRef->uSwap = 0;
pRef->uCrc = 0;
}
if (iKeySize < 0) {
if (*pKey == '\0') {
return;
}
iKeySize = 0;
do {
iKeySize++;
} while (pKey[iKeySize] != '\0');
}
if (iKeySize > 0) {
unsigned int i = 0;
unsigned int rnd = 0;
unsigned int prev_rnd = 0;
if (0 < iIter * 0x100) {
do {
rnd = rnd >> 8 ^ Crc32Table[(unsigned char)pKey[i % iKeySize] ^ prev_rnd];
unsigned char uBoxVal = pRef->sBox[i & 0xff];
rnd = rnd >> 8 ^ Crc32Table[ (unsigned char)rnd ^uBoxVal];
prev_rnd = rnd & 0xff;
pRef->sBox[i & 0xff] = pRef->sBox[prev_rnd];
i++;
pRef->sBox[prev_rnd] = uBoxVal;
} while (i < iIter * 0x100);
}
}
}
void CryptSSC2Apply(CryptSSC2T *pRef,unsigned char *pOutBuf,int iOutSize) {
unsigned char boxSwapVal1;
unsigned char boxSwapVal2;
unsigned char swap;
unsigned int rnd;
unsigned int crc;
unsigned char *pBox;
swap = pRef->uSwap;
crc = pRef->uCrc;
if (iOutSize > 0) {
pBox = pRef->sBox + swap;
rnd = crc & 0xff;
do {
crc = crc >> 8 ^ Crc32Table[(unsigned char)*pBox ^ rnd];
swap = swap + 1;
pBox = pRef->sBox + swap;
boxSwapVal1 = *pBox;
rnd = crc & 0xff;
boxSwapVal2 = pRef->sBox[rnd];
*pBox = boxSwapVal2;
pRef->sBox[rnd] = boxSwapVal1;
*pOutBuf++ ^= pRef->sBox[boxSwapVal1 - boxSwapVal2];
iOutSize--;
} while (iOutSize != 0);
}
pRef->uCrc = crc;
pRef->uSwap = swap;
}
void CryptSSC2StringEncrypt(char *pOutBuf,int iOutSize,char *pInpBuf,char *pKey,int iKeySize,int iIter) {
unsigned char uEnc;
unsigned char uDat;
CryptSSC2T ref;
int d;
uEnc = 0;
CryptSSC2Init(&ref, pKey, iKeySize, iIter);
CryptSSC2Init(&_CryptSSC2_State, pKey, iKeySize, -iIter);
CryptSSC2Init(&_CryptSSC2_State, "ru paranoid?", -1, -1);
while (iOutSize > 1) {
if (pInpBuf) {
uDat = *pInpBuf++;
if (!uDat)
pInpBuf = 0;
} else {
CryptSSC2Apply(&_CryptSSC2_State, &uDat, 1);
uDat = (uDat & 0x3f) + 0x20;
}
if (uDat < 0x20 || uDat >= 0x7f)
uDat = 0x7f;
CryptSSC2Apply(&ref, &uEnc, 1);
d = uDat + uEnc % 0x60 + 0x40;
*pOutBuf++ = d % 0x60 + 0x20;
--iOutSize;
}
if (iOutSize > 0)
*pOutBuf = 0;
}
void CryptSSC2StringDecrypt(char *pOutBuf, int iOutSize, char *pInpBuf, char *pKey, signed int iKeySize, int iIter) {
unsigned char uDec;
unsigned char uDat;
CryptSSC2T ref;
int d, n;
uDec = 0;
CryptSSC2Init(&ref, pKey, iKeySize, iIter);
n = 0;
while (n < iOutSize - 1)
{
uDat = pInpBuf[n++];
if (!uDat)
break;
CryptSSC2Apply(&ref, &uDec, 1);
d = uDat + 0x40 - uDec % 0x60;
*pOutBuf = d % 0x60 + 0x20;
if ( *pOutBuf == 0x7F )
break;
++pOutBuf;
}
if ( n < iOutSize )
*pOutBuf = 0;
}
Encryption algo is non-deterministic. The same original password can (and will) have different encrypted form.
Also you can try to decrypt password from the comment in nfsuserver.cpp
file.
Here is link to PHP implementation from the guy who made NFS U2 server (hopefully it will be public soon).
As for other bugs and crashes, U1 was build on early version of DirtySDK, so it has lots of bug. U2 is a little bit better but also has lots of bugs.
PS: This all is just my fantasy. Any resemblance to real code, dead or alive, is purely coincidental.