如何用Golang寫msf插件模塊


最近有空在看msf,發現msf里面有模塊的源碼是golang的,去翻了翻wiki,wiki上面的編寫日期是2018.12.13,搜了下國內,好像沒有這方面的文章,那就自己跟着做做記個筆記

首先第一步自然是安裝go,官方wiki上測試是在1.11.2通過,建議使用 version >= 1.11.2 的go,怎么安裝go我不再贅述

注意事項

模塊限制

不過golang目前還只支持以下幾個msf模塊的編寫

  • remote_exploit
  • remote_exploit_cmd_stager
  • capture_server
  • docs
  • single_scanner
  • single_host_login_scanner
  • multi_scanner

代碼限制

目前並不支持第三方庫,但是可以在模塊目錄的 share/src 文件夾下放置你的庫,整體上來說還是比較雞肋

模塊之間公有的庫的路徑在 lib/msf/core/modules/external/go/src/metasploit 目錄下,可以自行查看

格式

頂行

首先go源碼文件的頂行需要有可執行的標識,需要在文件頂行寫上 //usr/bin/env go run "$0" "$@"; exit "$?"

這個原因主要是因為msf是基於ruby的,執行golang代碼的話需要知曉執行路徑或環境的信息,所以必須插入這一行

比如

//usr/bin/env go run "$0" "$@"; exit "$?"

package main

import (
	"metasploit/module"
	"net/http"
)

元數據信息

下面是需要填入一些元數據的信息來初始化你的模塊,這一部分和ruby比較類似,主要是為了讓msf能夠讀取、搜索和使用這些信息

import "metasploit/module"
func main() {
  metadata := &module.Metadata{
    Name: "<module name",
    Description: "<describe>",
    Authors: []string{"<author 1>", "<author 2>"},
    Date: "<date module written",
    Type:"<module type>",
    Privileged:  <true|false>,
    References:  []module.Reference{},
    Options: map[string]module.Option{	
      "<option 1":     {Type: "<type>", Description: "<description>", Required: <true|false>, Default: "<default>"},		
      "<option 2":     {Type: "<type>", Description: "<description>", Required: <true|false>, Default: "<default>"},
  }}

  module.Init(metadata, <the entry method to your module>)
}

可以看到main()方法調用了module.Init,后面的注釋我們可以看到是 模塊的入口方法

我們剛才說過模塊之間公有的庫,我們跟到這里面看看這個 module.Init 的定義

/*
 * RunCallback represents the method to call from the module
 */
type RunCallback func(params map[string]interface{})

/*
 * Initializes the module waiting for input from stdin
 */
func Init(metadata *Metadata, callback RunCallback) {
	var req Request

	err := json.NewDecoder(os.Stdin).Decode(&req)
	if err != nil {
		log.Fatalf("could not decode JSON: %v", err)
	}

	switch strings.ToLower(req.Method) {
	case "describe":
		metadata.Capabilities = []string{"run"}
		res := &MetadataResponse{"2.0", req.ID, metadata}
		if err := rpcSend(res); err != nil {
			log.Fatalf("error on running %s: %v", req.Method, err)
		}
	case "run":
		params, e := parseParams(req.Parameters)
		if e != nil {
			log.Fatal(e)
		}
		callback(params)
		res := &RunResponse{"2.0", req.ID, RunResult{"Module complete", ""}}
		if err := rpcSend(res); err != nil {
			log.Fatalf("error on running %s: %v", req.Method, err)
		}
	default:
		log.Fatalf("method %s not implemented yet", req.Method)
	}
}

可以看到,入口方法就是一個參數格式為 map[string]interface{} 的回調函數,下面的 Init 也好理解,首先根據你的傳入參數,
查看是否是 describe 還是 run 指令,如果是 run 指令就執行回調,邏輯十分簡單。

例子

msf框架的wiki中給了一個完整的示例https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/scanner/msmail/exchange_enum.go

我們直接那這個來分析一下

//usr/bin/env go run "$0" "$@"; exit "$?"

package main

import (
	"crypto/tls"
	"fmt"
	"metasploit/module"
	"msmail"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

func main() {
	metadata := &module.Metadata{
		Name:        "Exchange email enumeration",
		Description: "Error-based user enumeration for Office 365 integrated email addresses",
		Authors:     []string{"poptart", "jlarose", "Vincent Yiu", "grimhacker", "Nate Power", "Nick Powers", "clee-r7"},
		Date:        "2018-11-06",
		Type:        "single_scanner",
		Privileged:  false,
		References:  []module.Reference{},
		Options: map[string]module.Option{
			"RHOSTS":     {Type: "string", Description: "Target endpoint", Required: true, Default: "outlook.office365.com"},
			"EMAIL":      {Type: "string", Description: "Single email address to do identity test against", Required: false, Default: ""},
			"EMAIL_FILE": {Type: "string", Description: "Path to file containing list of email addresses", Required: false, Default: ""},
		}}

	module.Init(metadata, run_exchange_enum)
}

首先開頭就是我們剛才所談到的格式,首先是頂行的固定格式,然后main方法是定義了元數據信息,里面定義這個模塊的基本信息,
然后定義了模塊的三個參數項 RHOSTS、EMAIL、EMAIL_FILE,緊接着調用了 module.Init

入口方法是 run_exchange_enum

我們看看這個方法的源碼

func run_exchange_enum(params map[string]interface{}) {
	email := params["EMAIL"].(string)
	emailFile := params["EMAIL_FILE"].(string)
	threads, e := strconv.Atoi(params["THREADS"].(string))
	ip := params["rhost"].(string)

	if e != nil {
		module.LogError("Unable to parse 'Threads' value using default (5)")
		threads = 5
	}

	if threads > 100 {
		module.LogInfo("Threads value too large, setting max(100)")
		threads = 100
	}

	if email == "" && emailFile == "" {
		module.LogError("Expected 'EMAIL' or 'EMAIL_FILE' field to be populated")
		return
	}

	var validUsers []string
	if email != "" {
		validUsers = o365enum(ip, []string{email}, threads)
	}

	if emailFile != "" {
		validUsers = o365enum(ip, msmail.ImportUserList(emailFile), threads)
	}

	msmail.ReportValidUsers(ip, validUsers)
}

首先方法的開頭取了模塊的基本配置信息,緊接着是一系列判斷,然后我們看到關鍵的代碼

var validUsers []string
if email != "" {
	validUsers = o365enum(ip, []string{email}, threads)
}

if emailFile != "" {
	validUsers = o365enum(ip, msmail.ImportUserList(emailFile), threads)
}

msmail.ReportValidUsers(ip, validUsers)

o365enum 方法就在這個方法的下面,但是這里並不是我們討論的重點,是什么我們不管,只需要知道,他執行了一些枚舉遍歷的操作,返回了可用的用戶,操作怎么做的不在我們的討論重點內

然后調用了 msmail.ReportValidUsers(ip, validUsers)

前面我們說過,並不支持第三方庫,msmail是他自己在 share/src 下創建的一個庫,具體代碼可以在https://github.com/rapid7/metasploit-framework/blob/master/modules/auxiliary/scanner/msmail/shared/src/msmail/msmail.go看到

我們直接定位到我們剛才說到的 msmail.ReportValidUsers

package msmail

import (
	...
	"metasploit/module"
	...
)
...
func ReportValidUsers(ip string, validUsers []string) {
	port := "443"
	service := "owa"
	protocol := "tcp"
	for _, user := range validUsers {
		opts := map[string]string{
			"port":         port,
			"service_name": service,
			"address":      ip,
			"protocol":     protocol,
		}
		module.LogInfo("Loging user: " + user)
		module.ReportCredentialLogin(user, "", opts)
	}
}

這里面把傳過來的用戶名遍歷了然后調用了

module.LogInfo("Loging user: " + user)
module.ReportCredentialLogin(user, "", opts)

前一個方法我們能猜到是日志輸出
后一個呢?我們都跟過去看看

func rpcSend(res interface{}) error {
	rpcMutex.Lock()
	defer rpcMutex.Unlock()

	resStr, err := json.Marshal(res)
	if err != nil {
		return err
	}
	f := bufio.NewWriter(os.Stdout)
	if _, err := f.Write(resStr); err != nil {
		return err
	}
	if err := f.Flush(); err != nil {
		return err
	}

	return nil
}

...

func LogInfo(message string) {
	msfLog(message, "info")
}

...

func msfLog(message string, level string) {
	req := &logRequest{"2.0", "message", logparams{level, message}}
	if err := rpcSend(req); err != nil {
		log.Fatal(err)
	}
}

可以看到 module.LogInfo 是基礎的日志輸出

func ReportCredentialLogin(username string, password string, opts map[string]string) {
	base := map[string]string{"username": username, "password": password}
	if err := report("credential_login", base, opts); err != nil {
		log.Fatal(err)
	}
}

func report(kind string, base map[string]string, opts map[string]string) error {
	for k, v := range base {
		opts[k] = v
	}
	req := &reportRequest{"2.0", "report", reportparams{kind, opts}}
	return rpcSend(req)
}

這個方法就是很簡單的把數據略微友好型展示了一下

所以整個的流程就是選擇好模塊,設置好參數,module.Init 進去,然后直接 run,就執行 module.Init 第二個參數傳入的回調方法

然后后面就只是你用golang進行其他操作最后再進行調用msf公有模塊進行信息展示了

References


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM