目录

一 GCC 安全编译简介

1 ASLR 地址随机

2 栈溢出保护

3 FORTIFY

4 RELRO

二  动态库搜索路径

RPATH VS. RUNPATH

三 其它

checksec

四 文件级加密

启用文件级加密

 五 总结


一 GCC 安全编译简介

操作系统提供了许多安全机制来尝试降低或阻止缓冲区溢出攻击带来的安全风险,例如 NX ASLR PIE CANARY FORTIFY RELRO 等手段,存在 NX 的话就不能直接执行栈上的数据,存在 ASLR 的话各个系统调用的地址就是随机化的等等。

GCC 在生成代码时,实际上已经提供了一些针对安全相关的编译选项。

 OPT_CFLAGS +=  -fPIC -fPIE -fstack-protector-all  -z now -D_FORTIFY_SOURCE=2 -fvisibility=hidden 

操作系统提供了许多安全机制来尝试降低或阻止缓冲区溢出攻击带来的安全风险,例如 ASLR、NX 等等,这里简单介绍一些常见的使用项。

1 ASLR 地址随机

Address Space Layout Randomization, ASLR 地址空间布局随机化,该技术在 2005 年的 Kernel 2.6.12 版本中引入,会将进程的某些内存空间地址进行随机化来增大入侵者预测目的地址的难度,从而降低进程被成功入侵的风险。

当前 Linux、Windows 等主流操作系统都已经采用该项技术。

2 栈溢出保护

当启用栈保护后,函数开始执行的时候会先往栈里插入 Cookie 信息,函数返回时会验证 Cookie 信息是否合法,非法则停止运行。

攻击者在覆盖返回地址的时候往往也会将 Cookie 信息给覆盖掉,导致栈保护检查失败进而阻止 ShellCode 的执行,在 Linux 中将 Cookie 信息称为 Canary 。

GCC 在 4.2 版本中添加了 -fstack-protector 和 -fstack-protector-all 编译参数以支持栈保护功能,4.9 新增了 -fstack-protector-strong 编译参数让保护的范围更广,在编译时可以控制是否开启栈保护以及程度,例如:

  1. $ gcc -o test test.c // 默认不开启 Canary 保护

  2. $ gcc -fno-stack-protector -o test test.c // 禁用栈保护

  3. $ gcc -fstack-protector -o test test.c // 启用堆栈保护,只为局部变量中含有 char 数组的函数插入保护代码

  4. $ gcc -fstack-protector-all -o test test.c // 启用堆栈保护,为所有函数插入保护代码

3 FORTIFY

用于检查是否存在缓冲区溢出的错误,针对的是字符串、内存操作函数,例如 memcpy memset strcpy strcats snprintf 等等。

可以通过 _FORTIFY_SOURCE 宏定义检查的级别:

  • _FORTIFY_SOURCE=1 仅在编译时检查。
  • _FORTIFY_SOURCE=2 在程序运行时也会检查,如果判断到缓冲区溢出则会直接终止程序。

实际上 GCC 会到生成了一些附加代码,通过对数组大小的大小进行判断,从而达到防止缓冲区溢出的作用,使用示例如下:

  1. $ gcc -o test test.c // 默认不会开启检查

  2. $ gcc -D_FORTIFY_SOURCE=1 -o test test.c // 较弱的检查

  3. $ gcc -D_FORTIFY_SOURCE=2 -o test test.c // 较强的检查

4 RELRO

在 Linux 系统安全领域,数据可写的存储区就会是攻击的目标,尤其是存储函数指针的区域,所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处。

GCC 提供了一种 Read Only Relocation 的方法,其原理为是由 linker 指定 binary 的一块经过 dynamic linker 处理过 relocation 之后的区域为只读.

设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对 Global Offset Table, GOT 攻击

二  动态库搜索路径

防止将一些动态库恶意替换,以达到攻击目的。

详细的加载过程可以参考 man 1 ld 中关于 -rpath-link 选项的介绍,比较关键的是使用 --rpath 或者 LD_LIBRARY_PATH 指定,分别是在编译阶段或者环境变量指定。

RPATH VS. RUNPATH

在通过 rpath 指定路径后,会在二进制文件中生成这两个参数,可以通过 readelf -d main | grep -E (RPATH|RUNPATH) 命令查看,老版本中使用的是 RPATH 新版本使用 RUNPATH 替换掉。

查找动态库的过程中,大致的顺序是 RPATH LD_LIBRARY_PATH RUNPATH ,所以,如果使用的是 RPATH 用户将无法进行调整,所以建议使用 RUNPATH ,这也是 gcc 的默认值。

可以通过 -Wl,--disable-new-dtags 表明使用 RPATH ;通过 -Wl,--enable-new-dtags 标示使用 RUNPATH 。

三 其它

checksec

checksec 是一个 Bash 脚本,可以用来检查可执行文件属性,例如 PIE RELRO PaX Canaries, ASLR, Fortify Source等等属性。

详细可以查看官网 TrapKit CheckSec 或者 Github CheckSec ,也可以直接使用 本地保存 。

四 文件级加密

Android 7.0 及更高版本支持文件级加密 (FBE)。采用文件级加密时,可以使用不同的密钥对不同的文件进行加密,也可以对加密文件单独解密

启用文件级加密

如需在设备上启用文件级加密 (FBE),就必须在内部存储设备 (userdata) 上启用 FBE。这也会自动为可合并的存储设备启用 FBE;但是,如有必要,可以覆盖可合并的存储设备的加密格式。

内部存储设备

通过将 fileencryption=contents_encryption_mode[:filenames_encryption_mode[:flags]] 选项添加到 userdata 的 fstab 行 fs_mgr_flags 列,可启用 FBE。此选项用于定义内部存储设备的加密格式。它最多包含三个以英文冒号分隔的参数:

  • contents_encryption_mode 参数指定将哪种加密算法用于加密文件内容,可为 aes-256-xts 或 adiantum。从 Android 11 开始,它也可以留空以指定默认算法,即 aes-256-xts
  • filenames_encryption_mode 参数指定将哪种加密算法用于加密文件名,可为 aes-256-ctsaes-256-heh 或 adiantum。如果不指定,则当 contents_encryption_mode 为 aes-256-xts 时该参数默认为 aes-256-cts,当 contents_encryption_mode 为 adiantum 时该参数默认为 adiantum
  • Android 11 中新增的 flags 参数是以 + 字符分隔的一个标记列表。支持以下标记:
    • v1 标记用于选择第 1 版加密政策;v2 标记用于选择第 2 版加密政策。第 2 版加密政策使用更安全、更灵活的密钥派生函数。如果设备搭载的是 Android 11 或更高版本(由 ro.product.first_api_level 确定),则默认选择第 2 版;如果设备搭载的是 Android 10 或更低版本,则默认选择第 1 版。
    • inlinecrypt_optimized 标记用于选择针对无法高效处理大量密钥的内嵌加密硬件进行了优化的加密格式。其具体做法是仅为每个 CE 或 DE 密钥派生一个文件内容加密密钥,而不是为每个文件派生一个。IV(初始化向量)的生成也会相应地进行调整。
    • emmc_optimized 标记与 inlinecrypt_optimized 类似,但它还选择了将 IV 限制为 32 位的 IV 生成方法。此标记应仅在符合 JEDEC eMMC v5.2 规范的内嵌加密硬件上使用,因此仅支持 32 位 IV。在其他内嵌加密硬件上,请改用 inlinecrypt_optimized。此标记一律不得在基于 UFS 的存储设备上使用;UFS 规范允许使用 64 位 IV。
    • 在支持硬件封装密钥的设备上,wrappedkey_v0 标记允许为 FBE 使用硬件封装的密钥。此标记只能与 inlinecrypt 装载选项以及 inlinecrypt_optimized 或 emmc_optimized 标记结合使用。

如果不使用内嵌加密硬件,则建议对大多数设备采用设置 fileencryption=aes-256-xts。如果使用的是内嵌加密硬件,则建议对大多数设备采用设置 fileencryption=aes-256-xts:aes-256-cts:inlinecrypt_optimized(或等效的 fileencryption=::inlinecrypt_optimized)。在没有采用任何形式的 AES 加速的设备上,可以设置 fileencryption=adiantum,从而用 Adiantum 代替 AES。

 五 总结

各种安全选择的编译参数如下:

  1. ----- (OS) Linux开启地址随机化

  2. echo 2 > /proc/sys/kernel/randomize_va_space

  3. ----- (GCC) 栈保护,优先strong(4.9后gcc),次优all

  4. -fstack-protector-strong -fstack-protector-all

  5. ----- (GCC) GOT表保护,建议添加-z,now全部保护

  6. -Wl, -z,relro

  7. -Wl, -z,relro -z,now

  8. ----- (GCC) 不建议指定搜索路径,用户可以配置但是需要保护目录权限

  9. ----- (GCC) 堆栈不可执行

  10. -Wl,-z,noexecstack

  11. ----- (GCC) 生成地址无关代码

  12. -fPIC

  13. ----- (GCC) 随机化

  14. -fPIE

  15. ----- (GCC) 检查缓冲区溢出(可选)

  16. _FORTIFY_SOURCE=2

参考  GCC 安全编译选项_yunshouhu的博客-CSDN博客_gcc安全编译

更多推荐

gcc 编译安全选项