无法使代码段只执行(不可读)
Cannot Make Code Segment Execute-Only (Not Readable)
我正在尝试使代码段只执行(不可读)。
但是我 失败 在我尝试了手册告诉我的所有事情之后。这是我使代码段不可读的方法。
>uname -a
Linux Emmet-VM 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:18:00 UTC 2015 i686 i686 i686 GNU/Linux
>lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.3 LTS
Release: 14.04
Codename: trusty
首先,我在“Intel(R)64 and IA-32 Architectures Software Developer's Manual(Combined Volumes 1,2A,2B,2C,2D,3A,3B,3C) 中找到了这个和 3D)”:
Set read-enable bit to enable read and Segment Types。(抱歉,我仍然不允许在我的帖子中嵌入图片,所以 links 代替)
所以,我想如果我改变 %CS,让它指向一个段描述符,它的读取启用位设置为 0,我应该使代码段不可读。
然后,我使用下面的代码将一个新的段插入 LDT.entry[2],并且我将代码段类型设置为 8,也就是 1000B,这意味着 "Execute-Only" 根据"Segment Types" link 上面的帖子:
typedef struct user_desc UserDesc;
UserDesc *seg = (UserDesc*)malloc(sizeof(UserDesc));
seg->entry_number = 0x2;
seg->base_addr = 0x00000000;
seg->limit = 0xffffffff;
seg->seg_32bit = 0x1;
seg->contents = 0x02;
seg->read_exec_only = 0x1;
seg->limit_in_pages = 0x1;
seg->seg_not_present = 0x0;
seg->useable = 0x0;
int ret = modify_ldt(1, (void*)seg, sizeof(UserDesc));
之后,我将 %CS 更改为 0x17(00010111B,表示 LDT 中的条目 2) ljmp
。
asm("ljmp [=12=]x17, $reload_cs\n"
"reload_cs:");
但是,即使这样,我仍然可以读取代码段中的字节码:
void foo() {printf("foo\n");}
void test(){
char* a = (char*)foo;
printf("0x%x\n", (unsigned int)a[0]);// This prints 0x55
}
如果代码段不可读,上面的代码应该抛出 segment fault
错误。但它打印 0x55
成功。
所以,我想知道,我在测试中是否犯了任何错误?
或者这只是英特尔手册中的一个错误?
您在执行 (unsigned int)a[0]
时仍在通过 DS
访问代码。
只写段不存在(如果存在,将 DS
设置为只写是个坏主意)。
如果你做的一切都正确 mov eax, [cs:...]
(NASM 语法)将会失败(但 mov eax, [ds:...]
不会)。
快速浏览了英特尔手册后,只执行页面不应该存在(至少直接),因此使用 mprotect 和 PROT_EXEC
可能用途有限(代码仍然可读).
不过值得一试。
可以通过三种方式解决这个问题。
尽管 None 可以在不借助 OS 的情况下实现,因此它们的理论性多于实践性。
保护键
如果 CPU 支持它们(参见 Intel 手册 3 的第 4.6.2 节),它们会在读取代码和数据的方式上引入不对称。
读取数据受密钥保护。
但是获取是 not:
How a linear address’s protection key controls access to the address depends on the mode of a linear address:
- A linear address’s protection controls only data accesses to the address. It does not in any way affect instructions fetches from the address.
因此可以为应用程序的 PKRU
寄存器中没有的代码页设置保护密钥。
您仍然可以执行代码,但不能阅读它。
取消 TLB 同步
如果您的应用程序从未接触过要读取的代码页,它们将占用 ITLB 中的一些条目,但不会占用 DTLB 中的一些条目.
如果然后,OS 将它们映射为仅主管 而不刷新 TLB,则在作为数据访问时阻止对它们的访问(因为没有 DTLB 这些页面的条目存在,迫使在内存上走动)但是由于 ITLB 代码仍然可以获取。
这在实践中涉及更多,因为代码跨越多个页面并且实际上被 OS 读取为数据。
EPT
扩展数据页在虚拟化过程中用于将客户机物理地址转换为主机物理地址。
尽管它们看起来只是另一个间接级别,但它们具有 单独的读取、写入和执行控制位 。
A paper has been written关于防止内核代码泄露(抵消动态Return面向编程)
我正在尝试使代码段只执行(不可读)。
但是我 失败 在我尝试了手册告诉我的所有事情之后。这是我使代码段不可读的方法。
>uname -a
Linux Emmet-VM 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:18:00 UTC 2015 i686 i686 i686 GNU/Linux
>lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.3 LTS
Release: 14.04
Codename: trusty
首先,我在“Intel(R)64 and IA-32 Architectures Software Developer's Manual(Combined Volumes 1,2A,2B,2C,2D,3A,3B,3C) 中找到了这个和 3D)”: Set read-enable bit to enable read and Segment Types。(抱歉,我仍然不允许在我的帖子中嵌入图片,所以 links 代替)
所以,我想如果我改变 %CS,让它指向一个段描述符,它的读取启用位设置为 0,我应该使代码段不可读。
然后,我使用下面的代码将一个新的段插入 LDT.entry[2],并且我将代码段类型设置为 8,也就是 1000B,这意味着 "Execute-Only" 根据"Segment Types" link 上面的帖子:
typedef struct user_desc UserDesc;
UserDesc *seg = (UserDesc*)malloc(sizeof(UserDesc));
seg->entry_number = 0x2;
seg->base_addr = 0x00000000;
seg->limit = 0xffffffff;
seg->seg_32bit = 0x1;
seg->contents = 0x02;
seg->read_exec_only = 0x1;
seg->limit_in_pages = 0x1;
seg->seg_not_present = 0x0;
seg->useable = 0x0;
int ret = modify_ldt(1, (void*)seg, sizeof(UserDesc));
之后,我将 %CS 更改为 0x17(00010111B,表示 LDT 中的条目 2) ljmp
。
asm("ljmp [=12=]x17, $reload_cs\n"
"reload_cs:");
但是,即使这样,我仍然可以读取代码段中的字节码:
void foo() {printf("foo\n");}
void test(){
char* a = (char*)foo;
printf("0x%x\n", (unsigned int)a[0]);// This prints 0x55
}
如果代码段不可读,上面的代码应该抛出 segment fault
错误。但它打印 0x55
成功。
所以,我想知道,我在测试中是否犯了任何错误? 或者这只是英特尔手册中的一个错误?
您在执行 (unsigned int)a[0]
时仍在通过 DS
访问代码。
只写段不存在(如果存在,将 DS
设置为只写是个坏主意)。
如果你做的一切都正确 mov eax, [cs:...]
(NASM 语法)将会失败(但 mov eax, [ds:...]
不会)。
快速浏览了英特尔手册后,只执行页面不应该存在(至少直接),因此使用 mprotect 和 PROT_EXEC
可能用途有限(代码仍然可读).
不过值得一试。
可以通过三种方式解决这个问题。
尽管 None 可以在不借助 OS 的情况下实现,因此它们的理论性多于实践性。
保护键
如果 CPU 支持它们(参见 Intel 手册 3 的第 4.6.2 节),它们会在读取代码和数据的方式上引入不对称。
读取数据受密钥保护。 但是获取是 not:
How a linear address’s protection key controls access to the address depends on the mode of a linear address:
- A linear address’s protection controls only data accesses to the address. It does not in any way affect instructions fetches from the address.
因此可以为应用程序的 PKRU
寄存器中没有的代码页设置保护密钥。
您仍然可以执行代码,但不能阅读它。
取消 TLB 同步
如果您的应用程序从未接触过要读取的代码页,它们将占用 ITLB 中的一些条目,但不会占用 DTLB 中的一些条目.
如果然后,OS 将它们映射为仅主管 而不刷新 TLB,则在作为数据访问时阻止对它们的访问(因为没有 DTLB 这些页面的条目存在,迫使在内存上走动)但是由于 ITLB 代码仍然可以获取。
这在实践中涉及更多,因为代码跨越多个页面并且实际上被 OS 读取为数据。
EPT
扩展数据页在虚拟化过程中用于将客户机物理地址转换为主机物理地址。
尽管它们看起来只是另一个间接级别,但它们具有 单独的读取、写入和执行控制位 。
A paper has been written关于防止内核代码泄露(抵消动态Return面向编程)