Šodien turpinām stāstu par to, kā kopā ar puišiem no Innopolis universitātes izstrādājam tehnoloģiju Active Restore, lai ļautu lietotājam pēc iespējas ātrāk atsākt darbu savā datorā pēc avārijas. Mēs runāsim par Windows lietotnēm, tostarp to izstrādes un palaišanas īpatnībām. Zemāk ir nedaudz informācijas par mūsu projektu, kā arī praktisks ceļvedis, kā rakstīt nativās lietotnes.

Iepriekšējās ziņās mēs jau runājām par to, kas ir , un kā studenti no Innopolis to izstrādā. Šodien es vēlos pievērst uzmanību nativām lietotnēm, līdz kurām mēs vēlamies "iestrādāt" mūsu aktīvās atjaunošanas pakalpojumu. Ja viss izdosies, tad mēs spēsim:
- Ieslēgt pašu pakalpojumu daudz agrāk
- Šķirties no mākonis, kurā atrodas dublējums, daudz agrāk
- Saprast, kādā režīmā atrodas sistēma – normālā palaišanā vai atjaunošanā, daudz agrāk
- Atjaunot daudz mazāk failu iepriekš
- Ļaut lietotājam ātrāk uzsākt darbu.
Kas vispār ir nativa lietotne?
Lai atbildētu uz šo jautājumu, aplūkosim izsaukumu virkni, ko veic sistēma, piemēram, ja programmētājs savā lietotnē mēģina izveidot failu.

Pavel Yosifovich — Windows Kernel Programming (2019)
Programmētājs izmanto funkciju , kas ir definēta galvenes failā fileapi.h un realizēta Kernel32.dll. Tomēr šī funkcija neizveido failu, tā vienkārši pārbauda izejas argumentus un izsauc funkciju (prefikss Nt tieši norāda, ka funkcija ir nativa). Šī funkcija ir definēta galvenes failā winternl.h un realizēta ntdll.dll. Tā veic sagatavošanos lekt kodola telpā, pēc kā tiek veikts sistēmas izsaukums, lai izveidotu failu. Tādējādi izrādās, ka Kernel32 ir tikai apvalks ap Ntdll. Viens no iemesliem, kāpēc tā tas tiek darīts, Microsoft tādejādi var izmainīt nativā pasaulē esošās funkcijas, neietekmējot standarta saskarnes. Microsoft neiesaka tieši izsaukt nativās funkcijas un nenorāda lielāko daļu no tām. Starp citu, nepublicētās funkcijas var atrast. .
Galvenā noderīgo lietotņu priekšrocība ir tā, ka ntdll tiek ielādēta sistēmā ievērojami ātrāk nekā kernel32. Tas ir loģiski, jo kernel32 prasa ntdll darbībai. Tādēļ lietotnes, kas izmanto noderīgas funkcijas, var uzsākt darbību ievērojami agrāk.
Tādējādi, Windows Native Applications ir programmas, kas spēj tikt uzsāktas agrīnā Windows ielādes posmā. Tās izmanto TIKAI funkcijas no ntdll. Šādas lietotnes piemērs ir: , kas izpilda disku pārbaudei uz kļūdām vēl pirms galveno pakalpojumu palaišanas. Tieši šādā līmenī mēs vēlamies redzēt mūsu Active Restore.
Kas mums būs nepieciešams?
- (Driver Development Kit), tagad pazīstams arī kā WDK 7 (Windows Driver Kit).
- Virtuālā mašīna (piemēram, Windows 7 x64)
- Tas nav obligāti, bet var palīdzēt virsrakstu faili, kurus var lejupielādēt.
Kas tad kodā?
Sāksim nedaudz trenēties un kā piemēru uzrakstīsim nelielu lietotni, kas:
- Izejas ziņu ekrānā
- Alocē nedaudz atmiņas
- Gaida ievadi no tastatūras
- Atbrīvo aizņemto atmiņu
Noderīgajās lietotnēs ieejas punkts nav mainīgais, vai winmain, bet gan funkcija NtProcessStartup, jo mēs faktiski tieši uzsākam jaunu procesu sistēmā.
Sāksim ar ziņas parādīšanu ekrānā. Tam mēs izmantojam noderīgo funkciju , kas kā argumentu pieņem rādītāju uz UNICODE_STRING struktūras objektu. To mēs varam inicializēt, izmantojot RtlInitUnicodeString. Rezultātā, ziņas izvadīšanai uz ekrāna varam uzrakstīt šādu nelielu funkciju:
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}Tā kā mums ir pieejamas tikai funkcijas no ntdll, un citu bibliotēku atmiņā vienkārši vēl nav, mums neizbēgami radīsies problēmas ar atmiņas alocēšanu. Jaunā operators vēl nav pieejams (jo tas nāk no pārāk augsta līmeņa C++ pasaules), kā arī nav funkcijas malloc (tai nepieciešamas C runtime bibliotēkas). Protams, varam izmantot tikai steku. Bet, ja mums ir nepieciešams dinamiski alocēt atmiņu, būs jādara to kaudzē (t.i., heap). Tāpēc šai vajadzībai izveidosim kaudzi un ņemsim no tās atmiņu, kad tas būs nepieciešams.
Šai uzdevumam derēs funkcija . Pēc tam, izmantojot RtlAllocateHeap un RtlFreeHeap, mēs būs brīvi izmantojuši un atbrīvojuši atmiņu, kad tas būs nepieciešams.
PVOID memory = NULL;
PVOID buffer = NULL;
ULONG bufferSize = 42;
// izveidojiet kaudzi, lai vēlāk piešķirtu atmiņu
memory = RtlCreateHeap(
HEAP_GROWABLE,
NULL,
1000,
0, NULL, NULL
);
// piešķiriet buferi ar izmēru bufferSize
buffer = RtlAllocateHeap(
memory,
HEAP_ZERO_MEMORY,
bufferSize
);
// atbrīvo buferi (faktiski tas nav nepieciešams, jo nākamajā solī mēs iznīcinām kaudzi)
RtlFreeHeap(memory, 0, buffer);
RtlDestroyHeap(memory);Pāriesim pie ievades gaidīšanas no tastatūras.
// 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;
}
}Viss, kas mums nepieciešams, – ir izmantot atvērtajā ierīcē un gaidīt, kamēr tastatūra atgriezīs kādu nospiešanu. Ja tiek nospiesta taustiņš ESC, mēs turpināsim darbu. Lai atvērtu ierīci, mums būs jāizsauc funkcija NtCreateFile (jāatver būs DeviceKeyboardClass0). Mēs arī izsauksim , lai inicializētu gaidīšanas objektu. Mēs patstāvīgi paziņosim struktūru KEYBOARD_INPUT_DATA, kas attēlo tastatūras datus. Tas atvieglos mūsu darbu.
Natīvas aplikācijas darbība tiek pabeigta, izsaucot funkciju , jo mēs vienkārši nogalina savu pašu procesu.
Visu mūsu nelielās aplikācijas kodu:
#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: Mēs varam viegli izmantot funkciju DbgBreakPoint() kodā, lai apstātos debuggēšanā. Patiesībā jābūt pievienotam WinDbg virtuaļai mašīnai, lai veiktu kernela debuggēšanu. Instrukciju kā to izdarīt var atrast vai vienkārši izmantot .
Kompilācija un salikšana
Vienkāršākais veids, kā savākt natīvu aplikāciju, ir izmantot (Driver Development Kit). Mums nepieciešama tieši senā septītā versija, jo vēlākās versijas ir nedaudz citādas un cieši strādā ar Visual Studio. Ja izmantosim DDK, tad mūsu projektam būs nepieciešami tikai Makefile un sources.
Makefile
!INCLUDE $(NTMAKEENV)makefile.defsources:
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 = 1Jūsu Makefile būs tieši tāds pats, par sources mazliet detalizētāk apstāsimies. Šajā failā norādīti jūsu programmas avoti (faili .c), kompilācijas opcijas un citi parametri.
- TARGETNAME – izpildāmā faila nosaukums, kas beigās jāiegūst.
- TARGETTYPE – izpildāmā faila tips, tas var būt draiveris (.sys), tad lauka vērtībai jābūt DRIVER, ja bibliotēka (.lib), tad vērtība LIBRARY. Mūsu gadījumā nepieciešams izpildāmais fails (.exe), tādēļ mēs iestatām vērtību PROGRAM.
- UMTYPE – iespējamās vērtības šim laukam: console konsoles lietojumprogrammām, windows darbam logu režīmā. Taču mums jānorāda nt, lai iegūtu nativu lietojumprogrammu.
- BUFFER_OVERFLOW_CHECKS – steka pārbaude uz buferu pārsniegšanu, diemžēl tas nav mūsu gadījums, izslēdzam.
- MINWIN_SDK_LIB_PATH – šī vērtība attiecas uz mainīgo SDK_LIB_PATH, nav jāuztraucas, ka jums nav deklarētas šādas sistēmas mainīgās, kad mēs palaidīsim checked build no DDK, šī mainīgā tiks deklarēta un norādīs uz nepieciešamajām bibliotēkām.
- SOURCES – jūsu programmas avotu saraksts.
- INCLUDES – galvene faili, kas nepieciešami kompilācijai. Šeit parasti norāda ceļu uz failiem, kas nāk komplektā ar DDK, taču jūs varat norādīt arī jebkura cita papildu faila ceļu.
- TARGETLIBS – bibliotēku saraksts, kas jāpievieno.
- USE_NTDLL – obligāts lauks, kas jāiestata uz 1. Pamatota iemesla dēļ.
- USER_C_FLAGS – jebkuri karogus, kurus varat izmantot priekšapstrādes direktīvās, gatavojot lietojumprogrammas kodu.
Tādēļ, lai veiktu kompilāciju, mums jāpalaiž x86 (vai x64) Checked Build, jāmaina darba katalogs uz projekta mapi un jāizpilda komanda Build. Rezultāts ekrānšāviņā parāda, ka esam izveidojuši vienu izpildāmo failu.

Šo failu nebūs viegli palaist, sistēma sūdzas un nosūta mūs apsvērt tās uzvedību ar nākamo kļūdu:

Kā palaist nativu lietojumprogrammu?
Palaides brīdī autochk programmu palaišanas secību nosaka reģistra atslēgas vērtība:
HKLMSystemCurrentControlSetControlSession ManagerBootExecuteSesijas menedžeris secīgi izpilda programmas no šī saraksta. Paši izpildāmie faili sesijas menedžeris meklē direkotorijā system32. Reģistra atslēgas vērtības formāts ir šāds:
autocheck autochk *MyNativeVērtībai jābūt heksadecimālā formātā un nevis ierastajā ASCII, līdz ar to iepriekš minētā atslēga būs formātā:
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,00Lai konvertētu nosaukumu, varat izmantot tiešsaistes pakalpojumu, piemēram, .

Tādējādi, lai palaistu nativu lietojumprogrammu, mums ir jā:
- Kopējiet izpildāmo failu uz mapi system32
- Pievienojiet reģistram atslēgu
- Restartējiet mašīnu
Lai atvieglotu, šeit jums ir gatavs skripts nativas lietojumprogrammas uzstādīšanai:
install.bat
@echo off
copy MyNative.exe %systemroot%system32.
regedit /s add.reg
echo Native Example Installed
pauseadd.reg
REGEDIT4
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session 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,00Pēc instalēšanas un restartēšanas pirms lietotāju izvēles ekrāna mēs iegūsim šādu ainu:

Rezultāts
Piemērā ar šādu mazu lietotni mēs pārliecinājāmies, ka palaist lietotni Windows Native līmenī ir pilnīgi iespējams. Turpmāk mēs kopā ar Innopolis Universitātes puišiem turpināsim veidot pakalpojumu, kas iniciēs mijiedarbības procesu ar vadītāju daudz agrāk, nekā iepriekšējā mūsu projekta versijā. Un ar win32 saskarnes parādīšanos ir loģiski nodot kontroli pilnvērtīgam pakalpojumam, kas jau ir izstrādāts (par to sīkāk ).
Nākamajā rakstā mēs pievērsīsimies vēl vienam Active Restore pakalpojuma komponentam, proti, UEFI vadītājam. Sekojiet mūsu emuāram, lai nepalaistu garām nākamo ierakstu.
Avots: habr.com
