Fscan简介和基础结构

fscan是用Go语言编写的端口扫描工具,它具有高效、快速的特点,能够对目标主机的端口进行扫描,并识别开放的服务等信息。在内网探测的时候效果挺好,很多攻击队在打点突破拿到边界机之后都会喜欢上传fscan、frp等工具来进行内网探测,辅助横向移动进行下一步攻击

现在fscan已经重构生成了2.0版本,但是因为文章写慢了,刚开始 写的时候用的是1.x的版本,2.0也没细看,所以先分析1.x版本,后面有时间在写2.0(都鸽们,怎么会骗你呢),本文主要从fscan的源码入手,分析fscan的原理,并简单介绍如何进行fscan的免杀和魔改,如果没有go语言基础的同学建议学习一下

1代的最新版本是1.8.4,可在以下链接下载

https://github.com/shadow1ng/fscan/archive/refs/tags/1.8.4.zip

基本目录结构如下

image-20250309150606982

Fscan ├─common ├─image ├─Plugins ├─WebScan ├─go.mod ├─go.sum ├─main.go ├─README.md └─LICENSE.txt

common是基础模块,包含了如启动参数解析、地址解析、日志记录、代理、基础配置等功能的实现源码

image是readme的图片存储目录

Plugins是插件目录,主要功能为实现ftp、rdp、mysql等服务的探测以及弱口令的检测

webscan是web扫描实现部分,用于使用内置的poc进行漏洞扫描

gomd和gosum就不说了

main.go是主方法,程序入口点

代码分析

从main.go入手分析

image-20250309164635838

start := time.Now()记录了程序开始的时间,用于后续计算程序运行的总耗时

flag是打印logo和参数的模块,从common中能直接看到相关代码

image-20250309165131907

顺便介绍一下参数

参数名 参数类型 默认值 描述
-h string "" 要扫描的主机IP地址,例如: 192.168.11.11
-hn string "" 不扫描的主机,例如: -hn 192.168.1.1/24
-p string 默认端口 指定端口,例如: 22
-pa string "" 基于DefaultPorts添加端口,-pa 3389
-usera string "" 基于DefaultUsers添加用户,-usera user
-pwda string "" 基于DefaultPasses添加密码,-pwda password
-pn string "" 不扫描的端口,例如: -pn 445
-c string "" 执行命令 (ssh和wmi)
-sshkey string "" ssh密钥文件 (id_rsa)
-domain string "" smb域
-user string "" 用户名
-pwd string "" 密码
-time int64 3 设置超时时间
-m string “all” 选择扫描类型,例如: -m ssh
-path string "" fcgi、smb远程文件路径
-t int 600 线程数
-top int 10 显示存活数量前几位
-hf string "" 主机文件,-hf ip.txt
-userf string "" 用户名文件
-pwdf string "" 密码文件
-portf string "" 端口文件
-pocpath string "" poc文件路径
-rf string "" 写入sshkey文件的redis文件 (例如: -rf id_rsa.pub)
-rs string "" 写入计划任务文件的redis shell (例如: -rs 192.168.1.1:6666)
-nopoc bool false 不扫描web漏洞
-nobr bool false 不暴力破解密码
-br int 1 暴力破解线程数
-np bool false 不进行ping扫描
-ping bool false 使用ping替代icmp
-o string “result.txt” 输出文件
-no bool false 不保存输出日志
-debug int64 60 每次记录错误日志的时间间隔
-silent bool false 静默扫描
-nocolor bool false 不使用颜色输出
-full bool false poc全面扫描,例如: shiro 100 key
-u string "" url
-uf string "" url文件
-pocname string "" 使用包含pocname的poc,-pocname weblogic
-proxy string "" 设置poc代理,-proxy http://127.0.0.1:8080
-socks5 string "" 设置socks5代理,将在tcp连接中使用,超时设置将不生效
-cookie string "" 设置poc cookie,-cookie rememberMe=login
-wt int64 5 设置web超时时间
-dns bool false 使用dnslog poc
-num int 20 poc速率
-sc string "" ms17 shellcode,例如 -sc add
-wmi bool false 开启wmi
-hash string "" hash
-noredis bool false 不进行redis安全测试
-json bool false json格式输出

Scanner实现

Parse.go文件用于解析url、密码字典路径、指定用户名等等参数的相关内容,不展开了,感兴趣的可以自己去看一下

核心是Plugin.scan中的内容,在Plugin/scanner.go中,定义了Scan方法,在代码中可以看到定义了ch和wg两个参数控制多线程,并通过icmp进行主机识别

image-20250312234809115

icmp主机识别的主要逻辑在同级目录的icmp.go中实现,根据注释可以看到优先尝试监听本地icmp进行批量探测,如果报错再尝试无监听icmp探测

image-20250314004411762

构造的ICMP消息部分为makemsg,感觉也能作为fscan的特征(这个还没测试过,后面看情况分析

image-20250314004215349

完成主机识别后基于插件进行端口识别,端口识别的实现通过Plugins/portscan.go,也是用了多线程扫描的方式提升效率

image-20250315093029165

根据上下文中的PortConnect函数,其实现扫描的方式是TCP连接扫描端口

image-20250315093334014

检测完存活主机和端口扫描后,进入正式扫描流程,分析Scanner的其他部分,主要是AddScan和ScanFunc函数,通过反射调用的方式加载了扫描相关插件

image-20250315102546762

扫描插件的定义在base.go中,其中的PluginList定义了插件,相关插件在Plugins目录下对应的go文件中,用于实现对应功能的扫描,如果要进行二开,增加插件等操作时也需要编写go脚本,并在插件列表中进行声明

image-20250315112622027

选一个简单的模块进行分析,本文使用了postgres.go,这个模块用于实现potgresql的扫描,其实就是单独实现的漏洞检测逻辑,先从端口中匹配到5432端口,然后就调用该模块进行扫描,并检测弱口令

image-20250315142349566

除了Plugins中的检测模块外,还有自带的webscan用于web漏洞探测,webscan模块在根目录的WebScan目录下实现,在插件的webtitle.go中进行调用

image-20250315143545544

在该模块中,使用initpoc方法初始化poc,filterpoc方法解析poc,execute方法定义动作,webscan方法执行

image-20250315144053531

在webtitle中,还有一个很有意思的地方,最后定一了一个GetProtocol方法,这个方法用于在端口为非常规web端口(80和443)时,通过该方法区判断协议并进行tls连接,如果使用go开发其他工具时可能会提供一些思路

image-20250323112721999

其中对发包字段中的UA、Accept、Cookie等字段都在common/config.go中进行定义和修改,其中UA和Accept字段是硬编码的,建议修改来规避特征检测

image-20250315144249475

websacn中还有一个infoscan模块,infoscan用于指纹识别,在InfoCheck方法中,通过info.RuleDatas进行正则匹配,判断对应的应用指纹

image-20250315150323220

具体的应用指纹信息在WebScan/info/reles.go中进行了定义

image-20250315150650470

再找一个poc进行分析,这里选用一个nacos的未授权访问漏洞作为模板

image-20250322151901468

首先在name部分定义poc的显示名,匹配到对应漏洞后fscan会显示输出这个名称

set部分为参数,此处的r1和r2两个参数,使用随机的16位小写字符

rules部分为检测规则,method定义了请求方式,常规的HTTP请求如POST、GET、DELETE等;path定义请求路径;headers定义请求头,能包含UA、Accept等字段、expression为表达式,和pocsuite有点类似,通过响应码和页面内容判定是否存在漏洞

编译只需在根目录下执行如下命令,这个命令通过对ldflags和trimpath的参数的使用,省略符号表和调试信息、DWARF调试信息、去除文件路径信息等操作,让编译出来的文件相对特征没那么明显

go build -ldflags="-s -w " -trimpath main.go

但是仍然无情被杀,刚执行就被火绒直接识别出并且干掉了fscan,一会在讲二开的时候会讲到

image-20250315153301760

流量分析

首先进行-h参数的自动扫描,然后用wireshark抓包,能确认是TCP全连接扫描

image-20250315160607256

在端口探测完成后,fscan默认会针对已经识别到的服务进行扫描,如mysql,不断尝试进行登录(弱口令爆破),箭头处为登录请求,方框为响应,在fscan中,爆破检测到弱口令就会进行输出

image-20250319005602344

此外,在上文中分析的源码部分也能发现ICMP Echo请求无载荷,超高请求速率+无载荷Echo的特征就很明显了

在检测到开放端口后会进入poc检测环节,此时数据包中会出现大量的http请求,是各种各种的路径

image-20250322153654307

免杀

前文写到了按照官方推荐的编译参数一编译就被杀,先来分析一下特征,最常见的特征就是字符串,搜索一下能出现大量包含字符串

image-20250322162818551

如果考虑便捷性,可以直接用garble进行编译

garble -tiny -literals -seed=random build -ldflags="-w -s" main.go

编译后重新使用strings命令时就几乎没有什么字段了,但是这边还出现了一些别的奇怪的字段,也算是泄露了特征

image-20250323111546555

但是火绒已经过了

image-20250323115113685

从源码上也进行一些修改,在源码中搜索fscan字符串时代码中有37处关键词,主要都是引入库的问题github.com/shadow1ng/fscan

image-20250323113149245

首先在gopath的src目录下下新建一个目录用于存放代码文件,然后将common、Plugins、WebScan三个文件夹移动到对应目录,再将所有代码中的

github.com/shadow1ng/fscan统一替换为刚刚新建的目录文件夹名称(不用写全,只要文件夹名字就行了),进一步减少特征,重新编译后即没有相关字符串了

image-20250323114859406

某数字也能过

image-20250323115353250

上了VT也是效果拔群,就瑞星还是认出来了fscan,其他几个特征也不是很明显

image-20250323122913639

二开——随机UA

前面核心代码已经分析了,可以来考虑二开了,二开部分先实现一个简单功能,别的看看以后再写吧,首先定位到config.go文件,看到其中关于UA的定义部分,此时的UA是定值

image-20250327232026415

新功能准备使用随机的UA,将UA定义为一个列表,每次请求时从列表中获取UA,并将随机值赋给UA即可

var RandUAlist = []string{"UA列表"}

func getrandUA() string {
	rand.Seed(time.Now().UnixNano())
	index := rand.Intn(len(RandUAlist))
	return RandUAlist[index]
}

var UserAgent  = getrandUA()

image-20250327233043196

重新进行编译,为了方便观察(主要是懒得再用wireshark抓了),重新启动时的参数将代理地址设置为BP的监听端口

image-20250327232411338

再进行测试时可以看到UA头已经发生了改变

image-20250327233212015

同理,如果觉得发包速度太快了可能容易被发现的话,也可以对PocNum和Thread参数进行随机化,可以控制每次的发包速率和线程等