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

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

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);

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

对于“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
}

攻击者可以

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

格式化字符串攻击的修复

image.png

image.png

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

%497d\x3c\xd3\xff\xbf<nops><shellcode>”。创建一个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)中的函数指针, 被动态链接函数所使用 ↩︎

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

框架主义者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值