Author: canutethegreat

  • Flipper Zero, part 3: Sub-GHz remotes, rolling codes and how gates should be built

    Flipper Zero, part 3: Sub-GHz remotes, rolling codes and how gates should be built

    Part 3 of 5 in the Flipper Zero series. Part 1: what it actually is and what it can’t do. Part 2: why so many office badges are weaker than they look.

    Garage doors, gates, car fobs, doorbells and cheap alarm sensors all talk on a handful of license-free bands, mostly around 315, 433.92, 868 and 915 MHz depending on where you live. The Flipper Zero has a CC1101 transceiver that covers 300 to 348, 387 to 464 and 779 to 928 MHz, with regional limits on where it will transmit. That makes it a convenient way to look at your own remotes and ask whether the thing guarding your driveway was designed by someone who had met an attacker.

    Same rules as the earlier parts: my own device, my own remotes, and a defender’s goal. Transmitting captured codes at a gate or car you do not own is illegal in most places, and the receiver you are testing may belong to a landlord or an employer. Get permission first.

    What the Flipper shows you

    The Sub-GHz menu has Read, Read RAW, Saved and Add Manually. Read tries to decode a signal against the protocols it knows and prints the frequency, modulation and the bits. Read RAW records the pulse timing without interpreting it.

    The first photo is my own Flipper’s main menu with Sub-GHz selected, and the second is its Sub-GHz submenu.

    Flipper Zero main menu with Sub-GHz highlighted
    Flipper Zero Sub-GHz submenu: Read, Read RAW, Saved, Add Manually

    Press a cheap fixed-code remote near it and the Flipper will show you the same bits every time. Press a rolling code remote and the bits change on every press. Seeing that difference on your own remote takes about ten seconds and is a better lesson than any slide.

    Fixed codes: nothing to defend

    Old remotes with DIP switches, or the cheapest 433 MHz clones, send the same code on every press. Anyone who hears it once can send it forever. There is no cryptography to attack, which is the whole problem. If your gate still uses one, the fix is to replace the receiver and remotes. Nothing you can configure makes a fixed code safe.

    Rolling codes: what they actually are

    A rolling code remote sends a serial number and a counter, protected so that a receiver can check the counter has moved forward. The best known design is Microchip’s KeeLoq. It uses a 32 bit block cipher with a 64 bit key and 528 rounds. The hopping part of the message carries a 16 bit synchronisation counter, and the serial number goes out in the clear next to it. In the common learning schemes the per-remote key is derived from the serial number and a manufacturer key.

    That last detail is where the trouble started. Several groups published cryptanalysis of KeeLoq in 2007 and 2008, and in 2008 Eisenbarth and colleagues presented “On the Power of Power Analysis in the Real World” at CRYPTO. They used power analysis on the receiver side to extract the manufacturer key. With that key, anyone who hears one transmission from a remote can compute its key from the serial number. That is a break of the whole product family, not of one gate.

    Receivers also have to tolerate lost presses. You pressed the button in your pocket twice, out of range, and the receiver has to accept counter 5 after seeing counter 2. So it accepts any value inside a look-ahead window. Here is a toy model of that logic. It is not KeeLoq, it uses HMAC-SHA256 truncated to 4 bytes, but the receiver behaviour is the same shape:

    import hmac, hashlib
    
    KEY = b"per-remote-secret"
    WINDOW = 16
    
    def code(counter):
        msg = counter.to_bytes(4, "big")
        return hmac.new(KEY, msg, hashlib.sha256).digest()[:4].hex()
    
    class CounterReceiver:
        def __init__(self):
            self.last = 0
        def press(self, c):
            for n in range(self.last + 1, self.last + 1 + WINDOW):
                if hmac.compare_digest(code(n), c):
                    self.last = n
                    return f"OPEN (counter {n})"
            return "rejected"

    Jam and capture

    Samy Kamkar showed the practical weakness at DEF CON 23 in 2015 with a device he called RollJam. The attacker jams the receiver and records the owner’s press at the same time. The owner presses again, the attacker jams and records that too, but this time releases the earlier code to the receiver, so the door opens and nobody suspects anything. The attacker now holds a valid code the receiver has never seen.

    The diagram below compares the two receiver designs side by side.

    Sequence diagram comparing a counter-only receiver, where jammed and recorded codes open the gate twice, with a challenge-response receiver, where a recorded answer fails against a fresh nonce. Annotation boxes added for explanation.

    The key point is that a counter-only receiver has no idea when a code was made. A code from yesterday is as good as one from a second ago, as long as the counter has not passed it. I ran the toy model through the scenario and rendered the real output below. The jammed presses are generated by my script, so this is a simulation of the logic, not a capture of any real remote.

    Terminal output of the toy rolling code receiver showing a replay rejected, a jam-and-capture attack succeeding twice, and a challenge-response receiver rejecting a captured answer

    Note the third line of the middle block. The receiver accepts the stored second code long after the owner walked off. Once the stored code is used, replaying it fails, so the attack is a one-shot, but one shot is enough to open a gate.

    Counter windows have caused real vulnerabilities too. CVE-2021-46145 covers the Honda Civic 2012 key fob, where the rolling code does not expire and the counter resynchronisation can be abused to replay unlock commands. CVE-2022-27254 is a worse case on the 2018 Civic, where the remote sends the same signal for every unlock request, so it was never rolling at all.

    How a gate should be built

    If I were specifying a new gate or garage opener, the list would be short.

    First, the receiver should reject codes older than a few seconds, or use challenge-response. In the last block of the demo the receiver sends a random nonce and the remote answers with an HMAC of it. A captured answer fails against the next nonce, so storing it gains nothing. This needs a two way radio, which costs a little more, and that is the price of getting rid of the stored-code attack.

    Second, use a modern authenticated protocol with a per-device key that is not derived from a shared manufacturer secret. AES-based systems exist. Ask the installer what the remote actually uses, and if the answer is “rolling code” with no name attached, treat that as a fixed code until proven otherwise.

    Third, keep the radio the weaker half of the system. A gate that opens on a single radio message should not also be the only barrier to something you care about. Put cameras on it, put a lock on the inner door, and do not rely on it to separate a vehicle from the street.

    Fourth, plan to replace the remotes. Lost ones should be removed from the receiver, not just forgotten, which means choosing a receiver that actually lets you delete individual remotes.

    Testing your own gear

    With the Flipper you can check three things on your own remote. Does Read decode it as a known protocol or leave you with raw pulses only? Do the bits change between presses? If they do not, you have a fixed code and a replacement project. And can you find the manufacturer and model on the receiver, so you can read its actual security claims rather than guessing?

    I would not go further than that on equipment I do not control.

    Part 4 moves to USB, keystroke injection from the Bad USB menu, and how to lock endpoints down.

    Other parts of the Flipper Zero series: part 1, part 2, part 3, part 4, part 5.

  • Hardware hacking, part 1: updating the firmware on a Pinecil V2

    Hardware hacking, part 1: updating the firmware on a Pinecil V2

    Part 1 of 7 in the Hardware hacking series.

    I do most of my soldering and rework with a Pine64 Pinecil V2, and it is the first tool in this series because it is the one I touch every time I open a device. This series is about the bench gear I actually own: the Pinecil, an FTDI serial cable, a cheap logic analyzer, an XGecu T48 programmer and a probe frame for chips I don’t want to desolder. Everything here is done on my own hardware, from the defender’s side. If you understand how a device looks from the inside, you know what an attacker with ten minutes and a screwdriver can learn about it.

    Firmware first, then. A soldering iron that talks USB-C power delivery and Bluetooth is a small computer, and it deserves updates like any other. The V2 runs IronOS, the open source firmware by Ralim, on a Bouffalo BL706 chip.

    Which release and which file

    As I write this (October 2026) the latest stable IronOS release is v2.23, published on 31 August 2025. Its changelog has a few items I care about: Bluetooth LE is now off by default on the V2, the default USB-PD negotiation uses a more conservative profile, and there are fixes for PD supplies that caused reboots with some laptops. Bluetooth support for the V2 first arrived in v2.21, so if you are on v2.21 or v2.22 this update switches it off by default, and anything older has no Bluetooth to switch off.

    The release page lists assets for every supported iron. For the V2 you want Pinecilv2.zip. The similarly named Pinecil.zip is for the original V1, which uses a different chip and a different file format.

    The Assets list of the IronOS v2.23 release on GitHub, with Pinecilv2.zip boxed in red and Pinecil.zip boxed in orange. Red annotation labels were added to the screenshot.

    That screenshot is the real GitHub page, with the two boxes and labels added by me. GitHub also shows a SHA-256 digest for each asset. After downloading I compared it with the local file:

    $ sha256sum Pinecilv2.zip
    fe75a2e3227ba53b7fdd8b0d8a362fc2ff0af264ce442ba76e26f21a3abb7503  Pinecilv2.zip

    That matches the full SHA-256 digest GitHub’s API reports for the asset (the release page truncates it, so compare all 64 characters, not just the start). It only proves the file I got is the file GitHub is serving, which is still worth ten seconds. Inside the zip there is one .bin per language. I use Pinecilv2_EN.bin, 194,508 bytes in this release. Ignore the .dfu and .hex files for a normal update. Pine64’s wiki is explicit that flashing a .dfu as firmware leaves the iron unable to boot.

    Putting the iron in flash mode

    This is the one step you can’t do from software. First a safety note: let the tip cool completely and park the iron in its stand before you unplug it or start fiddling with cables. If it boots normally instead of staying black, it is a live iron on a USB supply, so unplug it straight away rather than pressing buttons. Now unplug everything, hold the minus button, and only then plug the USB-C cable into the iron. Pine64’s wiki says to keep holding for around 10 to 15 seconds after plugging in. If the screen stays black, you are in flash mode. If it boots normally, unplug and try again, because the order matters.

    Screenshot of the Flash Mode section of the Pine64 wiki page on Pinecil firmware, including the warning not to use the DC barrel jack while updating.

    The warning at the top of that section is not decoration. Pine64’s wiki says that using the DC barrel jack during a firmware update may destroy both your computer and the Pinecil, and that the iron is designed to use only one power port at a time. So for the whole update only the USB-C cable goes in: no barrel jack, no second supply, nothing on the other rear port.

    I have not photographed the iron’s own screen and buttons for this post yet, so the black-screen state and the on-device menus are described here rather than shown. In flash mode the iron does not appear as a USB drive. According to the wiki it shows up on Linux as a ttyACM serial device, and Windows lists it as a COM port.

    Flashing with blisp

    blisp is Pine64’s command line flasher for Bouffalo chips, and it is what I would reach for first. Version 0.0.5 (August 2025) is the current release and there are prebuilt binaries for Linux (x86_64, aarch64, armv7, riscv64), macOS and Windows. Unzip it, put it next to the .bin and run:

    sudo ./blisp write -c bl70x --reset Pinecilv2_EN.bin

    On Windows it is .\blisp.exe write -c bl70x --reset .\Pinecilv2_EN.bin from an administrator terminal, and the leading .\ matters. I can’t plug the iron into the machine I used for the screenshots, so I ran the real tool with no iron attached, in a throwaway Ubuntu 24.04 container:

    Terminal output in a dark theme showing blisp v0.0.5, the sha256sum of Pinecilv2.zip, the size of Pinecilv2_EN.bin, and the blisp write command ending in Device not found with exit status 3.

    “Device not found” with exit status 3 is what you get when the iron isn’t in flash mode, or when the cable is charge-only. If you see it with the iron attached, check the troubleshooting list below. I haven’t seen the success output because there was no iron on that machine. With --reset, blisp restarts the chip after the write.

    PineFlash, the GUI option

    Pine64’s wiki lists PineFlash as the GUI route. It is a community project by Spagett1, not a Pine64 product, and its latest release is 0.5.5 from April 2024. I ran the Linux AppImage in the same kind of container, under a virtual display:

    The PineFlash 0.5.5 window with the status Soldering Iron Disconnected, Pinecil V2 selected, and an empty version dropdown.

    The layout is simple: pick the iron, pick a version, pick a language, press Update. The Connection Guide section repeats the hold-minus instruction with a diagram. The catch showed up in the log panel:

    PineFlash log panel showing the line Invalid json downloaded, could not fetch versions, highlighted with a red box and an annotation label.

    On my run the version list never filled in, and the only entry in the dropdown was “Custom”. My network could reach GitHub fine, so it is probably not a plain connectivity problem, although I haven’t ruled out something like API rate limiting. I suspect the app is tripping on the newer release layout (v2.23 ships a metadata.zip asset), but I haven’t confirmed that. The practical result: with PineFlash 0.5.5 you may need to download the .bin yourself and use the custom file option, at which point blisp is less work. I haven’t tested a real flash with PineFlash, so I won’t claim it works end to end.

    Checking the version and getting back out

    After the flash, unplug the iron and plug it in normally. Pine64’s wiki says to hold the minus button for a couple of seconds on the home screen to see the firmware version, and it should read v2.23. I would rather show you my own screen than paraphrase a wiki, so a photo of it will go in here later.

    Pine64’s wiki says the Pinecil is very hard to brick with a firmware update, and that re-flashing usually fixes a bad one: enter flash mode again and write a known good file. The flash-mode bootloader sits in the chip’s ROM, which is why that route is always available. To roll back, download the older release from the same GitHub page, take its Pinecilv2_EN.bin and run the same command. If something goes wrong, work through these in order:

    • Hold the minus button before the cable goes in, and keep holding for 10 seconds or more.
    • Try another cable. Some USB-A to USB-C cables carry power only.
    • Try another port, or another computer. Some laptop USB-C ports negotiate badly, and a plain USB-A port often works.
    • On Windows, name the port with -p COM3 (or whatever Device Manager shows). On macOS, allow the new device when prompted, then replug in flash mode.
    • Make sure you used the .bin, not the .dfu.

    Next up is the rest of the bench: the tools I use for opening up a device, and why each one earns its place on the desk.

    Other parts of the Hardware hacking series: 1, updating the firmware on a Pinecil V2; 2, the bench I work on; 3, using my 5V FTDI cable on 3.3V UARTs; 4, finding the UART console on a router I own; 5, reading SPI flash without wrecking it (publishes Nov 11); 6, decoding I2C and SPI with sigrok (publishes Nov 12); 7, JTAG and SWD basics (publishes Nov 13).

  • Flipper Zero, part 2: why so many office badges are weaker than they look

    Flipper Zero, part 2: why so many office badges are weaker than they look

    Part 2 of 5 in the Flipper Zero series. Part 1: what it actually is and what it can’t do.

    Most buildings I have walked into run on one of four card technologies, and the reception desk usually cannot tell you which. That matters, because the four have wildly different security properties and they look identical in a wallet. This part is about the two badge radios on the Flipper Zero, 125 kHz and 13.56 MHz, and what they reveal when you point them at your own cards.

    Same rules as part 1: hardware I own, cards I own, and a defender’s goal. A work badge usually belongs to your employer, so treat it as someone else’s card and get written permission before testing it. If you are auditing a site, get written permission first.

    Two radios, four families

    Comparison of four badge families (125 kHz proximity, MIFARE Classic, iCLASS Classic, DESFire/Seos) showing what each card sends, whether it can be copied, and a verdict, with Wiegand versus OSDP Secure Channel underneath

    The low frequency radio at 125 kHz reads old proximity cards. EM4100 and HID Prox are the common ones. The high frequency radio at 13.56 MHz covers ISO 14443 and the NFC family: MIFARE Classic, MIFARE Ultralight, DESFire and the rest. HID’s iCLASS also lives at 13.56 MHz, but it is based on ISO 15693 and Flipper handles it separately in its Picopass app. The Flipper’s main menu sorts the cards under “125 kHz RFID” and “NFC”, and its read screen tells you which family it found for most of them. That screen is the first step of the audit, not the whole of it.

    125 kHz: an ID, not a credential

    An EM4100 tag transmits a 40 bit value, over and over, to anything that powers it up. There is no key, no challenge and no secret. HID Prox is much the same idea wrapped in the 26 bit Wiegand format called H10301: one parity bit, an 8 bit facility code, a 16 bit card number and a closing parity bit.

    You can decode that yourself in a few lines:

    def parse_h10301(bits: str):
        assert len(bits) == 26
        facility = int(bits[1:9], 2)
        card = int(bits[9:25], 2)
        return facility, card
    
    print(parse_h10301("00000001100000000000011111"))  # (3, 15)

    Sixteen bits of card number means 65,536 possible values per facility code, and facility codes are 8 bits. Nothing in the card proves it is the original. Copying one to a blank tag takes seconds with a Flipper or a ten dollar reader. I have never seen a reason to defend this technology beyond “we have not got round to replacing it”.

    MIFARE Classic: encryption that did not hold

    MIFARE Classic is the long-lived 13.56 MHz card from NXP, and it shipped with a proprietary cipher called Crypto1 and 48 bit keys. In 2008 researchers at Radboud University Nijmegen (Garcia and colleagues) published “Dismantling MIFARE Classic” at ESORICS, after Karsten Nohl and Henryk Plötz had reverse engineered the cipher from the chip. In 2009 a nested attack from the same group (IEEE S&P) recovered every key on a card given one known key, and Courtois published a variant that needed none. In 2015 Meijer and Verdult’s “hardnested” attack handled the hardened cards that NXP issued after the first breaks.

    In practice the weak point is often not the cipher at all. Many deployments leave sectors on the factory default key, FFFFFFFFFFFF, and many readers only look at the 4 or 7 byte UID. A UID is not a secret, and there are “magic” cards sold with a rewritable UID block. If your reader just checks the UID, the cryptography is irrelevant.

    You can see what you have without special tools. With libnfc and an ordinary USB reader:

    $ nfc-list
    ISO/IEC 14443A (106 kbps) target:
        ATQA (SENS_RES): 00  04
           UID (NFCID1): 04  a2  3b  1c
          SAK (SEL_RES): 08

    Those values are illustrative, not from a real card, but the SAK byte is real: 0x08 identifies a MIFARE Classic 1K, 0x18 a 4K. A DESFire card answers differently, with a SAK of 0x20 and ISO 14443-4 support. The Flipper shows the same information on its screen without any laptop.

    iCLASS: the shared key problem

    HID’s original iCLASS (now called iCLASS Classic) used a master key shared across the product line. Milosch Meriac extracted it in 2010, and the 2012 paper “Dismantling iClass and iClass Elite” went on to break the Elite key diversification as well. Once a master key is public, every site using the default configuration is exposed, whether it knows or not. HID’s replacements, iCLASS SE and the Seos credential, moved to modern cryptography.

    What good looks like

    The fix is a card that authenticates with a per-card key, using a standard algorithm, and a reader that actually checks. MIFARE DESFire EV2 and EV3 use AES with mutual authentication, and Seos does too. Whichever you choose, ask your vendor four questions:

    1. Does the card use AES, and are keys diversified per card?
    2. Does the reader verify the credential, or only read a UID?
    3. Is the reader-to-controller link OSDP with Secure Channel enabled and a unique key, not Wiegand or the default install key?
    4. Who holds the master keys, and where do they live?

    The third question is the one people forget. Wiegand sends the card number down a few wires in cleartext, so a tap on the cable behind the reader defeats the best card on the market. OSDP v2 adds an AES-128 secure channel and also lets you supervise the reader, so a pulled or replaced one raises an alarm. I would put reader wiring on the same priority list as the cards.

    One caveat, since I like to be straight about limits: even a strong card can be relayed. An attacker who stands near the victim and near the door can pass the radio exchange along in real time, and the crypto will happily succeed. Distance bounding is the research answer. Some cards, DESFire EV2 and EV3 among them, offer a proximity check feature, but I rarely see it switched on. Defend with a second factor at sensitive doors, or camera coverage, rather than assuming the card solves it.

    Migrating without a big bang

    Nobody swaps every card in a weekend. Dual technology readers that accept the old and new credential let you issue new cards gradually, with a cutoff date after which the legacy mode is disabled in the controller. Skipping that last step is the usual failure, so put it in the project plan with a name next to it. I have seen buildings run “temporary” legacy support for six years.

    Start with the audit. Tap your own badge on a reader that tells you what it is, write down the family, and compare it with the list above. If the answer is 125 kHz or MIFARE Classic, you have your project.

    Part 3 moves to Sub-GHz remotes, rolling codes and how gates should be built.

    Other parts of the Flipper Zero series: part 1, part 2, part 3, part 4, part 5.