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

Энэ нь юу болох талаар бид өмнөх нийтлэлүүддээ аль хэдийн ярьсан , мөн Иннополисын оюутнууд хэрхэн хөгжиж байгааг харуулсан . Өнөөдөр би идэвхтэй сэргээх үйлчилгээгээ "булшлах" түвшинд хүргэх уугуул програмууд дээр анхаарлаа хандуулахыг хүсч байна. Хэрэв бүх зүйл амжилттай болвол бид дараахь зүйлийг хийх боломжтой болно.
- Үйлчилгээг өөрөө хамаагүй эрт эхлүүл
- Нөөцлөлт байгаа үүлтэй илүү эрт холбогдоно уу
- Систем ямар горимд байгааг ойлгохын тулд илүү эрт - хэвийн ачаалах эсвэл сэргээх
- Урьдчилан сэргээхэд цөөн тооны файл байна
- Хэрэглэгчийг илүү хурдан эхлүүлэхийг зөвшөөрнө үү.
Ямар ч байсан уугуул програм гэж юу вэ?
Энэ асуултад хариулахын тулд, жишээлбэл, программынхаа программист файл үүсгэхийг оролдох тохиолдолд систем хийдэг дуудлагын дарааллыг харцгаая.

Павел Йосифович — Windows Цөмийн програмчлал (2019)
Программист функцийг ашигладаг , үүнийг fileapi.h толгой файлд зарлаж, Kernel32.dll дээр хэрэгжүүлсэн. Гэхдээ энэ функц өөрөө файл үүсгэдэггүй, зөвхөн оролтын аргументуудыг шалгаж, функцийг дууддаг (Nt угтвар нь уг функцийг уугуул гэдгийг харуулж байна). Энэ функцийг winternl.h толгой файлд зарлаж, ntdll.dll дээр хэрэгжүүлдэг. Цөмийн орон зай руу үсрэхээр бэлтгэж, дараа нь файл үүсгэх системийн дуудлага хийдэг. Энэ тохиолдолд Kernel32 нь Ntdll-ийн зүгээр л боодол болж хувирдаг. Үүнийг хийх болсон шалтгаануудын нэг нь Майкрософт нь эх ертөнцийн функцуудыг өөрчлөх чадвартай боловч стандарт интерфэйсүүдэд хүрч чаддаггүй явдал юм. Майкрософт нь үндсэн функцуудыг шууд дуудахыг зөвлөдөггүй бөгөөд ихэнхийг нь баримтжуулдаггүй. Дашрамд хэлэхэд, баримтжуулаагүй функцүүдийг олж болно .
Төрөлх програмуудын гол давуу тал нь ntdll нь kernel32-ээс хамаагүй эрт системд ачаалагддаг. Энэ нь логик юм, учир нь kernel32 нь ажиллахын тулд ntdll шаарддаг. Үүний үр дүнд үндсэн функцийг ашигладаг програмууд илүү эрт ажиллаж эхэлдэг.
Иймээс Windows Төрөлх програмууд нь ачаалах үед эхэн үед ажиллаж чаддаг програмууд юм. WindowsТэд ЗӨВХӨН ntdll-ийн функцуудыг ашигладаг. Ийм програмын жишээ: хэн гүйцэтгэдэг Үндсэн үйлчилгээг эхлүүлэхийн өмнө дискэнд алдаа байгаа эсэхийг шалгах. Энэ бол бидний Active Restore-г яг ийм түвшинд байлгахыг хүсч байна.
Бидэнд юу хэрэгтэй байна вэ?
- (Драйверын хөгжүүлэлтийн хэрэгсэл), одоо WDK 7 гэгддэг (Windows Жолоочийн хэрэгсэл).
- Виртуал машин (жишээ нь Windows 7 x64)
- Шаардлагагүй, гэхдээ татаж авах боломжтой толгой файлууд нь тус болно
Кодод юу байгаа вэ?
Жаахан дадлага хийцгээе, жишээлбэл, дараах жижиг програм бичье.
- Дэлгэц дээр мессеж харуулна
- Зарим санах ойг хуваарилдаг
- Гарын оролтыг хүлээж байна
- Ашигласан санах ойг чөлөөлнө
Уугуул програмуудад нэвтрэх цэг нь үндсэн эсвэл winmain биш, харин NtProcessStartup функц юм, учир нь бид системд шинэ процессуудыг шууд эхлүүлдэг.
Дэлгэц дээр мессеж гарч эхэлцгээе. Үүний тулд бид эх функцтэй , энэ нь 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 функц байхгүй (үүнд ажиллах цагийн С номын сангууд шаардлагатай). Мэдээжийн хэрэг та зөвхөн стек ашиглаж болно. Гэхдээ хэрэв бид санах ойг динамикаар хуваарилах шаардлагатай бол бид үүнийг нуруулдан (өөрөөр хэлбэл нуруулдан) хийх хэрэгтэй болно. Тиймээс өөрсдөдөө зориулж овоолгыг бүтээж, хэрэгтэй үед нь дурсамжаа авцгаая.
Энэ функц нь энэ ажилд тохиромжтой . Дараа нь, 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;
}
}Бидэнд хэрэгтэй зүйл бол ашиглах явдал юм Нээлттэй төхөөрөмж дээр суулгаж, гар нь бидэнд ямар ч товчлуурыг буцааж өгөх хүртэл хүлээнэ үү. Хэрэв ESC товчлуур дарвал бид үргэлжлүүлэн ажиллах болно. Төхөөрөмжийг нээхийн тулд бид NtCreateFile функцийг дуудах шаардлагатай болно (бид DeviceKeyboardClass0-ыг нээх хэрэгтэй болно). Бид бас залгана хүлээх объектыг эхлүүлэх. Бид гарны өгөгдлийг илэрхийлдэг KEYBOARD_INPUT_DATA бүтцийг өөрсдөө зарлах болно. Энэ нь бидний ажлыг хөнгөвчлөх болно.
Үндсэн програм нь функцийн дуудлагаар төгсдөг Учир нь бид зүгээр л өөрсдийн үйл явцыг алж байна.
Манай жижиг програмын бүх код:
#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-г виртуал машинтай холбох хэрэгтэй болно. Үүнийг хэрхэн хийх зааврыг олж болно эсвэл зүгээр л ашиглах .
Эмхэтгэх, угсрах
Төрөлх програм бүтээх хамгийн хялбар арга бол ашиглах явдал юм (Жолооч хөгжүүлэх багц). Сүүлчийн хувилбарууд арай өөр арга барилтай бөгөөд 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 командыг ажиллуулах хэрэгтэй. Дэлгэцийн агшин дахь үр дүн нь бидэнд нэг гүйцэтгэгдэх файл байгааг харуулж байна.

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

Төрөлх програмыг хэрхэн эхлүүлэх вэ?
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Гарчиг хөрвүүлэхийн тулд та онлайн үйлчилгээг ашиглаж болно, жишээлбэл, .

Төрөлх програмыг ажиллуулахын тулд бидэнд дараахь зүйл хэрэгтэй болно.
- Гүйцэтгэх файлыг system32 хавтас руу хуулна уу
- Бүртгэлд түлхүүр нэмнэ үү
- Машиныг дахин ачаална уу
Тохиромжтой болгох үүднээс уугуул програм суулгахад бэлэн скриптийг энд оруулав.
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,00Суулгаж, дахин ачаалсны дараа хэрэглэгчийн сонгох дэлгэц гарч ирэхээс өмнө бид дараах зургийг авах болно.

Үр дүн
Энэхүү жижиг програмыг жишээ болгон ашигласнаар бид түвшинд програмыг ажиллуулахад итгэлтэй байсан Windows Native нь бүрэн боломжтой. Дараа нь, Иннополисын Их Сургуулийн залуус бид хоёр төслийнхөө өмнөх хувилбараас хамаагүй эрт драйвертай харилцан үйлчлэлийг эхлүүлэх үйлчилгээг үргэлжлүүлэн бүтээх болно. Win32 бүрхүүл гарч ирснээр аль хэдийн боловсруулсан бүрэн хэмжээний үйлчилгээнд хяналтыг шилжүүлэх нь логиктой болно (энэ талаар дараа нь дэлгэрэнгүй ярих болно). ).
Дараагийн өгүүллээр бид Active Restore үйлчилгээний өөр нэг бүрэлдэхүүн хэсэг болох UEFI драйверын талаар ярих болно. Дараагийн нийтлэлийг алдахгүйн тулд манай блогт бүртгүүлээрэй.
Эх сурвалж: www.habr.com
