Pull to refresh
2677.95
RUVDS.com
VDS/VPS-хостинг. Скидка 15% по коду HABR15

Хакерский квест RUVDS подходит к концу, но сразиться за главный приз 142 857 рублей еще не поздно

Reading time18 min
Views4.3K

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

Напомним, с чего всё началось: участники уже раскрыли секрет «Загадочной визитки», которую разобрали до последнего волокна и в результате залетели в блокчейн, а далее и в межпланетную файловую систему с новой порцией загадок.

Подробнее о первом этапе прохождения в прошлой статье.

Мини-оглавление:


Разбор задание 3.1
Разбор задания 3.2
Кому пельменей?
Смешные моменты

Итак, что же ждало участников дальше?




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

▍ Задание 3.1


Вскоре после написания сообщения @phuuki4uta:matrix.org получим пару файлов: code-XXXXX.pyc и code-XXXXX.pyc.sig (где XXXXX — 5 цифр) вместе с сообщением «I am a colleague of John Doe. He've passed something for you. Good luck!«. Как и прежде проверим подпись при помощи .sig-файла, и при помощи утилиты file выясним, что в файле .pyc байт-код программы на Python 3.7. Вооружимся xdis и дизассемблируем код:
Дизассемблированный код
# Python bytecode 3.7.0 (3394)
# Timestamp in code: 0 (1970-01-01 03:00:00)
# Source code size mod 2**32: 1357 bytes
# Method Name:       <module>
# Filename:          code-XXXXX.py
# Argument count:    0
# Keyword-only arguments: 0
# Number of locals:  0
# Stack size:        6
# Flags:             0x00000040 (NOFREE)
# First Line:        3
# Constants:
#    0: <Code3 code object wies3 at 0x7f699aa41840, file code-XXXXX.py>, line 3
#    1: 'wies3'
#    2: 0
#    3: 3
#    4: 268435455
#    5: 1
#    6: 2
#    7: 4
#    8: 2033349222
#    9: 859399546
#   10: 1914726229
#   11: 545537777
#   12: 4259271294
#   13: 3762695033
#   14: 2530072744
#   15: 1774340374
#   16: 314947544
#   17: 'Ciphertext = '
#   18: 'Plaintext = ?'
#   19: None
# Names:
#    0: wies3
#    1: roh3s
#    2: AssertionError
#    3: ieng8
#    4: kiiv4
#    5: op7bi
#    6: len
#    7: print
  3:           0 LOAD_CONST           (<Code3 code object wies3 at 0x7f699aa41840, file code-XXXXX.py>, line 3)
               2 LOAD_CONST           ('wies3')
               4 MAKE_FUNCTION        (Neither defaults, keyword-only args, annotations, nor closures)
               6 STORE_NAME           (wies3)

 31:           8 LOAD_CONST           (0)
              10 LOAD_CONST           (0)
              12 LOAD_CONST           (0)
              14 LOAD_CONST           (0)
              16 BUILD_LIST           4
              18 STORE_NAME           (roh3s)

 32:          20 LOAD_NAME            (roh3s)
              22 LOAD_CONST           (3)
              24 BINARY_SUBSCR
              26 LOAD_CONST           (268435455)
              28 BINARY_AND
              30 LOAD_CONST           (0)
              32 COMPARE_OP           (!=)
              34 POP_JUMP_IF_TRUE     (to 40)
              36 LOAD_GLOBAL          (AssertionError)
              38 RAISE_VARARGS        (exception instance)

 35:     >>   40 LOAD_CONST           (0)
              42 LOAD_CONST           (0)
              44 LOAD_CONST           (0)
              46 LOAD_CONST           (0)
              48 BUILD_LIST           4
              50 STORE_NAME           (ieng8)

 36:          52 LOAD_NAME            (ieng8)
              54 LOAD_CONST           (3)
              56 BINARY_SUBSCR
              58 LOAD_CONST           (268435455)
              60 BINARY_AND
              62 LOAD_CONST           (0)
              64 COMPARE_OP           (!=)
              66 POP_JUMP_IF_TRUE     (to 72)
              68 LOAD_GLOBAL          (AssertionError)
              70 RAISE_VARARGS        (exception instance)

 39:     >>   72 LOAD_CONST           (0)
              74 LOAD_CONST           (0)
              76 LOAD_CONST           (0)
              78 LOAD_CONST           (0)
              80 LOAD_CONST           (0)
              82 BUILD_LIST           5
              84 STORE_NAME           (kiiv4)

 40:          86 LOAD_NAME            (kiiv4)
              88 LOAD_CONST           (0)
              90 BINARY_SUBSCR
              92 LOAD_CONST           (0)
              94 COMPARE_OP           (!=)
              96 POP_JUMP_IF_FALSE    (to 146)
              98 LOAD_NAME            (kiiv4)
             100 LOAD_CONST           (1)
             102 BINARY_SUBSCR
             104 LOAD_CONST           (0)
             106 COMPARE_OP           (!=)
             108 POP_JUMP_IF_FALSE    (to 146)
             110 LOAD_NAME            (kiiv4)
             112 LOAD_CONST           (2)
             114 BINARY_SUBSCR
             116 LOAD_CONST           (0)
             118 COMPARE_OP           (!=)
             120 POP_JUMP_IF_FALSE    (to 146)
             122 LOAD_NAME            (kiiv4)
             124 LOAD_CONST           (3)
             126 BINARY_SUBSCR
             128 LOAD_CONST           (0)
             130 COMPARE_OP           (!=)
             132 POP_JUMP_IF_FALSE    (to 146)
             134 LOAD_NAME            (kiiv4)
             136 LOAD_CONST           (4)
             138 BINARY_SUBSCR
             140 LOAD_CONST           (0)
             142 COMPARE_OP           (!=)
             144 POP_JUMP_IF_TRUE     (to 150)
         >>  146 LOAD_GLOBAL          (AssertionError)
             148 RAISE_VARARGS        (exception instance)

 42:     >>  150 LOAD_CONST           (2033349222)
             152 LOAD_CONST           (859399546)
             154 BUILD_LIST           2
             156 STORE_NAME           (op7bi)

 44:         158 LOAD_NAME            (wies3)
             160 LOAD_NAME            (op7bi)
             162 LOAD_NAME            (len)
             164 LOAD_NAME            (op7bi)
             166 CALL_FUNCTION        (1 positional argument)
             168 LOAD_NAME            (roh3s)
             170 CALL_FUNCTION        (3 positional arguments)
             172 POP_TOP

 45:         174 LOAD_NAME            (wies3)
             176 LOAD_NAME            (op7bi)
             178 LOAD_NAME            (len)
             180 LOAD_NAME            (op7bi)
             182 CALL_FUNCTION        (1 positional argument)
             184 LOAD_NAME            (ieng8)
             186 CALL_FUNCTION        (3 positional arguments)
             188 POP_TOP

 47:         190 LOAD_NAME            (op7bi)
             192 LOAD_CONST           (1914726229)
             194 LOAD_CONST           (545537777)
             196 BUILD_LIST           2
             198 COMPARE_OP           (==)
             200 POP_JUMP_IF_TRUE     (to 206)
             202 LOAD_GLOBAL          (AssertionError)
             204 RAISE_VARARGS        (exception instance)

 49:     >>  206 LOAD_NAME            (wies3)
             208 LOAD_NAME            (kiiv4)
             210 LOAD_NAME            (len)
             212 LOAD_NAME            (kiiv4)
             214 CALL_FUNCTION        (1 positional argument)
             216 LOAD_NAME            (roh3s)
             218 CALL_FUNCTION        (3 positional arguments)
             220 POP_TOP

 50:         222 LOAD_NAME            (wies3)
             224 LOAD_NAME            (kiiv4)
             226 LOAD_NAME            (len)
             228 LOAD_NAME            (kiiv4)
             230 CALL_FUNCTION        (1 positional argument)
             232 LOAD_NAME            (ieng8)
             234 CALL_FUNCTION        (3 positional arguments)
             236 POP_TOP

 52:         238 LOAD_NAME            (kiiv4)
             240 LOAD_CONST           (4259271294)
             242 LOAD_CONST           (3762695033)
             244 LOAD_CONST           (2530072744)
             246 LOAD_CONST           (1774340374)
             248 LOAD_CONST           (314947544)
             250 BUILD_LIST           5
             252 COMPARE_OP           (==)
             254 EXTENDED_ARG         (256)
             256 POP_JUMP_IF_TRUE     (to 262)
             258 LOAD_GLOBAL          (AssertionError)
             260 RAISE_VARARGS        (exception instance)

 53:     >>  262 LOAD_NAME            (print)
             264 LOAD_CONST           ('Ciphertext = ')
             266 LOAD_NAME            (kiiv4)
             268 CALL_FUNCTION        (2 positional arguments)
             270 POP_TOP

 54:         272 LOAD_NAME            (print)
             274 LOAD_CONST           ('Plaintext = ?')
             276 CALL_FUNCTION        (1 positional argument)
             278 POP_TOP
             280 LOAD_CONST           (None)
             282 RETURN_VALUE

# Method Name:       wies3
# Filename:          code-XXXXX.py
# Argument count:    3
# Keyword-only arguments: 0
# Number of locals:  7
# Stack size:        6
# Flags:             0x00000003 (NEWLOCALS | OPTIMIZED)
# First Line:        3
# Constants:
#    0: None
#    1: <Code3 code object fie1z at 0x7f699aa415a0, file code-XXXXX.py>, line 5
#    2: 'wies3.<locals>.fie1z'
#    3: <Code3 code object xaeg4 at 0x7f699aa417b0, file code-XXXXX.py>, line 8
#    4: 'wies3.<locals>.xaeg4'
#    5: 0
#    6: 562984899
#    7: 1
#    8: 6
#    9: 52
#   10: 2
#   11: 3
# Varnames:
# fau0m, tuki6, ooch0, fie1z, xaeg4, oon0j, zae3u
# Positional arguments:
# fau0m, tuki6, ooch0
# Local variables:
#    3: fie1z
#    4: xaeg4
#    5: oon0j
#    6: zae3u
# Cell variables:
#    0: aph7a
#    1: coh1p
#    2: di5ei
#    3: eing8
#    4: ik4so
#    5: ooch0
  5:           0 LOAD_CLOSURE         (aph7a)
               2 LOAD_CLOSURE         (coh1p)
               4 LOAD_CLOSURE         (di5ei)
               6 LOAD_CLOSURE         (eing8)
               8 LOAD_CLOSURE         (ik4so)
              10 LOAD_CLOSURE         (ooch0)
              12 BUILD_TUPLE          6
              14 LOAD_CONST           (<Code3 code object fie1z at 0x7f699aa415a0, file code-XXXXX.py>, line 5)
              16 LOAD_CONST           ('wies3.<locals>.fie1z')
              18 MAKE_FUNCTION        (closure)
              20 STORE_FAST           (fie1z)

  8:          22 LOAD_CONST           (<Code3 code object xaeg4 at 0x7f699aa417b0, file code-XXXXX.py>, line 8)
              24 LOAD_CONST           ('wies3.<locals>.xaeg4')
              26 MAKE_FUNCTION        (Neither defaults, keyword-only args, annotations, nor closures)
              28 STORE_FAST           (xaeg4)

 11:          30 LOAD_FAST            (fau0m)
              32 LOAD_CONST           (0)
              34 BINARY_SUBSCR
              36 STORE_DEREF          (aph7a)

 12:          38 LOAD_CONST           (0)
              40 STORE_DEREF          (ik4so)

 13:          42 LOAD_CONST           (562984899)
              44 STORE_FAST           (oon0j)

 14:          46 LOAD_FAST            (fau0m)
              48 LOAD_FAST            (tuki6)
              50 LOAD_CONST           (1)
              52 BINARY_SUBTRACT
              54 BINARY_SUBSCR
              56 STORE_DEREF          (di5ei)

 15:          58 LOAD_CONST           (6)
              60 LOAD_CONST           (52)
              62 LOAD_FAST            (tuki6)
              64 BINARY_FLOOR_DIVIDE
              66 BINARY_ADD
              68 STORE_FAST           (zae3u)

 16:          70 SETUP_LOOP           (to 230)
         >>   72 LOAD_FAST            (zae3u)
              74 LOAD_CONST           (0)
              76 COMPARE_OP           (>)
              78 POP_JUMP_IF_FALSE    (to 228)

 17:          80 LOAD_FAST            (zae3u)
              82 LOAD_CONST           (1)
              84 INPLACE_SUBTRACT
              86 STORE_FAST           (zae3u)

 18:          88 LOAD_FAST            (xaeg4)
              90 LOAD_DEREF           (ik4so)
              92 LOAD_FAST            (oon0j)
              94 BINARY_ADD
              96 CALL_FUNCTION        (1 positional argument)
              98 STORE_DEREF          (ik4so)

 19:         100 LOAD_FAST            (xaeg4)
             102 LOAD_DEREF           (ik4so)
             104 LOAD_CONST           (2)
             106 BINARY_RSHIFT
             108 CALL_FUNCTION        (1 positional argument)
             110 LOAD_CONST           (3)
             112 BINARY_AND
             114 STORE_DEREF          (eing8)

 20:         116 LOAD_CONST           (0)
             118 STORE_DEREF          (coh1p)

 21:         120 SETUP_LOOP           (to 184)
         >>  122 LOAD_DEREF           (coh1p)
             124 LOAD_FAST            (tuki6)
             126 LOAD_CONST           (1)
             128 BINARY_SUBTRACT
             130 COMPARE_OP           (<)
             132 POP_JUMP_IF_FALSE    (to 182)

 22:         134 LOAD_FAST            (fau0m)
             136 LOAD_DEREF           (coh1p)
             138 LOAD_CONST           (1)
             140 BINARY_ADD
             142 BINARY_SUBSCR
             144 STORE_DEREF          (aph7a)

 23:         146 LOAD_FAST            (xaeg4)
             148 LOAD_FAST            (fau0m)
             150 LOAD_DEREF           (coh1p)
             152 BINARY_SUBSCR
             154 LOAD_FAST            (fie1z)
             156 CALL_FUNCTION        (0 positional arguments)
             158 BINARY_ADD
             160 CALL_FUNCTION        (1 positional argument)
             162 DUP_TOP
             164 STORE_DEREF          (di5ei)
             166 LOAD_FAST            (fau0m)
             168 LOAD_DEREF           (coh1p)
             170 STORE_SUBSCR

 24:         172 LOAD_DEREF           (coh1p)
             174 LOAD_CONST           (1)
             176 INPLACE_ADD
             178 STORE_DEREF          (coh1p)
             180 JUMP_ABSOLUTE        (to 122)
         >>  182 POP_BLOCK

 25:     >>  184 LOAD_FAST            (fau0m)
             186 LOAD_CONST           (0)
             188 BINARY_SUBSCR
             190 STORE_DEREF          (aph7a)

 26:         192 LOAD_FAST            (xaeg4)
             194 LOAD_FAST            (fau0m)
             196 LOAD_FAST            (tuki6)
             198 LOAD_CONST           (1)
             200 BINARY_SUBTRACT
             202 BINARY_SUBSCR
             204 LOAD_FAST            (fie1z)
             206 CALL_FUNCTION        (0 positional arguments)
             208 BINARY_ADD
             210 CALL_FUNCTION        (1 positional argument)
             212 DUP_TOP
             214 STORE_DEREF          (di5ei)
             216 LOAD_FAST            (fau0m)
             218 LOAD_FAST            (tuki6)
             220 LOAD_CONST           (1)
             222 BINARY_SUBTRACT
             224 STORE_SUBSCR
             226 JUMP_ABSOLUTE        (to 72)
         >>  228 POP_BLOCK

 28:     >>  230 LOAD_CONST           (None)
             232 RETURN_VALUE

# Method Name:       fie1z
# Filename:          code-XXXXX.py
# Argument count:    0
# Keyword-only arguments: 0
# Number of locals:  0
# Stack size:        5
# Flags:             0x00000013 (NESTED | NEWLOCALS | OPTIMIZED)
# First Line:        5
# Constants:
#    0: None
#    1: 5
#    2: 2
#    3: 3
#    4: 4
# Free variables:
#    0: aph7a
#    1: coh1p
#    2: di5ei
#    3: eing8
#    4: ik4so
#    5: ooch0
  6:           0 LOAD_DEREF           (di5ei)
               2 LOAD_CONST           (5)
               4 BINARY_RSHIFT
               6 LOAD_DEREF           (aph7a)
               8 LOAD_CONST           (2)
              10 BINARY_LSHIFT
              12 BINARY_XOR
              14 LOAD_DEREF           (aph7a)
              16 LOAD_CONST           (3)
              18 BINARY_RSHIFT
              20 LOAD_DEREF           (di5ei)
              22 LOAD_CONST           (4)
              24 BINARY_LSHIFT
              26 BINARY_XOR
              28 BINARY_ADD
              30 LOAD_DEREF           (ik4so)
              32 LOAD_DEREF           (aph7a)
              34 BINARY_XOR
              36 LOAD_DEREF           (ooch0)
              38 LOAD_DEREF           (coh1p)
              40 LOAD_CONST           (3)
              42 BINARY_AND
              44 LOAD_DEREF           (eing8)
              46 BINARY_XOR
              48 BINARY_SUBSCR
              50 LOAD_DEREF           (di5ei)
              52 BINARY_XOR
              54 BINARY_ADD
              56 BINARY_XOR
              58 RETURN_VALUE

# Method Name:       xaeg4
# Filename:          code-XXXXX.py
# Argument count:    1
# Keyword-only arguments: 0
# Number of locals:  1
# Stack size:        2
# Flags:             0x00000053 (NOFREE | NESTED | NEWLOCALS | OPTIMIZED)
# First Line:        8
# Constants:
#    0: None
#    1: 4294967295
# Varnames:
# vaeh7
# Positional arguments:
# vaeh7
  9:           0 LOAD_FAST            (vaeh7)
               2 LOAD_CONST           (4294967295)
               4 BINARY_AND
               6 RETURN_VALUE
Что же, и правда похоже на операции виртуальной машины интерпретатора CPython, эталонной реализации языка Python. Также заметим, что имена функций и переменных по-видимому обфусцированы для затруднения анализа.

Дизассемблер — это конечно хорошо, но ведь прогресс на них не остановился, и с декомпилятором наперевес восстановим практически идентичный натуральному исходный код, который читать значительно легче:
Декомпилированный код
# Python bytecode 3.7.0 (3394)
# Embedded file name: code-XXXXX.py
# Size of source mod 2**32: 1357 bytes
 
def wies3(fau0m, tuki6, ooch0):
 
 def fie1z():
        return (di5ei >> 5 ^ aph7a << 2) + (aph7a >> 3 ^ di5ei << 4) ^ (ik4so ^ aph7a) + (ooch0[(coh1p & 3 ^ eing8)] ^ di5ei)
 
 def xaeg4(vaeh7):
        return vaeh7 & 0xFFFFFFFF
 
 aph7a = fau0m[0]
 ik4so = 0
 oon0j = 562984899
 di5ei = fau0m[(tuki6 - 1)]
 zae3u = 6 + 52 // tuki6
 while zae3u > 0:
     zae3u -= 1
     ik4so = xaeg4(ik4so + oon0j)
     eing8 = xaeg4(ik4so >> 2) & 3
     coh1p = 0
     while coh1p < tuki6 - 1:
         aph7a = fau0m[(coh1p + 1)]
         di5ei = fau0m[coh1p] = xaeg4(fau0m[coh1p] + fie1z())
         coh1p += 1
 
     aph7a = fau0m[0]
     di5ei = fau0m[tuki6 - 1] = xaeg4(fau0m[(tuki6 - 1)] + fie1z())
 
 
roh3s = [0, 0, 0, 0]
assert roh3s[3] & 0x0FFFFFFF != 0
 
ieng8 = [0, 0, 0, 0]
assert ieng8[3] & 0x0FFFFFFF != 0
 
kiiv4 = [0, 0, 0, 0, 0]
if not (kiiv4[0] != 0 and kiiv4[1] != 0 and kiiv4[2] != 0 and kiiv4[3] != 0 and kiiv4[4] != 0):
 raise AssertionError
 
op7bi = [2033349222, 859399546]
wies3(op7bi, len(op7bi), roh3s)
wies3(op7bi, len(op7bi), ieng8)
assert op7bi == [1914726229, 545537777]
 
wies3(kiiv4, len(kiiv4), roh3s)
wies3(kiiv4, len(kiiv4), ieng8)
assert kiiv4 == [4259271294, 3762695033, 2530072744, 1774340374, 314947544]
 
print('Ciphertext = ', kiiv4)
print('Plaintext = ?')
Проанализировав код, выясним следующее:

  • Функция wies3 по-видимому реализует некое шифрование, и её аргументы — открытый текст, его длина и ключ. Такой вывод можно сделать, динамически проанализировав её, да и строки «Ciphertext»/«Plaintext» рядом с выводом одного из её аргументов в конце программы намекают на это;
  • Assert-ы намекают на задуманные значения или диапазоны значений, затёртых из кода данных;
  • У нас есть два неизменных ключа, они задаются переменными roh3s и ieng8, и применяются они последовательно, т.е. шифрование выполняется два раза: сначала на ключе roh3s, а затем — на ieng8. Заданные их значения и assert-ы намекают на формат затёртых из кода ключей: 4 32-битных слова, первые 3 из которых нулевые, а в последнем слове ненулевыми являются последние 28 бит;
  • Также у нас есть два шифротекста, заданных assert-ами в переменных op7bi и kiiv4, и для первого из них есть и открытый текст, заданный в изначальном значении op7bi;
  • Требуется найти открытый текст для шифротекста из kiiv4.

Функция шифрования невелика, и можно обратить её вручную, однако если проанализировать её далее или воспользоваться данной в чате подсказкой «если совсем запутались и устали — может заварите чаю?» — то можно выяснить, что в соответствии с заветами Брюса Шнайера в форме «don’t roll your own crypto« функция шифрования реализует вполне существующий алгоритм XXTEA, потомок изначального шифра TEA, применённый когда-то компанией Microsoft в своем Xbox (интересно, что в этом случае он был применён неудачно в качестве хеш-функции, что привело к взлому игровой консоли, но это совсем другая история).

Алгоритм достаточно компактен, не имеет публично известных уязвимостей, каким-либо образом способствующих его взлому (т.е. достаточно надёжен), и сравнительно быстр; однако подбирать 28×2 = 56 бит ключей занятие всё равно мягко говоря небыстрое (на Core i5 это бы заняло около 548 лет). Учитывая метод шифрования, а именно двойное шифрование на различных ключах — можно вспомнить про замечательный метод «встреча посередине», или «meet in the middle», который позволит нам значительно сократить вычислительную сложность перебора с 228×228 до 228+228, но конечно не бесплатно, а за счет памяти. В памяти мы будем сохранять результаты шифрования на первом ключе, и пробуя расшифровать на втором ключе будем искать в памяти совпадающий шифротекст, вот так:


Для ускорения можно взять готовую библиотеку, реализующую шифр XXTEA в качестве модуля Python, который написан на C, не забыв только пересобрать её, заменив стандартную константу delta равную 0x9E3779B9 на заданную в коде программы 0x218E77C3. Примерная реализация будет такой:

import xxtea
 
for firstKeyPart in range(0, 2**28):
  	fullKey = (b'\x00' * 12) + firstKeyPart.to_bytes(4, byteorder='little')
  	cipherText = xxtea.encrypt(givenPlainText, fullKey, padding=False)
  	encryptionTable[cipherText] = firstKeyPart
 
for secondKeyPart in range(0, 2**28):
  	fullKey = (b'\x00' * 12) + secondKeyPart.to_bytes(4, byteorder='little')
  	cipherText = xxtea.decrypt(givenCipherText, fullKey, padding=False)
  	
  	if cipherText in encryptionTable:
        	print("Key 1: ", (b'\x00' * 12) + encryptionTable[cipherText])
        	print("Key 2: ", fullKey)
        	break

Реализация подобной хеш-таблицы в Python потребляет около 28 ГБ ОЗУ, чего не каждый себе может позволить. Поэтому для сокращения её потребления можно пойти на хитрость — разделить первый цикл (где формируется таблица шифротекстов и идёт подбор первого ключа) на несколько частей (2, 4, 8, 16 — сколько потребуется), создавая как бы «окно», содержащее лишь часть таблицы.

Второй же цикл (для второго ключа) в таком случае следует оставить как есть, целиком, поскольку благодаря равновероятному,  криптографически-»хорошему» распределению полученных промежуточных шифротекстов по битовому пространству совершенно необязательно что в ином случае «окна» бы сошлись. Так, например, если разделить на 4 части — потребуется только около 7.5 ГБ ОЗУ, что уже гораздо реалистичнее (в аргументах программы как раз можно увидеть начальное и конечное значение текущего «окна» первого ключа):


Если и это непозволительно большой объём — можно разделить не на 4, а на 8 частей, и так далее уменьшая количество требуемой оперативной памяти, конечно ценой более долгого подбора. Компромисс времени выполнения и памяти в действии! При разделении на 4 части каждая часть на 1 ядре Core i5 выполнилась за 18 минут, и таким образом полный подбор занял бы 72 минуты (все ещё значительно лучше чем 548 лет), а если реализовать и распределение по ядрам — то конечно и ещё быстрее.

В результате для приведённого примера найдутся ключи 0x0000000000000000000000007257BEB и 0x0000000000000000000000002E3E626, а следом за ними и открытый текст 616E7377 65723A6B 39423132 3851746C 46566944 (держим в уме, что XXTEA здесь оперирует 32-битными словами), или «answer:k9B128QtlFViD». Если отправить полученный ответ John Roe в matrix, то можно получить следующее задание.

▍ Задание 3.2


В качестве следующего задания получаем файлы text-XXXXX.txt и соответствующая ему подпись. Внутри файла увидим что-то подобное:
Текст
bD+7BojXe2BbTH8Q0h+Ni3e9JKGXZcHcgDdIydgwfsRW+IiUWMlvubnJKVS/H4wc
1D6fLsYt9dOVyTkwpTVTtj3n9tEj0QaAzQOgqfXReNVWl7w0nt8qitlk68uLY3xC
rySFDhJoPxA0jmKEaPW4G+l5Ri6es4kA23XKtbf8EqOvAX3Tafy0AT3v5MNyBVDJ
cdpl1e+08ETYPLVLymt5pv/UReGSIlEuZ/aiOrikrWjMJCMiXfzjxT6Yi/wW+dOD
9BgJc/OsEAGbrvBZ3LYhJZ+QOpUDPy0IFh4u8A24NEJx+hA0V/PDOxYcNQ3ePvtf
IJOMHdcGLh8c6/tM9u21JBLdOdmwm+ZUFbYTPr9nT0Brwtqj8Jqplkf++i/6gIT6
Z6mcNftenDCuRgTkGJCypmsKlVzH/TESjrZGPQSO1/clsylGRhDogVq3BoddGXsP
+vTzfhm48AdXETY3psD+LB9+QQgo1D5b8pRamKUD8ekid74eitKKt8dHGVNeV1JG
MIIG4wIBAAKCAYEArMqFwTxD2SUd/zMlIp9LDX1/pRFQ/WU9reebFam0jdLwMv7Q
Q5bNIGS1cDbVzUCcpEGp5TlOiTijJTCzcv5AGHq0Fniii/HMSQVTLsPTzYabN2I4
4EXs9dxZHyncnOqwN66JYeOk5IAT8hlcQDydyviov0oBVaB6JvJ9tCGkOKmfohlZ
T4oWULLnBzX+1Q65HBPG5emxIXFLFRInqr3pFmqCni8HTa2v67Hx7r+vtabQZjfl
IMVyCO+pfxL9LukqQHoGSRxYLrhg6h/WYvrf4LGTOxWVIiLUys4+LdVgN+ONDA6a
fzoMuEN04fiF9Pz7wc4PNXMN1DfWbnGxry38DYLeTZWHE/7TpP5YXS7g9I9dchEr
jMBQUo29qYwqu0nsOGLPY3D0gck7HdwgrNPIMTKEISOif5FB16FV9cAKxwgkx3UP
48xd7cpG/L9xbPsx6aGXuEfnn++XzUsw8YOdRDz+cmLgLoH6J56KnGSRIdbeC4b/
kY6LYK/1nGgRWlk/AgMBAAECggGAZfMocBcqwRhRVp3Kr17lXZRKmA5bhucROWaJ
7oIu8e8fojcOkpKLfS1ukEMKawxQX+oOYB0r5XLxb6QIfTTehJMBZrDO11tXeU4X
AmSwt3dQZaEihdE8OuREAUsly7/9MR1eGc/DHr8jBZlJAO3C/Fsy1YrItsj0yb4R
xRKEXppWgcILHA8Rk5O7FFobfYIuXg6dMFJuHJlH/6qylN9wg7VcVBDTclWkkqBv
arrW5YsDMwyPuIJHAlbF3yBJGkCfoEK/QlvmKRvjabGIu43w2g6lp3ZVYZtUZQgy
YG5hsyylONnukEf/FbBi21OgDWfXey5FPQ97KHLZJrxrjkna6rhwzFiCJsQgutbc
XahjElJY/hn/9JP5co9FgGlk+aZ/bofJLQ5urMSFMm7NgCD7aWRQx6Zkcow899Gg
KBFgNZdVWdj3lkAELO6NZSSpRDNxiaFb59aplIOj6bq3QwUuHSWtpO3gCuvn6EZU
ohDyvUDa6fbtX/hHx2MGbofxb
Намётанный глаз безошибочно определит характерные «MIIG» в середине текста, которые декодируются из Base64 как 0x30 0x82 0x06. В нотации ASN.1 0x30 знаменует начало последовательности (sequence) данных, 0x82 (0x82-0x80 = 2) говорит о том, что длина поля длины этих данных – 2 байта, и старший байт длины равен 0x06. Декодируем полученные байты специализированным декодером dumpasn1 (или воспользуемся онлайн-инструментом) и разберём их полностью:


Картинка кликабельна
В копируемом виде
	<30 82 06 E3>
   0 1763: SEQUENCE {
	<02 01>
   4	1:   INTEGER 0 (0x00)
	<02 82 01 81>
   7  385:   INTEGER
    	     00 AC CA 85 C1 3C 43 D9 25 1D FF 33 25 22 9F 4B
    	     0D 7D 7F A5 11 50 FD 65 3D AD E7 9B 15 A9 B4 8D
    	     D2 F0 32 FE D0 43 96 CD 20 64 B5 70 36 D5 CD 40
    	     9C A4 41 A9 E5 39 4E 89 38 A3 25 30 B3 72 FE 40
    	     18 7A B4 16 78 A2 8B F1 CC 49 05 53 2E C3 D3 CD
    	     86 9B 37 62 38 E0 45 EC F5 DC 59 1F 29 DC 9C EA
    	     B0 37 AE 89 61 E3 A4 E4 80 13 F2 19 5C 40 3C 9D
    	     CA F8 A8 BF 4A 01 55 A0 7A 26 F2 7D B4 21 A4 38
    	     A9 9F A2 19 59 4F 8A 16 50 B2 E7 07 35 FE D5 0E
    	     B9 1C 13 C6 E5 E9 B1 21 71 4B 15 12 27 AA BD E9
    	     16 6A 82 9E 2F 07 4D AD AF EB B1 F1 EE BF AF B5
    	     A6 D0 66 37 E5 20 C5 72 08 EF A9 7F 12 FD 2E E9
    	     2A 40 7A 06 49 1C 58 2E B8 60 EA 1F D6 62 FA DF
    	     E0 B1 93 3B 15 95 22 22 D4 CA CE 3E 2D D5 60 37
    	     E3 8D 0C 0E 9A 7F 3A 0C B8 43 74 E1 F8 85 F4 FC
    	     FB C1 CE 0F 35 73 0D D4 37 D6 6E 71 B1 AF 2D FC
    	     0D 82 DE 4D 95 87 13 FE D3 A4 FE 58 5D 2E E0 F4
    	     8F 5D 72 11 2B 8C C0 50 52 8D BD A9 8C 2A BB 49
    	     EC 38 62 CF 63 70 F4 81 C9 3B 1D DC 20 AC D3 C8
    	     31 32 84 21 23 A2 7F 91 41 D7 A1 55 F5 C0 0A C7
    	     08 24 C7 75 0F E3 CC 5D ED CA 46 FC BF 71 6C FB
    	     31 E9 A1 97 B8 47 E7 9F EF 97 CD 4B 30 F1 83 9D
    	     44 3C FE 72 62 E0 2E 81 FA 27 9E 8A 9C 64 91 21
    	     D6 DE 0B 86 FF 91 8E 8B 60 AF F5 9C 68 11 5A 59
    	     3F
	<02 03>
 396	3:   INTEGER 65537 (0x10001)
	<02 82 01 80>
 401  384:   INTEGER
    	     65 F3 28 70 17 2A C1 18 51 56 9D CA AF 5E E5 5D
    	     94 4A 98 0E 5B 86 E7 11 39 66 89 EE 82 2E F1 EF
    	     1F A2 37 0E 92 92 8B 7D 2D 6E 90 43 0A 6B 0C 50
    	     5F EA 0E 60 1D 2B E5 72 F1 6F A4 08 7D 34 DE 84
    	     93 01 66 B0 CE D7 5B 57 79 4E 17 02 64 B0 B7 77
    	     50 65 A1 22 85 D1 3C 3A E4 44 01 4B 25 CB BF FD
    	     31 1D 5E 19 CF C3 1E BF 23 05 99 49 00 ED C2 FC
    	     5B 32 D5 8A C8 B6 C8 F4 C9 BE 11 C5 12 84 5E 9A
    	     56 81 C2 0B 1C 0F 11 93 93 BB 14 5A 1B 7D 82 2E
    	     5E 0E 9D 30 52 6E 1C 99 47 FF AA B2 94 DF 70 83
    	     B5 5C 54 10 D3 72 55 A4 92 A0 6F 6A BA D6 E5 8B
    	     03 33 0C 8F B8 82 47 02 56 C5 DF 20 49 1A 40 9F
    	     A0 42 BF 42 5B E6 29 1B E3 69 B1 88 BB 8D F0 DA
    	     0E A5 A7 76 55 61 9B 54 65 08 32 60 6E 61 B3 2C
    	     A5 38 D9 EE 90 47 FF 15 B0 62 DB 53 A0 0D 67 D7
    	     7B 2E 45 3D 0F 7B 28 72 D9 26 BC 6B 8E 49 DA EA
    	     B8 70 CC 58 82 26 C4 20 BA D6 DC 5D A8 63 12 52
    	     58 FE 19 FF F4 93 F9 72 8F 45 80 69 64 F9 A6 7F
    	     6E 87 C9 2D 0E 6E AC C4 85 32 6E CD 80 20 FB 69
    	     64 50 C7 A6 64 72 8C 3C F7 D1 A0 28 11 60 35 97
    	     55 59 D8 F7 96 40 04 2C EE 8D 65 24 A9 44 33 71
    	     89 A1 5B E7 D6 A9 94 83 A3 E9 BA B7 43 05 2E 1D
    	     25 AD A4 ED E0 0A EB E7 E8 46 54 A2 10 F2 BD 40
    	     DA E9 F6 ED 5F F8 47 C7 63 06 6E 87 F1
    	     Error: Unexpected EOF, 3 bytes missing.
Error: Inconsistent object length, 981 bytes difference.
    	   }
Error: Inconsistent object length, 980 bytes difference.
Помечено:

  • зелёным – тип записи; 30 – последовательность (sequence), 02 – целое число (integer);
  • голубым – длина длины значения записи, указывается как «количество байт + 0x80»;
  • синим – значение длины записи;
  • полужирным чёрным – само значение.

С учётом этого мы видим последовательность из 4х целых чисел: нуля, длинного значения, значения 65535 и частичного длинного значения, которому не хватает 3х байт. Также по заголовку последовательности видим, что вся последовательность должна быть длиной 1763 байта, а у нас только 786 байт. Самые внимательные уже могли заметить, что вообще-то у нас есть ещё 6 бит от символа «b» в исходном base64, и вот они: 0b011011. Они ещё сослужат нам свою службу позже.

Те из нас, кто когда-нибудь пробовал посмотреть, что же скрывается в «белиберде» между «-----BEGIN RSA PRIVATE KEY-----» и соответствующей закрывающей последовательности конечно уже поняли, к чему всё идёт. Да, всё верно, вся структура до боли напоминает приватный ключ алгоритма RSA, а точнее – RSA-3072 (обратим внимание на длину модуля n в 3072 бита).

Если мы сгенерируем ключ такой же длины командой openssl genrsa -out private_key.pem 3072, то получим пример для сравнения. В нём мы увидим и все недостающие нам числа, они записываются в последовательности в таком порядке:

  • версия (обычно задаётся как 0);
  • модуль (n);
  • открытая экспонента (e);
  • секретная экспонента (d);
  • простое число (p);
  • простое число (q);
  • параметр для ускорения вычислений (dp);
  • параметр для ускорения вычислений (dq);
  • параметр для ускорения вычислений (qInv).

В нашем частичном ключе есть версия (задана как 0), модуль (n, полностью), открытая экспонента (e, полностью, равна 65535), и частичная секретная экспонента (d, не хватает 3×8-6 = 18 бит из 3072 бит, указанных в заголовке этого целого числа). Вспомним, как вообще выполняется шифрование и расшифровка в RSA, и какие параметры нам действительно необходимы.

Шифрование:


Расшифровка:


Для шифрования нужны e и n, и они у нас есть в полном виде. А вот для расшифровки кроме n нужна ещё секретная экспонента d, которая у нас есть лишь частично. Благо, что 218 – всего лишь 262144 комбинации, и поэтому мы можем достаточно быстро подобрать нехватающие биты d, в качестве критерия правильности используя процедуру расшифровки, зашифровав любое понравившееся число (например 863) и пытаясь расшифровать его с очередным d, в случае совпадения исходного открытого текста и расшифрованного текста – нехватающие биты d успешно подобраны. Так, например, если мы будем использовать первую же попавшуюся реализацию RSA на Python, то код может выглядеть как-то так:
Код
def rsa_encrypt(pk, plaintext):
  	key, n = pk
  	cipher = pow(plaintext, key, n)
  	return cipher
  	
def rsa_decrypt(pk, ciphertext):
  	key, n = pk
  	plain = pow(ciphertext, key, n)
  	return plain
 
e = 65537
n = 0x00ACCA85C13C43D9251DFF3325229F4B0D7D7FA51150FD653DADE79B15A9B48DD2F032FED04396CD2064B57036D5CD409CA441A9E5394E8938A32530B372FE40187AB41678A28BF1CC4905532EC3D3CD869B376238E045ECF5DC591F29DC9CEAB037AE8961E3A4E48013F2195C403C9DCAF8A8BF4A0155A07A26F27DB421A438A99FA219594F8A1650B2E70735FED50EB91C13C6E5E9B121714B151227AABDE9166A829E2F074DADAFEBB1F1EEBFAFB5A6D06637E520C57208EFA97F12FD2EE92A407A06491C582EB860EA1FD662FADFE0B1933B15952222D4CACE3E2DD56037E38D0C0E9A7F3A0CB84374E1F885F4FCFBC1CE0F35730DD437D66E71B1AF2DFC0D82DE4D958713FED3A4FE585D2EE0F48F5D72112B8CC050528DBDA98C2ABB49EC3862CF6370F481C93B1DDC20ACD3C83132842123A27F9141D7A155F5C00AC70824C7750FE3CC5DEDCA46FCBF716CFB31E9A197B847E79FEF97CD4B30F1839D443CFE7262E02E81FA279E8A9C649121D6DE0B86FF918E8B60AFF59C68115A593F
dPart = (0x65F32870172AC11851569DCAAF5EE55D944A980E5B86E711396689EE822EF1EF1FA2370E92928B7D2D6E90430A6B0C505FEA0E601D2BE572F16FA4087D34DE84930166B0CED75B57794E170264B0B7775065A12285D13C3AE444014B25CBBFFD311D5E19CFC31EBF2305994900EDC2FC5B32D58AC8B6C8F4C9BE11C512845E9A5681C20B1C0F119393BB145A1B7D822E5E0E9D30526E1C9947FFAAB294DF7083B55C5410D37255A492A06F6ABAD6E58B03330C8FB882470256C5DF20491A409FA042BF425BE6291BE369B188BB8DF0DA0EA5A77655619B54650832606E61B32CA538D9EE9047FF15B062DB53A00D67D77B2E453D0F7B2872D926BC6B8E49DAEAB870CC588226C420BAD6DC5DA863125258FE19FFF493F9728F45806964F9A67F6E87C92D0E6EACC485326ECD8020FB696450C7A664728C3CF7D1A028116035975559D8F79640042CEE8D6524A944337189A15BE7D6A99483A3E9BAB743052E1D25ADA4EDE00AEBE7E84654A210F2BD40DAE9F6ED5FF847C763066E87F1 << 6) | 0b011011
 
publicKey = (e, n)
encryptedPlain = rsa_encrypt(publicKey, 863)
 
for keyPart in range(0, 2**18):
  	dFull = (dPart << 18) | keyPart
  	privateKey = (dFull, n)
  	decryptedPlain = rsa_decrypt(privateKey, encryptedPlain)
  	if decryptedPlain == 863:
        	print hex(dFull)
        	break
Для приведённого примера код найдёт нехватающие биты, а именно в виде тех самых трёх нехватающих байтов их можно записать как 0x6D 0xBD 0x61. Далее при желании можно восстановить и p и q, а из них – и полный исходный ключ со всеми параметрами:
Восстановленный ключ
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
А затем и расшифровать первую часть полученного текстового документа (не забыв раскодировать её) командой openssl rsautl -decrypt -in encrypted.txt -inkey priv.pem, и снова получить ответ answer:WBfilGenxrLUdZ67qwYj. Отправляем полученную инфу Johnу и дальше… ждём :)
 


Подсказкой к следующему заданию была онлайн-игра, основанная на известной игре сокобан – чтобы попасть в неё, ожидаемо надо было пройти предыдущие задания и прислать правильные ответы Джону.

Кому пельменей?




Всем настолько запала в душу идея с «пельменями от рувдс», что мы даже задумались – быть может стоит расширить свою линейку мерча :)

В итоговом посте мы обязательно разберём все самые безумные идеи от квестующихся (как они себя окрестили) и сам механизм квеста.

Впереди участников ждет самая активная фаза заданий, приключения и мега движ. Игрокам предстоит объединиться, ведь выигрыш — 142 857 рублей заберёт команда лучших.

Тем, кто всё ещё наблюдает за происходящим со стороны – не стоит затягивать своё вступление в игру – на этот раз времени мало…

Всего несколько дней… или часов?

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

По традиции завершаем материал небольшой подборкой смешных скринов от участников, для которых пельмени и черепок Юрий стали культовыми мемами квеста:


Больше смешных скринов





Tags:
Hubs:
+26
Comments0

Articles

Information

Website
ruvds.com
Registered
Founded
Employees
11–30 employees
Location
Россия
Representative
ruvds