littlefs
Driving a discontinued Saleae app headlessly through its socket API to open an undocumented capture format.
NNS{l0g1c_an4ly53rs_c4n_pr0vid3_ins1ght_int0_th3_w0rk1ng5_0f_4n_3mb3dded_sy5t3m} Challenge
A single file, littlefs.logicdata: a Saleae Logic 1.x capture of an SPI bus (channel 0 = MOSI, 1 = CS, 2 = SCK, 3 = MISO) between an nRF5340 and an external SPI NOR flash chip holding a littlefs filesystem with flag.txt. The firmware (zephyrapp/) just automounts littlefs and reads /lfs1/flag.txt
- the flag itself is never printed to a UART we can see, only exchanged over that SPI bus, which is what got captured.
The blocker: .logicdata is Saleae’s legacy, undocumented, Boost::serialization binary format, openable only by the deprecated “Logic 1.x” application (not the current “Logic 2”, which uses an unrelated .sal format and doesn’t understand .logicdata at all). No existing third-party parser for the binary format exists (even Saleae has said their own newer tooling doesn’t fully understand it).
Approach - drive Logic 1.x headlessly via its socket API
Rather than reverse-engineer the binary format, or need hands-on GUI control (no desktop-GUI automation available, only a browser pane):
-
Downloaded the official “Logic 1.2.40” Windows standalone build from saleae.com’s legacy downloads page (the last Logic 1.x release) and launched it.
-
Logic 1.x ships a legacy TCP Socket API (
SaleaeSocketApi, default port 10429) that lets an external script fully drive the app - includingLOAD_FROM_FILEto open a saved .logicdata capture andEXPORT_DATA2to export loaded data to CSV/VCD/binary - without ever touching the GUI. This is exactly the missing piece: no manual interaction needed once the server is listening. -
The socket server is disabled by default and normally must be turned on via Options -> Developer -> “Enable scripting socket server” in the GUI. Since the app is a fully custom-drawn Qt UI (no accessible control names for Windows UI Automation to target reliably), it was simpler to go straight to the setting’s storage: Logic 1.x keeps its preferences in
%AppData%\Saleae LLC\Logic\settings.xml, itself a boost::serialization XML archive (human-readable, unlike the binary capture format). Found<mEnableSocketServer>0</mEnableSocketServer>and<mTcpSocketServerPort>10429</mTcpSocketServerPort>, closed the app, flipped the flag to1directly in the XML, and relaunched -Test-NetConnection 127.0.0.1 -Port 10429confirmed the socket server came up with no GUI interaction required at all. -
Wrote a ~15-line raw-socket Python client (commands are NUL-terminated, responses end in
ACK/NAK) and ran, against the real SaleaeSocketApi.cs source pulled from GitHub for the exact command grammar:LOAD_FROM_FILE, <absolute path to littlefs.logicdata>IS_PROCESSING_COMPLETE(polled untilTRUE)EXPORT_DATA2, <path>.vcd, ALL_CHANNELS, ALL_TIME, VCDVCD was chosen over CSV because the wire protocol requires no further format-specific parameters for it (“no settings” for VCD per the client source) - the whole capture came out as a plain-text, trivially-parseable Value Change Dump. -
Parsed the VCD (regex over
$var/#<time>/0<sym>/1<sym>lines) and decoded the SPI bus in Python: track CS (active-low) to delimit transactions, and on every SCK rising edge while CS is low, sample MOSI and MISO into MSB-first byte streams (standard SPI mode 0). -
Rather than reconstruct the whole flash image and mount it as littlefs, just concatenated every transaction’s MOSI stream and searched it directly for
NNS{- the flag showed up immediately, in a page-program (write) transaction: the capture actually covers the flash being provisioned with the littlefs image (visible ASCII filenames “littlefs” and “flag.txt” appear as plaintext in earlier MOSI page- program payloads too), so the raw flag bytes are sent from MCU to flash chip over MOSI right there in the trace - no filesystem parsing needed. (A handful of stray 0x00 bytes landed mid-string, an artifact of the page-boundary chunking of my transaction segmentation; simply stripping null bytes from the matched region recovered the flag cleanly.)
Flag
NNS{l0g1c_an4ly53rs_c4n_pr0vid3_ins1ght_int0_th3_w0rk1ng5_0f_4n_3mb3dded_sy5t3m}