Изучение разделителей, внедрение кода и многое другое

Язык программирования Python чрезвычайно удобен для начинающих благодаря простому синтаксису, похожему на английский, и мощным, но интуитивно понятным встроенным функциям. Скорее всего, ваша первая строка кода Python выглядела примерно так:

print("Hello World")

Когда вы освоитесь с языком, вы начнете узнавать о различных возможностях встроенной функции print(). Чем больше опыта вы получите в качестве разработчика Python, тем больше пограничных случаев, хаков, экзотического синтаксиса и расширенных функций вы обнаружите.

В этой статье я покажу вам, на что способна функция print(), начиная с ее основных функций и заканчивая более сложными вариантами использования и интересными хаками.

Базовое использование print()

Самый простой способ использования оператора print(), а также наиболее распространенный — это запись сообщения (строки или другого объекта) в консоль.

И неудивительно, что вывод консоли будет следующим:

Hello World

Теперь давайте перейдем к менее известным функциям.

Конечный символ и разделитель

Функция print() также позволяет указать символ, который будет добавлен в конце строки с помощью параметра end. Например, если вы хотите напечатать последовательность "***" после основного аргумента, вы можете сделать это:

И это выведет следующий текст (без новой строки в конце):

Hello World***

Если вы хотите исключить новую строку из вывода, чтобы печатать все в одной строке, вы можете установить end='' следующим образом:

Разделители — еще одна ключевая функция оператора Python print(). Для начала вы можете вывести несколько значений с помощью одного и того же вызова функции, разделив их запятыми:

Hello World

Функция print() неявно устанавливает свой параметр sep=' ', чтобы аргументы, передаваемые таким образом, при печати разделялись пробелом. Однако вы можете указать свою собственную строку для разделения каждого аргумента:

Hello***World
HelloWorld

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

При печати сложных объектов в Python вы обычно указываете, как они должны выглядеть в виде строки, вместо того, чтобы полагаться на недескриптивное поведение по умолчанию:

<__main__.Entry object at 0x7fc3ddd5ffd0>

Итак, когда вы пишете класс, вы можете перезаписать __str__() и __repr__() методы dunder, чтобы Python знал, как он должен обрабатывать напечатанный объект.

Entry: a=1

Я всегда предпочитаю явно устанавливать и __str__(), и __repr__(), но вам это не нужно.

Запись в файлы с помощью print()

Вы когда-нибудь задумывались о том, как print() знает, куда вывести данное сообщение? По умолчанию строка записывается в поток sys.stdout, но вы можете указать выходной файл с параметром file.

Этот фрагмент кода создает файл с именем «output.txt» и записывает в него «Hello World». Это возможно, потому что print() неявно вызывает метод .write() потока, который вы передаете, так что это будет эквивалентно

f.write("Hello World\n")

Имея это в виду, вы можете создать элементарный регистратор, используя функцию print().

Или, что гораздо лучше, вы можете переопределить функцию print(), чтобы расширить ее функциональные возможности, записывая каждый оператор печати в файл.

При таком подходе вы можете легко выполнять каждый вызов print() в вашем файле и автоматически записывать сообщения в файл. Однако, если вам нравятся подобные вещи, вам лучше использовать модуль logging.

Выполнение кода путем печати в файлы

Что, если бы вы могли взломать функцию print(), чтобы заставить ее выполнять произвольный код? Если вы помните, я упоминал, что print() вызывает метод .write() файла, который вы передаете. Имея это в виду, вы можете перезаписать или переопределить этот метод своим собственным кодом.

This is the payload.
Payload function called with arguments: Hello World
This is the payload.
Payload function called with arguments

Кроме того, вы можете переопределить sys.stdout.write(), чтобы активировать функцию полезной нагрузки для любого вызова print() по умолчанию.

Executing payload
Hello WorldExecuting payload

Как вы, возможно, заметили, Python дважды вызывает функцию payload() (и, следовательно, метод .write()), поэтому вам, возможно, придется с этим справиться.

У этой техники внедрения кода может быть не так много реальных вариантов использования, но кто знает, что люди могут придумать?

Внедрение кода сторонних библиотек

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

В этом примере я покажу вам, как поставщик может включить вредоносный код, который запускается функцией print().

И затем вы будете использовать этот класс в своих программах:

В качестве альтернативы вы можете переопределить глобальный sys.stdout.write() в своем вредоносном модуле, чтобы каждый вызов print() выполнял вашу полезную нагрузку.

Теперь вы можете возразить, что любой, кто просматривает исходный код, сразу же заметит эти вредоносные строки кода. Я иногда проверяю библиотеки, которыми пользуюсь сам. Как насчет того, чтобы вместо этого спрятать вредоносный код внутри расширения C?

Разработчики Python гораздо реже проверяют исходный файл C из пакета Python, чем файл Python. Кроме того, если вы скомпилируете расширение локально при установке, антивирусы (если вы их используете) должны доверять полученному бинарному файлу как полученному от пользователя. Или как насчет обфускации кода?

Забавно осознавать, насколько уязвимыми могут быть разработчики программного обеспечения. А затем вы получаете обратную оболочку, запускаете кейлоггер с правами пользователя для сбора пароля администратора/рута и, наконец, получаете контроль над системой.

Хотя я не буду показывать, как именно этого добиться, вы можете взглянуть на это руководство по написанию расширений C для Python:



Заключение

Подводя итог, можно сказать, что функция print() имеет гораздо больше возможностей, чем обычно известно разработчикам Python, от простого вывода на консоль до записи в файлы и выполнения произвольного кода. В конце концов, функция print() — это всего лишь инструмент, и поэтому вы можете использовать его для достижения разных целей.

Кисть — это просто палочка с прикрепленными к ней щетинками. Художник должен использовать его творчески.

И можем ли мы утверждать, что художник с кистью — это всего лишь хакер с палкой?

Надеюсь, вам понравилась эта статья. Если вы знаете о любом другом практическом или теоретическом варианте использования функции print(), сообщите нам об этом в комментарии.

Спасибо за прочтение!

Если вы заинтересованы в том, чтобы сделать ваш код Python более профессиональным и удобным в сопровождении, ознакомьтесь с этой статьей: