Меню сайта
Наш опрос
Оцените мой сайт
Всего ответов: 6
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Главная » 2014 » Июль » 22 » Нюхаем сеть через Linux
15:18
Нюхаем сеть через Linux
В этой статье речь пойдёт не столько о написании сниффера для Linux, сколько о структуре пакетов стека протоколов TCP/IP. Идея статьи взята из статьи на сайте xakep.ru.Мы будем рассматривать пакеты, которые попадают к нам на сетевую карту. Эти пакеты относятся к протоколу Ethernet. Примерная структура этого пакета приведена в таблице ниже. Ethernet заголовокEthernet данные (фрейм сетевого протокола) IP заголовок IP данные (фрейм транспортного протокола) TCP или UDP заголовокДанные приклодного протокола (HTTP, FTP, SMB итд). Содержание статьиSnifferСтек протоколов TCP/IPEthernetInternet ProtocolTransmission Control ProtocolUser Datagram ProtocolАнализаторЗаключениеSnifferБыло бы не справедливо, если бы я приводил примеры анализа пакетов, не показав, как их получить. Для этой цели я написал сниффер, аналогичный тому, что описан в упомянутой статье. #ifndef __LINUX_SNIFFER_SHIFFER_H__#define __LINUX_SNIFFER_SHIFFER_H__#include <set>#include <string.h>#include <stdexcept>#include "Analyzer.h"namespace LinuxSniffer {class SnifferError : public std::runtime_error{public: SnifferError(const std::string & message) throw() : std::runtime_error(message) { } virtual ~SnifferError() throw() { }}; // class SnifferErrorclass Sniffer{public: explicit Sniffer(const std::string & device_name, bool sniff_all = false) throw(SnifferError); virtual ~Sniffer(); bool addAnalyzer(Analyzer * analyzer); bool removeAnalyzer(Analyzer * analyzer); void start() throw(SnifferError); void stop();private: void deinit(); void makeSocket() throw(SnifferError); void bindSocketToDevice() throw(SnifferError); void setPromiscuousMode() throw(SnifferError); void unsetPromiscuousMode(); void runAnalyzers(const unsigned char * frame, size_t frame_size);private: Sniffer(const Sniffer &); Sniffer & operator = (const Sniffer &);private: const std::string m_device; const bool m_sniff_all; std::set<Analyzer *> m_analyzers; int m_socket; bool m_is_promiscuouse_mode_set; bool m_is_stopping; unsigned char * mp_frame_buffer; static const size_t m_frame_buffer_size = 65536;}; // class Sniffer} // namespace LinuxSniffer#endif // __LINUX_SNIFFER_SHIFFER_H__Когда сниффер получает пакет, он запускает всех зарегистрированных анализаторов. Для их регистрации и служат методы addAnalyzer и removeAnalyzer. Каждый анализатор должен реализовывать интерфейс Analyzer class Analyzer{public: virtual ~Analyzer() { } virtual void analyze(const uint8_t * frame, size_t frame_size) = 0;}; // class AnalyzerИменно этим механизмом мы и будем пользоваться при анализе структуры пакетов стека TCP/IP. Приведу полную реализацию класса Sniffer #include <errno.h>#include <netpacket/packet.h>#include <sys/types.h>#include <sys/ioctl.h>#include <arpa/inet.h>#include <linux/if.h>#include <linux/if_ether.h>#include <linux/sockios.h>#include "Sniffer.h"using namespace LinuxSniffer;Sniffer::Sniffer(const std::string & device_name, bool sniff_all) throw(SnifferError) : m_device(device_name), m_sniff_all(sniff_all), m_is_promiscuouse_mode_set(false), m_is_stopping(false), mp_frame_buffer(0){ makeSocket(); mp_frame_buffer = new unsigned char[m_frame_buffer_size];}Sniffer::~Sniffer(){ deinit();}void Sniffer::deinit(){ ::close(m_socket); unsetPromiscuousMode(); delete [] mp_frame_buffer;}bool Sniffer::addAnalyzer(Analyzer * analyzer){ return m_analyzers.insert(analyzer).second;}bool Sniffer::removeAnalyzer(Analyzer * analyzer){ return m_analyzers.erase(analyzer) > 0;}void Sniffer::makeSocket() throw(SnifferError){ m_socket = ::socket(AF_PACKET, SOCK_RAW, ::htons(ETH_P_ALL)); if(-1 \0'; int setopt_result = ::setsockopt(m_socket, SOL_SOCKET, SO_BINDTODEVICE, device, device_name_len); delete [] device; if(-1 ProtocolFrame.h"#include "MacAddress.h"namespace LinuxSniffer {class EthernetFrameV2 : public ProtocolFrame{public: EthernetFrameV2() : ProtocolFrame("Ethernet Version 2") { } virtual ~EthernetFrameV2() { } virtual bool init(const uint8_t * buffer, size_t buffer_size); const MacAddress & getSourceMacAddress() const { return m_src_mac_addr; } const MacAddress & getDestinationMacAddress() const { return m_dest_mac_addr; } static const uint8_t (& getEthernetType())[2] { static bool is_init = false; static uint8_t type[2]; if(!is_init) { ::memcpy(type, "\x08\x00", 2); is_init = true; } return type; }private: MacAddress m_src_mac_addr; MacAddress m_dest_mac_addr;}; // class EthernetFrameV2} // namespace LinuxSniffer#endif // __LINUX_SNIFFER_ETHERNET_FRAME_V2_H__Дополнительные поля являются MAC адресами отправителя и получателя. Эти поля имеют тип MacAddress, который описан простым классом #ifndef __LINUX_SNIFFER_MAC_ADDRESS_H__#define __LINUX_SNIFFER_MAC_ADDRESS_H__#include <stdint.h>#include <string>#include <cstdio>namespace LinuxSniffer {class MacAddress{public: MacAddress() : b0(0), b1(0), b2(0), b3(0), b4(0), b5(0) { } explicit MacAddress(const uint8_t address[6]) : b0(address[0]), b1(address[1]), b2(address[2]), b3(address[3]), b4(address[4]), b5(address[5]) { } std::string toString() const { char str[16]; ::sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", b0, b1, b2, b3, b4, b5); return str; }public: uint8_t b0; uint8_t b1; uint8_t b2; uint8_t b3; uint8_t b4; uint8_t b5;}; // class MacAddress} // namespace LinuxSniffer#endif // __LINUX_SNIFFER_MAC_ADDRESS_H__Реализацтя метода init класса EthernetFrameV2 сводится к проверке версии протокола Ethernet и получению MAC адресов отправителя и получателя. Для простоты, получение контрольной суммы опустим. Для того, чтобы получить все необходимые поля, достаточно привести буфер фрейма к типу ether_header, объявленному в netinet/ether.h. #include <cstring>#include <netinet/ether.h>#include "EthernetFrameV2.h"using namespace LinuxSniffer;bool EthernetFrameV2::init(const uint8_t * buffer, size_t buffer_size){ if(buffer_size < sizeof(ether_header)) return false; const ether_header * hdr = reinterpret_cast<const ether_header *>(buffer); if(::memcmp(&hdr->ether_type, getEthernetType(), sizeof(getEthernetType()))) return false; setDataOffset(sizeof(ether_header)); m_src_mac_addr = MacAddress(hdr->ether_shost); m_dest_mac_addr = MacAddress(hdr->ether_shost); return true;}Internet ProtocolВ секции данных фрейма протокола ethernet хранится фрейм протокола IP. Этот протокол добавляет к пакету информацию об IP адресах отправителя и получателя и множество служебных данных. В следующей таблице показана структура фрейма IP версии 4. Байты Смещение в битах Размер в битах Описание 0 0 4 Версия 4 4 Размер заголовка 1 8 6 Точка кода дифференцированных услуг (Differentiated services code point) 14 2 Явное уведомление о перегруженности (Explicit congestion notification) 2-3 16 16 Размер пакета 4-5 32 16 Идентификатор 6-7 48 3 Флаги 51 13 Смещение фрагмента 8 64 8 Время жизни 9 72 8 Протокол 10 80 16 Контрольная сумма заголовка 11-15 96 32 IP адрес источника 16-20 128 32 IP адрес назначения 20-24 160 32 Опции (если размер заголовка > 5) 20+ или 24+ 160 или 192 - Данные В ранних версиях спецификации протокола "Точка кода дифференцированных услуг" и "Явное уведомление о перегруженности" были объединены в одно поле "Тип сервиса". Для того, чтобы использовать новое деление участники обмена должны договориться об этом. Я не буду разделять эти поля и буду использовать тип сервиса.В поле "Флаги" биты от старшего к младшему означают:0: Зарезервирован, должен быть равен 0;1: Не фрагментировать;2: У пакета еще есть фрагменты."Идентификатор" используется для предоставления информации о фрагментации пакета. Поле "Протокол" сообщает идентификатор протокола, фрейм которого находится в данных.Думаю, остальные поля не нуждаются в комментариях.Класс, описывающий IP фрейм приведён ниже #ifndef __LINUX_SNIFFER_IP_FRAME_V4_H__#define __LINUX_SNIFFER_IP_FRAME_V4_H__#include <string>#include "ProtocolFrame.h"namespace LinuxSniffer {class IpFrameV4 : public ProtocolFrame{public: IpFrameV4() : ProtocolFrame("Internet Protocol Version 4"), m_header_length(0), m_tos(0), m_package_size(0), m_id(0), m_flags(0), m_frag_offset(0), m_time_to_life(0), m_protocol(0), m_checksum(0) { } virtual ~IpFrameV4() { } virtual bool init(const uint8_t * buffer, size_t buffer_size); const std::string & getSourceAddress() const { return m_src_ip_addr; } const std::string & getDestinationAddress() const { return m_dest_ip_addr; } uint8_t getHeaderLength() const { return m_header_length; } uint8_t getTypeOfService() const { return m_tos; } uint16_t getPackageSize() const { return m_package_size; } uint16_t getId() const { return m_id; } uint8_t getFlags() const { return m_flags; } uint16_t getFragmintationOffset() const { return m_frag_offset; } uint8_t getTimeToLife() const { return m_time_to_life; } uint8_t getProtocolId() const { return m_protocol; } uint16_t getHeaderCheckSum() const { return m_checksum; }public: static const uint8_t m_ipv4_version = 4;private: void splitFragmentOffsetAndFlags();private: std::string m_src_ip_addr; std::string m_dest_ip_addr; uint8_t m_header_length; uint8_t m_tos; uint16_t m_package_size; uint16_t m_id; uint8_t m_flags; uint16_t m_frag_offset; uint8_t m_time_to_life; uint8_t m_protocol; uint16_t m_checksum;}; // class IpFrameV4} // namespace LinuxSniffer#endif // __LINUX_SNIFFER_IP_FRAME_V4_H__Для того, чтобы получить все поля из буфера фрейма достаточно привести указатель на него к указателю на структуру iphdr, объявленной в файле netinet/ip.h. В реализации метода init класса IpFrameV4 именно так и сделано #include <netinet/ip.h>#include <arpa/inet.h>#include "IpFrameV4.h"using namespace LinuxSniffer;bool IpFrameV4::init(const uint8_t * buffer, size_t buffer_size) virtual ~TransportProtocolFrame() { } uint16_t getSourcePort() const { return m_src_port; } uint16_t getDestinationPort() const { return m_dest_port; }protected: void setSourcePort(uint16_t port) { m_src_port = port; } void setDestinationPort(uint16_t port) { m_dest_port = port; }private: uint16_t m_src_port; uint16_t m_dest_port;}; // class TransportProtocolFrame} // namespace LinuxSniffer#endif // __LINUX_SNIFFER_TRANSPORT_PROTOCOL_FRAME_H__Структура фрейма протокола TCP, являющегося одним из протоколов транспортного уровня, показана в следующей таблице. Байты Смещение в битах Размер в битах Описание 0-1 0 16 Порт источника 2-3 16 16 Порт назначения 4-8 32 32 Номер последовательности 8-12 64 32 Номер подтверждения последовательности 13-14 96 4 Смещение данных 100 6 Зарезервировано 106 6 Флаги 15-16 112 16 Размер окна 17-18 128 16 Контрольная сумма 19-20 144 16 Указатель важности 21+ 160 - Необязательные опции За опциями или 21+ За опциями или 160 - Данные Протокол TCP контролирует последовательность пакетов. Именно для этого нужны поля "Номер последовательности" и "Номер подтверждения последовательности"."Флаги": URG - Поле "Указатель важности" задействовано;ACK - Поле "Номер подтверждения" задействовано;PSH - Инструктирует получателя протолкнуть данные, накопившиеся в приемном буфере, в приложение пользователя;RST - Оборвать соединения;SYN - Синхронизация номеров последовательности;FIN - флаг указывает на завершение соединения."Размер окна" - это число байтов, которые получатель готов принять."Указатель важности" указывает на номер октета, которым заканчиваются важные данные. Это поле игнорируется, если флаг URG не установлен.Для чтения фрейма протокола TCP я расширил класс TransportProtocolFrame классом TcpFrame. #ifndef __LINUX_SNIFFER_TCP_FRAME_H__#define __LINUX_SNIFFER_TCP_FRAME_H__#include "TransportProtocolFrame.h"namespace LinuxSniffer {class TcpFrame : public TransportProtocolFrame{public: TcpFrame() : TransportProtocolFrame("Transmission Control Protocol"), m_sequence_number(0), m_ascknowledgment_number(0), m_flag_fin(false), m_flag_syn(false), m_flag_rst(false), m_flag_psh(false), m_flag_ack(false), m_flag_urg(false), m_window_size(0), m_checksum(0), m_urgent_ptr(0) { } virtual ~TcpFrame() { } virtual bool init(const uint8_t * buffer, size_t buffer_size); uint32_t getSequenceNumber() const { return m_sequence_number; } uint32_t getAscknowledgmentNumber() const { return m_ascknowledgment_number; } bool isFinFlagSet() const { return m_flag_fin; } bool isSynFlagSet() const { return m_flag_syn; } bool isRstFlagSet() const { return m_flag_rst; } bool isPshFlagSet() const { return m_flag_psh; } bool isAckFlagSet() const { return m_flag_ack; } bool isUrgFlagSet() const { return m_flag_urg; } uint16_t getWindowSize() const { return m_window_size; } uint16_t getCheckSum() const { return m_checksum; } uint16_t getUrgentPtr() const { return m_urgent_ptr; }public: static const uint8_t m_protocol_id = 6;private: uint32_t m_sequence_number; uint32_t m_ascknowledgment_number; bool m_flag_fin; bool m_flag_syn; bool m_flag_rst; bool m_flag_psh; bool m_flag_ack; bool m_flag_urg; uint16_t m_window_size; uint16_t m_checksum; uint16_t m_urgent_ptr;}; // class TcpFrame} // namespace LinuxSniffer#endif // __LINUX_SNIFFER_TCP_FRAME_H__Как и в случае с IP, для фрейма TCP в заголовочных файлах linux припасена структура tcphdr. Находится она в файле netinet/tcp.h. Реализуем метод init с использованием этой структуры. #include <netinet/tcp.h>#include "TcpFrame.h"using namespace LinuxSniffer;bool TcpFrame::init(const uint8_t * buffer, size_t buffer_size){ if(buffer_size < sizeof(tcphdr)) return false; const tcphdr * tcp_header = reinterpret_cast<const tcphdr *>(buffer); setSourcePort(tcp_header->source); setDestinationPort(tcp_header->dest); setDataOffset(tcp_header->doff); m_sequence_number = tcp_header->seq; m_ascknowledgment_number = tcp_header->ack_seq; m_flag_fin = tcp_header->fin TransportProtocolFrame.h"namespace LinuxSniffer {class UdpFrame : public TransportProtocolFrame{public: UdpFrame() : TransportProtocolFrame("User Datagram Protocol") { } virtual ~UdpFrame() { } virtual bool init(const uint8_t * buffer, size_t buffer_size); uint16_t getDatagramLength() const { return m_datagram_length; } uint16_t getCheckSum() const { return m_checksum; }public: static const uint8_t m_protocol_id = 17;private: uint16_t m_datagram_length; uint16_t m_checksum;}; // class UdpFrame} // namespace LinuxSniffer#endif // __LINUX_SNIFFER_UDP_FRAME_H__Аналогично фреймам протоколов IP и TCP, реализуем метод init с использованием структуры udphdr, объявленной в файле netinet/udp.h. #include <netinet/udp.h>#include "UdpFrame.h"using namespace LinuxSniffer;bool UdpFrame::init(const uint8_t * buffer, size_t buffer_size){ if(buffer_size < sizeof(udphdr)) return false; const udphdr * udp_header = reinterpret_cast<const udphdr *>(buffer); setSourcePort(udp_header->source); setDestinationPort(udp_header->dest); setDataOffset(sizeof(udphdr)); m_datagram_length = udp_header->len; m_checksum = udp_header->check; return true;}АнализаторТеперь, когда мы знаем структуру основных протоколов, ничего не стоит проанализировать данные, которые мы получаем от сниффера. Для этого реализуем интерфейс Analyzer. #ifndef __LINUX_SNIFFER_IP_ANALYZER_H__#define __LINUX_SNIFFER_IP_ANALYZER_H__#include <utility>#include "Analyzer.h"namespace LinuxSniffer {class TransportProtocolFrame;class IpAnalyzer : public Analyzer{public: virtual ~IpAnalyzer() { } virtual void analyze(const uint8_t * frame, size_t frame_size);private: size_t tryEthV2Analyze(const uint8_t * frame, size_t frame_size); std::pair<size_t, uint8_t> tryIpV4Analyze(const uint8_t * frame, ize_t frame_size); size_t tryTcpAnalyze(const uint8_t * frame, size_t frame_size); size_t tryUdpAnalyze(const uint8_t * frame, size_t frame_size); void printTransportProtocolFrame(const TransportProtocolFrame & frame) const;}; // class IpAnalyzer} // namespace LinuxSniffer#endif // __LINUX_SNIFFER_IP_ANALYZER_H__#include <iostream>#include "IpAnalyzer.h"#include "EthernetFrameV2.h"#include "IpFrameV4.h"#include "TcpFrame.h"#include "UdpFrame.h"using namespace LinuxSniffer;void IpAnalyzer::analyze(const uint8_t * frame, size_t frame_size){ const size_t ip_offset = tryEthV2Analyze(frame, frame_size); if(0 ; } std::cout << std::endl;}size_t IpAnalyzer::tryEthV2Analyze(const uint8_t * frame, size_t frame_size){ EthernetFrameV2 eth_frame; if(!eth_frame.init(frame, frame_size)) return 0; std::cout << << eth_frame.getName() << " << "Source MAC Address: " << eth_frame.getSourceMacAddress().toString() << std::endl << "Destination MAC Address: " << eth_frame.getDestinationMacAddress().toString() << std::endl; return eth_frame.getDataOffset();}std::pair<size_t, uint8_t> IpAnalyzer::tryIpV4Analyze(const uint8_t * frame, size_t frame_size){ IpFrameV4 ip_frame; if(!ip_frame.init(frame, frame_size)) return std::make_pair(0, 0); std::cout << << ip_frame.getName() << " << "Source IP Address: " << ip_frame.getSourceAddress() << std::endl << "Destination IP Address: " << ip_frame.getDestinationAddress() << std::endl << "Header Length: " << std::dec << static_cast<uint32_t>(ip_frame.getHeaderLength()) << std::endl << "Type Of Service: " << static_cast<uint32_t>(ip_frame.getTypeOfService())<< std::endl << "Package Size: "<< ip_frame.getPackageSize() << std::endl << "Identification: " << ip_frame.getId() << std::endl << "Flags: " << static_cast<uint32_t>(ip_frame.getFlags())<< std::endl << "Fragmentation Offset: " << ip_frame.getFragmintationOffset() << std::endl << "Time To Live: " << static_cast<uint32_t>(ip_frame.getTimeToLife()) << std::endl << "Transport Protocol ID: " << static_cast<uint32_t>(ip_frame.getProtocolId()) << std::endl << "CRC-16 Header CheckSum:" << std::hex << ip_frame.getHeaderCheckSum() << std::endl; return std::make_pair(ip_frame.getDataOffset(), ip_frame.getProtocolId());}size_t IpAnalyzer::tryTcpAnalyze(const uint8_t * frame, size_t frame_size){ TcpFrame tcp_frame; if(!tcp_frame.init(frame, frame_size)) return 0; printTransportProtocolFrame(tcp_frame); std::cout << std::dec << "Sequence Number: " << tcp_frame.getSequenceNumber() << std::endl << "Ascknowledgment Number: " << tcp_frame.getAscknowledgmentNumber() << std::endl << "Window Size: " << tcp_frame.getWindowSize() << std::endl << "Urgent Pointer: " << tcp_frame.getUrgentPtr() << std::endl << "Flags:\n" << " FIN: " << std::boolalpha << tcp_frame.isFinFlagSet() << std::endl << " SYN: " << tcp_frame.isSynFlagSet() << std::endl << " RST: " << tcp_frame.isRstFlagSet() << std::endl << " PSH: " << tcp_frame.isPshFlagSet() << std::endl << " ACK: " << tcp_frame.isAckFlagSet() << std::endl << " URG: " << tcp_frame.isUrgFlagSet() << std::endl << "CheckSum: " << std::hex << tcp_frame.getCheckSum() << std::endl; return tcp_frame.getDataOffset();}size_t IpAnalyzer::tryUdpAnalyze(const uint8_t * frame, size_t frame_size){ UdpFrame udp_frame; if(!udp_frame.init(frame, frame_size)) return 0; printTransportProtocolFrame(udp_frame); std::cout << std::dec << "Datagram Length: " << std::dec << udp_frame.getDatagramLength() << std::endl << "Datagram CheckSum: " << std::hex << udp_frame.getCheckSum() << std::endl; return udp_frame.getDataOffset();}void IpAnalyzer::printTransportProtocolFrame( const TransportProtocolFrame & frame) const{ std::cout << << frame.getName() << " << std::dec << "Source Port: " << frame.getSourcePort() << std::endl << "Destination Port: " << frame.getDestinationPort() << std::endl;}Всё очень просто. Пытаемся преобразовать полученный фрейм в фрейм протокола Ethernet версии 2. Затем, используя полученное смещение данных, получаем фрейм IP. Проанализировав номер протокола, пытаемся получить данные либо о TCP, либо о UDP фрейме. Попутно выводим всю полученную информацию на консоль. В конце анализа у нас остаётся переменная, хранящая смещение данных прикладного протокола, которую Вы можете применить для дальнейшего разворачивания стека. ЗаключениеПолученную программу нельзя запустить от пользователя, чей UID не равен нулю. Это и естественно, ведь мы слишком много себе позволили.Исходные тексты программы можно скачать здесь.
Просмотров: 186 | Добавил: admin | Рейтинг: 0.0/0
Всего комментариев: 0
avatar
Форма входа
Календарь
«  Июль 2014  »
ПнВтСрЧтПтСбВс
 123456
78910111213
14151617181920
21222324252627
28293031
Архив записей