Servidor activo desde hace17años,25días
Wiki técnica · especificación mínima de login

Red de protocolo

Todo lo necesario para reproducir un login completo de Carcelclient.exe: el magic header, el saludo, la clave, el cifrado, cómo se empaquetan los datos, el mensaje LOGIN y la lista de opcodes — byte a byte, y explicado para novatos.

Índice

  1. Los tres carteros (puertos UDP)
  2. El sobre: cabecera de 22 bytes
  3. El magic header (handshake)
  4. El saludo: SYN / SYN-ACK / ACK
  5. La clave K (intercambio)
  6. Cifrado XOR de los datos
  7. El envoltorio de registros
  8. El mensaje LOGIN (0x1388)
  9. La secuencia mínima de login
  10. Respuestas del servidor
  11. Chuleta de opcodes

1 · Los tres carteros

Todo viaja por UDP (mensajes sueltos, sin conexión previa). El juego habla con tres «ventanillas», cada una en su puerto:

25666
UDP · Login

Identidad + lista de cárceles. Aquí ocurre TODO este documento.

25667
UDP · Updates

Comprueba si el cliente está al día.

25001
UDP · Mundo

El juego: moverte, salas, combate.

En el código: PUERTO_LOGIN = 25666, PUERTO_UPDATES = 25667, PUERTO_MUNDO = 25001. El login entero (handshake + autenticación + selección de cárcel) ocurre en el 25666. Solo al pulsar «jugar» el cliente salta al mundo (25001).

2 · El sobre: cabecera de 22 bytes

Todo paquete (protocolo SNS) empieza con esta cabecera de 22 bytes (0x16). Todos los enteros van en little-endian. Detrás va el payload.

0x00u32connID_lo

MAGIC: 0xFFFFFFFF en el handshake; 0x00000001 en la fase de datos.

0x04u32connID_hi

Id de conexión que asigna el servidor (en fase de datos).

0x08u16flags

bit0=pide-ACK · bit1=es-ACK · bit2=lleva-datos (≤7).

0x0au8ventana

Ventana de envío. Distinto de 0.

0x0bu8ack

Hasta qué mensaje va confirmado (< ventana).

0x0cu32campoC

Id de mensaje; el ACK lo repite. En el SYN-ACK lleva f2 (clave).

0x10u32marcaTiempo

Timestamp; en el SYN-ACK lleva f1 (clave).

0x14u16secuencia

Nº de secuencia de esta trama.

0x16payload

Contenido. En claro durante el handshake; cifrado (XOR) en la fase de datos.

Las banderas (flags · 0x08)

El campo flags es un mapa de bits: cada bit es un sí/no independiente y el valor final es la suma (OR) de los que estén activos. Por eso siempre vale ≤ 7 (solo se usan 3 bits).

ConstanteBitValorSignificado
FLAG_PIDE_ACKbit01El emisor pide que le confirmes esta trama.
FLAG_ES_ACKbit12Esta trama ES una confirmación (un ACK).
FLAG_LLEVA_DATOSbit24Lleva datos de aplicación en el payload.
Ejemplos:
  flags = 5  →  1 + 4  =  PIDE_ACK | LLEVA_DATOS   (datos que hay que confirmar)
  flags = 2  →          =  ES_ACK                  (un ACK puro, sin datos)
  flags = 0  →          =  (nada)                  (control suelto)

🧠 En cristiano: Tres casillas que se marcan o no: «confírmame», «esto es tu acuse» y «llevo carta dentro». Un número las resume todas: 4+1 = 5 significa «llevo carta y quiero acuse».

3 · El magic header (handshake)

Lo que distingue un paquete de saludo de uno de datos es el «número mágico» en 0x00: si connID_lo == 0xFFFFFFFF, es handshake. En ese caso, el payload (desde 0x16) trae una sub-cabecera fija:

Paquete de HANDSHAKE (payload en claro):

 0x16  u16  tipo      3=SYN · 6=SYN-ACK · 7=ACK · 4=FIN · 5=FIN-ACK
 0x18  u16  const     SIEMPRE 4  ← el "magic" de la sub-cabecera
 0x1a  u32  nonce     aleatorio del cliente (el servidor lo repite)
 0x1e  ...  datos     en el SYN: usuario en ASCII terminado en '\0'

🧠 En cristiano: Dos «sellos» marcan que esto es un saludo y no datos: connID_lo = 0xFFFFFFFF (todos unos) y la constante 4 en 0x18. Si no están, el receptor lo trata como datos cifrados.

Referencia: constantes del código

Estos son los nombres exactos que usa el emulador (protocolo_sns.hpp). Se dividen en dos grupos: la cabecera común (0x000x15, en todos los paquetes) y la sub-cabecera de handshake (0x16+, solo en el saludo; en la fase de datos, desde 0x16 ya va el payload cifrado).

ConstanteOffsetTipoSignificado
TAM_CABECERA0x16Tamaño de la cabecera común: 22 bytes.
OFF_CONN_LO0x00u32connID_lo. MAGIC: 0xFFFFFFFF handshake, 1 en datos.
OFF_CONN_HI0x04u32connID_hi = id de conexión asignado.
OFF_FLAGS0x08u16flags: bit0 pide-ACK, bit1 es-ACK, bit2 lleva-datos.
OFF_VENTANA0x0au8ventana de envío (≠ 0).
OFF_ACK0x0bu8ack: hasta qué mensaje va confirmado.
OFF_CAMPO_C0x0cu32id de mensaje; en el SYN-ACK lleva f2 (clave).
OFF_MARCA_TIEMPO0x10u32timestamp; en el SYN-ACK lleva f1 (clave).
OFF_SECUENCIA0x14u16nº de secuencia de la trama.
OFF_PAYLOAD0x16Inicio del payload (mismo offset que OFF_TIPO).
OFF_TIPO0x16u16tipo de paquete: 3/6/7/4/5 (solo handshake).
OFF_CONST0x18u16constante = 4 (2º sello de handshake).
OFF_NONCE0x1au32nonce del cliente (el servidor lo repite).
OFF_DATOS0x1edatos tras el nonce (p. ej. el usuario en el SYN).

Detalle clave: OFF_PAYLOAD y OFF_TIPO valen lo mismo (0x16). No es un error: el payload arranca justo ahí, y en un paquete de saludo el primer dato del payload es precisamente el campo tipo. En un paquete de datos, ese mismo 0x16 es el primer byte cifrado.

🧠 En cristiano: Los primeros 22 bytes (0x000x15) son el sobre que llevan todas las cartas. Los campos tipo/const/nonce/datos solo existen cuando la carta es un «saludo»; en una carta normal, ahí empieza directamente el contenido cifrado.

4 · El saludo: SYN → SYN-ACK → ACK

  1. 1

    Cliente → SYN

    tipo 3

    El cliente abre la conexión y manda su usuario tras el nonce (en 0x1e).

    connID_lo=FFFFFFFF  connID_hi=FFFFFFFF  flags=…
    0x16 tipo=3   0x18 const=4   0x1a nonce=NNNN
    0x1e "innapmine\0"
  2. 2

    Servidor → SYN-ACK

    tipo 6

    El servidor asigna un idConexión y esconde la clave K en los campos campoC (f2) y marcaTiempo (f1). Repite seq y nonce del cliente:

    connID_lo=FFFFFFFF  connID_hi=FFFFFFFF  flags=0  ventana=1  ack=0
    0x0c campoC = f2      ← trozo de clave
    0x10 marca  = f1      ← trozo de clave
    0x14 seq    = <eco>
    0x16 tipo=6   0x18 const=4   0x1a nonce=<eco>
    0x1e u32 idConexión   ← tu número de conexión
  3. 3

    Cliente → ACK

    tipo 7

    El cliente confirma. Conexión establecida. A partir de aquí, la fase de datos.

(Cierre: el cliente manda tipo 4 (FIN) y el servidor responde tipo 5 (FIN-ACK).)

Tipos de paquete (offset 0x16)

ConstanteValorDirecciónQué es
TIPO_SYN3cliente →Abre la conexión (lleva el usuario).
TIPO_SYN_ACK6← servidorAcepta: asigna connID + material de clave.
TIPO_ACK7cliente →Confirma. Conexión ESTABLECIDA.
(FIN)4cliente →Pide cerrar la conexión.
(FIN-ACK)5← servidorConfirma el cierre.

Y el marcador que decide si un paquete es handshake: CONN_HANDSHAKE = 0xFFFFFFFF — el valor que lleva connID_lo (0x00) durante todo el saludo. En cuanto la conexión se establece, ese campo pasa a valer 1 y estos «tipos» dejan de existir (desde 0x16 ya va el payload cifrado).

5 · La clave K (intercambio)

Con la clave K, el timestamp TS y la máscara M = 0x7e34fc22, el servidor calcula dos campos y los mete en el SYN-ACK:

Servidor (mete en el SYN-ACK):
  f1 = ((TS ^ K) & M) ^ K        → va en 0x10 (marcaTiempo)
  f2 = ((TS ^ K) & M) ^ TS       → va en 0x0c (campoC)

Cliente (recupera la clave):
  K  = ((f1 ^ f2) & M) ^ f1

¿Qué es la máscara 0x7e34fc22?

Es una constante de 32 bits grabada a fuego en el cliente (constexpr uint32_t MASCARA_CLAVE = 0x7e34fc22u). No es un número calculado ni tiene significado matemático: es un simple patrón de bits que los autores del juego eligieron, y que se sacó del binario por ingeniería inversa. Lo único que importa es que cliente y servidor usen exactamente el mismo.

0x7e34fc22 en binario:
  0111 1110  0011 0100  1111 1100  0010 0010
    0x7e       0x34       0xfc       0x22

El AND (&) con M deja pasar solo los bits en 1 y pone a 0 el resto.

Por qué funciona (el álgebra sale sola). Llamando A = (TS ^ K) & M:

f1 = A ^ K
f2 = A ^ TS
f1 ^ f2 = K ^ TS          (el trozo A se cancela consigo mismo)
(f1 ^ f2) & M = A          (volvemos a filtrar por la máscara)
A ^ f1 = A ^ (A ^ K) = K   ✔  recuperada la clave

Fíjate en que f1 ^ f2 siempre da K ^ TS, que un espía SÍ puede calcular. La pieza que le falta es la máscara: sin conocer M no puede dar el último paso y despejar K. Por eso la máscara es, de hecho, el secreto compartido de todo el handshake.

🧠 En cristiano: La máscara es un «colador» de bits que los dos ya traen de fábrica. No es cripto de banco: es una «clave de silbato» para que un curioso con un sniffer no lea los mensajes de un vistazo. Ambos lados acaban con la misma K de 4 bytes.

6 · Cifrado XOR de los datos

Ya establecida la conexión, el payload viaja cifrado. Es un XOR encadenado con la clave de 4 bytes k[0..3], la posición i y el byte anterior (semilla 0x3b):

k[4] = bytes de K (little-endian)
prev = 0x3b
para cada byte i:
    salida = k[(i>>2) & 3]  ^  entrada[i]  ^  (i & 0xFF)  ^  prev
    prev   = salida          ← al CIFRAR se encadena con el byte de salida
                               (al DESCIFRAR, con el byte de entrada)

🧠 En cristiano: Cada byte se mezcla con un trozo de la clave, con su posición y con el byte de al lado. Es reversible: el otro extremo deshace la mezcla y lee el mensaje.

7 · El envoltorio de registros

El contenido de aplicación no va suelto: se envuelve en registros con su longitud delante, y la lista termina con un registro de longitud 0. En el código lo arma componerMensajeApp(salida, idConexión, datosApp, n). El cliente exige justo este formato:

payload  = [idConexión:4][1:4]  +  M

M        = [longReg:4][registro]  …  [0:4]     ← el 0 final = "fin"
registro = [idConexión:4][1:4][datosApp]        (longReg = 8 + len(datosApp))
datosApp = [opcode:2][ …campos… ]

→ El OPCODE de aplicación queda en el offset 0x14 del payload descifrado.

🧠 En cristiano: Sobres dentro del sobre grande, cada uno etiquetado con su tamaño, y un sobre vacío al final que dice «hasta aquí».

8 · El mensaje LOGIN (0x1388)

El primer mensaje de aplicación del cliente. Los offsets son relativos al payload descifrado:

0x14  u16  opcode    = 0x1388 (LOGIN)
0x16  u16  ?         (campo previo)
0x18  u16  longitud  del bloque de credenciales
0x1a  ...  bloque    credenciales cifradas (longitud bytes)

El «bloque» no es un hash: es el texto usuario \0 CONTRASEÑA \0 \0 (opcionalmente con un \0 PIN) tapado con un keystream XOR de 4 bytes que se repite:

cifrado[i] = claro[i] ^ KS[i % 4]

Como el servidor YA conoce el usuario (llegó en el SYN), despeja el
keystream con el propio mensaje:

    KS[i % 4] = cifrado[i] ^ usuario[i]

…y con eso descifra la contraseña. Funciona con cualquier sesión.

🧠 En cristiano: La contraseña va «tapada», pero como el nombre de usuario viajó antes en claro, el servidor usa esas letras conocidas para averiguar el patrón y destapar el resto.

9 · La secuencia mínima de login

El orden exacto de mensajes de aplicación, tal como lo procesa el servidor (→ cliente envía · ← servidor responde):

  1. 1

    LOGIN

    → 0x1388

    Cliente manda usuario+contraseña. Si algo falla, el servidor responde LOGINRECHAZADO (0x138a) con un código y se acabó.

  2. 2

    Cuenta + servidores

    ← 0x1389 + 0x13a9

    Si va bien, el servidor envía la estructura de CUENTA (0x1389, con las 3 ranuras de personaje) y la lista de nombres de cárceles SERVERADDED (0x13a9).

  3. 3

    Latido

    → 0x13a1

    El cliente empieza a mandar «latidos» cada ~5 s. El primero desbloquea el paso 4.

  4. 4

    Login aceptado + clases

    ← 0x13d5 + 0x13c2

    El servidor confirma con LOGINACCEPTED (0x13d5) (count=0) y manda CLASSINFO (0x13c2) (tabla de delitos/clases para la pantalla de personaje).

  5. 5

    Pide cárceles

    → 0x13aa

    Al abrir la lista, el cliente pide GETAVAILABLESERVERS (0x13aa) y el servidor responde AVAILABLESERVERS (0x13ac) con el nº de reclusos.

  6. 6

    Jugar → al mundo

    → 0x13f1 · ← 0x13f2

    El cliente pulsa jugar (JUGAR 0x13f1); el servidor concede ENTERINGGAMEACCEPTED (0x13f2) y el cliente se conecta al mundo (25001). Fin del login.

Detalle del desfase +1: el cliente hace dec al recibir, así que el servidor ENVÍA la etiqueta del manejador (p. ej. cuenta = 0x1389, jugar =0x13f2).

10 · Respuestas del servidor (byte a byte)

LOGINACCEPTED · 0x13d5

[u16 0x13d5][u32 count=0]     ← forma mínima que desbloquea el login

LOGINRECHAZADO · 0x138a

[u16 0x138a][u8 código]       (+[u32 timestamp] si código == 7)

SERVERADDED · 0x13a9 (nombres de cárcel)

[u16 0x13a9][u16 count]
por cada prisión:
   [u32 id][u8 flag][u32 extra][nombre \0][nombre2 \0]

AVAILABLESERVERS · 0x13ac (reclusos)

[u16 0x13ac][u8 count][u32 dataLen][data]
data, por cada prisión:
   [u32 id][u8 nMod][ nMod × u16 reclusos_parciales ]
   (la SUMA de los u16 = nº de reclusos de esa prisión)

La estructura de CUENTA (0x1389) es la más grande (parte fija de 0x9cd bytes + 3 ranuras de personaje + inventario) y se envía fragmentada; se documenta aparte por su tamaño.

11 · Chuleta de opcodes

→ Cliente al servidor

0x1388LOGINInicia sesión (usuario + contraseña)
0x13a1LATIDOHeartbeat, cada ~5 s
0x13aaGETAVAILABLESERVERSPide la lista de cárceles
0x139fSELECCIONAREntra con un personaje
0x1394CREAR_PERSONAJECrea personaje (nick + atributos)
0x1393BORRAR_PERSONAJEBorra el personaje de una ranura
0x13f1JUGARPide entrar al mundo

← Servidor al cliente

0x1389CUENTAEstructura de cuenta + personajes
0x138aLOGINREJECTEDLogin rechazado (con código)
0x13d5LOGINACCEPTEDLogin aceptado
0x13a9SERVERADDEDNombres de las cárceles
0x13acAVAILABLESERVERSReclusos por cárcel
0x13c2CLASSINFOTabla de clases/delitos
0x1395CHARCREATEDPersonaje creado
0x13f2ENTERINGGAMEACCEPTEDPermiso para entrar al mundo

Esta wiki describe el protocolo reconstruido por ingeniería inversa del cliente. Los offsets y opcodes están tomados del emulador; pueden afinarse según se documente más.