|
Appunti informatica |
|
Visite: 1500 | Gradito: | [ Picolo appunti ] |
Leggi anche appunti:Lavorare con i file batchLavorare con i file batch L'interprete dei comandi (COMMAND.COM nella configurazione Dall'idea all'applicazioneDall'idea all'applicazione Vale la pena, a questo punto, di descrivere brevemente Separatore di espressioniSeparatore di espressioni In una sola istruzione C è possibile raggruppare |
Il presente paragrafo intende rappresentare esclusivamente un esempio di come i debugger e il linguaggio C possano essere utilizzati per gestire in profondità l'interazione tra hardware e software; il caso pratico descritto va interpretato esclusivamente come un espediente didattico: non abbiamo alcuna intenzione di incitare, neppure indirettamente, il lettore all'illecito.
Inoltre non è questa la sede per addentrarsi in una descrizione dettagliata delle tecniche utilizzate per proteggere il software dalla duplicazione; basta ricordare che esse si basano spesso su modalità particolari di formattazione del dischetto da proteggere, tali da rendere il medesimo non riproducibile dai programmi FORMAT e DISKCOPY. Il programma protetto effettua uno o più controlli, di norma accessi in lettura e/o scrittura alle tracce formattate in modo non standard, dai risultati dei quali è in grado di 'capire' se il proprio supporto fisico (in altre parole, il disco) è la copia originale oppure ne è un duplicato.
Veniamo ora al nostro esempio. Il programma in questione, che per comodità indichiamo col nome fittizio di PROG.COM, è protetto contro la duplicazione non autorizzata; il comando DISKCOPY è in grado di copiare il disco sul quale esso si trova, ma con risultati scadenti: la copia di PROG.COM, non appena invocata, visualizza un messaggio di protesta e restitusce il controllo al DOS. Come si può facilmente prevedere, neppure il comando COPY è in grado di superare l'ostacolo: copiando il contenuto del disco originale sul disco fisso (o su altro floppy disk) si ottiene un risultato analogo al precedente. Quando viene invocata la copia su hard‑disk di PROG.COM la spia del drive A: si illumina per qualche istante, viene visualizzato il solito messaggio e l'esecuzione si interrompe.
Proviamo a studiare un interessante frammento tratto dal disassemblato di PROG.COM, ottenuto mediante il solito DEBUG del DOS (i commenti sono stati aggiunti in seguito):
CS:0100 EB50 JMP 0136
CS:0199 3C20 XOR AL,AL ; azzera AL
CS:019B 8AD0 MOV DL,AL ; muove AL in DL
CS:019D 32F6 XOR DH,DH ; azzera DH
CS:019F 8CDB MOV BX,DS ; muove DS in ES
CS:01A1 8EC3 MOV ES,BX ; attraverso BX
CS:01A3 BBF007 MOV BX,18FB ; carica BX
CS:01A6 B90102 MOV CX,0201 ; carica CX
CS:01A9 B80402 MOV AX,0204 ; carica AX
CS:01AC CD13 INT 13 ; chiama int 13h
CS:01AE 720A JC 01BA ; salta se CF = 1
CS:01B0 BABA00 MOV DX,154F ; carica DX
CS:01B3 B409 MOV AH,09 ; stampa stringa
CS:01B5 CD21 INT 21 ; tramite DOS
CS:01B7 E97DFF JMP 0156 ; salta indietro
CS:01BA 80FC04 CMP AH,04 ; confr. AH con 4
CS:01BD 75F1 JNE 01B0 ; salta se !=
CS:01BF B82144 MOV AX,4421
Come per tutti i programmi .COM, l'entry point si trova alla locazione CS:0100: dopo l'istruzione JMP 0152 l'esecuzione prosegue a CS:0136, presumibilmente con le opportune operazioni di inizializzazione, ininfluenti ai nostri fini. Le istruzioni commentate sul listato meritano particolare attenzione. A CS:01AC viene richiesto l'int 13h (che gestisce i servizi BIOS relativi ai dischi; vedere pag.
I valori caricati nei registri della CPU rivelano che PROG.COM, mediante l'int 13h, legge in memoria i settori e della seconda traccia del lato del disco che si trova nel drive A:. Al ritorno dall'int 13h (CS:01AE) viene effettuato un test sul CarryFlag: se questo è nullo, cioè se in fase di lettura non si è verificato alcun errore (comportamento 'normale' se il disco è formattato secondo lo standard DOS) l'esecuzione prosegue a CS:01B1, è stampata una stringa (il messaggio di protesta) e viene effettuato un salto a ritroso alla locazione CS:0156, ove sono effettuate, con ogni probabilità, le operazioni di cleanup e uscita dal programma (infatti il programma termina l'esecuzione subito dopo avere visualizzato il messaggio). Se, al contrario, il CarryFlag vale (condizione di errore), l'esecuzione salta a CS:01BA, dove PROG.COM effettua un controllo sul valore che la chiamata all'int 13h ha restituito in AH. Se esso è 04h (codice di errore per 'settore non trovato') l'esecuzione prosegue a CS:01BF (controlli superati: il disco è la copia originale); in caso contrario avviene il salto a CS:01B0, con le conseguenze già evidenziate.
La strategia è ormai chiara: la traccia del lato della copia originale è formattata in modo non standard. Un tentativo di leggerne alcuni settori determina il verificarsi di una condizione di errore, ed in particolare di 'settore non trovato'. Se l'int 13h non riporta entrambi questi risultati, allora PROG.COM 'conclude' che il disco è una copia non autorizzata. Si tratta ora, semplicemente, di trarlo in inganno.
Smascherata la strategia di PROG.COM, occorre resistere alla tentazione di modificare il codice disassemblato, ad esempio sostituendo una serie di NOP alle istruzioni comprese tra CS:01AD e CS:01BE (significherebbe eliminare l'accesso al disco e tutti i controlli), e riassemblarlo per ottenerne una versione meno 'agguerrita': si rischierebbe di incappare in altri trabocchetti e rendersi la vita difficile senza alcuna utilità. E' sicuramente più opportuno simulare il verificarsi delle condizioni di errore ricercate in fase di test: per ottenere tale risultato è sufficiente un programmino in grado di installare un gestore personalizzato dell'int 13h.
Questo deve scoprire se la chiamata proviene da PROG.COM: in tal caso occorre restituire le ormai note condizioni di errore senza neppure accedere al disco; altrimenti è sufficiente concatenare la routine originale di interrupt.
Intercettare la chiamata di PROG.COM è semplice: basta un'occhiata alla tabellina riportata poc'anzi per capire che un test sui registri AX CX e DX può costituire una 'trappola' (quasi) infallibile.
Ecco il listato:
LOADPROG.C - Barninga_Z! - 1990
Lancia PROG.COM gestendo opportunamente l'int 13h
COMPILABILE SOTTO BORLAND C++ 2.0
bcc -O -d -mt -lt loadprog.c
#pragma inline
#pragma option -k- // fondamentale!!! Evita std stack frame
#include <stdio.h>
#include <process.h>
#include <dos.h>
#define CHILD_NAME 'PROG.COM' // programma da lanciare
char *credit =
'GO-PROG.EXE - Cracking loader for 'CHILD_NAME' - Barninga_Z!, 1992nn
Press a key when readyan';
char *errorP =
'Error while executing 'CHILD_NAME'a';
void oldint13h(void) // dummy function: puntatore a int 13h originale
void far newint13h(void)
}
else
asm jmp dword ptr oldint13h; // concatena l'int 13h originale
void cdecl kbdclear(void)
void cdecl main(void)
La struttura di LOADPROG.C è semplice: esso si compone di tre routine, a ciascuna delle quali è affidato un compito particolare.
La funzione main() installa il nuovo vettore dell'int 13h, lancia PROG.COM mediante spawnl() e, al termine dell'esecuzione di PROG.COM, ripristina il vettore originale dell'int 13h. Si noti che spawnl() viene invocata con la costante manifesta (definita in PROCESS.H P_WAIT: ciò significa che LOADPROG.COM rimane in RAM durante l'esecuzione di PROG.COM (che ne costituisce un child process) in attesa di riprendere il controllo al termine di questo, in quanto è necessario effettuare il ripristino dell'int 13h originale. Se non si prendesse tale precauzione, una chiamata all'int 13h da parte di un programma eseguito successivamente avrebbe conseguenze imprevedibili (e quasi sicuramente disastrose, come al solito).
La funzione newint13h() è il gestore truffaldino dell'int 13h. Essa, in ingresso, controlla se i registri AX BX e DX contengono i valori utilizzati da PROG.COM nella chiamata all'interrupt: in tal caso viene posto uguale a il CarryFlag AX a e AL a ; il controllo ritorna alla routine chiamante senza che sia effettuato alcun accesso al disco. Si noti l'istruzione RET 2, che sostituisce la più consueta IRET. Una chiamata ad interrupt salva automaticamente sullo stack i flag: se newint13h() restituisse il controllo a PROG.COM con una IRET, questa ripristinerebbe in modo altrettanto automatico i flag prelevandoli dallo stack e l'istruzione STC non avrebbe alcun effetto; d'altra parte una RET senza parametro 'dimenticherebbe' una word sullo stack, causando probabilmente un crash di sistema (vedere pag. e seguenti). Se, al contrario, i valori di AX BX e DX non sono quelli cercati, newint13h() concatena il vettore originale saltando all'indirizzo salvato da getvect() nei byte riservati dalla funzione jolly oldint13h() , lasciando che tutto proceda come se LOADPROG non esistesse.
La funzione kbdclear() pulisce il buffer della tastiera e attende la pressione di un tasto: maggiori particolari sull'argomento a pag.
Un'ultima osservazione: la direttiva
#pragma option -k-
evita che il compilatore generi il codice necessario al mantenimento della standard stack frame (vedere pag. ). In assenza di tale opzione sarebbe indispensabile aggiungere l'struzione POP BP prima della RET e della JMP in newint13h(); inoltre si potrebbe eliminare l'istruzione DD 0 in oldint13h() in quanto il codice della funzione occuperebbe di per sé 5 byte (gli opcode corrispondenti alle istruzioni necessarie alla standard stack frame stessa). La direttiva può essere eliminata qualora si specifichi ‑k‑ tra le opzioni sulla riga di comando del compilatore.
A cosa può servire un esempio di pirateria? Ovviamente, a fornire spunti utili in situazioni reali (e lecite!). Vi sono programmi, piuttosto datati, che alla partenza modificano vettori di interrupt senza poi ripristinarli in uscita. Se tra i vettori modificati ve ne sono alcuni utilizzati da più recenti software di sistema, il risultato è quasi certamente la necessità di un reset della macchina. Un loader analogo a quello testè presentato può validamente ovviare.
Non si tratta di un caso reale. Il frammento di codice disassemblato qui riportato è stato costruito appositamente ai fini dell'esempio.
Null OPeration; istruzione assembler priva di qualunque effetto ed avente il solo scopo di occupare un byte nel file eseguibile.
In effetti, PROG.COM potrebbe incorporare una routine per l'effettuazione di una sorta di checksum sul file stesso, al fine di verificare, ad esempio, che la somma dei valori esadecimali dei byte che compongono il codice binario (o almeno la parte di esso che comprende il frammento esaminato) corrisponda ad un valore predeterminato, scoprendo così eventuali modifiche al codice originale.
Appunti su: |
|