Ripple 20:Treck TCP/IP协议漏洞技术分析
Ripple20是一系列影响数亿台设备的0day(19个),是JSOF研究实验室在Treck TCP/IP协议栈中发现的【1】,该协议栈广泛应用于嵌入式和物联网设备。而由于这些漏洞很底层,并且流传版本很广(6.0.1.67以下),通过供应链的传播,使得这些漏洞影响十分广泛和深远。这也是这个漏洞代称Ripple的由来:The damaging effects of a these vulnerabilities has been amplified like a ripple effect to a dramatic extent due to the supply chain factor. (Ripple意思就是涟漪,连锁作用) 我们在本文中,重点分析了Ripple20中的CVE-2020-11896(CVSS V3 10.0)漏洞原理和利用。
背景知识
IP 分片
IP分片的概念是为了解决数据包最大长度的限制,例如以太网最大传输单元MTU为1500字节。那么如果发送的IP packet超过这个长度,就需要将数据包分为小的片段fragments。分片之后,想要将分片的数据包,在接收端重组,还需要一些标志位,用于表示接下来是否还有分片,该分片在整个数据包的偏移offset和是否是同一数据包等信息。在IP头部有4个字节(4-8字节)是用于存储这些分片信息的:
其中Flags字段有3部分:Reverse位、DF(Don't Fragment)和MF(More Fragments)。
IP隧道
IP隧道是将IP报文封装在另一个IP报文中的技术,如果外层和内存协议都是IP协议,那么我们将之称为ip-in-ip,结构如下图所示:
Treck TCP/IP的内部实现
Treck TCP/IP中,通过tsPacket结构表示数据包Packet:
struct tsPacket {
ttUserPacket pktUserStruct;
ttSharedDataPtr pktSharedDataPtr; // Point to corresponding sharable ttSharedData
struct tsPacket * pktChainNextPtr; // Next packet (head of a new datagram in a queue)
struct tsDeviceEntry * pktDeviceEntryPtr; // pointer to network Device struct
union anon_union_for_pktPtrUnion pktPtrUnion;
tt32Bit pktTcpXmitTime;
tt16Bit pktUserFlags;
tt16Bit pktFlags;
tt16Bit pktFlags2;
tt16Bit pktMhomeIndex;
tt8Bit pktTunnelCount; // Number of times this packet has been decapsulated. Initially setto zero.
tt8Bit pktIpHdrLen; // Number of bytes occupied by the IP header.
tt8Bit pktNetworkLayer; // Specifies the network layer type of this packet (IPv4, IPv6, ARP, etc).
tt8Bit pktFiller[1];
};
该结构重要字段包括: tsShareData字段:指向存储处理数据包所需信息的Buffer的指针 pktChainNextPtr字段:指向下一个Packet pktUserStruct字段:表示数据包分片 pktUserStruct字段是另外一个重要的数据结构ttUserPacket(typedef struct tsUserPacket):
struct tsUserPacket {
void * pktuLinkNextPtr; // Next tsUserPacket for fragmented data 指向下一个分片
ttUser8BitPtr pktuLinkDataPtr; // Pointer to data
ttPktLen pktuLinkDataLength; // Size of data pointed by pktuLinkDataPtr 当前buffer的大小
ttPktLen pktuChainDataLength; // Total packet length (of chained fragmented data). Valid in first link only. 整个packet的长度,如果没有分片,等于pktuLinkDataLength
int pktuLinkExtraCount; // Number of links linked to this one (not including this one). Valid in first link only.
};
该结构重要字段包括: pktuLinkNextPtr字段:指向下一个分片的指针 pktuLinkDataPtr字段:指向数据buffer的指针 pktuLinkDataLength字段:表示当前分片长度 pktuChainDataLength字段:表示整个Packet长度,当不存在分片的时候,这个长度与pktuLinkDataLength长度相等
数据包在不同层级中进行处理的的时候,需要调整pktuLinkDataPtr指针。例如一个ICMP请求包,该数据包有3层:Ethernet、IPv4和ICMP。在以太网层处理过程中(tfEtherRecv),pktuLinkDataPtr字段指向的是以太网头部,当下一层处理的时候,该字段和一些其他字段将会做以下调整:
在这个例子中,以太网包头长度为0xE,所以pktuLinkDataPtr指针向前调整0xE,而长度字段则减少0xE。 在接下来,IPv4层(tfIpIncomingPacket)开始处理数据包,pktuLinkDatatr此时指向的是IP头部。Treck协议栈通过在tfIpIncomingPacket调用tfIpReassemblePacket函数来重组分片,每接受到一个分片,该函数将该分片插入到链表中,链表之间通过pktuLinkNextPtr指针连接起来:
如果分片有遗漏,那么最终返回Null;如果没有,那么该函数将会把分片链表交给下一层去处理。
CVE-2020-11896漏洞原理
其中4字节到8字节表示的是IP分片相关信息,包括表示,Flags和Fragment Offset。 而前4字节则有两个重要字段: IP Header Length:IP头部长度 Total Length:IP数据包总长度
接着输入理解一下tfIpIncomingPacket函数的实现,该函数首先对数据包进行一些简单的check:
接下来检查IpTotalLength是否小于等于pktuChainDataLength字段,这意味着实际接收到的数据比IP头部标识的IP数据包总长度要长,在这种情况下,就对多余数据进行裁剪:
裁剪(trimming)的方式很简单,就是将pktuChainDataLength和pktuLinkDataLength设置为ipTotalLength的长度。注意,漏洞就在这个裁剪这里。 回顾一下先前所说:pktuChainDataLength代表的是整个数据包的长度,pktuLinkDataLength代表的是当前分片的长度,如果上述操作成功,那么就会出现: pktuLinkDataLength = pktuChainDataLength = IpTotalLength 可是如果这个时候,如果pktuLinkNextPtr还是指向其他的分片呢,那么就会存在不一致现象,这种不一致的现象将会导致后续处理数据包产生错误。但是还是有问题,因为在tfIpIncomingPacket函数处理中,首先进行的是trimming操作,接着调用tfIpReassemblePacket对根据pktuChainDataLength大小,建立分片链表,而在tfIpReassemblePacket函数中并不会将分片数据包复制到buffer中。也就是说,先进行trimming操作,接着调用tfIpReassemblePacket建立分片链表,最终将分片链表返回给下一层进行处理,而下一层并不会再次调用tfIpIncomingPacket。这种情况下,上述不一致现象实际上并不能利用。
使用IP隧道
为了使得分片在IP层被处理,并且可以到达脆弱代码位置,我们使用IP隧道。 tfIpIncomingPacket函数在处理内层IP数据包时,将之作为没有分片的数据包进行处理,也就是说MF标志位=0,此时将不会调用tfIpReassemblePacket函数进行处理。这个tfIpIncomingPacket函数将会在两个地方被调用,一次是在内层的IP packet(没有分片,只调用一次),一次是在外层的ip packet(多次,每个分片调用一次)。在这个处理过程中,tfIpIncomingPacket首先将会接收所有的外层ip分片,对于每个分片,都会调用tfIpReassemblePacket函数。在接收全部的分片之后,将会进入下一网络层的处理,这里由于ip-in-ip,tfIncomingPacket函数将会被本身所递归调用,去处理内层ip数据。ip-in-ip结构如下图所示:
考虑如下这个场景: 内层IP数据包:IPv4{len=32, proto=17}/UDP{checksum=0, len=12} payload为:’A’*1000 外层IP数据包(分片1):IPv4{frag offset=0, MF=1, proto=4, id=0xabcd} 外层IP数据包(分片2):IPv4{frag offset=40, MF=0, proto=4, id=0xabcd} 整体数据包及分片情况如下图所示:
当tfIpIncomingPacket函数(该函数被处理外层数据包的tfIpIncomingPacket所递归调用)处理内层IP数据包的时候,此时已经完成了分片的重组,这两个ip分片被tsUserPacket->pktuLinkDataPtr所连接起来。 那么接下来在该函数接下来的流程中,内层IP数据包的total length(32),是小于pktuChainDataLength(1000+ 8 + 20 = 1028)的,进入trimming分支进行裁剪操作,将pktuChainDataLength设置为32。
if ((uint)ipTotalLength <= pkt->pktuChainDataLength) { if ((uint)ipTotalLength != pkt->pktuChainDataLength) { pkt->pktuChainDataLength = (uint)ipTotalLength; pkt->pktuLinkDataLength = (uint)ipTotalLength; } }
现在我们新的问题出现了:如何将这个不一致问题(inconsistency)转为一个内存破坏(memory corruption)?
UDP 2.3.2中的heap overflow
在UDP数据处理中,可以确定的是,至少有一条代码路径是将IP分片复制到自定义的buffer里面,那么就存在漏洞的可能性。这个过程需要malloc一个堆空间,堆空间的大小取决于pktuChainDataLength字段,然后将ip分片复制到heap中。做复制这个事情的函数是tfCopyPacket,该函数逻辑抽象如下:
可以看到,这个memcpy的过程并不考虑长度。而堆块本身的大小是取决于pktuChainDataLength,这个字段在先前漏洞trimming被触发后,实际上是小于实际IP数据包总大小的,heap overflow就这样出现了。 接下来是一些UDP本身字段的校验,这部分就不再详细叙述了,为了解决接收队列非空的要求,还需要快速的发数据包保持接受队列中存在数据包。
CVE-2020-11896漏洞利用
接下来作者利用Digi Connect ME 9210进行了验证,证明可以做到远程代码执行。这个设备如下图所示:
这是一个极小的嵌入式设备,用于完成串口到以太网的转化,串口常包括SPI、I2C和CAN等,里面有嵌入式的ARM CPU,有一个NET+OS操作系统。本次实验的目标是通过远程执行shellcode,将开发板上面的LED灯点亮。
exploit编写策略
heap overflow的利用大概有两种方式: 1. 覆盖堆中的meta-data信息。所谓meta-data就是堆中的元信息,包括堆块的大小、空闲位的标志等等堆本身自带的信息,这种利用方式和libc pwn中的off by one原理类似,溢出到下一个堆块的重要信息。 2. 覆盖堆上面的数据结构。这种攻击方式是希望堆上面存着一些数据结构,该结构中存着一些函数指针可以被覆盖。
第二种利用方式与特定应用程序相关,第一种则适用面更加广泛,本文选择第一种利用方式。
理解Treck heap的内部实现
Treck中实现了一套自己的堆分配机制,在内部使用的是固定大小的堆分配模式,一个分配单元被称之为bucket。在Treck实现中,有如下几种固定大小:
而释放后的bucket,有自己对应的free buckets list,每次释放一个bucket后,就将其插入到对应大小list的头部,这里可以类比于ptmalloc中的tcache机制。在Treck协议栈中通过tfGetRawBuffer,该函数参数为一个4字节大小的size,返回一个指向分配内存的指针,这个分配的内存我们称之为raw buffer,raw buffer的释放通过tfFreeRawBuffer。分配的过程就是从对应空闲链表中取出一个堆块,然后转为ttRawBufferPtr类型指针返回给用户:
如果空闲链表中没有空闲的bucket,那么就调用tfBufferDoubleMalloc分配一个新堆块返回给用户:
可以看到,空闲bucket插入到空闲链表后,将会利用rawNextPtr指针链接起来,rawNextPtr指向上一个空闲bucket,结构如下图所示:
到目前为止,我们搞清楚了Trec中的堆结构,堆的分配和释放过程,特别是空闲bucket也是类似ptmalloc一样通过链表利用堆中的指针链接起来的。那么我们自然可以想到,如果可以覆盖掉rawNexPtr指针为可控值,就可以做到任意地址分配了:
就像上图所示,通过两次分配之后,我们就可以在栈上分配一个可控的堆块。这种攻击方式,在libc Pwn中是非常常见的,例如fastbin attack打malloc_hook改为one_gadget。 这个嵌入式设备是ARM v9,并且这个堆实现过程基本没有check,甚至没有开NX,最后白皮书中采取的方法也是通过ROP跳到shellcode来做到RCE的。 目前笔者手头还没有实际运行Treck的设备,所以没法实际动手调试和写exp,运行效果可以看JSOF官方的视频演示效果【1】,启明星辰ADLab也做了一个视频【2】。
总结
Ripple 20存在于Treck TCP/IP协议栈中,该协议栈广泛存在于嵌入式设备中,波及的行业包括:工业设备、电力设备、企业网络设备、交通、能源等等,而上述设施我们称之为:关键基础设施
Ripple 20不好修补,首先由于供应链的广泛传播,漏洞存在形式又很底层,所以排查设备是否运行Treck TCP/IP协议栈容易疏漏。其次虽然Treck官方已经提供了最新稳定版本6.0.1.67,但是这种基础协议栈软件的更新还是比较麻烦的
从白皮书披露来看,漏洞利用技术并不十分复杂,所以漏洞利用门槛可能不会很高,例如这个漏洞,即使无法做到RCE,至少远程令设备crash是比较容易做到的,而关键基础设施的crash也已经算是大问题了
(通过该实验了解SYN-Flooding攻击,RST攻击,TCP会话劫持,并通过会话劫持拿到服务器shell 权限。)
声明:笔者初衷用于分享与普及网络知识,若读者因此作出任何危害网络安全行为后果自负,与合天智汇及原作者无关!
- TCP/IP协议中backlog参数
TCP建立连接是要进行三次握手,但是否完成三次握手后,服务器就处理(accept)呢? backlog其实是一个连接队列,在Linux内核2.2之前,backlog大小包括半连接状态和全连接状态两种队 ...
- TCP之三:TCP/IP协议中backlog参数(队列参数)
目录: <TCP洪水攻击(SYN Flood)的诊断和处理> <TCP/IP协议中backlog参数> TCP建立连接是要进行三次握手,但是否完成三次握手后,服务器就处理(ac ...
- 【原创】技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)
1.前言 作为应用层开发人员,接触最多的网络协议通常都是传输层的TCP(与之同处一层的另一个重要协议是UDP协议),但对于IP协议,对于应用程序员来说更多的印象还是IP地址这个东西,再往深一点也就很难 ...
- 技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)
1.前言 作为应用层开发人员,接触最多的网络协议通常都是传输层的TCP(与之同处一层的另一个重要协议是UDP协议),但对于IP协议,对于应用程序员来说更多的印象还是IP地址这个东西,再往深一点也就很难 ...
- [转]技术往事:改变世界的TCP/IP协议
原文链接 : http://www.52im.net/thread-520-1-1.html 1.前言 作为应用层开发人员,接触最多的网络协议通常都是传输层的TCP(与之同处一层的另一个重要协议是UD ...
- TCP/IP协议竟然有这么多漏洞
网络攻击是指利用网络存在的漏洞和安全缺陷对网络系统的软硬件及其系统数据进行攻击的行为.TCP/IP协议作为网络的基础协议,从设计之初并没有考虑到网络将会面临如此多的威胁,导致出现了许多攻击方法.由于网 ...
- 读书笔记——网络编程与开发技术(3)基于TCP/IP协议的网络编程相关知识
TCP/IP协议:数据链路层,网络层,传输层,应用层. IP地址分为5类:A类.B类.C类.D类.E类. (A类.B类.C类是基本类,D类多用于多播传送,E类为保留类.) "*"表 ...
- OSI七层模型详解 TCP/IP协议
总结 OSI中的层 功能 TCP/IP协议族 应用层 文件传输,电子邮件,文件服务,虚拟终端 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等 表示层 数据格式化,代码转 ...
- TCP/IP协议(一)网络基础知识
参考书籍为<图解tcp/ip>-第五版.这篇随笔,主要内容还是TCP/IP所必备的基础知识,包括计算机与网络发展的历史及标准化过程(简述).OSI参考模型.网络概念的本质.网络构建的设备等 ...
随机推荐
- GetLastError返回值含义
GetLastError的返回值的含义: (0)-操作成功完成. (1)-功能错误. (2)- 系统找不到指定的文件. (3)-系统找不到指定的路径. (4)-系统无法打开文件. (5)-拒绝访问. ...
- MFC套接字连接不成功-记得在app的cpp文件里面初始化套接字
MFC套接字连接不成功-记得在app的cpp文件里面初始化套接字 stdafx.h文件中添加:#include "afxsock.h" BOOL CMFC_TCP_Client_c ...
- JDK开发环境的搭建和环境变量的配置
首先博主先说一下JDK.JRE.JVM的区别和联系.我们正常运行程序的话只需安装JRE就行啦,如果要编译运行Java程序就得需要JKD下的bin目录下的编译工具. JDK -- java develo ...
- spring boot admin 源码包的编译
https://github.com/codecentric/spring-boot-admin 下载地址: 编译要求: Build Requirements: Node.js v8.x (LTS) ...
- 马士兵老师Java虚拟机调优
该视频主要讲解的内容如下所示: 1.虚拟机的内存结构 1.每一个线程都有一个虚拟机栈,线程中每调用一个方法都会开启一个栈帧,栈帧里面保存方法中的局部变量. 2.方法区在java8以后改名为永久区域pe ...
- Halcon斑点分析BlobAnalysis解析
斑点分析的算法非常简单:在图像中,相关对象的像素(也称为前景)通过其灰度值来识别.例如,图中示例显示了液体中的组织颗粒.这些粒子是明亮的,液体(背景)是暗的.通过选择明亮的像素(阈值),可以很容易检测 ...
- Python初识类与对象
Python初识类与对象 类与对象 世界观角度分析类与对象 类是一个抽象的概念,而对象是一个实体的存在,对象由类创造而出,每个对象之间互相独立互不影响,一个对象可以同时拥有多个类的方法,实例化就是通过 ...
- javadoc导出成word文档
刚刚上次弄完了一个坑爹的任务,这次我领导又给我一个让人脑瓜子疼的任务了. 基本上客户他在验收我们系统的时候,都会要求我们编写相关的文档,这次也不例外. 只是这次的客户要求我们给出接口文档.不仅是要整个 ...
- 入门大数据---Flink开发环境搭建
一.安装 Scala 插件 Flink 分别提供了基于 Java 语言和 Scala 语言的 API ,如果想要使用 Scala 语言来开发 Flink 程序,可以通过在 IDEA 中安装 Scala ...
- Python实用笔记 (8)高级特性——迭代
如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration). 比如dict就可以迭代: >>> d = {'a ...