十分粗糙的读书记录,主要是案例大多不怎么醒目

字符串

  • 标准库函数拷贝越界 puts越界 -> 缓冲区溢出 ->栈溢出 ->代码注入

  • 字符串数组越界

  • 空字符串结尾错误

  • 处理字符串漏洞的方案

    • 输入验证,避免缓冲区溢出
  • 堆栈保护器

几个安全函数替代以及使用(注意,gcc基本没实现,用不上唉)

原函数 安全版本 变化后的函数原型 引入版本
vsnprintf vsnprintf_s 无变化 C11
memcpy memcpy_s 增加长度为第二个参数,限制拷贝长度 errno_t memcpy_s( void *restrict dest, rsize_t destsz, const void *restrict src, rsize_t count ); C11
strncpy strncpy_s 同上 errno_t strncpy_s(char *restrict dest, rsize_t destsz, const char *restrict src, rsize_t count); C11
snprintf snprintf_s 无变化 c11
sscanf sscanf_s 无变化,但涉及到%c %s %[ 要提供长度信息 https://en.cppreference.com/w/c/io/fscanf https://stackoverflow.com/questions/24078746/confusion-with-sscanf-s c11
memset memset_s 增加长度为第二个参数,限制拷贝长度errno_t memset_s( void *dest, rsize_t destsz, int ch, rsize_t count ); c11
注意需要定义
`__STDC_WANT_LIB_EXT1__`

指针

  • 缓冲溢出改写函数指针
  • 修改指令指针的指向
  • 修改全局偏移表
  • 改写.dtor区,调用析构转移权限
  • 改写虚函数指针
  • atexit注入,转移权限
  • longjmp溢出

内存管理

  • 不要假定分配的内存被初始化了
  • 检查malloc返回值 (不过现在的设备,霉有检查的必要吧,如果用new 遇到bad_alloc挂掉就挂掉吧)
  • 指针引用,不要引用已经释放 的指针
  • double free
  • 内存泄漏问题
  • malloc(0) 傻逼行为
  • 垃圾回收中的伪装指针 -> std::pointer_safety 看SO这个问题https://stackoverflow.com/questions/27728142/c11-what-is-its-gc-interface-and-how-to-implement
  • 在条件分支中没有检查new分配失败,因为new失败抛异常,强制nothrow
  • new delete malloc free没有正确配对
    • 注意new数组的坑
    • 如果是placement new一定要知道自己在做什么
  • 容器装指针导致的释放 ->智能指针 ptr_container等等
    • 引入智能指针,比如shared_ptr,有可能又引入循环引用。注意
  • 析构函数不能抛异常
  • gc库原理以及对gc库的缓冲区溢出共计,复写边界标志块
  • 一些解决方案,静态检查等等

整数安全

  • 无符号数回绕
    • ` if (sum +i > UINT_MAX) -> if (i > UINT_MAX - sum)`
    • if (sum -j < 0) -> if (sum < j)
  • 有符号整数类型溢出,注意值范围

    • 除了char默认都是signed char在arm上表现是unsigned
  • 整数转换与类型提升。

    • 简单说,低到高的转换提升可以,注意符号可能错误解释但是没关系,高到低可能会丢或者读错数据
  • 整数操作,注意溢出回绕与截断场景,几个规避方案
    • 用更大的类型强制转换,简单粗暴
    • 先验 ,类似上面的回绕判定
    • 后验
      • 状态位,比如jc。这种实践价值不大
      • 类似回绕,反向判断运算结果是不是被回绕了
  • 重点考虑一下场景
    • 数组索引

    • 指针计算

    • 对象长度大小

    • 循环边界

    • 内存分配

      • 比如传入有符号数但是这个数可能溢出了 or截断了

格式化输出

  • %s替换导致的缓冲区溢出
  • 格式化字符和对应的参数不匹配,导致读取栈上内存,可能读到错误地址segfault
    • 通过这个手段查看栈内容
    • %n 通过printf改写内存
  • 规避方案。避免用户输入字符串,编译检查避免低级错误

并发

  • 概念:阿姆达尔定律?竞争条件和临界区。也有翻译成竞态的
  • volatile 不保证多个线程间同步,不防止并发内存访问,不保证对对象的原子性访问(经常被滥用)
  • 内存模型以及原语。这里没有讲漏洞,只是讲了几个锁,原子量的用法以及避免用错锁

文件IO

  • 进程特权
  • 文件权限
  • 目录 缓冲区溢出
  • 等价错误
  • 符号链接串改

上述解决方案,目录规范化

  • 文件竞争!

推荐实践

  • 安全的开发生命周期
  • QA
  • 设计
  • 静态检查,编译检查等等
  • 验证,代码审计,静态分析,渗透测试,安全检查,攻击面回顾等等

###

gcc/linux平台安全编译链接选项

  • 栈溢出保护 (c_flags)-fstack-protector-all/-fstack-protector-strong
    • 原理,canary word,在堆栈边界标记,然后检查,变过,则发生了溢出 会打印__stack_chk_fail
  • 重定位只读 (ld_flags) -Wl, -z,relro 避免缓冲区溢出修改GOT
    • 立即绑定 (ld_flags) -Wl, -z,relro, -z,now
    • 原理,符号只读不能复写,直接segfault
    • 防不了GOT PLT重写
  • 禁止缓存区溢出shellcode (ld_flags) -Wl, -z, noexecstack
    • 原理,代码段标记不可执行,访问直接segfault 通过观察data端可以看到权限是rwp不是rwxp
    • 注意,堆区,栈区shellcode无效
  • 位置无关代码(c_flags) -fPIC 这个和安全关系不是特别大感觉
  • 位置无关可执行文件(c_flags, ld_flags) -fPIE -pie
  • 实时路径选择 –rpath,可能会被替换造成hook,不安全
    • 动态链接库查找顺序,检查rpath,检查LD_LIBRARY_PATH,检查ld.so.cache,rpath优先级较高导致hook
  • 隐藏符号 -fvisibility=hidden针对共享库隐藏符号
  • 堆栈检查 -fstack-check影响性能
    • 检查栈空间大小,预留缓冲区被使用会告警
  • 删除符号 strip(ld_flags) -s 影响调试
  • 整数溢出-ftrapv 强制检查一遍所有参数再计算,异常直接abort,严重影响性能和稳定性,应该没人用这个吧
  • 缓冲区检查 宏-D_FORTIFY_SOURCE=2 影响运行时性能
    • 原理,hook函数, 替换成_chk函数,检查长度
    • 缺陷,动态分配的空间无法检查