bitcanon / visonicalarm

A simple library for the Visonic Alarm API written in Python 3.
MIT License
12 stars 2 forks source link

Support for bypass #16

Closed amaduain closed 1 year ago

amaduain commented 1 year ago

Hi all, thanks for the great work, I was guessing if you already thought on supporting bypassing, I've been trying to find the api for bypassing, but so far no luck. Do you have any details on the api?

Thanks!

bitcanon commented 1 year ago

Hi,

I don't think I have stumbled upon something called bypassing so I'm not sure what the purpose of this function is. The way I have developed the library is by reverse engineering the mobile app. What is bypassing used for? Is there a way for you to use it in the mobile app? If there is a way in the app, what are the steps I need to take to use bypassing. Sadly, I have no access to the API documentation which makes it a bit harder to develop against :)

No problems, thanks for your input!

amaduain commented 1 year ago

Thanks a lot for the answer, the application I'm using is Connect Alarm, and there is an option called bypass that disables one of the sensor, to avoid false positives. Which application are you reversing? Maybe I can take a look too. Anyway, feel free to close it, if I found anything I will tell you. Best regards!

bitcanon commented 1 year ago

Hi, I'm using Connect Alarm as well, but the reason I have not implemented it might be that I can't see that option in my alarm system. If I click Devices and select a sensor (a "Contact" for example), the only option that is available for me is "Rename". Can you see other options here? Please let me know if you figure it out.

amaduain commented 1 year ago

In my case, some sensors can by bypassed and renamed: WhatsApp Image 2022-12-12 at 16 27 55 WhatsApp Image 2022-12-12 at 16 28 41

amaduain commented 1 year ago

Also, any good source / training for reverse engineering apps?

bitcanon commented 1 year ago

It's probably a setting in the alarm panel itself or simply that my sensors doesn't have that feature.

If you are comfortable with Python and know how to setup a reverse proxy with a Let's Encrypt certificate you can try my dummy API server that I've built.

The way I have developed this library is by implementing a Python Flask application that responds to the API requests that the Connect Alarm app sends.

The workflow is something like this:

  1. Run my API server script on the development machine.
  2. Setup a reverse proxy (I run mine on my Synology NAS), that points for example https://alarm.mydomain.com to http://10.0.0.123:8080 (my dev machine). It's important to have a valid certificate, otherwise the app won't connect.
  3. Then I just press the buttons in the app and see which endpoints the app tries to contact.
  4. I then implement that endpoint in my API server script and check what the request from the app looks like.
  5. After that I implement a method in my visonicalarm library that sends that same information to the real API server and now I can see what the response from the real server looks like.
  6. Now I have the JSON messages sent from both the app and the server and can stitch together the visonicalarm library piece by piece.
  7. Go back to step 3 and press another button i the app and do it all again :)

Initially I tried using applications like Proxyman and Burp Suite, but this approach didn't work since the Connect Alarm app require a valid SSL certificate for its connections.

I hope this made it a bit more clear and let me know if you would like to try my Python Flask API server to find out which endpoint the Bypass button calls on the API server.

I don't think I have any good training material for you more than the apps I mentioned earlier and perhaps to check out my Python server script and simply learn by doing :)

amaduain commented 1 year ago

Thanks a lot for the hints! I have no problem with python or let's encrypt I use them on other projects, can you share your api server? Once I got the info I will share it with you of course! I'm using your actual libraries on the Home Assistant (I got a script to translate from your scripts to MQTT).

bitcanon commented 1 year ago

No problems! :) You can get the server script here:

Get up and running in a few steps.

  1. cd visonicalarm-api-server
  2. paste the gist code into server.py
  3. virtualenv venv
  4. source venv/bin/activate
  5. pip install flask
  6. python server.py

Great to hear that the library is being used!

Happy hunting πŸ˜„

amaduain commented 1 year ago

Thanks for the heads up, it was nice to have a way to reverse engineer the app, super cool. So, what I did is create the certs for the flask server, once you have them you can skip the reverse proxy if you have a local dns (I got Pihole) and route it to the fake server. So in the server code you can add this: import ssl ... ... if __name__ == '__main__': context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) context.load_cert_chain("fullchain2.pem","privkey2.pem") app.run(host='0.0.0.0', port=443, ssl_context=context) After that I was able to emulate the zone bypassing: URL : /rest_api/9.0/set_bypass_zone Action: Post Enable bypassing payload json: { "zone": 5, "set": true } Disable bypassing json: { "zone": 5, "set": false } Zone id has to be one that you have defined on the panel :-D Don't know if you plan to add it to your code, if so, let me know if you need anything else.

bitcanon commented 1 year ago

Thanks! It has been very helpful in developing the library. But I would call it an "ugly hack" rather than "super cool" πŸ˜‰

Also, thanks for the tip on using the certificate with Flask, I didn't know you could do that. I will definitely use this approach in the future, since I seem to be using the Flask server a lot in different projects. Much appreciated! πŸ‘πŸ»

Sure, I'm already working on adding this to the library, so I will need some help testing it soon πŸ˜„

One thing that would be nice if you could check is what the output of the following call is on your system. It will print the raw output from the API server for the get_panel_info endpoint:

print(alarm.api.get_panel_info())

What I'm interested in is the value of the bypass_mode key value pair on your system. On my alarm system it is set to 'bypass_mode': 'no_bypass', which is probably why I can't see this option in the app. I suspect this would make it possible to trick the app to show the Bypass button for some dummy sensors.

amaduain commented 1 year ago

There you go: { "serial": "XXXXXXXXX", "model": "XXXXXXX", "manufacturer": "DSC", "current_user": "master_user", "state_sets": { "all": [ { "name": "AWAY", "settable": true, "options": [ "NOENTRY" ], "statuses": [ "EXIT", "ENTRY", "HOLD" ], "transitions": [ "DISARM", "STAY" ] }, { "name": "NIGHT", "settable": true, "options": [], "statuses": [ "EXIT", "ENTRY", "HOLD" ], "transitions": [ "DISARM", "AWAY", "STAY" ] }, { "name": "STAY", "settable": true, "options": [ "NOENTRY", "NOEXIT" ], "statuses": [ "EXIT", "ENTRY", "HOLD" ], "transitions": [ "DISARM", "AWAY", "NIGHT" ] }, { "name": "DISARM", "settable": true, "options": [], "statuses": [], "transitions": [ "DISARM", "STAY", "NIGHT", "AWAY" ] }, { "name": "PROGRAMMING", "settable": false, "options": [], "statuses": [], "transitions": [] } ] }, "partitions": [ { "id": 1, "active": true, "exit_delay_time": 45, "state_set": "all", "name": "ParticiΓ³n 1" }, { "id": 2, "active": false, "exit_delay_time": 120, "state_set": "all", "name": "ParticiΓ³n 2" }, { "id": -1, "active": true, "exit_delay_time": 0, "state_set": "all", "name": "ALL" } ],

El mar, 13 dic 2022 a las 18:22, mikael @.***>) escribiΓ³:

Thanks! It has been very helpful in developing the library. But I would call it an "ugly hack" rather than "super cool" πŸ˜‰

Also, thanks for the tip on using the certificate with Flask, I didn't know you could do that. I will definitely use this approach in the future, since I seem to be using the Flask server a lot in different projects. Much appreciated! πŸ‘πŸ»

Sure, I'm already working on adding this to the library, so I will need some help testing it soon πŸ˜„

One thing that would be nice if you could check is what the output of the following call is on your system. It will print the raw output from the API server for the get_panel_info endpoint:

print(alarm.api.get_panel_info())

What I'm interested in is the value of the bypass_mode key value pair on your system. On my alarm system it is set to 'bypass_mode': 'no_bypass', which is probably why I can't see this option in the app. I suspect this would make it possible to trick the app to show the Bypass button for some dummy sensors.

β€” Reply to this email directly, view it on GitHub https://github.com/bitcanon/visonicalarm/issues/16#issuecomment-1349151970, or unsubscribe https://github.com/notifications/unsubscribe-auth/AC5Z7SBCI7FBZ7MKH2LTPGLWNCWFFANCNFSM6AAAAAARXC3RPE . You are receiving this because you authored the thread.Message ID: @.***>

bitcanon commented 1 year ago

Thanks for the output, sadly I wasn't able to get the Bypass button appear in the app.

But, to the good news. I have pushed the code for set_bypass_zone() to GitHub now so you should be able to call:

alarm.set_bypass_zone(5, True)

My guess is that the API will return a token that we can poll to check if the operation succeeded.

I haven't pushed the changes to PyPI yet so you have to clone the GitHub repository and try it that way to see if it works first.

Let me know how it goes or if any weird messages appear (most likely) πŸ˜„

amaduain commented 1 year ago

I will test it tomorrow and let you know. For your testing I can sanitize the whole data set and send it to you. Seems there is relationship between the panel, and the devices. It didn't allow me to bypass till I add all data.

On Tue, Dec 13, 2022, 21:22 mikael @.***> wrote:

Thanks for the output, sadly I wasn't able to get the Bypass button appear in the app.

But, to the good news. I have pushed the code for set_bypass_zone() to GitHub now so you should be able to call:

alarm.set_bypass_zone(5, True)

My guess is that the API will return a token that we can poll to check if the operation succeeded.

I haven't pushed the changes to PyPI yet so you have to clone the GitHub repository and try it that way to see if it works first.

Let me know how it goes or if any weird messages appear (most likely) πŸ˜„

β€” Reply to this email directly, view it on GitHub https://github.com/bitcanon/visonicalarm/issues/16#issuecomment-1349637155, or unsubscribe https://github.com/notifications/unsubscribe-auth/AC5Z7SGAYY7KLJCEGNGD2Q3WNDLJHANCNFSM6AAAAAARXC3RPE . You are receiving this because you authored the thread.Message ID: @.***>

amaduain commented 1 year ago

You can find your server.py with the valid payloads for bypassing here: https://github.com/amaduain/visonic_findings/blob/main/server.py

amaduain commented 1 year ago

Thanks for the output, sadly I wasn't able to get the Bypass button appear in the app.

But, to the good news. I have pushed the code for set_bypass_zone() to GitHub now so you should be able to call:

alarm.set_bypass_zone(5, True)

My guess is that the API will return a token that we can poll to check if the operation succeeded.

I haven't pushed the changes to PyPI yet so you have to clone the GitHub repository and try it that way to see if it works first.

Let me know how it goes or if any weird messages appear (most likely) πŸ˜„

Yep, the return is something like:

{
    "process_token": "d32aa971-4ed4-4674-8dd0-ea6b5263f10b"
}
bitcanon commented 1 year ago

You can find your server.py with the valid payloads for bypassing here: https://github.com/amaduain/visonic_findings/blob/main/server.py

Thanks, with your code I could figure out that each device that support bypassing also has a trait called bypass:

  {
    ...
    "traits": {
      "rssi": {},
      "bypass": {
        "enabled": false
      },
      "soak": {
        "enabled": false
      },
      "rarely_used": {
        "enabled": false
      }
    },
    ...
  },

This attribute will have to be added to the Device class.

bitcanon commented 1 year ago

I will test it tomorrow and let you know. For your testing I can sanitize the whole data set and send it to you. Seems there is relationship between the panel, and the devices. It didn't allow me to bypass till I add all data.

I have now added support for bypassing a device in the GitHub repository, so you should now be able to check the bypass status of a device like so:

print("Gettings Devices...")
for device in alarm.get_devices():
    print(f"Device: {device.id} - Bypassed: {device.bypass}")

Output:

Gettings Devices...
<output truncated>
Device: 1234004 - Bypassed: False
Device: 1234005 - Bypassed: True
Device: 1234006 - Bypassed: False
<output truncated>

And like previously mentioned, bypass a device with:

alarm.set_bypass_zone(5, True)

If your testing goes smoothly I can push to PyPI as well πŸ˜„

amaduain commented 1 year ago

Nice, I have been testing in my system today and it is working. Only issue I had it was with the GSM device, which is not reporting the signal so the get_devices was failing on line 181 alarm.py so I added a check as a workaround:

elif device['device_type'] == 'GSM':
                if 'signal_level' in device['traits'].keys():
                    signal_level=device['traits']['signal_level']['level']
                else:   
                    signal_level=0
                contact_device = GSMDevice(
                    device_number=device['device_number'],
                    device_type=device['device_type'],
                    enrollment_id=device['enrollment_id'],
                    id=device['id'],
                    name=device['name'],
                    partitions=device['partitions'],
                    preenroll=device['preenroll'],
                    removable=device['removable'],
                    renamable=device['renamable'],
                    subtype=device['subtype'],
                    warnings=device['warnings'],
                    zone_type=device['zone_type'],
                    signal_level=signal_level
                )

If you have time maybe you can check the inputs before assign them, traits seems to be quite tricky.

Shall you close this issue?

bitcanon commented 1 year ago

Nice, I have been testing in my system today and it is working. Only issue I had it was with the GSM device, which is not reporting the signal so the get_devices was failing on line 181 alarm.py so I added a check as a workaround:

elif device['device_type'] == 'GSM':
                if 'signal_level' in device['traits'].keys():
                    signal_level=device['traits']['signal_level']['level']
                else:   
                    signal_level=0
                contact_device = GSMDevice(
                    device_number=device['device_number'],
                    device_type=device['device_type'],
                    enrollment_id=device['enrollment_id'],
                    id=device['id'],
                    name=device['name'],
                    partitions=device['partitions'],
                    preenroll=device['preenroll'],
                    removable=device['removable'],
                    renamable=device['renamable'],
                    subtype=device['subtype'],
                    warnings=device['warnings'],
                    zone_type=device['zone_type'],
                    signal_level=signal_level
                )

If you have time maybe you can check the inputs before assign them, traits seems to be quite tricky.

Shall you close this issue?

Great, thanks for all of your help and support! Much appreciated πŸ˜„

Version 3.10.0 of the library is now available for installation via PyPI/pip, and I also fixed the bug you found regarding the signal_level trait (created issue #18 for this). Traits sure are quite tricky to predict since they seem to be different on different systems.

I will close the issue for now. The new version seem to be working fine, let me know otherwise.

msp1974 commented 1 year ago

I know this is a closed issue but found the info here really helpful in understanding the rest api. There are a few endpoints we could add to the api (which I will do a PR for soon) but wanted to a) thanks for the effort on this and b) contribute some enhancement I did on your server.py script. This gist server.py

Instead of having to modify it with each endpoint you discover, it is actually forwarding the request to the true endpoint servers, getting the response, and passing back to the client. This is then very easy to now use the app to see the resp api request and response. Hope it helps!

bitcanon commented 1 year ago

Hi, thanks a lot for your improvement to the server.py script. I have already given it a go and it will make development much easier in the future. Great work and thanks again!πŸ‘πŸ»