ゾンビ狩りクラブ

Linux, Server, Network, Security 関連などをゆるーくテキトーに載せてます

packet socketsを使用してパケットを送信する

packetソケットを使えば、L2レベルでパケットを好きなように作成、送信することができる。作成したパケットは修正されずにそのまま送信されるので、アドレスの偽造もできる。 今回はこれを使ってICMP Echo requestパケットを作成、送信する。

書式はMan page of PACKETより、

#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h> /* L2 プロトコル */
packet_socket = socket(AF_PACKET, int socket_type, int protocol)

socket_type: SOCK_DGRAMまたはSOCK_RAW
protocol: <linux/if_ether.h>に記述されたプロトコル

AF_PACKETで扱うsockaddr_ll構造体は、以下のようになっている。

struct sockaddr_ll {
    unsigned short sll_family;   /* 常に AF_PACKET */
    unsigned short sll_protocol; /* 物理層のプロトコル */
    int            sll_ifindex;  /* インターフェース番号 */
    unsigned short sll_hatype;   /* ARP ハードウェア種別 */
    unsigned char  sll_pkttype;  /* パケット種別 */
    unsigned char  sll_halen;    /* アドレスの長さ */
    unsigned char  sll_addr[8];  /* 物理層のアドレス */
};

以下はICMPのEcho requestパケットを送信するソースコードである。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <linux/if_ether.h>
#include <netinet/ip_icmp.h>

//RFC 1071
int checksum(unsigned short *buf, int bufsize) {
    unsigned long sum = 0;
    
    while(bufsize > 1) {
        sum += *buf++;
        bufsize -= 2;
    }

    if(bufsize > 0) {
        sum += *(unsigned char*)buf;
    }

    while(sum >> 16) {
        sum = (sum & 0xffff) + (sum >> 16);
    }

    return ~sum;
}


int main(int argc, char** argv) {
    struct sockaddr_ll addr;
    int i, sockfd;
    char buf[4096];
    struct ethhdr* ethheader;
    struct iphdr* ipheader;
    struct icmphdr* icmpheader;
    char* interface = "wlan0"; //インターフェース
    char* sipaddr = ""; //送信元ipアドレス (例えば"192.168.1.1")
    char* dipaddr = ""; //宛先ipアドレス
    char* shwaddr = ""; //送信元macアドレス (例えば"\x00\x00\x00\x00\x00\x00")
    char* dhwaddr = ""; //宛先macアドレス

    if((sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
        perror("sockfd");
        exit(1);
    }

    ethheader = (struct ethhdr*)buf;
    ipheader = (struct iphdr*)(buf + ETH_HLEN);
    icmpheader = (struct icmphdr*)(buf + ETH_HLEN + sizeof(struct iphdr));
    
    memcpy(ethheader->h_dest, dhwaddr, ETH_ALEN);
    memcpy(ethheader->h_source, shwaddr, ETH_ALEN);
    
    ethheader->h_proto = htons(0x0800);

    ipheader->version = 4;
    ipheader->ihl = 5;
    ipheader->tos = 0;
    ipheader->tot_len = htons(sizeof(struct iphdr) + sizeof(struct icmphdr));
    ipheader->id = htons(1);
    ipheader->frag_off = htons(0x4000);
    ipheader->ttl = 32;
    ipheader->protocol = 1;
    inet_pton(AF_INET, sipaddr, &ipheader->saddr);
    inet_pton(AF_INET, dipaddr, &ipheader->daddr);
    
    ipheader->check = checksum((unsigned short*)ipheader, sizeof(struct iphdr));
    
    icmpheader->type = ICMP_ECHO;
    icmpheader->code = 8;
    icmpheader->checksum = 0;
    icmpheader->un.echo.id = 0;
    icmpheader->un.echo.sequence = 0;

    icmpheader->checksum = checksum((unsigned short*)icmpheader, sizeof(struct icmphdr));

    memset(&addr, 0, sizeof(addr));

    addr.sll_family = AF_PACKET;
    addr.sll_protocol = htons(ETH_P_ALL);
    addr.sll_ifindex = if_nametoindex(interface);
    addr.sll_halen = IFHWADDRLEN;

    if((sendto(sockfd, (char*)ethheader, ETH_HLEN + sizeof(struct iphdr) + sizeof(struct icmphdr), 0, (struct sockaddr*)&addr, sizeof(addr))) == -1) {
        perror("sendto");
        exit(1);
    }

    close(sockfd);

    return 0;
}

実際にipアドレスとmacアドレスを指定して送信したものをwiresharkでキャプチャした図が以下である。 f:id:Garfields:20150615193235p:plain

Echo requestが送信できていること、また、それに対して宛先からEcho replyが来ているのも確認できた。