Các loại socket thông thường chỉ cho phép ứng dụng truy cập vào payload của tầng transport do hệ điều hành chịu trách nhiệm tạo các header cho tầng transport, network và data link. Để có thể truy cập và chỉnh sửa các header thuộc tầng network và data link, chúng ta cần sử dụng raw socket.
Có hai loại raw socket tương ứng với tầng network (L3 socket) và tầng data link (L2 socket).
Seealso
Nếu muốn lập trình raw socket trên Windows thì cần dùng thư viện
winpcap
.
Opening a Raw Socket
Để mở một socket, ta cần xác định họ socket, loại socket và giao thức rồi truyền vào hàm socket()
(sys/socket.h
):
int open_raw_socket(int sock_family, int sock_type, int protocol)
{
int raw_socket = socket(sock_family, sock_type, protocol);
if (raw_socket < 0)
{
perror("Error: socket() failed");
return -1;
}
return raw_socket;
}
Giá trị trả về của hàm socket()
là một socket descriptor có kiểu là int
.
Với raw socket ở layer 3:
- Họ socket là
AF_INET
(bits/socket.h
). - Loại socket là
SOCK_RAW
(bits/socket_types.h
). - Loại giao thức là
IPPROTO_RAW
(netinet/in.h
).
Ví dụ:
/* Open layer-3 raw socket */
int layer3_socket_descriptor = open_raw_socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
Còn đối với raw socket ở layer 2:
- Họ socket là
AF_PACKET
- Loại socket là
SOCK_RAW
- Loại giao thức là các macro trong
linux/if_ether.h
. Macro cho giao thức IP làETH_P_IP
, tương ứng với giá trị0x0800
. Ví dụ:
/* Open layer-2 raw socket*/
int layer2_socket_descriptor = open_raw_socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
Info
Hàm
htons()
(netinet/in.h
) giúp chuyển thứ tự byte của host thành thứ tự byte của gói tin mạng.
Interface Request
Để truy xuất đến các thuộc tính của network interface, ta cần dùng cấu trúc ifreq
(linux/if.h
):
struct ifreq
{
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union
{
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void *ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
#define ifr_name ifr_ifrn.ifrn_name /* interface name */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
#define ifr_map ifr_ifru.ifru_map /* device map */
#define ifr_slave ifr_ifru.ifru_slave /* slave device */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */
#define ifr_newname ifr_ifru.ifru_newname /* New name */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/
Xây dựng hàm khởi tạo cấu trúc như sau:
struct ifreq initialize_ifreq(char *if_name)
{
struct ifreq if_request;
memset(&if_request, 0, sizeof(struct ifreq));
strncpy(if_request.ifr_name, if_name, strlen(if_name));
return if_request;
}
Với if_name
là tên network interface mà ta muốn truy xuất thông tin.
Sử dụng như sau:
struct ifreq if_req = initialize_ifreq(if_name);
Getting the Index of the Interface to Send a Packet
Khi cần gửi gói tin thông qua raw socket ở layer 2, ta cần biết index của network interface.
Sử dụng hàmioctl()
(sys/ioctl.h
) với macro SIOCGIFINDEX
(bits/ioctls.h
) để lấy index của network interface:
int get_if_index(int socket_descriptor, struct ifreq if_request)
{
/* Get the interface index */
if (ioctl(socket_descriptor, SIOCGIFINDEX, &if_request) < 0)
{
perror("Error: ioctl() failed");
return -1;
}
return if_request.ifr_ifindex;
}
Getting the MAC Address of the Interface
Chúng ta cũng dùng hàm ioct()
để lấy địa chỉ MAC của interface. Macro cần sử dụng là SIOCGIFHWADDR
:
char *get_if_mac(int socket_descriptor, struct ifreq if_request)
{
/* Get the MAC address */
if (ioctl(socket_descriptor, SIOCGIFHWADDR, &if_request) < 0)
{
perror("Error: ioctl() failed");
return NULL;
}
return mac_str(if_request.ifr_hwaddr.sa_data);
}
Thuộc tính sa_data
được định nghĩa trong bits/socket.h
là một mảng char
14 byte:
struct sockaddr
{
__SOCKADDR_COMMON(sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
Để chuyển thành chuỗi địa chỉ MAC có dạng AA:BB:CC:DD:EE:FF
thì ta cần xây dựng hàm mac_str()
như sau:
char *mac_str(char *mac_addr)
{
char *mac_str = malloc(18);
sprintf(mac_str, "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x",
(byte)mac_addr[0],
(byte)mac_addr[1],
(byte)mac_addr[2],
(byte)mac_addr[3],
(byte)mac_addr[4],
(byte)mac_addr[5]);
return mac_str;
}
Getting the IP Address of the Interface
Nếu muốn lấy địa chỉ IP của interface, ta dùng hàm ioctl()
với macro SIOCGIFADDR
:
char *get_if_ip(int socket_descriptor, struct ifreq if_request)
{
if (ioctl(socket_descriptor, SIOCGIFADDR, &if_request) < 0)
{
perror("Error: ioctl() failed");
return NULL;
}
return inet_ntoa(((struct sockaddr_in *)&if_request.ifr_addr)->sin_addr);
}
Tương tự với thuộc tính ifr_hwaddr
, thuộc tính ifr_addr
cũng có kiểu là sockaddr
. Để chuyển mảng byte trong sockaddr
thành chuỗi địa chỉ IP có dạng A.B.C.D
thì ta cần dùng hàm inet_ntoa()
(arpa/inet.h
). Hàm này nhận vào đối số là struct in_addr
(netinet/in.h
):
/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
Nên ta cần chuyển thuộc tính ifr_addr
về kiểu sockaddr_in
(netinet/in.h
):
/* Structure describing an Internet socket address. */
struct sockaddr_in
{
__SOCKADDR_COMMON(sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof(struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof(in_port_t) - sizeof(struct in_addr)];
};
Rồi truyền thuộc tính sin_addr
của sockaddr_in
vào hàm inet_ntoa()
:
inet_ntoa(((struct sockaddr_in *)&if_request.ifr_addr)->sin_addr)
Constructing the ICMP Packet
Xây dựng hàm tạo gói tin ICMP (mà cụ thể là gói tin echo request) như sau:
Packet make_ping_packet(Packet payload)
{
/* Create packet */
Packet ping_packet = {
.data = malloc(sizeof(struct icmphdr) + payload.len),
.len = sizeof(struct icmphdr) + payload.len};
/* Point to the ICMP header */
struct icmphdr *icmph = (struct icmphdr *)ping_packet.data;
/* Set ICMP header fields */
icmph->type = ICMP_ECHO; // ICMP echo request
icmph->code = 0; // no code
icmph->checksum = 0; // set to 0 for now (will be calculated later)
icmph->un.echo.id = htons(0); // identifier
icmph->un.echo.sequence = htons(0); // sequence number
/* Copy packet payload */
memcpy(&ping_packet.data[sizeof(struct icmphdr)], payload.data, payload.len);
/* Calculate checksum */
icmph->checksum = checksum(ping_packet.data, ping_packet.len);
return ping_packet;
}
Với Packet
là một cấu trúc dữ liệu lưu trữ nội dung và kích thước của gói tin:
typedef struct
{
byte *data;
size_t len;
} Packet;
Và payload
truyền vào có thể có dạng như sau:
/* Create ICMP payload */
char *ping_payload = "Hello there!";
Packet payload = {
.data = (byte *)ping_payload,
.len = strlen(ping_payload)};
Quá trình xây dựng gói tin:
- Tạo ra một instance của cấu trúc
Packet
đồng thời cấp phát vùng nhớ cho thuộc tínhdata
. Kích thước của vùng nhớ được cấp phát cũng như là của gói tin là tổng kích thước các header của ICMP và kích thước củapayload
. - Trỏ con trỏ của
struct icmphdr *icmph
(netinet/ip_icmp.h
) đến vùng nhớ đã được cấp phát để thiết lập dữ liệu cho gói tin:- Type là
ICMP_ECHO
(tương ứng với8
) cho biết rằng đây là gói tin echo request. - Ta không sử dụng code nên gán giá trị là
0
. - Checksum tạm thời gán bằng
0
(sẽ tính sau). - Id và sequence number của gói tin có thể gán giá trị tùy ý. Ở đây ta gán bằng 0. Lý do mà sử dụng hàm
htons
vì hai trường này đều có kích thước là 2 byte và do đó cần sử dụng network byte order.
- Type là
- Sao chép dữ liệu trong
payload
vào các vùng nhớ kế tiếp. - Tính toán checksum rồi gán cho trường
checksum
.
Hàm tính checksum:
/*
Calculate checksum by summing up half-words - 2 bytes - 16 bits (1 word is 4 bytes) and taking the 1's complement
@param: buff: pointer to the buffer
@param: len: buffer length
@return: checksum value (half-word - 2 bytes)
*/
unsigned short checksum(byte buff[], int len)
{
unsigned int sum = 0; // checksum, iterator
if (len < 1)
return 0; // if buffer is empty, exit
for (int i = 0; i < len - 1; i += 2)
sum += *(word *)&buff[i]; // sum up half-words
if (len & 1)
sum += buff[len - 1]; // if buffer length is odd, add the last byte
/*
sum >> 16: shift right 16 bits to get the high order word
sum & 0xffff: get the low order word
+: add the high and low order words to wrap around into 16 bits
~: 1's complement
*/
return ~((sum >> 16) + (sum & 0xffff));
}
Để tính checksum, ta cộng tất cả các cụm 2 byte (nửa word) trong gói tin lại rồi lấy bù 1. Nếu không tính đúng checksum thì bên nhận sẽ hủy bỏ gói tin.
Seealso
Tham khào thêm: How to Calculate IP Header Checksum (With an Example) (thegeekstuff.com)
Constructing the IP Header
Xây dựng hàm tạo gói tin IP:
Packet make_ip_packet(char *src, char *dst, byte proto, Packet payload)
{
/* Create packet */
Packet ip_packet = {
.data = malloc(sizeof(struct iphdr) + payload.len),
.len = sizeof(struct iphdr) + payload.len};
/* Point to the IP header */
struct iphdr *iph = (struct iphdr *)ip_packet.data;
/* Set IP header fields */
iph->version = 4; // version
iph->ihl = 5; // IP header length (5 means 5 words, 1 word is 4 bytes, so it will be 5x4=20 bytes in total)
iph->tos = 0; // priority
iph->tot_len = htons(ip_packet.len); // total length (includes header and data)
iph->id = htons(9999); // identification (anything is valid)
iph->frag_off = 0; // fragment offset (0 means no fragmentation)
iph->ttl = 64; // time to live
iph->protocol = proto; // protocol
iph->check = 0; // set to 0 for now (will be calculated later)
iph->saddr = inet_addr(src); // source IP address
iph->daddr = inet_addr(dst); // destination IP address
iph->check = checksum(ip_packet.data, sizeof(struct iphdr)); // calculate checksum
/* Copy packet payload */
memcpy(&ip_packet.data[sizeof(struct iphdr)], payload.data, payload.len);
return ip_packet;
}
Các bước xây dựng gói tin tương tự với ICMP. Có một số điểm cần chú ý:
- Loại giao thức (
proto
) là giá trị của đối số truyền vào (nếu là ICMP thì truyền vàoIPPROTO_ICMP
, thuộcnetinet/in.h
). - Giá trị IP nguồn và giá trị IP đích có kiểu là
in_addr_t
(bản chất là kiểuunsigned int
). Ta sẽ dùng hàminet_aton()
để chuyển từ chuỗi có dạngA.B.C.D
thành số nguyên không dấu. - Checksum của gói tin IP chỉ bao gồm các header và không tính phần
payload
(sizeof(struct iphdr)
).
Constructing the Ethernet Header
Xây dựng hàm tạo frame Ethernet như sau:
Packet make_ethernet_frame(char *src, char *dst, Packet payload)
{
/* Create packet */
Packet ethernet_frame = {
.data = malloc(sizeof(struct ether_header) + payload.len),
.len = sizeof(struct ether_header) + payload.len};
/* Point to the Ethernet header */
struct ether_header *eh = (struct ether_header *)ethernet_frame.data;
eh->ether_type = htons(ETH_P_IP); // ethernet type
mac_addr(eh->ether_shost, (unsigned short *)src); // source MAC address
mac_addr(eh->ether_dhost, (unsigned short *)dst); // destination MAC address
/* Copy frame payload */
memcpy(ðernet_frame.data[sizeof(struct ether_header)], payload.data, payload.len);
return ethernet_frame;
}
Loại gói tin Ethernet là ETH_P_IP
thuộc linux/if_ether.h
(tương ứng với 0x0800
). Do giá trị có 2 byte nên cần dùng htons()
.
Hàm mac_addr
là một macro giúp lưu chuỗi địa chỉ MAC có dạng AA:BB:CC:DD:EE:FF
vào một mảng byte:
#define mac_addr(a, mac) sscanf(mac, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &a[0], &a[1], &a[2], &a[3], &a[4], &a[5])
Các bước xây dựng tương tự với ICMP và IP.
Sending the Packet
Xây dựng hàm như sau để gửi gói tin thông qua raw socket ở layer 3:
int send_packet(int socket_descriptor, char *dst, Packet packet)
{
/* Target's address information */
struct sockaddr_in target_address = {
/* Padding to make the structure the same size as struct sockaddr */
.sin_zero = {0, 0, 0, 0, 0, 0, 0, 0},
/* IPv4 */
.sin_family = AF_INET,
/* Ignore port number */
.sin_port = 0,
/* Destination address in network byte order */
.sin_addr.s_addr = inet_addr(dst)};
/*
Send packet
@param: socket_descriptor: socket file descriptor
@param: packet.data: pointer to the packet data
@param: packet.len: packet length
@param: flags, 0 means no flags
@param: (struct sockaddr *)&target_address: pointer to the target address
@param: sizeof(target_address): size of the target address
*/
if (sendto(socket_descriptor, packet.data, packet.len, 0, (struct sockaddr *)&target_address, sizeof(target_address)) < 0)
{
perror("Error: sendto() failed");
return -1;
}
else
{
printf("Success: spoofed IP packet sent\n");
}
return 0;
}
Biến sockaddr_in target_address
lưu thông tin địa chỉ của bên nhận, với:
.sin_family
có giá trị làAF_INET
, giống với họ socket truyền vào hàmsocket()
..sin_addr.s_addr
lưu địa chỉ IP của bên nhận.
Hàm sendto()
có 6 đối số:
- Socket descriptor.
- Nội dung của gói tin.
- Kích thước của gói tin.
- Các flag. Giá trị
0
tức là ta không dùng flag nào. - Thông tin địa chỉ của bên nhận có kiểu là
struct sockaddr *
. - Kích thước của cấu trúc lưu thông tin địa chỉ của bên nhận.
Hàm này trả về -1
nếu không gửi được gói tin.
Xây dựng và gửi gói tin như sau:
int main()
{
char *if_name = "br-88ab64b587ba";
char *dst = "10.9.0.5";
char *ping_payload = "Hello there!";
/* Open layer-3 raw socket */
int layer3_socket_descriptor = open_raw_socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
/* Open layer-2 raw socket*/
int layer2_socket_descriptor = open_raw_socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
/* Get layer 2 interface information */
struct ifreq if_req = initialize_ifreq(if_name);
char *src = get_if_ip(layer2_socket_descriptor, if_req);
printf("Source IP address: %s\n", src);
/* Create ICMP payload */
Packet payload = {
.data = (byte *)ping_payload,
.len = strlen(ping_payload)};
/* Create ping packet */
Packet ping_packet = make_ping_packet(payload);
/* Create ip packet */
Packet ip_packet = make_ip_packet(src, dst, IPPROTO_ICMP, ping_packet);
print_buff(ip_packet);
/* Send packet */
send_packet(layer3_socket_descriptor, dst, ip_packet);
}
Biên dịch và chạy:
root@docker-desktop:/volumes# ./icmp_spoofer
Source IP address: 10.9.0.1
Packet data: 45 00 00 28 27 0f 00 00 40 01 3f af 0a 09 00 01 0a 09 00 05 08 00 95 11 00 00 00 00 48 65 6c 6c 6f 20 74 68 65 72 65 21
Packet length: 40
Success: spoofed IP packet sent
Sending the Frame
Hàm gửi frame thông qua raw socket ở layer 2:
int send_frame(int if_index, int socket_descriptor, char *dst, Packet frame)
{
/* Low-level target's address information */
struct sockaddr_ll target_address = {
.sll_ifindex = if_index,
.sll_halen = ETH_ALEN,
};
mac_addr(target_address.sll_addr, dst);
/* Send frame */
if (sendto(socket_descriptor, frame.data, frame.len, 0, (struct sockeaddr *)&target_address, sizeof(target_address)) < 0)
{
perror("Error: sendto() failed");
return -1;
}
else
{
printf("Success: spoofed Ethernet frame sent\n");
}
return 0;
}
Tương tự với hàm send_packet()
nhưng ta dùng cấu trúc sockaddr_ll
để lưu thông tin địa chỉ của bên nhận thay vì sockaddr_in
. Ta cần thiết lập 2 thuộc tính sau:
.sll_ifindex
: index của interface gửi gói tin..sll_halen
: độ dài của Ethernet header..sll_addr
: địa chỉ MAC của bên nhận.
Xây dựng frame và gửi như sau:
int main()
{
char *if_name = "br-88ab64b587ba";
char *dst = "10.9.0.5";
char *dst_mac = "02:42:0a:09:00:05";
char *ping_payload = "Hello there!";
/* Open layer-2 raw socket*/
int layer2_socket_descriptor = open_raw_socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP));
/* Get layer 2 interface information */
struct ifreq if_req = initialize_ifreq(if_name);
int if_index = get_if_index(layer2_socket_descriptor, if_req);
printf("Source interface index: %d\n", if_index);
char *src_mac = get_if_mac(layer2_socket_descriptor, if_req);
printf("Source MAC address: %s\n", src_mac);
char *src = get_if_ip(layer2_socket_descriptor, if_req);
printf("Source IP address: %s\n", src);
/* Create ICMP payload */
Packet payload = {
.data = (byte *)ping_payload,
.len = strlen(ping_payload)};
/* Create ping packet */
Packet ping_packet = make_ping_packet(payload);
/* Create ip packet */
Packet ip_packet = make_ip_packet(src, dst, IPPROTO_ICMP, ping_packet);
print_buff(ip_packet);
/* Create ethernet frame */
Packet ethernet_frame = make_ethernet_frame(src_mac, dst_mac, ip_packet);
print_buff(ethernet_frame);
/* Send frame */
send_frame(if_index, layer2_socket_descriptor, dst_mac, ethernet_frame);
}
Có thể thấy, ta bọc gói tin IP ở bên trong một Ethernet frame.
Biên dịch và chạy:
root@docker-desktop:/volumes# ./icmp_spoofer
Source interface index: 5
Source MAC address: 02:42:cc:5a:fd:37
Source IP address: 10.9.0.1
Packet data: 45 00 00 28 27 0f 00 00 40 01 3f af 0a 09 00 01 0a 09 00 05 08 00 95 11 00 00 00 00 48 65 6c 6c 6f 20 74 68 65 72 65 21
Packet length: 40
Packet data: 02 42 0a 09 00 05 02 42 cc 5a fd 37 08 00 45 00 00 28 27 0f 00 00 40 01 3f af 0a 09 00 01 0a 09 00 05 08 00 95 11 00 00 00 00 48 65 6c 6c 6f 20 74 68 65 72 65 21
Packet length: 54
Success: spoofed Ethernet frame sent
Related
list
from outgoing([[Raw Socket Programming]])
sort file.ctime asc