大家好,我们是 NOP Team,今天这篇文章要介绍的是如何处置 ICMP/DNS 隧道,处理思路是一致的,但 icmp 更具有代表性,所以下文均以 icmp 隧道视角讲述
在 2024-05-23 这天,有朋友通过公众号留言,询问了我关于 ICMP 隧道的处置方法,但是后来没聊几句这位朋友就没回复了,可能是解决了吧
昨天有看到,一个朋友在朋友圈发布了一款 ICMP 通信工具,还提到演练将至,给蓝队兄弟上点强度
于是便有了今天这篇文章,当然这是玩笑话,根本原因在于,我看到的网络安全文章中,基本上都在讨论如何发现 ICMP 隧道,感觉要把它拆开了,揉碎了,仔细研究一遍,但对于应急响应人员来说,如何发现意义不大,那是安全设备的功能,我们要考虑的是如何处置,而处置的难点在于:如何找到发出ICMP数据包的进程,这个内容我没有在网络安全文章中看到,反而是搞网络的朋友们可能研究过,这里涉及一个近几年很火的技术 —— BPF
BPF 是 Berkeley Packet Filter 的缩写,它是一种高效的数据包过滤和程序执行框架,最初设计于1992年,用于提高网络数据包的处理性能。起初,它的主要应用是在Unix和Linux系统中作为数据包嗅探工具的一部分,比如tcpdump和Wireshark,用来在内核级别过滤网络数据包,从而减少不必要的数据从内核空间传递到用户空间的过程,提升了效率。
随着时间的发展,BPF的功能得到了极大的扩展,特别是在2013年之后,Alexei Starovoitov和Daniel Borkman等人对BPF进行了重新设计和实现,引入了eBPF(extended Berkeley Packet Filter),使其成为了一个更加通用的内核执行引擎。eBPF不仅限于网络数据包过滤,还可以用于各种内核跟踪、安全监控、网络功能、性能分析等广泛场景。
通过eBPF(extended Berkeley Packet Filter),我们编写程序来获取ICMP请求包与之关联的进程信息,包括但不限于以下几点:
进程ID (PID):可以获取发起ICMP请求的进程的唯一标识符。
进程名称:通常通过PID查找对应的可执行文件名,了解是哪个应用程序发起了ICMP请求。
命令行参数:进一步获取进程启动时使用的完整命令行,有助于识别进程的具体行为或目的。
用户ID和组ID:了解是哪个用户账户运行了发起ICMP请求的进程。
网络连接信息:包括源IP地址、目的IP地址、端口号(尽管ICMP没有端口号,但IP头信息依然重要)等网络层详情。
进程路径:进程的可执行文件在文件系统中的完整路径。
内存使用情况:虽然不是直接从ICMP请求获取,但可以通过eBPF关联到进程上下文后进一步查询。
CPU和时间使用统计:可以收集进程消耗的CPU时间和执行时间的信息。
打开的文件描述符:列出进程当前打开的文件和网络套接字等资源。
由于并没有学过相关知识,所以这里使用 bpftrace 来利用 ebpf 帮助我们完成功能,这是一个高级的动态跟踪工具和域特定语言(Domain Specific Language, DSL),专为Linux系统设计,用于简化和加速系统及应用程序的监控及故障排查过程。它是基于eBPF(Extended Berkeley Packet Filter)技术构建的,允许用户编写脚本以收集内核和用户空间的运行时信息,而无需修改或重启系统
其实核心思路很简单,icmp 协议的数据包没有端口的概念,当然icmp隧道可能会需要监听端口哈,但很难直接通过端口来判断,我们还是聚焦在 icmp 数据包对应的ip上
我们来处理 icmp 隧道,肯定是安全设备有告警了,那么我们需要获取到icmp隧道的对应的ip或者域名,如果是ip,我们后期在利用 ebpf 的过程中,筛选与该ip通信的icmp流量,之后查找pid 就好了
如果是域名,可能会涉及到cdn,我们可以通过修改系统 hosts
文件,将该域名指向到特定的ip,当然是存在的ip,之后进行过滤
首先,我们需要在要运行的服务器上安装 bpftrace
,以 Ubuntu 为例
`sudo apt update sudo apt install bpftrace `
我们使用以下 bfptrace 脚本监控与某个特定IP的所有请求,包括icmp,也包括dns
request_monitor.bt
`#!/usr/bin/bpftrace #include <linux/skbuff.h> #include <linux/ip.h> #include <linux/socket.h> kprobe:__dev_queue_xmit { @dev_queue_xmit[tid]=count(); @skb[tid]=(struct sk_buff *)arg0; } kprobe:__dev_queue_xmit /@skb[tid]/ { $skb = @skb[tid]; $iph = (struct iphdr *)($skb->head + $skb->network_header); $sip = ntop(AF_INET, $iph->saddr); $dip = ntop(AF_INET, $iph->daddr); if ($iph->daddr == $1 || $iph->daddr == $2){ printf("[+] Found the request to %s \n", $dip); printf("[-] pid=%d, thread_id=%d, comm=%s \n", pid,tid,comm); } } `
这里涉及到一个问题,我们是希望查看特定IP的请求,但是直接将IP传递给 bpftrace 脚本比较困难,它不是很好处理,所以这里使用 shell 脚本进行辅助,主要是帮我们把 IP 转化为数字,并且同时转化为大端和小端序,也就是说我们会同时监听大端和小端的地址
request_monitor.sh
`#!/bin/bash convert_ip_to_integers() { local ip=$1 IFS='.' read -r a b c d <<< "$ip" # 计算大端序 (big-endian) be_ip_int=$((a << 24 | b << 16 | c << 8 | d)) # 计算小端序 (little-endian),需要颠倒字节顺序 le_ip_int=$((d << 24 | c << 16 | b << 8 | a)) echo "$be_ip_int $le_ip_int" } # IP地址参数 IP="$1" # 调用函数,获取大端和小端的整数表示 read big_endian little_endian <<< $(convert_ip_to_integers "$IP") # 假设BPFtrace脚本期望两个参数,分别对应大端和小端 echo "Start listening for the request to $IP" echo "" sudo ./request_monitor.bt $big_endian $little_endian `
脚本参考
使用 pingtunnel 工具来模拟攻击者搭建的 icmp 隧道,服务端(攻击者)地址为 192.168.31.83
`wget https://github.com/esrrhs/pingtunnel/releases/download/2.8/pingtunnel_linux_amd64.zip unzip pingtunnel_linux_amd64.zip # 启动服务端,设置 key 为 1234 ./pingtunnel -type server -key 1234 `
`wget https://github.com/esrrhs/pingtunnel/releases/download/2.8/pingtunnel_linux_amd64.zip unzip pingtunnel_linux_amd64.zip # 连接服务端 sudo ./pingtunnel -type client -l :4445 -s 192.168.31.83 -t 192.168.31.83:4444 -tcp 1 -key 1234 `
根据安全设备告警,得知存在 ICMP 隧道,连接地址为 192.168.31.83
通过 netstat
进行查看
果然看不到
因为当前服务器已经默认存在 bpftrace
,所以这里就先不重复安装了,将 request_monitor.sh
和 request_monitor.bt
复制到受害服务器上,给两个脚本赋予执行权限
`chmod +x request_monitor.sh chmod +x request_monitor.bt `
之后执行以下命令
`sudo ./request_monitor.sh 192.168.31.83 `
等待几秒后,开始监听
可以看到,发现了 pid 为 14990 的进程存在与 192.168.31.83 的通信,而且是多线程的,我们使用 ps -aux
验证一下是不是这样
可以看到, 14990 进程确实为 icmp 隧道的进程,成功找到icmp 隧道
发现进程id后剩下就没什么可说的了,常规处置流程了,至于DNS隧道就不掩饰了,大同小异
如果文中不好复制,可以通过以下 Github 地址获取
有态度,不苟同