Windows Native Applications və Acronis Active Restore xidməti

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.

Windows Native Applications və Acronis Active Restore xidməti

Əvvəlki yazılarda artıq demişik ki, Active Restore, və İnnopolis tələbələri necə inkişaf etdirir xidmət. 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.

Windows Native Applications və Acronis Active Restore xidməti
Pavel Yosifovich — Windows Kernel Programming (2019)

Proqramçı CreateFile, 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ə NtCreateFile (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, burada.

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: autochk hansı ki, chkdisk utility 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?

  • DDK (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. burada

Koda nə var?

Gəlin bir az məşq edək və nümunə üçün kiçik bir proqram yazaq ki:

  1. Ekrana mesaj çıxarsın
  2. Bir az yaddaş ayırsın
  3. Klaviaturadan giriş gözləsin
  4. 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 NtDisplayString, 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 RtlCreateHeapfunksiyası 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 NtReadFile 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, NtCreateEventfunksiyası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 NtTerminateProcessfunksiyası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 burada ya da sadəcə istifadə edə bilərsiniz VirtualKD.

Kompilyasiya və yığma

Yerinə yetirilmiş tətbiqi yığmaq üçün ən asan yol DDK (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.def

sources:

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			= 1

Sizin 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.

Windows Native Applications və Acronis Active Restore xidməti

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:

Windows Native Applications və Acronis Active Restore xidməti

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 ManagerBootExecute

Sessiya 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 *MyNative

Də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,00

Adı konvertasiya etmək üçün onlayn xidmətlərdən istifadə edə bilərsiniz, məsələn, bu.

Windows Native Applications və Acronis Active Restore xidməti
Beləliklə, nativ tətbiqi başlatmaq üçün bizə lazım olanlar:

  1. İcra olunan faylı system32 qovluğuna köçürmək
  2. Qeydiyyata açar əlavə etmək
  3. 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
pause

add.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,00

Quraş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:

Windows Native Applications və Acronis Active Restore xidməti

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 burada).

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

DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər 🔥 DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər | ProHoster