免责声明
💡 本公众号绝不涉及任何非法用途。💡使用者风险自担,违规后果自负。💡守法为先,技术向善。💡 请合理、合法地使用网络安全技术,共同维护一个健康、安全的网络环境!
前序
主要是介绍命令行工具开发用到的包,有的集成了一个框架,只要学会了基本每个人都能开发安全工具了。
该文章先学flags包,是比较经典的一个包,相比后面要学习的集成框架这个比较自由比较细化点,自定义可能高一些,后续会学到一个Cobra框架,这个很多安全工具都在使用,先学会flags包入门再去理解Cobra框架就比较好学。
flags包
-
• 支持段选项长选项意思是使用的时候可以用简写也可以用完整的参数写,比如一个工具叫 a.exe
,用的时候可以a.exe -h
也可以a.exe -help
-
• 支持短选项组合写,比如: a.exe -p
a.exe -s
两种可以写在一起:a.exe -ps
-
• 一个参数选项可以给多个值(代码中很容易了解到这一点) -
• flags包默认有-h选项,解析完后--help和-h会返回你的所有参数解释,不用自己写help
安装
go get github.com/jessevdk/go-flags
个人感觉学习这个框架只需要理解两步:
-
• 指定结构体选项 -
• 解析结构体
基础的选项:随便写几个展示即可
-
• short:短选项名字 -
• long:长选项名字 -
• description:显示你当前选项的一个解释,-help或者-h的时候显示 -
• tips:
// 参数选项type Option struct {//所有结构体首字母一定要大写,否则解析不了,但是short或者long的名字就随便你起 V string`short:"v" long:"verbose" description:"显示详细信息"` Vlist []string`short:"V" long:"verbose-list" description:"显示详细信息列表"` I int`short:"i" long:"input" description:"int类型测试"`//这里可以给了一个默认值default:"xxx",不给的话在不使用该参数的时候就不用调用那个函数 P func(string)`short:"p" long:"print" description:"打印"`// default:"myPrint" IntMap map[string]int`short:"m" long:"intmap" description:"intmap"`}
函数这里是要给一个函数传递进去使用,具体如下:是在你实例化你的选项结构体后,将你写好的函数传递进去,或者你写一个匿名函数也行。
// 打印功能funcmyPrint(str string) { fmt.Println("打印-p参数值:", str)}funcmain() {var opt Option //定义一个选项 opt.P = myPrint //把打印函数赋值给P}
使用的时候就可以用该参数了,传递的参数值就是给到函数变量使用的:
参数传递格式
-
• -V传递: -V 123 -V 456 -V 789
这样才能传递进map里面,不可以使用逗号或者空格隔开-V 123 456
/-V 123,456
都是不行的 -
• map的传递: -m key1:123 -m key2:456
,不建议使用等号=
,至少我在windows测试的时候等号不能作为键值对分割,只能使用:
分割,同时多个map键值对也是多个-m才能传递添加进去。
剩下几个类型自己实践即可,这里就够用了。
选项设置
选项设置都是直接在结构体选项反引号中直接添加即可,他会解析的。
-
• required:在结构体选项中可设置,true的时候,该选项必选,否则报错,一定要给这个选项一个值比如:V string short:"v" long:"verbose" description:"显示详细信息" required:true
-
• default:表示你这个选项参数有一个默认值,就算你不加他也会以默认值形式作用本次运行比如:上面结构体代码注释其实有提到P func(string) short:"p" long:"print" description:"打印" default:"myPrint"
这里就是可以给一个default,表示这个选项的默认值是多少,可以看到我这里给的是一个函数名,这个函数名我也在代码这种实现了吗,所以运行的时候肯定是可以找到我这个函数然后执行
分组
这个功能比较好用,至少对我来说以后开发构思中肯定需要用到这个,以及后面讲的子命令都是在一些比较完整的工具开发中用的比较多。
先看运行的效果图
这里和之前的不一样了,之前的没有分组的时候就是直接把一些参数打印出来,这里会有参数分组,对比之下会更加直观一点。
分组的结构体之间的联系是比较紧密的
-
• 先创建好组名(组名也要用结构体)结构体内的字段都是你接下来要创建的结构体选项,所以在这里可以先提前想好选项的结构体名字比如下面的就是 Host
和Scan
就是我们接下来要创建的分组的选项参数结构体group
就是组名,到时候你在help中就能看到分组的组名namespace
是空间名,在help中显示为Host.xxx选项
,就是类似这样,主要是告诉你哪个组下的选项,重要是group
是组名即可,显示的时候比较明显。
// basic 分组type Basic struct { HostOption Host `group:"host" namespace:"Host"` ScanOption Scan `group:"scan" namespace:"Scan"`}
-
• 正常创建你在分组的时候想好的那几个结构体字段,我们给的名字是: Host
和ScanOption
,那么接下来就是写这两个结构体了注意:我在Scan
中又开了一个组ScanType
,所以还要写一个ScanType
的结构体这是help效果图,应该很容易理解是怎么分组的了,也知道那个namespace
是什么了,注意区分Host
和host
,host是分组的组名,Host才是那个namespace。
type Host struct { HostName string`short:"N" long:"hostname" description:"主机名"` HostMac string`short:"M" long:"hostmac" description:"主机mac"`}// 在扫描类型中继续分组type ScanType struct { HttpType string`short:"T" long:"http" description:"http扫描"` DataBase string`short:"D" long:"database" description:"数据库扫描"` Other string`short:"O" long:"other" description:"其他扫描"`}type Scan struct { BasicScan ScanType `group:"scantype" namespace:"scantype"` ScanPort int`short:"P" long:"scanport" description:"扫描端口"` ScanIP string`short:"I" long:"scanip" description:"扫描ip"`}
子命令
简单的来说:go version
这个 version
就是子命令,不用带-
,直接用的就是子命令,-version
这种带-
的就是选项而不是子命令哈,注意一个符号的区别。
同时记住一点:子命令在flags包中也自动实现了-h /h命令,所以不用编写帮助信息。(但是你想要实现不添加-h /h 就实现比如 xx.exe finger 也能给出帮助信息的话就要在接口中实现了,后续会在finger中讲明白)
version
-
• 先实现 version
子命令继承了flags.Command的Execute接口就成功实现了子命令了,只剩下注册到主要的结构体中,也就是之前学到的需要一个结构体注册选项(tips:version
的结构体为空,不是拿来切割或者分组,只是一个显示版本号,后面讲finger
的时候他才是作为这个分组命令来弄)
//给一个空的,因为version一般都不需要什么其他参数来辅助就可以看到版本了type ChildCommand struct {}// 继承了flags.Command即可自动调用func(c *ChildCommand) Execute(args []string) error { fmt.Println("version: 1.0.0")returnnil}
-
• 注册子命令到主选项结构体中,这里才是给到flags解析的结构体,注意这里给的键不再是 short
/long
,而是command
type VOption struct { Version ChildCommand `command:"version" description:"Version显示版本信息"`}
运行效果没问题(源码稍后放)
finger(测试)
用finger
来加深理解一般指纹识别的都是xx.exe finger -u xxx -p xxx
所以我认为用这个例子非常好
-
• 依旧是先做好一个子命令,但是这里要给子命令上两个选项,用来指纹识别的ip和端口同时要记得实现接口 Execute
// 模拟一下指纹扫描中常见的一个子命令type Finger struct { U string`short:"u" long:"url" description:"url"` P string`short:"p" long:"port" description:"port"`}func(c *Finger) Execute(args []string) error {if c.U == "" || c.P == "" {// 如果没有提供参数,显示帮助信息,尽量做的完美一点 parser := flags.NewParser(c, flags.Default) parser.WriteHelp(os.Stdout)returnnil }returnnil}
-
• 在主结构体中注册这个子命令我就直接在之前的VOption结构体注册了
type VOption struct { Version ChildCommand `command:"version" description:"Version显示版本信息"`// 指纹扫描 Finger Finger `command:"finger" description:"Finger指纹扫描"`}
注册完成就可以用了
-
• 加帮助参数 -
• 这个没有加参数 -h 或者 /h等等
以上就是flags包的一些基础常用的内容了,拿到参数之后就是往后丢给你自己写的功能函数即可。
所有测试源码
test1 基础使用测试、test2分组测试 和 test3子命令 自己看着用就行。
package mainimport ("fmt""log""os""github.com/jessevdk/go-flags")// 参数选项type Option struct {//所有结构体首字母一定要大写,否则解析不了,但是short或者long的名字就随便你起 V string`short:"v" long:"verbose" description:"显示详细信息"` Vlist []string`short:"V" long:"verbose-list" description:"显示详细信息列表"` I int`short:"i" long:"input" description:"int类型测试"`//这里可以给了一个默认值default:"xxx",不给的话在不使用该参数的时候就不用调用那个函数 P func(string)`short:"p" long:"print" description:"打印"`// default:"myPrint" IntMap map[string]int`short:"m" long:"intmap" description:"intmap"`}// 打印功能funcmyPrint(str string) { fmt.Println("打印-p参数值:", str)}// basic 分组type Basic struct { HostOption Host `group:"host" namespace:"Host"` ScanOption Scan `group:"scan" namespace:"Scan"`}type Host struct { HostName string`short:"N" long:"hostname" description:"主机名"` HostMac string`short:"M" long:"hostmac" description:"主机mac"`}// 在扫描类型中继续分组type ScanType struct { HttpType string`short:"T" long:"http" description:"http扫描"` DataBase string`short:"D" long:"database" description:"数据库扫描"` Other string`short:"O" long:"other" description:"其他扫描"`}type Scan struct { BasicScan ScanType `group:"scantype" namespace:"scantype"` ScanPort int`short:"P" long:"scanport" description:"扫描端口"` ScanIP string`short:"I" long:"scanip" description:"扫描ip"`}type ChildCommand struct {}// 继承了flags.Command即可自动调用func(c *ChildCommand) Execute(args []string) error { fmt.Println("version: 1.0.0")returnnil}// 模拟一下指纹扫描中常见的一个子命令type Finger struct { U string`short:"u" long:"url" description:"url"` P string`short:"p" long:"port" description:"port"`}func(c *Finger) Execute(args []string) error {if c.U == "" || c.P == "" {// 如果没有提供参数,显示帮助信息 parser := flags.NewParser(c, flags.Default) parser.WriteHelp(os.Stdout)returnnil }returnnil}type VOption struct { Version ChildCommand `command:"version" description:"Version显示版本信息"`// 指纹扫描 Finger Finger `command:"finger" description:"Finger指纹扫描"`}functest3() { parser := flags.NewParser(&VOption{}, flags.Default) _, err := parser.Parse()if err != nil {if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {return// 帮助信息已打印,直接退出 }//如果不是打印的帮助信息报错的话就直接log就行 log.Println("parses failed: ", err)return }}functest1() {var opt Option //定义一个选项 opt.P = myPrint //把打印函数赋值给P parser := flags.NewParser(&opt, flags.Default) _, err := parser.Parse()if err != nil {if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {return// 帮助信息已打印,直接退出 }//如果不是打印的帮助信息报错的话就直接log就行 log.Println("parses failed: ", err)return } fmt.Println("--------------------------") fmt.Println("打印-v参数值:", opt.V) fmt.Println("打印-V 列表所有的参数值:", opt.Vlist) fmt.Println("打印-i参数值:", opt.I) fmt.Println("打印--intmap参数值:", opt.IntMap)}functest2() {var BasicOption Basic parser := flags.NewParser(&BasicOption, flags.Default) _, err := parser.Parse()if err != nil {if flagsErr, ok := err.(*flags.Error); ok && flagsErr.Type == flags.ErrHelp {return// 帮助信息已打印,直接退出 }//如果不是打印的帮助信息报错的话就直接log就行 log.Println("parses failed: ", err)return } fmt.Println("--------------------------")}funcmain() {// test1()// test2() test3()}
原文始发于微信公众号(竹等寒):Go红队开发—CLI框架(一)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论