Windows Native Proqramları və Acronis Active Restore xidməti

Bu gün biz İnnopolis Universitetinin əməkdaşları ilə birlikdə istifadəçiyə nasazlıqdan sonra tez bir zamanda öz maşınında işləməyə başlamaq imkanı vermək üçün Active Restore texnologiyasını necə inkişaf etdirdiyimiz haqqında hekayəni davam etdiririk. Doğma Windows proqramları, o cümlədən onların yaradılması və işə salınma xüsusiyyətləri haqqında danışacağıq. Kəsimin altında layihəmiz haqqında bir az, həmçinin yerli tətbiqlərin necə yazılmasına dair praktiki bələdçi var.

Windows Native Proqramları və Acronis Active Restore xidməti

Əvvəlki yazılarımızda bunun nə olduğunu artıq danışdıq Aktiv bərpavə İnnopolisdən olan tələbələrin necə inkişaf etməsi xidmət. Bu gün mən aktiv bərpa xidmətimizi “basdırmaq” istədiyimiz yerli tətbiqlərə diqqət yetirmək istəyirəm. Hər şey qaydasındadırsa, biz aşağıdakıları edə bilərik:

  • Xidmətin özünü çox erkən işə salın
  • Yedəkləmənin yerləşdiyi buludla daha əvvəl əlaqə saxlayın
  • Sistemin hansı rejimdə olduğunu başa düşmək üçün çox əvvəllər - normal yükləmə və ya bərpa
  • Əvvəlcədən bərpa etmək üçün daha az fayl
  • İstifadəçiyə daha sürətli işə başlamağa icazə verin.

Hər halda yerli proqram nədir?

Bu suala cavab vermək üçün sistemin etdiyi zənglərin ardıcıllığına baxaq, məsələn, proqramçı öz proqramında fayl yaratmağa çalışırsa.

Windows Native Proqramları və Acronis Active Restore xidməti
Pavel Yosifoviç - Windows Kernel Programming (2019)

Proqramçı funksiyadan istifadə edir Fayl yaradınfileapi.h başlıq faylında elan edilən və Kernel32.dll-də həyata keçirilən . Bununla belə, bu funksiya özü fayl yaratmır, yalnız daxil olan arqumentləri yoxlayır və funksiyanı çağırır NtCreateFile (Nt prefiksi sadəcə funksiyanın yerli olduğunu göstərir). Bu funksiya winternl.h başlıq faylında elan edilir və ntdll.dll-də həyata keçirilir. O, nüvə məkanına atlamağa hazırlaşır, bundan sonra fayl yaratmaq üçün sistem çağırışı edir. Bu halda, Kernel32-nin sadəcə Ntdll üçün sarğı olduğu ortaya çıxır. Bunun səbəblərindən biri Microsoft-un beləliklə, yerli dünyanın funksiyalarını dəyişmək, lakin standart interfeyslərə toxunmamaq qabiliyyətinə malik olmasıdır. Microsoft yerli funksiyaları birbaşa çağırmağı tövsiyə etmir və onların əksəriyyətini sənədləşdirmir. Yeri gəlmişkən, sənədsiz funksiyalar tapıla bilər burada.

Doğma proqramların əsas üstünlüyü ondan ibarətdir ki, ntdll sistemə kernel32-dən xeyli əvvəl yüklənir. Bu məntiqlidir, çünki kernel32 işləmək üçün ntdll tələb edir. Nəticədə, yerli funksiyalardan istifadə edən proqramlar daha erkən işə başlaya bilər.

Beləliklə, Windows Native Proqramları Windows açılışında erkən başlaya bilən proqramlardır. Onlar YALNIZ ntdll-dən olan funksiyalardan istifadə edirlər. Belə bir tətbiqin nümunəsi: autochk kim ifa edir chkdisk yardım proqramı əsas xidmətlərə başlamazdan əvvəl diskdə səhvləri yoxlamaq üçün. Bu, Aktiv Bərpamızın olmasını istədiyimiz səviyyədir.

Bizə nə lazımdır?

  • DDK (Driver Development Kit), indi WDK 7 (Windows Driver Kit) kimi də tanınır.
  • Virtual maşın (məsələn, Windows 7 x64)
  • Lazım deyil, lakin endirilə bilən başlıq faylları kömək edə bilər burada

Kodda nə var?

Bir az məşq edək və məsələn, kiçik bir ərizə yazaq:

  1. Ekranda bir mesaj göstərir
  2. Bəzi yaddaş ayırır
  3. Klaviatura daxil edilməsini gözləyir
  4. İstifadə olunmuş yaddaşı boşaldır

Doğma tətbiqlərdə giriş nöqtəsi əsas və ya winmain deyil, NtProcessStartup funksiyasıdır, çünki biz həqiqətən sistemdə yeni prosesləri birbaşa işə salırıq.

Ekranda bir mesaj göstərməklə başlayaq. Bunun üçün yerli funksiyamız var NtDisplayString, arqument kimi UNICODE_STRING struktur obyektinə göstərici götürür. RtlInitUnicodeString bizə onu işə salmağa kömək edəcək. Nəticədə, mətni ekranda göstərmək üçün bu kiçik funksiyanı yaza bilərik:

//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
    UNICODE_STRING string;
    RtlInitUnicodeString(&string, Message);
    NtDisplayString(&string);
}

Yalnız ntdll-dən olan funksiyalar bizim üçün əlçatan olduğundan və yaddaşda sadəcə olaraq başqa kitabxanalar olmadığından, yaddaşın necə ayrılması ilə bağlı problemlərimiz mütləq olacaq. Yeni operator hələ mövcud deyil (çünki o, çox yüksək səviyyəli C++ dünyasından gəlir) və malloc funksiyası yoxdur (bu, işləmə vaxtı C kitabxanalarını tələb edir). Əlbəttə ki, yalnız bir yığın istifadə edə bilərsiniz. Ancaq yaddaşı dinamik olaraq ayırmaq lazımdırsa, bunu yığın (yəni yığın) üzərində etməliyik. Beləliklə, gəlin özümüz üçün bir yığın yaradaq və ehtiyac duyduğumuz zaman ondan yaddaş götürək.

Funksiya bu vəzifə üçün uyğundur RtlCreateHeap. Sonra, RtlAllocateHeap və RtlFreeHeap istifadə edərək, ehtiyac duyduğumuz zaman yaddaşı tutacağıq və boşaldacağıq.

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);

Klaviatura girişini gözləməyə davam edə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;
	}
}

Bizə lazım olan tək şey istifadə etməkdir NtReadFile açıq cihazda seçin və klaviatura hər hansı bir basmağı bizə qaytarana qədər gözləyin. ESC düyməsini basarsanız, işləməyə davam edəcəyik. Cihazı açmaq üçün NtCreateFile funksiyasına zəng etməliyik (DeviceKeyboardClass0-ı açmalıyıq). Biz də zəng edəcəyik NtCreateEventgözləmə obyektini işə salmaq üçün. Klaviatura məlumatlarını təmsil edən KEYBOARD_INPUT_DATA strukturunu özümüz elan edəcəyik. Bu, işimizi asanlaşdıracaq.

Doğma proqram funksiya çağırışı ilə başa çatır NtTerminateProcessçünki biz sadəcə öz prosesimizi öldürürük.

Kiçik tətbiqimiz üçün bütün kodlar:

#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: Sazlayıcıda dayandırmaq üçün kodumuzdakı DbgBreakPoint() funksiyasından asanlıqla istifadə edə bilərik. Doğrudur, nüvənin ayıklanması üçün WinDbg-ni virtual maşına qoşmalısınız. Bunu necə etmək barədə təlimatları tapa bilərsiniz burada və ya sadəcə istifadə edin VirtualKD.

Kompilyasiya və montaj

Doğma proqram qurmağın ən asan yolu istifadə etməkdir DDK (Sürücü İnkişaf Kiti). Bizə qədim yeddinci versiya lazımdır, çünki sonrakı versiyalar bir az fərqli yanaşmaya malikdir və Visual Studio ilə sıx işləyir. DDK-dan istifadə etsək, layihəmizə yalnız Makefile və mənbələr lazımdır.

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

mənbələri:

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 tam olaraq eyni olacaq, lakin gəlin mənbələrə bir az daha ətraflı baxaq. Bu fayl proqramınızın mənbələrini (.c faylları), qurma seçimlərini və digər parametrləri müəyyən edir.

  • TARGETNAME – sonunda hazırlanmalı olan icra olunan faylın adı.
  • TARGETTYPE – icra edilə bilən faylın növü, o, drayver (.sys) ola bilər, onda sahənin qiyməti DRIVER olmalıdır, əgər kitabxana (.lib), onda qiymət KİTABXANYA-dır. Bizim vəziyyətimizdə bizə icra edilə bilən fayl (.exe) lazımdır, ona görə də dəyəri PROGRAM-a təyin edirik.
  • UMTYPE – bu sahə üçün mümkün dəyərlər: konsol tətbiqi üçün konsol, pəncərə rejimində işləmək üçün pəncərələr. Ancaq yerli proqram əldə etmək üçün nt təyin etməliyik.
  • BUFFER_OVERFLOW_CHECKS – buferin daşması üçün yığını yoxlayır, təəssüf ki, bizdə deyil, biz onu söndürürük.
  • MINWIN_SDK_LIB_PATH – bu dəyər SDK_LIB_PATH dəyişəninə aiddir, narahat olmayın ki, elan edilmiş belə bir sistem dəyişəniniz yoxdur, biz DDK-dan yoxlanılmış quruluşu işə saldığımız zaman bu dəyişən elan olunacaq və lazımi kitabxanalara işarə edəcək.
  • MƏNBƏLƏR – proqramınız üçün mənbələrin siyahısı.
  • DAHİLDİR – montaj üçün tələb olunan başlıq faylları. Burada adətən DDK ilə gələn faylların yolunu göstərirlər, lakin siz əlavə olaraq hər hansı digərini təyin edə bilərsiniz.
  • TARGETLIBS – əlaqələndirilməsi lazım olan kitabxanaların siyahısı.
  • USE_NTDLL aydın səbəblərə görə 1-ə təyin edilməli olan tələb olunan sahədir.
  • USER_C_FLAGS – proqram kodunu hazırlayarkən preprosessor direktivlərində istifadə edə biləcəyiniz hər hansı bayraqlar.

Beləliklə, qurmaq üçün biz x86 (və ya x64) Checked Build proqramını işə salmalı, işçi qovluğunu layihə qovluğuna dəyişdirməli və Build əmrini işlətməliyik. Ekran görüntüsündəki nəticə göstərir ki, bir icra edilə bilən faylımız var.

Windows Native Proqramları və Acronis Active Restore xidməti

Bu fayl asanlıqla işə salına bilməz, sistem bizi lənətləyir və aşağıdakı xəta ilə davranışı haqqında düşünməyə göndərir:

Windows Native Proqramları və Acronis Active Restore xidməti

Doğma tətbiqi necə işə salmaq olar?

Autochk işə salındıqda, proqramların başlanğıc ardıcıllığı qeyd açarının dəyəri ilə müəyyən edilir:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Sessiya meneceri bu siyahıdan proqramları bir-bir icra edir. Sessiya meneceri system32 qovluğunda icra olunan faylları axtarır. Reyestr açarının dəyər formatı aşağıdakı kimidir:

autocheck autochk *MyNative

Dəyər adi ASCII deyil, onaltılıq formatda olmalıdır, ona görə də yuxarıda göstərilən açar formatda 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

Başlığı çevirmək üçün onlayn xidmətdən istifadə edə bilərsiniz, məsələn, bu.

Windows Native Proqramları və Acronis Active Restore xidməti
Belə çıxır ki, yerli tətbiqi işə salmaq üçün bizə lazımdır:

  1. İcra olunan faylı system32 qovluğuna kopyalayın
  2. Qeydə bir açar əlavə edin
  3. Maşını yenidən başladın

Rahatlıq üçün burada yerli tətbiqi quraşdırmaq üçün hazır skript var:

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, hətta istifadəçi seçim ekranı görünməzdən əvvəl belə bir şəkil alacağıq:

Windows Native Proqramları və Acronis Active Restore xidməti

Ümumi

Belə kiçik bir proqram nümunəsindən istifadə edərək, biz əmin olduq ki, proqramı Windows Native səviyyəsində işə salmaq olduqca mümkündür. Bundan sonra, İnnopolis Universitetinin uşaqları və mən layihəmizin əvvəlki versiyasından xeyli əvvəl sürücü ilə qarşılıqlı əlaqə prosesini başlatacaq bir xidmət qurmağa davam edəcəyik. Win32 qabığının meydana gəlməsi ilə idarəetməni artıq hazırlanmış tam hüquqlu bir xidmətə ötürmək məntiqli olardı (bu barədə daha çox məlumat). burada).

Növbəti məqalədə Active Restore xidmətinin başqa bir komponentinə, yəni UEFI sürücüsünə toxunacağıq. Növbəti yazını qaçırmamaq üçün blogumuza abunə olun.

Mənbə: www.habr.com

Добавить комментарий