31c3 CTF - safelock (signals20)

For the 31c3 CTF, Eindbazen and fail0verflow joined forces as 0xffa, the Final Fail Alliance.
Don't miss out on other write-ups at Eindbazen's site!
safelock
Signals (20 pts)
-------------------
This is the circuit of a safe lock. Get the key to open it!
http://188.40.18.86/safelock/

It's neither about webtronics nor ngspice. Disregard bugs in both.

If you want to write spice code directly, use something like this

cat test.cir | curl --data-binary '@-' http://188.40.18.86/safelock/contest_spice/spice.cgi

Interesting: a (terribad) web interface for SPICE, with a “black box” circuit that we have to interact with. The above diagram is the visual representation of the “black box” in the webapp, but don’t be fooled: it’s just a single SVG file, so we don’t know if it matches the actual implementation. We can only assume that it roughly does, though.

The web interface is absolutely terrible, but it does show the SPICE output generated, so let’s get an example to work with and get the interface out of the way. Just hooking up a voltage source to the target:

.title 31c3 ctf
.include challenge_common.cir
v 1 0
x 0 1 2 3 4 5 6 7 8 9 10 11 safelock
.end

So the block is just called safelock. And indeed, feeding this to the CGI script mentioned in the challenge description does indeed produce ngspice output.

Time to reverse engineer the circuit. What we have is a rather simple “combination lock”. Each of the key inputs (active low) is inverted with a 74HC04 (except for keys 8 and 9), and then NORed together in two groups, to produce an active low “any key” signal. The two outputs are then NANDed together with keys 8 and 9 (which are buffered through two inverters), yielding a strobe line that goes high every time any button is pressed. Separately, each of the keys 0-7 are connected to the input of a serial-in, parallel-out shift register clocked by the generated strobe (keys 8 and 9 are not used, they only trigger the strobe). Every time a key is pressed, a one bit is shifted into its associated shift register, and all the other shift registers just shift in a 0.

The idea is that the jumper blocks connected to each shift register allow you to choose one “position” for each key, thus specifying the order in which the keys must be pressed. The outputs are then averaged together with resistors and connected to an unspecificed “ASIC”. We can only guess that the lock will open when all of the outputs are high, and thus the average voltage level is equal to Vcc. One limitation is that each key may only be used once, because there is only one shared resistor for each block (jumpering together two jumpers would short circuit two outputs of the shift register).

We still don’t know how to get any output from the system though - there are no output pins. We don’t know if the ASIC will do anything “special”. We can’t brute force the combination - SPICE is slow and there are too many combinations.

But we can still attack it.

The trick is to realize that the entire circuit is built out of HC series chips. These are CMOS chips which have essentially zero static power consumption - the vast majority of the power in the circuit is spent in any resistors through which current is flowing. Ignoring the ASIC for the moment, what we have at the output of the shift registers is 8 resistors that connect 8 CMOS outputs to a common point. When all the outputs are 0 (or 1), no current flows. But as soon as one output disagrees, current will be dissipated in the resistors.

The plan is to place a resistor in series with the power supply (to measure current by measuring the voltage across it), then repeatedly press each button while monitoring the power consumption of the circuit. The shift register associated to it will start shifting in ones. As soon as the one bit hits an output that is jumpered to the resistor network, power consumption should increase.

Let’s try it out. We’ll press key 0 every 5ms, for 1ms:

.title 31c3 ctf
.include challenge_common.cir
.tran 1m 100m
v 12 0 DC 5
r 12 1 51
x 0 1 2 3 4 5 6 7 8 9 10 11 safelock

vt 2 0 PULSE(5 0 5m 0.1m 0.1m 1m 5m)
.print tran v(1) v(2)
.end

Feeding this into the challenge server, we get the following (abbreviated) output:

Solver: solver1@31c3ctf-spice
Warning: vt: no DC value, transient time 0 value used
(snip)
No. of Data Rows : 983
                            31c3 ctf 
                            Transient Analysis  Tue Dec 30 12:57:30  2014
-------------------------------------------------------------------------
Index   time            v(1)            v(2)            
-------------------------------------------------------------------------
0   0.000000e+00    4.819684e+00    5.000000e+00    
(snip)
12  5.000000e-03    4.819684e+00    5.000000e+00    
13  5.010000e-03    4.810454e+00    4.500000e+00    
14  5.030000e-03    4.791892e+00    3.500000e+00    
15  5.070000e-03    4.754450e+00    1.500000e+00    
16  5.100000e-03    4.726078e+00    0.000000e+00    
(snip)
33  6.025867e-03    4.723863e+00    0.000000e+00    
34  6.100000e-03    4.723860e+00    1.084202e-14    
35  6.100440e-03    4.724275e+00    2.201610e-02    
(snip)
42  6.179481e-03    4.798378e+00    3.974027e+00    
43  6.200000e-03    4.813087e+00    5.000000e+00    
(snip)
54  6.765384e-03    4.819338e+00    5.000000e+00    
(snip)
61  1.000000e-02    4.819684e+00    5.000000e+00    
62  1.001000e-02    4.810454e+00    4.500000e+00    
63  1.003000e-02    4.791892e+00    3.500000e+00    
64  1.007000e-02    4.754450e+00    1.500000e+00    
65  1.010000e-02    4.726078e+00    0.000000e+00    
(snip)
83  1.110000e-02    4.723860e+00    0.000000e+00    
84  1.110044e-02    4.724275e+00    2.201609e-02    
(snip)
91  1.117948e-02    4.798378e+00    3.974026e+00    
92  1.120000e-02    4.813087e+00    5.000000e+00    
(snip)
102 1.162485e-02    4.819063e+00    5.000000e+00    
(snip)
110 1.500000e-02    4.819684e+00    5.000000e+00    
111 1.501000e-02    4.810454e+00    4.500000e+00    
112 1.503000e-02    4.791892e+00    3.500000e+00    
113 1.507000e-02    4.754450e+00    1.500000e+00    
114 1.510000e-02    4.726078e+00    1.172396e-13    
115 1.510318e-02    4.725917e+00    0.000000e+00    
(snip)
132 1.610000e-02    4.723860e+00    0.000000e+00    
133 1.610044e-02    4.724275e+00    2.201609e-02    
(snip)
141 1.620000e-02    4.813087e+00    5.000000e+00    
(snip)
159 2.000000e-02    4.819684e+00    5.000000e+00    
160 2.001000e-02    4.810454e+00    4.500000e+00    
161 2.003000e-02    4.791892e+00    3.500000e+00    
162 2.007000e-02    4.754450e+00    1.500000e+00    
163 2.010000e-02    4.726078e+00    3.019807e-14    
164 2.010318e-02    4.725917e+00    0.000000e+00    
(snip)
218 2.105820e-02    4.702927e+00    0.000000e+00    
219 2.110000e-02    4.702907e+00    1.084202e-14    
(snip)
229 2.120000e-02    4.792868e+00    5.000000e+00    
(snip)
239 2.161755e-02    4.798821e+00    5.000000e+00    
240 2.175914e-02    4.799110e+00    5.000000e+00    
241 2.194370e-02    4.799306e+00    5.000000e+00    
242 2.214779e-02    4.799401e+00    5.000000e+00    
243 2.244963e-02    4.799453e+00    5.000000e+00    
(snip)

This is an analog simulation, so there is quite a bit of switching noise at the transitions. The circuit starts with a supply voltage of 4.819V, and at index 13 the button is pressed (the input takes some time to fall). After it rises again at index 43, the power consumption quickly stabilizes (index 54) back to 4.819V. This repeats for two further button presses. At index 160, the button is pressed for the fourth time. This time, we get a lot more sample rows - only 1ms elapses between rows 164 and 218, which means that there was more transient activity in the circuit. After the button is released at index 229, we wait, and by index 243, the voltage has stabilized to… 4.799 volts! The power consumption of the circuit increased after the 4th press of key 0.

This means that key 0 is present in the combination 4 positions from the end. Repeating this process, we find that we need to press each button the following number of times to trigger the increased power consumption:

0 - 4
1 - unused (no change)
2 - unused (no change)
3 - 1
4 - 7
5 - 3
6 - 8
7 - 5

This leaves positions 2, 6 undefined. At this point, we assume that buttons 8 and 9, though unconnected in the schematic (there are only 8 shift registers) may actually be in use, and we indeed find that button 8 triggers increased current draw after 2 presses, while button 9 is unused. That still leaves position 6 undefined. Again, the next step is to assume that the schematic has more lies, and that in fact it is possible to use a button twice (this would require each jumper output to have its own individual resistor, to avoid shorting out the shift register outputs). Going back through all the buttons and looking for further current draw, it turns out that button 8 also triggers an output at position 6.

Sorting by position and flipping the order of the buttons, we get the final combination:

Position  8 7 6 5 4 3 2 1
Button    6 4 8 7 0 5 8 3

Disappointingly, feeding the combination to the circuit doesn’t seem to do anything interesting. However, as it turns out, 64870583 is itself the flag. It seems the “ASIC” will remain a mystery…