Tutorial 21: Pipe
From UIC
Tutorial 21: Pipe
Contents |
| Tutorial 21: Pipe | |
|---|---|
| Author: | Iczelion |
| Email: | |
| Website: | Mirror |
| Date: | 10/02/2009 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | Formattazione Wiki: Antelox |
Introduzione
In questo tutorial, esploreremo il pipe, che cos'è e per che cosa possiamo usarlo. Per renderlo più interessante, inserirò nella tecnica come cambiare il colore del testo e del background di una casella di modifica.
Tools
Essay
Un pipe, traducendo banalmente dall'inglese, è un "tubo". In informatica identifica uno strumento che consente di scambiare dati fra due processi differenti, o all'interno dello stesso processo. È come un walkie-talkie: date all'altra parte un set e potrà usarlo per comunicare con voi.
Tipi di pipe
Ci sono due tipi di pipe: pipe anonimi e con nome (named). Il pipe anonimo è, chiaramente, anonimo: cioè è possibile usarlo senza conoscere il relativo nome (per esempio se è stato ereditato da un processo padre). Un named pipe è l'opposto: è necessario conoscere il relativo nome prima che di poterlo usare. I pipe si possono anche distinguere in unidirezionale o bidirezionale. In un pipe unidirezionale i dati possono entrare soltanto in un senso: da un' estremità all' altra; in un pipe bidirezionale, invece, i dati possono essere scambiati fra entrambe le estremità. Un pipe anonimo è sempre unidirezionale mentre un named pipe può essere unidirezionale o bidirezionale. I named pipe sono utilizzati solitamente in un ambiente di rete in cui un server può collegarsi a parecchi client.
In questo tutorial, esamineremo in certi dettagli il pipe anonimo. Lo scopo principale del pipe anonimo è di essere usato come comunicazione tra un processo padre ed i processi figli o tra processi figli.
Utilità dei pipe anonimi
Il pipe anonimo è molto utile quando si sta realizzando un'applicazione a console. Tuttavia, un'applicazione a console è un programma completamente 32-bit. Può usare tutte le funzione GUI, come gli altri programmi GUI.
Un'applicazione a console ha tre handle che può utilizzare per i relativi ingressi e uscite. Sono chiamati handle standard (stdin per l'input, stdout per l'output e stderr per l'output di "scorta", solitamente gli errori). Un'applicazione a console può richiamare questi tre handle standard chiamando la funzione GetStdHandle, specificando l' handle che si desidera ottenere. Un'applicazione GUI non ha necessariamente una console attaccata: chiamando GetStdHandle, questa fallirà. Se il programma necessita di una console DOS è sufficiente chiamare AllocConsole per crearne una nuova. Tuttavia non dimenticatevi di chiamare FreeConsole quando avete finito con la console.
Il pipe anonimo è utilizzato più frequentemente per riorientare l'input e/o l'uscita di un'applicazione figlia a console. Il processo genitore può essere una console o un'applicazione GUI ma il figlio deve essere un'applicazione a console affinchè questo funzioni. Com'è (ormai) noto, un'applicazione a console utilizza gli handle standard per i relativi ingressi ed uscite. Se desideriamo riorientare l'input e/o l'uscita di un'applicazione a console, possiamo sostituire l' handle con l' handle di un'estremità del pipe. L'applicazione non saprà che sta utilizzando l'handle di un'estremità del pipe, la userà come handle standard. Ciò è simile al concetto di polimorfismo, parlando a oggetti. E non è nemmeno necessario modificare il processo figlio in nessun modo.
Un' altra cosa che dovreste conoscere circa un'applicazione a console è da dove ottiene questi handle standard. Quando un'applicazione a console è creata il processo genitore ha due scelte: può creare una nuova console per il figlio o può lasciare che il figlio erediti la propria console. Affinchè il secondo metodo funzioni il processo genitore deve essere un'applicazione a console o, se è un'applicazione GUI, deve prima chiamare AllocConsole per allocare una sezione comandi.
Un po' di codice
Cominciamo il lavoro: per creare un pipe anonimo bisogna chiamare CreatePipe, che ha il seguente prototipo:
pWriteHandle:DWORD,
pPipeAttributes:DWORD,
nBufferSize:DWORD
- pReadHandle è un puntatore ad una variabile dword che riceverà l'handle dell'estremità di lettura del pipe.
- pWriteHandle è un puntatore ad una variabile dword che riceverà l'handle dell'estremità di scrittura del pipe.
- pPipeAttributes puntatore ad una struttura SECURITY_ATTRIBUTES che determina se gli handle restituiti per la lettura & scrittura sono ereditate dai processi figli.
- nBufferSize è la dimensione suggerita del buffer che il pipe riserverà per l'uso. Potete usare NULL per dire alla funzione di usare il formato di default.
Se la chiamata riesce, il valore di ritorno è diverso da zero. Se fallisse, il valore di ritorno è zero. Dopo che la chiamata è riuscita otterrete gli handle: uno dell'estremità di lettura del pipe e l'altro dell'estremità di scrittura.
Redirigere gli stream
Notate che il mio metodo differisce da quello del riferimento win32 api della Borland. Il metodo nel riferimento win32 api presuppone che il processo genitore sia un'applicazione a console ed il figlio può così ereditare gli handle standard. Per la maggior parte delle volte dovremo riorientare l'uscita dell'applicazione a console ad una di tipo GUI.
1) Creare un pipe anonimo con CreatePipe. Non dimenticate di settare il membro bInheritable della struttura SECURITY_ATTRIBUTES su TRUE in modo che le handles siano ereditabili.
2) Ora dobbiamo preparare i parametri che passeremo a CreateProcess poiché la useremo per caricare l'applicazione a console figlia. Una struttura importante è la struttura STARTUPINFO. Questa struttura determina la condizione iniziale della finestra principale del processo figlio quando compare per la prima volta. Questa struttura è vitale al nostro scopo: potete nascondere la finestra principale e passare un handle del pipe al processo a console figlio con essa. Di seguito ci sono i membri che dovete riempire:
- cb: la dimensione della struttura STARTUPINFO
- dwFlags: i flag che determinano quali membri della struttura sono validi e anche la condizione di visibilità/nascosta della finestra principale. Per il nostro scopo, dovrete usare una combinazione di STARTF_USESHOWWINDOW e di STARTF_USESTDHANDLES
- hStdOutput e hStdError: gli handle che desiderate che il processo figlio utilizzi come handles standard di output/error. Per il nostro scopo, passeremo l'handle di scrittura del pipe per l'uscita e l'errore standard al figlio. Così quando il figlio produce qualcosa di output/error standard, realmente passa l'Info tramite il pipe al processo genitore.
- wShowWindow governa la condizione di visibilità/nascosta della finestra principale. Per il nostro scopo, non desideriamo mostrare la finestra della console del figlio così mettiamo SW_HIDE in questo membro.
3) Chiamare CreateProcess per caricare l'applicazione figlia. Dopo la riuscita di CreateProcess il figlio è ancora disattivo: è caricato nella memoria ma non è in esecuzione immediatamente.
4) Chiudere l'handle di scrittura del pipe: poiché il processo genitore non ha bisogno di usare l'handle di scrittura del pipe ed il pipe non funzionerà se ci saranno più di un' estremità di scrittura, DOBBIAMO chiuderla prima della lettura dei dati dal pipe. Tuttavia non chiudete l'handle di scrittura prima della chiamata a CreateProcess, altrimenti il vostro pipe sarà rotto. Dovreste chiuderla subito dopo che CreateProcess è ritornata e prima di leggere i dati dall'estremità di lettura del pipe.
5) Ora potete leggere i dati dall' estremità di lettura del pipe con ReadFile. Usando ReadFile il processo figlio inizia ad essere eseguito, e quando scriverà qualcosa nell'handle standard per l'uscita (che è attualmente l'handle dell'estremità di scrittura del pipe), i dati saranno trasmessi tramite il pipe all'estremità di lettura. Dovete chiamare ripetutamente ReadFile fino a che non restituisca 0 quando le informazioni non saranno più dati da leggere. Potete fare qualsiasi cosa con i dati letti dal pipe. Nel nostro esempio, li ho messi in una text box.
6) Chiudere l' handle di lettura del pipe.
Esempio
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDR_MAINMENU equ 101 ; ID del menu principale
IDM_ASSEMBLE equ 40001
.data
ClassName db "PipeWinClass",0
AppName db "One-way Pipe Example",0 EditClass db "EDIT",0
CreatePipeError db "Error during pipe creation",0
CreateProcessError db "Error during process creation",0
CommandLine db "ml /c /coff /Cp test.asm",0
.data?
hInstance HINSTANCE ?
hwndEdit dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT, CW_USEDEFAULT,400,200,NULL,NULL, hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rect:RECT
LOCAL hRead:DWORD
LOCAL hWrite:DWORD
LOCAL startupinfo:STARTUPINFO
LOCAL pinfo:PROCESS_INFORMATION
LOCAL buffer[1024]:byte
LOCAL bytesRead:DWORD
LOCAL hdc:DWORD
LOCAL sat:SECURITY_ATTRIBUTES
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL
mov hwndEdit,eax
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetBkColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
.elseif uMsg==WM_SIZE
mov edx,lParam
mov ecx,edx
shr ecx,16
and edx,0ffffh
invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
.if eax==NULL
invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK
.else
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
.if eax==NULL
invoke MessageBox,hWnd,addr CreateProcessError,addr AppName,MB_ICONERROR+MB_OK
.else
invoke CloseHandle,hWrite
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
.endif
invoke CloseHandle,hRead
.endif
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret
.endif
xor eax,eax
ret
WndProc endp
end start
Analisi
L'esempio chiamerà ml.exe per assemblare un file chiamato test.asm e per riorientare l'uscita di ml.exe nell'area client della text box. Quando il programma è caricato registra il codice per la classe finestra e crea la finestra principale come di consueto. La prima cosa che fa durante la creazione della finestra principale è di creare una casella di testo che sarà usato per visualizzare l'uscita di ml.exe. Ora la parte interessante: cambieremo il colore di background e del testo della casella di modifica. Quando la casella di modifica sta per disegnare la sua zona client, trasmette il messaggio WM_CTLCOLOREDIT al relativo genitore. wParam contiene l'handle del device context che la casella di modifica userà per scrivere la relativa zona client. Possiamo usare questa occasione per modificare le caratteristiche dell'HDC.
invoke SetTextColor,wParam,Yellow
invoke SetTextColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
SetTextColor cambia il colore del testo in giallo. SetTextColor cambia il colore di background del testo a nero. Ed infine, otteniamo l' handle del brush nero che restituiamo a Window. Con il messaggio WM_CTLCOLOREDIT dovete restituire un handle ad un brush che Windows userà per disegnare il background della casella di testo. Nel nostro esempio desidero che il background sia nero quindi restituisco l'handle del brush nero a Windows. Ora, quando l'utente seleziona Assemble dal menu, viene creato un pipe anonimo.
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
Prima di chiamare CreatePipe dobbiamo riempire la struttura SECURITY_ATTRIBUTES: Si noti che possiamo usare NULL nel membro lpSecurityDescriptor se non ci preoccupiamo della sicurezza. Ed il membro bInheritHandle deve essere TRUE in modo che le handles del pipe siano ereditabili dal processo figlio.
Dopo questo chiamiamo CreatePipe che, se riuscita, riempirà le variabili hWrite e hRead rispettivamente con gli handle dell'estremità di lettura e scrittura del pipe.
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
Successivamente dobbiamo riempire la struttura STARTUPINFO. Chiamiamo GetStartupInfo per riempire la struttura STARTUPINFO con i valori di default del processo genitore. DOVETE riempire la struttura STARTUPINFO con questa chiamata se desiderate che il vostro codice possa lavorare sia sotto win9x che NT. Dopo che la chiamata a GetStartupInfo è ritornata potete modificare i membri importanti della struttura. Copiamo l'handle dell'estremità di scrittura del pipe in hStdOutput ed in hStdError poiché desideriamo che il processo fglio le usi anziché gli handle standard di default per output/error. Desideriamo anche nascondere la finestra della sezione comandi del processo figlio, quindi mettiamo il valore SW_HIDE nel membro wShowWidow. Ed infine dobbiamo indicare che i membri hStdOutput, hStdError e wShowWindow sono validi e deve essere fatto specificando i flags STARTF_USESHOWWINDOW e STARTF_USESTDHANDLES nel membro dwFlags.
Ora creiamo il processo figlio con la chiamata di CreateProcess. Si noti che il parametro bInheritHandles deve essere regolato su TRUE affinchè l' handle del pipe funzioni.
Dopo aver creato con successo il processo figlio, dobbiamo chiudere l'estremità di scrittura del pipe. Ricordatevi che abbiamo passato l'handle di scrittura al processo figlio tramite la struttura STARTUPINFO: se non chiudiamo l'handle di scrittura dalla nostra estremità, ci saranno due estremità di scrittura, e il pipe non funzionerà. Dobbiamo chiudere l'handle di scrittura dopo CreateProcess ma prima di leggere i dati dall'estremità del pipe.
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
Ora siamo pronti a leggere i dati dall'uscita standard del processo figlio. Rimarremo in un ciclo infinito fino a che non ci saranno più dati coerenti da leggere dal pipe. Chiamiamo RtlZeroMemory per riempire il buffer di zeri quindi chiamiamo ReadFile, passando l' handle di lettura del pipe al posto dell'handle di un file. Si noti che leggiamo soltanto un massimo di 1023 byte poiché vogliamo che i dati siano una stringa ASCIIZ che possiamo passare a una casella di testo.
Quando ReadFile ritorna con i dati nel buffer, riempiamo la casella di modifica con i dati. Tuttavia c'è un piccolo problema. Se usiamo SetWindowText per immettere i dati nella casella di modifica, i nuovi dati saranno scritti sopra i dati attuali! Desideriamo che i dati siano aggiunti alla conclusione dei dati esistenti.
Per raggiungere lo scopo prima spostiamo il cursore verso la fine del testo nella casella di modifica trasmettendo il messaggio EM_SETSEL con wParam == -1, poi aggiungiamo i dati a quel punto con il messaggio EM_REPLACESEL.
Quando ReadFile restituisce NULL, usciamo dal ciclo e chiudiamo l'handle di lettura.
Disclaimer
I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.
Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.