$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: Recover password from I2C logic analyzer capture of LCD display. Solution: Decode PCF8574 I2C backpack protocol to HD44780 LCD in 4-bit mode, extract nibbles on EN pulse, group by sessions to recover each character as it was typed.
Our field agent cannot access the enemy base due to the password-protected internal gates, but observed that the password seemed to be partially displayed as it was typed into the security keypad. Thanks to an audacious mission, we were able to implant an embedded device into the wiring for the keypad's monitor, and intercepted some data. Your mission is to recover the password from the collected data.
Provided files:
op_pinpossible.logicdata — Saleae Logic 1.x logic analyzer capture (I2C bus)security_keypad.jpeg — photo of a QAPASS 16x2 LCD display (HD44780) with PCF8574 I2C backpack, showing "Enter Password"The photo shows a standard Arduino setup:
.logicdata — proprietary Saleae Logic 1.x format (NOT compatible with Logic 2 or sigrok)PCF8574 outputs 8 bits to a parallel port connected to the LCD in 4-bit mode:
Bit 0: RS (Register Select: 0=command, 1=data/character)
Bit 1: RW (Read/Write: 0=write)
Bit 2: EN (Enable: HIGH→LOW pulse to latch data)
Bit 3: BL (Backlight)
Bits 4-7: D4-D7 (high nibble of data in 4-bit mode)
Each nibble is transmitted via three I2C writes:
Two nibbles (high first, low second) form one byte. RS=0 → LCD command, RS=1 → character to display.
.logicdata is a proprietary binary format for Saleae Logic version 1.x. It:
Opened op_pinpossible.logicdata in Saleae Logic 1.2.40:
i2c.txt)Python script processes the CSV:
Found 36 sessions (matching the number of characters in the password). Each session is a full LCD redraw after entering the next character:
Session 1: "Enter Password" + "H" ← first character
Session 2: "Enter Password" + "*T" ← H masked, T visible
Session 3: "Enter Password" + "**B" ← HT masked, B visible
Session 4: "Enter Password" + "***{" ← HTB masked, { visible
...
Session 36: "Enter Password" + "***...***}" ← last character }
Pattern: all previously entered characters are displayed as *, while the last entered character is visible. Collecting the last visible character from each session:
H, T, B, {, 8, 4, d, _, d, 3, 5, 1, 9, n, _, c, 4, n, _, 1, 3, 4, d, _, 7, 0, _, 1, 3, 4, k, 5, !, d, @, }
After full password entry, the LCD displayed: "ACCESS GRANDED" and "SYSTEM DISARMED".
#!/usr/bin/env python3 """ Solve script for HackTheBox Mission Pinpossible Decodes I2C data from PCF8574 -> HD44780 LCD to recover password. PCF8574 pin mapping (standard Arduino LiquidCrystal_I2C): Bit 0: RS (Register Select: 0=command, 1=data/character) Bit 1: RW (Read/Write: 0=write) Bit 2: EN (Enable: pulse HIGH->LOW to latch data) Bit 3: Backlight Bits 4-7: D4-D7 (4-bit mode data nibble) HD44780 4-bit mode: each byte sent as two nibbles (high first, then low). Each nibble requires 3 I2C writes: 1. data | EN=0 (setup) 2. data | EN=1 (enable pulse high) <-- we capture nibble here 3. data | EN=0 (enable pulse low - data latched) """ import csv import re def parse_i2c_csv(filename): """Parse I2C CSV export from Saleae Logic.""" writes = [] with open(filename, "r") as f: reader = csv.reader(f) header = next(reader) # skip header for row in reader: if len(row) >= 5: time_s = float(row[0]) addr = int(row[2], 16) data = int(row[3], 16) rw = row[4].strip() if addr == 0x27 and rw == "Write": writes.append((time_s, data)) return writes def decode_lcd(writes): """ Decode PCF8574 I2C writes into LCD commands and data. Each nibble is sent as 3 writes: setup(EN=0), pulse(EN=1), latch(EN=0). We capture the nibble when EN=1 (the middle write). Two consecutive nibbles form one byte (high nibble first). """ # Extract nibbles when EN is HIGH nibbles = [] for time_s, data in writes: en = (data >> 2) & 1 if en == 1: rs = data & 0x01 # RS: 0=command, 1=data rw = (data >> 1) & 0x01 # RW: should be 0 for writes d4_7 = (data >> 4) & 0x0F nibbles.append((time_s, rs, d4_7)) # Combine nibble pairs into bytes results = [] for i in range(0, len(nibbles) - 1, 2): time1, rs1, high_nibble = nibbles[i] time2, rs2, low_nibble = nibbles[i + 1] byte_val = (high_nibble << 4) | low_nibble results.append((time1, rs1, byte_val)) return results def main(): filename = "i2c.txt" writes = parse_i2c_csv(filename) print(f"Total I2C writes to 0x27: {len(writes)}") results = decode_lcd(writes) print(f"Decoded LCD bytes: {len(results)}") # Group by time gaps (sessions separated by ~0.6s) sessions = [] current_session = [] prev_time = 0 for t, rs, v in results: if t - prev_time > 0.1 and current_session: sessions.append(current_session) current_session = [] current_session.append((t, rs, v)) prev_time = t if current_session: sessions.append(current_session) print(f"Found {len(sessions)} sessions\n") # Decode each session all_data_text = [] for idx, session in enumerate(sessions): cmds = [(t, v) for t, rs, v in session if rs == 0] chars = [(t, v) for t, rs, v in session if rs == 1] text = "".join(chr(v) for t, v in chars if 0x20 <= v <= 0x7E) print(f"--- Session {idx + 1} (t={session[0][0]:.3f}s) ---") print(f" Commands: {' '.join(f'{v:02X}' for t, v in cmds)}") char_desc = [] for t, v in chars: if 0x20 <= v <= 0x7E: char_desc.append(f"'{chr(v)}'") else: char_desc.append(f"0x{v:02X}") print(f" Characters: {' '.join(char_desc)}") print(f" Text: '{text}'") all_data_text.append(text) print(f"\n{'=' * 60}") print("All decoded text:") for i, text in enumerate(all_data_text): print(f" Session {i + 1}: '{text}'") full_text = "".join(all_data_text) print(f"\nFull text: '{full_text}'") # Look for flag flags = re.findall(r"HTB\{[^}]+\}", full_text) if flags: print(f"\n*** FLAG: {flags[0]} ***") else: print(f"\nPassword/data on LCD: '{full_text}'") if __name__ == "__main__": main()
Use this technique when:
.logicdata file (proprietary Saleae Logic 1.x format).logicdata format is ONLY for version 1.x. Logic 2 uses .sal format. Download legacy: https://downloads.saleae.com/logic/1.2.40/*. Each LCD redraw is a separate "session" in I2C traffic$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md