Coming back to Hong Kong I noticed these new traffic light poles at crossings. Chunkier boxes, a little light on top, a tactile map on the side.
A few days later I saw a post online about them. Someone claimed you could "press the crossing button with your phone." Sounded like nonsense, so I went to check.
The Transport Department calls them Electronic Audible Traffic Signals, eATS for short.
They're swapping out about 13,000 of them, starting April 2025 and finishing end of 2027.
The new ones add a light to help people with low vision find the pole, a tactile map, a spoken location message, and a touchless button.
A visually impaired person can use the government's HKeMobility app to temporarily turn up the volume of the audible signal when they need to cross.
So there's no secret "summon the green light" button. The phone just tells the pole to get louder for a bit. It's a good accessibility feature, and I wanted to know how the phone talks to the pole.
I pulled the HKeMobility APK (
com.hketransport, version 6.2.85) and dropped it into JADX, buried inside is a separate library:Internally it's "Cross Easy" (過路易). It ships its own activity, a background
AdvertiserService, and a TimerReceiver, none of which show up in the normal app, unless accessibility features are enabled on your phone (TalkBack, VoiceOver).The manifest contains the following permissions, which are not present in most trasnit apps:
BLUETOOTH_ADVERTISE means the phone transmits, which is what you need to poke a roadside receiver.The app also bundles Talsec RASP, a security scanner that detects and blocks malicious apps.
AdvertiserService.a() builds a 24 byte manufacturer payload and broadcasts it as a non connectable advertisement under manufacturer ID 2619 (0x0A3B):- An 8 byte ASCII header,
"GaLiLeO ". - A 16 byte block, AES-128, ECB, no padding.
The key gets built in
onCreate:The plaintext looked almost empty at first:
Those 6 middle bytes are the low 48 bits of
System.currentTimeMillis() from when the service starts, and the same value gets written into the registration record as Verified;galileo;<timestamp>, a per session nonce.So the block rebuilds from the timestamp for any nonce:
python
Zero nonce gives
1E F8 BC B7 ..., and the full advertisement on air is:The service listens for one service UUID:
Not random. Decode it as ASCII:
"Galileo Tech, button." The roadside box is a "Galileo Tech" traffic light button receiver, and the phone pretends to be its paired remote.
The config profile is stamped
HongKongProduction, dated 20220501, so this has been shipped for quite a while."Galileo" also appears in different places of the app codebase.
Cross Easy has two controls, and the accessibility labels spell out what they do:
- ON/OFF: a 20 minute function. The "turn the audio up for a while" mode. A
TimerReceiverfires a stop after 1,200,000 ms so it shuts itself off. - TR: a 10 second trigger. One short poke.
Both send the same encrypted bytes. The only difference is how long the phone keeps advertising, so the pole tells a hold from a tap by duration.
The app also plays little 3 kHz beeps from bundled WAV files on state change, same tone family as the crossings.
Once you have the packet the phone stops being special.
Anything that can send a custom BLE manufacturer advertisement can send the same bytes.
Using my Flipper Zero, I wrote a small app in in Rust with
flipperzero-rs.And apparently you can send a custom manufacturer advertisement without using C!
The firmware exposes an "extra beacon" slot through
furi_hal_bt_extra_beacon_*, wrapped by flipperzero::bluetooth::beacon, so the whole app can just be written in Rust.The app runs AES on device and generates a fresh timestamp nonce on every broadcast instead of replaying a canned packet. Two modes to match the two buttons on the official app.
I assumed the extra beacon API was a Momentum or Xtreme thing at first, but the official firmware docs have it stock.
It just needs firmware new enough to include it and a FAP built against a matching API version.
Yes! I tested it against a real upgraded crossing this noon and the app did what it says it does.
I didn't have time to record a video though, so you need to take my word for it!
Want to try it out yourself? You can find the Flipper app on my GitHub: https://github.com/Thereallo1026/flipper-crosseasy.
Please note that this is an accessibility feature, it exists so visually impaired pedestrians can cross safely.
And the right way to use my Flipper build is the same as the app, if you're someone it's for, it's a handier way to invoke it.
It's not for standing at a junction spamming crossings you don't need (the signal actually travels quite a long way). I don't encourage messing with public safety infrastructure.