双机本地环境
环境:
台式机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 | R1#conf t |
dhcp会自动为路由器分配一个局域网ip地址
1 | R1#show ip int brief |
配置DNS服务器
1 | R1#conf t |
配置完DNS就可以ping通外网了,下面启动路由器的snmp服务
1 | Router#conf t |
3.配置完路由器可以从笔记本ping通路由器,但是主机本身ping不通路由器,因此在笔记本上运行poc向路由器直接发送SNMP数据包
1 | $ sudo python test-poc.py 192.168.1.11 |
发送数据包之后,路由器的console没有反应了,笔记本也ping不通路由器了,因此判断路由器已经崩溃。poc脚本如下:
1 | from scapy.all import * |
这个数据包由三部分组成,前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 | $ sudo apt install libelf-dev libpcap0.8-dev uuid-dev |
编译产生的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 | $ wget https://ftp.gnu.org/gnu/gdb/gdb-8.1.tar.gz |
编译出的gdb可以远程连接到mips平台的调试接口
1 | $ ./gdb -q |
根据漏洞描述可知,该漏洞是一个缓冲区溢出漏洞,想要找到溢出点有几种思路
1.写45个字节覆盖返回地址高两字节,使程序崩溃,查看返回地址低两字节。写IDAPython脚本,从程序中找出所有低两字节与崩溃地址低两字节相同的指令,如果指令的前两条指令是jal,则有可能是崩溃点的上层函数,在所有满足条件的jal指令处下断点,运行poc看看能否断住。实验中崩溃地址是0x8000f568,IDAPython脚本这样写:
1 | import idc |
因为程序的代码段是从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/