reverse, 17 баллов
Данное задание подготовлено сообществом SPbCTF https://vk.com/spbctf
Программа принимает ключ аргументом командной строки, и проверяет его.
Linux: ./main 1234567890qwerty
*ссылка*
Windows: main.exe 1234567890qwerty
*ссылка*
Найдите верный ключ.
Формат ответа: yaprofi{...}
Находим место с проверками (взял main.exe
):
Строка 50. И тут время остановилось, пение птиц гудение ноутбука
прекратилось. Ничто не беспокоит меня. Я един с природой. Так сделаем то,
что мы должны - поставим точку останова на условный переход с изменением регистра ZF.
Простой таск, который учит ставить conditional breakpoint.
Флаг: yaprofi{SPLuxztrNpxrmipKYOQseRl}
reverse, 24 балла
Данное задание подготовлено сообществом SPbCTF https://vk.com/spbctf
Раньше можно было набрать 1000 очков, и покажут мультик. Здесь можно пройти 1000 уровней, и покажут флаг!
Файл: *ссылка*
Debian/Ubuntu: установите apt-get install python python-tk python-pip
Windows: установите Python 2.7.15 https://www.python.org/ftp/python/2.7.15/python-_2.7.15.msi
Установите модули: pip install pycryptodome uncompyle6
Windows only: если возникли сложности с установкой пакета pycryptodome на предыдущем шаге, воспользуйтесь следующей ссылкой: http://www.voidspace.org.uk/python/modules.shtml#pycrypto
Запуск: python main.pyc
Декомпиляция: uncompyle6 main.pyc
Формат ответа: yaprofi{...}
Запустим файл Декомпилируем с помощью uncompyle6
# uncompyle6 version 3.5.1
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.15+ (default, Oct 7 2019, 17:39:04)
# [GCC 7.4.0]
# Embedded file name: /tmp/py2/container.py
# Compiled at: 2019-11-12 02:48:46
from Crypto.Cipher import AES
import hashlib, marshal
from Tkinter import *
import base64, types
def unpad(data):
return data[:-ord(data[(-1)])]
def round():
pass
def x2(winner_code, win_pic, offset):
key = win_pic.decode('base64')[offset:offset + 16]
aes = AES.new(key, mode=AES.MODE_ECB)
wc = aes.decrypt(winner_code)
return wc
def entry():
key = hashlib.md5(entry.__code__.co_code).digest()
winner_code = round.__code__.co_consts[(-1)]
new_function = round.__code__.co_consts[(-2)]
pics_size = round.__code__.co_consts[(-3)]
aes = AES.new(key, mode=AES.MODE_ECB)
new_code = marshal.loads(unpad(aes.decrypt(new_function)))
pics_sel = list(round.__code__.co_consts[-3 - pics_size:-3])
pics = list(round.__code__.co_consts[-3 - pics_size - 200:-3 - pics_size])
round.__code__ = new_code
round(pics, pics_sel, winner_code)
if __name__ == '__main__':
entry()
# okay decompiling main_3.pyc
Мы видим грустную ситуацию. Функция entry
берет свой байт-код
и использует его для расшифровки new_function
- переменной типа
code
, которая будет присвоена функции round
. Также есть winner_code
и лист pics_sel
. В качестве контейнера используются константы
функции round
, которые uncompyle6
не отобразил просто потому, что
они не используются в round.__code__.co_code
.
Начнем диссекцию:
Python 2.7.15+ (default, Oct 7 2019, 17:39:04)
[GCC 7.4.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> globals()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': '__main__', '__doc__': None, '__package__': None}
>>> from main_3 import *
>>> globals()
{'getdouble': <type 'float'>, 'MULTIPLE': 'multiple', 'TypeType': <type 'type'>, 'mainloop':
<function mainloop at 0x7f98f26dd848>, 'Canvas': <class Tkinter.Canvas at 0x7f98efaf13f8>,
'AtSelLast': <function AtSelLast at 0x7f98efaf4b18>, 'UNITS': 'units', 'CodeType': <type 'code'>,
'TRUE': 1, 'getboolean': <function getboolean at 0x7f98f2705230>, 'LAST': 'last', 'BASELINE': 'baseline',
'BOTTOM': 'bottom', 'Wm': <class Tkinter.Wm at 0x7f98efaf1050>,
'base64': <module 'base64' from '/usr/lib/python2.7/base64.pyc'>, 'NUMERIC': 'numeric',
'Toplevel': <class Tkinter.Toplevel at 0x7f98efaf1328>, 'DictProxyType': <type 'dictproxy'>,
'Pack': <class Tkinter.Pack at 0x7f98efaf1120>, 'round': <function round at 0x7f98efb15aa0>,
'ObjectType': <type 'object'>, 'DictType': <type 'dict'>, 'EXTENDED': 'extended', 'OFF': 0, 'ALL': 'all',
'CURRENT': 'current', 'CallWrapper': <class Tkinter.CallWrapper at 0x7f98f26d0e88>,
'Scrollbar': <class Tkinter.Scrollbar at 0x7f98efaf1870>, 'ListType': <type 'list'>, 'X': 'x',
'ModuleType': <type 'module'>, 'FIRST': 'first', 'ON': 1, 'TracebackType': <type 'traceback'>,
'image_names': <function image_names at 0x7f98efaf4c08>, 'Text': <class Tkinter.Text at 0x7f98efaf18d8>,
'YES': 1, 'LambdaType': <type 'function'>, 'GROOVE': 'groove', 'XRangeType': <type 'xrange'>,
'AES': <module 'Crypto.Cipher.AES' from '/usr/lib/python2.7/dist-packages/Crypto/Cipher/AES.pyc'>,
'Scale': <class Tkinter.Scale at 0x7f98efaf1808>, 'NORMAL': 'normal',
'Misc': <class Tkinter.Misc at 0x7f98f26d0e20>, 'BooleanType': <type 'bool'>,
'Label': <class Tkinter.Label at 0x7f98efaf1598>, 'PAGES': 'pages',
'LabelFrame': <class Tkinter.LabelFrame at 0x7f98efaf1bb0>, 'ROUND': 'round',
'image_types': <function image_types at 0x7f98efb00b90>, 'AtInsert': <function AtInsert at 0x7f98efaf4a28>,
'StringType': <type 'str'>, 'NONE': 'none', 'CENTER': 'center', 'FloatType': <type 'float'>,
'Spinbox': <class Tkinter.Spinbox at 0x7f98efaf1b48>, 'Radiobutton': <class Tkinter.Radiobutton at 0x7f98efaf17a0>,
'Checkbutton': <class Tkinter.Checkbutton at 0x7f98efaf1460>, 're': <module 're' from '/usr/lib/python2.7/re.pyc'>,
'Grid': <class Tkinter.Grid at 0x7f98efaf11f0>, 'StringTypes': (<type 'str'>, <type 'unicode'>),
'GeneratorType': <type 'generator'>, 'Button': <class Tkinter.Button at 0x7f98efaf1390>, 'FLAT': 'flat',
'LongType': <type 'long'>, 'END': 'end', 'VERTICAL': 'vertical', '__builtins__': <module '__builtin__' (built-in)>,
'MITER': 'miter', 'Widget': <class Tkinter.Widget at 0x7f98efaf12c0>, 'DISABLED': 'disabled', 'S': 's',
'COMMAND': 'command', 'EllipsisType': <type 'ellipsis'>, 'W': 'w', 'ACTIVE': 'active', '__name__': '__main__',
'EW': 'ew', 'FrameType': <type 'frame'>, 'TclVersion': 8.6, 'CHORD': 'chord',
'tkinter': <module '_tkinter' from '/usr/lib/python2.7/lib-dynload/_tkinter.so'>,
'Listbox': <class Tkinter.Listbox at 0x7f98efaf1600>, 'Image': <class Tkinter.Image at 0x7f98efaf1a10>,
'BitmapImage': <class Tkinter.BitmapImage at 0x7f98efaf1ae0>, 'AtSelFirst': <function AtSelFirst at 0x7f98efaf4aa0>,
'RADIOBUTTON': 'radiobutton', 'Place': <class Tkinter.Place at 0x7f98efaf1188>, 'HIDDEN': 'hidden',
'marshal': <module 'marshal' (built-in)>, 'Event': <class Tkinter.Event at 0x7f98f26d0bb0>,
'NoDefaultRoot': <function NoDefaultRoot at 0x7f98f26dd6e0>, 'CHAR': 'char', 'SEPARATOR': 'separator',
'BUTT': 'butt', 'HORIZONTAL': 'horizontal', 'TclError': <class '_tkinter.TclError'>, 'MOVETO': 'moveto',
'WORD': 'word', 'SUNKEN': 'sunken', 'NO': 0, 'DictionaryType': <type 'dict'>,
'NotImplementedType': <type 'NotImplementedType'>, 'READABLE': 2, 'NE': 'ne', 'CHECKBUTTON': 'checkbutton',
'Variable': <class Tkinter.Variable at 0x7f98f26d0c18>, 'SEL': 'sel', '__doc__': None, 'NW': 'nw', 'RAISED': 'raised',
'DoubleVar': <class Tkinter.DoubleVar at 0x7f98f26d0d50>, 'RIDGE': 'ridge',
'BooleanVar': <class Tkinter.BooleanVar at 0x7f98f26d0db8>, 'Tributton': <class Tkinter.Tributton at 0x7f98efaf1ce8>,
'SOLID': 'solid', 'N': 'n', 'CASCADE': 'cascade', 'SEL_FIRST': 'sel.first', 'TkVersion': 8.6, 'UNDERLINE': 'underline',
'x2': <function x2 at 0x7f98efb15b18>, 'TupleType': <type 'tuple'>,
'OptionMenu': <class Tkinter.OptionMenu at 0x7f98efaf19a8>,
'hashlib': <module 'hashlib' from '/usr/lib/python2.7/hashlib.pyc'>, 'NS': 'ns', 'FALSE': 0,
'ClassType': <type 'classobj'>, 'Frame': <class Tkinter.Frame at 0x7f98efaf1530>, 'SEL_LAST': 'sel.last', 'SW': 'sw',
'SINGLE': 'single', 'ANCHOR': 'anchor', 'MemberDescriptorType': <type 'member_descriptor'>, 'UnicodeType': <type 'unicode'>,
'unpad': <function unpad at 0x7f98efb15a28>, 'LEFT': 'left', 'FunctionType': <type 'function'>, 'wantobjects': 1, 'SE': 'se',
'EXCEPTION': 8, 'IntType': <type 'int'>, 'XView': <class Tkinter.XView at 0x7f98f26d0ef0>,
'Menu': <class Tkinter.Menu at 0x7f98efaf1668>, 'TOP': 'top', 'GetSetDescriptorType': <type 'getset_descriptor'>,
'DOTBOX': 'dotbox', 'BaseWidget': <class Tkinter.BaseWidget at 0x7f98efaf1258>, 'OUTSIDE': 'outside',
'Tk': <class Tkinter.Tk at 0x7f98efaf10b8>, 'IntVar': <class Tkinter.IntVar at 0x7f98f26d0ce8>,
'YView': <class Tkinter.YView at 0x7f98f26d0f58>, 'UnboundMethodType': <type 'instancemethod'>,
'PanedWindow': <class Tkinter.PanedWindow at 0x7f98efaf1c18>, 'BOTH': 'both',
'BuiltinMethodType': <type 'builtin_function_or_method'>, 'AtEnd': <function AtEnd at 0x7f98efaf0e60>, 'BROWSE': 'browse',
'Tcl': <function Tcl at 0x7f98f27052a8>, 'BuiltinFunctionType': <type 'builtin_function_or_method'>, '__package__': None,
'ARC': 'arc', 'MethodType': <type 'instancemethod'>, 'SliceType': <type 'slice'>, 'BEVEL': 'bevel', 'E': 'e',
'InstanceType': <type 'instance'>, 'INSERT': 'insert', 'PIESLICE': 'pieslice', 'FileType': <type 'file'>,
'sys': <module 'sys' (built-in)>, 'Y': 'y', 'Entry': <class Tkinter.Entry at 0x7f98efaf14c8>,
'Message': <class Tkinter.Message at 0x7f98efaf1738>, 'types': <module 'types' from '/usr/lib/python2.7/types.pyc'>,
'PhotoImage': <class Tkinter.PhotoImage at 0x7f98efaf1a78>, 'RIGHT': 'right', 'BufferType': <type 'buffer'>,
'Studbutton': <class Tkinter.Studbutton at 0x7f98efaf1c80>, 'INSIDE': 'inside',
'Menubutton': <class Tkinter.Menubutton at 0x7f98efaf16d0>, 'WRITABLE': 4,
'StringVar': <class Tkinter.StringVar at 0x7f98f26d0c80>, 'PROJECTING': 'projecting', 'At': <function At at 0x7f98efaf4b90>,
'ComplexType': <type 'complex'>, 'entry': <function entry at 0x7f98efb15b90>, 'NSEW': 'nsew', 'SCROLL': 'scroll',
'NoneType': <type 'NoneType'>, 'getint': <type 'int'>}
За мусор благодарим from Tkinter import *
Нам интересно, как много всего находится в константах неперезаписанного round
:
>>> for i in range(len(consts)):
... if "__len__" in dir(consts[i]):
... print i, len(consts[i]), type(consts[i])
... else:
... print i, type(consts[i])
...
0 <type 'NoneType'>
1 56903 <type 'str'>
2 40628 <type 'str'>
3 74083 <type 'str'>
...
198 80166 <type 'str'>
199 73835 <type 'str'>
200 76295 <type 'str'>
201 4 <type 'tuple'>
202 4 <type 'tuple'>
203 4 <type 'tuple'>
...
1198 4 <type 'tuple'>
1199 4 <type 'tuple'>
1200 4 <type 'tuple'>
1201 <type 'int'>
1202 2224 <type 'str'>
1203 208 <type 'str'>
Получается, winner_code - последний (1203-й) элемент, зашифрованный экземпляр code
- 1202-й элемент, размер
картинок - 1201-й элемент, далее идут кортеджи pics_sel
и строки pics
. Так как длина последних составляет десятки
тысяч строк, для исследования просто сохраним любую из них.
…
Это b64 гифка альпаки (ламы?). Мило)
Создадим файл loader.py
, чтобы руками каждый раз не вводить команды из функции entry
:
from main_3 import *
import dis
if 1==1:
key = hashlib.md5(entry.__code__.co_code).digest()
winner_code = round.__code__.co_consts[(-1)]
new_function = round.__code__.co_consts[(-2)]
pics_size = round.__code__.co_consts[(-3)]
aes = AES.new(key, mode=AES.MODE_ECB)
new_code = marshal.loads(unpad(aes.decrypt(new_function)))
pics_sel = list(round.__code__.co_consts[-3 - pics_size:-3])
pics = list(round.__code__.co_consts[-3 - pics_size - 200:-3 - pics_size])
#round.__code__ = new_code
#round(pics, pics_sel, winner_code)
Дизассемблируем new_code
:
>>> len(new_code.co_code)
573
>>> dis.dis(new_code)
9 0 LOAD_CONST 1 ('xx')
3 LOAD_CONST 26 (())
6 LOAD_CONST 2 (<code object xx at 0x7ffafe3a9830, file "/tmp/py2/tkinter_t.py", line 9>)
9 MAKE_FUNCTION 0
12 CALL_FUNCTION 0
15 BUILD_CLASS
16 STORE_FAST 3 (xx)
14 19 LOAD_CONST 3 (1)
22 STORE_FAST 4 (level)
15 25 SETUP_LOOP 507 (to 535)
28 LOAD_FAST 1 (pics_sel)
31 GET_ITER
>> 32 FOR_ITER 499 (to 534)
35 STORE_FAST 5 (i)
16 38 LOAD_FAST 5 (i)
41 LOAD_CONST 4 (0)
44 BINARY_SUBSCR
45 LOAD_FAST 5 (i)
48 LOAD_CONST 3 (1)
51 BINARY_SUBSCR
52 LOAD_FAST 5 (i)
55 LOAD_CONST 5 (2)
58 BINARY_SUBSCR
59 ROT_THREE
60 ROT_TWO
61 STORE_FAST 6 (photo1)
64 STORE_FAST 7 (photo2)
67 STORE_FAST 8 (res)
17 70 LOAD_FAST 0 (pics)
73 LOAD_FAST 6 (photo1)
76 BINARY_SUBSCR
77 STORE_FAST 6 (photo1)
18 80 LOAD_FAST 0 (pics)
83 LOAD_FAST 7 (photo2)
86 BINARY_SUBSCR
87 STORE_FAST 7 (photo2)
19 90 LOAD_FAST 3 (xx)
93 LOAD_FAST 8 (res)
96 CALL_FUNCTION 1
99 STORE_DEREF 1 (v)
21 102 LOAD_CONST 6 (<code object disable_event at 0x7ffafe3b24b0, file "/tmp/py2/tkinter_t.py", line 21>)
105 MAKE_FUNCTION 0
108 STORE_FAST 9 (disable_event)
23 111 LOAD_CLOSURE 0 (root)
114 BUILD_TUPLE 1
117 LOAD_CONST 7 (<code object close_window at 0x7ffafe3b8030, file "/tmp/py2/tkinter_t.py", line 23>)
120 MAKE_CLOSURE 0
123 STORE_FAST 10 (close_window)
25 126 LOAD_CLOSURE 0 (root)
129 LOAD_CLOSURE 1 (v)
132 BUILD_TUPLE 2
135 LOAD_CONST 8 (<code object win at 0x7ffafe3c3f30, file "/tmp/py2/tkinter_t.py", line 25>)
138 MAKE_CLOSURE 0
141 STORE_FAST 11 (win)
29 144 LOAD_GLOBAL 0 (Tk)
147 CALL_FUNCTION 0
150 STORE_DEREF 0 (root)
30 153 LOAD_DEREF 0 (root)
156 LOAD_ATTR 1 (title)
159 LOAD_CONST 9 ('Alpacker: Llama or Alpaca?')
162 CALL_FUNCTION 1
165 POP_TOP
31 166 LOAD_DEREF 0 (root)
169 LOAD_ATTR 2 (resizable)
172 LOAD_GLOBAL 3 (False)
175 LOAD_GLOBAL 3 (False)
178 CALL_FUNCTION 2
181 POP_TOP
32 182 LOAD_GLOBAL 4 (Button)
185 LOAD_DEREF 0 (root)
188 LOAD_CONST 10 ('justify')
191 LOAD_GLOBAL 5 (LEFT)
194 LOAD_CONST 11 ('command')
197 LOAD_FAST 10 (close_window)
200 CALL_FUNCTION 513
203 STORE_FAST 12 (b)
33 206 LOAD_GLOBAL 4 (Button)
209 LOAD_DEREF 0 (root)
212 LOAD_CONST 10 ('justify')
215 LOAD_GLOBAL 6 (RIGHT)
218 LOAD_CONST 11 ('command')
221 LOAD_FAST 11 (win)
224 CALL_FUNCTION 513
227 STORE_FAST 13 (b2)
34 230 LOAD_GLOBAL 7 (PhotoImage)
233 LOAD_CONST 12 ('data')
236 LOAD_FAST 6 (photo1)
239 CALL_FUNCTION 256
242 STORE_FAST 14 (photo)
35 245 LOAD_GLOBAL 7 (PhotoImage)
248 LOAD_CONST 12 ('data')
251 LOAD_FAST 7 (photo2)
254 CALL_FUNCTION 256
257 STORE_FAST 15 (photo3)
36 260 LOAD_FAST 12 (b)
263 LOAD_ATTR 8 (config)
266 LOAD_CONST 13 ('image')
269 LOAD_FAST 14 (photo)
272 CALL_FUNCTION 256
275 POP_TOP
37 276 LOAD_FAST 12 (b)
279 LOAD_ATTR 9 (pack)
282 LOAD_CONST 14 ('side')
285 LOAD_GLOBAL 5 (LEFT)
288 CALL_FUNCTION 256
291 POP_TOP
38 292 LOAD_FAST 13 (b2)
295 LOAD_ATTR 8 (config)
298 LOAD_CONST 13 ('image')
301 LOAD_FAST 15 (photo3)
304 CALL_FUNCTION 256
307 POP_TOP
39 308 LOAD_FAST 13 (b2)
311 LOAD_ATTR 9 (pack)
314 LOAD_CONST 14 ('side')
317 LOAD_GLOBAL 6 (RIGHT)
320 CALL_FUNCTION 256
323 POP_TOP
40 324 LOAD_GLOBAL 10 (Label)
327 LOAD_DEREF 0 (root)
330 LOAD_CONST 15 ('text')
333 LOAD_CONST 16 ('{} of {}')
336 LOAD_ATTR 11 (format)
339 LOAD_FAST 4 (level)
342 LOAD_CONST 17 (1000)
345 CALL_FUNCTION 2
348 CALL_FUNCTION 257
351 STORE_FAST 16 (w)
41 354 LOAD_FAST 16 (w)
357 LOAD_ATTR 9 (pack)
360 LOAD_CONST 18 ('padx')
363 LOAD_CONST 19 (5)
366 LOAD_CONST 20 ('pady')
369 LOAD_CONST 19 (5)
372 CALL_FUNCTION 512
375 POP_TOP
42 376 LOAD_GLOBAL 10 (Label)
379 LOAD_DEREF 0 (root)
382 LOAD_CONST 15 ('text')
385 LOAD_CONST 21 ('Choose Alpaca!')
388 LOAD_ATTR 11 (format)
391 LOAD_CONST 3 (1)
394 LOAD_CONST 17 (1000)
397 CALL_FUNCTION 2
400 CALL_FUNCTION 257
403 STORE_FAST 17 (w1)
43 406 LOAD_FAST 17 (w1)
409 LOAD_ATTR 9 (pack)
412 LOAD_CONST 18 ('padx')
415 LOAD_CONST 19 (5)
418 LOAD_CONST 20 ('pady')
421 LOAD_CONST 19 (5)
424 CALL_FUNCTION 512
427 POP_TOP
44 428 LOAD_DEREF 0 (root)
431 LOAD_ATTR 12 (protocol)
434 LOAD_CONST 22 ('WM_DELETE_WINDOW')
437 LOAD_FAST 9 (disable_event)
440 CALL_FUNCTION 2
443 POP_TOP
45 444 LOAD_DEREF 0 (root)
447 LOAD_ATTR 13 (mainloop)
450 CALL_FUNCTION 0
453 POP_TOP
46 454 LOAD_DEREF 1 (v)
457 LOAD_ATTR 14 (x)
460 LOAD_CONST 3 (1)
463 COMPARE_OP 2 (==)
466 POP_JUMP_IF_FALSE 487
47 469 LOAD_CONST 23 ('Nope!')
472 PRINT_ITEM
473 PRINT_NEWLINE
48 474 LOAD_GLOBAL 15 (exit)
477 LOAD_CONST 4 (0)
480 CALL_FUNCTION 1
483 POP_TOP
484 JUMP_FORWARD 0 (to 487)
49 >> 487 LOAD_GLOBAL 16 (x2)
490 LOAD_FAST 2 (wc)
493 LOAD_FAST 0 (pics)
496 LOAD_FAST 5 (i)
499 LOAD_FAST 5 (i)
502 LOAD_CONST 5 (2)
505 BINARY_SUBSCR
506 BINARY_SUBSCR
507 BINARY_SUBSCR
508 LOAD_FAST 5 (i)
511 LOAD_CONST 24 (3)
514 BINARY_SUBSCR
515 CALL_FUNCTION 3
518 STORE_FAST 2 (wc)
50 521 LOAD_FAST 4 (level)
524 LOAD_CONST 3 (1)
527 INPLACE_ADD
528 STORE_FAST 4 (level)
531 JUMP_ABSOLUTE 32
>> 534 POP_BLOCK
52 >> 535 LOAD_CONST 25 (<code object y at 0x7ffafb7e3730, file "/tmp/py2/tkinter_t.py", line 52>)
538 MAKE_FUNCTION 0
541 STORE_FAST 18 (y)
54 544 LOAD_GLOBAL 17 (marshal)
547 LOAD_ATTR 18 (loads)
550 LOAD_FAST 2 (wc)
553 CALL_FUNCTION 1
556 LOAD_FAST 18 (y)
559 STORE_ATTR 19 (__code__)
55 562 LOAD_FAST 18 (y)
565 CALL_FUNCTION 0
568 POP_TOP
569 LOAD_CONST 0 (None)
572 RETURN_VALUE
Окей, 55 строк, 573 байта.
Задача - выбрать 1000 альпак, остерегаясь лам.
За полчаса можно пройти игру эмпирическим путем на виртуалке, делая снапшот каждые 10-50 решений. Но это разве наш вариант?)
Так что будем патчить код функции. Я решил таск, просто за-nop-ив проверку правильности выбора (байты 454-486) и прокликав 1000 раз, т.к. не увидел использование своего байт-кода как части ключа, IV либо других параметров для расшифровки. Данная статья приводит функцию по замене байт-кода. Давайте модифицируем наш загрузчик, чтобы он запатчил функцию:
from main_3 import *
import dis
from types import CodeType
def fix_function(fn_code, payload):
return CodeType(fn_code.co_argcount,
#fn_code.co_kwonlyargcount,
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
payload,
fn_code.co_consts,
fn_code.co_names,
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab,
fn_code.co_freevars,
fn_code.co_cellvars,
)
if 1==1:
key = hashlib.md5(entry.__code__.co_code).digest()
winner_code = round.__code__.co_consts[(-1)]
new_function = round.__code__.co_consts[(-2)]
pics_size = round.__code__.co_consts[(-3)]
aes = AES.new(key, mode=AES.MODE_ECB)
new_code = marshal.loads(unpad(aes.decrypt(new_function)))
pics_sel = list(round.__code__.co_consts[-3 - pics_size:-3])
pics = list(round.__code__.co_consts[-3 - pics_size - 200:-3 - pics_size])
# nopping...
new_co_code = list(new_code.co_code)
for i in range(454, 486+1):
new_co_code[i] = chr(0x9) # NOP opcode
new_co_code = "".join(new_co_code)
new_code = fix_function(new_code, new_co_code)
round.__code__ = new_code
round(pics, pics_sel, winner_code)
Этот способ действительно даст ключ:
You win! here is your flag: yaprofi{MgrfVhFUqApexYrdfvuijjP}
Но ведь хочется облегчить анализ кода, а не смотреть на эти длинные строки dis.dis в необходимости разобраться, где именно патчить)
Уровень 1: uncompyle6.
loader.py:
from main_3 import *
from uncompyle6.main import decompile
import sys
if 1==1:
key = hashlib.md5(entry.__code__.co_code).digest()
winner_code = round.__code__.co_consts[(-1)]
new_function = round.__code__.co_consts[(-2)]
pics_size = round.__code__.co_consts[(-3)]
aes = AES.new(key, mode=AES.MODE_ECB)
new_code = marshal.loads(unpad(aes.decrypt(new_function)))
pics_sel = list(round.__code__.co_consts[-3 - pics_size:-3])
pics = list(round.__code__.co_consts[-3 - pics_size - 200:-3 - pics_size])
round.__code__ = new_code
decompile(2.7, round.__code__, sys.stdout)
Вывод:
>>> from loader import *
# uncompyle6 version 3.5.1
# Python bytecode 2.7
# Decompiled from: Python 2.7.15+ (default, Oct 7 2019, 17:39:04)
# [GCC 7.4.0]
# Embedded file name: /tmp/py2/tkinter_t.py
class xx:
def __init__(self, v):
self.x = v
def toggle(self):
self.x = self.x ^ 1
level = 1
for i in pics_sel:
photo1, photo2, res = i[0], i[1], i[2]
photo1 = pics[photo1]
photo2 = pics[photo2]
v = xx(res)
def disable_event():
pass
def close_window():
root.destroy()
def win():
v.toggle()
root.destroy()
root = Tk()
root.title('Alpacker: Llama or Alpaca?')
root.resizable(False, False)
b = Button(root, justify=LEFT, command=close_window)
b2 = Button(root, justify=RIGHT, command=win)
photo = PhotoImage(data=photo1)
photo3 = PhotoImage(data=photo2)
b.config(image=photo)
b.pack(side=LEFT)
b2.config(image=photo3)
b2.pack(side=RIGHT)
w = Label(root, text=('{} of {}').format(level, 1000))
w.pack(padx=5, pady=5)
w1 = Label(root, text=('Choose Alpaca!').format(1, 1000))
w1.pack(padx=5, pady=5)
root.protocol('WM_DELETE_WINDOW', disable_event)
root.mainloop()
if v.x == 1:
print 'Nope!'
exit(0)
wc = x2(wc, pics[i[i[2]]], i[3])
level += 1
def y():
pass
y.__code__ = marshal.loads(wc)
y()
Оформим решение в виде файла writeup.py:
from main_3 import *
def x2(winner_code, win_pic, offset):
key = win_pic.decode('base64')[offset:offset + 16]
aes = AES.new(key, mode=AES.MODE_ECB)
wc = aes.decrypt(winner_code)
return wc
def rround(pics, pics_sel, wc):
class xx:
def __init__(self, v):
self.x = v
def toggle(self):
self.x = self.x ^ 1
for i in pics_sel:
wc = x2(wc, pics[i[i[2]]], i[3])
def y():
pass
y.__code__ = marshal.loads(wc)
y()
if 1==1:
winner_code = round.__code__.co_consts[(-1)]
pics_size = round.__code__.co_consts[(-3)]
pics_sel = list(round.__code__.co_consts[-3 - pics_size:-3])
pics = list(round.__code__.co_consts[-3 - pics_size - 200:-3 - pics_size])
rround(pics, pics_sel, winner_code)
python2 ./writeup.py
You win! here is your flag: yaprofi{MgrfVhFUqApexYrdfvuijjP}
Давайте заглянем внутрь финальной версии функции y
с помощью uncompyle6:
print 'You win! here is your flag: yaprofi{MgrfVhFUqApexYrdfvuijjP}'
Т.е. никакой криптографии, которая бы зависела от внешних факторов, нет.
Уровень 2: control flow graph
Представим, что организаторы пошли дальше и испортили часть инструкций так, чтобы uncompyle6 не мог справиться,
и нам следовало бы ковырять бинарщину) У fireeye есть замечательная тулза, которая позволяет строить
cfg для питоновского байт-кода. Вот так выглядит функция
round
в ее представлении:
С CFG жизнь становится проще. Используем нашу патч-машину, чтобы за-nop-ить 102-486 байты
from main_3 import *
import dis
from types import CodeType
def fix_function(fn_code, payload):
return CodeType(fn_code.co_argcount,
#fn_code.co_kwonlyargcount,
fn_code.co_nlocals,
fn_code.co_stacksize,
fn_code.co_flags,
payload,
fn_code.co_consts,
fn_code.co_names,
fn_code.co_varnames,
fn_code.co_filename,
fn_code.co_name,
fn_code.co_firstlineno,
fn_code.co_lnotab,
fn_code.co_freevars,
fn_code.co_cellvars,
)
if 1==1:
key = hashlib.md5(entry.__code__.co_code).digest()
winner_code = round.__code__.co_consts[(-1)]
new_function = round.__code__.co_consts[(-2)]
pics_size = round.__code__.co_consts[(-3)]
aes = AES.new(key, mode=AES.MODE_ECB)
new_code = marshal.loads(unpad(aes.decrypt(new_function)))
pics_sel = list(round.__code__.co_consts[-3 - pics_size:-3])
pics = list(round.__code__.co_consts[-3 - pics_size - 200:-3 - pics_size])
# nopping...
new_co_code = list(new_code.co_code)
for i in range(102, 486+1):
new_co_code[i] = chr(0x9) # NOP opcode
new_co_code = "".join(new_co_code)
new_code = fix_function(new_code, new_co_code)
round.__code__ = new_code
round(pics, pics_sel, winner_code)
python2 loader.py
You win! here is your flag: yaprofi{MgrfVhFUqApexYrdfvuijjP}
author, editor: Rakovsky Stanislav, Unicorn CTF