Skip to content

Latest commit

 

History

History
278 lines (222 loc) · 8.2 KB

artifact-kunpeng.md

File metadata and controls

278 lines (222 loc) · 8.2 KB

带你读神器之PoC框架KunPeng

Category Research Language Tag Timestamp Progress

* 转安全之后粗略的读过很多开源工具,一直考虑写一些神器的源代码分析文章。回想以前PocSuite和BugScan那时插件化PoC盛行,很多人都梦寐以求自己能拥有这么一套强大的PoC框架,昨天看到@wolf和几个大牛们开源了一个Go的跨语言PoC检测框架KunPeng,刚好它代码量不大,适合过年,就拿它当作这个系列的开篇吧。

根据官方文档的介绍,这个项目是以动态链接库的形式提供给Go以及其他语言进行调用的,因此会存在调用方和被调用方两个角色,请大家仔细区分。

获取对象

调用方调用plugin包加载so文件,并获取其中的Greeter对象:

plug, _ := plugin.Open("./kunpeng_go.so")
g, _ := plug.Lookup("Greeter")

由于Plugin.Lookup()获得的是一个interface{}类型对象,需要将其进行一次类型断言才能访问到Greeter对象内的公有属性。好在Go具有非侵入式接口的语言特性,使调用方在本地定义一个属于Greeter对象公有方法子集的接口就能断言成功:

type Greeter interface {
	Check(string) ([]map[string]string)
    GetPlugins() []map[string]string
    ...
}

// omit
kunpeng, ok := g.(Greeter)
// kunpeng.GetPlugins()

执行检测

调用方定义一个Task结构体,实例化后转成JSON字符串传给Greeter.Check(),即可等待检测结果:

type Meta struct {
	System   string   `json:"system"`
	PathList []string `json:"pathlist"`
	FileList []string `json:"filelist"`
	PassList []string `json:"passlist"`
}

type Task struct {
	Type   string `json:"type"`
	Netloc string `json:"netloc"`
	Target string `json:"target"`
	Meta   Meta   `json:"meta"`
}

// omit
task, _ := json.Marshal(Task{"service", "0.0.0.0:0000", "mysql", Meta{}})
result := kunpeng.Check(string(task))

被调用方的Check()在解析JSON字符串拿到plugin.Task对象 (结构体与调用方定义的Task一致) 后,直接交给plugin.Scan()去执行实际检测逻辑,拿到的结果又转成JSON字符串返回:

func Check(task *C.char) *C.char {
	var m plugin.Task
    if err := json.Unmarshal([]byte(C.GoString(task)), &m); err != nil {
        return C.CString("[]")
	}
	result := plugin.Scan(m)
	if len(result) == 0{
		return C.CString("[]")
	}
	b, err := json.Marshal(result)
    if err != nil {
        return C.CString("[]")
	}
	return C.CString(string(b))
}

从上面的代码中可以看到,为了支持跨语言调用,KunPeng使用更底层兼容性更高的CGo来处理几个入口函数中的原始数据类型。

plugin.Scan()分别遍历GoPluginsJSONPlugins (两种类型插件的具体区别见下面插件开发和插件加载章节) ,根据TaskTarget字段选择PoC子集进行检测并返回结果集合:

func Scan(task Task) (result []map[string]interface{}) {
	for n, pluginList := range GoPlugins {
		if strings.Contains(strings.ToLower(task.Target), strings.ToLower(n)) || task.Target == "all" {
			for _, plugin := range pluginList {
				plugin.Init()
				if len(task.Meta.PassList) == 0 {
					task.Meta.PassList = Config.PassList
				}
				if !plugin.Check(task.Netloc, task.Meta) {
					continue
				}
				for _, res := range plugin.GetResult() {
					result = append(result, util.Struct2Map(res))
				}
			}
		}
	}
	if task.Type == "service" {
		return result
	}
	for target, pluginList := range JSONPlugins {
		if strings.Contains(strings.ToLower(task.Target), strings.ToLower(target)) || task.Target == "all" {
			for _, plugin := range pluginList {
				if yes, res := jsonCheck(task.Netloc, plugin); yes {
					result = append(result, util.Struct2Map(res))
				}
			}
		}
	}
	return result
}

插件开发

Go类型插件

KunPeng定义了一个用于描述插件信息的公有结构体Plugin

type References struct {
	URL string `json:"url"`
	CVE string `json:"cve"`
}

type Plugin struct {
	Name       string     `json:"name"`
	Remarks    string     `json:"remarks"`
	Level      int        `json:"level"`
	Type       string     `json:"type"`
	Author     string     `json:"author"`
	References References `json:"references"`
	Request    string
	Response   string
}

以及公有接口GoPlugin

type GoPlugin interface {
	Init() Plugin
	Check(string, TaskMeta) bool
	GetResult() []Plugin
}

由于KunPeng未定义插件的相关基类及缺省字段和方法,所以我们需要在plugin/go/目录下创建一个新的.go文件,在其中自定义一个包含inforesult字段的结构体来表示新的插件:

type pluginXXX struct {
	info   plugin.Plugin
	result []plugin.Plugin
}

随后,为该结构体实现GoPlugin接口中所有的方法:

func (p *pluginXXX) Init() plugin.Plugin {
    p.info = plugin.Plugin{}
}

func (p *pluginXXX) Check(netloc string, meta TaskMeta) bool {
    // 自定义检测过程逻辑,成功返回true,失败返回false
    return false
}

func (p *pluginXXX) GetResult() []plugin.Plugin {
    return p.result
}

并在文件的init()方法中调用plugin.Regist()注册该插件即可:

func init() {
	plugin.Regist("xxx", new(plugin))
}

JSON类型插件

KunPeng同样为我们准备好了用于描述JSON插件信息的公有结构体JSONPlugin,并对它实现了统一的检测方法jsonCheck()

type JSONPlugin struct {
	Target  string `json:"target"`
	Meta    Plugin `json:"meta"`
	Request struct {
		Path     string `json:"path"`
		PostData string `json:"postdata"`
	} `json:"request"`
	Verify struct {
		Type  string `json:"type"`
		Match string `json:"match"`
	} `json:"verify"`
}

func jsonCheck(URL string, p JSONPlugin) (bool, Plugin) {
    // 常规的HTTP发包和结果比较,略
    return false, result
}

我们可以在plugin/json/目录下创建一个新的.json文件,写入我们需要的信息 (具体内容参考官方文档) 即可:

{
    "target": "xxx",
    "meta": {
        "name": "xxx",
        "remarks": "xxx",
        "level": 0,
        "type": "RCE",
        "author": "gyyyy",
        "references": {
            "url": "https://github.com/gyyyy/",
            "cve": ""
        }
    },
    "request":{
        "path": "/index.html",
        "postData": ""
    },
    "verify":{
        "type": "string",
        "match": "gyyyy"
    }
}

插件加载

Go类型插件

前面说了,Go类型插件需要在init()时进行注册,Regist()会将插件对象以target为键放入GoPlugins集合,并初始化插件信息:

func Regist(target string, plugin GoPlugin) {
	GoPlugins[target] = append(GoPlugins[target], plugin)
	var pluginInfo = plugin.Init()
}

由于入口文件中匿名导入了plugin/go包,所以在程序启动时,所有编写好的Go类型插件就都会init()GoPlugins中完成加载。

JSON类型插件

相比之下,JSON类型插件的加载过程就要繁琐一些。

入口文件匿名导入plugin/json包后,会调用plugin/json/init.go文件中的init()方法进行加载:

func init() {
	loadJSONPlugin(false, "/plugin/json/")
	go loadExtraJSONPlugin()
}

loadJSONPlugin()遍历目录中的所有.json文件,交给readPlugin()进行处理。readPlugin()读取文件后将JSON字符串解析为JSONPlugin对象返回,所有非重复插件对象将以target为键全部放入JSONPlugins集合中。

而新开的Goroutine调用loadExtraJSONPlugin(),每隔20秒对配置的extra_plugin_path目录执行loadJSONPlugin()操作进行加载。

为了节省篇幅这里就不细述了。

说在最后的话

非常敬佩几位大牛的开源精神,请大家多多PR,为这个新生的PoC框架和漏洞库贡献一份力量。

参考

  1. KunPeng