• 【软件与系统安全笔记】四、内存破坏漏洞


    【软件与系统安全】四、内存破坏漏洞

    Image

    这是《【软件与系统安全】笔记与期末复习》系列中的一篇

    2022-03-21 第四次课后半部分

    mem-vul-part.pdf 内存破坏漏洞

    与安全相关的语言问题(C 与 Java 比较)

    回顾:

    漏洞、攻击与危害

    漏洞(vulnerability):可以被对缺陷具有利用能力的攻击者访问利用缺陷, 要素:

    • 缺陷 (flaw)
    • 攻击者能访问缺陷
    • 攻击者有利用缺陷的能力

    攻击(attack): 指攻击者尝试利用漏洞,例如 主动、被动、DoS…

    危害(compromise):攻击成功则危害发生

    • 软件错误(Software error):会导致软件无法满足预期的编程错误
    • 软件漏洞(Software vulnerability):可能导致攻击的软件错误

    C 字符串的用法与陷阱

    在 C/C++ 程序中, 用 C 语言字符串编程时, 容易出现的错误

    • 缓冲区溢出(Buffer overflows)
    • Null 终止错误(null-termination errors)
    • 差一错误(off-by-one errors)

    差一错误 是在计数时由于边界条件判断失误导致结果多了一或少了一的错误

    gets:无边界的字符串复制

    strcpy 和 strcat

    缓冲区溢出:栈溢出(stack smashing)

    “x86 Linux 系统的线性地址空间分层”1

    “System V AMD64 ABI 调用惯例”2

    缓冲区溢出可被利用于修改:

    • 栈上的: 返回指令指针, 函数指针, 局部变量. . .
    • 堆数据结构

    覆写返回指令指针

    image.png

    代码注入

    将 ret 设置为被注入代码的起始地址, 被注入代码可以做任何事情,如下载和安装蠕虫

    注入 Shell Code

    shellcode: 注入的 payload 代码会执行起来一个 shell
    在该 shell 中, 攻击者可以执行任何命令
    该 shell 与当前进程具有相同的特权级
    通常具有 root 权限的进程会被攻击

    如何使用被注入的代码调用“execve”?

    • 将函数“execve”的地址注入栈上

    • “execve”是一个 libc 中的函数, 动态链接到进程地址空间中

    • 为了使得我们的可执行程序能够调用 libc 中的函数,可执行程序必须能找到被调用函数的地址

      • 如何做到? 当前程序通过一个 stub(PLT, Procedure Linkage Table )调用“execve”, PLT 在链接时检索 GOT (Global Offset Table)中的地址集合(请回忆动态链接过程)
        “劫持全局偏移量表(GOT)中的函数指针, 被动态链接函数所使用”3

    整数溢出

    堆溢出

    【NOTES.0x04】简易 Glibc heap exploit 笔记 - arttnba3’s blog

    堆溢出: 在堆上开辟的缓冲区中的缓冲区溢出漏洞

    溢出发生在堆缓冲区

    溢出堆上的元数据

    Heap allocators (又称内存管理器)

    • 哪些内存区域已被开辟,它们的大小
    • 哪些内存区域可以被开辟

    Heap allocators 维护了元数据 (前块大小, 本块大小, previous 指针, next 指针)

    • 堆元数据与堆数据是内联关系
    • 内存管理函数(如 malloc()和 free())内部,会修改元数据

    堆溢出攻击会篡改元数据,并等待内存管理函数把被篡改的元数据写入目标地址

    Heap Allocator

    • 维护一个已开辟的块的双向链表,以及一个空闲块的双向链表
    • malloc()和 free()会修改双向链表

    image.png

    移除一个块

    image.png

    攻击 Heap Allocator

    • 通过溢出 chunk2, 攻击者控制 chunk2 的 bk 和 fd 指针

      • 实际上就控制了“向哪儿写数据”(where)以及 “写入什么数据”(what), 可以随意修改堆, 因此又称 “write-what-where” 漏洞
    • 假设攻击者想将 value 写入内存地址 addr

      • 攻击者将 chunk2->fd 设为 value
      • 攻击者将 chunk2->bk 设为 addr - offset,其中 offset 为 fd 字段在 chuck 结构体内的偏移量

    free():

    • chunk2->bk->fd = chunk2->fd
    • chunk2->fd->bk = chunk2->bk

    变为

    • “(addr - offset)->fd = value”, 等同于(*addr)=value
    • “value->bk = addr - offset” (应确保value->bk有可写权限)

    第一个写操作实现了攻击者的目标, 实现了任意的内存写操作

    write-what-where 操作由free()完成, 故需从free()向上寻找潜在溢出

    当溢出与代码注入/代码重用结合时,

    • *(addr)=value”的addr是预先选择的函数指针、返回指令指针、影响控制流的变量地址等, value是恶意的跳转目标
    • fd字段在chunk结构体中的偏移量固定
    • 因此, 决定被 free 的 chunk2 的元数据 (chunk2->fd, chunk2->bk) 应该被溢出修改的取值, 是可计算的

    Use After Free

    定义: 程序在堆上释放内存, 然后引用该内存, 就好像该内存位置仍然合法:攻击者可以控制使用已释放的指针进行的数据写入

    又称作悬挂指针使用,也是一个 write-what-where 漏洞

    大多数有效的 use-after-free 攻击利用另一类型的数据

    struct A {
        void (*fnptr)(char *arg);
        char *buf;
    };
        struct B {
        int B1;
        int B2;
        char info[32];
    };
    
    // 释放A, 开辟B, 实际做了什么?
    x = (struct A *)malloc(sizeof(struct A));
    free(x);
    y = (struct B *)malloc(sizeof(struct B));
    
    // 如何利用此漏洞?
    y->B1 = 0xDEADBEEF;
    x->fnptr(x->buf);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    Use-after-free 是类型混淆的一个实例

    禁止 Use After Free

    如何以较简单的方式禁止use-after-free?

    设置所有被释放的堆空间为NULL

    这时在使用被释放的堆空间时产生一个 null-pointer dereference

    目前, 操作系统对 null-pointer deference 有内建的防护机制

    复杂度: 需要设置所有别名指针(aliased pointers)为NULL

    Double Free

    类型混淆

    格式化字符串攻击

    能够导致很灵活的恶意利用

    • 代码注入: 被注入代码直接放入字符串
    • 各种代码重用

    int i; printf (“i = %d with address %08x\n", i, &i);

    将格式化字符串的地址指针, i&i 分别通过寄存器 rdi, rsi 和 rdx 传递, 并调用 printf

    当程序运行至printf内部, 会在这些寄存器中查找参数;如果参数超过6个,还会在栈上查找参数

    格式指示符字母的含义: printf - C++ Reference (cplusplus.com)

    image.png

    image.png

    格式化字符串中的“%n

    能够将到“%n”位置为止已经由printf打印出的字节数写到一个我们选定的变量中

    int i;
    printf ("foobar%n\n", (int *) &i);
    printf ("i = %d\n", i);
    // i 的值最终为6
    
    • 1
    • 2
    • 3
    • 4

    对于“format-string.c”程序, 如果用户输入“foobar%n”会怎样?

    • 从rsi获得一个地址, 且向该地址上的内存单元中写入整数6
    • 如果输入的是“foobar%10u%n”呢? 可能会向该内存位置写入16
    • 如何向一个任意地址写? 将该地址放在正确的位置 (寄存器或栈单元, 以作为printf的参数)

    因此, 攻击者可以用任意内容更新任意内存。可否覆写一个函数指针并劫持控制流(且安装某些蠕虫代码)?

    int main(int argc, char *argv[]) {
        char buf[512];
        fgets(buf, sizeof(buf), stdin); // no buffer overflow here
        printf("The input is:");
        printf(buf); // format string attacks here
        return 0
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    攻击者可以

    • 查看/修改内存的任意部分
    • 执行任意代码, 只需要把代码也放入buf

    格式化字符串攻击的修复

    image.png

    image.png

    通过提供一个特殊的格式化字符串, 可以规避“%400s”的限制:

    %497d\x3c\xd3\xff\xbf”。创建一个497字符长的字符串,加上错误字符串(“ERR Wrong command: ”),超过了outbuf的长度4字节。虽然“user”字符串只允许 400字节,可以通过滥用格式化字符串参数扩展其长度。因为第二个sprintf不检查长度, 它可以用来突破outbuf的长度界限。此时我们写入了一个返回地址 (0xbfffd33c), 并可以以之前栈溢出的利用方式进行攻击

    防止格式化字符串漏洞

    即限制攻击者控制格式化字符串的能力

    • 如果有可能,硬编码字符串;且不用包含“%*”的格式化字符串
    • 如果必须要用格式化字符串,至少不要用“printf(arg)
    • 不要使用 %n
    • 小心其他的引用: %ssprintf 能够被用于构造栈内容披露攻击
    • 编译器支持printf参数与格式化字符串的匹配检查

    image.png

    image.png

    • 最大 3G ?

    1. x86 Linux 系统的线性地址空间分层

      ↩︎
    2. System V AMD64 ABI 调用惯例 ↩︎

    3. 劫持全局偏移量表(GOT)中的函数指针, 被动态链接函数所使用 ↩︎

  • 相关阅读:
    张乐:研发效能的黄金三角及需求与敏捷协作领域的实践
    基于JavaScript开发的网页多点绘图程序
    十亿数据如何去重,红黑树到hash再到布隆过滤器
    【论文研读】-Defining the Ethereum Virtual Machine for Interactive Theorem Provers
    社交媒体营销策略——如何病毒式传播:增加受众范围的9个技巧
    (附源码)php丽江旅游服务网站系统 毕业设计 010149
    C&C++指针实训(国防科大)
    机器人用到的专业Toolbox总结
    crm客户管理系统为企业带来的价值
    mysql主从备份
  • 原文地址:https://blog.csdn.net/weixin_47102975/article/details/126822598