演示处理器环 - 运行环 0 指令的汇编代码
Demo processor rings - assembly code that runs ring 0 instructions
我想创建一个演示来向学生解释处理器环和系统调用。我想在演示文稿中做这样的事情:
- 编写一些汇编代码,尝试执行一些只能 运行 在 ring 0 中的代码(例如直接访问磁盘)
- 看到代码失败,并重新编写它以使用系统调用(例如,使用系统调用读取文件)
- 如果不是那么难:运行环0中的初始代码
我可以使用 Linux 或 Windows,哪个更简单
关于在哪里可以找到对我有帮助的代码的任何想法?
我可以使用哪些受保护的指令?
谢谢!
一个 attention-catching 演示可能会关闭计算机电源。
不幸的是,电源管理是一件复杂的事情,它涉及 ACPI specification that are rather abstract and long, further more they are not the first attempt。
ACPI 很复杂,因为不同的供应商需要不同的操作来管理计算机的某个方面,无论如何,如果您 碰巧拥有 Intel 芯片组 (推荐使用 200 系列,但其他系列也应该可以)我们可以跳过大部分 ACPI 层并改用数据表。
在 class.
中使用它之前,您应该在最终硬件上检查该程序
ACPI定义了四种全局系统状态,G0-G3,其中G3是机械状态关机(即插头已拔,电池已取出),G2为soft-off.
软件只能进入G2,进入S5休眠状态即可。
睡眠状态由PCH(Intel芯片组)通过IO寄存器(PM1a_CNT_BLK
)控制,这个寄存器位于PCI配置space中定义的ACPI块中设备 31 功能 2(PM 控制器)。
应该读取块的基地址,然后加上四 (4) 以获得感兴趣的寄存器的地址。
我不会以编程方式执行此操作,而是汇编程序需要一个具有该地址的符号。
要检索寄存器 PM1a_CNT_BLK
的地址,可以使用 /proc/ioports
如下:
sudo cat /proc/ioports | grep 'PM1a_CNT_BLK' | cut -f3 -d' ' | cut -f1 -d'-'
这给出了寄存器的十六进制地址。如果打印出 none,则可能是芯片组不受支持。
在可能的情况下,地址是 1804
。
睡眠状态由寄存器的位 10:12 (SLP_TYP
) 和位 13 (SLP_EN
).
控制
SLP_TYP
是 select 要进入的状态的 3 位值(S5 是 7),第 13 位是使能位。
寄存器有其他值必须保存,所以必须进行read-modify-write操作。
使用 in
和 out
指令是不可能的 Ring 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
可以是assembled和nasm po.asm -DPM1a_CNT_BLK= -DIOPL= -felf64 -o po.o
其中</code>是上面找到的<code>PM1a_CNT_BLK
的端口地址但是前缀是0x(在我的例子中,它变为 0x1804
)并且 </code> 是一个数字 (0-3),用于将 IOPL 设置为。<br>
如果 IOPL 不为 0,则设置 IOPL,因为 0 是默认值(即只有 Ring 0 可以使用 <code>in
和 out
)
注意:将合理的值传递给符号,否则程序不会assemble。
这在以下方面很有趣:
- 如果 运行 的 IOPL 为 0,程序会因使用
in
.
而崩溃并显示 #GP
这演示了 CPU 的安全机制。
- 如果 运行 IOPL > 0 但不是作为 root,它将因权限不足而失败。
这演示了 OS 的安全机制,只允许 root 更改 IOPL。
- 如果 运行 的 IOPL > 0 但 < 3 并且作为 root 它将 #GP 由于使用
in
。
这表明用户程序 运行 在 Ring 3(IOPL 不够高)。
- 如果 运行 IOPL = 3 并且作为 root 用户,它将关闭计算机(或者在 return 时失败,可能使系统处于未知状态)。
这表明允许用户程序访问硬件的风险。
- 如果 IOPL > 3,它将因 IOPL 值无效而失败。
这表明只有四个环。
我制作了 a git repository with the code 和一个您可以用来构建的脚本 built.sh
和 运行 不同版本的程序。
这个脚本很有用因为它将po
的退出状态转换成user-friendly字符串,适合做实验。
脚本需要 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 上创建一个程序 运行ning,它对调试很有用,但仅此而已。
为了 运行 Ring 0 的代码,您需要一个 LKM(可加载内核模块)。
在同一个存储库中,我包含了一个 lkm 目录和一个 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
定义 ,然后运行 make
在同一个目录中(你需要内核头文件),Makefile
是 LKM 的标准。
要加载模块,请使用 insmod po
作为根用户(这是一个 OS 安全机制)。
因为我已经开始写这个答案,所以我已经编译但没有测试这个 LKM。
您最终可能会修复它,使用 dmesg
检查模块的输出。
您可以使用 LKM 作为框架来 运行 编写 Ring 0,但在处理内存时您必须了解 Linux 如何处理虚拟内存。
最后一点,如果你要check/use这个程序,一定要关闭所有应用程序,运行一个sync
,如果愿意,切换到运行 级别 1(对于 systemd,它是 systemctl isolate rescue
)或至少停止所有关键服务。
我想创建一个演示来向学生解释处理器环和系统调用。我想在演示文稿中做这样的事情:
- 编写一些汇编代码,尝试执行一些只能 运行 在 ring 0 中的代码(例如直接访问磁盘)
- 看到代码失败,并重新编写它以使用系统调用(例如,使用系统调用读取文件)
- 如果不是那么难:运行环0中的初始代码
我可以使用 Linux 或 Windows,哪个更简单
关于在哪里可以找到对我有帮助的代码的任何想法? 我可以使用哪些受保护的指令?
谢谢!
一个 attention-catching 演示可能会关闭计算机电源。
不幸的是,电源管理是一件复杂的事情,它涉及 ACPI specification that are rather abstract and long, further more they are not the first attempt。
ACPI 很复杂,因为不同的供应商需要不同的操作来管理计算机的某个方面,无论如何,如果您 碰巧拥有 Intel 芯片组 (推荐使用 200 系列,但其他系列也应该可以)我们可以跳过大部分 ACPI 层并改用数据表。
在 class.
中使用它之前,您应该在最终硬件上检查该程序ACPI定义了四种全局系统状态,G0-G3,其中G3是机械状态关机(即插头已拔,电池已取出),G2为soft-off.
软件只能进入G2,进入S5休眠状态即可。
睡眠状态由PCH(Intel芯片组)通过IO寄存器(PM1a_CNT_BLK
)控制,这个寄存器位于PCI配置space中定义的ACPI块中设备 31 功能 2(PM 控制器)。
应该读取块的基地址,然后加上四 (4) 以获得感兴趣的寄存器的地址。
我不会以编程方式执行此操作,而是汇编程序需要一个具有该地址的符号。
要检索寄存器 PM1a_CNT_BLK
的地址,可以使用 /proc/ioports
如下:
sudo cat /proc/ioports | grep 'PM1a_CNT_BLK' | cut -f3 -d' ' | cut -f1 -d'-'
这给出了寄存器的十六进制地址。如果打印出 none,则可能是芯片组不受支持。
在可能的情况下,地址是 1804
。
睡眠状态由寄存器的位 10:12 (SLP_TYP
) 和位 13 (SLP_EN
).
控制
SLP_TYP
是 select 要进入的状态的 3 位值(S5 是 7),第 13 位是使能位。
寄存器有其他值必须保存,所以必须进行read-modify-write操作。
使用 in
和 out
指令是不可能的 Ring 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
可以是assembled和nasm po.asm -DPM1a_CNT_BLK= -DIOPL= -felf64 -o po.o
其中</code>是上面找到的<code>PM1a_CNT_BLK
的端口地址但是前缀是0x(在我的例子中,它变为 0x1804
)并且 </code> 是一个数字 (0-3),用于将 IOPL 设置为。<br>
如果 IOPL 不为 0,则设置 IOPL,因为 0 是默认值(即只有 Ring 0 可以使用 <code>in
和 out
)
注意:将合理的值传递给符号,否则程序不会assemble。
这在以下方面很有趣:
- 如果 运行 的 IOPL 为 0,程序会因使用
in
.
而崩溃并显示 #GP 这演示了 CPU 的安全机制。 - 如果 运行 IOPL > 0 但不是作为 root,它将因权限不足而失败。
这演示了 OS 的安全机制,只允许 root 更改 IOPL。 - 如果 运行 的 IOPL > 0 但 < 3 并且作为 root 它将 #GP 由于使用
in
。
这表明用户程序 运行 在 Ring 3(IOPL 不够高)。 - 如果 运行 IOPL = 3 并且作为 root 用户,它将关闭计算机(或者在 return 时失败,可能使系统处于未知状态)。
这表明允许用户程序访问硬件的风险。 - 如果 IOPL > 3,它将因 IOPL 值无效而失败。
这表明只有四个环。
我制作了 a git repository with the code 和一个您可以用来构建的脚本 built.sh
和 运行 不同版本的程序。
这个脚本很有用因为它将po
的退出状态转换成user-friendly字符串,适合做实验。
脚本需要 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 上创建一个程序 运行ning,它对调试很有用,但仅此而已。
为了 运行 Ring 0 的代码,您需要一个 LKM(可加载内核模块)。
在同一个存储库中,我包含了一个 lkm 目录和一个 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
定义 ,然后运行 make
在同一个目录中(你需要内核头文件),Makefile
是 LKM 的标准。
要加载模块,请使用 insmod po
作为根用户(这是一个 OS 安全机制)。
因为我已经开始写这个答案,所以我已经编译但没有测试这个 LKM。
您最终可能会修复它,使用 dmesg
检查模块的输出。
您可以使用 LKM 作为框架来 运行 编写 Ring 0,但在处理内存时您必须了解 Linux 如何处理虚拟内存。
最后一点,如果你要check/use这个程序,一定要关闭所有应用程序,运行一个sync
,如果愿意,切换到运行 级别 1(对于 systemd,它是 systemctl isolate rescue
)或至少停止所有关键服务。