前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Fscan源码解读

Fscan源码解读

作者头像
TomatoCool
发布于 2023-09-17 01:09:46
发布于 2023-09-17 01:09:46
70000
代码可运行
举报
文章被收录于专栏:TomatoCoolTomatoCool
运行总次数:0
代码可运行

入口函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func main() {
    start := time.Now()
    var Info common.HostInfo
    common.Flag(&Info)
    common.Parse(&Info)
    Plugins.Scan(Info)
    t := time.Now().Sub(start)
    fmt.Printf("[*] 扫描结束,耗时: %s\n", t)
}
  • common.Flag:从命令行获取输入的参数,并根据参数准备程序运行的方式
  • common.Parse:解析输入的内容,如从文件中读取主机,将主机范围转化为主机切片
  • Plugins.Scan:开始进行扫描

Parse

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func Parse(Info *HostInfo) {
    ParseUser()
    ParsePass(Info)
    ParseInput(Info)
    ParseScantype(Info)
}

Parse函数会解析用户的输入,得到运行时所需要的目标,参数等。

ParseUser

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func ParseUser()

从全局变量或取用户输入的username和userFile,将输入的用户名和文件中的用户名合并,再去重,最后把得到的用户名切片返回到全局变量Userdict[name]中。

Userdict保存了常见用户名信息,例如Userdict['mysql']的内容为{"root", "mysql"}

ParsePass

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func ParsePass(Info *HostInfo)

从全局变量获取用户输入的password和passFile,将输入的密码和文件中的密码合并,再去重,最后把得到的密码切片返回到全局变量Passwords中。

Passwords是一个字符串切片,用来保存密码信息。

ParsePass还对URLURLFilePortFile这三个变量进行解析,将信息返回到Urls和Info.Ports中。

这里是否有些不严谨?

ParseInput

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func ParseInput(Info *HostInfo)

初始化Info.Ports,添加web常见端口和host常见端口,将用户额外输入的PortAddUserAddPassAdd分别添加到对应的全局变量中。如果用户指定了代理,会将代理也添加到对应的全局变量中。

ParseScantype

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func ParseScantype(Info *HostInfo)

处理用户输入的Scantype,根据扫描类型指定要扫描的端口,并赋值给Info.Ports

使用switch对Info.Ports进行赋值,是否可以改为对Info.Ports添加端口,实现同时指定多种扫描类型?

Scan

获取主机切片

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Hosts, err := common.ParseIP(info.Host, common.HostFile, common.NoHosts)
if err != nil {
    fmt.Println("len(hosts)==0", err)
    return
}

ParseIP函数会将info.Hostcommon.HostFile中的字符串解析成单个的主机字符串,并放到一个切片中,同时将common.NoHosts中的主机排除,最后返回一个包含目标主机的切片。

存活主机扫描

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if common.NoPing == false && len(Hosts) > 0 {
    Hosts = CheckLive(Hosts, common.Ping)
    fmt.Println("[*] Icmp alive hosts len is:", len(Hosts))
}
if common.Scantype == "icmp" {
    common.LogWG.Wait()
    return
}
common.GC()

CheckLive函数(源码解读在icmp.go中)会返回包含存活主机的切片,common.Ping是一个布尔值,表示是否进行ping扫描。LogWG.Wait()会等待CheckLive函数中的goroutine执行完毕后,再执行后面的代码,因为里面有Add和Done操作。执行完毕后进行一次垃圾回收。

common.GC()是封装后的GC函数。

开放端口扫描

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var AlivePorts []string
if common.Scantype == "webonly" || common.Scantype == "webpoc" {
    AlivePorts = NoPortScan(Hosts, info.Ports)
} else if common.Scantype == "hostname" {
    info.Ports = "139"
    AlivePorts = NoPortScan(Hosts, info.Ports)
} else if len(Hosts) > 0 {
    AlivePorts = PortScan(Hosts, info.Ports, common.Timeout)
    fmt.Println("[*] alive ports len is:", len(AlivePorts))
    if common.Scantype == "portscan" {
        common.LogWG.Wait()
        return
    }
}
if len(common.HostPort) > 0 {
    AlivePorts = append(AlivePorts, common.HostPort...)
    AlivePorts = common.RemoveDuplicate(AlivePorts)
    common.HostPort = nil
    fmt.Println("[*] AlivePorts len is:", len(AlivePorts))
}
common.GC()

首先判断扫描类型,如果是web扫描或者主机名扫描,则调用NoPortScan扫描info.Ports中的端口,此时info.Ports中的端口都是指定端口。其它情况则调用PortScan函数(源码解读在portscan.go中)扫描存活的端口。LogWG.Wait()会等待PortScan函数中的goroutine执行完毕后,再执行后面的代码。最后加上HostPort中的"host:ip",去重后得到完整的AlivePorts。将HostPort的值设为空,再进行垃圾回收,释放内存。

暂时不清楚为什么要加上HostPort中的"host:ip",唯一确定的是ParseIP.go中对HostPort进行了操作,然而只是从文件中读取了"host:port",并没有进行扫描操作。(是否默认用户输入的"host:port"一定存在?)

漏洞扫描

存活主机扫描与开放端口扫描后,会对常见的漏洞进行扫描,如ms17010,ve20200796等,通过AddScan函数针对这些漏洞的端口进行扫描,暂时不做过多的分析。

AddScan函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func AddScan(scantype string, info common.HostInfo, ch *chan struct{}, wg *sync.WaitGroup) {
    *ch <- struct{}{}
    wg.Add(1)
    go func() {
        Mutex.Lock()
        common.Num += 1
        Mutex.Unlock()
        ScanFunc(&scantype, &info)
        Mutex.Lock()
        common.End += 1
        Mutex.Unlock()
        wg.Done()
        <-*ch
    }()
}

参数说明:

  • scantype:扫描的类型
  • info:主机信息
  • ch:用于计数的管道
  • wg:等待组

info参数实际上是通过for循环反复对Scan函数中的info变量赋值而产生的。每次循环都对info.Hostinfo.Ports重新赋值,再传入到AddScan中。

Scan函数部分代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
for _, targetIP := range AlivePorts {
    info.Host, info.Ports = strings.Split(targetIP, ":")[0], strings.Split(targetIP, ":")[1]
    ...
    //  info.Ports实际上是scantype
    AddScan(info.Ports, info, &ch, &wg)

AddScan函数只在Scan函数中的漏洞扫描过程中调用,并且Scan函数通过多次调用AddScan函数会开启多个goroutine执行ScanFunc函数,由于是并发执行程序,因此需要共用同一个等待组,当所有AddScan函数中的ScanFunc函数执行完毕后,Scan函数才会继续执行。AddScan函数中的NumEnd是用来计数的,分别表示已开启的任务数量和已完成的任务数量。Mutex是互斥锁,防止多个goroutine同时对NumEnd操作时产生冲突导致结果不正确。

对单个参数进行操作,是否可以改用原子锁提高性能?

ScanFunc函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func ScanFunc(name *string, info *common.HostInfo) {
    f := reflect.ValueOf(PluginList[*name])
    in := []reflect.Value{reflect.ValueOf(info)}
    f.Call(in)
}

AddScan函数会调用ScanFunc函数,并传入name参数(AddScan中的scantype),通过反射从PluginList中取出name对应的函数名。同理将info转化成对应的参数,供f调用。

IsContain函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
func IsContain(items []string, item string) bool {
    for _, eachItem := range items {
        if eachItem == item {
            return true
        }
    }
    return false
}

判断元素是否在切片中。

icmp.go

CheckLive函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
chanHosts := make(chan string, len(hostslist))

chanHosts用来接收存活的主机地址。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
go func() {
    for ip := range chanHosts {
        if _, ok := ExistHosts[ip]; !ok && IsContain(hostslist, ip) {
            ExistHosts[ip] = struct{}{}
            if common.Silent == false {
                if Ping == false {
                    fmt.Printf("(icmp) Target %-15s is alive\n", ip)
                } else {
                    fmt.Printf("(ping) Target %-15s is alive\n", ip)
                }
            }
            AliveHosts = append(AliveHosts, ip)
        }
        livewg.Done()
    }
}()

接下来开启一个goroutine,从chanHosts中读取存活的主机并输出。

ExistHosts[ip]用来判断主机是否被输出过,如果没有,则添加一个ExistHosts[ip],再将ip进行输出。IsContain判断ip是否在目标范围(即hostslist)内(一般不会出现目标外的ip)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if Ping == true {
    RunPing(hostslist, chanHosts)
}

根据全局变量Ping判断是否用RunPing函数探测。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
else {
    //优先尝试监听本地icmp,批量探测
    conn, err := icmp.ListenPacket("ip4:icmp", "0.0.0.0")
    if err == nil {
        RunIcmp1(hostslist, conn, chanHosts)
    } else {
        common.LogError(err)
        //尝试无监听icmp探测
        fmt.Println("trying RunIcmp2")
        conn, err := net.DialTimeout("ip4:icmp", "127.0.0.1", 3*time.Second)
        defer func() {
            if conn != nil {
                conn.Close()
            }
        }()
        if err == nil {
            RunIcmp2(hostslist, chanHosts)
        } else {
            common.LogError(err)
            //使用ping探测
            fmt.Println("The current user permissions unable to send icmp packets")
            fmt.Println("start ping")
            RunPing(hostslist, chanHosts)
        }
    }
}

函数解析:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//  监听本地网络地址laddr,网络类型net必须是面向数据包的网络类型
func ListenPacket(net, laddr string) (PacketConn, error)
//  在网络network上连接地址address,并返回一个Conn接口。
func Dial(network, address string) (Conn, error)
//  同Dial函数,增加了超时
func DialTimeout(network, address string, timeout time.Duration) (Conn, error)

如果Ping为false,则使用icmp检测。

  1. 首先建立一个conn监听本地icmp网络地址,如果建立成功,则运行RunIcmp1函数
  2. 如果建立失败,则进行无监听icmp探测,会建立一个连接conn连接本地回环地址,如果建立成功,则运行RunIcmp2函数
  3. 如果还建立失败,就使用Ping方法进行检测

使用defer关键字,在程序结束时自动关闭conn。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
livewg.Wait()
close(chanHosts)

RunPingRunIcmp1RunIcmp2函数中,如果有存活主机,会对livewg执行Add操作;在前面接收结果的goroutine中,成功接收结果会执行Done操作,当所有结果接收完时,等待组livewg才会释放,然后关闭结果管道。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if len(hostslist) > 1000 {
    arrTop, arrLen := ArrayCountValueTop(AliveHosts, common.LiveTop, true)
    for i := 0; i < len(arrTop); i++ {
        output := fmt.Sprintf("[*] LiveTop %-16s 段存活数量为: %d", arrTop[i]+".0.0/16", arrLen[i])
        common.LogSuccess(output)
    }
}
if len(hostslist) > 256 {
    arrTop, arrLen := ArrayCountValueTop(AliveHosts, common.LiveTop, false)
    for i := 0; i < len(arrTop); i++ {
        output := fmt.Sprintf("[*] LiveTop %-16s 段存活数量为: %d", arrTop[i]+".0/24", arrLen[i])
        common.LogSuccess(output)
    }
}

return AliveHosts

最后根据主机数量确定输出类型,即根据B段或C段进行分类。再返回包含存活主机的切片。

代码重复有点多...

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Go编写工具教程第二课 高并发主机发现
这是Go编写工具教程第二课:高并发主机发现,本文持续连载在【狼组安全平台】plat.wgpsec.org
WgpSec
2021/02/04
1.4K1
Go编写工具教程第二课 高并发主机发现
Go编写工具教程第一课 高并发端口扫描
今天我们一起来学习下如何用GO 编写一个高并发端口扫描工具,本教学文章持续连载,后面会接连着实现主机发现,漏洞探测,远程执行,暴力破解等等的教学,有兴趣的师傅可关注公众号回复加群一起讨论~
WgpSec
2021/02/04
2.5K0
Go编写工具教程第一课 高并发端口扫描
Go实现TCP端口扫描器
从端口管道中读取端口后与主机拼接成完整地址,使用net.Dial测试TCP连接是否成功,并将结果发送到results管道,失败则返回0,成功则返回端口。
TomatoCool
2023/08/26
2250
Golang实现ping
ICMP部分的结构 报头 ICMP报头从IP报头的第160位开始,即第20个字节开始(除非使用了IP报头的可选部分)。 Bits 160-167 168-175 176-183 184-191 160TypeCode校验码(checksum)192ID序号(sequence) Type - ICMP的类型,标识生成的错误报文; Code - 进一步划分ICMP的类型,该字段用来查找产生错误的原因.;例如,ICMP的目标不可达类型可以把这个位设为1至15等来表示不同的意思。 Checksum - 校
李海彬
2018/03/20
2.6K0
golang 基础编程
map 是一种无序的键值对, 它是数据结构 hash 表的一种实现方式,类似 Python 中的字典
全栈程序员站长
2022/07/19
1.3K0
golang 基础编程
用Go实现Ping操作
这次我们来看一下什么是 Ping 操作,以及它有什么用处,并且我们来动手实现一个简易版的 Ping 工具。
用户6256742
2024/05/30
2740
用Go实现Ping操作
这次我们来看一下什么是 Ping 操作,以及它有什么用处,并且我们来动手实现一个简易版的 Ping 工具。
程序猿川子
2024/06/08
1490
用Go实现Ping操作
Golang实现ping
在使用Go语言的net.Dial函数时,发送echo request报文时,不用考虑i前20个字节的ip头;但是在接收到echo response消息时,前20字节是ip头。后面的内容才是icmp的内容,应该与echo request的内容一致。
李海彬
2018/07/26
1.3K0
手写的 Go ping 实现
Michel_Rolle
2024/09/25
2.9K0
利用Go语言实现简单Ping过程的方法
、准备工作 安装最新的Go 1、由于Google被墙的原因,如果没有V**的话,就到这里下载:http://www.golangtc.com/download 2、使用任意文本编辑器,或者Li
李海彬
2018/03/26
2.7K0
利用Go语言实现简单Ping过程的方法
用腾讯混元ai编写了一个go语言端口扫描器
qingjiegong
2023/12/14
2320
用腾讯混元ai编写了一个go语言端口扫描器
使用Python调用Nessus 接口实现自动化扫描
之前在项目中需要接入nessus扫描器,研究了一下nessus的api,现在将自己的成果分享出来。 Nessus提供了丰富的二次开发接口,无论是接入其他系统还是自己实现自动化扫描,都十分方便。 同时Nessus也提供了完备的API文档,可以在 Settings->My Account->API Keys->API documentation
Masimaro
2020/02/12
3.9K0
What!!! so fast
不要以为我是标题党,真的是so fast。。。最近有个小项目的需要,使用golang写了个端口扫描工具,不得不说golang的效率确实比python快的太多了。在使用一段时间golang之后,感觉有三个方面是优于python的:
七夜安全博客
2018/10/08
6860
What!!! so fast
go语言ping实现
package main import ( "flag" "fmt" "net" "os" "strconv" "time" ) func main() { var count int var timeout int64 var size int var neverstop bool flag
李海彬
2018/03/27
8550
内网隧道之pingtunnel
github:https://github.com/esrrhs/pingtunnel
中龙技术
2022/09/29
1.8K0
内网隧道之pingtunnel
从 Masscan, Zmap 源码分析到开发实践
Zmap和Masscan都是号称能够快速扫描互联网的扫描器,十一因为无聊,看了下它们的代码实现,发现它们能够快速扫描,原理其实很简单,就是实现两种程序,一个发送程序,一个抓包程序,让发送和接收分隔开从而实现了速度的提升。但是它们识别的准确率还是比较低的,所以就想了解下为什么准确率这么低以及应该如何改善。
知道创宇云安全
2019/10/14
1.7K0
Go 1.7 相比 Go 1.6 有哪些值得注意的改动?
运行上述 Go 程序(需要 C 编译器环境),C 函数 process_data 将能正确接收并打印 Go 传递过来的字节数据。
Piper破壳
2025/04/17
670
转--Golang语言版 ssh口令破解工具
使用说明: iplist的格式为ip:port,如111.111.111.111:22 user.txt为用户名字典 password.txt为密码字典 github:https://github.c
李海彬
2018/03/21
1.4K0
转--Golang语言版 ssh口令破解工具
net
net 包提供了可移植的网络i/o接口,包括TCP/IP、UDP、域名解析和Unix域socket
酷走天涯
2019/06/11
1.2K0
net
【Golang】快速复习指南QuickReview(十)——goroutine池
goroutine的栈在其生命周期开始时很小,可能只有2KB,但是它并不固定,可按需增大或减小。虽然我们可以无脑创建很多goroutine来执行操作,但是如果程序出现意外,goroutine可能会暴涨占据内存,一切就变得不可控,比如我们通过循环来创建goroutine,当循环条件满足,创建巨额的goroutine,严重时系统会崩溃。博主也是通过杨旭老师的TCP端口扫描器中发现了这个问题。
DDGarfield
2022/06/23
2470
相关推荐
Go编写工具教程第二课 高并发主机发现
更多 >
LV.1
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验