GO黑帽子学习笔记-3.3 msf客户端的实现
2022-10-21 18:1:29 Author: 渗透安全团队(查看原文) 阅读量:16 收藏

零基础学go

在学习这节之前,我还不知道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会话的列表。

我们是选来看接收,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。

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

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

所以我们根据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进行创建。

但是我们现在还有一个小问题没有解决,我们首先需要登陆也就是调用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 %s\n", session.ID, session.Info)
}
}

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

未设置host地址和密码

开启msfrpc服务器

设置用户名和地址后,进行访问,查找上线的主机,因为我这里没有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
}

星 球 免 费 福 利

 转发公众号本文到朋友圈

 截图到公众号后台第1、3、5名获取免费进入星球

星球的最近主题和星球内部工具一些展示

欢 迎 加 入 星 球 !

关 注 有 礼

关注下方公众号回复“666”可以领取一套精品渗透测试工具集和百度云视频链接。

 还在等什么?赶紧点击下方名片关注学习吧!


群聊 | 技术交流群-群除我佬

干货|史上最全一句话木马

干货 | CS绕过vultr特征检测修改算法

实战 | 用中国人写的红队服务器搞一次内网穿透练习

实战 | 渗透某培训平台经历

实战 | 一次曲折的钓鱼溯源反制

免责声明
由于传播、利用本公众号渗透安全团队所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号渗透安全团队及作者不为承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!
好文分享收藏赞一下最美点在看哦

文章来源: http://mp.weixin.qq.com/s?__biz=MzkxNDAyNTY2NA==&mid=2247493819&idx=2&sn=63a7c2e853080d0dbc715a1444956495&chksm=c1761514f6019c02afdf14a6b865ad8f15571b9a720ae3f1990f14084bfc94632cb9ba2c0383#rd
如有侵权请联系:admin#unsafe.sh