Windows Native Applications болон Acronis Active Restore үйлчилгээ

Өнөөдөр бид Иннополисын их сургуулийн залуустай хамт хэрэглэгчдэд бүтэлгүйтлийн дараа машин дээрээ аль болох хурдан ажиллаж эхлэх боломжийг олгохын тулд Active Restore технологийг хэрхэн хөгжүүлж байгаа тухай түүхийг үргэлжлүүлж байна. Бид Windows-ийн уугуул програмуудын талаар, түүний дотор тэдгээрийг үүсгэх, эхлүүлэх онцлогуудын талаар ярих болно. Зүсэлтийн доор манай төслийн талаар бага зэрэг, мөн уугуул програмуудыг хэрхэн бичих талаар практик гарын авлага байна.

Windows Native Applications болон Acronis Active Restore үйлчилгээ

Энэ нь юу болох талаар бид өмнөх нийтлэлүүддээ аль хэдийн ярьсан Идэвхтэй сэргээх, мөн Иннополисын оюутнууд хэрхэн хөгжиж байгааг харуулсан үйлчилгээ. Өнөөдөр би идэвхтэй сэргээх үйлчилгээгээ "булшлах" түвшинд хүргэх уугуул програмууд дээр анхаарлаа хандуулахыг хүсч байна. Хэрэв бүх зүйл амжилттай болвол бид дараахь зүйлийг хийх боломжтой болно.

  • Үйлчилгээг өөрөө хамаагүй эрт эхлүүл
  • Нөөцлөлт байгаа үүлтэй илүү эрт холбогдоно уу
  • Систем ямар горимд байгааг ойлгохын тулд илүү эрт - хэвийн ачаалах эсвэл сэргээх
  • Урьдчилан сэргээхэд цөөн тооны файл байна
  • Хэрэглэгчийг илүү хурдан эхлүүлэхийг зөвшөөрнө үү.

Ямар ч байсан уугуул програм гэж юу вэ?

Энэ асуултад хариулахын тулд, жишээлбэл, программынхаа программист файл үүсгэхийг оролдох тохиолдолд систем хийдэг дуудлагын дарааллыг харцгаая.

Windows Native Applications болон Acronis Active Restore үйлчилгээ
Павел Йосифович - Windows цөмийн програмчлал (2019)

Программист функцийг ашигладаг Файл үүсгэх, үүнийг fileapi.h толгой файлд зарлаж, Kernel32.dll дээр хэрэгжүүлсэн. Гэхдээ энэ функц өөрөө файл үүсгэдэггүй, зөвхөн оролтын аргументуудыг шалгаж, функцийг дууддаг NtCreateFile (Nt угтвар нь уг функцийг уугуул гэдгийг харуулж байна). Энэ функцийг winternl.h толгой файлд зарлаж, ntdll.dll дээр хэрэгжүүлдэг. Цөмийн орон зай руу үсрэхээр бэлтгэж, дараа нь файл үүсгэх системийн дуудлага хийдэг. Энэ тохиолдолд Kernel32 нь Ntdll-ийн зүгээр л боодол болж хувирдаг. Үүнийг хийх болсон шалтгаануудын нэг нь Майкрософт нь эх ертөнцийн функцуудыг өөрчлөх чадвартай боловч стандарт интерфэйсүүдэд хүрч чаддаггүй явдал юм. Майкрософт нь үндсэн функцуудыг шууд дуудахыг зөвлөдөггүй бөгөөд ихэнхийг нь баримтжуулдаггүй. Дашрамд хэлэхэд, баримтжуулаагүй функцүүдийг олж болно энд.

Төрөлх програмуудын гол давуу тал нь ntdll нь kernel32-ээс хамаагүй эрт системд ачаалагддаг. Энэ нь логик юм, учир нь kernel32 нь ажиллахын тулд ntdll шаарддаг. Үүний үр дүнд үндсэн функцийг ашигладаг програмууд илүү эрт ажиллаж эхэлдэг.

Тиймээс Windows Native Applications нь Windows-ийг ачаалахад эрт эхлэх боломжтой програмууд юм. Тэд ЗӨВХӨН ntdll-ийн функцуудыг ашигладаг. Ийм хэрэглээний жишээ: autochk хэн гүйцэтгэдэг chkdisk хэрэгсэл Үндсэн үйлчилгээг эхлүүлэхийн өмнө дискэнд алдаа байгаа эсэхийг шалгах. Энэ бол бидний Active Restore-г яг ийм түвшинд байлгахыг хүсч байна.

Бидэнд юу хэрэгтэй байна вэ?

  • DDK (Драйвер хөгжүүлэх хэрэгсэл), одоо WDK 7 (Windows Driver Kit) гэж нэрлэгддэг.
  • Виртуал машин (жишээ нь, Windows 7 x64)
  • Шаардлагагүй, гэхдээ татаж авах боломжтой толгой файлууд нь тус болно энд

Кодод юу байгаа вэ?

Жаахан дадлага хийцгээе, жишээлбэл, дараах жижиг програм бичье.

  1. Дэлгэц дээр мессеж харуулна
  2. Зарим санах ойг хуваарилдаг
  3. Гарын оролтыг хүлээж байна
  4. Ашигласан санах ойг чөлөөлнө

Уугуул програмуудад нэвтрэх цэг нь үндсэн эсвэл winmain биш, харин NtProcessStartup функц юм, учир нь бид системд шинэ процессуудыг шууд эхлүүлдэг.

Дэлгэц дээр мессеж гарч эхэлцгээе. Үүний тулд бид эх функцтэй NtDisplayString, энэ нь UNICODE_STRING бүтцийн объект руу заагчийг аргумент болгон авдаг. RtlInitUnicodeString бидэнд үүнийг эхлүүлэхэд тусална. Үүний үр дүнд дэлгэцэн дээр текстийг харуулахын тулд бид дараах жижиг функцийг бичиж болно.

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

Зөвхөн ntdll-ийн функцууд бидэнд боломжтой бөгөөд санах ойд өөр номын сан байхгүй байгаа тул санах ойг хэрхэн хуваарилах талаар асуудал гарах нь гарцаагүй. Шинэ оператор хараахан байхгүй байна (учир нь энэ нь C++-ийн хэт өндөр түвшний ертөнцөөс гаралтай) бөгөөд malloc функц байхгүй (үүнд ажиллах цагийн С номын сангууд шаардлагатай). Мэдээжийн хэрэг та зөвхөн стек ашиглаж болно. Гэхдээ хэрэв бид санах ойг динамикаар хуваарилах шаардлагатай бол бид үүнийг нуруулдан (өөрөөр хэлбэл нуруулдан) хийх хэрэгтэй болно. Тиймээс өөрсдөдөө зориулж овоолгыг бүтээж, хэрэгтэй үед нь дурсамжаа авцгаая.

Энэ функц нь энэ ажилд тохиромжтой RtlCreateHeap. Дараа нь, RtlAllocateHeap болон RtlFreeHeap-ийг ашиглан бид санах ойг хэрэгцээтэй үед эзэлнэ.

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

Гарын оролтыг хүлээнэ үү.

// 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;
	}
}

Бидэнд хэрэгтэй зүйл бол ашиглах явдал юм NtReadFile Нээлттэй төхөөрөмж дээр суулгаж, гар нь бидэнд ямар ч товчлуурыг буцааж өгөх хүртэл хүлээнэ үү. Хэрэв ESC товчлуур дарвал бид үргэлжлүүлэн ажиллах болно. Төхөөрөмжийг нээхийн тулд бид NtCreateFile функцийг дуудах шаардлагатай болно (бид DeviceKeyboardClass0-ыг нээх хэрэгтэй болно). Бид бас залгана NtCreateEventхүлээх объектыг эхлүүлэх. Бид гарны өгөгдлийг илэрхийлдэг KEYBOARD_INPUT_DATA бүтцийг өөрсдөө зарлах болно. Энэ нь бидний ажлыг хөнгөвчлөх болно.

Үндсэн програм нь функцийн дуудлагаар төгсдөг NtTerminateProcessУчир нь бид зүгээр л өөрсдийн үйл явцыг алж байна.

Манай жижиг програмын бүх код:

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

Жич: Бид кодын DbgBreakPoint() функцийг хялбархан ашиглан дибаг хийгч дээр зогсоох боломжтой. Үнэн бол та цөмийн дибаг хийхэд WinDbg-г виртуал машинтай холбох хэрэгтэй болно. Үүнийг хэрхэн хийх зааврыг олж болно энд эсвэл зүгээр л ашиглах VirtualKD.

Эмхэтгэх, угсрах

Төрөлх програм бүтээх хамгийн хялбар арга бол ашиглах явдал юм DDK (Жолооч хөгжүүлэх багц). Сүүлчийн хувилбарууд арай өөр арга барилтай бөгөөд Visual Studio-той нягт хамтран ажилладаг тул бидэнд эртний долоо дахь хувилбар хэрэгтэй байна. Хэрэв бид DDK ашигладаг бол манай төсөлд зөвхөн Makefile болон эх сурвалж хэрэгтэй болно.

Makefile

!INCLUDE $(NTMAKEENV)makefile.def

эх сурвалж:

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

Таны Makefile яг адилхан байх болно, гэхдээ эх сурвалжийг бага зэрэг дэлгэрэнгүй харцгаая. Энэ файл нь таны програмын эх сурвалж (.c файлууд), бүтээх сонголтууд болон бусад параметрүүдийг тодорхойлдог.

  • TARGETNAME – эцэст нь гаргах ёстой гүйцэтгэх файлын нэр.
  • TARGETTYPE – гүйцэтгэгдэх файлын төрөл, энэ нь драйвер (.sys) байж болно, тэгвэл талбарын утга нь DRIVER, хэрэв номын сан (.lib) байвал утга нь LIBRARY байна. Манай тохиолдолд гүйцэтгэх боломжтой файл (.exe) хэрэгтэй тул бид утгыг PROGRAM болгон тохируулсан.
  • UMTYPE – энэ талбарын боломжит утгууд: консол програмын консол, цонхтой горимд ажиллах цонх. Гэхдээ бид уугуул програм авахын тулд nt зааж өгөх хэрэгтэй.
  • BUFFER_OVERFLOW_CHECKS – буфер халих эсэхийг стекийг шалгаж байгаа нь харамсалтай нь бидний тохиолдол биш, бид үүнийг унтраадаг.
  • MINWIN_SDK_LIB_PATH – энэ утга нь SDK_LIB_PATH хувьсагчийг хэлж байгаа тул танд ийм системийн хувьсагч зарлагдаагүй байна гэж бүү санаа зов, бид DDK-ээс checked build-г ажиллуулахад энэ хувьсагч зарлагдах бөгөөд шаардлагатай номын сангууд руу чиглүүлэх болно.
  • SOURCES – таны хөтөлбөрийн эх сурвалжуудын жагсаалт.
  • ОРУУЛНА – угсрахад шаардлагатай толгой файлууд. Энд тэд ихэвчлэн DDK-тэй хамт ирдэг файлуудын замыг зааж өгдөг боловч та бусад файлуудыг нэмж зааж өгч болно.
  • TARGETLIBS – холбох шаардлагатай номын сангуудын жагсаалт.
  • USE_NTDLL нь тодорхой шалтгааны улмаас 1 болгож тохируулах шаардлагатай талбар юм.
  • USER_C_FLAGS – програмын код бэлтгэхдээ урьдчилан процессорын зааварт ашиглаж болох аливаа туг.

Тиймээс бүтээхийн тулд бид x86 (эсвэл x64) Checked Build програмыг ажиллуулж, ажлын лавлахыг төслийн хавтас болгон өөрчилж, Build командыг ажиллуулах хэрэгтэй. Дэлгэцийн агшин дахь үр дүн нь бидэнд нэг гүйцэтгэгдэх файл байгааг харуулж байна.

Windows Native Applications болон Acronis Active Restore үйлчилгээ

Энэ файлыг тийм ч амархан эхлүүлэх боломжгүй тул систем нь биднийг хараан зүхэж, дараах алдаатай үйлдлийнхээ талаар бодохыг бидэнд илгээдэг.

Windows Native Applications болон Acronis Active Restore үйлчилгээ

Төрөлх програмыг хэрхэн эхлүүлэх вэ?

Autochk эхлэхэд програмыг эхлүүлэх дарааллыг бүртгэлийн түлхүүрийн утгаар тодорхойлно.

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Сеанс менежер энэ жагсаалтаас программуудыг нэг нэгээр нь гүйцэтгэдэг. Сеанс менежер нь system32 лавлахаас гүйцэтгэх боломжтой файлуудыг өөрөө хайдаг. Бүртгэлийн түлхүүрийн утгын формат дараах байдалтай байна.

autocheck autochk *MyNative

Утга нь ердийн ASCII биш XNUMX-тын форматтай байх ёстой тул дээр үзүүлсэн түлхүүр нь дараах форматтай байна:

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

Гарчиг хөрвүүлэхийн тулд та онлайн үйлчилгээг ашиглаж болно, жишээлбэл, энэ нь.

Windows Native Applications болон Acronis Active Restore үйлчилгээ
Төрөлх програмыг ажиллуулахын тулд бидэнд дараахь зүйл хэрэгтэй болно.

  1. Гүйцэтгэх файлыг system32 хавтас руу хуулна уу
  2. Бүртгэлд түлхүүр нэмнэ үү
  3. Машиныг дахин ачаална уу

Тохиромжтой болгох үүднээс уугуул програм суулгахад бэлэн скриптийг энд оруулав.

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

Суулгаж, дахин ачаалсны дараа хэрэглэгчийн сонгох дэлгэц гарч ирэхээс өмнө бид дараах зургийг авах болно.

Windows Native Applications болон Acronis Active Restore үйлчилгээ

Үр дүн

Ийм жижиг програмын жишээг ашигласнаар бид програмыг Windows Native түвшинд ажиллуулах бүрэн боломжтой гэдэгт итгэлтэй байсан. Дараа нь, Иннополисын их сургуулийн залуус бид хоёр төслийн өмнөх хувилбараас хамаагүй эрт жолоочтой харилцах үйл явцыг эхлүүлэх үйлчилгээг үргэлжлүүлэн барих болно. Win32 бүрхүүл гарч ирснээр хяналтыг аль хэдийн боловсруулсан бүрэн үйлчилгээнд шилжүүлэх нь логик юм (энэ талаар дэлгэрэнгүй энд).

Дараагийн өгүүллээр бид Active Restore үйлчилгээний өөр нэг бүрэлдэхүүн хэсэг болох UEFI драйверын талаар ярих болно. Дараагийн нийтлэлийг алдахгүйн тулд манай блогт бүртгүүлээрэй.

Эх сурвалж: www.habr.com

сэтгэгдэл нэмэх