26 minute read

Dragonball z budokai 3 è un videogioco per playstation 2 uscito nel 2004. Il miglior gioco di dragonball di sempre, se lo chiedete a me :)

Dopo averci passato innumerevoli ore da ragazzino, e dopo averlo rigiocato un’altra volta di recente, ho deciso di reversare il gioco per capire come funziona la meccanica delle password dei personaggi.

L’obiettivo di questo progettino è quindi capire come il gioco trasforma una password in un personaggio, e come validarla.

Iniziamo!

TL;DR

Ho reversato l’algoritmo che genera le password e riesco a generare password per personaggi arbitrari. E anche possibile generare personaggi con vita “infinita” sfruttando un integer underflow. Se vuoi generare un personaggio come vuoi per poterci giocare contro prova questo sito: https://lucapalumbo.github.io/DBZ3-Passwords-Generator

img

Figura: Screenshot del gioco Dragon Ball Z Budokai 3

Feature del gioco

Il gioco implementa una meccanica RPG, dove ogni personaggio può salire di livello e migliorare le proprie statistiche.

Le statistiche dei personaggi sono 7:

  • Salute (quanta salute ha il personaggio)
  • Ki (velocità di ricarica del ki)
  • Attacco (quanto danno fa il personaggio)
  • Difesa (quanto resistente è il personaggio)
  • Arti (danno aggiuntivo delle mosse speciali)
  • Capacità (per potenziare gli effetti degli oggetti speciali)
  • CPU (quanto è intelligente il personaggio quando è controllato dalla CPU)

A ogni pesonaggio poi si possono assegnare delle ‘capsule’, ossia le mosse speciali / oggetti che il personaggio può usare durante i combattimenti

La cosa carina è che ogni volta che un personaggio sale di livello, il gioco genera una password che codifica le informazioni del personaggio: livello, statistiche, capsule.

Questa password può essere condivisa con un amico, che può inserirla all’interno del suo gioco e a quel punto giocare contro il nostro personaggio controllato dal computer. Una curiosa e originale meccanica di interazione tra giocatori senza bisogno di internet.

img img

Figure: Screenshot di una password di esempio e della finestra per inserire password

Di seguito descrivo come indentificare la funzione che valida la password partendo dalla ISO del gioco.

Nota: Ciò che sto per descrivere non è esattamente il processo che ho seguito io per reversare il gioco. Il mio primo approccio è stato molto più confuso e disordinato: ho girovagato per la codebase a lungo e piazzato numerosi breakpoint prima di trovare le funzioni più interessanti.

Reversing

La prima cosa da fare è scaricare un emulatore e ottenere una ISO del gioco*. Un file iso è solo una copia digitale del contenuto di un disco ottico. Si tratta sostanzialmente di un archivio estraibile con tool come 7zip.

Una volta estratto l’archivio otteniamo numerosi file e cartelle, la maggior parte di questi sono asset di gioco (musiche, immagini, suoni…). Il file SLES_527.30 è un ELF, il cuore logico del gioco, quindi il candidato naturale da cui partire. Si tratta di un binario compilato per l’Emotion Engine, processore proprietario della Sony e Toshiba, basato su MIPS.

img

Nota: se si vuole debuggare step by step codice assembly del Emotion Engine consiglio di familiarizzare con il concetto di Delay Slot

Quello che possiamo fare è aprire su ghidra questo binario.

Nota: ghidra di default supporta l’architettura MIPS, tuttavia non supporta la variante Emotion Engine. Per far funzionare correttamente ghidra con il gioco esiste un’estensione di ghidra: Emotion Engine Reloaded

Quando si reversa bisogna tenere a mente che le stringhe di un binario sono un aiuto fondamentale. Quindi la prima operazione da fare è controllare se esistono delle stringhe che contengono la parola “password”. In effetti ne esistono tre diverse. Ovviamente ci interessa capire quali funzioni hanno delle reference a queste stringhe.

img

Scopriamo che due di queste stringhe non hanno reference, mentre una viene usata da una sola funzione. Capire cosa fa questa funzione non è proprio facile. Quindi una idea è mettere un breakpoint su questa funzione e cercare di hittarlo con l’emulatore.

undefined8 reference_to_password_string(void)

{
  undefined8 uVar1;
  int iVar2;
  int global_ptr;
  
  global_ptr = iGpffff8c8c;
  uVar1 = FUN_00144fc0(0x2f5f00,0,0x4000,8,2,0x4730b8);
  *(int *)(global_ptr + 0x18) = (int)uVar1;
  global_ptr = FUN_0013aab0(0x174,0,0x4730c8,0xb1);
  *(int *)((int)uVar1 + 0x30) = global_ptr;
  FUN_001258f8(global_ptr,0,0x174);
  *(undefined4 *)(global_ptr + 0x11c) = 0x43700000;
  iVar2 = 0;
  *(undefined4 *)(global_ptr + 0x120) = 0x42a00000;
  do {
    *(undefined2 *)(global_ptr + 0x128) = 0xffff;
    *(undefined2 *)(global_ptr + 0x12a) = 0xffff;
    iVar2 = iVar2 + 6;
    *(undefined2 *)(global_ptr + 300) = 0xffff;
    *(undefined2 *)(global_ptr + 0x12e) = 0xffff;
    *(undefined2 *)(global_ptr + 0x130) = 0xffff;
    *(undefined2 *)(global_ptr + 0x132) = 0xffff;
    global_ptr = global_ptr + 0xc;
  } while (iVar2 < 0x24);
  return uVar1;
}

Il decompilato di questa funzione non sembra essere accuratissimo, ma ci facciamo bastare questo

img

Facendo in questo modo scopriamo che questa funzione viene chiamata non appena cerchiamo di aprire la tastiera per inserire la password. Quindi possiamo assumere si tratti di una funzione di inizializzazione della finestra di input della password.

Analizzando un po’ più nel dettaglio questa funzione notiamo scrive in un buffer di 36 short il valore 0xffff. Chissà cosa rapprensenta questo buffer. L’idea è a questo punto usare ancora una volta analisi dinamica: osserviamo la zona di memoria usando il debugger e iniziamo a inserire la password a mano nel gioco per vedere cosa succede.

img img

Quello che scopriamo è che quel buffer di memoria contiene la password che digitiamo. Il buffer di 36 short corrisponde ai 36 caratteri della password inserita a schermo. Questo ci dà 2 informazioni esenziali:

  • i caratteri sono codificati in short e l’encoding è molto semplice: ‘A’ = 0, ‘B’ = 1, …
  • adesso conosciamo l’indirizzo dove viene salvata la password inserita

Il prossimo passo è sicuramente capire come viene usata questa password inserita. Vogliamo trovare quindi una funzione che legge questi bytes e li utilizza in qualche modo. Ci affidiamo quindi ai memory breakpoint (sì, il debugger di PCSX2 ha pure i memory breakpoint).

Un memory breakpoint è un tipo di breakpoint che viene triggerato quando un certo indirizzo di memoria viene letto/scritto. In un emulatore sono implementati in maniera sicuramente diversa, ma questo tipo di breakpoint esistono anche nelle moderne architetture e sono implementati tramite degli appositi registri hardware.

img

Osserva che ho aggiunto una condizione: program counter diverso da 0x2f53a0. Questo perchè a quell’indirizzo c’è una istruzione che legge il buffer in questione a ogni frame del gioco. Probabilmente si tratta di una funzione che gestisce la grafica della finestra, quindi non è interessante ed è solo fastidiosa

Utilizzando i memory breakpoint riusciamo a individuare 2 punti in cui viene effettuata una read da quel buffer. In uno di questi punti osserviamo una copy dal buffer globale della password a un altro buffer locale nello stack. Poi questo buffer viene passato a un’altra funzione come argomento.

void FUN_002f4920(int param_1)

{
  int iVar1;
  undefined4 uVar2;
  long lVar3;
  character_struct *ptr_char_struct;
  int iVar4;
  int iVar5;
  character_struct char_struct_array [3];
  
  iVar5 = 0;
  iVar1 = *(int *)(param_1 + 0x30);
  ptr_char_struct = char_struct_array;
  iVar4 = iVar1;
  do {
    iVar5 = iVar5 + 6;
    ptr_char_struct[1].char_id = *(short *)(iVar4 + 0x128);
    ptr_char_struct[1].level = *(short *)(iVar4 + 0x12a);
    ptr_char_struct[1].stats[0] = *(short *)(iVar4 + 300);
    ptr_char_struct[1].stats[1] = *(short *)(iVar4 + 0x12e);
    ptr_char_struct[1].stats[2] = *(short *)(iVar4 + 0x130);
    ptr_char_struct[1].stats[3] = *(short *)(iVar4 + 0x132);
    iVar4 = iVar4 + 0xc;
    ptr_char_struct = (character_struct *)(ptr_char_struct->stats + 4);
  } while (iVar5 < 0x24);
  lVar3 = decode_password(char_struct_array);

Questa funzione che viene chiamata è esattamente la funzione che cerchiamo: è la routine di decoding della password!

Descrizione dell’algoritmo

L’algoritmo è diviso in 3 step ben separati.

void decode_password(character_struct *character)
{
  undefined1 buffer [32];
  
  stage1(buffer,&character->char_id);
  stage2(buffer);
  stage3(character,buffer);
  return;
}

Il primo non decodifica i dati della password, ma li ricompone in un array più compatto. Ogni carattere come abbiamo detto è encodato da uno short, e i caratteri possibili sono 64. Questo significa che solo 6 bit sono veramente importanti, gli altri varranno 0. Quello che fa questo stage è quindi rimuovere i bit inutili e concatenare i bit con informazioni. In questo modo si ottiene un buffer di 27 bytes.

Stadio 1
void stage1(byte *buffer,short *password)
{
  *buffer = (byte)((int)password[0x10] << 2);
  *buffer = *buffer | (byte)(password[0x11] >> 4) & 3;
  buffer[1] = (byte)((int)password[0x11] << 4);
  buffer[1] = buffer[1] | (byte)(password[0x12] >> 2) & 0xf;
  buffer[2] = (byte)((int)password[0x12] << 6);
  buffer[2] = buffer[2] | (byte)password[0x13] & 0x3f;
  buffer[3] = (byte)((int)password[0x14] << 2);
  buffer[3] = buffer[3] | (byte)(password[0x15] >> 4) & 3;
  buffer[4] = (byte)((int)password[0x15] << 4);
  buffer[4] = buffer[4] | (byte)(password[0x16] >> 2) & 0xf;
  buffer[5] = (byte)((int)password[0x16] << 6);
  buffer[5] = buffer[5] | (byte)password[0x17] & 0x3f;
  buffer[6] = (byte)((int)password[0x18] << 2);
  buffer[6] = buffer[6] | (byte)(password[0x19] >> 4) & 3;
  buffer[7] = (byte)((int)password[0x19] << 4);
  buffer[7] = buffer[7] | (byte)(password[0x1a] >> 2) & 0xf;
  buffer[8] = (byte)((int)password[0x1a] << 6);
  buffer[8] = buffer[8] | (byte)password[0x1b] & 0x3f;
  buffer[9] = (byte)((int)password[0x1c] << 2);
  buffer[9] = buffer[9] | (byte)(password[0x1d] >> 4) & 3;
  buffer[10] = (byte)((int)password[0x1d] << 4);
  buffer[10] = buffer[10] | (byte)(password[0x1e] >> 2) & 0xf;
  buffer[0xb] = (byte)((int)password[0x1e] << 6);
  buffer[0xb] = buffer[0xb] | (byte)password[0x1f] & 0x3f;
  buffer[0xc] = (byte)((int)password[0x20] << 2);
  buffer[0xc] = buffer[0xc] | (byte)(password[0x21] >> 4) & 3;
  buffer[0xd] = (byte)((int)password[0x21] << 4);
  buffer[0xd] = buffer[0xd] | (byte)(password[0x22] >> 2) & 0xf;
  buffer[0xe] = (byte)((int)password[0x22] << 6);
  buffer[0xe] = buffer[0xe] | (byte)password[0x23] & 0x3f;
  buffer[0xf] = (byte)((int)password[0x24] << 2);
  buffer[0xf] = buffer[0xf] | (byte)(password[0x25] >> 4) & 3;
  buffer[0x10] = (byte)((int)password[0x25] << 4);
  buffer[0x10] = buffer[0x10] | (byte)(password[0x26] >> 2) & 0xf;
  buffer[0x11] = (byte)((int)password[0x26] << 6);
  buffer[0x11] = buffer[0x11] | (byte)password[0x27] & 0x3f;
  buffer[0x12] = (byte)((int)password[0x28] << 2);
  buffer[0x12] = buffer[0x12] | (byte)(password[0x29] >> 4) & 3;
  buffer[0x13] = (byte)((int)password[0x29] << 4);
  buffer[0x13] = buffer[0x13] | (byte)(password[0x2a] >> 2) & 0xf;
  buffer[0x14] = (byte)((int)password[0x2a] << 6);
  buffer[0x14] = buffer[0x14] | (byte)password[0x2b] & 0x3f;
  buffer[0x15] = (byte)((int)password[0x2c] << 2);
  buffer[0x15] = buffer[0x15] | (byte)(password[0x2d] >> 4) & 3;
  buffer[0x16] = (byte)((int)password[0x2d] << 4);
  buffer[0x16] = buffer[0x16] | (byte)(password[0x2e] >> 2) & 0xf;
  buffer[0x17] = (byte)((int)password[0x2e] << 6);
  buffer[0x17] = buffer[0x17] | (byte)password[0x2f] & 0x3f;
  buffer[0x18] = (byte)((int)password[0x30] << 2);
  buffer[0x18] = buffer[0x18] | (byte)(password[0x31] >> 4) & 3;
  buffer[0x19] = (byte)((int)password[0x31] << 4);
  buffer[0x19] = buffer[0x19] | (byte)(password[0x32] >> 2) & 0xf;
  buffer[0x1a] = (byte)((int)password[0x32] << 6);
  buffer[0x1a] = buffer[0x1a] | (byte)password[0x33] & 0x3f;
  return;
}

Lo step 2 non cambia il contenuto informativo, ma rimescola i bytes secondo una permutazione. Questo step utilizza una chiave che determina quale sarà l’effettiva permutazione: la chiave è data dai primi 4 bytes (la chiave non sarà permutata)

Stadio 2
void stage2(byte *code)
{
  int iVar1;
  int iVar2;
  long lVar3;
  long lVar4;
  long lVar5;
  byte abStack_20 [32];
  byte k0;
  byte k1;
  byte k2;
  byte k3;
  
  lVar5 = 2;
  k2 = code[2];
  k0 = *code;
  k1 = code[1];
  k3 = code[3];
  do {
    lVar4 = 0;
    iVar1 = 0x1b - (int)lVar5;
    if (0 < lVar5) {
      if (8 < lVar5) {
        do {
          iVar2 = (int)lVar4;
          abStack_20[iVar2] = code[iVar2 + iVar1];
          code[iVar2 + iVar1] = 0;
          abStack_20[iVar2 + 1] = code[iVar2 + 1 + iVar1];
          code[iVar2 + 1 + iVar1] = 0;
          abStack_20[iVar2 + 2] = code[iVar2 + 2 + iVar1];
          code[iVar2 + 2 + iVar1] = 0;
          lVar4 = lVar4 + 8;
          abStack_20[iVar2 + 3] = code[iVar2 + 3 + iVar1];
          code[iVar2 + 3 + iVar1] = 0;
          abStack_20[iVar2 + 4] = code[iVar2 + 4 + iVar1];
          code[iVar2 + 4 + iVar1] = 0;
          abStack_20[iVar2 + 5] = code[iVar2 + 5 + iVar1];
          code[iVar2 + 5 + iVar1] = 0;
          abStack_20[iVar2 + 6] = code[iVar2 + 6 + iVar1];
          code[iVar2 + 6 + iVar1] = 0;
          abStack_20[iVar2 + 7] = code[iVar2 + 7 + iVar1];
          code[iVar2 + 7 + iVar1] = 0;
        } while (lVar4 < lVar5 + -8);
      }
      for (; lVar4 < lVar5; lVar4 = lVar4 + 1) {
        abStack_20[(int)lVar4] = code[(int)lVar4 + iVar1];
        code[(int)lVar4 + iVar1] = 0;
      }
    }
    lVar4 = FUN_00120a00((ulong)k3 | (ulong)k2 << 8 | (ulong)k0 << 0x18 | (ulong)k1 << 0x10,lVar5);
    lVar3 = 0;
    if (0 < lVar5) {
      do {
        iVar2 = (int)(lVar4 + lVar3);
        if (lVar4 + lVar3 < lVar5) {
          code[iVar1 + (int)lVar3] = abStack_20[iVar2];
        }
        else {
          code[iVar1 + (int)lVar3] = abStack_20[iVar2 - (int)lVar5];
        }
        lVar3 = lVar3 + 1;
      } while (lVar3 < lVar5);
    }
    lVar5 = lVar5 + 1;
  } while (lVar5 < 0x18);
  return;
}

L’ultimo step ricostruisce le informazioni del personaggio (livello, statistiche, capsule) ricombinando i singoli bit in posizioni specifiche del buffer permutato dallo step precedente. Questo step controlla anche che la password sia internamente coerente tramite checksum e parità dei bit. Il controllo dei checksum è particolare, infatti controlla che la ricombinazione di alcuni bit della password generi esattamente i primi 4 byte della stessa. Tuttavia i bit usati per questo controllo di integrità sono bit aggiuntivi, che non portano alcuna informazione sul personaggio descritto dalla password che stiamo decodificando.

Stadio 3
undefined4 stage3(character_struct *character_struct,byte *code)
{
  short level;
  undefined4 uVar1;
  long next_i;
  uint checksum;
  ulong bit_counter;
  long i;
  short capsule [8];
  byte code_0x10;
  byte code_0x11;
  byte code_0x12;
  byte code_0x13;
  byte code_0x14;
  byte code_0x15;
  byte code_0x16;
  byte code_0x17;
  byte code_0x18;
  byte code_0x19;
  byte code_0x1a;
  byte code_0xc;
  byte code_0xd;
  byte code_0xe;
  byte code_0xf;
  byte code_10;
  byte code_11;
  byte code_4;
  byte code_5;
  byte code_6;
  byte code_7;
  byte code_8;
  byte code_9;
  short stat_0;
  ushort stat_1;
  short stat_2;
  short stat_3;
  short stat_4;
  short stat_5;
  short stat_6;
  
  code_4 = code[4];
  code_0xd = code[0xd];
  code_5 = code[5];
  code_6 = code[6];
  code_7 = code[7];
  checksum = (uint)code[3] | (uint)code[2] << 8 | (uint)*code << 0x18 | (uint)code[1] << 0x10;
  code_8 = code[8];
  code_9 = code[9];
  code_10 = code[10];
  code_11 = code[0xb];
  code_0xc = code[0xc];
  stat_1 = code_8 >> 1 & 1 |
           (code_8 >> 2 & 1) << 1 |
           (code_8 >> 4 & 1) << 2 | (ushort)(code_8 >> 7) << 4 | (code_8 >> 6 & 1) << 3;
  code_0xe = code[0xe];
  code_0xf = code[0xf];
  code_0x10 = code[0x10];
  code_0x11 = code[0x11];
  code_0x12 = code[0x12];
  code_0x13 = code[0x13];
  code_0x14 = code[0x14];
  code_0x15 = code[0x15];
  code_0x16 = code[0x16];
  code_0x18 = code[0x18];
  code_0x17 = code[0x17];
  code_0x19 = code[0x19];
  code_0x1a = code[0x1a];
  if (checksum ==
      ~((code_5 >> 6 & 1) << 0x1b |
        (code_4 & 1) << 0x1c |
        (code_4 >> 2 & 1) << 0x1d | (uint)(code_4 >> 6) << 0x1f | (code_4 >> 4 & 1) << 0x1e |
        (code_6 & 1) << 0x16 |
        (code_6 >> 2 & 1) << 0x17 |
        (code_6 >> 5 & 1) << 0x18 | (code_5 >> 3 & 1) << 0x1a | (code_5 & 1) << 0x19 |
        (code_7 >> 1 & 1) << 0x13 | (uint)(code_7 >> 7) << 0x15 | (code_7 >> 4 & 1) << 0x14 |
        (code_8 & 1) << 0x10 | (code_8 >> 5 & 1) << 0x12 | (code_8 >> 3 & 1) << 0x11 |
        (code_9 >> 2 & 1) << 0xd | (code_9 >> 6 & 1) << 0xf | (code_9 >> 5 & 1) << 0xe |
        (code_10 >> 1 & 1) << 10 | (uint)(code_10 >> 7) << 0xc | (code_10 >> 4 & 1) << 0xb |
        (code_11 & 1) << 7 | (code_11 >> 6 & 1) << 9 | (code_11 >> 3 & 1) << 8 |
        (code_0xc >> 2 & 1) << 4 | (uint)(code_0xc >> 7) << 6 | (code_0xc >> 5 & 1) << 5 |
        (code_0xd >> 1 & 1) << 1 | (uint)(code_0xd >> 7) << 3 | (code_0xd >> 4 & 1) << 2 |
       code_0xe >> 5 & 1)) {
    uVar1 = 0xffffffff;
    if (checksum ==
        ~((code_0xf >> 4 & 1) << 0x1d |
          (uint)(code_0xe >> 2) << 0x1f | (uint)(code_0xf >> 7) << 0x1e |
          (code_0x11 >> 6 & 1) << 0x19 |
          (code_0x10 >> 2 & 1) << 0x1a | (code_0xf >> 1 & 1) << 0x1c | (code_0x10 >> 6 & 1) << 0x1b
          | (code_0x13 >> 6 & 1) << 0x15 |
            (code_0x12 >> 2 & 1) << 0x16 |
            (code_0x11 >> 2 & 1) << 0x18 | (code_0x12 >> 6 & 1) << 0x17 |
          (code_0x14 >> 1 & 1) << 0x11 |
          (code_0x14 >> 4 & 1) << 0x12 |
          (code_0x13 >> 2 & 1) << 0x14 | (uint)(code_0x14 >> 7) << 0x13 |
          (code_0x16 >> 4 & 1) << 0xd |
          (uint)(code_0x16 >> 7) << 0xe | (code_0x15 >> 5 & 1) << 0x10 | (code_0x15 >> 2 & 1) << 0xf
          | (code_0x18 >> 6 & 1) << 9 |
            (code_0x17 >> 2 & 1) << 10 | (code_0x16 >> 1 & 1) << 0xc | (code_0x17 >> 6 & 1) << 0xb |
         code_0x1a >> 1 & 1 |
         (code_0x1a >> 2 & 1) << 1 |
         (code_0x1a >> 3 & 1) << 2 |
         (code_0x1a >> 4 & 1) << 3 |
         (code_0x1a >> 5 & 1) << 4 |
         (code_0x19 >> 1 & 1) << 5 |
         (code_0x19 >> 5 & 1) << 6 | (code_0x18 >> 3 & 1) << 8 | (code_0x18 & 1) << 7)) {
      bit_counter = 0;
      i = 0;
      do {
        next_i = i + 1;
        code_8 = code[(int)i];
        checksum = (uint)code_8;
        bit_counter = bit_counter + (long)(int)(code_8 & 1) +
                      (long)(int)((int)(uint)code_8 >> 1 & 1) +
                      (long)(int)((int)(uint)code_8 >> 2 & 1) + (long)(int)((int)checksum >> 3 & 1)
                      + (long)(int)((int)checksum >> 4 & 1) + (long)(int)((int)checksum >> 5 & 1) +
                      (long)(int)((int)checksum >> 6 & 1) + (long)((int)(uint)code_8 >> 7);
        i = next_i;
      } while (next_i < 0x1b);
      uVar1 = 0xffffffff;
      if ((bit_counter & 1) == 0) {
        stat_0 = (code_7 & 1 |
                 (code_7 >> 2 & 1) << 1 |
                 (code_7 >> 3 & 1) << 2 | (code_7 >> 6 & 1) << 4 | (code_7 >> 5 & 1) << 3) - 7;
        stat_2 = (code_9 & 1 |
                 (code_9 >> 1 & 1) << 1 |
                 (code_9 >> 3 & 1) << 2 | (ushort)(code_9 >> 7) << 4 | (code_9 >> 4 & 1) << 3) - 7;
        capsule[0] = (code_0xf >> 2 & 1 |
                     (code_0xf >> 3 & 1) << 1 |
                     (code_0xf >> 5 & 1) << 2 |
                     (code_0xf >> 6 & 1) << 3 |
                     (code_0xe & 1) << 4 |
                     (code_0xe >> 1 & 1) << 5 |
                     (code_0xe >> 3 & 1) << 6 |
                     (code_0xe >> 4 & 1) << 7 |
                     (ushort)(code_0xe >> 7) << 9 | (code_0xe >> 6 & 1) << 8) - 300;
        stat_3 = (code_10 & 1 |
                 (code_10 >> 2 & 1) << 1 |
                 (code_10 >> 3 & 1) << 2 | (code_10 >> 6 & 1) << 4 | (code_10 >> 5 & 1) << 3) - 7;
        stat_4 = (code_11 >> 1 & 1 |
                 (code_11 >> 2 & 1) << 1 |
                 (code_11 >> 4 & 1) << 2 | (ushort)(code_11 >> 7) << 4 | (code_11 >> 5 & 1) << 3) -
                 7;
        stat_5 = (code_0xc & 1 |
                 (code_0xc >> 1 & 1) << 1 |
                 (code_0xc >> 3 & 1) << 2 | (code_0xc >> 6 & 1) << 4 | (code_0xc >> 4 & 1) << 3) - 7
        ;
        stat_6 = (code_0xd & 1 |
                 (code_0xd >> 2 & 1) << 1 |
                 (code_0xd >> 3 & 1) << 2 | (code_0xd >> 6 & 1) << 4 | (code_0xd >> 5 & 1) << 3) - 7
        ;
        level = (code_6 >> 1 & 1 |
                (code_6 >> 3 & 1) << 1 |
                (code_6 >> 4 & 1) << 2 |
                (code_6 >> 6 & 1) << 3 |
                (ushort)(code_6 >> 7) << 4 |
                (code_5 >> 1 & 1) << 5 | (code_5 >> 4 & 1) << 7 | (code_5 >> 2 & 1) << 6) - 7;
        capsule[1] = (code_0x11 >> 4 & 1 |
                     (code_0x11 >> 5 & 1) << 1 |
                     (ushort)(code_0x11 >> 7) << 2 |
                     (code_0x10 & 1) << 3 |
                     (code_0x10 >> 1 & 1) << 4 |
                     (code_0x10 >> 3 & 1) << 5 |
                     (code_0x10 >> 4 & 1) << 6 |
                     (code_0x10 >> 5 & 1) << 7 | (code_0xf & 1) << 9 | (ushort)(code_0x10 >> 7) << 8
                     ) - 300;
        capsule[2] = ((ushort)(code_0x13 >> 7) |
                     (code_0x12 & 1) << 1 |
                     (code_0x12 >> 1 & 1) << 2 |
                     (code_0x12 >> 3 & 1) << 3 |
                     (code_0x12 >> 4 & 1) << 4 |
                     (code_0x12 >> 5 & 1) << 5 |
                     (ushort)(code_0x12 >> 7) << 6 |
                     (code_0x11 & 1) << 7 | (code_0x11 >> 3 & 1) << 9 | (code_0x11 >> 1 & 1) << 8) -
                     300;
        capsule[3] = (code_0x14 & 1 |
                     (code_0x14 >> 2 & 1) << 1 |
                     (code_0x14 >> 3 & 1) << 2 |
                     (code_0x14 >> 5 & 1) << 3 |
                     (code_0x14 >> 6 & 1) << 4 |
                     (code_0x13 & 1) << 5 |
                     (code_0x13 >> 1 & 1) << 6 |
                     (code_0x13 >> 3 & 1) << 7 |
                     (code_0x13 >> 5 & 1) << 9 | (code_0x13 >> 4 & 1) << 8) - 300;
        capsule[4] = (code_0x16 >> 2 & 1 |
                     (code_0x16 >> 3 & 1) << 1 |
                     (code_0x16 >> 5 & 1) << 2 |
                     (code_0x16 >> 6 & 1) << 3 |
                     (code_0x15 & 1) << 4 |
                     (code_0x15 >> 1 & 1) << 5 |
                     (code_0x15 >> 3 & 1) << 6 |
                     (code_0x15 >> 4 & 1) << 7 |
                     (ushort)(code_0x15 >> 7) << 9 | (code_0x15 >> 6 & 1) << 8) - 300;
        capsule[5] = (code_0x18 >> 4 & 1 |
                     (code_0x18 >> 5 & 1) << 1 |
                     (ushort)(code_0x18 >> 7) << 2 |
                     (code_0x17 & 1) << 3 |
                     (code_0x17 >> 1 & 1) << 4 |
                     (code_0x17 >> 3 & 1) << 5 |
                     (code_0x17 >> 4 & 1) << 6 |
                     (code_0x17 >> 5 & 1) << 7 |
                     (code_0x16 & 1) << 9 | (ushort)(code_0x17 >> 7) << 8) - 300;
        capsule[6] = (code_0x1a >> 6 & 1 |
                     (ushort)(code_0x1a >> 7) << 1 |
                     (code_0x19 & 1) << 2 |
                     (code_0x19 >> 2 & 1) << 3 |
                     (code_0x19 >> 3 & 1) << 4 |
                     (code_0x19 >> 4 & 1) << 5 |
                     (code_0x19 >> 6 & 1) << 6 |
                     (ushort)(code_0x19 >> 7) << 7 |
                     (code_0x18 >> 2 & 1) << 9 | (code_0x18 >> 1 & 1) << 8) - 300;
        if ((long)level ==
            (long)((int)stat_6 +
                  (int)stat_5 +
                  (int)stat_4 + (int)stat_3 + (int)stat_2 + (int)stat_0 + (int)(short)stat_1)) {
          i = 0;
          do {
            if ((capsule[(int)i] != 0x3ff) && (0x254 < capsule[(int)i])) {
              return 0xffffffff;
            }
            i = i + 1;
          } while (i < 7);
          uVar1 = 0;
          character_struct->char_id =
               (code_5 >> 5 & 1 |
               (ushort)(code_5 >> 7) << 1 |
               (code_4 >> 1 & 1) << 2 |
               (code_4 >> 3 & 1) << 3 | (ushort)(code_4 >> 7) << 5 | (code_4 >> 5 & 1) << 4) - 7;
          character_struct->level = level;
          character_struct->stats[0] = stat_0;
          character_struct->stats[1] = stat_1;
          character_struct->stats[2] = stat_2;
          character_struct->stats[3] = stat_3;
          character_struct->stats[4] = stat_4;
          character_struct->stats[5] = stat_5;
          character_struct->stats[6] = stat_6;
          character_struct->capsule[0] = capsule[0];
          character_struct->capsule[1] = capsule[1];
          character_struct->capsule[2] = capsule[2];
          character_struct->capsule[3] = capsule[3];
          character_struct->capsule[4] = capsule[4];
          character_struct->capsule[5] = capsule[5];
          character_struct->capsule[6] = capsule[6];
        }
        else {
          uVar1 = 0xffffffff;
        }
      }
    }
  }
  else {
    uVar1 = 0xffffffff;
  }
  return uVar1;
}

Nota: Si vede che i valori delle statistiche sono ottenuti ricombinando i bit del buffer e poi sottraendo 7 (eccezione fatta per la statistica del Ki). Questo -7 tornerà utile dopo.

Bene, quindi adesso possiamo invertire l’algoritmo e generare personaggi arbitrari? Non ancora per la verità, perché per generare un personaggio, diciamo Broly, abbiamo bisogno di conoscere il character_id di Broly, e per assegnargli le giuste capsule abbiamo bisogno di conoscere i giusti capsule_id. Dove possiamo trovare adesso tutti questi id numerici?

Trovare gli ID

Ci sono molti modi per trovare gli id, sia dei personaggi che delle capsule. Ad esempio diciamo che voglio trovare l’id di Broly: posso generare un personaggio base con character_id = 0, lv = 1, statistiche = [1,0,0,0,0,0,0] e capsule = [-1,-1,-1,-1,-1,-1] (-1 significa nessuna capsula). Inserendo questa password scoprirei che l’id 0 corrisponde a Goku, quindi procedo nel generare la password cambiando solo il character_id a 1, e così via. Questo è un processo lento (perchè prevede inserire a mano tutte le password) ma i personaggi sono circa 20, quindi decisamente fattibile. Questa è in effetti la strategia che ho usato per enumerare tutte le coppie (id, personaggio).

Il problema sorge quando voglio brutare le capsule. Non è così semplice perchè il gioco non ti dice quali capsule ha il personaggio della password che hai appena inserito. L’unico modo, per quanto ne so io, di capire se la capsula che hai assegnato è quella giusta è giocare contro il personaggio e vedere se durante lo scontro il computer utilizza quella capsula. Quindi i tempi sono molto più lunghi, per di più le capsule del gioco sono molto più numerose, ordine di 500.

Quello che possiamo fare è quindi provare a reversare il gioco più approfonditamente. Le informazioni su questi id si trovaeranno da qualche parte, probabilmente negli asset di gioco.

La strategia, molto più pigra, che ho adottato io è invece molto diversa: Sapevo che questo gioco ha alcune community di modder molto attive.

img

Esempio di una mod fatta dalla community

Dopo svariate ricerche sono riuscito a entrare nel server discord di una di queste community, ho quindi chiesto loro mi sapessero indicare dove trovare questa lista delle capsule, e loro mi hanno mandato un comodissimo foglio di calcolo con tutte le informazioni che cercavo. :)

img

Generazione arbitraria di personaggi

Adesso siamo finalmente in grado di generare i personaggi come più ci piacciono con uno script python come questo:

reversed.py
from typing import List

DICTIONARY = [
    'A','B','C','D','E','F','G','H','I','J','K','L','M',
    'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
    'a','b','c','d','e','f','g','h','i','j','k','l','m',
    'n','o','p','q','r','s','t','u','v','w','x','y','z',
    '!','#','$','%','&','@','-','+','*','(',')','?'
]


CHARACTERS_ID = [
    "GOKU", "KID GOKU", "KID GOHAN", "TEEN GOHAN", "ADULT GOHAN", "GREAT SAIYAMA", "GOTEN", "VEGETA", "FUTURE TRUNKS", "KID TRUNKS", "KRILLIN", "PICCOLO", "TIEN SHINHAN", "YAMCHA", "HERCULE SATAN", "VIDEL", "KAIOSHIN", "UUB", "RADITZ", "NAPPA", "GINYU", "RECOOME", "FRIEZA", "ANDROID 16", "ANDROID 17", "ANDROID 18", "DR. GERO", "CELL", "MAJIN BUU", "SUPER BUU", "KID BUU", "DABURA", "COOLER", "BARDOCK", "BROLY", "OMEGA SHENRON", "SAIBAMEN", "CELL JR."
]

def set_bit(output, position, bit, value):
    # set output[idx] at bit position to value (0 or 1)
    if value:
        output[position] |= (1 << bit)
    else:
        output[position] &= ~(1 << bit)

def set_byte(output, position, value):
    output[position] = value & 0xFF


def step3_inverse(character, checksum = 0xffffffff):
    output = [0]*27

    id_byte = character['character_id'] + 7 
    set_bit(output, 5, 5, id_byte & 1)
    set_bit(output, 5, 7, (id_byte >> 1) & 1)
    set_bit(output, 4, 1, (id_byte >> 2) & 1)
    set_bit(output, 4, 3, (id_byte >> 3) & 1)
    set_bit(output, 4, 5, (id_byte >> 4) & 1)
    set_bit(output, 4, 7, (id_byte >> 5) & 1)


    # # Level (8 bits, composite)
    # level = (
    #     ((b[6] >> 1) & 1) << 0 |
    #     ((b[6] >> 3) & 1) << 1 |
    #     ((b[6] >> 4) & 1) << 2 |
    #     ((b[6] >> 6) & 1) << 3 |
    #     ((b[6] >> 7) & 1) << 4 |
    #     ((b[5] >> 1) & 1) << 5 |
    #     ((b[5] >> 4) & 1) << 7 |
    #     ((b[5] >> 2) & 1) << 6
    # ) - 7
    level_byte = character['level'] + 7
    set_bit(output, 6, 1, level_byte & 1)
    set_bit(output, 6, 3, (level_byte >> 1) & 1)
    set_bit(output, 6, 4, (level_byte >> 2) & 1)
    set_bit(output, 6, 6, (level_byte >> 3) & 1)
    set_bit(output, 6, 7, (level_byte >> 4) & 1)
    set_bit(output, 5, 1, (level_byte >> 5) & 1)
    set_bit(output, 5, 2, (level_byte >> 6) & 1)
    set_bit(output, 5, 4, (level_byte >> 7) & 1)


    # stat0 = (
    #     ((b[7] >> 0) & 1) << 0 |
    #     ((b[7] >> 2) & 1) << 1 |
    #     ((b[7] >> 3) & 1) << 2 |
    #     ((b[7] >> 6) & 1) << 4 |
    #     ((b[7] >> 5) & 1) << 3
    # ) - 7
    stat_byte = character['stats'][0] + 7
    set_bit(output, 7, 0, stat_byte & 1)
    set_bit(output, 7, 2, (stat_byte >> 1) & 1)
    set_bit(output, 7, 3, (stat_byte >> 2) & 1)
    set_bit(output, 7, 5, (stat_byte >> 3) & 1)
    set_bit(output, 7, 6, (stat_byte >> 4) & 1)


    # stat1 = (
    #     ((b[8] >> 1) & 1) << 0 |
    #     ((b[8] >> 2) & 1) << 1 |
    #     ((b[8] >> 4) & 1) << 2 |
    #     ((b[8] >> 7) & 1) << 4 |
    #     ((b[8] >> 6) & 1) << 3
    # ) 
    stat_byte = character['stats'][1]
    set_bit(output, 8, 1, stat_byte & 1)
    set_bit(output, 8, 2, (stat_byte >> 1) & 1)
    set_bit(output, 8, 4, (stat_byte >> 2) & 1)
    set_bit(output, 8, 6, (stat_byte >> 3) & 1)
    set_bit(output, 8, 7, (stat_byte >> 4) & 1)


    # stat2 = (
    #     ((b[9] >> 0) & 1) << 0 |
    #     ((b[9] >> 1) & 1) << 1 |
    #     ((b[9] >> 3) & 1) << 2 |
    #     ((b[9] >> 7) & 1) << 4 |
    #     ((b[9] >> 4) & 1) << 3
    # ) - 7
    stat_byte = character['stats'][2] + 7
    set_bit(output, 9, 0, stat_byte & 1)
    set_bit(output, 9, 1, (stat_byte >> 1) & 1)
    set_bit(output, 9, 3, (stat_byte >> 2) & 1)
    set_bit(output, 9, 4, (stat_byte >> 3) & 1)
    set_bit(output, 9, 7, (stat_byte >> 4) & 1)

    # stat3 = (
    #     ((b[10] >> 0) & 1) << 0 |
    #     ((b[10] >> 2) & 1) << 1 |
    #     ((b[10] >> 3) & 1) << 2 |
    #     ((b[10] >> 6) & 1) << 4 |
    #     ((b[10] >> 5) & 1) << 3
    # ) - 7
    stat_byte = character['stats'][3] + 7
    set_bit(output, 10, 0, stat_byte & 1)
    set_bit(output, 10, 2, (stat_byte >> 1) & 1)
    set_bit(output, 10, 3, (stat_byte >> 2) & 1)
    set_bit(output, 10, 5, (stat_byte >> 3) & 1)
    set_bit(output, 10, 6, (stat_byte >> 4) & 1)

    # stat4 = (
    #     ((b[11] >> 1) & 1) << 0 |
    #     ((b[11] >> 2) & 1) << 1 |
    #     ((b[11] >> 4) & 1) << 2 |
    #     ((b[11] >> 7) & 1) << 4 |
    #     ((b[11] >> 5) & 1) << 3
    # ) - 7
    stat_byte = character['stats'][4] + 7
    set_bit(output, 11, 1, stat_byte & 1)
    set_bit(output, 11, 2, (stat_byte >> 1) & 1)
    set_bit(output, 11, 4, (stat_byte >> 2) & 1)
    set_bit(output, 11, 5, (stat_byte >> 3) & 1)
    set_bit(output, 11, 7, (stat_byte >> 4) & 1)

    # stat5 = (
    #     ((b[0xc] >> 0) & 1) << 0 |
    #     ((b[0xc] >> 1) & 1) << 1 |
    #     ((b[0xc] >> 3) & 1) << 2 |
    #     ((b[0xc] >> 6) & 1) << 4 |
    #     ((b[0xc] >> 4) & 1) << 3
    # ) - 7
    stat_byte = character['stats'][5] + 7
    set_bit(output, 12, 0, stat_byte & 1)
    set_bit(output, 12, 1, (stat_byte >> 1) & 1)
    set_bit(output, 12, 3, (stat_byte >> 2) & 1)
    set_bit(output, 12, 4, (stat_byte >> 3) & 1)
    set_bit(output, 12, 6, (stat_byte >> 4) & 1)

    # stat6 = (
    #     ((b[0xd] >> 0) & 1) << 0 |
    #     ((b[0xd] >> 2) & 1) << 1 |
    #     ((b[0xd] >> 3) & 1) << 2 |
    #     ((b[0xd] >> 6) & 1) << 4 |
    #     ((b[0xd] >> 5) & 1) << 3
    # ) - 7
    stat_byte = character['stats'][6] + 7
    set_bit(output, 13, 0, stat_byte & 1)
    set_bit(output, 13, 2, (stat_byte >> 1) & 1)
    set_bit(output, 13, 3, (stat_byte >> 2) & 1)
    set_bit(output, 13, 5, (stat_byte >> 3) & 1)
    set_bit(output, 13, 6, (stat_byte >> 4) & 1)

    # capsule0 = (
    #     ((b[15] >> 2) & 1) << 0 |
    #     ((b[15] >> 3) & 1) << 1 |
    #     ((b[15] >> 5) & 1) << 2 |
    #     ((b[15] >> 6) & 1) << 3 |
    #     ((b[14] >> 0) & 1) << 4 |   
    #     ((b[14] >> 1) & 1) << 5 |
    #     ((b[14] >> 3) & 1) << 6 |
    #     ((b[14] >> 4) & 1) << 7 |
    #     ((b[14] >> 7) & 1) << 9 |
    #     ((b[14] >> 6) & 1) << 8
    # ) - 300
    capsule_byte = character['capsules'][0] + 300
    set_bit(output, 15, 2, capsule_byte & 1)
    set_bit(output, 15, 3, (capsule_byte >> 1) & 1)
    set_bit(output, 15, 5, (capsule_byte >> 2) & 1)
    set_bit(output, 15, 6, (capsule_byte >> 3) & 1)
    set_bit(output, 14, 0, (capsule_byte >> 4) & 1)
    set_bit(output, 14, 1, (capsule_byte >> 5) & 1)
    set_bit(output, 14, 3, (capsule_byte >> 6) & 1)
    set_bit(output, 14, 4, (capsule_byte >> 7) & 1)
    set_bit(output, 14, 6, (capsule_byte >> 8) & 1)
    set_bit(output, 14, 7, (capsule_byte >> 9) & 1)

    # capsule1 = (
    #     ((b[17] >> 4) & 1) << 0 |
    #     ((b[17] >> 5) & 1) << 1 |
    #     ((b[17] >> 7) & 1) << 2 |
    #     ((b[16] >> 0) & 1) << 3 |
    #     ((b[16] >> 1) & 1) << 4 |
    #     ((b[16] >> 3) & 1) << 5 |
    #     ((b[16] >> 4) & 1) << 6 |
    #     ((b[16] >> 5) & 1) << 7 |
    #     ((b[15] >> 0) & 1) << 9 |
    #     ((b[16] >> 7) & 1) << 8
    # ) - 300
    capsule_byte = character['capsules'][1] + 300
    set_bit(output, 17, 4, capsule_byte & 1)
    set_bit(output, 17, 5, (capsule_byte >> 1) & 1)
    set_bit(output, 17, 7, (capsule_byte >> 2) & 1)
    set_bit(output, 16, 0, (capsule_byte >> 3) & 1)
    set_bit(output, 16, 1, (capsule_byte >> 4) & 1)
    set_bit(output, 16, 3, (capsule_byte >> 5) & 1)
    set_bit(output, 16, 4, (capsule_byte >> 6) & 1)
    set_bit(output, 16, 5, (capsule_byte >> 7) & 1)
    set_bit(output, 16, 7, (capsule_byte >> 8) & 1)
    set_bit(output, 15, 0, (capsule_byte >> 9) & 1)


    # capsule2 = (
    #     ((b[19] >> 7) & 1) << 0 |
    #     ((b[18] >> 0) & 1) << 1 |
    #     ((b[18] >> 1) & 1) << 2 |
    #     ((b[18] >> 3) & 1) << 3 |
    #     ((b[18] >> 4) & 1) << 4 |
    #     ((b[18] >> 5) & 1) << 5 |
    #     ((b[18] >> 7) & 1) << 6 |
    #     ((b[17] >> 0) & 1) << 7 |
    #     ((b[17] >> 3) & 1) << 9 |
    #     ((b[17] >> 1) & 1) << 8
    # ) - 300
    capsule_byte = character['capsules'][2] + 300
    set_bit(output, 19, 7, capsule_byte & 1)
    set_bit(output, 18, 0, (capsule_byte >> 1) & 1)
    set_bit(output, 18, 1, (capsule_byte >> 2) & 1)
    set_bit(output, 18, 3, (capsule_byte >> 3) & 1)
    set_bit(output, 18, 4, (capsule_byte >> 4) & 1)
    set_bit(output, 18, 5, (capsule_byte >> 5) & 1)
    set_bit(output, 18, 7, (capsule_byte >> 6) & 1)
    set_bit(output, 17, 0, (capsule_byte >> 7) & 1)
    set_bit(output, 17, 1, (capsule_byte >> 8) & 1)
    set_bit(output, 17, 3, (capsule_byte >> 9) & 1)


    # capsule3 = (
    #     ((b[20] >> 0) & 1) << 0 |
    #     ((b[20] >> 2) & 1) << 1 |
    #     ((b[20] >> 3) & 1) << 2 |
    #     ((b[20] >> 5) & 1) << 3 |
    #     ((b[20] >> 6) & 1) << 4 |
    #     ((b[19] >> 0) & 1) << 5 |
    #     ((b[19] >> 1) & 1) << 6 |
    #     ((b[19] >> 3) & 1) << 7 |
    #     ((b[19] >> 5) & 1) << 9 |
    #     ((b[19] >> 4) & 1) << 8
    # ) - 300
    capsule_byte = character['capsules'][3] + 300
    set_bit(output, 20, 0, capsule_byte & 1)
    set_bit(output, 20, 2, (capsule_byte >> 1) & 1)
    set_bit(output, 20, 3, (capsule_byte >> 2) & 1)
    set_bit(output, 20, 5, (capsule_byte >> 3) & 1)
    set_bit(output, 20, 6, (capsule_byte >> 4) & 1)
    set_bit(output, 19, 0, (capsule_byte >> 5) & 1)
    set_bit(output, 19, 1, (capsule_byte >> 6) & 1)
    set_bit(output, 19, 3, (capsule_byte >> 7) & 1)
    set_bit(output, 19, 4, (capsule_byte >> 8) & 1)
    set_bit(output, 19, 5, (capsule_byte >> 9) & 1)

    # capsule4 = (
    #     ((b[22] >> 2) & 1) << 0 |
    #     ((b[22] >> 3) & 1) << 1 |
    #     ((b[22] >> 5) & 1) << 2 |
    #     ((b[22] >> 6) & 1) << 3 |
    #     ((b[21] >> 0) & 1) << 4 |
    #     ((b[21] >> 1) & 1) << 5 |
    #     ((b[21] >> 3) & 1) << 6 |
    #     ((b[21] >> 4) & 1) << 7 |
    #     ((b[21] >> 7) & 1) << 9 |
    #     ((b[21] >> 6) & 1) << 8
    # ) - 300
    capsule_byte = character['capsules'][4] + 300
    set_bit(output, 22, 2, capsule_byte & 1)
    set_bit(output, 22, 3, (capsule_byte >> 1) & 1)
    set_bit(output, 22, 5, (capsule_byte >> 2) & 1)
    set_bit(output, 22, 6, (capsule_byte >> 3) & 1)
    set_bit(output, 21, 0, (capsule_byte >> 4) & 1)
    set_bit(output, 21, 1, (capsule_byte >> 5) & 1)
    set_bit(output, 21, 3, (capsule_byte >> 6) & 1)
    set_bit(output, 21, 4, (capsule_byte >> 7) & 1)
    set_bit(output, 21, 6, (capsule_byte >> 8) & 1)
    set_bit(output, 21, 7, (capsule_byte >> 9) & 1)

    # capsule5 = (
    #     ((b[24] >> 4) & 1) << 0 |
    #     ((b[24] >> 5) & 1) << 1 |
    #     ((b[24] >> 7) & 1) << 2 |
    #     ((b[23] >> 0) & 1) << 3 |
    #     ((b[23] >> 1) & 1) << 4 |
    #     ((b[23] >> 3) & 1) << 5 |
    #     ((b[23] >> 4) & 1) << 6 |
    #     ((b[23] >> 5) & 1) << 7 |
    #     ((b[22] >> 0) & 1) << 9 |
    #     ((b[23] >> 7) & 1) << 8
    # ) - 300
    capsule_byte = character['capsules'][5] + 300
    set_bit(output, 24, 4, capsule_byte & 1)
    set_bit(output, 24, 5, (capsule_byte >> 1) & 1)
    set_bit(output, 24, 7, (capsule_byte >> 2) & 1)
    set_bit(output, 23, 0, (capsule_byte >> 3) & 1)
    set_bit(output, 23, 1, (capsule_byte >> 4) & 1)
    set_bit(output, 23, 3, (capsule_byte >> 5) & 1)
    set_bit(output, 23, 4, (capsule_byte >> 6) & 1)
    set_bit(output, 23, 5, (capsule_byte >> 7) & 1)
    set_bit(output, 23, 7, (capsule_byte >> 8) & 1)
    set_bit(output, 22, 0, (capsule_byte >> 9) & 1)

    # capsule6 = (
    #     ((b[26] >> 6) & 1) << 0 |
    #     ((b[26] >> 7) & 1) << 1 |
    #     ((b[25] >> 0) & 1) << 2 |
    #     ((b[25] >> 2) & 1) << 3 |
    #     ((b[25] >> 3) & 1) << 4 |
    #     ((b[25] >> 4) & 1) << 5 |
    #     ((b[25] >> 6) & 1) << 6 |
    #     ((b[25] >> 7) & 1) << 7 |
    #     ((b[24] >> 2) & 1) << 9 |
    #     ((b[24] >> 1) & 1) << 8
    # ) - 300
    capsule_byte = character['capsules'][6] + 300
    set_bit(output, 26, 6, capsule_byte & 1)
    set_bit(output, 26, 7, (capsule_byte >> 1) & 1)
    set_bit(output, 25, 0, (capsule_byte >> 2) & 1)
    set_bit(output, 25, 2, (capsule_byte >> 3) & 1)
    set_bit(output, 25, 3, (capsule_byte >> 4) & 1)
    set_bit(output, 25, 4, (capsule_byte >> 5) & 1)
    set_bit(output, 25, 6, (capsule_byte >> 6) & 1)
    set_bit(output, 25, 7, (capsule_byte >> 7) & 1)
    set_bit(output, 24, 1, (capsule_byte >> 8) & 1)
    set_bit(output, 24, 2, (capsule_byte >> 9) & 1)

    

    

    set_byte(output, 3, checksum & 0xff)
    set_byte(output, 2, (checksum >> 8) & 0xff)
    set_byte(output, 1, (checksum >> 16) & 0xff)
    set_byte(output, 0, (checksum >> 24) & 0xff)

    #   if (checksum ==
    #   ~((code_5 >> 6 & 1) << 0x1b |
    #     (code_4 & 1) << 0x1c |
    #     (code_4 >> 2 & 1) << 0x1d | (uint)(code_4 >> 6) << 0x1f | (code_4 >> 4 & 1) << 0x1e |
    #     (code_6 & 1) << 0x16 |
    #     (code_6 >> 2 & 1) << 0x17 |
    #     (code_6 >> 5 & 1) << 0x18 | (code_5 >> 3 & 1) << 0x1a | (code_5 & 1) << 0x19 |
    #     (code_7 >> 1 & 1) << 0x13 | (uint)(code_7 >> 7) << 0x15 | (code_7 >> 4 & 1) << 0x14 |
    #     (code_8 & 1) << 0x10 | (code_8 >> 5 & 1) << 0x12 | (code_8 >> 3 & 1) << 0x11 |
    #     (code_9 >> 2 & 1) << 0xd | (code_9 >> 6 & 1) << 0xf | (code_9 >> 5 & 1) << 0xe |
    #     (code_10 >> 1 & 1) << 10 | (uint)(code_10 >> 7) << 0xc | (code_10 >> 4 & 1) << 0xb |
    #     (code_11 & 1) << 7 | (code_11 >> 6 & 1) << 9 | (code_11 >> 3 & 1) << 8 |
    #     (code_0xc >> 2 & 1) << 4 | (uint)(code_0xc >> 7) << 6 | (code_0xc >> 5 & 1) << 5 |
    #     (code_0xd >> 1 & 1) << 1 | (uint)(code_0xd >> 7) << 3 | (code_0xd >> 4 & 1) << 2 |
    #    code_0xe >> 5 & 1)) {
    set_bit(output, 5, 6, ~(checksum >> 0x1b ) & 1)
    set_bit(output, 4, 0, ~(checksum >> 0x1c ) & 1)
    set_bit(output, 4, 2, ~(checksum >> 0x1d ) & 1)
    set_bit(output, 4, 6, ~(checksum >> 0x1f ) & 1)
    set_bit(output, 4, 4, ~(checksum >> 0x1e ) & 1)
    set_bit(output, 6, 0, ~(checksum >> 0x16 ) & 1)
    set_bit(output, 6, 2, ~(checksum >> 0x17 ) & 1)
    set_bit(output, 6, 5, ~(checksum >> 0x18 ) & 1)
    set_bit(output, 5, 3, ~(checksum >> 0x1a ) & 1)
    set_bit(output, 5, 0, ~(checksum >> 0x19 ) & 1)
    set_bit(output, 7, 1, ~(checksum >> 0x13 ) & 1)
    set_bit(output, 7, 7, ~(checksum >> 0x15 ) & 1)
    set_bit(output, 7, 4, ~(checksum >> 0x14 ) & 1)
    set_bit(output, 8, 0, ~(checksum >> 0x10 ) & 1)
    set_bit(output, 8, 5, ~(checksum >> 0x12 ) & 1)
    set_bit(output, 8, 3, ~(checksum >> 0x11 ) & 1)
    set_bit(output, 9, 2, ~(checksum >> 0xd ) & 1)
    set_bit(output, 9, 6, ~(checksum >> 0xf ) & 1)
    set_bit(output, 9, 5, ~(checksum >> 0xe ) & 1)
    set_bit(output, 10, 1, ~(checksum >> 10 ) & 1)
    set_bit(output, 10, 7, ~(checksum >> 0xc) & 1)
    set_bit(output, 10, 4, ~(checksum >> 0xb) & 1)
    set_bit(output, 11, 0, ~(checksum >> 7 ) & 1)
    set_bit(output, 11, 6, ~(checksum >> 9 ) & 1)
    set_bit(output, 11, 3, ~(checksum >> 8 ) & 1)
    set_bit(output, 12, 2, ~(checksum >> 4 ) & 1)
    set_bit(output, 12, 7, ~(checksum >> 6 ) & 1)
    set_bit(output, 12, 5, ~(checksum >> 5 ) & 1)
    set_bit(output, 13, 1, ~(checksum >> 1 ) & 1)
    set_bit(output, 13, 7, ~(checksum >> 3 ) & 1)
    set_bit(output, 13, 4, ~(checksum >> 2 ) & 1)
    set_bit(output, 14, 5, ~(checksum) & 1)

    # if (checksum ==
    # ~((code_0xf >> 4 & 1) << 0x1d |
    #   (uint)(code_0xe >> 2) << 0x1f | (uint)(code_0xf >> 7) << 0x1e |
    #   (code_0x11 >> 6 & 1) << 0x19 |
    #   (code_0x10 >> 2 & 1) << 0x1a | (code_0xf >> 1 & 1) << 0x1c | (code_0x10 >> 6 & 1) << 0x1b
    #   | (code_0x13 >> 6 & 1) << 0x15 |
    #     (code_0x12 >> 2 & 1) << 0x16 |
    #     (code_0x11 >> 2 & 1) << 0x18 | (code_0x12 >> 6 & 1) << 0x17 |
    #   (code_0x14 >> 1 & 1) << 0x11 |
    #   (code_0x14 >> 4 & 1) << 0x12 |
    #   (code_0x13 >> 2 & 1) << 0x14 | (uint)(code_0x14 >> 7) << 0x13 |
    #   (code_0x16 >> 4 & 1) << 0xd |
    #   (uint)(code_0x16 >> 7) << 0xe | (code_0x15 >> 5 & 1) << 0x10 | (code_0x15 >> 2 & 1) << 0xf
    #   | (code_0x18 >> 6 & 1) << 9 |
    #     (code_0x17 >> 2 & 1) << 10 | (code_0x16 >> 1 & 1) << 0xc | (code_0x17 >> 6 & 1) << 0xb |
    #  code_0x1a >> 1 & 1 |
    #  (code_0x1a >> 2 & 1) << 1 |
    #  (code_0x1a >> 3 & 1) << 2 |
    #  (code_0x1a >> 4 & 1) << 3 |
    #  (code_0x1a >> 5 & 1) << 4 |
    #  (code_0x19 >> 1 & 1) << 5 |
    #  (code_0x19 >> 5 & 1) << 6 | (code_0x18 >> 3 & 1) << 8 | (code_0x18 & 1) << 7)) {

    set_bit(output, 15, 4, ~(checksum >> 0x1d ) & 1)
    set_bit(output, 14, 2, ~(checksum >> 0x1f ) & 1)
    set_bit(output, 15, 7, ~(checksum >> 0x1e ) & 1)
    set_bit(output, 17, 6, ~(checksum >> 0x19 ) & 1)
    set_bit(output, 16, 2, ~(checksum >> 0x1a ) & 1)
    set_bit(output, 15, 1, ~(checksum >> 0x1c ) & 1)
    set_bit(output, 16, 6, ~(checksum >> 0x1b ) & 1)
    set_bit(output, 0x13, 6, ~(checksum >> 0x15 ) & 1)
    set_bit(output, 0x12, 2, ~(checksum >> 0x16 ) & 1)
    set_bit(output, 0x11, 2, ~(checksum >> 0x18 ) & 1)
    set_bit(output, 0x12, 6, ~(checksum >> 0x17 ) & 1)
    set_bit(output, 0x14, 1, ~(checksum >> 0x11 ) & 1)
    set_bit(output, 0x14, 4, ~(checksum >> 0x12 ) & 1)
    set_bit(output, 0x13, 2, ~(checksum >> 0x14 ) & 1)
    set_bit(output, 0x14, 7, ~(checksum >> 0x13 ) & 1)
    set_bit(output, 0x16, 4, ~(checksum >> 0xd ) & 1)
    set_bit(output, 0x16, 7, ~(checksum >> 0xe ) & 1)
    set_bit(output, 0x15, 5, ~(checksum >> 0x10) & 1)
    set_bit(output, 0x15, 2, ~(checksum >> 0xf ) & 1)
    set_bit(output, 0x18, 6, ~(checksum >> 9 ) & 1)
    set_bit(output, 0x17, 2, ~(checksum >> 10 ) & 1)
    set_bit(output, 0x16, 1, ~(checksum >> 0xc ) & 1)
    set_bit(output, 0x17, 6, ~(checksum >> 0xb ) & 1)
    set_bit(output, 0x1a, 1, ~(checksum ) & 1)
    set_bit(output, 0x1a, 2, ~(checksum >> 1 ) & 1 )
    set_bit(output, 0x1a, 3, ~(checksum >> 2 ) & 1 )
    set_bit(output, 0x1a, 4, ~(checksum >> 3 ) & 1 )
    set_bit(output, 0x1a, 5, ~(checksum >> 4 ) & 1 )
    set_bit(output, 0x19, 1, ~(checksum >> 5 ) & 1 )
    set_bit(output, 0x19, 5, ~(checksum >> 6 ) & 1 )
    set_bit(output, 0x18, 3, ~(checksum >> 8 ) & 1 )
    set_bit(output, 0x18, 0, ~(checksum >> 7 ) & 1 )

    # count bits = 1 in output
    bit_count = sum(bin(byte).count('1') for byte in output)
    if bit_count % 2 == 1:
        set_bit(output, 3, 0, 1)
        set_bit(output, 0x1a, 1, 0)
        set_bit(output, 14, 5, 0)


        # set_bit(output, 3, 1, 1)
    bit_count = sum(bin(byte).count('1') for byte in output)
    # print("bit count:", bit_count)


    return output


def step2_inverse(byte_array):
    key = (byte_array[0] << 24) | (byte_array[1] << 16) | (byte_array[2] << 8) | byte_array[3]
    size = 23
    while size > 1:
        offset = 27 - size
        temp = byte_array[offset:offset+size]
        orig = [0] * size
        rotation = key % size
        for i in range(size):
            orig[(rotation + i) % size] = temp[i]
        byte_array[offset:offset+size] = orig
        size -= 1
    return byte_array


def step1_inverse(b: List[int], length=36) -> str:
    bits = ''.join(f'{x:08b}' for x in b)
    return ''.join(DICTIONARY[int(bits[i:i+6],2)] for i in range(0, length*6, 6))




def bytes_to_checksum(text = 'DAJE!'):
    bits = ''
    for i in range(5):
        index = DICTIONARY.index(text[i])
        bits += f'{index:06b}'
    bits+= '00'

    # print(hex(int(bits, 2)))
    return int(bits, 2)

def hex_print(byte_array):
    print(' '.join(f'{b:02x}' for b in byte_array))


if __name__ == "__main__":
    character = {
        'character_id': CHARACTERS_ID.index('BROLY'),
        'level': 70,
        'stats': [10, 10, 10, 10, 10, 10, 10],
        'capsules': [140, 141, 141, 142, 142, 143, 143]
    }

    print('Character data:')
    print(f"Character ID: {character['character_id']}")
    print(f"Level: {character['level']}")
    print(f"Stats: {character['stats']}")
    print(f"Capsules: {character['capsules']}")
    print(f"Key/checksum: {hex(bytes_to_checksum())}")
    print()


    a = step3_inverse(character, checksum = bytes_to_checksum())
    print('step3 inverse:')
    hex_print(a)

    b = step2_inverse(a)
    print('step2 inverse:')
    hex_print(b)

    password = step1_inverse(b)
    print('step1 inverse:')
    print(password)


    print('Password 6x6:')
    for i in range(0, len(password), 6):
        print(password[i:i+6], end=' ')
    print()

Ho poi scritto un tool disponibile qui per generare comodamente, tramite UI web, le password di tutti i personaggi che si desidera.

Integer underflow sulle statistiche

Ritorniamo allo stadio 3. Come abbiamo detto dai bit contenuti nella password viene estratto un numero per ogni statistica, sottraendo 7 a questo numero si ottiene il valore della statistica. Ad esempio:

stat_0 = (code_7 & 1 |
			(code_7 >> 2 & 1) << 1 |
			(code_7 >> 3 & 1) << 2 | (code_7 >> 6 & 1) <<
			4 | (code_7 >> 5 & 1) << 3) - 7;

Che succede se faccio in modo che questi bit encodino un valore minore di 7, diciamo 6 per esempio. Beh, otteniamo che la statistica in questione assume come valore 0xffff. Possiamo quindi sfruttare questo comportamento per generare un personaggio con molta più vita di quella dovrebbe poter avere.

Nota: Potrei provare a sfruttare lo stesso bug per generare un personaggio con statistiche di attacco o difesa altissime. Tuttavia ho notato che solo la statistica della salute beneficia di questo exploit. Probabilmente è l’unica statistica che viene castata a unsigned?

Nota: Per qualche motivo la statistica del Ki non soffre di questo problema, infatti non ha il -7: stat_1 = code_8 >> 1 & 1 | (code_8 >> 2 & 1) << 1 | (code_8 >> 4 & 1) << 2 | (ushort)(code_8 >> 7) << 4 | (code_8 >> 6 & 1) << 3;

Ecco il risultato, un personaggio fondamentalmente imbattibile.

img img


*Dispongo di una copia fisica del gioco