feat(q-solved): add first q-solved writeup
Signed-off-by: Julien CLEMENT <julien.clement@epita.fr>
This commit is contained in:
parent
8cbb1a0b89
commit
6f5bbb7ceb
@ -7,6 +7,7 @@ paginate = 10
|
|||||||
PygmentsCodeFences = true
|
PygmentsCodeFences = true
|
||||||
PygmentsStyle = "monokai"
|
PygmentsStyle = "monokai"
|
||||||
|
|
||||||
|
|
||||||
[author]
|
[author]
|
||||||
name = "Julien CLEMENT"
|
name = "Julien CLEMENT"
|
||||||
|
|
||||||
@ -56,10 +57,6 @@ PygmentsStyle = "monokai"
|
|||||||
[params.footer]
|
[params.footer]
|
||||||
author = true
|
author = true
|
||||||
|
|
||||||
[taxonomies]
|
|
||||||
category = "blog"
|
|
||||||
tag = "tags"
|
|
||||||
series = "series"
|
|
||||||
|
|
||||||
[languages]
|
[languages]
|
||||||
[languages.en]
|
[languages.en]
|
||||||
@ -71,7 +68,7 @@ PygmentsStyle = "monokai"
|
|||||||
|
|
||||||
[languages.en.params.logo]
|
[languages.en.params.logo]
|
||||||
logoMark = "juju@synacktiv ~$ "
|
logoMark = "juju@synacktiv ~$ "
|
||||||
logoText = "cryptoratio"
|
logoText = "cryptopouneur"
|
||||||
logoHomeLink = "/"
|
logoHomeLink = "/"
|
||||||
logoCursorColor = "#3884FF"
|
logoCursorColor = "#3884FF"
|
||||||
# or
|
# or
|
||||||
@ -84,3 +81,8 @@ PygmentsStyle = "monokai"
|
|||||||
identifier = "posts"
|
identifier = "posts"
|
||||||
name = "Posts"
|
name = "Posts"
|
||||||
url = "/post/"
|
url = "/post/"
|
||||||
|
|
||||||
|
[[menu.main]]
|
||||||
|
identifier = "writeups"
|
||||||
|
name = "Writeups"
|
||||||
|
url = "/writeups/"
|
||||||
|
@ -8,5 +8,5 @@ tags: ["Reverse", "Exploit"]
|
|||||||
{{< youtube 5g2eZSST7YE >}}
|
{{< youtube 5g2eZSST7YE >}}
|
||||||
|
|
||||||
{{< rawhtml >}}
|
{{< 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 >}}
|
{{< /rawhtml >}}
|
||||||
|
111
jujure/content/writeups/q-solved.md
Normal file
111
jujure/content/writeups/q-solved.md
Normal file
@ -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;" >}}
|
3
jujure/layouts/shortcodes/code.html
Normal file
3
jujure/layouts/shortcodes/code.html
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{{ $file := .Get "file" | readFile }}
|
||||||
|
{{ $lang := .Get "language" }}
|
||||||
|
{{ (print "```" $lang "\n" $file "\n```") | markdownify }}
|
1
jujure/static/q-solved/circuit.json
Executable file
1
jujure/static/q-solved/circuit.json
Executable file
File diff suppressed because one or more lines are too long
17414
jujure/static/q-solved/circuit_linted.json
Executable file
17414
jujure/static/q-solved/circuit_linted.json
Executable file
File diff suppressed because it is too large
Load Diff
BIN
jujure/static/q-solved/flag.png
Executable file
BIN
jujure/static/q-solved/flag.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
33
jujure/static/q-solved/flag.py
Executable file
33
jujure/static/q-solved/flag.py
Executable file
@ -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'))
|
BIN
jujure/static/q-solved/poc_flag.png
Executable file
BIN
jujure/static/q-solved/poc_flag.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
19
jujure/static/q-solved/poc_flag.py
Executable file
19
jujure/static/q-solved/poc_flag.py
Executable file
@ -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'))
|
56
jujure/static/q-solved/solve.py
Executable file
56
jujure/static/q-solved/solve.py
Executable file
@ -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'))
|
Loading…
Reference in New Issue
Block a user