Reverse Engineering eines Warteschlangenanzeige-Protokolls: BCD4-Kodierung und Carry-Arithmetik
Ein Ingenieur hat ein proprietäres Warteschlangenanzeige-Protokoll ohne Dokumentation reverse-engineered. Das Protokoll verwendet BCD4-Kodierung, wobei jedes Datenbit ein volles Byte belegt, um Rauschimmunität auf langen RS-232-Leitungen bei 1200 Baud zu gewährleisten. Die Paketstruktur umfasst 33 Bytes mit einem Marker, Zahlziffern, Fensternummer und Prüfsummenfeldern mit Carry.
BCD4: Kodierung für zuverlässige Übertragung
Das Protokoll nutzt eine Hardware-Methode zur Rauschunterdrückung: Jedes Bit einer Zahl (0–15) wird als Byte kodiert. 0x7F steht für 1 (sieben Einsen für einen klaren Impuls), 0x00 steht für 0. Dies streckt einen 4-Bit-Wert auf 4 Bytes.
Kodierungsbeispiele:
| Zahl | Bits | BCD4-Bytes |
|------|--------|------------------|
| 0 | 0000 | 00 00 00 00 |
| 1 | 0001 | 7F 00 00 00 |
| 2 | 0010 | 00 7F 00 00 |
| 5 | 0101 | 7F 00 7F 00 |
| 9 | 1001 | 7F 00 00 7F |
| 12 | 1100 | 00 00 7F 7F |
Implementierung in Python:
def _bcd4(value: int) -> bytes:
return bytes([0x7F if value & (1 << i) else 0x00 for i in range(4)])
print(_bcd4(5).hex(' ')) # 7f 00 7f 00
print(_bcd4(9).hex(' ')) # 7f 00 00 7f
Die Baudrate von 1200 Baud sorgt für Zuverlässigkeit auf Kosten des Durchsatzes.
Paketstruktur für die Zahlenanzeige
Ein 33-Byte-Paket zeigt eine Zahl in einem Fenster an:
- Byte 0:
0x55— Startmarker. - Bytes 1–4: Modus-Header.
- Bytes 5–8: BCD4(Einerziffer).
- Bytes 9–12: BCD4(Zehnerziffer).
- Bytes 13–16: BCD4(Hunderterziffer).
- Bytes 17–20: BCD4(Fenster-Einerziffer).
- Bytes 21–24: BCD4(Fenster-Zehnerziffer).
- Bytes 25–28: BCD4(c1) — unterer Prüfsummenteil.
- Bytes 29–32: BCD4(c2) — oberer Teil mit Carry.
Beispiel für Zahl 487 in Fenster 19:
55 00 7F 00 00 # Header
7F 7F 7F 00 # BCD4(7)
00 00 00 7F # BCD4(8)
00 00 7F 00 # BCD4(4)
7F 00 00 7F # BCD4(9)
7F 00 00 00 # BCD4(1)
[Prüfsummenfelder]
Prüfsummenberechnung mit Carry
Die Prüfsummenfelder c1 und c2 bilden einen 8-Bit-Wert mit Carry-Mechanik:
- Berechne raw_c1 als lineare Kombination der Paketfelder.
- Wenn raw_c1 >= 16: Carry = 1, c1 = raw_c1 - 16.
- Sonst: Carry = 0, c1 = raw_c1.
- c2 wird mit Carry aus anderen Feldern berechnet.
Carry tritt bei Zahlen wie 80–90 abhängig vom Fenster auf. KI (Claude) identifizierte das Muster aus 30 Paketbeispielen nach Verfeinerung der Hypothese: zwei 4-Bit-Nibbles mit Carry wie in der Arithmetik.
Im "Alle Fenster"-Modus (25 Bytes) gibt es keinen Carry, und die Formel ist einfacher.
Der Fensterprogrammiermodus verwendet zwei unabhängige Carry-Ketten mit Multiplikation und Konstanten:
- Kette A: raw_A aus W_lo, carry_A = raw_A >> 4, field_A1 = raw_A & 0xF, field_A2 aus W_hi und carry_A.
- Kette B: ähnlich für eine andere lineare Funktion.
Zusammenbau und Senden des Pakets
Paketzusammenbau-Funktion:
def build_window_packet(window: int, number: int) -> bytes:
d1 = number % 10
d2 = (number // 10) % 10
d3 = (number // 100) % 10
w_lo = window % 10
w_hi = window // 10
c1, c2 = _checksum(d1, d2, d3, w_lo, w_hi)
packet = bytearray(b'\x55\x00\x7F\x00\x00')
packet += _bcd4(d1) + _bcd4(d2) + _bcd4(d3)
packet += _bcd4(w_lo) + _bcd4(w_hi)
packet += _bcd4(c1) + _bcd4(c2)
assert len(packet) == 33
return bytes(packet)
Das Paket wird an einen COM-Port gesendet.
Integration in ein Web-System
Ein Python-Server akzeptiert HTTP POST:
POST / {"window": 5, "ticket": "487"}
Er parst JSON, baut das Paket und schreibt auf den Port. Ein Heartbeat-Mechanismus stellt die Verbindung wieder her:
- 60-Sekunden-Intervall bei OK.
- 10 Sekunden bei Fehler.
def _heartbeat_loop():
while True:
ok = _try_register(...)
was = _server_state['connected']
_server_state['connected'] = ok
if not was and ok:
print("[OK] Verbindung wiederhergestellt")
elif was and not ok:
print("[WARN] Verbindung verloren")
time.sleep(60 if ok else 10)
Wichtige Erkenntnisse
- BCD4-Kodierung streckt 1 Bit auf ein Byte für Rauschimmunität bei 1200 Baud.
- Die Prüfsumme verwendet Carry zwischen Nibbles c1/c2.
- Reverse Engineering mit KI ist effektiv mit präzisen Hypothesen und Paketbeispielen.
- Der Server ermöglicht nahtlose Integration von Legacy-Geräten ins Web.
- Protokoll: 33 Bytes, Marker 0x55, Ziffern + Fenster + Prüfsumme.
— Editorial Team
Noch keine Kommentare.