Bu gün İnnopolis Universitetindən olan dostlarımızla birgə Active Restore texnologiyasını inkişaf etdirdiyimiz barədə danışmağa davam edirik. Bu texnologiya istifadəçiyə sistem çökdükdən sonra mümkün qədər tez işə başlamağa imkan verəcək. Windows nativ tətbiqləri, onların yaradılması və işə salınma xüsusiyyətləri ilə tanış olacağıq. Aşağıda layihəmizdən və nativ tətbiq yazma ilə bağlı praktiki bir bələdçidən biraz söhbət açacağıq.

Əvvəlki yazılarda artıq demişik ki, , və İnnopolis tələbələri necə inkişaf etdirir . Bu gün nativ tətbiqlər haqqında danışmaq istəyirəm, bunların səviyyəsinə bizim aktiv bərpa xidmətimizi “götürmək” niyyətindəyik. Hər şey alındığı halda, biz:
- Xidməti çox tez başlada biləcəyik
- Buludla, yəni ehtiyat nüsxənin saxlandığı yerdə, çox tez əlaqə qurmağa imkanımız olacaq
- Sistemin necə bir rejimdə olduğunu – normal yüklənmə və ya bərpa rejimində olduğunu — çox tez başa düşəcəyik
- Çox az faylı əvvəlcədən bərpa etməyə imkan verəcəyik
- İstifadəçinin işə başlama müddətini daha da tezləşdirəcəyik.
Yerli tətbiq nədir?
Bu sualın cavabını tapmaq üçün, məsələn, bir proqramçının tətbiqində bir fayl yaratmağa çalışarkən sistemin nəzərə aldığı çağırışlar ardıcıllığına baxaq.

Pavel Yosifovich — Windows Kernel Programming (2019)
Proqramçı , funksiyasını istifadə edir ki, bu da fileapi.h başlıq faylında açıqlanmışdır və Kernel32.dll-də həyata keçirilir. Ancaq bu funksiya özü fayl yaratmaqla məşğul olmur, sadəcə olaraq, giriş arqumentlərini yoxlayır və (Nt prefiksi bu funksiyanın nativ olduğunu göstərir) funksiyasını çağırır. Bu funksiya winternl.h başlıq faylında açıqlanmışdır və ntdll.dll-də həyata keçirilir. O, nüvə məkanına atlamaq üçün hazırlıq aparır və fayl yaratmaq üçün sistem çağırışını həyata keçirir. Bu halda, Kernel32, Ntdll üçün yalnız bir qabıq rolunu oynayır. Bunun edilməsinin səbəblərindən biri budur ki, Microsoft bu vasitə ilə nativ dünya funksiyalarını dəyişdirmək imkanına malikdir, lakin standart interfeysləri əzmədən. Microsoft birbaşa nativ funksiyaları çağırmağı tövsiyə etmir və onların böyük bir hissəsini dəqiqləşdirmir. Qeyd edim ki, sənədləşdirilməyən funksiyaları tapmaq mümkündür, .
Nativ tətbiqlərin əsas üstünlüyü odur ki, ntdll sistemə kernel32-dən xeyli əvvəl yüklənir. Bu məntiqlidir, çünki kernel32 ntdll-in fəaliyyətini təmin edir. Nəticə etibarilə, nativ funksiyalardan istifadə edən tətbiqlər işə çox tez başlaya bilirlər.
Beləliklə, Windows Native Applications – Windows yüklənməsinin başlanğıc mərhələsində işləyə bilən proqramlardır. Onlar YALNIZCA ntdll-in funksiyalarını istifadə edirlər. Belə bir tətbiqin nümunəsi: hansı ki, diskin xətalarını yoxlamaq üçün hələ əsas xidmətləri başlatmadan əvvəl. Biz məhz belə bir səviyyədə Active Restore-u görmək istəyirik.
Nəyə ehtiyacımız var?
- (Driver Development Kit), indi WDK 7 (Windows Driver Kit) adı altında tanınır.
- Virtual maşın (məsələn, Windows 7 x64)
- Mütləq deyil, amma yüklənə bilən başlıq faylları faydalı ola bilər.
Koda nə var?
Gəlin bir az məşq edək və nümunə üçün kiçik bir proqram yazaq ki:
- Ekrana mesaj çıxarsın
- Bir az yaddaş ayırsın
- Klaviaturadan giriş gözləsin
- Ayırılan yaddaşı sərbəst buraxsın
Təbii proqramlarda giriş nöqtəsi main və ya winmain deyil, NtProcessStartup funksiyasıdır, çünki biz faktiki olaraq sistemdə yeni proseslər açırıq.
Ekrana mesaj çıxarmaqla başlayacağıq. Bunun üçün bizim nativ funksiyamız var , hansı ki, UNICODE_STRING strukturunun obyektinə işarəçi qəbul edir. Bunu başlatmaq üçün RtlInitUnicodeString-dən istifadə edə bilərik. Nəticədə, mətni ekrana çıxarmaq üçün belə kiçik bir funksiya yaza bilərik:
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}Yalnızca ntdll-dən olan funksiyalar mövcud olduğundan və yaddaşda digər kitabxanalar hələ yoxdur, yaddaş ayırmaqla bağlı problemlərimiz olacaq. new operatoru hələ mövcud deyil (çünki bu, C++-nun çox yüksək səviyyəli dünyasından gəlir), həmçinin malloc funksiyası da yoxdur (onun üçün C runtime kitabxanaları lazımdır). Yalnızca yığımdan istifadə edə bilərik. Ancaq bizə dinamik yaddaş ayırmaq lazım olsa, bunu quyuda (yəni, heap) etmək lazım olacaq. Odur ki, gəlin bizim üçün bir heap yaradaq və lazım olduqca oradan yaddaş alaq.
Bu məqsəd üçün funksiyası uyğundur. Daha sonra RtlAllocateHeap və RtlFreeHeap istifadə edərək, yaddaşı lazım olduğu zaman ayıracağıq və sərbəst buraxa biləcəyik.
PVOID memory = NULL;
PVOID buffer = NULL;
ULONG bufferSize = 42;
// yaddaşı ayırmaq üçün heap yaradın
memory = RtlCreateHeap(
HEAP_GROWABLE,
NULL,
1000,
0, NULL, NULL
);
// bufferSize ölçüsündə buffer ayırın
buffer = RtlAllocateHeap(
memory,
HEAP_ZERO_MEMORY,
bufferSize
);
// buffer-i sərbəst buraxın (əslində lazım deyil, çünki növbəti mərhələdə heap-i məhv edirik)
RtlFreeHeap(memory, 0, buffer);
RtlDestroyHeap(memory);Klaviaturadan giriş gözləməyə keçək.
// 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;
}
}Bütün lazım olan, açıq cihazda istifadə edib, klaviatura hər hansı bir basmağı geri qaytarana qədər gözləməkdir. ESC düyməsi basıldıqda, işimizi davam etdirəcəyik. Cihazı açmaq üçün NtCreateFile funksiyasını (DeviceKeyboardClass0 açılacaq) çağırmalıyıq. Həmçinin, funksiyasını çağıracağıq ki, gözləmə obyekti hazırlayaq. Klaviatura məlumatlarını ifadə edən KEYBOARD_INPUT_DATA strukturunu özümüz elan edəcəyik. Bu, işimizi asanlaşdıracaq.
Təbii tətbiqin işi funksiyasını çağırmaqla sona çatır, çünki biz sadəcə öz prosesimizi öldürürük.
Kiçik proqramımızın tam 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);
}Məqalədə təsvir olunmuş bütün kod parçaları Kodda DbgBreakPoint() funksiyasını debuqatoru dayandırmaq üçün asanlıqla istifadə edə bilərik. Ancaq kernel debuqiyası üçün WinDbg-ni virtual maşına bağlamaq lazım olacaq. Bunu necə edəcəyinizi ətraflı инструкцияda tapa bilərsiniz ya da sadəcə istifadə edə bilərsiniz .
Kompilyasiya və yığma
Yerinə yetirilmiş tətbiqi yığmaq üçün ən asan yol (Driver Development Kit). Bizə xüsusi olaraq köhnə yeddinci versiya lazımdır, çünki daha sonrakı versiyalar fərqli bir yanaşmaya malikdir və Visual Studio ilə sıx işləyir. Əgər DDK istifadə etsəniz, layihəmiz yalnız Makefile və sources-a ehtiyac duyur.
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 = 1Sizin Makefile tamamilə eyni olacaq, sources-da isə bir az daha ətraflı dayanmağı düşünək. Bu faylda proqramınızın başlanğıc kodları (.c faylları), yığma seçimləri və digər parametrlər göstərilir.
- TARGETNAME – alınacaq icra edilən faylın adıdır.
- TARGETTYPE – icra edilən faylın növü, bu sürücü (.sys) ola bilər, o zaman sahənin dəyəri DRIVER olmalıdır, əgər kitabxana (.lib) isə, dəyəri LIBRARY. Bizim halda icra edilən fayl (.exe) lazımdır, buna görə də dəyəri PROGRAM olaraq təyin edirik.
- UMTYPE – bu sahənin mümkün dəyərləri: konsol tətbiqi üçün console, pəncərəli rejimdə işləmək üçün windows. Lakin bizə yerli tətbiq almaq üçün nt göstərmək lazımdır.
- BUFFER_OVERFLOW_CHECKS – yığın üzərində tamponun aşırma yoxlaması, təəssüf ki, bizim hallarımızda yoxdur, onu söndürürük.
- MINWIN_SDK_LIB_PATH – bu dəyər SDK_LIB_PATH dəyişəninə işarə edir, buna görə də sizdə belə sistem dəyişəni olmadığından narahat olmamalısınız, checked build başlatdığımızda DDK-dan bu dəyişən elan olunacaq və lazımi kitabxanalara işarə edəcək.
- SOURCES – proqramınızın başlanğıc kodlarının siyahısı.
- INCLUDES – yığma üçün lazım olan başlıq faylları. Burada adətən DDK ilə birlikdə gələn faylların yolunu göstərirlər, lakin siz əlavə hər hansı digər faylın yolunu da göstərə bilərsiniz.
- TARGETLIBS – bağlanması zəruri olan kitabxanaların siyahısı.
- USE_NTDLL – mütləq olaraq 1 vəziyyətinə gətirilməsi gərəkən bir sahədir. Tamamilə aydın səbəblərə görə.
- USER_C_FLAGS – tətbiqin kodunu hazırlıq mərhələsində istifadə edə biləcəyiniz hər hansı flaglar.
Beləliklə, yığma üçün x86 (və ya x64) Checked Build başlatmalıyıq, iş qovluğunu layihə qovluğuna dəyişdirin və Build əmrini icra edin. Ekrandakı nəticə göstərir ki, biz bir icra edilən fayl əldə etmişik.

Bu faylı o qədər asanlıqla işə salmaq mümkün olmayacaq, sistem bizi düşünməyə göndərir və aşağıdakı səhvlə qarşılaşır:

Yerinə yetirilmiş tətbiqi necə işə salmaq olar?
Autochk başlandığı zaman, proqramların işə salınma sırası qeydiyyat açarının dəyəri ilə müəyyən edilir:
HKLMSystemCurrentControlSetControlSession ManagerBootExecuteSessiya meneceri bu siyahıdan proqramları növbə ilə icra edir. İcra olunan faylları sessiya meneceri system32 qovluğunda axtarır. Qeydiyyat açarının dəyərinin formatı aşağıdakı kimidir:
autocheck autochk *MyNativeDəyər onaltılı formatda olmalıdır, yəni tanınmış ASCII deyil, buna görə də yuxarıda təqdim olunan açarın formatı olacaq:
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,00Adı konvertasiya etmək üçün onlayn xidmətlərdən istifadə edə bilərsiniz, məsələn, .

Beləliklə, nativ tətbiqi başlatmaq üçün bizə lazım olanlar:
- İcra olunan faylı system32 qovluğuna köçürmək
- Qeydiyyata açar əlavə etmək
- Maşını yenidən başlatmaq
Rahatlıq üçün sizə nativ tətbiqin qurulması üçün hazır skripti təqdim edirik:
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,00Quraşdırıldıqdan və yenidən başladıqdan sonra, istifadəçi seçim ekranı görünməzdən əvvəl biz aşağıdakı mənzərəni əldə edəcəyik:

Sonuç
Bu cür kiçik bir tətbiq nümunəsini təqdim edərək, Windows Native səviyyəsində tətbiqi başlatmağın tamamilə mümkün olduğunu təsdiqlədik. Sonrakı addımda İnnopolis Universitetinin dostları ilə sürəti artıraraq, əvvəlki layihəmizdən daha tez sürücünün işləməyə başlayacağı bir xidmət qurmağa davam edəcəyik. Win32 interfeysinin göründüyü zaman daha inkişaf etmiş bir xidmətə nəzarət vermək mantıqlı olacaq (daha ətraflı bunun haqqında ).
Sonrakı məqalədə Active Restore xidmətinin bir komponenti olan UEFI sürücüsündən danışacağıq. Sonrakı yazını qaçırmamaq üçün blogumuza abunə olun.
Mənbə: habr.com
