ವಿಂಡೋಸ್ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಅಕ್ರೊನಿಸ್ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆ ಸೇವೆ

ಇಂದು ನಾವು ಇನ್ನೊಪೊಲಿಸ್ ವಿಶ್ವವಿದ್ಯಾನಿಲಯದ ಹುಡುಗರೊಂದಿಗೆ ಹೇಗೆ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆ ತಂತ್ರಜ್ಞಾನವನ್ನು ಅಭಿವೃದ್ಧಿಪಡಿಸುತ್ತಿದ್ದೇವೆ ಎಂಬ ಕಥೆಯನ್ನು ನಾವು ಮುಂದುವರಿಸುತ್ತೇವೆ, ಬಳಕೆದಾರರು ವಿಫಲವಾದ ನಂತರ ಸಾಧ್ಯವಾದಷ್ಟು ಬೇಗ ತಮ್ಮ ಯಂತ್ರದಲ್ಲಿ ಕೆಲಸ ಮಾಡಲು ಪ್ರಾರಂಭಿಸುತ್ತೇವೆ. ನಾವು ಸ್ಥಳೀಯ ವಿಂಡೋಸ್ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಬಗ್ಗೆ ಮಾತನಾಡುತ್ತೇವೆ, ಅವುಗಳ ರಚನೆ ಮತ್ತು ಉಡಾವಣೆಯ ವೈಶಿಷ್ಟ್ಯಗಳು ಸೇರಿದಂತೆ. ಕಟ್ ಕೆಳಗೆ ನಮ್ಮ ಪ್ರಾಜೆಕ್ಟ್ ಬಗ್ಗೆ ಸ್ವಲ್ಪ, ಹಾಗೆಯೇ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಹೇಗೆ ಬರೆಯುವುದು ಎಂಬುದರ ಕುರಿತು ಪ್ರಾಯೋಗಿಕ ಮಾರ್ಗದರ್ಶಿಯಾಗಿದೆ.

ವಿಂಡೋಸ್ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಅಕ್ರೊನಿಸ್ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆ ಸೇವೆ

ಹಿಂದಿನ ಪೋಸ್ಟ್‌ಗಳಲ್ಲಿ ಅದು ಏನು ಎಂಬುದರ ಕುರಿತು ನಾವು ಈಗಾಗಲೇ ಮಾತನಾಡಿದ್ದೇವೆ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆ, ಮತ್ತು ಇನ್ನೊಪೊಲಿಸ್‌ನ ವಿದ್ಯಾರ್ಥಿಗಳು ಹೇಗೆ ಅಭಿವೃದ್ಧಿ ಹೊಂದುತ್ತಾರೆ ಸೇವೆ. ಇಂದು ನಾನು ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಮೇಲೆ ಕೇಂದ್ರೀಕರಿಸಲು ಬಯಸುತ್ತೇನೆ, ಅದರ ಮಟ್ಟಕ್ಕೆ ನಾವು ನಮ್ಮ ಸಕ್ರಿಯ ಚೇತರಿಕೆ ಸೇವೆಯನ್ನು "ಸಮಾಧಿ" ಮಾಡಲು ಬಯಸುತ್ತೇವೆ. ಎಲ್ಲವೂ ಕಾರ್ಯರೂಪಕ್ಕೆ ಬಂದರೆ, ನಮಗೆ ಸಾಧ್ಯವಾಗುತ್ತದೆ:

  • ಸೇವೆಯನ್ನು ಬಹಳ ಹಿಂದೆಯೇ ಪ್ರಾರಂಭಿಸಿ
  • ಬ್ಯಾಕಪ್ ಹೆಚ್ಚು ಮುಂಚಿತವಾಗಿ ಇರುವ ಕ್ಲೌಡ್ ಅನ್ನು ಸಂಪರ್ಕಿಸಿ
  • ಸಿಸ್ಟಮ್ ಯಾವ ಮೋಡ್ನಲ್ಲಿದೆ ಎಂಬುದನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳಲು ಹೆಚ್ಚು ಮುಂಚಿತವಾಗಿ - ಸಾಮಾನ್ಯ ಬೂಟ್ ಅಥವಾ ಚೇತರಿಕೆ
  • ಮುಂಚಿತವಾಗಿ ಚೇತರಿಸಿಕೊಳ್ಳಲು ಕಡಿಮೆ ಫೈಲ್‌ಗಳು
  • ಇನ್ನೂ ವೇಗವಾಗಿ ಪ್ರಾರಂಭಿಸಲು ಬಳಕೆದಾರರನ್ನು ಅನುಮತಿಸಿ.

ಹೇಗಾದರೂ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್ ಎಂದರೇನು?

ಈ ಪ್ರಶ್ನೆಗೆ ಉತ್ತರಿಸಲು, ಸಿಸ್ಟಮ್ ಮಾಡುವ ಕರೆಗಳ ಅನುಕ್ರಮವನ್ನು ನೋಡೋಣ, ಉದಾಹರಣೆಗೆ, ಪ್ರೋಗ್ರಾಮರ್ ತನ್ನ ಅಪ್ಲಿಕೇಶನ್‌ನಲ್ಲಿ ಫೈಲ್ ರಚಿಸಲು ಪ್ರಯತ್ನಿಸಿದರೆ.

ವಿಂಡೋಸ್ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಅಕ್ರೊನಿಸ್ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆ ಸೇವೆ
ಪಾವೆಲ್ ಯೋಸಿಫೊವಿಚ್ - ವಿಂಡೋಸ್ ಕರ್ನಲ್ ಪ್ರೋಗ್ರಾಮಿಂಗ್ (2019)

ಪ್ರೋಗ್ರಾಮರ್ ಕಾರ್ಯವನ್ನು ಬಳಸುತ್ತಾರೆ ಫೈಲ್ ರಚಿಸಿ, ಇದು ಹೆಡರ್ ಫೈಲ್ fileapi.h ನಲ್ಲಿ ಘೋಷಿಸಲ್ಪಟ್ಟಿದೆ ಮತ್ತು Kernel32.dll ನಲ್ಲಿ ಅಳವಡಿಸಲಾಗಿದೆ. ಆದಾಗ್ಯೂ, ಈ ಕಾರ್ಯವು ಸ್ವತಃ ಫೈಲ್ ಅನ್ನು ರಚಿಸುವುದಿಲ್ಲ, ಇದು ಇನ್ಪುಟ್ ಆರ್ಗ್ಯುಮೆಂಟ್ಗಳನ್ನು ಮಾತ್ರ ಪರಿಶೀಲಿಸುತ್ತದೆ ಮತ್ತು ಕಾರ್ಯವನ್ನು ಕರೆಯುತ್ತದೆ NtCreateFile (ಎನ್ಟಿ ಪೂರ್ವಪ್ರತ್ಯಯವು ಕಾರ್ಯವು ಸ್ಥಳೀಯವಾಗಿದೆ ಎಂದು ಸೂಚಿಸುತ್ತದೆ). ಈ ಕಾರ್ಯವನ್ನು winternl.h ಹೆಡರ್ ಫೈಲ್‌ನಲ್ಲಿ ಘೋಷಿಸಲಾಗಿದೆ ಮತ್ತು ntdll.dll ನಲ್ಲಿ ಅಳವಡಿಸಲಾಗಿದೆ. ಇದು ಪರಮಾಣು ಬಾಹ್ಯಾಕಾಶಕ್ಕೆ ನೆಗೆಯುವುದನ್ನು ಸಿದ್ಧಪಡಿಸುತ್ತದೆ, ಅದರ ನಂತರ ಫೈಲ್ ರಚಿಸಲು ಸಿಸ್ಟಮ್ ಕರೆ ಮಾಡುತ್ತದೆ. ಈ ಸಂದರ್ಭದಲ್ಲಿ, Kernel32 ಕೇವಲ Ntdll ಗಾಗಿ ಒಂದು ಹೊದಿಕೆಯಾಗಿದೆ ಎಂದು ಅದು ತಿರುಗುತ್ತದೆ. ಇದನ್ನು ಮಾಡಲು ಒಂದು ಕಾರಣವೆಂದರೆ ಮೈಕ್ರೋಸಾಫ್ಟ್ ಸ್ಥಳೀಯ ಪ್ರಪಂಚದ ಕಾರ್ಯಗಳನ್ನು ಬದಲಾಯಿಸುವ ಸಾಮರ್ಥ್ಯವನ್ನು ಹೊಂದಿದೆ, ಆದರೆ ಪ್ರಮಾಣಿತ ಇಂಟರ್ಫೇಸ್ಗಳನ್ನು ಸ್ಪರ್ಶಿಸುವುದಿಲ್ಲ. ಸ್ಥಳೀಯ ಕಾರ್ಯಗಳನ್ನು ನೇರವಾಗಿ ಕರೆ ಮಾಡಲು Microsoft ಶಿಫಾರಸು ಮಾಡುವುದಿಲ್ಲ ಮತ್ತು ಅವುಗಳಲ್ಲಿ ಹೆಚ್ಚಿನದನ್ನು ದಾಖಲಿಸುವುದಿಲ್ಲ. ಮೂಲಕ, ದಾಖಲೆರಹಿತ ಕಾರ್ಯಗಳನ್ನು ಕಾಣಬಹುದು ಇಲ್ಲಿ.

ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳ ಮುಖ್ಯ ಪ್ರಯೋಜನವೆಂದರೆ ntdll ಅನ್ನು kernel32 ಗಿಂತ ಮುಂಚೆಯೇ ಸಿಸ್ಟಮ್‌ಗೆ ಲೋಡ್ ಮಾಡಲಾಗಿದೆ. ಇದು ತಾರ್ಕಿಕವಾಗಿದೆ, ಏಕೆಂದರೆ kernel32 ಗೆ ಕೆಲಸ ಮಾಡಲು ntdll ಅಗತ್ಯವಿದೆ. ಪರಿಣಾಮವಾಗಿ, ಸ್ಥಳೀಯ ಕಾರ್ಯಗಳನ್ನು ಬಳಸುವ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಹೆಚ್ಚು ಮುಂಚಿತವಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸಲು ಪ್ರಾರಂಭಿಸಬಹುದು.

ಹೀಗಾಗಿ, ವಿಂಡೋಸ್ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ವಿಂಡೋಸ್ ಬೂಟ್‌ನಲ್ಲಿ ಪ್ರಾರಂಭವಾಗುವ ಪ್ರೋಗ್ರಾಂಗಳಾಗಿವೆ. ಅವರು ntdll ನಿಂದ ಕಾರ್ಯಗಳನ್ನು ಮಾತ್ರ ಬಳಸುತ್ತಾರೆ. ಅಂತಹ ಅಪ್ಲಿಕೇಶನ್‌ನ ಉದಾಹರಣೆ: autochk ಯಾರು ನಿರ್ವಹಿಸುತ್ತಾರೆ chkdisk ಉಪಯುಕ್ತತೆ ಮುಖ್ಯ ಸೇವೆಗಳನ್ನು ಪ್ರಾರಂಭಿಸುವ ಮೊದಲು ದೋಷಗಳಿಗಾಗಿ ಡಿಸ್ಕ್ ಅನ್ನು ಪರೀಕ್ಷಿಸಲು. ನಮ್ಮ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆಯು ನಿಖರವಾಗಿ ನಾವು ಬಯಸುವ ಮಟ್ಟವಾಗಿದೆ.

ನಮಗೆ ಏನು ಬೇಕು?

  • ಡಿಡಿಕೆ (ಚಾಲಕ ಅಭಿವೃದ್ಧಿ ಕಿಟ್), ಈಗ ಇದನ್ನು WDK 7 (ವಿಂಡೋಸ್ ಡ್ರೈವರ್ ಕಿಟ್) ಎಂದೂ ಕರೆಯುತ್ತಾರೆ.
  • ವರ್ಚುವಲ್ ಯಂತ್ರ (ಉದಾಹರಣೆಗೆ, ವಿಂಡೋಸ್ 7 x64)
  • ಅಗತ್ಯವಿಲ್ಲ, ಆದರೆ ಡೌನ್‌ಲೋಡ್ ಮಾಡಬಹುದಾದ ಹೆಡರ್ ಫೈಲ್‌ಗಳು ಸಹಾಯ ಮಾಡಬಹುದು ಇಲ್ಲಿ

ಕೋಡ್‌ನಲ್ಲಿ ಏನಿದೆ?

ನಾವು ಸ್ವಲ್ಪ ಅಭ್ಯಾಸ ಮಾಡೋಣ ಮತ್ತು ಉದಾಹರಣೆಗೆ, ಒಂದು ಸಣ್ಣ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಬರೆಯಿರಿ:

  1. ಪರದೆಯ ಮೇಲೆ ಸಂದೇಶವನ್ನು ಪ್ರದರ್ಶಿಸುತ್ತದೆ
  2. ಸ್ವಲ್ಪ ಮೆಮೊರಿಯನ್ನು ನಿಯೋಜಿಸುತ್ತದೆ
  3. ಕೀಬೋರ್ಡ್ ಇನ್‌ಪುಟ್‌ಗಾಗಿ ಕಾಯುತ್ತಿದೆ
  4. ಬಳಸಿದ ಮೆಮೊರಿಯನ್ನು ಮುಕ್ತಗೊಳಿಸುತ್ತದೆ

ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳಲ್ಲಿ, ಪ್ರವೇಶ ಬಿಂದುವು ಮುಖ್ಯ ಅಥವಾ ವಿನ್‌ಮೈನ್ ಅಲ್ಲ, ಆದರೆ 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);
}

ಪಿಎಸ್: ಡಿಬಗ್ಗರ್‌ನಲ್ಲಿ ಅದನ್ನು ನಿಲ್ಲಿಸಲು ನಾವು ನಮ್ಮ ಕೋಡ್‌ನಲ್ಲಿ DbgBreakPoint() ಕಾರ್ಯವನ್ನು ಸುಲಭವಾಗಿ ಬಳಸಬಹುದು. ನಿಜ, ನೀವು WinDbg ಅನ್ನು ಕರ್ನಲ್ ಡೀಬಗ್ ಮಾಡಲು ವರ್ಚುವಲ್ ಯಂತ್ರಕ್ಕೆ ಸಂಪರ್ಕಿಸಬೇಕಾಗುತ್ತದೆ. ಇದನ್ನು ಹೇಗೆ ಮಾಡಬೇಕೆಂಬುದರ ಕುರಿತು ಸೂಚನೆಗಳನ್ನು ಕಾಣಬಹುದು ಇಲ್ಲಿ ಅಥವಾ ಕೇವಲ ಬಳಸಿ ವರ್ಚುವಲ್ ಕೆಡಿ.

ಸಂಕಲನ ಮತ್ತು ಜೋಡಣೆ

ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ನಿರ್ಮಿಸಲು ಸುಲಭವಾದ ಮಾರ್ಗವೆಂದರೆ ಬಳಸುವುದು ಡಿಡಿಕೆ (ಚಾಲಕ ಅಭಿವೃದ್ಧಿ ಕಿಟ್). ನಮಗೆ ಪ್ರಾಚೀನ ಏಳನೇ ಆವೃತ್ತಿಯ ಅಗತ್ಯವಿದೆ, ಏಕೆಂದರೆ ನಂತರದ ಆವೃತ್ತಿಗಳು ಸ್ವಲ್ಪ ವಿಭಿನ್ನವಾದ ವಿಧಾನವನ್ನು ಹೊಂದಿವೆ ಮತ್ತು ವಿಷುಯಲ್ ಸ್ಟುಡಿಯೊದೊಂದಿಗೆ ನಿಕಟವಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತವೆ. ನಾವು DDK ಅನ್ನು ಬಳಸಿದರೆ, ನಮ್ಮ ಯೋಜನೆಗೆ ಮೇಕ್‌ಫೈಲ್ ಮತ್ತು ಮೂಲಗಳು ಮಾತ್ರ ಅಗತ್ಯವಿದೆ.

ಮೇಕ್ಫೈಲ್

!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

ನಿಮ್ಮ ಮೇಕ್‌ಫೈಲ್ ಒಂದೇ ಆಗಿರುತ್ತದೆ, ಆದರೆ ಮೂಲಗಳನ್ನು ಸ್ವಲ್ಪ ಹೆಚ್ಚು ವಿವರವಾಗಿ ನೋಡೋಣ. ಈ ಫೈಲ್ ನಿಮ್ಮ ಪ್ರೋಗ್ರಾಂನ ಮೂಲಗಳು (.c ಫೈಲ್‌ಗಳು), ಬಿಲ್ಡ್ ಆಯ್ಕೆಗಳು ಮತ್ತು ಇತರ ನಿಯತಾಂಕಗಳನ್ನು ನಿರ್ದಿಷ್ಟಪಡಿಸುತ್ತದೆ.

  • TARGETNAME – ಕೊನೆಯಲ್ಲಿ ಉತ್ಪಾದಿಸಬೇಕಾದ ಕಾರ್ಯಗತಗೊಳಿಸಬಹುದಾದ ಫೈಲ್‌ನ ಹೆಸರು.
  • TARGETTYPE - ಕಾರ್ಯಗತಗೊಳಿಸಬಹುದಾದ ಫೈಲ್ ಪ್ರಕಾರ, ಅದು ಚಾಲಕ (.sys) ಆಗಿರಬಹುದು, ನಂತರ ಕ್ಷೇತ್ರ ಮೌಲ್ಯವು ಡ್ರೈವರ್ ಆಗಿರಬೇಕು, ಲೈಬ್ರರಿ (.lib) ಆಗಿದ್ದರೆ, ಮೌಲ್ಯವು ಲೈಬ್ರರಿ ಆಗಿರುತ್ತದೆ. ನಮ್ಮ ಸಂದರ್ಭದಲ್ಲಿ, ನಮಗೆ ಕಾರ್ಯಗತಗೊಳಿಸಬಹುದಾದ ಫೈಲ್ (.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) ಪರಿಶೀಲಿಸಿದ ಬಿಲ್ಡ್ ಅನ್ನು ಚಲಾಯಿಸಬೇಕು, ಕೆಲಸದ ಡೈರೆಕ್ಟರಿಯನ್ನು ಪ್ರಾಜೆಕ್ಟ್ ಫೋಲ್ಡರ್ಗೆ ಬದಲಾಯಿಸಿ ಮತ್ತು ಬಿಲ್ಡ್ ಆಜ್ಞೆಯನ್ನು ಚಲಾಯಿಸಬೇಕು. ಸ್ಕ್ರೀನ್‌ಶಾಟ್‌ನಲ್ಲಿನ ಫಲಿತಾಂಶವು ನಾವು ಒಂದು ಕಾರ್ಯಗತಗೊಳಿಸಬಹುದಾದ ಫೈಲ್ ಅನ್ನು ಹೊಂದಿದ್ದೇವೆ ಎಂದು ತೋರಿಸುತ್ತದೆ.

ವಿಂಡೋಸ್ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಅಕ್ರೊನಿಸ್ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆ ಸೇವೆ

ಈ ಫೈಲ್ ಅನ್ನು ಅಷ್ಟು ಸುಲಭವಾಗಿ ಪ್ರಾರಂಭಿಸಲಾಗುವುದಿಲ್ಲ, ಸಿಸ್ಟಮ್ ಶಪಿಸುತ್ತದೆ ಮತ್ತು ಕೆಳಗಿನ ದೋಷದೊಂದಿಗೆ ಅದರ ನಡವಳಿಕೆಯ ಬಗ್ಗೆ ಯೋಚಿಸಲು ನಮಗೆ ಕಳುಹಿಸುತ್ತದೆ:

ವಿಂಡೋಸ್ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಅಕ್ರೊನಿಸ್ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆ ಸೇವೆ

ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಹೇಗೆ ಪ್ರಾರಂಭಿಸುವುದು?

ಆಟೋಚ್ಕ್ ಪ್ರಾರಂಭವಾದಾಗ, ಕಾರ್ಯಕ್ರಮಗಳ ಆರಂಭಿಕ ಅನುಕ್ರಮವನ್ನು ನೋಂದಾವಣೆ ಕೀಲಿಯ ಮೌಲ್ಯದಿಂದ ನಿರ್ಧರಿಸಲಾಗುತ್ತದೆ:

HKLMSystemCurrentControlSetControlSession ManagerBootExecute

ಸೆಷನ್ ಮ್ಯಾನೇಜರ್ ಈ ಪಟ್ಟಿಯಿಂದ ಪ್ರೋಗ್ರಾಂಗಳನ್ನು ಒಂದೊಂದಾಗಿ ಕಾರ್ಯಗತಗೊಳಿಸುತ್ತಾನೆ. ಸೆಷನ್ ಮ್ಯಾನೇಜರ್ ಸಿಸ್ಟಮ್ 32 ಡೈರೆಕ್ಟರಿಯಲ್ಲಿ ಕಾರ್ಯಗತಗೊಳಿಸಬಹುದಾದ ಫೈಲ್‌ಗಳನ್ನು ಹುಡುಕುತ್ತದೆ. ನೋಂದಾವಣೆ ಕೀ ಮೌಲ್ಯದ ಸ್ವರೂಪವು ಈ ಕೆಳಗಿನಂತಿರುತ್ತದೆ:

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

ಶೀರ್ಷಿಕೆಯನ್ನು ಪರಿವರ್ತಿಸಲು, ನೀವು ಆನ್‌ಲೈನ್ ಸೇವೆಯನ್ನು ಬಳಸಬಹುದು, ಉದಾಹರಣೆಗೆ, ಇದು.

ವಿಂಡೋಸ್ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಅಕ್ರೊನಿಸ್ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆ ಸೇವೆ
ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು, ನಮಗೆ ಅಗತ್ಯವಿದೆ ಎಂದು ಅದು ತಿರುಗುತ್ತದೆ:

  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

ಅನುಸ್ಥಾಪನೆಯ ನಂತರ ಮತ್ತು ರೀಬೂಟ್ ಮಾಡಿದ ನಂತರ, ಬಳಕೆದಾರರ ಆಯ್ಕೆಯ ಪರದೆಯು ಕಾಣಿಸಿಕೊಳ್ಳುವ ಮೊದಲು, ನಾವು ಈ ಕೆಳಗಿನ ಚಿತ್ರವನ್ನು ಪಡೆಯುತ್ತೇವೆ:

ವಿಂಡೋಸ್ ಸ್ಥಳೀಯ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಮತ್ತು ಅಕ್ರೊನಿಸ್ ಸಕ್ರಿಯ ಮರುಸ್ಥಾಪನೆ ಸೇವೆ

ಫಲಿತಾಂಶ

ಅಂತಹ ಸಣ್ಣ ಅಪ್ಲಿಕೇಶನ್‌ನ ಉದಾಹರಣೆಯನ್ನು ಬಳಸಿಕೊಂಡು, ವಿಂಡೋಸ್ ಸ್ಥಳೀಯ ಮಟ್ಟದಲ್ಲಿ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಚಲಾಯಿಸಲು ಸಾಕಷ್ಟು ಸಾಧ್ಯವಿದೆ ಎಂದು ನಮಗೆ ಮನವರಿಕೆಯಾಯಿತು. ಮುಂದೆ, ಇನ್ನೋಪೊಲಿಸ್ ವಿಶ್ವವಿದ್ಯಾಲಯದ ವ್ಯಕ್ತಿಗಳು ಮತ್ತು ನಾನು ನಮ್ಮ ಯೋಜನೆಯ ಹಿಂದಿನ ಆವೃತ್ತಿಗಿಂತ ಮುಂಚೆಯೇ ಚಾಲಕನೊಂದಿಗೆ ಸಂವಹನ ಪ್ರಕ್ರಿಯೆಯನ್ನು ಪ್ರಾರಂಭಿಸುವ ಸೇವೆಯನ್ನು ನಿರ್ಮಿಸುವುದನ್ನು ಮುಂದುವರಿಸುತ್ತೇವೆ. ಮತ್ತು win32 ಶೆಲ್‌ನ ಆಗಮನದೊಂದಿಗೆ, ನಿಯಂತ್ರಣವನ್ನು ಈಗಾಗಲೇ ಅಭಿವೃದ್ಧಿಪಡಿಸಿದ ಪೂರ್ಣ ಪ್ರಮಾಣದ ಸೇವೆಗೆ ವರ್ಗಾಯಿಸುವುದು ತಾರ್ಕಿಕವಾಗಿದೆ (ಇದರ ಬಗ್ಗೆ ಇನ್ನಷ್ಟು ಇಲ್ಲಿ).

ಮುಂದಿನ ಲೇಖನದಲ್ಲಿ ನಾವು ಸಕ್ರಿಯ ಪುನಃಸ್ಥಾಪನೆ ಸೇವೆಯ ಮತ್ತೊಂದು ಘಟಕವನ್ನು ಸ್ಪರ್ಶಿಸುತ್ತೇವೆ, ಅವುಗಳೆಂದರೆ UEFI ಚಾಲಕ. ನಮ್ಮ ಬ್ಲಾಗ್‌ಗೆ ಚಂದಾದಾರರಾಗಿ ಆದ್ದರಿಂದ ನೀವು ಮುಂದಿನ ಪೋಸ್ಟ್ ಅನ್ನು ತಪ್ಪಿಸಿಕೊಳ್ಳಬೇಡಿ.

ಮೂಲ: www.habr.com

ಕಾಮೆಂಟ್ ಅನ್ನು ಸೇರಿಸಿ