Windows Native Тиркемелер жана Acronis Active Restore кызматы

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

Windows Native Тиркемелер жана Acronis Active Restore кызматы

Мурунку билдирүүлөрүбүздө бул эмне жөнүндө сөз кылганбыз Активдүү калыбына келтирүү, жана Иннополистеги студенттер кантип өнүгөт кызмат. Бүгүн мен жергиликтүү тиркемелерге токтолгум келет, алардын деңгээлине чейин биз активдүү калыбына келтирүү кызматыбызды "көмгүбүз" келет. Эгер баары ойдогудай болсо, анда биз:

  • Кызматтын өзүн бир топ эрте ишке киргизиңиз
  • Камдык көчүрмөсү алда канча мурда жайгашкан булут менен байланышыңыз
  • Система кандай режимде экенин түшүнүү үчүн алда канча мурда - кадимки жүктөө же калыбына келтирүү
  • Алдын ала калыбына келтирүү үчүн файлдар азыраак
  • Колдонуучуга дагы тезирээк баштоого уруксат бериңиз.

Ансыз деле жергиликтүү колдонмо деген эмне?

Бул суроого жооп берүү үчүн, келгиле, система жасаган чалуулардын ырааттуулугун карап көрөлү, мисалы, эгерде анын тиркемесинде программист файл түзүүгө аракет кылса.

Windows Native Тиркемелер жана Acronis Active Restore кызматы
Павел Йосифович - Windows ядросун программалоо (2019)

Программист функцияны колдонот CreateFile, fileapi.h баш файлында жарыяланган жана Kernel32.dllде ишке ашырылган. Бирок, бул функция өзү файлды түзбөйт, ал киргизилген аргументтерди гана текшерет жана функцияны чакырат NtCreateFile (Nt префикси жөн гана функциянын түпнуска экенин көрсөтүп турат). Бул функция winternl.h баш файлында жарыяланып, ntdll.dllде ишке ашырылат. Ал өзөктүк мейкиндикке секирүүгө даярданууда, андан кийин файлды түзүү үчүн системалык чакыруу жасайт. Бул учурда, Kernel32 жөн гана Ntdll үчүн орогуч экени белгилүү болду. Мунун бирден бир себеби, Microsoft түпнуска дүйнөнүн функцияларын өзгөртө алат, бирок стандарттуу интерфейстерге тийбейт. Microsoft жергиликтүү функцияларды түз чакырууну сунуштабайт жана алардын көбүн документтештирбейт. Айтмакчы, документтештирилбеген функцияларды табууга болот бул жерде.

Жергиликтүү тиркемелердин негизги артыкчылыгы - ntdll тутумга kernel32ге караганда алда канча эрте жүктөлөт. Бул логикалуу, анткени kernel32 иштеши үчүн ntdll талап кылынат. Натыйжада, жергиликтүү функцияларды колдонгон тиркемелер бир топ эрте иштей башташы мүмкүн.

Ошентип, Windows Native Тиркемелери - бул Windows жүктөөнүн башында баштала турган программалар. Алар NTdll функцияларын гана колдонушат. Мындай өтүнмөнүн мисалы: autochk ким аткарат chkdisk утилитасы негизги кызматтарды баштоодон мурун дискте каталарды текшерүү үчүн. Бул биздин Active Restore болушун каалаган деңгээлибиз.

Биз эмне кылышыбыз керек?

  • DDK (Driver Development Kit), азыр 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 функциясы жок (ал иштөө убактысынын C китепканаларын талап кылат). Албетте, сиз бир гана стек колдоно аласыз. Бирок эстутумду динамикалык түрдө бөлүштүрүү керек болсо, анда биз аны үймөктө (б.а. үймөктө) кылышыбыз керек болот. Андыктан, келгиле, өзүбүзгө үймөк түзөлү жана керек болгон учурда андан эстелик алалы.

Функция бул тапшырмага ылайыктуу 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);
}

PS: Мүчүлүштүктөрдү оңдоочуда аны токтотуу үчүн кодубуздагы DbgBreakPoint() функциясын оңой колдоно алабыз. Туура, сиз өзөктүк мүчүлүштүктөрдү оңдоо үчүн WinDbgти виртуалдык машинага туташтырышыңыз керек болот. Муну кантип жасоо керектиги боюнча нускамаларды тапса болот бул жерде же жөн эле колдон VirtualKD.

Компиляция жана чогултуу

Жергиликтүү тиркемени түзүүнүн эң оңой жолу - колдонуу DDK (Driver Development Kit). Бизге байыркы жетинчи версия керек, анткени кийинки версиялар бир аз башкача мамилеге ээ жана 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ден текшерилген курууну иштеткенде, бул өзгөрмө жарыяланып, керектүү китепканаларды көрсөтөт.
  • БУЛАКТАР – программаңыз үчүн булактардын тизмеси.
  • КАМТАТ – монтаждоо үчүн талап кылынган баш файлдар. Бул жерде алар, адатта, DDK менен келген файлдардын жолун көрсөтөт, бирок сиз дагы башкаларды белгилей аласыз.
  • TARGETLIBS – байланыштырылышы керек болгон китепканалардын тизмеси.
  • USE_NTDLL - бул айкын себептерден улам 1ге коюлушу керек болгон милдеттүү талаа.
  • USER_C_FLAGS – колдонмо кодун даярдоодо препроцессордун директивасында колдоно турган бардык желектер.

Ошентип, куруу үчүн, биз x86 (же x64) Checked Build программасын иштетишибиз керек, жумушчу каталогду долбоордун папкасына өзгөртүшүбүз жана Build буйругун иштетүүбүз керек. Скриншоттун натыйжасы бизде бир аткарылуучу файл бар экенин көрсөтүп турат.

Windows Native Тиркемелер жана Acronis Active Restore кызматы

Бул файлды оңой эле ишке киргизүү мүмкүн эмес, система бизди каргап, төмөнкү ката менен өзүнүн жүрүм-туруму жөнүндө ойлонууга жөнөтөт:

Windows Native Тиркемелер жана Acronis Active Restore кызматы

Жергиликтүү тиркемени кантип ишке киргизүү керек?

Autochk башталганда, программаларды баштоо ырааттуулугу реестр ачкычынын мааниси менен аныкталат:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

Сеанс менеджери бул тизмедеги программаларды бирден аткарат. Сеанс менеджери system32 каталогунан аткарылуучу файлдарды издейт. Реестр ачкычынын маани форматы төмөнкүдөй:

autocheck autochk *MyNative

Маани кадимки ASCII эмес, он алтылык форматта болушу керек, андыктан жогоруда көрсөтүлгөн ачкыч форматта болот:

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 Тиркемелер жана 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 Тиркемелер жана Acronis Active Restore кызматы

жыйынтык

Мындай кичинекей тиркеменин мисалын колдонуп, биз тиркемени Windows Native деңгээлинде иштетүү толук мүмкүн экенине ынандык. Андан кийин, Иннополис университетинин балдары жана мен айдоочу менен өз ара аракеттенүү процессин биздин долбоордун мурунку версиясына караганда бир топ эрте баштаган кызматты курууну улантабыз. Ал эми win32 кабыгынын пайда болушу менен башкарууну буга чейин иштелип чыккан толук кандуу кызматка өткөрүп берүү логикалык болмок (бул жөнүндө көбүрөөк маалымат). бул жерде).

Кийинки макалада биз Active Restore кызматынын дагы бир компонентине, тактап айтканда UEFI драйверине токтолобуз. Кийинки постту өткөрүп жибербеш үчүн блогубузга жазылыңыз.

Source: www.habr.com

Комментарий кошуу