故障注入详解(一)

admin 2025年2月21日22:05:48评论25 views字数 7911阅读26分22秒阅读模式
    在对物联网设备进行物理安全评估时,其中一个目标是利用调试接口或可访问的芯片来研究设备的工作原理。理想的情况是从设备中提取完整的文件系统,以找到获得设备 root 访问权限的方法。之后,就更容易检查正在运行哪些服务,如有需要可对其进行调试,最终实现对目标设备的控制。通常会遇到调试接口上的保护措施,这些措施禁止访问其全部功能,或者在启动链上设置保护,禁止对其进行任何修改。故障注入是尝试绕过此类保护的一种方法。在本篇文章中,我们将深入探讨电压故障注入了解其工作原理。

引言

故障注入是一种用于评估设备安全性的技术,其原理是故意向硬件组件引入故障或错误,以绕过诸如调试保护或密码认证等安全功能。这些注入操作应在特定时刻进行,并持续受控的时间,以便破坏内存或跳过指令。实现故障注入的方法包括:

  • 硬件设备
  • 软件方法
  • 结合硬件和软件的混合方法

这种攻击方式在支付卡或内容保护等敏感领域得到了广泛应用,并且在近几年变得易于实施。

让我们考虑以下场景:密码检查是通过一个返回 0 或 1 的 if 语句来实现的。在第一种情况下,用户被拒绝访问;而当返回 1 时,用户则可以登录。以下是故障注入时可能出现的情况:

  • 可以注入特定值 1,从而绕过身份验证。
  • 产生随机字节(这种情况更有可能发生)。根据实现方式的不同,一些保护措施仍可能被绕过。
  • 跳过指令,例如 if 语句本身。

一些寄存器会被破坏,设备可能会以非标准状态运行。过去,故障注入攻击曾被用来突破安全措施,例如 Xbox360 的复位故障攻击、STM32 的闪存读取、MediaTek MT8163V3 的安全启动绕过,或者最近对 DJI 无人机的成功故障注入攻击。

时钟故障注入

时钟故障注入是一种针对配备外部时钟的设备的攻击方式。通过在正常时钟脉冲周期之间注入时钟故障脉冲,且在极其恰当的时刻进行,可以在算术逻辑单元(ALU)使用的原始时钟信号中,两个合法的时钟边沿之间移除或插入一个边沿。当这种情况发生时,可能会触发意外行为,例如处理器跳过一条指令,可能正是负责安全检查的指令。

ChipWhisperer 代码库中的故障注入课程给出了一个关于对 Atmel AVRM ATMega 328P进行此类攻击的非常清晰的示例:

该系统并非从 FLASH 中加载每条指令并执行整个过程,而是采用流水线来加快执行速度。这意味着一条指令正在被解码,而下一条指令正在被获取,如下图所示:

故障注入详解(一)

但如果对时钟进行修改,可能会出现系统没有足够时间来实际执行指令的情况。考虑以下情况,其中“Execute #1”实际上被跳过了。在系统实际执行该指令之前,另一个时钟边沿就到来了,导致微控制器开始执行下一条指令。

故障注入详解(一)
然而,如果您的目标设备使用的是内部时钟信号,这种方法通常并不适用。

光学故障注入

光学故障注入领域实际上包含了多种不同的技术,从使用X射线的昂贵设备来定位闪存单元上的单个比特,到使用相机闪光灯的廉价方法,通过引发随机故障来恢复AES密钥。

在硬件安全评估中,用于安全评估的主要方法是激光故障注入(LFI)。使用脉冲激光来物理操纵和扭曲数据,从而在运行中的设备中引发故障。

主要目标是找到芯片上故障注入成功率最高的区域。在进行此类攻击时,需要考虑X、Y和Z坐标,以确定最佳的注入空间位置。

这种技术的标准设备包括:

  • 控制设备
  • 电动平台
  • 激光源
  • 物镜

然而,这种技术也有一些副作用,例如组装设备的成本较高,以及需要将芯片从电路板上移除,这可能会损坏设备。这是由于故障注入是从芯片的背面进行的,尽管已经开发出了从正面进行注入的新技术。

总的来说,光学故障注入技术提供了高精度和可重复性,但成本较高。

电磁故障注入

电磁(EM)辐射对模拟和数字模块均有影响,尽管它们的物理特性有所不同。为了改变数字模块(这些模块是时钟驱动的),可以利用短暂的电磁脉冲在特定的时钟周期内注入故障,这需要借助高压脉冲发生器和带有铁氧体磁芯的线圈(注入探针)来实现。这种注入方法在成本和精确度之间取得了良好的平衡。

与激光故障注入不同,电磁故障注入无需将芯片从电路板上拆焊,且需仅在两个维度上寻找合适的空间位置。此外,与时钟或电压故障注入不同,电磁故障注入无需在芯片上焊接或连接导线。

下图展示了一款PicoEMP设备正在对树莓派进行攻击:

故障注入详解(一)

    电磁故障注入是一种对攻击者而言较为实用的技术。它具备良好的精准度,其成本较电压或时钟故障注入略高,但远低于激光故障注入。此外,该技术无需采用拆焊或其他侵入性方法,因此能够保持系统级芯片(SoC)的完整性。针对此类攻击,可采用的方法是运行一个无限循环,同时利用网格扫描CPU表面。首先对一个点进行多次测试,随后将探针移动到另一个点(通常相距约1毫米),依此类推,以完成对整个SoC的扫描。此外,还可以使用不同尺寸的探针。

电磁故障注入技术的一个应用实例是Riscure的相关工作,通过故障注入干扰ESP32 CPU,从而在启动时绕过安全启动摘要验证。

电压故障注入

电压故障注入的目标是精确控制微控制器的电源,且控制时间足够短。如果控制时间过长,将导致芯片复位;而正确地进行控制,则可使其进入一种未定义状态,从而引发不确定的行为。这意味着需要找到合适的故障脉冲宽度以及恰当的触发时机。

故障注入详解(一)
    这是发生电压故障注入时示波器的屏幕截图。我们可以看到,电压首先被拉低至接近地电位(GND),持续一段时间后,又迅速上升至接近14伏特,随后恢复至原始状态。通过控制电压的变化幅度及其持续时间,可以导致指令出现异常行为,例如执行额外的跳转操作,或者改变某个整数值。

    这种方法面临的一个主要问题是,存在一些旨在维持电压稳定的组件,例如去耦电容器,它们可能会干扰故障注入的效果,因此可能需要将这些电容器拆焊。此外,将注入装置尽可能靠近目标设备进行焊接,也有助于提高故障注入的效果。

Creating a simple glitch

为了详细介绍的不同目标上进行电压故障注入,我们使用了 ChipWhisperer Lite。

短路是通过一个 MOSFET 实现的,MOSFET 是一种晶体管,其主要功能是控制导电性,即根据其栅极所施加的电压量来控制电流在其源极和漏极之间流动的程度。它可以被建模为一个简单的受电控的开关。在此,我们将它并联在电源轨道上,以便短暂地将 VCC 与地短路。

首先,我们进行一个没有任何限制条件的简单故障注入。为此,我们使用了一块 Arduino Uno 开发板,并将系统级芯片(SoC)放置在面包板上。这样,我们可以非常方便地控制微控制器的地线,且不会受到任何干扰电容的影响。在其他情况下,为了确保我们的故障注入尽可能有效,我们可能需要移除连接到 Vcore 和复位线路上的去耦电容器。这可以通过使用标准的电烙铁和一些耐心来完成。

故障注入详解(一)

    需要重新连接的引脚至少包括PIN 2、3(RX / TX)、7(VCC)、9和10(Clock)。这些引脚负责基本的电源供应和操作以及与微控制器的串行通信。如果需要将代码上传到Arduino,还需要连接Pin 1(Reset)。使用示波器来检查故障是否发生,其探头连接到Pin 7。下面是一个简单的示意图,以便更好地理解这些连接关系:

故障注入详解(一)

    使用Arduino SDK编写并上传到Uno板上的是一个相当简单的C++脚本。

void setup() {  Serial.begin(115200);}void loop() {int ctr0for(int i=0; i<2; i++){for(int j=0; j<2; j++){delay(100);      Serial.print("i: ");      Serial.print(i);      Serial.print(" j:");      Serial.print(j);      Serial.print(" ctr:");      Serial.println(ctr);      ctr++;    }  }}
    这只是一个带有计数器的双重for循环,计数器的值从0增加到3。变量i和j的值从0增加到1。以下是用于对其进行故障注入的Python脚本:
import chipwhisperer as cwcw.set_all_log_levels(cw.logging.CRITICAL)SCOPETYPE = 'OPENADC'PLATFORM = 'CWLITEXMEGA'scope = cw.scope()# We adjust the clock to fit with the ATMega 328p frequency.scope.clock.clkgen_freq = 8E6 # Set clock to internal chipwhisperer clockscope.glitch.clk_src = "clkgen"#"enable_only" - insert a glitch for an entire clock cycle based on the clock of the CW (here at 8MHz so 0,125 micro seconds)scope.glitch.output = "enable_only"# Enable Low power and High power transistors.scope.io.glitch_lp = Truescope.io.glitch_hp = True# LP provides a faster response, so sharper glitches. HP can provide better results for targets that have strong power supplies or decoupling capacitors that ca not be removed.scope.io.vglitch_reset() #it simply sets scope.io.glitch_hp/lp to False, waits delay seconds, then returns to their original settings. In short, reset the glitching module.# How many times the glitch is repeatedscope.glitch.repeat = 1# Send the glitchscope.glitch.manual_trigger()scope.dis()

    发送了一个持续整个时钟周期的故障信号,并将时钟频率调整为与ATMega 328p的时钟频率一致。其中,“repeat”参数用于控制故障重复的次数。起初,我们将该参数值设置得非常低,随后逐步增加(我们采用每次增加50次重复,但为了降低损坏设备的风险,也可以使用更小的增量值),以便观察Arduino在不同设置下的行为表现。

故障注入详解(一)

单片机的重复次数较多。

在示波器上,我们可以看到故障信号被发送出去:

故障注入详解(一)

我们尝试在故障信号的起始点和结束点放置光标,并将时钟频率设置为8MHz,因此一个时钟周期为1/(8×10⁶)=1.25×10⁻⁷秒。故障信号重复了500次,所以1.25×10⁻⁷×500=0.0000625秒=62.5微秒。测量结果显示为63.6微秒,因此考虑到测量误差,一切工作正常。

如您所见,由于故障信号过于强烈,Arduino Uno发生了重启。在故障注入中,正常行为与设备重启之间的界限非常微妙,在此区间内会发生一些不寻常的事情。经过一番尝试后,我们发现当故障信号重复380次时,设备开始重启。因此,我们将故障信号的重复次数从1次增加到380次,并检查Arduino的行为表现:

for i in range(380):    scope.io.vglitch_reset(0.5)    scope.glitch.repeat = i    scope.glitch.manual_trigger()

我们记录了 minicom 会话并启动了脚本。接下来,我们来分析其内容:

i0 j:0 ctr:0i: 0 j:1 ctr:1i: 1 j:0 ctr:2i: 1 j:1 ctr:3i: 0 j:0 ctr:0i: 0 j:1 ctr:1i: 1 j:0 ctr:2i: 1 j:1 ctr:3i: 0 j:0 ctr:0i: 0 j:1 ctr:1i: 1 j:0 ctr:2i: 0 j:0 ctr:0i: 0 j:1 ctr:1i: 0 j:0 ctr:0i: 0 j:1 ctr:1i: 0 j:0 ctr:0i: 0 j:1 ctr:1i: 0 j:0 ctr:0i: 0 j:1 ctr:1i: 0 j:0 ctr:0i: 0 j:1 ctr:1

如您所见,我们成功地跳过了一些指令。起初,计数器从0跳到3,然后跳到2,最后不再超过1。从示波器上可以看到,在脚本执行过程中,故障信号的重复次数越来越多:

故障注入详解(一)

示波器上显示了一个较大的故障信号。

如果我们要修改某些值,可以清楚地看到,此处的故障信号过于强烈,因为我们跳过了一些循环迭代。我们修改了脚本,以发送更窄的故障信号并减少重复次数。

import chipwhisperer as cw[...]scope.clock.clkgen_freq =192E6 # Maximum frequency of the internal clock of the CW[...]insert a glitch for a portionof a clock cyclescope.glitch.output = "glitch_only" [...]gc = cw.GlitchController(groups=["success", "reset", "normal"], parameters=["width", "repeat"])gc.set_range("width", 035)gc.set_range("repeat", 135)# The steps could be reduced to be more precisegc.set_global_step(1)for glitch_setting in gc.glitch_values():    scope.glitch.width = glitch_setting[0]    scope.glitch.repeat = glitch_setting[1]    print(f"{scope.glitch.width} {scope.glitch.repeat}")    scope.glitch.manual_trigger()    scope.io.vglitch_reset()scope.dis()
故障注入详解(一)

我们在脚本执行过程中成功地修改了某些值!

i0 j:-16777215 ctr:-16777215[…]i: -8023668 j:1 ctr:-805831672i: 1 j:0 ctr:-805831671i: 1 j:1 ctr:-805831670[...]

Glitching a logging prompt

    我们之前的故障注入并不十分精确,因为我们只是在随机时间发送故障信号,并等待异常行为的出现。在接下来的示例中,我们在Arduino Uno上编写了一个登录提示脚本。
String PASSWORD = "passw";boolcheckPass(String buffer) {for (int i = 0; i < PASSWORD.length(); i++) {if (buffer[i] != PASSWORD[i]) {returnfalse;    }  }returntrue;}voidsetup() {  Serial.begin(115200);  Serial.println("Password:");}voidloop() {if (Serial.available() > 0) {char pass[PASSWORD.length()];    Serial.readBytesUntil('n', pass, PASSWORD.length());bool correct = checkPass(pass);if (correct) {      Serial.println("Logged in!");      Serial.flush();       exit(0);    } else {      Serial.println("Incorrect password.");      Serial.println("Password:");    }  }}
我们还增强了攻击脚本,以检测在故障注入后是否需要对Uno进行复位。
import chipwhisperer as cwimport timeimport serialimport oscw.set_all_log_levels(cw.logging.CRITICAL)SCOPETYPE = 'OPENADC'PLATFORM = 'CWLITEXMEGA'scope = cw.scope()scope.clock.clkgen_freq = 192E6scope.glitch.clk_src = "clkgen"scope.glitch.output = "glitch_only"scope.io.glitch_lp = Truescope.io.glitch_hp = Truegc = cw.GlitchController(groups=["success""reset""normal"], parameters=["width""repeat"]) gc.set_global_step(0.4)gc.set_range("width"145)gc.set_range("repeat"150)gc.set_step("repeat"1)for glitch_setting in gc.glitch_values():# Try to connect to the arduino uno using the serial connection:    try:        with serial.Serial("/dev/ttyACM1", 115200, timeout=1) as ser:            scope.glitch.width = glitch_setting[0]            scope.glitch.repeat = glitch_setting[1]            print(f"Width: {scope.glitch.width}, repeat: {scope.glitch.repeat}")# Send the glitch and a wrong password            scope.glitch.manual_trigger()            ser.write(b'tatat')            scope.io.vglitch_reset()# If the serial connection breaks, use uhubctl to poweroff / poweron the usb port on the USB hub where the Arduino Uno is plugged    except Exception as e:        os.system('/usr/sbin/uhubctl -S -p 2 -a cycle > /dev/null 2>&1')        time.sleep(5)        passscope.dis()
故障注入详解(一)
在这里,我们不仅绕过了登录提示,而且在大约30分钟后成功恢复了密码!这可能是因为在调用println函数时出现了异常行为。
故障注入详解(一)
故障注入详解(一)
故障注入详解(一)

原文始发于微信公众号(安全脉脉):故障注入详解(一)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月21日22:05:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   故障注入详解(一)https://cn-sec.com/archives/3768074.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息