零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现

admin 2022年10月20日13:34:51评论39 views字数 9345阅读31分9秒阅读模式

零基础学go

GO黑帽子学习笔记-3.3 msf客户端的实现

在学习这节之前,我还不知道msf原来还有一个用来交互的api,曾经只知道cs是将msf封装在底层实现了客户端与服务端的架构,但是通过本节学习,让我了解到msf客户端的编写方法,如果我们需要,我们完全可以对本节进行扩展,来完成一个我们自己的msf客户端用来进行远控。

环境配置

首先我们需要安装Metasploit社区版。

使用命令msfconsole进行msf控制台,输入命令load msgrpc Pass=xxx ServerHost=xx.xx.xx.xx启动一个msf的 服务器,用来与我们的客户端进行交互。

为了便于我们后面的代码调用,我们还需要对代码中的值进行一些硬编码,本次使用的是在系统环境变量中进行编码的方式。命令行输入命令export可以看到当前存在的环境变量,我们可以使用如下命令设置我们需要的环境变量。

export MSFHOST=127.0.0.1:55552
export MSFPASS=xxxxx

对于msf的API开发文档,我们可以在https://metasploit.help.rapid7.com/docs/rpc-api/上进行查看。其次,与我们之前完成的fofa客户端不同,msfapi使用的通信方式并不是json,而是MessagePack进行通信,这是一种紧凑而搞笑的二进制格式。由于go不含标准的MessagePack程序包,所以我们需要使用第三方库进行实现。

go get gopkg.in/vmihailenco/msgpack.v2

当然,这个命令并不需要我们现在输入,我们将一会在我们创建的msf第三方库中输入该命令以获取MessagePack第三方库。

然后我们创建两个文件,分别为client和rpc。首先进入rpc,创建msf.go,并为其创建mod,使用刚刚的命令获取MessagePack第三方库

mkdir rpc
cd rpc
vim msf.go
go mod init github.com/haochen1204/msf
go get gopkg.in/vmihailenco/msgpack.v2

然后进入我们的client文件进行操作

mkdir client
cd client
vim main.go
go mod init msfclient

接下来按照我们之前的操作,我们应该先完成rpc文件夹下的msf.go文件,然行将其上传到我们的github上,并创建tag,然行在我们的client文件中对其进行引入。但是我们这次不想将其传到github上,只想在本地调用怎么办,这就需要我们对我们的go.mod文件进行操作。我们编辑client文件中的go.mod。

module msfclient

go 1.18

replace github.com/haochen1204/msf => ../rpc
require github.com/haochen1204/msf v0.0.0

定义目标

我们首先需要定义我们的目标,从文档中进行查看,我们可以看到session.list给出了我们解决方法,来检索当前Meterpreter会话的列表。

零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现

我们是选来看接收,msf希望接收要实现的方法的名称和令牌,token是一个身份令牌,而该身份令牌是我们成功登陆RPC服务器后发出的。而从msf返回的session.list的响应采用上图的格式。

在上图的响应中,我们可以看出,该响应是作为映射返回,Meterpreter绘画标识符是键,而回话的详细信息是值。我们现在需要构建Go数据类型意外事件的处理请求和响应数据。

编辑rpc下的msg.go文件

type SessionListReq struct {
_msgpack struct{} `msgpack:",asArray"`
Method string
Token string
}

type SessionListRes struct {
ID uint32 `msgpack:",omitempty"`
Type string `msgpack:"type"`
TunnelLocal string `msgpack:"tunnel_local"`
TunnelPreer string `msgpack:"tunnel_peer"`
ViaExploit string `msgpack:"via_exploit"`
ViaPayload string `msgpack:"via_payload"`
Description string `msgpack:"desc"`
Info string `msgpack:"info"`
Workspace string `msgpack:"worrkspace"`
SessionHost string `msgpack:"session_host"`
SessionPort int `msgpack:"session_port"`
Username string `msgpack:"username"`
UUID string `msgpack:"uuid"`
ExploitUUID string `msgpack:"exploit_uuid"`
}

首先在第一行我们创建请求结构体SessionListReq,按照msf服务器的需要的接收方式,将数据结构化为MessagePack格式。需要注意的是,数据以数组的方式而不是映射的方式进行传递,因此RPC接口希望接收到的数据是作为值的位置数组,而不是键值。所以我们无须定义键名。但是,在默认情况下,结构体将被编码为包含从属性名推倒出得键名的映射。因此要禁用该功能并强制将其编码为位置数组,需要添加上述代码中第二行的内容。即_msfpack的特殊字段,该字段用来描述asArray显式指示编码器/解码器将数据视为数组。

获取有效令牌

现在我们想发送上面的数据来获取msf上上线的主机信息,但是我们缺少一个参数,那就是登陆使用的token,所以这是我们需要调用api中的auth.login()方法来获取一个token。

零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现

我们根据api文档可知,我们发送的登陆请求应该包含auth.login,用户名和密码三个参数。而我们在上面的环境配置中已经设置过Metasploit中的用户名和密码的值。假如我们身份验证成功,那么服务器会给我们返回result以及token,如果身份验证失败则会返回error,error_class、error_message三个参数。

同时,我们还需要创建一个推出登陆的功能,用来销毁我们的token。

零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现

所以我们根据api文档内容继续完成关于msfapi调用时需要的结构体的定义。

type loginReq struct {
_msgpack struct{} `msgpack:",asArray"`
Method string
Username string
Password string
}

type loginRes struct {
Result string `msgpack:"result"`
Token string `msgpack:"token"`
Error bool `msgpack:"error"`
ErrorClass string `msgpack:"errror_class"`
ErrorMessage string `msgpack:"error_message"`
}

type logoutReq struct {
_msgpack struct{} `msgpack:",asArray"`
Method string
Token string
LogoutToken string
}

type logoutRes struct {
Result string `msgpack:"result"`
}

需要注意的是,Go动态地对登陆响应进行了序列化,仅填充了存在的字段,这意味着我们可以使用单一的结构体来表示成功和失败的登陆。

创建配置结构体和RPC方法

此时我们已经完成了我们登陆、查询、推出登陆所需要的结构体,那么就像在我们之前fofa客户端那样,我们需要一个存放身份信息的地方,用来将登陆的用户名、token等信息进行存放,并为其创建new方法,之后的登陆、查询、推出登陆的方法都依据该结构体进行。

type Metasploit struct {
host string
user string
pass string
token string
}

func New(host, user, pass string) (*Metasploit, error) {
msf := &Metasploit{
host: host,
user: user,
pass: pass,
}
return msf
}

执行远程调用

我们现在,需要在Metasploit上构建方法,来执行远程调用,为了减少代码量,我们不必为登陆、查询、推出登陆分别完成发送方法,而是使用一个统一的发送方法来进行。

func (msf *Metasploit) send(req interface{}, res interface{}) error {
buf := new(bytes.Buffer)
msgpack.NewEncoder(buf).Encode(req)
dest := fmt.Sprintf("http://%s/api", msf.host)
r, err := http.Post(dest, "binary/message-pack", buf)
if err != nil {
return err
}
defer r.Body.Close()
if err := msgpack.NewDecoder(r.Body).Decode(&res); err != nil {
return err
}
return nil
}

我们在这里首先遇到一个问题,我们在发送登陆、查询、推出登陆时,使用的结构体都不为相同,我们如何定义一个参数来用来统一接受这些不同的结构体呢。我这里就需要使用到interface{}类型,该类型可以将任何请求的结构体传到方法当中。所以我们现在定义两个interface类型的变量,一个为我们发送请求所用的结构体req,一个为我们获取响应所用的结构体res。

然后我们首先创建一个字节类型,然后使用msgpack库对请求进行编码(首先通过NewEncoder创建编码器,然后通过Encode()进行编码,并将编码结果存入buf)然后我们通过msf中的host地址来构建msf的api请求地址。然后通过post请求将请求发送,内容类型显式设置为binary/message-pack,并且将主题设置为序列化数据。

最后利用msgpack包对响应内容进行解码,并将结果赋值给我们在上面传入的接收响应所用的结构体res上。

然后我们分别完成3个方法,登陆、推出以及查询。

func (msf *Metasploit) Login() error {
ctx := &loginReq{
Method: "auth.login",
Username: msf.user,
Password: msf.pass,
}
var res loginRes
if err := msf.send(ctx, &res); err != nil {
return err
}
msf.token = res.Token
return nil
}

func (msf *Metasploit) Logout() error {
ctx := &logoutReq{
Method: "auth.logout",
Token: msf.token,
LogoutToken: msf.token,
}
var res logoutRes
if err := msf.send(ctx, &res); err != nil {
return err
}
msf.token = ""
return nil
}

func (msf *Metasploit) SessionList() (map[uint32]SessionListRes, error) {
req := &SessionListReq{Method: "session.list", Token: msf.token}
res := make(map[uint32]SessionListRes)
if err := msf.send(req, &res); err != nil {
return nil, err
}
for id, session := range res {
session.ID = id
res[id] = session
}
return res, nil
}

我们的三个方法,Login()、Logout()、SessionList()都使用了相同的流程进行创建,首先创建请求的结构体,然后创建用来接收的结构体,通过send()函数进行发送。对于登陆和推出,因为主要操作的是token,所以我们需要将获取到的token赋值给我们的Metasploit结构体,如果是退出,那么将token清空即可。而对于信息的查询,我们的可以通过上面的api文档知道,首先会返回一个数组,该数组中的每个值都是一个映射关系,类似于列表。所以我们要获取其中的内容,首先我们需要利用for循环将返回内容中最外层的数组打开,获取其中的每一个值,然后通过映射关系打印出这个值其中我们要获取的数据。

而为什么是map[unit32]SessionListRes呢。我们可以去上面查看我们的查询结构的响应值,首先是1 => host的具体信息,而我们只定义了SessionListRes来接收host的具体信息,而实际上,是有多个host的,而因为接收时,又是使用的键值映射的方式,且键为数字,所以这时就需要我们的map,map在go中其实和python中的列表对应,都是键值的方式,而map[uint32]则代表键为数字,而在31行,使用make来将我们需要的这个map进行创建。

零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现

但是我们现在还有一个小问题没有解决,我们首先需要登陆也就是调用Login()方法,来获取token,然后才能进行查询,而我们为了方便,其实可以在New的时候就对登陆进行初始化,来获取token。所以我们修改为我们刚刚写的New函数。我们在New的时候便去调用Login函数来获取token,省去了我们在客户端中New后自行调用Login()进行登陆的步骤。

func New(host, user, pass string) (*Metasploit, error) {
msf := &Metasploit{
host: host,
user: user,
pass: pass,
}

if err := msf.Login(); err != nil {
return nil, err
}
return msf, nil
}

msf客户端

上面我们完成了关于msfAPI调用的代码,而下一步我们进入到我们的clinet文件夹中来完成我们的客户端。在上面环境配置时,我们已经将我们刚刚写好的包通过替换路径的方式来进行本地引入,但是还是需要使用ge get命令来获取加载一下我们写好的msf包。

go get github.com/haochen1204/msf

然后我们接着完成我们的代码

package main

import (
"fmt"
"log"
"os"

"github.com/haochen1204/msf"
)

func main() {
host := os.Getenv("MSFHOST")
pass := os.Getenv("MSFPASS")
user := "msf"

if host == "" || pass == "" {
log.Fatalln("Missing required enviroment variable MSFHOST of MSFPASS")
}
msf, err := msf.New(host, user, pass)
if err != nil {
log.Panicln(err)
}
defer msf.Logout()
sessions, err := msf.SessionList()
if err != nil {
log.Panicln(err)
}
fmt.Println("Sessions:")
for _, session := range sessions {
fmt.Printf("%5d %sn", session.ID, session.Info)
}
}

可以看到,首先我们利用os.Getenv()方法来获取到了我们存入系统变量的msf地址和密码,然后设置了user,判断msf的服务器地址和密码是否为空,如果为空则进行提示,然后利用new方法创建了一个msf的客户端,此时我们已经成功登陆,如果登陆失败则会返回响应的错误信息。然后我们通过SessinList()方法来获取上线的主机信息,最后将我们需要的信息打印出来。

未设置host地址和密码

零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现

开启msfrpc服务器

零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现

设置用户名和地址后,进行访问,查找上线的主机,因为我这里没有msf主机上线,所以打印的内容为空。

零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现

完整代码

package msf

import (
"bytes"
"fmt"
"net/http"

"gopkg.in/vmihailenco/msgpack.v2"
)

type SessionListReq struct {
_msgpack struct{} `msgpack:",asArray"`
Method string
Token string
}

type SessionListRes struct {
ID uint32 `msgpack:",omitempty"`
Type string `msgpack:"type"`
TunnelLocal string `msgpack:"tunnel_local"`
TunnelPreer string `msgpack:"tunnel_peer"`
ViaExploit string `msgpack:"via_exploit"`
ViaPayload string `msgpack:"via_payload"`
Description string `msgpack:"desc"`
Info string `msgpack:"info"`
Workspace string `msgpack:"worrkspace"`
SessionHost string `msgpack:"session_host"`
SessionPort int `msgpack:"session_port"`
Username string `msgpack:"username"`
UUID string `msgpack:"uuid"`
ExploitUUID string `msgpack:"exploit_uuid"`
}

type loginReq struct {
_msgpack struct{} `msgpack:",asArray"`
Method string
Username string
Password string
}

type loginRes struct {
Result string `msgpack:"result"`
Token string `msgpack:"token"`
Error bool `msgpack:"error"`
ErrorClass string `msgpack:"errror_class"`
ErrorMessage string `msgpack:"error_message"`
}

type logoutReq struct {
_msgpack struct{} `msgpack:",asArray"`
Method string
Token string
LogoutToken string
}

type logoutRes struct {
Result string `msgpack:"result"`
}

type Metasploit struct {
host string
user string
pass string
token string
}

func New(host, user, pass string) (*Metasploit, error) {
msf := &Metasploit{
host: host,
user: user,
pass: pass,
}

if err := msf.Login(); err != nil {
return nil, err
}
return msf, nil
}

func (msf *Metasploit) send(req interface{}, res interface{}) error {
buf := new(bytes.Buffer)
msgpack.NewEncoder(buf).Encode(req)
dest := fmt.Sprintf("http://%s/api", msf.host)
r, err := http.Post(dest, "binary/message-pack", buf)
if err != nil {
return err
}
defer r.Body.Close()
if err := msgpack.NewDecoder(r.Body).Decode(&res); err != nil {
return err
}
return nil
}

func (msf *Metasploit) Login() error {
ctx := &loginReq{
Method: "auth.login",
Username: msf.user,
Password: msf.pass,
}
var res loginRes
if err := msf.send(ctx, &res); err != nil {
return err
}
msf.token = res.Token
return nil
}

func (msf *Metasploit) Logout() error {
ctx := &logoutReq{
Method: "auth.logout",
Token: msf.token,
LogoutToken: msf.token,
}
var res logoutRes
if err := msf.send(ctx, &res); err != nil {
return err
}
msf.token = ""
return nil
}

func (msf *Metasploit) SessionList() (map[uint32]SessionListRes, error) {
req := &SessionListReq{Method: "session.list", Token: msf.token}
res := make(map[uint32]SessionListRes)
if err := msf.send(req, &res); err != nil {
return nil, err
}
for id, session := range res {
session.ID = id
res[id] = session
}
return res, nil
}
使


零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现


零基础学go—GO黑帽子学习笔记-3.2 FoFa客户端实现

零基础学go GO黑帽子学习笔记-3.1 GO的HTTP基础知识

零基础学GO-GO黑帽子学习笔记-2.3 端口扫描

POC bomber 利用大量高危害漏洞快速获取目标服务器权限

GO语言免杀器 Go_Bypass

原文始发于微信公众号(开普勒安全团队):零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月20日13:34:51
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   零基础学go—GO黑帽子学习笔记-3.3 msf客户端的实现https://cn-sec.com/archives/1360698.html

发表评论

匿名网友 填写信息