Linux raw socket的总结
介绍相关结构体和常量, 最后实现ICMP接收和响应
创建套接字
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
创建一个IPV4 ICMP原始套接字(IPV4 ICMP raw socket)AF_INET
: 代表IPV4协议SOCK_RAW
: 代表原始套接字IPPROTO_ICMP
: 代表ICMP协议
禁用内核自动附加IP头
int on = 1;
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));
启用IP_HDRINCL选项, 数据包含IP数据头, 发送数据时需要自己构建IP数据头, 内核不再生成
接收数据
char* buf = calloc(1, IPPROTO_MAX);
struct sockaddr in;
socklen_t in_len = sizeof(in);
recvfrom(sock, buf, IPPROTO_MAX, 0, &in, &in_len);
将数据写入buf
, 发送方地址将保存在in
变量
解析数据段
struct iphdr *ip = (struct iphdr*)buf;
struct icmphdr *icmp = (struct icmphdr*)((char*)ip + 4 * ip->ihl);
printf("data: %s\n", (char*)icmp + 8);
iphdr是ip header的缩写, 表示一个IP数据头结构
IHL(Internet Header Length): IP数据头是不定长的, 所以需要IHL记录IP数据头大小, 以4字节为单位, 所以IP数据头大小等于4 * IHL bytes
Total Length: IP数据包总大小, 包含数据头和数据, 用于计算IP数据段大小:
Total Length - 4 * IHL bytes
Protocol: 协议标识, IP协议主要用于寻址路由, 一般会包含其他协议进行数据交换, 所以这个字段用来标识IP数据段是什么协议, 内核有一组常量定义已知协议
Options: 这个字段保留用作自定义协议, 可配合Protocol字段开发自定义协议
字段详解: https://en.wikipedia.org/wiki/Internet_Protocol_version_4#Header
icmphdr是icmp header的缩写, 代表一个ICMP数据头结构
Type: 定义了一组常量
常量细节及描述: https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#Control_messages
Code: 这个字段在Echo类型中总是0(回复和请求)
字段详解: https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol#Header
所以一个IP数据包, 首先把IP头和IP数据段分开
IP数据段是一个ICMP包
一个ICMP数据包又包含ICMP头和ICMP数据段
就这样层层解析
完整代码
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
uint16_t in_cksum(uint16_t *addr, int len)
{
int nleft = len;
uint32_t sum = 0;
uint16_t *w = addr;
uint16_t answer = 0;
// Adding 16 bits sequentially in sum
while (nleft > 1) {
sum += *w;
nleft -= 2;
w++;
}
// If an odd byte is left
if (nleft == 1) {
*(unsigned char *) (&answer) = *(unsigned char *) w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
answer = ~sum;
return answer;
}
int main() {
char* buf = calloc(1, IPPROTO_MAX);
struct sockaddr_in in;
socklen_t in_len = sizeof(in);
// int on = 1;
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock < 0) {
goto error;
}
// /* Set that header is included with data, kernel do not generate the header */
// if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) == -1){
// goto error;
// }
ssize_t n = recvfrom(sock, buf, IPPROTO_MAX, 0, (struct sockaddr*)&in, &in_len);
if (n == -1){
goto error;
}
struct iphdr *ip = (struct iphdr*)buf;
struct icmphdr *icmp = (struct icmphdr*)((char*)ip + 4 * ip->ihl);
uint16_t dataSize = ntohs(ip->tot_len) - 4 * ip->ihl - 8;
icmp->type = ICMP_ECHOREPLY;
icmp->checksum = 0;
icmp->checksum = in_cksum((unsigned short *)icmp, sizeof(struct icmphdr) + dataSize);
sendto(sock, icmp, sizeof(struct icmphdr) + dataSize, 0, (struct sockaddr*)&in, in_len);
goto success;
error:
free(buf);
buf = NULL;
close(sock);
printf("error: %s\n", strerror(errno));
exit(EXIT_FAILURE);
success:
free(buf);
buf = NULL;
close(sock);
return 0;
}
需要高权限执行, 而且还需要关闭内核的ICMP回复功能
否则会出现异常情况(一个请求两个回答)
禁用内核ICMP回复
临时禁用:
echo "1" > /proc/sys/net/ipv4/icmp_echo_ignore_all
永久禁用:
在/etc/sysctl.conf
里添加下面这一行
net.ipv4.icmp_echo_ignore_all=1
更新设置:
sysctl -p
思考
- IP协议支持自定义协议, 木马是否可以使用自定义协议在网络层(Internet layer)通信
- IP数据包里的源地址伪造, 是否可以让我们匿名发送数据包(只不过收不到回复)
- IP数据包源地址伪造后, 是否可以控制目标地址向伪造源地址发送数据包
思考结果
IP协议支持自定义协议, 木马是否可以使用自定义协议在网络层(Internet layer)通信 理论可以, 未来做一个POC
IP数据包里的源地址伪造, 是否可以让我们匿名发送数据包(只不过收不到回复) 可以, 除去物理层以及链路层, 接下来就是网络层了, 所以网络层数据包伪造可以在网络层匿名发送数据包, 因为数据包能否发送成功无所谓源地址.
实验开始:
构造IPV4数据包, 发送一个ICMP请求, 伪造源地址为本地(127.0.0.1) 可以看到, 源地址已经被伪造了, 但是由于是局域网, 所以以太网头会有我们的源MAC地址 但是在实际网络中, 我们的MAC地址不会泄露出去(数据包会多次中转) 伪造数据包代码:
use std::net::SocketAddr;
use etherparse::{IcmpEchoHeader, Icmpv4Type, PacketBuilder};
use socket2::{Domain, Protocol, SockAddr, Socket, Type};
fn main() {
let mut packet_eth = Vec::<u8>::with_capacity(4096);
let payload = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let addr:SocketAddr = "192.168.20.174:80".parse().unwrap();
let addr = SockAddr::from(addr);
let conn = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))
.unwrap();
conn.set_header_included(true).unwrap();
let packet = PacketBuilder::
ipv4(
[127,0,0,1],
[192,168,20,174],
64)
.icmpv4(
Icmpv4Type::EchoRequest(
IcmpEchoHeader{ id: 1, seq: 1}));
packet.write(&mut packet_eth, payload.as_bytes()).unwrap();
let n = conn.send_to(packet_eth.as_slice(), &addr).unwrap();
println!("sent {n} bytes.");
}
伪造源地址一个作用是匿名: 攻击者只需要发送, 不需要收到回复的场景
思考: 是否可以绕过IP封锁, 或者防火墙?
- IP数据包源地址伪造后, 是否可以控制目标地址向伪造源地址发送数据包
可以, 刚刚将源地址伪造成127.0.0.1后, 系统并没有向127.0.0.1发送ICMP回复数据包, 原因不明, 但是如果将源地址伪造成其他局域网地址呢?
我的实验系统是VMWare虚拟机, 一台Windows, 一台Ubuntu
IP 192.168.20.1是物理机上的一块虚拟网卡, 我们尝试伪造源地址使其向物理机的虚拟网卡发送ICMP回复数据包 这个也许可以被用做DoS, 发送一个数据包会回复很大数据包的场景