TCP-клиент Black Hat Python

Я работаю над книгой Black Hat Python, и хотя она была написана в 2015 году, часть кода кажется немного устаревшей. Например, операторы печати не используют круглые скобки. Однако я не могу запустить приведенный ниже скрипт и продолжаю получать сообщение об ошибке.

    # TCP Client Tool

import socket

target_host = "www.google.com"
target_port = 80

# creates a socket object. AF_INET parameter specifies IPv4 addr/host. SOCK_STREAM is TCP specific, not UDP.
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# connect the client
client.connect((target_host, target_port))

# sending some data
client.send("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n\")

# receive some data
response = client.recv(4096)

print(response)

Ошибка, которую я получаю, просто гласит: Файл "", строка 15 client.send("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n\") ^


person Steppenwolf1917    schedule 08.06.2020    source источник
comment
Похоже, в запросе client.send слишком много символов обратной косой черты. Вы избежали закрытия ". Попробуйте: client.send("GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")   -  person larsks    schedule 08.06.2020
comment
Внимание: эта книга была написана с учетом Python2. Что является DOA с 31 декабря 2019 года. Поэтому, если вы не имеете четкого представления об основах Python, начинать с этой книги будет непросто. Поскольку во многих местах будет несколько проблем с преобразованием между строками и байтами. Например, все запросы к сокету требуют bytes передачи объектов в сокет и из него. так что socket.send("GET ...) на самом деле должно быть, например, socket.send(bytes("GET ...", 'UTF-8')).   -  person Torxed    schedule 08.06.2020
comment
@torxed спасибо за вклад! Так что мне просто нужно указать байты раньше, или он будет читать его как строку?   -  person Steppenwolf1917    schedule 09.06.2020
comment
Байты - это строка, просто не закодированная кодеком (очень простыми словами)   -  person Torxed    schedule 09.06.2020
comment
@Torxed проще говоря :) как вы можете описать разницу между байтами и строками с/без кодека или что-то еще, что было бы лучшим способом понять этот конкретный сценарий?   -  person Steppenwolf1917    schedule 10.06.2020
comment
@ Steppenwolf1917 Нет простого способа сделать это, но ниже я старался изо всех сил. Это длинный текст, но это потому, что он описывает фундаментальные концепции строк (кодированные байтовые объекты) и байтов (потоки данных). И почему они существуют и когда их использовать.   -  person Torxed    schedule 10.06.2020


Ответы (2)


Вы экранируете ", помещая \ перед, что означает, что python не знает, что строка заканчивается здесь. Вы можете заметить, что в вашем сообщении весь код после этой строки окрашен, как если бы это была строка.

client.send также требуется byte-like object, а не строка. Вы можете указать это, поставив b перед вашей строкой:

client.send(b"GET / HTTP/1.1\r\nHost: google.com\r\n\r\n")

После этого скрипт работает нормально

person Anonyme2000    schedule 08.06.2020
comment
спасибо за отзыв, очень приятно! Какой ввод мне нужен для байтовых объектов, если не строки? - person Steppenwolf1917; 09.06.2020
comment
@ Steppenwolf1917 Ниже я написал описание, когда и зачем их использовать (и почему строки необходимо преобразовывать в байтоподобные объекты, и почему это одно и то же, когда вы сводите это, просто разные слои абстракции поверх строк). - person Torxed; 10.06.2020

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

Струны

В Python, как и во многих других языках, есть так называемые Escape-последовательности, короче говоря, помещающие \ перед чем-то означает, что все, что последует, будет иметь особое значение. Два примера:

Пример 1: разрывы строк (новая строка)

print("Something \nThis is a new line")

Это заставит python интерпретировать n не как букву «n», а как специальный символ, указывающий, что «здесь должна быть новая строка», и все благодаря тому, что \n перед буквой n. \r также является "новой строкой", но в прежние времена это было эквивалентно перемещению переместить головку принтера в начало строки, а не только на одну строку вниз.

Пример 2. Экранирование кавычек в строках

print("I want to print this quote: \" in my string")

В этом примере, поскольку мы используем символ кавычек " для начала и конца нашей строки, добавление его в середину приведет к разрыву строки (надеюсь, вам это понятно) , Чтобы затем продолжить добавление кавычек в середине текста, нам нужно снова добавить символ escape-последовательности \ перед кавычкой, это говорит Python не анализировать кавычку как кавычку, а просто добавьте его в строку.Есть альтернатива этому, а именно:

print('I want to print this quote: " in my string')

И это потому, что вместо этого вся строка начинается и заканчивается ', что позволяет Python точно угадать (разобрать) начало и конец фактической всей строки, что делает его на 100% уверенным, что цитата в этом случай - просто еще один кусок строки. Эти управляющие последовательности описаны здесь с дополнительными примерами.


Байты против строк

Чтобы лучше понять разницу, мы сначала посмотрим, как взаимодействуют Python и терминал, который вы используете. Я предполагаю, что вы запускаете свои скрипты Python из cmd.exe, powershell.exe или в Linux что-то вроде xterm или что-то в этом роде. Основные терминалы, которые есть.

Терминал попытается проанализировать все, что отправлено в его выходной буфер, и представить его вам. Вы можете проверить это, выполнив:

print('\xc3\xa5\xc3\xa4\xc3\xb6') # Most Linux systems
print('\xe5\xe4\xf6') # Most Windows systems

Теоретически один из приведенных выше отпечатков должен был позволить вам просто напечатать кучу байтов, которые терминал каким-то образом знал, как отображать как åäö. Даже ваш браузер только что сделал это за вас (забавное замечание, так они решают и проблему эмодзи, все согласны с тем, что определенные комбинации байтов должны стать ????). Я говорю большинство Windows и Linux, потому что этот результат полностью зависит от того, какой регион/язык вы выбрали при установке операционной системы. Я нахожусь на севере ЕС (Швеция), поэтому мой кодек по умолчанию в Windows — ISO-8859-1, а на всех моих машинах с Linux у меня UTF-8. Эти кодеки важны, так как это машинно-человеческий интерфейс для представления текста.

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

Именно здесь Python2 и Python3 начинают становиться двумя разными зверями. И именно поэтому вы здесь сегодня. Проще говоря, Python2 проделал множество автоматических и волшебных «угадываний» над строками, чтобы вы могли отправить строку в сокет, а Python позаботьтесь о кодировке для вас. Python2 анализировал их и преобразовывал всевозможными способами. В Python3 многие автоматические догадки были удалены, потому что это чаще всего сбивало с толку людей. И данные, отправляемые через функции и сокеты, были, по сути, данными Шрёдингера, иногда это были строки, а иногда — байты. Вместо этого теперь вы, разработчик, должны преобразовывать данные и кодировать их... всегда.

Итак, что такое байты против строк?

байты man термины, строка, которая не была закодирована каким-либо образом и, следовательно, может содержать что-либо, связанное с «данными». Это не обязательно должна быть просто строка (a-Z, 0-9, !"#¤% и т. д.), она также может содержать специальные байты, такие как \x00 который представляет собой Нулевой байт/символ. И Python никогда не будет пытаться автоматически анализировать эти данные в Python 3. И при выполнении:

print(b'\xe5\xe4\xf6')

Как и выше, за исключением того, что вы определяете строку как bytes string в Python3, Python вместо этого отправит представление байтов а не фактические байты в буфер терминала, таким образом, терминал никогда не будет интерпретировать их как фактические байты они.

Пример 1: Кодирование ваших данных

Что подводит нас к этому первому примеру. Так как же преобразовать ваш bytes, содержащий print(b'\xe5\xe4\xf6'), в представленные символы в вашем терминале, ну, преобразовав его в строки с определенной кодировкой. В приведенном выше примере три символа \xe5\xe4\xf6 оказались кодировщиком ISO-8859-1 в процессе создания. Я знаю это, потому что в настоящее время я работаю в Windows, и если вы запустите команду chcp в своем терминале, вы получите какую кодовую страницу/кодер вы используете.

Там я могу сделать:

print(b'\xe5\xe4\xf6'.decode('ISO-8859-1')

И это преобразует объекты bytes в объект string (с кодировкой).
Проблема здесь заключается в том, что если вы отправите эти string на мою машину с Linux, он не будет иметь ни малейшего представления о том, что происходит. Потому что, если вы попробуете:

print(b'\x86\x84\x94'.decode('UTF-8'))

Вы получите сообщение об ошибке, подобное этому:

>>> print(b'\x86\x84\x94'.decode('UTF-8'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x86 in position 0: invalid start byte

Это потому, что в стране UTF-8 байта \x86 не существует. Так что у него нет способа узнать, что с ним делать. И поскольку кодировщик по умолчанию на моей машине с Linux - UTF-8, ваши данные Windows - мусор для моей машины.

Что приводит нас к ..

Розетки

В Python3 и большинстве физических областей компьютера кодировки и строки не приветствуются, поскольку на самом деле они не являются чем-то особенным. Вместо этого машины взаимодействуют битами, в короткие, 1 и 0. 8 из них становятся byte, и именно здесь в игру вступает bytes Python. При отправке чего-либо с машины на машину (или из приложения в приложение) нам придется преобразовать любое текстовое представление в последовательность bytes, чтобы машины могли общаться друг с другом. Без кодировок, без разбора вещей. Просто - возьмите данные.

Мы делаем это тремя способами, и они:

print('åäö'.encode('UTF-8'))
print(bytes('åäö', 'UTF-8'))
print(b'åäö')

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

Все эти параметры вернут bytes представление åäö с использованием кодировщика * (кроме последнего, он будет кодировать только с использованием ASCII синтаксический анализатор, который в лучшем случае ограничен).

В случае UTF-8 вам будет возвращено что-то вроде:

b'\xc3\xa5\xc3\xa4\xc3\xb6'

И это, это то, что вы можете отправить через сокет. Потому что это всего лишь последовательность байтов, которую терминалы, машины и приложения не будут касаться или обрабатывать каким-либо иным образом, кроме как последовательность единиц и нулей * ("11000011 10100101 11000011 10100100 11000011 10110110", если быть точным)

Вместе с некоторой сетевой логикой это то, что будет отправлено на ваш сокет. И вот как машины общаются.

введите здесь описание изображения

Это обзор того, что происходит. «Человек» — это терминал, также известный как машинно-человеко-интерфейс, куда вы вводите свой åäö, а терминал кодирует/анализирует его как определенную кодировку. Ваше приложение должно творить чудеса, чтобы преобразовать его во что-то, с чем может работать сокет/физический мир.

person Torxed    schedule 10.06.2020