编译器是否将数据放入 PE 或 ELF 文件的 .text 部分?如果是这样,为什么?

Do compilers put data inside .text section of PE or ELF files? if so, why?

所以刚才有人问了这个问题:

但是最上面的答案说文本部分没有数据,编译器不会那样做!

但我遇到了一些二进制文件,在 ollydbg 中调试时,我在 .text 中看到了一些奇怪的字节,我猜这可能是数据,而且我仍然阅读声称数据可能在 .text 中的论文节

这实际上是静态反汇编成为一个无法确定的问题的原因(至少学术论文声称它是),因为他们说数据可能在文本部分内,而我们永远无法知道

所以我想一劳永逸地解决这个问题,如果您想回答这个问题,请提供来源 :

  1. 编译器是否将数据放入 .text 部分?如果是这样,您知道哪些编译器和编译器版本可以执行此操作?

  2. 如果他们这样做,那是为什么?我阅读了我链接的问题的答案,但我无法理解,因为我不是真正的硬件专家,所以你们能提供一个更简单的解释,软件开发人员可以理解的东西吗?

这里有另一个来源说我们无法区分可执行文件中的数据和代码:

https://www.usenix.org/legacy/publications/library/proceedings/usenix03/tech/full_papers/prasad/prasad_html/node5.html

distinguishing code from data in a binary file is a fundamentally undecidable problem

对于 x86,gcc/clang/ICC/MSVC 不要将数据与代码混合,因为它毫无意义,就像我在对链接问题的回答中所说的那样。 (不包括立即数据,很明显,它会作为指令的一部分进行解码)。 .text 部分的结尾和 .rodata 部分的开头可能在 TEXT 段内相邻,但这不是你的意思。

对于非 x86 ELF 二进制文件(例如 ARM),它们确实混合了代码和只读数据以允许 PC 相关加载只有 12 位或更小的偏移量适合固定宽度加载指令。

混淆的 x86 二进制文件肯定会混入一些数据,或者只是使反汇编变得困难,所以看起来可能有一些。对于编译器生成的未故意混淆的代码,静态反汇编通常很容易。任何混淆反汇编的东西都会让它看起来像可能的数据。是的,它是不可判定的。


在我的链接答案中,我没有说混合代码 + 常量的二进制文件不存在。我只是说普通的优化编译器不会这样做,而且它没有性能优势。只有抗逆向工程的优势,假设数据是只读的,性能成本很小。 (或者,如果数据是 read/write,成本会非常高。)

二进制混淆是人们在商业软件上真正使用的东西。对于您在野外发现不能完全反汇编的二进制文件,我一点也不感到惊讶。但这是在编译之后完成的,从编译器输出中生成一个新的混淆二进制文件。 (或者可能使用编译器插件?我真的不确定)。但这不是编译器 proper 做的,那是构建工具链中的后续步骤。我认为,销售二进制混淆软件的人销售的是二进制-> 二进制转换器,而不是编译器。

我在任何 Linux 发行版上反汇编 gcc/clang 输出时从未遇到过任何问题(例如 /usr/bin 或 /usr/lib 中的东西)。如果没有调试符号,您会得到大量的指令块,但反汇编不会与执行到达它的方式不同步。函数之间的填充是在函数底部的 retjmp 之后正常解码的长 NOP。或者对于 MSVC,填充是单字节 int3 指令,它们不会像 00 00 字节(add [rax], al)那样对下一个函数的开头进行解码。

请注意您的声明(存在混淆的二进制文件)与链接 的论文中提出的更强有力的声明之间的区别 来自另一个问题(优化编译器积极地这样做出于性能原因,包括在 x86 上)。

如果你想实现必须对 每个 二进制文件都有效的二进制重写,那么是的,你有一个大问题。但是,如果您只需要关心未混淆的编译器输出,那就容易多了。