UMassCTF 2026 - Smart Brick v2

I managed to get my hands on a design files from LEGO HQ. Apparently it is the design for the new Smart Brick v2. I want to analyze it but I don’t have the hardware to do so. Can you help me figure out what it does?? 我设法从乐高总部拿到了一份设计文件。我想对它进行分析,但我没有分析所需的硬件。你能帮我弄明白它是做什么的吗?

Hint 1: I think I have seen that file format before on an popular open-source eCAD software but I can’t remember which one… (我好像在一款流行的开源 eCAD 软件上见过这种文件格式,但记不清是哪一款了) Hint 2: Hmmm… there seem to be 7 inputs, I wonder what encoding uses only 7 bits? (嗯……似乎有 7 个输入,我想知道什么编码只使用 7 位?) Hint 3: I found a great python library to interact with this file programmatically: kiutils (我发现了一个很棒的 Python 库,可以通过编程与该文件交互:kiutils)

Initial Analysis

The challenge provides a KiCad PCB design file (smart-brick-v2.kicad_pcb) and asks to analyze it to discover its function. Hints suggest that the board uses a 7-bit encoding (ASCII) and points toward the kiutils Python library for programmatic analysis.

Opening the file or inspecting the raw text reveals it is a KiCad 9.0 board file. Key features identified:

  • Inputs: 7 nets labeled /IN0 through /IN6. This confirms the hint about 7-bit encoding (ASCII).
  • Outputs: 19 LEDs (D1D19) driven by 19 MOSFETs (Q1Q19).
  • Logic: A large array of 74LS series discrete logic gates (AND, NAND, OR, NOR, XOR, NOT).

The circuit is a combinational logic “decoder” where each LED represents a character in the flag. An LED will light up if the 7-bit input matches a specific character programmed into the logic gates for that stage.

Solution

To solve this without physical hardware or a manual schematic trace, we can automate the logic extraction and simulation using Python.

1. Technical Approach

Step 1: Parsing the PCB Using the kiutils library, we extract all footprints, their values (e.g., 74LS00), and the nets connected to their pins.

Step 2: Mapping Logic Gates Each 74LS chip contains multiple gates. For example: - 74LS00: Quad 2-input NAND. - 74LS08: Quad 2-input AND. - 74LS86: Quad 2-input XOR. - 74LS21: Dual 4-input AND.

We build a dependency graph where each net’s value is determined by the boolean operation of its input nets.

Step 3: Simulation Since there are only 128 possible values for a 7-bit input (ASCII 0–127), we can brute-force the inputs for each of the 19 output stages. For each character c from 0 to 127, we propagate the values through the logic gate graph and check which MOSFET gates (Q1Q19) are pulled HIGH.

2. Execution

The simulation reveals that each LED corresponds to exactly one ASCII character:

LED Hex Char LED Hex Char
D1 0x55 U D11 0x68 h
D2 0x4D M D12 0x33 3
D3 0x41 A D13 0x5F _
D4 0x53 S D14 0x47 G
D5 0x53 S D15 0x34 4
D6 0x7B { D16 0x74 t
D7 0x49 I D17 0x33 3
D8 0x6E n D18 0x73 s
D9 0x5F _ D19 0x7D }
D10 0x54 T

3. Simulation Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import re
from kiutils.board import Board

def get_logic():
board = Board.from_file('smart-brick-v2.kicad_pcb')

# Map components by reference
components = {}
for fp in board.footprints:
ref = fp.properties.get('Reference', '')
val = fp.properties.get('Value', '')

pins = {}
for pad in fp.pads:
if pad.net:
pins[pad.number] = pad.net.name

components[ref] = {
'value': val,
'pins': pins
}

# Define gate logic
# (inputs, output)
gate_defs = {
'74LS00': [(('1', '2'), '3'), (('4', '5'), '6'), (('9', '10'), '8'), (('12', '13'), '11')], # NAND
'74LS02': [(('2', '3'), '1'), (('5', '6'), '4'), (('8', '9'), '10'), (('11', '12'), '13')], # NOR
'74LS04': [(('1',), '2'), (('3',), '4'), (('5',), '6'), (('9',), '8'), (('11',), '10'), (('13',), '12')], # NOT
'74LS08': [(('1', '2'), '3'), (('4', '5'), '6'), (('9', '10'), '8'), (('12', '13'), '11')], # AND
'74LS20': [(('1', '2', '4', '5'), '6'), (('9', '10', '12', '13'), '8')], # NAND 4
'74LS21': [(('1', '2', '4', '5'), '6'), (('9', '10', '12', '13'), '8')], # AND 4
'74LS27': [(('1', '2', '13'), '12'), (('3', '4', '5'), '6'), (('9', '10', '11'), '8')], # NOR 3
'74LS32': [(('1', '2'), '3'), (('4', '5'), '6'), (('9', '10'), '8'), (('12', '13'), '11')], # OR
'74LS86': [(('1', '2'), '3'), (('4', '5'), '6'), (('9', '10'), '8'), (('12', '13'), '11')], # XOR
}

# Build the net dependency graph
net_logic = {}

for ref, comp in components.items():
val = comp['value']
if val in gate_defs:
for inputs, output in gate_defs[val]:
if output in comp['pins']:
out_net = comp['pins'][output]
in_nets = [comp['pins'][i] for i in inputs if i in comp['pins']]
if len(in_nets) == len(inputs):
op = val[4:] # 00, 02, etc.
net_logic[out_net] = (op, in_nets)

# MOSFET gates driving LEDs
led_nets = []
for i in range(1, 20):
ref = f'Q{i}'
if ref in components:
if '1' in components[ref]['pins']:
led_nets.append(components[ref]['pins']['1'])

# Simulation function
def simulate(inputs_bits):
vals = {f'/IN{i}': inputs_bits[i] for i in range(7)}
vals['GND'] = False
vals['+5V'] = True

memo = {}
def get_val(net):
if net in vals: return vals[net]
if net in memo: return memo[net]
if net not in net_logic: return False

op, ins = net_logic[net]
in_vals = [get_val(i) for i in ins]

if op == '00': return not (in_vals[0] and in_vals[1])
elif op == '02': return not (in_vals[0] or in_vals[1])
elif op == '04': return not in_vals[0]
elif op == '08': return in_vals[0] and in_vals[1]
elif op == '20': return not all(in_vals)
elif op == '21': return all(in_vals)
elif op == '27': return not any(in_vals)
elif op == '32': return any(in_vals)
elif op == '86': return in_vals[0] ^ in_vals[1]
else: return False

memo[net] = res
return res

return [get_val(ln) for ln in led_nets]

# Brute force 7-bit ASCII
for i in range(19):
print(f"LED {i+1}: ", end='')
for code in range(128):
bits = [(code >> j) & 1 == 1 for j in range(7)]
outputs = simulate(bits)
if outputs[i]:
print(f"'{chr(code)}' (0x{code:02x})", end=' ')
print()

get_logic()

Flag

UMASS{In_Th3_G4t3s}