Битвы хакеров: разбор заданий PHDays CTF Quals

image

Теги: PHDays, Positive Technologies, информация

Positive Hack Days CTF — международные соревнования по защите информации, которые проводятся по игровому принципу Capture the Flag.

Positive Hack Days CTF — международные соревнования по защите информации, которые проводятся по игровому принципу Capture the Flag. Несколько команд в течение заранее отведенного времени защищают свои сети и атакуют чужие. Основная задача участников — выявлять уязвимости в системах противников и получать доступ к секретной информации (флагам), при этом обнаруживая и устраняя подобные уязвимости в своей системе.

В сегодняшнем топике мы представим разбор некоторых интересных заданий, с которыми сталкивались участники прошедших соревнований.

История и география

В этом году PHDays CTF состоится уже в четвертый раз. Впервые соревнования прошли во время форума Positive Hack Days в 2011 году, тогда победителями стали участники американской команды PPP, в следующем году победила российская команда Leet More, а на PHDays III чемпионами стали Eindbazen из Голландии. Каждый год в PHDays CTF принимают участие команды со всего мира — от США до Японии.

Для участия в отборочных соревнованиях в этом году зарегистрировалось больше 600 команд.

2.jpg

Задания и атмосфера

По сложившейся традиции игровые задания и инфраструктура подготавливаются в соответствии с легендой соревнований — специальной сюжетной линией, которая превращает простой набор задач PHDays CTF в увлекательное состязание, у которого есть цель. Например, в прошлом году участники спасали от гибели вымышленный мир D’Errorim. Предстоящие соревнования продолжат этот сюжет.

Задания соревнований, как правило, основаны на реальных прототипах: уязвимости, заложенные в задания и сервисы CTF, можно встретить в различных системах в реальной жизни. Соревнования PHDays CTF интересны и оригинальными игровыми механиками, которые делают возможной реализацию разных и непохожих друг на друга стратегий ведения игры (подробнее на сайте PHDays).

Обычно организаторы готовят для команд необычные задания, напрямую не связанные со взломом. Например, во время PHDays 2012 дополнительные очки можно было заработать обнаружив бонусные флаги в специальном контейнере с мусором, а во время PHDays III команды должны были быстрее всех преодолеть «хакерский лабиринт» — полосу препятствий с лазерным полем, датчиками слежения, секретными дверьми, комнатой с жучками и другими интересными испытаниями.

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

Разбор

Квалификационный этап соревнования (PHDays CTF Quals) относится к типу task-based CTF, то есть команды должны решать задания и получать за них очки. Задания могут относиться к одной из следующих категорий:

• Forensic — компьютерно-криминалистическая экспертиза,

• Reverse (reverse engineering) — анализ бинарного кода,

• Pwn — эксплуатация уязвимостей,

• Admin — навыки администрирования,

• Network — знание сетевой инфраструктуры и протоколов,

• Crypto — криптография,

• Stegano — стеганография,

• PPC (professional programming and coding) — олимпиадное программирование,

• Web — поиск и использование веб-уязвимостей,

• Misc — разное.

Начнем с последней категории.

Неочевидные задания

Участникам PHDays IV CTF Quals в рамках одного из заданий нужно было расшифровать сообщение, скрытое в mp3-файле.

Как правило, если в условии задачи упоминается извлечение скрытого в некотором контейнере сообщения, используется одно из готовых решений из области стеганографии. При этом для отыскания ответа обычно нужно подобрать программу для дешифровки и запустить ее с правильными ключами. То есть «ключ к успеху» при решении конкретного задания лежит в поиске подходящего варианта, предварительно прописанного авторами.

В нашем случае все несколько иначе. Если открыть предложенный файл в текстовом редакторе, то он выглядит вот так:

0000000000: 49 44 33 04 00 00 00 00 │ 1F 76 54 52 43 4B 00 00 ID3♦ ▼vTRCK

0000000010: 00 09 00 00 01 FF FE 30 │ 00 31 00 00 00 52 47 42 ○ ☺яю0 1 RGB

0000000020: 37 00 00 00 0D 00 00 03 │ 35 2C 31 38 33 2C 20 4E 7 ♪ ♥5,183, N

0000000030: 55 4C 4C 00 52 47 42 36 │ 00 00 00 0A 00 00 03 30 ULL RGB6 ◙ ♥0

0000000040: 2C 34 32 2C 31 35 39 00 │ 52 47 42 35 00 00 00 0C ,42,159 RGB5 ♀

0000000050: 00 00 03 31 39 34 2C 32 │ 34 34 2C 36 38 00 52 47 ♥194,244,68 RG

0000000060: 42 34 00 00 00 09 00 00 │ 03 34 37 2C 37 37 2C 36 B4 ○ ♥47,77,6

0000000070: 00 52 47 42 33 00 00 00 │ 0B 00 00 03 34 34 2C 37 RGB3 ♂ ♥44,7

0000000080: 33 2C 31 34 31 00 52 47 │ 42 32 00 00 00 0C 00 00 3,141 RGB2 ♀

0000000090: 03 31 34 30 2C 32 30 37 │ 2C 37 32 00 52 47 42 31 ♥140,207,72 RGB1

00000000A0: 00 00 00 0D 00 00 03 31 │ 32 30 2C 31 35 36 2C 32 ♪ ♥120,156,2

00000000B0: 30 33 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00 03

00000000C0: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00

00000000D0: 00 00 00 00 00 00 00 00 │ 00 00 00 00 00 00 00 00

В начале файла располагаются метаданные в формате ID3. Сначала идет тег TRCK (track number), а потом — какие-то куски текста:

RGB7 5,183, NULL RGB6 0,42,159 RGB5 194,244,68 RGB4 47,77,6 RGB3 44,73,141 RGB2 140,207,72 RGB1 120,156,203

Эту информацию можно разбить на семь записей (от RGB7 до RGB1):

RGB7 5,183, NULL

RGB6 0,42,159
RGB5 194,244,68

RGB4 47,77,6
RGB3 44,73,141

RGB2 140,207,72 RGB1 120,156,203

После каждого из RGB-идентификаторов стоит три значения. Обычно это числа, но в одном случае — NULL. Легко предположить, что это массив записей, каждая из которых содержит до трех однобайтовых значений. Можно записи отсортировать, объединить, превратить десятичные коды в символы и вывести в шестнадцатеричном виде, например, при помощи вот такой программы:

>>> a = [120,156,203, 140,207,72, 44,73,141, 47,77,6, 194,244,68, 0,42,159, 5,183]

>>> print "".join(map(chr, a)).encode("hex")

В результате получим:

789ccb8ccf482c498d2f4d06c2f444002a9f05b7

Шестнадцатеричная последовательность начинается с байтов с кодами 0x78 0x9С, а это подсказывает нам, что используется алгоритм сжатия данных zlib. Если использовать zlib в режиме компрессии с параметрами по умолчанию, выходная последовательность будет начинаться именно с этих байтов.

Одним вызовом функции decompress библиотеки zlib в Python можно распаковать упакованное сообщение:

>>> import zlib

>>> print zlib.decompress("".join(map(chr, a)))

И тогда будет выведен текст:

i_hate_ucucuga

Именно этот флаг нужно было отослать организаторам соревнований.

Неправильная криптография

Данное задание относится к категории Crypto. По легенде, был перехвачен сеанс связи, и командам нужно расшифровать переданные сообщения.

> REQUEST EXCHANGE

< CONFIRMED
> KEY
7abed4245dec0a5df17c672d799488c7b5ef5b9110c43e0c 3eae586c21b7369095569fc3aa0cc2cde94a362cc6e7902b8bb1bd6d32f358d 8e7091e7e8c48b9fa2aabe1579328cfa18e55927c5e9c7d50ccba5b5f4e173b 606b65a95c6f6c27b9fa8b482c45ce9605700856869b2beb7f663bf6244902a b57bf681a31fdbe86d7107daa7cc53dcff6633cb7a591508da29e1016dc8521 1f83052efc8e987e4d5cf5bac407ee187795dbba722e8b3650c555d119c556 76a5ff90af6a0eabbba477/010001
< KEY 55b0a8cbba652468362b92444a4336da542eb8641ac83fc85e4e44bc779658 42aa18f783d150a03a084f04e6809ee60e5bfe8379397a665936d91b5f3433b 48339d22b8b3de2544898376c87ddc385b8e6994eda6e385996a102a6cf5e2 41dbb0c78eae345c01a1bdad9d30d9bf84c02d976965654ec028d965833bd7 072fbeb8c576b49cc9f666757300fca0877f88a0975b3b5c67555f1de17aa153 d2b9b54c6a51bcb35ca840e7f5cabdb1bd4eab542cbae8a2dd5a59f8e84785089bdd383/010001
> MSG 2cb5e614c40eb325cdcec0410884b5e4b20cb63c39f93b720710b2b4a2d66b7 6588129d24172faf0ef84ca02b3f89b718eaf2a7ba7a13e444a7772ed79b3db6 c621c02a0d32ec09c510fcd39b1c76d715507de8634d217dceb33cb4d0cb030 9b58bf89e4e27e6ab0a24c32f97d820013cefcd35408e12d11644928a368526 d1482503995c0d3921164fc0b301329ffb1aac13f66dcaa21a6f63e371308be1 2366de0e68db27751ec12f9e02976bc52b2a2888f07608d26a637a46f03a6f1a1ab
< MSG 3c94c8dffb44880a13acc3e40826d021e4da41aca832cb6d396e83f528eb6fdd4a ed8f59ed837d072230b0d6e38e57d90dd409de1c2e867a5b6a3962a83aa330b 12516c313b0ba4820887fc7761c673d223b51f4f97ed1a7a90a009a880f7317b3 681a8ca64b53f2416d7daf43b8da2358d19d7e38d10c22bf7c55cf8fb587d33b9e e87492149ad959c0154b4708c5961705f08a45423d4f5cabcbb3b2ec43266589 ac3dbaf72a88377b9fe2afa84be2feacdb622f508d5d2874fa3106334bbd

Прежде всего, ясно виден процесс обмена ключами и затем передача зашифрованных данных. Необходимо понять, на основании какого криптографического базиса можно построить подобное общение.

Задание называется mars, можно предположить, что это означает Modified RSA.

Каждый ключ состоит из двух частей, и вторая часть в обоих случаях равна 0x010001 == 65537 — часто используемая публичная экспонента RSA (e). Значит, в сеансе связи сначала идет обмен открытыми ключами (n1/e1, n2/e2), а затем обмен зашифрованными на них сообщениями (c1, c2).
Если это действительно что-то похожее на RSA, то ci = pow(mi, ei, ni). Требуется найти m1 и m2 .

pow — функция модульного возведения в степень, pow(val, еxp, modulus) == valexp % modulus.

Согласно алгоритму RSA:

• mi = pow(ci, di, ni),

• di*ei ≡ 1 mod φ(ni),

• ni — произведение нескольких простых чисел,

• φ(n) — функция Эйлера, количество натуральных чисел взаимно простых с n и меньших n.

В задании n1 и n2 имеют длину 1535 бит, то есть не поддаются факторизации (разложению на простые сомножители).

Воспользуемся реализацией расширенного алгоритма Евклида на Python:

def egcd(a, b): # Extended Greatest Common Divisor
if a == 0: return (b, 0, 1)
else:
g, y, x = egcd (b % a, a)
return (g, x - (b // a) * y, y)

Найдем НОД (наибольший общий делитель) чисел n1 и n2:

gcd = egcd(n1,n2)[0]

НОД(n1,n2) имеет длину 1024 бита. Найдем другие делители чисел n1 и n2:

p1 = n1 / gcd

p2 = n2 / gcd

p1 и p2 — простые числа длиной 512 бит, gcd — составное число длиной 1024 бита (скорее всего 512*512), и оно также слишком велико для факторизации…

Рассмотрим случай, когда искомые сообщения mi могут быть представлены числами, не превосходящими pi.

Пусть ni = pi*q*r, тогда при 0 < mi < pi будет справедливо следующее выражение:

pow(mi, ei, ni) % pi == pow(mi, ei, pi)

Тогда экспонента для расшифрования d’i должна удовлетворять следующему выражению:

ei*d’i ≡ 1 mod φ(pi)

Значение d’i может быть найдено путем вычисления алгебраического дополнения:

d’i = invmod(ei, φ(pi))

Для простого pi справедливо:

φ(pi) == pi-1,

Следовательно:

d’i = invmod(ei, pi-1)

Вычисление алгебраического дополнения реализуется следующей функцией на Python:

def invmod(a, m): # Modular inversion
g, x, y = egcd (a, m)
if g == 1: return x % m
raise Exception("modular inverse does not exist")

Также понадобится функция, которая превращает число в строчку и оставляет только текст от последнего символа ‘\0’ до конца строки:

def showX(v):

print ("%0256X" % v).decode("hex").split('\0')[-1]

Вычисляем di , выполняем расшифрование:

d1 = invmod(e, p1-1)
d2 = invmod(e, p2-1)

showX(pow(c1, d1, p1))

showX(pow(c2, d2, p2))

И получаем результат:

REQUEST: GET_FLAG (SIGNATURE: 5e2d5e0323591b1c).

RESPONSE: its_n0t_ab0ut_p4dd1ng

Флаг — строка «its_n0t_ab0ut_p4dd1ng».

Криптографическое задание secc

Дано: архив source.tar.gz, содержащий файлы ecc.py и task.py, в которых содержится схема верификации ключа, реализованная с помощью эллиптической криптографии. Известно, что подключившись по адресу 195.133.87.171 на порт 5555, можно установить соединение с каким-то сервером:

nc 195.133.87.171 5555

password: secch4l*

Поскольку даны исходники, то стоит начать с их анализа. Можно их даже запустить.

Так как у меня не было модуля libnum, пришлось написать его самому. В нем достаточно реализовать уже упомянутую ранее функцию модульной инверсии и используемый ею расширенный алгоритма Евклида:

def egcd(a, b): # Extended Greatest Common Divisor

if a == 0: return (b, 0, 1)

else:

g, y, x = egcd (b % a, a)

return (g, x - (b // a) * y, y)

def invmod(a, m): # Modular inversion

g, x, y = egcd (a % m, m)

if g != 1: raise Exception("modular inverse does not exist")

else: return x % m

Итак, функция main из task.py:

def main():

print "Auth:“

auth = raw_input()

if hashlib.sha1(auth).hexdigest() !=
"375d5c01ca1b8c3863024d10aac7713472eb5033": # secch4l*

print "nope“

return

prefix = os.urandom(8)

print "Proof of work, please“

print "Prefix is (hexed) ", prefix.encode("hex")

test = raw_input().decode("hex")

if not test.startswith(prefix) or len(test) > 16:

print "nope“

return

h = hashlib.sha1(test).hexdigest()

if not h.startswith("000000"):

print "nope“

return

goflag()

Читается строка, SHA-1 от которой должно быть равно заданному значению («secch4l*»).

Потом генерируется случайный 8-байтовый префикс, отправляемый клиенту. Байты кодируются как hex-строка. В ответ клиент должен прислать строку не длиннее 16 байт, при этом чтобы она начиналась с заданного префикса, а первые 3 байт значения SHA-1 от этой строки должны быть нулевыми. Если все этапы пройдены успешно — вызывается функция goflag().

Следующий фрагмент кода соединяется с сервером, отправляет пароль, получает префикс, вычисляет и отправляет ответ:

def readLn(sock):

a = []

while True:

c = sock.recv(1)

if '\n' == c: return "".join(a)

a.append(c)

HOST = "195.133.87.171"

PORT = 5555

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect((HOST, PORT))

print readLn(sock) # Auth:

sock.send("secch4l*\n")

print readLn(sock) # Proof of work, please

s = readLn(sock)

print s # Prefix is (hexed) 0b3997e62b9ffbf4

prefix = s.split()[-1].decode("hex")

for i in xrange(0x7FFFFFFF):

s = "%s%X" % (prefix, i)

if hashlib.sha1(s).digest()[:3] == '\0\0\0': break

sock.send(s + '\n')

После выполнения этого кода на стороне клиента сервер выполняет функцию goflag(), выводящей примерно следующий текст:

EC PASSWORD CHECK

R = 572115218124168948525078362547166172445820217705568707355669424304224832114

SHARED SECRET = R ^ PASSWORD

ENCRYPTED MESSAGE: 7a93846a011e0d0382e94f32d705239e6298169dcec20da5d6

Что же происходит в функции goflag из task.py:

def goflag():

print "EC PASSWORD CHECK"

r = random.randint(31337, 1 << 250)

R = p256.power(G, r)

print "R =", R

print "SHARED SECRET = R ^ PASSWORD"

S = p256.power(R, PASSWORD)

key = p256.derive(S)

cipher = encrypt(FLAG, key)

print "ENCRYPTED MESSAGE:", cipher.encode("hex")

Используется асимметричная криптография на эллиптических кривых. Выбрана кривая P-256, рекомендованная NIST. Реализация операций над точками кривой не содержит очевидных уязвимостей.

Мы знаем значение R, но без знания значения PASSWORD (которое читается сервером из файла password.txt) мы не можем вычислить S. Знание S позволило бы нам легко вычислить key. Так может быть шифрование реализовано с ошибкой?

Функция encrypt из task.py:

def encrypt(msg, key):

iv = os.urandom(8)

stream = hashlib.sha256(iv + key).digest()

stream = hashlib.sha256(stream + iv + key).digest()

cipher = iv + xor(msg, stream)

return cipher

По коду видно, что зашифрованному сообщению предшествует случайный 8-байтовый вектор инициализации iv, а шифрование выполняется как XOR флага c гаммой, сгенерированной как выход двух вычислений SHA-256. Без знания значения key получить гамму нереально. Но как key получается в программе?

Функция derive из task.py:

def derive(self, p):

return hashlib.sha256(str((p[0] << 10) / p[1])).digest()

Оказывается, значение точки S (состоящее из двух координат — x и y) используется как вход SHA-256. Фактически на вход хеша подается значение str(int(x*1024/y)). Так как x и y имеют близкие значения (это большие целые числа), то результат арифметических действий должен близок к 1024 (хотя может и превышать его в разы).

Таким образом, из-за особенностей реализации функции derive, значение key может принимать совсем небольшое количество состояний. Можно просто перебрать их все, попытаться на каждом ключе расшифровать сообщение, и если оно состоит только из печатных символов — мы достигли успеха.

import hashlib, ecc

enc = "7a93846a011e0d0382e94f32d705239e6298169dcec20da5d6".decode("hex")

iv = enc[:8]

def decrypt(key):

stream = hashlib.sha256(iv + key).digest()

stream = hashlib.sha256(stream + iv + key).digest()

return ecc.xor(enc[8:], stream)

for i in xrange(0x7FFFFFFF):

s = decrypt(hashlib.sha256(str(i)).digest())

for c in bytearray(s):

if c < 32 or c >= 128: break

else:

print s # ecc_is_too_s3cure

break

Таким образом, флагом является строчка «ecc_is_too_s3cure».

Реверс-инжиниринг. Shadelt900

Обратная разработка — еще одна популярная категория заданий. Помимо CTF, в конкурсной программе PHDays присутствует конкурс Best Reverser.

Задание Shadelt900, так же как и три предыдущих, было частью программы PHDays IV CTF Quals, прошедших в январе 2014 года. Команды должны были расшифровать изображение под названием 'derrorim_enc.bmp'. Было известно средство, примененное для его зашифрования, — оно как раз и называется Shadelt9000.exe, но декриптор обнаружить не удалось. Вот это изображение:

3.png

При ближайшем рассмотрении файла Shadelt9000.exe становится ясно, что приложение использует OpenGL. Также есть копирайт inflate 1.2.8 Copyright 1995-2013 Mark Adler, указывающий на то, что в программе используется популярная библиотека компрессии zlib.

Если в дизассемблере посмотреть, откуда идут обращения к функциям zlib, можно довольно быстро найти вот такой кусок кода:

4.png

По адресам 0x47F660 и 0x47F7B8 расположены массивы данных, упакованные zlib. Распакуем их:

from zlib import decompress as unZ
base = 0x47C000 - 0x7AE00 # data section base
ab=open("ShadeIt9000.exe", "rb").read()
open("1.txt", "w").write(unZ(ab[0x47F660-base:],-15))
open("2.txt", "w").write(unZ(ab[0x47F7B8-base:],-15))

После распаковки файл 1.txt содержит пиксельный шейдер:

#version 330

uniform sampler2D u_texture;

uniform sampler2D u_gamma;

varying vec4 texCoord0;

varying vec3 v_param;

uint func(vec3 co){

return uint(fract(sin(dot(co ,vec3(17.1684, 94.3498, 124.9547))) * 68431.4621) * 255.);

}

uvec3 rol(uvec3 value, int shift) {

return (value << shift) | (value >> (8 - shift));

}

const uvec3 m = uvec3(0xff);

void main()

{

uvec3 t = uvec3(texture2D(u_texture, vec2(texCoord0)).rgb * 0xff) & m;

uvec3 g = uvec3(texture2D(u_gamma, vec2(texCoord0)).rgb * 0xff) & m;

int s = int(mod(func(v_param), 8));

t = rol(t, s);

vec3 c = vec3((t ^ g) & m) / 0xff;

gl_FragColor = vec4(c, 1.);

}

Файл 2.txt содержит вершинный шейдер:

attribute vec3 a_param;

varying vec4 texCoord0;

varying vec3 v_param;

void main(void)

{

gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;

texCoord0 = gl_MultiTexCoord0;

v_param = a_param;

}

Главная информация о пиксельном шейдере выделена красным:

void main()

{

uvec3 t = uvec3(texture2D(u_texture, vec2(texCoord0)).rgb * 0xff) & m;

uvec3 g = uvec3(texture2D(u_gamma, vec2(texCoord0)).rgb * 0xff) & m;

int s = int(mod(func(v_param), 8));

t = rol(t, s);

vec3 c = vec3((t ^ g) & m) / 0xff;

gl_FragColor = vec4(c, 1.);

}

В переменной t оказывается текущий элемент обрабатываемой текстуры (входного файла),

а в переменной g — текущий элемент гаммы (сгенерированной псевдослучайным образом).

В переменной s мы видим некоторое значение, используемое позже для циклического сдвига s.

Выходное значение фактически вычисляется как

(rol(t,s) ^ g)

Причем если запускать программу несколько раз с одним и тем же входным файлом, то для каждого элемента значение g будет меняться от запуска к запуску, а t и s будут оставаться одними и теми же.

Найдем, как генерируется гамма:

unsigned char *pbGamma = malloc(cbGamma);

srand(time(0));

for (i = 0; i < cbGamma; i++) {

pbGamma[i] = rand();

}

Видно, что она зависит от текущего времени.

Из исходного архива можно узнать, что файл derrorim_enc.bmp создан 21.01.2014 в 18:37:52.

Получаем значение, которое в тот момент вернула бы функция time():

>>> import time

>>> print hex(int(time.mktime((2014,1,21, 18,37,52, 0,0,0))))

0x52de8640

Теперь копируем файл ShadeIt9000.exe в ShadeIt9000_f.exe и исправляем его.

По смещению 00015557 надо байты

E8 A5 31 01 00

заменить на

B8 40 86 DE 52

Это эквивалентно замене

call _time

на

mov eax,52de8640h

Таким образом мы получили версию ShadeIt9000_f, которая будет всегда шифровать с той же гаммой, какая была в момент зашифрования интересующего нас файла.

Теперь нужно подготовить значения, которые помогут расшифровать изображение:

import os

bmp=open("derrorim_enc.bmp", "rb").read()

hdr = bmp[:0x36]

abData = bytearray(bmp[0x36:])

cbBody = len(bmp) - len(hdr)

open("00.bmp", "wb").write(hdr + '\0'*cbBody)

open("XX.bmp", "wb").write(hdr + '\2'*cbBody)

os.system("ShadeIt9000_f.exe 00.bmp")

os.system("ShadeIt9000_f.exe XX.bmp")

В файле 00_enc.bmp окажется результат зашифрования картинки, состоящий из нулевых байтов. Это и будет гамма в чистом виде.

В файле XX_enc.bmp окажется результат зашифрования картинки, состоящий из байтов со значением 2. Это поможет нам узнать, на сколько битов циклически сдвигался каждый байт.

Наконец, расшифровыванием Shadelt9000:

def rol(v,i): return (((v<<i) & 0xFF) | ((v>>(8-i)) & 0xFF))

def ror(v,i): return (((v>>i) & 0xFF) | ((v<<(8-i)) & 0xFF))

dRot = {rol(1,i):i for i in xrange(8)}

bmp=open("derrorim_enc.bmp", "rb").read()

hdr = bmp[:0x36]

abData = bytearray(bmp[0x36:])

abGamma = bytearray(open("00_enc.bmp", "rb").read()[0x36:])

abRot = bytearray(open("XX_enc.bmp", "rb").read()[0x36:])

for i,b in enumerate(abGamma): abRot[i] = dRot[abRot[i] ^ b]

for i,b in enumerate(abGamma): abData[i] = ror(abData[i] ^ b, abRot[i])

open("derrorim.bmp", "wb").write(hdr + str(abData))

получаем:

5.png

Выше был описан верный, но не самый эффективный путь решения задания. Есть способ короче.

Сразу за вершинным шейдером по адресам 0x47F848 и 0x47F9A0 лежит упакованный zlib-код пиксельного и вершинного шейдера для выполнения обратного преобразования. Возможно, он был случайно забыт разработчиком задания. А может и оставлен намеренно.

Коды вершинного шейдера для зашифрования и расшифрования идентичны, так что трогать их не имеет смысла. А что будет, если подменить пиксельный шейдер?

Копируем ShadeIt9000_f.exe в ShadeIt9000_d.exe и исправляем его:

00015775: 60 F6 ==> 48 F8

Затем запускаем ShadeIt9000_d.exe derrorim_enc.bmp. И получаем на выходе расшифрованный файл derrorim_enc_enc.bmp, который (за исключением мелких артефактов) совпадает с тем, который мы расшифровали скриптом на Python.

На сегодня все! Всем спасибо за внимание, будем рады ответить на вопросы в комментариях.

P. S. Архив всех заданий PHDays CTF и CTF Quals можно найти на сайте PHDays. Так что, если есть желание испытать себя — вперед!

P. P. S. Подробный разбор представленных в топике заданий проходил на специальном вебинаре, ведущим которого был Дмитрий Скляров. Запись вебинара доступна по ссылке: http://my.webinar.ru/record/290241/


или введите имя

CAPTCHA
Gates
08-05-2014 10:20:01
Полный бред
0 |
prada handbags uk
26-05-2014 11:31:13
3. Unknown - "I don't understand why Cupid was chosen to represent Valentine's Day. When I think about romance, the last thing on my mind is a short, chubby toddler coming at me with a weapon." prada handbags uk http://www.docklandspilates.co.uk/Times___Descriptions.html
0 |
maillot de foot 2014
29-05-2014 08:18:01
Its such as you read my thoughts! You appear to understand so much about this, like you wrote the guide in it or something. I think that you just can do with a few p.c. to drive the message home a little bit, but other than that, that is excellent blog. A great read. I’ll certainly be back. maillot de foot 2014 http://maillotdefootpascher.oursland.org/
0 |