迷你无人机 FPV WIFI CAMERA图传破解,mini drone WIFI camera

本文介绍了一种利用Wireshark抓包并逆向分析无人机图像模块的通信协议的方法。通过分析得知,控制指令通过UDP 8090端口发送,而视频流则通过UDP 8080端口以MJPEG格式传输。作者还分享了如何用Python脚本接收和解码这些视频流。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Author: Aric Wang

72ce8b6544b847f1bb2bdc0390a3daaf.jpg

 

0f72e452efb14120ab219ee279e10db8.png

 

在闲鱼上买了个迷你无人机的图像模块,介绍说要用wificam app可以查看实时视频和控制。因为我想用电脑显示,或者开发自己的APP来控制和显示实时视频,于是逆向分析了一下在此记录。

首先抓包分析: 准备一台有双无线网卡的电脑和安装好wireshark,电脑一张无线网卡设置好AP,另一张无线网卡连FPV WIFI  相机。手机安装好wificam app(apk),并且该手机连到电脑建的热点。

让电脑开起路由功能。此时打开wificam app,就可以看到视频了。   用于wireshark抓一个完整的包,包括,开始,视频传输,结束的。

通过分析抓包可知:  UDP 8090用于控制, UDP 8080于来传输视频,probe出来,视频格式为MJPEG的VGA流。 使用 UDP分片传输JPEG思路挺不错的,效果了不错,毕竟是商用方案。

d344cb0784fc4b1d93f5fd313c5480ee.png

向UDP 8090,发 AA 80 80 00 80 00 80 55 命令, 再向UPD 8080发 42 76 就开始实时视频。

5f9095db99624d3baa73bf838f4cd1f9.png

 结束也是类似,先向UDP 8090发命令,再向UDP 8080发 42 77就可以停止视频。

MJPEG通常使用TCP稳定的传输, 该项目的创意之处就是UDP分片传输,一帧一帧的传。

4179906079e7431fb48fbbfcb0f721bf.png

抓包可以清析的看到一帧一帧的图片,最后一个包变小了,应该是传输剩余数据。

UDP分片也有引导头,包括JPEG的续号sequence number,1-255循环,只用了一个字节表示。头第二位用来表示该帧的最后一个分片 。

1dc232afa29d44a491c03f1807ec2b74.png

02就是第二张图片的意思,01就是分片的最后一片。

下图可以看到,非最后分片, 第二位为0

69df4bd860d7421981bdd3a8de4b40b6.png

 UDP有大可能丢包,所以尾部也要处理一下,这里我猜了很多,以为是bcc crc8 crc-sum等等,原来只是little endian 的unsigned short。 那这就简单了!

 

 写个向标准输出,输出视频流的脚本:

udp_decode_std.py

 

#!/usr/bin/env python
import os,sys,time,socket
import select
import numpy as np
import struct, queue, _thread

UDP_PKT_SIZE= 2000

out_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
out_sock.bind(('0.0.0.0', 6666))

out_sock.sendto(b'\xaa\x80\x80\x00\x80\x00\x80\x55',("192.168.4.153", 8090))
out_sock.sendto(b'\x42\x76',("192.168.4.153", 8080))

      
def isValidJPEG():
    t_len = len(s_buf)
    if s_buf[0] == 0xff and s_buf[1] == 0xd8 and s_buf[t_len-1] == 0xd9 and s_buf[t_len-2] == 0xff:
        return True
    else:
        return False
  
def chk_tail(jpg_len, b_jpg_len):
    unpack_len = struct.unpack("<H", b_jpg_len)
    try:
        tail_jpeg_len = int(unpack_len[0])
        if tail_jpeg_len != jpg_len:
            print("Check tail failed.",tail_jpeg_len, jpg_len)
            return False
        else:
            return True
    except:
        return False
    pass
    
s_buf = b''
sn_old = 0
b_jpg_len=b''

udp_recv_buf_q  = queue.Queue()


def decode_jpeg_proc():
    global udp_recv_buf_q
    while 1:
        if not udp_recv_buf_q.empty():
            m_item = udp_recv_buf_q.get()
            rx_buf_len = len(m_item[0])
            
            if chk_tail(rx_buf_len, m_item[1]):
                #decode_jpeg(m_item[0])
                os.write(1,m_item[0])
            else:
                print("#### CHECK FAILED ####")
                pass
        else:
            time.sleep(0.001)
        pass
        
_thread.start_new_thread(decode_jpeg_proc, (()))

while 1:
    rx_buf, addr = out_sock.recvfrom(UDP_PKT_SIZE)
    rv_len = len(rx_buf)
    sn = rx_buf[0]
    isEof = rx_buf[1]
    if sn_old != sn:
        sn_old = sn
        if len(s_buf) == 0 :
            continue
        #Finish a whole picture. Decode later.
        #print("Got a frame, try decode:",len(s_buf))
        #Decode
        #decode_jpeg(s_buf)
        if not udp_recv_buf_q.full():
            if isValidJPEG():
                udp_recv_buf_q.put((s_buf, b_jpg_len))
            else:
                print("Not valid JPEG.")
                pass
        #Clear buffer
        s_buf = b''
     
    if isEof != 1:
        s_buf=s_buf+rx_buf[8:]
        pass
    else:
        s_buf=s_buf+rx_buf[8:rv_len-5]
        #Featch jpeg length, little endian ushort 
        b_jpg_len = rx_buf[rv_len-4:rv_len-2]
        time.sleep(0.001)



然后就可以用ffplay ffmpeg gstreamer来播放了

#ffplay 播放,有时延
python3 udp_decode_std.py | ffplay -

#low latency play
python3 udp_decode_std.py | ffmpeg -threads 1 -re -fflags nobuffer  -f mjpeg -i - -pix_fmt yuv420p -f sdl -

python3 wificam_udp_cam_stop.py 

#Gstreamer也可以播放,
python3 udp_decode_std.py  | gst-launch-1.0  filesrc location=/dev/stdin ! queue ! jpegdec ! autovideosink

 

当然也可以用opencv来显示实时视频:

#!/usr/bin/env python
import os,sys,time,socket
import select
import cv2
import numpy as np
import struct, queue, _thread

UDP_PKT_SIZE= 2000

out_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
out_sock.bind(('0.0.0.0', 6666))

out_sock.sendto(b'\xaa\x80\x80\x00\x80\x00\x80\x55',("192.168.4.153", 8090))
out_sock.sendto(b'\x42\x76',("192.168.4.153", 8080))

def decode_jpeg(buf):
    try:
        img = cv2.imdecode(np.fromstring(buf, dtype=np.uint8) ,cv2.IMREAD_COLOR)
        cv2.imshow('IMG', img)
        if cv2.waitKey(1) == 27:
            exit(0)
        #img = Image.fromarray(np.fromstring(buf, dtype=np.uint8) )
        #cv2.imshow(img) 
    except Exception as e:
        print(">>>>>>>>>> imdecode error!", e)
        pass
      
def isValidJPEG():
    t_len = len(s_buf)
    if s_buf[0] == 0xff and s_buf[1] == 0xd8 and s_buf[t_len-1] == 0xd9 and s_buf[t_len-2] == 0xff:
        return True
    else:
        return False
  
def chk_tail(jpg_len, b_jpg_len):
    unpack_len = struct.unpack("<H", b_jpg_len)
    try:
        tail_jpeg_len = int(unpack_len[0])
        if tail_jpeg_len != jpg_len:
            print("Check tail failed.",tail_jpeg_len, jpg_len)
            return False
        else:
            return True
    except:
        return False
    pass
    
s_buf = b''
sn_old = 0
b_jpg_len=b''

udp_recv_buf_q  = queue.Queue()


def decode_jpeg_proc():
    global udp_recv_buf_q
    while 1:
        if not udp_recv_buf_q.empty():
            m_item = udp_recv_buf_q.get()
            rx_buf_len = len(m_item[0])
            
            if chk_tail(rx_buf_len, m_item[1]):
                decode_jpeg(m_item[0])
            else:
                print("#### CHECK FAILED ####")
                pass
        else:
            time.sleep(0.001)
        pass
        
_thread.start_new_thread(decode_jpeg_proc, (()))

while 1:
    rx_buf, addr = out_sock.recvfrom(UDP_PKT_SIZE)
    rv_len = len(rx_buf)
    sn = rx_buf[0]
    isEof = rx_buf[1]
    if sn_old != sn:
        sn_old = sn
        if len(s_buf) == 0 :
            continue
        #Finish a whole picture. Decode later.
        #print("Got a frame, try decode:",len(s_buf))
        #Decode
        #decode_jpeg(s_buf)
        if not udp_recv_buf_q.full():
            if isValidJPEG():
                udp_recv_buf_q.put((s_buf, b_jpg_len))
            else:
                print("Not valid JPEG.")
                pass
        #Clear buffer
        s_buf = b''
     
    if isEof != 1:
        s_buf=s_buf+rx_buf[8:]
        pass
    else:
        s_buf=s_buf+rx_buf[8:rv_len-5]
        #Featch jpeg length, little endian ushort 
        b_jpg_len = rx_buf[rv_len-4:rv_len-2]
        time.sleep(0.001)


 

 

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值