Metasploit RPC API 远程调用
in Python with 0 comment

Metasploit RPC API 远程调用

in Python with 0 comment

背景


有时,在脚本自动化时,会有msf远程调用的需求,而网上的资料凤毛麟角。本文较简单地介绍了MSF RPC API调用的方式及常见问题。

前言


Metasploit官方提供了API调用方式,有RPC,REST两种API调用方式,REST方式只支持专业版使用,这里推荐使用RPC方式调用,即标准API调用。在通过对Cobalt Strike2.4版本客户端和armitage客户端进行反编译,发现其API调用也为RPC调用。可以认为RPC API调用是"稳定",“可靠”的。

RPC API 调用官方文档
https://metasploit.help.rapid7.com/docs/standard-api-methods-reference

实现


无论在免费版还是商业版的msf中,都会提供msfrpc服务,以便于去远程调用。

开启服务端RPC 服务

开启服务端API服务有两种方式:

  1. 通过msfconsole加载msfrpc插件来开启RPC
  2. 通过msfrpcd服务来开启RPC

msfconsole其实也可以理解为metasploit客户端,和msfclient,armitage的功能一致。只是操作方式有所不同。

通过msfconsole加载RPC

进入msfconsole之后,运行加载命令

msf5 > load msgrpc ServerHost=127.0.0.1 ServerPort=55553 User='msf' Pass='msf'
[*] MSGRPC Service:  127.0.0.1:55553
[*] MSGRPC Username: msf
[*] MSGRPC Password: msf
[*] Successfully loaded plugin: msgrpc
msf5 >

其中Serverhost即运行msf的主机,可以为127.0.0.1也可以是0.0.0.0区别是前者只能本机连接。

通过msfrpcd来开启RPC服务

$ msfrpcd -U msf -P msf -S -f  
[*] MSGRPC starting on 0.0.0.0:55553 (NO SSL):Msg...
[*] MSGRPC ready at 2018-10-17 11:06:46 +0800.

即以用户名和密码分别为msfmsf,不启用SSL来开启服务。msfrpcdmsfconsole命令一般在同一目录下。如果环境变量设置正确,一般可以直接使用。

关于msfrpcd的详细参数如下:

$ msfrpcd -h
   Usage: msfrpcd <options>
   OPTIONS:
       -P <opt>  设置RPC登录密码
       -S        在RPC socket上禁止使用SSL
       -U <opt>  设置RPC登录用户名
       -a <opt>  绑定一个IP地址(本机IP地址)
       -f        在后台以精灵进程(守护进程)的方式运行、启动
       -h        帮助菜单
       -n        禁止使用数据库
       -p <opt>  绑定某个端口,默认为55553
       -u <opt>  设置Web服务器的URI

MSF RPC 与msgpack


与msf rpc api通信需要对通信的内容使用msgpack进行序列化,简单来说就是将要发送的数据包转换为二进制形式,以便于传输和格式统一。msgpack序列化之后的数据包支持多种语言,可以在msf服务端由ruby正常解析。

Python下安装msgpack包:

$ pip install msgpack
>>> import msgpack
>>> dic = {'result': 'success', 'token': 'TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK'}
>>> res = msgpack.packb(dic)
>>> res
'\x82\xa5token\xda\x00 TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK\xa6result\xa7success'
>>>

MSF API请求

在服务端开启RPC之后,可以使用HTTP协议去访问,会提示404,访问'api'会将文件下载下来。如果发生上述效果,表明服务端开启成功。

其实,MSF的RPC调用也利用HTTP协议,需要先连接RPC socket然后构造POST请求,不同的是,需要指定Content-typebinary/message-pack,这样客户端才会正确解析包。

登录认证API调用

登录认证时向服务端POST序列化发送如下数据包:

成功的请求示例

客户:

[ "auth.login", "MyUserName", "MyPassword"]

服务器:
{ "result" => "success", "token" => "a1a1a1a1a1a…" }

这里用一个连接MSF服务端并进行登录的简单demo来演示:


# _*_ encoding:utf-8 _*_
# __author__ = "dr0op"
# python3

import msgpack
import http.client

HOST="127.0.0.1"
PORT="55553"
headers = {"Content-type" : "binary/message-pack"}

# 连接MSF RPC Socket
req = http.client.HTTPConnection(HOST, PORT)
options = ["auth.login","msf","msf"]

# 对参数进行序列化(编码)
options = msgpack.packb(options)

# 发送请求,序列化之后的数据包
req.request("POST","/api/1.0",body=options,headers=headers)

# 获取返回
res = req.getresponse().read()

# 对返回进行反序列户(解码)
res = msgpack.unpackb(res)
res = res[b'token'].decode()
print(res)

成功执行的结果res如下:

{'result': 'success', 'token': 'TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK'}

其中Token是一个随机字符串,是登录认证后的标识。

API详解

以上使用一个简单的例子理解请求的API调用数据包格式及请求方式,其他的API请求都是同理的。只是请求的内容有所改变而已。

关于常用的API请求和返回总结如下:

认证

成功的请求示例
客户:
[ "auth.login", "MyUserName", "MyPassword"]
服务器:
{ "result" => "success", "token" => "a1a1a1a1a1a…" }
不成功的请求示例
服务器:

{ "error" => true,
"error_class" => "Msf::RPC::Exception",
"error_message" => "Invalid User ID or Password" }

退出同理

其他常见调用

更全面的API调用方式可参考官网
https://metasploit.help.rapid7.com/docs/standard-api-methods-reference
只需要稍微翻译一下即可。

MsfRpcClient

再使用一个MSF RPC Demo来演示一下:
https://github.com/dr0op/MsfRpcApi/blob/master/demo2.py
在这个例子中,调用MSF RPC登录获取Token之后,创建一个console,并发送命令到`console,由msf服务端去执行。执行成功之后会将结果以序列化后的形式返回。反序列化之后成为一个dict,包含了返回后的结果。

API封装


以上是一个基础的MSF API简单调用模块去攻击的demo,但是在应用中,需要对其常见的API调用进行封装,做成一个属于我们自己的,使用时,只需要去调用它即可。

简单的封装如下:

https://github.com/dr0op/MsfRpcApi/blob/master/demo2.py

使用这个库去调用攻击模块:

# _*_ encoding:utf-8 _*_
# __author__ = "dr0op"

from pymsfrpc import msfrpc
import time

ip = "10.10.11.180"
port = "55553"
user = "msf"
passwd = "msf"
c = msfrpc.Client(ip,port,user,passwd)

console_id = c.create_console().get(b'id')
cmd = """
         use auxiliary/scanner/ssh/ssh_login

         set RHOSTS 127.0.0.1
        
         set USERNAME root
         
         set PASS_FILE /tmp/pass.txt
        
         exploit
      """
res = c.get_version()
resp = c.write_console(console_id,cmd)
time.sleep(1)
while True:
    res = c.read_console(console_id)
    if res[b'busy'] == True:
        time.sleep(1)
        continue
    elif res[b'busy'] == False:
        print(res[b'data'].decode())
        break
c.destroy_console(console_id)

以上封装改自github开源代码msf-autopwn
https://github.com/DanMcInerney/msf-autopwn
有所改动。

更全面的封装

更全面的封装可参考

https://github.com/isaudits/msfrpc_console/blob/master/modules/pymetasploit/src/metasploit/msfrpc.py

要在较成熟系统上使用可参考,使用GPL0.4开源协议。

问题及解决

1. 反序列化后的格式问题

Python3版本测试过程中,发现对返回数据进行反序列化之后,出现类似:

{b'result': b'success', b'token': b'TEMPEqU3buWpncDeoBryIWOgKJ9O34cJ'}

这种格式的dict,这表示dict的内容即keys和values是bytes类型的。这给我们的后续操作带来很大的不便,在判断时需要将其转化为str类型。要转化,只需要将其项decode()即可。然而,dict并不支持decode,需要遍历其中的项并进行转化。

转换方法现提供如下:

def convert(data):
    """
    对Bytes类型的dict进行转化,转化为项为Str类型
    """
    if isinstance(data, bytes):  return data.decode('ascii')
    if isinstance(data, dict):   return dict(map(convert, data.items()))
    if isinstance(data, tuple):  return map(convert, data)
    return data

2. meterpreter无法获取session问题

使用msfvenom生成一个木马并在目标执行。在msf服务端使用MSF RPC进行监听。使用session.list成功获取session列表。返回结果如下:

{14: {b'type': b'meterpreter', b'tunnel_local': b'10.10.11.180:3355', b'tunnel_peer': b'10.10.11.180:55656', b'via_exploit': b'exploit/multi/handler', b'via_payload': b'payload/windows/meterpreter/reverse_tcp', b'desc': b'Meterpreter', b'info': b'LAPTOP-0IG64IBE\\dr0op @ LAPTOP-0IG64IBE', b'workspace': b'false', b'session_host': b'10.10.11.180', b'session_port': 55656, b'target_host': b'10.10.11.180', b'username': b'dr0op', b'uuid': b'j3oe1mtk', b'exploit_uuid': b'nxyfbzx4', b'routes': b'', b'arch': b'x86', b'platform': b'windows'}}

session ID为14。

成功获取session列表后,就可以向session读写meterpreter命令。

c.write_meterpreter(14,'getuid\n')
c.read_meterpreter(14)

然而,MSF RPC端返回如下:

write meterpreter {b'error': True,  b'error_string': b'Unknown API Call: \'"rpc_meterperter_write"\'', ... b'error_message': b'Unknown API Call: \'"rpc_meterperter_write"\''}
read meterpreter {b'error': True, ... b'error_message': b'Unknown API Call: \'"rpc_meterperter_read"\''}

暂未找到解决方案。

总结


该文档由浅入深地描述了MSF API调用开发的方式及常见问题。并且由于每个人的环境不同,相同的代码在不同的环境中可能无法运行,需自行解决环境及依赖问题。封装方法精力允许的情况下推荐第二种封装方式。更为专业及具有可扩展性。已将部分代码push至githubhttps://github.com/dr0op/MsfRpcApi

REFERENCE


[1] https://metasploit.help.rapid7.com/docs/standard-api-methods-reference
[2] https://github.com/isaudits/msfrpc_console/
[3] https://github.com/DanMcInerney/msf-autopwn

Responses