I dag fortsetter vi historien vår om hvordan vi samarbeider med Innopolis University for å utvikle Active Restore-teknologi som lar brukere gjenoppta arbeidet på maskinene sine så raskt som mulig etter et krasj. Vi skal snakke om native apper. Windows, inkludert detaljene rundt opprettelsen og lanseringen. Nedenfor finner du litt bakgrunnsinformasjon om prosjektet vårt, samt en praktisk veiledning om hvordan man skriver native apper.

I tidligere innlegg har vi allerede snakket om hva det er , og hvordan studenter fra Innopolis utvikler seg . I dag ønsker jeg å fokusere på innfødte applikasjoner, til det nivået vi ønsker å "begrave" vår aktive gjenopprettingstjeneste. Hvis alt ordner seg, vil vi kunne:
- Start selve tjenesten mye tidligere
- Kontakt skyen der sikkerhetskopien er plassert mye tidligere
- Mye tidligere for å forstå hvilken modus systemet er i - normal oppstart eller gjenoppretting
- Mye færre filer å gjenopprette på forhånd
- La brukeren komme i gang enda raskere.
Hva er egentlig en innebygd app?
For å svare på dette spørsmålet, la oss se på sekvensen av samtaler som systemet foretar, for eksempel hvis en programmerer i applikasjonen hans prøver å lage en fil.

Pavel Josifovich — Windows Kjerneprogrammering (2019)
Programmereren bruker funksjonen , som er deklarert i overskriftsfilen fileapi.h og implementert i Kernel32.dll. Denne funksjonen i seg selv oppretter imidlertid ikke filen, den sjekker bare input-argumentene og kaller opp funksjonen (prefikset Nt indikerer bare at funksjonen er opprinnelig). Denne funksjonen er deklarert i overskriftsfilen winternl.h og implementert i ntdll.dll. Den forbereder seg på å hoppe inn i kjernefysisk rom, hvoretter den foretar et systemanrop for å lage en fil. I dette tilfellet viser det seg at Kernel32 bare er en innpakning for Ntdll. En av grunnene til at dette ble gjort er at Microsoft dermed har muligheten til å endre funksjonene til den innfødte verden, men ikke berøre standardgrensesnittene. Microsoft anbefaler ikke å kalle opp opprinnelige funksjoner direkte og dokumenterer ikke de fleste av dem. Forresten, udokumenterte funksjoner kan finnes .
Den største fordelen med native applikasjoner er at ntdll lastes inn i systemet mye tidligere enn kernel32. Dette er logisk, fordi kernel32 krever at ntdll fungerer. Som et resultat kan applikasjoner som bruker innebygde funksjoner begynne å fungere mye tidligere.
Dermed blir Windows Native applikasjoner er programmer som kan kjøre tidlig under oppstart. WindowsDe bruker KUN funksjoner fra ntdll. Et eksempel på et slikt program: som opptrer for å sjekke disken for feil før du starter hovedtjenestene. Dette er akkurat det nivået vi vil at vår Active Restore skal være.
Hva trenger vi?
- (Driverutviklingssett), nå også kjent som WDK 7 (Windows Driversett).
- Virtuell maskin (f.eks. Windows 7 x 64)
- Ikke nødvendig, men overskriftsfiler som kan lastes ned kan hjelpe
Hva står i koden?
La oss øve litt og for eksempel skrive en liten søknad som:
- Viser en melding på skjermen
- Tildeler noe minne
- Venter på tastaturinndata
- Frigjør brukt minne
I native applikasjoner er ikke inngangspunktet hoved- eller winmain, men NtProcessStartup-funksjonen, siden vi faktisk starter nye prosesser i systemet direkte.
La oss starte med å vise en melding på skjermen. For dette har vi en innfødt funksjon , som tar som argument en peker til et UNICODE_STRING-strukturobjekt. RtlInitUnicodeString vil hjelpe oss å initialisere den. Som et resultat, for å vise tekst på skjermen, kan vi skrive denne lille funksjonen:
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}Siden bare funksjoner fra ntdll er tilgjengelige for oss, og det rett og slett ikke er andre biblioteker i minnet ennå, vil vi definitivt ha problemer med hvordan vi skal allokere minne. Den nye operatøren eksisterer ikke ennå (fordi den kommer fra den for høye verdenen av C++), og det er ingen malloc-funksjon (den krever runtime C-biblioteker). Selvfølgelig kan du bare bruke en stabel. Men hvis vi trenger å tildele minne dynamisk, må vi gjøre det på haugen (dvs. haugen). Så la oss lage en haug for oss selv og ta minnet fra den når vi trenger det.
Funksjonen er egnet for denne oppgaven . Deretter, ved å bruke RtlAllocateHeap og RtlFreeHeap, vil vi okkupere og frigjøre minne når vi trenger det.
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);La oss gå videre til å vente på tastaturinndata.
// 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;
}
}Alt vi trenger er å bruke på en åpen enhet, og vent til tastaturet returnerer et trykk til oss. Hvis ESC-tasten trykkes, fortsetter vi arbeidet. For å åpne enheten må vi kalle opp NtCreateFile-funksjonen (vi må åpne DeviceKeyboardClass0). Vi ringer også for å initialisere venteobjektet. Vi vil erklære KEYBOARD_INPUT_DATA-strukturen selv, som representerer tastaturdataene. Dette vil gjøre arbeidet vårt enklere.
Den opprinnelige applikasjonen avsluttes med et funksjonskall fordi vi rett og slett dreper vår egen prosess.
All koden for vår lille applikasjon:
#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: Vi kan enkelt bruke funksjonen DbgBreakPoint() i koden vår for å stoppe den i debuggeren. Riktignok må du koble WinDbg til en virtuell maskin for kjernefeilsøking. Instruksjoner for hvordan du gjør dette finner du eller bare bruke .
Sammenstilling og montering
Den enkleste måten å bygge en innebygd applikasjon på er å bruke (Driver Development Kit). Vi trenger den gamle syvende versjonen, siden senere versjoner har en litt annen tilnærming og jobber tett med Visual Studio. Hvis vi bruker DDK, trenger prosjektet vårt bare Makefile og kilder.
Makefile
!INCLUDE $(NTMAKEENV)makefile.defkilder:
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 = 1Makefilen din vil være nøyaktig den samme, men la oss se på kildene litt mer detaljert. Denne filen spesifiserer programmets kilder (.c-filer), byggealternativer og andre parametere.
- TARGETNAME – navnet på den kjørbare filen som skal produseres til slutt.
- TARGETTYPE – type kjørbar fil, det kan være en driver (.sys), så skal feltverdien være DRIVER, hvis et bibliotek (.lib), så er verdien LIBRARY. I vårt tilfelle trenger vi en kjørbar fil (.exe), så vi setter verdien til PROGRAM.
- UMTYPE - mulige verdier for dette feltet: konsoll for en konsollapplikasjon, vinduer for å jobbe i vindusmodus. Men vi må spesifisere nt for å få en innfødt applikasjon.
- BUFFER_OVERFLOW_CHECKS – sjekker stabelen for bufferoverløp, dessverre ikke vårt tilfelle, vi slår den av.
- MINWIN_SDK_LIB_PATH – denne verdien refererer til SDK_LIB_PATH-variabelen, ikke bekymre deg for at du ikke har en slik systemvariabel deklarert, når vi kjører sjekket build fra DDK, vil denne variabelen bli deklarert og vil peke til de nødvendige bibliotekene.
- KILDER – en liste over kilder for programmet ditt.
- INKLUDERER – overskriftsfiler som kreves for montering. Her angir de vanligvis banen til filene som følger med DDK, men du kan i tillegg spesifisere andre.
- TARGETLIBS – liste over biblioteker som må kobles sammen.
- USE_NTDLL er et obligatorisk felt som må settes til 1 av åpenbare grunner.
- USER_C_FLAGS – alle flagg som du kan bruke i preprocessor-direktiver når du forbereder applikasjonskode.
Så for å bygge, må vi kjøre x86 (eller x64) Sjekket Build, endre arbeidskatalogen til prosjektmappen og kjøre Build-kommandoen. Resultatet i skjermbildet viser at vi har én kjørbar fil.

Denne filen kan ikke startes så lett, systemet forbanner og sender oss til å tenke på oppførselen med følgende feil:

Hvordan starte en innebygd applikasjon?
Når autochk starter, bestemmes oppstartssekvensen for programmer av verdien til registernøkkelen:
HKLMSystemCurrentControlSetControlSession ManagerBootExecuteSesjonslederen kjører programmer fra denne listen én etter én. Sesjonsbehandleren ser etter de kjørbare filene selv i system32-katalogen. Registernøkkelverdiformatet er som følger:
autocheck autochk *MyNativeVerdien må være i heksadesimalt format, ikke den vanlige ASCII, så nøkkelen vist ovenfor vil være i formatet:
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,00For å konvertere tittelen kan du bruke en nettbasert tjeneste, for eksempel .

Det viser seg at for å starte en innebygd applikasjon trenger vi:
- Kopier den kjørbare filen til system32-mappen
- Legg til en nøkkel til registeret
- Start maskinen på nytt
For enkelhets skyld er her et ferdig skript for å installere en innebygd applikasjon:
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,00Etter installasjon og omstart, selv før brukervalgskjermen vises, får vi følgende bilde:

Total
Ved å bruke denne lille applikasjonen som eksempel, var vi overbevist om at det å lansere en applikasjon på nivå Windows Native er fullt mulig. Deretter skal jeg og gutta fra Innopolis University fortsette å bygge en tjeneste som vil starte interaksjon med driveren mye tidligere enn i den forrige versjonen av prosjektet vårt. Og med fremveksten av Win32-skallet vil det være logisk å overføre kontrollen til en fullverdig tjeneste som allerede er utviklet (mer om det senere). ).
I den neste artikkelen vil vi berøre en annen komponent av Active Restore-tjenesten, nemlig UEFI-driveren. Abonner på bloggen vår slik at du ikke går glipp av neste innlegg.
Kilde: www.habr.com
