lurume84 / bling-viewer

Web viewer for Blink Home Security Camera
MIT License
80 stars 6 forks source link

Add support for IMMIS protocol #6

Open alexeyvasilyev opened 4 years ago

alexeyvasilyev commented 4 years ago

Blink supports two protocols for live view: rtsps and immis. Live view works via TLS connection with cipher TLS_RSA_WITH_AES_128_CBC_SHA256 only (at least in my case in Europe).

$nmap --script ssl-enum-ciphers -p 443 -Pn 3.125.19.18
Starting Nmap 7.80 ( https://nmap.org ) at 2020-07-24 11:09 EEST
Nmap scan report for ec2-3-125-19-18.eu-central-1.compute.amazonaws.com (3.125.19.18)
Host is up (0.036s latency).

PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers: 
|   TLSv1.2: 
|     ciphers: 
|       TLS_RSA_WITH_AES_128_CBC_SHA256 (rsa 2048) - A
|     compressors: 
|       NULL
|     cipher preference: indeterminate
|     cipher preference error: Too few ciphers supported
|     warnings: 
|       Forward Secrecy not supported by any cipher
|_  least strength: A

Nmap done: 1 IP address (1 host up) scanned in 1.02 seconds

I have started reverse engineering Blink's Android app. This is what I found.

At the beginning only RTSP protocol was supported. It looks like they modified RTSP protocol since Blink skips some major RTSP commands.

Blink app 1.4.2 (2016-10-18)

 public enum RTSPStrictnessLevel {
        Strict,
        SkipOPTIONSAndDESCRIBE,
        SkipOPTIONSAndDESCRIBEAndSETUP
    }
RTSPPlayer.setRTSPStrictnessLevel(RTSPPlayer.RTSPStrictnessLevel.SkipOPTIONSAndDESCRIBEAndSETUP);

Then starting from Android app Blink version 3.0 they added IMMIS protocol.

Blink app 6.0.12 (2020-07-03), libwalnut.so

RTSPStreamSource can only operate on rtsps: URLs
IMMIStreamSource can only operate on immis: URLs

I think IMMIS is a binary protocol. Typical live view URL is "immis://3.125.19.18:443/uMegdsAvghOuqc4j__IMDS_G8T1940001460JD4?client_id=12345"

These are headers of exported functions of libwalnut.so:

 walnut::IMMIStreamSource::EnableDebug
 walnut::IMMIStreamSource::processInput(unsigned char const*, unsigned long)
 walnut::IMMIStreamSource::sendKeepAlive()
 walnut::IMMIStreamSource::sendMediaInfo(unsigned char, unsigned int, std::__ndk1::vector<unsigned char, std::__
 walnut::IMMIStreamSource::processMessage()
 walnut::IMMIStreamSource::sendAuthHeader()
 walnut::IMMIStreamSource::IMMIDefaultPort
 walnut::IMMIStreamSource::closeConnection()
 walnut::IMMIStreamSource::sendAudioConfig(unsigned int)
 walnut::IMMIStreamSource::IMMISDefaultPort
 walnut::IMMIStreamSource::sendLatencyStats(unsigned int, unsigned int, unsigned short, unsigned short, unsigned
 walnut::IMMIStreamSource::sendRoundTripAck(unsigned int)
 walnut::IMMIStreamSource::submitAudioFrame(std::__ndk1::vector<unsigned char, std::__ndk1::allocator<unsigned c
 walnut::IMMIStreamSource::DeviceSupportsAEC
 walnut::IMMIStreamSource::setConnectionState(walnut::IMMIStreamSource::ConnectionState)
 walnut::IMMIStreamSource::updateLatencyStats()
 walnut::IMMIStreamSource::setIMMIMessageState(walnut::IMMIStreamSource::IMMIMessageState)
 walnut::IMMIStreamSource::validateAudioConfig(unsigned int)
 walnut::IMMIStreamSource::LatencyStatsInterval
 walnut::IMMIStreamSource::processMessagePayload()
 walnut::IMMIStreamSource::updatePresentationStats(walnut::PresentationStats const&)
 walnut::IMMIStreamSource::convertIMMIAudioFormatToCodedAudioFormat(unsigned int)
 walnut::IMMIStreamSource::processAdaptivePresentationConfigPayload()
 walnut::IMMIStreamSource::automaticEchoCancelationEnabledInIMMIAudioFormat(unsigned int)
 walnut::IMMIStreamSource::stop()
 walnut::IMMIStreamSource::start()
 walnut::IMMIStreamSource::sendStop(unsigned int)
 walnut::IMMIStreamSource::sendAudio(std::__ndk1::vector<unsigned char, std::__ndk1::allocator<unsigned char> >
 walnut::IMMIStreamSource::IMMIStreamSource(walnut::URL const&)
 walnut::IMMIStreamSource::IMMIStreamSource(walnut::URL const&)
 walnut::IMMIStreamSource::~IMMIStreamSource()
 walnut::IMMIStreamSource::~IMMIStreamSource()
 walnut::IMMIStreamSource::getBytesRead() const
 walnut::IMMIStreamSource::getBytesWritten() const
 walnut::IMMIStreamSource::getConnectionTimeInMS() const
 walnut::IMMIStreamSource::getSessionDurationInMS() const
 walnut::IMMIStreamSource::getLastReceivedByteTimeInMS() const
 walnut::IMMIStreamSource::getFirstReceivedByteTimeInMS() const
lurume84 commented 4 years ago

Thanks for the feedback! I also arrived to the same conclusions. IMMIS is a custom protocol but I suspect there is the same RTSP protocol under the hood, we just need to find out what the first characters of the communication mean. Take a look at my project bling-wrt, after applying that guide I could mitm the communication and saw that custom data doing dns spoofing and using sslstrip.

I also tried static analysis with retdec and confirmed they do not do mutual ssl but I didn't invest more time with the investigation. I also do not have XT2 cameras so it's difficult to implement it.

alexeyvasilyev commented 4 years ago

Have you been able to dump TCP data from liveview server? Can you share it?

lurume84 commented 4 years ago

Sorry I have only sniffed traffic between syncmodule and blinkservers and saw that binary data for commands. Liveview is directly done between camera and blink servers that's why camera needs wifi connection. The only way is DNS spoofing at your router level.

alexeyvasilyev commented 4 years ago

DNS spoofing will not work. It uses IP address, not FQDN (immis://3.125.19.18:443). More advanced approach is to make simple Android app with Blink's libwalnut.so component, run fake https server which just logs data and try to establish connection via libwalnut.so to that server. At least first connection packets should be logged.

Can you share sniffed traffic between syncmodule and blinkservers?

lurume84 commented 4 years ago

That immis URL is sent by blink to clients (app, bling) through rest api, nothing to do with actual communication between camera and blink. I talk about dnsspoofing at camera level as it uses DNS.

I do not have traces since that was several months ago, if you follow bling-wrt guide you will be able to get It. What I can tell you is that communication works doing a long-polling. What I could decrypt were commands like snapshot or liveview and I was impressed about the little data sent. Seems like an identifier to the action to execute plus the camera id.

Why not implementing your own .so that facades the libwalnut.so functions but also logs then decompile blinkapp replace and compile again?

alexeyvasilyev commented 4 years ago

Are you talking about injecting log functions into Blink's libwalnut.so? That is too complicated.

lurume84 commented 4 years ago

I haven't worked with so files but would It be possible to create a so library that declares same functions as libwalnut but inside directly calls libwalnut library? Then in your library you could log everything. So you have libwalnut.so (fake) that redirects calls to libwalnut_original.so. Just an idea I only develop por Windows platforms so no idea if it's feasible.

alexeyvasilyev commented 4 years ago

That will not work. The only possible solution is to inject log code by patching binary.