Skip to content

Instantly share code, notes, and snippets.

@ky28059
Last active December 9, 2023 00:09
Show Gist options
  • Save ky28059/e7fed465703f2943e482cd2d6618ceb4 to your computer and use it in GitHub Desktop.
Save ky28059/e7fed465703f2943e482cd2d6618ceb4 to your computer and use it in GitHub Desktop.

iCTF 2023 — IslandParty

You open your mailbox and find a strange postcard (invite.bmp). Flipping it around, you squint your eyes and try to decipher the wobbly handwriting:

...

MYSTERIOUS INVITE: 'Generative AI is all the rage this days, so I couldn't pass up the opportunity to use it for this year's invite. Have you heard models like Google's Imagen will include a hidden watermark on AI generated images? I might not have algorithms quite as fancy as Google's, but I've also encoded a little something into this invite--the address! Decode it, and you'll be more than welcome to attend.'

...

MYSTERIOUS INVITE: 'One last piece of advice. All great thing come in three. Three sides to a triangle, three wise monkeys, three lights in a stoplight. Let the number three guide you, and you shall find my island.'

We're given the following image, within which is encoded data about the flag:

(the above image has been converted from a .bmp to a .jpg for GitHub, and likely won't work with the rest of this writeup)

Examining the histograms for this image yield some interesting results:

import cv2
import numpy as np

import matplotlib
import matplotlib.pyplot as plt

matplotlib.use('tkagg')

use_cv2_hist = False


def make_histogram(img: np.ndarray, title: str, n: int, lim: int = 256):
    dims = len(img.shape)
    channel = n - 1 if dims == 3 else 0

    if n != -1:
        plt.subplot(3, 1, n)

    if use_cv2_hist:
        plt.plot(cv2.calcHist([img], [channel], None, [lim], [0, lim]))
    else:
        data = img[:, :, channel].ravel() if dims == 3 else img.ravel()
        plt.hist(data, bins=256, range=(0, 255))

    plt.ylabel('Occurrences')
    plt.xlim([0, lim])
    plt.title(title)


def show_img(img: np.ndarray):
    make_histogram(img, "Reds", 1)
    make_histogram(img, "Greens", 2)
    make_histogram(img, "Blues", 3)
    plt.show()

    img2 = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)

    make_histogram(img2, "Hues", 1, lim=180)
    make_histogram(img2, "Saturations", 2)
    make_histogram(img2, "Values", 3)
    plt.show()

    img3 = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

    make_histogram(img3, "Intensities", -1)
    plt.show()


img = cv2.imread('invite.bmp')
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
show_img(img)

Figure_d3 Figure_d2

A clue is that the invite image features the DALL-E 2 signature, telling us what it was generated with. Going to the DALL-E 2 website (you'll need to sign in) and scrolling down the list of examples, we find the original "otter with pearl earring" image:

image (11)

(the above image has been converted from a .webp to a .jpg for GitHub, and might not work with the rest of this writeup)

Now that we have the original, we can compare its histograms to those of our invite; notice how the semi-periodic dips in RGB are gone.

Figure_g

This hints that we should taking the diff between the two images. Doing so yields this vibrant mess:

img = cv2.imread('original.webp')
# img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# show_img(img)

img2 = cv2.imread('invite.bmp')
# img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
# show_img(img2)

diff = img2 - img
cv2.imwrite('invite_diff.jpg', diff)
# show_img(diff)

invite_diff

(can you make out some digits already? 👀)

The challenge description mentioned groups of 3, which might just be related to the 3 channels of a color image. Splitting this diff into its R, G, and B channels gives

and summing these channels with overflow results in the following:

invite_diff_combined

From here, the encoded pattern becomes slightly more discernable. You can make use of the fact that the pattern seems to repeat every 2 rows and columns to disambiguate noisy characters. Inspecting this pattern carefully gives the coordinates of the island:

22.441N
74.220W
image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment