In my spare time I'm working on a quadcopter as hobby. I started by the basics, a remote controlled quadcopter easy to flight. A magnometer and barometer helped me to avoid crash the drone at me and I could go forward to the next stage.
See what the quadcopter sees
An FPV camera is coming but is not here now. So, what can I do?
Buy a cheap sport camera on the local commerce
This camera above is frequently rebranded by the local seller. It is widely known as SJ400 [1] but here in Chile is known as Ultra [2].
Video stream
According to the included manual, wifi support works in companion to GoPlus Cam App (iOS/Android), but guess what, I don't want to use my cellphone to see it, I want my computer/notebook to do that.
So, let's see the cam network
nmap it
I connected to an AP WEP protected with ESSID "BOLD3 " (BOLD3 is the model). The IP address assigned was one in the 192.168.25.0/24 subnet:
wlp2s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.25.101 netmask 255.255.255.0 broadcast 192.168.25.255
I suspect 192.168.25.1 should be the cam. Let's try:
Command:
nmap 192.168.25.1
Output:
Starting Nmap 7.60 ( https://nmap.org ) at 2018-08-18 15:31 -03
Nmap scan report for _gateway (192.168.25.1)
Host is up (0.043s latency).
Not shown: 997 closed ports
PORT STATE SERVICE
80/tcp open http
8080/tcp open http-proxy
8081/tcp open blackice-icecap
Is the stream in port 80 ?
Command:
curl -v http://192.168.25.1/
Output:
* Trying 192.168.25.1...
* TCP_NODELAY set
* Connected to 192.168.25.1 (192.168.25.1) port 80 (#0)
> GET / HTTP/1.1
> Host: 192.168.25.1
> User-Agent: curl/7.58.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: lwIP/1.3.1 (http://savannah.nongnu.org/projects/lwip)
< Content-type: text/html
At least I know the cam is powered by open source software :) but what it is ?
My browser shows:
In this point I close my eyes and I let the flow going throught every button there.
Interesting! At first I got a run status page after clicking some button:
run_status.shtml:
But there is even more:
wireless_config.shtml
net_config.shtml
system_config.shtml
Maybe if I would have guessed the user/password at the beginnig, something would be showed on system config page.
What about another ports 8080 and 8081 ?
Command:
curl -v http://192.168.25.1:8080/
Output:
* Trying 192.168.25.1...
* TCP_NODELAY set
* Connected to 192.168.25.1 (192.168.25.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: 192.168.25.1:8080
> User-Agent: curl/7.58.0
> Accept: */*
>
* Empty reply from server
* Connection #0 to host 192.168.25.1 left intact
8080 is discarded because of the empty response
Command:
curl -v http://192.168.25.1:8081/
Output:
* Trying 192.168.25.1...
* TCP_NODELAY set
* Connected to 192.168.25.1 (192.168.25.1) port 8081 (#0)
> GET / HTTP/1.1
> Host: 192.168.25.1:8081
> User-Agent: curl/7.58.0
> Accept: */*
>
Connection is stuck by a long time. This port is discarded too.
I did not get my precious stream so I should try another approach.
The App
What I know is the app works.
Thereby I have two ways for getting my precious stream source:
- Sniff the communication between the app and the cam
- Decompile the Android App and find URLs (very easy)
Decompiling an Android App
The first step is download the "GoPlus Cam" APK. After googling I found one here:
https://www.apkmonk.com/app/generalplus.com.GPCamDemo/
The second step is to get a tool for doing the job:
https://ibotpeaches.github.io/Apktool/
Apktool is able to convert Dalvik dex code into a human readable one called smali. dex and smali are in the same level, they just differ in the representation.
The third step is run the tool:
Command:
java -jar apktool_2.3.3.jar d generalplus.com.GPCamDemo_2017-12-18.apk
Output:
I: Using Apktool 2.3.3 on generalplus.com.GPCamDemo_2017-12-18.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
S: WARNING: Could not write to (/home/felipe/.local/share/apktool/framework), using /tmp instead...
S: Please be aware this is a volatile directory and frameworks could go missing, please utilize --frame-path if the default storage directory is unavailable
I: Loading resource table from file: /tmp/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
Here it is!
Command:
generalplus.com.GPCamDemo_2017-12-18$ ls
Output:
AndroidManifest.xml apktool.yml assets lib original res smali
Finding URLs on decoded resources
The lazy way for lazy people like me is let the machine do the hard work:
Command:
generalplus.com.GPCamDemo_2017-12-18$ find . -name *.smali -exec egrep http {} \;
Output:
const-string v10, "http://%s:8080/?action=stream"
const-string v1, "http://%s:8080/?action=stream"
const-string v2, "http://%s:8080/?action=stream"
.field public static final STREAMING_URL:Ljava/lang/String; = "http://%s:8080/?action=stream"
Finally!!
Do you remember 8080 port gives me an empty response ? just a query param action was missing
Command:
ffprobe http://192.168.25.1:8080/?action=stream
Output:
ffprobe version 3.4.2-2 Copyright (c) 2007-2018 the FFmpeg developers
built with gcc 7 (Ubuntu 7.3.0-16ubuntu2)
Input #0, mpjpeg, from 'http://192.168.25.1:8080/?action=stream':
Duration: N/A, bitrate: N/A
Stream #0:0: Video: mjpeg, yuvj422p(pc, bt470bg/unknown/unknown), 480x272, 25 tbr, 25 tbn, 25 tbc
The resolution is very disappointing and there is no audio stream, but it's something.
Camera mounting
The weight in a quadcopter is important and every gram counts so I stripped down parts that are not useful on the flight:
The quadcopter looks like this:
Conclusion
Beyond the specific steps made here, what have value for me in this tutorial is the behind reasoning, for example, take the easier way and let the most hard work to the machine, it's part of it.