外 文 译 文
IP语音(VoIP)通信中实时传输协议(RTP)的脆弱性
Mike Adams
Acme Packet公司
伯灵顿,MA 01803
Email: madams@
Minseok Kwon
计算机科学系
罗切斯特技术学院
Email: jmk@
摘要——在过去的几十年中,IP语音(VoIP)已经给电信业带来的革命性的改变。VoIP已经变得史无前例的普遍,以至于越来越多的用户已经从使用传统的公共交换电话网(PSTN)进行通话,转向了使用基于IP的数据网络。然而,这也带来了一个挑战:保证这些VoIP网络电话连接的安全。在本文中,我们研究了一种新的方法,用来证明VoIP连接中存在的脆弱性。我们所研究的方法是监测实时数据流(例如,RTP),并且在预期的时间点插入包含从监测过程中评估得出的虚假话音数据。由于这些虚假的数据包都是通过原始数据包巧妙修改而来的,所以我们可以在最小化所插入包的数量的同时,最大化测试的效果。最小化发包数量也有利于使本测试避免拒绝服务防御机制的检测。我们的研究结果表明,在期望的时间点插入的数据包,确实可以扰乱原始的RTP媒体流,并且不会使网络流量产生任何明显的增加。
关键字 IP语音,RTP,SIP,多媒体网络,安全,脆弱性,实验
I. 绪论
近年来,IP语音(VoIP)技术已经同时在研究和商业领域取得了重大的进展[1]-[4]。VoIP通过使用一些技术如SIP[1],RTP[2]和H.323[5],允许用户通过基于IP的数据网络拨打电话,而不仅限于传统的公共交换电话网(PSTN)。随着各种技术的进步,VoIP可以提供比PSTN质量更高的服务,且具备比PSTN更强的服务负担能力。毫无疑问的,电信业现正逐步趋向于使用VoIP作为其主要的电话基础设施。
尽管VoIP技术已经有了很大程度上的进步,但是绝大部分由电信运营商提供的VoIP服务并不安全,它们容易受到各种恶意行为的威胁。如果一个威胁者对两个终端用户之间的通信网络进行嗅探,那么他可以看到数据线间传送的几乎所有的数据包。威胁者甚至可以将自己伪造的数据包插入到数据线中,从而导致合法用户成为无法使用网络资源的受害者(类似于拒绝服务威胁)。
本文的目的是要证明,针对VoIP网络的拒绝服务威胁可以被人工的制造,并且很难检测和防御。虽然这个威胁可以适用于若干类型的实时数据流,但是我们只关注使用RTP协议传输的、进行G.711(U-LAW和A-LAW格式)编码的数字化语音数据[2],[6]。这种威胁也可以被看作是RTP的脆弱性测试。其主要思想是在特定的时间注入虚假的RTP数据包,以促使接收者接收这些虚假的RTP数据包,而丢弃合法的。这种类型的威胁是可能
实现的,因为我们可以预见某个特定的RTP包被交付的确切时刻。此外,互联网供应商(ISPs)由于只有有限的资源,因此通常并不检查RTP的有效载荷,同时,他们也无法发现可反映此类威胁中任何恶意行为的明显迹象。我们也采取了类似的方法发起低速率(shrew)TCP拥塞控制[7]威胁,可以使得这种威胁难以检测和防御。值得注意的是,我们研究的重点是证明这样的威胁是可行的,而不是研究VoIP客户在受到威胁后为了诉求服务质量所作的反应。
我们的系统包括两个部分:1)一个数据包捕获引擎和2)一个数据包注入引擎。数据包捕获引擎负责监视网络中所有的RTP流量,并且在检测到VoIP通话时激活数据包注入引擎。数据包注入引擎随后与底层的RTP媒体流同步,并且在每个正常的RTP数据包的预计到达时间之前,向其中插入虚假的RTP数据包。我们的研究结果表明:在期望的时间点插入的数据包,确实可以扰乱原始的RTP媒体流,并且不会使网络流量产生任何明显的增加。结果还表明:这种类型的威胁可以使用隐蔽的方式来完成,从而使其难以被普通的拒绝服务防御机制追踪。
该文接下来的部分组织如下。在第II章中,我们讨论了脆弱性测试的基本理论,并且说明为什么我们的方案在VoIP中是可行的。在第III章中,我们给出我们的相关工作概况。在第IV章中,我们描述了本文所提方案的基本设计和实施细节。在第V章中,我们呈现了实验的结果。最后,我们对结论进行总结,并在第VI章中提出针对下一步工作的计划。
II. 动机
我们的脆弱性测试方案并没有没有在技术上扰乱RTP或者制造任何有害的行为。该方案实际上是通过增加虚假重复的RTP数据包,扭曲在终端用户的音频表象。这种威胁是如何成为可能的?答案有两个方面:1)对ISP来说,检查RTP有效载荷是不可行的,2)除了在终端用户处的音频表象外,没有其他明显的迹象可以反映出恶意行为的发生。
ISP不检查RTP有效载荷、加密和认证信息[8],它们也不进行任何的数据包深度分析,因为这些计算会带来经济开销,同时也会造成实时传输流的延迟。为了验证RTP有效载荷,一个ISP将需要缓存一些当前的RTP有效载荷,然后可以使用数字信号处理(DSP)技术,在处理新进数据的同时处理这些缓存的数据。为了运行尽量接近实时,该过程需要昂贵的设备来支持。当终端用户意识到通话质量低下,他们通常会挂断电话并到ISP处抱怨。然而,这样的抱怨并不能帮助ISP识别出任何的错误行为。如果ISP检查通话记录(技术上避免访问到通话内容),ISP将会发现通话从开始到结束都是正确无
误的。同时,ISP未经授权去窃听用户的通话,即使是出于质量监控的目的,也是不合法的。因此,我们的威胁将确实不会被拒绝服务防御机制检测出来,也不会被标记为任何类型的异常行为。
为了验证我们的想法的可行性,我们进行了一个简单的实验。该试验台由两台运行在Debian Linux操作系统下、内核为2.6.21版的PC机组成,分别充当呼叫者和被叫者。当这两个PC之间进行通信时,另一台运行在相同的Linux系统下PC机充当一个威胁者,试图扰乱呼叫者和被叫者之间的数据包。威胁者检测语音数据包,并与其进行同步,然后少量增加自己定制的RTP数据包。本实验的主要目的是测试RTP媒体流是否可以预测。如果可以预测,威胁者应该能够在原始RTP数据包到达的前一小段时间内,将多余的RTP数据包注入其中。随后,被叫者就会转而接收这些注入的数据包,并且经历通话质量的下降。这将最终扰乱整个通信,却由于其“少量”的特征而不会被检测到。
图1. Wireshark中的RTP 抖动分析
图1展示了一幅网络中RTP抖动的快照。我们使用Wireshark[9]来分析这个RTP抖动数据。其中,横轴是该RTP流的时间轴,纵轴代表抖动量。在该图左下角的四组统计数字中,“Graph1”和“Graph 3”分别显示正向和反向的抖动情况。该图显示,在通话的大部分时间中,抖动并不存在,即使是有一些抖动发生,其坚持时间也不会超过50毫秒。在我们的其他试验中,被观察的本地和专用网络通话中,没有高于1毫秒的抖动发生。这些结果有力的表明,我们可以在预期的时间,用更加可预计的方式添加伪造的数据包。
III. 相关工作
VoIP将小时间量程的语音数据包数字化为IP数据包,并将它们通过互联网(Internet)数据流从一个IP电话发送到另一个。由于数据流中的每个数据包只在几毫秒(通常是20-60毫秒)内有效,所以要恢复一个丢失或被破坏的数据包是极其困难的。VoIP是一种多协议的聚集,尤其是1)信令协议和2)传输协议。信令协议初始化两个通话端点,中继通话信道信息和传输协议规范。
会话初始化协议(SIP)[1]是目前最流行的VoIP信令协议,他可以帮助建立、修改和拆除两个通话端点之间的会话。为了进行通话,一个SIP用户代理首先需要将自己注册到一个SIP服务器,该服务器具备将名字(例如,电子邮件地址)解析为IP地址的功能。在此之后,这个代理服务器与服务器取得联系并获取接收者的IP地址,从而与接收者对一些通话细节信息进行协商,包括编码器类型、采样率、将使用的IP地址和端口以及传输协议等。在进行协商的过程中,SIP实际上又使用了另一个协
议,即会话描述协议(SDP)[10]。一个SDP的有效载荷可能包含一个列表,列表中是呼叫者所了解的编码器及可接受的采样率。呼叫者,特别是用户代理服务器(UAS)将检查该列表,选择一个选项,并且回复给呼叫者也就是用户代理客户端(UAC)。
实时传输协议(RTP)[2]是当前最流行的VoIP传输协议。RTP定义了一个数据包结构,包括有效载荷标识、序列号、时间戳和一些用于不同目的的标志位(例如,语音数据中填充、RTP扩展等)。此外,RTP使用实时传输控制协议(RTCP)[2]监控和报告传输状态。这些状态包括字节发送数、数据包发送数、数据包丢失数、抖动和往返延迟。这里存在不同种类的RTCP包:1)SR(发送者报告),2)RR(接收者报告),3)SDES(资源描述),4)BYE,和5)APP。根据这些报告,通话端点之间就可以使用如SIP等信令协议,对通话细节信息进行重新协商。
Kuzmanovic等人调查了低速率威胁,这种威胁可以只在特定的时间间隔插入很少的数据包,就使TCP拥塞控制失效,同时这种威胁还不会被大部分的拒绝服务检测机制检测到[7]。在他们的方法中,由额外插入的数据包而引起的数据包突发,迫使TCP继续后退,从而导致性能大大降低。Rosenberg调查了一种针对RTP的潜在的拒绝服务威胁,这种威胁可以使RTP数据包洪泛[11]。他还提出了一个可能的预防机制,即是使用互动式连接建立(ICE)技术。
IV. RTP的脆弱性
我们的系统由三个主要部分组成:1)控制器,2)数据包捕获引擎(或线程),3)数据包注入引擎(如图2所示)。我们的系统实现在Debian Linux系统下,内核版本是2.6.2,并且采用了易于满足我们系统的实时处理需求的C语言。数据捕获和注入引擎都以POSIX线程[12]的方式运行,它们之间通过控制器来通信。我们将在本章讨论这些引擎的细节。
图2. 数据包捕获、注入和分析系统总体结构
A. 数据包捕获引擎
要从数据线捕获数据包,我们要使用在Linux上可用的libpcap库[13]。该库使网卡运行于混杂模式,可以抓获流经本地计算机的所有数据包。具体来说,我们调用pcap_open_live这个系统调用,方法是:pcap_descriptor = pcap_open_live(“eth1”, BUFSIZ, PERMISC, 1000, errorbuf )。这个系统调用打开以太网设备eth1和一个大小为BUFSIZ的缓冲区,设置超时时间为1000毫秒并且设置错误流errorbuf。这个调用返回一个在libpcap中具有唯一性的、标识一个连接的标识符pcap_descriptor。
除了捕获数据包,libpcap还提供数据包过滤功能,使用tcpdump[14]命令行过滤格式。由于RTP在底层(以太网,IP和UDP)实现上没有独特性,所以,我们需要制定一个RTP指纹识
别方法来识别RTP数据包。我们的观察显示,RTP包的长度为180-220字节,封装在UDP包中,并且不被广播。这些限制被表现为一个字符串,并且被传送给数据包过滤编译器,方法是pcap_compile(pcap_descriptor, kern_filter, filter_string, 0, ip)。这里的filter_string是之前所提的限制,kern_filter是一个包含过滤结果的数据结构。我们可以设置当前连接中的libpcap过滤器,方法是:pcap_setfilter(pcap_descriptor, kern_filter)。虽然不同音频编码机制可能有不同的数据包大小和时间间隔,但是RTP头部的大小是不会有明显改变的,并且数据包捕获引擎会将自己同步于那些数据包的时间间隔。
一旦过滤器已经准备好,剩下的步骤就是设置我们的连接为非阻塞模式,使得系统可以在没有数据包可以捕获时立刻返回。该功能非常有用,因为它使得将控制更多的用于花费时间的地方。我们调用pcap_setnonblock(pcap_descriptor, 1, errorbuf)来设置lippcap连接为非阻塞模式。
在lipcap连接完全初始化以后,数据包捕获引擎就可以开始捕获数据包的主循环了。每一次循环中要判断是否应该结束本捕获线程。如果该线程应该结束,数据包捕获线程会通知数据包注入线程,并且加入主线程。否则,它将尝试使用libpcap抓取数据包。如果在抓取中失败,则此时可能有也可能没有正在进行的通话。如果此时确有正在进行的通话,该线程应当重启循环,等待可能在短时间后到带来的数据包;否则,该线程进入睡眠。在成功的捕获数据包后,线程处理该数据包,如果需要,还会复制该包到它自己的内存空间中。当线程启动时,它也会开启一个200毫秒定时报警器,并且每当有新的数据包到达时就复位。当报警器到时时,一个信号就会发送到捕获线程,通知它在过去的200毫秒内都没有数据包到达。这反过来指示注入线程进入睡眠,并且重启捕获线程的主循环。捕获线程装有信号处理程序来处理从警报器发来的信号。当RTP流不再可用时(通话停止),这个处理程序会通知捕获线程,设置通话完成时间为当前时间,并且发信号使注入线程进入睡眠。
B. 数据包注入引擎
如前所述,数据包注入线程主要是由数据包捕获引擎通过信号来控制的。数据包捕获线程发出SIGALRM信号给数据包注入引擎,使伪造的数据包同步注入到底层RTP流。一个挑战是,很难确定哪一个线程去接收信号,因为无论是捕获还是注入线程,都是用同一个信号。为了避免这种不确定性,我们使用nanosleep()系统调用,它使用处理器时钟来安排时间,而不是使用usleep()。注入和捕获线程都是POSIX线程,可以相互访问内存空间。当注入线程接收到信号时,
它将从捕获线程的内存空间中拷贝最近的数据包到自己的内存空间。
虽然我们可以通过libpcap注入数据包,但我们选择使用原始的Unix套接字,以避免使用libpcap可能产生的额外开销。通过原始的Unix套接字,我们可以在用户空间构造一个以太网帧、一个IP数据包和一个UDP数据包。要创建一个原始套接字,需要调用socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))。PF_PACKET允许用户在用户空间创建一个协议栈,SOCK_RAW参数确保操作系统不对数据包进行修改,ETH_P_ALL指定该套接字允许应用任何高层协议。然后,我们把创建好的原始套接字绑定到一个以太网设备上,方法如下:
int rawsock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
struct sockaddr_ll sll;
struct ifreq ifr;
bzero(&sll, szieof(sll));
bzero(&ifr, szieof(ifr));
strncpy((char*)ifr.ifr_name, device, IFNAMSIZ);
if ((ioctl(rawsock, SIOCGIFINDEX, &ifr)) == -1) {
printf("Error getting Interface index!\n");
exit(-1);
}
sll.sll_family = AF_PACKET;
sll.sll_ifindex = ifr.ifr_ifindex;
sll.sll_protocol = htons(protocol);
if ((bind(rawsock,(struct sockaddr *)&sll,sizeof(sll)))==-1)
{
perror("Error binding raw socket to interface\n");
exit(-1);
}
为了绑定套接字到接口,我们首先创建和填充一个套接字地址结构(structaddr_ll)和一个接口请求结构(ifreq)。注意到,我们使用一个I/O控制的驱动函数(ioctl),来确定网络接口设备的索引。当我们用索引和套接字类型填充好sockaddr_ll结构后,我们就可以绑定原始套接字到网络驱动上。数据包注入引擎此时就可以通过该原始套接字和网络设备发送数据包,方法如下:
if ((sent = write(rawsock, pkt, pkt_len)) != pkt_len) {
printf("%d bytes of packet of length %d sent\n",
sent, pkt_len);
return 0;
}
注入线程修改一个捕获到的数据包,特别是修改RTP头部及有效载荷,并创建一个伪造的数据包。为了使接收端接收这个伪造包,该包的头部需要有正确的序列号和时间戳。由于RTP创建的是可预计的序列号和时间戳,因此我们能够很容易的设置伪造数据包的这两个参数。注入线程只是改变了有效载荷的内容,使其变为我们想要的虚假数据。在我们的实验中,我们覆盖了原始的有效载荷。
对于数据包创建和修改来说有一个问题,就是注入率。创建一个合法的伪造数据包所需要的时间,比发送数据包的时间窗口(20毫秒)要少。如果不对注入率进行控制,那么数据包注入器将会造成一些拒绝服务威胁,而不是更加智能化的伪造数据包威胁。为此,我们再次使用了nanosleep(),并且限制了数据包注入率。困难之一在于nanosleep()存在的误差。具体来说,睡眠后的数据包注入
有0-10毫秒的延迟。为了减轻这个误差,我们需要在发送了一轮测试数据包(约15包)后,重新计算序列号和时间戳。此外,数据包捕获引擎睡眠时间要延长(约500毫秒)。重新同步注入线程到底层的RTP流,序列号和时间戳都增加了,看上去好像有24个数据包,而实际上有25个数据包已经发送了(500毫秒/20毫秒每包=25包)。注入线程每800毫秒丢失一个时间量子(15包*20毫秒每包=300毫秒+500毫秒睡眠=800毫秒)这是一个进程时间安排的负面影响,但我们可以很容易地避免这个错误。
V. 性能评价
A. 实验设置
我们具体实现了上述脆弱性测试方案,并且在一个局域网(LAN)环境下进行了实验。我们的LAN连接到几台Linux主机和一台作为VoIP通话路由器的Asterisk服务器。服务器维护一个通话清单,并在收到请求时转发呼叫到正确的接收者。我们的测试中,这个服务器使用预先录音的方式重放呼叫请求。此外,两台PC都安装了软电话和与Asterisk服务器协同工作、收集在线通话的SIPp。这些被记录的通话被用于测试基准。图3说明了本实验LAN的配置。
图3. 本实验LAN配置
一旦这些通话被以正确的格式记录,我们在一台单独的Linux主机上,通过本地套接字连接,模拟现实世界的VoIP网络。当测试程序意识到一个实际通话发生了,它开始注入数据包到RTP流。测试程序初始每20毫秒发送10个数据包,然后停500毫秒,然后再继续发送10个包。这个过程一直重复到通话结束。所有的预先录制好的Asterisk服务器和任一软电话间的通话,都要持续大概8秒。这些通话使用Wireshark[9]来记录,然后通过一台主机上的本地套接字,使用SIPp进行重放。
为了评估该漏洞测试方案的有效性,我们分析通话过程中的声音剪辑。该测试的基准声音片段是内容为“one two three four five six seven eight nine ten hello world”的8秒女声。当用户拨打1,上述声音片段就会被播放,通话将会在Asterisk服务器上结束。为了再现这段声音,通话包含的所有RTP包都要被Wireshark[9]记录为.au类型[15]的文件。然后丢弃捕获到的其他数据包。我们可以使用音频处理软件Audacity[16]修改和保存该音频文件。当获取的音频是被压缩的mp3格式时,我们才修改该音频剪辑。
B. 结果
图4(a)展示了正常的音频剪辑。图中的气泡代表声音,第一个是“one”,第二个是“two”,等等。最后一个气泡是“hello world”。如图所示,声音气泡中并没有重大的中断或尖峰,所有的声音都相当清晰可辨。对比正常的图,图4(b)展示的是脆弱性威胁测试时的音频剪辑。可见图中最开始的部分,音频被打断了,并且由于被干扰,气泡中存在
许多尖峰。这一点清楚的表明,有一些非法的音频数据包被注入到正常的音频中。
(a) 正常语音波
(b) 被威胁语音波
图4. 未使用抖动缓冲模拟的声音剪辑结果分析
另一个有趣的结果是,声音片段的长度确实超过了原来的。这是因为我们的测试程序产生的虚假RTP被简单的插入到了RTP抖动缓冲中,而不是替换掉正确的数据包。因此,所有的复本都存在于缓冲器中,并且被传送到主机进行音频处理。我们发现,Wireshark的记录方式也有助于这一现象的发生。Wireshark将RTP流存储为一个音频文件的时候,并没有使用抖动缓冲模拟机制,而是简单地将所有的音频比特一起按到达顺序存起来。所以,这个结果不能作为典型代表。
(a) 部分使用抖动缓冲模拟的被威胁语音波
(b) 完全使用抖动缓冲模拟的被威胁语音波
图5. 使用抖动缓冲模拟的声音剪辑结果分析
为了解决这个问题,我们修改了音频剪辑,RTP流中先到的数据包被保存,后到的被丢弃。这使得Wireshark创建的音频文件与人听到的是相同的。处理后的音频剪辑如图5所示。
第一个图显示了部分使用抖动缓冲模拟的被干扰话音;第二个图显示的是完全使用抖动缓冲模拟的被干扰话音。我们可以清楚的看到第一幅图中,有许多被截断的声音。这些截断线就是注入由测试程序生成的RTP数据包的地方,那些数据包的载荷是空的,代表静音。在第二个图中,虽然没有像第一幅一样多的截断线,但我们可以观察到许多扭曲的声音气泡。在该实验中。听者可以实际上听出来被干扰的声音效果(话音中有停顿),并且难以理解对方在说的内容。
这种类型的威胁会在伪造的数据包被插入RTP流时,引入另一个复杂性。鉴于底层RTP流到达的速率是20毫秒一包,那么每700毫秒会有附加的10个数据包。也就是说,一分钟的通话过程中,将有3000个正常的RTP数据包和857个伪造的数据包。这个附加数据包引起的开销大约是28.5%。我们认为该开销是可以接受的。
VI. 结论和下一步工作
我们已经证明,使用RTP的VoIP流容易受到简单的数据包注入威胁。该威胁只是在预期时间向RTP流中插入少量的伪造数据包。接收者遵从RTP协议,接收这些伪造的数据包,而丢弃原始的数据包。虽然这个威胁可能会对通话造成破坏,但是它并不容易被识别,原因有以下三个:首先,拒绝服务防范机制对其无效,因为只有少量的数据包被插入;其次,ISP不能对数据包进行检查,尤其不能检查有效载荷部分;第三,如果只看头部信息而不看内容的话,通话是完全合法的。实验的结果验证了我们的假说。我们可以扩展延伸我们的
研究,通过搜集更多的数据资料,帮助理解以不同的方式注入数据包时,检测该威胁的困难程度。我们也可以跟全面地调查和研究,拒绝服务威胁检测机制对我们的威胁所做的反应。
参考文献
略