TĂ€na jĂ€tkame rÀÀkimist sellest, kuidas koos Innopolis Ălikooli noorte arendame Active Restore tehnoloogiat, et vĂ”imaldada kasutajal vĂ”imalikult kiiresti oma masinal tööle asuda pĂ€rast riket. RÀÀgime Windowsi kohalikke rakendusi, sealhulgas nende loomise ja kĂ€ivitamise eripĂ€radest. Postituse all â natuke meie projektist ning praktiline juhend kohalike rakenduste kirjutamiseks.

Varasemates postitustes oleme juba rÀÀkinud, mis see on , ja kuidas Innopolis ĂŒliĂ”pilased seda arendavad. TĂ€na soovin peatuda kohalikel rakendustel, millesse soovime âkaevataâ meie aktiivse taastamise teenust. Kui kĂ”ik Ă”nnestub, siis suudame:
- Oluliselt varem kÀivitada teenuse
- Oluliselt varem ĂŒhenduda pilvega, kus asub varukoopia
- Oluliselt varem mĂ”ista, millises reĆŸiimis sĂŒsteem asub â tavaliselt kĂ€ivitamisel vĂ”i taastamisel
- Oluliselt vÀhem faile taastuda ette
- Katta kasutaja vÔimalikult kiiresti tööle asuda.
Mis ĂŒldse on kohalik rakendus?
Et sellele kĂŒsimusele vastata, vaatame jĂ€rjestust, mille sĂŒsteem kutsub vĂ€lja, nĂ€iteks siis, kui programmeerija ĂŒritab oma rakenduses faili luua.

Pavel Yosifovich â Windows Kernel Programming (2019)
Programmeerija kasutab funktsiooni , mis on deklareeritud pealkirja failis fileapi.h ja rakendatud Kernel32.dll-is. Kuid see funktsioon ei loo faili, vaid kontrollib sisendi argumente ja kutsub vĂ€lja funktsiooni (Nt eesliide nĂ€itab, et funktsioon on kohaliku). See funktsioon on deklareeritud pealkirja failis winternl.h ja rakendatud ntdll.dll-is. See valmistab ette hĂŒppe kerneli ruumi, seejĂ€rel kutsub vĂ€lja sĂŒsteemikĂ”ne faili loomiseks. Sel juhul selgub, et Kernel32 on vaid Ntdll-i wrapper. Ăks pĂ”hjus, miks see on tehtud, on see, et Microsoftil on vĂ”imalus muuta kohalike maailmade funktsioone, kuid samas mitte segada standardseid liideseid. Microsoft ei soovita otse kutsuda kohalikke funktsioone ega dokumenteeri enamikku neist. Muide, dokumenteerimata funktsioone on vĂ”imalik leida .
Peamine eelis natiivrakenduste puhul on see, et ntdll laaditakse sĂŒsteemi mĂ€rgatavalt varem kui kernel32. See on mĂ”istlik, kuna kernel32 vajab ntdll-i oma toimimiseks. Selle tulemusena saavad rakendused, mis kasutavad natiivseid funktsioone, alustada tööd mĂ€rgatavalt varem.
Seega on Windowsi natiivrakendused programmid, mis suudavad kÀivituda Windowsi kÀivitamise varases etapis. Need kasutavad AINULT ntdll-i funktsioone. Sellise rakenduse nÀide: mis tÀidab kettaka vigade kontrollimiseks juba enne peamiste teenuste laadimist. Just sellisel tasemel tahame nÀha meie Active Restore'i.
Mida me vajame?
- (Driver Development Kit), tuntud ka kui WDK 7 (Windows Driver Kit).
- Virtuaalne masin (nÀiteks Windows 7 x64)
- Ei ole kohustuslik, kuid vÔib aidata pealkirja faile, mida saab alla laadida
Mis on koodis?
Hakkame natuke harjutama ja kirjutame nÀiteks vÀikese rakenduse, mis:
- Kuvab sÔnumi ekraanile
- KÀivitab natuke mÀlu
- Ootab sisendit klaviatuurilt
- Vabastab hÔivatud mÀlu
Natiivrakendustes on sisenemispunktiks mitte main ega winmain, vaid funktsioon NtProcessStartup, kuna me tegelikult kĂ€ivitame uusi protsesse sĂŒsteemis otse.
Alustame ekraanile sÔnumi kuvamisest. Selleks on meil natiivne funktsioon , mis vÔtab argumentidena UNICODE_STRING struktuuri objekti nÀitaja. Selle initsialiseerimiseks aitab meid RtlInitUnicodeString. Tulemuseks, et teksti kuvamiseks ekraanil, saame kirjutada sellise vÀikese funktsiooni:
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}Kuna meile on saadaval ainult ntdll-i funktsioonid ja teisi teeke pole mĂ€lu veel, siis tekivad meil kindlasti probleemid mĂ€lu haldamisega. new operaatorit veel ei eksisteeri (sest see kuulub liiga kĂ”rgema taseme C++ maailma), samuti ei ole funktsiooni malloc (selle jaoks on vajalikke runtime C teeke). Loomulikult vĂ”ib kasutada ainult steeki. Kuid kui peame dĂŒnaamiliselt mĂ€lu haldama, tuleb seda teha kuhjast (tuntud ka kui heap). Seega loome endale kuhja ja vĂ”tame sellest mĂ€lu siis, kui seda vajame.
Selle ĂŒlesande jaoks sobib funktsioon . Edasi liikudes kasutame RtlAllocateHeap ja RtlFreeHeap, et hallata mĂ€lu, kui meil seda vaja on.
PVOID memory = NULL;
PVOID buffer = NULL;
ULONG bufferSize = 42;
// loome vahe mÀlu jaotamiseks hiljem
memory = RtlCreateHeap(
HEAP_GROWABLE,
NULL,
1000,
0, NULL, NULL
);
// jaota buffer suurusega bufferSize
buffer = RtlAllocateHeap(
memory,
HEAP_ZERO_MEMORY,
bufferSize
);
// vabasta buffer (tegelikult ei ole vajalik, kuna hÀvitage vahe jÀrgmises etapis)
RtlFreeHeap(memory, 0, buffer);
RtlDestroyHeap(memory);Liigume edasi ootama klaviatuuri sisendit.
// 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;
}
}KĂ”ik, mida meil on vaja â on kasutada avatud seadmes ja oodata, kuni klaviatuur tagastab meile mingi vajutuse. Kui vajutatakse klahvi ESC, jĂ€tkame tööd. Seadme avamiseks peame kutsuma funktsiooni NtCreateFile (avatakse DeviceKeyboardClass0). Samuti kutsume ĂŒles , et initsialiseerida ootamisobjekt. Me deklareerime ise KEYBOARD_INPUT_DATA struktuuri, mis esindab klaviatuuri andmeid. See lihtsustab meie tööd.
Natiivse rakenduse töö lÔpeb funktsiooni kutsumisega , kuna me lihtsalt tapame oma protsessi.
Kogu meie vÀikese rakenduse kood:
#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: Me vĂ”ime koodis lihtsalt kasutada funktsiooni DbgBreakPoint() debugeerimise peatamiseks. TĂ”si, tuleb WinDbg ĂŒhendada virtuaalmasinaga tuumadebugimiseks. Juhiseid, kuidas seda teha, saab leida vĂ”i lihtsalt kasutada .
Kompileerimine ja kokkupanek
KĂ”ige lihtsam viis natiivse rakenduse kokkupanekuks on kasutada (Driver Development Kit). Me vajame just seda iidset seitsmendat versiooni, kuna hilisemad versioonid töötavad suuresti teistmoodi ja on tihedalt seotud Visual Studioâga. Kui kasutada DDK-d, piisab meie projektist vaid Makefileâist ja allikatest.
Makefile
!INCLUDE $(NTMAKEENV)makefile.defallikad:
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 = 1Teie Makefile on tÀpselt samasugune, allikatele peatume natuke rohkem. Selles failis nÀidatakse teie programmi allikaid (failid .c), kokkupaneku valikuid ja muid parameetreid.
- TARGETNAME â tĂ€idetava faili nimi, mis lĂ”puks peaks valmima.
- TARGETTYPE â tĂ€idetava faili tĂŒĂŒp, see vĂ”ib olla draiver (.sys), siis peab vÀÀrtus olema DRIVER, kui teek (.lib), siis vÀÀrtus LIBRARY. Meie puhul on vajalik tĂ€idetav fail (.exe), seega seame vÀÀrtuseks PROGRAM.
- UMTYPE â selle vĂ€lja vĂ”imalikke vÀÀrtusi: console konsooli rakenduse jaoks, windows aknarakenduse tööks. Kuid me peame mÀÀrama nt, et saada kohaliku rakenduse.
- BUFFER_OVERFLOW_CHECKS â ĂŒlevaatus mĂ€lustaki ĂŒleliigse tĂ€itmise kontrollimiseks, kahjuks ei ole see meie juhtum, lĂŒlitame vĂ€lja.
- MINWIN_SDK_LIB_PATH â see vÀÀrtus viitab muutuja SDK_LIB_PATH, ei maksa muretseda, et sellist sĂŒsteemset muutujat ei ole deklareeritud, hetkel, kui kĂ€ivitame checked build DDKst, see muutuja deklareeritakse ja osutab vajalikele raamatukogudele.
- SOURCES â teie programmi allikate nimekiri.
- INCLUDES â pĂ€isefailid, mis on vajalikud kompileerimiseks. Siin tavaliselt mÀÀratakse tee failidele, mis tulevad koos DDKga, kuid saate lisada ka teised.
- TARGETLIBS â raamatukogude nimekiri, mida tuleb linkida.
- USE_NTDLL â kohustuslik vĂ€ljad, mis tuleb seada vÀÀrtusele 1. Ilmselgetel pĂ”hjustel.
- USER_C_FLAGS â kĂ”ik lipud, mida saate kasutada eelprotsessorite suunistes rakenduse koodi ettevalmistamisel.
KokkuvĂ”ttes, et kompileerida, peame kĂ€ivitama x86 (vĂ”i x64) Checked Build, muutma töökausta projekti kaustaks ja kĂ€ivitama Build kĂ€su. Tulemus ekraanil nĂ€itab, et meil on kogutud ĂŒks tĂ€itmisfail.

Seda faili ei saa lihtsalt kĂ€ivitada, sĂŒsteem nuriseb ja saadab meid mĂ”tlema oma kĂ€itumise jĂ€rgmise veaga:

Kuidas kÀivitada kohalikku rakendust?
KÀivitamise hetkel mÀÀrab autochk programmide kÀivitamise jÀrjekorra registri vÔtme vÀÀrtus:
HKLMSystemCurrentControlSetControlSession ManagerBootExecuteSeansi haldur kÀivitab jÀrjekorras programme sellest nimekirjast. Isegi tÀidetavaid faile otsib seansi haldur direktoriumist system32. Registri vÔtme vÀÀrtuse formaat on jÀrgmine:
autocheck autochk *MyNativeVÀÀrtus peab olema kuuesajandilisel kujul, mitte tavalises ASCII formaadis, seega saab ĂŒlaltoodud vĂ”ti jĂ€rgmise formaadi:
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,00Nime konverteerimiseks saate kasutada veebiteenust, nÀiteks, .

Tuleb vÀlja, et kohalikku rakendust kÀivitamiseks on meil vajalik:
- Kopeerida tÀitmisfail kausta system32
- Lisada registrisse vÔti
- TaaskÀivitada masin
Mugavuse huvides on siin valmis skript kohaliku rakenduse installimiseks:
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Àrast installimist ja taaskÀivitamist, enne kasutajakonto valimise ekraani, nÀeme jÀrgmiseid tulemusi:

KokkuvÔte
Selle vĂ€ikses rakenduses oleme veendunud, et rakenduse kĂ€ivitamine Windows Native tasemel on tĂ€iesti vĂ”imalik. Edasi koos Innopolis Ălikooli tiimiga ehitame teenust, mis algatab suhtlemisprotsessi draiveriga palju varem kui meie projekti eelmine versioon. Ja pĂ€rast win32 liidese ilmumist on mĂ”istlik anda juhtimine juba vĂ€lja töötatud tĂ€isfunktsionaalsesse teenusesse (sellega tutvume lĂ€hemalt ).
JĂ€rgmises artiklis kĂ€sitleme veel ĂŒhte Active Restore teenuse komponenti, nimelt UEFI draiverit. JĂ€lgige meie blogi, et mitte jÀÀda ilma jĂ€rgmistest postitustest.
Allikas: habr.com
