Home | Chi sono | Contattami
 

Progr. lineare

Delphi
 
Componenti
  Database
 
Miei articoli

Windows

Miei articoli 

 

Applicazioni Native


Questo fine settimana ho deciso di raccogliere ed ordinare un pò di materiale inerente la realizzazione di applicativi che vadano in esecuzione al boot di Windows prima che compaia la finestra di login. Applicativi di questo tipo devono usare solo ed unicamente le funzioni esportate da ntdll.dll: non c'è spazio per la kernel32.dll o user32.dll etc... Inizialmente avevo intenzione di realizzare un documento piuttosto esteso che affrontasse per gradi l'argomento ponendo notevole enfasi su ogni singolo dettaglio, poi ho deciso di optare per un raggruppamento delle documentazioni e dei sorgenti presenti sul web al riguardo perchè sinceramente mi stavo perdendo e poi a volte il tutto è come il nulla ed una sbrodolata immensa di nozioni finisce per sortire l'effetto indesiderato dell'esclusione immediata per sopraggiunta nausea. Sicuramente non sarà un trattato con paragrafi e sottoparagrafi rifiniti chirurgicamente ma in ogni caso sarà di una discreta utilità. Partiamo quindi con l'elenco delle documentazioni disponibili sul web.

1. Esempio base di Mark Russinovich

Penso che questo sia effettivamente il primo esempio concreto che sia stato pubblicato sul web. Facendo una ricerca su Google con la stringa "Native Applications" il primo risultato è il seguente

http://technet.microsoft.com/en-us/sysinternals/bb897447.aspx

Si tratta della trascrizione di un documento del periodo 1997/98 nel sito Sysinternals e di cui è possibile trovare un mirror all'indirizzo

http://doc.sch130.nsc.ru/www.sysinternals.com/ntw2k/info/native.shtml

Stranamente al nuovo indirizzo (quello Microsoft) non è disponibile per il download l'esempio allegato (sorgenti inclusi). Nel mirror si può downloadare l'esempio ma per tagliare la testa al toro ho deciso di riportare nel seguito una copia dell'articolo ed in fondo anche l'esempio

Inside Native Applications
Copyright © Mark Russinovich

Introduction

If you have some familiarity with NT's architecture you are probably aware that the API that Win32 applications use isn't the "real" NT API. NT's operating environments, which include POSIX, OS/2 and Win32, talk to their client applications via their own APIs, but talk to NT using the NT "native" API. The native API is mostly undocumented, with only about 25 of its 250 functions described in the Windows NT Device Driver Kit.

What most people don't know, however, is that "native" applications exist on NT that are not clients of any of the operating environments. These programs speak the native NT API and can't use operating environment APIs like Win32. Why would such programs be needed" Any program that must run before the Win32 subsystem is started (around the time the logon box appears) must be a native application. The most visible example of a native application is the "autochk" program that runs chkdsk during the initialization Blue Screen (its the program that prints the "."'s on the screen). Naturally, the Win32 operating environment server, CSRSS.EXE (Client-Server Runtime Subsystem), must also be a native application.

In this article I'm going to describe how native applications are built and how they work. I also provide you the source code for an example native application, Native, that you can easily install and that will print out a string you specify on the boot-time Blue Screen.

How Does Autochk Get Executed

Autochk runs in between the time that NT's boot and system start drivers are loaded, and when paging is turned on. At this point in the boot sequence Session Manager (smss.exe) is getting NT's user-mode environment off-the-ground and no other programs are active.
The HKLM\System\CurrentControlSet\Control\Session Manager\BootExecute value, a MULTI_SZ, contains the names and arguments of programs that are executed by Session Manager, and is where Autochk is specified. Here is what you'll typically find if you look at this value, where "Autochk" is passed "*" as an argument:

Autocheck Autochk *

Session Manager looks in the <winnt>\system32 directory for the executables listed in this value. When Autochk runs there are no files open so Autochk can open any volume in raw-mode, including the boot drive, and manipulate its on-disk data structures. This wouldn't be possible at any later point.

Building Native Applications

Microsoft doesn't document it, but the NT DDK Build utility knows how to make native applications (and its probably used to compile Autochk). You specify information in a SOURCES file that defines the application, the same as would be done for device drivers. However, instead of indicating to Build that you want a driver, you tell it you want a native applicationin the SOURCES file like this:

TARGETTYPE=PROGRAM

The Build utility uses a standard makefile to guide it, \ddk\inc\makefile.def, which looks for a run-time library named nt.lib when compiling native applications. Unfortunately, Microsoft doesn't ship this file with the DDK (its included in the Server 2003 DDK, but I suspect that if you link with that version your native application won't run on XP or Windows 2000). However, you can work around this problem by including a line in makefile.def that overrides the selection of nt.lib by specifying Visual C++'s runtime library, msvcrt.lib

If you run Build under the DDK's "Checked Build" environment it will produce a native application with full debug information under %BASEDIR%\lib\%CPU%\Checked (e.g. c:\ddk\lib\i386\checked\native.exe), and if you invoke it in the "Free Build" environment a release version of the program will end up in %BASEDIR%\lib\%CPU%\Free. These are the same places device driver images are placed by Build.

Native applications have ".exe" file extensions but you cannot run them like Win32 .exe's. If you try you'll get the message:

The <Application Name> application cannot be run in Windows NT mode.

Inside a Native Application

Instead of winmain or main, the entry point for native applications is NtProcessStartup. Also unlike the other Win32 entry points, native applications must reach into a data structure passed as its sole parameter to locate command-line arguments.

The majority of a native application's runtime environment is provided by NTDLL.DLL, NT's native API export library. Native applications must create their own heap from which to allocate storage by using RtlCreateHeap, a NTDLL function. Memory is allocated from a heap with RtlAllocateHeap and freed with RtlFreeHeap. If a native application wishes to print something to the screen it must use the function NtDisplayString, which will output to the initialization Blue Screen.

Native applications don't simply return from their startup function like Win32 programs, since there is no runtime code to return to. Instead, they must terminate themselves by calling NtProcessTerminate.

The NTDLL runtime consists of hundreds of functions that allow native applications to perform file I/O, interact with device drivers, and perform interprocess communications. Unfortunately, as I stated earlier, the vast majority of these functions are undocumented.

An Example Native Application

I've created a toy native application that demonstrates how native applications are built and how they work. The program, Native, is installed by running the install.bat batch file. The batch file copies native.exe to your <winnt>\system32 directory and adds the following entry to the BootExecute Registry value:

native Hello world!

When you reboot, Session Manager executes Native after running Autochk. Native allocates some heap, locates its command line argument and then prints the argument ("Hello world!") to the Blue Screen, using the functions I described above. If you want it to print something else simply edit the BootExecute value under Regedit or Regedt32 and change "Hello world!" to your message.

To uninstall Native execute the uinstall.bat batch file. This deletes native.exe from <winnt>\system32 and changes BootExecute back to its typical value.

If you want to build Native you must have the Windows NT Device Driver Kit. Copy the makefile.def included with Native's sources to \ddk\inc and then you can run Build.

Native.7z

Beh, sia l'articolo che l'esempio allegato sono un buon punto di partenza per la realizzazione di questo tipo di applicativi. Due anni fa mi ero messo a fare un pò di ricerche sul web spinto dall'interesse verso una implementazione in Delphi dell'applicazione base sviluppata in C da Mark Russinovich: questo è appunto il tema del prossimo paragrafo.

2. Esempio base in Delphi di Uall (porting in Delphi dell'esempio base in C)

All'indirizzo

http://uall.cheat-project.com/

tra i vari file è presente una applicazione Native in Delphi perfettamente funzionante: si tratta di una traduzione esatta dell'esempio in C trattato nel paragrafo precedente. Ho apportato alcune modifiche al codice e vorrei descrivere dettagliatamente il tutto: ho deciso di andare giù di sottoparagrafi per fare una trattazione per gradi.

2.1 Come iniziare

Quando c'è già qualcun'altro che ha realizzato un esempio base si è già a metà dell'opera: bisogna studiare, bisogna capire e documentarsi ma c'è già qualcuno che ha aperto il vaso di pandora. Ben diversa è la situazione in cui bisogna implementare da zero un applicativo avendo a disposizione solo la descrizione delle api e delle strutture coinvolte. Come già visto nel precedente paragrafo 1., già nel 1998 era disponibile sul web una implementazione basilare di una applicazione Native con tanto di un' articolo molto ben fatto. Inoltre, come appena detto, esiste anche un porting di tale applicazione in Delphi. Quello che farò nel seguito sarà descrivere passo dopo passo come passare dall'applicazione originale in C a quella in Delphi usando come riferimento le 2 implementazioni in questione.

2.2 Riscriviamo in Delphi l'applicativo in C

Il sorgente originale in C è il seguente

//====================================================================== // // Native.c // // Mark Russinovich // http://www.ntinternals.com // // This is a demonstration of a Native NT program. These programs // run outside of the Win32 environment and must rely on the raw // services provided by NTDLL.DLL. AUTOCHK (the program that executes // a chkdsk activity during the system boot) is an example of a // native NT application. // // This example is a native 'hello world' program. When installed with // the regedit file associated with it, you will see it print // "hello world" on the initialization blue screen during the system // boot. This program cannot be run from inside the Win32 environment. // //====================================================================== #include "ntddk.h" // include this for its native functions and defn's #include "stdio.h" #include "native.h" // // Our heap // HANDLE Heap; //---------------------------------------------------------------------- // // NtProcessStartup // // Instead of a 'main' or 'winmain', NT applications are entered via // this entry point. // //---------------------------------------------------------------------- void NtProcessStartup( PSTARTUP_ARGUMENT Argument ) { PUNICODE_STRING commandLine; PWCHAR stringBuffer, argPtr; UNICODE_STRING helloWorld; RTL_HEAP_DEFINITION heapParams; // // Initialize some heap // memset( &heapParams, 0, sizeof( RTL_HEAP_DEFINITION )); heapParams.Length = sizeof( RTL_HEAP_DEFINITION ); Heap = RtlCreateHeap( 2, 0, 0x100000, 0x1000, 0, &heapParams ); // // Point at command line // commandLine = &Argument->Environment->CommandLine; // // Locate the argument // argPtr = commandLine->Buffer; while( *argPtr != L' ' ) argPtr++; argPtr++; // // Print out the argument // stringBuffer = RtlAllocateHeap( Heap, 0, 256 ); swprintf( stringBuffer, L"\n%s", argPtr ); helloWorld.Buffer = stringBuffer; helloWorld.Length = wcslen( stringBuffer ) * sizeof(WCHAR); helloWorld.MaximumLength = helloWorld.Length + sizeof(WCHAR); NtDisplayString( &helloWorld ); // // Free heap // RtlFreeHeap( Heap, 0, stringBuffer ); // // Terminate // NtTerminateProcess( NtCurrentProcess(), 0 ); }

Passare ad una versione Delphi non è affatto difficile

program native; {$I 'defs.inc'} var Heap: Cardinal; procedure ZeroMemory(Destination: Pointer; Length: Cardinal); begin FillChar(Destination^, Length, 0); end; procedure TextOut(sText: PWideChar); var StringBuffer: PWideChar; Text: TUNICODE_STRING; begin StringBuffer := RtlAllocateHeap(Heap,0,256); swprintf( stringBuffer, #13#10'%s', sText ); Text.pBuffer := stringBuffer; Text.wLength := wcslen( stringBuffer ) * sizeof(WideChar); Text.wMaximumLength := Text.wLength + sizeof(WideChar); NtDisplayString(@Text); RtlFreeHeap(Heap,0,StringBuffer); end; procedure NtProcessStartup( Argument: PSTARTUP_ARGUMENT ); stdcall; var commandLine: PUNICODE_STRING; argPtr: PWideChar; heapParams: TRTL_HEAP_DEFINITION; begin // // Initialize some heap // ZeroMemory( @heapParams, sizeof( TRTL_HEAP_DEFINITION )); heapParams.Length := sizeof( TRTL_HEAP_DEFINITION ); Heap := RtlCreateHeap( 2, nil, $100000, $1000, False, @heapParams ); // // Point at command line // commandLine := @Argument^.Environment^.CommandLine; // // Locate the argument // argPtr := commandLine^.pBuffer; while (argPtr^ <> ' ') do argPtr := PWideChar(Cardinal(argPtr)+SizeOf(WideChar)); argPtr := PWideChar(Cardinal(argPtr)+SizeOf(WideChar)); // // Print out the argument // TextOut(argPtr); // // Terminate // NtTerminateProcess(Cardinal(-1), 0); end; exports NtProcessStartup; begin end.

Il file defs.inc contiene le dichiarazioni delle api e delle strutture coinvolte:

const ntdll = 'ntdll.dll'; type TUNICODE_STRING = packed record wLength : Word; wMaximumLength: Word; pBuffer : PWideChar; end; PUNICODE_STRING = ^TUNICODE_STRING; TENVIRONMENT_INFORMATION = packed record Unknown: array[0..20] of Cardinal; CommandLine: TUNICODE_STRING; ImageFile: TUNICODE_STRING; end; PENVIRONMENT_INFORMATION = ^TENVIRONMENT_INFORMATION; PSTARTUP_ARGUMENT = ^TSTARTUP_ARGUMENT; TSTARTUP_ARGUMENT = packed record Unknown: array [0..2] of Cardinal; Environment: PENVIRONMENT_INFORMATION; end; TRTL_HEAP_DEFINITION = packed record Length, Unknown1, Unknown2, Unknown3, Unknown4, Unknown5, Unknown6, Unknown7, Unknown8, Unknown9, Unknown10, Unknown11, Unknown12: Cardinal; end; PRTL_HEAP_DEFINITION = ^TRTL_HEAP_DEFINITION; function NtTerminateProcess( dwInHandle: Cardinal; dwInExitCode: Cardinal ): Cardinal; stdcall; external ntdll; function RtlCreateHeap( dwInFlags: Cardinal; pInBase: Pointer; dwInReserve: Cardinal; dwInCommit: Cardinal; bInLock: Boolean; RtlHeapParams: PRTL_HEAP_DEFINITION ): Cardinal; stdcall; external ntdll; function NtDisplayString(sString: PUNICODE_STRING): Cardinal; stdcall; external ntdll; function RtlAllocateHeap( dwInHeapHandle: Cardinal; dwInFlags: Cardinal; dwInSize: Cardinal ): Pointer; stdcall; external ntdll; function RtlFreeHeap( dwInHeapHandle: Cardinal; dwInFlags: Cardinal; pInMemoryPointer: Pointer ): Cardinal; stdcall; external ntdll; function swprintf( sDestText: PWideChar; sFormat: PWideChar; sSourceText: PWideChar ): Cardinal; cdecl; external ntdll; function wcslen(sText: PWideChar): Cardinal; cdecl; external ntdll;

Behh, non stiamo qui a guardare alle microdifferenze che non fanno la differenza (funzione ZeroMemory, funzione TextOut) ed iniziamo l'analisi seria.

2.3 Elenco delle modifiche da apportare al .exe per renderlo Native

Andremo ora ad elencare in maniera sintetica le operazioni necessarie per rendere Native il nostro applicativo.

1) Settare come entrypoint la funzione NtProcessStartup (behh, la si può chiamare anche pallino, l'importante è che la tipologia sia quella richiesta ossia una procedura che prende in input un argomento di 4 byte che nel caso specifico sarà l'indirizzo di un record di tipo TSTARTUP_ARGUMENT): in pratica devo inserire nei 4 byte che si trovano all'offset $00000010 dell' Optional Header, l'RVA della NtProcessStartup.

2) Settare la Flag di Subsystem Native (valore $0001 nei 2 byte all'offset $00000044 dell' Optional Header).

3) Nell'ambito della Data Directory, azzerare puntatore e dimensione relativamente a tutte le Directory fatta eccezione per la Import Directory: in questo modo non viene eliminato lo spazio sul .exe dedicato alle Directory in questione ma è come se non ci fossero.

4) Nell'ambito della Import Directory , eliminare dall'array dei Descriptors tutte le entry che non corrispondono a ntdll.dll.

Nel paragrafo successivo vedremo come eseguire le modifiche in questione

2.4 Trasformazione in applicativo Native

Per la realizzazione delle modifiche si potrebbe creare un altro applicativo che prende in input l'eseguibile in questione e lo rende conforme ai requisiti richiesti; in alternativa può essere lo stesso eseguibile che, al primo avvio all'interno di una normale sessione Windows, modifica una copia in memoria del file .exe ed alla fine salva il risultato in un nuovo .exe che sarà appunto un'applicazione Native vera e propria. Utilizzeremo questa seconda opzione. Bene, è ora di inziare; come visto al paragrafo precedente il punto 1) consiste nel settaggio del nuovo entrypoint; come faccio a determinare l'RVA della funzione NtProcessStartup (valore che poi andrò ad inserire nei 4 byte all'offset $00000010 dell' Optional Header)? La soluzione migliore è esportare la funzione NtProcessStartup. Poi dobbiamo inserire il codice che al primo avvio, creerà la versione Native del .exe: quindi è del codice che dovrà essere inserito nel blocco begin-end oppure (ancora meglio) potrà essere inserito nel blocco initialization di una unit inclusa nel blocco uses; useremo questa seconda opzione che mi pare proprio più elegante. Quindi il sorgente originale viene modificato nel modo seguente

program native; uses createnative in 'createnative.pas'; {$I 'defs.inc'} ... procedure NtProcessStartup(Argument: PSTARTUP_ARGUMENT); stdcall; var ... begin ... ... end; exports NtProcessStartup; begin end.

unit createnative; interface uses windows; implementation .... .... //procedura che Crea la versione Native del .exe procedure Create; var ... ... begin ... end; initialization Create; end.

Al primo avvio del .exe verrà quindi eseguita la procedura Create che si occuperà della conversione in versione Native del .exe. Occorre osservare che, modificando l'entrypoint, l'applicazione in versione Native considererà il blocco di codice definito dalla funzione NtProcessStartup e quindi tutto ciò che è incluso nel blocco begin-end e nei blocchi initialization delle unit incluse non verrà preso minimamente in considerazione; questo è da sottolineare in quanto la procedura Create utilizzerà funzioni di kernel32.dll, e come espresso dal punto 4) nel paragrafo precedente, la Import Directory nella versione Native conterrà solo i Descriptor di ntdll.dll ed uno potrebbe chiedersi come fa tale procedura ad andare in esecuzione nella versione Native: non andrà semplicemente in esecuzione in quanto la versione Native userà il blocco di codice definito dalla funzione NtProcessStartup (avendo cambiato appunto l'entrypoint). 

Procediamo quindi con l'implementazione della procedura Create

unit createnative; interface uses windows; implementation //Section Table type TSections = array [0..0] of TImageSectionHeader; type TImage_Import_Descriptor = packed record OriginalFirstThunk: Cardinal; TimeDateStamp: Cardinal; ForwarderChain: Cardinal; Name: Cardinal; FirstThunk: Cardinal; end; TImage_Import_Descriptors = array [0..0] of TImage_Import_Descriptor; function RvaToFileOffset(INH: PImageNtHeaders; dwRVA: Cardinal): Cardinal; var x: Word; FNumberOfSections : Cardinal; PSections: ^TSections; begin Result := 0; FNumberOfSections := INH^.FileHeader.NumberOfSections; //ottengo il puntatore alla Section Table PSections := pointer(Cardinal(@(INH^.OptionalHeader)) + INH^.FileHeader.SizeOfOptionalHeader); for x := 0 to (FNumberOfSections - 1) do begin if ((dwRVA >= PSections[x].VirtualAddress) and (dwRVA < PSections[x].VirtualAddress + PSections[x].SizeOfRawData) ) then begin Result := dwRVA - PSections[x].VirtualAddress + PSections[x].PointerToRawData; Break; end; end; end; procedure Create; var sFile: String; iFileHandle: Integer; dwSize: Cardinal; pMem: Pointer; dwRead: Cardinal; IDH: PImageDosHeader; INH: PImageNtHeaders; pEntry: Pointer; i, j: Cardinal; pDllName: PChar; iFSave: Integer; // PImage_Import_Descriptors: ^TImage_Import_Descriptors; pRaw: Cardinal; // begin pEntry := GetProcAddress(GetModuleHandle(nil),'NtProcessStartup'); if (pEntry = nil) then begin MessageBoxA(0,'NtProcessStartup not exported',nil,0); Exit; end; pEntry := Pointer(DWord(pEntry)-GetModuleHandle(nil)); //pEntry contiene quindi l'RVA della funzione NtProcessStartup; //tale valore verrà poi assegnato all'EntryPoint //ottengo il nome dell'eseguibile corrente sFile := Paramstr(0); //apro il file eseguibile iFileHandle := CreateFileA( PChar(sFile), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 ); if (iFileHandle < 0) then begin MessageBoxA(0,'Error while opening file',nil,0); Exit; end; //ottengo la dimensione del file eseguibile dwSize := GetFileSize(iFileHandle,nil); if (dwSize = 0) then begin CloseHandle(iFileHandle); MessageBoxA(0,'Filesize not valid',nil,0); Exit; end; //vado ad allocare una quantità di memoria pari alla dimensione //del file eseguibile pMem := VirtualAlloc( nil, dwSize, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE ); if (pMem = nil) then begin CloseHandle(iFileHandle); MessageBoxA(0,'Error while allocating memory',nil,0); Exit; end; //copio il contenuto del file eseguibile nella fetta di memoria //appena allocata if (not ReadFile(iFileHandle, pMem^, dwSize, dwRead, nil)) or (DwRead <> dwSize) then begin VirtualFree(pMem,dwSize,MEM_DECOMMIT); CloseHandle(iFileHandle); MessageBoxA(0,'Error while reading file',nil,0); Exit; end; //ora che ho copiato il contenuto, posso anche chiudere //l'handle al file eseguibile CloseHandle(iFileHandle); //ok, d'ora in poi lavorerò con il contenuto di memoria indirizzato //da pMem che contiene appunto il file eseguibile //ottengo i puntatori al Dos Header ed al NT Image Header ed eseguo //i 2 controlli di base per verificare che il file copiato in memoria //sia effettivamente un PE: direi che è un controllo praticamente inutile IDH := pMem; if (IDH^.e_magic <> IMAGE_DOS_SIGNATURE) then begin VirtualFree(pMem,dwSize,MEM_DECOMMIT); MessageBoxA(0,'No PE File',nil,0); Exit; end; INH := Pointer(Integer(IDH)+IDH._lfanew); if (INH^.Signature <> IMAGE_NT_SIGNATURE) then begin VirtualFree(pMem,dwSize,MEM_DECOMMIT); MessageBoxA(0,'No PE File',nil,0); Exit; end; with INH^.OptionalHeader do begin //annullo i puntatori ed azzero le dimensioni relativamente a tutte le //Directory fatta eccezione per la Import Directory; le Directory presenti //sono le seguenti: resource, tls, export, relocate; //questo accorgimento non elimina tali Directory dal PE ma in ogni caso //fa in modo che non vengano prese in considerazione DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress := 0; DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE].Size := 0; DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress := 0; DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size := 0; DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress := 0; DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size := 0; DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress := 0; DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size := 0; //assegnazione dell'EntryPoint AddressOfEntryPoint := DWord(pEntry); //setto il SubSystem Native Subsystem := 1; //vado a determinare il File Offset dell'Import Directory pRaw := RvaToFileOffset( INH, DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ); if (pRaw = 0) then begin VirtualFree(pMem,dwSize,MEM_DECOMMIT); MessageBoxA(0,'Error while converting VA to RA',nil,0); Exit; end; end; //enumero i Descriptors; tutte le entry corrispondenti ad ntdll.dll //le sposto in alto ed alla fine azzero la entry successiva all'ultima entry //con ntdll.dll; in questo modo verranno prese in considerazione solo //le entry con ntdll.dll: è da ricordare infatti che la entry nulla //rappresenta la fine lista nell'elenco dei Descriptors PImage_Import_Descriptors := Pointer(DWord(IDH)+DWord(pRaw)); i := 0; j := 0; while (PImage_Import_Descriptors[i].Name <> 0) do begin pDllName := PChar( Cardinal(IDH) + RvaToFileOffset(INH, PImage_Import_Descriptors[i].Name) ); if (pDllName = 'ntdll.dll') then begin PImage_Import_Descriptors[j] := PImage_Import_Descriptors[i]; Inc(j); end; Inc(i); end; ZeroMemory(@PImage_Import_Descriptors[j], SizeOf(TImage_Import_Descriptor)); //salvo il buffer modificato su un nuovo file su disco iFSave := CreateFileA( PChar(sFile+'_new.exe'), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); if (iFSave < 0) then begin VirtualFree(pMem,dwSize,MEM_DECOMMIT); MessageBoxA(0,'Error while saving file',nil,0); Exit; end; if (not WriteFile(iFSave,pMem^,dwSize,dwRead,nil)) or (dwSize <> dwRead) then begin VirtualFree(pMem,dwSize,MEM_DECOMMIT); CloseHandle(iFSave); MessageBoxA(0,'Error while saving file',nil,0); Exit; end; MessageBoxA(0,'native file sucessful created','native',0); CloseHandle(iFSave); VirtualFree(pMem,dwSize,MEM_DECOMMIT); end; initialization Create; end.

A questo punto si crea l'eseguibile con Delphi: si lancia normalmente il .exe e verrà creato il .exe in versione Native (file di nome Native.exe_new.exe che poi uno può tranquillamente rinominare come si vuole)

Per testare il programma basta copiare l'eseguibile nella versione Native nella cartella di sistema (C:\Windows\System32) ed apportare le seguenti modifiche al registro di sistema

come si può vedere dai sorgenti dell'applicativo, come prima cosa, l'applicazione andrà a scrivere a video la stringa coi parametri da linea di comando che, come si può notare dall'immagine, consiste nelle stringhe Hello e World!. Poi andrà a scrivere altro testo informativo. Di seguito una serie di screenshot che descrivono il tutto

Di seguito i sorgenti con il semplice batch install.bat per la copia dell'applicazione nella cartella di sistema e l'applicazione delle modifiche al registro di sistema (ed anche il corrispondente batch uinstall.bat per la disinstallazione): l'eseguibile nella versione Native deve essere sistemato nella stessa cartella e chiamato Native.exe.

native_demo.7z

Ora che abbiamo visto come realizzare una applicazione Native basilare anche in Delphi, è arrivato il momento di elencare alcune applicazioni più evolute disponibili sul web: sarà questo l'argomento dei prossimi paragrafi

3) Esempio su Rootkit.com by Rodream

Come da titolo, all'indirizzo http://www.rootkit.com/newsread.php?newsid=773 è disponibile un buon esempio di applicazione Native in C con capacità di manipolare File e Registro. Riporto di seguito l'articolo ed anche l'applicativo (caso andassero persi alla fonte)

One possible way to avoid UAC in Windows Vista
By: rodream

Author: rodream ( rodream@gmail.com )
WebSite: http://rodream.net

Windows Vista has UAC function and it protects hamful file system operation such as copying file to Windows directory or Program Files.
Users can turn off UAC, but people can not do this because they don't know about it or want to protect system.
In this situation, many system programer has got a headache. I wanna know how to kickout this damn protection.

Finally, I found one way to kickout.

The method which I found is use Native Application.

Native Application is user mode program which uses ntdll.dll and runs likes autochk.exe (scandisk's WindowsNT version)
You can get additional information from the SysInternals (http://www.microsoft.com/technet/sysinternals/information/nativeapplications.mspx)


You can setup Native Application to your system by registry. Following instruction is how to install Native Application to your system.

Intructions(How to install):
1. run regedit.exe
2. move to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager
3. Edit BootExecute (REG_MULTI_SZ value) like this :
-< BootExecute Value >------------
autocheck autochk *
NativeTest test!!!
-< BootExecute Value >------------
4. Copy NativeTest.exe file to 'C:\Windows\system32' (copy NativeTest.exe C:\Windows\System32\NativeTest.exe)

NativeTest.exe is sample code's output file name.

But, Native Application can not same as application and device driver. It has some different features between others.
Following requirements are differents.

The requirements are :
1. Native Application requires ddk to compile successfully.
2. You can use only ntdll.dll's function. You can't use any Win32 function.
3. You can't access all registry. because when Native Application executed the System don't load all registry yet. (You can use HKLM/SYSTEM)
4. You must setup heap memory space manually.

The problems is not hard. I think it's easy to you (rootkit user)

When you finished coding, you can compile it with Windows DDK console by 'build' command.
In my sample code's 'SOURCES' file contains all sources file. if you wanna add some your own file, you can edit it.

The sample code consists some functions which controls registry(NtReg.c NtReg.h) and file system(NtFile.c , NtFile.h).
If you using sample code, it setup heap memory space, so you can use heap memory without manual setup. :)

This is simple function tree of sample code.

(native.c)
- NtProcessStartup (entry point)
- UserMain (User main)
- RemoveEntryFromBootExecute (remove entry from bootexecute registry entry)
- FileTest (file access test)


Di seguito i sorgenti del progetto

NativeFile.7z
 

4) Windows Boot Program SDK di Joannes Rudolph

http://virtual-void.net/projects/bootpgm/

Penso che questo sia l'esempio più avanzato in quanto oltre a consentire l'interazione con File e Registro, coinvolge anche l'utilizzo della tastiera; oltretutto vengono anche analizzate varie problematiche inerenti la creazione di applicazioni di questo tipo. A volte mi è capitato di avere difficoltà a visualizzare il sito e così ho deciso di riportare nel seguito il contenuto (incluso l'applicativo con sorgenti) inserendo in qualche punto alcuni miei interventi


How to build the sample boot program

1) Download the latest sources from http://virtual-void.net/projects/bootpgm/changeset/HEAD/trunk?format=zip

2) Install the Windows DDK

3) Start the Windows DDK Command Line Environment

4) cd to the sources

5) use build-interactive.bat or simply build -wg to build the boot program
 

Di seguito un mirror dei sorgenti

NativeSDK.7z


The Windows Native API

Windows has many ways to access system functions. The normal programmer would just use the methods exported by the dynamic link libraries kernel32.dll, user32.dll and others. They belong to a user-mode API called Win32. Windows was designed to have many of those user-mode APIs called Subsystems. There were or are Win32, POSIX and Os/2 subsystems. Every subsystem is an API and a runtime environment an application can use to access the system functions of the OS.

But how do these subsystems access the kernel?

The answer is: through ntdll. Ntdll is a native dynamic link library providing direct links to kernel mode functions. A program which only uses this API is called a native program and a flag in the executable header marks that fact (see the MS linker's /SUBSYSTEM parameter). The subsystems themselves are native programs, of course.

Boot programs and the native API

Boot programs always have to use the native API and link against ntdll. That is because of the fact that other subsystems are just not available at the time a boot program gets executed. A corrollar: boot programs can't use the normal runtime library because it references Win32 APIs for various tasks. Therefore ntdll.dll exports several common runtime functions boot programs and other native programs can use.

Per una documentazione sulle Native APi si può far riferimento all'indirizzo

http://undocumented.ntinternals.net/

è anche possibile scaricarsi il tutto in formato chm

The registry at boot-time

At the time a boot program is executed the registry is not yet initialized. Machine/system and machine/hardware are loaded because they need to be loaded for Windows to find the drivers. The SAM at machine\SAM and the machine\software are not yet loaded. If you have to read values from this keys you have to plug the keys into the registry (NtLoadKey) from the corresponding file. Don't forget to unload the keys (NtUnloadKey) afterwards because Windows fails with a bluescreen afterwards if it finds hives mapped it is not used to...

Writing to Registry at boot-time

Writing to Registry is even more difficult: The Registry is read-only at boot-time. The causes for this are not known to me but I guess it's because of security issues and they wanted to stop some faulty driver to wreak havoc in the registry even before Windows has booted.

There is a variable in the Windows Configuration Manager which controls if the Registry can be flushed to disk. It is called CmpNoWrite. You may use the kernel debugger to lookup the value...

So the registry is not writeable at boot-time.

What are the solutions for this problem?

* Unset the flag using the Kernel Debugger. This works but this is not a very automatic solution. It is not portable as well since CmpNoWrite is at another position in the kernel everytime the kernel is built.
* Unset the flag in the boot program from user-mode using a hack. (see /trunk/win32/experimental.cpp function showNoWrite). The same issues regarding portability apply.
* Use NtInitializeRegistry to initialize the registry like smss.exe would do it after executing the boot program. That loads the software and SAM hives and marks the Registry as writable. You don't even have to flush the registry to disk since the changes are there nevertheless after booting. That is because the registry can be only initialized once. If you call NtInitializeRegistry, the normal call from smss.exe will fail but Windows will start though. It might be there will be any problems, but for now no such problems occured.
A side note: I found this fact - how you can make the registry writable - in a usenet post from 1997 but it still works...


C++ exceptions in native programs

This native api program/library uses C++-features like classes in many places. This seems to work without problems so far. It would be appropriate to use C++ exceptions as well. This won't work. At least not with much effort. C++ exceptions are working through the subtle mechanisms of Windows, the C++ compiler *and* the runtime library working together.

To use exception handling one has to enable the specific options in the compiler. You can use this lines in your SOURCES to enable it:

USE_NATIVE_EH=1
USE_RTTI=1

If we don't link a runtime library linking will error with unresolved externals like __CxxFrameHandler and others.

Since we can't use Win32 dlls in a native program we can't link against the standard rt (msvcrt). So the right choice seems to be the use of the staticly linkable runtime library libc. This does not work either. Even libc contains uncountable references to functions defined in kernel32 and user32. We cannot link to them, of course.

So your choices are:

* reimplement C++ exception handling on top of the native (API) features provided by Windows and the compiler
* use structured exception handling as documented by Microsoft, this will not work in functions relying on automatic object deconstruction
* don't use exceptions at all (that was my choice)

More information

* Reversing Microsoft Visual C++ Part I: Exception Handling
* How a C++ compiler implements exception handling
* The Exception Model
* A Crash Course in SEH
* New Vectored Exception Handling in Windows XP

Paper & Presentation

* Paper(German)
* Presentation(German)

Di seguito un Local Mirrror dei 2 papers

papers.7z


More Information & Links

* Sysinternals' Inside Native Applications
* Gary Nebbett's Windows NT/2000 Native API Reference
* Sven Schreiber's Undocumented Windows 2000 Secrets, w. CD-ROM: A Programmer's Cookbook or the PDF Version
* NTInternals' Undocumented NT Functions for Microsoft Windows NT/2000
* Microsoft Windows Internals en / de
* A thread on OSR's forum
* Syscall Table and comparison of different Windows versions
* Petter Nordahl's site about the Offline Nt Password & Registry Editor with some interesting information regarding the structure of the SAM
* Bruce Ediger's comment about Windows NT, Secret APIs and the Consequences

Tra questi ultimi link è doveroso riportare il contenuto del Thread sul forum OSR in cui viene descritto rapidamente sia come scrivere una stringa alla console (utilizzo della NtDisplayString già esposto più volte in precedenza all'interno di questo articolo) sia soprattutto l'esatto contrario ossia come leggere ciò che viene digitato da tastiera

//You would use NtDisplayString() to write strings to the console: void OutputString(PWCHAR pwszText) { UNICODE_STRING usText; usText.Buffer = pwszText; usText.Length = wcslen(pwszText) * sizeof(WCHAR); usText.MaximumLength = usText.Length + sizeof(WCHAR); NtDisplayString(&usText); } //To receive keyboard input you must open and read the keyboard device //directly: HANDLE g_hKeyboardAsync = NULL; NTSTATUS CreateKeyboard() { UNICODE_STRING ustrFileName; HANDLE hKeyboard = NULL; OBJECT_ATTRIBUTES oa; IO_STATUS_BLOCK iosb; NTSTATUS status; PWCHAR pszFileName = L"\\Device\\KeyboardClass0"; RtlInitUnicodeString( &ustrFileName, pszFileName ); InitializeObjectAttributes ( &oa, &ustrFileName, OBJ_CASE_INSENSITIVE, NULL, NULL ); memset( &iosb, 0, sizeof( iosb ) ); status = ZwCreateFile( &g_hKeyboardAsync, GENERIC_READ | SYNCHRONIZE | FILE_READ_ATTRIBUTES, &oa, &iosb, 0, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN, FILE_DIRECTORY_FILE, NULL, 0 ); return status; } //Once you have a handle to the keyboard device, NtWaitForSingleObject() on //it and if it returns STATUS_SUCCESS (as opposed to STATUS_PENDING) then //ZwReadFile() the handle to retrieve the make/break code and the scan code //value. You must translate the scan codes to ASCII codes yourself. //For the read, declare an array of type: CHAR g_KeyBuf[ 0xf0 ]; //The make/break code will be at g_KeyBuf[ 4 ], and the scan code will be at //g_KeyBuf[ 2 ]: PWORD pwScanCode; PWORD pwMakeBreak; pwMakeBreak = ( PWORD ) &g_KeyBuf[ 4 ]; pwScanCode = ( PWORD ) &g_KeyBuf[ 2 ]; //Hope this helps. //Rick Howard //Principal Engineer //Ontrack Data International, Inc. //Boulder,CO

Ok, abbiamo toccato il top: ora analizzeremo un pò se e come è possibile eseguire un applicativo Native nel Win32 Subsytem (ossia all'interno di una comune sessione Windows)

5) Eseguire applicativi Native all'interno di una comune sessione Windows

Finora abbiamo visto che se volgiamo eseguire un applicativo prima della nota maschera di login, tale applicativo deve essere un applicativo Native; ora invece proviamo a lanciare tale applicativo Native in una normale sessione Windows: non viene eseguito; c'è un modo per farlo andare in esecuzione? Ho trovato 2 esempi sul web al riguardo e li andremo ad analizzare

5.1 Esempio di Elicz

Questo esempio di Elicz, consiste in 2 eseguibili (no sorgenti purtroppo, azz azz ....): uno è l'applicazione Native (NtExample.exe) e l'altro è il Runner (RunNative.exe) ossia l'eseguibile in grado di lanciare in Win32 l'applicazione Native

http://www.defendion.com/EliCZ/export/RunNative.zip

Di seguito anche un mirror locale

RunNative.7z

L'esempio funzia ... tuttavia il Runner funziona solo sul suo esempio; testandolo sulle altre applicazioni native di cui si è parlato in precedenza non va. Non chiedetemi il perchè (poi senza i sorgenti la vedo dura).

5.2 Esempio di Mike McCormack

Ho trovato recentemente quest'altro esempio: in questo caso sono resi disponibili anche i sorgenti. Il materiale si trova all'indirizzo

http://mandoo.dyndns.org/hostnt.html

How can I use NtCreateProcess to run an NT Native API program?

There is an easier way to run Native applications than via the BootExecute key!

It works using NtCreateProcess.

Native API's NtCreateProcess call sounds deceptively similar to CreateProcess, but is actually quite a bit more complex to use. It's prototype is as follows:

NTSTATUS NTAPI NtCreateProcess(
PHANDLE Handle,
ACCESS_MASK Access,
POBJECT_ATTRIBUTES oa,
HANDLE Parent,
BOOLEAN InheritHandles,
HANDLE Section,
HANDLE DebugPort,
HANDLE ExceptionPort);

NtCreateProcess sets up an address space for the new process, then maps in:

* the Section specified as its 6th parameter
* ntdll.dll (0x77f80000)
* the NT shared memory area (0x7ffe0000)
* a new Process Environment Block (PEB) (0x7ffed000)

That's about it. Unlike CreateProcess, it creates no threads, thus executes no code in the new process's context.

It's left up to the caller to create the initial thread and its associated stack, a Process Parameters Block (PPB), and to map in the locale data for the process.

The new native process doesn't have a console to write to, so how do you get console output from a native application?

HostNT - "hello world" with an NT native API program

NtCreateProcess, can be used to start an NT native process, but the Native API offers no easy way to get output to the console from the native program.

hostnt (download hostnt.tar.gz) is a simple server that starts an NT native application, then prints messages from it on the console.

As a native program has no associated console, getting output from it isn't straight forward. Instead, hostnt creates a mailslot and listens to it for messages. The native application sends messages to the mailslot, then sets an event flag and they're printed to the console by hostnt.

When running hostnt, you'll see something like:

C:\hostnt>hostnt native.exe
hello world
process exitted (00000000)

C:\hostnt>

This is a useful tool for writing short programs that check the behavior of the NT Native API.

Di seguito un Local Mirror dell'applicativo

hostnt.7z 

L'applicativo funziona correttamente con il suo esempio ma anche in questo caso da errore con gli esempi precedenti: il sorgente dell'applicazione Native va modificato in maniera tale da seguire il protocollo di comunicazione previsto dal Runner ossia comunicazione tramite Mailslot; come sottolineato nel testo originale, una applicazione Native non ha associata una console e quindi per ottenerne l'output, il Runner (hostnt.exe) crea un Mailslot e si mette in ascolto; l'applicazione Native che si vuole analizzare deve quindi inviare l'output al Mailslot: il Runner leggerà dal Mailslot (per sapere quando è stato scritto qualcosa sul Mailslot, fa riferimento ad un oggetto Event che viene settato signaled dall'applicazione Native subito dopo la scrittura del testo sul Mailslot) e visualizzerà il contenuto sulla console. I sorgenti sono semplicissimi.

6. Conclusioni

Siamo arrivati alla fine di questo buttasù di materiale inerente la realizzazione di applicativi Native: che dire ... son finalmente riuscito a mettere un pò di ordine tra gli appunti.

 

 

 
 
Your Ad Here