5

Linux堆溢出总结(0x02)

 3 years ago
source link: https://www.ascotbe.com/2021/05/13/HeapOverflow_Linux_0x02/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

我还是太天真了,HVV还在继续,GTMD!但是学习不能落下,只能先放弃写其他项目的代码了

image-20210517140400619

展示先停止更新这个,发现知识面跟不上学的内容了

寻找堆分配函数

  • malloc
  • calloc
  • realloc

通常来说堆是通过调用 glibc 函数 malloc 进行分配的,在某些情况下会使用 calloc 分配。calloc 与 malloc 的区别是:calloc 在分配后会自动进行清空,这对于某些信息泄露漏洞的利用来说是致命的。

calloc(0x20);
//等同于
ptr=malloc(0x20);
memset(ptr,0,0x20);

除此之外,还有一种分配是经由 realloc 进行的,realloc 函数可以身兼 malloc 和 free 两个函数的功能。

#include <stdio.h>

int main(void)
{
char *chunk,*chunk1;
chunk=malloc(16);
chunk1=realloc(chunk,32);
return 0;
}

realloc 的操作并不是像字面意义上那么简单,其内部会根据不同的情况进行不同操作

  • 当 realloc(ptr,size) 的 size 不等于 ptr 的 size 时
    • 如果申请 size > 原来 size
      • 如果 chunk 与 top chunk 相邻,直接扩展这个 chunk 到新 size 大小
      • 如果 chunk 与 top chunk 不相邻,相当于 free(ptr),malloc(new_size)
    • 如果申请 size < 原来 size
      • 如果相差不足以容得下一个最小 chunk(64 位下 32 个字节,32 位下 16 个字节),则保持不变
      • 如果相差可以容得下一个最小 chunk,则切割原 chunk 为两部分,free 掉后一部分
  • 当 realloc(ptr,size) 的 size 等于 0 时,相当于 free(ptr)
  • 当 realloc(ptr,size) 的 size 等于 ptr 的 size,不进行任何操作

寻找危险函数

通过寻找危险函数,我们快速确定程序是否可能有堆溢出,以及有的话,堆溢出的位置在哪里。

常见的危险函数如下

  • 输入
    • gets,直接读取一行,忽略 '\x00'
    • scanf
    • vscanf
  • 输出
    • sprintf
  • 字符串
    • strcpy,字符串复制,遇到 '\x00' 停止
    • strcat,字符串拼接,遇到 '\x00' 停止
    • bcopy

确定填充长度

这一部分主要是计算我们开始写入的地址与我们所要覆盖的地址之间的距离。 一个常见的误区是 malloc 的参数等于实际分配堆块的大小,但是事实上 ptmalloc 分配出来的大小是对齐的。这个长度一般是字长的 2 倍,比如 32 位系统是 8 个字节,64 位系统是 16 个字节。但是对于不大于 2 倍字长的请求,malloc 会直接返回 2 倍字长的块也就是最小 chunk,比如 64 位系统执行malloc(0)会返回用户区域为 16 字节的块。

#include <stdio.h>

int main(void)
{
char *chunk;
chunk=malloc(0);
puts("Get input:");
gets(chunk);
return 0;
}

注意用户区域的大小不等于 chunk_head.size,chunk_head.size = 用户区域大小 + 2 * 字长

还有一点是之前所说的用户申请的内存大小会被修改,其有可能会使用与其物理相邻的下一个 chunk 的 prev_size 字段储存内容。回头再来看下之前的示例代码

#include <stdio.h>

int main(void)
{
char *chunk;
chunk=malloc(24);
puts("Get input:");
gets(chunk);
return 0;
}

观察如上代码,我们申请的 chunk 大小是 24 个字节。但是我们将其编译为 64 位可执行程序时,实际上分配的内存会是 16 个字节而不是 24 个。

0x602000:   0x0000000000000000  0x0000000000000021
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000020fe1

16 个字节的空间是如何装得下 24 个字节的内容呢?答案是借用了下一个块的 pre_size 域。我们可来看一下用户申请的内存大小与 glibc 中实际分配的内存大小之间的转换。

/* pad request bytes into a usable size -- internal version */
//MALLOC_ALIGN_MASK = 2 * SIZE_SZ -1
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) \
? MINSIZE \
: ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

当 req=24 时,request2size(24)=32。而除去 chunk 头部的 16 个字节。实际上用户可用 chunk 的字节数为 16。而根据我们前面学到的知识可以知道 chunk 的 pre_size 仅当它的前一块处于释放状态时才起作用。所以用户这时候其实还可以使用下一个 chunk 的 prev_size 字段,正好 24 个字节。

实际上 ptmalloc 分配内存是以双字为基本单位,以 64 位系统为例,分配出来的空间是 16 的整数倍,即用户申请的 chunk 都是 16 字节对齐的。

TCache

上篇内容关于内存分配实现为glibc-2.23,后面由于glibc-2.26(ubuntu 17.10)加入了TCache机制而有了较大的变化(see commit),TCache全名为Thread Local Caching,它为每一个线程创建一个缓存,里面包含了一些小堆块,无须对arena上锁即可使用,这种无锁的分配算法有不错的性能提升。虽然线程缓存在多年以前已经在另一个内存分配器TCMalloc(Thread-Caching Malloc)中实现,但是直到2017年ptmalloc2才在libc-2.26中将其正式加入,并默认开启。

相关结构体

tcache引入了两个新的结构体,tcache_entrytcache_perthread_struct

这其实和fast bin很像,但又不一样。

tcache_entry

/* We overlay this structure on the user-data portion of a chunk when
the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;

tcache_entry 用于链接空闲的 chunk 结构体,其中的 next 指针指向下一个大小相同的 chunk。

需要注意的是这里的 next 指向 chunk 的 user data,而 fastbin 的 fd 指向 chunk 开头的地址。

而且,tcache_entry 会复用空闲 chunk 的 user data 部分。

tcache_perthread_struct

/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

# define TCACHE_MAX_BINS 64

static __thread tcache_perthread_struct *tcache = NULL;

每个 thread 都会维护一个 tcache_perthread_struct,它是整个 tcache 的管理结构,一共有 TCACHE_MAX_BINS 个计数器和 TCACHE_MAX_BINS项 tcache_entry,其中

  • tcache_entry 用单向链表的方式链接了相同大小的处于空闲状态(free 后)的 chunk,这一点上和 fastbin 很像。
  • counts 记录了 tcache_entry 链上空闲 chunk 的数目,每条链上最多可以有 7 个 chunk。

Off-By-One

严格来说,off-by-one漏洞是一种特殊的溢出漏洞,指程序向缓冲区中写入时,写入的字节数超过了缓冲区本身的大小,并且只越界了一个字节。这种漏洞的产生往往与边界验证不严或字符串操作有关,当然也有可能写入的size正好就只多了一个字节:

  • 使用循环语句向缓冲区中写入数据时,循环的次数设置错误导致多写入一个字节
  • 字符串操作不合适,比如忽略了字符串末尾的\x00

Asis CTF 2016

题目下载

首先还是需要先关闭ALSR

echo 0 > /proc/sys/kernel/randomize_va_space

接着查看保护

ascotbe@ubuntu:~/Desktop/Pwn$ checksec b00ks
[*] '/home/ascotbe/Desktop/Pwn/b00ks'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
https://ctf-wiki.org/pwn/linux/glibc-heap/implementation/tcache/

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK