Zoom Icon

Tutorial 13: I Memory Mapped Files

From UIC

Tutorial 13: I Memory Mapped Files

Contents


Tutorial 13: I Memory Mapped Files
Author: Iczelion
Email: Traduttore: -NeuRaL_NoiSE
Website: Mirror
Date: 30/12/2008 (dd/mm/yyyy)
Level: Slightly hard
Language: Italian Flag Italian.gif
Comments: Formattazione Wiki: Antelox


Introduzione

Vi mostrero' cosa sono i memory mapped files e come usarli per i vostri scopi. Usare un memory mapped file e' abbastanza semplice, come vedrete in questo tutorial.


Tools

Esempio


Preliminari

Se esaminate l'esempio del tutorial precedente con molta attenzione, vi accorgerete che ha una limitazione abbastanza grave: che si fa se il file che volete leggere e' piu' grande del blocco di memoria allocato? O che facciamo se la stringa che vogliamo cercare e' tagliata a meta' alla fine del blocco di memoria? La risposta tradizionale alla prima domanda e' che si devono leggere i dati dal file ripetutamente, fino a che non si raggiunge la fine del file. La risposta alla seconda domanda e' che dovreste prepararvi alla particolare situazione della fine del blocco di memoria. Quest viene chiamato "problema di bonduary value". Presenta mal di testa per i programmatori e causa innumerevoli bugs. Sarebbe bello se potessimo allocare un blocco di memoria molto grande, abbastanza da contenere tutto il file, ma il nostro programma sarebbe riempito come un uovo dalle risorse. La salvezza ci arriva con il file mapping. Usando il file mapping, potete pensare all'intero file come gia' caricato in memoria, e potete pertanto usare un puntatore di memoria per leggere o scrivere dati sul file. E' davvero cosi' semplice. Non c'e' piu' bisogno di usare le funzioni API riguardanti la memoria e, separatamente, le funzioni API riguardanti l' I/O con i files, perche' sono un tutt'uno, con il file mapping. Ai memory mapped files possono accedere tutti i processi del sistema, non solo il processo che li crea. Il file mapping e' inoltre usato come mezzo per condividere dati tra i processi. Usando il file mapping in questo modo, non c'e' nessun file fisicamente coinvolto. E' piu' come un blocco di memoria riservato che tutti i processi possono *vedere*. Ma condividere i dati tra i processi e' un argomento delicato, e non va trattato superficialmente. Dovete implementare la process e la thread synchronization o altrimenti le vostre applicazioni crasheranno in breve tempo. Non affronteremo l'argomento del file mapping come un mezzo per creare una regione di memoria condivisa in questo tutorial. Ci concentreremo invece sull'uso del file mapping come modo per "mappare" un file in memoria. Infatti, il PE loader usa il file mapping per caricare gli eseguibili in memoria. E' molto conveniente perche' solo le porzioni necessarie possono essere lette selettivamente dal file sul disco. Sotto Win32, dovreste usare il file mapping quanto piu' possibile. Ci sono pero' anche alcune limitazioni nel file mapping. Una volta creato un memory mapped file, la sua grandezza non puo' essere variata durante quella sessione. Percio' il file mapping e' eccezionale per i files di sola lettura o per operazioni con i files che non modificano la grandezza del file. Cio' non significa che non potete usare il file mapping se volete aumentare la grandezza del file. Potete stimare la nuova grandezza e creare il memory mapped file basandovi su di essa, e il file aumentera' a quella grandezza. E' semplicemente un piccolo inconveniente, ecco tutto. Ok, abbastanza spiegazioni. Tuffiamoci nell'implementazione del file mapping. Per usare il file mapping, questi tre passi devono essere seguiti:

  1. chiamate CreateFile, esclusivamente per aprire il file che volete mappare. Dovete specificare il parametro dwShareMode come 0 per evitare che gli altri processi scrivano sul file durante la sessione.
  2. chiamate CreateFileMapping con il file handle restituito dal CreateFile come uno dei parametri. Questa funzione crea un file mapping object dal file aperto da CreateFile.
  3. chiamate MapViewOfFile per mappare una regione del file selezionata (o l'intero file) in memoria. Questa funzione restituisce un puntatore al primo byte della regione del mapped file.
  4. Usate il puntatore per leggere o scrivere sul file.
  5. chiamate UnmapViewOfFile per "demappare" il file.
  6. chiamate CloseHandle con l'handle del mapped file come parametro per chiudere il mapped file.
  7. chiamate CloseHandle di nuovo, ma stavolta con il file handle restituito da CreateFile per chiudere fisicamente il file.


Essay

Il seguente programma vi fa aprire un file con un dialog box "apri file". Esso apre il file usando il file mapping. Se riesce ad aprirlo con successo, il titolo della finestra viene cambiato con il nome del file aperto. Potete salvare il file con un altro nome selezionando il menuitem File/Save. Il programma copiera' l'intero contenuto del file aperto nel nuovo file. Notate che non dovete chiamare GlobalAlloc per allocare un blocco di memoria in questo programma.

include windows.inc
includelib user32.lib
includelib kernel32.lib
includelib gdi32.lib
includelib comdlg32.lib

.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260

.data
ClassName db "Win32ASMFileMappingClass",0
AppName  db "Win32 ASM File Mapping Example",0
MenuName db "FirstMenu",0
ofn   OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
             db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
hMapFile HANDLE 0     ; Handle del memory mapped file, deve essere
                     ;inizializzato con 0 perche' lo vogliamo usare anche
                     ;come flag nella sezione WM_DESTROY.

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ?                     ; Handle del file sorgente
hFileWrite HANDLE ?                    ; Handle del file di output
hMenu HANDLE ?
pMemory DWORD ?                        ; puntatore ai dati del file sorgente
SizeWritten DWORD ?           ; numero di bytes effettivamente scritti da WriteFile
FileSize DWORD ?              ; low DWORD della file size (dimensione file) del file sorgente
FileSizeHighWord DWORD ?      ; high DWORD della file size del file sorgente

.code
start:
        invoke GetModuleHandle, NULL
        mov    hInstance,eax
        invoke GetCommandLine
        invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
 invoke ExitProcess,eax

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
    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  hInstance
    pop   wc.hInstance
    mov   wc.hbrBackground,COLOR_WINDOW+1
    mov   wc.lpszMenuName,OFFSET MenuName
    mov   wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov   wc.hIcon,eax
    mov   wc.hIconSm,0
    invoke LoadCursor,NULL,IDC_ARROW
    mov   wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,\
                ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
               CW_USEDEFAULT,300,200,NULL,NULL,\
    hInst,NULL
    mov   hwnd,eax
    invoke ShowWindow, hwnd,SW_SHOWNORMAL
    invoke UpdateWindow, hwnd
    .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 uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    mov   eax,uMsg
    .IF eax==WM_CREATE
        invoke GetMenu,hWnd                        ; Ottieni l'handle del menu
        mov  hMenu,eax
        mov ofn.lStructSize,SIZEOF ofn
        push hWnd
        pop  ofn.hwndOwner
        push hInstance
        pop  ofn.hInstance
        mov  ofn.lpstrFilter, OFFSET FilterString
        mov  ofn.lpstrFile, OFFSET buffer
        mov  ofn.nMaxFile,MAXSIZE
    .ELSEIF eax==WM_DESTROY
        .if hMapFile!=0
            call CloseMapFile
        .endif
        invoke PostQuitMessage,NULL
    .ELSEIF eax==WM_COMMAND
        mov eax,wParam
        .if lParam==0
            .if ax==IDM_OPEN
                mov  ofn.Flags, OFN_FILEMUSTEXIST or \
                                OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
                                OFN_EXPLORER or OFN_HIDEREADONLY
                                invoke GetOpenFileName, ADDR ofn
                .if eax==TRUE
                    invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ ,\
                                                0,\
                                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL
                    mov hFileRead,eax
                    invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
                    mov     hMapFile,eax
                    push    ebx
                    mov     eax,OFFSET buffer
                    movzx  bx,ofn.nFileOffset
                    add      eax,ebx
                    pop      ebx
                    invoke SetWindowText,hWnd,eax
                    invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
                .endif
            .elseif eax==IDM_SAVE
                mov ofn.Flags,OFN_LONGNAMES or\
                                OFN_EXPLORER or OFN_HIDEREADONLY
                invoke GetSaveFileName, ADDR ofn
                .if eax==TRUE
                    invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ or GENERIC_WRITE ,\
                                                FILE_SHARE_READ or FILE_SHARE_WRITE,\
                                                NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL
                    mov hFileWrite,eax
                    invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
                    mov pMemory,eax
                    invoke GetFileSize,hFileRead,ADDR FileSizeHighWord
                    invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
                    invoke UnmapViewOfFile,pMemory
                    call   CloseMapFile
                    invoke CloseHandle,hFileWrite
                    invoke SetWindowText,hWnd,ADDR AppName
                    invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
                .endif
            .else
                invoke DestroyWindow, hWnd
            .endif
        .endif
    .ELSE
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam
        ret
    .ENDIF
    xor    eax,eax
    ret
WndProc endp

CloseMapFile PROC
        invoke CloseHandle,hMapFile
        mov    hMapFile,0
        invoke CloseHandle,hFileRead
        ret
CloseMapFile endp

end start


Analisi

invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ ,\
                                                0,\
                                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL

Quando l'utente seleziona un file nel dialog box "apri file", noi chiamiamo CreateFile per aprirlo. Notate che specifichiamo GENERIC_READ per aprire questo file con accesso di sola lettura e dwShareMode e' zero perche' non vogliamo che altri processi modifichino il file durante la nostra operazione.

invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL

Quindi chiamiamo CreateFileMapping per creare un memory mapped file dal file aperto. CreateFileMapping ha la seguente sintassi:

HANDLE CreateFileMapping(
    HANDLE  hFile,                             ;// handle del file da mappare
    LPSECURITY_ATTRIBUTES  lpFileMappingAttributes, ;// attributi di sicurezza opzionali
    DWORD  flProtect,                        ;// protezione per il mapping object
    DWORD  dwMaximumSizeHigh,  ;// high-order 32 bits della grandezza dell'object
    DWORD  dwMaximumSizeLow,   ;// low-order 32 bits della grandezza dell'object
    LPCTSTR  lpName                        ;// nome del file-mapping object
   );

Dovreste prima sapere che CreateFileMapping non deve mappare per forza l'intero file in memoria. Potete usare la funzione anche per mappare solo una parte del file fisico in memoria. Voi specificate la grandezza del memory mapped file nei parametri dwMaximumSizeHigh e dwMaximumSizeLow. Se specificate una grandezza che e' piu' grande della grandezza effettiva del file, il file fisico verra' espanso alla nuova grandezza. Se volete che il memory mapped file sia di dimensioni identiche al file fisico, mettete zero in entrambi i parametri. Potete usare NULL nel parametro lpFileMappingAttributes per dire a Windows di creare un memory mapped file con gli attributi di sicurezza predefiniti. flProtect definisce la protezione desiderata per il memory mapped file. Nel nostro esempio, noi usiamo PAGE_READONLY per permettere solo operazioni di lettura sul memory mapped file. Notate che questo attributo non deve contraddire l'attributo usato in CreateFile o altrimenti CreateFileMapping fallira'. lpName punta al nome del memory mapped file. Se volete condividere questo file con altri processi, dovete dargli un nome. Ma, nel nostro esempio, il nostro processo e' l'unico che usa questo file quindi ignoreremo questo parametro.

push    ebx
                    mov     eax,OFFSET buffer
                    movzx  bx,ofn.nFileOffset
                    add      eax,ebx
                    pop      ebx
                    invoke SetWindowText,hWnd,eax

Se CreateFileMapping ha successo, cambiamo il titolo della finestra con il nome del file aperto. Il nome del file con l'intera path viene conservato in buffer; noi vogliamo mostrare solo il nome del file nella barra e quindi dobbiamo aggiungere il valore del membro nFileOffset della struttura OPENFILENAME all'indirizzo di buffer.

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

Come precauzione, non vogliamo che l'utente apra piu' files alla volta, percio' disattiviamo (o meglio ingrigiamo) il menu item "Open" e abilitiamo il menu item "Save". EnableMenuItem e' usata per cambiare gli attributi di un menu item. Dopo di cio', aspettiamo che l'utente selezioni il menu item File/Save per chiudere il nostro programma. Se l'utente sceglie di chiudere il programma, dobbiamo chiudere il memory mapped file e il file fisico con il seguente codice:

.ELSEIF eax==WM_DESTROY
        .if hMapFile!=0
            call CloseMapFile
        .endif
        invoke PostQuitMessage,NULL

In questo spezzone, quando la window procedure riceve il messaggio WM_DESTROY, verifica prima se il valore di hMapFile e' zero o no. Se non e' zero, chiama la funzione CloseMapFile che contiene il codice seguente:

CloseMapFile PROC
        invoke CloseHandle,hMapFile
        mov    hMapFile,0
        invoke CloseHandle,hFileRead
        ret
CloseMapFile endp

CloseMapFile chiude il memory mapped file e il file fisico cosi' che non ci sara' nessuna perdita di risorse quando il nostro programma esce a Windows. Se l'utente sceglie di salvare i dati in un altro file, il programma gli presenta un dialog box "Salva con nome". Dopo che l'utente ha scritto il nome del nuovo file, il file viene creato con la funzione CreateFile.

invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
                    mov pMemory,eax

Immediatamente dopo che il file di output e' stato creato, chiamiamo MapViewOfFile per mappare in memoria la porzione desiderata del memory mapped file. Questa funzione ha la seguente sintassi:

LPVOID MapViewOfFile(
    HANDLE  hFileMappingObject,          ;// file-mapping object da mappare nell'address space
    DWORD  dwDesiredAccess,               ;// modo di accesso
    DWORD  dwFileOffsetHigh,                ;// high-order 32 bits del file offset
    DWORD  dwFileOffsetLow,                ;// low-order 32 bits del file offset
    DWORD  dwNumberOfBytesToMap  ;//numero di bytes da mappare
   );

dwDesiredAccess specifica che operazione vogliamo eseguire sul file. Nel nostro esempio, vogliamo solo leggere i dati, percio' usiamo FILE_MAP_READ. dwFileOffsetHigh e dwFileOffsetLow specificano il file offset iniziale della porzione di file che volete mappare in memoria. Nel nostro caso, vogliamo leggere tutto il file percio' cominciamo a mappare dall'offset 0 in avanti. dwNumberOfBytesToMap specifica il numero di bytes da mappare in memoria. Se volete mappare tutto il file (specificato da CreateFileMapping), passate 0 a MapViewOfFile. Dopo aver chiamato MapViewOfFile, la porzione desiderata e' caricata in memoria. Vi sara' dato il puntatore al blocco di memoria che contiene i dati dal file.

invoke GetFileSize,hFileRead,ADDR FileSizeHighWord

Trova la lunghezza del file, che e' restituita in eax. Se il file e' piu' grande di 4 GB, la high DWORD della lunghezza del file e' conservata in FileSizeHighWord.

invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL

Scrive i dati che sono mappati in memoria nel file di output.

invoke UnmapViewOfFile,pMemory

Quando abbiamo finito con il file sorgente, lo "demappiamo" dalla memoria...

call   CloseMapFile
                    invoke CloseHandle,hFileWrite

...e chiudiamo tutti i files.

invoke SetWindowText,hWnd,ADDR AppName

Ripristina il titolo originale della finestra.

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
                    invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED

Abilita il menu item "Open" e disabilita "Save As".


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.