feat(q-solved): add first q-solved writeup
Signed-off-by: Julien CLEMENT <julien.clement@epita.fr>
This commit is contained in:
1 parent
8cbb1a0b89
commit
6f5bbb7ceb
12 files changed
+17645
-6
No files matched your search
+7
-5
@@ -7,6 +7,7 @@ paginate = 10
|
||||
PygmentsCodeFences = true
|
||||
PygmentsStyle = "monokai"
|
||||
|
||||
|
||||
[author]
|
||||
name = "Julien CLEMENT"
|
||||
|
||||
@@ -56,10 +57,6 @@ PygmentsStyle = "monokai"
|
||||
[params.footer]
|
||||
author = true
|
||||
|
||||
[taxonomies]
|
||||
category = "blog"
|
||||
tag = "tags"
|
||||
series = "series"
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
@@ -71,7 +68,7 @@ PygmentsStyle = "monokai"
|
||||
|
||||
[languages.en.params.logo]
|
||||
logoMark = "juju@synacktiv ~$ "
|
||||
logoText = "cryptoratio"
|
||||
logoText = "cryptopouneur"
|
||||
logoHomeLink = "/"
|
||||
logoCursorColor = "#3884FF"
|
||||
# or
|
||||
@@ -84,3 +81,8 @@ PygmentsStyle = "monokai"
|
||||
identifier = "posts"
|
||||
name = "Posts"
|
||||
url = "/post/"
|
||||
|
||||
[[menu.main]]
|
||||
identifier = "writeups"
|
||||
name = "Writeups"
|
||||
url = "/writeups/"
|
||||
@@ -8,5 +8,5 @@ tags: ["Reverse", "Exploit"]
|
||||
{{< youtube 5g2eZSST7YE >}}
|
||||
|
||||
{{< rawhtml >}}
|
||||
<embed src="/post/conf_exploit.pdf" type="application/pdf" width="100%" height="430">
|
||||
<embed src="/conf_exploit/slides.pdf" type="application/pdf" width="100%" height="430">
|
||||
{{< /rawhtml >}}
|
||||
@@ -0,0 +1,111 @@
|
||||
---
|
||||
title: "Reversing quantum algorithms ~~for ctf points~~ | q-solved - zer0pts 2022"
|
||||
date: "2022-03-25 18:00:00"
|
||||
author: "Juju"
|
||||
tags: ["Reverse", "Quantum", "Writeup", "zer0pts"]
|
||||
---
|
||||
|
||||
# Challenge description
|
||||
`quantum` `reverse` | `304 pts` `8 solves`
|
||||
```
|
||||
I copied the solver of a reversing task from the future. But it doesn't
|
||||
show the flag forever :thinking:
|
||||
```
|
||||
|
||||
# Given files
|
||||
|
||||
{{< code file="/static/q-solved/solve.py" language="py" >}}
|
||||
|
||||
[circuit.json](/q-solved/circuit.json)
|
||||
|
||||
# TL;DR
|
||||
|
||||
The scripts builds a quantum circuit describing an unstructured search
|
||||
algorithm inspired by the grover's algorithm. Its goal is to find among all
|
||||
possible inputs, the one(s) with the highest probability to match a predefined
|
||||
criteria.
|
||||
|
||||
It is composed of an `oracle` and a `diffuser`. The oracle is a black-box
|
||||
function taking a state vector as input and introducing a phase shift in the
|
||||
target qubit if the input matches the predefined criteria.
|
||||
|
||||
The diffuser then performs amplitude amplification using the target qubit's
|
||||
phase kickback when the oracle matches, thus increasing the probability of a
|
||||
matching input vector to be measured.
|
||||
|
||||
Basically, the output of the circuit is the input matching the criteria
|
||||
described by the oracle.
|
||||
|
||||
All that remains to do for us is to understand what that criteria is.
|
||||
|
||||
# Reversing the Oracle
|
||||
|
||||
We can see that the oracle is built using the `circuit.json`.
|
||||
|
||||
The oracle is composed of 1408 multi-controlled X (MCX) gates, each controlled by 1
|
||||
or 3 input qubits with with a control state given in the json. Each MCX gate
|
||||
acts on a dedicated ancilla qubit.
|
||||
|
||||
After all 1408 MCX, the circuit adds an other MCX on the target qubit with all
|
||||
control states set to 0. The target qubit is therefore introduced a phase shift
|
||||
when all ancillas are in `|0>`.
|
||||
|
||||
So we want all ancillas to be `|0>` but it is also their original state. We
|
||||
therfore have to influence the control qubits of each MCX so that none actually
|
||||
performs the X gate on any ancilla. This means that among all control qubits of
|
||||
a MCX, at least one must have a state different from its control state, thus
|
||||
deactivating the gate.
|
||||
|
||||
If we look closely, we can see that the control state of a control qubit is `1`
|
||||
when the json specifies `False`, and `0` when `True`.
|
||||
|
||||
Remember, inputs must be different from their control state specified on the
|
||||
gate. Therefore a qubit marked as `False`, must take value `|0>` to deactivate
|
||||
the gate. Similarly, a qubit marked `True` must take value `|1>`.
|
||||
|
||||
So we said earlier that the MCX have either 3 or 1 control bits and that at
|
||||
least 1 of the control qubits must mismatch from their control state.
|
||||
|
||||
# POC with trivial qubits
|
||||
|
||||
Obviously this results in an equation system but let's see what we get with
|
||||
only the obvious qubits: the ones controlling an ancilla by themselves.
|
||||
|
||||
Indeed if an ancilla is controlled by a single input qubit then this qubit MUST
|
||||
be different from his control state so the X gate stays disabled. Therefore, any input qubit marked as `False` and as the only control of a gate MUST be set to `|0>` to match the oracle. Same is true for qubits marked as `True` that must be in state `|1>`.
|
||||
|
||||
So let's try to set all obvious qubits:
|
||||
|
||||
{{< code file="/static/q-solved/poc_flag.py" language="py" >}}
|
||||
|
||||
{{< image src="/q-solved/poc_flag.png" style="border-radius: 8px;" >}}
|
||||
|
||||
Well, most of them are 0, except, the first byte: `z`
|
||||
|
||||
Which is a really good sign that we are indead decoding a flag of the form
|
||||
`zer0pts{...}`
|
||||
|
||||
# Equation system
|
||||
|
||||
For MCX with 3 control bits, we simply need to put them in an equation system,
|
||||
with the trivial qubits.
|
||||
|
||||
We will have a total of 1408 equations, 1 for each MCX, each equation basically
|
||||
saying that at least 1 Qubit must be different from its control state, and
|
||||
therefore equal to its assigned boolean in the json.
|
||||
|
||||
Once the system is solved, we will know the state of all qubits that match the
|
||||
oracle, which is the one outputed by the quantum circuit. We will then be able
|
||||
to decode it to get the flag.
|
||||
|
||||
# Solve
|
||||
|
||||
I used z3 to build and solve the equation system:
|
||||
|
||||
{{< code file="/static/q-solved/flag.py" language="py" >}}
|
||||
|
||||
Running the script outputs us the equation system, sat indicated that z3 found
|
||||
a solution to the system and the decoded solution of the system with the flag:
|
||||
`zer0pts{FLAG_by_Gr0v3r's_4lg0r1thm}`
|
||||
|
||||
{{< image src="/q-solved/flag.png" style="border-radius: 8px;" >}}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{ $file := .Get "file" | readFile }}
|
||||
{{ $lang := .Get "language" }}
|
||||
{{ (print "```" $lang "\n" $file "\n```") | markdownify }}
|
||||
File renamed without changes.
Executable
+1
@@ -0,0 +1 @@
|
||||
{"memory": 560, "ancilla": 1408, "circuit": [[[false, 0]], [[true, 0], [false, 8], [false, 280]], [[false, 0], [true, 8], [false, 280]], [[true, 280], [false, 0], [false, 8]], [[true, 0], [true, 8], [true, 280]], [[false, 280]], [[false, 19], [false, 11], [false, 281]], [[true, 11], [true, 19], [false, 281]], [[false, 19], [true, 11], [true, 281]], [[false, 11], [true, 19], [true, 281]], [[false, 281]], [[false, 18], [false, 26], [false, 282]], [[true, 18], [true, 26], [false, 282]], [[true, 18], [false, 26], [true, 282]], [[false, 18], [true, 26], [true, 282]], [[false, 282]], [[false, 25], [false, 33], [false, 283]], [[true, 25], [true, 33], [false, 283]], [[true, 25], [false, 33], [true, 283]], [[false, 25], [true, 33], [true, 283]], [[false, 283]], [[false, 32], [false, 40], [false, 284]], [[true, 32], [true, 40], [false, 284]], [[true, 32], [false, 40], [true, 284]], [[false, 32], [true, 40], [true, 284]], [[false, 284]], [[true, 40], [false, 48], [false, 285]], [[true, 48], [false, 40], [false, 285]], [[true, 285], [false, 40], [false, 48]], [[true, 40], [true, 48], [true, 285]], [[false, 285]], [[false, 50], [false, 58], [false, 286]], [[true, 50], [true, 58], [false, 286]], [[true, 50], [false, 58], [true, 286]], [[false, 50], [true, 58], [true, 286]], [[false, 286]], [[false, 57], [false, 65], [false, 287]], [[true, 57], [true, 65], [false, 287]], [[true, 57], [false, 65], [true, 287]], [[true, 65], [false, 57], [true, 287]], [[false, 287]], [[false, 68], [false, 76], [false, 288]], [[true, 68], [true, 76], [false, 288]], [[true, 68], [false, 76], [true, 288]], [[false, 68], [true, 76], [true, 288]], [[false, 288]], [[false, 73], [false, 81], [false, 289]], [[true, 73], [true, 81], [false, 289]], [[true, 73], [false, 81], [true, 289]], [[false, 73], [true, 81], [true, 289]], [[false, 289]], [[false, 88], [false, 80], [false, 290]], [[true, 80], [true, 88], [false, 290]], [[false, 88], [true, 80], [true, 290]], [[false, 80], [true, 88], [true, 290]], [[false, 290]], [[false, 88], [false, 96], [false, 291]], [[true, 88], [true, 96], [false, 291]], [[true, 88], [false, 96], [true, 291]], [[false, 88], [true, 96], [true, 291]], [[false, 291]], [[false, 97], [false, 105], [false, 292]], [[true, 97], [true, 105], [false, 292]], [[true, 97], [false, 105], [true, 292]], [[false, 97], [true, 105], [true, 292]], [[false, 292]], [[false, 106], [false, 114], [false, 293]], [[true, 106], [true, 114], [false, 293]], [[true, 106], [false, 114], [true, 293]], [[false, 106], [true, 114], [true, 293]], [[false, 293]], [[false, 112], [false, 120], [false, 294]], [[true, 112], [true, 120], [false, 294]], [[true, 112], [false, 120], [true, 294]], [[false, 112], [true, 120], [true, 294]], [[false, 294]], [[false, 122], [false, 130], [false, 295]], [[true, 122], [true, 130], [false, 295]], [[true, 122], [false, 130], [true, 295]], [[false, 122], [true, 130], [true, 295]], [[false, 295]], [[false, 129], [false, 137], [false, 296]], [[true, 129], [true, 137], [false, 296]], [[true, 129], [false, 137], [true, 296]], [[false, 129], [true, 137], [true, 296]], [[false, 296]], [[false, 139], [false, 147], [false, 297]], [[true, 139], [true, 147], [false, 297]], [[true, 139], [false, 147], [true, 297]], [[false, 139], [true, 147], [true, 297]], [[false, 297]], [[false, 144], [false, 152], [false, 298]], [[true, 144], [true, 152], [false, 298]], [[true, 144], [false, 152], [true, 298]], [[false, 144], [true, 152], [true, 298]], [[false, 298]], [[false, 161], [false, 153], [false, 299]], [[true, 153], [true, 161], [false, 299]], [[false, 161], [true, 153], [true, 299]], [[false, 153], [true, 161], [true, 299]], [[false, 299]], [[false, 161], [false, 169], [false, 300]], [[true, 161], [true, 169], [false, 300]], [[true, 161], [false, 169], [true, 300]], [[false, 161], [true, 169], [true, 300]], [[false, 300]], [[false, 169], [false, 177], [false, 301]], [[true, 169], [true, 177], [false, 301]], [[true, 169], [false, 177], [true, 301]], [[false, 169], [true, 177], [true, 301]], [[false, 301]], [[false, 179], [false, 187], [false, 302]], [[true, 179], [true, 187], [false, 302]], [[true, 179], [false, 187], [true, 302]], [[false, 179], [true, 187], [true, 302]], [[false, 302]], [[false, 185], [false, 193], [false, 303]], [[true, 185], [true, 193], [false, 303]], [[true, 185], [false, 193], [true, 303]], [[false, 185], [true, 193], [true, 303]], [[false, 303]], [[false, 194], [false, 202], [false, 304]], [[true, 194], [true, 202], [false, 304]], [[true, 194], [false, 202], [true, 304]], [[false, 194], [true, 202], [true, 304]], [[false, 304]], [[false, 210], [false, 202], [false, 305]], [[true, 202], [true, 210], [false, 305]], [[false, 210], [true, 202], [true, 305]], [[false, 202], [true, 210], [true, 305]], [[false, 305]], [[false, 210], [false, 218], [false, 306]], [[true, 210], [true, 218], [false, 306]], [[true, 210], [false, 218], [true, 306]], [[false, 210], [true, 218], [true, 306]], [[false, 306]], [[false, 227], [false, 219Line truncated
|
||||
Executable
+17414
File diff suppressed because it is too large.
Load diff
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
from z3 import *
|
||||
|
||||
with open('circuit.json', 'r') as f:
|
||||
j = json.load(f)
|
||||
|
||||
nq = j['memory']
|
||||
|
||||
system = [Bool(str(i)) for i in range(nq)]
|
||||
|
||||
s = Solver()
|
||||
|
||||
for cs in j['circuit']:
|
||||
term = Or()
|
||||
for c in cs:
|
||||
term = Or(term, system[c[1]] == c[0])
|
||||
s.add(term)
|
||||
|
||||
print(s)
|
||||
print(s.check())
|
||||
|
||||
m = s.model()
|
||||
|
||||
result_arr = [''] * nq
|
||||
|
||||
for d in m.decls():
|
||||
result_arr[int(d.name())] = '1' if m[d] else '0'
|
||||
|
||||
result_arr.reverse()
|
||||
result = "".join(result_arr)
|
||||
|
||||
print(int.to_bytes(int(result, 2), nq//8, 'little'))
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
|
||||
with open('circuit.json', 'r') as f:
|
||||
j = json.load(f)
|
||||
|
||||
nq = j['memory']
|
||||
|
||||
system = ['0'] * nq
|
||||
|
||||
for cs in j['circuit']:
|
||||
if len(cs) == 1:
|
||||
system[cs[0][1]] = '1' if cs[0][0] else '0'
|
||||
|
||||
|
||||
system.reverse()
|
||||
result = "".join(system)
|
||||
|
||||
print(int.to_bytes(int(result, 2), nq//8, 'little'))
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
import gmpy2
|
||||
from qiskit import QuantumCircuit, execute, Aer
|
||||
from qiskit.circuit.library import XGate
|
||||
import json
|
||||
|
||||
with open("circuit.json", "r") as f:
|
||||
circ = json.load(f)
|
||||
|
||||
nq = circ['memory']
|
||||
na = circ['ancilla']
|
||||
target = nq + na
|
||||
|
||||
print("[+] Constructing circuit...")
|
||||
main = QuantumCircuit(nq + na + 1, nq)
|
||||
sub = QuantumCircuit(nq + na + 1)
|
||||
|
||||
main.x(target)
|
||||
main.h(target)
|
||||
for i in range(circ['memory']):
|
||||
main.h(i)
|
||||
|
||||
t = circ['memory']
|
||||
for cs in circ['circuit']:
|
||||
ctrl = ''.join(['0' if x else '1' for (x, _) in cs])
|
||||
l = [c for (_, c) in cs]
|
||||
sub.append(XGate().control(len(cs), ctrl_state=ctrl), l + [t])
|
||||
t += 1
|
||||
|
||||
sub.append(XGate().control(na, ctrl_state='0'*na),
|
||||
[i for i in range(nq, nq + na)] + [target])
|
||||
|
||||
for cs in circ['circuit'][::-1]:
|
||||
t -= 1
|
||||
ctrl = ''.join(['0' if x else '1' for (x, _) in cs])
|
||||
l = [c for (_, c) in cs]
|
||||
sub.append(XGate().control(len(cs), ctrl_state=ctrl), l + [t])
|
||||
|
||||
sub.h([i for i in range(nq)])
|
||||
sub.append(XGate().control(nq, ctrl_state='0'*nq),
|
||||
[i for i in range(nq)] + [target])
|
||||
sub.h([i for i in range(nq)])
|
||||
|
||||
for i in range(round(0.785 * int(gmpy2.isqrt(2**nq)) - 0.5)):
|
||||
main.append(sub, [i for i in range(na + nq + 1)])
|
||||
|
||||
for i in range(nq):
|
||||
main.measure(i, i)
|
||||
|
||||
print("[+] Calculating flag...")
|
||||
emulator = Aer.get_backend('qasm_simulator')
|
||||
job = execute(main, emulator, shots=1024)
|
||||
hist = job.result().get_counts()
|
||||
result = sorted(hist.items(), key=lambda x: -x[1])[0][0]
|
||||
|
||||
print("[+] FLAG:")
|
||||
print(int.to_bytes(int(result, 2), nq//8, 'little'))
|
||||