Задача:

Найдите ключ.

Формат: “RUCTF_.*”

Решение:

Так, у нас есть гифка. Первая идея, которая пришла всем в голову это посмотреть, а может в ней несколько кадров (фреймов). И действительно. Их оказалось восемь. Леша Лобанов грубо проверил их на равенство с помощью md5sum. Они были разные, но картинки были на них идентичны. Я думал, что ответ будет спрятам где-то среди недр gif формата. Нашел на питоне исходник, который читает gif формат. Немножко printов (дебажного вывода) и я представлял что и где читается и как обрабатывается. Но вот где искать ответ. И тут на помощь пришел Борис Чуприн. Сейчас найду точный его комментарий:

есть мнение что различия скрыты палитрой
т.е. номера пикселей разные, а цвет в палитре на них стоит одинаковый

Быстро проанализировав палитру получил, что некоторые цвета действительно в палитре используются несколько раз:

(0, 0, 0) 7
(0, 18, 17) 3
(0, 10, 10) 3
(8, 0, 0) 2
(183, 0, 0) 2

(0, 197, 0) 2
(0, 255, 0) 2
(21, 0, 0) 2
(0, 9, 0) 2
(0, 10, 0) 2

А значит один и от же цвет может показываться одинаково, но нам самом деле он должен отличаться. Создаем свою палитру и подгружаем её в каждый фрейм:

palette = []
for i in range(256):
    palette.extend((i, i, i))
assert len(palette) == 768

im = Image.open("task.gif")
images = []

try:
    ind = 0
    while True:
        original = im
        converted = Image.new('P', original.size)
        converted.putpalette(palette)
        converted.paste(original, (0, 0))
        images.append(converted)
        im.seek(im.tell() + 1)

except EOFError:
    pass

Ну а дальше надо было проанализировать отличия. Первая идея была от Андрея Малевич:

Мне вот интересно, а что эти штуки рассмотреть как биты
топовый бит я бы за 0 принял

На выходе получалось совсем уж не то, что требовалось. Пробовалось даже как-то менять порядок этих битов, но это совсем было неправильно и глупо. Но играясь с числами я заметил, что каждый символ в таком преобразовании является степенью двойки. То есть получается, что какой-то пиксель изменен максимум в одном из восьми фреймов. А значит можно их друг на друга наложить, т.е. они как бы не мешают друг друга. И все тот же Андрей предложил то, над чем я уже был в процессе реализации:

А если попробовать разбить эту штук на 8ки последовательных битов?

И на выходе получаем:

RUCTF_e4dd9f5cee307b322c3a27abe66e3df9

Исходный код целиком:

from PIL import Image, ImageChops
import Image

def main():

    palette = []
    for i in range(256):
        palette.extend((i, i, i))
    assert len(palette) == 768

    im = Image.open("task.gif")
    images = []

    try:
        ind = 0
        while True:
            original = im
            converted = Image.new('P', original.size)
            converted.putpalette(palette)
            converted.paste(original, (0, 0))
            images.append(converted)
            im.seek(im.tell() + 1)

    except EOFError:
        pass
    assert len(images) == 8

    for i in range(0, len(images)):
        images[i].save("images/task_%d.gif" % i)

    target = Image.new('P', original.size)
    trgt = target.load()

    sm = 0
    for ind in range(0, len(images)):
        diff = ImageChops.difference(images[0], images[ind])
        pix = diff.load()
        count = 0
        with open("data/data_%d.txt" % ind, "w") as fo:
            for i in range(diff.size[0]):
                for j in range(diff.size[1]):
                    if pix[i, j]:
                        fo.write("%d %d\n" % (i, j))
                        pix[i, j] = 255
                        trgt[i, j] = 255
                        count += 1
        sm += count
        diff.save("images/diff_%d.gif" % ind)

    count = 0
    a = []
    for j in range(target.size[1]):
        for i in range(target.size[0]):
            a.append(1 if trgt[i, j] else 0)
            if trgt[i, j]:
                count += 1
    target.save("images/result.gif")
    s = ""
    while len(a) > 7:
        x = 0
        for i in a[:8]:
            x = x * 2 + i
        if x == 0:
            break
        s += chr(x)
        a = a[8:]
    print s

if __name__ == "__main__":
    main()