, ,

Unity vuole tenere tutto sotto controllo? Mostragli chi comanda e salta il controllo CRC per tradurre i file a piacimento!

Un’avventura insolita e divertente si è presentata di fronte a noi grazie a Polyzen, un collega traduttore. Ci siamo trovati ad affrontare un problema alquanto strano: era impossibile tradurre i file del gioco Fabledom.

Ma partiamo dall’inizio…

I file da tradurre potevano essere facilmente modificati tramite UABEA (abbiamo già discusso di questo strumento eccezionale qui: UABEA: la chiave segreta per trasformare le risorse di Unity!). Il problema era che i file modificati non venivano accettati dal gioco. Ecco cosa appariva una volta modificato un file.

Nei log del gioco veniva lanciata questa eccezione:

CRC Mismatch. Provided 4a4dc0d9, calculated b57b8335 from data. Will not load AssetBundle 'aa\StandaloneWindows64\localization-string-tables-english(en)_assets_all.bundle'

A questo punto si sono delineate due possibilità: la strada più elegante consisteva nell’effettuare uno spoofing del CRC b57b8335, iniettandolo nel nuovo pacchetto (anche se avrebbe richiesto del lavoro aggiuntivo per tutti i pacchetti modificati). Oppure, c’era l’alternativa più “sporca”… modificare Unity in modo tale da non calcolare il CRC.

Dato il nostro stato d’animo svogliato e pigro, la seconda opzione ci è subito sembrata la migliore e l’unica praticabile. Quindi, senza esitazione, abbiamo fatto affidamento sulla nostra vecchia e fidata IDA Free.

Okay, ammettiamolo, IDA fa venire i brividi, ma ci sono affezionato. Lo uso da molto tempo e devo dire che è un vero mostro per le sue potenzialità.

Per farla breve, il controllo del CRC di Unity risiede nella UnityPlayer.dll. Con l’aiuto di IDA, abbiamo disassemblato questo file per capire dove e come veniva effettuato il controllo del CRC.

Si disassembla!

Apriamo IDA e carichiamo UnityPlayer.dll

Ci troveremo davanti al codice in Assembler X64 della dll… bene… moooolto bene… Ora, qual era il messaggio di errore? CRC Mismatch. Provided blah blah blah, perfetto.

Apriamo il menu Search -> Texts… [ALT+T] e nel box di ricerca inseriamo CRC Mismatch. Provided

Cliccando su OK inizierà a cercare l’occorrenza nelle risorse delle stringhe della libreria e *POOOF*

Troverà l’occorrenza e, IDA nella sua infinita bontà ci indicherà anche chi utilizza questa risorsa. Nel caso specifico le due subroutine:

sub_18097BB70:loc_18097BC1C
sub_1809871B0+227↑o

Perfetto, ora andiamo a vederle una per volta. Click con il tasto destro sulla prima e selezioniamo Jump in a new Window

Nel nuovo Tab ci troveremo nel punto del codice della subroutine dove viene utilizzata quella stringa

Ora la parte più divertente…tasto destro e scegliamo Graphic View

In questa fantastica nuova vista vedremo in modo visuale il Flow del codice, dove ci si rende conto che il JZ [in azzurrino] (Jump Zero, ovvero viene eseguito il salto se il valore è zero) divide il flusso in due (rosso e verde)… se non salta (rosso), incorriamo nell’errore di CRC, se invece salta (verde), sorpassa la sezione del controllo (CRC mismatch è il blocco in fondo all’immagine).

Bene, facciamolo saltare sempre…come fare? Semplice, lo si trasforma in un JMP incodizionato, ovvero un JMP che salta sempre.

Piccola digressione sulla codifica (davvero base) delle istruzioni ASM per i salti in esadecimale:

  1. Salti condizionati con un byte:
    • L’opcode esadecimale comune per i salti condizionati a un byte è 7X, dove x rappresenta un valore esadecimale corrispondente al tipo di salto. Ad esempio, JZ (Jump if Zero) ha l’opcode 74, JNZ (Jump if Not Zero) ha l’opcode 75, ecc.
    • La codifica completa include anche un byte aggiuntivo che rappresenta l’offset a 8 bit relativo all’indirizzo di destinazione del salto.
    • Esempio di codifica completa: 7X YY (dove x rappresenta il tipo di salto e YY rappresenta l’offset a 8 bit)
  2. Salti incondizionati con un byte:
    • L’opcode esadecimale comune per i salti incondizionati a un byte è EB.
    • La codifica completa include anche un byte aggiuntivo che rappresenta l’offset a 8 bit relativo all’indirizzo di destinazione del salto.
    • Esempio di codifica completa: EB YY (dove YY rappresenta l’offset a 8 bit)
  3. Salti condizionati con due byte:
    • L’opcode esadecimale comune per i salti condizionati a due byte è 0F 8X, dove x rappresenta un valore esadecimale corrispondente al tipo di salto.
    • La codifica completa include anche un word (16 bit) o un dword (32 bit) che rappresenta l’offset relativo all’indirizzo di destinazione del salto.
    • Esempio di codifica completa: 0F 8X YY YY (dove x rappresenta il tipo di salto e YY YY rappresenta l’offset a 16 o 32 bit)
  4. Salti incondizionati con un byte ma indirizzo a 16 o 32 bit
    • L’opcode esadecimale per i salti incondizionati a un byte è E9
    • Esempio di codifica completa: E9 XX XX XX XX (se l’offset è a 32 bit) o E9 XX XX (se l’offset è a 16 bit)

La differenza principale tra i salti codificati con un byte e quelli codificati con due byte è la dimensione dell’offset. I salti con un byte utilizzano un offset a 8 bit, mentre i salti con due byte utilizzano un offset a 16 o 32 bit, consentendo di raggiungere indirizzi di memoria più lontani (ad eccezione del JMP al punto 4).

Noi ci troviamo nel caso 3, andiamo a vedere la codifica esadecimale. Per farlo, copiamo l’indirizzo del JZ [000000018097BC06] e ci spostiamo nel Tab Hex View.

Clicchiamo sul menu Jump -> Jump to address… e inseriamo 000000018097BC06

Ecco il nostro JZ!

0F 84 2F 01 00 00

Ora dobbiamo cambiarlo in JMP, usando la codifica 4 poichè l’indirizzo non è a 8 bit. Quindi l’OpCode da usare è E9. Ma come facciamo a sostituirlo? il JZ è 0F 84 noi abbiamo solo E9…bene, utilizziamo un altro OpCode: il NOP ovvero No Operation, un Comando che come piace a noi…non fa nulla 🙂 e la sua codifica in Esadecimale è 90

Quindi sostituiremo 0F 84 2F 01 00 00 con 90 E9 2F 01 00 00. Per farlo potete aprire la dll con un qualsiasi editor esadecimale, cercate una sequenza di byte relativamente lunga, e controllate che ci sia solo quella nel file (non vogliamo modificare a caso codice all’interno della DLL). Ad esempio potete prendere:

C9 74 76 45 3B C1 0F 84 2F 01 00 00 48 8D 87 48
01 00 00 80 78 20 01 74 03 48 8B 00 48 8D 15 BD
A2 E1 00 48 89 44 24 20 48 8D 4D E7 E8 5F 8E BC
FF 80 78 20 01 74 03 48 8B 00 4C 8B C0 BA 02 00
00 00 48 8B CF E8 66 DF 00 00 44 38 7D 07 75 19
8B 55 0B 4C 8D 05 02 E9 CE 00 48 8B 4D E7 41 B9
0D 02 00 00 E8 F7 B9 8F FF 32 C0 48 81 C4 A0 00
00 00 41 5F 5F 5E 5B 5D C3 48 8B 15 80 0A 0D 01
48 8D 4F 70 4C 89 B4 24 E0 00 00 00 48 8B C3 4C
89 7D 6F C7 45 0B 01 00 00 00 44 88 7D E7 40 88

cercarla nel file e modificarla così:

C9 74 76 45 3B C1 90 E9 2F 01 00 00 48 8D 87 48
01 00 00 80 78 20 01 74 03 48 8B 00 48 8D 15 BD
A2 E1 00 48 89 44 24 20 48 8D 4D E7 E8 5F 8E BC
FF 80 78 20 01 74 03 48 8B 00 4C 8B C0 BA 02 00
00 00 48 8B CF E8 66 DF 00 00 44 38 7D 07 75 19
8B 55 0B 4C 8D 05 02 E9 CE 00 48 8B 4D E7 41 B9
0D 02 00 00 E8 F7 B9 8F FF 32 C0 48 81 C4 A0 00
00 00 41 5F 5F 5E 5B 5D C3 48 8B 15 80 0A 0D 01
48 8D 4F 70 4C 89 B4 24 E0 00 00 00 48 8B C3 4C
89 7D 6F C7 45 0B 01 00 00 00 44 88 7D E7 40 88

e salvare il file! Bene, metà del lavoro è fatto!

Compito a casa, applicate lo stesso metodo per l’altro JZ, quello presente nella subroutine sub_1809871B0+227↑o

non questo JZ…

Una volta fatto, avrete la UnityPlayer.dll ripulita dai vari check di CRC… e BAAAAAM

Apro questo post nel forum per eventuali domande o dubbi: Rimuovere il CRC check da Unity

Una risposta a “Unity vuole tenere tutto sotto controllo? Mostragli chi comanda e salta il controllo CRC per tradurre i file a piacimento!”

Lascia un commento