• Category: coding challenge
  • Points: 5
  • Description: Can you pass this vision test, if it’s not working it’s definitely not your brain implant. Please calibrate your vision chip.
vision.png

The blurriness of the picture is intended. As part of the ctf it was intended for a dystopic world where humans had a microchip implanted in their brain.

Introduction

You are presented with a website with an image and two choices:

  • Animal 🐶
  • Thing 🧁

If you submit the correct answer you are greeted with a message Congrats. You currently have 1 out of 15 000. Meaning you’d need to guess right for 15 000 consecutive time before getting the flag. It would take insane people during an insane amount of time to get it manually, and unfortunately we’re limited in time.

Looking at the type of image we’d get it looked like some kind of machine learning challenge, with images of dog looking like a muffin or a donut:

vision-dog.png
vision-muffin.png

However, after trying some, it feels like some images come back very often meaning there must not be more than 20 to 50 images total. Based on that assumption I build a little script.

Solution

We will use python to build the script, basically it would follow these 4 steps:

  • Get the image from the site at http://vision.ctf/catchat.php
  • Compare it with the already saved images
  • Post the answer to the website and check the result
  • In case of failure, save the image and retry

Get the image

We will use requests for that:

import requests

response = requests.get('http://vision.ctf/catchat.php', headers=headers, cookies=cookies)

with open(os.path.join(catchat, "sample.png"), 'wb') as f:
    f.write(response.content)

Then we save the image as sample.png

Compare the image

So I stored all the images in a things folder, and I compare the just obtained sample to the one stored there. I created a function that return True if the sample image is already in the things folder, False otherwise:

def in_things():
    result = False
    for filename in os.listdir(things_path):
        if filename.endswith(".png"):
            pre_result = image_compare(Image.open(os.path.join(catchat, "sample.png")),
                                       Image.open(os.path.join(things_path, filename)))
            if pre_result < 10:
                result = True
    print("in Things? " + str(result))
    return result

For the image_compare function, I tried multiple online and finally got the one from Rosetta Code which I managed to use using the PIL library.

from PIL import Image

def image_compare(i1, i2):
    pairs = zip(i1.getdata(), i2.getdata())
    if len(i1.getbands()) == 1:
        # for gray-scale jpegs
        dif = sum(abs(p1 - p2) for p1, p2 in pairs)
    else:
        dif = sum(abs(c1 - c2) for p1, p2 in pairs for c1, c2 in zip(p1, p2))

    ncomponents = i1.size[0] * i1.size[1] * 3
    return (dif / 255.0 * 100) / ncomponents

Post the answer back

From observing the request sent manually, the website uses a cookie and a specific payload to validate for the results, so I copy those to add it to my post request:

cookies = {'PHPSESSID': '6d0db2mni2vrv65e98mo5md1gr'}

animal = {'result': 'animal'}
thing = {'result': 'thing'}

So the now if the image is not in the things folder, then it must mean it is an animal. So depending on the in_things() method, I know what result I need to submit:

if not in_things():
    payload = animal
    actual_type = "things"

else:
    payload = thing
    actual_type = "animals"

post = requests.post('http://vision.ctf/index.php', cookies=cookies, data=payload)    
status = re.search("(C.* 15 000<)+", str(post.content))
if_wrong_save_image(status, image_type=actual_type)

Let’s see what happens when it fails, with the status and the actual_type.

In case of failure

The trick to know if the POST succeeded, and that I have correctly guessed if it is an animal, or a thing is to check the returning page. If I guessed correctly I should see something like Congrats. You currently have 1 out of 15 000. So I used the re module (regex) to search for exactly that sentence:

  • status = re.search("(C.* 15 000<)+", str(post.content))

In the case it’s wrong status will return None and so I would copy sample.png which is a new image to the correct folder using the actual_type as the image_type:

def if_wrong_save_image(congrats_regex, image_type):
    if congrats_regex is None:
        print("new {}".format(image_type))
        new_image = "thing" + time.strftime("%Y%m%d-%H%M%S") + ".png"
        copyfile(os.path.join(catchat, "sample.png"),
                 os.path.join(os.path.join(catchat, "/samples/{}".format(image_type)), new_image))

Finally, running the whole code inside an infinite loop, and a couple of retry at first (there was just around 40 images in the end).

it would take around 3 to 4 hours to get the flag:

  • FLAG-0af9e4f2996a7169ebf85f91133d73a1

That was an awesome little challenge! I was worried they’d throw a new random image toward the end, that would make the whole thing retry again for 4 hours. They were not so evil. 😅