Всем привет!
Сегодня закончился CTF NeoQuest, и я хочу показать решение одной задачи из школьного трека.Название: Коридоры замка
Описание: "Из странных писем складывается различимое послание. Кажется, я могу прочитать. «Странник! Пишет тебе князь Ксеритха. Много лет назад, на охоте, я подстрелил черную птицу. Она оказалась подручной ведьмы, и та прокляла город. С тех пор мы не можем разговаривать, не способны покинуть Ксеритх и живем во тьме. Многие пытались спасти меня – приглашенные маги, инженеры, рыцари. Но ведьма сказала, что лишь тот, кто придет из другого мира, сможет снять проклятие. Если справишься, сделаю для тебя что угодно. Чтобы начать, найди придворного мага. Он подскажет путь». Не могу поверить. Что еще за Ксеритх? Посмотреть бы в поисковике, но мой телефон не работает. Странный сбой. Серый экран, дата и время: 17 февраля 2024 года 14:12. И больше ничего. Осматриваю себя. Я как-то изменился… Меч, высокие сапоги, перчатки. Ладно… Допустим… Я – и спасение целого города? Вот к такому повороту событий привыкнуть сложнее. Я, может быть, и не хочу никого спасать. Я этот Ксеритх впервые вижу! Сдался он мне. Как до этого мага дойти-то вообще? У меня даже карты нет."
Дан файл: easy_chipher.exe (приложил)
Запустим его:
Видно, что сообщение шифруется с динамичным ключом, так как на один ввод разные ответы.
Посмотрим его в DetectItEasy:
exe'шник упакован с помощью Pyinstaller, значит язык - Python.
Достаем исходный код
Попробуем достать .pyc файл из exe.Можно использовать pyinstxtractor, но я декомпилировал
Ссылка скрыта от гостей
:Скачиваем архив. Нам нужен файл "easy_chipher.pyc". Если его запустить, то мы получим тот же самый функционал, как и в exe.
Открыть в редакторе его не получится, так как ".pyc" файлы - это скомпилированный байткод скрипта.
Попробуем декомпилировать в `.py` файл с помощью "decompyle3":
Видим ошибку, так как "decompyle3"не работает с версией python 3.12.
Путем проб и ошибок стало ясно, что, так как 3.12 - это новая версия Python, то большинство декомпиляторов её ещё не поддерживают.
К счастью я наткнулся на
Ссылка скрыта от гостей
, в которой описывается подобный случай.Для декомпиляции будем использовать pycdc, которая активно поддерживается сообществом. Для использования надо скомпилировать "pycdc.exe" из исходников.
1. Создаем папку, например "another";
2. Копируем в него репозиторий:
git clone https://github.com/zrax/pycdc.git
3. Компилируем:
cmake pycdc
4. Выходим из папки "another":
cd ..
5. Ещё раз компилируем:
cmake --build another
6. В папке ".\another\Debug\" лежит "pycdc.exe".
Запускаем:
.\pycdc.exe .\easy_chipher.pyc
:Уже лучше! Получаем исходный код программы, правда не полный.
Видны 2 ошибки:
Unsupported opcode: BINARY_SLICE
и Unsupported opcode: JUMP_BACKWARD
.Ошибки в байткоде обрабатываются pycdc в файле
ASTree.cpp
, строки 2466-2471:
C++:
default:
fprintf(stderr, "Unsupported opcode: %s\n", Pyc::OpcodeName(opcode & 0xFF));
cleanBuild = false;
return new ASTNodeList(defblock->nodes());
C++:
default:
fprintf(stderr, "Unsupported opcode: %s\n", Pyc::OpcodeName(opcode & 0xFF));
break; // <----------------
cleanBuild = false;
return new ASTNodeList(defblock->nodes());
Теперь заново компилируем "pycdc.exe" и запускаем.
Получаем сий код:
Python:
import random
import time
def encoding(string, key):
Unsupported opcode: BINARY_SLICE
Unsupported opcode: JUMP_BACKWARD
Unsupported opcode: END_FOR
Unsupported opcode: JUMP_BACKWARD
Unsupported opcode: END_FOR
Warning: block stack is not empty!
str_encode = ''
for i in range(0, len(string), 6):
block = i + 6
encode_block = ''
for j in range(len(block)):
temp = ord(block[j]) ^ key[j]
encode_block += chr(temp)
str_encode += encode_block
return str_encode.encode()
def getkey():
Unsupported opcode: JUMP_BACKWARD
Unsupported opcode: END_FOR
Warning: block stack is not empty!
random.seed(int(time.time()))
key = []
for i in range(0, 6):
temp = random.randint(0, 256)
key.append(temp)
return key
print('[*] Hey guys! This is cipher program')
print('[*] Write the message:')
message = str(input())
if message.encode() == b'\r\xc2\xa9\xc3\xae\xc3\x97\xc2\x85~t\xc2\x9d\xc2\xb9\xc3\x95\xc3\x93/u\xc3\x8f\xc2\xb9\xc3\x9f\xc2\x84xt\xc2\x9a\xc2\xb9\xc3\x97\xc3\x93+\'\xc3\x8f\xc2\xbe\xc3\x92\xc2\x8f+%\xc2\x9a\xc3\xad\xc3\x9f\xc2\x80ru\xc3\x8b\xc2\xb9\xc2\x83\xc2\x80,w\xc2\x9d\xc3\xaa\xc3\x95\xc3\x96,{\xc3\x8d\xc3\xaf\xc3\x9f\xc2\x87}!\xc3\x88\xc2\xb9\xc2\x84\xc3\x93{%\xc3\x8a\xc3\xaf\xc2\x84\xc3\x96r"\xc3\x81\xc3\xac\xc3\x9e':
print('[ERORR] You want to hack me!? Nope.')
else:
encode = encoding(message, getkey())
print('[*] Encoding message: ', encode)
print('[*] Press enter to exit')
input()
Анализируем код
Первым делом приведем код в нормальный вид (исправим паддинги и другие ошибки):
Python:
import random
import time
def encoding(string, key):
str_encode = ''
for i in range(0, len(string), 6):
# block = i + 6 # <--- неправильная декомпиляция
block = string[i:i + 6] # <--- правильный вариант
encode_block = ''
for j in range(len(block)):
temp = ord(block[j]) ^ key[j]
encode_block += chr(temp)
str_encode += encode_block
return str_encode.encode() # <--- return вернул из циклов for
def getkey():
random.seed(int(time.time()))
key = []
for i in range(0, 6):
temp = random.randint(0, 256)
key.append(temp)
return key # <--- return вернул из цикла for
print('[*] Hey guys! This is cipher program')
print('[*] Write the message:')
message = str(input())
if message.encode() == b'\r\xc2\xa9\xc3\xae\xc3\x97\xc2\x85~t\xc2\x9d\xc2\xb9\xc3\x95\xc3\x93/u\xc3\x8f\xc2\xb9\xc3\x9f\xc2\x84xt\xc2\x9a\xc2\xb9\xc3\x97\xc3\x93+\'\xc3\x8f\xc2\xbe\xc3\x92\xc2\x8f+%\xc2\x9a\xc3\xad\xc3\x9f\xc2\x80ru\xc3\x8b\xc2\xb9\xc2\x83\xc2\x80,w\xc2\x9d\xc3\xaa\xc3\x95\xc3\x96,{\xc3\x8d\xc3\xaf\xc3\x9f\xc2\x87}!\xc3\x88\xc2\xb9\xc2\x84\xc3\x93{%\xc3\x8a\xc3\xaf\xc2\x84\xc3\x96r"\xc3\x81\xc3\xac\xc3\x9e':
print('[ERORR] You want to hack me!? Nope.')
else:
encode = encoding(message, getkey())
print('[*] Encoding message: ', encode)
print('[*] Press enter to exit')
input()
Обратим внимание на способ генерации ключа: используется
random.seed(int(time.time()))
.int(time.time())
в данном случае возвращает UNIX-time время, т.е. каждую секунду будет уникальный ключ.Переведём время "17.02.2024 14:12:00" в UNIX вид: получим
1708179120
Из описания таска мы знаем, что дата и время - 17 февраля 2024 года 14:12, секунды нам неизвестны. Благо в минуте их всего 60, мы можем пробрутить XOR с каждым из них.
Напишем программу:
Python:
import random
import time
current_time = 1708179120
def encoding(string, key):
str_encode = ''
for i in range(0, len(string), 6):
block = string[i:i + 6]
# print(block)
encode_block = ''
for j in range(len(block)):
temp = ord(block[j]) ^ key[j]
encode_block += chr(temp)
str_encode += encode_block
return str_encode.encode()
def getkey():
# 1708179178
random.seed(current_time)
key = []
for i in range(0, 6):
temp = random.randint(0, 256)
key.append(temp)
return key
print('[*] Hey guys! This is cipher program')
print('[*] Write the message:')
message = b'\r\xc2\xa9\xc3\xae\xc3\x97\xc2\x85~t\xc2\x9d\xc2\xb9\xc3\x95\xc3\x93/u\xc3\x8f\xc2\xb9\xc3\x9f\xc2\x84xt\xc2\x9a\xc2\xb9\xc3\x97\xc3\x93+\'\xc3\x8f\xc2\xbe\xc3\x92\xc2\x8f+%\xc2\x9a\xc3\xad\xc3\x9f\xc2\x80ru\xc3\x8b\xc2\xb9\xc2\x83\xc2\x80,w\xc2\x9d\xc3\xaa\xc3\x95\xc3\x96,{\xc3\x8d\xc3\xaf\xc3\x9f\xc2\x87}!\xc3\x88\xc2\xb9\xc2\x84\xc3\x93{%\xc3\x8a\xc3\xaf\xc2\x84\xc3\x96r"\xc3\x81\xc3\xac\xc3\x9e'
message = message.decode()
for i in range(60):
encode = encoding(message, getkey())
print('[*] Encoding message: ', encode.decode())
current_time+=1
Запускаем. На 58 секунде (1708179178) получаем верный флаг.
Ответ:
NQ20247ee2de67e8327be0dad7b58afb187863ed7f4e62af853807b0ecd1f23ca8a909