2017-11-13

Security CCTV

DefCamp CTF Finals 2017

misc

We are presented with a website that ask to “Enter your authentication token!” and are given a pretty large image of a desk.

The challenge description mentioned something along the lines that we should be able to find the authentication token. Observing the image a bit further I notice a QR code on the smartphone along with its reflection.

At this point, I thought I solved the challenge and began playing in Photoshop to fix and read the QR code. This turned out to be a waste of time since the QR code changes about every 30 seconds.

Moving on, I noticed there’s an area of gray pixels that perfectly fits the QR code. This probably means that the image is generated without much processing to account for shadows for example. The perspective is not very natural either, you can see that the QR code is not fitting well the border of the right side of the smartphone. This means it should be relatively easy to script it.

Luckily, I’ve had a bit of experience with OpenCV on a robotic project to automatically remove the perspective distortion of a camera and the task turns out to be similar.

The first step is to find a homography between the QR code on the camera image and a target square image then warp the image such that the QR code fits the given square. The same process is applied to the reflection on the laptop. This method is simple to use since it only requires a mapping of points on the image to points on the target square. Then, OpenCV does some magic maths to figure out how to transform the image.

When those images are composed, we have a nearly complete QR code but there’s is still one thing missing. There’s an alignment tracker hidden underneath the tissue that prevents most QR code reader from successfully reading the code. This tracker is added over the composed images approximately where it should be. QR codes have a lot of error correction embedded in them. Even though some parts of missing, it doesn’t prevent QR code reader from reading it.

Visually, the process looks like the following:

Here is the script that was used to solve the challenge:

import cv2
import numpy as np
import os
import subprocess

# Download the current frame
os.system("curl https://security-cctv.dctf-f1nals-2017.def.camp/img/streamframe.png > out.png")

# Find the homography for the QR code displayed on the phone to a 200x200 image
# These are the coordinates of the corners of the QR code on the image
from_points = [
    (2325, 1626), # top left
    (2244, 1710), # top right
    (2121, 1655), # bottom right
    (2204, 1572)  # bottom left
]

to_points = [
    (0, 0),
    (200, 0),
    (200, 200),
    (0, 200)
]

h, _ = cv2.findHomography(np.array(from_points), np.array(to_points))

print h

base_image = cv2.imread('out.png')

warped = cv2.warpPerspective(base_image, h, (200, 200))

# Find the homography for the QR code reflection to a 200x200 image
from_points = [
    (2073, 1617),
    (2123, 1607),
    (2158, 1705),
    (2133, 1710)
]

# These points have been approximated such that the resulting image is somewhat straight
to_points = [
    (0, 0),
    (0, 110),
    (200, 55),
    (200, 0)
]

h, _ = cv2.findHomography(np.array(from_points), np.array(to_points))

print h

corner = cv2.warpPerspective(base_image, h, (200, 200))

# Fill in cyan the part of the phone QR code that is overlayed by a tissue
cv2.fillConvexPoly(warped, np.array([(0, 0), (150, 0), (55, 100), (0, 80)]), (255, 255, 0))

cv2.imwrite('back.png', warped)
cv2.imwrite('corner.png', corner)

# Make the cyan transparent to overlay the images
os.system("convert back.png -transparent cyan back_t.png")

# The reflection is a bit darker so the brightness/contrast is increased a bit
os.system("mogrify -brightness-contrast 20x50 corner.png")

# Compose both images and apply a slight shift (+2 in x, +1 in y)
os.system("composite corner.png back_t.png -geometry +2+1 -compose dstover result.png")

# Add the missing "tracking box" otherwise the QR code can't be perperly decoded
os.system("composite tracker.png result.png -gravity center -compose over result2.png")

# Add a white margin around the resulting QR code
os.system("composite result2.png white.png -gravity center -compose over result3.png")

# Increase the brightness/contrast of the overall image to make zbarimg happier
os.system("mogrify -brightness-contrast 10,20 result3.png")

# Read the QR code
token = subprocess.check_output(['zbarimg', '-q', 'result3.png']).split(':')[1].strip()

print token

# Submit it, get that flag
os.system("curl 'https://security-cctv.dctf-f1nals-2017.def.camp/index.php' --data 'token=%s'" % token)