View Full Version : leggere eseguibile.
telluccio
08-11-2005, 18:05
ciao a tutti
eventualmente giustificate la mia domanda con la mia inesperienza. :rolleyes:
scrivo un sorgente, lo compilo e genero un eseguibile.
in sintesi l eseguibile e il risultato della "traduzione" del mio sorgente.
l eseguibile contiene valori in binario che il processore interpreta come istruzioni.
da qualche parte ho letto che il processore legge l eseguibile dal primo all ultimo indirizzo dove risiede, in ordine sequenziale.
quindi faccio un programma che legge valori binario per stringhe... e leggo un eseguibile.
e lecito credere che il processore legga una stringa di istruzioni per interpretarla fino a '\n' ...per poi passare alla sucessiva??
come posso interpretare anche in linea di massima, quelle stringhe??
e giusto pensare che le prime parti dell eseguibile siano il "boot"??
spero di non aver sparato troppo...
ciao :D
Fenomeno85
08-11-2005, 18:40
mmm non ci ho capito un gran che.
Non penso proprio che il processore si vada a leggere tutte le istruzioni. Poi a seconda dell'implementazione dei processi si usano diversi tipi di scheduling. Se non erro quello dei processi è sotto il round robin sotto windows e linux. Poi ovvio che l'implemetazione te la puoi fare anche in sottomodi.
Cmq la teoria per spiegare come un compilatore o un interprete traduce o interpreta il codice è abbastanza lunga.
Cmq quando esegui un programma avrai il linker che serve a caricare le libreri esterne, poi avrai il loader che associa gli indirrizzi relativi a quelli assoluti.
Poi avrai anche la paginazione se è supportata cioè te non carichi tutto l'eseguibile ma vai a caricare una pagina del codice e una pagina per lo stack.
Poi a seconda di come è strutturato incomincerai ad avere un bel page fault e così caricherai la pagina che ti serve. Va be poi ci sarebbe da parlare di mmu e mucca pazza ma traslascio sta parte :D
Adesso è detto alla carlona. Se vuoi sapere di + prenditi un libro :D
Per il fatto delle stringhe non ho capito che chiedi
~§~ Sempre E Solo Lei ~§~
telluccio
08-11-2005, 19:13
ok...grazie per esserti espresso alla "carlona", gia cosi devo rileggere un po le dritte che mi dai.
provo a interpretare il tuo concetto (per me complesso).
per le stringhe intendo se le operazioni (che gentilmente mi hai spiegato) sono distinte per stringhe e terminate da '\n' (in .c)..
:doh: spero di essermi spiegato meglio.
ciao
Fenomeno85
08-11-2005, 19:19
aspetta devi definire due cose:
- stringhe definite come costanti;
- stringhe prese in input.
Le prime se non erro vengono caricate immediatamente in memoria, le stringhe in input ci possono essere varie strade a seconda di come allochi la stringa.
Se la allochi nel metodo
char string[20];
il loader associa a string un valore di indirizzo fisico e alloca direttamente lo spazio
char *string;
allora qui alloca semplicemente i 32bit o 64 a seconda dell'architettura e poi quando richiamerai malloc o realloc a seconda allocherà spazio. Ovviamente se con realloc lui non ha spazio sufficiente viene spostato il contenuto in una nuova zona. Non so se mi son spiegato o ho detto delle minchiate colossali :D ai posteri l'ardua sentenza :D
~§~ Sempre E Solo Lei ~§~
telluccio
08-11-2005, 19:29
:mbe:
aspetta fenomeno...
scrivo codice megari mi spiego meglio..
in=fopen("file.exe","rb")
while((c=getc(in))!=EOF&&c!='\n')
per stringhe intendo quelle che leggo da file.exe......
ciao
Fenomeno85
08-11-2005, 19:33
:mbe:
aspetta fenomeno...
scrivo codice megari mi spiego meglio..
in=fopen("file.exe","rb")
while((c=getc(in))!=EOF&&c!='\n')
per stringhe intendo quelle che leggo da file.exe......
ciao
non ho voglia di rileggere il primo post ... ma avevi chiesto come funziona l'apertura a la lettura di un file :wtf:
cmq non vedo come possa funzionare la lettura di un file eseguibile in quel modo.
Il codice che hai non è mica un carattere. Se vuoi leggere il codice devi:
disassemblarlo e capirlo (auguri e figli maschi).
Poi non credo che sia possibile ritornare esattamente al codice sorgente di partenza se si fanno anche ottimizzazioni del codice.
~§~ Sempre E Solo Lei ~§~
telluccio
08-11-2005, 21:05
grazie fenomeno, pensavo si potesse spiegare facilmente......
ciao
jappilas
08-11-2005, 21:26
- dovresti avere una conoscenza dell'assembly della macchina
- dovresti sapere come far corrispondere una sequenza di 0 e 1 a una sequenza di istruzioni (cosa complicata dal fatto che ogni istruzione x86 ha un opcode di lunghezza variabile), quindi ti serve conoscere la codifica binaria di ogni istruzione
- dovresti avere una conoscenza perfetta del funzionamento delle impostazioni del compilatore (tante volte a seconda delle #define nel codice o di certe flag passate nella riga di comando, viene generato codice di tipo diverso -> la sequenza in assembly ha ampi margini di variabilita')
- e soprattutto dovresti conoscere come "ragiona" un compilatore per convertire blocchi di codice di alto livello in funzioni procedurali a livello HW, (che istruzioni genera in caso di if- o while- statement ad esempio ) dovresti conoscere il modo in cui si comporta con chiamate a funzione, passaggio di argomenti per stack, il funzionamento delle indirezioni ...
a quel punto, con molta pazienza, puoi risalire, dato un tuo codice sorgente in linguaggio c/c++, ad una possibile sequenza binaria :sofico:
telluccio
08-11-2005, 22:24
sempre per curiosita...quante saranno le persone capaci di questo??
per persone intendo programmatori....o e solo una questione di tempi...
ciao
fuocofatuo
09-11-2005, 03:01
Essenzialmente, è come contare le lenticchie contenute in dozzine di camion avendo a disposizione solo un rosario: nessun folle lo farebbe, a meno che non si tratti di un programma estremamente semplice ed estremamente importante.
Come se non bastasse, può essere fattibile per programmi scritti in assembly, ben difficilmente per i linguaggi di alto livello. Nel primo caso, disassemblando ottieni qualcosa di molto simile a quanto ha scritto l'autore, e quindi interpretare quanto ha scritto è solo -incredibilmente arduo-. Nel caso dei linguaggi di alto livello, le complessità introdotte dai compilatori fan sì che il compito sia pressochè -impossibile-.
oddiosanto quante sparate che avete fatto tutti quanti O_________O
l'unico che ha detto cose corrette è jappilas...
allora, ricominciamo daccapo: telluccio, per leggere il contenuto di un exe devi conoscere il suo formato, che è il PE (scaricati il doc presente in MSDN, se non lo trovi te lo passo io).
una volta scaricato il doc avrai una visione molto più chiara al di sopra delle boiate di un certo non dico chi altrimenti fenomeno si offende: scoprirai ad es. che il file inizia con degli headers per poi essere suddiviso in cosiddette sezioni. se tu vuoi disassemblare un programma, la sezione che ti interessa è quella contenente il codice dello stesso, la quale sezione spesso si chiama ".text", ma in alcuni compilatori (Borland) si chiama "CODE".
disassemblare un programma C o C++ odierno per quanto mi riguarda è molto più semplice che disassemblare un programma vecchio scritto direttamente in assembly da qualcuno: le persone umane quando programmano in assembly usano ottimizzazioni e ragionamenti loro che è poi molto difficile dedurre nel disassemblaggio, mentre i compilatori usano sempre schemi fissi. ti faccio un esempio: appena vedi una sequenza di
push ebp
mov ebp,esp
mov ecx,N ; N è il numero di bytes occupati dalle variabili locali
sub esp,ecx
rep stos
allora sai che quello è l'inizio di una funzione, mentre la fine in genere risulta essere più o meno
pop ebp
ret N ; N qui è il numero di bytes dello stack da ripulire (quelli dei parametri)
e così sei già stato in grado di ricostruire la divisione in funzioni del programma, che è molto ;)
inoltre, facendoti magari aiutare da un API tracer, puoi addirittura ricostruire le funzioni API chiamate dal programma ;)
anzi per aiutarti ulteriormente posso dirti che in un'altra sezioni, generalmente a sola lettura, i compilatori mettono la cosiddetta IAT, Import Address Table, la quale inizialmente (cioè quando il file è scritto su disco ma non avviato) contiene numeri che si possono interpretare in una maniera abbastanza complessa che mo non sto a spiegarti (è documentata nel doc del PE), ma quando il file è in esecuzione essa contiene (a seguito del binding effettuato dal loader) gli indirizzi delle funzioni importate da altre DLL, tra cui anche le API Win32, perciò sai che ogni istruzione CALL che preleva l'indirizzo di destinazione di uno slot dalla IAT, è un CALL che chiama la rispettiva funzione importata.
aggiungendoci un po' di lavoro col Dependency Walker hai praticamente ricostruito l'aspetto originale del programma: :D hai diviso le varie funzioni e all'interno di esse sei in grado di riconoscere le chiamate a funzioni esterne ;)
a proposito, tante volte non lo sapessi, secondo la convenzione STDCALL (quella usata più spesso) il risultato di una funzione viene sempre restituito in EAX, e lo stack viene ripulito dalla funzione chiamata, non dal chiamante (sono cose che possono esserti utili).
ciao
Non penso proprio che il processore si vada a leggere tutte le istruzioni. Poi a seconda dell'implementazione dei processi si usano diversi tipi di scheduling. Se non erro quello dei processi è sotto il round robin sotto windows e linux. si ma in questo caso lo scheduling che c'entra?
Poi ovvio che l'implemetazione te la puoi fare anche in sottomodi. questa me la devi spiegare...
Cmq quando esegui un programma avrai il linker che serve a caricare le libreri esterne, no, il linker non è richiamato in fase di esecuzione, il linker è richiamato dal compilatore e serve a linkare i vari moduli COFF. l'altra operazione che descrivi tu (caricamento delle librerie esterne e conseguente binding) è effettuato sempre dal loader.
poi avrai il loader che associa gli indirrizzi relativi a quelli assoluti. rilocamento, ok; ma il loader non fa mica solo quello... anche in una visione semplicistica il rilocamento è l'ultimo aspetto da trattare di un loader.
Poi avrai anche la paginazione se è supportata cioè te non carichi tutto l'eseguibile ma vai a caricare una pagina del codice e una pagina per lo stack. ancora co sta storia -.-'
la situazione è più complessa: il loader di Windows mappa i files PE che carica in dei File Mapping Objects, quindi non scrive in memoria nessuna pagina; lo stack è tutt'altro discorso: viene allocato all'avvio di un thread (in genere è 1 kb di commit e 1 mega di reserve) e a noi poveri mortali non è dato sapere se viene direttamente scritto in RAM o se si procede a page faults, ma dato che si tratta di un solo kilobyte (che è meno della dimensione di una pagina) posso immagine che scriverlo direttamente in RAM non costi granché...
comunque il meccanismo della paginazione è trasparente al programmatore, quindi per telluccio è inutile da sapere; gli basti sapere che processi diversi a indirizzi uguali vedono cose diverse (cosa che molti professori e studenti universitari a quanto pare non sanno).
Va be poi ci sarebbe da parlare di mmu e mucca pazza ma traslascio sta parte :D :wtf: sarei proprio curioso... :wtf:
bottomap
09-11-2005, 10:46
Ciao,
Tutto corretto finché non gli arriva sottomano un DOS exe 16bit o, peggio un NE... in tal caso è bene saper distinguere e sapere quali sono le strutture in gioco...
Ogni tipo di eseguibile (eccetto i .com) ha un header determinato dalla struttura IMAGE_DOS_HEADER.
La signature del campo deve essere sempre "MZ" per un eseguibile.
Il campo e_lfanew può puntare ad una signature (una "firma") che può essere "PE\0\0" (4bytes) "PE" (2bytes) o "NE". Se non è presente una di queste "firme" il file non è un eseguibile standard.
A partire da questo le cose cominciano a ramificarsi a seconda del formato...
Il formato PE prevede una IMAGE_FILE_HEADER seguita da una IMAGE_OPTIONAL_HEADER (che, nonostante il nome NON è opzionale) e dalle informazioni sulle sezioni - IMAGE_SECTION_HEADER (indirizzo di caricamento in memoria, offset nel file, dimensioni e numerosi flags).
Le tre sezioni di base (.text,.data,.rsrc) contengono rispettivamente codice, dati e risorse (come dice 71104 i nomi potrebbero essere leggermente diversi a seconda del compilatore - nell'IMAGE_OPTIONAL_HEADER è presente l'Entry Point, ovvero il punto da cui inizia l'esecuzione).
Due sezioni (.idata,.edata) contengono le informazioni di import/export di funzioni da DLL esterne.
Una sezione (.reloc) contiene le informazioni di rilocazione.
I formati sono abbastanza ben documentati (ti consiglio di cercare "PE Format" in MSDN - su cd o sul sito http://msdn.microsoft.com nella sezione Library).
L'EntryPoint di eseguibili scritti in VB o che usano MFC puntano ad una funzioncina striminzita di inizializzazione, è il framework MFC o la VM di VB che poi gestiscono l'eseguibile... gli eseguibili VC/BC contengono codice più "sequenziale".
Il loader di windows carica l'eseguibile, controlla le sezioni creando un selettore (ex segmento) per le sezioni principali, si preoccupa della rilocazione del codice all'indirizzo di base (per gli eseguibili VC è di default 0x00400000 - NB:Virtuale!) e della trasformazione della idt da un insieme di numeri in un'insieme di riferimenti (indirizzi "virtuali" che corrispondono alle funzioni delle varie DLL nello spazio del processo).
Fatto questo (e non è poco) inizia l'esecuzione del codice dall'Entry Point specifico. Eseguibili che si basano su un framework o su una VM passeranno alle funzioni di tale FW o VM i dati del programma (in questo caso il funzionamento può essere piuttosto complicato da rintracciare).
Anche un programma semplice in VC può essere piuttosto lungo da studiare (il compilatore inserisce diverso codice per l'inizializzazione dell'applicazione)... non è mai una passeggiata ed è necessaria una buona dose di conoscenza sia per quanto riguarda il funzionamento del SO che dell'asm in sé.
Per quanto riguarda il formato NE... si tratta di codice windows a 16bit... il funzionamento è totalmente diverso da quello degli eseguibili PE e decisamente più macchinoso (ogni segmento contiene in coda una serie di informazioni di rilocazione/idt di vario tipo).
Il formato exe DOS/16bit è abbastanza lineare (sempre se si riesce a determinare velocemente la segmentazione del programma - un seg in questo caso sono al massimo 64k di codice/dati).
Insomma, non è certo una passeggiata, neanche per i programmi più semplici... ma nemmeno una cosa infattibile quando si ha una certa esperienza ed una buona conoscenza della programmazione strutturata (non si può imparare a programmare studiando il funzionamento di un eseguibile ma è sicuramente vero il contrario)...
Ciaociao :)
Fenomeno85
09-11-2005, 10:52
si ma in questo caso lo scheduling che c'entra?
questa me la devi spiegare...
avevo capito male il problema :O
no, il linker non è richiamato in fase di esecuzione, il linker è richiamato dal compilatore e serve a linkare i vari moduli COFF. l'altra operazione che descrivi tu (caricamento delle librerie esterne e conseguente binding) è effettuato sempre dal loader.
rilocamento, ok; ma il loader non fa mica solo quello... anche in una visione semplicistica il rilocamento è l'ultimo aspetto da trattare di un loader.
si hai ragione :D
:wtf: sarei proprio curioso... :wtf:
[/quote]
cercatelo non ho voglia di spiegare quella tabella :D
~§~ Sempre E Solo Lei ~§~
fuocofatuo
09-11-2005, 12:38
oddiosanto quante sparate che avete fatto tutti quanti O_________OMa scusa un attimo, forse qualcosa non mi e` perfettamente chiaro...
1- e` possibile sapere che compilatore e` stato adoperato e con che impostazioni? Se si`, non lo sapevo; se no, come fai? Bisognerebbe forse procedere a tentativi, perche` solo conoscendo queste informazioni puoi risalire alla struttura corretta del linguaggio ad alto livello.
2- hai detto che si arriva, piu` o meno automaticamente, ad un programma suddiviso in funzioni; ma a questo punto, come fai a interpretarlo senza avere a disposizione il nome di variabili e metodi, commenti e quant'altro? E` un lavoro assurdo, specialmente se non hai la piena conoscenza della funzione che deve svolgere...
Prendo in prestito del codice da questa discussione (http://www.hwupgrade.it/forum/showpost.php?p=10119202&postcount=1):
int fun1(int par1){
if (par1>0)
return (par1%10)+fun1(par1/100);
else return 0;
}
int fun2(int par1){
par1/=10;
if (par1>0)
return (par1%10)+fun2(par1/100);
else return 0;
}
int fun3(int par1){
return (fun3(abs(fun2(par1)-fun1(par1))));
}E qui chi lo capisce che e` il codice per testare il criterio di divisibilita` per 11 su un numero? Se lo capisci e` perche` gia` lo sai, ed allora chi te lo fa fare di decompilare se puoi scrivertelo da solo?
bottomap
09-11-2005, 13:50
Ciao,
Provo a risponderti io:
1 - E'possibile in una buona misura sapere con che compilatore è stato creato un eseguibile... vengono in aiuto un paio di campi nell'IMAGE_FILE_HEADER e un sistema "standard" di organizzare il codice... gli exe VC (in generale quelli MS) si identificano bene dal nome delle sezioni (.text,.data,.rsrc), nell'header menzionato sopra è presente la versione (major e minor) del compilatore utilizzato. Per VB è semplice, basta osservare che le chiamate non sono quasi mai a DLL ma a funzioni linkate dalla VM (Virtual Machine). Gli eseguibili Borland si riconoscono dai nomi delle sezioni e dalle risorse tipiche (ci vanno a finire un sacco di cose inutili per un programma standard).
Non abbiamo parlato ovviamente di compressori/crittatori di eseguibili (esistono ed offrono vari gradi di sicurezza) in questi casi un'analisi prevede una preventiva decompressione/decrittazione... quest'ultima può essere piuttosto impegnativa...
2 - Come ho già postato più sopra (concordo con te) è un lavoro parecchio faticoso e prevede un'ottima conoscenza di SO ed asm... il sorgente a cui si può risalire molto difficilmente sarà quello originale (si può al limite approssimare parecchio)... in pratica un lavoraccio ingrato ed in cui, in genere, il gioco non vale la candela... con un ottima conoscenza e molto tempo si può risalire al discorso della divisibilità per 11 se si sa (da una esecuzione) che il programma contiene una funzione simile...
E' una cosa più che altro di intuito e che si può arrivare ad avere solo dopo aver visto diversi milioni di righe di codice scritte da altri (ovvero dopo taaaaanti anni che si programma).
Ciaociao :)
fuocofatuo
09-11-2005, 14:45
Grazie mille.
P.S. Molto... ehmmm... carini i puffi...
bottomap
09-11-2005, 15:49
Ehmmmm... chiedo scusa a nome del grafico Cinfa (trattasi di animale - e anche di stracanovista insensibile alla pesantezza di una pagina zeppa di immagini grandi) :( :(
Ciaociao :)
telluccio
09-11-2005, 18:38
:eek: :eek:
un casino grazie ...per ora coppio e incollo e rileggo i post con calma..
forse per ora e meglio che continuo a crearli gli eseguibili...
ciao.
Tutto corretto finché non gli arriva sottomano un DOS exe 16bit o, peggio un NE... in tal caso è bene saper distinguere e sapere quali sono le strutture in gioco... be' hai ragione, in effetti io ho spiegato a telluccio solo come leggere i PE da 32 o 64 bit; non penso che un eseguibile a 16 bit gli capiterà mai sottomano oggi che Windows XP 64 manco supporta più Win16... :D
Ogni tipo di eseguibile (eccetto i .com) ha un header determinato dalla struttura IMAGE_DOS_HEADER.
La signature del campo deve essere sempre "MZ" per un eseguibile. pensa che nel doc Microsoft del PE il vecchio header Ms-DOS non è neanche documentato... :wtf:
lì dice semplicemente che quando si legge un file PE bisogna andare all'offset 0x3C e leggere un altro offset a cui è situato l'inizio del nuovo header PE, seguito poi dall'header opzionale, da quelli delle sezioni, ed infine dalle sezioni stesse.
Il campo e_lfanew può puntare ad una signature (una "firma") che può essere "PE\0\0" (4bytes) "PE" (2bytes) o "NE". Se non è presente una di queste "firme" il file non è un eseguibile standard. suppongo che il campo che hai citato sia quello situato all'offset 0x3C... ma oddio la signature "NE" che cos'è??? O_o' mai sentita...
A partire da questo le cose cominciano a ramificarsi a seconda del formato... si però vabbè così sei cattivo dai :D
già telluccio non ne sa molto e la situazione è abbastanza confusa di per se', tu gli vai addirittura ad insegnare formati sconosciuti ("NE"??? O_o') che non si useranno mai... :D
Il formato PE prevede una IMAGE_FILE_HEADER seguita da una IMAGE_OPTIONAL_HEADER (che, nonostante il nome NON è opzionale) [...] non è opzionale solo su Windows! :)
Le tre sezioni di base (.text,.data,.rsrc) contengono rispettivamente codice, dati e risorse (come dice 71104 i nomi potrebbero essere leggermente diversi a seconda del compilatore - nell'IMAGE_OPTIONAL_HEADER è presente l'Entry Point, ovvero il punto da cui inizia l'esecuzione). aggiungo però che la sezione .rsrc non è sempre presente: ci sono molti exe e dll che non hanno risorse! ;)
Due sezioni (.idata,.edata) contengono le informazioni di import/export di funzioni da DLL esterne. sai una cosa? non sempre i compilatori (anche quelli Microsoft-compatibili) mettono le informazioni di importazione in una sezione apposita .idata e quelle di esportazione in una sezione .edata; ho visto moduli PE che hanno importazioni ed esportazioni tutte in .rdata! :wtf:
L'EntryPoint di eseguibili scritti in VB o che usano MFC puntano ad una funzioncina striminzita di inizializzazione, è il framework MFC o la VM di VB che poi gestiscono l'eseguibile... gli eseguibili VC/BC contengono codice più "sequenziale". la funzioncina dell'entry point tanto striminzita non mi risulta... :wtf: si tratta della mainCRTStartup del runtime di Visual C++...
Il loader di windows carica l'eseguibile, controlla le sezioni creando un selettore (ex segmento) per le sezioni principali, qui è bene chiarire una cosa che molti non sanno: la segmentazione oggi non è più usata, se non in maniera minimale: in tutto il sistema penso che siano definiti un paio di segmenti: uno per lo user space (metà inferiore della memoria) e uno per il kernel space (metà superiore); come dice anche Pietrek, l'unico caso in cui Win32 modifica i registri di segmento in modalità protetta è negli switch tra user mode e kernel mode. ;)
oggidì si usa solo la paginazione, e la segmentazione viene usata in maniera minimale perché non è possibile disattivarla sugli Intel; l'unica protezione che essa fornisce è tra user space e kernel space.
si preoccupa della rilocazione del codice all'indirizzo di base (per gli eseguibili VC è di default 0x00400000 - NB:Virtuale!) e della trasformazione della idt da un insieme di numeri in un'insieme di riferimenti (indirizzi "virtuali" che corrispondono alle funzioni delle varie DLL nello spazio del processo). tutto esatto, aggiungo che quest'ultima operazione si chiama binding.
Fatto questo (e non è poco) inizia l'esecuzione del codice dall'Entry Point specifico. Eseguibili che si basano su un framework o su una VM passeranno alle funzioni di tale FW o VM i dati del programma (in questo caso il funzionamento può essere piuttosto complicato da rintracciare). al contrario, è proprio quando un programma usa (ad esempio) MFC che praticamente conosci già la metà dell'assembly contenuto! :D
Anche un programma semplice in VC può essere piuttosto lungo da studiare (il compilatore inserisce diverso codice per l'inizializzazione dell'applicazione)... non è mai una passeggiata ed è necessaria una buona dose di conoscenza sia per quanto riguarda il funzionamento del SO che dell'asm in sé. io direi che nel disassemblaggio di un programma scritto in VC6 con MFC non devi procedere come lasci intendere tu, cioè analizzando anche il codice di MFC e del runtime di VC++, che sarebbe una cosa inutile oltre che umanamente impossibile; devi piuttosto procedere confrontando il tuo programma con altri fatti sempre in MFC di cui hai il sorgente e vedere quali sono i CALL che devi saltare per evitare di leggerti il codice di MFC e quello del runtime e per arrivare direttamente agli entry points di codice che non conosci (i gestori di eventi in altre parole).
Per quanto riguarda il formato NE... si tratta di codice windows a 16bit... il funzionamento è totalmente diverso da quello degli eseguibili PE e decisamente più macchinoso (ogni segmento contiene in coda una serie di informazioni di rilocazione/idt di vario tipo). capito; non conoscevo questo NE; si tratta in pratica del formato degli exe di Windows 3.1, vero? :)
Il formato exe DOS/16bit è abbastanza lineare (sempre se si riesce a determinare velocemente la segmentazione del programma - un seg in questo caso sono al massimo 64k di codice/dati). il formato del DOS a parte l'header dovebbe essere praticamente quello dei .com, cioè codice puro...
telluccio
09-11-2005, 21:11
forse meglio passare al settaccio i post prima di copiare e incollare.. :D
io che sono pratico, da nubio :rolleyes: ho capito alcune cose.
ho fatto un programma che legge un file binario dividendolo per stringhe ('\n') ad ogni stringa il programma si blocca e visualizza il valore in hex delle prime 2 word per passare alla succesiva..
tutti gli eseguibili fatti da me iniziano sempre con la stessa stringa di 119 byte:
Z ÿÿ ¸ @ € º ´ Í!¸LÍ!This program cannot be run in DOS mode.
poi le successive stringhe cambiano a seconda dell eseguibile...pero capitano gruppi di stringhe che sono uguali (o diverse solo nel valore dell ultimo byte della seconda word)come valore delle prime 2 word...
e giusto pensare che il processore inerpreta la stringa dal valore delle prime word??
ps:
molte cose che gentilmente mi spiegate non le conosco..
ciao
non ho ben capito la domanda, ma tutti i programmi che fai iniziano come hai scritto tu (a parte che ti sei scordato la M iniziale) perché quella parte che vedi è il vecchio header di DOS seguito dallo stub program che stampa a video quella stringa se sta girando in DOS anziché in Windows (utilizza qualche interrupt del BIOS).
per leggere un file PE da un programma C devi (come è scritto nella documentazione Microsoft del formato PE) saltare subito all'offset 0x3C, leggere un DWORD, saltare all'offset specificato da quel DWORD, e poi leggere i nuovi headers PE.
è impossibile leggere un programma con Blocco Note... per ricavare informazioni da un file PE esistono altri strumenti, tipo il PEView (http://www.magma.ca/~wjr/) e il Resource Hacker (http://www.angusj.com/resourcehacker/) .
bottomap
10-11-2005, 22:39
Ciao,
Direi che hai fatto tutte ottime precisazioni... un post completo sull'argomento sarebbe in effetti piuttosto oneroso ;) - pagine e pagine solo di introduzione.
Gli eseguibili NE sono, come hai intuito gli eseguibili di win3.1 anche se il formato è stato introdotto con OS/2... ancora numerosi VXD e diverse applicazioni (queste ultime almeno fino a win98) utilizzano questo formato. La signature NE è proprio quella di questo tipo di eseguibili... sono comunque abbastanza frequenti per piccole applicazioni o programmi vecchiotti, non è impossibile che ti capiti sottomano... win 32/64bit è ovviamente sempre in grado di caricarli ed eseguirli.
Il campo e_lfanew è effettivamente l'offset 0x3C di cui parli, praticamente l'unico punto "fisso" di tutto l'eseguibile...
Non sapevo che l'optional header fosse opzionale solo sotto windows... quali altri SO utilizzano il PE come formato di eseguibile?
Tutte le sezioni, probabilmente non ho postato in maniera chiara, possono non essere presenti (a parte, ovviamente, quella contenente il codice)... file privi di .rsrc sono comuni, .idata è un po'più raro che non ci sia, .edata è presente quasi solo nelle DLL... .rdata in generale contiene informazioni di debug (quindi non è generalmente presente nelle versioni release dei programmi), tutto questo almeno con i compilatori MS, non so se altri compilatori la utilizzano in altro modo...
Cito, a questo riguardo MSDN:
The .rdata section is used for at least two things. First, in Microsoft linker-produced EXEs, the .rdata section holds the debug directory, which is only present in EXE files. (In TLINK32 EXEs, the debug directory is in a section named .debug.) The debug directory is an array of IMAGE_DEBUG_DIRECTORY structures. These structures hold information about the type, size, and location of the various types of debug information stored in the file. Three main types of debug information appear: CodeView®, COFF, and FPO.
Il discorso sulla EntryPoint striminzita riguarda, come ho postato, MFC e VB... nel primo caso ci sono due chiamate a funzioni Afx (che impostano il framework mfc e ricevono un puntatore ad una lunga vtable - che rappresenta poi l'oggetto CWinApp), dopodiché la funzione di Entry termina...
Lo stesso avviene più o meno per VB...
Ma forse ho unito la start con la WinMain in questa mia considerazione... la funzione start che inizia effettivamente il programma (in pratica imposta tutto il necessario alla CRT per proseguire) è praticamente sempre uguale sotto lo stesso compilatore... ed in genere non influisce nello studio del codice.
Ad ogni modo, per questo motivo andare a rintracciare una funzione all'interno dell'intero asm può essere complicato... soprattutto in un'applicazione con bottoni, menù e quant'altro è estremamente difficile capire esattamente lo schema degli eventi che si susseguono (es: il CListView si aggiorna, invia un WM_ ad una delle CEdit che nella sua OnChange invia un WM_ ad un altro controllo, il tutto mentre alla CWinApp arrivano n messaggi NC_ dai controlli) se si ha a disposizione solo l'asm.
Per la creazione dei selettori non sono del tutto sicuro... credo ne vengano creati due (data e codice) giusto per impostarne il flag per i diritti di accesso...
Per il formato DOS si deve distinguere tra .EXE e .COM...
Il secondo (COM), come dici è codice puro (deve essere contenuto tutto in un segmento, inizia l'esecuzione all'offset 0x100 e nei primi 256 bytes è presente il PSP - ProgramSegmentPrefix che contiene un bel po'di informazioni, tra cui la linea di comando). Nei .COM non è possibile utilizzare registri di segmento, per cui codice, stack e dati devono stare tutti in 64k.
Il primo (EXE) contiene un IMAGE_DOS_HEADER come i PE e gli NE che danno informazioni oltre che sull'entry point (in questo caso è variabile) sulla corretta segmentazione (in genere viene creato un CS, un DS=ES ed un SS), sulla dimensione dello stack e numerose altre cose. DS ed ES puntano inizialmente al PSP del programma.
Al caricamento da parte del loader vengono creati i segmenti, vengono rilocati gli indirizzi e l'esecuzione parte dall'entry point definita nell'IMAGE_DOS_HEADER (per completezza, negli eseguibili PE questo EntryPoint DOS punta ad una funzione di stub a 16bit che in genere stampa il messaggio "Questo programma richiede windows" o qualcosa del genere, sicuramente l'avrai vista).
Ciaociao :)
Non sapevo che l'optional header fosse opzionale solo sotto windows... quali altri SO utilizzano il PE come formato di eseguibile? nessuno :D
il concetto dell'header opzionale sarebbe che può variare da un sistema all'altro, ma alla fine l'unico SO che usa il PE è Windows :p
Windows CE forse ha un header opzionale diverso, ma non ne sono neanche troppo sicuro :p
.rdata in generale contiene informazioni di debug (quindi non è generalmente presente nelle versioni release dei programmi), tutto questo almeno con i compilatori MS, non so se altri compilatori la utilizzano in altro modo... veramente .rdata dovrebbe contenere più in generale dati a sola lettura... in teoria potrebbe anche essere presente nei programmi compilati in modalità release...
Ad ogni modo, per questo motivo andare a rintracciare una funzione all'interno dell'intero asm può essere complicato... soprattutto in un'applicazione con bottoni, menù e quant'altro è estremamente difficile capire esattamente lo schema degli eventi che si susseguono (es: il CListView si aggiorna, invia un WM_ ad una delle CEdit che nella sua OnChange invia un WM_ ad un altro controllo, il tutto mentre alla CWinApp arrivano n messaggi NC_ dai controlli) se si ha a disposizione solo l'asm. per tracciare i messaggi ci sono gli hook globali ;)
vedi Spy++, è un tool molto utile.
Per la creazione dei selettori non sono del tutto sicuro... credo ne vengano creati due (data e codice) giusto per impostarne il flag per i diritti di accesso... no, i segmenti penso che siano uno per ogni processo, ma non sono sicuro neanche di questo, forse sono un paio in tutto il sistema (user space e kernel space).
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.