bisdk / sdk

Reverse Engineer the App <-> BiSecure Gateway Protocol
MIT License
18 stars 3 forks source link

Build Status

Bisecure SDK

The BiSecure Gateway SDK is a library that can be used to speak to the Hoermann BiSecure Gateway. It is a small device that can speak the BiSecure wireless protocol with your Hoermann devices (e.g. garage door).

Be aware that this is unofficial / experimental code that can potentially damage your devices! We cannot give you any guarantee! Use at your own risk!!!!

Example code

See RealGatewayTest class for flow implementation. Currently working:

Things to take care

Local Development

Install as Maven Dependency

./gradlew build
mvn install:install-file -Dfile=build/libs/sdk-jvm-0.0.2-SNAPSHOT.jar -DpomFile=build/publications/jvm/pom-default.xml 

Tested Environments

Ubuntu 18.04.5 Server
- sudo apt install openjdk-8-jdk
- git clone repository
- ./gradlew build

Simple Test (Needs a real gateway available in the network)

0. Recommended to create a second user in the App and use that to login.
1. Set your username and password
    set env variable
    export "bisecureUsername=<yourusername>"
    export "bisecurePassword=<yourpassword>"
2. Run ./gradlew clean jvmTest --tests RealGatewayTest -i
    This runs src/jvmTest/kotlin/org/bisdk/sdk/RealGatewayTest.kt
3. The test will print all interactions with the gateway.

Analysis

Protocol

This is the result of the Reverse Engineering of the App <-> BiSecure Gateway Protocol It is the attempt to reverse engineer the protocol between the Hoermann BiSecure gateway and the corresponding app.

The goal is to be able to build an adapter for home automation system to control the garage doors from the automation software. Especially to be able to get the door open when you drive home automatically.

I'm not an expert in reverse engineering nor IP protocols, so my findings could be sometimes wrong :-)

Discovery

See file Discovery.kt

The app looks for a gateway in the local network by sending out an UDP message to Port 4001 (Destination= 255.255.255.255) with the following payload <Discover target="LogicBox" />

The gateway returns the following UDP package to port 4002 at the App's IP: <LogicBox swVersion="2.5.0" hwVersion="1.0.0" mac="XX:XX:XX:XX:XX:XX" protocol="MCP V3.0"/>

After that they apparently create a TCP connection:

546 14.294589   192.168.178.45  192.168.178.58  TCP 74  32922 → 4000 [SYN] Seq=0 Win=65535 Len=0 MSS=1360 SACK_PERM=1 TSval=70242312 TSecr=0 WS=64
547 14.295620   192.168.178.58  192.168.178.45  TCP 60  4000 → 32922 [SYN, ACK] Seq=0 Ack=1 Win=512 Len=0 MSS=536
548 14.298392   192.168.178.45  192.168.178.58  TCP 60  32922 → 4000 [ACK] Seq=1 Ack=1 Win=65535 Len=0

After that the exchange request response pair over the TCP/IP connection.

Message Exchange

The messages that are exchanged over the TCP connection have the following format:

The whole hex value array is converted to a string (as human readable hex string) and this is converted to bytes and sent through th TCP connection.

Get Name

The get name request is the first request made from APP to GW.

Authentication

Send a LOGIN command and receive the token (session id) as response payload.

It seems that only one client can be logged in into the GW. So if your app is also logged in it will be logged out. Or the App will log out your session.

Get Transition

Command GET_TRANSITION => Results in door state

Set State

To open / close the door send a SET_STATE Command

JCMP Protocol

JCMP protocol is JSON over CMP. The commands are probably the same as to the cloud service. The Sequence is:

  1. {"cmd":"GET_USERS"} Result:

    [{"ID":0,"NAME":"ADMIN","ISADMIN":TRUE,"GROUPS":[]}]
  2. {"CMD":"GET_GROUPS", "FORUSER":1} Result:

    [{"ID":0,"NAME":"GARAGE ","PORTS":[{"ID":0,"TYPE":1}]}]
  3. {"cmd":"GET_VALUES"}

    Result:

    {"00":1,"01":0,"02":0,"03":0,"04":0,"05":0,"06":0,"07":0,"08":0,"09":0,"10":0,"11":0,"12":0,"13":0,"14":0,"15":0,"16":0,"17":0,"18":0,"19":0,"20":0,"21":0,"22":0,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0}

Time out of Responses

The gateway responds quite quickly to a request and there are only a few exceptions. Sometimes it does not respond at all, only a reconnect and retry works then.

Example (199 x GetTransition): 2020-01-20T19:23:37.266 INFO: Times (ms): [655, 654, 654, 654, 654, 604, 654, 705, 605, 655, 660, 705, 654, 654, 704, 654, 653, 704, 2917, 652, 703, 653, 653, 653, 704, 704, 653, 652, 703, 653, 653, 653, 653, 652, 653, 652, 653, 652, 653, 652, 652, 655, 652, 652, 653, 652, 652, 655, 603, 652, 653, 602, 652, 652, 653, 652, 653, 652, 602, 653, 652, 653, 652, 656, 602, 652, 653, 602, 652, 603, 652, 602, 602, 602, 653, 653, 602, 602, 703, 652, 652, 702, 703, 652, 653, 702, 653, 702, 703, 652, 652, 702, 703, 652, 702, 703, 652, 652, 703, 652, 702, 652, 652, 653, 652, 651, 653, 652, 702, 652, 651, 651, 652, 702, 652, 652, 652, 657, 702, 652, 702, 702, 652, 702, 657, 1103, 653, 652, 5947, 652, 652, 651, 602, 654, 658, 652, 702, 652, 652, 706, 652, 652, 702, 652, 652, 652, 703, 652, 652, 703, 652, 652, 652, 652, 652, 652, 653, 1102, 652, 657, 652, 652, 653, 601, 652, 602, 652, 652, 602, 652, 652, 652, 652, 652, 652, 652, 652, 653, 652, 652, 652, 652, 602, 652, 652, 652, 653, 652, 652, 602, 652, 652, 602, 652, 652, 652, 602, 652, 652, 602] 2020-01-20T19:23:37.266 INFO: Under 1s: 196, Under 2s: 198, Under 5s: 199

Example 2 (199 x GetGroups): [857, 280, 288, 288, 277, 280, 225, 272, 274, 325, 279, 272, 269, 268, 272, 216, 213, 265, 266, 212, 324, 212, 267, 372, 259, 310, 254, 255, 260, 203, 254, 204, 255, 204, 254, 253, 255, 254, 253, 254, 255, 259, 258, 254, 202, 254, 256, 255, 258, 257, 303, 262, 203, 254, 203, 253, 255, 254, 203, 303, 253, 255, 253, 253, 257, 254, 252, 254, 253, 258, 254, 254, 203, 202, 253, 306, 304, 254, 258, 255, 306, 203, 252, 253, 253, 253, 255, 252, 206, 206, 303, 303, 253, 253, 256, 304, 252, 253, 255, 203, 252, 254, 253, 257, 254, 202, 252, 306, 304, 252, 303, 303, 306, 202, 253, 256, 253, 253, 252, 255, 256, 253, 253, 202, 202, 309, 308, 308, 303, 303, 253, 252, 256, 257, 217, 256, 253, 252, 353, 308, 253, 256, 258, 252, 254, 302, 204, 202, 254, 257, 5544, 305, 255, 253, 254, 302, 255, 252, 253, 254, 253, 202, 257, 252, 256, 255, 204, 305, 252, 252, 306, 306, 252, 252, 256, 257, 255, 253, 202, 203, 256, 252, 253, 202, 302, 305, 305, 253, 252, 253, 256, 255, 252, 253, 302, 254, 252, 253, 254, 254] 2020-01-21T16:41:42.539 INFO: Under 1s: 199, Under 2s: 199, Under 5s: 199

As you can see the most of the get transition commands return after <1s (96%), 2 need little more than 1s and 1 had to be retried after timeout of 2s and needed almost 3s then.

As a result I set the timeout for waiting for a response to 2s, that should be enough for all responses that come and we don't wait too long if we won't get any response at all.

Local Development

Build

 ./gradlew build

Deployment to local Maven Repo

./gradlew publishToMavenLocal

Other Related Projects

https://github.com/skelsec/pysecur3