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
基本目录结构如下
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入手分析
start := time.Now()记录了程序开始的时间,用于后续计算程序运行的总耗时
flag是打印logo和参数的模块,从common中能直接看到相关代码
顺便介绍一下参数
参数名 |
参数类型 |
默认值 |
描述 |
---|---|---|---|
-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进行主机识别
icmp主机识别的主要逻辑在同级目录的icmp.go中实现,根据注释可以看到优先尝试监听本地icmp进行批量探测,如果报错再尝试无监听icmp探测
构造的ICMP消息部分为makemsg,感觉也能作为fscan的特征()
完成主机识别后基于插件进行端口识别,端口识别的实现通过Plugins/portscan.go,也是用了多线程扫描的方式提升效率
根据上下文中的PortConnect函数,其实现扫描的方式是TCP连接扫描端口
检测完存活主机和端口扫描后,进入正式扫描流程,分析Scanner的其他部分,主要是AddScan和ScanFunc函数,通过反射调用的方式加载了扫描相关插件
扫描插件的定义在base.go中,其中的PluginList定义了插件,相关插件在Plugins目录下对应的go文件中,用于实现对应功能的扫描,如果要进行二开,增加插件等操作时也需要编写go脚本,并在插件列表中进行声明
选一个简单的模块进行分析,本文使用了postgres.go,这个模块用于实现potgresql的扫描,其实就是单独实现的漏洞检测逻辑,先从端口中匹配到5432端口,然后就调用该模块进行扫描,并检测弱口令
除了Plugins中的检测模块外,还有自带的webscan用于web漏洞探测,webscan模块在根目录的WebScan目录下实现,在插件的webtitle.go中进行调用
在该模块中,使用initpoc方法初始化poc,filterpoc方法解析poc,execute方法定义动作,webscan方法执行
在webtitle中,还有一个很有意思的地方,最后定一了一个GetProtocol方法,这个方法用于在端口为非常规web端口(80和443)时,通过该方法区判断协议并进行tls连接,如果使用go开发其他工具时可能会提供一些思路
其中对发包字段中的UA、Accept、Cookie等字段都在common/config.go中进行定义和修改,其中UA和Accept字段是硬编码的,建议修改来规避特征检测
websacn中还有一个infoscan模块,infoscan用于指纹识别,在InfoCheck方法中,通过info.RuleDatas进行正则匹配,判断对应的应用指纹
具体的应用指纹信息在WebScan/info/reles.go中进行了定义
再找一个poc进行分析,这里选用一个nacos的未授权访问漏洞作为模板
首先在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,一会在讲二开的时候会讲到
流量分析
首先进行-h参数的自动扫描,然后用wireshark抓包,能确认是TCP全连接扫描
在端口探测完成后,fscan默认会针对已经识别到的服务进行扫描,如mysql,不断尝试进行登录(弱口令爆破),箭头处为登录请求,方框为响应,在fscan中,爆破检测到弱口令就会进行输出
此外,在上文中分析的源码部分也能发现ICMP Echo请求无载荷,超高请求速率+无载荷Echo的特征就很明显了
在检测到开放端口后会进入poc检测环节,此时数据包中会出现大量的http请求,是各种各种的路径
免杀
前文写到了按照官方推荐的编译参数一编译就被杀,先来分析一下特征,最常见的特征就是字符串,搜索一下能出现大量包含字符串
如果考虑便捷性,可以直接用garble进行编译
garble -tiny -literals -seed=random build -ldflags="-w -s" main.go
编译后重新使用strings命令时就几乎没有什么字段了,但是这边还出现了一些别的奇怪的字段,也算是泄露了特征
但是火绒已经过了
从源码上也进行一些修改,在源码中搜索fscan字符串时代码中有37处关键词,主要都是引入库的问题github.com/shadow1ng/fscan
首先在gopath的src目录下下新建一个目录用于存放代码文件,然后将common、Plugins、WebScan三个文件夹移动到对应目录,再将所有代码中的
github.com/shadow1ng/fscan统一替换为刚刚新建的目录文件夹名称(不用写全,只要文件夹名字就行了),进一步减少特征,重新编译后即没有相关字符串了
某数字也能过
上了VT也是效果拔群,就瑞星还是认出来了fscan,其他几个特征也不是很明显
二开——随机UA
前面核心代码已经分析了,可以来考虑二开了,二开部分先实现一个简单功能,别的看看以后再写吧,首先定位到config.go文件,看到其中关于UA的定义部分,此时的UA是定值
新功能准备使用随机的UA,将UA定义为一个列表,每次请求时从列表中获取UA,并将随机值赋给UA即可
var RandUAlist = []string{"UA列表"}
funcgetrandUA() string {
rand.Seed(time.Now().UnixNano())
index := rand.Intn(len(RandUAlist))
return RandUAlist[index]
}
var UserAgent = getrandUA()
重新进行编译,为了方便观察(主要是懒得再用wireshark抓了),重新启动时的参数将代理地址设置为BP的监听端口
再进行测试时可以看到UA头已经发生了改变
同理,如果觉得发包速度太快了可能容易被发现的话,也可以对PocNum和Thread参数进行随机化,可以控制每次的发包速率和线程等
扫描二维码
获取更多精彩
FS_Sec Lab
原文始发于微信公众号(魔影安全实验室):红队基础设施建设与改造(五)——Fscan从代码逻辑到二开
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论