PDA

View Full Version : [c/c++]aggiungere codice eseguibile dentro ad un altro eseguibile in modo permanente


mamo139
19-11-2006, 14:42
aggiungere codice eseguibile dentro ad un altro eseguibile in modo permanente è possibile???
vorrei nel mio computer aggiungere dentro al programma explorer.exe (quello di windows...) dei comandi per svolgere alcune operazioni di rutin che nn voglio sempre fare a mano o con un programma che si carica per i fatti suoi...

si puo fare???

grazie ciao :)

wingman87
19-11-2006, 14:46
Non puoi usare un semplice file batch?

mamo139
19-11-2006, 15:14
Non puoi usare un semplice file batch?
se usassi un semplice file batch
1) dovrei far avviare qualcos'altro ad ogni avvio
2) non eseguirebbe comandi che devono eseguirsi magari al verificarsi di determinate situazioni che avvengono dopo l'avvio
3 last but not least) se lo usassi come migliorerei le mie scarse capacità di programmatore??? :D :D


grazie comunque per l'idea anche se nn era ciò che cercavo :)

andbin
19-11-2006, 15:36
se usassi un semplice file batchSe mi permetti un consiglio, lascia perdere i file batch e lascia anche perdere l'aggiunta di codice in modo permanente dentro un eseguibile. Non perché non sia fattibile. Tecnicamente penso che potrebbe essere fattibile, a patto di conoscere molto bene la struttura del "Portable Executable" (PE) cioè il formato dei file eseguibili in Windows.
Ma perché molto probabilmente quello che vuoi fare si può realizzare sviluppando semplicemente un normale eseguibile.

1) dovrei far avviare qualcos'altro ad ogni avvioEd è un problema???
2) non eseguirebbe comandi che devono eseguirsi magari al verificarsi di determinate situazioni che avvengono dopo l'avvioDipende da cosa devi fare. Spiega in modo più preciso quali situazioni e cosa dovresti fare.
3 last but not least) se lo usassi come migliorerei le mie scarse capacità di programmatore??? :D :D Motivo in più per lasciare perdere i batch e concentrarsi sulla programmazione in Windows. ;)

71104
19-11-2006, 16:46
aggiungere codice eseguibile dentro ad un altro eseguibile in modo permanente è possibile???
vorrei nel mio computer aggiungere dentro al programma explorer.exe (quello di windows...) dei comandi per svolgere alcune operazioni di rutin che nn voglio sempre fare a mano o con un programma che si carica per i fatti suoi...

si puo fare??? me lo sento, a forza di insegnarti a scrivere malware un giorno me ne pentirò :D :D :D
vorrà dire che dovrò mandarti le arance con dentro le limette :asd:

infettare un eseguibile per Windows è un'operazione dalla difficoltà molto variabile. il contenuto di un eseguibile è diviso in sezioni, e in genere una sezione di nome ".text" è dedicata esclusivamente al codice. le sezioni non possono avere dimensioni qualsiasi, ma devono avere dimensioni multiple di un certo valore di allineamento scritto negli headers del file. a causa di ciò spesso e volentieri serve del padding, cioè il compilatore una volta che ha finito di scrivere il codice deve concludere la sezione con un po' di zeri che la riempiono finché non raggiunge le dimensioni di allineamento.

ora: se tu vuoi scrivere del codice nell'eseguibile chiaramente per prima cosa devi vedere se la sezione ".text" contiene abbastanza padding. se lo contiene (e spesso è così) bene, sennò...? ti tocca ingrandire una sezione, cosa a dir poco complessa...

questo era il primo passo, ora veniamo al secondo: una volta che hai scritto il codice devi anche fare in modo che esso venga eseguito. questo per fortuna è un'operazione semplice che funziona sempre: gli headers del file contengono tra le altre cose anche l'RVA (Relative Virtual Address, indirizzo virtuale relativo al base address di caricamento del file, praticamente l'indirizzo) dell'entry point, cioè del punto (che dovrebbe trovarsi sempre nella sezione ".text") a cui inizia l'esecuzione del programma. ciò che devi fare è cambiare quel valore con l'RVA del tuo codice "clandestino", il quale codice clandestino alla fine dovrà contenere un JMP che salti all'entry point originale.

questo è quanto, se vuoi entro in dettagli.

mamo139
19-11-2006, 17:29
me lo sento, a forza di insegnarti a scrivere malware un giorno me ne pentirò :D :D :D
vorrà dire che dovrò mandarti le arance con dentro le limette :asd:


non credo che diventerò così abile da fartene pentire :sofico: è solo che fare sta roba mi piace da impazzire :)
e cmq se in futuro mi regaleranno delle arance saprò che nn le devo accettare :D :D :D :D


questo è quanto, se vuoi entro in dettagli.
sono pronto a seguirti....
grazie mille

71104
19-11-2006, 20:05
sono pronto a seguirti.... tanto per cominciare vai qua e scarica le specifiche ufficiali del formato PE/COFF, che sicuramente ti saranno utili:
http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx

poi vai anche qua e mettitelo tra i preferiti, è un articolo bellissimo di Matt Pietrek che aiuta la comprensione di tutta una serie di cose che sarebbero propedeutiche allo studio delle specifiche ufficiali del formato PE, e che stanno sparse qua e là per tutta MSDN e sono quindi difficili da trovare:
http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx

è segnato a 3 come livello di difficoltà ma non è vero, io lo trovo chiarissimo. e casomai se vuoi leggerti anche la seconda parte ecco qua:
http://msdn.microsoft.com/msdnmag/issues/02/03/PE2/default.aspx

detto questo, io ti consiglio di leggerti prima l'articolo tenendoti accanto le specifiche ufficiali da consultare, e poi di leggerti la mia spiegazione che segue.

nell'header winnt.h sono dichiarate tutte le struct e le macro necessarie alla lettura di un file PE, e poiché winnt.h è incluso da windows.h come includi windows.h hai automaticamente tutto il necessario.

si assume inoltre che tu lavori in Win32, cioè Windows a 32 bit (a 64 bit alcune cose cambiano).


primo passo: headers del file.

apri il file, spostati all'offset 0x3C e leggi un DWORD; poi spostati all'offset espresso da quel DWORD (attenzione: offset assoluto, non relativo! devi andarci, non devi avanzare) e leggi una struct IMAGE_NT_HEADERS. tra le varie cose ci troverai un campo NumberOfSections che ti dice il numero di sezioni in cui è diviso il contenuto vero e proprio del file. ciascuna sezione è descritta da un proprio header, e tutti gli headers delle sezioni si trovano esattamente dopo gli headers principali che hai letto nella struttura IMAGE_NT_HEADERS. quindi alloca un array di NumberOfSections strutture IMAGE_SECTION_HEADER (vado a memoria, spero che si chiamasse così la struct) e prendine il contenuto direttamente dal file.


secondo passo: trovare la sezione di codice.

analizza gli header delle sezioni uno per uno: la sezione di codice è quella il cui header ha attivato il flag IMAGE_SCN_CNT_CODE nel campo Characteristics (non ti fidare del nome ".text", è una convenzione che non tutti i compilatori rispettano: quello della Borland la chiama "CODE"). in teoria potresti addirittura trovare più d'una sezione contenente codice, nel qual caso a parer mio devi congegnare l'algoritmo in modo da scrivere il codice clandestino in quella con padding sufficiente.

una volta individuato un section header che descrive una sezione di codice, i seguenti campi dell'header ti possono essere utili:

PointerToRawData, esprime l'offset al quale si trova la sezione nel file
VirtualAddress, esprime l'RVA della sezione, in un certo senso il suo offset dal base address una volta che essa è caricata in memoria
SizeOfRawData, esprime la dimensione della sezione del file
VirtualSize, esprime la dimensione della sezione una volta che il file è caricato in memoria (potrebbe differire da SizeOfRawData)



terzo passo: calcolare la dimensione minima di padding

il padding inizia semplicemente quando da un certo punto in poi trovi solo zeri; ma non è detto che uno dei primi di quegli zeri sia già padding, potrebbe anche essere la parte finale della codifica dell'ultima istruzione, e se la sovrascrivi sono guai. che io ricordi non c'è campo nel section header che ti dica la dimensione del padding, perciò devi analizzare il contenuto della sezione partendo dalla fine e andando a ritroso, finché non trovi un byte diverso da zero; poi però devi ri-avanzare di qualche byte per essere sicuro di non sovrascrivere "zeri utili". di quanti byte avanzare? per sicurezza è meglio avanzare di N con N = dimensione massima di un'istruzione assembly x86 ;)


il resto te lo dico dopo che è pronta la cena :D

mamo139
19-11-2006, 20:24
il resto te lo dico dopo che è pronta la cena :D

ooook... ma sii consapevole che dopo questa bellissima guida teorica ti toccherà anche correggere i miei tentativi sbagliati :D :D
buon appetito

grazie davvero!! aspetto il resto :D

dupa
19-11-2006, 20:38
Io quando mi occupavo di ste cose avevo fatto un po' di modifiche a un po' di roba.

Il primo passo è disassemblare l'eseguibile, tramite ad esempio wdasm32.

Quindi cerchi al suo interno riferimenti a stringhe o a chiamate ad API di windows.

Ad esempio se nel programma in fase di chiusura appare la stringa "sei sicuro di voler uscire?" tu cerchi nelle string reference questo messaggio, e pratica puoi andare nella sezione di codice assembler dove questa stringa viene "usata", magari abbinata a una chiamata all'API MessageBox o qualcosa del genere...

Quindi ti guardi attorno, magari poche istruzioni più su, trovi una reference a una call, in pratica puoi andare a scoprire nel codice disassemblato da dove questa funzioncina viene chiamata, e boom.. magari finisci in una sezione di codice dalla quale la funzione che poi mostra la messagebox viene chiamata.

Vabbè in sostanza devi trovare un punto buono dove "iniettare" il tuo codice.
A quel tutto è abbastanza semplice, cerchi un'area "zero padded" dell'eseguibile, di solito stanno verso la fine.
Nel punto dove vuoi iniettare il codice sostituisci un'istruzione con una JUMP che ti manda nella zona zero-padded, lì ci scrivi quello che ti pare e infine scrivi l'istruzione che avevi tolto prima e fai un JUMP per tornare nella zona originale.

Comunque se cerchi su google "code injection" "reverse engineering" o altra roba del genere dovresti trovare un mucchio di tutorial.
Visto che facevo ste cose nello scorso millennio, non sono più molto documentato sulla faccenda ;)

jappilas
19-11-2006, 21:02
...
ma non è detto che uno dei primi di quegli zeri sia già padding, potrebbe anche essere la parte finale della codifica dell'ultima istruzione, e se la sovrascrivi sono guai. che io ricordi non c'è campo nel section header che ti dica la dimensione del padding, perciò devi analizzare il contenuto della sezione partendo dalla fine e andando a ritroso, finché non trovi un byte diverso da zero; poi però devi ri-avanzare di qualche byte per essere sicuro di non sovrascrivere "zeri utili". di quanti byte avanzare? per sicurezza è meglio avanzare di N con N = dimensione massima di un'istruzione assembly x86 ;)
ah, le istruzioni a lunghezza variabile ! ... :D

71104
19-11-2006, 23:05
@mamo139, la seconda parte viene domani (e probabilmente di sera) ché oggi non ho avuto tempo. hai tutto il tempo di fare gli esperimenti e di leggerti l'articolo di Pietrek :p

71104
20-11-2006, 07:58
@mamo139, la seconda parte viene domani (e probabilmente di sera) ché oggi non ho avuto tempo. e invece viene di mattina (cambio di programma :p).


quarto passo: scrittura del codice

una volta che hai trovato l'area di padding ci scrivi il codice e via. tipicamente potrà essere una chiamata a LoadLibraryA che carica una tua DLL. inoltre come abbiamo detto alla fine deve esserci un JMP relativo che ritorno all'RVA dell'entry point originario. tale RVA è scritto nel campo AddressOfEntryPoint dell'header principale del file, e chiaramente dovrai anche modificare quel campo mettendoci l'RVA del codice clandestino (il byte a cui sei sicuro che inizia il padding), in modo che l'esecuzione del programma inizi dal tuo codice.


quinto passo, opzionale: espansione di una sezione, e qui siamo sull'orlo della ricompilazione :sofico:

se per caso decidessi di rendere l'installer del tuo malware ( :asd: ) a prova di casi sfigati di totale assenza di padding, avrai la necessità di scrivere il codice necessario ad esandere una sezione. espandere una sezione non è un problema finché lo spazio che c'è dopo è gap (spazio vuoto tra sezioni), o finché la sezione in questione è l'ultima del file; il problema è quando ci stanno altre sezioni dopo. :help: in quel caso le sezioni successive vanno spostate in avanti, e vanno rilocate, altrimenti di sicuro il programma non funzionerà più.

anzitutto devi calcolare il delta per lo spostamento, cioè di quanto le sezioni successive vanno spostate in avanti; e devi calcolarlo in modo che (come stabilito dalle specifiche) l'inizio di ciascuna rimanga un multiplo del valore di allineamento specificato dal membro FileAlignment dell'header principale (credo che solitamente corrisponda a 0x1000, la dimensione di una pagina). dopo aver fisicamente riscritto le sezioni più avanti nel file devi aggiornare nei rispettivi section header i campi PointerToRawData e VirtualAddress aggiungendo semplicemente il delta di spostamento. infine devi rilocare le sezioni: consultando la normale fixup table dell'eseguibile esegui un normale algoritmo di rilocazione che applica i fixup solo quando questi vadano a cadere dentro una delle sezioni spostate, e li applica addizionando il delta di spostamento.

71104
20-11-2006, 08:09
infine devi rilocare le sezioni: consultando la normale fixup table dell'eseguibile esegui un normale algoritmo di rilocazione che applica i fixup solo quando questi vadano a cadere dentro una delle sezioni spostate, e li applica addizionando il delta di spostamento. problema, ho scordato un pezzo: una volta spostate le sezioni le entries della fixup table non puntano più nei punti giusti :mc:

non si tratta banalmente di addizionare il delta per ritrovare il punto in cui applicare il fixup, devi proprio correggere il valore di offset di tutte le entries interessate; e potrebbe essere un bel c***o di problema in quanto le entries non esprimono il punto di applicazione del fixup con un RVA da 32 bit, ma per risparmiare spazio lo esprimono con un offset da 12 bit calcolato rispetto ad un certo RVA di base. per l'esattezza, la fixup table è divisa in più fixup blocks, ciascuno dei quali contiene un RVA di base (a 32 bit) ed N fixup entries da 16 bit, di cui 12 sono l'offset.

il problema è evidente: avendo a disposizione solo 12 bit per gli offset, spostando una sezione e semplicemente addizionando il delta all'offset potresti mandarlo in overflow. ciò che puoi fare nel caso in cui tutte le entries di quel fixup block cadano nella stessa sezione è addizionare il delta direttamente all'RVA di base del blocco anziché ai singoli offset. altrimenti non lo so, potrebbe anche non esserci soluzione al problema (perlomeno non soluzione che valga la pena, perché al limite c'è sempre la ricompilazione totale è chiaro :sofico: ) in quanto se a seguito dello spostamento le entries di uno stesso blocco si distanziassero di più di 0x1000 bytes (dimensione massima descrivibile dagli offset di 12 bit) sarebbe in teoria necessario splittare il fixup block in due blocchi.

ma le sorprese non sono finite, aspetta che ti spiego come va scritto il codice clandestino :sofico:

71104
20-11-2006, 08:46
appendice: generazione del codice clandestino

ricordiamoci che il nostro scopo è chiamare LoadLibraryA passandogli l'indirizzo di una stringa situata nel codice stesso, e subito dopo fare un JMP all'entry point originale.

possiamo strutturare il codice come segue:

PUSH
CALL
JMP
nome del file della DLL


cominciamo dal PUSH, già abbiamo un problema: con una rapida consultatina al manuale Intel vediamo che la versione che dovremmo usare è PUSH imm32, opcode 0x68 seguito da 4 bytes di valore immediato. calcolare il valore immediato è banale, basta sommare l'RVA della stringa al base address dell'eseguibile; il problema è che avremmo (in teoria) bisogno di un fixup in quel punto... possibili soluzioni?

1) ce ne freghiamo - tanto è un eseguibile, verrà sicuramente caricato al suo base address predefinito, a meno che il programmatore sadico non l'abbia definito come 0x7C800000 (il base address di kernel32.dll) o come >= 0x80000000 (base address in kernel space).

2) pur di essere sicuri di non doverci rilocare spostiamo tutto l'eseguibile al base address standard, 0x00400000 :sofico:
e a dire il vero non è neanche una soluzione tanto difficile: basta rilocare tutto il file utilizzando (0x00400000 - il suo base address) come delta :D

col PUSH siamo quindi (più o meno :asd: ) a posto: iniziamo a scrivere 0x68 e lasciamo 4 byte vuoti perché l'RVA della stringa ancora non lo sappiamo.

ora passiamo al CALL: stavolta, al contrario del caso precedente, se vogliamo usare un valore immediato nella chiamata non abbiamo a disposizione una versione absolute dell'istruzione, ma solo relative; peccato che adesso invece ci serviva proprio la absolute (eccheppalle :sofico: ).

abbiamo due casi:
1) l'eseguibile importa LoadLibraryA da kernel32.dll
2) l'eseguibile non importa LoadLibraryA

nel primo caso siamo fortunati: dobbiamo usare CALL r/m32 specificando l'indirizzo della entry relativa a LoadLibraryA nella Import Address Table. non entro in dettagli perché dal manuale non ho capito la codifica di quella versione di CALL ^__^''

nel secondo caso invece, considerando che nello spazio di indirizzamento di qualsiasi processo il base address di kernel32.dll non cambia mai (e quindi neanche l'indirizzo di LoadLibraryA), utilizzeremo CALL rel32: scriviamo 0xE8 e subito dopo un delta così calcolato:

(indirizzo di LoadLibraryA ottenuto da una GetProcAddress chiamata in qualsiasi processo) - (VA a cui stiamo scrivendo il CALL) - (dimensione del CALL, cioè 5)

e la cosa bella è che qui non ci serve il fixup ;)

andiamo avanti: il JMP. caso analogo: ciò che dobbiamo usare è JMP rel32, opcode 0xE9 seguito dai 4 bytes di delta così calcolati:

(valore originale del membro AddressOfEntryPoint dell'header principale) - (VA del JMP) - (dimensione del JMP, cioè 5)

dopodiché scriviamo la stringa e a questo punto, conoscendo anche l'RVA della stringa stessa, possiamo ultimare il PUSH iniziale. occhio però: ai 4 bytes immediati del PUSH non dobbiamo scrivere l'RVA, ma il VA (l'RVA sommato al base address predefinito, sperando che sia sempre anche quello effettivo :D).