Hardware is a Scam

Description: Maybe software isn’t so bad after all… My current password l3ak{...} seems to be working fine, but something tells me someone else has been messing around on my machine 💀. Note: This challenge is the revenge challenge for Software is a scam. It is suggested that you solve that one first.

Solved by: Vitruvian, hamaj

After solving the electronics homework, we got a seizure by just looking at this circuit.

auth2

c1 through c4 is the same as the challenge Software is a Scam.

~(((c1*c4) + c1) ^ (c2)) = 0x5c
~((c1 & c3) || (c2 ^ c4)) = 0x87
(c3 ^ c4) = 0x0a 
(c2 - c1) = 0xe7
(c3 - c1) > 0xf4

On solving this set of equations we got 2 solutions, which were l3ak and L3AK.

The description mentioned that the current password worked fine, which is l3ak{...}. We ignored L3AK, for now.

So we are assuming:

Pin Character
c1 l
c2 3
c3 a
c4 k
c5 {
c34 }

Divider Logic

If we observe every gate connected to a divider , we observe that all of the gates are commutative gates.

Which means a <gate> b = b <gate> a.

Commutative gates:

Example:

Here we can derive the output of the divider as:

(c15 | c24) / (c24 | c14) 

We know that OR is a commutative gate (which means a or b = b or a), so the output will be:

((c15 | c24) / (c24 | c14)) = 1

If we look at all the inputs of all dividers, it follows the same logic:

(a <gate> b) / (b <gate> a) = 1

We checked for a couple of them and assumed all were this exact logic because it doesn’t make sense to solve the whole circuit, it had to be a reduction of sorts.

Equation derivation:
c6 - (c1>>5) mul (1 OR 1<<4) = 20
c7 - (c3>>5) mul (1 OR 1<<6) = b2
c8 - (c4>>5) mul ((1 OR 1<<2) OR 1<<3) = 49
c9 + (c1>>5) mul ((1<<3 OR 1<<4) OR 1<<5) = db
c10 - (c3>>5) mul ((1<<3) OR (1<<1 OR 1<<2)) = 48
c11 + (c4>>5) mul (((1 OR 1<<4) OR 1<<6) OR 1<<7) = d2
c12 + (c1>>5) mul (((1<<1 OR 1<<3) OR 1<<4) OR 1<<5) = e3
c13 XOR ((1 OR 1<<6) mul (c3>>5)) = f0
c14 - (c4>>5) mul (1<<2) = 57
c15 + (c1>>5) mul ((1 OR 1<<1) OR 1<<4) = 8e
c16 - (c3>>5) mul (1 OR 1<<6) = af
c17 + (c4>>5) mul ((1 OR 1<<4) OR 1<<5) = c6
c18 + (c1>>5) mul (1 OR 1<<2) = 6e
c19 + (c3>>5) mul (((1 OR 1<<1) OR 1<<6) OR 1<<7) = b9
c20 + (c3>>5) mul ((1<<1 OR 1<<3) OR 1<<4) = 82
c21 - (c1>>5) mul ((1<<2) OR 1<<4) = 37
c22 - (c3>>5) mul ((1<<1) OR 1<<3) = 55
c23 - (c4>>5) mul ((1 OR 1<<3) OR 1<<5) = fc
c24 - (c1>>5) mul (((1 OR 1<<4) OR 1<<6) OR 1<<7) = bd
c25 - (c3>>5) mul ((1<<1 OR 1<<3) OR 1<<5) = f4
c26 - (c4>>5) mul (1<<4) = 14
c27 + (c1>>5) mul ((1 OR 1<<1) OR 1<<4) = 98
c28 XOR ((c3>>5) mul ((1<<1 OR 1<<2) OR 1<<3)) = 58
c29 + (c4>>5) mul ((1<<1 OR 1<<2) OR 1<<6) = 03
c30 - (c1>>5) mul (((1 OR 1<<1) OR 1<<4) OR 1<<5) = ce
c31 + (c3>>5) mul (1<<1 OR 1<<3) = 66
c32 XOR ((c4>>5) mul ((1<<1 OR 1<<4) OR 1<<6)) = c1
c33 XOR ((c1>>5) mul (1<<1 OR 1<<2)) = 2d

Final script after considering l3ak{} as known wrapper

from z3 import *

c1 = BitVec('c1', 8)
c3 = BitVec('c3', 8)
c4 = BitVec('c4', 8)

names = [f'c{i}' for i in range(6, 34)]
c = {i: BitVec(f'c{i}', 8) for i in range(6, 34)}

s1 = LShR(c1, 5)   
s3 = LShR(c3, 5)
s4 = LShR(c4, 5)

solver = Solver()
solver.add(c1 == 0x6c)
solver.add(c3 == 0x61)
solver.add(c4 == 0x6b)

eqs = [
    (6,  s1, 0x11, 0x20, 'sub'),
    (7,  s3, 0x41, 0xb2, 'sub'),
    (8,  s4, 0x0d, 0x49, 'sub'),
    (9,  s1, 0x38, 0xdb, 'add'),
    (10, s3, 0x0e, 0x48, 'sub'),
    (11, s4, 0xd1, 0xd2, 'add'),
    (12, s1, 0x3a, 0xe3, 'add'),
    (13, s3, 0x41, 0xf0, 'xor'),
    (14, s4, 0x04, 0x57, 'sub'),
    (15, s1, 0x13, 0x8e, 'add'),
    (16, s3, 0x41, 0xaf, 'sub'),
    (17, s4, 0x31, 0xc6, 'add'),
    (18, s1, 0x05, 0x6e, 'add'),
    (19, s3, 0xc3, 0xb9, 'add'),
    (20, s3, 0x1a, 0x82, 'add'),
    (21, s1, 0x14, 0x37, 'sub'),
    (22, s3, 0x0a, 0x55, 'sub'),
    (23, s4, 0x29, 0xfc, 'sub'),
    (24, s1, 0xd1, 0xbd, 'sub'),
    (25, s3, 0x2a, 0xf4, 'sub'),
    (26, s4, 0x10, 0x14, 'sub'),
    (27, s1, 0x13, 0x98, 'add'),
    (28, s3, 0x0e, 0x58, 'xor'),
    (29, s4, 0x46, 0x03, 'add'),
    (30, s1, 0x33, 0xce, 'sub'),
    (31, s3, 0x0a, 0x66, 'add'),
    (32, s4, 0x52, 0xc1, 'xor'),
    (33, s1, 0x06, 0x2d, 'xor'),
]

for idx, shift_expr, mask, target, op in eqs:
    var = c[idx]
    term = shift_expr * mask   
    if op == 'sub':
        solver.add(var - term == target)
    elif op == 'add':
        solver.add(var + term == target)
    elif op == 'xor':
        solver.add(var ^ term == target)

if solver.check() == sat:
    m = solver.model()
    def val(v):
        return m.eval(v, model_completion=True).as_long()

    print(f"c1 = 0x{val(c1):02x}")
    print(f"c3 = 0x{val(c3):02x}")
    print(f"c4 = 0x{val(c4):02x}")
    print()
    for i in range(6, 34):
        print(f"c{i} = 0x{val(c[i]):02x}")

    known = {1: 0x6c, 2: 0x33, 3: 0x61, 4: 0x6b, 5: 0x7b, 34: 0x7d}

    all_bytes = dict(known)
    all_bytes[1] = val(c1)
    all_bytes[3] = val(c3)
    all_bytes[4] = val(c4)
    for i in range(6, 34):
        all_bytes[i] = val(c[i])

    flag = ''.join(chr(all_bytes[i]) for i in sorted(all_bytes))
    print()
    print(f"{flag}")
else:
    print("UNSAT")

Output:

$ python3 solve.py
c1 = 0x6c
c3 = 0x61
c4 = 0x6b

c6 = 0x53
c7 = 0x75
c8 = 0x70
c9 = 0x33
c10 = 0x72
c11 = 0x5f
c12 = 0x35
c13 = 0x33
c14 = 0x63
c15 = 0x55
c16 = 0x72
c17 = 0x33
c18 = 0x5f
c19 = 0x70
c20 = 0x34
c21 = 0x73
c22 = 0x73
c23 = 0x77
c24 = 0x30
c25 = 0x72
c26 = 0x44
c27 = 0x5f
c28 = 0x72
c29 = 0x31
c30 = 0x67
c31 = 0x48
c32 = 0x37
c33 = 0x3f

l3ak{Sup3r_53cUr3_p4ssw0rD_r1gH7?}

This output is the original password that the author uses to unlock his system.

Unfortunately, this was not the actual flag. After chatting a bit on tickets and reading the description carefully, we understood what was the intended solution.

The description mentioned: My current password l3ak{...} seems to be working fine, but something tells me someone else has been messing around on my machine 💀.

After that we considered L3AK as the known wrapper

Final Script after considering L3AK as the compromised wrapper

from z3 import *

c1 = BitVec('c1', 8)
c3 = BitVec('c3', 8)
c4 = BitVec('c4', 8)

names = [f'c{i}' for i in range(6, 34)]
c = {i: BitVec(f'c{i}', 8) for i in range(6, 34)}

s1 = LShR(c1, 5)   
s3 = LShR(c3, 5)
s4 = LShR(c4, 5)

solver = Solver()
solver.add(c1 == 0x4c)
solver.add(c3 == 0x41)
solver.add(c4 == 0x4b)

eqs = [
    (6,  s1, 0x11, 0x20, 'sub'),
    (7,  s3, 0x41, 0xb2, 'sub'),
    (8,  s4, 0x0d, 0x49, 'sub'),
    (9,  s1, 0x38, 0xdb, 'add'),
    (10, s3, 0x0e, 0x48, 'sub'),
    (11, s4, 0xd1, 0xd2, 'add'),
    (12, s1, 0x3a, 0xe3, 'add'),
    (13, s3, 0x41, 0xf0, 'xor'),
    (14, s4, 0x04, 0x57, 'sub'),
    (15, s1, 0x13, 0x8e, 'add'),
    (16, s3, 0x41, 0xaf, 'sub'),
    (17, s4, 0x31, 0xc6, 'add'),
    (18, s1, 0x05, 0x6e, 'add'),
    (19, s3, 0xc3, 0xb9, 'add'),
    (20, s3, 0x1a, 0x82, 'add'),
    (21, s1, 0x14, 0x37, 'sub'),
    (22, s3, 0x0a, 0x55, 'sub'),
    (23, s4, 0x29, 0xfc, 'sub'),
    (24, s1, 0xd1, 0xbd, 'sub'),
    (25, s3, 0x2a, 0xf4, 'sub'),
    (26, s4, 0x10, 0x14, 'sub'),
    (27, s1, 0x13, 0x98, 'add'),
    (28, s3, 0x0e, 0x58, 'xor'),
    (29, s4, 0x46, 0x03, 'add'),
    (30, s1, 0x33, 0xce, 'sub'),
    (31, s3, 0x0a, 0x66, 'add'),
    (32, s4, 0x52, 0xc1, 'xor'),
    (33, s1, 0x06, 0x2d, 'xor'),
]

for idx, shift_expr, mask, target, op in eqs:
    var = c[idx]
    term = shift_expr * mask   
    if op == 'sub':
        solver.add(var - term == target)
    elif op == 'add':
        solver.add(var + term == target)
    elif op == 'xor':
        solver.add(var ^ term == target)

if solver.check() == sat:
    m = solver.model()
    def val(v):
        return m.eval(v, model_completion=True).as_long()

    print(f"c1 = 0x{val(c1):02x}")
    print(f"c3 = 0x{val(c3):02x}")
    print(f"c4 = 0x{val(c4):02x}")
    print()
    for i in range(6, 34):
        print(f"c{i} = 0x{val(c[i]):02x}")

    known = {1: 0x4c, 2: 0x33, 3: 0x41, 4: 0x4b, 5: 0x7b, 34: 0x7d}

    all_bytes = dict(known)
    all_bytes[1] = val(c1)
    all_bytes[3] = val(c3)
    all_bytes[4] = val(c4)
    for i in range(6, 34):
        all_bytes[i] = val(c[i])

    flag = ''.join(chr(all_bytes[i]) for i in sorted(all_bytes))
    print()
    print(f"{flag}")
else:
    print("UNSAT")

Output:

$ python3 solve.py
c1 = 0x4c
c3 = 0x41
c4 = 0x4b

c6 = 0x42
c7 = 0x34
c8 = 0x63
c9 = 0x6b
c10 = 0x64
c11 = 0x30
c12 = 0x6f
c13 = 0x72
c14 = 0x5f
c15 = 0x68
c16 = 0x31
c17 = 0x64
c18 = 0x64
c19 = 0x33
c20 = 0x4e
c21 = 0x5f
c22 = 0x69
c23 = 0x4e
c24 = 0x5f
c25 = 0x48
c26 = 0x34
c27 = 0x72
c28 = 0x44
c29 = 0x77
c30 = 0x34
c31 = 0x52
c32 = 0x65
c33 = 0x21

L3AK{B4ckd0or_h1dd3N_iN_H4rDw4Re!}

Final flag:

L3AK{B4ckd0or_h1dd3N_iN_H4rDw4Re!}