snap circuits
04/26/2023
By: HELLOPERSON
Tags: crypto AngstromCTF-2023Problem Description:
Introducing Snap Circuits: Garbled Edition. P.S. You might find [this](https://github.com/defund/garble) useful.
Hints:
Reveal Hints
NoneWU: look up garbled circuits. read up on them.
algorithm: ask for AND and XOR tables between 0 and x wire for all flagbits.
since we know the 0 wire is 0 bit we can use this.
- xor corresponding entries in both tables
- locate duplicate
- use known wire labels, hash them, xor them with the corresponding square in the AND and XOR table.
- xor your results from the previous table. compare this with the duplicate. if the result is equal to the duplicate, then the x wire is a 1 bit. otherwise, it is a 0 bit.
proof is left as an exercise for the reader.
output.txt obtaining script:
from pwn import *
import os
io = remote("challs.actf.co", 32511)
print(io.recvuntil(b"proof of work: "))
print(y:=io.recvline().strip().decode())
x = os.popen(y).read().strip()
print(x)
print(io.recvuntil(b"solution: "))
io.sendline(x.encode())
io.recvline()
writething = b""
for i in range(159):
io.recvuntil(b"gate: ")
io.sendline(x:= b"and 0 " + str(i+1).encode())
print(x)
io.recvuntil(b"gate: ")
io.sendline(x:= b"xor 0 " + str(i+1).encode())
print(x)
io.sendline(b"")
try:
while True:
writething += io.recv(timeout=0.01)
except:
pass
f = open("outputs.txt", "wb")
f.write(writething)
solving script:
from Crypto.Hash import SHAKE128
from Crypto.Util.strxor import strxor
from Crypto.Util.number import *
f = open('outputs.txt', "r")
outputs = f.read()
outputs = outputs.splitlines()
#print(outputs)
wires = outputs[:160]
for i in range(len(wires)):
wires[i] = wires[i].split()[2:]
#print(wires)
tables = outputs[161:]
for i in range(len(tables)):
tables[i] = tables[i].split()[0]
#print(tables)
xors = []
for i in range(159):
xors.append([])
for j in range(4):
#print(tables[i].split()[0], tables[i+4].split()[0])
xors[-1].append(long_to_bytes(int(tables[i*8+j], 16) ^ int(tables[i*8+j+4], 16)).hex())
for i in range(1, 160):
key = bytes.fromhex(wires[0][0]) + bytes.fromhex(wires[i][0])
index = bytes.fromhex(tables[i*8-8 + int(wires[0][1]) * 2 + int(wires[i][1])])
index1 = bytes.fromhex(tables[i*8-4 + int(wires[0][1]) * 2 + int(wires[i][1])])
shaker = SHAKE128.new(key)
z = shaker.read(16)
and0 = strxor(z, index)
xor0 = strxor(z, index1)
xor = xors[i-1]
if xor[0] == xor[-1]:
rept = xor[0]
xor.pop(-1)
else:
rept = xor[1]
xor.pop(1)
if strxor(xor0, and0) == bytes.fromhex(rept):
print(1, end = "")
else:
print(0, end = "")
#print(xors)
# key = b''.join([l.key for l in labels])
# self.shake = SHAKE128.new(key)
manually decode the binary to get flag: actf{L3akY_g@rbl1ng}