HTB: Low Logic Challenge

Low Logic - HackTheBox Challenge Writeup

Challenge Information

FieldValue
NameLow Logic
CategoryMisc / Hardware
DifficultyVery Easy
Authord3vn0mi

Description

I have this simple chip, I want you to understand how it’s works and then give me the output.

The challenge ships a schematic image of a small transistor-level circuit and a CSV of input vectors. The goal is to reverse-engineer the boolean function the circuit implements and evaluate it against the provided inputs to recover the flag.

Solution

The challenge archive contained two files: chip.jpg, a hand-drawn RTL (resistor-transistor logic) schematic built from NPN transistors and 1kΩ resistors, and input.csv, 192 rows of 4-bit input vectors (in0–in3).

The circuit has four inputs and one output, built from three transistor-pair “blocks” that at a glance all look identical. The trick is that they aren’t: two of the blocks stack their transistors in series (an AND gate), while the third wires its transistors in parallel (an OR gate). Reading all three as OR — the intuitive read at normal zoom — produces garbage output. Only a pixel-level inspection of how the emitters and collectors are actually wired reveals the correct logic function:

OUT = (IN0 AND IN1) OR (IN2 AND IN3)

With the correct boolean function in hand, each of the 192 CSV rows produces one output bit. Concatenated MSB-first, 192 bits packs cleanly into 24 ASCII bytes — the flag.

Key Steps

1. Recover the archive contents

The staged chip.jpg initially showed as a 0-byte artifact. The real files were inside the challenge zip and needed extraction with the standard HTB archive password:

Terminal window
# List the zip contents to confirm what's actually inside
unzip -l /out/<challenge-id>.zip
# => chip.jpg (47KB) + input.csv (hidden, not staged separately)
# Extract with the standard HTB zip password
unzip -o -P hackthebox /out/<challenge-id>.zip

2. Inspect the input data shape

Terminal window
head -20 input.csv
wc -l input.csv

192 data rows, 4 boolean input columns (in0..in3) → 192 output bits total, which divides evenly into 24 bytes (192 / 8) — a strong hint the output is a 24-character ASCII flag.

3. Trace the schematic at pixel-level zoom

The full schematic looked like three copies of the same paired-transistor block. Cropping and enlarging each block individually was necessary to tell them apart:

from PIL import Image
im = Image.open('chip.jpg')
# Crop and heavily upscale each transistor block for pixel-level tracing
im.crop((390, 190, 460, 330)).resize((70*8, 140*8), Image.NEAREST).save('z_top.png')
im.crop((390, 455, 460, 630)).resize((70*8, 175*8), Image.NEAREST).save('z_bot.png')
im.crop((90, 20, 480, 320)).resize((390*2, 300*2)).save('z_right.png')

Findings from the zoomed crops:

  • Left block (IN0, IN1): upper transistor’s emitter feeds directly into the lower transistor’s collector — a series stack. Top collector to +6V, bottom emitter to a 1kΩ pull-down. Series NPN pairs behave as an AND gate (both must conduct to complete the path to ground).
  • Middle block (IN2, IN3): identical series-stack topology → also an AND gate.
  • Right block: the two transistors sit side-by-side, both collectors tied to +6V, both emitters joined at a shared 1kΩ pull-down — a parallel emitter-follower arrangement. This behaves as an OR gate (either transistor conducting pulls the shared node high).

Net function: OUT = (IN0 & IN1) | (IN2 & IN3)

4. Evaluate the function against all 192 input rows and decode

import csv
rows = list(csv.DictReader(open('input.csv')))
print('rows:', len(rows)) # 192
# Evaluate OUT = (IN0 AND IN1) OR (IN2 AND IN3) for every row
bits = ''.join(
str((int(r['in0']) & int(r['in1'])) | (int(r['in2']) & int(r['in3'])))
for r in rows
)
# 192 bits -> 24 bytes, MSB-first
flag = ''.join(
chr(int(bits[i:i+8], 2)) for i in range(0, len(bits), 8)
)
print(flag)

This decoded cleanly on the first try into the flag string.

HTB{REDACTED}

Tools Used

  • unzip — password-protected archive extraction
  • Python 3 + Pillow (PIL) — image cropping/upscaling for schematic pixel-tracing
  • Python 3 csv module — parsing the 192-row input vector table
  • Manual circuit tracing (transistor topology analysis)

Key Learnings

  • Series vs. parallel NPN transistor pairs are the whole challenge. Two stacked (series) NPNs form an AND gate — both must conduct to complete the current path to the pull-down resistor. Two side-by-side NPNs sharing a single pull-down (parallel) form an OR gate — either conducting is enough to pull the shared node high. At normal zoom, both layouts can look deceptively similar; the distinction only becomes clear when tracing exactly where each transistor’s collector and emitter terminate.
  • A single misread gate silently poisons the entire output. Assuming all three blocks were OR gates produced 192 bits of plausible-looking but ultimately garbage output — there was no error or crash to signal the mistake, only a non-ASCII/non-flag-shaped result. When decoded output doesn’t look right, re-verify the boolean model before trusting the bit count or encoding.
  • Data shape hints the encoding. 192 rows of a single-bit-output function cleanly dividing into 24 bytes was a strong early signal that the answer was a flag-length ASCII string, which helped confirm MSB-first bit packing was the right decode strategy.
  • Check for incompletely staged archives before assuming a file is corrupt. A 0-byte chip.jpg wasn’t actually broken — it just hadn’t been extracted from the accompanying zip yet, alongside a second file (input.csv) that wasn’t staged separately at all.