first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:29:38 +03:00
commit 427856cd3a
176 changed files with 27613 additions and 0 deletions

85
utils/analyze-idv1.py Executable file
View File

@@ -0,0 +1,85 @@
#!/usr/bin/env python3
import sys
import json
import re
from rich import print
RE_NUM = re.compile("/[0-9]+$")
def load(name):
# print(f"loading {name}..")
return json.load(open(name))["data"]
def idv1_type(idv1):
if not idv1.endswith("/0"):
idv1 = RE_NUM.sub("/:id", idv1)
if idv1.startswith("/scenes/"):
idv1 = "/scenes/:id"
return idv1
def analyze_unique_ids(res):
ids = set()
for obj in res:
if "id_v1" in obj:
rtype = obj["type"]
idv1 = idv1_type(obj["id_v1"])
ids.add((idv1, rtype))
for idv1, rtype in sorted(ids):
print(f"{idv1:20} {rtype:30}")
def analyze_idv1_percent(res):
ids = dict()
for obj in res:
rtype = obj["type"]
if rtype not in ids:
ids[rtype] = [0, 0]
cnt = ids[rtype]
cnt[0] += 1
if "id_v1" in obj:
cnt[1] += 1
for name, (total, idv1) in sorted(ids.items()):
pct = (idv1 / total) * 100.0
print(f"{name:30} {pct:6.2f}% [{idv1:5} / {total:5}]")
def analyze_idv1_mapping(res):
ids = dict()
for obj in res:
rtype = obj["type"]
if rtype not in ids:
ids[rtype] = set()
if "id_v1" in obj:
idv1 = idv1_type(obj["id_v1"])
else:
idv1 = "<none>"
ids[rtype].add(idv1)
for name, types in sorted(ids.items()):
if types == {"<none>"}:
continue
print(f"{name:30} {' '.join(sorted(types, reverse=True))}")
def main(args):
objs = []
for name in args:
objs.extend(load(name))
analyze_unique_ids(objs)
analyze_idv1_percent(objs)
analyze_idv1_mapping(objs)
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))

52
utils/attribute-bruteforce.py Executable file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python3
import sys
import time
import json
## Delay between each query, in seconds (adjust as needed)
##
## WARNING: Too small values might overload the zigbee network and/or the
## coordinator.
DELAY = 0.3
TMPL = {
"topic":"DUMMY/set",
"payload":{
"read":{
"cluster":64516,
"attributes":[],
"options":{
"timeout": 500.0,
"manufacturerCode":4107
},
}
}
}
def note(msg):
print(f">>> {msg} <<<", file=sys.stderr)
def main(argv0, *args):
if len(args) != 1:
print(f"usage: {argv0} <topic>")
return 1
topic = f"{args[0]}/set"
TMPL["topic"] = topic
note(f"Using topic {topic}")
for cluster in [0xFC00, 0xFC01, 0xFC02, 0xFC03, 0xFC04]:
note(f"Brute forcing attributes in cluster {cluster:04X}")
TMPL["payload"]["read"]["cluster"] = cluster
for x in range(64):
TMPL["payload"]["read"]["attributes"] = [x]
print(json.dumps(TMPL))
sys.stdout.flush()
time.sleep(DELAY)
note("DONE")
return 0
if __name__ == "__main__":
sys.exit(main(*sys.argv))

18
utils/capture-event-stream.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/sh
set -ue
if [ $# -ne 2 ]; then
echo "usage: $0 <hue-key> <bridge-addr>"
exit 1
fi
KEY="${1}"
IP="${2}"
exec curl -N --http2 \
-H "Accept:text/event-stream" \
-s \
-k \
-H"hue-application-key: ${KEY}" \
"https://${IP}/eventstream/clip/v2"

74
utils/extract-error-sample.py Executable file
View File

@@ -0,0 +1,74 @@
#!/usr/bin/env python3
import sys
import re
import enum
import json
RE_LOG = re.compile("\s*[0-9]{4}-[0-9]{2}-[0-9]{2}.+(ERROR|INFO |TRACE|DEBUG|WARN ) ([a-z0-9_::-]+)\s+> (.+)")
RE_FAILED_TO_PARSE = re.compile("\[(.+)\] Failed to parse \(non-critical\) z2m bridge message on \[(.+)\]:$")
class Mode(enum.Enum):
Idle = 0
IgnoreObj = 1
IgnoreLogo = 2
Sample = 3
SampleStart = 4
mode = Mode.Idle
res = []
buf = None
topic = None
BAR = " ==================================================================="
for line in sys.stdin:
line = line.rstrip()
if not line:
continue
match mode:
case Mode.Idle:
if line == "{":
mode = Mode.IgnoreObj
if line == BAR:
mode = Mode.IgnoreLogo
if (log := RE_LOG.match(line)):
if (err := RE_FAILED_TO_PARSE.match(log.group(3))):
mode = Mode.SampleStart
buf = ""
topic = err.group(2)
case Mode.IgnoreObj:
if line == "}":
mode = Mode.Idle
case Mode.IgnoreLogo:
if line == BAR:
mode = Mode.Idle
case Mode.SampleStart:
if (log := RE_LOG.match(line)):
mode = Mode.Sample
buf = log.group(3)
case Mode.Sample:
if (log := RE_LOG.match(line)) or line == BAR:
try:
val = json.loads(buf)
res.append((topic, val))
except:
print(f"FAILED TO PARSE JSON: {buf}")
mode = Mode.IgnoreLogo if line == BAR else Mode.Idle
buf = None
topic = None
else:
buf += line
for topic, data in res:
print(json.dumps({"topic": topic, "payload": data}))

24
utils/filter-zigbee.jq Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env -S jq -c --unbuffered -f
### convert json file:
#
# tshark -r zigbee-2025-01-27_08.57.08-hue.pcap -lq -T ek -x | utils/pcap-extract-zigbee.jq > /tmp/sample.json
#
### live capture
#
# tshark -i lo -lq -T ek -x 'udp port 1337' | utils/pcap-extract-zigbee.jq
#
.layers
| select(.zbee_aps["zbee_aps_zbee_aps_cluster_raw"])
| {
time: .frame["frame_frame_time_epoch"],
index: .frame["frame_frame_number"] | tonumber,
src_mac: (.zbee_nwk["zbee_nwk_zbee_sec_src64_raw"] // .wpan["wpan_wpan_addr64_raw"]),
src: (.zbee_nwk["zbee_nwk_zbee_nwk_src_raw"] // .wpan["wpan_wpan_addr16_raw"]),
dst: (.zbee_nwk["zbee_nwk_zbee_nwk_dst_raw"] // .wpan["wpan_wpan_dst16_raw"]),
cluster: .zbee_aps["zbee_aps_zbee_aps_cluster_raw"],
cmd: .zbee_zcl["zbee_zcl_zbee_zcl_cmd_id_raw"],
data: .zbee_zcl_raw
}

16
utils/live-zigbee.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
# this script expects a zigbee sniffer to be running,
# which dumps zigbee packets to udp on localhost
#
# one example is zsmartsystems zigbee sniffer:
#
# git clone https://github.com/zsmartsystems/com.zsmartsystems.zigbee.sniffer.git
# cd com.zsmartsystems.zigbee.sniffer
#
# java -jar com.zsmartsystems.zigbee.sniffer-1.0.2.jar -p /dev/serial/by-id/${ZIGBEE_ADAPTER} -c $ZIGBEE_CHANNEL -b 115200 -flow software -r 1337
#
BASEDIR="$(realpath -- $(dirname $0))"
tshark -ni lo -lq -T ek -x | stdbuf -i0 -o0 ${BASEDIR}/filter-zigbee.jq | cargo run --example=ez-parse

12
utils/pcap-extract-zigbee.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/sh
BASEDIR="$(realpath -- $(dirname $0))"
if [ $# -lt 1 ]; then
echo "usage: $0 <input.pcap>"
exit 1
fi
for name in "$@"; do
tshark -r "${name}" -lq -T ek -x | "${BASEDIR}"/filter-zigbee.jq
done