[Update: 2012 habe ich mir Tickets erneut angesehen.]
Pünktlich zu Ostern wird es endlich Zeit, mal zu zeigen, was sich denn in den Barcodes der Onlinetickets der Bahn alles an Informationen findet und daraus abzuleiten, wie wohl deren System funktioniert.
Ganz dumm haben sie sich dabei nicht angestellt, sondern – und das war der unerwartete Teil – mal ausnahmsweise alles richtig gemacht:
Die Ticketdaten werden zlib-komprimiert, dieser Blob dann DSA signiert und das alles mit dem „Aztec“ Matrixcode auf’s Ticket gebracht. Die mobilen Endgeräte prüfen (hoffentlich) die Signatur und speichern einen Kontrolldatensatz, der später mit der Datenbank abgeglichen wird, sodass sie zumindest erkennen, wenn stornierte Tickets oder das selbe Ticket mehrfach genutzt wurden (Quelle).
Letztendlich scheinen die mobilen Kontrollgeräte der vermutlich einzig verwundbare Punkt. Käme man an die Firmware ließen sich sicherlich Wege finden, diese trotz falscher Signatur von einem echten Onlineticket zu überzeugen. Ansatzpunkte wären beispielsweise Carrier-IDs die dem Gerät unbekannt sind, fehlerhafte ASN.1-codierte Signaturen oder gar Pufferüberläufe durch zu lange Payloads (Dank zlib-Kompression passt ja einiges in den Barcode).
Weitere Ressourcen:
- Wikipedia: Diskussion:Online-Ticket dokumentiert die Ergebnisse des Reverse-Engineerings und die Bedeutungen der einzelnen Felder
- onlineticket.py ist ein proof-of-concept python-Modul, das einen Parser implementiert um den Inhalt von Onlineticket-Dumps auszulesen
- BCTester: Software um den Aztec-Code zu dekodieren
- Die Slides der Präsentation auf dem Easterhegg 2010 sind auch verfügbar (svg+javascript im Firefox getestet)
Hi,
dein Onlineticket.py liefert mir für den Signaturblock nur den code, nicht die Ausgabe:
signature: (Sequence().setComponentByPosition(0, Integer(‘831850300058523969331890131572054925229994625947’)).setComponentByPosition(1, Integer(‘497113864828103579676642050019363524670552150813’)), ‘\x00\x00\x00’)
Versucht hab ich es mit Python 2.5 und 2.7. Leider kenne ich mich mit Python nicht genug aus um den Fehler selbst zu fixen. Habe jedoch das gefühl das lediglich irgendwo ein print fehlt.
Kannst du weiterhelfen?
Ich weiß nicht genau, was du meinst? Die zwei Zahlen werden doch angezeigt, oder was erwartest du genau zu sehen?
Hier ein kleines Tutorial:
* 1. bcTester herunterladen (nicht die Webcamedition)
* 2. Screenshot von der Onlineticket-PDF machen. Ein Webcam-Bild wird so gut wie nicht erkannt.
* 3. Bereich in bcTester auswählen und Aztec-Test starten. Ein Automatischer-Test funktioniert nicht (er wird “Interleaved” o.ä. erkennen anstelle Aztec-Code)
* 4. Den Hex-Code im unteren Codefenster auswählen und in Datei “tickets” speichern.
* 5. Python sowie Python-Modul “python-pyasn1” installieren (Paket-Name bei Debian)
* 6. “python onlineticket.py” aufrufen
Und noch ein Verbesserungsvorschlag:
Die Kontrolle mittels “Personalausweis” ist “09” (gibt es seit 2010).
Zeile 71 ist nun also
(‘ausweis_typ’, 2, {’01’: ‘CC’, ’04’: ‘BC’, ’07’: ‘EC’, ’09’: ‘ID’}),
anstelle
(‘ausweis_typ’, 2, {’01’: ‘CC’, ’04’: ‘BC’, ’07’: ‘EC’}),
habe die fehlende Angabe bereits bei Wikipedia ergänzt.
Noch eine Info:
Bei dem Feld “TBD: CC-#/Ausweis-ID” steht bei mir die vollständige Personalausweis-ID und nicht die CC#. Ich habe den Personalausweis als Identifikation ausgewählt und per Kreditkarte gezahlt. Das Onlineticket ist von Juni 2011.
Ist damit Ihr “TBD” Problem gelöst?
Haben Sie weitere Informationen über die TBD herausgefunden?
Hallo,
Ich habe ein Ticket ausgeleset mit BCTester und die Text Copy-Pasted in die Datei “tickets”. Aber wann ich die Program läuft kriege ich:
Traceback (most recent call last):
File “app_main.py”, line 53, in run_toplevel
File “onlineticket.py”, line 208, in
ots = [OT(readot(i)) for i in open(“tickets”)]
File “onlineticket.py”, line 178, in
readot = lambda x: ”.join([chr(int(i,16)) for i in x.split(” “)])
ValueError: invalid literal for int() with base 16: ‘#UT010080000010-##rp├¹##lSj
TË╗í_y#²╝#J#####Õ└¸#┘###q#Tx#┴ÔA#I###0270x#m#¤J├@#ã}##5###²7±#&í¡¡ííTí##ðC!&#ñ##
╠#┼#A##¦╦■µ█of┐¦├Ù¬╠#@#g###X»8K▄═l##%@D###▓(ı©.@td@´}Pe▒#┼#5#Ï##O#ª§|##ºs╚■¯#Ï#
r@d├##Õ▒6
In welche format soll die Text in tickets sein? HEX ANSI? Mit Linebrakes? Ich freue mich für alle hilfe.
Hallo Jonas, das Skript erwartet die Daten im hex-Format. Dann sollte das auch klappen 😉
Hallo Hagen,
Okay, noch eine Frage. Genau wie soll die Datei aussehen? Alles auf eine reihe? Alles zusammen oder mit ein delimiter (space, komma etc?
Können Sie eine kleine Beispiel geben wie es formatiert sein soll?
Vielen dank,
Jonas
Space-separierte Hexadezimalzahlen. Ein Ticket pro Zeile:
z.B.:
23 55 54 30 31 31 31 38 30 54 54 30 30 31 30 …
23 55 54 30 31 31 31 38 30 39 30 30 30 31 30 …
Hmmm jetzt sieht meine Datei so aus:
23 55 54 30 31 30 30 38…
Ergebnis ist:
Traceback (most recent call last):
File “app_main.py”, line 53, in run_toplevel
File “onlineticket.py”, line 208, in
ots = [OT(readot(i)) for i in open(“tickets”)]
File “onlineticket.py”, line 28, in __init__
self.data = self.dict_read(self.fields)
File “onlineticket.py”, line 51, in dict_read
dat = val[3](self, res)
File “onlineticket.py”, line 202, in
lambda self, res: read_blocks(zlib.decompress(self.read(res[‘data_length’]))
, read_block))
File “onlineticket.py”, line 184, in read_blocks
block = read_func(data, offset)
File “onlineticket.py”, line 176, in read_block
return block_types[data[offset:offset+6]](data, offset)
File “onlineticket.py”, line 28, in __init__
self.data = self.dict_read(self.fields)
File “onlineticket.py”, line 51, in dict_read
dat = val[3](self, res)
File “onlineticket.py”, line 113, in read_sblocks
typ, mod = typen.get(typ, (typ,))
ValueError: expected length 2, got 1
Was ist falsch? Vielen dank.
Da mich das ganze gerade eine ganze Weile eingespannt hat, werde ich mal meine Erkenntnisse niederschreiben (ich hab nämlich keine Dokumentation für das Skript gefunden)
* es wird die Bibliothek python-pyasn1 benötigt
* da das Skript den zu verwendenden Parser nicht enthält, wird es als bash-Skript geparst, außer man startet es via python
* der Code auf dem Ticket lässt sich mit dem oben verlinkten BCTester in eine Hex-Reihe umwaldeln
* diese Reihe muss in der Datei tickets gespeichert werden, pro Zeile ein Ticket
* am Ende einer jeden Zeile darf kein Leerzeichen stehen
Wenn das so gemacht wird, sollte das eigentlich funktionieren
Bei einem Onlineticket dieser Tage ist der Header jetzt scheinbar “OTI” statt “#UT” und das Python-Script steigt mit “Error: strptime() argument 1 must be string without null bytes, not str (orig); Error -3 while decompressing data: invalid code lengths set (zxing)” aus.
Außerdem muss man aufpassen, wenn man den Code mit einem UTF-8-fähigen Tool scanned, z.B. der Android-App “Barcode Scanner+”. Dann wird z.B. aus 0xA8 plötzlich 0xC2 0xA8 – was die UTF-8-Entsprechung von 0xA8 ist. D.h., man muss die Daten vorher noch von UTF-8 nach ANSI wandeln, bevor das Python-Script die frisst.
Hallo,
gibt es denn eine Möglichkeit die Onlinetickets von Heute mit dem Script auszulesen? Falls ja wo muss das Script modifiziert werden?
@mbirth: Hast du eine Version, die neuere Tickets lesen kann?
Aktuelle Tickets sollten nun wieder funktionieren. Falls nicht, am besten auf github einen Bug aufmachen und das Ticket, das nicht erkannt wird hochladen.
Aktuelle Tickets sollten nun wieder funktionieren. Falls nicht, am besten auf github einen Bug aufmachen und das Ticket, das nicht erkannt wird hochladen.