Image

Category:

Гайд по триатлону: CTF, часть 1/2

В этом посте я расскажу про одну из дисциплин триатлона — соревнования по информационной безопасности CTF. CTF расшифровывается как Capture The Flag. Изначально соревнования такого рода представляли собой противоборство двух команд, каждая из которых пыталась взломать сервера другой и не допустить взлома своих, поддерживая, тем не менее, их доступность. Этот формат многим (включая меня) не нравится. Я предпочитаю формат Jeopardy, похожий на одноименную игру («Своя игра»): задачи различной стоимости разделены на несколько категорий, а положение команды вычисляется из суммы стоимостей решённых задач.

Традиционными категориями являются Cryptography, Web hack, Reversing, Forensics. В последнее время всё чаще на соревнованиях встречаются новые категории: Algorithms, похожая на алгоритмическое спортивное программирование, и Reconnaissance, про разведку по открытым источникам в интернете.

В процессе решения задачи, будь то взлом криптоалгоритма, реверсинг какого-нибудь запутанного бинарника или поиск нужного аккаунта в сети, участники находят флаги — короткие опознаваемые строки. Они отправляются автоматическому чекеру в качестве решения. Например, на описываемом контесте флаги имели вид abctf{s0m3th1ng_s1m1l4r}.

Чтобы проиллюстрировать, какого рода бывают задачи, я написал разбор (обычно называемый write-up) соревнования ABCTF, прошедшего в июле 2016. Оно ориентировано на школьников, не так давно интересующихся информационной безопасностью, поэтому значительная часть задач имеет образовательную ценность для новичков. К сожалению, сейчас сайт соревнования и многие из ссылок не работают.

Если вы заинтересовались темой, почитайте учебник от UFO CTF.
Список предстоящих соревнований



Cryptography


Эта категория требует знакомства с известными криптопримитивами и криптосхемами, умения искать в них уязвимости, понимания математики, которая лежит в основе криптографических алгоритмов. Из инструментария чаще всего оказываются полезными Python, Maple и ручка с бумагой.


Caesar Salad - 10 (Cryptography)
Most definitely the best salad around. Can you decrypt this for us?
xyzqc{t3_qelrdeq_t3_k33a3a_lk3_lc_qe3p3}

Любой интересующийся криптографией человек, несомненно, читал популярные книжки по ней, рассказывающие, в том числе, про ее историю. Желание скрыть свое сообщение от чужих глаз имеет историю не в одну тысячу лет. И шифр Цезаря — один из самых старых. В нем к порядковому номеру символа прибавляется какое-то фиксированное число, и символ заменяется на символ с получившимся номером. Например, если сдвиг равен 1, то A становится B, B становится C, а Z переходит в A. Видно, что шифротекст похож на ключ, у него 5 символов перед {}, внутри которых строка, содержащая знаки подчеркивания. Кроме того буквы xyz идут подряд, как и abc, что позволяет заключить, что сдвиг равен -4. Пишем простую программу на Python:

s = 'xyzqc{t3_qelrdeq_t3_k33a3a_lk3_lc_qe3p3}'

result = ''
for i in range(len(s)):
    code = ord(s[i]) + 3
    if code > ord('z'):
        code -= 26
    result += chr(code) if s[i] not in '{}_3' else s[i]
print result

которая выдает ответ abctf{w3_thought_w3_n33d3d_on3_of_th3s3}.


Yummi - 60 (Cryptography)
Well this image means something and we need you to figure it out!
Hint: Water -> Fish, Mud -> ???

По ссылке скачивается черно-белая картинка baconian.bmp 9×10.

Image

Название должно намекнуть знатоку криптографии про еще один древний шифр — шифр Бэкона. Он стеганографически скрывал (верхним или нижним регистром, курсивом или прямым текстом) в большом тексте биты сообщения, сгруппированные по 5. Каждая группа соответствовала порядковому номеру символа в двоичной системе счисления. Здесь в виде битов очевидно выступают черные и белые пиксели, а группировку по 5 явно удобнее производить, двигаясь вертикально, по колонкам. Действительно, A, видимо, соответствует всем черным пикселям в верхней половине первой колонки (00000), B в нижней половине первой колонки уже содержит один белый пиксель в самом низу (00001), C сверху во второй колонке 00010 и так далее.

Чтобы не делать вручную то, что можно не делать вручную, напишем дешифровщик на Python:

im = imread('baconian.bmp')
bits = ''.join(['0' if all(im[i,j,:] == [0,0,0]) else '1' for j in range(9) for i in range(10)])
print ''.join([chr(ord('A') + int(bits[off : off + 5], 2)) for off in range(0, len(bits), 5)])

И флаг ABCTFLOVESBACONIAN.


Old RSA - 70 (Cryptography)
I'm sure you can retrieve the flag from this file.
Hint: Some good math skills may help.

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

По ссылке даны 3 числа, c, n и e. Число c это шифротекст, c = m^e mod n, где m — открытый текст. Чтобы в RSA извлечь из шифротекста открытый текст, нужно знать d, d = e^{-1} mod phi(n), где, поскольку n это произведение двух больших простых чисел p и q, phi(n) = phi(pq) = (p-1)(q-1). Тогда тот, кто знает d, может вычислить c^d mod n = m^{ed} mod n = m^{t phi(n) + 1} mod n. По теореме Эйлера о тотиент-функции m^{phi(n)} mod n = 1, так что мы просто вычислили открытый текст m, потому что n берется всегда больше m.

Число n сравнительно небольшое, 256 бит, поэтому можно попробовать факторизовать его напрямую, используя Maple:

n := 70736025239265239976315088690174594021646654881626421461009089480870633400973;
ifactor(n);


И действительно, через 3 с небольшим минуты Maple даст ответ (238324208831434331628131715304428889871)(296805874594538235115008173244022912163). Найдем d:

e := 3:
p := 238324208831434331628131715304428889871:
q := 296805874594538235115008173244022912163:
phin := (p - 1) * (q - 1):
d := 1 / e mod phin;


и открытый текст m:

c := 29846947519214575162497413725060412546119233216851184246267357770082463030225:
m := c &^ d mod n:
convert(ListTools[Reverse](convert(m, base, 256)), bytes);


Флаг ABCTF{th1s_was_h4rd_in_1980}.


AES Mess - 75 (Cryptography)
We encrypted a flag with AES-ECB encryption using a secret key, and got the hash: e220eb994c8fc16388dbd60a969d4953f042fc0bce25dbef573cf522636a1ba3fafa1a7c21ff824a5824c5dc4a376e75
However, we lost our plaintext flag and also lost our key and we can't seem to decrypt the hash back :(.
Luckily we encrypted a bunch of other flags with the same key. Can you recover the lost flag using this?
Hint: There has to be some way to work backwards, right?

Алгоритм AES-ECB просто шифрует текст независимыми блоками по 16 байт. Поэтому если вдруг мы обнаружим в гисте с 2000 примерами шифрования строк вида abctf{...} все нужные шифроблоки e220eb994c8fc16388dbd60a969d4953, f042fc0bce25dbef573cf522636a1ba3 и fafa1a7c21ff824a5824c5dc4a376e75, для которых мы знаем прообраз, то выясним и флаг. Конечно, так и выходит.

dic = {}
for line in file_get('aes.txt').splitlines():
    p, h = line.split(':')
    for i in range((len(p) + 15) / 16):
        dic[h[32 * i : 32 * i + 32]] = p[i * 16 : i * 16 + 16]

hh = 'e220eb994c8fc16388dbd60a969d4953f042fc0bce25dbef573cf522636a1ba3fafa1a7c21ff824a5824c5dc4a376e75'
for i in range(3):
    part = hh[i * 32 : i * 32 + 32]
    if part in dic:
        print dic[part]

Скрипт выводит флаг abctf{looks_like_you_can_break_aes}.


A Small Broadcast - 125 (Cryptography)
I RSA encrypted the same message 3 different times with the same exponent. Can you decrypt this?

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

В Википедии описана атака Хастада, которая как раз возможна, если мы имеем маленькую экспоненту e и не меньше, чем e результатов шифрования одного и того же сообщения. Похоже, здесь именно этот случай и e=3.

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

C_1 = M^3 mod N_1 C_2 = M^3 mod N_2 C_3 = M^3 mod N_3

и это число M^3 < N_1 N_2 N_3, то по китайской теореме об остатках мы можем его однозначно восстановить по формуле

M^3 = sum_{i=1}^3 C_i Pi_i Pi_i^{-1} mod Pi Pi = prod_{i=1}^3 N_i Pi_i = frac{Pi}{N_i} Pi_i^{-1} = left( frac{1}{Pi_i} right)^{-1} mod N_i

Реализуем в Maple:

N1 := 79608037716527910392060670707842954224114341083822168077002144855358998405023007345791355970838437273653492726857398313047195654933011803740498167538754807659255275632647165202835846338059572102420992692073303341392512490988413552501419357400503232190597741120726276250753866130679586474440949586692852365179:
C1 := 34217065803425349356447652842993191079705593197469002356250751196039765990549766822180265723173964726087016890980051189787233837925650902081362222218365748633591895514369317316450142279676583079298758397507023942377316646300547978234729578678310028626408502085957725408232168284955403531891866121828640919987:
N2 := 58002222048141232855465758799795991260844167004589249261667816662245991955274977287082142794911572989261856156040536668553365838145271642812811609687362700843661481653274617983708937827484947856793885821586285570844274545385852401777678956217807768608457322329935290042362221502367207511491516411517438589637:
C2 := 48038542572368143315928949857213341349144690234757944150458420344577988496364306227393161112939226347074838727793761695978722074486902525121712796142366962172291716190060386128524977245133260307337691820789978610313893799675837391244062170879810270336080741790927340336486568319993335039457684586195656124176:
N3 := 95136786745520478217269528603148282473715660891325372806774750455600642337159386952455144391867750492077191823630711097423473530235172124790951314315271310542765846789908387211336846556241994561268538528319743374290789112373774893547676601690882211706889553455962720218486395519200617695951617114702861810811:
C3 := 55139001168534905791033093049281485849516290567638780139733282880064346293967470884523842813679361232423330290836063248352131025995684341143337417237119663347561882637003640064860966432102780676449991773140407055863369179692136108534952624411669691799286623699981636439331427079183234388844722074263884842748:
M3 := chrem([C1, C2, C3], [N1, N2, N3]):
M := iroot(M3, 3):
convert(ListTools[Reverse](convert(M, base, 256)), bytes);


Флаг abctf{ch!n3s3_rema1nd3r_the0rem_is_to0_op_4_m3}.


Encryption Service - 140 (Cryptography)
See if you can break this!!
You can connect with nc 107.170.122.6 7765 and the source can be found here.

Программа принимает по сети hex-строку, приписывает слева "ENCRYPT:", справа флаг, дополняет до размера, кратного 16 (добавляя 16 символов, если уже). Потом шифрует это AES с помощью неизвестного ключа.

AES независимо шифрует блоки по 16 байт. Мы можем (примерно 100 запросами) получить шифротекст для строк вида

000000000000000A
000000000000000B
000000000000000C
...


и так далее, забив первый блок до конца нулями. Для этого нужно передать серверу зеленую строку:

ENCRYPT:00000000 000000000000000A ABCTF{unknown}xx

Что произойдет, если теперь мы отрежем у нее на конце 1 символ и снова ее передадим?

ENCRYPT:00000000 000000000000000A BCTF{unknown}xxx

Один байт флага попадет во второй блок и шифроблок совпадет с каким-то из ранее посчитанных! Таким образом мы узнаем первый байт флага. Вновь посчитаем AES от группы строк

ENCRYPT:00000000 00000000000000AA ABCTF{unknown}xx
ENCRYPT:00000000 00000000000000AB ABCTF{unknown}xx
ENCRYPT:00000000 00000000000000AC ABCTF{unknown}xx
...


и отрежем уже 2 байта с конца передаваемой строки.

ENCRYPT:00000000 00000000000000AB CTF{unknown}xxxx

Уже два байта флага попадут во второй шифроблок и совпадут с одним из примерно 100 заново посчитанных, ведь первый мы уже знаем. Так, символ за символом, можно выяснить все байты флага. Но для начала нужно еще определить его длину, посмотрев, как меняется длина шифротекста, если передавать все более и более длинные строки. Окажется 20, так что придется добавить еще нулей и алгоритм использовать уже на третьем блоке.

Напишем код для брутфорса. Сервер выдавал примерно 1 rps, так что понадобилось чуть больше получаса.

from Crypto.Cipher.AES import AESCipher
import socket

def cr(hexs):
    sock = socket.socket()
    sock.connect(('107.170.122.6', 7765))
    data1 = sock.recv(1024)
    sock.send(hexs + '\n')
    result = ''
    while True:
        data = sock.recv(1024)
        if data:
            result += data
        else:
            break
    sock.close()
    return result[len('Here you go:'):-1]

known_key = ''
for get_char in range(20):
    s = '00' * (39 - len(known_key))
    their_char = cr(s)
    for next_ch in range(ord(' '), ord('~') + 1):
        print '\r', known_key + chr(next_ch),
        sc = s + known_key.encode('hex') + '%02x' % next_ch
        own_char = cr(sc)
        if own_char[64 : 96] == their_char[64 : 96]:
            known_key += chr(next_ch)
            break

Буквы появлялись одна за другой, как в голливудском боевике, пока не показался целиком флаг ABCTF{p4dding_4_fun}.


Sexy RSA - 160 (Cryptography)
Check this out!

В этот раз n длинный и просто так его не факторизовать. Если только p и q не слишком близки. Вычисляем квадратный корень в Maple

n := 1209143407476550975641959824312993703149920344437422193042293131572745298662696284279928622412441255652391493241414170537319784298367821654726781089600780498369402167443363862621886943970468819656731959468058528787895569936536904387979815183897568006750131879851263753496120098205966442010445601534305483783759226510120860633770814540166419495817666312474484061885435295870436055727722073738662516644186716532891328742452198364825809508602208516407566578212780807:
evalf(sqrt(n), 250);

1099610570827941329700237866432657027914359798062896153406865588143725813368448278118977438921370935678732434831141304899886705498243884638860011461262640420256594271701812607875254999146529955445651530660964259381322198377196122396.000000000000000000

и, получив ответ, обнаруживаем, что они именно такие. Действительно,

isqrt(n) ** 2 - n;
9

а значит, n = (isqrt(n) - 3) * (isqrt(n) + 3). Назовем первое p, второе q, и расшифруем сообщение.

e := 65537:
c := 293430917376708381243824815247228063605104303548720758108780880727974339086036691092136736806182713047603694090694712685069524383098129303183298249981051498714383399595430658107400768559066065231114145553134453396428041946588586604081230659780431638898871362957635105901091871385165354213544323931410599944377781013715195511539451275610913318909140275602013631077670399937733517949344579963174235423101450272762052806595645694091546721802246723616268373048438591:
p := isqrt(n) - 3:
q := isqrt(n) + 3:
phin := (p - 1) * (q - 1):
d := 1 / e mod phin:
m := c &^ d mod n:
convert(ListTools[Reverse](convert(m, base, 256)), bytes);


и флаг ABCTF{i_h4ve_an_RSA_fetish_;)}.


Custom Authentication - 160 (Cryptography)
I just learned about encryption and tried to write my own authentication system. Can you get in? Here is the source! And here is the site.

При запросе на /login скрипт создает объект {username: req.username, password: req.password, admin: "false"} (на самом деле admin: результат проверки, но мы вряд ли угадаем пароль). Объект кодируется в JSON и шифруется AES-CBC.

При запросе / желанный флаг отображается, если admin:"true" в JSON, полученном строкой

JSON.parse(decrypt(req.cookies.auth).replace(/[^0-9a-zA-Z{}":, ]+/g, ''))

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

","admin":"true","x":"

После кодирования объект превратится в

{"username":"1","password":"\",\"admin\":\"true\",\"x\":\"","admin":"false"}

А после удаления бэкслешей

{"username":"1","password":"", "admin":"true", "x":"", "admin":"false"}

К сожалению, для взлома этого недостаточно, декодированный из JSON объект будет использовать последнее указанное значение ключа admin, то есть "false".

Но можно попробовать испортить имя ключа, зная, что после расшифровки его шифроблока полученная строка ксорится с предыдущим шифроблоком. Конечно, даже изменение одного бита в предыдущем шифроблоке приведет к тому, что открытый текст в нем превратится в тыкву, но можно сделать так, чтобы он приходился на строку, и тыква была отфильтрована все той же заменой. Лишь бы там случайно не появились кавычки :-)

{"username":"1",
"password":"\",\
"admin\":\"true\
",\"x\":\"000000
1234567812345678
","admin":"false
"}


Мы инвертируем младший бит в 4-м байте 5-го шифроблока. При этом вместо красных байт дешифруется упячка, а вместо зеленого — @.

Зашифруем строку

def get_encrypted_cookie(pwd):
    resp = requests.post('http://107.170.122.6:3001/login',
                         data={'username':'1', 'password': crafted_pwd})
    return resp.history[0].cookies.get('auth')

crafted_pwd = r'","admin":"true","x":"0000001234567812345678'
enc = get_encrypted_cookie(crafted_pwd)
print enc

Сервер прислал

c5113f3d155e021350229f966a02636f
c95d772f8c585d0ac07654819dbfc515
b2070ab67a48d854454ac4683bd55e11
28dd4684913dc7f5dce8e6fcb49578a2
ff1c543d35f114bd76bbab6727e73bff
299a9752c441f3aeaa61423825d49354
94ab819da6010bcf7e0906c23cc9cfe2


Превратим второй ключ admin в @dmin, инвертировав младший бит в 4-м байте 5-го шифроблока, и отправим это на сервер.

enc2 = ''.join('''
c5113f3d155e021350229f966a02636f
c95d772f8c585d0ac07654819dbfc515
b2070ab67a48d854454ac4683bd55e11
28dd4684913dc7f5dce8e6fcb49578a2
ff1c543c35f114bd76bbab6727e73bff
299a9752c441f3aeaa61423825d49354
94ab819da6010bcf7e0906c23cc9cfe2'''
.splitlines()[1:])
resp = requests.get('http://107.170.122.6:3001/', cookies={'auth': enc2})
print resp.content

Сервер сообщит нам, что мы админы и флаг abctf{i_used_encryption_so_it_must_be_secure}.



Forensics


Для форенсики полезно уметь опознавать распространенные форматы файлов, понимать, какого рода данные могут в них неявно храниться. Из инструментов пригождаются hex-редактор, TweakPNG, многочисленные распаковщики, Python.


Just open it - 15 (Forensics)
I'm almost positive we put a flag in this file. Can you find it for me?
Hint: So many editors out there!

Предлагаемый файл это JPEG-картинка, на которой нет ключа, зато есть артефакт, будто она побилась: с определенного места все квадратики 8×8 сдвинуты на один влево. Многие задачи основаны на том, что в файле одного формата хранятся данные в другом. Такие задачи-матрешки часто требуют широкой эрудиции, но в данном случае все просто. Флаг ABCTF{forensics_1_tooo_easy?} просто написан plain-текстом посреди бинарных данных JPEG, что и привело к побитому блоку.


GZ - 30 (Forensics)
We shot a flag into this file but some things got messed up on the way...

По ссылке качается файл flag без расширения, но опытный глаз без труда различает в заголовке 46-байтного файла сигнатуру 1F 8B. Заголовок задачи не врет, это действительно архив в формате GZ, который открывает, например, 7-zip. А внутри файл с ключом ABCTF{broken_zipper}.


Best Ganondorf - 50 (Forensics)
You know the deal. Find a flag in this this file?

Скачивается файл с расширением .jpg, который, однако, не открывается просмотрщиком картинок. Смотрим на него в hex-редакторе или просто в Lister, если есть Total. Действительно, в его начале отсутствует привычный JFIF-заголовок. Зато в остальном файл вполне похож на JPEG — низкая энтропия первые байт 300, а потом высокая до конца файла. Позаимствуем 11 байт из нормального JPEG-файла и добавим их в этот в hex-редакторе. FF D8 FF E0 00 10 4A 46 49 46 00, и картинка открывается. На ней счастливый азиат, много баксов и наискосок флаг abctf{tfw_kage_r3kt_nyway}.


MoonWalk - 60 (Forensics)
There is something a little off about this picture. If you could help us we could give you some points! Just find us a flag!

На двухметровой PNG-картинке изображен Инки из Pac-Man в темных очках. Слишком большой размер файла вызвал подозрения о том, что внутри PNG могут быть еще какие-то данные, и я воспользовался программой TweakPNG для промотра секций файла. Та немедленно обнаружила «мусор в конце файла» в секции puNK размером 1588789 байт, то есть на 2/3 файла. Находим эту строку и видим, что за ней идет знакомый JFIF-заголовок. Просто пересохраняем содержимое секции:

f = file_get('PurpleThing.png')
file_put('punk.jpg', f[f.index('puNK') + 4:])

и открываем. Там фотография листка бумаги, на котором карандашом от руки написан флаг ABCTF{PNG_S0_C00l}. Здесь авторы от души поиздевались над участниками, потому что весьма непросто опознать, что в их неровном почерке нули, а не буквы, и все, кроме последней, буквы заглавные.


PasswordPDF - 80 (Forensics)
Oh no. We locked this PDF and forgot the password. Can you help us?
Hint: Is there anyway to guess the password?

Брутфорсим пароль на PDF силами Advanced PDF Password Recovery, Passware Password Recovery Kit Forensic по словарю из стандартной поставки или чем-то похожим. Пароль оказывается elephant.

Но открыв PDF, мы видим, что там написано Password:, а флаг закрыт большой красной плашкой "Censored!!! You hackers will never get me now". В STDU Viewer можно просто Файл > Экспортировать > как текст и узнать, что флаг ABCTF{Damn_h4x0rz_always_bypassing_my_PDFs}.


Zippy - 120 (Forensics)
If your could fix this mess I am sure there would be a flag waiting for you.

По ссылке скачивается архив с 54 ZIP-файлами внутри с именами вида chunk53.zip и размером 130 байт. Внутри каждого архива лежит файл data.txt размером 4 байта, каждый запаролен. Алгоритм шифрования в ZIP слабый и, например, уязвим к plain-text атаке, если известно хотя бы 13 символов, но в данном случае она неприменима и простой брутфорс тоже не дает результата.

Заметим, однако, что к каждому файлу внутри ZIP-архива прилагается CRC32 для контроля целостности. И если внутри просто 4 байта текста, то вполне можно перебрать все возможные тексты и искать среди них с нужным CRC32. Для разнообразия большей эффективности я взял C++, скодогенерировав для него функцию проверки CRC32 на совпадение с одним из искомых.

#include <iostream>
#include <string>
#include <boost/crc.hpp>
using namespace std;

void check(char *data, unsigned int h)
{
    if (h == 3731210267u) { cout << "chunk0.zip: " << string(data, 4) << endl; }
    if (h == 2975268760u) { cout << "chunk1.zip: " << string(data, 4) << endl; }
    ...
}

int main()
{
    char data[4];
    for (data[0] = ' '; data[0] < '~'; ++data[0]) {
        cout << data[0] << endl;
        for (data[1] = ' '; data[1] < '~'; ++data[1]) {
            for (data[2] = ' '; data[2] < '~'; ++data[2]) {
                for (data[3] = ' '; data[3] < '~'; ++data[3]) {
                    boost::crc_32_type crc32;
                    crc32.process_bytes(data, 4);
                    check(data, crc32.checksum());
                }
            }
        }
    }
}

И весьма быстро получил результат! Впрочем, это оказался не пароль, а закодированный в BASE-64... еще один запароленный ZIP-архив. Внутри лежал flag.txt размером 35 байт. Этот пароль уже вскрылся брутфорсом, потому что состоял всего из 3 символов: z1P. И флаг flag{i_z1pp3d_a_zip_w1th_sum_zips}.



Virtual Series


Эта категория включала тоже задачи на форенсику, но в отличие от предыдущей, где можно было проанализировать файл целиком, здесь предлагался образ, содержащий уйму файлов, среди которых нужно было найти несколько интересных. Инструменты: VirtualBox, файловый менеджер, hex-редактор.


Virtual Box 1 - 10 (Virtual Series)
Do you know how to use a VM? Download the Virtual Machine here.

В этой и последующих задачах этой категории используется один и тот же 645-метровый образ виртуальной машины в формате .ova. Его понимает VirtualBox, да и сам по себе он просто архив с файлом параметров машины и образом диска в формате vmdk. А вот в VMWare он не завелся. Практичным виндоподходом к этой задаче было просто сконвертировать vmdk-файл в vhd-образ командой вида VBoxManage.exe clonehd --format vhd M:disk1.vmdk M:disk1.vhd и примонтировать его через Computer Management > Disk Management > Attach VHD. После этого в системе появлялся диск, к которому можно было применять всю мощь инструментария форенсики, а в несжатом образе vhd найти половину флагов простым поиском подстроки "abctf{".

Image

Первый флаг лежал на рабочем столе в файле "flag 1.doc", намекая, что его было бы неплохо открыть. В нем просто была написан флаг ABCTF{FREE_P0INTS}.

Стоит отметить, что автор задачи предпринял меры, чтобы все вставленные им флаги нельзя было найти, просто отсортировав файлы на ФС по дате. Для этого он использовал FileDate Changer от NirSoft, от использования которой, тем не менее, осталось еще больше следов на ФС :-)


Virtual Box 2 - 15 (Virtual Series)
Darn, I found this flag so I put it in flag 1.doc but I can't seem to be able to see it anymore.

Как и в задаче Just open it - 15 бинарный формат DOC содержит plain-текст с флагом, поэтому достаточно открыть его в plain-текстовом редакторе. ABCTF{H1DDEN_AWAY}.

Image


Virtual Box 3 - 35 (Virtual Series)
This mysterious file was left here, but I have no idea how to open it. Do you? I left it in a folder named 2016 just for you.

В Моих документах лежит папка 2016, а в ней XLSX файл. Если ранние версии Office использовали для форматов DOC и XLS COM Structured Storage, то сейчас, вслед за OpenOffice, данные документа пишутся в XML, которые потом пакуются в ZIP. Флаг находится простым поиском по строке "abctf" внутри архива, в файле sharedStrings: ABCTF{FR0M_THE_FUTURE}.

Image


Virtual Box 4 - 60 (Virtual Series)
What's this?

В Корзине лежит whatsthis.txt, а в нем ABCTF{N0T_S0_D1ff1CULT}, но он неправильный, позднее авторы признались, что просто забыли удалить его, когда создавали задачу. Делаем поиск по ABCTF в имени файла по всему диску и находим c:\program files\Outlook Express\ABCTF{Y0U_F0UND_ME}\whats this.txt. ABCTF{Y0U_F0UND_ME} и есть флаг к этой задаче.

Image


Virtual Box 5 - 75 (Virtual Series)
I accidentaly closed out this odd message I found. Can you get it back?

В форенсике традиционно любопытство к веб-активности пользователя. У IE6 в кэше или истории, в которой не удалили только одну запись, можно найти картинку http://imgur.com/FQJ4JtO:

Image

Сперва кажется, что это шифр простой замены, где первые 5 символов, очевидно, abctf, сильно отличающиеся треугольники это {}, а толстые стрелки вправо соответствуют _. Из abctf{1t2_с34_435167_bac5} заключаем, что 5 наверняка k, 1t2 это its, а окончание предпоследнего слова king, но дальше abctf{its_с34_43king_back} фантазия пробуксовывала, родив разве что неверный остроумный вариант its_сat_taking_back.

Но несколько позже обнаружилось, что это не шифр простой замены, а просто текст, набранный идеографическим шрифтом Wingdings 3. Дублирующиеся стрелки вниз соответствовали двум символам, а не одному, а флаг был ABCTF{ITS_C00L_L00KING_BACK}.


Virtual Box 6 - 75 (Virtual Series)
I really love this pattern, can I set it as my wallpaper? Here it is.

Смотрим список стандартных обоев рабочего стола и замечаем, что к Houndstooth, узор которого используется на платье с картинки по ссылке, справа пририсован текст флага. ABCTF{I_L0VE_PATTERNS.

Image


Virtual Box 7 - 100 (Virtual Series)
Hmm, I wish I could figure out the team that created Windows 98 without the map hassle.

Олдфаги помнят, что если в Windows 98 открыть карту выбора временной зоны (есть, например, в Control Panel > Regional Settings), зажать Ctrl, нажать левую кнопку мыши в районе Каира, отпустить в районе Мемфиса, вновь нажать и отпустить в районе Редмонда вместе с Ctrl, можно увидеть пасхалку, со списком команды разработки Windows 98. Можете еще поискать Польшу :-)

Image

Ту же самую пасхалку можно вызвать и другим способом, если создать ярлык к файлу C:\Application Data\Microsoft\WELCOME\Weldata.exe, передав параметром командной строки You_are_a_real_rascal и указав, что программа должна стартовать с минимизированным окном.

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

Файл intl.cpl, отвечающий за региональные настройки в Панели управления, действительно по событиям левой кнопки мыши проверяет, используя WinAPI PtInRect и конечный автомат, попало ли событие в нужную область и выполнены ли они в нужном порядке. Если да, он достает строку "Weldata.exe You_are_a_real_rascal" из ресурсов, зашифрованную увеличением кодов символов на 1, и запускает ее через ShellExecute, добавив путь к папке.

Программа Weldata.exe создает окно, на нем окно браузера, достает из своего тела длинную строку, зашифрованную XOR с аргументом командной строки, пишет ее в ~x520.htm, который запускает в браузере, тут же переименовывает в ~x405.htm и удаляет. Если самому расксорить эту строку со строкой You_are_a_real_rascal, получается HTML-файл с кучей JavaScript, списком авторов, списком картинок, вставлявшихся из res://membg.dll/, фоновой музыкой, и небольшим VBScript, обрабатывавшим нажатия клавиш.

Решительно нигде, ни в intl.cpl, ни в Weldata.exe, ни в Welcome.exe, который она запускает по дефолту, ни в Welcome.dat не было ничего подозрительного, что бы могли изменить организаторы. Ничего не дала даже попытка проксорить с разными сдвигами строки You_are_a_real_rascal весь гигабайтный образ диска. Измучившись сравнением изрядно пропатченной Windows 98 с оригинальным дистрибутивом и парсингом своп-файла, я подумал, а не может ли ответ быть просто ABCTF{You_are_a_real_rascal}? Да, это и был последний флаг категории!


Разбор категорий Reconaissance, Programming, Web Exploitation, Reverse Engineering, Binary Exploitation в следующем посте