Joh*_*nce 5 cpu x86 reverse-engineering elf compiler-optimization
所以我参考这篇论文:
二进制搅拌:旧版x86二进制代码的自随机指令地址
https://www.utdallas.edu/~hamlen/wartell12ccs.pdf
代码与数据交织:由于性能原因,现代编译器在PE和ELF二进制文件的代码段中积极插入静态数据。在编译的二进制文件中,通常没有办法将数据字节与代码区分开。不经意地将数据与代码随机化会破坏二进制文件,从而给指令级随机化器带来困难。可行的解决方案必须以某种方式保留数据,同时随机化所有可访问的代码。
但我有一些问题:
如何提高程序速度?我只能想象这只会使cpu的执行更加复杂吗?
CPU如何区分代码和数据?因为据我所知,除非有跳转类型的指令,否则cpu将以线性方式依次执行每个指令,那么cpu怎么知道代码中的哪些指令是代码,哪些指令是数据?
考虑到代码部分是可执行的,并且CPU可能会错误地将恶意数据作为代码执行,这对安全性是否非常不利?(也许攻击者将程序重定向到该指令?)
是的,他们提出的二进制随机化器需要处理这种情况,因为可能存在混淆的二进制文件,或者手写代码可能会做任意的事情,因为作者不了解或出于某些奇怪的原因。
但是,不,普通编译器不会针对x86执行此操作。该答案针对书面提出的SO问题,而不是包含这些主张的论文:
由于性能原因,现代编译器会在PE和ELF二进制文件中的代码段中主动插入静态数据
需要引用! 对于x86,根据我在GCC和clang等编译器方面的经验,以及在查看MSVC和ICC的asm输出方面的经验,这对于x86来说是完全错误的。
普通编译器将静态只读数据放入section .rodata(ELF平台)或section .rdata(Windows)中。 该.rodata 部分(和该.text部分)作为文本段的一部分链接,但是整个可执行文件或库的所有只读数据都组合在一起,而所有代码则分别组合在一起。 ELF文件格式的节和段有什么区别 (或者最近,即使在单独的ELF段中,.rodata也可以映射为noexec。)
在同一页面中混合代码和数据在x86上的优势几乎为零,并且浪费了代码字节的数据-TLB覆盖范围,浪费了数据字节的指令-TLB覆盖范围。在64字节高速缓存行中也是如此,以浪费L1i / L1d中的空间。唯一的优势是统一缓存(L2和L3)的代码+数据局部性,但是通常不会这样做。(例如,在代码获取将一条线放入L2之后,从同一行获取数据可能会进入L2,而不得不从另一缓存行到RAM以获得数据。)
但是,使用分离的L1iTLB和L1dTLB,以及将L2 TLB作为统一的受害者缓存,x86 CPU 并未为此进行优化。 从现代Intel CPU上的同一高速缓存行读取字节时,在获取“冷”功能时出现iTLB丢失并不能防止dTLB丢失。
x86上的代码大小优势为零。x86-64的PC相对寻址模式为[RIP + rel32],因此它可以寻址当前位置+ -2GiB之内的任何内容。32位x86甚至没有PC相关的寻址模式。
也许作者是在考虑ARM,附近的静态数据允许PC相对负载(具有较小的偏移量)将32位常量存储到寄存器中? (在ARM上,这称为“文字池”,您将在函数之间找到它们。)
我认为它们并不意味着立即数据,例如mov eax, 12345,其中32位12345是指令编码的一部分。这不是要通过加载指令加载的静态数据。立即数据是另一回事。
显然,它仅用于只读数据;在指令指针附近进行写操作将触发清除管道以处理自修改代码的可能性。而且,您通常希望W ^ X(写或exec,而不是两者)用于内存页面。
CPU如何区分代码和数据?
逐步增加。CPU通过RIP提取字节,并将其解码为指令。从程序入口点开始后,执行将在已执行的分支之后进行,并落入未执行的分支等。
从体系结构上讲,它不关心当前正在执行的字节或指令正在将其作为数据加载/存储的字节。如果再次需要,最近执行的字节将保留在L1-I高速缓存中,并且与L1-D高速缓存中的数据相同。
在无条件分支或a之后紧接数据而不是其他代码ret并不重要。 函数之间的填充可以是任何东西。在极少数情况下,如果数据具有某种模式,则数据可能会暂停预解码或解码阶段(例如,由于现代CPU以16或32字节的宽块为单位进行取/解码),但随后的CPU阶段都是仅查看来自正确路径的实际解码指令。(或者由于分支的错误推测...)
因此,如果执行到达一个字节,则该字节是指令的一部分。这对于CPU完全没问题,但对于想要查看可执行文件并将每个字节分类为“或”的程序无济于事。
代码提取总是检查TLB中的权限,因此,如果RIP指向不可执行的页面,它将导致错误。(页表条目中的NX位)。
但是就CPU而言,并没有真正的区别。x86是冯·诺依曼架构。如果需要,一条指令可以加载自己的代码字节。
例如,movzx eax, byte ptr [rip - 1]将EAX设置为0x000000FF,加载rel32 = -1 = 0xffffffff位移的最后一个字节。
考虑到代码部分是可执行的,并且CPU可能会错误地将恶意数据作为代码执行,这对安全性是否非常不利?(也许攻击者将程序重定向到该指令?)
可执行页中的只读数据可以用作Spectre小工具,也可以用作面向返回编程(ROP)攻击的小工具。但是我认为通常在真实代码中已经有足够多的小工具了,这没什么大不了的。
但是,是的,与您的其他观点不同,这实际上是一个次要的反对。
最近(2019年或2018年末),GNU Binutils ld已开始将本.rodata节与本节放在单独的页面中,.text因此无需执行权限即可将其只读。这使得静态只读数据在x86-64之类的ISA上不可执行,而exec权限与读取权限是分开的。即在单独的ELF段中。
您可以使不可执行的事情越多越好,并且将代码+常量混合在一起将要求它们是可执行的。
| 归档时间: |
|
| 查看次数: |
171 次 |
| 最近记录: |