在带有 Intel CPU 的 linux 机器上,假设我使用 -fstack-protect-all 进行二进制编译。
这是如何编码成二进制的?(我可以使用 readelf 查看此信息吗?)。它是编码到每个页面/段中还是在一个地方添加到二进制文件中并且加载器将其拾取?
加载页面时内核/加载程序如何获取此信息?它是如何使用它的?
用于将页面设置为只读的 x86 指令是什么?
在带有 Intel CPU 的 linux 机器上,假设我使用 -fstack-protect-all 进行二进制编译。
这是如何编码成二进制的?(我可以使用 readelf 查看此信息吗?)。它是编码到每个页面/段中还是在一个地方添加到二进制文件中并且加载器将其拾取?
加载页面时内核/加载程序如何获取此信息?它是如何使用它的?
用于将页面设置为只读的 x86 指令是什么?
在带有 Intel CPU 的 linux 机器上,假设我使用 -fstack-protect-all 进行二进制编译。
由于没有明确说明,因此假定这指的是使用带有 -fstack-protect或-all 参数的 GCC 编译的 ELF 二进制文件。
-fstack-protector-all
是 的延伸-fstack-protector
:
发出额外的代码来检查缓冲区溢出,例如堆栈粉碎攻击。这是通过将保护变量添加到具有易受攻击对象的函数来完成的。这包括调用 alloca 的函数,以及缓冲区大于 8 字节的函数。进入函数时初始化守卫,然后在函数退出时检查。如果保护检查失败,则会打印一条错误消息并退出程序。
除了
-fstack-protector
所有功能都受到保护外。
前面提到的“保护变量”通常被称为金丝雀:
堆栈保护背后的基本思想是在函数返回指针被压入之后将“金丝雀”(随机选择的整数)压入堆栈。然后在函数返回之前检查金丝雀值;如果它发生了变化,程序将中止。通常,堆栈缓冲区溢出(又名“堆栈粉碎”)攻击将不得不更改金丝雀的值,因为它们写入超出缓冲区的末尾,然后才能到达返回指针。由于攻击者不知道金丝雀的价值,因此无法被攻击所取代。因此,堆栈保护允许程序在发生这种情况时中止,而不是返回到攻击者希望它去的任何地方。1
现在到第一组问题:
这是如何编码成二进制的?(我可以使用 readelf 查看此信息吗?)。它是编码到每个页面/段中还是在一个地方添加到二进制文件中并且加载器将其拾取?
这-fstack-protector-all
导致编译器生成代码,其中将保护变量推送到所有函数并在返回之前进行检查。换句话说,会生成额外的机器指令,这些指令与推送、检查和弹出堆栈金丝雀有关。
这可以使用从同一源生成的 2 个示例二进制文件来说明,其中一个是使用-fstack-protector-all
参数编译的,另一个不是。
源代码:
int test(int i) {
return i;
}
int main(void) {
int x;
int i = 10;
x = test(i);
return x;
}
没有 编译的二进制函数-fstack-protector-all
:
$ objdump -dj .text test | grep -A7 "<test>:"
00000000004004ed <test>:
4004ed: 55 push %rbp
4004ee: 48 89 e5 mov %rsp,%rbp
4004f1: 89 7d fc mov %edi,-0x4(%rbp)
4004f4: 8b 45 fc mov -0x4(%rbp),%eax
4004f7: 5d pop %rbp
4004f8: c3 retq
从二元函数编译有 -fstack-protector-all
:
$ objdump -dj .text protected_test | grep -A20 "<test>:"
000000000040055d <test>:
40055d: 55 push %rbp
40055e: 48 89 e5 mov %rsp,%rbp
400561: 48 83 ec 20 sub $0x20,%rsp
400565: 89 7d ec mov %edi,-0x14(%rbp)
400568: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax <- get guard variable value
40056f: 00 00
400571: 48 89 45 f8 mov %rax,-0x8(%rbp) <- save guard variable on stack
400575: 31 c0 xor %eax,%eax
400577: 8b 45 ec mov -0x14(%rbp),%eax
40057a: 48 8b 55 f8 mov -0x8(%rbp),%rdx <- move it to register
40057e: 64 48 33 14 25 28 00 xor %fs:0x28,%rdx <- check it against original
400585: 00 00
400587: 74 05 je 40058e <test+0x31>
400589: e8 b2 fe ff ff callq 400440 <__stack_chk_fail@plt>
40058e: c9 leaveq
40058f: c3 retq
由于堆栈保护是通过汇编代码实现的,-fstack-protector
因此最好使用反汇编程序(如objdump
. readelf
技术上显示此信息,因为该-x
参数会生成所选部分的十六进制转储。
readelf
没有堆栈保护的二进制编译的十六进制转储:
$ readelf -x .text test
Hex dump of section '.text':
0x00400400 31ed4989 d15e4889 e24883e4 f0505449 1.I..^H..H...PTI
0x00400410 c7c09005 400048c7 c1200540 0048c7c7 ....@.H.. .@.H..
0x00400420 f9044000 e8b7ffff fff4660f 1f440000 ..@.......f..D..
0x00400430 b83f1060 0055482d 38106000 4883f80e .?.`.UH-8.`.H...
0x00400440 4889e577 025dc3b8 00000000 4885c074 H..w.]......H..t
0x00400450 f45dbf38 106000ff e00f1f80 00000000 .].8.`..........
0x00400460 b8381060 0055482d 38106000 48c1f803 .8.`.UH-8.`.H...
0x00400470 4889e548 89c248c1 ea3f4801 d048d1f8 H..H..H..?H..H..
0x00400480 75025dc3 ba000000 004885d2 74f45d48 u.]......H..t.]H
0x00400490 89c6bf38 106000ff e20f1f80 00000000 ...8.`..........
0x004004a0 803d910b 20000075 11554889 e5e87eff .=.. ..u.UH...~.
0x004004b0 ffff5dc6 057e0b20 0001f3c3 0f1f4000 ..]..~. ......@.
0x004004c0 48833d58 09200000 741eb800 00000048 H.=X. ..t......H
0x004004d0 85c07414 55bf200e 60004889 e5ffd05d ..t.U. .`.H....]
0x004004e0 e97bffff ff0f1f00 e973ffff ff554889 .{.......s...UH.
0x004004f0 e5897dfc 8b45fc5d c3554889 e54883ec ..}..E.].UH..H..
0x00400500 10c745f8 0a000000 8b45f889 c7e8dbff ..E......E......
0x00400510 ffff8945 fc8b45fc c9c3660f 1f440000 ...E..E...f..D..
0x00400520 41574189 ff415649 89f64155 4989d541 AWA..AVI..AUI..A
0x00400530 544c8d25 d8082000 55488d2d d8082000 TL.%.. .UH.-.. .
0x00400540 534c29e5 31db48c1 fd034883 ec08e855 SL).1.H...H....U
0x00400550 feffff48 85ed741e 0f1f8400 00000000 ...H..t.........
0x00400560 4c89ea4c 89f64489 ff41ff14 dc4883c3 L..L..D..A...H..
0x00400570 014839eb 75ea4883 c4085b5d 415c415d .H9.u.H...[]A\A]
0x00400580 415e415f c366662e 0f1f8400 00000000 A^A_.ff.........
0x00400590 f3c3 ..
readelf
使用堆栈保护编译的二进制文件的十六进制转储:
$ readelf -x .text protected_test
Hex dump of section '.text':
0x00400470 31ed4989 d15e4889 e24883e4 f0505449 1.I..^H..H...PTI
0x00400480 c7c05006 400048c7 c1e00540 0048c7c7 ..P.@.H....@.H..
0x00400490 90054000 e8b7ffff fff4660f 1f440000 ..@.......f..D..
0x004004a0 b8471060 0055482d 40106000 4883f80e .G.`.UH-@.`.H...
0x004004b0 4889e577 025dc3b8 00000000 4885c074 H..w.]......H..t
0x004004c0 f45dbf40 106000ff e00f1f80 00000000 .].@.`..........
0x004004d0 b8401060 0055482d 40106000 48c1f803 .@.`.UH-@.`.H...
0x004004e0 4889e548 89c248c1 ea3f4801 d048d1f8 H..H..H..?H..H..
0x004004f0 75025dc3 ba000000 004885d2 74f45d48 u.]......H..t.]H
0x00400500 89c6bf40 106000ff e20f1f80 00000000 ...@.`..........
0x00400510 803d290b 20000075 11554889 e5e87eff .=). ..u.UH...~.
0x00400520 ffff5dc6 05160b20 0001f3c3 0f1f4000 ..].... ......@.
0x00400530 48833de8 08200000 741eb800 00000048 H.=.. ..t......H
0x00400540 85c07414 55bf200e 60004889 e5ffd05d ..t.U. .`.H....]
0x00400550 e97bffff ff0f1f00 e973ffff ff554889 .{.......s...UH.
0x00400560 e54883ec 20897dec 64488b04 25280000 .H.. .}.dH..%(..
0x00400570 00488945 f831c08b 45ec488b 55f86448 .H.E.1..E.H.U.dH
0x00400580 33142528 00000074 05e8b2fe ffffc9c3 3.%(...t........
0x00400590 554889e5 4883ec10 64488b04 25280000 UH..H...dH..%(..
0x004005a0 00488945 f831c0c7 45f00a00 00008b45 .H.E.1..E......E
0x004005b0 f089c7e8 a5ffffff 8945f48b 45f4488b .........E..E.H.
0x004005c0 55f86448 33142528 00000074 05e86efe U.dH3.%(...t..n.
0x004005d0 ffffc9c3 662e0f1f 84000000 00006690 ....f.........f.
0x004005e0 41574189 ff415649 89f64155 4989d541 AWA..AVI..AUI..A
0x004005f0 544c8d25 18082000 55488d2d 18082000 TL.%.. .UH.-.. .
0x00400600 534c29e5 31db48c1 fd034883 ec08e8f5 SL).1.H...H.....
0x00400610 fdffff48 85ed741e 0f1f8400 00000000 ...H..t.........
0x00400620 4c89ea4c 89f64489 ff41ff14 dc4883c3 L..L..D..A...H..
0x00400630 014839eb 75ea4883 c4085b5d 415c415d .H9.u.H...[]A\A]
0x00400640 415e415f c366662e 0f1f8400 00000000 A^A_.ff.........
0x00400650 f3c3 ..
显然这不是很有用。
页面和段的讨论无关紧要,因为在 ELF 二进制文件中标记为可执行的所有段都映射到同一段(text
段)中。
加载页面时内核/加载程序如何获取此信息?它是如何使用它的?
的使用与-fstack-protector-all
节或段权限无关。
有关内核程序加载器的信息,请参见:
用于将页面设置为只读的 x86 指令是什么?
段权限由链接编辑器设置:段权限
GCC 的堆栈保护是基于软件的,与 DEP 的基于硬件的保护无关。当操作系统启用 DEP 时,在其上运行的所有程序(或用户定义的某些子集)都会通过硬件标志自动受到保护,而不管用于构建二进制文件的任何编译器标志。
当在 GCC 中启用堆栈保护标志时,它通过放置额外的保护来提供保护,并检查函数何时被调用或返回,以确保程序中止而不是违反其完整性。这会稍微增加可执行文件的大小并导致代码运行速度稍慢(占用更多 CPU 时间)。
加载程序和操作系统都知道 GCC 的堆栈保护,也不需要知道。就操作系统而言,这只是普通的可执行代码。操作系统可以在不启用堆栈保护的情况下提供 DEP,而堆栈保护不需要 DEP。
使用 DEP,如果 IP(指令指针)落在 CS(代码段)之外,则会引发异常并且操作系统终止程序。使用 GCC 的堆栈保护,堆栈被额外的空间缓冲,其中加载了某些值,并检查以确保这些缓冲区没有被修改。如果某个功能未能通过警戒检查,则程序将终止以防止进一步损坏。