Одной привлекающей внимание демонстрацией может быть выключение компьютера.
К сожалению, управление питанием является сложным вопросом, оно включает в себя ACPI-спецификацию , которая является довольно абстрактнойи долго, еще больше они не первая попытка .
ACPI сложен, потому что разные поставщики требуют разных действий для управления аспектом компьютера, в любом случае, если у вас есть чипсет Intel (рекомендуется серия 200, но должны работать и другие серии), мы можем пропустить большую часть слоя ACPI и использовать вместо этого таблицу данных.
Вы должны проверить эту программу на конечном оборудовании, прежде чем использовать ее в классе .
ACPI определяет четыре глобальных состояния системы, G0 - G3 , где G3 - механическое отключение (т. Е. Вилка была извлечена, батарея извлечена), а G2 - мягкое выключение .
Программное обеспечение может ввести только G2 , и это можно сделать, войдя в режим сна S5 .
Состояние сна контролируется PCH (набор микросхем Intel) через регистр ввода-вывода (PM1a_CNT_BLK
), этот регистр находится в блоке ACPI, определенном в пространстве конфигурации PCI функции 31 устройства 2 (контроллер PM).
Нужно прочитать базовый адрес блока и затем добавить четыре (4), чтобы получить адрес интересующего регистра.
Я не буду делать это программно, вместо этого программа сборкиожидая символ с этим адресом.
Для получения адреса регистра PM1a_CNT_BLK
можно использовать /proc/ioports
следующим образом:
sudo cat /proc/ioports | grep 'PM1a_CNT_BLK' | cut -f3 -d' ' | cut -f1 -d'-'
Это дает шестнадцатеричный адрес регистра.Если ничего не распечатано, вероятно, что чипсет не поддерживается.
В любом случае адрес может быть 1804
.
Состояние сна контролируется битами 10:12 (SLP_TYP
) регистра и битом 13 (* 1053).*).
SLP_TYP
- это 3-битное значение для выбора состояния для ввода ( S5 равно 7), а бит 13 - это бит разрешения.
Регистр имеет другое значение, которое должнобыть сохранено , поэтому необходимо выполнить операцию чтения-изменения-записи.
Использование инструкций in
и out
невозможно для кольца 3, если TSS процесса не имеет IOPL (уровень привилегий IO) 3 (или порты включены в карте портов IO).
IOPL сообщает, какие кольца могут использовать in
и out
, значение X
означает, что все кольца могут X
или меньше.
Эта программа пытается выключить компьютер и при желании установить IOPL на заданное значение (через символ IOPL
):
BITS 64
GLOBAL _start
SECTION .text
_start:
;Set the IOPL, only if greater than 0 (since 0 is the default)
%if IOPL > 0
lea rsi, [rsp-80h] ;We don't care about the pt_regs struct and we use the RED ZONE
mov edi, IOPL ;IOPL to set
mov eax, 172
syscall ;Set iopl
and eax, 0fh ;Just keep the last nibble, it can be 0 (success), 10 (invalid IOPL) or 15 (insufficient OS permissions)
test eax, eax ;Test for errors
mov edi, eax ;We exit with status 10 or 15 if the iopl syscall failed
jnz .exit
%endif
;Power off the PC
mov dx, PM1a_CNT_BLK
in eax, dx ;Read the current value
and eax, 0ffffc003h ;Clear SLP_TYP and SLP_EN
or eax, (7 << 10) | (1 << 13) ;Set SLP_TYP to 7 and SLP_EN to 1
out dx, eax ;Power off
;This is just for safety, execution should STOP BEFORE arriving here. This exits the process with status
;0
xor edi, edi
;Exit the process with a numerical status as specified in RDI
.exit:
mov eax, 60
syscall
Может быть собран с nasm po.asm -DPM1a_CNT_BLK=$1 -DIOPL=$2 -felf64 -o po.o
, где$1
- это адрес порта PM1a_CNT_BLK
, как указано выше , но с префиксом 0x (в моем случае он становится 0x1804
), а $2
- это число (0-3) для установки IOPLк.
IOPL устанавливается, если он не равен 0, поскольку 0 является значением по умолчанию (т. е. только кольцо 0 может использовать in
и out
)
Примечание :Передайте разумные значения символам, иначе программа не будет собрана.
Это интересно следующими способами:
- При запуске с IOPL, равным 0, программа аварийно завершает работу с #GP из-за использования
in
.
Это демонстрирует механизм безопасности процессора. - Если запустить с IOPL> 0, но не с правами root, произойдет сбой из-за недостаточных привилегий.
Это демонстрирует механизм безопасности ОС, позволяющий только IOPL изменять только root. - Если запустить с IOPL> 0, но <3 и в качестве пользователя root, он будет #GP из-за использования <code>in.
Это показывает, что пользовательские программы выполняются на кольце 3 (IOPL недостаточно высок). - При запуске с IOPL = 3 и в качестве пользователя root он выключит компьютер (или завершится с ошибкой и вернется, возможно, оставив систему в неизвестном состоянии).
Это демонстрирует риск предоставления пользовательским программам доступа к оборудованию., - Если IOPL> 3, произойдет сбой из-за недопустимого значения IOPL.
Это показывает, что есть только четыре кольца.
Я сделал репозиторий git с кодом и скриптом built.sh
, который вы можете использовать для сборки и запуска другой версии программы.
Этот скрипт полезен , поскольку он преобразует состояние выхода po
в удобную для пользователя строку , подходящую для экспериментов.
Скрипт ожидает адрес PM1a_CNT_BLK
в качестве первого аргумента (с префиксом 0x
) и IOPL в качестве второго.
Используйте это как:
./build.sh 0x1804 0
./build.sh 0x1804 3
sudo ./build.sh 0x1804 2
sudo ./build.sh 0x1804 4
sudo ./build.sh 0x1804 3
Конечно, измените адрес регистра.
Трюк с IOPL - это просто ... трюк. Это не делает программу работающей на Ring 0, она полезна для отладки, но не намного.
Чтобы запустить код в Ring 0, вам нужен LKM (загружаемый модуль ядра).
В том же репозитории я включил lkm dir с примером LKM.
При загрузке модуля попытка выключить компьютер (мгновенно).
Код минимальный:
#include <linux/module.h> /* Needed by all modules */
#include <linux/kernel.h> /* Needed for KERN_INFO */
#include <linux/fs.h> /* Needed for KERN_INFO */
#include <asm/io.h> /* Needed for inl and outl */
#define PM1a_CNT_BLK 0x1804
unsigned char bytes[10];
int __init lkm_init(void)
{
unsigned int pm1a;
printk(KERN_INFO "I'm going to power the computer off");
pm1a = inl(PM1a_CNT_BLK);
pm1a = ( pm1a & 0xffffc003 ) | ( 7 << 10 ) | ( 1 << 13 );
outl(pm1a, PM1a_CNT_BLK);
printk(KERN_WARNING "Powering off failed");
return 0;
}
static void __exit lkm_exit(void)
{
}
module_init(lkm_init);
module_exit(lkm_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("M.Bloom");
MODULE_DESCRIPTION("Attempt to power down the computer");
Чтобы сделать LKM, сначала отредактируйте PM1a_CNT_BLK
define , затем запустите make
в том же каталоге (вам понадобятся заголовки ядра), Makefile
является стандартным для *. загружаемые модули 1147 *
Для загрузки модуля используйте insmod po
в качестве пользователя root (это механизм безопасности ОС).
Я скомпилировал, но не тестировал этот LKM, так как я уже начал писать этот ответ .
Вы можете со временем исправить это, используйте dmesg
, чтобы проверить вывод модуля.
Вы можете использовать LKM в качестве каркаса для выполнения кода Ring 0, хотя при работе с памятью вы должны знать, как Linux обрабатывает виртуальную память.
И последнее замечание: если вы собираетесь проверить / использовать эти программы, обязательно закройте все приложения, запустите sync
и, если хотите, переключитесь на запуск уровня 1 (для systemd это systemctl isolate rescue
) или по крайней мере, остановить все критические службы.