Approccio alla Programmazione per il Romhacking : “Sviluppo di un Estrattore di File”

Moderatori: Romhacker, Traduttori

Avatar utente
Reikashi
Romhacker
Messaggi: 65
Iscritto il: 27 gennaio 2016, 14:06
Contatta:

Approccio alla Programmazione per il Romhacking : “Sviluppo di un Estrattore di File”

Messaggio da leggereda Reikashi » 10 maggio 2016, 20:05

Approccio alla Programmazione per il Romhacking : “Sviluppo di un Estrattore di File”

Oggi ho intenzione di illustrarvi come è possibile scrivere uno stupidissimo estrattore di file da una ISO. Il gioco in questione è uno dei più semplici da modificare : “Kingdom Hearts Birth By Sleep (versione Final Mix e non)”.

Occorrente :

+ Un IDE per lo sviluppo di un’applicazione (Code::Blocks, Orwell Dev C/C++, Eclipse, ecc…)

+ Il Dump dell’UMD del gioco. Nel caso ne foste sprovvisti, potete ottenerlo seguendo questa guida qui :
Guida al dump dell'UMD

Per acquistare il gioco, andate qui : Amazon - KHBBS

+ Un Hex Editor (Hex Editor Neo, Madedit, XVI32 [Sconsigliato, ma se proprio lo volete usare… fatelo], ecc…)

+ Conoscenza delle basi di un linguaggio di programmazione (C, C++, C#, Java, ecc…)

PREMESSE :
Questo tutorial deve essere solo illustrativo e, in nessun modo, deve essere considerato come un perfetto esempio da seguire. Ho deciso di proposito di omettere qualsiasi tipo di ottimizzazione al codice, lettura da descrittori di file (filetable), usando anche schemi procedurali non propriamente consigliati durante delle SERIE sessioni di programmazione. Tutto ciò al fine di garantire un approccio più “immediato” con il sorgente del programma.

Qualsiasi file vogliate analizzare, dovrete farlo con l’ausilio di un Hex Editor. È importante sapere che esistono due modi di immagazzinare i dati in memoria di un calcolatore (o per meglio dire, due ‘ordini’):

– Little Endian, in cui i dati sono immagazzinati a partire dal byte meno significativo a quello più significativo (es. : se devo memorizzare il valore esadecimale 0x1234, lo scriverò come 0x3412). [Utilizzato da questo gioco e moltissimi altri]

– Big Endian, in cui si memorizzano i dati nell’ordine in cui sono scritti (es. : 0x1234 resterà invariato). [Meno comune, ma presente in diversi giochi e formati di file]

Bene, iniziamo subito con un’immagine dell’header dei file di testo del gioco :

Immagine

Dall’analisi dell’header emerge che :

– 4 Byte sono l’identificativo del file (0x40 = @, 0x43 = C, 0x54 = T, 0x44 = D). (Importante in questo tool!)
– 4 Byte sono sempre statici (0x01, 0x00, 0x00, 0x00). (Importante in questo tool!)
– 4 Byte che troverete spesso nel pattern dei puntatori, a volte incrementato di 80 ed altre no. (Poco importante al momento.)
– 2 Byte che seguono la formula (Offset_Testo – Offset_Proprietà_Testo)/32 ed indicano i puntatori. (Poco importante al momento.)
– 2 Byte che seguono la stessa formula di sopra, solo che indicano il numero di proprietà dei testi. (Poco importante al momento.)
– 4 Byte specificano il size dell’header. (Poco importante al momento.)
– 4 Byte costituiscono un puntatore ad un offset del file in cui sono contenute le proprietà dei testi del gioco (posizione della stampa delle stringhe, colore, dimensione font, tipologia della dialogue box, ecc…). (Poco importante al momento.)
– 4 Byte puntano all’offset della prima riga di testo contenuta nel file.
– 4 Byte di padding. (Poco importante al momento.)

Terminato l’header, compariranno i puntatori del testo. Qualora foste intenzionati a modificarli, basterà ignorare (almeno per il momento) i primi 4 byte che vi si presentano e modificare il valore che li succede (‘0x0C00’ e '0x0C27', dell’immagine di sopra, ne sono due esempi).

Ho contrassegnato i dati dell’header importanti e quelli che non lo sono per lo sviluppo di un semplice estrattore di file. A questo punto non resta che scrivere delle righe di codice, che per questioni di comodità scriverò in un unico main (si consiglia di dividere sempre il sorgente in più funzioni e procedure distinte), per poter vedere in pratica quanto appena esaminato :

Codice: Seleziona tutto

#include <stdio.h>
#include <stdlib.h>

#define BUFSIZE 8

int main()
{
    /*In C le dichiarazioni delle variabili, e le relative inizializzazioni, devono essere poste in cima al codice.*/
    FILE *ISO, *CTD; //*FILELOG; Questo File potrebbe essere usato qualora foste intenzionati a creare un file di log per salvare gli offset d'inizio del file.
    unsigned char buffer[BUFSIZE], nomeiso[128], nomectd[128]; //Creo tre array di caratteri in cui inserirò dei dati.
    unsigned short counter = 0, scelta = 0, flag = 0;
    unsigned int dummycounter = 0; //Questa variabile è inutile, ma è solo un modo stupido di impedire al programma di generare file enormi in caso di errore.
   // fpos_t pos = 0; Servirebbe nel caso in cui si volessero usare fgetpos() e fsetpos() per salvare ed impostare un offset nel file.

    //Mostra un menu testuale ed il copyright del programma.
    printf(":::::::::::: Kingdom Hearts Birth By Sleep/Final Mix CTD Extractor ::::::::::::\n");
    printf(":::::::::::::::::::::::::::::::::::: Developed by Sorakairi ::::::::::::::::::::::::::::::::::::\n\n");

    printf("This is a @CTD Extractor for Kingdom Hearts Birth By Sleep/Final Mix.\n"
           "Every extracted file contains text that shows up during gameplay.\n"
           "When using this simple tool, make sure you put your ISO file in the\n"
           "same folder of this .exe.\nThank you and best regards,\n\n"
           "\t\t\t\t\t\t-Sorakairi\n\n\n"
           "What do you want to do?\n1) Extract CTD Files [1]\n"
           "2) Exit [2]\nMake your choice: ");
    scanf("%hd", &scelta); //Salvo l'input dell'utente nella variabile 'scelta'.

    if(scelta == 2)
    {
        exit(100);
    }

    else if(scelta != 1 && scelta != 2)
    {
        printf("\n\nWrong input.\n\n");
        exit(101);
    }

    gets(nomeiso); //Salva la stringa data in input dall'utente all'interno dell'array di caratteri inserito tra parentesi.
*SCONSIGLIATO IL SUO UTILIZZO NELLA PROGRAMMAZIONE!
    //Apro la ISO in modalità lettura (binaria) e controllo se si sono verificati degli errori durante l'apertura. In caso affermativo, chiudo l'applicazione.
    ISO = fopen(nomeiso, "rb");
    if(ISO == NULL)
        exit(1);

/*Creo l'eventuale file di log.

    FILELOG = fopen("filelog.off", "wb");
    if(FILELOG == NULL)
        exit(3);
*/

    mkdir("@CTD"); //Creo una cartella chiamata @CTD nella directory dell'eseguibile.

    while(!feof(ISO)) //Questo ciclo itera fino alla fine del file ISO. *SCONSIGLIO L'USO DI FEOF NELLA PROGRAMMAZIONE!
    {
        //Leggo un numero BUFSIZE di elementi, ciascuno di 1 byte, dalla ISO e li salvo in buffer.
        fread(buffer, BUFSIZE, 1, ISO);

        //Confronto i byte contenuti nel buffer appena riempito con i byte iniziali dell'header di un @CTD.
        if(buffer[0] == 0x40 && buffer[1] == 0x43 && buffer[2] == 0x54
&& buffer[3] == 0x44 && buffer[4] == 0x01 && buffer[5] == 0x00
&& buffer[6] == 0x00 && buffer[7] == 0x00)
        {
            /*fgetpos(ISO, &pos); Da usare solo per la parte di file logging. Scrive nel filelog l'offset in esadecimale del @CTD.
            fprintf(FILELOG, "%X\n", pos - BUFSIZE);*/
            //Salverò in nomectd il nome del file CTD da estrarre. Il suo nome sarà, per ragioni di semplicità, un numero (si autoincrementerà di un'unità ad ogni iterazione).
            sprintf(nomectd, "@CTD\\%hd.CTD", counter++);

            //Apro un CTD in modalità scrittura ed esce nel caso di errore durante la creazione dello stesso.
            CTD = fopen(nomectd, "wb");
            if(CTD == NULL)
               exit(2);

            printf("Extracting : ");
            printf("%s\n", nomectd);

            dummycounter = 0; //Lo resetto ogni volta che un file CTD è stato trovato, altrimenti se supera il suo limite il programma smetterà di estrarre CTD.

            while(dummycounter < 100000)
            {
                fwrite(buffer, BUFSIZE, 1, CTD);
                fread(buffer, BUFSIZE, 1, ISO);

                flag = 0;

                //I file CTD si susseguono l'un l'altro nella ISO, ecco perché quando un nuovo file CTD è stato trovato, bisogna arrestare questo ciclo.
                if(buffer[0] == 0x40 && buffer[1] == 0x43 && buffer[2] == 0x54 &&
buffer[3] == 0x44 && buffer[4] == 0x01 && buffer[5] == 0x00 && buffer[6] == 0x00
&& buffer[7] == 0x00)
                {
                    fseek(ISO, -BUFSIZE, SEEK_CUR);
                    break;
                }

                 //Questi controlli servono solo per l'ultimo CTD, siccome dopo di esso compare un file ITE.
                else if(buffer[0] == 0x49 && buffer[1] == 0x54
&& buffer[2] == 0x45 && buffer[3] == 0x00)
                {
                    fseek(ISO, -0x04, SEEK_CUR);
                    flag = 1;
                    break;
                }

                if(flag == 1)
                    break;

                 dummycounter += BUFSIZE; //Lo aumento di BUFSIZE ogni volta, dal momento che leggo BUFSIZE elementi ogni volta.
            }

            fclose(CTD); //Chiudo il file CTD appena scritto.
        }
    }

    fclose(ISO); //Chiudo la ISO.
    //fclose(FILELOG); Chiudo il filelog.
    puts("Done!");
    getch();

    return 0;
}


* Sconsiglio l’utilizzo di gets(), scanf(), feof(), allocazione statica di array, e delle varie funzioni delle stringhe per operazioni di string matching per diversi motivi :

1) L’allocazione statica della memoria di un array, insieme all’uso di gets() e scanf() (per le stringhe), sono una delle cause principali di attacchi come il Buffer Overflow. Ci sono diversi articoli dettagliati al riguardo, personalmente consiglio la lettura del seguente: http://www.cprogramming.com/tutorial/secure.html.

2) Feof(). Una funzione comoda, ma con alcune falle. Ritorna un valore diverso da zero se e soltanto se l’EOF (End of File) è impostato nello stream del file. Ma che cosa vuol dire? Il controllo che essa compie avviene sull’indicatore di fine file e non sullo stream di dati aperto. È necessario che all’interno del ciclo while canonico in cui viene inserita, si aggiunga un controllo ad-hoc che verifichi l’effettivo raggiungimento di tale indicatore. Nel mio codice questo controllo non è stato integrato, in quanto sfrutto un flag ed il comando ‘break’ per arrestare il programma non appena sono stati estratti tutti i file necessari.

3) Le funzioni delle stringhe, come strcmp e la più sicura strncmp, non sono idonee per lo string matching su file. Se doveste cercare un pattern composto da un valore NULL (0x00) interposto fra altri byte, avreste dei problemi. Perché? Il byte 0x00 è interpretato, da diversi linguaggi di programmazione e dalle funzioni in questione, come carattere terminatore della stringa e pertanto se i byte precedenti ad esso coincidono con quelli presenti e quelli successivi no, questi ultimi verrebbero ignorati e vi ritrovereste a compiere operazioni non volute. Ad esempio estrarreste dati non voluti, leggereste zone del file errate, ecc… Il motivo per cui vedete quei lunghi e poco estetici ‘if’ è dovuto a questo problema.

IMPORTANTE : Siete vivamente pregati di non copiare e spacciare per vostro il codice/tutorial presente in questa pagina, né tanto meno di copiarlo da qualche altra parte senza la nostra autorizzazione.

– Sorakairi

Pubblicato il 25 Dicembre 2014.
Immagine

Torna a “Romhacking”

Chi c’è in linea

Visitano il forum: Nessuno e 1 ospite