_______________________________________________________________________________________________ REVERSING, AGGIUNTA DI FUNZIONI, MODIFICA DEL CODICE ESISTENTE E CRACKING CLASSICO IN UN BERSAGLIO DOC DI CASA MICRO$OFT: NOTEPAD.EXE....DESCRIZIONE DETTAGLIATA DELLA CREAZIONE DI HNOTEPAD. REVISIONE 1 By -NeuRaL_NoiSE 1999 _______________________________________________________________________________________________ Hiho! 'Mmazza che titolone impegnativo :))) allora....eccoci qui di nuovo alle prese con mamma Micro$oft...in questo tutorial spieghero' per filo e per segno il procedimento che ho seguito per modificare qua e la' il caro vecchio Notepad.exe in modo da aggiungere/rimuovere/modificare qualche funzione, per meglio adattarlo al progetto che stiamo cercando di portare a termine io, Anub|s e Insanity. Ho gia' pubblicato un tutorial sulla creazione di Hnotepad, ma chi ci ha dato un'occhiata si sara' reso conto che il tutorial precedente era improntato solo e unicamente alla compatibilita' con Windows 95, niente 98, e soprattutto avra' notato che la generazione del codice era strettamente legata al computer su cui veniva eseguita. Io stesso suggerivo a qualcuno di lavorarci su, ma alla fine ho reso il tutto trasportabile...e qui vi descrivero' come, con poche aggiunte/cambiamenti marginali, il vostro codice funzionera' sia sotto 98 che sotto 95. Un'ulteriore miglioria rispetto al tutorial precedente sta nel fatto che dopo le modifiche che vi descrivero' in questo tutorial il vostro notepad leggera' files ben piu' grandi dei soliti ffff bytes (un ringraziamento particolare a GEnius per la sua idea, scusate il gioco di parole, a dir poco geniale :), fermo restando che potrete solo visualizzarli, non editarli -- ricordate il nag della memoria insufficiente ? ho ritenuto opportuno lasciarlo, puo' essere comodo avere un nag che vi avverte se la vostra pagina sta eccedendo i 50 k. Ciononostante potrebbe darsi che alla prossima edizione ci infilo un'opzione apposta, chi lo sa...hnotepad e' un progetto in continua evoluzione, ed e' strabiliante il numero di persone che mi chiede di aggiungere funzioni :) Ecco le sezioni del tutorial: * PARTE 1 : Premesse * PARTE 2 : INTRO A HNOTEPAD * PARTE 3 : CREAZIONE DI HNOTEPAD: FASE 1 : IL NUOVO MENU FASE 2 : L'ABOUT BOX FASE 3 : AGGIUNTA DI MASKS ALLA STRUTTURA OPENFILENAME FASE 4 : AGGIUNTA DEL FILE .INI FASE 5 : RIMOZIONE DEL LIMITE NELLA GRANDEZZA DEI FILES APERTI * PARTE 4 : Bugs conosciuti * PARTE 5 : Saluti _________________ PARTE 1: Premesse _________________ Era un tempestoso giorno di bufera, quando mi avventurai per i meandri dell'IRC in cerca di un canale amico, che trovai essere #crack-it....c'erano i soliti amici di sempre, a partire da Insanity, fino a Quequero e ad Anub|s....chissa' come io ed Insa (che e' il webmaster di RingZ3r0) cominciammo a parlare della programmazione in raw html (cosa in cui lui e' particolarmente capace:), e mi disse che il tool usato per le sue pagine web e' semplicemente il Notepad di Window$....notai una certa partecipazione anche da parte di Anub|s, che evidentemente sosteneva le tesi del nostro amico...Ad un certo punto, mi venne un idea....perche' non iniziare un simpatico progetto ? proposi l'idea agli astanti: creare un bel file .hlp (tipo guida di Window$) con tutte le basi necessarie a fornire una COMPLETA conoscenza della programmazione in raw html, senza bisogno di ingombranti editors visuali (WYSIWYG).....ovviamente, unito a tale file, ci sarebbe stato il dovuto editor di testo, e chi meglio di un Notepad.exe "riveduto e corretto" sarebbe servito ai nostri scopi ?? Anub|s sembro' entusiasta dell'idea, e Insa successivamente gli promise che avrebbe messo la sua parte nella scrittura del file di help...cosicche' i compiti erano gia' stati divisi...a loro la scrittura dell'help, e a me il reversing e la modifica del notepad.... Ora so benissimo che questo tutorial non ha nessun motivo di essere :), ma ho ritenuto opportuno scrivere qualcosa, giusto per dimostrare quanto puo' essere (relativamente) semplice modificare, rimuovere e aggiungere funzioni a un qualsiasi target gia' compilato...e per questo semplice motivo, eccovi la descrizione completa della creazione di Hnotepad.exe... NOTA IMPORTANTE: Nonostante in questo tutorial venga descritta la creazione di un Hnotepad universale (sia per 95 che per 98), io usero' il file Notepad.exe che viene fornito con WINDOWS 95...quindi vi consiglio di procurarvi quello per seguire questo tutorial. Funzionera' perfettamente anche sotto il vostro Windows 98, garantito. La premessa finale e' quella solita, consiglio vivamente al lettore una buona conoscenza di base del linguaggio Assembly per Windows, e del reversing in generale. TOOLS USATI: ____________ * W32Dasm v8.93 * SoftICE v3.24 * HIEW v6.1 * Borland Resource Workshop (BRW) v4.5 * ProcDump32 v1.3 * API Reference __________________________ PARTE 2 : INTRO A HNOTEPAD __________________________ C'e' poco da dire....comunque vi enunciero' in modo sommario cio' che faremo in questo tutorial: * Aggiungeremo un menu a Notepad, che aprira' il nostro nuovo file .hlp * Creeremo l'about box (ora come ora non ne esiste uno vero, ma capirete in seguito) * Faremo in modo che nelle masks (*.bla) dei dialogs "Apri file" e "Salva con nome" compaiano anche i "Files HTML" (sia *.htm che *.html), "Files JS", e i "Files VBS". * Aggiungeremo un comodo file .INI, in cui verra' salvato lo stato del WordWrapping (A capo automatico) e il nome dell'ultimo file aperto. Al riavvio, il wrapping verra' riposizionato nello stato definito dal file, e l'ultimo file aperto sara' riaperto automaticamente se hnotepad viene lanciato senza command line. Inoltre, tale file .INI viene creato automaticamente da Hnotepad ad ogni uscita dal programma, quindi non c'e' problema nel danneggiarlo o nello scriverci dentro. * Elimineremo la limitazione nella grandezza del file aperto, il vostro Hnotepad non avra' limiti di dimensione del file visualizzato. Per fare cio', non toccheremo la Import Table ma utilizzeremo codice di scansione del Kernel (e di qualsiasi altra libreria in memoria), che, come vedrete in seguito, e' universale e potra' ritornarvi MOLTO utile quando non avete spazio per inserire GetProcAddress tra le API importate. Ecco tutto....andiamo!!!! __________________________________ PARTE 3 : LA CREAZIONE DI HNOTEPAD FASE 1 : IL NUOVO MENU __________________________________ Allora.....cominciamo con il cercare di capire COSA dovremo fare al nostro target: dobbiamo AGGIUNGERE un menu a quelli gia' presenti del programma...ma teniamo presente che nel corso della sessione avremo SICURAMENTE bisogno di aggiungere parti di codice "nostro" al file...nel caso del menu, dovremo aggiungere quella parte della window procedure che analizza le id di menu selezionate relativa alla scelta del nostro nuovo menu. Cominciamo con il vedere come si comporta il nostro target quando deve analizzare le id...potremmo risalire alla window procedure tramite la API RegisterClass e ottenere l'indirizzo della window procedure, ma saremo molto pratici, e breakeremo nel punto esatto che ci interessa. Abbiamo detto che ci interessa aggiungere un menu. Quindi proviamo ad analizzare il comportamento del programma nel caso in cui viene scelto un menu: l'API da utilizzare e' percio' WinHelpA (che fa partire il file winhelp.exe sotto \windows, che serve ad aprire qualsiasi file .HLP in formato guida di windows). In SoftICE, quindi, "BPX WinHelpA". Poi andiamo in notepad, e selezioniamo, dal menu "?", l'item "Guida in linea". Sice breakkera' su WinHelpA: F12 e vi ritroverete al caller, che sara' qui: :00401E51 FF7514 push [ebp+14] :00401E54 57 push edi :00401E55 FF7508 push [ebp+08] :00401E58 E80FF3FFFF call 0040116C ; <-- TORNIAMO DA QUESTA CALL :00401E5D 85C0 test eax, eax :00401E5F 0F8582000000 jne 00401EE7 :00401E65 FF7514 push [ebp+14] :00401E68 57 push edi :00401E69 56 push esi :00401E6A FF7508 push [ebp+08] * Reference To: USER32.DefWindowProcA, Ord:0078h | :00401E6D FF1534744000 Call dword ptr [00407434] :00401E73 EB74 jmp 00401EE9 Bene....DefWindowProcA sta a rappresentare la zona di codice della window procedure dove i messaggi vengono elaborati con le modalita' predefinite (ad es, se non analizziamo un tal messaggio, questi comunque verra' "catturato" da DefWindowProcA che lo elaborera' di conseguenza). Cio' significa che comunque il valore del menu item passa per (e viene controllato da) quella call a 1E58 (come il parametro 2/3, passato in EDI) dalla quale noi stiamo tornando. Entriamovi: * Referenced by a CALL at Address: |:00401E58 | :0040116C 55 push ebp :0040116D 8BEC mov ebp, esp :0040116F 81EC04010000 sub esp, 00000104 :00401175 33C0 xor eax, eax :00401177 56 push esi :00401178 57 push edi :00401179 BE38614000 mov esi, 00406138 :0040117E 8DBDFCFEFFFF lea edi, dword ptr [ebp+FFFFFEFC] :00401184 B940000000 mov ecx, 00000040 :00401189 A4 movsb :0040118A 8DBDFDFEFFFF lea edi, dword ptr [ebp+FFFFFEFD] :00401190 F3 repz :00401191 AB stosd :00401192 66AB stosw :00401194 AA stosb :00401195 0FB7750C movzx esi, word ptr [ebp+0C] ; <-- L'ID VALUE DEL MENU SCELTO :00401199 83FE20 cmp esi, 00000020 ; COMPARA ESI E 20h :0040119C 8BC6 mov eax, esi :0040119E 7F1A jg 004011BA ; SE ESI > 20h, SALTA :004011A0 0F84C1030000 je 00401567 :004011A6 48 dec eax :004011A7 83F81B cmp eax, 0000001B :004011AA 7736 ja 004011E2 :004011AC 0FB68838174000 movzx ecx, byte ptr [eax+00401738] :004011B3 FF248DF8164000 jmp dword ptr [4*ecx+004016F8] ; ALTRIMENTI ELABORA DI CONSEGUENZA Abbiamo quindi l'ID del menu (potete controllarle una per una con il brw, sotto la voce menu) in esi, e poi un check se esi e' maggiore di 20h. 20h equivale a 32 dec, quindi sappiamo che le id con valore inferiore a 32 verranno elaborate da un jump variabile da caso a caso, che puntera' ogni volta al codice adeguato (il jmp a 11B3). Quindi, per non complicarci la vita, il nostro menu dovra' avere un ID value maggiore di 32, cosicche' potremo elaborarlo susseguentemente al jg a 119E, che porta qui: * Referenced by a JUMP at Address: |:0040119E(C) | * Possible Ref to Menu: MenuID_0001, Item: "Taglia CTRL+X" | :004011BA 3D00030000 cmp eax, 00000300 ; 300h (768 dec) = menu item "Taglia" :004011BF 7C21 jl 004011E2 * Possible Ref to Menu: MenuID_0001, Item: "Copia CTRL+C" | :004011C1 3D01030000 cmp eax, 00000301 :004011C6 0F8E0F030000 jle 004014DB * Possible Ref to Menu: MenuID_0001, Item: "Incolla CTRL+V" | :004011CC 3D02030000 cmp eax, 00000302 :004011D1 0F8427030000 je 004014FE .............. .......... e cosi' via con gli altri checks. Ecco trovato un buon punto per inserire la deviazione al NOSTRO codice, che elaborera' per primo l'ID value relativo al nuovo menu. Ma prima dobbiamo crearlo, questo menu! :) Per fare cio', andate in BRW e, nella sezione menu, sotto il menu "?", aggiungete un nuovo item, io l'ho chiamato "Anub|s+Insa's Help su HTML". Assegnategli il valore 33 (21h), che servira' a farlo analizzare da questa procedura e non dal jmp variabile di prima. Qualsiasi valore piu' alto di 32 andra' bene (purche' non sia gia' legato a qualche altro item). Ora, nella sezione "KEY" aggiungete "VK_F1", che servira' a far uscire QUESTO menu e NON quello di notepad quando premiamo il tasto F1. Non dimenticatevi di cancellare la stessa dicitura dal menu attuale di notepad. Salvate, e come per magia al prossimo avvio di notepad vi ritroverete questo nuovo menu che come sapete ha id value 21h. Adesso, quindi, dobbiamo risolvere il problema dell'aggiunta del nostro codice al file. Come facciamo a inserirlo ?? Ci sono un paio di cosette da controllare. Se aprite il file con HIEW, con ProcDump o con chissa' quanti altri programmi, avrete tutte le info che vi servono all'aggiunta del vostro codice. Ovviamente, come sapete, non possiamo occupare PIU' bytes di quanti sono effettivamente presenti nella sezione .TEXT, e quindi siamo costretti ad APPENDERE il nostro codice alla fine del file, per poi ridirezionare i jumps dalla sezione .TEXT del pe alla sezione in cui abbiamo aggiunto il codice, che ora andremo a vedere qual'e'....solitamente, l'ultima sezione andra' bene: nel caso di notepad, pero', abbiamo gia' aggiunto un menu, cosa che ha portato all'aumento in dimensione della sezione .RSRC: questo ingrandimento ha portato allo spostamento della suddetta sezione alla fine del file. Potete controllare facilmente entrando in HIEW, premendo F8 (dal modo hex o asm) per visualizzare le info relative all'header e quindi F6 per le sezioni. Quindi, dicevo, il pe ora e' stato modificato da parte del brw, e ora l'ultima sezione e' la .RSRC. Ma resta il fatto che, "nativamente", il file notepad.exe finiva con la sezione .RELOC, che e' quella in cui andremo ad aggiungere il nostro codice (almeno all'inizio, quando finiremo lo spazio disponibile ci serviremo della .RSRC). La prima cosa da fare e' controllare lo SPAZIO che abbiamo (in termini di bytes) per aggiungere codice: controlliamo la dimensione virtuale (VirtSize, = 91Eh) e quella fisica (PhysSize, = A00h); la virtsize ci indica il numero di bytes che non dobbiamo modificare, quelli cioe' che sono gia' usati dal prog, mentre la physsize ci indica la dimensione "raw" della sezione: una semplice sottrazione A00h-91Eh ci dara' E2h (cioe' 226 dec.) bytes liberi, in cui potremo aggiungere il nostro codice. Non avremo bisogno di ingrandire la sezione, perche' abbiamo tutto lo spazio che ci serve; se la dovevate ingrandire, PRIMA di creare il menu con il brw, avreste dovuto caricare il file con procdump, editare la sezione, e aumentarne la dimensione fisica. Bene! Resta un problema....come fare a sapere A QUALE OFFSET scrivere il nostro codice?? Semplice...dobbiamo tenere d'occhio l'offset di inizio della sezione: sappiamo che e' 5000h, quindi Offset_Iniziale+VirtSize = 5000h+91Eh = 591Eh, il nostro entrypoint per l'aggiunta di codice. Abbiamo pero' anche un'ALTRO problema...CHE codice dobbiamo aggiungere ?? :)) Prima di tutto, tracciamo uno schema delle operazioni che vogliamo che il nostro nuovo codice compia. Per far si' che il codice salti dalla sezione .TEXT (dove c'e' la call che elabora le id values) alla sezione .RELOC (dove c'e' il nostro codice), avremo bisogno necessariamente di sostituire qualche byte con un jump....prenderemo il CMP a 11BA: dovremo fare quindi in modo di fare si che quel cmp diventi un jump ad un'altra sezione: questo, una volta, poteva comportare qualche fastidio: un tempo di solito un cracker che aggiungeva codice proprio ai programmi utilizzava un compilatore per generare gli opcodes validi, dovendo perdere tempo con sottrazioni inerenti il RVA della prima e della seconda sezione, ecc....poi, un bel giorno e' arrivato SoftICE con la sua funzione A (Assemble instruction) :)))))))) Grazie a questa salvezza di tool, possiamo assemblare le istruzioni al runtime, volta per volta, e ottenere pertanto gli opcodes con i quali successivamente patcheremo fisicamente il target. Gli opcodes saranno universali per le push, per i cmp ecc., e pertanto per QUESTO tipo di operazioni possiamo utilizzare HIEW direttamente, ma per i jumps a sezioni diverse avremo bisogno di SoftICE, poiche' la distanza tra gli offsets del punto di partenza e del punto di arrivo in due sezioni diverse non sono ottenibili con il patching da HIEW. Si, allora ok per i jumps, ma per le CALLS come facciamo ?? Le calls alle funzioni API (che noi utilizzeremo) non sono nient'altro che calls ad un puntatore dword, che punta ad uno specifico offset in una sezione denominata .IDATA (imported data): ogni API che verra' usata dal programma viene inserita in quella sezione all'avvio, e per servircene dal codice dovremo usare CALL [DWORD_PTR_DELLA_API]. Per scoprire quale Dword Pointer chiamare per una determinata API, utilizzeremo W32Dasm. Ad ogni API chiamata (basta fare una ricerca con il nome della funzione nel disassembly) corrispondera' una call dword ptr [xyz]: ogni volta che vi serve una tale API chiamerete da hiew quel dword ptr, niente piu' niente meno. Tutto cio' e' molto semplice, tuttavia potreste chiedervi una cosa...COME facciamo a sapere, da SoftICE, a QUALE offset saltare dalla sezione .TEXT per raggiungere il nostro codice ?? La locazione non la possiamo trovare con W32Dasm perche' la parte disassemblata riguarda soltanto la sezione .TEXT, non la .RELOC (dove ci sara' il nostro codice)...la soluzione sta in HIEW: aprite notepad.exe con hiew: una volta in modo HEX o DISASM, premete ALT+F1 per cambiare tra visualizzazione degli offsets in modo Global o Local. Posizionatevi su LOCAL, e noterete che, se prima avevate "global", ora verra' mostrato l'offset completo di imagebase, che sara' tutto cio' di cui abbiamo bisogno per sapere a quale VA (Virtual Address) corrisponde l'offset a cui vogliamo saltare, e inserire "JMP VA_del_nuovo_codice" da Sice, lasciando a lui il compito della generazione degli opcodes. SOLO PER I JMPS (jump short, quelli da 2 opcodes per intenderci) e per quelli non troppo distanti non avremo bisogno di Sice, ma potremo inserire direttamente l'offset relativo in HIEW, in quanto il jump non esce da un raggio ristretto di codice, e HIEW e' ancora in grado di generare degli opcodes validi. Percio' d'ora in poi, quando vedrete degli asterischi (*******) a fianco dell'istruzione, significa che la stessa e' stata assemblata con softice, e che ho patchato il file con i bytes che questi mi ha restituito. Un'altra cosa....abbiamo detto che dobbiamo far aprire un nuovo file con questo menu, che sara' il file di Anub|s e Insanity sulla programmazione in HTML...questo file avra' pure un nome (qualcosa.hlp), anche se io non lo conosco ancora (ma nemmeno loro due mi sa :))), comunque per la prova useremo un file fittizio HNOTEPAD.HLP. Allora, dicevo, dobbiamo fare si che il codice riconosca anche il nome "HNOTEPAD.HLP", solo che questo nome, ovviamente, non e' presente da nessuna parte....quindi dobbiamo aggiungerlo alla STRINGTABLE del target. La stringtable altro non e' che il posto dove possiamo visualizzare tutte le stringhe contenute in un file alle cui risorse abbiamo accesso. Potremmo anche sostituire qualche stringa vecchia con la nuova "HNOTEPAD.HLP", oppure inserire direttamente nel codice .exe la nostra stringa (ideale per quando non avete accesso alle risorse, e cosa che faremo anche nel corso di questo tutorial), ma visto che abbiamo accesso alle risorse di notepad.exe, la AGGIUNGEREMO alla stringtable, e la tratteremo proprio come una qualsiasi stringa esistente gia' da prima. Quindi il primo passo e', in BRW, selezionare l'ULTIMA stringtable (la 48), premere il tasto destro e EDITARLA IN MODO TESTUALE (attenti a non premere solo edit, o non potrete aggiungere items). Alche', semplicemente aggiungete questa riga alla fine: 58, "hnotepad.hlp" Salvate il file, ed il gioco e' fatto! Adesso abbiamo una stringa nuova di zecca che potremo utilizzare a nostro piacimento...cominciate ad intravedere l'enormita' delle possibilita' che ci si parano davanti ?? :D Allora....a questo punto dovete sapere (se non lo sapevate gia':) che le stringhe si caricano dalla stringtable con la funzione LoadStringA, la cui sintassi e': int LoadString( INSTANCE hInstance, // handle del modulo contenente la risorsa della stringa UINT uID, // ID della stringa (nel nostro caso 58, quindi 3Ah) LPTSTR lpBuffer, // Indirizzo del buffer di ricezione della stringa int nBufferMax // dimensione massima del buffer (n. di chars da prelevare dalla stringa) // se la stringa e' piu' corta non fa niente, ma se e' piu' lunga viene // troncata! ); Hmmm....il problema qui e' il buffer....come facciamo a sapere quale buffer utilizzare per inserirvi la nostra nuova stringa ?? Beh, un po' di esperienza pratica direi...cominciate a settare breakpoints, e troverete molto presto un buffer adatto alla ricezione. Avete due scelte: trovare un buffer che viene caricato UNA volta all'inizio e MAI piu' (in tal caso dovrete usare il loadstring una volta in piu', una specie di PUSH e POP con gli indirizzi per intenderci), o usare un buffer che viene caricato da un altro LoadStringA quando serve....in tal caso, potete sfruttarlo quando e quanto volete, tanto alla fine sara' il programma stesso a ripristinare il suo contenuto originale, all'occorrenza. Quest'ultima e' la scelta piu' comoda, decisamente. Quindi, ad esempio, prendiamo la message box "Questo documento e' troppo grande per notepad, aprire wordpad??"...se premete si, noterete un LoadStringA che carica in un buffer la variabile "wordpad.exe", che serve ad avviare il programma....eccovi il disasm (ricordatevi che i valori vengono pushati sulla stack in ordine inverso): :00402D70 6804010000 push 00000104 ; PUSHA N.DI CARATTERI DA PRENDERE DALLA STRINGA :00402D75 8D85B8FEFFFF lea eax, dword ptr [ebp+FFFFFEB8] ; EAX DIVENTA=63F8C4h :00402D7B 50 push eax ; PUSHA 63F8C4h COME BUFFER DI RICEZIONE DELLA STRING * Reference To: USER32.LoadStringA, Ord:0168h | :00402D7C 8B1DB0734000 mov ebx, dword ptr [004073B0] :00402D82 837D1001 cmp dword ptr [ebp+10], 00000001 ; (DUMMY) :00402D86 1BFF sbb edi, edi ; (DUMMY) * Possible Reference to String Resource ID=00056: "wordpad.exe" | :00402D88 6A38 push 00000038 ; ID DI "WORDPAD.EXE" NELLA TABLE :00402D8A FF3570514000 push dword ptr [00405170] ; HANDLE DEL MODULO CON LA TABLE :00402D90 FFD3 call ebx ; CHIAMA LOADSTRING Ottimo! Abbiamo trovato il nostro buffer...segnatevi quell'indirizzo di memoria : 63F8C4h...anche se vi sovrascriveremo la nostra stringa "hnotepad.hlp", il buffer verra' ripristinato da questo loadstring ogni qual volta si cerchera' di avviare wordpad. Notate che possiamo, in questo modo, anche impossessarci dell' informazione "invariabile" relativa alla funzione, ovvero l'handle del modulo contenente la stringtable (il "dword ptr [405170]" pushato a 2D8A, che contiene il valore 400000h, che sara' il nostro handle). Potremmo pushare direttamente 400000h all'occorrenza, ma utilizzeremo sempre questo puntatore (ricordatevi che quando modificate targets gia' compilati, e' sempre piu' sicuro, soprattutto per chi non ha molta dimistichezza, verificare i dati due volte, e in linea generale usare IL PIU' POSSIBILE i puntatori e IL MENO POSSIBILE i valori immediati). Adesso sappiamo come ottenere la stringa "hnotepad.hlp" nel nostro codice, ma ci manca ancora il dettaglio finale....COME chiamiamo la funzione WinHelpA? Ancora una volta la API reference ci viene in soccorso: BOOL WinHelp( HWND hWndMain, // handle della finestra che sta aprendo l'help LPCTSTR lpszHelp, // indirizzo della stringa con la path UINT uCommand, // tipo di help DWORD dwData // dati extra ); Beh, qui e' semplice....ancora una volta, ci renderemo conto del modo in cui viene chiamata la funzione esaminando esempi PRATICI della stessa all'interno del codice: quindi, un BPX WinHelpA ci servira' per analizzare il codice quando premiamo ?/Guida in linea... ecco a voi: :0040121C 6A00 push 00000000 ; NIENTE DATI EXTRA :0040121E A194604000 mov eax, dword ptr [00406094] ; EAX=PUNTATORE A STRINGA "NOTEPAD.HLP" :00401223 6A0B push 0000000B ; =HELP_FINDER, SEMPLICEMENTE UN MODO DI CHIAMATA HELP :00401225 50 push eax ; PUSHA "NOTEPAD.HLP" :00401226 FF3500604000 push dword ptr [00406000] ; ECCO IL PUNTATORE ALL'HANDLE DELLA WINDOW * Reference To: USER32.WinHelpA, Ord:0225h | :0040122C FF154C744000 Call dword ptr [0040744C] ; CHIAMA WINHELPA Fantastico....l'handle (che era la cosa che ci premeva di piu') lo troveremo, a tempo debito, nel dword ptr [406000]... Un'ultima parola riguardo a questa funzione: la api reference ci informa che dovremo chiamare la funzione WinHelpA con il parametro 02 (=HELP_QUIT) quando chiudiamo il programma, ma non dovremo preoccuparci di questo, perche' il notepad lo fa automaticamente al momento dell'uscita. Bene! Adesso abbiamo tutte le informazioni che ci servono per buttarci nella mischia e aggiungere codice al target! Per prima cosa, sostituiremo il CMP a 4011BA con un bel JMP 40891E era: :004011BA 3D00030000 cmp eax, 00000300 :004011BF 7C21 jl 004011E2 e diventa: :004011BA E95F770000 ************ jmp 0040891E ; -> VERSO IL NUOVO CODICE :004011BF 90 nop :004011C0 90 nop Abbiamo pertanto sostituito un CMP e un JL con un JMP e due NOP. I nop servono per riempire i bytes del jl che non ci serve piu' (potevamo anche tenerli, ma e' una questione di ordine e chiarezza...troverete piu' facile organizzarvi cosi'), perche' spostiamo l'intero check nella sezione .RELOC. Un ultimo avvertimento prima di andare ad inserire il nosro codice. Bisogna tener presente che quando eseguiamo operazioni nostre la stack e i registri vanno mantenuti intatti, in altre parole non dobbiamo lasciare traccia del nostro "passaggio"...lo possiamo fare facilmente salvando tutti i registri all'inizio e ripristinandoli alla fine con PUSHAD e POPAD. Ed ora ecco il codice che andremo ad aggiungere con HIEW all'offset 591Eh (F3 poi F2 per inserire l'istruzione asm...quando trovate gli asterischi significa che dovete uscire da F2 e inserire manualmente i bytes, che leggete dopo la dicitura "OPCODES:", cosi' come sono, perche' sono stati generati da softice, mentre quando trovate le calls a funzioni API potete leggere l'indirizzo del dword ptr da chiamare in CALL D,[40xxxx]): N.B.: IN HIEW, PER SCRIVERE "DWORD PTR [xyz]", USATE "D,[xyz]", per "BYTE PTR" usate "B" ecc. IN GENERALE, RIFERITEVI ALLE ALTRE ISTRUZIONI PER CAPIRE IN CHE FORMA INSERIRE LE VOSTRE. 591Eh: cmp eax, 21 ; eax contiene l'id dell'item. 21h = Id del nuovo menu "Anub|s+Insa ecc." jz 5933 ; se hai clickato li', elaboralo - jump a 2 opcodes, quindi hiew e' ok cmp eax,300 ; QUESTE SONO LE DUE RIGHE CHE ABBIAMO SOSTITUITO CON IL JMP NELLA ****** jl 4011E2 ; SEZIONE .TEXT: LE RIPRISTINIAMO QUI!...OPCODES: 0F8CB488FFFF ****** jmp 4011C1 ; continua con gli altri checks, ritornando alla sezione .TEXT ; OPCODES: E98E88FFFF 5933h: pushad ; salva tutti i registri push 20 ; numero max di chars da prendere dalla stringa push 63F8C4 ; indirizzo del buffer di ricezione della stringa, vedi sopra push 3a ; 3Ah=58 dec...ovvero l'id della nostra stringa "hnotepad.hlp" push dword ptr [405170] ; handle del modulo contenente la table call LoadStringA ; carica 20 chars della stringa "hnotepad.hlp" in 63F8C4 ; CALL D,[4073B0] popad ; ripristina tutti i registri ; a questo punto dobbiamo inserire la chiamata a WinHelpA push 00 ; niente dati extra push 03 ; (=HELP_INDEX, ci fara' uscire l'indice della guida all'apertura) push 63f8c4 ; buffer contenente "hnotepad.hlp" push dword ptr [406000] ; handle della owner window (vedi sopra) ****** jmp 40122c ; torna alla sezione .text dove c'e' la call a WinHelpA ; OPCODES: E9CE88FFFF Fantastico...adesso fate partire il vostro "nuovo" notepad, facendo attenzione a mettere un file di help di windows con il nome "hnotepad.hlp" nella stessa directory da cui state facendo partire notepad....clickate sul nuovo menu che vedete sotto "?", et voila'! il gioco e' fatto...abbiamo aggiunto una funzione nuova di zecca a notepad.exe :) __________________________________ FASE 2 : L'ABOUT BOX __________________________________ Eccoci quindi alla fase due....perche' mettere un nuovo about box ?? semplice...innanzitutto, e' importante che Insa e Anub|s vengano ringraziati e "creditati" nell'about, e poi un minimo di soddisfazione personale....:)) Inoltre, scopriremo che non e' tutto oro quello che luccica...credevate che sarebbe bastato cambiare un paio di stringhe qua e la' per cambiare l'about box ?? Vi sbagliavate...quei lamers alla Micro$oft hanno inventato una funzione molto pratica, che si trova in shell32.dll...la funzione di cui parlo e' ShellAboutA. Questa API non fa altro che creare una specie di message box PREDEFINITA, con la percentuale di risorse rimaste, la memoria ecc. (insomma l'about di notepad) piu' qualche info addizionale che si puo' specificare al momento. Inutile dirlo, cio' ci fa una tale rabbia che NON POSSIAMO PASSARCI SOPRA :) Un bpx ShellAboutA (caricate prima gli exports di shell32.dll, con "File/Load Exports" dal symbol loader di SoftICE, o inserendola tra gli exports di winice.dat e riavviando) ci indica presto il punto in cui comincia il codice per l'about box: :004013D0 6A02 push 00000002 :004013D2 A170514000 mov eax, dword ptr [00405170] :004013D7 50 push eax * Reference To: USER32.LoadIconA, Ord:015Eh | :004013D8 FF1550744000 Call dword ptr [00407450] :004013DE 50 push eax :004013DF 683C614000 push 0040613C :004013E4 FF3560604000 push dword ptr [00406060] :004013EA FF3500604000 push dword ptr [00406000] * Reference To: SHELL32.ShellAboutA, Ord:004Ch | :004013F0 FF1580734000 Call dword ptr [00407380] bah, tutta roba inutile....piuttosto, cerchiamo di inserire un po' di codice nostro che faccia uscire un nuovo about box.... Come nella situazione di prima, dobbiamo renderci conto di quello che stiamo per fare. Una message box andra' benissimo. Ecco la sintassi per questa semplice funzione API: int MessageBox( HWND hWnd, // handle della owner window LPCTSTR lpText, // indirizzo del buffer del TESTO DELLA MESSAGE BOX LPCTSTR lpCaption, // indirizzo del buffer del TITOLO DELLA MESSAGE BOX UINT uType // stile della message box ); Hmmm....cosa facciamo con quei due buffer ?? il testo che vogliamo inserire e' lunghetto.... potremmo aggiungere altre stringhe alla stringtable e utilizzare altri buffers per fungere da titolo e testo della msgbox, ma pensate ad una cosa...nella FASE 4 di questo tutorial rimuoveremo il box "file troppo esteso, avviare wordpad?", visto che elimineremo la limitazione nella grandezza dei files aperti, e, di conseguenza, potremmo inserire il nostro testo di about in questa stringa nella table...cosicche' il puntatore a questa stringa (che sta a 4060B0, come possiamo vedere breakkando sulla message box suddetta) punterebbe adesso al testo del nostro box di about...quindi tutto cio' che dobbiamo fare e', in BRW, modificare la stringa che ha come id 52....cambiarla da "File troppo esteso per essere aperto.\nUtilizzare WordPad per leggere il file?" in "RingZ3r0's Hnotepad v1.00\nVersione modificata di Micro$oft's Notepad.exe\n\n\n* Reversing del codice e implementazione nuove funzioni di -NeuRaL_NoiSE\n\n* File di help sulla programmazione in HTML di Anub|s e Insanity" Potete semplicemente fare copy&paste del testo suddetto. I "\n" vanno a capo, come in c. Resta il problema del titolo. Hmm vediamo un po'....il titolo dell'about box deve essere qualcosa come "Hnotepad" giusto ?? Beh, adesso date un'occhiata a tutte quelle stringhe in cui compare "Blocco note" nella stringtable...facciamo una cosa. Modifichiamo tutti i "Blocco note" che troviamo in "Hnotepad"....adesso facciamo partire il programma e facciamoci un giro in memoria....una semplice s 0 l ffffff 'Hnotepad' da SoftICE ci indichera' un punto della memoria dove c'e' la stringa suddetta....soffermiamoci un attimo sulla corrispondenza a 41029F. Noterete che e' preceduta e seguita da bytes 00....cio' significa che la stringa e' tutta li', e' solo "Hnotepad"...un titolo perfetto per una msgbox non trovate ?? :) Potremmo, come sopra, pushare direttamente l'indirizzo 41029F al momento della call a MessageBoxA...ma troveremo il puntatore a questo buffer, e pusheremo quello, invece...sempre per gli stessi motivi di prima. Non fatevi spaventare dal suono delle parole, un buffer e' semplicemente una variabile, e un puntatore.....beh diciamo che e' il "nome" di quella variabile, giusto per dirla in modo spicciolo. I puntatori sono nella sezione .DATA. Abbiamo gia' visto un puntatore prima, ricordate? Parlo dell'handle del "modulo contenente la stringtable" per la funzione LoadStringA....quel puntatore era a 405170....cio' ci basta per individuare la sezione .DATA...ora tutto sta a trovare un puntatore (il 'nome') al buffer (la 'variabile') 41029f ("Hnotepad")...presto fatto, ecco li' il nostro puntatore: 406060. allora....cosa altro ci resta ?? hmmm, l'handle della owner window.....da un esempio pratico di MessageBoxA (quello relativo alla richiesta "caricare wordpad?"), notiamo che ebp+8 contiene l'hwnd....e questo non cambia, come vedrete se sperimentate un po'....quindi, abbiamo anche l'ultimo dato che ci serviva....l'hwnd! Ora non resta che decidere lo stile della message box....direi che, trattandosi di un box di about, un valore 0 (=MB_OK, cioe' un unico button con su scritto "ok") andra' benissimo come style.... Allora siamo pronti! Modifichiamo anche quest'altro codice in modo che faccia quello che gli ordiniamo....il VA a cui appenderemo il nostro codice dell'about box sara' 40895E (offset 595Eh), quindi dovremo inserire un jump nella sezione .TEXT per saltare al nostro codice. L'inizio della routine di ShellAboutA andra' bene: era :004013D0 6A02 push 00000002 :004013D2 A170514000 mov eax, dword ptr [00405170] e diventa :004013D0 E989750000 ************ jmp 0040895E ; --> VERSO IL NUOVO CODICE Il codice che segue il nuovo jmp viene modificato completamente, ma non ci interessa visto che non ci serviva a niente...adesso il programma continuera' nella sezione .RELOC all'offset 595Eh, con il NOSTRO codice: ... 595Eh: pushad ; salva tutti i registri push 00 ; 0 = MB_OK push dword ptr [406060] ; pusha il puntatore a "Hnotepad" come TITOLO push dword ptr [4060B0] ; pusha il puntatore alla nuova stringa "RingZ3r0 ecc" come TESTO mov esi, [ebp+8] ; hWnd della owner window in esi push esi ; pusha l'handle call MessageBoxA ; mostra la message box - CALL D,[407430] popad ; ripristina tutti i registri ****** jmp 4016eb ; torna a .text - OPCODES: E96E8DFFFF nop ; finisce anche questa parte di codice, il nop e' inutile ma se ; state seguendo questo tutorial passo passo inseritelo anche ; voi, per ritrovarvi con gli offsets. Bene! Anche questa e' andata.....provate ora a far uscire l'about box....noterete che mostra la stringa che gli abbiamo cambiato, con in piu' "Hnotepad" come titolo....ovviamente se ora provate ad aprire un file piu' grosso di FFFFh bytes vi uscira' un box senza senso, poiche' la vecchia stringa "File troppo esteso..." non esiste piu'...ma e' solo questione di pazienza, elimineremo anche quel box. ______________________________________________________ FASE 3 : AGGIUNTA DI MASKS ALLA STRUTTURA OPENFILENAME ______________________________________________________ Bene....visto che questo sara' un editor per files Html, mi sembra pure ovvio che debba contenere anche le masks "Files HTML", "Files JS" e "Files VBS" tra quelle dell'apertura/salvataggio files....(o meglio, lo devo fare altrimenti Anub|s mi ammazza.....:)))))) sto scherzando ovviamente ;))) Partiamo dal dialog box "Apri file". Innanzitutto, dovete sapere che quel dialog box "Apri File" e' un dialog box predefinito, che viene chiamato con una API specifica, ovvero GetOpenFileNameA, contenuta in Comdlg32.dll... quindi, per lavorare con questa API, cominciate a caricare gli exports di questa dll. Un'altra cosa da sapere, e' che comunque le stringhe filtro (masks) vengono specificate, assieme ad altri parametri (vi rimando alla API reference o ai tutorials di Iczelion per maggiori dettagli) in una struttura di tipo OPENFILENAME, che viene pushata sulla stack prima della call a GetOpenFileNameA. Ergo, la struttura conterra' tutte i dati di cui abbiamo bisogno - quindi anche i filtri. I filtri vengono definiti in questo modo (prendiamo ad es. un filtro per files di testo): Filtro db "FILES DI TESTO",0,"*.txt",0,0 .....<--- il secondo zero non e' un errore, ma viene messo se il filtro e' l'ultimo della lista. Altrimenti un solo zero e poi la descrizione del filtro successivo. Questo e' tutto quello che ci serve. Possiamo definire nella stringtable una nuova stringa, che andra' alla posizione 59 (3Bh): 59, "Files HTML|*.htm; *.html|Files JS|*.js|Files VBS|*.vbs" Non preoccupatevi per ora per quel "|" tra "descrizione" e "filtro", ci serve solo per identificare un punto in cui piazzeremo uno ZERO BYTE una volta APPESA la stringa alla lista dei filtri gia' presenti, in modo da far credere al programma che si tratta di varie stringhe diverse e non di un'unica stringa continua ("descrizione",0,"filtro effettivo",0,ecc.ecc.). NB: Abbiamo inserito la dicitura "*.htm;*.html" per far capire al programma che dovra' includere sia i files con estensione .htm che quelli con estensione .html sotto la stessa mask. In Sice, BPX GetOpenFileNameA e poi scegliete "File/Apri". Sice poppera', premete F12 e il dialog "Apri file" vi comparira' sullo schermo. Premete ESC e vi ritroverete in Sice, a questo punto: :004012E7 6880524000 push 00405280 ; LA STRUTTURA OPENFILENAME VIENE PUSHATA.... :004012EC C7058C52400080514000 mov dword ptr [0040528C], 00405180 :004012F6 C7059052400030524000 mov dword ptr [00405290], 00405230 :00401300 C705B452400004100000 mov dword ptr [004052B4], 00001004 :0040130A 83C003 add eax, 00000003 :0040130D A3BC524000 mov dword ptr [004052BC], eax * Reference To: comdlg32.GetOpenFileNameA, Ord:0005h | :00401312 E8FA350000 Call 00404911 ; <-- .......ED ECCO LA CALL ! Benissimo, adesso diamo un'occhiata in memoria a 405280. Saliamo un po' con il puntatore e, un paio di PGUP piu' su, troverete chiare traccie delle stringhe-filtro ("Documenti di testo *.txt" ecc.). Le stringhe filtro terminano, come potete vedere, all'indirizzo di memoria 4051B0 (con il secondo BYTE 00, che denota la fine del membro). Quindi, un semplice LoadStringA su questo indirizzo ci permettera' di APPENDERE la nostra nuova stringa-filtro ai filtri gia' presenti. il nostro codice, ancora una volta, continuera', giu' nella sezione .RELOC, al VA 40897E (offset 597Dh). Il jump al nostro codice lo inseriremo al posto della push (all'offset 6E7h), che ripristineremo in seguito. era :004012E7 6880524000 push 00405280 :004012EC C7058C52400080514000 mov dword ptr [0040528C], 00405180 e diventa :004012E7 E992760000 ************ jmp 0040897E ; --> VERSO IL NUOVO CODICE :004012EC C7058C52400080514000 mov dword ptr [0040528C], 00405180 Come potete notare, il MOV seguente non cambia, percio' potremo semplicemente tornare qui quando avremo finito con la modifica e il pushing della struttura. Adesso tutto cio' che dobbiamo fare e' modificare, allo stesso scopo, il codice relativo al dialog "Salva con nome". Questo dialog viene chiamato con la API GetSaveFileNameA (sempre in comdlg32.dll). Non avremo bisogno di grosse modifiche, bastera' far puntare il codice della push della struttura relativa al dialog "salva con nome" al nostro nuovo pezzo di codice, esattamente lo stesso procedimento del dialog "Apri file". Percio', con un bpx su GetSaveFileNameA ci accorgiamo che il codice da modificare e' questo: era :0040168A 6880524000 push 00405280 ; pusha struttura :0040168F E86B320000 Call 004048FF ; Call GetSaveFileNameA e diventa :0040168A E9EF720000 ************ jmp 0040897E ; --> VERSO IL NUOVO CODICE :0040168F E86B320000 Call 004048FF ; Call GetSaveFileNameA Come vedete la successiva call a GetSaveFileNameA resta invariata, quindi non abbiamo bisogno di ulteriori modifiche. Ma come fara' il nostro codice a differenziare tra un GetSaveFileNameA e un GetOpenFileNameA (una differenziazione e' necessaria, visto che DOBBIAMO sapere in QUALE PUNTO della sezione .TEXT tornare successivamente all'esecuzione del nostro codice) ?? Date un'occhiata ai registri, una volta arrivati al nostro codice (o al jump che ci porta li') : quando abbiamo scelto "apri file", il valore in ESI sara' sempre 0Ah. Se invece abbiamo scelto "Salva con nome" sara' sempre un altro. La spiegazione e' molto semplice: in BRW, editate il menu e scegliete File/Apri. Notate l'item ID: e' 10, che in hex e' 0Ah. esi quindi contiene ancora l'item ID quando raggiungiamo il nostro codice. Pertanto, ci bastera' controllare se ESI=Ah, e tornare a .TEXT nel punto relativo. Il nostro codice, quindi, sara' questo: ... 597Eh: pushad ; salva tutti i registri push 50 ; numero max di chars da prendere dalla stringa push 4051b0 ; indirizzo del buffer di ricezione della stringa (appende ai ; filtri gia' esistenti) push 3b ; 3Bh=59 dec...ovvero l'id della nostra stringa ; "Files HTML|*.htm; *.html|Files JS|*.js|Files VBS|*.vbs" push dword ptr [405170] ; handle del modulo contenente la table call LoadStringA ; carica la stringa "Files HTML|*.htm; *.html ecc." in 63F8C4 ; CALL D,[4073B0] mov byte ptr [4051ba], 0 ; mette uno ZERO BYTE al posto del "|" tra "Files HTML" e ; "*.htm", di modo da rendere effettiva la separazione tra ; descriz. e filtro, e far credere al programma che si tratta di ; due stringhe diverse mov byte ptr [4051c8], 0 ; come sopra ma tra "*.html" e "Files JS" mov byte ptr [4051d1], 0 ; come sopra ma tra "Files JS" e "*.js" mov byte ptr [4051d6], 0 ; come sopra ma tra "*.js" e "Files VBS" mov byte ptr [4051e0], 0 ; come sopra ma tra "Files VBS" e "*.vbs" cmp esi,0ah ; ESI=Ah se veniamo da un click su "APRI FILE" jnz 59c7 ; ESI != 0ah?? popad ; se no, ripristina tutti i registri... push 405280 ; ...pusha la struttura modificata... ****** jmp 4012ec ; E CONTINUA CON LA PARTE DEL GetOpenFileNameA (torna a .TEXT) ; OPCODES: E92589FFFF 59C7: popad ; se invece esi!=0ah, ripristina tutti i registri... push 405280 ; ...pusha la struttura modificata... ****** jmp 40168f ; E CONTINUA CON IL GetSaveFileNameA (torna a .TEXT) ; OPCODES: E9BD8CFFFF Ecco fatto...ora, i vostri dialogs "Apri File" e "Salva con nome" mostreranno 3 masks in piu'. Potete anche allungare la nuova stringa-filtro, e conseguentemente aggiungere piu' filtri, sempre tenendo presente che non dovete superare il blocco di memoria allocata per la struttura, che non dovete sovrascrivere gli altri membri e che dovrete mettere uno 0 byte tra le varie "descrizione","filtro","descrizione" ecc...OLTRE A *DUE* ZERO BYTES ALLA FINE DELL'ULTIMO FILTRO. _______________________________ FASE 4 : AGGIUNTA DEL FILE .INI _______________________________ Allora, eccoci arrivati alla parte forse un po' piu' complessa di questo tutorial. Innanzitutto diamo un'occhiata a COME apparira' questo file .INI per default: **** FILE GENERATO AUTOMATICAMENTE DA HNOTEPAD.EXE UTILIZZARE SOLO 'S' O 'N' ALLA VOCE 'AutoWrap' -NeuRaL_NoiSE 1999 **** AutoWrap=N UltimoFile= Ora possiamo pensare a come vogliamo strutturare il codice relativo a questa funzione. Naturalmente dovremo scrivere una parte di codice che LEGGE il .INI all'avvio del programma e una che lo SCRIVE all'uscita. Analizziamo prima la funzione di LETTURA: * All'avvio di Hnotepad, il codice dovra' cercare un file predefinito C:\Windows\Hnotepad.ini. Se tale file non viene trovato, continuare come se nulla fosse. * Se viene trovato, leggerne i dati contenuti e cercare il primo uguale ('=') in esso contenuto. Dopo quest'uguale ci sara' la posizione desiderata del WordWrapping all'avvio. Se il '=' non viene trovato, ritornare a .text senza ulteriori checks e procedere in modo predefinito. * Se viene trovato il primo '=', analizzare la lettera successiva ad esso, e controllare se e' 'S', 's', 'N' o 'n'. Se la lettera non corrisponde, tornare a .text e procedere in modo predefinito. * Se la lettera corrisponde, accodare o meno il messaggio del wordwrapping alla message queue del processo, capirete in seguito come fare. * Controllare se la Command Line == NULL. Se si e' scelto un file per l'apertura, si dovra' caricare questo e non l'ultimo file stabilito nel .INI, poiche' ovviamente quest'ultimo ha priorita' inferiore rispetto al file scelto da command line. * SOLO se la Command Line == NULL, si cerchera' l'ultimo file aperto da .INI. Partendo dalla posizione precedente, cercare il '=' successivo. Dopo questo '=' ci sara' il nome dell'ultimo file aperto. Se il '=' non viene trovato, tornare a .text e procedere con l'apertura di un nuovo documento (come notepad si sarebbe comportato normalmente, insomma). * se il '=' viene trovato, controllare che immediatamente dopo di esso ci sia una lettera compresa tra 'a' e 'z', oppure tra 'A' e 'Z'. Cio' ci serve per stabilire (a grandi linee) se il nome del file e' corretto (da non confondersi, qui non stiamo controllando se il file esiste, stiamo solo controllando che non si verifichi il tentativo di apertura di un file, ad esempio, 7:\xyz\xyz.txt"). In altre parole controlliamo che la lettera iniziale sia una lettera dell'alfabeto, cioe' un identificatore valido per un drive, e non, magari, un numero o un simbolo. Se non e' una lettera valida, tornare a .text e procedere in modo predefinito. * Se abbiamo trovato una lettera valida, procedere cercando la LUNGHEZZA della stringa che rappresenta il nome del file da aprire. salvare la posizione iniziale della stringa, e avanzare cercando uno spazio (' ', =20h) o un carriage return (=0Dh). In assenza di uno di questi, procedere fino alla fine del file. Da precisare una cosa: quando salviamo il nostro file di default, dopo il secondo '=' c'e' uno spazio (' '), che se viene ritrovato nel check iniziale indica la fine del nome del file. Se invece salviamo il .INI con il nome di un file da riaprire successivamente, vi appenderemo un CR (=0Dh), che funzionera' ne' piu' ne' meno come lo spazio. Se l'utente ha inserito manualmente il nome dell'ultimo file nel .INI, e non ha inserito alla fine uno spazio o un CR, non c'e' problema comunque poiche' avremo un terzo check, quello della lunghezza del file: una volta raggiunta la EOF si considerera' finita la stringa di definizione dell'ultimo file. * Una volta trovata la fine della stringa di definizione del file da aprire, copiarla direttamente nel buffer che viene utilizzato per l'apertura di un file quando cmdline!=NULL. Notepad provvedera' automaticamente a caricare il file perche' noi effettuiamo questa operazione PRIMA che il check 'nativo' per la cmdline viene effettuato. In parole povere, 'bruteforceremo' il nome di un file nel buffer che altrimenti avrebbe contenuto solo zero bytes, 'simulando' quindi la scelta di un file da aprire da command line da parte dell'utente. * A questo punto, torneremo a .TEXT per continuare normalmente con il codice. Ora stabiliremo di quali funzioni API avremo bisogno. Quando nel codice leggerete una call ad una API, sostituite in HIEW semplicemente call d,[dword_ptr_della_api]. ecco quali sono i ptr che vi serviranno: LoadStringA: call d,[4073b0] _lopen: call d,[407364] _lread: call d,[407368] PostMessageA: call d,[40745c] lstrcpyA: call d,[40733c] _lclose: call d,[4072f0] Adesso dobbiamo tenere presente una cosa. Per aggiungere altro codice, dovremo sfruttare la sezione .RSRC, poiche' lo spazio rimasto in .RELOC e' ormai alla frutta. Purtroppo c'e' una limitazione alla nostra voglia di reverserare tutto, e tale limitazione risiede in BRW. Ovviamente con un po' di semplice intuito la aggireremo, ciononostante mi sembra giusto che voi sappiate COSA stiamo affrontando qui :) La limitazione di cui parlo risiede nel fatto che BRW, ad ogni modifica che effettua sulle risorse, RICREA da zero la sezione .RSRC. Capite di cosa sto parlando ?? Il vostro simpatico codice aggiuntivo in .RSRC viene completamente cancellato ad una SINGOLA modifica in una qualsiasi risorsa effettuata con BRW, e del vostro lavoro non restano tracce. Ci sono vari sistemi per aggirare il problema, e noi ne utilizzeremo due: innanzitutto immetteremo (quasi) tutte le stringhe di cui abbiamo bisogno nella stringtable PRIMA di cominciare con il nuovo codice in .RSRC. Poi, quando tutto sara' scritto, ci ritroveremo a dover utilizzare altre stringhe per la 5 fase di questo tutorial, ma affronteremo il problema quando ci si parera' davanti, parlarne adesso mi sembra prematuro. Pertanto, cominciate con l'aggiungere queste due stringhe alla stringtable del nostro target, capirete in seguito a cosa serve ciascuna di esse: 60, "c:\\windows\\hnotepad.ini" 61, "****\nFILE GENERATO AUTOMATICAMENTE DA HNOTEPAD.EXE\nUTILIZZARE SOLO 'S' O 'N' ALLA VOCE 'AutoWrap'\n\n-NeuRaL_NoiSE 1999\n****\n\nAutoWrap=N\nUltimoFile= " Un altro problema che incontrerete nell'aggiunta del codice, inoltre, sara' nello spazio fisico che avete per inserire bytes. Verso la fine di questo spezzone di codice, infatti, starete quasi per finire anche lo spazio in .RSRC. Pertanto, gia' da ora, editate il PE di notepad.exe con ProcDump, poi clickate su "sections", e editate la sezione .RSRC. Ingrandite la Raw size e la Virt Size (per sicurezza) da 3000 a 3200 bytes, e salvate i cambiamenti solo al PE header. Questo ci dara' tutto lo spazio di cui necessiteremo per aggiungere il nostro ingombrante codice :) Detto cio', credo che possiamo cominciare a cercare il punto da cui far saltare al nostro codice per il controllo del .INI. Bisogna tener presenti due presupposti in questa ricerca: 1) per informare il programma che vogliamo il wordwrapping, abbiamo bisogno di 'simulare' un click sull'opzione "A capo automatico". Questo e' molto semplice, lo possiamo fare con SendMessageA o con PostMessageA. SendM. salta subito alla window procedure e quindi e' poco comodo per noi, che dobbiamo effettuare le dovute "pulizie" prima di tornare a .TEXT, pena un triste GPF o un Page Fault. Pertanto, utilizzeremo PostMessageA, che semplicemente AGGIUNGE il messaggio alla message queue (coda dei messaggi) del thread in esecuzione. Ma un attimo...per utilizzare PostMessageA abbiamo bisogno di un HWND a cui mandare il messaggio in questione! Pertanto il primo presupposto e' che il controllo di EDIT (che notepad usa attualmente) deve essere GIA' STATO CREATO QUANDO SALTIAMO AL NOSTRO CODICE. 2) tuttavia, se aspettiamo troppo, potremmo incappare nell'errore opposto: il thread ha gia' creato il controllo di edit, ma anche il check per la command line potrebbe essere gia' stato fatto! Ecco quindi qual'e' il secondo presupposto: IL CHECK PER LA COMMAND LINE *NON* DEVE ESSERE GIA' STATO FATTO. Con buona approssimazione, possiamo supporre che il check per la command line avviene DOPO la creazione del controllo, e questo e' decisamente un punto a nostro favore. Tutto sta a 'cogliere' quell' "attimo" tra le due operazioni, e inserirvici di forza tutti i nostri checks. Direi che un buon inizio puo' essere quella message box "File xyz.xxx non trovato" che poppa quando cerchiamo di aprire un file inesistente. Essa ci indica infatti un potenziale punto in cui il controllo e' stato creato e il file da aprire e' stato APPENA controllato. Ecco dove ci porta un BPX MessageBoxA: * Reference To: USER32.MessageBoxA, Ord:0176h | :00402251 FF1530744000 Call dword ptr [00407430] :00402257 8BE5 mov esp, ebp :00402259 5D pop ebp :0040225A C21400 ret 0014 Hmm, risaliamo per quel ret... :004028D0 E84FF9FFFF call 00402224 ; <-- TORNIAMO DA QUESTA CALL :004028D5 83F806 cmp eax, 00000006 :004028D8 7545 jne 0040291F Ecco il check del tasto che abbiamo premuto. Ma diamo un'occhiata al codice un po' piu' su nel disassembly, cos'e' quel CreateFileA?? :) :00402853 803B00 cmp byte ptr [ebx], 00 :00402856 0F84F4000000 je 00402950 :0040285C 53 push ebx :0040285D 68D0524000 push 004052D0 :00402862 E8F6F9FFFF call 0040225D :00402867 6A00 push 00000000 :00402869 6880000000 push 00000080 :0040286E 6A03 push 00000003 * Reference To: KERNEL32.CreateFileA, Ord:0039h | :00402870 8B1D44734000 mov ebx, dword ptr [00407344] Molto interessante...qui abbiamo proprio il check che ci serviva...il check della command line! il byte ptr [ebx] contiene 00 se non si e' scelta command line, ma proviamo a mettere un bpx sul check (a 402853) e ritorniamo al prompt del DOS, quindi scriviamo Notepad FILE_INESISTENTE... sice poppera' su 402853. Adesso provate a dare "D EBX"...Vi troverete FILE_INESISTENTE pari pari a come lo avete scritto...esatto! ecco il buffer della command line! :) ma un attimo...non e' quello il buffer che dovremo utilizzare...notate quella call successiva? eccola: :0040285C 53 push ebx :0040285D 68D0524000 push 004052D0 :00402862 E8F6F9FFFF call 0040225D Se vi tracciate dentro, noterete che la call e' paragonabile ad un lstrcpy, in altre parole COPIA il nome del file (in d,[ebx]) nel buffer a 4052d0: se nella command line, come nel nostro caso, avere inserito qualcosa di simile a FILE_INESISTENTE, dopo la call noterete che in 4052d0 e' presente questo: FILE_INESISTENTE.txt. Lo scopo quindi e' chiaro: il buffer usato per il file da aprire e' 4052d0 (come potete riscontrare anche dal fatto che viene pushato come parametro per il CreateFileA successivo). Questo quindi dovra' essere il buffer in cui andremo a scrivere il nome del nostro file. Ma c'erano due presupposti, ricordate ? Dobbiamo controllare che il controllo edit sia stato gia' creato a questo punto. Da SoftICE, arrivati a 402853, digitate HWND NOTEPAD (se il nome del file che state lanciando e' notepad.exe, altrimenti controllate i tasks attivi con il comando TASK); noterete che il secondo class name e' un controllo di tipo EDIT...quindi e' gia' stato creato, e anche il primo presupposto viene rispettato! Allora....tutto cio' che ci serve e' trovare un punto per la deviazione a .RSRC. Quel JE a 402856 fa proprio al nostro caso. *** NOTA *** I punti di ritorno sono diversi: * se non c'e' il file .INI (o c'e' ma presenta errori) e l'utente NON ha scelto files, torneremo a 402950 (dove salterebbe quel je normalmente). * se il .INI c'e' ma l'utente ha scelto una command line, torneremo a 40285c con la call di copia tra i buffers e di aggiunta dell'estensione. * se il .INI c'e' e l'utente ha lasciato una cmd line vuota, copieremo direttamente il nome dell'ultimo file aperto dal file .INI al buffer a 4052d0, e torneremo a 402867 con la prima push del CreateFileA, che si riferisce a tale buffer. ***FINE NOTA*** Il nostro codice cominciera' a 40BEED (@88edh), e quindi la deviazione sara' questa (@1c56h): era :00402856 0F84F4000000 je 00402950 e diventa :00402856 E992960000 jmp 0040BEED :0040285B 90 nop Ora diamo un'occhiata al nostro codice: Poiche' potrebbe risultare un po' difficile comprenderne la struttura mediante i soli offsets al posto di piu' comprensibili etichette, ho deciso di utilizzare le labels con a fianco, tra parentesi, l'offset a cui corrispondono nel file .exe: Inizio (@88ed): pushad ; salva tutti i registri push 20 ; # di caratteri da prendere dalla stringa push 63f8c4 ; buffer di ricezione push 3c ; =60 dec, cioe' l'ID di "c:\\windows\\hnotepad.ini" push 400000 ; handle del modulo con la stringtable call LoadStringA push 0 ; pusha un dummy parameter sulla stack, lo sovrascriveremo con l'handle del file se ; questo viene trovato push dword ptr [406000] ; salva l'hWnd. Lo trovate sempre qui, basta fare una ricerca tra i ; dword pointers della sezione .IDATA; non possiamo usare il vecchio ; [ebp+8] che utilizziamo in precedenza, poiche' tale passaggio viene ; effettuato dopo. ; ORA DOBBIAMO APRIRE IL FILE...USEREMO _lopen: ; ; HFILE _lopen( ; ; LPCSTR lpPathName, // pointer to name of file to open ; int iReadWrite // file access mode ; ); push 63f8c4 ; "c:\windows\hnotepad.ini" call _lopen cmp eax, -1 ; c'e' stato un errore nell'apertura? jnz FILE_TROVATO (@8924h) pop eax ; scarica il dummy param pop eax ; scarica l'hWnd popad ; ripristina tutti i registri jmp ERRORE_NEL_FILE (@8a08h) FILE_TROVATO (@8924h): mov [esp+4], eax ; file handle al posto del dummy param ; ORA DOBBIAMO LEGGERE I DATI DAL FILE....USEREMO _lread: ; ; UINT _lread( ; ; HFILE hFile, // handle to file ; LPVOID lpBuffer, // pointer to buffer for read data ; UINT uBytes // length, in bytes, of data buffer ; ); push ff; numero di bytes da leggere; ATTENZIONE - USATE GLI OPCODES 68FF000000, altrimenti ; HIEW inserira' 6AFF, che verra' interpretato da Win98 (non dal 95) come PUSH ; FFFFFFFF, non PUSH 000000FF push 41096f ; buffer "RingZ3r0's Hnotepad.....", ripristinato in CHIUDI (@89E2h) push eax ; file handle call _lread mov edi, 41096f ; cio' che abbiamo letto dal file mov ecx, eax ; lunghezza effettiva del file in ecx mov al, 3d ; '=' repnz scasb ; cerca AL ('=') nel buffer a cui punta EDI per una lunghezza di ECX bytes jnz ERRORE (@895Ah) ; se il '=' non e' stato trovato ; SE IL '=' e' stato trovato, EDI punta al byte SUCCESSIVO al '='. cmp byte ptr [edi], 53 ; 'S' jz AUTOWRAP (@8967h) cmp byte ptr [edi], 73 ; 's' jz AUTOWRAP (@8967h) cmp byte ptr [edi], 4E ; 'N' jz NEXTCHECK (@897Bh) cmp byte ptr [edi], 6E ; 'n' jz NEXTCHECK (@897Bh) ERRORE (@895Ah): pop eax ; scarica hWnd mov dword ptr [63f8c4], 40c008 ; questo dword ptr sara' la locazione contenente il VA ; variabile a cui salteremo con la nostra procedura di ; ritorno "universale", ovvero CHIUDI (@89E2h) ; 40c008 e' il VA per ERRORE_NEL_FILE (@8a08h) jmp CHIUDI (@89E2h) ; = jmps in HIEW AUTOWRAP (@8967h) pop eax ; ripristina hWnd.... push eax; ...e lo ri-salva push ecx; salva i bytes rimanenti per lo scasb del successivo check, poiche' PostMessageA ; modifica il registro ECX ; ORA INVIEREMO IL MESSAGGIO RELATIVO AL WRAPPING, USANDO POSTMESSAGEA: ; ; BOOL PostMessage( ; ; HWND hWnd, // handle of destination window ; UINT Msg, // message to post ; WPARAM wParam, // first message parameter ; LPARAM lParam // second message parameter ; ); ; ; OVVIAMENTE VOGLIAMO SIMULARE UN CLICK SU UN MENU ITEM: IL MESSAGGIO SARA' QUINDI 111h ; (WM_COMMAND), E wParam SARA' 1Bh (=27 DEC, OVVERO LA MENU ID DI "Modifica/A capo automatico") push 0 ; lParam push 1b ; wParam push 111; = WM_COMMAND push eax ; l'hWnd call PostMessageA pop ecx ; ripristina i bytes rimanenti per lo scasb del successivo check NEXTCHECK (@897Bh): cmp byte ptr [ebx], 0 ; check se CMDLINE==NULL jz CONTINUA (@898Dh) pop eax ; scarca hWnd mov dword ptr [63f8c4], 40285c ; altrimenti VA di ritorno = 40285c, vedi "*** NOTA ***" jmp CHIUDI (@89E2h) ; = JMPS in HIEW CONTINUA (898Dh): mov al, 3d ; '=' repnz scasb ; cerca AL ('=') nel buffer a cui punta EDI per una lunghezza di ECX bytes ; ecx ovviamente e' stato ridotto dal precedente repnz scasb del # di bytes ; che distano tra l'inizio del file e il primo '=' jnz ERRORE (@895Ah) ; se il '=' non e' stato trovato ; ADESSO VERIFICHIAMO SE LA LETTERA IMMEDIATAMENTE DOPO L'UGUALE E' COMPRESA TRA 'a' e 'z' o ; tra 'A' e 'Z' ; i valori ascii sono : 'a' = 61 , 'z' = 7A , 'A' = 41 e 'Z' = 5A cmp byte ptr [edi], 61 ; 'a' jge 2_MINUSCOLA (@899Ah) jmp MAIUSC (@899Fh) ; JMPS in HIEW 2_MINUSCOLA (899Ah): cmp byte ptr [edi], 7a; 'z' jle OK (@89B8h) MAIUSC (@899Ah): cmp byte ptr [edi], 41 ; 'A' jge 2_MAIUSCOLA (@89A6h) jmp INVALID (@89AB); = JMPS in HIEW 2_MAIUSCOLA (89A6h): cmp byte ptr [edi], 5a ; 'Z' jle OK (@89B8h) INVALID (@89ABh): pop eax ; scarica l'hWnd mov dword ptr [63f8c4], 402950 ; VA di ritorno, vedi "*** NOTA ***" jmp CHIUDI (@89E2h) ; = JMPS in HIEW OK (@89B8h): push edi ; salva posizione iniziale della stringa di definzione del file da aprire CERCA_CR_O_SPAZIO (@89B9): inc edi cmp byte ptr [edi], 20 ; ' ' jz TROVATO (@89C7h) cmp byte ptr [edi], 0D ; = Carriage Return jz TROVATO (@89C7h) dec ecx ; ecx contiene i bytes tra il secondo '=' e EOF jnz CERCA_CR_O_SPAZIO (@89B9) TROVATO (@89C7): mov byte ptr [edi], 0 ; marca in memoria la fine della stringa che definisce il file pop edi ; ripristina posizione inziale della stringa (che ora termina con uno 0 byte) push edi push 4052d0 call lstrcpyA ; copia EDI (inizio nomefile - zero byte) in 4052d0 (buffer per file da aprire) mov dword ptr [63f8c4], 402867 ; per il jmp di ritorno, vedi "*** NOTA ***" pop eax ; scarica hWnd CHIUDI (@89E2h): pop eax ; file handle in eax ; ORA DOBBIAMO CHIUDERE HNOTEPAD.INI; USEREMO _lclose: ; ; HFILE _lclose( ; ; HFILE hFile // handle to file to close ; ; ); push eax ; handle del file call _lclose push 100 --\ push 41096f | push 34 ; = 52 DEC | --> LoadStringA di ripristino buffer "RingZ3r0's Hnotepad....." push 400000 | call LoadStringA --/ popad jmp dword ptr [63f8c4] ; il nostro jump variabile di uscita ERRORE_NEL_FILE (@8A08h): cmp byte ptr [ebx], 0 ; il check per la CMDLINE, presente anche in .TEXT *** jnz 40285c ; se CMDLINE!=NULL, OPCODES: 0F854B68FFFF *** jmp 402950 ; se CMDLINE==NULL, OPCODES: E93A69FFFF NOP NOP ; li ho messi a rappresentare la fine di questo spezzone di codice NOP Fine.....ora provate ad editare un file C:\windows\hnotepad.ini, inseritevi due uguali, e dopo il primo scrivete 'S', mentre dopo il secondo il nome di un file sul vostro HD. Notepad usera' queste impostazioni automaticamente all'avvio. Adesso, pero', abbiamo risolto solo parte del problema. Analizziamo ora la parte relativa alla SCRITTURA automatica del file .INI ad ogni uscita dal programma: * Arrivati all'uscita, il programma dovra' creare il file "C:\windows\hnotepad.ini"; se il file esiste, lo tronca a zero e lo crea da capo; se la creazione fallisce, uscire come se niente fosse. * Se invece il file viene creato, caricare in memoria lo "scheletro" del file .INI, e ritrovare le informazioni necessarie alla scrittura dei dati in esso, ovvero il nome dell'ultimo file e lo stato del WordWrapping al momento dell'uscita. * Scrivere in memoria tutti i dati relativi al wrapping e all'ultimo file, aggiungendoli al punto giusto allo scheletro gia' presente in memoria. * Scrivere nel file lo scheletro completo dei nuovi dati * Uscire dal programma Per fare tutto cio', chiaramente, abbiamo bisogno di 2 cose: 1) sapere DOVE trovare il nome dell'ultimo file aperto prima di chiudere. 2) sapere DOVE possiamo verificare lo stato attuale del WordWrapping. Bene...per la prima domanda siamo fortunati: il nome dell'ultimo file aperto si trovera' sempre nello stesso buffer, anche all'uscita: 4052d0. Per la seconda, invece, abbiamo bisogno di un po' di tracing. Ricordate la call principale della window procedure ? quella che esaminava i messaggi inviati alla window ? diamole di nuovo un'occhiata: * Referenced by a CALL at Address: |:00401E58 | :0040116C 55 push ebp :0040116D 8BEC mov ebp, esp :0040116F 81EC04010000 sub esp, 00000104 :00401175 33C0 xor eax, eax :00401177 56 push esi :00401178 57 push edi :00401179 BE38614000 mov esi, 00406138 :0040117E 8DBDFCFEFFFF lea edi, dword ptr [ebp+FFFFFEFC] :00401184 B940000000 mov ecx, 00000040 :00401189 A4 movsb :0040118A 8DBDFDFEFFFF lea edi, dword ptr [ebp+FFFFFEFD] :00401190 F3 repz :00401191 AB stosd :00401192 66AB stosw :00401194 AA stosb :00401195 0FB7750C movzx esi, word ptr [ebp+0C] ; <-- L'ID VALUE DEL MENU SCELTO :00401199 83FE20 cmp esi, 00000020 ; COMPARA ESI E 20h :0040119C 8BC6 mov eax, esi :0040119E 7F1A jg 004011BA ; SE ESI > 20h, SALTA :004011A0 0F84C1030000 je 00401567 :004011A6 48 dec eax :004011A7 83F81B cmp eax, 0000001B :004011AA 7736 ja 004011E2 :004011AC 0FB68838174000 movzx ecx, byte ptr [eax+00401738] :004011B3 FF248DF8164000 jmp dword ptr [4*ecx+004016F8] ; ALTRIMENTI ELABORA DI CONSEGUENZA Bene, intuibilmente, per tracciare le operazioni che il codice fa quando selezioniamo il WordWrapping, la scelta migliore e', in sice, "BPX 401199 if esi==1b". Arriveremo al jmp variabile a 11b3 e questi, a sua volta, ci indirizzera' a 401491: :00401491 833D1860400001 cmp dword ptr [00406018], 00000001 ; [406018]=0 se wrap=OFF ; [406018]=1 se wrap=ON :00401498 1BC0 sbb eax, eax :0040149A F7D8 neg eax :0040149C 50 push eax :0040149D E8EC1E0000 call 0040338E :004014A2 85C0 test eax, eax :004014A4 7415 je 004014BB :004014A6 833D1860400001 cmp dword ptr [00406018], 00000001 :004014AD 1BC0 sbb eax, eax :004014AF F7D8 neg eax :004014B1 A318604000 mov dword ptr [00406018], eax ; AGGIORNA IL PTR CON LA NUOVA ; POSIZIONE DEL WRAPPING Possiamo dedurre facilmente da cio' che il dword ptr [406018] non e' nient'altro che una flag di status del WordWrapping. Se e' 0, il wrapping e' OFF, e questa procedura lo ATTIVA, settando il dword ptr [406018] a 1. Se e' 1, il wrapping e' ON, e la procedura fa l'esatto contrario. All'uscita, questo ptr e' mantenuto, e ci indica lo stato attuale del wordwrapping, quindi ci bastera' controllare questo e comportarci di conseguenza. Intuibilmente, il punto da cui saltare deve essere quando il programma manda WM_DESTROY, cioe' si e' ad un passo dall'uscita dal processo. Per rintracciare la zona esatta, ci serviremo di un sistema molto semplice: l'API RegisterClass. nel Disasm, cercate "registerclass" e vi ritroverete qui: :00402B19 C745F820604000 mov [ebp-08], 00406020 :00402B20 C745D8AD1A4000 mov [ebp-28], 00401AAD :00402B27 C745F006000000 mov [ebp-10], 00000006 :00402B2E C745D400100000 mov [ebp-2C], 00001000 :00402B35 50 push eax :00402B36 897DDC mov dword ptr [ebp-24], edi :00402B39 897DE0 mov dword ptr [ebp-20], edi * Reference To: USER32.RegisterClassExA, Ord:0196h | :00402B3C FF15CC734000 Call dword ptr [004073CC] Molto semplicemente, date un'occhiata a quelle locazioni che vengono inserite come membri in ebp-xy, e capirete ben presto che l'inizio della window procedure e' a 401aad: :00401AAD 55 push ebp :00401AAE 8BEC mov ebp, esp :00401AB0 56 push esi :00401AB1 57 push edi :00401AB2 8B750C mov esi, dword ptr [ebp+0C] :00401AB5 83FE05 cmp esi, 00000005 :00401AB8 7714 ja 00401ACE :00401ABA 0F8406010000 je 00401BC6 :00401AC0 83FE02 cmp esi, 00000002 :00401AC3 0F84F0000000 je 00401BB9 con un bpx a 401ab5, noterete che ESI contiene il messaggio che viene analizzato correntemente: quello che ci interessa, e' ovviamente WM_DESTROY (=02h). Bene, come vedete a 401ac3 c'e' un jump, che verra' preso solo se il messaggio corrente e' WM_DESTROY. Ci bastera' sostituire quel VA con l'inizio del nostro codice e avremo risolto il problema: era :00401AC3 0F84F0000000 je 00401BB9 e diventa :00401AC3 0F8450A50000 je 0040C019 Le nuove API che ci serviranno sono solo queste due: _lcreat: call d,[406370] _lwrite: call d,[4072f8] ExitProcess: call d,[407354] Il nostro codice, l'avrete capito, comincia al VA 40c019 (@8A19h): Inizio (@8A19h): pushad ; salva tutti i registri push 20 ; # di chars da prendere push 63f8c4 ; buffer di ricezione push 3c ; = 60 dec, "C:\\windows\\hnotepad.ini" push 400000 ; handle del modulo con la stringtable call LoadStringA ; ORA DOBBIAMO CREARE IL FILE; USIAMO _lcreat ; ; HFILE _lcreat( ; ; LPCSTR lpPathName,// pointer to name of file to open ; int iAttribute // file attribute ; ); ; ; L'ATTRIBUTO CHE USEREMO SARA' 0, CIOE' "Normal" (IL FILE PUO' ESSERE SCRITTO E LETTO SENZA ; RESTRIZIONI. ; _lcreat TRONCA AUTOMATICAMENTE A 0 LA DIMENSIONE DEL FILE SE QUESTO ESISTE GIA', O, IN CASO ; CONTRARIO, LO CREA. push 0 ; attributo "Normal" push 63f8c4 ; "C:\windows\hnotepad.ini" call _lcreat cmp eax, -1 ; se c'e' stato un errore nella creazione jnz CREATO_OK (@8A46h) popad ; ripristina tutti i registri *** jmp 401bb9 ; dove avrebbe continuato se non avessimo inserito la nostra deviazione ; OPCODES : E9735BFFFF CREATO_OK (@8A46h): push eax ; salva file handle push 100 ; # di chars da prendere push 41096f ; buffer di ricezione push 3d ; = 61 DEC, ovvero l'ID della stringa "****\nFILE GENERATO AUTOMATICAMENTE...." push 400000 ; handle del modulo con la stringtable call LoadStringA cmp dword ptr [406018], 0 ; e' zero se WordWrapping = OFF jz CHECK_ULTIMOFILE (@8A6E) mov byte ptr [4109f4], 53 ; sostituisce la "N" di default dopo "AutoWrap=" con "S" (053h) CHECK_ULTIMOFILE (@8A6E): cmp byte ptr [4052d0], 0 ; e' 0 se non c'e' un ultimo file aperto jnz ULTIMOFILE_PRESENTE (@8A80h) mov byte ptr [410a02], 0 ; il SECONDO BYTE DOPO IL SECONDO '='...cosi' lasciamo anche uno ; spazio immediatamente dopo il secondo '=', che ci serve nel ; controllo inziale, in apertura programma jmp SCRIVI_DATI (@8A90h) ; JMPS in HIEW ULTIMOFILE_PRESENTE (@8A6E): push 4052d0 ; nome dell'ultimo file push 400a01 ; buffer che inizia subito dopo il secondo '=' nello scheletro gia' in memoria call lstrcpyA SCRIVI_DATI (@8A90h): pop eax ; ripristina file handle in eax... push eax; ...e lo ri-salva ; ORA SCRIVEREMO LO SCHELETRO NEL FILE: USEREMO _lwrite, MA C'E' UN PICCOLO PROBLEMA. SUL MIO ; PC, PER CAUSE ANCORA SCONOSCIUTE, NON POSSO SALVARE PIU' DI 7Fh BYTES ALLA VOLTA, E POICHE' IL ; TESTO CHE DOBBIAMO SALVARE E' LUNGO 92h BYTES, USEREMO _lwrite 2 VOLTE, LA PRIMA CON I PRIMI ; 7Fh BYTES, LA SECONDA CON I RESTANTI 13h. ; ; UINT _lwrite( ; ; HFILE hFile, // handle to file ; LPCSTR lpBuffer, // pointer to buffer for data to be written ; UINT uBytes // number of bytes to write ; ); push 7f ; i primi 7fh bytes dello scheletro push 41096f ; inizio del buffer con lo scheletro push eax ; file handle call _lwrite pop eax ; ripristina file handle in eax... push eax; ...e lo ri-salva push 13 ; i restanti 13h bytes dello scheletro push 4109ee ; locazione dove cominciano i restanti 13 bytes dello scheletro push eax ; file handle call _lwrite ; ORA NON CI RESTA CHE CONTROLLARE LA LUNGHEZZA DEL NOME DEL FILE, PUSHARE QUEI BYTES E ; APPENDERE TALE NOME AL NOSTRO .INI xor eax, eax ; azzera contatore per la lunghezza del nome del file mov edi, 410a01 ; dove c'e' la prima lettera del nome file CHECK_LUNGHEZZA_NOME_ULTIMOFILE (@8AB7h): cmp byte ptr [edi], 0 jz FINECHECK (@8AC0h) inc edi inc eax jmp CHECK_LUNGHEZZA_NOME_ULTIMOFILE (@8AB7h); JMPS in HIEW FINECHECK (@8AC0h): mov byte ptr [edi], 0d ; appende un CR (carriage return) alla fine della stringa inc eax pop edi ; file handle in edi push eax ; # di bytes da scrivere push 410a01 ; dove comincia il nome del file push edi ; file handle call _lwrite push edi ; file handle call _lclose popad ; ripristina tutti i registri call ExitProcess ; esce dal programma NOP NOP ; fine del codice di questa sezione NOP Ci sarebbe qualcosa da spiegare qui. Come mai ho inserito una call diretta a ExitProcess senza tornare a .TEXT, e conseguentemente senza mandare il PostQuitMessage per un'uscita piu' "pulita" ? Il problema e' molto semplice...se provate a uscire sotto determinate condizioni (nel nostro caso in un qualsiasi altro modo che non scegliendo File/Esci) otterrete un fault. Il motivo di cio' e' ancora oscuro per me, ma gradirei se qualcuno mi fornisse qualche info in piu'. Tuttavia, nel nostro caso l'ExitProcess non causa problemi, ma e' soltanto un modo piu' "rapido" di terminare il processo. Elaborare WM_CLOSE invece di WM_DESTROY non da' risultati molto migliori. Ogni volta che uscirete e riaprirete, l'ultimo file e il WordWrapping verranno ripristinati. ______________________________________________________________ FASE 5 : RIMOZIONE DEL LIMITE NELLA GRANDEZZA DEI FILES APERTI ______________________________________________________________ Eccoci quindi arrivati alla quinta ed ultima fase della creazione di Hnotepad. La rimozione del fastidioso limite nella grandezza dei files aperti in Notepad. Cio' che faremo e' molto semplice: cambieremo il controllo di tipo Edit (che notepad usa) in un controllo di tipo RichEdit, tipo quello usato da WordPad. Per altre informazioni su questo argomento, vi rimando alla API reference. La cosa si presenterebbe molto semplice, se non fosse per un solo piccolo dettaglio: per utilizzare il controllo RichEdit, abbiamo bisogno della libreria Riched32.dll. E per caricare la libreria Riched32.dll abbiamo bisogno della funzione LoadLibraryA. Purtroppo per noi, pero', notepad non include questa funzione tra quelle importate (potete controllare con W32Dasm, oppure con hiew dando un'occhiata alla sezione .IDATA)...cio' non e' un grosso problema, potremmo importare GetProcAddress al posto di una funzione qualsiasi (GetTimeFormatA ad esempio) e utilizzare tale API per ritrovare l'indirizzo di LoadLibraryA dinamicamente ogni volta, al runtime. Tuttavia utilizzeremo qui un sistema che non tocca minimamente la import table, e cioe' un codice scritto da qualcuno (il programmatore di un virus, a quanto mi diceva GEnius) che esegue una operazione pari a quella di GetProcAddress: la scansione della memoria inerente la dll necessaria (nel nostro caso Kernel32.dll) e il recupero dell'indirizzo della funzione necessaria (nel nostro caso LoadLibraryA). Tuttavia non mi soffermero' sulla descrizione dei dettagli della modalita' di funzionamento del codice, il cui codice sorgente lo potete trovare accluso alla versione finale di Hnotepad.exe, ma mi limitero' a darvi la versione gia' compilata, che potete semplicemente COPIARE e INCOLLARE in qualsiasi punto del vostro codice, per poi chiamare (con una call) l'indirizzo inziale di tale codice; funzionera' sempre e comunque poiche' gli indirizzi sono tutti relativi a questo breve pezzo di codice. La funzione accetta due parametri, ovvero l'HANDLE della libreria che include la funzione (kernel32.dll) e il NOME della funzione ricercata (LoadLibraryA). Ma intanto ecco il codice in formato HEX: `� Copiate dal byte 60 ("`") al byte 00 prima del primo asterisco nella lunga serie finale. Potete quindi usare un programma come UltraEdit32 o Hex Workshop per incollare il codice al vostro programma .exe. Ma vediamo come funzionera' questo sistema nel NOSTRO codice: eccoci di fronte al piccolo problema di cui parlavo prima: avremo bisogno di alcune variabili (3 per la precisione), tuttavia non possiamo sfruttare la stringtable per i nostri scopi: infatti, una semplice aggiunta distruggerebbe tutto il nostro lavoro. Come fare quindi ? Semplice: scriveremo le nostre variabili DIRETTAMENTE nel file fisico, in un punto (alla fine) che non ci serve. Ma procediamo per gradi. Innanzitutto dobbiamo caricare la libreria riched32.dll per poi, al momento giusto, cambiare "EDIT" in "RICHEDIT" e ottenere un edit control che supporta i file grandi. Quindi, cominciamo a tracciare dall'inizio: :00401000 55 push ebp :00401001 8BEC mov ebp, esp :00401003 83EC44 sub esp, 00000044 :00401006 56 push esi * Reference To: KERNEL32.GetCommandLineA, Ord:00BCh | :00401007 FF1548734000 Call dword ptr [00407348] :0040100D 8BF0 mov esi, eax :0040100F 8A00 mov al, byte ptr [eax] :00401011 3C22 cmp al, 22 :00401013 7513 jne 00401028 La nostra deviazione sara' alla riga appena dopo la call a GetCommandLineA, ovvero il "mov esi, eax" a 100d. Il nostro codice, inclusi i NOPs precedenti, comincera' a 40c0e3 (@8AE3). Quindi ecco il cambiamento che effettueremo: era :0040100D 8BF0 mov esi, eax :0040100F 8A00 mov al, byte ptr [eax] :00401011 3C22 cmp al, 22 :00401013 7513 jne 00401028 e diventa :0040100D E9D1B00000 jmp 0040C0E3 :00401012 90 nop :00401013 7513 jne 00401028 Dovremo pertanto ricordarci di ripristinare le 3 istruzioni che abbiamo sovrascritto prima di ritornare con il jne a 401013. Ora abbiamo il problema delle variabili. Lasciamo un po' di spazio libero nella sezione .RSRC, e andiamo a scrivervi le variabili che ci servono. Contate che dovremo inserire il codice di scansione e il nostro codice, quindi dato un buon margine, cambiate in modo HEX in HIEW e posizionatevi all'offset 8BA0h, alche' editate gli ascii e scrivete : LoadLibraryA Questa sara' la nostra prima variabile. Poi, posizionatevi all'offset 8BB0h e scrivete : Riched32.dll Ecco la seconda variabile. Avremo bisogno anche di Kernel32.dll, ma come potete immaginare questa "variabile" e' gia' presente nel nostro codice: una ricerca da HIEW ci fara' arrivare alla sezione .IDATA, dove tra i nomi delle librerie importate troverete anche Kernel32.dll. Il VA e' 4076E0. Tracciando un rapido schema riassuntivo, quindi, ecco i VA che ci interessano: LoadLibraryA = 40C1A0 (@8BA0h) Riched32.dll = 40C1B0 (@8BB0h) KERNEL32.DLL = 4076E0 (@48E0h) Inoltre, useremo una nuova API, che e' GetModuleHandleA = call d,[40735c] essa ci fornira' l'HANDLE della libreria KERNEL32.DLL, la quale scansioneremo per la ricerca della nostra API LoadLibraryA. Ed ecco il nostro codice di caricamento della libreria: Inizio (@8AE3): pushad ; salva tutti i registri push 4076e0 ; "KERNEL32.DLL" call GetModuleHandleA ; rileva l'handle della libreria Kernel32.dll push 40c1a0 ; "LoadLibraryA" push eax ; handle di KERNEL32.DLL call CODICE_DI_SCANSIONE (@8B0D); LA CALL AL CODICE DI SCANSIONE : OPCODES E813000000 ; ATTENZIONE: IL CODICE DI SCANSIONE DA' COME RISULTATO L'INDIRIZZO DELL'API DESIDERATA, MA ; QUESTO NON VIENE RITORNATO IN EAX, BENSI' IN *ECX* push 40c1b0 ; "Riched32.dll" call ecx ; CHIAMA IL LoadLibraryA! popad ; ripristina tutti i registri mov esi, eax --\ mov al, [eax] |____ Ripristiniamo qui il codice sostituito in .TEXT con il JMP cmp al, 22 | OPCODES PER IL JMP : E9064FFFFF jmp 401013 --/ CODICE_DI_SCANSIONE (@8B0D): ; QUI DOVETE SEMPLICEMENTE INCOLLARE IL CODICE CHE HO FORNITO PRIMA, PER UNA DESCRIZIONE ; DETTAGLIATA DELLE SUE FUNZIONI VI RIMANDO AI COMMENTI, DA PARTE DELL'AUTORE, NEL SORGENTE. Benissimo, adesso il nostro codice e' pronto. Resta solo un problema...dobbiamo creare il controllo in modo RichEdit, non Edit...altrimenti tutte queste modifiche sono inutili. Per creare il controllo, dobbiamo trovare la call a CreateWindowExA (che notepad usa come variante di CreateWindow) che pushi, tra i parametri precedenti, anche una stringa "Edit", e cambiare quella stringa "Edit" in "RichEdit". Innanzitutto, CREIAMO questa variabile. Sempre in modo HEX, da HIEW editate gli ascii all'offset 8BC0, e scrivete RichEdit Il VA per questa nuova variabile e' quindi 40C1C0. Per trovare la call che ci interessa, in sice BPX CreateWindowExA e fate partire notepad. Arriverete a questo punto: :00402785 53 push ebx :00402786 A100604000 mov eax, dword ptr [00406000] :0040278B 56 push esi :0040278C 6A0F push 0000000F :0040278E 50 push eax :0040278F 6890010000 push 00000190 :00402794 6858020000 push 00000258 :00402799 53 push ebx :0040279A 53 push ebx :0040279B 6804013050 push 50300104 :004027A0 688C614000 push 0040618C * Possible StringData Ref from Data Obj ->"Edit" | :004027A5 6890614000 push 00406190 :004027AA 6800020000 push 00000200 :004027AF FFD7 call edi ; CHIAMA CREATEWINDOWEXA Molto bene...e' scontato quale sara' il cambiamento: era :004027A5 6890614000 push 00406190 ; "Edit" e diventa :004027A5 68C0C14000 push 0040C1C0 ; "RichEdit" Adesso rimangono altri DUE dettagli finali, e poi potremo finalmente dichiarare concluso questo lunghissimo tutorial :) 1) Se provate ad aprire i file grossi, vi uscira' ancora la message box che vi chiede se avviare WordPad: bpx MessageBoxA, e risalirete al check della grandezza, che e' :00402E8B 81FEFFFF0000 cmp esi, 0000FFFF :00402E91 0F8FEC010000 jg 00403083 Semplicemente NOPPATE quel jg e il problema non si ripresentera'. 2) Se aprite un file grosso e provate a inserire (o avete inserito) il WordWrapping, un fastidioso nag che dice qualcosa tipo "File troppo grande per eseguire A capo automatico" vi poppera', per eliminarlo sempre BPX MessageBoxA e risalirete qui: :004014A2 85C0 test eax, eax :004014A4 7415 je 004014BB ; EVIL JUMP :004014A6 833D1860400001 cmp dword ptr [00406018], 00000001 Evitando quel jump, eviteremo anche il NAG. Percio' NOPpatelo. E con questo abbiamo FINITO! :) _________________________ PARTE 4 : Bugs conosciuti _________________________ Innanzitutto, devo premettere che sto gia' lavorandoci su per cercare di risolverli, quindi con le prossime versioni di Hnotepad spero di non ritrovarli piu'...comunque, ora come ora i bugs che conosco sono due: * QUANDO SI CHIUDE UN FILE, SI OTTIENE SEMPRE IL DIALOG "IL FILE E' CAMBIATO, SALVARLO?" ANCHE SE NON SI E' MODIFICATO IL TESTO DI UNA VIRGOLA. IL PIU' FASTIDIOSO, DECISAMENTE, E CREDO SIA LEGATO AL CONTROLLO RICHEDIT. * A VOLTE, L'APERTURA DI UN FILE GROSSO QUANDO SI E' GIA' *DENTRO* HNOTEPAD, PORTA ALL'USCITA DEL VECCHIO NAG "APRI WORDPAD?", PUR LAVORANDO CON IL RICHEDIT E NON CON L'EDIT...CREDO CHE AL 99% SI TRATTI DI UN SECONDO CHECK SULLA LUNGHEZZA DEL FILE. CI STO LAVORANDO :) Se per caso vi capitasse di riscontrare qualche altro bug, per piacere comunicatemelo a neural_noise@hotmail.com . Se possibile indicatemi anche il sistema operativo sotto cui state facendo girare Hnotepad. ________________ PARTE 5 : Saluti ________________ Eccoci finalmente giunti al termine...questo tute e' dedicato a (nessun ordine particolare): GEnius, per avere contribuito enormemente con il suo supporto e le sue idee sul RichEdit, con il suo preziosissimo aiuto di beta tester, nonche' per avermi fornito il codice di scansione della memoria. Senza di lui tutto cio' non sarebbe stato possibile, quindi GRAZIE GENIO!! :D Kill3xx, per essere stato un paziente beta tester e un buon amico...ah, ottimo lavoro con il PeSentry killo! ;) d4eMoN (aka Patrizia ;P), per il suo aiuto e la sua disponibilita'...grazie MOSTRO :) {Suby}, per la sua amicizia e il suo aiuto come beta tester. Won't forget that man :) BrahzVooZ, per essere la persona piu' studiosa che conosca...TORNA AL CRACKING FRATELLO! :) Anub|s, per aver accolto con entusiasmo il progetto Hnotepad Insanity, per essere il web/botmaster piu' simpatico della storia :) +MaLaTTiA, per essere il moderatore della mailing list piu' inattiva dell'universo e perche' sara' cosi' buono da darmi qualche info sulla steganografia un giorno o l'altro...THX MALA! ;) Fravia+, Il grande, il miglior reverser attuale a mio modesto parere. +ORC, la leggenda, il mito. Un grande. Il PIU' grande :) Guybrush, Quequero, N6U5, Furb3t, ^courier, guiz, Tin, Carpathia, Zen, e tutti gli altri amici da #crack-it, #cracking4newbies e RingZ3r0. a presto, -NeuRaL_NoiSE 1999