Sot vazhdojmĂ« tregimin pĂ«r mĂ«nyrĂ«n se si ne, sĂ« bashku me djemtĂ« nga Universitetei Innopolis, zhvillojmĂ« teknologjinĂ« Active Restore, pĂ«r tĂ« lejuar pĂ«rdoruesit tĂ« fillojnĂ« sa mĂ« shpejt tĂ« jetĂ« e mundur punĂ«n nĂ« makinĂ«n e tij pas njĂ« dĂ«shtimi. Do tĂ« flasim pĂ«r aplikacionet native tĂ« Windows, duke pĂ«rfshirĂ« karakteristikat e krijimit dhe ekzekutimit tĂ« tyre. NĂ«n kĂ«tĂ« titull â pak rreth projektit tonĂ« dhe njĂ« udhĂ«zues praktik se si tĂ« shkruani aplikacione native.

Në postimet e kaluara, ne tashmë kemi folur se çfarë është , dhe se si studentët nga Innopolis po zhvillojnë . Sot dua të ndalem te aplikacionet native, deri në nivelin që ne dëshirojmë ta "thellojmë" shërbimin tonë të rikthimit aktiv. Nëse gjithçka shkon mirë, atëherë ne do të mund:
- Të nisim shërbimin shumë më herët
- Të lidhemi me cloud-in ku ruhet backup-i shumë më herët
- TĂ« kuptojmĂ« shumĂ« mĂ« herĂ«t nĂ« cilin modalitet ndodhet sistemi â ngarkimi normal apo rikthimi
- Të rikthejmë shumë më pak skedarë paraprakisht
- Të lejojmë përdoruesin të fillojë punën akoma më shpejt.
ĂfarĂ« Ă«shtĂ« nĂ« tĂ« vĂ«rtetĂ« njĂ« aplikacion native?
Për të përgjigjur këtë pyetje, le të shikojmë sekuencën e thirrjeve që bën sistemi, për shembull, nëse një programues përpiqet të krijojë një skedar në aplikacionin e tij.

Pavel Yosifovich â Programimi i Kernel-it tĂ« Windows (2019)
Programuesi përdor funksionin , i cili është shpallur në skedarin e titullit fileapi.h dhe është realizuar në Kernel32.dll. Megjithatë, vetë kjo funksion nuk merret me krijimin e skedarit, ajo vetëm kontrollon argumentet në hyrje dhe thërret funksionin (prefiksi Nt tregon se funksioni është native). Ky funksion është shpallur në skedarin e titullit winternl.h dhe është realizuar në ntdll.dll. Ai bën përgatitje për një skak të hapësirës së kernels, pas së cilës bën një thirrje sistemore për të krijuar skedarin. Në këtë rast, del se Kernel32 është thjesht një mbështjellës për Ntdll. Një nga arsyet pse kjo është bërë është se Microsoft ka mundësinë të ndryshojë funksionet e botës native, por pa prekur ndërfaqet standarde. Microsoft nuk rekomandon thirrjen direkte të funksioneve native dhe nuk dokumenton shumicën e tyre. Për më tepër, funksionet e pa dokumentuara mund të gjenden .
Avantazhi kryesor i aplikacioneve natyrale është se ntdll ngarkohet në sistem shumë më herët se kernel32. Kjo ka kuptim, pasi kernel32 kërkon praninë e ntdll për të operuar. Si pasojë, aplikacionet që përdorin funksione natyrale mund të fillojnë punën shumë më herët.
Prandaj, Aplikacionet Natyrore tĂ« Windows janĂ« programe qĂ« mund tĂ« fillojnĂ« nĂ« njĂ« fazĂ« tĂ« hershme tĂ« ngarkesĂ«s sĂ« Windows. Ato pĂ«rdorin VETĂM funksione nga ntdll. NjĂ« shembull i tillĂ« i aplikacionit: i cili ekzekuton pĂ«r tĂ« kontrolluar disqin pĂ«r gabime para se tĂ« nisin shĂ«rbimet kryesore. PikĂ«risht nĂ« kĂ«tĂ« nivel dĂ«shirojmĂ« tĂ« shohim Active Restore.
ĂfarĂ« na nevojitet?
- (Pako e Zhvillimit të Drejtuesve), tani e njohur gjithashtu si WDK 7 (Pako e Drejtuesve të Windows).
- Një makinë virtuale (p.sh., Windows 7 x64)
- Nuk është e domosdoshme, por mund të ndihmojnë skedarët e titujve që mund të shkarkoni
ĂfarĂ« ka nĂ« kod?
Le të praktikojmë pak dhe për shembull të shkruajmë një aplikacion të vogël që:
- Shfaq një mesazh në ekran
- Alokoni pak memorje
- Pret input nga tastiera
- Ăliron memorjen e zĂ«nĂ«
Në aplikacionet natyrale, pika e hyrjes nuk është main ose winmain, por funksioni NtProcessStartup, pasi realisht po fillojmë drejtpërdrejt procese të reja në sistem.
Le të fillojmë me shfaqjen e një mesazhi në ekran. Për këtë kemi funksionin natyral , i cili si argument merr një tregues në objektin e strukturës UNICODE_STRING. Ta inicializojmë atë do të na ndihmojë RtlInitUnicodeString. Si rezultat, për të shfaqur tekstin në ekran, mund të shkruajmë një funksion të vogël të tillë:
//usage: WriteLn(L"Here is my textn");
void WriteLn(LPWSTR Message)
{
UNICODE_STRING string;
RtlInitUnicodeString(&string, Message);
NtDisplayString(&string);
}Duke qenë se na janë në dispozicion vetëm funksionet nga ntdll, dhe asnjë bibliotekë tjetër në memorie thjesht nuk ka, patjetër do të kemi probleme me alokimin e memorjes. Operatori new ende nuk ekziston (sepse ai vjen nga një botë shumë e lartë C++), as nuk ka funksionin malloc (për të nevojiten bibliotekat e kohës së ekzekutimit C). Sigurisht, mund të përdorim vetëm stekin. Por nëse na nevojitet të alokojmë memorje në mënyrë dinamike, do të duhet ta bëjmë këtë në kupë (dmth. heap). Prandaj, le të krijojmë një kupë për veten tonë dhe të marrim prej saj memorje kur na nevojitet.
Për këtë qëllim, funksioni . Më pas, duke përdorur RtlAllocateHeap dhe RtlFreeHeap, ne do të marrim dhe çlirojmë memorje kur na nevojitet.
PVOID memory = NULL;
PVOID buffer = NULL;
ULONG bufferSize = 42;
// krijo heap për të alokuar më vonë memorje
memory = RtlCreateHeap(
HEAP_GROWABLE,
NULL,
1000,
0, NULL, NULL
);
// alokoni një tampon me madhësi bufferSize
buffer = RtlAllocateHeap(
memory,
HEAP_ZERO_MEMORY,
bufferSize
);
// çlironi tamponin (në të vërtetë nuk është e nevojshme sepse shkatërrojmë heap-in në hapin e ardhshëm)
RtlFreeHeap(memory, 0, buffer);
RtlDestroyHeap(memory);Të kalojmë te pritja e hyrjes nga tastiera.
// 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;
}
}Gjithçka që na nevojitet është të përdorim në një pajisje të hapur dhe të presim deri sa tastiera të kthejë një shtypje. Nëse shtypet çelësi ESC, ne do të vazhdojmë punën. Për të hapur pajisjen, do të na nevojitet të thërrasim funksionin NtCreateFile (duhet të hapim DeviceKeyboardClass0). Gjithashtu, do të thërrasim , për të inicializuar një objekt për pritje. Ne do të shpallim vetë strukturën KEYBOARD_INPUT_DATA, e cila përfaqëson të dhënat e tastierës. Kjo do ta bëjë punën tonë më të lehtë.
Puna e aplikacionit natyror përfundon me thirrjen e funksionit , sepse thjesht po vrasim procesin tonë.
I gjithë kodi i aplikacionit tonë të vogël:
#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: Ne mund ta përdorim lehtësisht në kod funksionin DbgBreakPoint() për të ndaluar në debagues. Megjithatë, duhet të lidhim WinDbg me makinën virtuale për debagimin e kernelit. Udhëzimet se si të bëni këtë mund të gjeni ose thjesht të përdorim .
Kompilimi dhe ndërtimi
Mënyra më e thjeshtë për të ndërtuar një aplikacion natyror është të përdorni (Driver Development Kit). Na nevojitet versioni antik i shtatë, sepse versionet më të vonshme kanë një qasje të ndryshme dhe punojnë ngushtë me Visual Studio. Nëse përdorim DDK, projekti ynë ka nevojë vetëm për Makefile dhe sources.
Makefile
!INCLUDE $(NTMAKEENV)makefile.defsources:
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 = 1Makefile juaj do të jetë pikërisht kështu, ndërsa për sources le të ndalemi pak më gjatë. Në këtë skedë tregohet kodet burimore të programit tuaj (skedarët .c), opsionet e ndërtimit dhe parametra të tjera.
- TARGETNAME â emri i skedarit ekzekutiv qĂ« duhet tĂ« rezultojĂ« nĂ« fund.
- TARGETTYPE â tipi i skedarit ekzekutiv, mund tĂ« jetĂ« driver (.sys), atĂ«herĂ« vlera e fushĂ«s duhet tĂ« jetĂ« DRIVER, nĂ«se Ă«shtĂ« bibliotekĂ« (.lib), atĂ«herĂ« vlera LIBRARY. NĂ« rastin tonĂ« na nevojitet njĂ« skedar ekzekutiv (.exe), prandaj e vendosim vlerĂ«n PROGRAM.
- UMTYPE â vlerat e mundshme tĂ« kĂ«tij fushe: console pĂ«r aplikacione konsolĂ«, windows pĂ«r operim nĂ« mĂ«nyrĂ« dritare. Por na nevojitet tĂ« tregojmĂ« nt, pĂ«r tĂ« marrĂ« njĂ« aplikacion natyror.
- BUFFER_OVERFLOW_CHECKS â kontrolli i stack-ut pĂ«r mbivendosje tĂ« buffer-it, fatkeqĂ«sisht nuk Ă«shtĂ« rasti ynĂ«, e çaktivizojmĂ«.
- MINWIN_SDK_LIB_PATH â kjo vlerĂ« i referohet ndryshoreve SDK_LIB_PATH, nuk e keni pĂ«r tĂ« shqetĂ«suar qĂ« nuk keni shpallur njĂ« ndryshore tĂ« tillĂ« sistemore, nĂ« momentin kur tĂ« iniciojmĂ« njĂ« ndĂ«rtim tĂ« kontrolluar nga DDK, kjo ndryshore do tĂ« shpallet dhe do tĂ« tregojĂ« libraritĂ« e nevojshme.
- SOURCES â lista e burimeve tĂ« programit tuaj.
- INCLUDES â skedarĂ«t header qĂ« janĂ« tĂ« nevojshĂ«m pĂ«r ndĂ«rtimin. KĂ«tu zakonisht tregohet rruga pĂ«r skedarĂ«t qĂ« vijnĂ« me DDK, por mund tĂ« tregoni pĂ«rveç kĂ«saj çdo skedĂ« tjetĂ«r.
- TARGETLIBS â lista e librarive qĂ« duhet tĂ« lidhni.
- USE_NTDLL â njĂ« fushĂ« e domosdoshme qĂ« duhet vendosur nĂ« pozita 1. PĂ«r arsye tĂ« mjaftueshme.
- USER_C_FLAGS â çdo flamur qĂ« mund tĂ« pĂ«rdorni nĂ« direktivat e preprocessorit gjatĂ« pĂ«rgatitjes sĂ« kodit tĂ« aplikacionit.
Pra, për ndërtimin na nevojitet të iniciojmë x86 (ose x64) Ndërtimin e Kontrolluar, të ndryshojmë katalogun e punës në dosjen e projektit dhe të ekzekutojmë komandën Build. Rezultati në skrin tregon se kemi ndërtuar një skedar ekzekutiv.

Ky skedar nuk mund të startohet kaq lehtë, sistemi e ankohet dhe na dërgon të mendojmë për sjelljen e tij me gabimin e mëposhtëm:

Si të iniciojmë një aplikacion natyror?
Në momentin e startit autochk, rendi i ekzekutimit të programeve përcaktohet nga vlera e çelësit të regjistrit:
HKLMSystemCurrentControlSetControlSession ManagerBootExecuteMenaxheri i seancës ekzekuton rend pas rendi programet nga ky listë. Vetë skedarët ekzekutivë i kërkon menaxheri i seancës në drejtorinë system32. Formati i vlerës së çelësit të regjistrit është si në vijim:
autocheck autochk *MyNativeVlera duhet të jetë në formatin heksadecimal dhe jo në ASCII të zakonshëm, prandaj çelësi i paraqitur më sipër do të ketë formatin:
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,00Për të konvertuar emrin, mund të përdorni një shërbim online, për shembull, .

Pra, për të nisur një aplikacion natyror, na nevojitet:
- Të kopjojmë skedarin ekzekutiv në dosjen system32
- Të shtojmë në regjistër çelësin
- Të rindezë makinën
Për lehtësi, ja një skenar i gatshëm për instalimin e aplikacionit natyror:
install.bat
@echo off
copy MyNative.exe %systemroot%system32.
regedit /s add.reg
echo Shembulli Native Instaluar
pauseadd.reg
REGEDIT4
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session 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,00Pas instalimit dhe rindezjes, para se të shfaqet ekrani i zgjedhjes së përdoruesve, ne do të kemi pamjen e mëposhtme:

Përfundimi
Në shembullin e kësaj aplikacioni të vogël, ne u siguruam se është e mundur të nisësh një aplikacion në nivelin Windows Native. Më tutje, me djemtë nga Universiteti Innopolis do të vazhdojmë të ndërtojmë një shërbim që do të nxisë procesin e ndërveprimit me drejtuesin shumë më herët se në versionin e mëparshëm të projektit tonë. Dhe me shfaqjen e shell-it win32, do të jetë logjike të transferohet kontrolli në një shërbim të plotë, i cili është zhvilluar tashmë (për këtë më shumë ).
Në artikullin e ardhshëm do të prekim një tjetër komponent të shërbimit Active Restore, pra drejtuesin UEFI. Regjistrohuni në blogun tonë për të mos humbur postimin e ardhshëm.
Burimi: habr.com
