HFish源码浅析
本文是对蜜罐项目HFish
最初的源码的浅析,该版本是v0.2
,代码里面写的。
熟悉HFIsh
的朋友可能了解,目前最新版是闭源的,但是在gitee
上还是有前期开源的项目分享了出来,本文是基于该版本对HFish
进行分析。
调试环境准备
使用 goland2022.1
源码下载地址,HFish源码
HFish的入口main.go文件
main.go
文件的代码只有二十几行,下面是它的源码:
func main() {
args := os.Args
if args == nil || len(args) < 2 {
setting.Help()
} else {
if args[1] == "help" || args[1] == "--help" {
setting.Help()
} else if args[1] == "init" || args[1] == "--init" {
setting.Init()
} else if args[1] == "version" || args[1] == "--version" {
fmt.Println("v0.2")
} else if args[1] == "run" || args[1] == "--run" {
setting.Run()
} else {
setting.Help()
}
}
}
HFish
的官网有一张图
其中就介绍了关键的三个参数:
1.run
---- 运行
2. version
---- 版本
3. help
---- 帮助
// 省略代码...
} else if args[1] == "init" || args[1] == "--init" {
setting.Init()
//省略代码...
控制台打印出来的三个参数如上,在源码里面还有第四个参数init
,下面我们进入到项目里面去了解一下。
进入HFish/utils/setting文件夹
总共六个函数,通过函数名简单推断,
RunWeb
---- 运行Web
蜜罐RunDeep
---- 运行 暗网蜜罐RunAdmin
---- 运行admin
管理后台Run
---- 对应main
函数,也就是主程序的run
参数Init
---- 上一步的init
参数Help
---- 主程序的help
参数
我是想了解一下这个init到底是干啥的,结果…
func Init() {
fmt.Println("test")
}
好叭~~~先放过它!
咱们从这个结构看特别好理解,HFish最关键的函数还是Run
函数,所以接下来还是把聚光灯放在它这儿先吧。
Run函数
通过查看run函数,可以看到它是从配置文件中读取信息,然后判断用户是否开启某个功能的蜜罐,再对该功能进行判断,结构特别清新,下面是部分截取代码
// 启动 FTP 蜜罐
ftpStatus := conf.Get("ftp", "status")
//省略代码...
// 启动 Telnet 蜜罐
telnetStatus := conf.Get("telnet", "status")
//省略代码...
// 启动 Mysql 蜜罐
mysqlStatus := conf.Get("mysql", "status")
//省略代码...
// 启动 Redis 蜜罐
redisStatus := conf.Get("redis", "status")
//省略代码...
// 启动 SSH 蜜罐
sshStatus := conf.Get("ssh", "status")
//省略代码...
// 启动 Web 蜜罐
webStatus := conf.Get("web", "status")
//省略代码...
// 启动 暗网 蜜罐
deepStatus := conf.Get("deep", "status")
//省略代码...
// 启动 RPC
rpcStatus := conf.Get("rpc", "status")
//省略代码...
// 启动 admin 管理后台
adminAddr := conf.Get("admin", "addr")
serverAdmin := &http.Server{
Addr: adminAddr,
Handler: RunAdmin(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
serverAdmin.ListenAndServe()
看到这里就不得不去看一下这个配置文件长啥样了。
项目的配置文件config.ini
通过Run
函数里面conf
对象的Get
方法来到HFish/utils/conf
路径下,可以看到下面源码:
func Get(node string, key string) string {
myConfig := new(Config)
myConfig.InitConfig("./config.ini")
r := myConfig.read(node, key)
return r
}
虽然上面的代码只有几行,但是可以阅读到不少项目的关键信息。
Get
函数的返回值是读取一个字符串,结合前面的Run
函数来看就是获取某个蜜罐的状态是否开启,通过一个字段来判断,0
或1
。
里面创建了一个Config
结构体,深入可得知它的结构是这样的
type Config struct {
Mymap map[string]string
MyNode map[string]string
strcet string
}
接下来是对myConfig
对象进行初始化,InitConfig
方法这里头主要是对配置文件内容的解析。
初始化函数的声明如下:
func (c *Config) InitConfig(path string)
Get函数已经间接的介绍了初始化函数的使用方法,直接放入一个路径名。
下面是初始化函数的源码,我在下面加了一些注释,方便理解这个函数的逻辑
func (c *Config) InitConfig(path string) {
c.Mymap = make(map[string]string)
c.MyNode = make(map[string]string)
//使用 os.Open 函数打开指定路径的配置文件,并检查是否出现错误
f, err := os.Open(path)
if err != nil {
panic(err)
}
defer f.Close()
//使用 bufio.NewReader 函数创建一个读取器对象,用于逐行读取配置文件的内容
r := bufio.NewReader(f)
for {
//使用 ReadLine 方法逐行读取配置文件的内容
b, _, err := r.ReadLine()
if err != nil {
if err == io.EOF {
break
}
panic(err)
}
//过滤掉注释行#和空行
s := strings.TrimSpace(string(b))
if strings.Index(s, "#") == 0 {
continue
}
//解析节点(以 [...] 包裹的行),并设置当前节点(c.strcet 字段)
n1 := strings.Index(s, "[")
n2 := strings.LastIndex(s, "]")
//这里的判断比较巧妙,目的是取出配置文件的节点字段
if n1 > -1 && n2 > -1 && n2 > n1+1 {
c.strcet = strings.TrimSpace(s[n1+1 : n2])
continue
}
if len(c.strcet) == 0 {
continue
}
//解析键值对
index := strings.Index(s, "=")
if index < 0 {
continue
}
frist := strings.TrimSpace(s[:index])
if len(frist) == 0 {
continue
}
second := strings.TrimSpace(s[index+1:])
pos := strings.Index(second, "\t#")
if pos > -1 {
second = second[0:pos]
}
pos = strings.Index(second, " #")
if pos > -1 {
second = second[0:pos]
}
pos = strings.Index(second, "\t//")
if pos > -1 {
second = second[0:pos]
}
pos = strings.Index(second, " //")
if pos > -1 {
second = second[0:pos]
}
if len(second) == 0 {
continue
}
//将解析得到的键值对存储到 Config 结构体的 Mymap 字段中
key := c.strcet + middle + frist
c.Mymap[key] = strings.TrimSpace(second)
key = c.strcet + middle + "introduce"
introduce, found := c.Mymap[key]
if !found {
}
key = c.strcet + middle + "mode"
mode, found := c.Mymap[key]
if !found {
}
c.MyNode[c.strcet] = strings.TrimSpace(mode) + "&&" + strings.TrimSpace(introduce)
}
}
通过调试代码可以看到它是这么解析键值对的,结合下面的配置文件更容易理解
通过上面的代码可以知道,配置文件是位于项目根目录下的config.ini
文件
下面是该项目的配置文件内容
[rpc]
status = 0 # 模式 0关闭 1服务端 2客户端
addr = 127.0.0.1:7879 # RPC 服务端地址 or 客户端地址
name = Server # 状态1 服务端 名称 状态2 客户端 名称
[admin] # RPC 状态为2 集群客户端的时候 admin 可以删掉
addr = 127.0.0.1:9001 # 管理后台启动地址
account = admin # 登录账号
password = admin # 登录密码
[api]
status = 1 # 是否启动 API 1 启动 0 关闭
web_url = /api/v1/post/report # 管理后台启动地址
deep_url = /api/v1/post/deep_report # 管理后台启动地址
sec_key = 9cbf8a4dcb8e30682b927f352d6559a0 # API 认证秘钥
[web]
status = 0 # 是否启动 WEB 1 启动 0 关闭, 启动 API 后 WEB 方可上报结果
addr = 0.0.0.0:9000 # WEB 启动地址,0.0.0.0 对外开放,127.0.0.1 对内开放 可走 Nginx 反向代理
template = wordPress/html # WEB 模板路径
index = index.html # WEB 首页文件
static = wordPress/static # WEB 静态文件路径 注意:必须存在两个目录,html 文件 和静态文件 不能平级
url = / # WEB 访问目录,默认 / 可更改成 index.html index.asp index.php
[deep]
status = 0 # 是否启动 暗网 1 启动 0 关闭, 启动 API 后 方可上报结果
addr = 0.0.0.0:9002 # 暗网 WEB 启动地址
template = deep/html # 暗网 WEB 模板路径
index = index.html # 暗网 WEB 首页文件
static = deep/static # 暗网 WEB 静态文件路径 注意:必须存在两个目录,html 文件 和静态文件 不能平级
url = / # 暗网 WEB 访问目录,默认 / 可更改成 index.html index.asp index.php
[ssh]
status = 0 # 是否启动 SSH 1 启动 0 关闭
addr = 0.0.0.0:22 # SSH 服务端地址 注意端口冲突,请先关闭服务器 openssh 服务 或 修改端口
[redis]
status = 0 # 是否启动 Redis 1 启动 0 关闭
addr = 0.0.0.0:6379 # Redis 服务端地址 注意端口冲突
[mysql]
status = 0 # 是否启动 Mysql 1 启动 0 关闭
addr = 0.0.0.0:3306 # Mysql 服务端地址 注意端口冲突
files = /etc/passwd,/etc/group # Mysql 服务端读取客户端任意文件; 多写逗号分隔,会随机取
[telnet]
status = 0 # 是否启动 Telnet 1 启动 0 关闭
addr = 0.0.0.0:23 # Telnet 服务端地址 注意端口冲突
[ftp]
status = 0 # 是否启动 Ftp 1 启动 0 关闭
addr = 0.0.0.0:21 # Ftp 服务端地址 注意端口冲突
关于Run函数的一点想法
阅读Run
函数能看到调用了很多次conf.Get
,这个Get
函数里面每次都调用了一次初始化函数,为了避免不必要的性能开销,还能对代码逻辑进行精简,在主程序初始化的时候将配置文件存放到全局变量中,之后的操作只需要读取这个全局变量即可。获取在HFish
之后的版本已经有所优化了叭。
未完待续…
本文对很多知识点的描述并不严谨,我只是对HFish这个项目很感兴趣,并且刚好找到了它的早期源码,在这里做一下记录和分享,希望同样有兴趣的朋友可以在这篇文章里面获取到你们想知道的信息。