Oghje, cuntinueremu a nostra storia nantu à cumu travagliamu cù l'Università Innopolis per sviluppà a tecnulugia Active Restore per permette à l'utilizatori di ripiglià u travagliu nantu à e so macchine u più prestu pussibule dopu un crash. Parleremu di l'applicazioni native. Windows, включая особенности их создания и запуска. Под катом – немного о нашем проекте, а также практическое руководство как писать нативные приложения.

In i posti precedenti avemu digià parlatu di ciò chì hè , è cumu si sviluppanu i studienti di Innopolis . Oghje vogliu fucalizza nantu à l'applicazioni native, à u livellu di quale vulemu "sepultà" u nostru serviziu di ricuperazione attiva. Se tuttu funziona, allora seremu capaci di:
- Lanciate u serviziu stessu assai prima
- Cuntattate u nuvulu induve a copia di salvezza hè situata assai prima
- Moltu prima per capisce in quale modu hè u sistema - boot normale o ricuperazione
- Assai menu schedari da ricuperà in anticipu
- Permette à l'utilizatore per cumincià ancu più veloce.
Cosa hè una app nativa in ogni casu?
Per risponde à sta quistione, fighjemu a sequenza di chjamate chì u sistema face, per esempiu, se un programatore in a so applicazione prova di creà un schedariu.

Pavel Yosifovich — Windows Kernel Programming (2019)
U programatore usa a funzione , chì hè dichjaratu in u file d'intestazione fileapi.h è implementatu in Kernel32.dll. Tuttavia, sta funzione ùn crea micca u schedariu, solu verifica l'argumenti di input è chjama a funzione (u prefissu Nt indica solu chì a funzione hè nativa). Sta funzione hè dichjarata in u schedariu di l'header winternl.h è implementata in ntdll.dll. Si prepara à saltà in u spaziu nucleari, dopu chì face una chjama di u sistema per creà un schedariu. In questu casu, risulta chì Kernel32 hè solu un wrapper per Ntdll. Unu di i mutivi per quessa hè statu fattu hè chì Microsoft hà cusì a capacità di cambià e funzioni di u mondu nativu, ma micca toccu l'interfaccia standard. Microsoft ùn ricumande micca di chjamà funzioni native direttamente è ùn documenta micca a maiò parte di elli. Per via, e funzioni senza documentu ponu esse truvate .
U vantaghju principali di l'applicazioni native hè chì ntdll hè caricatu in u sistema assai prima di kernel32. Questu hè logicu, perchè u kernel32 richiede ntdll per travaglià. In u risultatu, l'applicazioni chì utilizanu funzioni native ponu cumincià à travaglià assai prima.
Cusì, Windows Native Applications – это программы, способные запускаться на раннем этапе загрузки Windows. Они используют ТОЛЬКО функции из ntdll. Пример такого приложения: chì faci per verificà u discu per errori prima di inizià i servizii principali. Questu hè esattamente u livellu chì vulemu chì u nostru Active Restore sia.
Chì avemu bisognu ?
- (Driver Development Kit), ныне также известный под названием WDK 7 (Windows Driver Kit).
- Виртуальная машина (например, Windows 7 x 64)
- Ùn hè micca necessariu, ma i fugliali di header chì ponu esse scaricati ponu aiutà
Chì ci hè in u codice?
Pratichemu un pocu è, per esempiu, scrivite una piccula applicazione chì:
- Mostra un missaghju nantu à u screnu
- Alloca un pocu di memoria
- Aspetta l'input di u teclatu
- Libera a memoria usata
In l'applicazioni native, u puntu di ingressu ùn hè micca principalu o winmain, ma a funzione NtProcessStartup, postu chì in realtà lanciamu direttamente novi prucessi in u sistema.
Accuminciamu per vede un missaghju nantu à u screnu. Per questu avemu una funzione nativa , chì piglia cum'è argumentu un punteru à un oggettu di struttura UNICODE_STRING. RtlInitUnicodeString ci aiuterà à inizializzallu. In u risultatu, per vede u testu nantu à u screnu pudemu scrive sta piccula funzione:
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}Siccomu solu e funzioni da ntdll sò dispunibuli per noi, è ùn ci sò simpliciamente altre biblioteche in memoria, certamenti averemu prublemi cù cumu per assignà memoria. U novu operatore ùn esiste ancu (perchè vene da u mondu troppu altu di C++), è ùn ci hè micca una funzione malloc (esige biblioteche C runtime). Di sicuru, pudete aduprà solu una pila. Ma s'ellu ci vole à assignà dinamicamente a memoria, avemu da fà nantu à u munzeddu (vale à dì heap). Allora creemu un munzeddu per noi stessi è pigliamu a memoria da ellu ogni volta chì avemu bisognu.
A funzione hè adattata per questu compitu . In seguitu, usendu RtlAllocateHeap è RtlFreeHeap, occupemu è libereremu memoria quandu avemu bisognu.
PVOID memory = NULL;
PVOID buffer = NULL;
ULONG bufferSize = 42;
// create heap in order to allocate memory later
memory = RtlCreateHeap(
HEAP_GROWABLE,
NULL,
1000,
0, NULL, NULL
);
// allocate buffer of size bufferSize
buffer = RtlAllocateHeap(
memory,
HEAP_ZERO_MEMORY,
bufferSize
);
// free buffer (actually not needed because we destroy heap in next step)
RtlFreeHeap(memory, 0, buffer);
RtlDestroyHeap(memory);Passemu à aspittà l'input di u teclatu.
// https://docs.microsoft.com/en-us/windows/win32/api/ntddkbd/ns-ntddkbd-keyboard_input_data
typedef struct _KEYBOARD_INPUT_DATA {
USHORT UnitId;
USHORT MakeCode;
USHORT Flags;
USHORT Reserved;
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
//...
HANDLE hKeyBoard, hEvent;
UNICODE_STRING skull, keyboard;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK Iosb;
LARGE_INTEGER ByteOffset;
KEYBOARD_INPUT_DATA kbData;
// inialize variables
RtlInitUnicodeString(&keyboard, L"DeviceKeyboardClass0");
InitializeObjectAttributes(&ObjectAttributes, &keyboard, OBJ_CASE_INSENSITIVE, NULL, NULL);
// open keyboard device
NtCreateFile(&hKeyBoard,
SYNCHRONIZE | GENERIC_READ | FILE_READ_ATTRIBUTES,
&ObjectAttributes,
&Iosb,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN,FILE_DIRECTORY_FILE,
NULL, 0);
// create event to wait on
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, &ObjectAttributes, 1, 0);
while (TRUE)
{
NtReadFile(hKeyBoard, hEvent, NULL, NULL, &Iosb, &kbData, sizeof(KEYBOARD_INPUT_DATA), &ByteOffset, NULL);
NtWaitForSingleObject(hEvent, TRUE, NULL);
if (kbData.MakeCode == 0x01) // if ESC pressed
{
break;
}
}Tuttu ciò chì avemu bisognu hè di utilizà nantu à un dispositivu apertu, è aspettate finu à chì u teclatu torna ogni pressa à noi. Se a chjave ESC hè pressata, continueremu à travaglià. Per apre u dispusitivu, avemu bisognu di chjamà a funzione NtCreateFile (avemu bisognu di apre DeviceKeyboardClass0). Chjameremu ancu per inizializà l'ughjettu di aspittà. Dichjaremu noi stessi a struttura KEYBOARD_INPUT_DATA, chì rapprisenta i dati di u teclatu. Questu farà u nostru travagliu più faciule.
L'applicazione nativa finisce cù una chjama di funzione perchè simpricimenti uccidemu u nostru prucessu.
Tuttu u codice per a nostra piccula applicazione:
#include "ntifs.h" // WinDDK7600.16385.1incddk
#include "ntdef.h"
//------------------------------------
// Following function definitions can be found in native development kit
// but I am too lazy to include `em so I declare it here
//------------------------------------
NTSYSAPI
NTSTATUS
NTAPI
NtTerminateProcess(
IN HANDLE ProcessHandle OPTIONAL,
IN NTSTATUS ExitStatus
);
NTSYSAPI
NTSTATUS
NTAPI
NtDisplayString(
IN PUNICODE_STRING String
);
NTSTATUS
NtWaitForSingleObject(
IN HANDLE Handle,
IN BOOLEAN Alertable,
IN PLARGE_INTEGER Timeout
);
NTSYSAPI
NTSTATUS
NTAPI
NtCreateEvent(
OUT PHANDLE EventHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
IN EVENT_TYPE EventType,
IN BOOLEAN InitialState
);
// https://docs.microsoft.com/en-us/windows/win32/api/ntddkbd/ns-ntddkbd-keyboard_input_data
typedef struct _KEYBOARD_INPUT_DATA {
USHORT UnitId;
USHORT MakeCode;
USHORT Flags;
USHORT Reserved;
ULONG ExtraInformation;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;
//----------------------------------------------------------
// Our code goes here
//----------------------------------------------------------
// usage: WriteLn(L"Hello Native World!n");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}
void NtProcessStartup(void* StartupArgument)
{
// it is important to declare all variables at the beginning
HANDLE hKeyBoard, hEvent;
UNICODE_STRING skull, keyboard;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK Iosb;
LARGE_INTEGER ByteOffset;
KEYBOARD_INPUT_DATA kbData;
PVOID memory = NULL;
PVOID buffer = NULL;
ULONG bufferSize = 42;
//use it if debugger connected to break
//DbgBreakPoint();
WriteLn(L"Hello Native World!n");
// inialize variables
RtlInitUnicodeString(&keyboard, L"DeviceKeyboardClass0");
InitializeObjectAttributes(&ObjectAttributes, &keyboard, OBJ_CASE_INSENSITIVE, NULL, NULL);
// open keyboard device
NtCreateFile(&hKeyBoard,
SYNCHRONIZE | GENERIC_READ | FILE_READ_ATTRIBUTES,
&ObjectAttributes,
&Iosb,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN,FILE_DIRECTORY_FILE,
NULL, 0);
// create event to wait on
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL);
NtCreateEvent(&hEvent, EVENT_ALL_ACCESS, &ObjectAttributes, 1, 0);
WriteLn(L"Keyboard readyn");
// create heap in order to allocate memory later
memory = RtlCreateHeap(
HEAP_GROWABLE,
NULL,
1000,
0, NULL, NULL
);
WriteLn(L"Heap readyn");
// allocate buffer of size bufferSize
buffer = RtlAllocateHeap(
memory,
HEAP_ZERO_MEMORY,
bufferSize
);
WriteLn(L"Buffer allocatedn");
// free buffer (actually not needed because we destroy heap in next step)
RtlFreeHeap(memory, 0, buffer);
RtlDestroyHeap(memory);
WriteLn(L"Heap destroyedn");
WriteLn(L"Press ESC to continue...n");
while (TRUE)
{
NtReadFile(hKeyBoard, hEvent, NULL, NULL, &Iosb, &kbData, sizeof(KEYBOARD_INPUT_DATA), &ByteOffset, NULL);
NtWaitForSingleObject(hEvent, TRUE, NULL);
if (kbData.MakeCode == 0x01) // if ESC pressed
{
break;
}
}
NtTerminateProcess(NtCurrentProcess(), 0);
}PS: Pudemu facilmente aduprà a funzione DbgBreakPoint () in u nostru codice per piantà in u debugger. Hè veru, avete bisognu di cunnette WinDbg à una macchina virtuale per a debugging di u kernel. Istruzzioni nantu à cumu fà questu si ponu truvà o solu aduprà .
Cumpilazione è assemblea
U modu più faciule per custruisce una applicazione nativa hè di utilizà (Kit di sviluppu di u driver). Avemu bisognu di l'antica settima versione, postu chì e versioni più tardi anu un accostu un pocu sfarente è travaglià strettamente cù Visual Studio. Sè avemu aduprà u DDK, allura u nostru prughjettu solu bisognu Makefile è fonti.
makefile
!INCLUDE $(NTMAKEENV)makefile.deffonti:
TARGETNAME = MyNative
TARGETTYPE = PROGRAM
UMTYPE = nt
BUFFER_OVERFLOW_CHECKS = 0
MINWIN_SDK_LIB_PATH = $(SDK_LIB_PATH)
SOURCES = source.c
INCLUDES = $(DDK_INC_PATH);
C:WinDDK7600.16385.1ndk;
TARGETLIBS = $(DDK_LIB_PATH)ntdll.lib
$(DDK_LIB_PATH)nt.lib
USE_NTDLL = 1U vostru Makefile serà esattamente u listessu, ma fighjemu e fonti in un pocu più di dettu. Stu schedariu specifica i fonti di u vostru prugramma (.c files), l'opzioni di custruzzione, è altri paràmetri.
- TARGETNAME - u nome di u schedariu eseguibile chì deve esse pruduttu à a fine.
- TARGETTYPE - tipu di schedariu eseguibile, pò esse un driver (.sys), allora u valore di u campu deve esse DRIVER, se una biblioteca (.lib), allora u valore hè LIBRARY. In u nostru casu, avemu bisognu di un schedariu eseguibile (.exe), cusì avemu stabilitu u valore à PROGRAM.
- UMTYPE - valori pussibuli per questu campu: cunsola per una applicazione di cunsola, Windows per travaglià in modalità finestra. Ma avemu bisognu di specificà nt per uttene una applicazione nativa.
- BUFFER_OVERFLOW_CHECKS - cuntrollà a pila per u buffer overflow, sfurtunatamenti micca u nostru casu, l'avemu disattivatu.
- MINWIN_SDK_LIB_PATH - stu valore si riferisce à a variabile SDK_LIB_PATH, ùn vi preoccupate micca chì ùn avete micca una tale variabile di u sistema dichjarata, quandu eseguimu verificatu build da u DDK, sta variabile serà dichjarata è indicà à e librerie necessarie.
- SOURCES - una lista di fonti per u vostru prugramma.
- INCLUDE - i fugliali d'intestazione chì sò necessarii per l'assemblea. Quì sò generalmente indicanu a strada di i schedari chì venenu cù u DDK, ma pudete ancu specificà qualsiasi altri.
- TARGETLIBS - lista di biblioteche chì deve esse ligati.
- USE_NTDLL hè un campu obligatoriu chì deve esse stabilitu à 1 per ragioni evidenti.
- USER_C_FLAGS - qualsiasi bandiera chì pudete aduprà in direttive di preprocessore quandu preparanu u codice di l'applicazione.
Allora per custruisce, avemu bisognu di eseguisce x86 (o x64) Checked Build, cambiate u repertoriu di travagliu in u cartulare di u prughjettu è eseguite u cumandimu Build. U risultatu in a screenshot mostra chì avemu un schedariu eseguibile.

Stu schedariu ùn pò esse lanciatu cusì faciule, u sistema maledicà è ci manda à pensà à u so cumpurtamentu cù l'errore seguente:

Cumu lancià una applicazione nativa?
Quandu l'autochk principia, a sequenza di startup di i prugrammi hè determinata da u valore di a chjave di u registru:
HKLMSystemCurrentControlSetControlSession ManagerBootExecuteU manager di sessione eseguisce i prugrammi da sta lista unu per unu. U gestore di sessione cerca i fugliali eseguibili stessi in u cartulare system32. U furmatu di u valore di chjave di u registru hè cusì:
autocheck autochk *MyNativeU valore deve esse in formatu esadecimale, micca u solitu ASCII, cusì a chjave mostrata sopra serà in u formatu:
61,75,74,6f,63,68,65,63,6b,20,61,75,74,6f,63,68,6b,20,2a,00,4d,79,4e,61,74,69,76,65,00,00Per cunvertisce u titulu, pudete aduprà un serviziu in linea, per esempiu, .

Ci hè chì per lancià una applicazione nativa, avemu bisognu:
- Copia u schedariu eseguibile in u cartulare system32
- Aghjunghjite una chjave à u registru
- Reboot a macchina
Per comodità, quì hè un script prontu per installà una applicazione nativa:
install.bat
@echo off
copy MyNative.exe %systemroot%system32.
regedit /s add.reg
echo Native Example Installed
pauseadd.reg
REGEDIT4
[HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession Manager]
"BootExecute"=hex(7):61,75,74,6f,63,68,65,63,6b,20,61,75,74,6f,63,68,6b,20,2a,00,4d,79,4e,61,74,69,76,65,00,00Dopu a stallazione è u reboot, ancu prima chì a pantalla di selezzione di l'utilizatori apparisce, averemu a seguente stampa:

U risultatu
На примере вот такого маленького приложения мы убедились, что запустить приложение на уровне Windows Native вполне возможно. Дальше мы с ребятами из Университета Иннополис продолжим строить сервис, который будет инициировать процесс взаимодействия с драйвером намного раньше, чем в предыдущей версии нашего проекта. А с появлением оболочки win32 логично будет передать управление полноценному сервису, который уже был разработан (об этом подробнее ).
In u prossimu articulu, tuccheremu un altru cumpunente di u serviziu Active Restore, vale à dì u driver UEFI. Abbonate à u nostru blog per ùn mancate micca u prossimu post.
Source: www.habr.com
