Tags: pil xor cryptography
Rating:
# XQR
###### challenge and writeup by [phishfood](https://ctftime.org/user/136455)
## Challenge
I love QR codes! But maybe it's true what they say about having too much of a good thing...
[xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png)
## Solution
#### Intro to XOR
The name of the challenge, "XQR," hints at the fact the the solution will involve XORing the different QR codes. XOR (short for "exclusive or") is a logical operation that takes two inputs and returns `true` if and only if one input is `true` and the other is `false`. Below is a truth table for the XOR operation:
| a | b | a ⊕ b |
|---------|---------|---------|
| `false` | `false` | `false` |
| `false` | `true` | `true` |
| `true` | `false` | `true` |
| `true` | `true` | `false` |
You can XOR more than just booleans, however. For example you can XOR two integers:
`42 ⊕ 33 = 11`
In the above example, the bits in the binary representation of the digits are what is actually being XORed:
`101010 ⊕ 100001 = 001011`
#### XORing Images
The QR codes in [xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png) are made up of black and white pixels. If we treat each black pixel as a `0` or `false` and each white pixel as a `1` or `true`, they can be XORed just as we saw above with booleans and bits. Here is another truth table, this time using pixels:
| a | b | a ⊕ b |
|----|---|-------|
| ⬛️ | ⬛️ | ⬛️ |
| ⬛️ | ⬜️ | ⬜️ |
| ⬜️ | ⬛️ | ⬜️ |
| ⬜️ | ⬜️ | ⬛️ |
Let's consider a smaller challenge, [xqr3x3.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/xrq3x3.png):
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/xqr3x3.png)
To find the flag, we begin with any single QR code [xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png). Then, one by one, we XOR it with all of the other codes in [xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png). This process is demonstrated below:
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/demo.gif)
Scanning the final result reveals the flag!
#### Automating the Solution
Solving a 3x3 challenge isn't too difficult, even by hand (see [b&w_xor_using_gimp.xcf](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/extra_resources/b&w_xor_using_gimp.xcf) for an example of how this can be done), but if we're ever going to solve something as large as [xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png), we'll need to automate the process.
There are many different ways of doing this, but I used the [Pillow](https://pillow.readthedocs.io/en/stable/) (aka PIL) library in Python. PIL makes it easy to process images pixel by pixel, and even includes a function for XORing images. The following code can be used to reconstruct the flag QR code from [xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png):
```python
from PIL import Image, ImageChops
# the height/width of an individual QR code (including 1 px border)
CODE_SIZE = 27
# each extracted QR code will be temporarily stored here
tmp_qr = Image.new(mode='1', size=(CODE_SIZE, CODE_SIZE), color=1)
with Image.open('xqr.png') as image:
IMAGE_SIZE = image.size[0]
# iterate through each QR code
for start_y in range(0, IMAGE_SIZE, CODE_SIZE):
for start_x in range(0, IMAGE_SIZE, CODE_SIZE):
# copy the pixels of the current QR code into tmp_qr
for y in range(0, CODE_SIZE):
for x in range(0, CODE_SIZE):
p = image.getpixel((start_x + x, start_y + y))
tmp_qr.putpixel((x, y), p)
# if tmp_qr is the first code, copy its contents to qr_flag
if start_x == start_y == 0:
qr_flag = tmp_qr.copy()
# otherwise, set qr_flag to qr_flag XOR tmp_qr
else:
qr_flag = ImageChops.logical_xor(qr_flag, tmp_qr)
qr_flag.save('flag.png')
```
#### TL;DR
XOR all of the QR codes together, and the resulting QR code will reveal the flag when scanned!
## How It Works
The following is an explanation of how this challenge was created. It will explain the process of creating [xqr3x3.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/xqr3x3.png), but the process for creating larger challenges such as [xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png) works exactly the same way.
#### Using XOR to Hide Information
Let's say we want to hide a secret value, `1337`, in a set of numbers. We start by taking an arbitrary set of numbers, and XORing them together:
`500 ⊕ 123 ⊕ 999 ⊕ 42 = 578`
Next, we XOR our secret value with the result from above:
`1337 ⊕ 578 = 1915`
Now, XOR the result from the above equation with our first equation, we will discover our secret value!
`1915 ⊕ 500 ⊕ 123 ⊕ 999 ⊕ 42 = 1915 ⊕ 578 = 1337`
#### Hiding a QR Code
Now let's do it with QR codes. We'll start with the code that we want to hide, [flag.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/flag.png):
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/flag.png)
Next, we'll generate a set of 8 codes from random text, resulting in the following codes:
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/random.png)
If we XOR all of the random codes together (not including the flag), we obtain the following:
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/random_xored.png)
Lastly, we'll XOR the image above with the flag:
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/random_xored.png)
⊕
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/flag.png)
=
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/keystone.png)
You may notice that if you attempt to scan the resulting QR code, it is invalid. That is because it is not a QR code at all! It is an image specially designed to give us our flag QR code back when XORed with the 8 random codes that we started with. It is for this reason, along with the fact that it will be placed in the center of [xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png), that this image is referred to as the "keystone" in [create.py](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/create.py). Going back to our numeric example, the keystone would be analogous to the value of `1915`.
Now that we have all the elements of [xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png), we can put them all together. Notice that the code in the center matches the keystone as calculated above. Surrounding it are the 8 random codes that were generated.
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/img/xqr3x3.png)
All of these steps are automated in [create.py](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/create.py) to create massive challenges, such as [xqr.png](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png) (shown below).
![](https://raw.githubusercontent.com/danieltaylor/ctf-writeups/main/byuctf-22/xqr/xqr.png)